From 5c3c69400ef46fac25a798fdf3dc6d422ef8b79e Mon Sep 17 00:00:00 2001 From: Jiri Spac Date: Thu, 24 Aug 2023 23:10:51 +0200 Subject: [PATCH 1/2] Update MockPool.md intercept method description (#2220) --- docs/api/MockPool.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/api/MockPool.md b/docs/api/MockPool.md index 923c157aa64..de53914002e 100644 --- a/docs/api/MockPool.md +++ b/docs/api/MockPool.md @@ -35,7 +35,8 @@ const mockPool = mockAgent.get('http://localhost:3000') ### `MockPool.intercept(options)` -This method defines the interception rules for matching against requests for a MockPool or MockPool. We can intercept multiple times on a single instance. +This method defines the interception rules for matching against requests for a MockPool or MockPool. We can intercept multiple times on a single instance, but each intercept is only used once. +For example if you expect to make 2 requests inside a test, you need to call `intercept()` twice. Assuming you use `disableNetConnect()` you will get `MockNotMatchedError` on the second request when you only call `intercept()` once. When defining interception rules, all the rules must pass for a request to be intercepted. If a request is not intercepted, a real request will be attempted. From 111fd2388094270259814ca7ea5da7d07b2126f6 Mon Sep 17 00:00:00 2001 From: Khafra Date: Sat, 26 Aug 2023 07:32:35 -0400 Subject: [PATCH 2/2] Update wpts (#2226) --- lib/fetch/response.js | 2 +- lib/fetch/util.js | 25 +- test/wpt/runner/runner.mjs | 2 + test/wpt/status/fetch.status.json | 8 +- test/wpt/tests/.azure-pipelines.yml | 125 ++++--- test/wpt/tests/.taskcluster.yml | 2 +- .../filereader_readAsDataURL.any.js | 14 +- test/wpt/tests/common/media.js | 20 +- test/wpt/tests/common/rendering-utils.js | 8 +- test/wpt/tests/fetch/api/abort/general.any.js | 2 +- .../fetch/api/basic/integrity.sub.any.js | 12 +- .../tests/fetch/api/basic/keepalive.any.js | 36 +- .../fetch/api/basic/scheme-blob.sub.any.js | 4 + .../tests/fetch/api/cors/cors-basic.any.js | 44 ++- .../fetch/api/cors/cors-keepalive.any.js | 118 +++++++ .../fetch/api/cors/cors-preflight-star.any.js | 4 + .../api/crashtests/body-window-destroy.html | 11 + .../destination/resources/dummy_video.webm | Bin 0 -> 96902 bytes .../construct-in-detached-frame.window.js | 11 + .../fetch/api/resources/keepalive-helper.js | 30 +- .../fetch/api/resources/keepalive-iframe.html | 12 +- .../tests/fetch/api/resources/stash-put.py | 26 +- .../response-body-read-task-handling.html | 38 +- .../response/response-cancel-stream.any.js | 7 + .../api/response/response-static-json.any.js | 15 + .../resources/content-lengths.json | 16 + .../corb/resources/response_block_probe.js | 2 +- .../corb/response_block.tentative.https.html | 50 +++ .../response_block.tentative.sub.https.html | 44 --- test/wpt/tests/fetch/fetch-later/META.yml | 3 + test/wpt/tests/fetch/fetch-later/README.md | 3 + .../basic.tentative.https.window.js | 13 + .../fetch/fetch-later/non-secure.window.js | 8 + .../sendondiscard.tentative.https.window.js | 28 ++ .../resources-with-0x00-in-header.window.js | 2 +- .../resources/document-with-0x00-in-header.py | 2 +- .../fetch/local-network-access/README.md | 10 - .../orb/resources/script-asm-js-invalid.js | 4 + .../orb/resources/script-asm-js-valid.js | 4 + .../orb/tentative/known-mime-type.sub.any.js | 10 + .../META.yml | 0 .../fetch/private-network-access/README.md | 10 + ...eflight-required.tentative.https.window.js | 91 +++++ ...ubresource-fetch.tentative.https.window.js | 330 ++++++++++++++++++ .../fenced-frame.tentative.https.window.js | 150 ++++++++ ...treat-as-public.tentative.https.window.js} | 0 .../fetch.tentative.https.window.js} | 0 .../fetch.tentative.window.js} | 0 .../iframe.tentative.https.window.js | 142 ++++---- .../iframe.tentative.window.js | 26 +- ...ed-content-fetch.tentative.https.window.js | 92 +++-- .../nested-worker.tentative.https.window.js} | 0 .../nested-worker.tentative.window.js} | 0 ...preflight-cache.https.tentative.window.js} | 0 .../redirect.tentative.https.window.js} | 0 .../resources/executor.html | 0 .../resources/fenced-frame-fetcher.https.html | 25 ++ .../fenced-frame-fetcher.https.html.headers | 1 + ...ame-local-network-access-target.https.html | 8 + ...nced-frame-local-network-access.https.html | 14 + ...me-local-network-access.https.html.headers | 1 + .../resources/fetcher.html | 0 .../resources/fetcher.js | 0 .../resources/iframed.html | 0 .../resources/iframer.html | 0 .../resources/preflight.py | 6 + .../resources/service-worker-bridge.html | 0 .../resources/service-worker.js | 0 .../resources/shared-fetcher.js | 0 .../resources/shared-worker-blob-fetcher.html | 0 .../resources/shared-worker-fetcher.html | 0 .../resources/socket-opener.html | 0 .../resources/support.sub.js | 95 ++++- .../resources/worker-blob-fetcher.html | 0 .../resources/worker-fetcher.html | 0 .../resources/worker-fetcher.js | 0 .../resources/xhr-sender.html | 0 ...ackground-fetch.tentative.https.window.js} | 0 ...ce-worker-fetch.tentative.https.window.js} | 33 +- ...e-worker-update.tentative.https.window.js} | 0 .../service-worker.tentative.https.window.js} | 0 ...rker-blob-fetch.tentative.https.window.js} | 0 ...red-worker-blob-fetch.tentative.window.js} | 0 ...ed-worker-fetch.tentative.https.window.js} | 0 .../shared-worker-fetch.tentative.window.js} | 0 .../shared-worker.tentative.https.window.js} | 0 .../shared-worker.tentative.window.js} | 0 .../websocket.tentative.https.window.js} | 0 .../websocket.tentative.window.js} | 0 .../worker-blob-fetch.tentative.window.js} | 0 .../worker-fetch.tentative.https.window.js} | 0 .../worker-fetch.tentative.window.js} | 0 .../worker.tentative.https.window.js} | 0 .../worker.tentative.window.js} | 0 ...treat-as-public.tentative.https.window.js} | 0 .../xhr.https.tentative.window.js} | 0 .../xhr.tentative.window.js} | 0 test/wpt/tests/fetch/range/blob.any.js | 11 +- .../interfaces/attribution-reporting-api.idl | 16 +- .../captured-mouse-events.tentative.idl | 25 ++ .../tests/interfaces/css-anchor-position.idl | 11 + test/wpt/tests/interfaces/css-cascade-6.idl | 4 +- test/wpt/tests/interfaces/css-cascade.idl | 4 - test/wpt/tests/interfaces/cssom.idl | 16 +- .../document-picture-in-picture.idl | 34 ++ test/wpt/tests/interfaces/dom.idl | 1 + test/wpt/tests/interfaces/fenced-frame.idl | 15 +- test/wpt/tests/interfaces/fs.idl | 4 + test/wpt/tests/interfaces/html.idl | 8 + .../interfaces/mediastream-recording.idl | 2 + test/wpt/tests/interfaces/notifications.idl | 5 +- .../tests/interfaces/permissions-policy.idl | 1 + .../tests/interfaces/real-world-meshing.idl | 21 ++ test/wpt/tests/interfaces/resource-timing.idl | 2 + test/wpt/tests/interfaces/scheduling-apis.idl | 6 + test/wpt/tests/interfaces/screen-capture.idl | 2 +- .../tests/interfaces/scroll-animations.idl | 6 +- .../secure-payment-confirmation.idl | 4 + test/wpt/tests/interfaces/shared-storage.idl | 80 +++++ test/wpt/tests/interfaces/storage-buckets.idl | 53 +++ test/wpt/tests/interfaces/trust-token-api.idl | 7 +- test/wpt/tests/interfaces/turtledove.idl | 29 +- test/wpt/tests/interfaces/url.idl | 4 +- test/wpt/tests/interfaces/webauthn.idl | 75 +++- .../webcodecs-av1-codec-registration.idl | 8 + .../webcodecs-avc-codec-registration.idl | 8 + test/wpt/tests/interfaces/webcodecs.idl | 1 + test/wpt/tests/interfaces/webgpu.idl | 244 +++++++------ test/wpt/tests/interfaces/webnn.idl | 11 +- .../interfaces/webrtc-encoded-transform.idl | 5 +- test/wpt/tests/interfaces/webrtc-stats.idl | 4 +- test/wpt/tests/interfaces/webrtc.idl | 8 +- test/wpt/tests/interfaces/webtransport.idl | 5 +- test/wpt/tests/interfaces/webxrlayers.idl | 9 + test/wpt/tests/lint.ignore | 28 +- .../tests/resources/chromium/webusb-test.js | 9 +- .../tests/resources/chromium/webxr-test.js | 3 - test/wpt/tests/resources/idlharness.js | 7 +- .../tests/functional/assert-throws-dom.html | 55 +++ .../test_partial_interface_of.html | 1 - .../test_partial_interface_of.html | 1 - test/wpt/tests/resources/test/tox.ini | 2 +- test/wpt/tests/resources/testdriver.js | 171 ++++++++- test/wpt/tests/resources/testharness.js | 10 +- ...led-dedicatedworker-postMessage.https.html | 44 +++ .../controlled-iframe-postMessage.https.html | 67 ++++ .../detached-context.https.html | 19 +- .../service-worker/fetch-error.https.html | 8 +- .../navigation-redirect.https.html | 4 +- .../navigation-timing.https.html | 1 + .../partitioned-cookies.tentative.https.html | 48 ++- .../postMessage-client-worker.js | 23 ++ .../resource-timing.sub.https.html | 4 +- .../controlled-frame-postMessage.html | 39 +++ .../controlled-worker-late-postMessage.js | 6 + .../controlled-worker-postMessage.js | 4 + .../resources/fetch-access-control.py | 9 +- .../service-worker/resources/missing.asis | 4 + ...ioned-cookies-3p-credentialless-frame.html | 53 ++- .../partitioned-cookies-3p-frame.html | 49 ++- .../resources/partitioned-cookies-3p-sw.js | 29 +- .../partitioned-cookies-3p-window.html | 4 +- .../resources/partitioned-cookies-sw.js | 29 +- .../resources/resource-timing-iframe.sub.html | 2 +- .../tentative/static-router/README.md | 4 + .../static-router/resources/direct.txt | 1 + ...mple-test-for-condition-main-resource.html | 3 + .../static-router/resources/simple.html | 3 + .../resources/static-router-sw.js | 35 ++ .../resources/test-helpers.sub.js | 303 ++++++++++++++++ .../static-router-main-resource.https.html | 58 +++ .../static-router-subresource.https.html | 48 +++ ...ket-quota-indexeddb.tentative.https.any.js | 35 ++ ...cket-storage-policy.tentative.https.any.js | 21 ++ ...kets_storage_policy.tentative.https.any.js | 46 --- ...nager-persist-persisted-match.https.any.js | 9 + ...socket-connection-ccns.tentative.window.js | 5 +- ...socket-connection-ccns.tentative.window.js | 5 +- ...e-with-open-websocket-connection.window.js | 2 +- test/wpt/tests/xhr/blob-range.any.js | 246 +++++++++++++ .../tests/xhr/responsexml-invalid-type.html | 21 ++ ...-authentication-basic-cors-not-enabled.htm | 5 +- ...entication-cors-basic-setrequestheader.htm | 5 +- ...tication-cors-setrequestheader-no-cred.htm | 5 +- .../send-network-error-sync-events.sub.htm | 2 +- 185 files changed, 3480 insertions(+), 663 deletions(-) create mode 100644 test/wpt/tests/fetch/api/cors/cors-keepalive.any.js create mode 100644 test/wpt/tests/fetch/api/crashtests/body-window-destroy.html create mode 100644 test/wpt/tests/fetch/api/request/destination/resources/dummy_video.webm create mode 100644 test/wpt/tests/fetch/api/request/multi-globals/construct-in-detached-frame.window.js create mode 100644 test/wpt/tests/fetch/corb/response_block.tentative.https.html delete mode 100644 test/wpt/tests/fetch/corb/response_block.tentative.sub.https.html create mode 100644 test/wpt/tests/fetch/fetch-later/META.yml create mode 100644 test/wpt/tests/fetch/fetch-later/README.md create mode 100644 test/wpt/tests/fetch/fetch-later/basic.tentative.https.window.js create mode 100644 test/wpt/tests/fetch/fetch-later/non-secure.window.js create mode 100644 test/wpt/tests/fetch/fetch-later/sendondiscard.tentative.https.window.js delete mode 100644 test/wpt/tests/fetch/local-network-access/README.md create mode 100644 test/wpt/tests/fetch/orb/resources/script-asm-js-invalid.js create mode 100644 test/wpt/tests/fetch/orb/resources/script-asm-js-valid.js rename test/wpt/tests/fetch/{local-network-access => private-network-access}/META.yml (100%) create mode 100644 test/wpt/tests/fetch/private-network-access/README.md create mode 100644 test/wpt/tests/fetch/private-network-access/fenced-frame-no-preflight-required.tentative.https.window.js create mode 100644 test/wpt/tests/fetch/private-network-access/fenced-frame-subresource-fetch.tentative.https.window.js create mode 100644 test/wpt/tests/fetch/private-network-access/fenced-frame.tentative.https.window.js rename test/wpt/tests/fetch/{local-network-access/fetch-from-treat-as-public.https.window.js => private-network-access/fetch-from-treat-as-public.tentative.https.window.js} (100%) rename test/wpt/tests/fetch/{local-network-access/fetch.https.window.js => private-network-access/fetch.tentative.https.window.js} (100%) rename test/wpt/tests/fetch/{local-network-access/fetch.window.js => private-network-access/fetch.tentative.window.js} (100%) rename test/wpt/tests/fetch/{local-network-access => private-network-access}/iframe.tentative.https.window.js (63%) rename test/wpt/tests/fetch/{local-network-access => private-network-access}/iframe.tentative.window.js (86%) rename test/wpt/tests/fetch/{local-network-access => private-network-access}/mixed-content-fetch.tentative.https.window.js (77%) rename test/wpt/tests/fetch/{local-network-access/nested-worker.https.window.js => private-network-access/nested-worker.tentative.https.window.js} (100%) rename test/wpt/tests/fetch/{local-network-access/nested-worker.window.js => private-network-access/nested-worker.tentative.window.js} (100%) rename test/wpt/tests/fetch/{local-network-access/preflight-cache.https.window.js => private-network-access/preflight-cache.https.tentative.window.js} (100%) rename test/wpt/tests/fetch/{local-network-access/redirect.https.window.js => private-network-access/redirect.tentative.https.window.js} (100%) rename test/wpt/tests/fetch/{local-network-access => private-network-access}/resources/executor.html (100%) create mode 100644 test/wpt/tests/fetch/private-network-access/resources/fenced-frame-fetcher.https.html create mode 100644 test/wpt/tests/fetch/private-network-access/resources/fenced-frame-fetcher.https.html.headers create mode 100644 test/wpt/tests/fetch/private-network-access/resources/fenced-frame-local-network-access-target.https.html create mode 100644 test/wpt/tests/fetch/private-network-access/resources/fenced-frame-local-network-access.https.html create mode 100644 test/wpt/tests/fetch/private-network-access/resources/fenced-frame-local-network-access.https.html.headers rename test/wpt/tests/fetch/{local-network-access => private-network-access}/resources/fetcher.html (100%) rename test/wpt/tests/fetch/{local-network-access => private-network-access}/resources/fetcher.js (100%) rename test/wpt/tests/fetch/{local-network-access => private-network-access}/resources/iframed.html (100%) rename test/wpt/tests/fetch/{local-network-access => private-network-access}/resources/iframer.html (100%) rename test/wpt/tests/fetch/{local-network-access => private-network-access}/resources/preflight.py (97%) rename test/wpt/tests/fetch/{local-network-access => private-network-access}/resources/service-worker-bridge.html (100%) rename test/wpt/tests/fetch/{local-network-access => private-network-access}/resources/service-worker.js (100%) rename test/wpt/tests/fetch/{local-network-access => private-network-access}/resources/shared-fetcher.js (100%) rename test/wpt/tests/fetch/{local-network-access => private-network-access}/resources/shared-worker-blob-fetcher.html (100%) rename test/wpt/tests/fetch/{local-network-access => private-network-access}/resources/shared-worker-fetcher.html (100%) rename test/wpt/tests/fetch/{local-network-access => private-network-access}/resources/socket-opener.html (100%) rename test/wpt/tests/fetch/{local-network-access => private-network-access}/resources/support.sub.js (86%) rename test/wpt/tests/fetch/{local-network-access => private-network-access}/resources/worker-blob-fetcher.html (100%) rename test/wpt/tests/fetch/{local-network-access => private-network-access}/resources/worker-fetcher.html (100%) rename test/wpt/tests/fetch/{local-network-access => private-network-access}/resources/worker-fetcher.js (100%) rename test/wpt/tests/fetch/{local-network-access => private-network-access}/resources/xhr-sender.html (100%) rename test/wpt/tests/fetch/{local-network-access/service-worker-background-fetch.https.window.js => private-network-access/service-worker-background-fetch.tentative.https.window.js} (100%) rename test/wpt/tests/fetch/{local-network-access/service-worker-fetch.https.window.js => private-network-access/service-worker-fetch.tentative.https.window.js} (88%) rename test/wpt/tests/fetch/{local-network-access/service-worker-update.https.window.js => private-network-access/service-worker-update.tentative.https.window.js} (100%) rename test/wpt/tests/fetch/{local-network-access/service-worker.https.window.js => private-network-access/service-worker.tentative.https.window.js} (100%) rename test/wpt/tests/fetch/{local-network-access/shared-worker-blob-fetch.https.window.js => private-network-access/shared-worker-blob-fetch.tentative.https.window.js} (100%) rename test/wpt/tests/fetch/{local-network-access/shared-worker-blob-fetch.window.js => private-network-access/shared-worker-blob-fetch.tentative.window.js} (100%) rename test/wpt/tests/fetch/{local-network-access/shared-worker-fetch.https.window.js => private-network-access/shared-worker-fetch.tentative.https.window.js} (100%) rename test/wpt/tests/fetch/{local-network-access/shared-worker-fetch.window.js => private-network-access/shared-worker-fetch.tentative.window.js} (100%) rename test/wpt/tests/fetch/{local-network-access/shared-worker.https.window.js => private-network-access/shared-worker.tentative.https.window.js} (100%) rename test/wpt/tests/fetch/{local-network-access/shared-worker.window.js => private-network-access/shared-worker.tentative.window.js} (100%) rename test/wpt/tests/fetch/{local-network-access/websocket.https.window.js => private-network-access/websocket.tentative.https.window.js} (100%) rename test/wpt/tests/fetch/{local-network-access/websocket.window.js => private-network-access/websocket.tentative.window.js} (100%) rename test/wpt/tests/fetch/{local-network-access/worker-blob-fetch.window.js => private-network-access/worker-blob-fetch.tentative.window.js} (100%) rename test/wpt/tests/fetch/{local-network-access/worker-fetch.https.window.js => private-network-access/worker-fetch.tentative.https.window.js} (100%) rename test/wpt/tests/fetch/{local-network-access/worker-fetch.window.js => private-network-access/worker-fetch.tentative.window.js} (100%) rename test/wpt/tests/fetch/{local-network-access/worker.https.window.js => private-network-access/worker.tentative.https.window.js} (100%) rename test/wpt/tests/fetch/{local-network-access/worker.window.js => private-network-access/worker.tentative.window.js} (100%) rename test/wpt/tests/fetch/{local-network-access/xhr-from-treat-as-public.https.window.js => private-network-access/xhr-from-treat-as-public.tentative.https.window.js} (100%) rename test/wpt/tests/fetch/{local-network-access/xhr.https.window.js => private-network-access/xhr.https.tentative.window.js} (100%) rename test/wpt/tests/fetch/{local-network-access/xhr.window.js => private-network-access/xhr.tentative.window.js} (100%) create mode 100644 test/wpt/tests/interfaces/captured-mouse-events.tentative.idl create mode 100644 test/wpt/tests/interfaces/css-anchor-position.idl create mode 100644 test/wpt/tests/interfaces/document-picture-in-picture.idl create mode 100644 test/wpt/tests/interfaces/real-world-meshing.idl create mode 100644 test/wpt/tests/interfaces/shared-storage.idl create mode 100644 test/wpt/tests/interfaces/storage-buckets.idl create mode 100644 test/wpt/tests/resources/test/tests/functional/assert-throws-dom.html create mode 100644 test/wpt/tests/service-workers/service-worker/controlled-dedicatedworker-postMessage.https.html create mode 100644 test/wpt/tests/service-workers/service-worker/controlled-iframe-postMessage.https.html create mode 100644 test/wpt/tests/service-workers/service-worker/postMessage-client-worker.js create mode 100644 test/wpt/tests/service-workers/service-worker/resources/controlled-frame-postMessage.html create mode 100644 test/wpt/tests/service-workers/service-worker/resources/controlled-worker-late-postMessage.js create mode 100644 test/wpt/tests/service-workers/service-worker/resources/controlled-worker-postMessage.js create mode 100644 test/wpt/tests/service-workers/service-worker/resources/missing.asis create mode 100644 test/wpt/tests/service-workers/service-worker/tentative/static-router/README.md create mode 100644 test/wpt/tests/service-workers/service-worker/tentative/static-router/resources/direct.txt create mode 100644 test/wpt/tests/service-workers/service-worker/tentative/static-router/resources/simple-test-for-condition-main-resource.html create mode 100644 test/wpt/tests/service-workers/service-worker/tentative/static-router/resources/simple.html create mode 100644 test/wpt/tests/service-workers/service-worker/tentative/static-router/resources/static-router-sw.js create mode 100644 test/wpt/tests/service-workers/service-worker/tentative/static-router/resources/test-helpers.sub.js create mode 100644 test/wpt/tests/service-workers/service-worker/tentative/static-router/static-router-main-resource.https.html create mode 100644 test/wpt/tests/service-workers/service-worker/tentative/static-router/static-router-subresource.https.html create mode 100644 test/wpt/tests/storage/buckets/bucket-quota-indexeddb.tentative.https.any.js create mode 100644 test/wpt/tests/storage/buckets/bucket-storage-policy.tentative.https.any.js delete mode 100644 test/wpt/tests/storage/buckets/buckets_storage_policy.tentative.https.any.js create mode 100644 test/wpt/tests/storage/storagemanager-persist-persisted-match.https.any.js create mode 100644 test/wpt/tests/xhr/blob-range.any.js create mode 100644 test/wpt/tests/xhr/responsexml-invalid-type.html diff --git a/lib/fetch/response.js b/lib/fetch/response.js index 66c0e50e32b..88deb71a062 100644 --- a/lib/fetch/response.js +++ b/lib/fetch/response.js @@ -49,7 +49,7 @@ class Response { } // https://fetch.spec.whatwg.org/#dom-response-json - static json (data = undefined, init = {}) { + static json (data, init = {}) { webidl.argumentLengthCheck(arguments, 1, { header: 'Response.json' }) if (init !== null) { diff --git a/lib/fetch/util.js b/lib/fetch/util.js index 98a049dc79d..fcbba84bc9a 100644 --- a/lib/fetch/util.js +++ b/lib/fetch/util.js @@ -556,16 +556,37 @@ function bytesMatch (bytes, metadataList) { const algorithm = item.algo // 2. Let expectedValue be the val component of item. - const expectedValue = item.hash + let expectedValue = item.hash + + // See https://github.com/web-platform-tests/wpt/commit/e4c5cc7a5e48093220528dfdd1c4012dc3837a0e + // "be liberal with padding". This is annoying, and it's not even in the spec. + + if (expectedValue.endsWith('==')) { + expectedValue = expectedValue.slice(0, -2) + } // 3. Let actualValue be the result of applying algorithm to bytes. - const actualValue = crypto.createHash(algorithm).update(bytes).digest('base64') + let actualValue = crypto.createHash(algorithm).update(bytes).digest('base64') + + if (actualValue.endsWith('==')) { + actualValue = actualValue.slice(0, -2) + } // 4. If actualValue is a case-sensitive match for expectedValue, // return true. if (actualValue === expectedValue) { return true } + + let actualBase64URL = crypto.createHash(algorithm).update(bytes).digest('base64url') + + if (actualBase64URL.endsWith('==')) { + actualBase64URL = actualBase64URL.slice(0, -2) + } + + if (actualBase64URL === expectedValue) { + return true + } } // 6. Return false. diff --git a/test/wpt/runner/runner.mjs b/test/wpt/runner/runner.mjs index 0b66f58466b..5bec326d3e1 100644 --- a/test/wpt/runner/runner.mjs +++ b/test/wpt/runner/runner.mjs @@ -312,6 +312,8 @@ export class WPTRunner extends EventEmitter { `unexpected failures: ${failed - expectedFailures}, ` + `skipped: ${skipped}` ) + + process.exit(0) } addInitScript (code) { diff --git a/test/wpt/status/fetch.status.json b/test/wpt/status/fetch.status.json index cb5949579cf..5910bf37f6f 100644 --- a/test/wpt/status/fetch.status.json +++ b/test/wpt/status/fetch.status.json @@ -2,11 +2,13 @@ "api": { "abort": { "general.any.js": { + "note": "TODO(@KhafraDev): Clone aborts with original controller can probably be fixed", "fail": [ "Already aborted signal rejects immediately", "Underlying connection is closed when aborting after receiving response - no-cors", "Stream errors once aborted. Underlying connection closed.", - "Readable stream synchronously cancels with AbortError if aborted before reading" + "Readable stream synchronously cancels with AbortError if aborted before reading", + "Clone aborts with original controller" ] }, "cache.https.any.js": { @@ -128,6 +130,10 @@ ] } }, + "fetch-later": { + "note": "this is not part of the spec, only a proposal", + "skip": true + }, "headers": { "header-setcookie.any.js": { "note": "undici doesn't filter headers", diff --git a/test/wpt/tests/.azure-pipelines.yml b/test/wpt/tests/.azure-pipelines.yml index 20d5ec0f431..75a87df90f0 100644 --- a/test/wpt/tests/.azure-pipelines.yml +++ b/test/wpt/tests/.azure-pipelines.yml @@ -21,6 +21,7 @@ trigger: - triggers/edge_canary - triggers/safari_stable - triggers/safari_preview +- triggers/wktr_preview # Set safaridriver_diagnose to true to enable safaridriver diagnostics. The # logs won't appear in `./wpt run` output but will be uploaded as an artifact. @@ -34,11 +35,11 @@ jobs: displayName: 'affected tests: Safari Technology Preview' condition: eq(variables['Build.Reason'], 'PullRequest') pool: - vmImage: 'macOS-12' + vmImage: 'macOS-13' steps: - task: UsePythonVersion@0 inputs: - versionSpec: '3.10' + versionSpec: '3.11' - template: tools/ci/azure/affected_tests.yml parameters: artifactName: 'safari-preview-affected-tests' @@ -51,11 +52,11 @@ jobs: displayName: 'affected tests without changes: Safari Technology Preview' condition: eq(variables['Build.Reason'], 'PullRequest') pool: - vmImage: 'macOS-12' + vmImage: 'macOS-13' steps: - task: UsePythonVersion@0 inputs: - versionSpec: '3.10' + versionSpec: '3.11' - template: tools/ci/azure/affected_tests.yml parameters: checkoutCommit: 'HEAD^1' @@ -76,7 +77,7 @@ jobs: steps: - task: UsePythonVersion@0 inputs: - versionSpec: '3.10' + versionSpec: '3.11' - template: tools/ci/azure/checkout.yml - script: | set -eux -o pipefail @@ -93,17 +94,18 @@ jobs: dependsOn: decision condition: dependencies.decision.outputs['test_jobs.wptrunner_infrastructure'] pool: - vmImage: 'macOS-12' + vmImage: 'macOS-13' steps: - task: UsePythonVersion@0 inputs: - versionSpec: '3.10' + versionSpec: '3.11' - template: tools/ci/azure/checkout.yml - template: tools/ci/azure/pip_install.yml parameters: packages: virtualenv - template: tools/ci/azure/install_fonts.yml - template: tools/ci/azure/install_certs.yml + - template: tools/ci/azure/color_profile.yml - template: tools/ci/azure/install_chrome.yml - template: tools/ci/azure/install_firefox.yml - template: tools/ci/azure/install_safari.yml @@ -111,20 +113,25 @@ jobs: - template: tools/ci/azure/update_manifest.yml - script: | set -eux -o pipefail - ./wpt run --yes --no-manifest-update --manifest MANIFEST.json --metadata infrastructure/metadata/ --log-mach - --log-mach-level info --channel dev chrome infrastructure/ + ./wpt run --yes --no-manifest-update --manifest MANIFEST.json --metadata infrastructure/metadata/ --log-mach - --log-mach-level info --log-wptreport $(Build.ArtifactStagingDirectory)/wpt_report_macos_chrome.json --channel dev chrome infrastructure/ condition: succeededOrFailed() displayName: 'Run tests (Chrome Dev)' - script: | set -eux -o pipefail - ./wpt run --yes --no-manifest-update --manifest MANIFEST.json --metadata infrastructure/metadata/ --log-mach - --log-mach-level info --channel nightly firefox infrastructure/ + ./wpt run --yes --no-manifest-update --manifest MANIFEST.json --metadata infrastructure/metadata/ --log-mach - --log-mach-level info --log-wptreport $(Build.ArtifactStagingDirectory)/wpt_report_macos_firefox.json --channel nightly firefox infrastructure/ condition: succeededOrFailed() displayName: 'Run tests (Firefox Nightly)' - script: | set -eux -o pipefail export SYSTEM_VERSION_COMPAT=0 - ./wpt run --yes --no-manifest-update --manifest MANIFEST.json --metadata infrastructure/metadata/ --log-mach - --log-mach-level info --channel preview safari infrastructure/ + ./wpt run --yes --no-manifest-update --manifest MANIFEST.json --metadata infrastructure/metadata/ --log-mach - --log-mach-level info --log-wptreport $(Build.ArtifactStagingDirectory)/wpt_report_macos_safari.json --channel preview safari infrastructure/ condition: succeededOrFailed() displayName: 'Run tests (Safari Technology Preview)' + - task: PublishBuildArtifacts@1 + condition: succeededOrFailed() + displayName: 'Publish results' + inputs: + artifactName: 'infrastructure-results' - template: tools/ci/azure/publish_logs.yml - template: tools/ci/azure/sysdiagnose.yml @@ -133,76 +140,79 @@ jobs: dependsOn: decision condition: dependencies.decision.outputs['test_jobs.tools_unittest'] pool: - vmImage: 'macOS-12' + vmImage: 'macOS-13' steps: - task: UsePythonVersion@0 inputs: - versionSpec: '3.7' + # TODO(#40525): Revert back to 3.7 once the Mac agent's Python v3.7 contains bz2 again. + versionSpec: '3.7.16' - template: tools/ci/azure/checkout.yml - template: tools/ci/azure/tox_pytest.yml parameters: directory: tools/ toxenv: py37 -- job: tools_unittest_mac_py310 - displayName: 'tools/ unittests: macOS + Python 3.10' +- job: tools_unittest_mac_py311 + displayName: 'tools/ unittests: macOS + Python 3.11' dependsOn: decision condition: dependencies.decision.outputs['test_jobs.tools_unittest'] pool: - vmImage: 'macOS-12' + vmImage: 'macOS-13' steps: - task: UsePythonVersion@0 inputs: - versionSpec: '3.10' + versionSpec: '3.11' - template: tools/ci/azure/checkout.yml - template: tools/ci/azure/tox_pytest.yml parameters: directory: tools/ - toxenv: py310 + toxenv: py311 - job: wptrunner_unittest_mac_py37 displayName: 'tools/wptrunner/ unittests: macOS + Python 3.7' dependsOn: decision condition: dependencies.decision.outputs['test_jobs.wptrunner_unittest'] pool: - vmImage: 'macOS-12' + vmImage: 'macOS-13' steps: - task: UsePythonVersion@0 inputs: - versionSpec: '3.7' + # TODO(#40525): Revert back to 3.7 once the Mac agent's Python v3.7 contains bz2 again. + versionSpec: '3.7.16' - template: tools/ci/azure/checkout.yml - template: tools/ci/azure/tox_pytest.yml parameters: directory: tools/wptrunner/ toxenv: py37 -- job: wptrunner_unittest_mac_py310 - displayName: 'tools/wptrunner/ unittests: macOS + Python 3.10' +- job: wptrunner_unittest_mac_py311 + displayName: 'tools/wptrunner/ unittests: macOS + Python 3.11' dependsOn: decision condition: dependencies.decision.outputs['test_jobs.wptrunner_unittest'] pool: - vmImage: 'macOS-12' + vmImage: 'macOS-13' steps: - task: UsePythonVersion@0 inputs: - versionSpec: '3.10' + versionSpec: '3.11' - template: tools/ci/azure/checkout.yml - template: tools/ci/azure/tox_pytest.yml parameters: directory: tools/wptrunner/ - toxenv: py310 + toxenv: py311 - job: wpt_integration_mac_py37 displayName: 'tools/wpt/ tests: macOS + Python 3.7' dependsOn: decision condition: dependencies.decision.outputs['test_jobs.wpt_integration'] pool: - vmImage: 'macOS-12' + vmImage: 'macOS-13' steps: # full checkout required - task: UsePythonVersion@0 inputs: - versionSpec: '3.7' + # TODO(#40525): Revert back to 3.7 once the Mac agent's Python v3.7 contains bz2 again. + versionSpec: '3.7.16' - template: tools/ci/azure/install_chrome.yml - template: tools/ci/azure/install_firefox.yml - template: tools/ci/azure/update_hosts.yml @@ -212,17 +222,17 @@ jobs: directory: tools/wpt/ toxenv: py37 -- job: wpt_integration_mac_py310 - displayName: 'tools/wpt/ tests: macOS + Python 3.10' +- job: wpt_integration_mac_py311 + displayName: 'tools/wpt/ tests: macOS + Python 3.11' dependsOn: decision condition: dependencies.decision.outputs['test_jobs.wpt_integration'] pool: - vmImage: 'macOS-12' + vmImage: 'macOS-13' steps: # full checkout required - task: UsePythonVersion@0 inputs: - versionSpec: '3.10' + versionSpec: '3.11' - template: tools/ci/azure/install_chrome.yml - template: tools/ci/azure/install_firefox.yml - template: tools/ci/azure/update_hosts.yml @@ -230,7 +240,7 @@ jobs: - template: tools/ci/azure/tox_pytest.yml parameters: directory: tools/wpt/ - toxenv: py310 + toxenv: py311 - job: tools_unittest_win_py37 displayName: 'tools/ unittests: Windows + Python 3.7' @@ -251,8 +261,8 @@ jobs: directory: tools/ toxenv: py37 -- job: tools_unittest_win_py310 - displayName: 'tools/ unittests: Windows + Python 3.10' +- job: tools_unittest_win_py311 + displayName: 'tools/ unittests: Windows + Python 3.11' dependsOn: decision condition: dependencies.decision.outputs['test_jobs.tools_unittest'] pool: @@ -260,13 +270,13 @@ jobs: steps: - task: UsePythonVersion@0 inputs: - versionSpec: '3.10' + versionSpec: '3.11' addToPath: false - template: tools/ci/azure/checkout.yml - template: tools/ci/azure/tox_pytest.yml parameters: directory: tools/ - toxenv: py310 + toxenv: py311 - job: wptrunner_unittest_win_py37 displayName: 'tools/wptrunner/ unittests: Windows + Python 3.7' @@ -285,8 +295,8 @@ jobs: directory: tools/wptrunner/ toxenv: py37 -- job: wptrunner_unittest_win_py310 - displayName: 'tools/wptrunner/ unittests: Windows + Python 3.10' +- job: wptrunner_unittest_win_py311 + displayName: 'tools/wptrunner/ unittests: Windows + Python 3.11' dependsOn: decision condition: dependencies.decision.outputs['test_jobs.wptrunner_unittest'] pool: @@ -294,13 +304,13 @@ jobs: steps: - task: UsePythonVersion@0 inputs: - versionSpec: '3.10' + versionSpec: '3.11' addToPath: false - template: tools/ci/azure/checkout.yml - template: tools/ci/azure/tox_pytest.yml parameters: directory: tools/wptrunner/ - toxenv: py310 + toxenv: py311 - job: wpt_integration_win_py37 displayName: 'tools/wpt/ tests: Windows + Python 3.7' @@ -324,8 +334,8 @@ jobs: directory: tools/wpt/ toxenv: py37 -- job: wpt_integration_win_py310 - displayName: 'tools/wpt/ tests: Windows + Python 3.10' +- job: wpt_integration_win_py311 + displayName: 'tools/wpt/ tests: Windows + Python 3.11' dependsOn: decision condition: dependencies.decision.outputs['test_jobs.wpt_integration'] pool: @@ -334,7 +344,7 @@ jobs: # full checkout required - task: UsePythonVersion@0 inputs: - versionSpec: '3.10' + versionSpec: '3.11' # currently just using the outdated Chrome/Firefox on the VM rather than # figuring out how to install Chrome Dev channel on Windows # - template: tools/ci/azure/install_chrome.yml @@ -344,7 +354,7 @@ jobs: - template: tools/ci/azure/tox_pytest.yml parameters: directory: tools/wpt/ - toxenv: py310 + toxenv: py311 - job: results_edge_stable displayName: 'all tests: Edge Stable' @@ -360,7 +370,7 @@ jobs: steps: - task: UsePythonVersion@0 inputs: - versionSpec: '3.10' + versionSpec: '3.11' - template: tools/ci/azure/system_info.yml - template: tools/ci/azure/checkout.yml - template: tools/ci/azure/pip_install.yml @@ -399,7 +409,7 @@ jobs: steps: - task: UsePythonVersion@0 inputs: - versionSpec: '3.10' + versionSpec: '3.11' - template: tools/ci/azure/system_info.yml - template: tools/ci/azure/checkout.yml - template: tools/ci/azure/pip_install.yml @@ -438,7 +448,7 @@ jobs: steps: - task: UsePythonVersion@0 inputs: - versionSpec: '3.10' + versionSpec: '3.11' - template: tools/ci/azure/checkout.yml - template: tools/ci/azure/pip_install.yml parameters: @@ -472,16 +482,17 @@ jobs: parallel: 8 # chosen to make runtime ~2h timeoutInMinutes: 180 pool: - vmImage: 'macOS-12' + vmImage: 'macOS-13' steps: - task: UsePythonVersion@0 inputs: - versionSpec: '3.10' + versionSpec: '3.11' - template: tools/ci/azure/checkout.yml - template: tools/ci/azure/pip_install.yml parameters: packages: virtualenv - template: tools/ci/azure/install_certs.yml + - template: tools/ci/azure/color_profile.yml - template: tools/ci/azure/install_safari.yml parameters: channel: stable @@ -490,8 +501,9 @@ jobs: - script: | set -eux -o pipefail export SYSTEM_VERSION_COMPAT=0 - ./wpt run --no-manifest-update --no-restart-on-unexpected --no-fail-on-unexpected --this-chunk=$(System.JobPositionInPhase) --total-chunks=$(System.TotalJobsInPhase) --chunk-type hash --log-wptreport $(Build.ArtifactStagingDirectory)/wpt_report_$(System.JobPositionInPhase).json --log-wptscreenshot $(Build.ArtifactStagingDirectory)/wpt_screenshot_$(System.JobPositionInPhase).txt --log-mach - --log-mach-level info --channel stable --kill-safari safari + ./wpt run --no-manifest-update --no-restart-on-unexpected --no-fail-on-unexpected --this-chunk=$(System.JobPositionInPhase) --total-chunks=$(System.TotalJobsInPhase) --chunk-type hash --log-wptreport $(Build.ArtifactStagingDirectory)/wpt_report_$(System.JobPositionInPhase).json --log-wptscreenshot $(Build.ArtifactStagingDirectory)/wpt_screenshot_$(System.JobPositionInPhase).txt --log-mach - --log-mach-level info --channel stable --kill-safari --max-restarts 100 safari displayName: 'Run tests' + retryCountOnTaskFailure: 2 - task: PublishBuildArtifacts@1 displayName: 'Publish results' inputs: @@ -513,24 +525,26 @@ jobs: parallel: 8 # chosen to make runtime ~2h timeoutInMinutes: 180 pool: - vmImage: 'macOS-12' + vmImage: 'macOS-13' steps: - task: UsePythonVersion@0 inputs: - versionSpec: '3.10' + versionSpec: '3.11' - template: tools/ci/azure/checkout.yml - template: tools/ci/azure/pip_install.yml parameters: packages: virtualenv - template: tools/ci/azure/install_certs.yml + - template: tools/ci/azure/color_profile.yml - template: tools/ci/azure/install_safari.yml - template: tools/ci/azure/update_hosts.yml - template: tools/ci/azure/update_manifest.yml - script: | set -eux -o pipefail export SYSTEM_VERSION_COMPAT=0 - ./wpt run --no-manifest-update --no-restart-on-unexpected --no-fail-on-unexpected --this-chunk=$(System.JobPositionInPhase) --total-chunks=$(System.TotalJobsInPhase) --chunk-type hash --log-wptreport $(Build.ArtifactStagingDirectory)/wpt_report_$(System.JobPositionInPhase).json --log-wptscreenshot $(Build.ArtifactStagingDirectory)/wpt_screenshot_$(System.JobPositionInPhase).txt --log-mach - --log-mach-level info --channel preview --kill-safari safari + ./wpt run --no-manifest-update --no-restart-on-unexpected --no-fail-on-unexpected --this-chunk=$(System.JobPositionInPhase) --total-chunks=$(System.TotalJobsInPhase) --chunk-type hash --log-wptreport $(Build.ArtifactStagingDirectory)/wpt_report_$(System.JobPositionInPhase).json --log-wptscreenshot $(Build.ArtifactStagingDirectory)/wpt_screenshot_$(System.JobPositionInPhase).txt --log-mach - --log-mach-level info --channel preview --kill-safari --max-restarts 100 safari displayName: 'Run tests' + retryCountOnTaskFailure: 2 - task: PublishBuildArtifacts@1 displayName: 'Publish results' inputs: @@ -551,22 +565,23 @@ jobs: parallel: 8 # chosen to make runtime ~2h timeoutInMinutes: 180 pool: - vmImage: 'macOS-12' + vmImage: 'macOS-13' steps: - task: UsePythonVersion@0 inputs: - versionSpec: '3.10' + versionSpec: '3.11' - template: tools/ci/azure/checkout.yml - template: tools/ci/azure/pip_install.yml parameters: packages: virtualenv - template: tools/ci/azure/install_certs.yml + - template: tools/ci/azure/color_profile.yml - template: tools/ci/azure/update_hosts.yml - template: tools/ci/azure/update_manifest.yml - script: | set -eux -o pipefail export SYSTEM_VERSION_COMPAT=0 - ./wpt run --no-manifest-update --no-restart-on-unexpected --no-fail-on-unexpected --this-chunk=$(System.JobPositionInPhase) --total-chunks=$(System.TotalJobsInPhase) --chunk-type hash --log-wptreport $(Build.ArtifactStagingDirectory)/wpt_report_$(System.JobPositionInPhase).json --log-wptscreenshot $(Build.ArtifactStagingDirectory)/wpt_screenshot_$(System.JobPositionInPhase).txt --log-mach - --log-mach-level info --channel main --install-browser wktr + ./wpt run --no-manifest-update --no-restart-on-unexpected --no-fail-on-unexpected --this-chunk=$(System.JobPositionInPhase) --total-chunks=$(System.TotalJobsInPhase) --chunk-type hash --log-wptreport $(Build.ArtifactStagingDirectory)/wpt_report_$(System.JobPositionInPhase).json --log-wptscreenshot $(Build.ArtifactStagingDirectory)/wpt_screenshot_$(System.JobPositionInPhase).txt --log-mach - --log-mach-level info --channel experimental --install-browser --yes wktr displayName: 'Run tests' - task: PublishBuildArtifacts@1 displayName: 'Publish results' diff --git a/test/wpt/tests/.taskcluster.yml b/test/wpt/tests/.taskcluster.yml index c80e92af204..c817999b8e6 100644 --- a/test/wpt/tests/.taskcluster.yml +++ b/test/wpt/tests/.taskcluster.yml @@ -57,7 +57,7 @@ tasks: owner: ${owner} source: ${event.repository.clone_url} payload: - image: webplatformtests/wpt:0.53 + image: webplatformtests/wpt:0.54 maxRunTime: 7200 artifacts: public/results: diff --git a/test/wpt/tests/FileAPI/reading-data-section/filereader_readAsDataURL.any.js b/test/wpt/tests/FileAPI/reading-data-section/filereader_readAsDataURL.any.js index d6812121295..4f9dbf7a754 100644 --- a/test/wpt/tests/FileAPI/reading-data-section/filereader_readAsDataURL.any.js +++ b/test/wpt/tests/FileAPI/reading-data-section/filereader_readAsDataURL.any.js @@ -39,4 +39,16 @@ async_test(function(testCase) { testCase.done(); }); reader.readAsDataURL(blob); -}, 'readAsDataURL result for Blob with unspecified MIME type'); \ No newline at end of file +}, 'readAsDataURL result for Blob with unspecified MIME type'); + +async_test(function(testCase) { + var blob = new Blob([]); + var reader = new FileReader(); + + reader.onload = this.step_func(function() { + assert_equals(reader.result, + "data:application/octet-stream;base64,"); + testCase.done(); + }); + reader.readAsDataURL(blob); +}, 'readAsDataURL result for empty Blob'); \ No newline at end of file diff --git a/test/wpt/tests/common/media.js b/test/wpt/tests/common/media.js index f2dc8612660..800593f5343 100644 --- a/test/wpt/tests/common/media.js +++ b/test/wpt/tests/common/media.js @@ -9,10 +9,15 @@ function getVideoURI(base) var videotag = document.createElement("video"); - if ( videotag.canPlayType && - videotag.canPlayType('video/ogg; codecs="theora, vorbis"') ) + if ( videotag.canPlayType ) { - extension = '.ogv'; + if (videotag.canPlayType('video/webm; codecs="vp9, opus"') ) + { + extension = '.webm'; + } else if ( videotag.canPlayType('video/ogg; codecs="theora, vorbis"') ) + { + extension = '.ogv'; + } } return base + extension; @@ -46,10 +51,11 @@ function getAudioURI(base) function getMediaContentType(url) { var extension = new URL(url, location).pathname.split(".").pop(); var map = { - "mp4": "video/mp4", - "ogv": "application/ogg", - "mp3": "audio/mp3", - "oga": "application/ogg", + "mp4" : "video/mp4", + "ogv" : "application/ogg", + "webm": "video/webm", + "mp3" : "audio/mp3", + "oga" : "application/ogg", }; return map[extension]; } diff --git a/test/wpt/tests/common/rendering-utils.js b/test/wpt/tests/common/rendering-utils.js index 8027cd5f848..46283bd5d07 100644 --- a/test/wpt/tests/common/rendering-utils.js +++ b/test/wpt/tests/common/rendering-utils.js @@ -7,14 +7,12 @@ */ function waitForAtLeastOneFrame() { return new Promise(resolve => { - // Different web engines work slightly different on this area but 1) waiting - // for two requestAnimationFrames() to happen one after another and 2) - // adding a step_timeout(0) to guarantee events have finished should be + // Different web engines work slightly different on this area but waiting + // for two requestAnimationFrames() to happen, one after another, should be // sufficient to ensure at least one frame has been generated anywhere. - // See https://bugzilla.mozilla.org/show_bug.cgi?id=1785615 window.requestAnimationFrame(() => { window.requestAnimationFrame(() => { - setTimeout(resolve, 0); + resolve(); }); }); }); diff --git a/test/wpt/tests/fetch/api/abort/general.any.js b/test/wpt/tests/fetch/api/abort/general.any.js index 7bf98ba9b24..3727bb42afe 100644 --- a/test/wpt/tests/fetch/api/abort/general.any.js +++ b/test/wpt/tests/fetch/api/abort/general.any.js @@ -566,7 +566,7 @@ test(() => { controller.abort(); - assert_array_equals(log, ['clone-aborted', 'original-aborted'], "Abort events fired in correct order"); + assert_array_equals(log, ['original-aborted', 'clone-aborted'], "Abort events fired in correct order"); assert_true(request.signal.aborted, 'Signal aborted'); assert_true(clonedRequest.signal.aborted, 'Signal aborted'); }, "Clone aborts with original controller"); diff --git a/test/wpt/tests/fetch/api/basic/integrity.sub.any.js b/test/wpt/tests/fetch/api/basic/integrity.sub.any.js index 56dbd4909f6..e3cfd1b2f6e 100644 --- a/test/wpt/tests/fetch/api/basic/integrity.sub.any.js +++ b/test/wpt/tests/fetch/api/basic/integrity.sub.any.js @@ -28,6 +28,9 @@ function integrity(desc, url, integrity, initRequestMode, shouldPass) { const topSha256 = "sha256-KHIDZcXnR2oBHk9DrAA+5fFiR6JjudYjqoXtMR1zvzk="; const topSha384 = "sha384-MgZYnnAzPM/MjhqfOIMfQK5qcFvGZsGLzx4Phd7/A8fHTqqLqXqKo8cNzY3xEPTL"; const topSha512 = "sha512-D6yns0qxG0E7+TwkevZ4Jt5t7Iy3ugmAajG/dlf6Pado1JqTyneKXICDiqFIkLMRExgtvg8PlxbKTkYfRejSOg=="; +const topSha512wrongpadding = "sha512-D6yns0qxG0E7+TwkevZ4Jt5t7Iy3ugmAajG/dlf6Pado1JqTyneKXICDiqFIkLMRExgtvg8PlxbKTkYfRejSOg"; +const topSha512base64url = "sha512-D6yns0qxG0E7-TwkevZ4Jt5t7Iy3ugmAajG_dlf6Pado1JqTyneKXICDiqFIkLMRExgtvg8PlxbKTkYfRejSOg=="; +const topSha512base64url_nopadding = "sha512-D6yns0qxG0E7-TwkevZ4Jt5t7Iy3ugmAajG_dlf6Pado1JqTyneKXICDiqFIkLMRExgtvg8PlxbKTkYfRejSOg"; const invalidSha256 = "sha256-dKUcPOn/AlUjWIwcHeHNqYXPlvyGiq+2dWOdFcE+24I="; const invalidSha512 = "sha512-oUceBRNxPxnY60g/VtPCj2syT4wo4EZh2CgYdWy9veW8+OsReTXoh7dizMGZafvx9+QhMS39L/gIkxnPIn41Zg=="; @@ -38,13 +41,20 @@ const corsUrl = const corsUrl2 = `https://{{host}}:{{ports[https][0]}}${path}` integrity("Empty string integrity", url, "", /* initRequestMode */ undefined, - /* shouldPass */ true); + /* shouldPass */ true); integrity("SHA-256 integrity", url, topSha256, /* initRequestMode */ undefined, /* shouldPass */ true); integrity("SHA-384 integrity", url, topSha384, /* initRequestMode */ undefined, /* shouldPass */ true); integrity("SHA-512 integrity", url, topSha512, /* initRequestMode */ undefined, /* shouldPass */ true); +integrity("SHA-512 integrity with missing padding", url, topSha512wrongpadding, + /* initRequestMode */ undefined, /* shouldPass */ true); +integrity("SHA-512 integrity base64url encoded", url, topSha512base64url, + /* initRequestMode */ undefined, /* shouldPass */ true); +integrity("SHA-512 integrity base64url encoded with missing padding", url, + topSha512base64url_nopadding, /* initRequestMode */ undefined, + /* shouldPass */ true); integrity("Invalid integrity", url, invalidSha256, /* initRequestMode */ undefined, /* shouldPass */ false); integrity("Multiple integrities: valid stronger than invalid", url, diff --git a/test/wpt/tests/fetch/api/basic/keepalive.any.js b/test/wpt/tests/fetch/api/basic/keepalive.any.js index 4f33284d0c7..899d41d676a 100644 --- a/test/wpt/tests/fetch/api/basic/keepalive.any.js +++ b/test/wpt/tests/fetch/api/basic/keepalive.any.js @@ -14,16 +14,30 @@ const { HTTP_REMOTE_ORIGIN_WITH_DIFFERENT_PORT } = get_host_info(); -for (const method of ['GET', 'POST']) { - promise_test(async (test) => { - const token1 = token(); - const iframe = document.createElement('iframe'); - iframe.src = getKeepAliveIframeUrl(token1, method); - document.body.appendChild(iframe); - await iframeLoaded(iframe); - assert_equals(await getTokenFromMessage(), token1); - iframe.remove(); +/** + * In a different-site iframe, test to fetch a keepalive URL on the specified + * document event. + */ +function keepaliveSimpleRequestTest(method) { + for (const evt of ['load', 'pagehide', 'unload']) { + const desc = + `[keepalive] simple ${method} request on '${evt}' [no payload]`; + promise_test(async (test) => { + const token1 = token(); + const iframe = document.createElement('iframe'); + iframe.src = getKeepAliveIframeUrl(token1, method, {sendOn: evt}); + document.body.appendChild(iframe); + await iframeLoaded(iframe); + if (evt != 'load') { + iframe.remove(); + } + assert_equals(await getTokenFromMessage(), token1); + + assertStashedTokenAsync(desc, token1); + }, `${desc}; setting up`); + } +} - assertStashedTokenAsync(`simple ${method} request: no payload`, token1); - }, `simple ${method} request: no payload; setting up`); +for (const method of ['GET', 'POST']) { + keepaliveSimpleRequestTest(method); } diff --git a/test/wpt/tests/fetch/api/basic/scheme-blob.sub.any.js b/test/wpt/tests/fetch/api/basic/scheme-blob.sub.any.js index a6059ea93d9..8afdc033c9d 100644 --- a/test/wpt/tests/fetch/api/basic/scheme-blob.sub.any.js +++ b/test/wpt/tests/fetch/api/basic/scheme-blob.sub.any.js @@ -57,6 +57,10 @@ let empty_data_blob = new Blob([], {type: "text/plain"}); checkFetchResponse(URL.createObjectURL(empty_data_blob), "", "text/plain", 0, "Fetching URL.createObjectURL(empty_data_blob) is OK"); +let invalid_type_blob = new Blob([], {type: "invalid"}); +checkFetchResponse(URL.createObjectURL(invalid_type_blob), "", "", 0, + "Fetching URL.createObjectURL(invalid_type_blob) is OK"); + promise_test(function(test) { return fetch("/images/blue.png").then(function(resp) { return resp.arrayBuffer(); diff --git a/test/wpt/tests/fetch/api/cors/cors-basic.any.js b/test/wpt/tests/fetch/api/cors/cors-basic.any.js index 23f5f91c87d..95de0af2d8f 100644 --- a/test/wpt/tests/fetch/api/cors/cors-basic.any.js +++ b/test/wpt/tests/fetch/api/cors/cors-basic.any.js @@ -1,37 +1,43 @@ // META: script=../resources/utils.js // META: script=/common/get-host-info.sub.js +const { + HTTPS_ORIGIN, + HTTP_ORIGIN_WITH_DIFFERENT_PORT, + HTTP_REMOTE_ORIGIN, + HTTP_REMOTE_ORIGIN_WITH_DIFFERENT_PORT, + HTTPS_REMOTE_ORIGIN, +} = get_host_info(); + function cors(desc, origin) { - var url = origin + dirname(location.pathname); - var urlParameters = "?pipe=header(Access-Control-Allow-Origin,*)"; + const url = `${origin}${dirname(location.pathname)}${RESOURCES_DIR}top.txt`; + const urlAllowCors = `${url}?pipe=header(Access-Control-Allow-Origin,*)`; - promise_test(function(test) { - return fetch(url + RESOURCES_DIR + "top.txt" + urlParameters, {"mode": "no-cors"} ).then(function(resp) { + promise_test((test) => { + return fetch(urlAllowCors, {'mode': 'no-cors'}).then((resp) => { assert_equals(resp.status, 0, "Opaque filter: status is 0"); assert_equals(resp.statusText, "", "Opaque filter: statusText is \"\""); assert_equals(resp.type , "opaque", "Opaque filter: response's type is opaque"); - return resp.text().then(function(value) { + return resp.text().then((value) => { assert_equals(value, "", "Opaque response should have an empty body"); }); }); - }, desc + " [no-cors mode]"); + }, `${desc} [no-cors mode]`); - promise_test(function(test) { - return promise_rejects_js(test, TypeError, fetch(url + RESOURCES_DIR + "top.txt", {"mode": "cors"})); - }, desc + " [server forbid CORS]"); + promise_test((test) => { + return promise_rejects_js(test, TypeError, fetch(url, {'mode': 'cors'})); + }, `${desc} [server forbid CORS]`); - promise_test(function(test) { - return fetch(url + RESOURCES_DIR + "top.txt" + urlParameters, {"mode": "cors"} ).then(function(resp) { + promise_test((test) => { + return fetch(urlAllowCors, {'mode': 'cors'}).then((resp) => { assert_equals(resp.status, 200, "Fetch's response's status is 200"); assert_equals(resp.type , "cors", "CORS response's type is cors"); }); - }, desc + " [cors mode]"); + }, `${desc} [cors mode]`); } -var host_info = get_host_info(); - -cors("Same domain different port", host_info.HTTP_ORIGIN_WITH_DIFFERENT_PORT); -cors("Same domain different protocol different port", host_info.HTTPS_ORIGIN); -cors("Cross domain basic usage", host_info.HTTP_REMOTE_ORIGIN); -cors("Cross domain different port", host_info.HTTP_REMOTE_ORIGIN_WITH_DIFFERENT_PORT); -cors("Cross domain different protocol", host_info.HTTPS_REMOTE_ORIGIN); +cors('Same domain different port', HTTP_ORIGIN_WITH_DIFFERENT_PORT); +cors('Same domain different protocol different port', HTTPS_ORIGIN); +cors('Cross domain basic usage', HTTP_REMOTE_ORIGIN); +cors('Cross domain different port', HTTP_REMOTE_ORIGIN_WITH_DIFFERENT_PORT); +cors('Cross domain different protocol', HTTPS_REMOTE_ORIGIN); diff --git a/test/wpt/tests/fetch/api/cors/cors-keepalive.any.js b/test/wpt/tests/fetch/api/cors/cors-keepalive.any.js new file mode 100644 index 00000000000..f68d90ef9aa --- /dev/null +++ b/test/wpt/tests/fetch/api/cors/cors-keepalive.any.js @@ -0,0 +1,118 @@ +// META: global=window +// META: timeout=long +// META: title=Fetch API: keepalive handling +// META: script=/resources/testharness.js +// META: script=/resources/testharnessreport.js +// META: script=/common/utils.js +// META: script=/common/get-host-info.sub.js +// META: script=../resources/keepalive-helper.js +// META: script=../resources/utils.js + +'use strict'; + +const { + HTTP_NOTSAMESITE_ORIGIN, + HTTPS_ORIGIN, + HTTP_ORIGIN_WITH_DIFFERENT_PORT, + HTTP_REMOTE_ORIGIN, + HTTP_REMOTE_ORIGIN_WITH_DIFFERENT_PORT, + HTTPS_REMOTE_ORIGIN, +} = get_host_info(); + +/** + * Tests to cover the basic behaviors of keepalive + cors/no-cors mode requests + * to different `origin` when the initiator document is still alive. They should + * behave the same as without setting keepalive. + */ +function keepaliveCorsBasicTest(desc, origin) { + const url = `${origin}${dirname(location.pathname)}${RESOURCES_DIR}top.txt`; + const urlAllowCors = `${url}?pipe=header(Access-Control-Allow-Origin,*)`; + + promise_test((test) => { + return fetch(urlAllowCors, {keepalive: true, 'mode': 'no-cors'}) + .then((resp) => { + assert_equals(resp.status, 0, 'Opaque filter: status is 0'); + assert_equals(resp.statusText, '', 'Opaque filter: statusText is ""'); + assert_equals( + resp.type, 'opaque', 'Opaque filter: response\'s type is opaque'); + return resp.text().then((value) => { + assert_equals( + value, '', 'Opaque response should have an empty body'); + }); + }); + }, `${desc} [no-cors mode]`); + + promise_test((test) => { + return promise_rejects_js( + test, TypeError, fetch(url, {keepalive: true, 'mode': 'cors'})); + }, `${desc} [cors mode, server forbid CORS]`); + + promise_test((test) => { + return fetch(urlAllowCors, {keepalive: true, 'mode': 'cors'}) + .then((resp) => { + assert_equals(resp.status, 200, 'Fetch\'s response\'s status is 200'); + assert_equals(resp.type, 'cors', 'CORS response\'s type is cors'); + }); + }, `${desc} [cors mode]`); +} + +keepaliveCorsBasicTest( + `[keepalive] Same domain different port`, HTTP_ORIGIN_WITH_DIFFERENT_PORT); +keepaliveCorsBasicTest( + `[keepalive] Same domain different protocol different port`, HTTPS_ORIGIN); +keepaliveCorsBasicTest( + `[keepalive] Cross domain basic usage`, HTTP_REMOTE_ORIGIN); +keepaliveCorsBasicTest( + `[keepalive] Cross domain different port`, + HTTP_REMOTE_ORIGIN_WITH_DIFFERENT_PORT); +keepaliveCorsBasicTest( + `[keepalive] Cross domain different protocol`, HTTPS_REMOTE_ORIGIN); + +/** + * In a same-site iframe, and in `unload` event handler, test to fetch + * a keepalive URL that involves in different cors modes. + */ +function keepaliveCorsInUnloadTest(description, origin, method) { + const evt = 'unload'; + for (const mode of ['no-cors', 'cors']) { + for (const disallowOrigin of [false, true]) { + const desc = `${description} ${method} request in ${evt} [${mode} mode` + + (disallowOrigin ? `, server forbid CORS]` : `]`); + const shouldPass = !disallowOrigin || mode === 'no-cors'; + promise_test(async (test) => { + const token1 = token(); + const iframe = document.createElement('iframe'); + iframe.src = getKeepAliveIframeUrl(token1, method, { + frameOrigin: '', + requestOrigin: origin, + sendOn: evt, + mode: mode, + disallowOrigin + }); + document.body.appendChild(iframe); + await iframeLoaded(iframe); + iframe.remove(); + assert_equals(await getTokenFromMessage(), token1); + + assertStashedTokenAsync(desc, token1, {shouldPass}); + }, `${desc}; setting up`); + } + } +} + +for (const method of ['GET', 'POST']) { + keepaliveCorsInUnloadTest( + '[keepalive] Same domain different port', HTTP_ORIGIN_WITH_DIFFERENT_PORT, + method); + keepaliveCorsInUnloadTest( + `[keepalive] Same domain different protocol different port`, HTTPS_ORIGIN, + method); + keepaliveCorsInUnloadTest( + `[keepalive] Cross domain basic usage`, HTTP_REMOTE_ORIGIN, method); + keepaliveCorsInUnloadTest( + `[keepalive] Cross domain different port`, + HTTP_REMOTE_ORIGIN_WITH_DIFFERENT_PORT, method); + keepaliveCorsInUnloadTest( + `[keepalive] Cross domain different protocol`, HTTPS_REMOTE_ORIGIN, + method); +} diff --git a/test/wpt/tests/fetch/api/cors/cors-preflight-star.any.js b/test/wpt/tests/fetch/api/cors/cors-preflight-star.any.js index e8cbc80b808..f9fb20469cf 100644 --- a/test/wpt/tests/fetch/api/cors/cors-preflight-star.any.js +++ b/test/wpt/tests/fetch/api/cors/cors-preflight-star.any.js @@ -80,3 +80,7 @@ preflightTest(true, true, "PATCH", "*", "PATCH", []) preflightTest(false, true, "PATCH", "*", "patch", []) preflightTest(false, true, "patch", "*", "PATCH", []) preflightTest(true, true, "patch", "*", "patch", []) + +// "Authorization" header can't be wildcarded. +preflightTest(false, false, "*", "*", "POST", ["Authorization", "123"]) +preflightTest(true, false, "*", "*, Authorization", "POST", ["Authorization", "123"]) diff --git a/test/wpt/tests/fetch/api/crashtests/body-window-destroy.html b/test/wpt/tests/fetch/api/crashtests/body-window-destroy.html new file mode 100644 index 00000000000..646d3c5f8ce --- /dev/null +++ b/test/wpt/tests/fetch/api/crashtests/body-window-destroy.html @@ -0,0 +1,11 @@ + + + diff --git a/test/wpt/tests/fetch/api/request/destination/resources/dummy_video.webm b/test/wpt/tests/fetch/api/request/destination/resources/dummy_video.webm new file mode 100644 index 0000000000000000000000000000000000000000..c3d433a3e02e86eee1026b6620dc0c50498e61a4 GIT binary patch literal 96902 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%0AxWB;E~=xj7<{Wt4OeKYACt+d&&88fiV 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 O{=nA(AP!glu>TjvI>Ctm literal 0 HcmV?d00001 diff --git a/test/wpt/tests/fetch/api/request/multi-globals/construct-in-detached-frame.window.js b/test/wpt/tests/fetch/api/request/multi-globals/construct-in-detached-frame.window.js new file mode 100644 index 00000000000..b0d6ba5b80d --- /dev/null +++ b/test/wpt/tests/fetch/api/request/multi-globals/construct-in-detached-frame.window.js @@ -0,0 +1,11 @@ +// This is a regression test for Chromium issue https://crbug.com/1427266. +test(() => { + const iframe = document.createElement('iframe'); + document.body.append(iframe); + const otherRequest = iframe.contentWindow.Request; + iframe.remove(); + const r1 = new otherRequest('resource', { method: 'POST', body: 'string' }); + const r2 = new otherRequest(r1); + assert_true(r1.bodyUsed); + assert_false(r2.bodyUsed); +}, 'creating a request from another request in a detached realm should work'); diff --git a/test/wpt/tests/fetch/api/resources/keepalive-helper.js b/test/wpt/tests/fetch/api/resources/keepalive-helper.js index c7048d1ff33..ad1d4b2c7c3 100644 --- a/test/wpt/tests/fetch/api/resources/keepalive-helper.js +++ b/test/wpt/tests/fetch/api/resources/keepalive-helper.js @@ -1,19 +1,35 @@ // Utility functions to help testing keepalive requests. -// Returns a different-site URL to an iframe that loads a keepalive URL. +// Returns a URL to an iframe that loads a keepalive URL on iframe loaded. // // The keepalive URL points to a target that stores `token`. The token will then -// be posted back to parent document. +// be posted back on iframe loaded to the parent document. // `method` defaults to GET. -// `sendOnPagehide` to tell if request should be sent on pagehide instead. -function getKeepAliveIframeUrl(token, method, sendOnPagehide = false) { +// `frameOrigin` to specify the origin of the iframe to load. If not set, +// default to a different site origin. +// `requestOrigin` to specify the origin of the fetch request target. +// `sendOn` to specify the name of the event when the keepalive request should +// be sent instead of the default 'load'. +// `mode` to specify the fetch request's CORS mode. +// `disallowOrigin` to ask the iframe to set up a server that forbids CORS +// requests. +function getKeepAliveIframeUrl(token, method, { + frameOrigin = 'DEFAULT', + requestOrigin = '', + sendOn = 'load', + mode = 'cors', + disallowOrigin = false +} = {}) { const https = location.protocol.startsWith('https'); - const frameOrigin = - get_host_info()[https ? 'HTTPS_NOTSAMESITE_ORIGIN' : 'HTTP_NOTSAMESITE_ORIGIN']; + frameOrigin = frameOrigin === 'DEFAULT' ? + get_host_info()[https ? 'HTTPS_NOTSAMESITE_ORIGIN' : 'HTTP_NOTSAMESITE_ORIGIN'] : + frameOrigin; return `${frameOrigin}/fetch/api/resources/keepalive-iframe.html?` + `token=${token}&` + `method=${method}&` + - `sendOnPagehide=${sendOnPagehide}`; + `sendOn=${sendOn}&` + + `mode=${mode}&` + (disallowOrigin ? `disallowOrigin=1&` : ``) + + `origin=${requestOrigin}`; } // Returns a different-site URL to an iframe that loads a keepalive URL. diff --git a/test/wpt/tests/fetch/api/resources/keepalive-iframe.html b/test/wpt/tests/fetch/api/resources/keepalive-iframe.html index ac00f3a331a..335a1f8e318 100644 --- a/test/wpt/tests/fetch/api/resources/keepalive-iframe.html +++ b/test/wpt/tests/fetch/api/resources/keepalive-iframe.html @@ -3,12 +3,18 @@ diff --git a/test/wpt/tests/fetch/api/resources/stash-put.py b/test/wpt/tests/fetch/api/resources/stash-put.py index dbc7ceebb88..0530e1ba5b4 100644 --- a/test/wpt/tests/fetch/api/resources/stash-put.py +++ b/test/wpt/tests/fetch/api/resources/stash-put.py @@ -1,17 +1,19 @@ from wptserve.utils import isomorphic_decode def main(request, response): - if request.method == u'OPTIONS': - # CORS preflight - response.headers.set(b'Access-Control-Allow-Origin', b'*') - response.headers.set(b'Access-Control-Allow-Methods', b'*') - response.headers.set(b'Access-Control-Allow-Headers', b'*') - return 'done' + if request.method == u'OPTIONS': + # CORS preflight + response.headers.set(b'Access-Control-Allow-Origin', b'*') + response.headers.set(b'Access-Control-Allow-Methods', b'*') + response.headers.set(b'Access-Control-Allow-Headers', b'*') + return 'done' + + url_dir = u'/'.join(request.url_parts.path.split(u'/')[:-1]) + u'/' + key = request.GET.first(b'key') + value = request.GET.first(b'value') + # value here must be a text string. It will be json.dump()'ed in stash-take.py. + request.server.stash.put(key, isomorphic_decode(value), url_dir) - url_dir = u'/'.join(request.url_parts.path.split(u'/')[:-1]) + u'/' - key = request.GET.first(b"key") - value = request.GET.first(b"value") - # value here must be a text string. It will be json.dump()'ed in stash-take.py. - request.server.stash.put(key, isomorphic_decode(value), url_dir) + if b'disallow_origin' not in request.GET: response.headers.set(b'Access-Control-Allow-Origin', b'*') - return "done" + return 'done' diff --git a/test/wpt/tests/fetch/api/response/response-body-read-task-handling.html b/test/wpt/tests/fetch/api/response/response-body-read-task-handling.html index c2c90eaa8bd..64b07556661 100644 --- a/test/wpt/tests/fetch/api/response/response-body-read-task-handling.html +++ b/test/wpt/tests/fetch/api/response/response-body-read-task-handling.html @@ -35,9 +35,7 @@ // The fulfill handler above shouldn't have run yet. If it has run, // throw to reject this promise and fail the test. - if (executed) { - throw "shouldn't have run microtasks yet"; - } + assert_false(executed, "shouldn't have run microtasks yet"); // Otherwise act as if there's no "then" property so the promise // fulfills and the test passes. @@ -49,6 +47,40 @@ return response.body.getReader().read(); }); }, "reading from a body stream should occur in a microtask scope"); + +promise_test(function() { + return fetch("../resources/data.json").then(function(response) { + // Add a getter for "then" that will incidentally be invoked + // during promise resolution. + Object.prototype.__defineGetter__('then', () => { + // Clean up behind ourselves. + delete Object.prototype.then; + + // This promise should (like all promises) be resolved + // asynchronously. + var executed = false; + Promise.resolve().then(_ => { executed = true; }); + + // This shouldn't run microtasks! They should only run + // after the fetch is resolved. + performMicrotaskCheckpoint(); + + // The fulfill handler above shouldn't have run yet. If it has run, + // throw to reject this promise and fail the test. + assert_false(executed, "shouldn't have run microtasks yet"); + + // Otherwise act as if there's no "then" property so the promise + // fulfills and the test passes. + return undefined; + }); + + // Create a read request, incidentally resolving a promise with an + // object value, thereby invoking the getter installed above. + return response.body.pipeTo(new WritableStream({ + write(chunk) {} + })) + }); +}, "piping from a body stream to a JS-written WritableStream should occur in a microtask scope"); diff --git a/test/wpt/tests/fetch/api/response/response-cancel-stream.any.js b/test/wpt/tests/fetch/api/response/response-cancel-stream.any.js index baa46de4039..91140d1afd1 100644 --- a/test/wpt/tests/fetch/api/response/response-cancel-stream.any.js +++ b/test/wpt/tests/fetch/api/response/response-cancel-stream.any.js @@ -55,3 +55,10 @@ promise_test(function() { return readAll(reader).then(() => reader.cancel()); }); }, "Cancelling a closed Response stream"); + +promise_test(async () => { + const response = await fetch(RESOURCES_DIR + "top.txt"); + const { body } = response; + await body.cancel(); + assert_equals(body, response.body, ".body should not change after cancellation"); +}, "Accessing .body after canceling it"); diff --git a/test/wpt/tests/fetch/api/response/response-static-json.any.js b/test/wpt/tests/fetch/api/response/response-static-json.any.js index 3c8a2b637f7..5ec79e69aa3 100644 --- a/test/wpt/tests/fetch/api/response/response-static-json.any.js +++ b/test/wpt/tests/fetch/api/response/response-static-json.any.js @@ -79,3 +79,18 @@ promise_test(async function () { } ) }, "Check static json() propagates JSON serializer errors"); + +const encodingChecks = [ + ["𝌆", [34, 240, 157, 140, 134, 34]], + ["\uDF06\uD834", [34, 92, 117, 100, 102, 48, 54, 92, 117, 100, 56, 51, 52, 34]], + ["\uDEAD", [34, 92, 117, 100, 101, 97, 100, 34]], +]; + +for (const [input, expected] of encodingChecks) { + promise_test(async function () { + const response = Response.json(input); + const buffer = await response.arrayBuffer(); + const data = new Uint8Array(buffer); + assert_array_equals(data, expected); + }, `Check response returned by static json() with input ${input}`); +} diff --git a/test/wpt/tests/fetch/content-length/resources/content-lengths.json b/test/wpt/tests/fetch/content-length/resources/content-lengths.json index dac9c82dc09..ac6f1a24680 100644 --- a/test/wpt/tests/fetch/content-length/resources/content-lengths.json +++ b/test/wpt/tests/fetch/content-length/resources/content-lengths.json @@ -31,6 +31,22 @@ "input": "Content-Length: 30\r\nContent-Length: 30,30", "output": 30 }, + { + "input": "Content-Length: 30,30\r\nContent-Length: 30,30", + "output": 30 + }, + { + "input": "Content-Length: 30,30, 30 \r\nContent-Length: 30 ", + "output": 30 + }, + { + "input": "Content-Length: 30,42\r\nContent-Length: 30", + "output": null + }, + { + "input": "Content-Length: 30,42\r\nContent-Length: 30,42", + "output": null + }, { "input": "Content-Length: 42,30", "output": null diff --git a/test/wpt/tests/fetch/corb/resources/response_block_probe.js b/test/wpt/tests/fetch/corb/resources/response_block_probe.js index d23ad488af2..9c3b87bcbd3 100644 --- a/test/wpt/tests/fetch/corb/resources/response_block_probe.js +++ b/test/wpt/tests/fetch/corb/resources/response_block_probe.js @@ -1 +1 @@ -window.script_callback(); +alert(1); // Arbitrary JavaScript. Details don't matter for the test. diff --git a/test/wpt/tests/fetch/corb/response_block.tentative.https.html b/test/wpt/tests/fetch/corb/response_block.tentative.https.html new file mode 100644 index 00000000000..6b116000d45 --- /dev/null +++ b/test/wpt/tests/fetch/corb/response_block.tentative.https.html @@ -0,0 +1,50 @@ + + + + + + diff --git a/test/wpt/tests/fetch/corb/response_block.tentative.sub.https.html b/test/wpt/tests/fetch/corb/response_block.tentative.sub.https.html deleted file mode 100644 index 860e0d3b93c..00000000000 --- a/test/wpt/tests/fetch/corb/response_block.tentative.sub.https.html +++ /dev/null @@ -1,44 +0,0 @@ - - - - - diff --git a/test/wpt/tests/fetch/fetch-later/META.yml b/test/wpt/tests/fetch/fetch-later/META.yml new file mode 100644 index 00000000000..f8fd46bec3e --- /dev/null +++ b/test/wpt/tests/fetch/fetch-later/META.yml @@ -0,0 +1,3 @@ +spec: https://whatpr.org/fetch/1647/094ea69...152d725.html#fetch-later-method +suggested_reviewers: + - mingyc diff --git a/test/wpt/tests/fetch/fetch-later/README.md b/test/wpt/tests/fetch/fetch-later/README.md new file mode 100644 index 00000000000..661e2b91843 --- /dev/null +++ b/test/wpt/tests/fetch/fetch-later/README.md @@ -0,0 +1,3 @@ +# FetchLater Tests + +These tests cover [FetchLater method](https://whatpr.org/fetch/1647/094ea69...152d725.html#fetch-later-method) related behaviors. diff --git a/test/wpt/tests/fetch/fetch-later/basic.tentative.https.window.js b/test/wpt/tests/fetch/fetch-later/basic.tentative.https.window.js new file mode 100644 index 00000000000..a8ca011a7c9 --- /dev/null +++ b/test/wpt/tests/fetch/fetch-later/basic.tentative.https.window.js @@ -0,0 +1,13 @@ +// META: script=/resources/testharness.js +// META: script=/resources/testharnessreport.js + +'use strict'; + +test(() => { + assert_throws_js(TypeError, () => fetchLater()); +}, `fetchLater() cannot be called without request.`); + +test(() => { + const result = fetchLater('/'); + assert_false(result.sent); +}, `fetchLater()'s return tells the deferred request is not yet sent.`); diff --git a/test/wpt/tests/fetch/fetch-later/non-secure.window.js b/test/wpt/tests/fetch/fetch-later/non-secure.window.js new file mode 100644 index 00000000000..2f2c3ea8d34 --- /dev/null +++ b/test/wpt/tests/fetch/fetch-later/non-secure.window.js @@ -0,0 +1,8 @@ +// META: script=/resources/testharness.js +// META: script=/resources/testharnessreport.js + +'use strict'; + +test(() => { + assert_false(window.hasOwnProperty('fetchLater')); +}, `fetchLater() is not supported in non-secure context.`); diff --git a/test/wpt/tests/fetch/fetch-later/sendondiscard.tentative.https.window.js b/test/wpt/tests/fetch/fetch-later/sendondiscard.tentative.https.window.js new file mode 100644 index 00000000000..0613d18dffb --- /dev/null +++ b/test/wpt/tests/fetch/fetch-later/sendondiscard.tentative.https.window.js @@ -0,0 +1,28 @@ +// META: script=/resources/testharness.js +// META: script=/resources/testharnessreport.js +// META: script=/common/utils.js +// META: script=/pending-beacon/resources/pending_beacon-helper.js + +'use strict'; + +parallelPromiseTest(async t => { + const uuid = token(); + const url = generateSetBeaconURL(uuid); + const numPerMethod = 20; + const total = numPerMethod * 2; + + // Loads an iframe that creates `numPerMethod` GET & POST fetchLater requests. + const iframe = await loadScriptAsIframe(` + const url = "${url}"; + for (let i = 0; i < ${numPerMethod}; i++) { + let get = fetchLater(url); + let post = fetchLater(url, {method: 'POST'}); + } + `); + + // Delete the iframe to trigger deferred request sending. + document.body.removeChild(iframe); + + // The iframe should have sent all requests. + await expectBeacon(uuid, {count: total}); +}, 'A discarded document sends all its fetchLater requests with default config.'); diff --git a/test/wpt/tests/fetch/h1-parsing/resources-with-0x00-in-header.window.js b/test/wpt/tests/fetch/h1-parsing/resources-with-0x00-in-header.window.js index f1afeeb740b..37a61c12b56 100644 --- a/test/wpt/tests/fetch/h1-parsing/resources-with-0x00-in-header.window.js +++ b/test/wpt/tests/fetch/h1-parsing/resources-with-0x00-in-header.window.js @@ -15,7 +15,7 @@ async_test(t => { // https://github.com/whatwg/html/issues/125 and https://github.com/whatwg/html/issues/1230 this // should be changed to use the load event instead. t.step_timeout(() => { - assert_equals(frame.contentDocument, null); + assert_equals(window.frameLoaded, undefined); t.done(); }, 1000); document.body.append(frame); diff --git a/test/wpt/tests/fetch/h1-parsing/resources/document-with-0x00-in-header.py b/test/wpt/tests/fetch/h1-parsing/resources/document-with-0x00-in-header.py index 223b3c40278..d91998b998d 100644 --- a/test/wpt/tests/fetch/h1-parsing/resources/document-with-0x00-in-header.py +++ b/test/wpt/tests/fetch/h1-parsing/resources/document-with-0x00-in-header.py @@ -1,4 +1,4 @@ def main(request, response): response.headers.set(b"Content-Type", b"text/html") response.headers.set(b"Custom", b"\0") - return b"This is a document." + return b"This is a document." diff --git a/test/wpt/tests/fetch/local-network-access/README.md b/test/wpt/tests/fetch/local-network-access/README.md deleted file mode 100644 index 8995e3d7ef6..00000000000 --- a/test/wpt/tests/fetch/local-network-access/README.md +++ /dev/null @@ -1,10 +0,0 @@ -# Local Network Access tests - -This directory contains tests for Local Network Access' integration with -the Fetch specification. - -See also: - -* [The specification](https://wicg.github.io/local-network-access/) -* [The repository](https://github.com/WICG/local-network-access/) -* [Open issues](https://github.com/WICG/local-network-access/issues/) diff --git a/test/wpt/tests/fetch/orb/resources/script-asm-js-invalid.js b/test/wpt/tests/fetch/orb/resources/script-asm-js-invalid.js new file mode 100644 index 00000000000..8d1bbd6abc8 --- /dev/null +++ b/test/wpt/tests/fetch/orb/resources/script-asm-js-invalid.js @@ -0,0 +1,4 @@ +function f() { + "use asm"; + return; +} diff --git a/test/wpt/tests/fetch/orb/resources/script-asm-js-valid.js b/test/wpt/tests/fetch/orb/resources/script-asm-js-valid.js new file mode 100644 index 00000000000..79b375fe054 --- /dev/null +++ b/test/wpt/tests/fetch/orb/resources/script-asm-js-valid.js @@ -0,0 +1,4 @@ +function f() { + "use asm"; + return {}; +} diff --git a/test/wpt/tests/fetch/orb/tentative/known-mime-type.sub.any.js b/test/wpt/tests/fetch/orb/tentative/known-mime-type.sub.any.js index 66a63c8b28a..b0521e8b363 100644 --- a/test/wpt/tests/fetch/orb/tentative/known-mime-type.sub.any.js +++ b/test/wpt/tests/fetch/orb/tentative/known-mime-type.sub.any.js @@ -74,3 +74,13 @@ promise_test(async () => { promise_test(async () => { await fetchORB(`${path}/script-iso-8559-1.js`, null, contentType("application/json")); }, "ORB shouldn't block opaque text/javascript (iso-8559-1 encoded)"); + +// Test javascript validation can correctly parse asm.js. +promise_test(async () => { + await fetchORB(`${path}/script-asm-js-valid.js`, null, contentType("application/json")); +}, "ORB shouldn't block text/javascript with valid asm.js"); + +// Test javascript validation can correctly parse invalid asm.js with valid JS syntax. +promise_test(async () => { + await fetchORB(`${path}/script-asm-js-invalid.js`, null, contentType("application/json")); +}, "ORB shouldn't block text/javascript with invalid asm.js"); diff --git a/test/wpt/tests/fetch/local-network-access/META.yml b/test/wpt/tests/fetch/private-network-access/META.yml similarity index 100% rename from test/wpt/tests/fetch/local-network-access/META.yml rename to test/wpt/tests/fetch/private-network-access/META.yml diff --git a/test/wpt/tests/fetch/private-network-access/README.md b/test/wpt/tests/fetch/private-network-access/README.md new file mode 100644 index 00000000000..a69aab48723 --- /dev/null +++ b/test/wpt/tests/fetch/private-network-access/README.md @@ -0,0 +1,10 @@ +# Private Network Access tests + +This directory contains tests for Private Network Access' integration with +the Fetch specification. + +See also: + +* [The specification](https://wicg.github.io/private-network-access/) +* [The repository](https://github.com/WICG/private-network-access/) +* [Open issues](https://github.com/WICG/private-network-access/issues/) diff --git a/test/wpt/tests/fetch/private-network-access/fenced-frame-no-preflight-required.tentative.https.window.js b/test/wpt/tests/fetch/private-network-access/fenced-frame-no-preflight-required.tentative.https.window.js new file mode 100644 index 00000000000..21233f61ea6 --- /dev/null +++ b/test/wpt/tests/fetch/private-network-access/fenced-frame-no-preflight-required.tentative.https.window.js @@ -0,0 +1,91 @@ +// META: script=/common/dispatcher/dispatcher.js +// META: script=/common/utils.js +// META: script=resources/support.sub.js +// META: script=/fenced-frame/resources/utils.js +// META: timeout=long +// +// Spec: https://wicg.github.io/private-network-access/#integration-fetch +// +// These tests verify that contexts can navigate fenced frames to more-public or +// same address spaces without private network access preflight request header. + +setup(() => { + assert_true(window.isSecureContext); +}); + +// Source: secure local context. +// +// All fetches unaffected by Private Network Access. + +promise_test_parallel( + t => fencedFrameTest(t, { + source: {server: Server.HTTPS_LOCAL}, + target: {server: Server.HTTPS_LOCAL}, + expected: FrameTestResult.SUCCESS, + }), + 'local to local: no preflight required.'); + +promise_test_parallel( + t => fencedFrameTest(t, { + source: {server: Server.HTTPS_LOCAL}, + target: {server: Server.HTTPS_PRIVATE}, + expected: FrameTestResult.SUCCESS, + }), + 'local to private: no preflight required.'); + +promise_test_parallel( + t => fencedFrameTest(t, { + source: {server: Server.HTTPS_LOCAL}, + target: {server: Server.HTTPS_PUBLIC}, + expected: FrameTestResult.SUCCESS, + }), + 'local to public: no preflight required.'); + +promise_test_parallel( + t => fencedFrameTest(t, { + source: {server: Server.HTTPS_PRIVATE}, + target: {server: Server.HTTPS_PRIVATE}, + expected: FrameTestResult.SUCCESS, + }), + 'private to private: no preflight required.'); + +promise_test_parallel( + t => fencedFrameTest(t, { + source: {server: Server.HTTPS_PRIVATE}, + target: {server: Server.HTTPS_PUBLIC}, + expected: FrameTestResult.SUCCESS, + }), + 'private to public: no preflight required.'); + +promise_test_parallel( + t => fencedFrameTest(t, { + source: {server: Server.HTTPS_PUBLIC}, + target: {server: Server.HTTPS_PUBLIC}, + expected: FrameTestResult.SUCCESS, + }), + 'public to public: no preflight required.'); + +promise_test_parallel( + t => fencedFrameTest(t, { + source: { + server: Server.HTTPS_LOCAL, + treatAsPublic: true, + }, + target: {server: Server.HTTPS_PUBLIC}, + expected: FrameTestResult.SUCCESS, + }), + 'treat-as-public-address to public: no preflight required.'); + +promise_test_parallel( + t => fencedFrameTest(t, { + source: { + server: Server.HTTPS_LOCAL, + treatAsPublic: true, + }, + target: { + server: Server.HTTPS_PUBLIC, + behavior: {preflight: PreflightBehavior.optionalSuccess(token())} + }, + expected: FrameTestResult.SUCCESS, + }), + 'treat-as-public-address to local: optional preflight'); diff --git a/test/wpt/tests/fetch/private-network-access/fenced-frame-subresource-fetch.tentative.https.window.js b/test/wpt/tests/fetch/private-network-access/fenced-frame-subresource-fetch.tentative.https.window.js new file mode 100644 index 00000000000..2dff325e3e1 --- /dev/null +++ b/test/wpt/tests/fetch/private-network-access/fenced-frame-subresource-fetch.tentative.https.window.js @@ -0,0 +1,330 @@ +// META: script=/common/subset-tests-by-key.js +// META: script=/common/utils.js +// META: script=resources/support.sub.js +// META: script=/fenced-frame/resources/utils.js +// META: variant=?include=baseline +// META: variant=?include=from-local +// META: variant=?include=from-private +// META: variant=?include=from-public +// META: timeout=long +// +// Spec: https://wicg.github.io/private-network-access/#integration-fetch +// +// These tests verify that secure contexts can fetch subresources in fenced +// frames from all address spaces, provided that the target server, if more +// private than the initiator, respond affirmatively to preflight requests. +// + +setup(() => { + // Making sure we are in a secure context, as expected. + assert_true(window.isSecureContext); +}); + +// Source: secure local context. +// +// All fetches unaffected by Private Network Access. + +subsetTestByKey( + 'from-local', promise_test, t => fencedFrameFetchTest(t, { + source: {server: Server.HTTPS_LOCAL}, + target: {server: Server.HTTPS_LOCAL}, + fetchOptions: {method: 'GET', mode: 'cors'}, + expected: FetchTestResult.SUCCESS, + }), + 'local to local: no preflight required.'); + +subsetTestByKey( + 'from-local', promise_test, + t => fencedFrameFetchTest(t, { + source: {server: Server.HTTPS_LOCAL}, + target: { + server: Server.HTTPS_PRIVATE, + behavior: {response: ResponseBehavior.allowCrossOrigin()}, + }, + fetchOptions: {method: 'GET', mode: 'cors'}, + expected: FetchTestResult.SUCCESS, + }), + 'local to private: no preflight required.'); + + +subsetTestByKey( + 'from-local', promise_test, + t => fencedFrameFetchTest(t, { + source: {server: Server.HTTPS_LOCAL}, + target: { + server: Server.HTTPS_PUBLIC, + behavior: {response: ResponseBehavior.allowCrossOrigin()}, + }, + fetchOptions: {method: 'GET', mode: 'cors'}, + expected: FetchTestResult.SUCCESS, + }), + 'local to public: no preflight required.'); + +// Strictly speaking, the following two tests do not exercise PNA-specific +// logic, but they serve as a baseline for comparison, ensuring that non-PNA +// preflight requests are sent and handled as expected. + +subsetTestByKey( + 'baseline', promise_test, + t => fencedFrameFetchTest(t, { + source: {server: Server.HTTPS_LOCAL}, + target: { + server: Server.HTTPS_PUBLIC, + behavior: { + preflight: PreflightBehavior.failure(), + response: ResponseBehavior.allowCrossOrigin(), + }, + }, + fetchOptions: {method: 'PUT', mode: 'cors'}, + expected: FetchTestResult.FAILURE, + }), + 'local to public: PUT preflight failure.'); + +subsetTestByKey( + 'baseline', promise_test, + t => fencedFrameFetchTest(t, { + source: {server: Server.HTTPS_LOCAL}, + target: { + server: Server.HTTPS_PUBLIC, + behavior: { + preflight: PreflightBehavior.success(token()), + response: ResponseBehavior.allowCrossOrigin(), + } + }, + fetchOptions: {method: 'PUT', mode: 'cors'}, + expected: FetchTestResult.SUCCESS, + }), + 'local to public: PUT preflight success.'); + +// Generates tests of preflight behavior for a single (source, target) pair. +// +// Scenarios: +// +// - cors mode: +// - preflight response has non-2xx HTTP code +// - preflight response is missing CORS headers +// - preflight response is missing the PNA-specific `Access-Control` header +// - final response is missing CORS headers +// - success +// - success with PUT method (non-"simple" request) +// - no-cors mode: +// - preflight response has non-2xx HTTP code +// - preflight response is missing CORS headers +// - preflight response is missing the PNA-specific `Access-Control` header +// - success +// +function makePreflightTests({ + subsetKey, + source, + sourceDescription, + targetServer, + targetDescription, +}) { + const prefix = `${sourceDescription} to ${targetDescription}: `; + + subsetTestByKey( + subsetKey, promise_test, + t => fencedFrameFetchTest(t, { + source, + target: { + server: targetServer, + behavior: { + preflight: PreflightBehavior.failure(), + response: ResponseBehavior.allowCrossOrigin(), + }, + }, + fetchOptions: {method: 'GET', mode: 'cors'}, + expected: FetchTestResult.FAILURE, + }), + prefix + 'failed preflight.'); + + subsetTestByKey( + subsetKey, promise_test, + t => fencedFrameFetchTest(t, { + source, + target: { + server: targetServer, + behavior: { + preflight: PreflightBehavior.noCorsHeader(token()), + response: ResponseBehavior.allowCrossOrigin(), + }, + }, + fetchOptions: {method: 'GET', mode: 'cors'}, + expected: FetchTestResult.FAILURE, + }), + prefix + 'missing CORS headers on preflight response.'); + + subsetTestByKey( + subsetKey, promise_test, + t => fencedFrameFetchTest(t, { + source, + target: { + server: targetServer, + behavior: { + preflight: PreflightBehavior.noPnaHeader(token()), + response: ResponseBehavior.allowCrossOrigin(), + }, + }, + fetchOptions: {method: 'GET', mode: 'cors'}, + expected: FetchTestResult.FAILURE, + }), + prefix + 'missing PNA header on preflight response.'); + + subsetTestByKey( + subsetKey, promise_test, + t => fencedFrameFetchTest(t, { + source, + target: { + server: targetServer, + behavior: {preflight: PreflightBehavior.success(token())}, + }, + fetchOptions: {method: 'GET', mode: 'cors'}, + expected: FetchTestResult.FAILURE, + }), + prefix + 'missing CORS headers on final response.'); + + subsetTestByKey( + subsetKey, promise_test, + t => fencedFrameFetchTest(t, { + source, + target: { + server: targetServer, + behavior: { + preflight: PreflightBehavior.success(token()), + response: ResponseBehavior.allowCrossOrigin(), + }, + }, + fetchOptions: {method: 'GET', mode: 'cors'}, + expected: FetchTestResult.SUCCESS, + }), + prefix + 'success.'); + + subsetTestByKey( + subsetKey, promise_test, + t => fencedFrameFetchTest(t, { + source, + target: { + server: targetServer, + behavior: { + preflight: PreflightBehavior.success(token()), + response: ResponseBehavior.allowCrossOrigin(), + }, + }, + fetchOptions: {method: 'PUT', mode: 'cors'}, + expected: FetchTestResult.SUCCESS, + }), + prefix + 'PUT success.'); + + subsetTestByKey( + subsetKey, promise_test, t => fencedFrameFetchTest(t, { + source, + target: {server: targetServer}, + fetchOptions: {method: 'GET', mode: 'no-cors'}, + expected: FetchTestResult.FAILURE, + }), + prefix + 'no-CORS mode failed preflight.'); + + subsetTestByKey( + subsetKey, promise_test, + t => fencedFrameFetchTest(t, { + source, + target: { + server: targetServer, + behavior: {preflight: PreflightBehavior.noCorsHeader(token())}, + }, + fetchOptions: {method: 'GET', mode: 'no-cors'}, + expected: FetchTestResult.FAILURE, + }), + prefix + 'no-CORS mode missing CORS headers on preflight response.'); + + subsetTestByKey( + subsetKey, promise_test, + t => fencedFrameFetchTest(t, { + source, + target: { + server: targetServer, + behavior: {preflight: PreflightBehavior.noPnaHeader(token())}, + }, + fetchOptions: {method: 'GET', mode: 'no-cors'}, + expected: FetchTestResult.FAILURE, + }), + prefix + 'no-CORS mode missing PNA header on preflight response.'); + + subsetTestByKey( + subsetKey, promise_test, + t => fencedFrameFetchTest(t, { + source, + target: { + server: targetServer, + behavior: {preflight: PreflightBehavior.success(token())}, + }, + fetchOptions: {method: 'GET', mode: 'no-cors'}, + expected: FetchTestResult.OPAQUE, + }), + prefix + 'no-CORS mode success.'); +} + +// Source: private secure context. +// +// Fetches to the local address space require a successful preflight response +// carrying a PNA-specific header. + +makePreflightTests({ + subsetKey: 'from-private', + source: {server: Server.HTTPS_PRIVATE}, + sourceDescription: 'private', + targetServer: Server.HTTPS_LOCAL, + targetDescription: 'local', +}); + +subsetTestByKey( + 'from-private', promise_test, t => fencedFrameFetchTest(t, { + source: {server: Server.HTTPS_PRIVATE}, + target: {server: Server.HTTPS_PRIVATE}, + fetchOptions: {method: 'GET', mode: 'cors'}, + expected: FetchTestResult.SUCCESS, + }), + 'private to private: no preflight required.'); + +subsetTestByKey( + 'from-private', promise_test, + t => fencedFrameFetchTest(t, { + source: {server: Server.HTTPS_PRIVATE}, + target: { + server: Server.HTTPS_PRIVATE, + behavior: {response: ResponseBehavior.allowCrossOrigin()}, + }, + fetchOptions: {method: 'GET', mode: 'cors'}, + expected: FetchTestResult.SUCCESS, + }), + 'private to public: no preflight required.'); + +// Source: public secure context. +// +// Fetches to the local and private address spaces require a successful +// preflight response carrying a PNA-specific header. + +makePreflightTests({ + subsetKey: 'from-public', + source: {server: Server.HTTPS_PUBLIC}, + sourceDescription: 'public', + targetServer: Server.HTTPS_LOCAL, + targetDescription: 'local', +}); + +makePreflightTests({ + subsetKey: 'from-public', + source: {server: Server.HTTPS_PUBLIC}, + sourceDescription: 'public', + targetServer: Server.HTTPS_PRIVATE, + targetDescription: 'private', +}); + +subsetTestByKey( + 'from-public', promise_test, t => fencedFrameFetchTest(t, { + source: {server: Server.HTTPS_PUBLIC}, + target: {server: Server.HTTPS_PUBLIC}, + fetchOptions: {method: 'GET', mode: 'cors'}, + expected: FetchTestResult.SUCCESS, + }), + 'public to public: no preflight required.'); diff --git a/test/wpt/tests/fetch/private-network-access/fenced-frame.tentative.https.window.js b/test/wpt/tests/fetch/private-network-access/fenced-frame.tentative.https.window.js new file mode 100644 index 00000000000..370cc9fbe9d --- /dev/null +++ b/test/wpt/tests/fetch/private-network-access/fenced-frame.tentative.https.window.js @@ -0,0 +1,150 @@ +// META: script=/common/dispatcher/dispatcher.js +// META: script=/common/utils.js +// META: script=resources/support.sub.js +// META: script=/fenced-frame/resources/utils.js +// META: timeout=long +// +// Spec: https://wicg.github.io/private-network-access/#integration-fetch +// +// These tests verify that contexts can navigate fenced frames to less-public +// address spaces iff the target server responds affirmatively to preflight +// requests. + +setup(() => { + assert_true(window.isSecureContext); +}); + +// Generates tests of preflight behavior for a single (source, target) pair. +// +// Scenarios: +// +// - parent navigates child: +// - preflight response has non-2xx HTTP code +// - preflight response is missing CORS headers +// - preflight response is missing the PNA-specific `Access-Control` header +// - preflight response has the required PNA related headers, but still fails +// because of the limitation of fenced frame that subjects to PNA checks. +// +function makePreflightTests({ + sourceName, + sourceServer, + sourceTreatAsPublic, + targetName, + targetServer, +}) { + const prefix = `${sourceName} to ${targetName}: `; + + const source = { + server: sourceServer, + treatAsPublic: sourceTreatAsPublic, + }; + + promise_test_parallel( + t => fencedFrameTest(t, { + source, + target: { + server: targetServer, + behavior: {preflight: PreflightBehavior.failure()}, + }, + expected: FrameTestResult.FAILURE, + }), + prefix + 'failed preflight.'); + + promise_test_parallel( + t => fencedFrameTest(t, { + source, + target: { + server: targetServer, + behavior: {preflight: PreflightBehavior.noCorsHeader(token())}, + }, + expected: FrameTestResult.FAILURE, + }), + prefix + 'missing CORS headers.'); + + promise_test_parallel( + t => fencedFrameTest(t, { + source, + target: { + server: targetServer, + behavior: {preflight: PreflightBehavior.noPnaHeader(token())}, + }, + expected: FrameTestResult.FAILURE, + }), + prefix + 'missing PNA header.'); + + promise_test_parallel( + t => fencedFrameTest(t, { + source, + target: { + server: targetServer, + behavior: { + preflight: PreflightBehavior.success(token()), + response: ResponseBehavior.allowCrossOrigin() + }, + }, + expected: FrameTestResult.FAILURE, + }), + prefix + 'failed because fenced frames are incompatible with PNA.'); +} + +// Source: private secure context. +// +// Fetches to the local address space require a successful preflight response +// carrying a PNA-specific header. + +makePreflightTests({ + sourceServer: Server.HTTPS_PRIVATE, + sourceName: 'private', + targetServer: Server.HTTPS_LOCAL, + targetName: 'local', +}); + +// Source: public secure context. +// +// Fetches to the local and private address spaces require a successful +// preflight response carrying a PNA-specific header. + +makePreflightTests({ + sourceServer: Server.HTTPS_PUBLIC, + sourceName: 'public', + targetServer: Server.HTTPS_LOCAL, + targetName: 'local', +}); + +makePreflightTests({ + sourceServer: Server.HTTPS_PUBLIC, + sourceName: 'public', + targetServer: Server.HTTPS_PRIVATE, + targetName: 'private', +}); + +// The following tests verify that `CSP: treat-as-public-address` makes +// documents behave as if they had been served from a public IP address. + +makePreflightTests({ + sourceServer: Server.HTTPS_LOCAL, + sourceTreatAsPublic: true, + sourceName: 'treat-as-public-address', + targetServer: Server.OTHER_HTTPS_LOCAL, + targetName: 'local', +}); + +promise_test_parallel( + t => fencedFrameTest(t, { + source: { + server: Server.HTTPS_LOCAL, + treatAsPublic: true, + }, + target: {server: Server.HTTPS_LOCAL}, + expected: FrameTestResult.FAILURE, + }), + 'treat-as-public-address to local (same-origin): fenced frame embedder ' + + 'initiated navigation has opaque origin.'); + +makePreflightTests({ + sourceServer: Server.HTTPS_LOCAL, + sourceTreatAsPublic: true, + sourceName: 'treat-as-public-address', + targetServer: Server.HTTPS_PRIVATE, + targetName: 'private', +}); diff --git a/test/wpt/tests/fetch/local-network-access/fetch-from-treat-as-public.https.window.js b/test/wpt/tests/fetch/private-network-access/fetch-from-treat-as-public.tentative.https.window.js similarity index 100% rename from test/wpt/tests/fetch/local-network-access/fetch-from-treat-as-public.https.window.js rename to test/wpt/tests/fetch/private-network-access/fetch-from-treat-as-public.tentative.https.window.js diff --git a/test/wpt/tests/fetch/local-network-access/fetch.https.window.js b/test/wpt/tests/fetch/private-network-access/fetch.tentative.https.window.js similarity index 100% rename from test/wpt/tests/fetch/local-network-access/fetch.https.window.js rename to test/wpt/tests/fetch/private-network-access/fetch.tentative.https.window.js diff --git a/test/wpt/tests/fetch/local-network-access/fetch.window.js b/test/wpt/tests/fetch/private-network-access/fetch.tentative.window.js similarity index 100% rename from test/wpt/tests/fetch/local-network-access/fetch.window.js rename to test/wpt/tests/fetch/private-network-access/fetch.tentative.window.js diff --git a/test/wpt/tests/fetch/local-network-access/iframe.tentative.https.window.js b/test/wpt/tests/fetch/private-network-access/iframe.tentative.https.window.js similarity index 63% rename from test/wpt/tests/fetch/local-network-access/iframe.tentative.https.window.js rename to test/wpt/tests/fetch/private-network-access/iframe.tentative.https.window.js index 6a83b88d3ff..0c12970557d 100644 --- a/test/wpt/tests/fetch/local-network-access/iframe.tentative.https.window.js +++ b/test/wpt/tests/fetch/private-network-access/iframe.tentative.https.window.js @@ -1,6 +1,13 @@ +// META: script=/common/subset-tests-by-key.js // META: script=/common/dispatcher/dispatcher.js // META: script=/common/utils.js // META: script=resources/support.sub.js +// META: timeout=long +// META: variant=?include=from-local +// META: variant=?include=from-private +// META: variant=?include=from-public +// META: variant=?include=from-treat-as-public +// META: variant=?include=grandparent // // Spec: https://wicg.github.io/private-network-access/#integration-fetch // @@ -18,22 +25,22 @@ setup(() => { // // All fetches unaffected by Private Network Access. -promise_test_parallel(t => iframeTest(t, { +subsetTestByKey("from-local", promise_test_parallel, t => iframeTest(t, { source: { server: Server.HTTPS_LOCAL }, target: { server: Server.HTTPS_LOCAL }, - expected: IframeTestResult.SUCCESS, + expected: FrameTestResult.SUCCESS, }), "local to local: no preflight required."); -promise_test_parallel(t => iframeTest(t, { +subsetTestByKey("from-local", promise_test_parallel, t => iframeTest(t, { source: { server: Server.HTTPS_LOCAL }, target: { server: Server.HTTPS_PRIVATE }, - expected: IframeTestResult.SUCCESS, + expected: FrameTestResult.SUCCESS, }), "local to private: no preflight required."); -promise_test_parallel(t => iframeTest(t, { +subsetTestByKey("from-local", promise_test_parallel, t => iframeTest(t, { source: { server: Server.HTTPS_LOCAL }, target: { server: Server.HTTPS_PUBLIC }, - expected: IframeTestResult.SUCCESS, + expected: FrameTestResult.SUCCESS, }), "local to public: no preflight required."); // Generates tests of preflight behavior for a single (source, target) pair. @@ -47,6 +54,7 @@ promise_test_parallel(t => iframeTest(t, { // - success // function makePreflightTests({ + key, sourceName, sourceServer, sourceTreatAsPublic, @@ -67,7 +75,7 @@ function makePreflightTests({ server: targetServer, behavior: { preflight: PreflightBehavior.failure() }, }, - expected: IframeTestResult.FAILURE, + expected: FrameTestResult.FAILURE, }), prefix + "failed preflight."); promise_test_parallel(t => iframeTest(t, { @@ -76,7 +84,7 @@ function makePreflightTests({ server: targetServer, behavior: { preflight: PreflightBehavior.noCorsHeader(token()) }, }, - expected: IframeTestResult.FAILURE, + expected: FrameTestResult.FAILURE, }), prefix + "missing CORS headers."); promise_test_parallel(t => iframeTest(t, { @@ -85,7 +93,7 @@ function makePreflightTests({ server: targetServer, behavior: { preflight: PreflightBehavior.noPnaHeader(token()) }, }, - expected: IframeTestResult.FAILURE, + expected: FrameTestResult.FAILURE, }), prefix + "missing PNA header."); promise_test_parallel(t => iframeTest(t, { @@ -94,7 +102,7 @@ function makePreflightTests({ server: targetServer, behavior: { preflight: PreflightBehavior.success(token()) }, }, - expected: IframeTestResult.SUCCESS, + expected: FrameTestResult.SUCCESS, }), prefix + "success."); } @@ -103,23 +111,23 @@ function makePreflightTests({ // Fetches to the local address space require a successful preflight response // carrying a PNA-specific header. -makePreflightTests({ +subsetTestByKey('from-private', makePreflightTests, { sourceServer: Server.HTTPS_PRIVATE, - sourceName: "private", + sourceName: 'private', targetServer: Server.HTTPS_LOCAL, - targetName: "local", + targetName: 'local', }); -promise_test_parallel(t => iframeTest(t, { +subsetTestByKey("from-private", promise_test_parallel, t => iframeTest(t, { source: { server: Server.HTTPS_PRIVATE }, target: { server: Server.HTTPS_PRIVATE }, - expected: IframeTestResult.SUCCESS, + expected: FrameTestResult.SUCCESS, }), "private to private: no preflight required."); -promise_test_parallel(t => iframeTest(t, { +subsetTestByKey("from-private", promise_test_parallel, t => iframeTest(t, { source: { server: Server.HTTPS_PRIVATE }, target: { server: Server.HTTPS_PUBLIC }, - expected: IframeTestResult.SUCCESS, + expected: FrameTestResult.SUCCESS, }), "private to public: no preflight required."); // Source: public secure context. @@ -127,30 +135,30 @@ promise_test_parallel(t => iframeTest(t, { // Fetches to the local and private address spaces require a successful // preflight response carrying a PNA-specific header. -makePreflightTests({ +subsetTestByKey('from-public', makePreflightTests, { sourceServer: Server.HTTPS_PUBLIC, sourceName: "public", targetServer: Server.HTTPS_LOCAL, targetName: "local", }); -makePreflightTests({ +subsetTestByKey('from-public', makePreflightTests, { sourceServer: Server.HTTPS_PUBLIC, sourceName: "public", targetServer: Server.HTTPS_PRIVATE, targetName: "private", }); -promise_test_parallel(t => iframeTest(t, { +subsetTestByKey("from-public", promise_test_parallel, t => iframeTest(t, { source: { server: Server.HTTPS_PUBLIC }, target: { server: Server.HTTPS_PUBLIC }, - expected: IframeTestResult.SUCCESS, + expected: FrameTestResult.SUCCESS, }), "public to public: no preflight required."); // The following tests verify that `CSP: treat-as-public-address` makes // documents behave as if they had been served from a public IP address. -makePreflightTests({ +subsetTestByKey('from-treat-as-public', makePreflightTests, { sourceServer: Server.HTTPS_LOCAL, sourceTreatAsPublic: true, sourceName: "treat-as-public-address", @@ -158,16 +166,20 @@ makePreflightTests({ targetName: "local", }); -promise_test_parallel(t => iframeTest(t, { - source: { - server: Server.HTTPS_LOCAL, - treatAsPublic: true, - }, - target: { server: Server.HTTPS_LOCAL }, - expected: IframeTestResult.SUCCESS, -}), "treat-as-public-address to local (same-origin): no preflight required."); +subsetTestByKey( + 'from-treat-as-public', promise_test_parallel, + t => iframeTest(t, { + source: { + server: Server.HTTPS_LOCAL, + treatAsPublic: true, + }, + target: {server: Server.HTTPS_LOCAL}, + expected: FrameTestResult.SUCCESS, + }), + 'treat-as-public-address to local (same-origin): no preflight required.' +); -makePreflightTests({ +subsetTestByKey('from-treat-as-public', makePreflightTests, { sourceServer: Server.HTTPS_LOCAL, sourceTreatAsPublic: true, sourceName: "treat-as-public-address", @@ -175,49 +187,57 @@ makePreflightTests({ targetName: "private", }); -promise_test_parallel(t => iframeTest(t, { - source: { - server: Server.HTTPS_LOCAL, - treatAsPublic: true, - }, - target: { server: Server.HTTPS_PUBLIC }, - expected: IframeTestResult.SUCCESS, -}), "treat-as-public-address to public: no preflight required."); +subsetTestByKey( + 'from-treat-as-public', promise_test_parallel, + t => iframeTest(t, { + source: { + server: Server.HTTPS_LOCAL, + treatAsPublic: true, + }, + target: {server: Server.HTTPS_PUBLIC}, + expected: FrameTestResult.SUCCESS, + }), + 'treat-as-public-address to public: no preflight required.' +); -promise_test_parallel(t => iframeTest(t, { - source: { - server: Server.HTTPS_LOCAL, - treatAsPublic: true, - }, - target: { - server: Server.HTTPS_PUBLIC, - behavior: { preflight: PreflightBehavior.optionalSuccess(token()) } - }, - expected: IframeTestResult.SUCCESS, -}), "treat-as-public-address to local: optional preflight"); +subsetTestByKey( + 'from-treat-as-public', promise_test_parallel, + t => iframeTest(t, { + source: { + server: Server.HTTPS_LOCAL, + treatAsPublic: true, + }, + target: { + server: Server.HTTPS_PUBLIC, + behavior: {preflight: PreflightBehavior.optionalSuccess(token())} + }, + expected: FrameTestResult.SUCCESS, + }), + 'treat-as-public-address to local: optional preflight' +); // The following tests verify that when a grandparent frame navigates its // grandchild, the IP address space of the grandparent is compared against the // IP address space of the response. Indeed, the navigation initiator in this // case is the grandparent, not the parent. -iframeGrandparentTest({ - name: "local to local, grandparent navigates: no preflight required.", +subsetTestByKey('grandparent', iframeGrandparentTest, { + name: 'local to local, grandparent navigates: no preflight required.', grandparentServer: Server.HTTPS_LOCAL, - child: { server: Server.HTTPS_PUBLIC }, - grandchild: { server: Server.OTHER_HTTPS_LOCAL }, - expected: IframeTestResult.SUCCESS, + child: {server: Server.HTTPS_PUBLIC}, + grandchild: {server: Server.OTHER_HTTPS_LOCAL}, + expected: FrameTestResult.SUCCESS, }); -iframeGrandparentTest({ +subsetTestByKey('grandparent', iframeGrandparentTest, { name: "local to local (same-origin), grandparent navigates: no preflight required.", grandparentServer: Server.HTTPS_LOCAL, child: { server: Server.HTTPS_PUBLIC }, grandchild: { server: Server.HTTPS_LOCAL }, - expected: IframeTestResult.SUCCESS, + expected: FrameTestResult.SUCCESS, }); -iframeGrandparentTest({ +subsetTestByKey('grandparent', iframeGrandparentTest, { name: "public to local, grandparent navigates: failure.", grandparentServer: Server.HTTPS_PUBLIC, child: { @@ -228,10 +248,10 @@ iframeGrandparentTest({ server: Server.HTTPS_LOCAL, behavior: { preflight: PreflightBehavior.failure() }, }, - expected: IframeTestResult.FAILURE, + expected: FrameTestResult.FAILURE, }); -iframeGrandparentTest({ +subsetTestByKey('grandparent', iframeGrandparentTest, { name: "public to local, grandparent navigates: success.", grandparentServer: Server.HTTPS_PUBLIC, child: { @@ -242,5 +262,5 @@ iframeGrandparentTest({ server: Server.HTTPS_LOCAL, behavior: { preflight: PreflightBehavior.success(token()) }, }, - expected: IframeTestResult.SUCCESS, + expected: FrameTestResult.SUCCESS, }); diff --git a/test/wpt/tests/fetch/local-network-access/iframe.tentative.window.js b/test/wpt/tests/fetch/private-network-access/iframe.tentative.window.js similarity index 86% rename from test/wpt/tests/fetch/local-network-access/iframe.tentative.window.js rename to test/wpt/tests/fetch/private-network-access/iframe.tentative.window.js index e00cb202bec..c0770df8385 100644 --- a/test/wpt/tests/fetch/local-network-access/iframe.tentative.window.js +++ b/test/wpt/tests/fetch/private-network-access/iframe.tentative.window.js @@ -18,55 +18,55 @@ setup(() => { promise_test_parallel(t => iframeTest(t, { source: { server: Server.HTTP_LOCAL }, target: { server: Server.HTTP_LOCAL }, - expected: IframeTestResult.SUCCESS, + expected: FrameTestResult.SUCCESS, }), "local to local: no preflight required."); promise_test_parallel(t => iframeTest(t, { source: { server: Server.HTTP_LOCAL }, target: { server: Server.HTTP_PRIVATE }, - expected: IframeTestResult.SUCCESS, + expected: FrameTestResult.SUCCESS, }), "local to private: no preflight required."); promise_test_parallel(t => iframeTest(t, { source: { server: Server.HTTP_LOCAL }, target: { server: Server.HTTP_PUBLIC }, - expected: IframeTestResult.SUCCESS, + expected: FrameTestResult.SUCCESS, }), "local to public: no preflight required."); promise_test_parallel(t => iframeTest(t, { source: { server: Server.HTTP_PRIVATE }, target: { server: Server.HTTP_LOCAL }, - expected: IframeTestResult.FAILURE, + expected: FrameTestResult.FAILURE, }), "private to local: failure."); promise_test_parallel(t => iframeTest(t, { source: { server: Server.HTTP_PRIVATE }, target: { server: Server.HTTP_PRIVATE }, - expected: IframeTestResult.SUCCESS, + expected: FrameTestResult.SUCCESS, }), "private to private: no preflight required."); promise_test_parallel(t => iframeTest(t, { source: { server: Server.HTTP_PRIVATE }, target: { server: Server.HTTP_PUBLIC }, - expected: IframeTestResult.SUCCESS, + expected: FrameTestResult.SUCCESS, }), "private to public: no preflight required."); promise_test_parallel(t => iframeTest(t, { source: { server: Server.HTTP_PUBLIC }, target: { server: Server.HTTP_LOCAL }, - expected: IframeTestResult.FAILURE, + expected: FrameTestResult.FAILURE, }), "public to local: failure."); promise_test_parallel(t => iframeTest(t, { source: { server: Server.HTTP_PUBLIC }, target: { server: Server.HTTP_PRIVATE }, - expected: IframeTestResult.FAILURE, + expected: FrameTestResult.FAILURE, }), "public to private: failure."); promise_test_parallel(t => iframeTest(t, { source: { server: Server.HTTP_PUBLIC }, target: { server: Server.HTTP_PUBLIC }, - expected: IframeTestResult.SUCCESS, + expected: FrameTestResult.SUCCESS, }), "public to public: no preflight required."); promise_test_parallel(t => iframeTest(t, { @@ -75,7 +75,7 @@ promise_test_parallel(t => iframeTest(t, { treatAsPublic: true, }, target: { server: Server.HTTP_LOCAL }, - expected: IframeTestResult.FAILURE, + expected: FrameTestResult.FAILURE, }), "treat-as-public-address to local: failure."); promise_test_parallel(t => iframeTest(t, { @@ -84,7 +84,7 @@ promise_test_parallel(t => iframeTest(t, { treatAsPublic: true, }, target: { server: Server.HTTP_PRIVATE }, - expected: IframeTestResult.FAILURE, + expected: FrameTestResult.FAILURE, }), "treat-as-public-address to private: failure."); promise_test_parallel(t => iframeTest(t, { @@ -93,7 +93,7 @@ promise_test_parallel(t => iframeTest(t, { treatAsPublic: true, }, target: { server: Server.HTTP_PUBLIC }, - expected: IframeTestResult.SUCCESS, + expected: FrameTestResult.SUCCESS, }), "treat-as-public-address to public: no preflight required."); // The following test verifies that when a grandparent frame navigates its @@ -106,5 +106,5 @@ iframeGrandparentTest({ grandparentServer: Server.HTTP_LOCAL, child: { server: Server.HTTP_PUBLIC }, grandchild: { server: Server.HTTP_LOCAL }, - expected: IframeTestResult.SUCCESS, + expected: FrameTestResult.SUCCESS, }); diff --git a/test/wpt/tests/fetch/local-network-access/mixed-content-fetch.tentative.https.window.js b/test/wpt/tests/fetch/private-network-access/mixed-content-fetch.tentative.https.window.js similarity index 77% rename from test/wpt/tests/fetch/local-network-access/mixed-content-fetch.tentative.https.window.js rename to test/wpt/tests/fetch/private-network-access/mixed-content-fetch.tentative.https.window.js index 6f7d765617c..54485dc7047 100644 --- a/test/wpt/tests/fetch/local-network-access/mixed-content-fetch.tentative.https.window.js +++ b/test/wpt/tests/fetch/private-network-access/mixed-content-fetch.tentative.https.window.js @@ -1,10 +1,10 @@ // META: script=/common/utils.js // META: script=resources/support.sub.js // -// Spec: https://wicg.github.io/local-network-access +// Spec: https://wicg.github.io/private-network-access // // These tests verify that secure contexts can fetch non-secure subresources -// from more local address spaces, avoiding mixed context checks, as long as +// from more private address spaces, avoiding mixed context checks, as long as // they specify a valid `targetAddressSpace` fetch option that matches the // target server's address space. @@ -16,9 +16,9 @@ setup(() => { // Given `addressSpace`, returns the other three possible IP address spaces. function otherAddressSpaces(addressSpace) { switch (addressSpace) { - case "loopback": return ["unknown", "local", "public"]; - case "local": return ["unknown", "loopback", "public"]; - case "public": return ["unknown", "loopback", "local"]; + case "local": return ["unknown", "private", "public"]; + case "private": return ["unknown", "local", "public"]; + case "public": return ["unknown", "local", "private"]; } } @@ -169,66 +169,64 @@ function makeNoBypassTests({ source, target }) { }, fetchOptions: { targetAddressSpace: correctAddressSpace }, expected: FetchTestResult.FAILURE, - }), prefix + 'not a local network request.'); + }), prefix + 'not a private network request.'); } -// Source: loopback secure context. +// Source: local secure context. // -// Fetches to the loopback and local address spaces cannot use +// Fetches to the local and private address spaces cannot use // `targetAddressSpace` to bypass mixed content, as they are not otherwise -// blocked by Local Network Access. +// blocked by Private Network Access. -makeNoBypassTests({ source: "loopback", target: "loopback" }); -makeNoBypassTests({ source: "loopback", target: "local" }); -makeNoBypassTests({ source: "loopback", target: "public" }); +makeNoBypassTests({ source: "local", target: "local" }); +makeNoBypassTests({ source: "local", target: "private" }); +makeNoBypassTests({ source: "local", target: "public" }); -// Source: local secure context. +// Source: private secure context. // -// Fetches to the loopback address space requires the right `targetAddressSpace` +// Fetches to the local address space requires the right `targetAddressSpace` // option, as well as a successful preflight response carrying a PNA-specific // header. // -// Fetches to the local address space cannot use `targetAddressSpace` to -// bypass mixed content, as they are not otherwise blocked by Local Network +// Fetches to the private address space cannot use `targetAddressSpace` to +// bypass mixed content, as they are not otherwise blocked by Private Network // Access. -makeTests({ source: "local", target: "loopback" }); +makeTests({ source: "private", target: "local" }); -makeNoBypassTests({ source: "local", target: "local" }); -makeNoBypassTests({ source: "local", target: "public" }); +makeNoBypassTests({ source: "private", target: "private" }); +makeNoBypassTests({ source: "private", target: "public" }); // Source: public secure context. // -// Fetches to the loopback and local address spaces require the right +// Fetches to the local and private address spaces require the right // `targetAddressSpace` option, as well as a successful preflight response // carrying a PNA-specific header. -makeTests({ source: "public", target: "loopback" }); makeTests({ source: "public", target: "local" }); +makeTests({ source: "public", target: "private" }); makeNoBypassTests({ source: "public", target: "public" }); -// These tests verify that documents fetched from the `loopback` address space -// yet carrying the `treat-as-public-address` CSP directive are treated as if -// they had been fetched from the `public` address space. +// These tests verify that documents fetched from the `local` address space yet +// carrying the `treat-as-public-address` CSP directive are treated as if they +// had been fetched from the `public` address space. -promise_test_parallel( - t => fetchTest(t, { - source: { - server: Server.HTTPS_LOCAL, - treatAsPublic: true, - }, - target: { - server: Server.HTTP_LOCAL, - behavior: { - preflight: PreflightBehavior.optionalSuccess(token()), - response: ResponseBehavior.allowCrossOrigin(), - }, - }, - fetchOptions: {targetAddressSpace: 'local'}, - expected: FetchTestResult.FAILURE, - }), - 'https-treat-as-public to http-loopback: wrong targetAddressSpace "local".'); +promise_test_parallel(t => fetchTest(t, { + source: { + server: Server.HTTPS_LOCAL, + treatAsPublic: true, + }, + target: { + server: Server.HTTP_LOCAL, + behavior: { + preflight: PreflightBehavior.optionalSuccess(token()), + response: ResponseBehavior.allowCrossOrigin(), + }, + }, + fetchOptions: { targetAddressSpace: "private" }, + expected: FetchTestResult.FAILURE, +}), 'https-treat-as-public to http-local: wrong targetAddressSpace "private".'); promise_test_parallel(t => fetchTest(t, { source: { @@ -242,9 +240,9 @@ promise_test_parallel(t => fetchTest(t, { response: ResponseBehavior.allowCrossOrigin(), }, }, - fetchOptions: { targetAddressSpace: "loopback" }, + fetchOptions: { targetAddressSpace: "local" }, expected: FetchTestResult.SUCCESS, -}), "https-treat-as-public to http-loopback: success."); +}), "https-treat-as-public to http-local: success."); promise_test_parallel(t => fetchTest(t, { source: { @@ -258,9 +256,9 @@ promise_test_parallel(t => fetchTest(t, { response: ResponseBehavior.allowCrossOrigin(), }, }, - fetchOptions: { targetAddressSpace: "loopback" }, + fetchOptions: { targetAddressSpace: "local" }, expected: FetchTestResult.FAILURE, -}), 'https-treat-as-public to http-local: wrong targetAddressSpace "loopback".'); +}), 'https-treat-as-public to http-private: wrong targetAddressSpace "local".'); promise_test_parallel(t => fetchTest(t, { source: { @@ -274,6 +272,6 @@ promise_test_parallel(t => fetchTest(t, { response: ResponseBehavior.allowCrossOrigin(), }, }, - fetchOptions: { targetAddressSpace: "local" }, + fetchOptions: { targetAddressSpace: "private" }, expected: FetchTestResult.SUCCESS, -}), "https-treat-as-public to http-local: success."); +}), "https-treat-as-public to http-private: success."); diff --git a/test/wpt/tests/fetch/local-network-access/nested-worker.https.window.js b/test/wpt/tests/fetch/private-network-access/nested-worker.tentative.https.window.js similarity index 100% rename from test/wpt/tests/fetch/local-network-access/nested-worker.https.window.js rename to test/wpt/tests/fetch/private-network-access/nested-worker.tentative.https.window.js diff --git a/test/wpt/tests/fetch/local-network-access/nested-worker.window.js b/test/wpt/tests/fetch/private-network-access/nested-worker.tentative.window.js similarity index 100% rename from test/wpt/tests/fetch/local-network-access/nested-worker.window.js rename to test/wpt/tests/fetch/private-network-access/nested-worker.tentative.window.js diff --git a/test/wpt/tests/fetch/local-network-access/preflight-cache.https.window.js b/test/wpt/tests/fetch/private-network-access/preflight-cache.https.tentative.window.js similarity index 100% rename from test/wpt/tests/fetch/local-network-access/preflight-cache.https.window.js rename to test/wpt/tests/fetch/private-network-access/preflight-cache.https.tentative.window.js diff --git a/test/wpt/tests/fetch/local-network-access/redirect.https.window.js b/test/wpt/tests/fetch/private-network-access/redirect.tentative.https.window.js similarity index 100% rename from test/wpt/tests/fetch/local-network-access/redirect.https.window.js rename to test/wpt/tests/fetch/private-network-access/redirect.tentative.https.window.js diff --git a/test/wpt/tests/fetch/local-network-access/resources/executor.html b/test/wpt/tests/fetch/private-network-access/resources/executor.html similarity index 100% rename from test/wpt/tests/fetch/local-network-access/resources/executor.html rename to test/wpt/tests/fetch/private-network-access/resources/executor.html diff --git a/test/wpt/tests/fetch/private-network-access/resources/fenced-frame-fetcher.https.html b/test/wpt/tests/fetch/private-network-access/resources/fenced-frame-fetcher.https.html new file mode 100644 index 00000000000..b14601dba51 --- /dev/null +++ b/test/wpt/tests/fetch/private-network-access/resources/fenced-frame-fetcher.https.html @@ -0,0 +1,25 @@ + + + +Fetcher + \ No newline at end of file diff --git a/test/wpt/tests/fetch/private-network-access/resources/fenced-frame-fetcher.https.html.headers b/test/wpt/tests/fetch/private-network-access/resources/fenced-frame-fetcher.https.html.headers new file mode 100644 index 00000000000..6247f6d6321 --- /dev/null +++ b/test/wpt/tests/fetch/private-network-access/resources/fenced-frame-fetcher.https.html.headers @@ -0,0 +1 @@ +Supports-Loading-Mode: fenced-frame \ No newline at end of file diff --git a/test/wpt/tests/fetch/private-network-access/resources/fenced-frame-local-network-access-target.https.html b/test/wpt/tests/fetch/private-network-access/resources/fenced-frame-local-network-access-target.https.html new file mode 100644 index 00000000000..2b55e056f39 --- /dev/null +++ b/test/wpt/tests/fetch/private-network-access/resources/fenced-frame-local-network-access-target.https.html @@ -0,0 +1,8 @@ + + + +Fenced frame target + diff --git a/test/wpt/tests/fetch/private-network-access/resources/fenced-frame-local-network-access.https.html b/test/wpt/tests/fetch/private-network-access/resources/fenced-frame-local-network-access.https.html new file mode 100644 index 00000000000..98f118432e0 --- /dev/null +++ b/test/wpt/tests/fetch/private-network-access/resources/fenced-frame-local-network-access.https.html @@ -0,0 +1,14 @@ + + + + + +Fenced frame + + diff --git a/test/wpt/tests/fetch/private-network-access/resources/fenced-frame-local-network-access.https.html.headers b/test/wpt/tests/fetch/private-network-access/resources/fenced-frame-local-network-access.https.html.headers new file mode 100644 index 00000000000..6247f6d6321 --- /dev/null +++ b/test/wpt/tests/fetch/private-network-access/resources/fenced-frame-local-network-access.https.html.headers @@ -0,0 +1 @@ +Supports-Loading-Mode: fenced-frame \ No newline at end of file diff --git a/test/wpt/tests/fetch/local-network-access/resources/fetcher.html b/test/wpt/tests/fetch/private-network-access/resources/fetcher.html similarity index 100% rename from test/wpt/tests/fetch/local-network-access/resources/fetcher.html rename to test/wpt/tests/fetch/private-network-access/resources/fetcher.html diff --git a/test/wpt/tests/fetch/local-network-access/resources/fetcher.js b/test/wpt/tests/fetch/private-network-access/resources/fetcher.js similarity index 100% rename from test/wpt/tests/fetch/local-network-access/resources/fetcher.js rename to test/wpt/tests/fetch/private-network-access/resources/fetcher.js diff --git a/test/wpt/tests/fetch/local-network-access/resources/iframed.html b/test/wpt/tests/fetch/private-network-access/resources/iframed.html similarity index 100% rename from test/wpt/tests/fetch/local-network-access/resources/iframed.html rename to test/wpt/tests/fetch/private-network-access/resources/iframed.html diff --git a/test/wpt/tests/fetch/local-network-access/resources/iframer.html b/test/wpt/tests/fetch/private-network-access/resources/iframer.html similarity index 100% rename from test/wpt/tests/fetch/local-network-access/resources/iframer.html rename to test/wpt/tests/fetch/private-network-access/resources/iframer.html diff --git a/test/wpt/tests/fetch/local-network-access/resources/preflight.py b/test/wpt/tests/fetch/private-network-access/resources/preflight.py similarity index 97% rename from test/wpt/tests/fetch/local-network-access/resources/preflight.py rename to test/wpt/tests/fetch/private-network-access/resources/preflight.py index 4b0bfefd4d6..be3abdbb2a1 100644 --- a/test/wpt/tests/fetch/local-network-access/resources/preflight.py +++ b/test/wpt/tests/fetch/private-network-access/resources/preflight.py @@ -85,6 +85,9 @@ def _is_preflight_optional(request): def _get_preflight_uuid(request): return request.GET.get(b"preflight-uuid") +def _is_loaded_in_fenced_frame(request): + return request.GET.get(b"is-loaded-in-fenced-frame") + def _should_treat_as_public_once(request): uuid = request.GET.get(b"treat-as-public-once") if uuid is None: @@ -155,6 +158,9 @@ def _handle_final_request(request, response): if mime_type is not None: headers.append(("Content-Type", mime_type),) + if _is_loaded_in_fenced_frame(request): + headers.append(("Supports-Loading-Mode", "fenced-frame")) + body = _final_response_body(request) return (headers, body) diff --git a/test/wpt/tests/fetch/local-network-access/resources/service-worker-bridge.html b/test/wpt/tests/fetch/private-network-access/resources/service-worker-bridge.html similarity index 100% rename from test/wpt/tests/fetch/local-network-access/resources/service-worker-bridge.html rename to test/wpt/tests/fetch/private-network-access/resources/service-worker-bridge.html diff --git a/test/wpt/tests/fetch/local-network-access/resources/service-worker.js b/test/wpt/tests/fetch/private-network-access/resources/service-worker.js similarity index 100% rename from test/wpt/tests/fetch/local-network-access/resources/service-worker.js rename to test/wpt/tests/fetch/private-network-access/resources/service-worker.js diff --git a/test/wpt/tests/fetch/local-network-access/resources/shared-fetcher.js b/test/wpt/tests/fetch/private-network-access/resources/shared-fetcher.js similarity index 100% rename from test/wpt/tests/fetch/local-network-access/resources/shared-fetcher.js rename to test/wpt/tests/fetch/private-network-access/resources/shared-fetcher.js diff --git a/test/wpt/tests/fetch/local-network-access/resources/shared-worker-blob-fetcher.html b/test/wpt/tests/fetch/private-network-access/resources/shared-worker-blob-fetcher.html similarity index 100% rename from test/wpt/tests/fetch/local-network-access/resources/shared-worker-blob-fetcher.html rename to test/wpt/tests/fetch/private-network-access/resources/shared-worker-blob-fetcher.html diff --git a/test/wpt/tests/fetch/local-network-access/resources/shared-worker-fetcher.html b/test/wpt/tests/fetch/private-network-access/resources/shared-worker-fetcher.html similarity index 100% rename from test/wpt/tests/fetch/local-network-access/resources/shared-worker-fetcher.html rename to test/wpt/tests/fetch/private-network-access/resources/shared-worker-fetcher.html diff --git a/test/wpt/tests/fetch/local-network-access/resources/socket-opener.html b/test/wpt/tests/fetch/private-network-access/resources/socket-opener.html similarity index 100% rename from test/wpt/tests/fetch/local-network-access/resources/socket-opener.html rename to test/wpt/tests/fetch/private-network-access/resources/socket-opener.html diff --git a/test/wpt/tests/fetch/local-network-access/resources/support.sub.js b/test/wpt/tests/fetch/private-network-access/resources/support.sub.js similarity index 86% rename from test/wpt/tests/fetch/local-network-access/resources/support.sub.js rename to test/wpt/tests/fetch/private-network-access/resources/support.sub.js index a09c46031f5..27d733d8b7f 100644 --- a/test/wpt/tests/fetch/local-network-access/resources/support.sub.js +++ b/test/wpt/tests/fetch/private-network-access/resources/support.sub.js @@ -75,21 +75,21 @@ async function postMessageAndAwaitReply(target, message) { // Maps protocol (without the trailing colon) and address space to port. const SERVER_PORTS = { "http": { - "loopback": {{ports[http][0]}}, - "local": {{ports[http-private][0]}}, + "local": {{ports[http][0]}}, + "private": {{ports[http-private][0]}}, "public": {{ports[http-public][0]}}, }, "https": { - "loopback": {{ports[https][0]}}, - "other-loopback": {{ports[https][1]}}, - "local": {{ports[https-private][0]}}, + "local": {{ports[https][0]}}, + "other-local": {{ports[https][1]}}, + "private": {{ports[https-private][0]}}, "public": {{ports[https-public][0]}}, }, "ws": { - "loopback": {{ports[ws][0]}}, + "local": {{ports[ws][0]}}, }, "wss": { - "loopback": {{ports[wss][0]}}, + "local": {{ports[wss][0]}}, }, }; @@ -127,15 +127,15 @@ class Server { }; } - static HTTP_LOCAL = Server.get("http", "loopback"); - static HTTP_PRIVATE = Server.get("http", "local"); + static HTTP_LOCAL = Server.get("http", "local"); + static HTTP_PRIVATE = Server.get("http", "private"); static HTTP_PUBLIC = Server.get("http", "public"); - static HTTPS_LOCAL = Server.get("https", "loopback"); - static OTHER_HTTPS_LOCAL = Server.get("https", "other-loopback"); - static HTTPS_PRIVATE = Server.get("https", "local"); + static HTTPS_LOCAL = Server.get("https", "local"); + static OTHER_HTTPS_LOCAL = Server.get("https", "other-local"); + static HTTPS_PRIVATE = Server.get("https", "private"); static HTTPS_PUBLIC = Server.get("https", "public"); - static WS_LOCAL = Server.get("ws", "loopback"); - static WSS_LOCAL = Server.get("wss", "loopback"); + static WS_LOCAL = Server.get("ws", "local"); + static WSS_LOCAL = Server.get("wss", "local"); }; // Resolves a URL relative to the current location, returning an absolute URL. @@ -341,6 +341,40 @@ async function fetchTest(t, { source, target, fetchOptions, expected }) { } } +// Similar to `fetchTest`, but replaced iframes with fenced frames. +async function fencedFrameFetchTest(t, { source, target, fetchOptions, expected }) { + const fetcher_url = + resolveUrl("resources/fenced-frame-fetcher.https.html", sourceResolveOptions(source)); + + const target_url = preflightUrl(target); + target_url.searchParams.set("is-loaded-in-fenced-frame", true); + + fetcher_url.searchParams.set("mode", fetchOptions.mode); + fetcher_url.searchParams.set("method", fetchOptions.method); + fetcher_url.searchParams.set("url", target_url); + + const error_token = token(); + const ok_token = token(); + const body_token = token(); + const type_token = token(); + const source_url = generateURL(fetcher_url, [error_token, ok_token, body_token, type_token]); + + const urn = await generateURNFromFledge(source_url, []); + attachFencedFrame(urn); + + const error = await nextValueFromServer(error_token); + const ok = await nextValueFromServer(ok_token); + const body = await nextValueFromServer(body_token); + const type = await nextValueFromServer(type_token); + + assert_equals(error, expected.error || "" , "error"); + assert_equals(body, expected.body || "", "response body"); + assert_equals(ok, expected.ok !== undefined ? expected.ok.toString() : "", "response ok"); + if (expected.type !== undefined) { + assert_equals(type, expected.type, "response type"); + } +} + const XhrTestResult = { SUCCESS: { loaded: true, @@ -393,7 +427,7 @@ async function xhrTest(t, { source, target, method, expected }) { assert_equals(body, expected.body, "response body"); } -const IframeTestResult = { +const FrameTestResult = { SUCCESS: "loaded", FAILURE: "timeout", }; @@ -429,6 +463,37 @@ async function iframeTest(t, { source, target, expected }) { assert_equals(result, expected); } +// Similar to `iframeTest`, but replaced iframes with fenced frames. +async function fencedFrameTest(t, { source, target, expected }) { + // Allows running tests in parallel. + const target_url = preflightUrl(target); + target_url.searchParams.set("file", "fenced-frame-local-network-access-target.https.html"); + target_url.searchParams.set("is-loaded-in-fenced-frame", true); + + const frame_loaded_key = token(); + const child_frame_target = generateURL(target_url, [frame_loaded_key]); + + const source_url = + resolveUrl("resources/fenced-frame-local-network-access.https.html", sourceResolveOptions(source)); + source_url.searchParams.set("fenced_frame_url", child_frame_target); + + const urn = await generateURNFromFledge(source_url, []); + attachFencedFrame(urn); + + // The grandchild fenced frame writes a value to the server iff it loads + // successfully. + const result = (expected == FrameTestResult.SUCCESS) ? + await nextValueFromServer(frame_loaded_key) : + await Promise.race([ + nextValueFromServer(frame_loaded_key), + new Promise((resolve) => { + t.step_timeout(() => resolve("timeout"), 10000 /* ms */); + }), + ]); + + assert_equals(result, expected); +} + const iframeGrandparentTest = ({ name, grandparentServer, diff --git a/test/wpt/tests/fetch/local-network-access/resources/worker-blob-fetcher.html b/test/wpt/tests/fetch/private-network-access/resources/worker-blob-fetcher.html similarity index 100% rename from test/wpt/tests/fetch/local-network-access/resources/worker-blob-fetcher.html rename to test/wpt/tests/fetch/private-network-access/resources/worker-blob-fetcher.html diff --git a/test/wpt/tests/fetch/local-network-access/resources/worker-fetcher.html b/test/wpt/tests/fetch/private-network-access/resources/worker-fetcher.html similarity index 100% rename from test/wpt/tests/fetch/local-network-access/resources/worker-fetcher.html rename to test/wpt/tests/fetch/private-network-access/resources/worker-fetcher.html diff --git a/test/wpt/tests/fetch/local-network-access/resources/worker-fetcher.js b/test/wpt/tests/fetch/private-network-access/resources/worker-fetcher.js similarity index 100% rename from test/wpt/tests/fetch/local-network-access/resources/worker-fetcher.js rename to test/wpt/tests/fetch/private-network-access/resources/worker-fetcher.js diff --git a/test/wpt/tests/fetch/local-network-access/resources/xhr-sender.html b/test/wpt/tests/fetch/private-network-access/resources/xhr-sender.html similarity index 100% rename from test/wpt/tests/fetch/local-network-access/resources/xhr-sender.html rename to test/wpt/tests/fetch/private-network-access/resources/xhr-sender.html diff --git a/test/wpt/tests/fetch/local-network-access/service-worker-background-fetch.https.window.js b/test/wpt/tests/fetch/private-network-access/service-worker-background-fetch.tentative.https.window.js similarity index 100% rename from test/wpt/tests/fetch/local-network-access/service-worker-background-fetch.https.window.js rename to test/wpt/tests/fetch/private-network-access/service-worker-background-fetch.tentative.https.window.js diff --git a/test/wpt/tests/fetch/local-network-access/service-worker-fetch.https.window.js b/test/wpt/tests/fetch/private-network-access/service-worker-fetch.tentative.https.window.js similarity index 88% rename from test/wpt/tests/fetch/local-network-access/service-worker-fetch.https.window.js rename to test/wpt/tests/fetch/private-network-access/service-worker-fetch.tentative.https.window.js index 3d0f6d8097a..cb6d1f79b01 100644 --- a/test/wpt/tests/fetch/local-network-access/service-worker-fetch.https.window.js +++ b/test/wpt/tests/fetch/private-network-access/service-worker-fetch.tentative.https.window.js @@ -1,5 +1,8 @@ // META: script=/common/utils.js // META: script=resources/support.sub.js +// META: script=/common/subset-tests.js +// META: variant=?1-8 +// META: variant=?9-last // // Spec: https://wicg.github.io/private-network-access/#integration-fetch // @@ -75,13 +78,13 @@ async function makeTest(t, { source, target, expected }) { } } -promise_test(t => makeTest(t, { +subsetTest(promise_test, t => makeTest(t, { source: { server: Server.HTTPS_LOCAL }, target: { server: Server.HTTPS_LOCAL }, expected: TestResult.SUCCESS, }), "local to local: success."); -promise_test(t => makeTest(t, { +subsetTest(promise_test, t => makeTest(t, { source: { server: Server.HTTPS_PRIVATE }, target: { server: Server.HTTPS_LOCAL, @@ -90,7 +93,7 @@ promise_test(t => makeTest(t, { expected: TestResult.FAILURE, }), "private to local: failed preflight."); -promise_test(t => makeTest(t, { +subsetTest(promise_test, t => makeTest(t, { source: { server: Server.HTTPS_PRIVATE }, target: { server: Server.HTTPS_LOCAL, @@ -102,13 +105,13 @@ promise_test(t => makeTest(t, { expected: TestResult.SUCCESS, }), "private to local: success."); -promise_test(t => makeTest(t, { +subsetTest(promise_test, t => makeTest(t, { source: { server: Server.HTTPS_PRIVATE }, target: { server: Server.HTTPS_PRIVATE }, expected: TestResult.SUCCESS, }), "private to private: success."); -promise_test(t => makeTest(t, { +subsetTest(promise_test, t => makeTest(t, { source: { server: Server.HTTPS_PUBLIC }, target: { server: Server.HTTPS_LOCAL, @@ -117,7 +120,7 @@ promise_test(t => makeTest(t, { expected: TestResult.FAILURE, }), "public to local: failed preflight."); -promise_test(t => makeTest(t, { +subsetTest(promise_test, t => makeTest(t, { source: { server: Server.HTTPS_PUBLIC }, target: { server: Server.HTTPS_LOCAL, @@ -129,7 +132,7 @@ promise_test(t => makeTest(t, { expected: TestResult.SUCCESS, }), "public to local: success."); -promise_test(t => makeTest(t, { +subsetTest(promise_test, t => makeTest(t, { source: { server: Server.HTTPS_PUBLIC }, target: { server: Server.HTTPS_PRIVATE, @@ -138,7 +141,7 @@ promise_test(t => makeTest(t, { expected: TestResult.FAILURE, }), "public to private: failed preflight."); -promise_test(t => makeTest(t, { +subsetTest(promise_test, t => makeTest(t, { source: { server: Server.HTTPS_PUBLIC }, target: { server: Server.HTTPS_PRIVATE, @@ -150,13 +153,13 @@ promise_test(t => makeTest(t, { expected: TestResult.SUCCESS, }), "public to private: success."); -promise_test(t => makeTest(t, { +subsetTest(promise_test, t => makeTest(t, { source: { server: Server.HTTPS_PUBLIC }, target: { server: Server.HTTPS_PUBLIC }, expected: TestResult.SUCCESS, }), "public to public: success."); -promise_test(t => makeTest(t, { +subsetTest(promise_test, t => makeTest(t, { source: { server: Server.HTTPS_LOCAL, treatAsPublic: true, @@ -168,7 +171,7 @@ promise_test(t => makeTest(t, { expected: TestResult.FAILURE, }), "treat-as-public to local: failed preflight."); -promise_test(t => makeTest(t, { +subsetTest(promise_test, t => makeTest(t, { source: { server: Server.HTTPS_LOCAL, treatAsPublic: true, @@ -183,7 +186,7 @@ promise_test(t => makeTest(t, { expected: TestResult.SUCCESS, }), "treat-as-public to local: success."); -promise_test(t => makeTest(t, { +subsetTest(promise_test, t => makeTest(t, { source: { server: Server.HTTPS_LOCAL, treatAsPublic: true, @@ -192,7 +195,7 @@ promise_test(t => makeTest(t, { expected: TestResult.SUCCESS, }), "treat-as-public to local (same-origin): no preflight required."); -promise_test(t => makeTest(t, { +subsetTest(promise_test, t => makeTest(t, { source: { server: Server.HTTPS_LOCAL, treatAsPublic: true, @@ -204,7 +207,7 @@ promise_test(t => makeTest(t, { expected: TestResult.FAILURE, }), "treat-as-public to private: failed preflight."); -promise_test(t => makeTest(t, { +subsetTest(promise_test, t => makeTest(t, { source: { server: Server.HTTPS_LOCAL, treatAsPublic: true, @@ -219,7 +222,7 @@ promise_test(t => makeTest(t, { expected: TestResult.SUCCESS, }), "treat-as-public to private: success."); -promise_test(t => makeTest(t, { +subsetTest(promise_test, t => makeTest(t, { source: { server: Server.HTTPS_LOCAL, treatAsPublic: true, diff --git a/test/wpt/tests/fetch/local-network-access/service-worker-update.https.window.js b/test/wpt/tests/fetch/private-network-access/service-worker-update.tentative.https.window.js similarity index 100% rename from test/wpt/tests/fetch/local-network-access/service-worker-update.https.window.js rename to test/wpt/tests/fetch/private-network-access/service-worker-update.tentative.https.window.js diff --git a/test/wpt/tests/fetch/local-network-access/service-worker.https.window.js b/test/wpt/tests/fetch/private-network-access/service-worker.tentative.https.window.js similarity index 100% rename from test/wpt/tests/fetch/local-network-access/service-worker.https.window.js rename to test/wpt/tests/fetch/private-network-access/service-worker.tentative.https.window.js diff --git a/test/wpt/tests/fetch/local-network-access/shared-worker-blob-fetch.https.window.js b/test/wpt/tests/fetch/private-network-access/shared-worker-blob-fetch.tentative.https.window.js similarity index 100% rename from test/wpt/tests/fetch/local-network-access/shared-worker-blob-fetch.https.window.js rename to test/wpt/tests/fetch/private-network-access/shared-worker-blob-fetch.tentative.https.window.js diff --git a/test/wpt/tests/fetch/local-network-access/shared-worker-blob-fetch.window.js b/test/wpt/tests/fetch/private-network-access/shared-worker-blob-fetch.tentative.window.js similarity index 100% rename from test/wpt/tests/fetch/local-network-access/shared-worker-blob-fetch.window.js rename to test/wpt/tests/fetch/private-network-access/shared-worker-blob-fetch.tentative.window.js diff --git a/test/wpt/tests/fetch/local-network-access/shared-worker-fetch.https.window.js b/test/wpt/tests/fetch/private-network-access/shared-worker-fetch.tentative.https.window.js similarity index 100% rename from test/wpt/tests/fetch/local-network-access/shared-worker-fetch.https.window.js rename to test/wpt/tests/fetch/private-network-access/shared-worker-fetch.tentative.https.window.js diff --git a/test/wpt/tests/fetch/local-network-access/shared-worker-fetch.window.js b/test/wpt/tests/fetch/private-network-access/shared-worker-fetch.tentative.window.js similarity index 100% rename from test/wpt/tests/fetch/local-network-access/shared-worker-fetch.window.js rename to test/wpt/tests/fetch/private-network-access/shared-worker-fetch.tentative.window.js diff --git a/test/wpt/tests/fetch/local-network-access/shared-worker.https.window.js b/test/wpt/tests/fetch/private-network-access/shared-worker.tentative.https.window.js similarity index 100% rename from test/wpt/tests/fetch/local-network-access/shared-worker.https.window.js rename to test/wpt/tests/fetch/private-network-access/shared-worker.tentative.https.window.js diff --git a/test/wpt/tests/fetch/local-network-access/shared-worker.window.js b/test/wpt/tests/fetch/private-network-access/shared-worker.tentative.window.js similarity index 100% rename from test/wpt/tests/fetch/local-network-access/shared-worker.window.js rename to test/wpt/tests/fetch/private-network-access/shared-worker.tentative.window.js diff --git a/test/wpt/tests/fetch/local-network-access/websocket.https.window.js b/test/wpt/tests/fetch/private-network-access/websocket.tentative.https.window.js similarity index 100% rename from test/wpt/tests/fetch/local-network-access/websocket.https.window.js rename to test/wpt/tests/fetch/private-network-access/websocket.tentative.https.window.js diff --git a/test/wpt/tests/fetch/local-network-access/websocket.window.js b/test/wpt/tests/fetch/private-network-access/websocket.tentative.window.js similarity index 100% rename from test/wpt/tests/fetch/local-network-access/websocket.window.js rename to test/wpt/tests/fetch/private-network-access/websocket.tentative.window.js diff --git a/test/wpt/tests/fetch/local-network-access/worker-blob-fetch.window.js b/test/wpt/tests/fetch/private-network-access/worker-blob-fetch.tentative.window.js similarity index 100% rename from test/wpt/tests/fetch/local-network-access/worker-blob-fetch.window.js rename to test/wpt/tests/fetch/private-network-access/worker-blob-fetch.tentative.window.js diff --git a/test/wpt/tests/fetch/local-network-access/worker-fetch.https.window.js b/test/wpt/tests/fetch/private-network-access/worker-fetch.tentative.https.window.js similarity index 100% rename from test/wpt/tests/fetch/local-network-access/worker-fetch.https.window.js rename to test/wpt/tests/fetch/private-network-access/worker-fetch.tentative.https.window.js diff --git a/test/wpt/tests/fetch/local-network-access/worker-fetch.window.js b/test/wpt/tests/fetch/private-network-access/worker-fetch.tentative.window.js similarity index 100% rename from test/wpt/tests/fetch/local-network-access/worker-fetch.window.js rename to test/wpt/tests/fetch/private-network-access/worker-fetch.tentative.window.js diff --git a/test/wpt/tests/fetch/local-network-access/worker.https.window.js b/test/wpt/tests/fetch/private-network-access/worker.tentative.https.window.js similarity index 100% rename from test/wpt/tests/fetch/local-network-access/worker.https.window.js rename to test/wpt/tests/fetch/private-network-access/worker.tentative.https.window.js diff --git a/test/wpt/tests/fetch/local-network-access/worker.window.js b/test/wpt/tests/fetch/private-network-access/worker.tentative.window.js similarity index 100% rename from test/wpt/tests/fetch/local-network-access/worker.window.js rename to test/wpt/tests/fetch/private-network-access/worker.tentative.window.js diff --git a/test/wpt/tests/fetch/local-network-access/xhr-from-treat-as-public.https.window.js b/test/wpt/tests/fetch/private-network-access/xhr-from-treat-as-public.tentative.https.window.js similarity index 100% rename from test/wpt/tests/fetch/local-network-access/xhr-from-treat-as-public.https.window.js rename to test/wpt/tests/fetch/private-network-access/xhr-from-treat-as-public.tentative.https.window.js diff --git a/test/wpt/tests/fetch/local-network-access/xhr.https.window.js b/test/wpt/tests/fetch/private-network-access/xhr.https.tentative.window.js similarity index 100% rename from test/wpt/tests/fetch/local-network-access/xhr.https.window.js rename to test/wpt/tests/fetch/private-network-access/xhr.https.tentative.window.js diff --git a/test/wpt/tests/fetch/local-network-access/xhr.window.js b/test/wpt/tests/fetch/private-network-access/xhr.tentative.window.js similarity index 100% rename from test/wpt/tests/fetch/local-network-access/xhr.window.js rename to test/wpt/tests/fetch/private-network-access/xhr.tentative.window.js diff --git a/test/wpt/tests/fetch/range/blob.any.js b/test/wpt/tests/fetch/range/blob.any.js index 1db3b248f6c..7bcd4b9d11f 100644 --- a/test/wpt/tests/fetch/range/blob.any.js +++ b/test/wpt/tests/fetch/range/blob.any.js @@ -10,6 +10,15 @@ const supportedBlobRange = [ content_range: "bytes 9-21/30", result: "Hello, World!", }, + { + name: "A blob range request with no type.", + data: ["A simple Hello, World! example"], + type: undefined, + range: "bytes=9-21", + content_length: 13, + content_range: "bytes 9-21/30", + result: "Hello, World!", + }, { name: "A blob range request with no end.", data: ["Range with no end"], @@ -201,7 +210,7 @@ supportedBlobRange.forEach(({ name, data, type, range, content_length, content_r }); assert_equals(resp.status, 206, "HTTP status is 206"); assert_equals(resp.type, "basic", "response type is basic"); - assert_equals(resp.headers.get("Content-Type"), type, "Content-Type is " + resp.headers.get("Content-Type")); + assert_equals(resp.headers.get("Content-Type"), type || "", "Content-Type is " + resp.headers.get("Content-Type")); assert_equals(resp.headers.get("Content-Length"), content_length.toString(), "Content-Length is " + resp.headers.get("Content-Length")); assert_equals(resp.headers.get("Content-Range"), content_range, "Content-Range is " + resp.headers.get("Content-Range")); const text = await resp.text(); diff --git a/test/wpt/tests/interfaces/attribution-reporting-api.idl b/test/wpt/tests/interfaces/attribution-reporting-api.idl index 76640f54c8d..ed4497b56ff 100644 --- a/test/wpt/tests/interfaces/attribution-reporting-api.idl +++ b/test/wpt/tests/interfaces/attribution-reporting-api.idl @@ -4,9 +4,23 @@ // Source: Attribution Reporting (https://wicg.github.io/attribution-reporting-api/) interface mixin HTMLAttributionSrcElementUtils { - [CEReactions] attribute USVString attributionSrc; + [CEReactions, SecureContext] attribute USVString attributionSrc; }; HTMLAnchorElement includes HTMLAttributionSrcElementUtils; HTMLImageElement includes HTMLAttributionSrcElementUtils; HTMLScriptElement includes HTMLAttributionSrcElementUtils; + +dictionary AttributionReportingRequestOptions { + required boolean eventSourceEligible; + required boolean triggerEligible; +}; + +partial dictionary RequestInit { + AttributionReportingRequestOptions attributionReporting; +}; + +partial interface XMLHttpRequest { + [SecureContext] + undefined setAttributionReporting(AttributionReportingRequestOptions options); +}; diff --git a/test/wpt/tests/interfaces/captured-mouse-events.tentative.idl b/test/wpt/tests/interfaces/captured-mouse-events.tentative.idl new file mode 100644 index 00000000000..7b081cd9fd8 --- /dev/null +++ b/test/wpt/tests/interfaces/captured-mouse-events.tentative.idl @@ -0,0 +1,25 @@ +// https://screen-share.github.io/mouse-events/ + +enum CaptureStartFocusBehavior { + "focus-captured-surface", + "no-focus-change" +}; + +[Exposed=Window, SecureContext] +interface CaptureController : EventTarget { + constructor(); + undefined setFocusBehavior(CaptureStartFocusBehavior focusBehavior); + attribute EventHandler oncapturedmousechange; +}; + +[Exposed=Window] +interface CapturedMouseEvent : Event { + constructor(DOMString type, optional CapturedMouseEventInit eventInitDict = {}); + readonly attribute long surfaceX; + readonly attribute long surfaceY; +}; + +dictionary CapturedMouseEventInit : EventInit { + long surfaceX = -1; + long surfaceY = -1; +}; diff --git a/test/wpt/tests/interfaces/css-anchor-position.idl b/test/wpt/tests/interfaces/css-anchor-position.idl new file mode 100644 index 00000000000..c5da3f43f72 --- /dev/null +++ b/test/wpt/tests/interfaces/css-anchor-position.idl @@ -0,0 +1,11 @@ +// Source: CSS Anchor Positioning (https://drafts.csswg.org/css-anchor-position-1/) + +[Exposed=Window] +interface CSSPositionFallbackRule : CSSGroupingRule { + readonly attribute CSSOMString name; +}; + +[Exposed=Window] +interface CSSTryRule : CSSRule { + [SameObject, PutForwards=cssText] readonly attribute CSSStyleDeclaration style; +}; diff --git a/test/wpt/tests/interfaces/css-cascade-6.idl b/test/wpt/tests/interfaces/css-cascade-6.idl index 37cdfb82930..3bdf6ba3a6b 100644 --- a/test/wpt/tests/interfaces/css-cascade-6.idl +++ b/test/wpt/tests/interfaces/css-cascade-6.idl @@ -5,6 +5,6 @@ [Exposed=Window] interface CSSScopeRule : CSSGroupingRule { - readonly attribute CSSOMString start; - readonly attribute CSSOMString end; + readonly attribute CSSOMString? start; + readonly attribute CSSOMString? end; }; diff --git a/test/wpt/tests/interfaces/css-cascade.idl b/test/wpt/tests/interfaces/css-cascade.idl index 9011dc7fd9e..0dd9969f6eb 100644 --- a/test/wpt/tests/interfaces/css-cascade.idl +++ b/test/wpt/tests/interfaces/css-cascade.idl @@ -3,10 +3,6 @@ // (https://github.com/w3c/webref) // Source: CSS Cascading and Inheritance Level 5 (https://drafts.csswg.org/css-cascade-5/) -partial interface CSSImportRule { - readonly attribute CSSOMString? layerName; -}; - [Exposed=Window] interface CSSLayerBlockRule : CSSGroupingRule { readonly attribute CSSOMString name; diff --git a/test/wpt/tests/interfaces/cssom.idl b/test/wpt/tests/interfaces/cssom.idl index 222b3dc09ec..0574f1a771c 100644 --- a/test/wpt/tests/interfaces/cssom.idl +++ b/test/wpt/tests/interfaces/cssom.idl @@ -96,17 +96,13 @@ interface CSSRule { const unsigned short NAMESPACE_RULE = 10; }; -[Exposed=Window] -interface CSSStyleRule : CSSRule { - attribute CSSOMString selectorText; - [SameObject, PutForwards=cssText] readonly attribute CSSStyleDeclaration style; -}; - [Exposed=Window] interface CSSImportRule : CSSRule { readonly attribute USVString href; [SameObject, PutForwards=mediaText] readonly attribute MediaList media; - [SameObject] readonly attribute CSSStyleSheet styleSheet; + [SameObject] readonly attribute CSSStyleSheet? styleSheet; + readonly attribute CSSOMString? layerName; + readonly attribute CSSOMString? supportsText; }; [Exposed=Window] @@ -116,6 +112,12 @@ interface CSSGroupingRule : CSSRule { undefined deleteRule(unsigned long index); }; +[Exposed=Window] +interface CSSStyleRule : CSSGroupingRule { + attribute CSSOMString selectorText; + [SameObject, PutForwards=cssText] readonly attribute CSSStyleDeclaration style; +}; + [Exposed=Window] interface CSSPageRule : CSSGroupingRule { attribute CSSOMString selectorText; diff --git a/test/wpt/tests/interfaces/document-picture-in-picture.idl b/test/wpt/tests/interfaces/document-picture-in-picture.idl new file mode 100644 index 00000000000..742f65e6f06 --- /dev/null +++ b/test/wpt/tests/interfaces/document-picture-in-picture.idl @@ -0,0 +1,34 @@ +// GENERATED CONTENT - DO NOT EDIT +// Content was automatically extracted by Reffy into webref +// (https://github.com/w3c/webref) +// Source: Document Picture-in-Picture Specification (https://wicg.github.io/document-picture-in-picture/) + +[Exposed=Window] +partial interface Window { + [SameObject, SecureContext] readonly attribute DocumentPictureInPicture + documentPictureInPicture; +}; + +[Exposed=Window, SecureContext] +interface DocumentPictureInPicture : EventTarget { + [NewObject] Promise requestWindow( + optional DocumentPictureInPictureOptions options = {}); + readonly attribute Window window; + attribute EventHandler onenter; +}; + +dictionary DocumentPictureInPictureOptions { + [EnforceRange] unsigned long long width = 0; + [EnforceRange] unsigned long long height = 0; + boolean copyStyleSheets = false; +}; + +[Exposed=Window] +interface DocumentPictureInPictureEvent : Event { + constructor(DOMString type, DocumentPictureInPictureEventInit eventInitDict); + [SameObject] readonly attribute Window window; +}; + +dictionary DocumentPictureInPictureEventInit : EventInit { + required Window window; +}; diff --git a/test/wpt/tests/interfaces/dom.idl b/test/wpt/tests/interfaces/dom.idl index c5b5c94dbcc..c2def872fa2 100644 --- a/test/wpt/tests/interfaces/dom.idl +++ b/test/wpt/tests/interfaces/dom.idl @@ -95,6 +95,7 @@ interface AbortController { interface AbortSignal : EventTarget { [NewObject] static AbortSignal abort(optional any reason); [Exposed=(Window,Worker), NewObject] static AbortSignal timeout([EnforceRange] unsigned long long milliseconds); + [NewObject] static AbortSignal _any(sequence signals); readonly attribute boolean aborted; readonly attribute any reason; diff --git a/test/wpt/tests/interfaces/fenced-frame.idl b/test/wpt/tests/interfaces/fenced-frame.idl index 2869b95e6bb..440ec2bbaa1 100644 --- a/test/wpt/tests/interfaces/fenced-frame.idl +++ b/test/wpt/tests/interfaces/fenced-frame.idl @@ -1,7 +1,7 @@ // GENERATED CONTENT - DO NOT EDIT // Content was automatically extracted by Reffy into webref // (https://github.com/w3c/webref) -// Source: Fenced frame (https://wicg.github.io/fenced-frame/) +// Source: Fenced Frame (https://wicg.github.io/fenced-frame/) [Exposed=Window] interface HTMLFencedFrameElement : HTMLElement { @@ -10,19 +10,22 @@ interface HTMLFencedFrameElement : HTMLElement { [CEReactions] attribute FencedFrameConfig? config; [CEReactions] attribute DOMString width; [CEReactions] attribute DOMString height; + [CEReactions] attribute DOMString allow; }; enum OpaqueProperty {"opaque"}; typedef (unsigned long or OpaqueProperty) FencedFrameConfigSize; -typedef (USVString or OpaqueProperty) FencedFrameConfigURL; +typedef USVString FencedFrameConfigURL; [Exposed=Window] interface FencedFrameConfig { - constructor(USVString url); - readonly attribute FencedFrameConfigURL? url; - readonly attribute FencedFrameConfigSize? width; - readonly attribute FencedFrameConfigSize? height; + readonly attribute FencedFrameConfigSize? containerWidth; + readonly attribute FencedFrameConfigSize? containerHeight; + readonly attribute FencedFrameConfigSize? contentWidth; + readonly attribute FencedFrameConfigSize? contentHeight; + + undefined setSharedStorageContext(DOMString contextString); }; enum FenceReportingDestination { diff --git a/test/wpt/tests/interfaces/fs.idl b/test/wpt/tests/interfaces/fs.idl index e2474132abf..e341ab387d9 100644 --- a/test/wpt/tests/interfaces/fs.idl +++ b/test/wpt/tests/interfaces/fs.idl @@ -15,6 +15,7 @@ interface FileSystemHandle { Promise isSameEntry(FileSystemHandle other); }; + dictionary FileSystemCreateWritableOptions { boolean keepExistingData = false; }; @@ -26,6 +27,7 @@ interface FileSystemFileHandle : FileSystemHandle { [Exposed=DedicatedWorker] Promise createSyncAccessHandle(); }; + dictionary FileSystemGetFileOptions { boolean create = false; }; @@ -49,6 +51,7 @@ interface FileSystemDirectoryHandle : FileSystemHandle { Promise?> resolve(FileSystemHandle possibleDescendant); }; + enum WriteCommandType { "write", "seek", @@ -70,6 +73,7 @@ interface FileSystemWritableFileStream : WritableStream { Promise seek(unsigned long long position); Promise truncate(unsigned long long size); }; + dictionary FileSystemReadWriteOptions { [EnforceRange] unsigned long long at; }; diff --git a/test/wpt/tests/interfaces/html.idl b/test/wpt/tests/interfaces/html.idl index 33d4de0db97..99b33705b39 100644 --- a/test/wpt/tests/interfaces/html.idl +++ b/test/wpt/tests/interfaces/html.idl @@ -1643,6 +1643,14 @@ dictionary ValidityStateFlags { boolean customError = false; }; +[Exposed=(Window)] +interface VisibilityStateEntry : PerformanceEntry { + readonly attribute DOMString name; // shadows inherited name + readonly attribute DOMString entryType; // shadows inherited entryType + readonly attribute DOMHighResTimeStamp startTime; // shadows inherited startTime + readonly attribute unsigned long duration; // shadows inherited duration +}; + [Exposed=Window] interface UserActivation { readonly attribute boolean hasBeenActive; diff --git a/test/wpt/tests/interfaces/mediastream-recording.idl b/test/wpt/tests/interfaces/mediastream-recording.idl index 99f30282333..496bfcf2e27 100644 --- a/test/wpt/tests/interfaces/mediastream-recording.idl +++ b/test/wpt/tests/interfaces/mediastream-recording.idl @@ -34,6 +34,8 @@ dictionary MediaRecorderOptions { unsigned long videoBitsPerSecond; unsigned long bitsPerSecond; BitrateMode audioBitrateMode = "variable"; + DOMHighResTimeStamp videoKeyFrameIntervalDuration; + unsigned long videoKeyFrameIntervalCount; }; enum BitrateMode { diff --git a/test/wpt/tests/interfaces/notifications.idl b/test/wpt/tests/interfaces/notifications.idl index bfcfa2e66af..4300b171071 100644 --- a/test/wpt/tests/interfaces/notifications.idl +++ b/test/wpt/tests/interfaces/notifications.idl @@ -28,7 +28,7 @@ interface Notification : EventTarget { [SameObject] readonly attribute FrozenArray vibrate; readonly attribute EpochTimeStamp timestamp; readonly attribute boolean renotify; - readonly attribute boolean silent; + readonly attribute boolean? silent; readonly attribute boolean requireInteraction; [SameObject] readonly attribute any data; [SameObject] readonly attribute FrozenArray actions; @@ -47,7 +47,7 @@ dictionary NotificationOptions { VibratePattern vibrate; EpochTimeStamp timestamp; boolean renotify = false; - boolean silent = false; + boolean? silent = null; boolean requireInteraction = false; any data = null; sequence actions = []; @@ -72,6 +72,7 @@ dictionary NotificationAction { }; callback NotificationPermissionCallback = undefined (NotificationPermission permission); + dictionary GetNotificationOptions { DOMString tag = ""; }; diff --git a/test/wpt/tests/interfaces/permissions-policy.idl b/test/wpt/tests/interfaces/permissions-policy.idl index a789d41738c..16945e3a9b7 100644 --- a/test/wpt/tests/interfaces/permissions-policy.idl +++ b/test/wpt/tests/interfaces/permissions-policy.idl @@ -18,6 +18,7 @@ partial interface Document { partial interface HTMLIFrameElement { [SameObject] readonly attribute PermissionsPolicy permissionsPolicy; }; + [Exposed=Window] interface PermissionsPolicyViolationReportBody : ReportBody { readonly attribute DOMString featureId; diff --git a/test/wpt/tests/interfaces/real-world-meshing.idl b/test/wpt/tests/interfaces/real-world-meshing.idl new file mode 100644 index 00000000000..38fe71f6c66 --- /dev/null +++ b/test/wpt/tests/interfaces/real-world-meshing.idl @@ -0,0 +1,21 @@ +// GENERATED CONTENT - DO NOT EDIT +// Content was automatically extracted by Reffy into webref +// (https://github.com/w3c/webref) +// Source: WebXR Mesh Detection Module (https://immersive-web.github.io/real-world-meshing/) + +[Exposed=Window] interface XRMesh { + [SameObject] readonly attribute XRSpace meshSpace; + + readonly attribute FrozenArray vertices; + readonly attribute Uint32Array indices; + readonly attribute DOMHighResTimeStamp lastChangedTime; + readonly attribute DOMString? semanticLabel; +}; + +[Exposed=Window] interface XRMeshSet { + readonly setlike; +}; + +partial interface XRFrame { + readonly attribute XRMeshSet detectedMeshs; +}; diff --git a/test/wpt/tests/interfaces/resource-timing.idl b/test/wpt/tests/interfaces/resource-timing.idl index 151e5d46d84..33fed05b756 100644 --- a/test/wpt/tests/interfaces/resource-timing.idl +++ b/test/wpt/tests/interfaces/resource-timing.idl @@ -18,6 +18,7 @@ interface PerformanceResourceTiming : PerformanceEntry { readonly attribute DOMHighResTimeStamp connectEnd; readonly attribute DOMHighResTimeStamp secureConnectionStart; readonly attribute DOMHighResTimeStamp requestStart; + readonly attribute DOMHighResTimeStamp firstInterimResponseStart; readonly attribute DOMHighResTimeStamp responseStart; readonly attribute DOMHighResTimeStamp responseEnd; readonly attribute unsigned long long transferSize; @@ -25,6 +26,7 @@ interface PerformanceResourceTiming : PerformanceEntry { readonly attribute unsigned long long decodedBodySize; readonly attribute unsigned short responseStatus; readonly attribute RenderBlockingStatusType renderBlockingStatus; + readonly attribute DOMString contentType; [Default] object toJSON(); }; diff --git a/test/wpt/tests/interfaces/scheduling-apis.idl b/test/wpt/tests/interfaces/scheduling-apis.idl index 9ed49cfb689..1e84e79cd15 100644 --- a/test/wpt/tests/interfaces/scheduling-apis.idl +++ b/test/wpt/tests/interfaces/scheduling-apis.idl @@ -45,8 +45,14 @@ interface TaskController : AbortController { undefined setPriority(TaskPriority priority); }; +dictionary TaskSignalAnyInit { + (TaskPriority or TaskSignal) priority = "user-visible"; +}; + [Exposed=(Window, Worker)] interface TaskSignal : AbortSignal { + [NewObject] static TaskSignal _any(sequence signals, optional TaskSignalAnyInit init = {}); + readonly attribute TaskPriority priority; attribute EventHandler onprioritychange; diff --git a/test/wpt/tests/interfaces/screen-capture.idl b/test/wpt/tests/interfaces/screen-capture.idl index 9abd4d2c1ad..830b96d16fa 100644 --- a/test/wpt/tests/interfaces/screen-capture.idl +++ b/test/wpt/tests/interfaces/screen-capture.idl @@ -13,7 +13,7 @@ enum CaptureStartFocusBehavior { }; [Exposed=Window, SecureContext] -interface CaptureController { +interface CaptureController : EventTarget { constructor(); undefined setFocusBehavior(CaptureStartFocusBehavior focusBehavior); }; diff --git a/test/wpt/tests/interfaces/scroll-animations.idl b/test/wpt/tests/interfaces/scroll-animations.idl index 14215509c9f..31b3746e9d4 100644 --- a/test/wpt/tests/interfaces/scroll-animations.idl +++ b/test/wpt/tests/interfaces/scroll-animations.idl @@ -36,7 +36,11 @@ interface ViewTimeline : ScrollTimeline { readonly attribute CSSNumericValue endOffset; }; +dictionary AnimationTimeOptions { + DOMString? range; +}; + [Exposed=Window] partial interface AnimationTimeline { - CSSNumericValue? getCurrentTime(optional CSSOMString rangeName); + CSSNumericValue? getCurrentTime(optional AnimationTimeOptions options = {}); }; diff --git a/test/wpt/tests/interfaces/secure-payment-confirmation.idl b/test/wpt/tests/interfaces/secure-payment-confirmation.idl index 9061b243477..08ec8065c53 100644 --- a/test/wpt/tests/interfaces/secure-payment-confirmation.idl +++ b/test/wpt/tests/interfaces/secure-payment-confirmation.idl @@ -15,6 +15,7 @@ dictionary SecurePaymentConfirmationRequest { sequence locale; boolean showOptOut; }; + partial dictionary AuthenticationExtensionsClientInputs { AuthenticationExtensionsPaymentInputs payment; }; @@ -30,9 +31,11 @@ dictionary AuthenticationExtensionsPaymentInputs { PaymentCurrencyAmount total; PaymentCredentialInstrument instrument; }; + dictionary CollectedClientPaymentData : CollectedClientData { required CollectedClientAdditionalPaymentData payment; }; + dictionary CollectedClientAdditionalPaymentData { required USVString rpId; required USVString topOrigin; @@ -41,6 +44,7 @@ dictionary CollectedClientAdditionalPaymentData { required PaymentCurrencyAmount total; required PaymentCredentialInstrument instrument; }; + dictionary PaymentCredentialInstrument { required USVString displayName; required USVString icon; diff --git a/test/wpt/tests/interfaces/shared-storage.idl b/test/wpt/tests/interfaces/shared-storage.idl new file mode 100644 index 00000000000..eb5806f9a6d --- /dev/null +++ b/test/wpt/tests/interfaces/shared-storage.idl @@ -0,0 +1,80 @@ +// GENERATED CONTENT - DO NOT EDIT +// Content was automatically extracted by Reffy into webref +// (https://github.com/w3c/webref) +// Source: Shared Storage API (https://wicg.github.io/shared-storage/) + +[Exposed=(Window)] +interface SharedStorageWorklet : Worklet { +}; + +[Exposed=SharedStorageWorklet, Global=SharedStorageWorklet] +interface SharedStorageWorkletGlobalScope : WorkletGlobalScope { + undefined register(DOMString name, + SharedStorageOperationConstructor operationCtor); +}; + +callback SharedStorageOperationConstructor = + SharedStorageOperation(optional SharedStorageRunOperationMethodOptions options); + +[Exposed=SharedStorageWorklet] +interface SharedStorageOperation { +}; + +dictionary SharedStorageRunOperationMethodOptions { + object data; + boolean resolveToConfig = false; + boolean keepAlive = false; +}; + +[Exposed=SharedStorageWorklet] +interface SharedStorageRunOperation : SharedStorageOperation { + Promise run(object data); +}; + +[Exposed=SharedStorageWorklet] +interface SharedStorageSelectURLOperation : SharedStorageOperation { + Promise run(object data, + FrozenArray urls); +}; + +[Exposed=(Window,SharedStorageWorklet)] +interface SharedStorage { + Promise set(DOMString key, + DOMString value, + optional SharedStorageSetMethodOptions options = {}); + Promise append(DOMString key, + DOMString value); + Promise delete(DOMString key); + Promise clear(); +}; + +dictionary SharedStorageSetMethodOptions { + boolean ignoreIfPresent = false; +}; + +typedef (USVString or FencedFrameConfig) SharedStorageResponse; + +[Exposed=(Window)] +interface WindowSharedStorage : SharedStorage { + Promise run(DOMString name, + optional SharedStorageRunOperationMethodOptions options = {}); + Promise selectURL(DOMString name, + FrozenArray urls, + optional SharedStorageRunOperationMethodOptions options = {}); + + readonly attribute SharedStorageWorklet worklet; +}; + +dictionary SharedStorageUrlWithMetadata { + required USVString url; + object reportingMetadata; +}; + +[Exposed=(SharedStorageWorklet)] +interface WorkletSharedStorage : SharedStorage { + Promise get(DOMString key); + Promise length(); + Promise remainingBudget(); + + async iterable; +}; diff --git a/test/wpt/tests/interfaces/storage-buckets.idl b/test/wpt/tests/interfaces/storage-buckets.idl new file mode 100644 index 00000000000..f3d500a5711 --- /dev/null +++ b/test/wpt/tests/interfaces/storage-buckets.idl @@ -0,0 +1,53 @@ +// GENERATED CONTENT - DO NOT EDIT +// Content was automatically extracted by Reffy into webref +// (https://github.com/w3c/webref) +// Source: Storage Buckets API (https://wicg.github.io/storage-buckets/) + +[SecureContext] +interface mixin NavigatorStorageBuckets { + [SameObject] readonly attribute StorageBucketManager storageBuckets; +}; +Navigator includes NavigatorStorageBuckets; +WorkerNavigator includes NavigatorStorageBuckets; + +[Exposed=(Window,Worker), + SecureContext] +interface StorageBucketManager { + Promise open(DOMString name, optional StorageBucketOptions options = {}); + Promise> keys(); + Promise delete(DOMString name); +}; + +enum StorageBucketDurability { + "strict", + "relaxed" +}; + +dictionary StorageBucketOptions { + boolean? persisted = null; + StorageBucketDurability? durability = null; + unsigned long long? quota = null; + DOMHighResTimeStamp? expires = null; +}; + +[Exposed=(Window,Worker), + SecureContext] +interface StorageBucket { + readonly attribute DOMString name; + + [Exposed=Window] Promise persist(); + Promise persisted(); + + Promise estimate(); + + Promise durability(); + + Promise setExpires(DOMHighResTimeStamp expires); + Promise expires(); + + [SameObject] readonly attribute IDBFactory indexedDB; + + [SameObject] readonly attribute CacheStorage caches; + + Promise getDirectory(); +}; diff --git a/test/wpt/tests/interfaces/trust-token-api.idl b/test/wpt/tests/interfaces/trust-token-api.idl index ee339590827..f521acea1f5 100644 --- a/test/wpt/tests/interfaces/trust-token-api.idl +++ b/test/wpt/tests/interfaces/trust-token-api.idl @@ -5,14 +5,11 @@ enum RefreshPolicy { "none", "refresh" }; -enum TokenType { "private-state-token" }; - enum TokenVersion { "1" }; enum OperationType { "token-request", "send-redemption-record", "token-redemption" }; dictionary PrivateToken { - required TokenType type; required TokenVersion version; required OperationType operation; RefreshPolicy refreshPolicy = "none"; @@ -24,6 +21,6 @@ partial dictionary RequestInit { }; partial interface Document { - Promise hasPrivateTokens(USVString issuer, USVString type); - Promise hasRedemptionRecord(USVString issuer, USVString type); + Promise hasPrivateTokens(USVString issuer); + Promise hasRedemptionRecord(USVString issuer); }; diff --git a/test/wpt/tests/interfaces/turtledove.idl b/test/wpt/tests/interfaces/turtledove.idl index cd81a3d87ef..f5867e995a8 100644 --- a/test/wpt/tests/interfaces/turtledove.idl +++ b/test/wpt/tests/interfaces/turtledove.idl @@ -1,7 +1,7 @@ // GENERATED CONTENT - DO NOT EDIT // Content was automatically extracted by Reffy into webref // (https://github.com/w3c/webref) -// Source: FLEDGE (https://wicg.github.io/turtledove/) +// Source: Protected Audience (formerly FLEDGE) (https://wicg.github.io/turtledove/) [SecureContext] partial interface Navigator { @@ -25,7 +25,7 @@ dictionary AuctionAdInterestGroup { DOMString executionMode = "compatibility"; USVString biddingLogicURL; USVString biddingWasmHelperURL; - USVString dailyUpdateURL; + USVString updateURL; USVString trustedBiddingSignalsURL; sequence trustedBiddingSignalsKeys; any userBiddingSignals; @@ -67,9 +67,15 @@ dictionary AuctionAdConfig { AbortSignal? signal; }; +[Exposed=InterestGroupScriptRunnerGlobalScope] +interface InterestGroupScriptRunnerGlobalScope { +}; + [Exposed=InterestGroupBiddingScriptRunnerGlobalScope, -Global=InterestGroupBiddingScriptRunnerGlobalScope] -interface InterestGroupBiddingScriptRunnerGlobalScope { + Global=(InterestGroupScriptRunnerGlobalScope, + InterestGroupBiddingScriptRunnerGlobalScope)] +interface InterestGroupBiddingScriptRunnerGlobalScope + : InterestGroupScriptRunnerGlobalScope { boolean setBid(); boolean setBid(GenerateBidOutput generateBidOutput); undefined setPriority(double priority); @@ -77,14 +83,19 @@ interface InterestGroupBiddingScriptRunnerGlobalScope { }; [Exposed=InterestGroupScoringScriptRunnerGlobalScope, -Global=InterestGroupScoringScriptRunnerGlobalScope] -interface InterestGroupScoringScriptRunnerGlobalScope { + Global=(InterestGroupScriptRunnerGlobalScope, + InterestGroupScoringScriptRunnerGlobalScope)] +interface InterestGroupScoringScriptRunnerGlobalScope + : InterestGroupScriptRunnerGlobalScope { }; [Exposed=InterestGroupReportingScriptRunnerGlobalScope, -Global=InterestGroupReportingScriptRunnerGlobalScope] -interface InterestGroupReportingScriptRunnerGlobalScope { + Global=(InterestGroupScriptRunnerGlobalScope, + InterestGroupReportingScriptRunnerGlobalScope)] +interface InterestGroupReportingScriptRunnerGlobalScope + : InterestGroupScriptRunnerGlobalScope { undefined sendReportTo(DOMString url); + undefined registerAdBeacon(record map); }; dictionary AdRender { @@ -96,7 +107,7 @@ dictionary AdRender { dictionary GenerateBidOutput { required double bid; required (DOMString or AdRender) adRender; - DOMString ad; + any ad; sequence<(DOMString or AdRender)> adComponents; double adCost; double modelingSignals; diff --git a/test/wpt/tests/interfaces/url.idl b/test/wpt/tests/interfaces/url.idl index 6549e45f419..a5e4d1eb492 100644 --- a/test/wpt/tests/interfaces/url.idl +++ b/test/wpt/tests/interfaces/url.idl @@ -33,10 +33,10 @@ interface URLSearchParams { readonly attribute unsigned long size; undefined append(USVString name, USVString value); - undefined delete(USVString name); + undefined delete(USVString name, optional USVString value); USVString? get(USVString name); sequence getAll(USVString name); - boolean has(USVString name); + boolean has(USVString name, optional USVString value); undefined set(USVString name, USVString value); undefined sort(); diff --git a/test/wpt/tests/interfaces/webauthn.idl b/test/wpt/tests/interfaces/webauthn.idl index 58a9e285232..9a37207ba2c 100644 --- a/test/wpt/tests/interfaces/webauthn.idl +++ b/test/wpt/tests/interfaces/webauthn.idl @@ -12,51 +12,56 @@ interface PublicKeyCredential : Credential { static Promise isConditionalMediationAvailable(); PublicKeyCredentialJSON toJSON(); }; + typedef DOMString Base64URLString; typedef (RegistrationResponseJSON or AuthenticationResponseJSON) PublicKeyCredentialJSON; dictionary RegistrationResponseJSON { - Base64URLString id; - Base64URLString rawId; - AuthenticatorAttestationResponseJSON response; - DOMString? authenticatorAttachment; - AuthenticationExtensionsClientOutputsJSON clientExtensionResults; - DOMString type; + required Base64URLString id; + required Base64URLString rawId; + required AuthenticatorAttestationResponseJSON response; + DOMString authenticatorAttachment; + required AuthenticationExtensionsClientOutputsJSON clientExtensionResults; + required DOMString type; }; dictionary AuthenticatorAttestationResponseJSON { - Base64URLString clientDataJSON; - Base64URLString attestationObject; - sequence transports; + required Base64URLString clientDataJSON; + required Base64URLString attestationObject; + required sequence transports; }; dictionary AuthenticationResponseJSON { - Base64URLString id; - Base64URLString rawId; - AuthenticatorAssertionResponseJSON response; - DOMString? authenticatorAttachment; - AuthenticationExtensionsClientOutputsJSON clientExtensionResults; - DOMString type; + required Base64URLString id; + required Base64URLString rawId; + required AuthenticatorAssertionResponseJSON response; + DOMString authenticatorAttachment; + required AuthenticationExtensionsClientOutputsJSON clientExtensionResults; + required DOMString type; }; dictionary AuthenticatorAssertionResponseJSON { - Base64URLString clientDataJSON; - Base64URLString authenticatorData; - Base64URLString signature; - Base64URLString? userHandle; + required Base64URLString clientDataJSON; + required Base64URLString authenticatorData; + required Base64URLString signature; + Base64URLString userHandle; }; dictionary AuthenticationExtensionsClientOutputsJSON { }; + partial dictionary CredentialCreationOptions { PublicKeyCredentialCreationOptions publicKey; }; + partial dictionary CredentialRequestOptions { PublicKeyCredentialRequestOptions publicKey; }; + partial interface PublicKeyCredential { static Promise isUserVerifyingPlatformAuthenticatorAvailable(); }; + partial interface PublicKeyCredential { static PublicKeyCredentialCreationOptions parseCreationOptionsFromJSON(PublicKeyCredentialCreationOptionsJSON options); }; @@ -87,6 +92,7 @@ dictionary PublicKeyCredentialDescriptorJSON { dictionary AuthenticationExtensionsClientInputsJSON { }; + partial interface PublicKeyCredential { static PublicKeyCredentialRequestOptions parseRequestOptionsFromJSON(PublicKeyCredentialRequestOptionsJSON options); }; @@ -99,10 +105,12 @@ dictionary PublicKeyCredentialRequestOptionsJSON { DOMString userVerification = "preferred"; AuthenticationExtensionsClientInputsJSON extensions; }; + [SecureContext, Exposed=Window] interface AuthenticatorResponse { [SameObject] readonly attribute ArrayBuffer clientDataJSON; }; + [SecureContext, Exposed=Window] interface AuthenticatorAttestationResponse : AuthenticatorResponse { [SameObject] readonly attribute ArrayBuffer attestationObject; @@ -111,6 +119,7 @@ interface AuthenticatorAttestationResponse : AuthenticatorResponse { ArrayBuffer? getPublicKey(); COSEAlgorithmIdentifier getPublicKeyAlgorithm(); }; + [SecureContext, Exposed=Window] interface AuthenticatorAssertionResponse : AuthenticatorResponse { [SameObject] readonly attribute ArrayBuffer authenticatorData; @@ -118,10 +127,12 @@ interface AuthenticatorAssertionResponse : AuthenticatorResponse { [SameObject] readonly attribute ArrayBuffer? userHandle; [SameObject] readonly attribute ArrayBuffer? attestationObject; }; + dictionary PublicKeyCredentialParameters { required DOMString type; required COSEAlgorithmIdentifier alg; }; + dictionary PublicKeyCredentialCreationOptions { required PublicKeyCredentialRpEntity rp; required PublicKeyCredentialUserEntity user; @@ -136,37 +147,45 @@ dictionary PublicKeyCredentialCreationOptions { sequence attestationFormats = []; AuthenticationExtensionsClientInputs extensions; }; + dictionary PublicKeyCredentialEntity { required DOMString name; }; + dictionary PublicKeyCredentialRpEntity : PublicKeyCredentialEntity { DOMString id; }; + dictionary PublicKeyCredentialUserEntity : PublicKeyCredentialEntity { required BufferSource id; required DOMString displayName; }; + dictionary AuthenticatorSelectionCriteria { DOMString authenticatorAttachment; DOMString residentKey; boolean requireResidentKey = false; DOMString userVerification = "preferred"; }; + enum AuthenticatorAttachment { "platform", "cross-platform" }; + enum ResidentKeyRequirement { "discouraged", "preferred", "required" }; + enum AttestationConveyancePreference { "none", "indirect", "direct", "enterprise" }; + dictionary PublicKeyCredentialRequestOptions { required BufferSource challenge; unsigned long timeout; @@ -177,10 +196,13 @@ dictionary PublicKeyCredentialRequestOptions { sequence attestationFormats = []; AuthenticationExtensionsClientInputs extensions; }; + dictionary AuthenticationExtensionsClientInputs { }; + dictionary AuthenticationExtensionsClientOutputs { }; + dictionary CollectedClientData { required DOMString type; required DOMString challenge; @@ -195,42 +217,54 @@ dictionary TokenBinding { }; enum TokenBindingStatus { "present", "supported" }; + enum PublicKeyCredentialType { "public-key" }; + dictionary PublicKeyCredentialDescriptor { required DOMString type; required BufferSource id; sequence transports; }; + enum AuthenticatorTransport { "usb", "nfc", "ble", + "smart-card", "hybrid", "internal" }; + typedef long COSEAlgorithmIdentifier; + enum UserVerificationRequirement { "required", "preferred", "discouraged" }; + partial dictionary AuthenticationExtensionsClientInputs { USVString appid; }; + partial dictionary AuthenticationExtensionsClientOutputs { boolean appid; }; + partial dictionary AuthenticationExtensionsClientInputs { USVString appidExclude; }; + partial dictionary AuthenticationExtensionsClientOutputs { boolean appidExclude; }; + partial dictionary AuthenticationExtensionsClientInputs { boolean credProps; }; + dictionary CredentialPropertiesOutput { boolean rk; }; @@ -238,6 +272,7 @@ dictionary CredentialPropertiesOutput { partial dictionary AuthenticationExtensionsClientOutputs { CredentialPropertiesOutput credProps; }; + dictionary AuthenticationExtensionsPRFValues { required BufferSource first; BufferSource second; @@ -289,12 +324,14 @@ dictionary AuthenticationExtensionsLargeBlobOutputs { partial dictionary AuthenticationExtensionsClientInputs { boolean uvm; }; + typedef sequence UvmEntry; typedef sequence UvmEntries; partial dictionary AuthenticationExtensionsClientOutputs { UvmEntries uvm; }; + dictionary AuthenticationExtensionsDevicePublicKeyInputs { DOMString attestation = "none"; sequence attestationFormats = []; diff --git a/test/wpt/tests/interfaces/webcodecs-av1-codec-registration.idl b/test/wpt/tests/interfaces/webcodecs-av1-codec-registration.idl index 00e4493d3c0..ab20879728d 100644 --- a/test/wpt/tests/interfaces/webcodecs-av1-codec-registration.idl +++ b/test/wpt/tests/interfaces/webcodecs-av1-codec-registration.idl @@ -3,6 +3,14 @@ // (https://github.com/w3c/webref) // Source: AV1 WebCodecs Registration (https://w3c.github.io/webcodecs/av1_codec_registration.html) +partial dictionary VideoEncoderConfig { + AV1EncoderConfig av1; +}; + +dictionary AV1EncoderConfig { + boolean forceScreenContentTools = false; +}; + partial dictionary VideoEncoderEncodeOptions { VideoEncoderEncodeOptionsForAv1 av1; }; diff --git a/test/wpt/tests/interfaces/webcodecs-avc-codec-registration.idl b/test/wpt/tests/interfaces/webcodecs-avc-codec-registration.idl index d4074f647da..2b952c22194 100644 --- a/test/wpt/tests/interfaces/webcodecs-avc-codec-registration.idl +++ b/test/wpt/tests/interfaces/webcodecs-avc-codec-registration.idl @@ -15,3 +15,11 @@ enum AvcBitstreamFormat { "annexb", "avc", }; + +partial dictionary VideoEncoderEncodeOptions { + VideoEncoderEncodeOptionsForAvc avc; +}; + +dictionary VideoEncoderEncodeOptionsForAvc { + unsigned short? quantizer; +}; diff --git a/test/wpt/tests/interfaces/webcodecs.idl b/test/wpt/tests/interfaces/webcodecs.idl index 77649029db6..0b95dc8b757 100644 --- a/test/wpt/tests/interfaces/webcodecs.idl +++ b/test/wpt/tests/interfaces/webcodecs.idl @@ -161,6 +161,7 @@ dictionary AudioEncoderConfig { [EnforceRange] unsigned long sampleRate; [EnforceRange] unsigned long numberOfChannels; [EnforceRange] unsigned long long bitrate; + BitrateMode bitrateMode; }; dictionary VideoEncoderConfig { diff --git a/test/wpt/tests/interfaces/webgpu.idl b/test/wpt/tests/interfaces/webgpu.idl index 284327a5789..25943d99c35 100644 --- a/test/wpt/tests/interfaces/webgpu.idl +++ b/test/wpt/tests/interfaces/webgpu.idl @@ -18,6 +18,7 @@ interface GPUSupportedLimits { readonly attribute unsigned long maxTextureDimension3D; readonly attribute unsigned long maxTextureArrayLayers; readonly attribute unsigned long maxBindGroups; + readonly attribute unsigned long maxBindGroupsPlusVertexBuffers; readonly attribute unsigned long maxBindingsPerBindGroup; readonly attribute unsigned long maxDynamicUniformBuffersPerPipelineLayout; readonly attribute unsigned long maxDynamicStorageBuffersPerPipelineLayout; @@ -26,7 +27,6 @@ interface GPUSupportedLimits { readonly attribute unsigned long maxStorageBuffersPerShaderStage; readonly attribute unsigned long maxStorageTexturesPerShaderStage; readonly attribute unsigned long maxUniformBuffersPerShaderStage; - readonly attribute unsigned long maxFragmentCombinedOutputResources; readonly attribute unsigned long long maxUniformBufferBindingSize; readonly attribute unsigned long long maxStorageBufferBindingSize; readonly attribute unsigned long minUniformBufferOffsetAlignment; @@ -85,7 +85,7 @@ dictionary GPURequestAdapterOptions { enum GPUPowerPreference { "low-power", - "high-performance" + "high-performance", }; [Exposed=(Window, DedicatedWorker), SecureContext] @@ -98,7 +98,8 @@ interface GPUAdapter { Promise requestAdapterInfo(optional sequence unmaskHints = []); }; -dictionary GPUDeviceDescriptor : GPUObjectDescriptorBase { +dictionary GPUDeviceDescriptor + : GPUObjectDescriptorBase { sequence requiredFeatures = []; record requiredLimits = {}; GPUQueueDescriptor defaultQueue = {}; @@ -115,7 +116,7 @@ enum GPUFeatureName { "shader-f16", "rg11b10ufloat-renderable", "bgra8unorm-storage", - "float32-filterable" + "float32-filterable", }; [Exposed=(Window, DedicatedWorker), SecureContext] @@ -151,8 +152,8 @@ GPUDevice includes GPUObjectBase; [Exposed=(Window, DedicatedWorker), SecureContext] interface GPUBuffer { - readonly attribute GPUSize64 size; - readonly attribute GPUBufferUsageFlags usage; + readonly attribute GPUSize64Out size; + readonly attribute GPUFlagsConstant usage; readonly attribute GPUBufferMapState mapState; @@ -167,10 +168,11 @@ GPUBuffer includes GPUObjectBase; enum GPUBufferMapState { "unmapped", "pending", - "mapped" + "mapped", }; -dictionary GPUBufferDescriptor : GPUObjectDescriptorBase { +dictionary GPUBufferDescriptor + : GPUObjectDescriptorBase { required GPUSize64 size; required GPUBufferUsageFlags usage; boolean mappedAtCreation = false; @@ -204,18 +206,19 @@ interface GPUTexture { undefined destroy(); - readonly attribute GPUIntegerCoordinate width; - readonly attribute GPUIntegerCoordinate height; - readonly attribute GPUIntegerCoordinate depthOrArrayLayers; - readonly attribute GPUIntegerCoordinate mipLevelCount; - readonly attribute GPUSize32 sampleCount; + readonly attribute GPUIntegerCoordinateOut width; + readonly attribute GPUIntegerCoordinateOut height; + readonly attribute GPUIntegerCoordinateOut depthOrArrayLayers; + readonly attribute GPUIntegerCoordinateOut mipLevelCount; + readonly attribute GPUSize32Out sampleCount; readonly attribute GPUTextureDimension dimension; readonly attribute GPUTextureFormat format; - readonly attribute GPUTextureUsageFlags usage; + readonly attribute GPUFlagsConstant usage; }; GPUTexture includes GPUObjectBase; -dictionary GPUTextureDescriptor : GPUObjectDescriptorBase { +dictionary GPUTextureDescriptor + : GPUObjectDescriptorBase { required GPUExtent3D size; GPUIntegerCoordinate mipLevelCount = 1; GPUSize32 sampleCount = 1; @@ -228,7 +231,7 @@ dictionary GPUTextureDescriptor : GPUObjectDescriptorBase { enum GPUTextureDimension { "1d", "2d", - "3d" + "3d", }; typedef [EnforceRange] unsigned long GPUTextureUsageFlags; @@ -246,7 +249,8 @@ interface GPUTextureView { }; GPUTextureView includes GPUObjectBase; -dictionary GPUTextureViewDescriptor : GPUObjectDescriptorBase { +dictionary GPUTextureViewDescriptor + : GPUObjectDescriptorBase { GPUTextureFormat format; GPUTextureViewDimension dimension; GPUTextureAspect aspect = "all"; @@ -262,13 +266,13 @@ enum GPUTextureViewDimension { "2d-array", "cube", "cube-array", - "3d" + "3d", }; enum GPUTextureAspect { "all", "stencil-only", - "depth-only" + "depth-only", }; enum GPUTextureFormat { @@ -388,7 +392,7 @@ enum GPUTextureFormat { "astc-12x10-unorm", "astc-12x10-unorm-srgb", "astc-12x12-unorm", - "astc-12x12-unorm-srgb" + "astc-12x12-unorm-srgb", }; [Exposed=(Window, DedicatedWorker), SecureContext] @@ -396,8 +400,9 @@ interface GPUExternalTexture { }; GPUExternalTexture includes GPUObjectBase; -dictionary GPUExternalTextureDescriptor : GPUObjectDescriptorBase { - required HTMLVideoElement source; +dictionary GPUExternalTextureDescriptor + : GPUObjectDescriptorBase { + required (HTMLVideoElement or VideoFrame) source; PredefinedColorSpace colorSpace = "srgb"; }; @@ -406,7 +411,8 @@ interface GPUSampler { }; GPUSampler includes GPUObjectBase; -dictionary GPUSamplerDescriptor : GPUObjectDescriptorBase { +dictionary GPUSamplerDescriptor + : GPUObjectDescriptorBase { GPUAddressMode addressModeU = "clamp-to-edge"; GPUAddressMode addressModeV = "clamp-to-edge"; GPUAddressMode addressModeW = "clamp-to-edge"; @@ -422,17 +428,17 @@ dictionary GPUSamplerDescriptor : GPUObjectDescriptorBase { enum GPUAddressMode { "clamp-to-edge", "repeat", - "mirror-repeat" + "mirror-repeat", }; enum GPUFilterMode { "nearest", - "linear" + "linear", }; enum GPUMipmapFilterMode { "nearest", - "linear" + "linear", }; enum GPUCompareFunction { @@ -443,7 +449,7 @@ enum GPUCompareFunction { "greater", "not-equal", "greater-equal", - "always" + "always", }; [Exposed=(Window, DedicatedWorker), SecureContext] @@ -451,7 +457,8 @@ interface GPUBindGroupLayout { }; GPUBindGroupLayout includes GPUObjectBase; -dictionary GPUBindGroupLayoutDescriptor : GPUObjectDescriptorBase { +dictionary GPUBindGroupLayoutDescriptor + : GPUObjectDescriptorBase { required sequence entries; }; @@ -477,7 +484,7 @@ namespace GPUShaderStage { enum GPUBufferBindingType { "uniform", "storage", - "read-only-storage" + "read-only-storage", }; dictionary GPUBufferBindingLayout { @@ -489,7 +496,7 @@ dictionary GPUBufferBindingLayout { enum GPUSamplerBindingType { "filtering", "non-filtering", - "comparison" + "comparison", }; dictionary GPUSamplerBindingLayout { @@ -501,7 +508,7 @@ enum GPUTextureSampleType { "unfilterable-float", "depth", "sint", - "uint" + "uint", }; dictionary GPUTextureBindingLayout { @@ -511,7 +518,7 @@ dictionary GPUTextureBindingLayout { }; enum GPUStorageTextureAccess { - "write-only" + "write-only", }; dictionary GPUStorageTextureBindingLayout { @@ -528,7 +535,8 @@ interface GPUBindGroup { }; GPUBindGroup includes GPUObjectBase; -dictionary GPUBindGroupDescriptor : GPUObjectDescriptorBase { +dictionary GPUBindGroupDescriptor + : GPUObjectDescriptorBase { required GPUBindGroupLayout layout; required sequence entries; }; @@ -551,7 +559,8 @@ interface GPUPipelineLayout { }; GPUPipelineLayout includes GPUObjectBase; -dictionary GPUPipelineLayoutDescriptor : GPUObjectDescriptorBase { +dictionary GPUPipelineLayoutDescriptor + : GPUObjectDescriptorBase { required sequence bindGroupLayouts; }; @@ -561,7 +570,8 @@ interface GPUShaderModule { }; GPUShaderModule includes GPUObjectBase; -dictionary GPUShaderModuleDescriptor : GPUObjectDescriptorBase { +dictionary GPUShaderModuleDescriptor + : GPUObjectDescriptorBase { required USVString code; object sourceMap; record hints; @@ -574,7 +584,7 @@ dictionary GPUShaderModuleCompilationHint { enum GPUCompilationMessageType { "error", "warning", - "info" + "info", }; [Exposed=(Window, DedicatedWorker), Serializable, SecureContext] @@ -604,14 +614,15 @@ dictionary GPUPipelineErrorInit { enum GPUPipelineErrorReason { "validation", - "internal" + "internal", }; enum GPUAutoLayoutMode { - "auto" + "auto", }; -dictionary GPUPipelineDescriptorBase : GPUObjectDescriptorBase { +dictionary GPUPipelineDescriptorBase + : GPUObjectDescriptorBase { required (GPUPipelineLayout or GPUAutoLayoutMode) layout; }; @@ -633,7 +644,8 @@ interface GPUComputePipeline { GPUComputePipeline includes GPUObjectBase; GPUComputePipeline includes GPUPipelineBase; -dictionary GPUComputePipelineDescriptor : GPUPipelineDescriptorBase { +dictionary GPUComputePipelineDescriptor + : GPUPipelineDescriptorBase { required GPUProgrammableStage compute; }; @@ -643,7 +655,8 @@ interface GPURenderPipeline { GPURenderPipeline includes GPUObjectBase; GPURenderPipeline includes GPUPipelineBase; -dictionary GPURenderPipelineDescriptor : GPUPipelineDescriptorBase { +dictionary GPURenderPipelineDescriptor + : GPUPipelineDescriptorBase { required GPUVertexState vertex; GPUPrimitiveState primitive = {}; GPUDepthStencilState depthStencil; @@ -666,18 +679,18 @@ enum GPUPrimitiveTopology { "line-list", "line-strip", "triangle-list", - "triangle-strip" + "triangle-strip", }; enum GPUFrontFace { "ccw", - "cw" + "cw", }; enum GPUCullMode { "none", "front", - "back" + "back", }; dictionary GPUMultisampleState { @@ -686,7 +699,8 @@ dictionary GPUMultisampleState { boolean alphaToCoverageEnabled = false; }; -dictionary GPUFragmentState : GPUProgrammableStage { +dictionary GPUFragmentState + : GPUProgrammableStage { required sequence targets; }; @@ -731,7 +745,7 @@ enum GPUBlendFactor { "one-minus-dst-alpha", "src-alpha-saturated", "constant", - "one-minus-constant" + "one-minus-constant", }; enum GPUBlendOperation { @@ -739,7 +753,7 @@ enum GPUBlendOperation { "subtract", "reverse-subtract", "min", - "max" + "max", }; dictionary GPUDepthStencilState { @@ -774,12 +788,12 @@ enum GPUStencilOperation { "increment-clamp", "decrement-clamp", "increment-wrap", - "decrement-wrap" + "decrement-wrap", }; enum GPUIndexFormat { "uint16", - "uint32" + "uint32", }; enum GPUVertexFormat { @@ -812,15 +826,16 @@ enum GPUVertexFormat { "sint32", "sint32x2", "sint32x3", - "sint32x4" + "sint32x4", }; enum GPUVertexStepMode { "vertex", - "instance" + "instance", }; -dictionary GPUVertexState : GPUProgrammableStage { +dictionary GPUVertexState + : GPUProgrammableStage { sequence buffers = []; }; @@ -837,17 +852,43 @@ dictionary GPUVertexAttribute { required GPUIndex32 shaderLocation; }; -dictionary GPUImageDataLayout { GPUSize64 offset = 0; GPUSize32 bytesPerRow; GPUSize32 rowsPerImage;}; -dictionary GPUImageCopyBuffer : GPUImageDataLayout { required GPUBuffer buffer;}; -dictionary GPUImageCopyTexture { required GPUTexture texture; GPUIntegerCoordinate mipLevel = 0; GPUOrigin3D origin = {}; GPUTextureAspect aspect = "all";}; -dictionary GPUImageCopyTextureTagged : GPUImageCopyTexture { PredefinedColorSpace colorSpace = "srgb"; boolean premultipliedAlpha = false;}; -dictionary GPUImageCopyExternalImage { required (ImageBitmap or HTMLVideoElement or HTMLCanvasElement or OffscreenCanvas) source; GPUOrigin2D origin = {}; boolean flipY = false;}; +dictionary GPUImageDataLayout { + GPUSize64 offset = 0; + GPUSize32 bytesPerRow; + GPUSize32 rowsPerImage; +}; + +dictionary GPUImageCopyBuffer + : GPUImageDataLayout { + required GPUBuffer buffer; +}; + +dictionary GPUImageCopyTexture { + required GPUTexture texture; + GPUIntegerCoordinate mipLevel = 0; + GPUOrigin3D origin = {}; + GPUTextureAspect aspect = "all"; +}; + +dictionary GPUImageCopyTextureTagged + : GPUImageCopyTexture { + PredefinedColorSpace colorSpace = "srgb"; + boolean premultipliedAlpha = false; +}; + +dictionary GPUImageCopyExternalImage { + required (ImageBitmap or HTMLVideoElement or HTMLCanvasElement or OffscreenCanvas) source; + GPUOrigin2D origin = {}; + boolean flipY = false; +}; + [Exposed=(Window, DedicatedWorker), SecureContext] interface GPUCommandBuffer { }; GPUCommandBuffer includes GPUObjectBase; -dictionary GPUCommandBufferDescriptor : GPUObjectDescriptorBase { +dictionary GPUCommandBufferDescriptor + : GPUObjectDescriptorBase { }; interface mixin GPUCommandsMixin { @@ -900,14 +941,15 @@ GPUCommandEncoder includes GPUObjectBase; GPUCommandEncoder includes GPUCommandsMixin; GPUCommandEncoder includes GPUDebugCommandsMixin; -dictionary GPUCommandEncoderDescriptor : GPUObjectDescriptorBase { +dictionary GPUCommandEncoderDescriptor + : GPUObjectDescriptorBase { }; interface mixin GPUBindingCommandsMixin { - undefined setBindGroup(GPUIndex32 index, GPUBindGroup bindGroup, + undefined setBindGroup(GPUIndex32 index, GPUBindGroup? bindGroup, optional sequence dynamicOffsets = []); - undefined setBindGroup(GPUIndex32 index, GPUBindGroup bindGroup, + undefined setBindGroup(GPUIndex32 index, GPUBindGroup? bindGroup, Uint32Array dynamicOffsetsData, GPUSize64 dynamicOffsetsDataStart, GPUSize32 dynamicOffsetsDataLength); @@ -932,21 +974,15 @@ GPUComputePassEncoder includes GPUCommandsMixin; GPUComputePassEncoder includes GPUDebugCommandsMixin; GPUComputePassEncoder includes GPUBindingCommandsMixin; -enum GPUComputePassTimestampLocation { - "beginning", - "end" -}; - -dictionary GPUComputePassTimestampWrite { +dictionary GPUComputePassTimestampWrites { required GPUQuerySet querySet; - required GPUSize32 queryIndex; - required GPUComputePassTimestampLocation location; + GPUSize32 beginningOfPassWriteIndex; + GPUSize32 endOfPassWriteIndex; }; -typedef sequence GPUComputePassTimestampWrites; - -dictionary GPUComputePassDescriptor : GPUObjectDescriptorBase { - GPUComputePassTimestampWrites timestampWrites = []; +dictionary GPUComputePassDescriptor + : GPUObjectDescriptorBase { + GPUComputePassTimestampWrites timestampWrites; }; [Exposed=(Window, DedicatedWorker), SecureContext] @@ -973,24 +1009,18 @@ GPURenderPassEncoder includes GPUDebugCommandsMixin; GPURenderPassEncoder includes GPUBindingCommandsMixin; GPURenderPassEncoder includes GPURenderCommandsMixin; -enum GPURenderPassTimestampLocation { - "beginning", - "end" -}; - -dictionary GPURenderPassTimestampWrite { +dictionary GPURenderPassTimestampWrites { required GPUQuerySet querySet; - required GPUSize32 queryIndex; - required GPURenderPassTimestampLocation location; + GPUSize32 beginningOfPassWriteIndex; + GPUSize32 endOfPassWriteIndex; }; -typedef sequence GPURenderPassTimestampWrites; - -dictionary GPURenderPassDescriptor : GPUObjectDescriptorBase { +dictionary GPURenderPassDescriptor + : GPUObjectDescriptorBase { required sequence colorAttachments; GPURenderPassDepthStencilAttachment depthStencilAttachment; GPUQuerySet occlusionQuerySet; - GPURenderPassTimestampWrites timestampWrites = []; + GPURenderPassTimestampWrites timestampWrites; GPUSize64 maxDrawCount = 50000000; }; @@ -1019,15 +1049,16 @@ dictionary GPURenderPassDepthStencilAttachment { enum GPULoadOp { "load", - "clear" + "clear", }; enum GPUStoreOp { "store", - "discard" + "discard", }; -dictionary GPURenderPassLayout : GPUObjectDescriptorBase { +dictionary GPURenderPassLayout + : GPUObjectDescriptorBase { required sequence colorFormats; GPUTextureFormat depthStencilFormat; GPUSize32 sampleCount = 1; @@ -1037,7 +1068,7 @@ interface mixin GPURenderCommandsMixin { undefined setPipeline(GPURenderPipeline pipeline); undefined setIndexBuffer(GPUBuffer buffer, GPUIndexFormat indexFormat, optional GPUSize64 offset = 0, optional GPUSize64 size); - undefined setVertexBuffer(GPUIndex32 slot, GPUBuffer buffer, optional GPUSize64 offset = 0, optional GPUSize64 size); + undefined setVertexBuffer(GPUIndex32 slot, GPUBuffer? buffer, optional GPUSize64 offset = 0, optional GPUSize64 size); undefined draw(GPUSize32 vertexCount, optional GPUSize32 instanceCount = 1, optional GPUSize32 firstVertex = 0, optional GPUSize32 firstInstance = 0); @@ -1055,7 +1086,8 @@ interface GPURenderBundle { }; GPURenderBundle includes GPUObjectBase; -dictionary GPURenderBundleDescriptor : GPUObjectDescriptorBase { +dictionary GPURenderBundleDescriptor + : GPUObjectDescriptorBase { }; [Exposed=(Window, DedicatedWorker), SecureContext] @@ -1068,12 +1100,14 @@ GPURenderBundleEncoder includes GPUDebugCommandsMixin; GPURenderBundleEncoder includes GPUBindingCommandsMixin; GPURenderBundleEncoder includes GPURenderCommandsMixin; -dictionary GPURenderBundleEncoderDescriptor : GPURenderPassLayout { +dictionary GPURenderBundleEncoderDescriptor + : GPURenderPassLayout { boolean depthReadOnly = false; boolean stencilReadOnly = false; }; -dictionary GPUQueueDescriptor : GPUObjectDescriptorBase { +dictionary GPUQueueDescriptor + : GPUObjectDescriptorBase { }; [Exposed=(Window, DedicatedWorker), SecureContext] @@ -1107,18 +1141,19 @@ interface GPUQuerySet { undefined destroy(); readonly attribute GPUQueryType type; - readonly attribute GPUSize32 count; + readonly attribute GPUSize32Out count; }; GPUQuerySet includes GPUObjectBase; -dictionary GPUQuerySetDescriptor : GPUObjectDescriptorBase { +dictionary GPUQuerySetDescriptor + : GPUObjectDescriptorBase { required GPUQueryType type; required GPUSize32 count; }; enum GPUQueryType { "occlusion", - "timestamp" + "timestamp", }; [Exposed=(Window, DedicatedWorker), SecureContext] @@ -1133,7 +1168,7 @@ interface GPUCanvasContext { enum GPUCanvasAlphaMode { "opaque", - "premultiplied" + "premultiplied", }; dictionary GPUCanvasConfiguration { @@ -1147,7 +1182,7 @@ dictionary GPUCanvasConfiguration { enum GPUDeviceLostReason { "unknown", - "destroyed" + "destroyed", }; [Exposed=(Window, DedicatedWorker), SecureContext] @@ -1166,24 +1201,27 @@ interface GPUError { }; [Exposed=(Window, DedicatedWorker), SecureContext] -interface GPUValidationError : GPUError { +interface GPUValidationError + : GPUError { constructor(DOMString message); }; [Exposed=(Window, DedicatedWorker), SecureContext] -interface GPUOutOfMemoryError : GPUError { +interface GPUOutOfMemoryError + : GPUError { constructor(DOMString message); }; [Exposed=(Window, DedicatedWorker), SecureContext] -interface GPUInternalError : GPUError { +interface GPUInternalError + : GPUError { constructor(DOMString message); }; enum GPUErrorFilter { "validation", "out-of-memory", - "internal" + "internal", }; partial interface GPUDevice { @@ -1220,6 +1258,10 @@ typedef [EnforceRange] unsigned long GPUIndex32; typedef [EnforceRange] unsigned long GPUSize32; typedef [EnforceRange] long GPUSignedOffset32; +typedef unsigned long long GPUSize64Out; +typedef unsigned long GPUIntegerCoordinateOut; +typedef unsigned long GPUSize32Out; + typedef unsigned long GPUFlagsConstant; dictionary GPUColorDict { diff --git a/test/wpt/tests/interfaces/webnn.idl b/test/wpt/tests/interfaces/webnn.idl index 2c2ab35e909..17e30803b88 100644 --- a/test/wpt/tests/interfaces/webnn.idl +++ b/test/wpt/tests/interfaces/webnn.idl @@ -127,10 +127,10 @@ interface MLGraphBuilder { constructor(MLContext context); // Create an operand for a graph input. - MLOperand input(DOMString name, MLOperandDescriptor desc); + MLOperand input(DOMString name, MLOperandDescriptor descriptor); // Create an operand for a graph constant. - MLOperand constant(MLOperandDescriptor desc, MLBufferView bufferView); + MLOperand constant(MLOperandDescriptor descriptor, MLBufferView bufferView); // Create a single-value operand from the specified number of the specified type. MLOperand constant(double value, optional MLOperandType type = "float32"); @@ -489,13 +489,8 @@ partial interface MLGraphBuilder { MLActivation sigmoid(); }; -dictionary MLSliceOptions { - sequence axes; -}; - partial interface MLGraphBuilder { - MLOperand slice(MLOperand input, sequence starts, sequence sizes, - optional MLSliceOptions options = {}); + MLOperand slice(MLOperand input, sequence starts, sequence sizes); }; partial interface MLGraphBuilder { diff --git a/test/wpt/tests/interfaces/webrtc-encoded-transform.idl b/test/wpt/tests/interfaces/webrtc-encoded-transform.idl index e48f1080c41..6dd2ba3fffa 100644 --- a/test/wpt/tests/interfaces/webrtc-encoded-transform.idl +++ b/test/wpt/tests/interfaces/webrtc-encoded-transform.idl @@ -27,7 +27,7 @@ typedef [EnforceRange] unsigned long long SmallCryptoKeyID; typedef (SmallCryptoKeyID or bigint) CryptoKeyID; [Exposed=(Window,DedicatedWorker)] -interface SFrameTransform { +interface SFrameTransform : EventTarget { constructor(optional SFrameTransformOptions options = {}); Promise setEncryptionKey(CryptoKey key, optional CryptoKeyID keyID); attribute EventHandler onerror; @@ -72,7 +72,8 @@ dictionary RTCEncodedVideoFrameMetadata { unsigned long temporalIndex; unsigned long synchronizationSource; octet payloadType; - sequence contributingSources; + sequence contributingSources; + long long timestamp; // microseconds }; // New interfaces to define encoded video and audio frames. Will eventually diff --git a/test/wpt/tests/interfaces/webrtc-stats.idl b/test/wpt/tests/interfaces/webrtc-stats.idl index 7e820a26df4..b398c73e3f4 100644 --- a/test/wpt/tests/interfaces/webrtc-stats.idl +++ b/test/wpt/tests/interfaces/webrtc-stats.idl @@ -13,8 +13,6 @@ enum RTCStatsType { "media-playout", "peer-connection", "data-channel", -"stream", -"track", "transport", "candidate-pair", "local-candidate", @@ -94,6 +92,8 @@ dictionary RTCInboundRtpStreamStats : RTCReceivedRtpStreamStats { boolean powerEfficientDecoder; unsigned long framesAssembledFromMultiplePackets; double totalAssemblyTime; + unsigned long long retransmittedPacketsReceived; + unsigned long long retransmittedBytesReceived; }; dictionary RTCRemoteInboundRtpStreamStats : RTCReceivedRtpStreamStats { diff --git a/test/wpt/tests/interfaces/webrtc.idl b/test/wpt/tests/interfaces/webrtc.idl index 578cbe92974..4c31d3be67a 100644 --- a/test/wpt/tests/interfaces/webrtc.idl +++ b/test/wpt/tests/interfaces/webrtc.idl @@ -441,13 +441,13 @@ enum RTCIceGathererState { }; enum RTCIceTransportState { + "closed", + "failed", + "disconnected", "new", "checking", - "connected", "completed", - "disconnected", - "failed", - "closed" + "connected" }; enum RTCIceRole { diff --git a/test/wpt/tests/interfaces/webtransport.idl b/test/wpt/tests/interfaces/webtransport.idl index 2bea483e1b9..a9f514e2366 100644 --- a/test/wpt/tests/interfaces/webtransport.idl +++ b/test/wpt/tests/interfaces/webtransport.idl @@ -98,6 +98,7 @@ dictionary WebTransportDatagramStats { [Exposed=(Window,Worker), SecureContext, Transferable] interface WebTransportSendStream : WritableStream { + attribute long long? sendOrder; Promise getStats(); }; @@ -130,12 +131,12 @@ interface WebTransportError : DOMException { constructor(optional DOMString message = "", optional WebTransportErrorOptions options = {}); readonly attribute WebTransportErrorSource source; - readonly attribute octet? streamErrorCode; + readonly attribute unsigned long? streamErrorCode; }; dictionary WebTransportErrorOptions { WebTransportErrorSource source = "stream"; - [Clamp] octet? streamErrorCode = null; + [Clamp] unsigned long? streamErrorCode = null; }; enum WebTransportErrorSource { diff --git a/test/wpt/tests/interfaces/webxrlayers.idl b/test/wpt/tests/interfaces/webxrlayers.idl index e182f47b9c5..c8b3a71c699 100644 --- a/test/wpt/tests/interfaces/webxrlayers.idl +++ b/test/wpt/tests/interfaces/webxrlayers.idl @@ -11,6 +11,12 @@ enum XRLayerLayout { "stereo-top-bottom" }; +enum XRLayerQuality { + "default", + "text-optimized", + "graphics-optimized" +}; + [Exposed=Window] interface XRCompositionLayer : XRLayer { readonly attribute XRLayerLayout layout; @@ -18,6 +24,7 @@ enum XRLayerLayout { attribute boolean forceMonoPresentation; attribute float opacity; readonly attribute unsigned long mipLevels; + attribute XRLayerQuality quality; readonly attribute boolean needsRedraw; @@ -106,6 +113,7 @@ dictionary XRProjectionLayerInit { GLenum colorFormat = 0x1908; // RGBA GLenum depthFormat = 0x1902; // DEPTH_COMPONENT double scaleFactor = 1.0; + boolean clearOnAccess = true; }; dictionary XRLayerInit { @@ -117,6 +125,7 @@ dictionary XRLayerInit { required unsigned long viewPixelHeight; XRLayerLayout layout = "mono"; boolean isStatic = false; + boolean clearOnAccess = true; }; dictionary XRQuadLayerInit : XRLayerInit { diff --git a/test/wpt/tests/lint.ignore b/test/wpt/tests/lint.ignore index 6f8ec10470b..489c717cd6a 100644 --- a/test/wpt/tests/lint.ignore +++ b/test/wpt/tests/lint.ignore @@ -48,6 +48,7 @@ TRAILING WHITESPACE, INDENT TABS, CR AT EOL: *.bmp TRAILING WHITESPACE, INDENT TABS, CR AT EOL: *.sxg TRAILING WHITESPACE, INDENT TABS, CR AT EOL: *.wbn TRAILING WHITESPACE, INDENT TABS, CR AT EOL: *.avif +TRAILING WHITESPACE, INDENT TABS, CR AT EOL: *.annexb ## .gitignore W3C-TEST.ORG: .gitignore @@ -225,7 +226,11 @@ SET TIMEOUT: service-workers/service-worker/activation.https.html SET TIMEOUT: service-workers/service-worker/fetch-frame-resource.https.html SET TIMEOUT: service-workers/service-worker/fetch-request-redirect.https.html SET TIMEOUT: service-workers/service-worker/fetch-waits-for-activate.https.html +SET TIMEOUT: service-workers/service-worker/postMessage-client-worker.js SET TIMEOUT: service-workers/service-worker/update-recovery.https.html +SET TIMEOUT: service-workers/service-worker/resources/controlled-frame-postMessage.html +SET TIMEOUT: service-workers/service-worker/resources/controlled-worker-late-postMessage.js +SET TIMEOUT: service-workers/service-worker/resources/controlled-worker-postMessage.js SET TIMEOUT: service-workers/service-worker/resources/extendable-event-async-waituntil.js SET TIMEOUT: service-workers/service-worker/resources/fetch-event-async-respond-with-worker.js SET TIMEOUT: service-workers/service-worker/resources/fetch-event-test-worker.js @@ -247,11 +252,16 @@ SET TIMEOUT: webaudio/the-audio-api/the-mediaelementaudiosourcenode-interface/me SET TIMEOUT: webauthn/*timeout.https.html SET TIMEOUT: webdriver/* SET TIMEOUT: webmessaging/* +SET TIMEOUT: webrtc-encoded-transform/script-metadata-transform-worker.js +SET TIMEOUT: webrtc-encoded-transform/script-transform-generateKeyFrame.js +SET TIMEOUT: webrtc-encoded-transform/script-transform-sendKeyFrameRequest.js SET TIMEOUT: webstorage/eventTestHarness.js SET TIMEOUT: webvtt/* SET TIMEOUT: workers/* SET TIMEOUT: xhr/resources/init.htm SET TIMEOUT: xhr/resources/xmlhttprequest-timeout.js +SET TIMEOUT: fenced-frame/resolve-to-config-promise.https.html +SET TIMEOUT: credential-management/support/fedcm-iframe.html # generate_tests implementation and sample usage GENERATE_TESTS: resources/test/tests/functional/generate-callback.html @@ -361,7 +371,6 @@ SET TIMEOUT: speculation-rules/prerender/resources/media-autoplay-attribute.html SET TIMEOUT: speculation-rules/prerender/resources/media-play.html SET TIMEOUT: html/browsers/browsing-the-web/back-forward-cache/timers.html SET TIMEOUT: dom/abort/crashtests/timeout-close.html -SET TIMEOUT: common/rendering-utils.js # setTimeout use in reftests SET TIMEOUT: acid/acid3/test.html @@ -427,8 +436,12 @@ TRAILING WHITESPACE: css/css-fonts/support/fonts/gsubtest-lookup3.ufo/features.f SET TIMEOUT: css/compositing/mix-blend-mode/mix-blend-mode-parent-with-3D-transform-and-transition.html SET TIMEOUT: css/compositing/mix-blend-mode/mix-blend-mode-sibling-with-3D-transform-and-transition.html +SET TIMEOUT: css/css-backgrounds/color-mix-currentcolor-background-repaint-parent.html +SET TIMEOUT: css/css-backgrounds/color-mix-currentcolor-background-repaint.html SET TIMEOUT: css/css-backgrounds/color-mix-currentcolor-border-repaint-parent.html SET TIMEOUT: css/css-backgrounds/color-mix-currentcolor-border-repaint.html +SET TIMEOUT: css/css-backgrounds/color-mix-currentcolor-outline-repaint-parent.html +SET TIMEOUT: css/css-backgrounds/color-mix-currentcolor-outline-repaint.html SET TIMEOUT: css/css-backgrounds/currentcolor-border-repaint-parent.html SET TIMEOUT: css/css-transitions/events-007.html SET TIMEOUT: css/css-transitions/support/generalParallelTest.js @@ -593,7 +606,7 @@ AHEM SYSTEM FONT: acid/acid3/test.html AHEM SYSTEM FONT: resource-timing/font-timestamps.html AHEM SYSTEM FONT: resource-timing/initiator-type/style.html AHEM SYSTEM FONT: resource-timing/resources/iframe-reload-TAO.sub.html -AHEM SYSTEM FONT: html/canvas/element/drawing-text-to-the-canvas/2d.text.measure.fontBoundingBox.ahem.html +AHEM SYSTEM FONT: html/canvas/element/text/2d.text.measure.fontBoundingBox.ahem.html AHEM SYSTEM FONT: css/css-font-loading/fontface-override-descriptors.html AHEM SYSTEM FONT: css/css-font-loading/fontface-size-adjust-descriptor.html AHEM SYSTEM FONT: css/css-font-loading/fontface-size-adjust-descriptor-ref.html @@ -602,7 +615,12 @@ AHEM SYSTEM FONT: css/css-fonts/font-size-adjust-012.html AHEM SYSTEM FONT: css/css-fonts/font-size-adjust-012-ref.html AHEM SYSTEM FONT: css/css-fonts/font-size-adjust-013.html AHEM SYSTEM FONT: css/css-fonts/font-size-adjust-013-ref.html +AHEM SYSTEM FONT: css/css-fonts/font-size-adjust-014.html +AHEM SYSTEM FONT: css/css-fonts/font-size-adjust-014-ref.html +AHEM SYSTEM FONT: css/css-fonts/font-size-adjust-metrics-override.html +AHEM SYSTEM FONT: css/css-fonts/font-size-adjust-metrics-override-ref.html AHEM SYSTEM FONT: css/css-fonts/line-gap-override.html +AHEM SYSTEM FONT: css/css-fonts/parsing/font-size-adjust-computed.html AHEM SYSTEM FONT: html/dom/render-blocking/remove-attr-unblocks-rendering.optional.html AHEM SYSTEM FONT: html/dom/render-blocking/remove-element-unblocks-rendering.optional.html @@ -655,8 +673,8 @@ TESTHARNESS-IN-OTHER-TYPE: svg/svg-in-svg/svg-in-svg-circular-filter-reference-c # Adding the testharnessreport.js script causes the test to never complete. MISSING-TESTHARNESSREPORT: accessibility/crashtests/computed-node-checked.html -PRINT STATEMENT: webdriver/tests/print/* PRINT STATEMENT: webdriver/tests/bidi/browsing_context/print/* +PRINT STATEMENT: webdriver/tests/classic/print/* PRINT STATEMENT: webdriver/tests/support/fixtures_bidi.py DUPLICATE-BASENAME-PATH: acid/acid3/empty.html @@ -669,6 +687,10 @@ DUPLICATE-BASENAME-PATH: svg/struct/reftests/reference/green-100x100.svg SET TIMEOUT: mediacapture-insertable-streams/MediaStreamTrackProcessor-video.https.html +# This is a subresource which cannot use step_timeout without becoming a test +# itself. See https://github.com/web-platform-tests/wpt/issues/16933 +SET TIMEOUT: scroll-to-text-fragment/iframe-target.html + # Ported crashtests from Mozilla SET TIMEOUT: editing/crashtests/backcolor-in-nested-editing-host-td-from-DOMAttrModified.html SET TIMEOUT: editing/crashtests/contenteditable-will-be-blurred-by-focus-event-listener.html diff --git a/test/wpt/tests/resources/chromium/webusb-test.js b/test/wpt/tests/resources/chromium/webusb-test.js index 94ff1bcadd9..7cca63d9196 100644 --- a/test/wpt/tests/resources/chromium/webusb-test.js +++ b/test/wpt/tests/resources/chromium/webusb-test.js @@ -440,11 +440,11 @@ class FakeWebUsbService { } } - getPermission(deviceFilters) { + getPermission(options) { return new Promise(resolve => { if (navigator.usb.test.onrequestdevice) { navigator.usb.test.onrequestdevice( - new USBDeviceRequestEvent(deviceFilters, resolve)); + new USBDeviceRequestEvent(options, resolve)); } else { resolve({ result: null }); } @@ -457,8 +457,9 @@ class FakeWebUsbService { } class USBDeviceRequestEvent { - constructor(deviceFilters, resolve) { - this.filters = convertMojoDeviceFilters(deviceFilters); + constructor(options, resolve) { + this.filters = convertMojoDeviceFilters(options.filters); + this.exclusionFilters = convertMojoDeviceFilters(options.exclusionFilters); this.resolveFunc_ = resolve; } diff --git a/test/wpt/tests/resources/chromium/webxr-test.js b/test/wpt/tests/resources/chromium/webxr-test.js index ab2c6faa0ee..c5eb1bd14b8 100644 --- a/test/wpt/tests/resources/chromium/webxr-test.js +++ b/test/wpt/tests/resources/chromium/webxr-test.js @@ -985,8 +985,6 @@ class MockRuntime { environmentProviderRequest.handle); } - setInputSourceButtonListener(listener) { listener.$.close(); } - // XREnvironmentIntegrationProvider implementation: subscribeToHitTest(nativeOriginInformation, entityTypes, ray) { if (!this.supportedModes_.includes(xrSessionMojom.XRSessionMode.kImmersiveAr)) { @@ -1212,7 +1210,6 @@ class MockRuntime { clientReceiver: clientReceiver, enabledFeatures: enabled_features, deviceConfig: { - usesInputEventing: false, defaultFramebufferScale: this.defaultFramebufferScale_, supportsViewportScaling: true, depthConfiguration: diff --git a/test/wpt/tests/resources/idlharness.js b/test/wpt/tests/resources/idlharness.js index 46aa11e5ca1..8f741b09b26 100644 --- a/test/wpt/tests/resources/idlharness.js +++ b/test/wpt/tests/resources/idlharness.js @@ -2357,12 +2357,13 @@ IdlInterface.prototype.do_member_operation_asserts = function(memberHolderObject assert_equals(typeof memberHolderObject[member.name], "function", "property must be a function"); - const ctors = this.members.filter(function(m) { - return m.type == "operation" && m.name == member.name; + const operationOverloads = this.members.filter(function(m) { + return m.type == "operation" && m.name == member.name && + (m.special === "static") === (member.special === "static"); }); assert_equals( memberHolderObject[member.name].length, - minOverloadLength(ctors), + minOverloadLength(operationOverloads), "property has wrong .length"); assert_equals( memberHolderObject[member.name].name, diff --git a/test/wpt/tests/resources/test/tests/functional/assert-throws-dom.html b/test/wpt/tests/resources/test/tests/functional/assert-throws-dom.html new file mode 100644 index 00000000000..4dd66b2372a --- /dev/null +++ b/test/wpt/tests/resources/test/tests/functional/assert-throws-dom.html @@ -0,0 +1,55 @@ + +assert_throws_dom + + +
+ + + diff --git a/test/wpt/tests/resources/test/tests/functional/idlharness/IdlDictionary/test_partial_interface_of.html b/test/wpt/tests/resources/test/tests/functional/idlharness/IdlDictionary/test_partial_interface_of.html index 05e6e0b1e06..f635768c69f 100644 --- a/test/wpt/tests/resources/test/tests/functional/idlharness/IdlDictionary/test_partial_interface_of.html +++ b/test/wpt/tests/resources/test/tests/functional/idlharness/IdlDictionary/test_partial_interface_of.html @@ -3,7 +3,6 @@ - idlharness: Partial dictionary diff --git a/test/wpt/tests/resources/test/tests/functional/idlharness/IdlInterface/test_partial_interface_of.html b/test/wpt/tests/resources/test/tests/functional/idlharness/IdlInterface/test_partial_interface_of.html index 671196cc5df..7dd9e676af4 100644 --- a/test/wpt/tests/resources/test/tests/functional/idlharness/IdlInterface/test_partial_interface_of.html +++ b/test/wpt/tests/resources/test/tests/functional/idlharness/IdlInterface/test_partial_interface_of.html @@ -3,7 +3,6 @@ - idlharness: Partial interface diff --git a/test/wpt/tests/resources/test/tox.ini b/test/wpt/tests/resources/test/tox.ini index 4fbeb67fb52..12013a1a705 100644 --- a/test/wpt/tests/resources/test/tox.ini +++ b/test/wpt/tests/resources/test/tox.ini @@ -1,5 +1,5 @@ [tox] -envlist = py37,py38,py39,py310 +envlist = py37,py38,py39,py310,py311 skipsdist=True [testenv] diff --git a/test/wpt/tests/resources/testdriver.js b/test/wpt/tests/resources/testdriver.js index 446b033b0a4..a23d6eaf4cf 100644 --- a/test/wpt/tests/resources/testdriver.js +++ b/test/wpt/tests/resources/testdriver.js @@ -296,12 +296,6 @@ inline: "nearest"}); } - var pointerInteractablePaintTree = getPointerInteractablePaintTree(element); - if (pointerInteractablePaintTree.length === 0 || - !element.contains(pointerInteractablePaintTree[0])) { - return Promise.reject(new Error("element send_keys intercepted error")); - } - return window.test_driver_internal.send_keys(element, keys); }, @@ -334,9 +328,9 @@ * to run the call, or null for the current * browsing context. * - * @returns {Promise} fulfilled with the previous {@link - * https://www.w3.org/TR/webdriver/#dfn-windowrect-object|WindowRect} - * value, after the window is minimized. + * @returns {Promise} fulfilled with the previous `WindowRect + * `_ + * value, after the window is minimized. */ minimize_window: function(context=null) { return window.test_driver_internal.minimize_window(context); @@ -349,8 +343,8 @@ * `_ * WebDriver command * - * @param {Object} rect - A {@link - * https://www.w3.org/TR/webdriver/#dfn-windowrect-object|WindowRect} + * @param {Object} rect - A `WindowRect + * `_ * @param {WindowProxy} context - Browsing context in which * to run the call, or null for the current * browsing context. @@ -680,6 +674,134 @@ set_spc_transaction_mode: function(mode, context=null) { return window.test_driver_internal.set_spc_transaction_mode(mode, context); }, + + /** + * Cancels the Federated Credential Management dialog + * + * Matches the `Cancel dialog + * `_ + * WebDriver command. + * + * @param {WindowProxy} context - Browsing context in which + * to run the call, or null for the current + * browsing context. + * + * @returns {Promise} Fulfilled after the dialog is canceled, or rejected + * in case the WebDriver command errors + */ + cancel_fedcm_dialog: function(context=null) { + return window.test_driver_internal.cancel_fedcm_dialog(context); + }, + + /** + * Selects an account from the Federated Credential Management dialog + * + * Matches the `Select account + * `_ + * WebDriver command. + * + * @param {number} account_index - Index of the account to select. + * @param {WindowProxy} context - Browsing context in which + * to run the call, or null for the current + * browsing context. + * + * @returns {Promise} Fulfilled after the account is selected, + * or rejected in case the WebDriver command errors + */ + select_fedcm_account: function(account_index, context=null) { + return window.test_driver_internal.select_fedcm_account(account_index, context); + }, + + /** + * Gets the account list from the Federated Credential Management dialog + * + * Matches the `Account list + * `_ + * WebDriver command. + * + * @param {WindowProxy} context - Browsing context in which + * to run the call, or null for the current + * browsing context. + * + * @returns {Promise} fulfilled after the account list is returned, or + * rejected in case the WebDriver command errors + */ + get_fedcm_account_list: function(context=null) { + return window.test_driver_internal.get_fedcm_account_list(context); + }, + + /** + * Gets the title of the Federated Credential Management dialog + * + * Matches the `Get title + * `_ + * WebDriver command. + * + * @param {WindowProxy} context - Browsing context in which + * to run the call, or null for the current + * browsing context. + * + * @returns {Promise} Fulfilled after the title is returned, or rejected + * in case the WebDriver command errors + */ + get_fedcm_dialog_title: function(context=null) { + return window.test_driver_internal.get_fedcm_dialog_title(context); + }, + + /** + * Gets the type of the Federated Credential Management dialog + * + * Matches the `Get dialog type + * `_ + * WebDriver command. + * + * @param {WindowProxy} context - Browsing context in which + * to run the call, or null for the current + * browsing context. + * + * @returns {Promise} Fulfilled after the dialog type is returned, or + * rejected in case the WebDriver command errors + */ + get_fedcm_dialog_type: function(context=null) { + return window.test_driver_internal.get_fedcm_dialog_type(context); + }, + + /** + * Sets whether promise rejection delay is enabled for the Federated Credential Management dialog + * + * Matches the `Set delay enabled + * `_ + * WebDriver command. + * + * @param {boolean} enabled - Whether to delay FedCM promise rejection. + * @param {WindowProxy} context - Browsing context in which + * to run the call, or null for the current + * browsing context. + * + * @returns {Promise} Fulfilled after the delay has been enabled or disabled, + * or rejected in case the WebDriver command errors + */ + set_fedcm_delay_enabled: function(enabled, context=null) { + return window.test_driver_internal.set_fedcm_delay_enabled(enabled, context); + }, + + /** + * Resets the Federated Credential Management dialog's cooldown + * + * Matches the `Reset cooldown + * `_ + * WebDriver command. + * + * @param {WindowProxy} context - Browsing context in which + * to run the call, or null for the current + * browsing context. + * + * @returns {Promise} Fulfilled after the cooldown has been reset, + * or rejected in case the WebDriver command errors + */ + reset_fedcm_cooldown: function(context=null) { + return window.test_driver_internal.reset_fedcm_cooldown(context); + } }; window.test_driver_internal = { @@ -805,5 +927,32 @@ throw new Error("set_spc_transaction_mode() is not implemented by testdriver-vendor.js"); }, + async cancel_fedcm_dialog(context=null) { + throw new Error("cancel_fedcm_dialog() is not implemented by testdriver-vendor.js"); + }, + + async select_fedcm_account(account_index, context=null) { + throw new Error("select_fedcm_account() is not implemented by testdriver-vendor.js"); + }, + + async get_fedcm_account_list(context=null) { + throw new Error("get_fedcm_account_list() is not implemented by testdriver-vendor.js"); + }, + + async get_fedcm_dialog_title(context=null) { + throw new Error("get_fedcm_dialog_title() is not implemented by testdriver-vendor.js"); + }, + + async get_fedcm_dialog_type(context=null) { + throw new Error("get_fedcm_dialog_type() is not implemented by testdriver-vendor.js"); + }, + + async set_fedcm_delay_enabled(enabled, context=null) { + throw new Error("set_fedcm_delay_enabled() is not implemented by testdriver-vendor.js"); + }, + + async reset_fedcm_cooldown(context=null) { + throw new Error("reset_fedcm_cooldown() is not implemented by testdriver-vendor.js"); + } }; })(); diff --git a/test/wpt/tests/resources/testharness.js b/test/wpt/tests/resources/testharness.js index 112790bb1ee..413993089be 100644 --- a/test/wpt/tests/resources/testharness.js +++ b/test/wpt/tests/resources/testharness.js @@ -1426,12 +1426,16 @@ function assert_wrapper(...args) { let status = Test.statuses.TIMEOUT; let stack = null; + let new_assert_index = null; try { if (settings.debug) { console.debug("ASSERT", name, tests.current_test && tests.current_test.name, args); } if (tests.output) { tests.set_assert(name, args); + // Remember the newly pushed assert's index, because `apply` + // below might push new asserts. + new_assert_index = tests.asserts_run.length - 1; } const rv = f.apply(undefined, args); status = Test.statuses.PASS; @@ -1445,7 +1449,7 @@ stack = get_stack(); } if (tests.output) { - tests.set_assert_status(status, stack); + tests.set_assert_status(new_assert_index, status, stack); } } } @@ -3673,8 +3677,8 @@ this.asserts_run.push(new AssertRecord(this.current_test, assert_name, args)) } - Tests.prototype.set_assert_status = function(status, stack) { - let assert_record = this.asserts_run[this.asserts_run.length - 1]; + Tests.prototype.set_assert_status = function(index, status, stack) { + let assert_record = this.asserts_run[index]; assert_record.status = status; assert_record.stack = stack; } diff --git a/test/wpt/tests/service-workers/service-worker/controlled-dedicatedworker-postMessage.https.html b/test/wpt/tests/service-workers/service-worker/controlled-dedicatedworker-postMessage.https.html new file mode 100644 index 00000000000..7e2a604621d --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/controlled-dedicatedworker-postMessage.https.html @@ -0,0 +1,44 @@ + + + + + + + + + + diff --git a/test/wpt/tests/service-workers/service-worker/controlled-iframe-postMessage.https.html b/test/wpt/tests/service-workers/service-worker/controlled-iframe-postMessage.https.html new file mode 100644 index 00000000000..8f39b7fdbf8 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/controlled-iframe-postMessage.https.html @@ -0,0 +1,67 @@ + + + + + + + + + + diff --git a/test/wpt/tests/service-workers/service-worker/detached-context.https.html b/test/wpt/tests/service-workers/service-worker/detached-context.https.html index 747a953f620..ce8e4cc8400 100644 --- a/test/wpt/tests/service-workers/service-worker/detached-context.https.html +++ b/test/wpt/tests/service-workers/service-worker/detached-context.https.html @@ -119,23 +119,6 @@ } assert_not_equals(get_navigator().serviceWorker, null); iframe.remove(); - assert_throws_js(TypeError, () => get_navigator()); - }, 'accessing navigator on a removed frame'); - -// It seems weird that about:blank and blank.html (the test above) have -// different behavior. These expectations are based on Chromium behavior, which -// might not be right. -test(t => { - const iframe = document.createElement('iframe'); - iframe.src = 'about:blank'; - document.body.appendChild(iframe); - const f = iframe.contentWindow.Function; - function get_navigator() { - return f('return navigator')(); - } assert_not_equals(get_navigator().serviceWorker, null); - iframe.remove(); - assert_equals(get_navigator().serviceWorker, null); - }, 'accessing navigator.serviceWorker on a removed about:blank frame'); - + }, 'accessing navigator on a removed frame'); diff --git a/test/wpt/tests/service-workers/service-worker/fetch-error.https.html b/test/wpt/tests/service-workers/service-worker/fetch-error.https.html index ca2f884a9b3..e9fdf574312 100644 --- a/test/wpt/tests/service-workers/service-worker/fetch-error.https.html +++ b/test/wpt/tests/service-workers/service-worker/fetch-error.https.html @@ -21,12 +21,8 @@ const iframe = await with_iframe(scope); test.add_cleanup(() => iframe.remove()); const response = await iframe.contentWindow.fetch("fetch-error-test"); - try { - await response.text(); - assert_unreached(); - } catch (error) { - assert_true(error.message.includes("Sorry")); - } + return promise_rejects_js(test, iframe.contentWindow.TypeError, + response.text(), 'text() should reject'); }, "Make sure a load that makes progress does not time out"); diff --git a/test/wpt/tests/service-workers/service-worker/navigation-redirect.https.html b/test/wpt/tests/service-workers/service-worker/navigation-redirect.https.html index d7d3d5259a4..a87de569316 100644 --- a/test/wpt/tests/service-workers/service-worker/navigation-redirect.https.html +++ b/test/wpt/tests/service-workers/service-worker/navigation-redirect.https.html @@ -1,8 +1,8 @@ Service Worker: Navigation redirection - - + + diff --git a/test/wpt/tests/service-workers/service-worker/navigation-timing.https.html b/test/wpt/tests/service-workers/service-worker/navigation-timing.https.html index 6b51a5c2da2..75cab40458c 100644 --- a/test/wpt/tests/service-workers/service-worker/navigation-timing.https.html +++ b/test/wpt/tests/service-workers/service-worker/navigation-timing.https.html @@ -54,6 +54,7 @@ t.add_cleanup(() => frame.remove()); const timing = await navigate_in_frame(frame, scope); + assert_greater_than(timing.workerStart, 0); verify(timing); }, 'Service worker controlled navigation timing network fallback'); diff --git a/test/wpt/tests/service-workers/service-worker/partitioned-cookies.tentative.https.html b/test/wpt/tests/service-workers/service-worker/partitioned-cookies.tentative.https.html index 6744edc0eac..5f6371cb428 100644 --- a/test/wpt/tests/service-workers/service-worker/partitioned-cookies.tentative.https.html +++ b/test/wpt/tests/service-workers/service-worker/partitioned-cookies.tentative.https.html @@ -23,6 +23,14 @@ const scope = './resources/partitioned-cookies-' const absolute_scope = new URL(scope, window.location).href; + // Set a Partitioned cookie. + document.cookie = '__Host-partitioned=123; Secure; Path=/; SameSite=None; Partitioned;'; + assert_true(document.cookie.includes('__Host-partitioned=123')); + + // Set an unpartitioned cookie. + document.cookie = 'unpartitioned=456; Secure; Path=/; SameSite=None;'; + assert_true(document.cookie.includes('unpartitioned=456')); + const reg = await service_worker_unregister_and_register(t, script, scope); await wait_for_state(t, reg.installing, 'activated'); t.add_cleanup(() => reg.unregister()); @@ -55,11 +63,36 @@ await wait_promise; assert_true(got.ok, 'Message passing'); - // Set a Partitioned cookie. - document.cookie = '__Host-partitioned=123; Secure; Path=/; SameSite=None; Partitioned;'; - assert_true(document.cookie.includes('__Host-partitioned=123')); + // Test that the partitioned cookie is available to this worker via HTTP. + wait_promise = new Promise(resolve => { + resolve_wait_promise = resolve; + }); + on_message = ev => { + got = ev.data; + resolve_wait_promise(); + }; + filtered_registrations[0].active.postMessage({type: 'echo_cookies_http'}); + await wait_promise; + assert_true(got.ok, 'Get cookies'); + assert_true(got.cookies.includes('__Host-partitioned'), 'Can access partitioned cookie via HTTP'); + assert_true(got.cookies.includes('unpartitioned'), 'Can access unpartitioned cookie via HTTP'); + + // Test that the partitioned cookie is available to this worker via CookieStore API. + wait_promise = new Promise(resolve => { + resolve_wait_promise = resolve; + }); + on_message = ev => { + got = ev.data; + resolve_wait_promise(); + }; + filtered_registrations[0].active.postMessage({type: 'echo_cookies_js'}); + await wait_promise; + assert_true(got.ok, 'Get cookies'); + assert_true(got.cookies.includes('__Host-partitioned'), 'Can access partitioned cookie via JS'); + assert_true(got.cookies.includes('unpartitioned'), 'Can access unpartitioned cookie via JS'); - // Test that the partitioned cookie is available to this worker. + // Test that the partitioned cookie is not available to this worker in HTTP + // requests from importScripts. wait_promise = new Promise(resolve => { resolve_wait_promise = resolve; }); @@ -67,10 +100,11 @@ got = ev.data; resolve_wait_promise(); }; - filtered_registrations[0].active.postMessage({type: 'echo_cookies'}); + filtered_registrations[0].active.postMessage({type: 'echo_cookies_import'}); await wait_promise; assert_true(got.ok, 'Get cookies'); - assert_true(got.cookies.includes('__Host-partitioned'), 'Can access partitioned cookie'); + assert_true(got.cookies.includes('__Host-partitioned'), 'Can access partitioned cookie via importScripts'); + assert_true(got.cookies.includes('unpartitioned'), 'Can access unpartitioned cookie via importScripts'); const popup = window.open( new URL( @@ -82,4 +116,4 @@ - \ No newline at end of file + diff --git a/test/wpt/tests/service-workers/service-worker/postMessage-client-worker.js b/test/wpt/tests/service-workers/service-worker/postMessage-client-worker.js new file mode 100644 index 00000000000..64d944d2b54 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/postMessage-client-worker.js @@ -0,0 +1,23 @@ +async function doTest(e) +{ + if (e.resultingClientId) { + const promise = new Promise(async resolve => { + let counter = 0; + const client = await self.clients.get(e.resultingClientId); + if (client) + client.postMessage(counter++); + if (e.request.url.includes("repeatMessage")) { + setInterval(() => { + if (client) + client.postMessage(counter++); + }, 100); + } + setTimeout(() => { + resolve(fetch(e.request)); + }, 1000); + }); + e.respondWith(promise); + } +} + +self.addEventListener("fetch", e => e.waitUntil(doTest(e))); diff --git a/test/wpt/tests/service-workers/service-worker/resource-timing.sub.https.html b/test/wpt/tests/service-workers/service-worker/resource-timing.sub.https.html index 9808ae5ae1b..e8328f3597b 100644 --- a/test/wpt/tests/service-workers/service-worker/resource-timing.sub.https.html +++ b/test/wpt/tests/service-workers/service-worker/resource-timing.sub.https.html @@ -114,9 +114,9 @@ }); verify({ performance: performance, - resource: 'resources/missing.jpg', + resource: 'resources/missing.asis', // ORB-compatible 404 response. mode: 'cross-origin', - description: 'Network fallback cross-origin load failure', + description: 'Network fallback cross-origin load failure (404 response)', }); // Tests for respondWith(fetch()). verify({ diff --git a/test/wpt/tests/service-workers/service-worker/resources/controlled-frame-postMessage.html b/test/wpt/tests/service-workers/service-worker/resources/controlled-frame-postMessage.html new file mode 100644 index 00000000000..c4428e88a3d --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/controlled-frame-postMessage.html @@ -0,0 +1,39 @@ + + + + + diff --git a/test/wpt/tests/service-workers/service-worker/resources/controlled-worker-late-postMessage.js b/test/wpt/tests/service-workers/service-worker/resources/controlled-worker-late-postMessage.js new file mode 100644 index 00000000000..41d2db43b1f --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/controlled-worker-late-postMessage.js @@ -0,0 +1,6 @@ +setTimeout(() => { + navigator.serviceWorker.onmessage = e => self.postMessage(e.data); +}, 500); +setTimeout(() => { + self.postMessage("No message received"); +}, 5000); diff --git a/test/wpt/tests/service-workers/service-worker/resources/controlled-worker-postMessage.js b/test/wpt/tests/service-workers/service-worker/resources/controlled-worker-postMessage.js new file mode 100644 index 00000000000..628dc65db11 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/controlled-worker-postMessage.js @@ -0,0 +1,4 @@ +navigator.serviceWorker.onmessage = e => self.postMessage(e.data); +setTimeout(() => { + self.postMessage("No message received"); +}, 5000); diff --git a/test/wpt/tests/service-workers/service-worker/resources/fetch-access-control.py b/test/wpt/tests/service-workers/service-worker/resources/fetch-access-control.py index 446af87b249..380a7d62225 100644 --- a/test/wpt/tests/service-workers/service-worker/resources/fetch-access-control.py +++ b/test/wpt/tests/service-workers/service-worker/resources/fetch-access-control.py @@ -35,8 +35,13 @@ def main(request, response): return headers, body if b"VIDEO" in request.GET: - headers.append((b"Content-Type", b"video/ogg")) - body = open(os.path.join(request.doc_root, u"media", u"movie_5.ogv"), "rb").read() + if b"mp4" in request.GET: + headers.append((b"Content-Type", b"video/mp4")) + body = open(os.path.join(request.doc_root, u"media", u"movie_5.mp4"), "rb").read() + else: + headers.append((b"Content-Type", b"video/ogg")) + body = open(os.path.join(request.doc_root, u"media", u"movie_5.ogv"), "rb").read() + length = len(body) # If "PartialContent" is specified, the requestor wants to test range # requests. For the initial request, respond with "206 Partial Content" diff --git a/test/wpt/tests/service-workers/service-worker/resources/missing.asis b/test/wpt/tests/service-workers/service-worker/resources/missing.asis new file mode 100644 index 00000000000..4846fe01d61 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/resources/missing.asis @@ -0,0 +1,4 @@ +HTTP/1.1 404 Not Found +Content-Type: text/javascript + +alert("hello"); diff --git a/test/wpt/tests/service-workers/service-worker/resources/partitioned-cookies-3p-credentialless-frame.html b/test/wpt/tests/service-workers/service-worker/resources/partitioned-cookies-3p-credentialless-frame.html index ff24bf3670c..25ddf601457 100644 --- a/test/wpt/tests/service-workers/service-worker/resources/partitioned-cookies-3p-credentialless-frame.html +++ b/test/wpt/tests/service-workers/service-worker/resources/partitioned-cookies-3p-credentialless-frame.html @@ -21,6 +21,9 @@ document.cookie = '__Host-partitioned=123; Secure; Path=/; SameSite=None; Partitioned;'; assert_true(document.cookie.includes('__Host-partitioned=123')); + // Make sure DOM cannot access the unpartitioned cookie. + assert_false(document.cookie.includes('unpartitioned=456')); + const reg = await service_worker_unregister_and_register(t, script, scope); await wait_for_state(t, reg.installing, 'activated'); @@ -50,7 +53,44 @@ await wait_promise; assert_true(got.ok, 'Message passing'); - // Test that the partitioned cookie is available to this worker. + // Test that the partitioned cookie is available to this worker via CookieStore API. + wait_promise = new Promise(resolve => { + resolve_wait_promise = resolve; + }); + on_message = ev => { + got = ev.data; + resolve_wait_promise(); + }; + filtered_registrations[0].active.postMessage({type: 'echo_cookies_js'}); + await wait_promise; + assert_true(got.ok, 'Get cookies'); + assert_true( + got.cookies.includes('__Host-partitioned'), + 'Credentialless frame worker can access partitioned cookie via JS'); + assert_false( + got.cookies.includes('unpartitioned'), + 'Credentialless frame worker cannot access unpartitioned cookie via JS'); + + // Test that the partitioned cookie is available to this worker via HTTP. + wait_promise = new Promise(resolve => { + resolve_wait_promise = resolve; + }); + on_message = ev => { + got = ev.data; + resolve_wait_promise(); + }; + filtered_registrations[0].active.postMessage({ type: 'echo_cookies_http' }); + await wait_promise; + assert_true(got.ok, 'Get cookies'); + assert_true( + got.cookies.includes('__Host-partitioned'), + 'Credentialless frame worker can access partitioned cookie via HTTP'); + assert_false( + got.cookies.includes('unpartitioned'), + 'Credentialless frame worker cannot access unpartitioned cookie via HTTP'); + + // Test that the partitioned cookie is not available to this worker in HTTP + // requests from importScripts. wait_promise = new Promise(resolve => { resolve_wait_promise = resolve; }); @@ -58,12 +98,17 @@ got = ev.data; resolve_wait_promise(); }; - filtered_registrations[0].active.postMessage({type: 'echo_cookies'}); + filtered_registrations[0].active.postMessage({ type: 'echo_cookies_import' }); await wait_promise; assert_true(got.ok, 'Get cookies'); - assert_true(got.cookies.includes('__Host-partitioned'), 'Can access partitioned cookie');; + assert_true( + got.cookies.includes('__Host-partitioned'), + 'Credentialless frame worker can access partitioned cookie via importScripts'); + assert_false( + got.cookies.includes('unpartitioned'), + 'Credentialless frame worker cannot access unpartitioned cookie via importScripts'); }); - \ No newline at end of file + diff --git a/test/wpt/tests/service-workers/service-worker/resources/partitioned-cookies-3p-frame.html b/test/wpt/tests/service-workers/service-worker/resources/partitioned-cookies-3p-frame.html index d3962d2e600..00b3412c41f 100644 --- a/test/wpt/tests/service-workers/service-worker/resources/partitioned-cookies-3p-frame.html +++ b/test/wpt/tests/service-workers/service-worker/resources/partitioned-cookies-3p-frame.html @@ -16,6 +16,7 @@ const absolute_scope = new URL(scope, window.location).href; assert_false(document.cookie.includes('__Host-partitioned=123'), 'DOM cannot access partitioned cookie'); + assert_true(document.cookie.includes('unpartitioned=456'), 'DOM can access unpartitioned cookie'); const reg = await service_worker_unregister_and_register(t, script, scope); await wait_for_state(t, reg.installing, 'activated'); @@ -46,7 +47,7 @@ await wait_promise; assert_true(got.ok, 'Message passing'); - // Test that the partitioned cookie is not available to this worker. + // Test that the partitioned cookie is not available to this worker via HTTP. wait_promise = new Promise(resolve => { resolve_wait_promise = resolve; }); @@ -54,14 +55,54 @@ got = ev.data; resolve_wait_promise(); }; - filtered_registrations[0].active.postMessage({type: 'echo_cookies'}); + filtered_registrations[0].active.postMessage({type: 'echo_cookies_http'}); await wait_promise; assert_true(got.ok, 'Get cookies'); assert_false( got.cookies.includes('__Host-partitioned'), - 'Worker cannot access partitioned cookie'); + 'Worker cannot access partitioned cookie via HTTP'); + assert_true( + got.cookies.includes('unpartitioned'), + 'Worker can access unpartitioned cookie via HTTP'); + + // Test that the partitioned cookie is not available to this worker via CookieStore API. + wait_promise = new Promise(resolve => { + resolve_wait_promise = resolve; + }); + on_message = ev => { + got = ev.data; + resolve_wait_promise(); + }; + filtered_registrations[0].active.postMessage({type: 'echo_cookies_js'}); + await wait_promise; + assert_true(got.ok, 'Get cookies'); + assert_false( + got.cookies.includes('__Host-partitioned'), + 'Worker cannot access partitioned cookie via JS'); + assert_true( + got.cookies.includes('unpartitioned'), + 'Worker can access unpartitioned cookie via JS'); + + // Test that the partitioned cookie is not available to this worker in HTTP + // requests from importScripts. + wait_promise = new Promise(resolve => { + resolve_wait_promise = resolve; + }); + on_message = ev => { + got = ev.data; + resolve_wait_promise(); + }; + filtered_registrations[0].active.postMessage({type: 'echo_cookies_import'}); + await wait_promise; + assert_true(got.ok, 'Get cookies'); + assert_false( + got.cookies.includes('__Host-partitioned'), + 'Worker cannot access partitioned cookie via importScripts'); + assert_true( + got.cookies.includes('unpartitioned'), + 'Worker can access unpartitioned cookie via importScripts'); }); - \ No newline at end of file + diff --git a/test/wpt/tests/service-workers/service-worker/resources/partitioned-cookies-3p-sw.js b/test/wpt/tests/service-workers/service-worker/resources/partitioned-cookies-3p-sw.js index 2f54a984b19..767dbf44327 100644 --- a/test/wpt/tests/service-workers/service-worker/resources/partitioned-cookies-3p-sw.js +++ b/test/wpt/tests/service-workers/service-worker/resources/partitioned-cookies-3p-sw.js @@ -6,8 +6,12 @@ async function onMessage(event) { switch (event.data.type) { case 'test_message': return onTestMessage(event); - case 'echo_cookies': - return onEchoCookies(event); + case 'echo_cookies_http': + return onEchoCookiesHttp(event); + case 'echo_cookies_js': + return onEchoCookiesJs(event); + case 'echo_cookies_import': + return onEchoCookiesImport(event); default: return; } @@ -18,8 +22,19 @@ async function onTestMessage(event) { event.source.postMessage({ok: true}); } +async function onEchoCookiesHttp(event) { + try { + const resp = await fetch( + `${self.origin}/cookies/resources/list.py`, {credentials: 'include'}); + const cookies = await resp.json(); + event.source.postMessage({ok: true, cookies: Object.keys(cookies)}); + } catch (err) { + event.source.postMessage({ok: false}); + } +} + // echo_cookies returns the names of all of the cookies available to the worker. -async function onEchoCookies(event) { +async function onEchoCookiesJs(event) { try { const cookie_objects = await self.cookieStore.getAll(); const cookies = cookie_objects.map(c => c.name); @@ -28,3 +43,11 @@ async function onEchoCookies(event) { event.source.postMessage({ok: false}); } } + +// Sets `self._cookies` variable, array of the names of cookies available to +// the request. +importScripts(`${self.origin}/cookies/resources/list-cookies-for-script.py`); + +function onEchoCookiesImport(event) { + event.source.postMessage({ok: true, cookies: self._cookies}); +} diff --git a/test/wpt/tests/service-workers/service-worker/resources/partitioned-cookies-3p-window.html b/test/wpt/tests/service-workers/service-worker/resources/partitioned-cookies-3p-window.html index 8e90609da22..40d38b3f79f 100644 --- a/test/wpt/tests/service-workers/service-worker/resources/partitioned-cookies-3p-window.html +++ b/test/wpt/tests/service-workers/service-worker/resources/partitioned-cookies-3p-window.html @@ -19,7 +19,7 @@ './partitioned-cookies-3p-frame.html', first_party_origin + location.pathname).href; document.body.appendChild(iframe); - fetch_tests_from_window(iframe.contentWindow); + await fetch_tests_from_window(iframe.contentWindow); const credentialless_frame = document.createElement('iframe'); credentialless_frame.credentialless = true; @@ -27,7 +27,7 @@ './partitioned-cookies-3p-credentialless-frame.html', first_party_origin + location.pathname).href; document.body.appendChild(credentialless_frame); - fetch_tests_from_window(credentialless_frame.contentWindow); + await fetch_tests_from_window(credentialless_frame.contentWindow); }); diff --git a/test/wpt/tests/service-workers/service-worker/resources/partitioned-cookies-sw.js b/test/wpt/tests/service-workers/service-worker/resources/partitioned-cookies-sw.js index 2f54a984b19..767dbf44327 100644 --- a/test/wpt/tests/service-workers/service-worker/resources/partitioned-cookies-sw.js +++ b/test/wpt/tests/service-workers/service-worker/resources/partitioned-cookies-sw.js @@ -6,8 +6,12 @@ async function onMessage(event) { switch (event.data.type) { case 'test_message': return onTestMessage(event); - case 'echo_cookies': - return onEchoCookies(event); + case 'echo_cookies_http': + return onEchoCookiesHttp(event); + case 'echo_cookies_js': + return onEchoCookiesJs(event); + case 'echo_cookies_import': + return onEchoCookiesImport(event); default: return; } @@ -18,8 +22,19 @@ async function onTestMessage(event) { event.source.postMessage({ok: true}); } +async function onEchoCookiesHttp(event) { + try { + const resp = await fetch( + `${self.origin}/cookies/resources/list.py`, {credentials: 'include'}); + const cookies = await resp.json(); + event.source.postMessage({ok: true, cookies: Object.keys(cookies)}); + } catch (err) { + event.source.postMessage({ok: false}); + } +} + // echo_cookies returns the names of all of the cookies available to the worker. -async function onEchoCookies(event) { +async function onEchoCookiesJs(event) { try { const cookie_objects = await self.cookieStore.getAll(); const cookies = cookie_objects.map(c => c.name); @@ -28,3 +43,11 @@ async function onEchoCookies(event) { event.source.postMessage({ok: false}); } } + +// Sets `self._cookies` variable, array of the names of cookies available to +// the request. +importScripts(`${self.origin}/cookies/resources/list-cookies-for-script.py`); + +function onEchoCookiesImport(event) { + event.source.postMessage({ok: true, cookies: self._cookies}); +} diff --git a/test/wpt/tests/service-workers/service-worker/resources/resource-timing-iframe.sub.html b/test/wpt/tests/service-workers/service-worker/resources/resource-timing-iframe.sub.html index 384c29b536b..ec4c726331d 100644 --- a/test/wpt/tests/service-workers/service-worker/resources/resource-timing-iframe.sub.html +++ b/test/wpt/tests/service-workers/service-worker/resources/resource-timing-iframe.sub.html @@ -5,6 +5,6 @@ - + diff --git a/test/wpt/tests/service-workers/service-worker/tentative/static-router/README.md b/test/wpt/tests/service-workers/service-worker/tentative/static-router/README.md new file mode 100644 index 00000000000..8826b3c7827 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/tentative/static-router/README.md @@ -0,0 +1,4 @@ +A test stuite for the ServiceWorker Static Routing API. + +WICG proposal: https://github.com/WICG/proposals/issues/102 +Specification PR: https://github.com/w3c/ServiceWorker/pull/1686 diff --git a/test/wpt/tests/service-workers/service-worker/tentative/static-router/resources/direct.txt b/test/wpt/tests/service-workers/service-worker/tentative/static-router/resources/direct.txt new file mode 100644 index 00000000000..f3d9861c137 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/tentative/static-router/resources/direct.txt @@ -0,0 +1 @@ +Network diff --git a/test/wpt/tests/service-workers/service-worker/tentative/static-router/resources/simple-test-for-condition-main-resource.html b/test/wpt/tests/service-workers/service-worker/tentative/static-router/resources/simple-test-for-condition-main-resource.html new file mode 100644 index 00000000000..0c3e3e78707 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/tentative/static-router/resources/simple-test-for-condition-main-resource.html @@ -0,0 +1,3 @@ + +Simple +Here's a simple html file. diff --git a/test/wpt/tests/service-workers/service-worker/tentative/static-router/resources/simple.html b/test/wpt/tests/service-workers/service-worker/tentative/static-router/resources/simple.html new file mode 100644 index 00000000000..0c3e3e78707 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/tentative/static-router/resources/simple.html @@ -0,0 +1,3 @@ + +Simple +Here's a simple html file. diff --git a/test/wpt/tests/service-workers/service-worker/tentative/static-router/resources/static-router-sw.js b/test/wpt/tests/service-workers/service-worker/tentative/static-router/resources/static-router-sw.js new file mode 100644 index 00000000000..4655ab5321c --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/tentative/static-router/resources/static-router-sw.js @@ -0,0 +1,35 @@ +'use strict'; + +var requests = []; + +self.addEventListener('install', e => { + e.registerRouter([ + { + condition: {urlPattern: '/**/*.txt??*'}, + // Note: "??*" is for allowing arbitrary query strings. + // Upon my experiment, the URLPattern needs two '?'s for specifying + // a coming string as a query. + source: 'network' + }, { + condition: { + urlPattern: '/**/simple-test-for-condition-main-resource.html'}, + source: 'network' + }]); + self.skipWaiting(); +}); + +self.addEventListener('activate', e => { + e.waitUntil(clients.claim()); +}); + +self.addEventListener('fetch', function(event) { + requests.push({url: event.request.url, mode: event.request.mode}); + const url = new URL(event.request.url); + const nonce = url.searchParams.get('nonce'); + event.respondWith(new Response(nonce)); +}); + +self.addEventListener('message', function(event) { + event.data.port.postMessage({requests: requests}); + requests = []; +}); diff --git a/test/wpt/tests/service-workers/service-worker/tentative/static-router/resources/test-helpers.sub.js b/test/wpt/tests/service-workers/service-worker/tentative/static-router/resources/test-helpers.sub.js new file mode 100644 index 00000000000..64a7f7d24fd --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/tentative/static-router/resources/test-helpers.sub.js @@ -0,0 +1,303 @@ +// Copied from +// service-workers/service-worker/resources/testharness-helpers.js to be used under tentative. + +// Adapter for testharness.js-style tests with Service Workers + +/** + * @param options an object that represents RegistrationOptions except for scope. + * @param options.type a WorkerType. + * @param options.updateViaCache a ServiceWorkerUpdateViaCache. + * @see https://w3c.github.io/ServiceWorker/#dictdef-registrationoptions + */ +function service_worker_unregister_and_register(test, url, scope, options) { + if (!scope || scope.length == 0) + return Promise.reject(new Error('tests must define a scope')); + + if (options && options.scope) + return Promise.reject(new Error('scope must not be passed in options')); + + options = Object.assign({ scope: scope }, options); + return service_worker_unregister(test, scope) + .then(function() { + return navigator.serviceWorker.register(url, options); + }) + .catch(unreached_rejection(test, + 'unregister and register should not fail')); +} + +// This unregisters the registration that precisely matches scope. Use this +// when unregistering by scope. If no registration is found, it just resolves. +function service_worker_unregister(test, scope) { + var absoluteScope = (new URL(scope, window.location).href); + return navigator.serviceWorker.getRegistration(scope) + .then(function(registration) { + if (registration && registration.scope === absoluteScope) + return registration.unregister(); + }) + .catch(unreached_rejection(test, 'unregister should not fail')); +} + +function service_worker_unregister_and_done(test, scope) { + return service_worker_unregister(test, scope) + .then(test.done.bind(test)); +} + +function unreached_fulfillment(test, prefix) { + return test.step_func(function(result) { + var error_prefix = prefix || 'unexpected fulfillment'; + assert_unreached(error_prefix + ': ' + result); + }); +} + +// Rejection-specific helper that provides more details +function unreached_rejection(test, prefix) { + return test.step_func(function(error) { + var reason = error.message || error.name || error; + var error_prefix = prefix || 'unexpected rejection'; + assert_unreached(error_prefix + ': ' + reason); + }); +} + +/** + * Adds an iframe to the document and returns a promise that resolves to the + * iframe when it finishes loading. The caller is responsible for removing the + * iframe later if needed. + * + * @param {string} url + * @returns {HTMLIFrameElement} + */ +function with_iframe(url) { + return new Promise(function(resolve) { + var frame = document.createElement('iframe'); + frame.className = 'test-iframe'; + frame.src = url; + frame.onload = function() { resolve(frame); }; + document.body.appendChild(frame); + }); +} + +function normalizeURL(url) { + return new URL(url, self.location).toString().replace(/#.*$/, ''); +} + +function wait_for_update(test, registration) { + if (!registration || registration.unregister == undefined) { + return Promise.reject(new Error( + 'wait_for_update must be passed a ServiceWorkerRegistration')); + } + + return new Promise(test.step_func(function(resolve) { + var handler = test.step_func(function() { + registration.removeEventListener('updatefound', handler); + resolve(registration.installing); + }); + registration.addEventListener('updatefound', handler); + })); +} + +// Return true if |state_a| is more advanced than |state_b|. +function is_state_advanced(state_a, state_b) { + if (state_b === 'installing') { + switch (state_a) { + case 'installed': + case 'activating': + case 'activated': + case 'redundant': + return true; + } + } + + if (state_b === 'installed') { + switch (state_a) { + case 'activating': + case 'activated': + case 'redundant': + return true; + } + } + + if (state_b === 'activating') { + switch (state_a) { + case 'activated': + case 'redundant': + return true; + } + } + + if (state_b === 'activated') { + switch (state_a) { + case 'redundant': + return true; + } + } + return false; +} + +function wait_for_state(test, worker, state) { + if (!worker || worker.state == undefined) { + return Promise.reject(new Error( + 'wait_for_state needs a ServiceWorker object to be passed.')); + } + if (worker.state === state) + return Promise.resolve(state); + + if (is_state_advanced(worker.state, state)) { + return Promise.reject(new Error( + `Waiting for ${state} but the worker is already ${worker.state}.`)); + } + return new Promise(test.step_func(function(resolve, reject) { + worker.addEventListener('statechange', test.step_func(function() { + if (worker.state === state) + resolve(state); + + if (is_state_advanced(worker.state, state)) { + reject(new Error( + `The state of the worker becomes ${worker.state} while waiting` + + `for ${state}.`)); + } + })); + })); +} + +// Declare a test that runs entirely in the ServiceWorkerGlobalScope. The |url| +// is the service worker script URL. This function: +// - Instantiates a new test with the description specified in |description|. +// The test will succeed if the specified service worker can be successfully +// registered and installed. +// - Creates a new ServiceWorker registration with a scope unique to the current +// document URL. Note that this doesn't allow more than one +// service_worker_test() to be run from the same document. +// - Waits for the new worker to begin installing. +// - Imports tests results from tests running inside the ServiceWorker. +function service_worker_test(url, description) { + // If the document URL is https://example.com/document and the script URL is + // https://example.com/script/worker.js, then the scope would be + // https://example.com/script/scope/document. + var scope = new URL('scope' + window.location.pathname, + new URL(url, window.location)).toString(); + promise_test(function(test) { + return service_worker_unregister_and_register(test, url, scope) + .then(function(registration) { + add_completion_callback(function() { + registration.unregister(); + }); + return wait_for_update(test, registration) + .then(function(worker) { + return fetch_tests_from_worker(worker); + }); + }); + }, description); +} + +function base_path() { + return location.pathname.replace(/\/[^\/]*$/, '/'); +} + +function test_login(test, origin, username, password, cookie) { + return new Promise(function(resolve, reject) { + with_iframe( + origin + base_path() + + 'resources/fetch-access-control-login.html') + .then(test.step_func(function(frame) { + var channel = new MessageChannel(); + channel.port1.onmessage = test.step_func(function() { + frame.remove(); + resolve(); + }); + frame.contentWindow.postMessage( + {username: username, password: password, cookie: cookie}, + origin, [channel.port2]); + })); + }); +} + +function test_websocket(test, frame, url) { + return new Promise(function(resolve, reject) { + var ws = new frame.contentWindow.WebSocket(url, ['echo', 'chat']); + var openCalled = false; + ws.addEventListener('open', test.step_func(function(e) { + assert_equals(ws.readyState, 1, "The WebSocket should be open"); + openCalled = true; + ws.close(); + }), true); + + ws.addEventListener('close', test.step_func(function(e) { + assert_true(openCalled, "The WebSocket should be closed after being opened"); + resolve(); + }), true); + + ws.addEventListener('error', reject); + }); +} + +function login_https(test) { + var host_info = get_host_info(); + return test_login(test, host_info.HTTPS_REMOTE_ORIGIN, + 'username1s', 'password1s', 'cookie1') + .then(function() { + return test_login(test, host_info.HTTPS_ORIGIN, + 'username2s', 'password2s', 'cookie2'); + }); +} + +function websocket(test, frame) { + return test_websocket(test, frame, get_websocket_url()); +} + +function get_websocket_url() { + return 'wss://{{host}}:{{ports[wss][0]}}/echo'; +} + +// The navigator.serviceWorker.register() method guarantees that the newly +// installing worker is available as registration.installing when its promise +// resolves. However some tests test installation using a element where +// it is possible for the installing worker to have already become the waiting +// or active worker. So this method is used to get the newest worker when these +// tests need access to the ServiceWorker itself. +function get_newest_worker(registration) { + if (registration.installing) + return registration.installing; + if (registration.waiting) + return registration.waiting; + if (registration.active) + return registration.active; +} + +function register_using_link(script, options) { + var scope = options.scope; + var link = document.createElement('link'); + link.setAttribute('rel', 'serviceworker'); + link.setAttribute('href', script); + link.setAttribute('scope', scope); + document.getElementsByTagName('head')[0].appendChild(link); + return new Promise(function(resolve, reject) { + link.onload = resolve; + link.onerror = reject; + }) + .then(() => navigator.serviceWorker.getRegistration(scope)); +} + +function with_sandboxed_iframe(url, sandbox) { + return new Promise(function(resolve) { + var frame = document.createElement('iframe'); + frame.sandbox = sandbox; + frame.src = url; + frame.onload = function() { resolve(frame); }; + document.body.appendChild(frame); + }); +} + +// Registers, waits for activation, then unregisters on a sample scope. +// +// This can be used to wait for a period of time needed to register, +// activate, and then unregister a service worker. When checking that +// certain behavior does *NOT* happen, this is preferable to using an +// arbitrary delay. +async function wait_for_activation_on_sample_scope(t, window_or_workerglobalscope) { + const script = '/service-workers/service-worker/resources/empty-worker.js'; + const scope = 'resources/there/is/no/there/there?' + Date.now(); + let registration = await window_or_workerglobalscope.navigator.serviceWorker.register(script, { scope }); + await wait_for_state(t, registration.installing, 'activated'); + await registration.unregister(); +} + diff --git a/test/wpt/tests/service-workers/service-worker/tentative/static-router/static-router-main-resource.https.html b/test/wpt/tests/service-workers/service-worker/tentative/static-router/static-router-main-resource.https.html new file mode 100644 index 00000000000..5a55783af57 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/tentative/static-router/static-router-main-resource.https.html @@ -0,0 +1,58 @@ + + +Static Router: simply skip fetch handler for main resource if pattern matches + + + + + + + diff --git a/test/wpt/tests/service-workers/service-worker/tentative/static-router/static-router-subresource.https.html b/test/wpt/tests/service-workers/service-worker/tentative/static-router/static-router-subresource.https.html new file mode 100644 index 00000000000..721c2797603 --- /dev/null +++ b/test/wpt/tests/service-workers/service-worker/tentative/static-router/static-router-subresource.https.html @@ -0,0 +1,48 @@ + + +Static Router: simply skip fetch handler if pattern matches + + + + + + diff --git a/test/wpt/tests/storage/buckets/bucket-quota-indexeddb.tentative.https.any.js b/test/wpt/tests/storage/buckets/bucket-quota-indexeddb.tentative.https.any.js new file mode 100644 index 00000000000..ba82edb72ec --- /dev/null +++ b/test/wpt/tests/storage/buckets/bucket-quota-indexeddb.tentative.https.any.js @@ -0,0 +1,35 @@ +// META: title=Bucket quota enforcement for indexeddb +// META: script=/storage/buckets/resources/util.js + +promise_test(async t => { + const arraySize = 1e6; + const objectStoreName = "storageManager"; + const dbname = + this.window ? window.location.pathname : 'estimate-worker.https.html'; + + let quota = arraySize / 2; + const bucket = await navigator.storageBuckets.open('idb', {quota}); + + await indexedDbDeleteRequest(bucket.indexedDB, dbname); + + const db = + await indexedDbOpenRequest(t, bucket.indexedDB, dbname, (db_to_upgrade) => { + db_to_upgrade.createObjectStore(objectStoreName); + }); + + const txn = db.transaction(objectStoreName, 'readwrite'); + const buffer = new ArrayBuffer(arraySize); + const view = new Uint8Array(buffer); + + for (let i = 0; i < arraySize; i++) { + view[i] = Math.floor(Math.random() * 255); + } + + const testBlob = new Blob([buffer], {type: 'binary/random'}); + txn.objectStore(objectStoreName).add(testBlob, 1); + + await promise_rejects_dom( + t, 'QuotaExceededError', transactionPromise(txn)); + + db.close(); +}, 'IDB respects bucket quota'); diff --git a/test/wpt/tests/storage/buckets/bucket-storage-policy.tentative.https.any.js b/test/wpt/tests/storage/buckets/bucket-storage-policy.tentative.https.any.js new file mode 100644 index 00000000000..d6dce3675d0 --- /dev/null +++ b/test/wpt/tests/storage/buckets/bucket-storage-policy.tentative.https.any.js @@ -0,0 +1,21 @@ +// META: title=Buckets API: Tests for bucket storage policies. +// META: script=/storage/buckets/resources/util.js +// META: global=window,worker + +'use strict'; + +promise_test(async testCase => { + await prepareForBucketTest(testCase); + + await promise_rejects_js( + testCase, TypeError, + navigator.storageBuckets.open('negative', {quota: -1})); + + await promise_rejects_js( + testCase, TypeError, navigator.storageBuckets.open('zero', {quota: 0})); + + await promise_rejects_js( + testCase, TypeError, + navigator.storageBuckets.open( + 'above_max', {quota: Number.MAX_SAFE_INTEGER + 1})); +}, 'The open promise should reject with a TypeError when quota is requested outside the range of 1 to Number.MAX_SAFE_INTEGER.'); diff --git a/test/wpt/tests/storage/buckets/buckets_storage_policy.tentative.https.any.js b/test/wpt/tests/storage/buckets/buckets_storage_policy.tentative.https.any.js deleted file mode 100644 index a66fd81cd43..00000000000 --- a/test/wpt/tests/storage/buckets/buckets_storage_policy.tentative.https.any.js +++ /dev/null @@ -1,46 +0,0 @@ -// META: title=Buckets API: Tests for bucket storage policies. -// META: script=/storage/buckets/resources/util.js -// META: global=window,worker - -'use strict'; - -promise_test(async testCase => { - await prepareForBucketTest(testCase); - - await promise_rejects_js( - testCase, TypeError, - navigator.storageBuckets.open('negative', {quota: -1})); - - await promise_rejects_js( - testCase, TypeError, navigator.storageBuckets.open('zero', {quota: 0})); - - await promise_rejects_js( - testCase, TypeError, - navigator.storageBuckets.open( - 'above_max', {quota: Number.MAX_SAFE_INTEGER + 1})); -}, 'The open promise should reject with a TypeError when quota is requested outside the range of 1 to Number.MAX_SAFE_INTEGER.'); - - -promise_test(async testCase => { - await prepareForBucketTest(testCase); - - // IndexedDB - { - const quota = 1; - const bucket = await navigator.storageBuckets.open('idb', {quota}); - - const objectStoreName = 'store'; - const db = await indexedDbOpenRequest( - testCase, bucket.indexedDB, 'db', (db_to_upgrade) => { - db_to_upgrade.createObjectStore(objectStoreName); - }); - - const overflowBuffer = new Uint8Array(quota + 1); - - const txn = db.transaction(objectStoreName, 'readwrite'); - txn.objectStore(objectStoreName).add('', overflowBuffer); - - await promise_rejects_dom( - testCase, 'QuotaExceededError', transactionPromise(txn)); - } -}, 'A QuotaExceededError is thrown when a storage API exceeds the quota of the bucket its in.'); diff --git a/test/wpt/tests/storage/storagemanager-persist-persisted-match.https.any.js b/test/wpt/tests/storage/storagemanager-persist-persisted-match.https.any.js new file mode 100644 index 00000000000..edbe67fae2c --- /dev/null +++ b/test/wpt/tests/storage/storagemanager-persist-persisted-match.https.any.js @@ -0,0 +1,9 @@ +// META: title=StorageManager: result of persist() matches result of persisted() + +promise_test(async t => { + var persistResult = await navigator.storage.persist(); + assert_equals(typeof persistResult, 'boolean', persistResult + ' should be boolean'); + var persistedResult = await navigator.storage.persisted(); + assert_equals(typeof persistedResult, 'boolean', persistedResult + ' should be boolean'); + assert_equals(persistResult, persistedResult); +}, 'navigator.storage.persist() resolves to a value that matches navigator.storage.persisted()'); diff --git a/test/wpt/tests/websockets/back-forward-cache-with-closed-websocket-connection-ccns.tentative.window.js b/test/wpt/tests/websockets/back-forward-cache-with-closed-websocket-connection-ccns.tentative.window.js index ccc45f2877d..f6ee5edeaa9 100644 --- a/test/wpt/tests/websockets/back-forward-cache-with-closed-websocket-connection-ccns.tentative.window.js +++ b/test/wpt/tests/websockets/back-forward-cache-with-closed-websocket-connection-ccns.tentative.window.js @@ -24,11 +24,8 @@ promise_test(async t => { // The page should not be eligible for BFCache because of the usage // of WebSocket. await assertBFCacheEligibility(rc1, /*shouldRestoreFromBFCache=*/ false); - // The `BrowsingInstanceNotSwapped` reason will be added because of the - // sticky feature, and it will be reported as "Internal error". await assertNotRestoredFromBFCache(rc1, [ 'WebSocketSticky', - 'MainResourceHasCacheControlNoStore', - 'Internal error' + 'MainResourceHasCacheControlNoStore' ]); }); diff --git a/test/wpt/tests/websockets/back-forward-cache-with-open-websocket-connection-ccns.tentative.window.js b/test/wpt/tests/websockets/back-forward-cache-with-open-websocket-connection-ccns.tentative.window.js index 563fd4792ef..f37a04af91a 100644 --- a/test/wpt/tests/websockets/back-forward-cache-with-open-websocket-connection-ccns.tentative.window.js +++ b/test/wpt/tests/websockets/back-forward-cache-with-open-websocket-connection-ccns.tentative.window.js @@ -24,12 +24,9 @@ promise_test(async t => { // The page should not be eligible for BFCache because of the usage // of WebSocket. await assertBFCacheEligibility(rc1, /*shouldRestoreFromBFCache=*/ false); - // The `BrowsingInstanceNotSwapped` reason will be added because of the - // sticky feature, and it will be reported as "Internal error". await assertNotRestoredFromBFCache(rc1, [ 'WebSocket', 'WebSocketSticky', - 'MainResourceHasCacheControlNoStore', - 'Internal error' + 'MainResourceHasCacheControlNoStore' ]); }); diff --git a/test/wpt/tests/websockets/back-forward-cache-with-open-websocket-connection.window.js b/test/wpt/tests/websockets/back-forward-cache-with-open-websocket-connection.window.js index 2baf38f303c..6c48a570101 100644 --- a/test/wpt/tests/websockets/back-forward-cache-with-open-websocket-connection.window.js +++ b/test/wpt/tests/websockets/back-forward-cache-with-open-websocket-connection.window.js @@ -17,5 +17,5 @@ promise_test(async t => { await openWebSocket(rc1); // The page should not be eligible for BFCache because of open WebSocket connection. await assertBFCacheEligibility(rc1, /*shouldRestoreFromBFCache=*/ false); - await assertNotRestoredFromBFCache(rc1, ['WebSocket']); + await assertNotRestoredFromBFCache(rc1, ['websocket']); }); diff --git a/test/wpt/tests/xhr/blob-range.any.js b/test/wpt/tests/xhr/blob-range.any.js new file mode 100644 index 00000000000..2a5c54fc34f --- /dev/null +++ b/test/wpt/tests/xhr/blob-range.any.js @@ -0,0 +1,246 @@ +// See also /fetch/range/blob.any.js + +const supportedBlobRange = [ + { + name: "A simple blob range request.", + data: ["A simple Hello, World! example"], + type: "text/plain", + range: "bytes=9-21", + content_length: 13, + content_range: "bytes 9-21/30", + result: "Hello, World!", + }, + { + name: "A blob range request with no type.", + data: ["A simple Hello, World! example"], + type: undefined, + range: "bytes=9-21", + content_length: 13, + content_range: "bytes 9-21/30", + result: "Hello, World!", + }, + { + name: "A blob range request with no end.", + data: ["Range with no end"], + type: "text/plain", + range: "bytes=11-", + content_length: 6, + content_range: "bytes 11-16/17", + result: "no end", + }, + { + name: "A blob range request with no start.", + data: ["Range with no start"], + type: "text/plain", + range: "bytes=-8", + content_length: 8, + content_range: "bytes 11-18/19", + result: "no start", + }, + { + name: "A simple blob range request with whitespace.", + data: ["A simple Hello, World! example"], + type: "text/plain", + range: "bytes= \t9-21", + content_length: 13, + content_range: "bytes 9-21/30", + result: "Hello, World!", + }, + { + name: "Blob content with short content and a large range end", + data: ["Not much here"], + type: "text/plain", + range: "bytes=4-100000000000", + content_length: 9, + content_range: "bytes 4-12/13", + result: "much here", + }, + { + name: "Blob content with short content and a range end matching content length", + data: ["Not much here"], + type: "text/plain", + range: "bytes=4-13", + content_length: 9, + content_range: "bytes 4-12/13", + result: "much here", + }, + { + name: "Blob range with whitespace before and after hyphen", + data: ["Valid whitespace #1"], + type: "text/plain", + range: "bytes=5 - 10", + content_length: 6, + content_range: "bytes 5-10/19", + result: " white", + }, + { + name: "Blob range with whitespace after hyphen", + data: ["Valid whitespace #2"], + type: "text/plain", + range: "bytes=-\t 5", + content_length: 5, + content_range: "bytes 14-18/19", + result: "ce #2", + }, + { + name: "Blob range with whitespace around equals sign", + data: ["Valid whitespace #3"], + type: "text/plain", + range: "bytes \t =\t 6-", + content_length: 13, + content_range: "bytes 6-18/19", + result: "whitespace #3", + }, +]; + +const unsupportedBlobRange = [ + { + name: "Blob range with no value", + data: ["Blob range should have a value"], + type: "text/plain", + range: "", + }, + { + name: "Blob range with incorrect range header", + data: ["A"], + type: "text/plain", + range: "byte=0-" + }, + { + name: "Blob range with incorrect range header #2", + data: ["A"], + type: "text/plain", + range: "bytes" + }, + { + name: "Blob range with incorrect range header #3", + data: ["A"], + type: "text/plain", + range: "bytes\t \t" + }, + { + name: "Blob range request with multiple range values", + data: ["Multiple ranges are not currently supported"], + type: "text/plain", + range: "bytes=0-5,15-", + }, + { + name: "Blob range request with multiple range values and whitespace", + data: ["Multiple ranges are not currently supported"], + type: "text/plain", + range: "bytes=0-5, 15-", + }, + { + name: "Blob range request with trailing comma", + data: ["Range with invalid trailing comma"], + type: "text/plain", + range: "bytes=0-5,", + }, + { + name: "Blob range with no start or end", + data: ["Range with no start or end"], + type: "text/plain", + range: "bytes=-", + }, + { + name: "Blob range request with short range end", + data: ["Range end should be greater than range start"], + type: "text/plain", + range: "bytes=10-5", + }, + { + name: "Blob range start should be an ASCII digit", + data: ["Range start must be an ASCII digit"], + type: "text/plain", + range: "bytes=x-5", + }, + { + name: "Blob range should have a dash", + data: ["Blob range should have a dash"], + type: "text/plain", + range: "bytes=5", + }, + { + name: "Blob range end should be an ASCII digit", + data: ["Range end must be an ASCII digit"], + type: "text/plain", + range: "bytes=5-x", + }, + { + name: "Blob range should include '-'", + data: ["Range end must include '-'"], + type: "text/plain", + range: "bytes=x", + }, + { + name: "Blob range should include '='", + data: ["Range end must include '='"], + type: "text/plain", + range: "bytes 5-", + }, + { + name: "Blob range should include 'bytes='", + data: ["Range end must include 'bytes='"], + type: "text/plain", + range: "5-", + }, + { + name: "Blob content with short content and a large range start", + data: ["Not much here"], + type: "text/plain", + range: "bytes=100000-", + }, + { + name: "Blob content with short content and a range start matching the content length", + data: ["Not much here"], + type: "text/plain", + range: "bytes=13-", + }, +]; + +supportedBlobRange.forEach(({ name, data, type, range, content_length, content_range, result }) => { + promise_test(async t => { + const blob = new Blob(data, { "type" : type }); + const blobURL = URL.createObjectURL(blob); + t.add_cleanup(() => URL.revokeObjectURL(blobURL)); + const xhr = new XMLHttpRequest(); + xhr.open("GET", blobURL); + xhr.responseType = "text"; + xhr.setRequestHeader("Range", range); + await new Promise(resolve => { + xhr.onloadend = resolve; + xhr.send(); + }); + assert_equals(xhr.status, 206, "HTTP status is 206"); + assert_equals(xhr.getResponseHeader("Content-Type"), type || "", "Content-Type is " + xhr.getResponseHeader("Content-Type")); + assert_equals(xhr.getResponseHeader("Content-Length"), content_length.toString(), "Content-Length is " + xhr.getResponseHeader("Content-Length")); + assert_equals(xhr.getResponseHeader("Content-Range"), content_range, "Content-Range is " + xhr.getResponseHeader("Content-Range")); + assert_equals(xhr.responseText, result, "Response's body is correct"); + const all = xhr.getAllResponseHeaders().toLowerCase(); + assert_true(all.includes(`content-type: ${type || ""}`), "Expected Content-Type in getAllResponseHeaders()"); + assert_true(all.includes(`content-length: ${content_length}`), "Expected Content-Length in getAllResponseHeaders()"); + assert_true(all.includes(`content-range: ${content_range}`), "Expected Content-Range in getAllResponseHeaders()") + }, name); +}); + +unsupportedBlobRange.forEach(({ name, data, type, range }) => { + promise_test(t => { + const blob = new Blob(data, { "type" : type }); + const blobURL = URL.createObjectURL(blob); + t.add_cleanup(() => URL.revokeObjectURL(blobURL)); + + const xhr = new XMLHttpRequest(); + xhr.open("GET", blobURL, false); + xhr.setRequestHeader("Range", range); + assert_throws_dom("NetworkError", () => xhr.send()); + + xhr.open("GET", blobURL); + xhr.setRequestHeader("Range", range); + xhr.responseType = "text"; + return new Promise((resolve, reject) => { + xhr.onload = reject; + xhr.onerror = resolve; + xhr.send(); + }); + }, name); +}); diff --git a/test/wpt/tests/xhr/responsexml-invalid-type.html b/test/wpt/tests/xhr/responsexml-invalid-type.html new file mode 100644 index 00000000000..57ba462551f --- /dev/null +++ b/test/wpt/tests/xhr/responsexml-invalid-type.html @@ -0,0 +1,21 @@ + + + +XMLHttpRequest: response with an invalid responseXML document + + + + + + + diff --git a/test/wpt/tests/xhr/send-authentication-basic-cors-not-enabled.htm b/test/wpt/tests/xhr/send-authentication-basic-cors-not-enabled.htm index 2e7cbaf0340..41201960cc3 100644 --- a/test/wpt/tests/xhr/send-authentication-basic-cors-not-enabled.htm +++ b/test/wpt/tests/xhr/send-authentication-basic-cors-not-enabled.htm @@ -5,6 +5,7 @@ + @@ -13,10 +14,10 @@ +
+ @@ -16,9 +17,9 @@ var test = async_test(desc) test.step(function() { var client = new XMLHttpRequest(), - urlstart = location.host + location.pathname.replace(/\/[^\/]*$/, '/'), + urlstart = get_host_info().REMOTE_ORIGIN + location.pathname.replace(/\/[^\/]*$/, '/'), user = token() - client.open("GET", location.protocol + "//www1." + urlstart + "resources/" + pathsuffix, false) + client.open("GET", urlstart + "resources/" + pathsuffix, false) client.setRequestHeader("x-user", user) client.setRequestHeader("x-pass", 'pass') client.setRequestHeader("Authorization", "Basic " + btoa(user + ":pass")) diff --git a/test/wpt/tests/xhr/send-network-error-sync-events.sub.htm b/test/wpt/tests/xhr/send-network-error-sync-events.sub.htm index 2266eb36e1e..8011c58bdd1 100644 --- a/test/wpt/tests/xhr/send-network-error-sync-events.sub.htm +++ b/test/wpt/tests/xhr/send-network-error-sync-events.sub.htm @@ -17,7 +17,7 @@ { var xhr = new XMLHttpRequest(); - xhr.open("POST", "http://nonexistent.{{host}}:{{ports[http][0]}}", false); + xhr.open("POST", "http://{{host}}:1", false); // Bad port. assert_throws_dom("NetworkError", function() {