From 8ef4c79bf28ee2dd313def5e7b56dd0c0524a2fc Mon Sep 17 00:00:00 2001 From: Robert Nagy Date: Wed, 22 Jan 2025 11:44:43 +0100 Subject: [PATCH 01/17] fix: apply byte offset on Buffer.from (#4019) Doesn't make any difference in practice as byteOffset is always going to be 0 but it's theoretically more correct and could avoid future bugs. --- lib/cache/sqlite-cache-store.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/cache/sqlite-cache-store.js b/lib/cache/sqlite-cache-store.js index 748016fbffd..682aac31326 100644 --- a/lib/cache/sqlite-cache-store.js +++ b/lib/cache/sqlite-cache-store.js @@ -232,7 +232,7 @@ module.exports = class SqliteCacheStore { const value = this.#findValue(key) return value ? { - body: value.body ? Buffer.from(value.body.buffer) : undefined, + body: value.body ? Buffer.from(value.body.buffer, value.body.byteOffset, value.body.byteLength) : undefined, statusCode: value.statusCode, statusMessage: value.statusMessage, headers: value.headers ? JSON.parse(value.headers) : undefined, From 35f5d45eb1d0f9836a842e4d0afbbb9ca2157b16 Mon Sep 17 00:00:00 2001 From: Aras Abbasi Date: Wed, 22 Jan 2025 22:30:26 +0100 Subject: [PATCH 02/17] fix: fetch body fallback random number generation (#4023) --- lib/web/fetch/body.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/web/fetch/body.js b/lib/web/fetch/body.js index 850a37fd801..81d9b6d0e67 100644 --- a/lib/web/fetch/body.js +++ b/lib/web/fetch/body.js @@ -23,7 +23,7 @@ try { const crypto = require('node:crypto') random = (max) => crypto.randomInt(0, max) } catch { - random = (max) => Math.floor(Math.random(max)) + random = (max) => Math.floor(Math.random() * max) } const textEncoder = new TextEncoder() From 4d75e12a5e32af0c0311b0f4227954b5fa44a184 Mon Sep 17 00:00:00 2001 From: Matteo Collina Date: Thu, 23 Jan 2025 09:47:49 +0100 Subject: [PATCH 03/17] Add release instructions (#4022) --- MAINTAINERS.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/MAINTAINERS.md b/MAINTAINERS.md index b98d904e911..cefd1ffa5d9 100644 --- a/MAINTAINERS.md +++ b/MAINTAINERS.md @@ -31,3 +31,13 @@ Maintainers are encouraged to use the extensive and detailed list of labels for * Issues with a low-barrier of entry should be assigned the `good first issue` label. * Do not use the `invalid` label, instead use `bug` or `Status: wontfix`. * Duplicate issues should initially be assigned the `duplicate` label. + + +## Making a Release + +1. Go to github actions, then select ["Create Release PR"](https://github.com/nodejs/undici/actions/workflows/release-create-pr.yml). +2. Run the workflow, selecting `main` and indicating if you want a specific version number or a patch/minor/major release +3. Wait for the PR to be created. Approve the PR ([this](https://github.com/nodejs/undici/pull/4021) is a an example). +4. Land the PR, wait for the CI to pass. +5. Got to the ["Release"](https://github.com/nodejs/undici/actions/workflows/release.yml) workflow, you should see a job waiting. +6. If you are one of the [releases](https://github.com/nodejs/undici?tab=readme-ov-file#releasers), then click "review deployments", then select "release" and click "approve and deploy". If you are not a releaser, contact one. From f0ffe1ff116a660c43119e6cc3e93217745fbf92 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 23 Jan 2025 00:59:35 -0800 Subject: [PATCH 04/17] chore: update cache tests (#4020) Co-authored-by: mcollina <52195+mcollina@users.noreply.github.com> --- test/fixtures/cache-tests/results/apache.json | 52 ++++++++----- test/fixtures/cache-tests/results/caddy.json | 73 ++++++++----------- test/fixtures/cache-tests/results/fastly.json | 11 +-- .../fixtures/cache-tests/results/firefox.json | 10 +-- test/fixtures/cache-tests/results/index.mjs | 18 ++--- test/fixtures/cache-tests/results/nginx.json | 53 +++++++------- test/fixtures/cache-tests/results/safari.json | 32 ++++---- test/fixtures/cache-tests/results/squid.json | 2 +- .../cache-tests/results/trafficserver.json | 2 +- .../fixtures/cache-tests/results/varnish.json | 7 +- test/fixtures/cache-tests/test-engine/cli.mjs | 3 +- .../cache-tests/test-engine/client/config.mjs | 11 --- .../test-engine/client/fetching.mjs | 2 +- .../cache-tests/test-engine/client/runner.mjs | 3 +- .../cache-tests/test-engine/client/test.mjs | 10 +-- .../cache-tests/test-engine/client/utils.mjs | 4 +- .../cache-tests/tests/lib/templates.mjs | 2 +- 17 files changed, 138 insertions(+), 157 deletions(-) diff --git a/test/fixtures/cache-tests/results/apache.json b/test/fixtures/cache-tests/results/apache.json index bba8e0994df..89072ab2633 100644 --- a/test/fixtures/cache-tests/results/apache.json +++ b/test/fixtures/cache-tests/results/apache.json @@ -100,7 +100,7 @@ "cdn-date-update-exceed": true, "cdn-expires-update-exceed": [ "Assertion", - "Response 2 header Expires is \"null\", not \"Tue, 09 Jul 2024 01:05:30 GMT\"" + "Response 2 header Expires is \"null\", not \"Tue, 21 Jan 2025 00:20:15 GMT\"" ], "cdn-fresh-cc-nostore": [ "Assertion", @@ -340,7 +340,10 @@ "headers-store-Cache-Control": true, "headers-store-Clear-Site-Data": true, "headers-store-Connection": true, - "headers-store-Content-Encoding": true, + "headers-store-Content-Encoding": [ + "AbortError", + "This operation was aborted" + ], "headers-store-Content-Foo": true, "headers-store-Content-Length": true, "headers-store-Content-Location": true, @@ -493,11 +496,11 @@ "other-cookie": true, "other-date-update": [ "Assertion", - "Response 2 header Date is \"Tue, 09 Jul 2024 01:05:24 GMT\", not \"Tue, 09 Jul 2024 01:05:21 GMT\"" + "Response 2 header Date is \"Tue, 21 Jan 2025 00:20:09 GMT\", not \"Tue, 21 Jan 2025 00:20:06 GMT\"" ], "other-date-update-expires": [ "Assertion", - "Response 2 header Date is \"Tue, 09 Jul 2024 01:05:24 GMT\", not \"Tue, 09 Jul 2024 01:05:21 GMT\"" + "Response 2 header Date is \"Tue, 21 Jan 2025 00:20:09 GMT\", not \"Tue, 21 Jan 2025 00:20:06 GMT\"" ], "other-date-update-expires-update": true, "other-fresh-content-disposition-attachment": true, @@ -545,27 +548,42 @@ "query-args-different": true, "query-args-same": true, "stale-503": true, - "stale-close": true, + "stale-close": [ + "TypeError", + "fetch failed" + ], "stale-close-must-revalidate": [ - "Assertion", - "Response 2 comes from cache" + "TypeError", + "fetch failed" ], "stale-close-no-cache": [ - "Assertion", - "Response 2 comes from cache" + "TypeError", + "fetch failed" ], "stale-close-proxy-revalidate": [ - "Assertion", - "Response 2 comes from cache" + "TypeError", + "fetch failed" ], "stale-close-s-maxage=2": [ - "Assertion", - "Response 2 comes from cache" + "AbortError", + "This operation was aborted" + ], + "stale-sie-503": [ + "TypeError", + "fetch failed" + ], + "stale-sie-close": [ + "TypeError", + "fetch failed" + ], + "stale-warning-become": [ + "TypeError", + "fetch failed" + ], + "stale-warning-stored": [ + "TypeError", + "fetch failed" ], - "stale-sie-503": true, - "stale-sie-close": true, - "stale-warning-become": true, - "stale-warning-stored": true, "stale-while-revalidate": [ "Assertion", "Response 2 does not come from cache" diff --git a/test/fixtures/cache-tests/results/caddy.json b/test/fixtures/cache-tests/results/caddy.json index e29f0dca42f..d45ef15d4a9 100644 --- a/test/fixtures/cache-tests/results/caddy.json +++ b/test/fixtures/cache-tests/results/caddy.json @@ -178,7 +178,7 @@ "cdn-date-update-exceed": true, "cdn-expires-update-exceed": [ "Assertion", - "Response 2 header Expires is \"null\", not \"Tue, 09 Jul 2024 01:03:11 GMT\"" + "Response 2 header Expires is \"null\", not \"Tue, 21 Jan 2025 00:23:23 GMT\"" ], "cdn-fresh-cc-nostore": true, "cdn-max-age": true, @@ -205,7 +205,10 @@ "Assertion", "Response 2 comes from cache" ], - "cdn-no-store-cc-fresh": true, + "cdn-no-store-cc-fresh": [ + "Assertion", + "Response 2 comes from cache" + ], "cdn-private": true, "cdn-remove-age-exceed": [ "Assertion", @@ -287,28 +290,28 @@ "Assertion", "Response 2 does not come from cache" ], - "freshness-expires-age-fast-date": true, - "freshness-expires-age-slow-date": true, - "freshness-expires-ansi-c": [ + "freshness-expires-age-fast-date": [ "Assertion", - "Response 2 does not come from cache" + "Response 2 comes from cache" ], - "freshness-expires-far-future": [ + "freshness-expires-age-slow-date": [ + "Assertion", + "Response 2 comes from cache" + ], + "freshness-expires-ansi-c": [ "Assertion", "Response 2 does not come from cache" ], - "freshness-expires-future": [ + "freshness-expires-far-future": [ "Assertion", "Response 2 does not come from cache" ], + "freshness-expires-future": true, "freshness-expires-invalid": true, "freshness-expires-invalid-1-digit-hour": true, "freshness-expires-invalid-2-digit-year": true, "freshness-expires-invalid-aest": true, - "freshness-expires-invalid-date": [ - "Assertion", - "Response 2 does not come from cache" - ], + "freshness-expires-invalid-date": true, "freshness-expires-invalid-date-dashes": true, "freshness-expires-invalid-multiple-lines": true, "freshness-expires-invalid-multiple-spaces": true, @@ -390,25 +393,22 @@ "freshness-none": true, "freshness-s-maxage-shared": true, "head-200-freshness-update": [ - "FetchError", - "request to http://localhost:8006/test/3a29d44d-d103-492b-9046-be67546d71d7 failed, reason: Parse Error: Empty Content-Length" + "Assertion", + "Response 3 does not come from cache" ], "head-200-retain": [ - "FetchError", - "request to http://localhost:8006/test/d07b2651-4270-4ea6-ae9f-041b1b82aae8 failed, reason: Parse Error: Empty Content-Length" + "Assertion", + "Response 2 header Template-A is \"null\", not \"1\"" ], "head-200-update": [ - "FetchError", - "request to http://localhost:8006/test/eb317c8b-cea4-4b4c-b72b-71df1aa6e863 failed, reason: Parse Error: Empty Content-Length" + "Setup", + "Response 3 does not come from cache" ], "head-410-update": [ - "FetchError", - "request to http://localhost:8006/test/7f947275-bffe-4c72-a493-c64352423d8e failed, reason: Parse Error: Empty Content-Length" - ], - "head-writethrough": [ - "FetchError", - "request to http://localhost:8006/test/f9b80f6e-7980-4a44-ae58-d58ac539223c failed, reason: Parse Error: Empty Content-Length" + "Setup", + "Response 3 does not come from cache" ], + "head-writethrough": true, "headers-omit-headers-listed-in-Cache-Control-no-cache": [ "Setup", "Response 2 does not come from cache" @@ -586,10 +586,7 @@ "Response 1 age header not present." ], "other-age-gen": true, - "other-age-update-expires": [ - "Assertion", - "Response 2 does not come from cache" - ], + "other-age-update-expires": true, "other-age-update-max-age": true, "other-authorization": true, "other-authorization-must-revalidate": [ @@ -606,14 +603,8 @@ ], "other-cookie": true, "other-date-update": true, - "other-date-update-expires": [ - "Assertion", - "Response 2 does not come from cache" - ], - "other-date-update-expires-update": [ - "Assertion", - "Response 2 does not come from cache" - ], + "other-date-update-expires": true, + "other-date-update-expires-update": true, "other-fresh-content-disposition-attachment": true, "other-heuristic-content-disposition-attachment": [ "Assertion", @@ -658,10 +649,7 @@ "Response 2 status is 200, not 206" ], "pragma-request-extension": true, - "pragma-request-no-cache": [ - "Assertion", - "Response 2 does not come from cache" - ], + "pragma-request-no-cache": true, "pragma-response-extension": true, "pragma-response-no-cache": true, "pragma-response-no-cache-heuristic": [ @@ -806,10 +794,7 @@ "vary-invalidate": true, "vary-match": true, "vary-no-match": true, - "vary-normalise-combine": [ - "Assertion", - "Response 2 does not come from cache" - ], + "vary-normalise-combine": true, "vary-normalise-lang-case": [ "Assertion", "Response 2 does not come from cache" diff --git a/test/fixtures/cache-tests/results/fastly.json b/test/fixtures/cache-tests/results/fastly.json index c380900b700..bbf11332be0 100644 --- a/test/fixtures/cache-tests/results/fastly.json +++ b/test/fixtures/cache-tests/results/fastly.json @@ -172,7 +172,7 @@ "cdn-date-update-exceed": true, "cdn-expires-update-exceed": [ "Assertion", - "Response 2 header Expires is \"null\", not \"Tue, 09 Jul 2024 01:17:30 GMT\"" + "Response 2 header Expires is \"null\", not \"Tue, 21 Jan 2025 00:41:28 GMT\"" ], "cdn-fresh-cc-nostore": [ "Assertion", @@ -637,11 +637,11 @@ "other-cookie": true, "other-date-update": [ "Assertion", - "Response 2 header Date is \"Tue, 09 Jul 2024 01:17:25 GMT\", not \"Tue, 09 Jul 2024 01:17:22 GMT\"" + "Response 2 header Date is \"Tue, 21 Jan 2025 00:41:23 GMT\", not \"Tue, 21 Jan 2025 00:41:20 GMT\"" ], "other-date-update-expires": [ "Assertion", - "Response 2 header Date is \"Tue, 09 Jul 2024 01:17:25 GMT\", not \"Tue, 09 Jul 2024 01:17:22 GMT\"" + "Response 2 header Date is \"Tue, 21 Jan 2025 00:41:23 GMT\", not \"Tue, 21 Jan 2025 00:41:20 GMT\"" ], "other-date-update-expires-update": true, "other-fresh-content-disposition-attachment": true, @@ -821,10 +821,7 @@ "vary-invalidate": true, "vary-match": true, "vary-no-match": true, - "vary-normalise-combine": [ - "Assertion", - "Response 2 does not come from cache" - ], + "vary-normalise-combine": true, "vary-normalise-lang-case": [ "Assertion", "Response 2 does not come from cache" diff --git a/test/fixtures/cache-tests/results/firefox.json b/test/fixtures/cache-tests/results/firefox.json index 6757050ea7c..2c3e5a87e67 100644 --- a/test/fixtures/cache-tests/results/firefox.json +++ b/test/fixtures/cache-tests/results/firefox.json @@ -73,10 +73,7 @@ "cc-resp-must-revalidate-fresh": true, "cc-resp-must-revalidate-stale": true, "cc-resp-no-cache": true, - "cc-resp-no-cache-case-insensitive": [ - "Assertion", - "Response 2 comes from cache" - ], + "cc-resp-no-cache-case-insensitive": true, "cc-resp-no-cache-revalidate": true, "cc-resp-no-cache-revalidate-fresh": true, "cc-resp-no-store": true, @@ -179,10 +176,7 @@ "Response 2 does not come from cache" ], "freshness-max-age-age": true, - "freshness-max-age-case-insenstive": [ - "Assertion", - "Response 2 does not come from cache" - ], + "freshness-max-age-case-insenstive": true, "freshness-max-age-date": true, "freshness-max-age-decimal-five": true, "freshness-max-age-decimal-zero": true, diff --git a/test/fixtures/cache-tests/results/index.mjs b/test/fixtures/cache-tests/results/index.mjs index e8204031ea0..3d1afde079f 100644 --- a/test/fixtures/cache-tests/results/index.mjs +++ b/test/fixtures/cache-tests/results/index.mjs @@ -4,54 +4,54 @@ export default [ file: 'chrome.json', name: 'Chrome', type: 'browser', - version: '126.0.6478.127' + version: '132.0.6834.84' }, { file: 'firefox.json', name: 'Firefox', type: 'browser', - version: '127.0.2', + version: '134.0.1', link: 'https://github.com/http-tests/cache-tests/wiki/Firefox' }, { file: 'safari.json', name: 'Safari', type: 'browser', - version: 'Version 17.5 (19618.2.12.11.6)' + version: '18.2 (20620.1.16.11.8)' }, { file: 'nginx.json', name: 'nginx', type: 'rev-proxy', - version: '1.26.0-1ubuntu2', + version: '1.26.0-3ubuntu1', link: 'https://github.com/http-tests/cache-tests/wiki/nginx' }, { file: 'squid.json', name: 'Squid', type: 'rev-proxy', - version: '6.9-1ubuntu1', + version: '6.10-1ubuntu1', link: 'https://github.com/http-tests/cache-tests/wiki/Squid' }, { file: 'trafficserver.json', name: 'ATS', type: 'rev-proxy', - version: '9.2.4+ds-2', + version: '9.2.5+ds-1', link: 'https://github.com/http-tests/cache-tests/wiki/Traffic-Server' }, { file: 'apache.json', name: 'httpd', type: 'rev-proxy', - version: '2.4.59-2ubuntu2', + version: '2.4.62-3ubuntu1', link: 'https://github.com/http-tests/cache-tests/wiki/Apache-httpd' }, { file: 'varnish.json', name: 'Varnish', type: 'rev-proxy', - version: '7.1.1-1.1ubuntu1', + version: '7.5.0-3', link: 'https://github.com/http-tests/cache-tests/wiki/Varnish' }, { @@ -65,7 +65,7 @@ export default [ file: 'fastly.json', name: 'Fastly', type: 'cdn', - version: '2024-07-09', + version: '2025-01-21', link: 'https://github.com/http-tests/cache-tests/wiki/Fastly' } ] diff --git a/test/fixtures/cache-tests/results/nginx.json b/test/fixtures/cache-tests/results/nginx.json index a1807689659..9b6614c0f1e 100644 --- a/test/fixtures/cache-tests/results/nginx.json +++ b/test/fixtures/cache-tests/results/nginx.json @@ -12,8 +12,8 @@ "Response 2 header Content-Encoding is \"arizqhypgxofwne\", not \"askcumewogyqias\"" ], "304-etag-update-response-Content-Foo": [ - "AbortError", - "The user aborted a request." + "Assertion", + "Response 2 header Content-Foo is \"awsokgcyuqmieaw\", not \"axurolifczwtqnk\"" ], "304-etag-update-response-Content-Length": true, "304-etag-update-response-Content-Location": [ @@ -33,12 +33,12 @@ "Response 2 header Content-Security-Policy is \"default-src 'self'\", not \"default-src 'self' cdn.example.com\"" ], "304-etag-update-response-Content-Type": [ - "AbortError", - "The user aborted a request." + "Assertion", + "Response 2 header Content-Type is \"text/plain\", not \"text/plain;charset=utf-8\"" ], "304-etag-update-response-ETag": [ - "AbortError", - "The user aborted a request." + "Assertion", + "Response 2 header ETag is \"\"abcdef\"\", not \"\"ghijkl\"\"" ], "304-etag-update-response-Expires": [ "Assertion", @@ -136,8 +136,8 @@ "Response 2 comes from cache" ], "ccreq-ma1": [ - "Assertion", - "Response 2 comes from cache" + "AbortError", + "This operation was aborted" ], "ccreq-magreaterage": [ "Assertion", @@ -147,10 +147,13 @@ "Assertion", "Response 2 does not come from cache" ], - "ccreq-max-stale-age": true, + "ccreq-max-stale-age": [ + "AbortError", + "This operation was aborted" + ], "ccreq-min-fresh": [ - "Assertion", - "Response 2 comes from cache" + "AbortError", + "This operation was aborted" ], "ccreq-min-fresh-age": [ "Assertion", @@ -181,7 +184,7 @@ "cdn-date-update-exceed": true, "cdn-expires-update-exceed": [ "Assertion", - "Response 2 header Expires is \"null\", not \"Tue, 09 Jul 2024 01:06:50 GMT\"" + "Response 2 header Expires is \"null\", not \"Tue, 21 Jan 2025 00:19:03 GMT\"" ], "cdn-fresh-cc-nostore": [ "Assertion", @@ -270,7 +273,10 @@ "Assertion", "Request 2 header If-None-Match is \"abcdef\", not \"\"abcdef\"\"" ], - "conditional-etag-strong-respond": true, + "conditional-etag-strong-respond": [ + "AbortError", + "This operation was aborted" + ], "conditional-etag-strong-respond-multiple-first": true, "conditional-etag-strong-respond-multiple-last": true, "conditional-etag-strong-respond-multiple-second": true, @@ -588,8 +594,8 @@ "Response 3 comes from cache" ], "invalidate-POST": [ - "AbortError", - "The user aborted a request." + "Assertion", + "Response 3 comes from cache" ], "invalidate-POST-cl": [ "Assertion", @@ -643,11 +649,11 @@ "other-cookie": true, "other-date-update": [ "Assertion", - "Response 2 header Date is \"Tue, 09 Jul 2024 01:06:44 GMT\", not \"Tue, 09 Jul 2024 01:06:41 GMT\"" + "Response 2 header Date is \"Tue, 21 Jan 2025 00:18:57 GMT\", not \"Tue, 21 Jan 2025 00:18:54 GMT\"" ], "other-date-update-expires": [ "Assertion", - "Response 2 header Date is \"Tue, 09 Jul 2024 01:06:44 GMT\", not \"Tue, 09 Jul 2024 01:06:41 GMT\"" + "Response 2 header Date is \"Tue, 21 Jan 2025 00:18:57 GMT\", not \"Tue, 21 Jan 2025 00:18:54 GMT\"" ], "other-date-update-expires-update": true, "other-fresh-content-disposition-attachment": true, @@ -803,10 +809,7 @@ "vary-invalidate": true, "vary-match": true, "vary-no-match": true, - "vary-normalise-combine": [ - "Assertion", - "Response 2 does not come from cache" - ], + "vary-normalise-combine": true, "vary-normalise-lang-case": [ "Assertion", "Response 2 does not come from cache" @@ -838,12 +841,12 @@ ], "vary-syntax-star": true, "vary-syntax-star-foo": [ - "Assertion", - "Response 2 comes from cache" + "AbortError", + "This operation was aborted" ], "vary-syntax-star-star": [ - "Assertion", - "Response 2 comes from cache" + "AbortError", + "This operation was aborted" ], "vary-syntax-star-star-lines": true } diff --git a/test/fixtures/cache-tests/results/safari.json b/test/fixtures/cache-tests/results/safari.json index 9a0e5eda4ab..dfbedaa4cf9 100644 --- a/test/fixtures/cache-tests/results/safari.json +++ b/test/fixtures/cache-tests/results/safari.json @@ -342,54 +342,54 @@ "heuristic-delta-60": true, "heuristic-delta-600": true, "heuristic-delta-86400": true, - "invalidate-DELETE": [ + "invalidate-DELETE": true, + "invalidate-DELETE-cl": [ "Assertion", "Response 3 comes from cache" ], - "invalidate-DELETE-cl": [ + "invalidate-DELETE-failed": [ "Assertion", - "Response 3 comes from cache" + "Response 3 does not come from cache" ], - "invalidate-DELETE-failed": true, "invalidate-DELETE-location": [ "Assertion", "Response 3 comes from cache" ], - "invalidate-M-SEARCH": [ + "invalidate-M-SEARCH": true, + "invalidate-M-SEARCH-cl": [ "Assertion", "Response 3 comes from cache" ], - "invalidate-M-SEARCH-cl": [ + "invalidate-M-SEARCH-failed": [ "Assertion", - "Response 3 comes from cache" + "Response 3 does not come from cache" ], - "invalidate-M-SEARCH-failed": true, "invalidate-M-SEARCH-location": [ "Assertion", "Response 3 comes from cache" ], - "invalidate-POST": [ + "invalidate-POST": true, + "invalidate-POST-cl": [ "Assertion", "Response 3 comes from cache" ], - "invalidate-POST-cl": [ + "invalidate-POST-failed": [ "Assertion", - "Response 3 comes from cache" + "Response 3 does not come from cache" ], - "invalidate-POST-failed": true, "invalidate-POST-location": [ "Assertion", "Response 3 comes from cache" ], - "invalidate-PUT": [ + "invalidate-PUT": true, + "invalidate-PUT-cl": [ "Assertion", "Response 3 comes from cache" ], - "invalidate-PUT-cl": [ + "invalidate-PUT-failed": [ "Assertion", - "Response 3 comes from cache" + "Response 3 does not come from cache" ], - "invalidate-PUT-failed": true, "invalidate-PUT-location": [ "Assertion", "Response 3 comes from cache" diff --git a/test/fixtures/cache-tests/results/squid.json b/test/fixtures/cache-tests/results/squid.json index 56f65a1ce27..0b3b23c5d41 100644 --- a/test/fixtures/cache-tests/results/squid.json +++ b/test/fixtures/cache-tests/results/squid.json @@ -91,7 +91,7 @@ "cdn-date-update-exceed": true, "cdn-expires-update-exceed": [ "Assertion", - "Response 2 header Expires is \"null\", not \"Tue, 09 Jul 2024 00:51:04 GMT\"" + "Response 2 header Expires is \"null\", not \"Tue, 21 Jan 2025 00:17:30 GMT\"" ], "cdn-fresh-cc-nostore": [ "Assertion", diff --git a/test/fixtures/cache-tests/results/trafficserver.json b/test/fixtures/cache-tests/results/trafficserver.json index 01a0da3d1a6..52d3c18c6e3 100644 --- a/test/fixtures/cache-tests/results/trafficserver.json +++ b/test/fixtures/cache-tests/results/trafficserver.json @@ -97,7 +97,7 @@ "cdn-date-update-exceed": true, "cdn-expires-update-exceed": [ "Assertion", - "Response 2 header Expires is \"null\", not \"Tue, 09 Jul 2024 01:02:07 GMT\"" + "Response 2 header Expires is \"null\", not \"Tue, 21 Jan 2025 00:21:39 GMT\"" ], "cdn-fresh-cc-nostore": [ "Assertion", diff --git a/test/fixtures/cache-tests/results/varnish.json b/test/fixtures/cache-tests/results/varnish.json index 1b10d18c80b..067c395fc15 100644 --- a/test/fixtures/cache-tests/results/varnish.json +++ b/test/fixtures/cache-tests/results/varnish.json @@ -124,7 +124,7 @@ "cdn-date-update-exceed": true, "cdn-expires-update-exceed": [ "Assertion", - "Response 2 header Expires is \"null\", not \"Tue, 09 Jul 2024 01:02:40 GMT\"" + "Response 2 header Expires is \"null\", not \"Tue, 21 Jan 2025 00:22:31 GMT\"" ], "cdn-fresh-cc-nostore": [ "Assertion", @@ -749,10 +749,7 @@ "vary-invalidate": true, "vary-match": true, "vary-no-match": true, - "vary-normalise-combine": [ - "Assertion", - "Response 2 does not come from cache" - ], + "vary-normalise-combine": true, "vary-normalise-lang-case": [ "Assertion", "Response 2 does not come from cache" diff --git a/test/fixtures/cache-tests/test-engine/cli.mjs b/test/fixtures/cache-tests/test-engine/cli.mjs index 241ea37c394..2493a51a9c5 100644 --- a/test/fixtures/cache-tests/test-engine/cli.mjs +++ b/test/fixtures/cache-tests/test-engine/cli.mjs @@ -1,7 +1,6 @@ import { runTests, getResults } from './client/runner.mjs' import { determineTestResult } from './lib/results.mjs' import { GREEN, NC } from './lib/defines.mjs' -import fetch from 'node-fetch-with-proxy' import tests from '../tests/index.mjs' const baseUrl = process.env.npm_config_base || process.env.npm_package_config_base @@ -27,7 +26,7 @@ if (testId !== '') { testsToRun = tests } -await runTests(testsToRun, fetch, false, baseUrl).catch(err => { +await runTests(testsToRun, false, baseUrl).catch(err => { console.error(err) process.exit(1) }) diff --git a/test/fixtures/cache-tests/test-engine/client/config.mjs b/test/fixtures/cache-tests/test-engine/client/config.mjs index 147bdcebbf5..11aa7910992 100644 --- a/test/fixtures/cache-tests/test-engine/client/config.mjs +++ b/test/fixtures/cache-tests/test-engine/client/config.mjs @@ -1,18 +1,7 @@ -export let fetch = null export let useBrowserCache = false export let baseUrl = '' export const requestTimeout = 10 // seconds -export function setFetch (call) { - if (call !== undefined) { - if ('bind' in call) { - fetch = call.bind(fetch) - } else { - fetch = call - } - } -} - export function setUseBrowserCache (bool) { if (bool !== undefined) useBrowserCache = bool } diff --git a/test/fixtures/cache-tests/test-engine/client/fetching.mjs b/test/fixtures/cache-tests/test-engine/client/fetching.mjs index f27a7633220..e1260da1e7f 100644 --- a/test/fixtures/cache-tests/test-engine/client/fetching.mjs +++ b/test/fixtures/cache-tests/test-engine/client/fetching.mjs @@ -11,7 +11,7 @@ export function init (idx, reqConfig, prevResp) { init.headers.push(['Cache-Control', 'nothing-to-see-here']) // ditto } if ('request_method' in reqConfig) init.method = reqConfig.request_method - if ('request_headers' in reqConfig) init.headers = reqConfig.request_headers + if ('request_headers' in reqConfig) init.headers = init.headers.concat(reqConfig.request_headers) if ('magic_ims' in reqConfig && reqConfig.magic_ims === true) { for (let i = 0; i < init.headers.length; i++) { const header = init.headers[i] diff --git a/test/fixtures/cache-tests/test-engine/client/runner.mjs b/test/fixtures/cache-tests/test-engine/client/runner.mjs index dd3bb6d9278..9c7426e658c 100644 --- a/test/fixtures/cache-tests/test-engine/client/runner.mjs +++ b/test/fixtures/cache-tests/test-engine/client/runner.mjs @@ -1,8 +1,7 @@ import * as config from './config.mjs' import { makeTest, testResults } from './test.mjs' -export async function runTests (tests, myFetch, browserCache, base, chunkSize = 50) { - config.setFetch(myFetch) +export async function runTests (tests, browserCache, base, chunkSize = 25) { config.setBaseUrl(base) config.setUseBrowserCache(browserCache) diff --git a/test/fixtures/cache-tests/test-engine/client/test.mjs b/test/fixtures/cache-tests/test-engine/client/test.mjs index 078fdd4eccd..9acaeea8538 100644 --- a/test/fixtures/cache-tests/test-engine/client/test.mjs +++ b/test/fixtures/cache-tests/test-engine/client/test.mjs @@ -33,7 +33,7 @@ export async function makeTest (test) { }, config.requestTimeout * 1000) init.signal = controller.signal if (test.dump === true) clientUtils.logRequest(url, init, reqNum) - return config.fetch(url, init) + return fetch(url, init) .then(response => { responses.push(response) return checkResponse(test, requests, idx, response) @@ -61,6 +61,10 @@ export async function makeTest (test) { } } + function handleError (err) { + console.error(`ERROR: ${uuid} ${err.name} ${err.message}`) + } + return clientUtils.putTestConfig(uuid, requests) .catch(handleError) .then(runNextStep) @@ -293,7 +297,3 @@ function checkServerRequests (requests, responses, serverState) { } } } - -function handleError (err) { - console.error(`ERROR: ${err}`) -} diff --git a/test/fixtures/cache-tests/test-engine/client/utils.mjs b/test/fixtures/cache-tests/test-engine/client/utils.mjs index b1ac45cce9c..21d918d1e98 100644 --- a/test/fixtures/cache-tests/test-engine/client/utils.mjs +++ b/test/fixtures/cache-tests/test-engine/client/utils.mjs @@ -29,7 +29,7 @@ export async function putTestConfig (uuid, requests) { headers: [['content-type', 'application/json']], body: JSON.stringify(requests) } - return config.fetch(`${config.baseUrl}/config/${uuid}`, init) + return fetch(`${config.baseUrl}/config/${uuid}`, init) .then(response => { if (response.status !== 201) { let headers = '' @@ -44,7 +44,7 @@ export async function putTestConfig (uuid, requests) { } export async function getServerState (uuid) { - return config.fetch(`${config.baseUrl}/state/${uuid}`) + return fetch(`${config.baseUrl}/state/${uuid}`) .then(response => { if (response.status === 200) { return response.text() diff --git a/test/fixtures/cache-tests/tests/lib/templates.mjs b/test/fixtures/cache-tests/tests/lib/templates.mjs index 56e87e2f7e1..914b97b45c0 100644 --- a/test/fixtures/cache-tests/tests/lib/templates.mjs +++ b/test/fixtures/cache-tests/tests/lib/templates.mjs @@ -5,7 +5,7 @@ templates take an optional request object; the template will be updated with the request object in the following manner: - Object members will be assigned from the request -- Array members will be concatonated from the request +- Array members will be concatenated from the request - Other members will be updated from the request */ export function makeTemplate (template) { From af0eee28c00dd0e0cf001e3db5d2e000106c74b8 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 23 Jan 2025 00:59:57 -0800 Subject: [PATCH 05/17] chore: update WPT (#4011) Co-authored-by: Uzlopak <5059100+Uzlopak@users.noreply.github.com> --- test/fixtures/wpt/fetch/api/basic/gc.any.js | 19 ++++ test/fixtures/wpt/interfaces/css-fonts-5.idl | 54 +++++++++++ test/fixtures/wpt/interfaces/css-fonts.idl | 38 -------- test/fixtures/wpt/interfaces/css-mixins.idl | 10 ++ .../wpt/interfaces/digital-credentials.idl | 2 +- test/fixtures/wpt/interfaces/fedcm.idl | 1 + test/fixtures/wpt/interfaces/html.idl | 2 + .../mediacapture-surface-control.idl | 2 +- .../wpt/interfaces/permissions-policy.idl | 1 + .../wpt/interfaces/shared-storage.idl | 44 +++++++-- test/fixtures/wpt/interfaces/webnn.idl | 12 ++- .../resources/srcdoc-iframe-worker.js | 11 +++ .../resources/srcdoc-iframe.html | 92 +++++++++++++++++++ .../service-worker/srcdoc-iframe.https.html | 66 +++++++++++++ 14 files changed, 306 insertions(+), 48 deletions(-) create mode 100644 test/fixtures/wpt/fetch/api/basic/gc.any.js create mode 100644 test/fixtures/wpt/interfaces/css-fonts-5.idl create mode 100644 test/fixtures/wpt/service-workers/service-worker/resources/srcdoc-iframe-worker.js create mode 100644 test/fixtures/wpt/service-workers/service-worker/resources/srcdoc-iframe.html create mode 100644 test/fixtures/wpt/service-workers/service-worker/srcdoc-iframe.https.html diff --git a/test/fixtures/wpt/fetch/api/basic/gc.any.js b/test/fixtures/wpt/fetch/api/basic/gc.any.js new file mode 100644 index 00000000000..70362ff39ce --- /dev/null +++ b/test/fixtures/wpt/fetch/api/basic/gc.any.js @@ -0,0 +1,19 @@ +// META: global=window,worker +// META: script=/common/gc.js + +promise_test(async () => { + let i = 0; + const repeat = 5; + const buffer = await new Response(new ReadableStream({ + pull(c) { + if (i >= repeat) { + c.close(); + return; + } + ++i; + c.enqueue(new Uint8Array([0])) + garbageCollect(); + } + })).arrayBuffer(); + assert_equals(buffer.byteLength, repeat, `The buffer should be ${repeat}-byte long`); +}, "GC/CC should not abruptly close the stream while being consumed by Response"); diff --git a/test/fixtures/wpt/interfaces/css-fonts-5.idl b/test/fixtures/wpt/interfaces/css-fonts-5.idl new file mode 100644 index 00000000000..06461b0366c --- /dev/null +++ b/test/fixtures/wpt/interfaces/css-fonts-5.idl @@ -0,0 +1,54 @@ +// GENERATED CONTENT - DO NOT EDIT +// Content was automatically extracted by Reffy into webref +// (https://github.com/w3c/webref) +// Source: CSS Fonts Module Level 5 (https://drafts.csswg.org/css-fonts-5/) + +[Exposed=Window] +interface CSSFontFaceDescriptors : CSSStyleDeclaration { + attribute [LegacyNullToEmptyString] CSSOMString src; + attribute [LegacyNullToEmptyString] CSSOMString fontFamily; + attribute [LegacyNullToEmptyString] CSSOMString font-family; + attribute [LegacyNullToEmptyString] CSSOMString fontStyle; + attribute [LegacyNullToEmptyString] CSSOMString font-style; + attribute [LegacyNullToEmptyString] CSSOMString fontWeight; + attribute [LegacyNullToEmptyString] CSSOMString font-weight; + attribute [LegacyNullToEmptyString] CSSOMString fontStretch; + attribute [LegacyNullToEmptyString] CSSOMString font-stretch; + attribute [LegacyNullToEmptyString] CSSOMString fontWidth; + attribute [LegacyNullToEmptyString] CSSOMString font-width; + attribute [LegacyNullToEmptyString] CSSOMString fontSize; + attribute [LegacyNullToEmptyString] CSSOMString font-size; + attribute [LegacyNullToEmptyString] CSSOMString sizeAdjust; + attribute [LegacyNullToEmptyString] CSSOMString size-adjust; + attribute [LegacyNullToEmptyString] CSSOMString unicodeRange; + attribute [LegacyNullToEmptyString] CSSOMString unicode-range; + attribute [LegacyNullToEmptyString] CSSOMString fontFeatureSettings; + attribute [LegacyNullToEmptyString] CSSOMString font-feature-settings; + attribute [LegacyNullToEmptyString] CSSOMString fontVariationSettings; + attribute [LegacyNullToEmptyString] CSSOMString font-variation-settings; + attribute [LegacyNullToEmptyString] CSSOMString fontNamedInstance; + attribute [LegacyNullToEmptyString] CSSOMString font-named-instance; + attribute [LegacyNullToEmptyString] CSSOMString fontDisplay; + attribute [LegacyNullToEmptyString] CSSOMString font-display; + attribute [LegacyNullToEmptyString] CSSOMString fontLanguageOverride; + attribute [LegacyNullToEmptyString] CSSOMString font-language-override; + attribute [LegacyNullToEmptyString] CSSOMString ascentOverride; + attribute [LegacyNullToEmptyString] CSSOMString ascent-override; + attribute [LegacyNullToEmptyString] CSSOMString descentOverride; + attribute [LegacyNullToEmptyString] CSSOMString descent-override; + attribute [LegacyNullToEmptyString] CSSOMString lineGapOverride; + attribute [LegacyNullToEmptyString] CSSOMString line-gap-override; + attribute [LegacyNullToEmptyString] CSSOMString superscriptPositionOverride; + attribute [LegacyNullToEmptyString] CSSOMString superscript-position-override; + attribute [LegacyNullToEmptyString] CSSOMString subscriptPositionOverride; + attribute [LegacyNullToEmptyString] CSSOMString subscript-position-override; + attribute [LegacyNullToEmptyString] CSSOMString superscriptSizeOverride; + attribute [LegacyNullToEmptyString] CSSOMString superscript-size-override; + attribute [LegacyNullToEmptyString] CSSOMString subscriptSizeOverride; + attribute [LegacyNullToEmptyString] CSSOMString subscript-size-override; +}; + +[Exposed=Window] +interface CSSFontFaceRule : CSSRule { + [SameObject, PutForwards=cssText] readonly attribute CSSFontFaceDescriptors style; +}; diff --git a/test/fixtures/wpt/interfaces/css-fonts.idl b/test/fixtures/wpt/interfaces/css-fonts.idl index d5c9dc86705..9b8034bc6d7 100644 --- a/test/fixtures/wpt/interfaces/css-fonts.idl +++ b/test/fixtures/wpt/interfaces/css-fonts.idl @@ -3,44 +3,6 @@ // (https://github.com/w3c/webref) // Source: CSS Fonts Module Level 4 (https://drafts.csswg.org/css-fonts-4/) -[Exposed=Window] -interface CSSFontFaceDescriptors : CSSStyleDeclaration { - attribute [LegacyNullToEmptyString] CSSOMString src; - attribute [LegacyNullToEmptyString] CSSOMString fontFamily; - attribute [LegacyNullToEmptyString] CSSOMString font-family; - attribute [LegacyNullToEmptyString] CSSOMString fontStyle; - attribute [LegacyNullToEmptyString] CSSOMString font-style; - attribute [LegacyNullToEmptyString] CSSOMString fontWeight; - attribute [LegacyNullToEmptyString] CSSOMString font-weight; - attribute [LegacyNullToEmptyString] CSSOMString fontStretch; - attribute [LegacyNullToEmptyString] CSSOMString font-stretch; - attribute [LegacyNullToEmptyString] CSSOMString fontWidth; - attribute [LegacyNullToEmptyString] CSSOMString font-width; - attribute [LegacyNullToEmptyString] CSSOMString unicodeRange; - attribute [LegacyNullToEmptyString] CSSOMString unicode-range; - attribute [LegacyNullToEmptyString] CSSOMString fontFeatureSettings; - attribute [LegacyNullToEmptyString] CSSOMString font-feature-settings; - attribute [LegacyNullToEmptyString] CSSOMString fontVariationSettings; - attribute [LegacyNullToEmptyString] CSSOMString font-variation-settings; - attribute [LegacyNullToEmptyString] CSSOMString fontNamedInstance; - attribute [LegacyNullToEmptyString] CSSOMString font-named-instance; - attribute [LegacyNullToEmptyString] CSSOMString fontDisplay; - attribute [LegacyNullToEmptyString] CSSOMString font-display; - attribute [LegacyNullToEmptyString] CSSOMString fontLanguageOverride; - attribute [LegacyNullToEmptyString] CSSOMString font-language-override; - attribute [LegacyNullToEmptyString] CSSOMString ascentOverride; - attribute [LegacyNullToEmptyString] CSSOMString ascent-override; - attribute [LegacyNullToEmptyString] CSSOMString descentOverride; - attribute [LegacyNullToEmptyString] CSSOMString descent-override; - attribute [LegacyNullToEmptyString] CSSOMString lineGapOverride; - attribute [LegacyNullToEmptyString] CSSOMString line-gap-override; -}; - -[Exposed=Window] -interface CSSFontFaceRule : CSSRule { - [SameObject, PutForwards=cssText] readonly attribute CSSFontFaceDescriptors style; -}; - partial interface CSSRule { const unsigned short FONT_FEATURE_VALUES_RULE = 14; }; [Exposed=Window] diff --git a/test/fixtures/wpt/interfaces/css-mixins.idl b/test/fixtures/wpt/interfaces/css-mixins.idl index 49806ab5470..6629b3861f6 100644 --- a/test/fixtures/wpt/interfaces/css-mixins.idl +++ b/test/fixtures/wpt/interfaces/css-mixins.idl @@ -5,3 +5,13 @@ [Exposed=Window] interface CSSFunctionRule : CSSGroupingRule { }; + +[Exposed=Window] +interface CSSFunctionDescriptors : CSSStyleDeclaration { + attribute [LegacyNullToEmptyString] CSSOMString result; +}; + +[Exposed=Window] +interface CSSFunctionDeclarations : CSSRule { + [SameObject, PutForwards=cssText] readonly attribute CSSFunctionDescriptors style; +}; diff --git a/test/fixtures/wpt/interfaces/digital-credentials.idl b/test/fixtures/wpt/interfaces/digital-credentials.idl index e20079efa14..e4ebb3b3e86 100644 --- a/test/fixtures/wpt/interfaces/digital-credentials.idl +++ b/test/fixtures/wpt/interfaces/digital-credentials.idl @@ -19,5 +19,5 @@ dictionary DigitalCredentialRequest { [Exposed=Window, SecureContext] interface DigitalCredential : Credential { readonly attribute DOMString protocol; - readonly attribute object data; + [SameObject] readonly attribute object data; }; diff --git a/test/fixtures/wpt/interfaces/fedcm.idl b/test/fixtures/wpt/interfaces/fedcm.idl index 16b5b2faf10..07f7955ff64 100644 --- a/test/fixtures/wpt/interfaces/fedcm.idl +++ b/test/fixtures/wpt/interfaces/fedcm.idl @@ -49,6 +49,7 @@ dictionary IdentityProviderRequestOptions : IdentityProviderConfig { USVString nonce; DOMString loginHint; DOMString domainHint; + sequence fields; any params; }; diff --git a/test/fixtures/wpt/interfaces/html.idl b/test/fixtures/wpt/interfaces/html.idl index d4f8b9a7680..3832a6f9b71 100644 --- a/test/fixtures/wpt/interfaces/html.idl +++ b/test/fixtures/wpt/interfaces/html.idl @@ -1222,9 +1222,11 @@ interface HTMLDialogElement : HTMLElement { [CEReactions] attribute boolean open; attribute DOMString returnValue; + [CEReactions] attribute DOMString closedBy; [CEReactions] undefined show(); [CEReactions] undefined showModal(); [CEReactions] undefined close(optional DOMString returnValue); + [CEReactions] undefined requestClose(optional DOMString returnValue); }; [Exposed=Window] diff --git a/test/fixtures/wpt/interfaces/mediacapture-surface-control.idl b/test/fixtures/wpt/interfaces/mediacapture-surface-control.idl index b0bbd22304f..964f662da7f 100644 --- a/test/fixtures/wpt/interfaces/mediacapture-surface-control.idl +++ b/test/fixtures/wpt/interfaces/mediacapture-surface-control.idl @@ -12,5 +12,5 @@ partial interface CaptureController { partial interface CaptureController { constructor(); - Promise forwardWheel(HTMLElement element); + Promise forwardWheel(HTMLElement? element); }; diff --git a/test/fixtures/wpt/interfaces/permissions-policy.idl b/test/fixtures/wpt/interfaces/permissions-policy.idl index 5878d8d150a..806d2eb80f6 100644 --- a/test/fixtures/wpt/interfaces/permissions-policy.idl +++ b/test/fixtures/wpt/interfaces/permissions-policy.idl @@ -27,4 +27,5 @@ interface PermissionsPolicyViolationReportBody : ReportBody { readonly attribute long? lineNumber; readonly attribute long? columnNumber; readonly attribute DOMString disposition; + readonly attribute DOMString? allowAttribute; }; diff --git a/test/fixtures/wpt/interfaces/shared-storage.idl b/test/fixtures/wpt/interfaces/shared-storage.idl index b6863eff8ee..6f38d673952 100644 --- a/test/fixtures/wpt/interfaces/shared-storage.idl +++ b/test/fixtures/wpt/interfaces/shared-storage.idl @@ -38,15 +38,49 @@ partial interface Window { [SecureContext] readonly attribute SharedStorage? sharedStorage; }; +[Exposed=(Window,SharedStorageWorklet)] +interface SharedStorageModifierMethod {}; + +[Exposed=(Window, SharedStorageWorklet)] +interface SharedStorageSetMethod : SharedStorageModifierMethod { + constructor(DOMString key, DOMString value, optional SharedStorageSetMethodOptions options = {}); +}; + +[Exposed=(Window, SharedStorageWorklet)] +interface SharedStorageAppendMethod : SharedStorageModifierMethod { + constructor(DOMString key, DOMString value, optional SharedStorageModifierMethodOptions options = {}); +}; + +[Exposed=(Window, SharedStorageWorklet)] +interface SharedStorageDeleteMethod : SharedStorageModifierMethod { + constructor(DOMString key, optional SharedStorageModifierMethodOptions options = {}); +}; + +[Exposed=(Window, SharedStorageWorklet)] +interface SharedStorageClearMethod : SharedStorageModifierMethod { + constructor(optional SharedStorageModifierMethodOptions options = {}); +}; + +dictionary SharedStorageModifierMethodOptions { + DOMString withLock; +}; + +dictionary SharedStorageSetMethodOptions : SharedStorageModifierMethodOptions { + boolean ignoreIfPresent; +}; + [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(); + DOMString value, + optional SharedStorageModifierMethodOptions options = {}); + Promise delete(DOMString key, optional SharedStorageModifierMethodOptions options = {}); + Promise clear(optional SharedStorageModifierMethodOptions options = {}); + Promise batchUpdate(sequence methods, + optional SharedStorageModifierMethodOptions options = {}); [Exposed=Window] Promise selectURL(DOMString name, @@ -76,10 +110,6 @@ interface SharedStorage { async iterable; }; -dictionary SharedStorageSetMethodOptions { - boolean ignoreIfPresent = false; -}; - dictionary SharedStoragePrivateAggregationConfig { USVString aggregationCoordinatorOrigin; USVString contextId; diff --git a/test/fixtures/wpt/interfaces/webnn.idl b/test/fixtures/wpt/interfaces/webnn.idl index 132280b05f8..3e1d9a9f440 100644 --- a/test/fixtures/wpt/interfaces/webnn.idl +++ b/test/fixtures/wpt/interfaces/webnn.idl @@ -34,6 +34,10 @@ interface ML { typedef record MLNamedTensors; +dictionary MLContextLostInfo { + DOMString message; +}; + [SecureContext, Exposed=(Window, DedicatedWorker)] interface MLContext { undefined dispatch(MLGraph graph, MLNamedTensors inputs, MLNamedTensors outputs); @@ -46,6 +50,10 @@ interface MLContext { undefined writeTensor(MLTensor tensor, AllowSharedBufferSource inputData); MLOpSupportLimits opSupportLimits(); + + undefined destroy(); + + readonly attribute Promise lost; }; dictionary MLOpSupportLimits { @@ -71,7 +79,9 @@ dictionary MLSingleInputSupportLimits { }; [SecureContext, Exposed=(Window, DedicatedWorker)] -interface MLGraph {}; +interface MLGraph { + undefined destroy(); +}; enum MLInputOperandLayout { "nchw", diff --git a/test/fixtures/wpt/service-workers/service-worker/resources/srcdoc-iframe-worker.js b/test/fixtures/wpt/service-workers/service-worker/resources/srcdoc-iframe-worker.js new file mode 100644 index 00000000000..548116c89ea --- /dev/null +++ b/test/fixtures/wpt/service-workers/service-worker/resources/srcdoc-iframe-worker.js @@ -0,0 +1,11 @@ +self.addEventListener('message', event => { + event.source.postMessage('passed'); +}); + +self.addEventListener('fetch', event => { + let url = new URL(event.request.url); + if (!url.searchParams.get('test_resource')) { + return; + } + event.respondWith(new Response('passed')); +}); diff --git a/test/fixtures/wpt/service-workers/service-worker/resources/srcdoc-iframe.html b/test/fixtures/wpt/service-workers/service-worker/resources/srcdoc-iframe.html new file mode 100644 index 00000000000..6407845c5ad --- /dev/null +++ b/test/fixtures/wpt/service-workers/service-worker/resources/srcdoc-iframe.html @@ -0,0 +1,92 @@ + + + + + + + + diff --git a/test/fixtures/wpt/service-workers/service-worker/srcdoc-iframe.https.html b/test/fixtures/wpt/service-workers/service-worker/srcdoc-iframe.https.html new file mode 100644 index 00000000000..9c1c88e15a8 --- /dev/null +++ b/test/fixtures/wpt/service-workers/service-worker/srcdoc-iframe.https.html @@ -0,0 +1,66 @@ + +Service Worker: srcdoc frame handling + + + + + + \ No newline at end of file From 5e234ac4e0314b0f067d25a9cba5d6c5c29c2370 Mon Sep 17 00:00:00 2001 From: Konrad Date: Fri, 24 Jan 2025 16:50:36 +0100 Subject: [PATCH 06/17] docs: document about global dispatcher and errors (#3987) (#4014) * docs about global dispatcher and errors (#3987) Signed-off-by: Konrad Baumgart * Update docs/docs/api/Errors.md Co-authored-by: Carlos Fuentes --------- Signed-off-by: Konrad Baumgart Co-authored-by: Konrad Baumgart Co-authored-by: Matteo Collina Co-authored-by: Carlos Fuentes --- README.md | 3 ++- docs/docs/api/Errors.md | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index b47a5fe367c..8fa1c94d697 100644 --- a/README.md +++ b/README.md @@ -337,7 +337,8 @@ See [Dispatcher.upgrade](./docs/docs/api/Dispatcher.md#dispatcherupgradeoptions- * dispatcher `Dispatcher` -Sets the global dispatcher used by Common API Methods. +Sets the global dispatcher used by Common API Methods. Global dispatcher is shared among compatible undici modules, +including undici that is bundled internally with node.js. ### `undici.getGlobalDispatcher()` diff --git a/docs/docs/api/Errors.md b/docs/docs/api/Errors.md index c32868912a6..dfba3b39ce0 100644 --- a/docs/docs/api/Errors.md +++ b/docs/docs/api/Errors.md @@ -28,6 +28,7 @@ import { errors } from 'undici' | `ResponseExceededMaxSizeError` | `UND_ERR_RES_EXCEEDED_MAX_SIZE` | response body exceed the max size allowed | | `SecureProxyConnectionError` | `UND_ERR_PRX_TLS` | tls connection to a proxy failed | +Be aware of the possible difference between the global dispatcher version and the actual undici version you might be using. We recommend to avoid the check `instanceof errors.UndiciError` and seek for the `error.code === ''` instead to avoid inconsistencies. ### `SocketError` The `SocketError` has a `.socket` property which holds socket metadata: From 008187b24a89d0417ae38577739e8c228633b43a Mon Sep 17 00:00:00 2001 From: tmair Date: Mon, 27 Jan 2025 20:11:59 +0100 Subject: [PATCH 07/17] docs: fix incorrect method signature of `onResponseError` (#4030) --- docs/docs/api/Dispatcher.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/docs/api/Dispatcher.md b/docs/docs/api/Dispatcher.md index fb7e87d4e54..b91969a50b7 100644 --- a/docs/docs/api/Dispatcher.md +++ b/docs/docs/api/Dispatcher.md @@ -210,7 +210,7 @@ Returns: `Boolean` - `false` if dispatcher is busy and further dispatch calls wo * **onResponseStart** `(controller: DispatchController, statusCode: number, headers: Record, statusMessage?: string) => void` - Invoked when statusCode and headers have been received. May be invoked multiple times due to 1xx informational headers. Not required for `upgrade` requests. * **onResponseData** `(controller: DispatchController, chunk: Buffer) => void` - Invoked when response payload data is received. Not required for `upgrade` requests. * **onResponseEnd** `(controller: DispatchController, trailers: Record) => void` - Invoked when response payload and trailers have been received and the request has completed. Not required for `upgrade` requests. -* **onResponseError** `(error: Error) => void` - Invoked when an error has occurred. May not throw. +* **onResponseError** `(controller: DispatchController, error: Error) => void` - Invoked when an error has occurred. May not throw. #### Example 1 - Dispatch GET request From a9176c9ec67b3efa1732799d18162c3a4307164e Mon Sep 17 00:00:00 2001 From: Shivam Sharma Date: Sun, 2 Feb 2025 16:01:54 +0530 Subject: [PATCH 08/17] feat(docs): copy to clipboard button (#4037) --- docs/index.html | 31 ++++++++++++++++++++----------- 1 file changed, 20 insertions(+), 11 deletions(-) diff --git a/docs/index.html b/docs/index.html index f6ebf258e32..f87d5bc9cc1 100644 --- a/docs/index.html +++ b/docs/index.html @@ -1,5 +1,6 @@ + Node.js Undici @@ -9,6 +10,7 @@ +
+ - + + \ No newline at end of file From d685d387f4b2b23ae0b81ce3a8e97a63bcaad80f Mon Sep 17 00:00:00 2001 From: Khafra Date: Thu, 6 Feb 2025 13:50:02 -0500 Subject: [PATCH 09/17] don't check AbortSignal maxListeners on some node versions (#4045) --- lib/web/fetch/request.js | 21 ++++++++++++--------- test/fetch/long-lived-abort-controller.js | 3 ++- 2 files changed, 14 insertions(+), 10 deletions(-) diff --git a/lib/web/fetch/request.js b/lib/web/fetch/request.js index 97fea22cdbd..82a419162fa 100644 --- a/lib/web/fetch/request.js +++ b/lib/web/fetch/request.js @@ -37,6 +37,14 @@ const requestFinalizer = new FinalizationRegistry(({ signal, abort }) => { const dependentControllerMap = new WeakMap() +let abortSignalHasEventHandlerLeakWarning + +try { + abortSignalHasEventHandlerLeakWarning = getMaxListeners(new AbortController().signal) > 0 +} catch { + abortSignalHasEventHandlerLeakWarning = false +} + function buildAbort (acRef) { return abort @@ -424,15 +432,10 @@ class Request { const acRef = new WeakRef(ac) const abort = buildAbort(acRef) - // Third-party AbortControllers may not work with these. - // See, https://github.com/nodejs/undici/pull/1910#issuecomment-1464495619. - try { - // If the max amount of listeners is equal to the default, increase it - // This is only available in node >= v19.9.0 - if (typeof getMaxListeners === 'function' && getMaxListeners(signal) === defaultMaxListeners) { - setMaxListeners(1500, signal) - } - } catch {} + // If the max amount of listeners is equal to the default, increase it + if (abortSignalHasEventHandlerLeakWarning && getMaxListeners(signal) === defaultMaxListeners) { + setMaxListeners(1500, signal) + } util.addAbortListener(signal, abort) // The third argument must be a registry key to be unregistered. diff --git a/test/fetch/long-lived-abort-controller.js b/test/fetch/long-lived-abort-controller.js index 819f2580292..561ebee0661 100644 --- a/test/fetch/long-lived-abort-controller.js +++ b/test/fetch/long-lived-abort-controller.js @@ -2,7 +2,7 @@ const http = require('node:http') const { fetch } = require('../../') -const { once } = require('events') +const { once, setMaxListeners } = require('node:events') const { test } = require('node:test') const { closeServerAsPromise } = require('../utils/node-http') const { strictEqual } = require('node:assert') @@ -30,6 +30,7 @@ test('long-lived-abort-controller', { skip: true }, async (t) => { }) const controller = new AbortController() + setMaxListeners(1500, controller.signal) // The maxListener is set to 1500 in request.js. // we set it to 2000 to make sure that we are not leaking event listeners. From f7986284fa817e6e96fa55f24d6a9b6361251785 Mon Sep 17 00:00:00 2001 From: Antoine du Hamel Date: Fri, 7 Feb 2025 21:06:49 +0100 Subject: [PATCH 10/17] feat: mark `EnvHttpProxyAgent` as stable (#4049) * docs: mark `EnvHttpProxyAgent` as stable * remove experimental warning --- docs/docs/api/EnvHttpProxyAgent.md | 2 -- lib/dispatcher/env-http-proxy-agent.js | 9 --------- 2 files changed, 11 deletions(-) diff --git a/docs/docs/api/EnvHttpProxyAgent.md b/docs/docs/api/EnvHttpProxyAgent.md index 0bcbf25895a..adc2a242457 100644 --- a/docs/docs/api/EnvHttpProxyAgent.md +++ b/docs/docs/api/EnvHttpProxyAgent.md @@ -1,7 +1,5 @@ # Class: EnvHttpProxyAgent -Stability: Experimental. - Extends: `undici.Dispatcher` EnvHttpProxyAgent automatically reads the proxy configuration from the environment variables `http_proxy`, `https_proxy`, and `no_proxy` and sets up the proxy agents accordingly. When `http_proxy` and `https_proxy` are set, `http_proxy` is used for HTTP requests and `https_proxy` is used for HTTPS requests. If only `http_proxy` is set, `http_proxy` is used for both HTTP and HTTPS requests. If only `https_proxy` is set, it is only used for HTTPS requests. diff --git a/lib/dispatcher/env-http-proxy-agent.js b/lib/dispatcher/env-http-proxy-agent.js index 897011adbcd..48cc3f88e7f 100644 --- a/lib/dispatcher/env-http-proxy-agent.js +++ b/lib/dispatcher/env-http-proxy-agent.js @@ -10,8 +10,6 @@ const DEFAULT_PORTS = { 'https:': 443 } -let experimentalWarned = false - class EnvHttpProxyAgent extends DispatcherBase { #noProxyValue = null #noProxyEntries = null @@ -21,13 +19,6 @@ class EnvHttpProxyAgent extends DispatcherBase { super() this.#opts = opts - if (!experimentalWarned) { - experimentalWarned = true - process.emitWarning('EnvHttpProxyAgent is experimental, expect them to change at any time.', { - code: 'UNDICI-EHPA' - }) - } - const { httpProxy, httpsProxy, noProxy, ...agentOpts } = opts this[kNoProxyAgent] = new Agent(agentOpts) From c7f3d77011234fe243c317ada1398044032342cc Mon Sep 17 00:00:00 2001 From: Carlos Fuentes Date: Sun, 9 Feb 2025 17:30:46 +0100 Subject: [PATCH 11/17] test: fix windows wpt (#4050) * test: fixup * chore: narrow to windows * test: another batch * test: one moreq * chore: narrow to wpt * test: one moreq * Revert "chore: narrow to wpt" This reverts commit cb56079a2434b0c7f2840131838de9a7d1d70ec2. * Revert "chore: narrow to windows" This reverts commit 02d9c315d6b93a9f8b1971e73725d9c87d4f82d4. --- test/wpt/runner/runner.mjs | 4 ++-- test/wpt/server/server.mjs | 2 +- test/wpt/start-cacheStorage.mjs | 2 +- test/wpt/start-eventsource.mjs | 2 +- test/wpt/start-fetch.mjs | 2 +- test/wpt/start-mimesniff.mjs | 2 +- test/wpt/start-websockets.mjs | 2 +- 7 files changed, 8 insertions(+), 8 deletions(-) diff --git a/test/wpt/runner/runner.mjs b/test/wpt/runner/runner.mjs index a800cfff9bf..4012c71eb69 100644 --- a/test/wpt/runner/runner.mjs +++ b/test/wpt/runner/runner.mjs @@ -7,7 +7,7 @@ import { colors, handlePipes, normalizeName, parseMeta, resolveStatusPath } from const alwaysExit0 = process.env.GITHUB_WORKFLOW === 'Daily WPT report' -const basePath = fileURLToPath(join(import.meta.url, '../..')) +const basePath = join(fileURLToPath(import.meta.url), '../..') const testPath = join(basePath, '../fixtures/wpt') const statusPath = join(basePath, 'status') @@ -135,7 +135,7 @@ export class WPTRunner extends EventEmitter { } async run () { - const workerPath = fileURLToPath(join(import.meta.url, '../worker.mjs')) + const workerPath = join(fileURLToPath(import.meta.url), '../worker.mjs') /** @type {Set} */ const activeWorkers = new Set() let finishedFiles = 1 diff --git a/test/wpt/server/server.mjs b/test/wpt/server/server.mjs index 8cd81b069ec..2ade5291c6e 100644 --- a/test/wpt/server/server.mjs +++ b/test/wpt/server/server.mjs @@ -10,7 +10,7 @@ import { route as redirectRoute } from './routes/redirect.mjs' import { Pipeline } from './util.mjs' import { symbols } from './constants.mjs' -const tests = fileURLToPath(join(import.meta.url, '../../../fixtures/wpt')) +const tests = join(fileURLToPath(import.meta.url), '../../../fixtures/wpt') // https://web-platform-tests.org/tools/wptserve/docs/stash.html class Stash extends Map { diff --git a/test/wpt/start-cacheStorage.mjs b/test/wpt/start-cacheStorage.mjs index fe818dddc14..bb5d2c20060 100644 --- a/test/wpt/start-cacheStorage.mjs +++ b/test/wpt/start-cacheStorage.mjs @@ -4,7 +4,7 @@ import { fileURLToPath } from 'url' import { fork } from 'child_process' import { on } from 'events' -const serverPath = fileURLToPath(join(import.meta.url, '../server/server.mjs')) +const serverPath = join(fileURLToPath(import.meta.url), '../server/server.mjs') const child = fork(serverPath, [], { stdio: ['pipe', 'pipe', 'pipe', 'ipc'] diff --git a/test/wpt/start-eventsource.mjs b/test/wpt/start-eventsource.mjs index f2a92562108..213aa4e3d8a 100644 --- a/test/wpt/start-eventsource.mjs +++ b/test/wpt/start-eventsource.mjs @@ -4,7 +4,7 @@ import { fileURLToPath } from 'url' import { fork } from 'child_process' import { on } from 'events' -const serverPath = fileURLToPath(join(import.meta.url, '../server/server.mjs')) +const serverPath = join(fileURLToPath(import.meta.url), '../server/server.mjs') const child = fork(serverPath, [], { stdio: ['pipe', 'pipe', 'pipe', 'ipc'] diff --git a/test/wpt/start-fetch.mjs b/test/wpt/start-fetch.mjs index 0e2168e610c..520dd2a8876 100644 --- a/test/wpt/start-fetch.mjs +++ b/test/wpt/start-fetch.mjs @@ -6,7 +6,7 @@ import { on } from 'events' const { WPT_REPORT } = process.env -const serverPath = fileURLToPath(join(import.meta.url, '../server/server.mjs')) +const serverPath = join(fileURLToPath(import.meta.url), '../server/server.mjs') const child = fork(serverPath, [], { stdio: ['pipe', 'pipe', 'pipe', 'ipc'] diff --git a/test/wpt/start-mimesniff.mjs b/test/wpt/start-mimesniff.mjs index ebe4e783e3b..d28782fe926 100644 --- a/test/wpt/start-mimesniff.mjs +++ b/test/wpt/start-mimesniff.mjs @@ -6,7 +6,7 @@ import { on } from 'events' const { WPT_REPORT } = process.env -const serverPath = fileURLToPath(join(import.meta.url, '../server/server.mjs')) +const serverPath = join(fileURLToPath(import.meta.url), '../server/server.mjs') const child = fork(serverPath, [], { stdio: ['pipe', 'pipe', 'pipe', 'ipc'] diff --git a/test/wpt/start-websockets.mjs b/test/wpt/start-websockets.mjs index ec42af0eeb1..2a909dde3c2 100644 --- a/test/wpt/start-websockets.mjs +++ b/test/wpt/start-websockets.mjs @@ -22,7 +22,7 @@ if (process.env.CI) { // process.exit(0) } -const serverPath = fileURLToPath(join(import.meta.url, '../server/websocket.mjs')) +const serverPath = join(fileURLToPath(import.meta.url), '../server/websocket.mjs') const child = fork(serverPath, [], { stdio: ['pipe', 'pipe', 'pipe', 'ipc'] From c14781c6a9106cec8276db8741ad0b9b396ebf9b Mon Sep 17 00:00:00 2001 From: Frederik Prijck Date: Sun, 16 Feb 2025 10:02:27 +0100 Subject: [PATCH 12/17] fix: do not throw unhandled exception when data is undefined in interceptor.reply (#4036) --- lib/mock/mock-utils.js | 4 +++- test/mock-interceptor.js | 40 ++++++++++++++++++++++++++++++++++++++++ test/mock-utils.js | 6 ++++++ 3 files changed, 49 insertions(+), 1 deletion(-) diff --git a/lib/mock/mock-utils.js b/lib/mock/mock-utils.js index b19aaaf8e11..cce9f818c84 100644 --- a/lib/mock/mock-utils.js +++ b/lib/mock/mock-utils.js @@ -124,8 +124,10 @@ function getResponseData (data) { return data } else if (typeof data === 'object') { return JSON.stringify(data) - } else { + } else if (data) { return data.toString() + } else { + return '' } } diff --git a/test/mock-interceptor.js b/test/mock-interceptor.js index 7b17efe1a6e..09b3b13c5a6 100644 --- a/test/mock-interceptor.js +++ b/test/mock-interceptor.js @@ -107,6 +107,46 @@ describe('MockInterceptor - reply options callback', () => { }) }) + test('should handle undefined data', t => { + t = tspl(t, { plan: 2 }) + + const mockInterceptor = new MockInterceptor({ + path: '', + method: '' + }, []) + const result = mockInterceptor.reply((options) => ({ + statusCode: 200, + data: undefined + })) + t.ok(result instanceof MockScope) + + // Test parameters + + const baseUrl = 'http://localhost:9999' + const mockAgent = new MockAgent() + after(() => mockAgent.close()) + + const mockPool = mockAgent.get(baseUrl) + + mockPool.intercept({ + path: '/test', + method: 'GET' + }).reply((options) => { + t.deepStrictEqual(options, { path: '/test', method: 'GET', headers: { foo: 'bar' } }) + return { statusCode: 200, data: 'hello' } + }) + + mockPool.dispatch({ + path: '/test', + method: 'GET', + headers: { foo: 'bar' } + }, { + onHeaders: () => { }, + onData: () => { }, + onComplete: () => { } + }) + }) + test('should error if passed options invalid', async (t) => { t = tspl(t, { plan: 4 }) diff --git a/test/mock-utils.js b/test/mock-utils.js index b80db2a401c..2d0044b60dc 100644 --- a/test/mock-utils.js +++ b/test/mock-utils.js @@ -174,6 +174,12 @@ describe('getResponseData', () => { const responseData = getResponseData(new TextEncoder().encode('{"test":true}').buffer) t.ok(responseData instanceof ArrayBuffer) }) + + test('it should handle undefined', (t) => { + t = tspl(t, { plan: 1 }) + const responseData = getResponseData(undefined) + t.strictEqual(responseData, '') + }) }) test('getStatusText', (t) => { From 4269dabb6c8a7027c0bcb7c7d24e7a09c90549cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=BCrg=C3=BCn=20Day=C4=B1o=C4=9Flu?= Date: Sun, 16 Feb 2025 10:03:21 +0100 Subject: [PATCH 13/17] fix: handle missing vary header values (#4031) --- lib/cache/memory-cache-store.js | 8 +++- lib/cache/sqlite-cache-store.js | 21 ++++----- lib/util/cache.js | 17 +++---- test/cache-interceptor/utils.js | 34 ++++++++++++++ test/issue-3959.js | 65 ++++++++++++++++++++++++++ test/types/cache-interceptor.test-d.ts | 39 ++++++++++++++++ types/cache-interceptor.d.ts | 4 +- 7 files changed, 166 insertions(+), 22 deletions(-) create mode 100644 test/issue-3959.js diff --git a/lib/cache/memory-cache-store.js b/lib/cache/memory-cache-store.js index dd5ac00b5a3..6fa7356b366 100644 --- a/lib/cache/memory-cache-store.js +++ b/lib/cache/memory-cache-store.js @@ -79,7 +79,13 @@ class MemoryCacheStore { const entry = this.#entries.get(topLevelKey)?.find((entry) => ( entry.deleteAt > now && entry.method === key.method && - (entry.vary == null || Object.keys(entry.vary).every(headerName => entry.vary[headerName] === key.headers?.[headerName])) + (entry.vary == null || Object.keys(entry.vary).every(headerName => { + if (entry.vary[headerName] === null) { + return key.headers[headerName] === undefined + } + + return entry.vary[headerName] === key.headers[headerName] + })) )) return entry == null diff --git a/lib/cache/sqlite-cache-store.js b/lib/cache/sqlite-cache-store.js index 682aac31326..e027cff84ea 100644 --- a/lib/cache/sqlite-cache-store.js +++ b/lib/cache/sqlite-cache-store.js @@ -411,10 +411,6 @@ module.exports = class SqliteCacheStore { let matches = true if (value.vary) { - if (!headers) { - return undefined - } - const vary = JSON.parse(value.vary) for (const header in vary) { @@ -440,18 +436,21 @@ module.exports = class SqliteCacheStore { * @returns {boolean} */ function headerValueEquals (lhs, rhs) { + if (lhs == null && rhs == null) { + return true + } + + if ((lhs == null && rhs != null) || + (lhs != null && rhs == null)) { + return false + } + if (Array.isArray(lhs) && Array.isArray(rhs)) { if (lhs.length !== rhs.length) { return false } - for (let i = 0; i < lhs.length; i++) { - if (rhs.includes(lhs[i])) { - return false - } - } - - return true + return lhs.every((x, i) => x === rhs[i]) } return lhs === rhs diff --git a/lib/util/cache.js b/lib/util/cache.js index 35c53512b2a..41d1c1b7656 100644 --- a/lib/util/cache.js +++ b/lib/util/cache.js @@ -26,10 +26,14 @@ function makeCacheKey (opts) { if (typeof key !== 'string' || typeof val !== 'string') { throw new Error('opts.headers is not a valid header map') } - headers[key] = val + headers[key.toLowerCase()] = val } } else if (typeof opts.headers === 'object') { - headers = opts.headers + headers = {} + + for (const key of Object.keys(opts.headers)) { + headers[key.toLowerCase()] = opts.headers[key] + } } else { throw new Error('opts.headers is not an object') } @@ -260,19 +264,16 @@ function parseVaryHeader (varyHeader, headers) { return headers } - const output = /** @type {Record} */ ({}) + const output = /** @type {Record} */ ({}) const varyingHeaders = typeof varyHeader === 'string' ? varyHeader.split(',') : varyHeader + for (const header of varyingHeaders) { const trimmedHeader = header.trim().toLowerCase() - if (headers[trimmedHeader]) { - output[trimmedHeader] = headers[trimmedHeader] - } else { - return undefined - } + output[trimmedHeader] = headers[trimmedHeader] ?? null } return output diff --git a/test/cache-interceptor/utils.js b/test/cache-interceptor/utils.js index 2fc8bc17dea..2ae75d15489 100644 --- a/test/cache-interceptor/utils.js +++ b/test/cache-interceptor/utils.js @@ -214,6 +214,40 @@ describe('parseVaryHeader', () => { 'another-one': '123' }) }) + + test('handles missing headers with null', () => { + const result = parseVaryHeader('Accept-Encoding, Authorization', {}) + deepStrictEqual(result, { + 'accept-encoding': null, + authorization: null + }) + }) + + test('handles mix of present and missing headers', () => { + const result = parseVaryHeader('Accept-Encoding, Authorization', { + authorization: 'example-value' + }) + deepStrictEqual(result, { + 'accept-encoding': null, + authorization: 'example-value' + }) + }) + + test('handles array input', () => { + const result = parseVaryHeader(['Accept-Encoding', 'Authorization'], { + 'accept-encoding': 'gzip' + }) + deepStrictEqual(result, { + 'accept-encoding': 'gzip', + authorization: null + }) + }) + + test('preserves existing * behavior', () => { + const headers = { accept: 'text/html' } + const result = parseVaryHeader('*', headers) + deepStrictEqual(result, headers) + }) }) describe('isEtagUsable', () => { diff --git a/test/issue-3959.js b/test/issue-3959.js new file mode 100644 index 00000000000..2d83a6030e0 --- /dev/null +++ b/test/issue-3959.js @@ -0,0 +1,65 @@ +const { describe, test, after } = require('node:test') +const assert = require('node:assert') +const { createServer } = require('node:http') +const MemoryCacheStore = require('../lib/cache/memory-cache-store.js') +const { request, Agent, setGlobalDispatcher } = require('..') +const { interceptors } = require('..') + +describe('Cache with Vary headers', () => { + async function runCacheTest (store) { + let requestCount = 0 + const server = createServer((req, res) => { + requestCount++ + res.setHeader('Vary', 'Accept-Encoding') + res.setHeader('Cache-Control', 'max-age=60') + res.end(`Request count: ${requestCount}`) + }) + + await new Promise(resolve => server.listen(0, resolve)) + const port = server.address().port + const url = `http://localhost:${port}` + + const agent = new Agent() + setGlobalDispatcher( + agent.compose( + interceptors.cache({ + store, + cacheByDefault: 1000, + methods: ['GET'] + }) + ) + ) + + const res1 = await request(url) + const body1 = await res1.body.text() + assert.strictEqual(body1, 'Request count: 1') + assert.strictEqual(requestCount, 1) + + const res2 = await request(url) + const body2 = await res2.body.text() + assert.strictEqual(body2, 'Request count: 1') + assert.strictEqual(requestCount, 1) + + const res3 = await request(url, { + headers: { + 'Accept-Encoding': 'gzip' + } + }) + const body3 = await res3.body.text() + assert.strictEqual(body3, 'Request count: 2') + assert.strictEqual(requestCount, 2) + + await new Promise(resolve => server.close(resolve)) + } + + test('should cache response with MemoryCacheStore when Vary header exists but request header is missing', async () => { + await runCacheTest(new MemoryCacheStore()) + }) + + test('should cache response with SqliteCacheStore when Vary header exists but request header is missing', { skip: process.versions.node < '22' }, async () => { + const SqliteCacheStore = require('../lib/cache/sqlite-cache-store.js') + const sqliteStore = new SqliteCacheStore() + await runCacheTest(sqliteStore) + after(() => sqliteStore.close()) + }) +}) diff --git a/test/types/cache-interceptor.test-d.ts b/test/types/cache-interceptor.test-d.ts index 6fb66955de5..ce9543646cb 100644 --- a/test/types/cache-interceptor.test-d.ts +++ b/test/types/cache-interceptor.test-d.ts @@ -78,6 +78,45 @@ expectNotAssignable({ deleteAt: '' }) +expectAssignable({ + statusCode: 200, + statusMessage: 'OK', + headers: {}, + vary: { + 'accept-encoding': null, + authorization: 'example-value' + }, + cachedAt: 0, + staleAt: 0, + deleteAt: 0 +}) + +expectAssignable({ + statusCode: 200, + statusMessage: 'OK', + headers: {}, + vary: { + 'accept-encoding': null, + authorization: null + }, + cachedAt: 0, + staleAt: 0, + deleteAt: 0 +}) + +expectNotAssignable({ + statusCode: 200, + statusMessage: 'OK', + headers: {}, + vary: { + 'accept-encoding': undefined, + authorization: 'example-value' + }, + cachedAt: 0, + staleAt: 0, + deleteAt: 0 +}) + expectAssignable({}) expectAssignable({ maxSize: 0 diff --git a/types/cache-interceptor.d.ts b/types/cache-interceptor.d.ts index 1713ca7e747..e53be60a611 100644 --- a/types/cache-interceptor.d.ts +++ b/types/cache-interceptor.d.ts @@ -70,7 +70,7 @@ declare namespace CacheHandler { statusCode: number statusMessage: string headers: Record - vary?: Record + vary?: Record etag?: string cacheControlDirectives?: CacheControlDirectives cachedAt: number @@ -88,7 +88,7 @@ declare namespace CacheHandler { statusCode: number statusMessage: string headers: Record - vary?: Record + vary?: Record etag?: string body?: Readable | Iterable | AsyncIterable | Buffer | Iterable | AsyncIterable | string cacheControlDirectives: CacheControlDirectives, From 608d5f6683b70576aa8dc0fb87a3514f370c28d5 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Sun, 16 Feb 2025 10:38:01 +0100 Subject: [PATCH 14/17] chore: update WPT (#4028) Co-authored-by: Uzlopak <5059100+Uzlopak@users.noreply.github.com> --- .../dispatcher/remote-executor-worker.js | 5 + .../common/dispatcher/remote-executor.html | 5 +- .../iframe.tentative.https.window.js | 24 -- .../new-window.tentative.https.window.js | 32 +- ...tribute-redirect.tentative.https.window.js | 9 +- ...rmissions-policy.tentative.https.window.js | 13 +- ...-by-permissions-policy.tentative.window.js | 1 + .../permissions-policy/resources/helper.js | 5 +- .../quota.tentative.https.window.js | 134 -------- .../wpt/fetch/fetch-later/quota/README.md | 10 + ...versized-payload.tentative.https.window.js | 63 ++++ .../quota/cross-origin-iframe/README.md | 9 + ...versized-payload.tentative.https.window.js | 56 ++++ .../empty-payload.tentative.https.window.js | 37 +++ .../max-payload.tentative.https.window.js | 113 +++++++ ...multiple-iframes.tentative.https.window.js | 62 ++++ ...versized-payload.tentative.https.window.js | 25 ++ .../small-payload.tentative.https.window.js | 22 ++ .../empty-payload.tentative.https.window.js | 37 +++ .../max-payload.tentative.https.window.js | 48 +++ ...multiple-origins.tentative.https.window.js | 43 +++ ...versized-payload.tentative.https.window.js | 24 ++ .../fetch-later/quota/resources/helper.js | 35 ++ .../quota/same-origin-iframe/README.md | 9 + ...versized-payload.tentative.https.window.js | 55 ++++ .../empty-payload.tentative.https.window.js | 37 +++ .../max-payload.tentative.https.window.js | 61 ++++ ...multiple-iframes.tentative.https.window.js | 65 ++++ ...versized-payload.tentative.https.window.js | 25 ++ .../small-payload.tentative.https.window.js | 21 ++ .../small-payload.tentative.https.window.js | 19 ++ .../resources/fetch-later-helper.js | 308 ++++++++++++++++++ .../fetch-later/resources/fetch-later.html | 28 +- .../wpt/interfaces/css-highlight-api.idl | 8 + test/fixtures/wpt/interfaces/cssom-view.idl | 3 + test/fixtures/wpt/interfaces/html.idl | 18 +- .../interest-invokers.tentative.idl | 15 +- .../wpt/interfaces/media-capabilities.idl | 6 +- .../mediacapture-surface-control.idl | 6 +- test/fixtures/wpt/interfaces/observable.idl | 2 +- .../wpt/interfaces/permissions-policy.idl | 1 + .../wpt/interfaces/shared-storage.idl | 1 + test/fixtures/wpt/interfaces/speech-api.idl | 31 +- test/fixtures/wpt/interfaces/turtledove.idl | 22 +- .../wpt/interfaces/web-animations-2.idl | 24 ++ test/fixtures/wpt/interfaces/webcodecs.idl | 2 + test/fixtures/wpt/interfaces/webgpu.idl | 3 + .../wpt/resources/chromium/webxr-test.js | 20 +- .../service-worker/resources/router-rules.js | 10 +- .../static-router-invalid-rules.https.html | 26 +- .../static-router-no-fetch-handler.https.html | 12 + test/fixtures/wpt/websockets/constants.sub.js | 1 + .../cookies/cross-origin-cookie-set.html | 40 +++ .../opening-handshake/received-301-code.html | 23 ++ .../resources/redirect_response.py | 3 + 55 files changed, 1489 insertions(+), 228 deletions(-) create mode 100644 test/fixtures/wpt/common/dispatcher/remote-executor-worker.js delete mode 100644 test/fixtures/wpt/fetch/fetch-later/quota.tentative.https.window.js create mode 100644 test/fixtures/wpt/fetch/fetch-later/quota/README.md create mode 100644 test/fixtures/wpt/fetch/fetch-later/quota/accumulated-oversized-payload.tentative.https.window.js create mode 100644 test/fixtures/wpt/fetch/fetch-later/quota/cross-origin-iframe/README.md create mode 100644 test/fixtures/wpt/fetch/fetch-later/quota/cross-origin-iframe/accumulated-oversized-payload.tentative.https.window.js create mode 100644 test/fixtures/wpt/fetch/fetch-later/quota/cross-origin-iframe/empty-payload.tentative.https.window.js create mode 100644 test/fixtures/wpt/fetch/fetch-later/quota/cross-origin-iframe/max-payload.tentative.https.window.js create mode 100644 test/fixtures/wpt/fetch/fetch-later/quota/cross-origin-iframe/multiple-iframes.tentative.https.window.js create mode 100644 test/fixtures/wpt/fetch/fetch-later/quota/cross-origin-iframe/oversized-payload.tentative.https.window.js create mode 100644 test/fixtures/wpt/fetch/fetch-later/quota/cross-origin-iframe/small-payload.tentative.https.window.js create mode 100644 test/fixtures/wpt/fetch/fetch-later/quota/empty-payload.tentative.https.window.js create mode 100644 test/fixtures/wpt/fetch/fetch-later/quota/max-payload.tentative.https.window.js create mode 100644 test/fixtures/wpt/fetch/fetch-later/quota/multiple-origins.tentative.https.window.js create mode 100644 test/fixtures/wpt/fetch/fetch-later/quota/oversized-payload.tentative.https.window.js create mode 100644 test/fixtures/wpt/fetch/fetch-later/quota/resources/helper.js create mode 100644 test/fixtures/wpt/fetch/fetch-later/quota/same-origin-iframe/README.md create mode 100644 test/fixtures/wpt/fetch/fetch-later/quota/same-origin-iframe/accumulated-oversized-payload.tentative.https.window.js create mode 100644 test/fixtures/wpt/fetch/fetch-later/quota/same-origin-iframe/empty-payload.tentative.https.window.js create mode 100644 test/fixtures/wpt/fetch/fetch-later/quota/same-origin-iframe/max-payload.tentative.https.window.js create mode 100644 test/fixtures/wpt/fetch/fetch-later/quota/same-origin-iframe/multiple-iframes.tentative.https.window.js create mode 100644 test/fixtures/wpt/fetch/fetch-later/quota/same-origin-iframe/oversized-payload.tentative.https.window.js create mode 100644 test/fixtures/wpt/fetch/fetch-later/quota/same-origin-iframe/small-payload.tentative.https.window.js create mode 100644 test/fixtures/wpt/fetch/fetch-later/quota/small-payload.tentative.https.window.js create mode 100644 test/fixtures/wpt/websockets/cookies/cross-origin-cookie-set.html create mode 100644 test/fixtures/wpt/websockets/opening-handshake/received-301-code.html create mode 100644 test/fixtures/wpt/websockets/opening-handshake/resources/redirect_response.py diff --git a/test/fixtures/wpt/common/dispatcher/remote-executor-worker.js b/test/fixtures/wpt/common/dispatcher/remote-executor-worker.js new file mode 100644 index 00000000000..afba4bae24f --- /dev/null +++ b/test/fixtures/wpt/common/dispatcher/remote-executor-worker.js @@ -0,0 +1,5 @@ +importScripts('./dispatcher.js'); + +const params = new URLSearchParams(location.search); +const uuid = params.get('uuid'); +const executor = new Executor(uuid); // `execute()` is called in constructor. diff --git a/test/fixtures/wpt/common/dispatcher/remote-executor.html b/test/fixtures/wpt/common/dispatcher/remote-executor.html index 8b0030390d0..f87f566be96 100644 --- a/test/fixtures/wpt/common/dispatcher/remote-executor.html +++ b/test/fixtures/wpt/common/dispatcher/remote-executor.html @@ -3,7 +3,10 @@ - + + + diff --git a/test/fixtures/wpt/interfaces/css-highlight-api.idl b/test/fixtures/wpt/interfaces/css-highlight-api.idl index f3c6b2e9d21..61bf6d4caba 100644 --- a/test/fixtures/wpt/interfaces/css-highlight-api.idl +++ b/test/fixtures/wpt/interfaces/css-highlight-api.idl @@ -25,3 +25,11 @@ partial namespace CSS { interface HighlightRegistry { maplike; }; + +partial interface HighlightRegistry { + sequence highlightsFromPoint(float x, float y, optional HighlightsFromPointOptions options = {}); +}; + +dictionary HighlightsFromPointOptions { + sequence shadowRoots = []; +}; diff --git a/test/fixtures/wpt/interfaces/cssom-view.idl b/test/fixtures/wpt/interfaces/cssom-view.idl index f922bc81486..88abb078485 100644 --- a/test/fixtures/wpt/interfaces/cssom-view.idl +++ b/test/fixtures/wpt/interfaces/cssom-view.idl @@ -103,8 +103,11 @@ enum ScrollLogicalPosition { "start", "center", "end", "nearest" }; dictionary ScrollIntoViewOptions : ScrollOptions { ScrollLogicalPosition block = "start"; ScrollLogicalPosition inline = "nearest"; + ScrollIntoViewContainer container = "all"; }; +enum ScrollIntoViewContainer { "all", "nearest" }; + dictionary CheckVisibilityOptions { boolean checkOpacity = false; boolean checkVisibilityCSS = false; diff --git a/test/fixtures/wpt/interfaces/html.idl b/test/fixtures/wpt/interfaces/html.idl index 3832a6f9b71..f10bb72e913 100644 --- a/test/fixtures/wpt/interfaces/html.idl +++ b/test/fixtures/wpt/interfaces/html.idl @@ -1305,12 +1305,15 @@ typedef (HTMLOrSVGImageElement or enum PredefinedColorSpace { "srgb", "display-p3" }; +enum CanvasColorType { "unorm8", "float16" }; + enum CanvasFillRule { "nonzero", "evenodd" }; dictionary CanvasRenderingContext2DSettings { boolean alpha = true; boolean desynchronized = false; PredefinedColorSpace colorSpace = "srgb"; + CanvasColorType colorType = "unorm8"; boolean willReadFrequently = false; }; @@ -1320,9 +1323,8 @@ enum ImageSmoothingQuality { "low", "medium", "high" }; interface CanvasRenderingContext2D { // back-reference to the canvas readonly attribute HTMLCanvasElement canvas; - - CanvasRenderingContext2DSettings getContextAttributes(); }; +CanvasRenderingContext2D includes CanvasSettings; CanvasRenderingContext2D includes CanvasState; CanvasRenderingContext2D includes CanvasTransform; CanvasRenderingContext2D includes CanvasCompositing; @@ -1340,6 +1342,11 @@ CanvasRenderingContext2D includes CanvasPathDrawingStyles; CanvasRenderingContext2D includes CanvasTextDrawingStyles; CanvasRenderingContext2D includes CanvasPath; +interface mixin CanvasSettings { + // settings + CanvasRenderingContext2DSettings getContextAttributes(); +}; + interface mixin CanvasState { // state undefined save(); // push state on state stack @@ -1594,6 +1601,7 @@ interface OffscreenCanvasRenderingContext2D { readonly attribute OffscreenCanvas canvas; }; +OffscreenCanvasRenderingContext2D includes CanvasSettings; OffscreenCanvasRenderingContext2D includes CanvasState; OffscreenCanvasRenderingContext2D includes CanvasTransform; OffscreenCanvasRenderingContext2D includes CanvasCompositing; @@ -1987,6 +1995,7 @@ interface NavigateEvent : Event { readonly attribute DOMString? downloadRequest; readonly attribute any info; readonly attribute boolean hasUAVisualTransition; + readonly attribute Element? sourceElement; undefined intercept(optional NavigationInterceptOptions options = {}); undefined scroll(); @@ -2003,6 +2012,7 @@ dictionary NavigateEventInit : EventInit { DOMString? downloadRequest = null; any info; boolean hasUAVisualTransition = false; + Element? sourceElement = null; }; dictionary NavigationInterceptOptions { @@ -2119,10 +2129,10 @@ interface NotRestoredReasonDetails { [Exposed=Window] interface NotRestoredReasons { - readonly attribute DOMString? src; + readonly attribute USVString? src; readonly attribute DOMString? id; readonly attribute DOMString? name; - readonly attribute DOMString? url; + readonly attribute USVString? url; readonly attribute FrozenArray? reasons; readonly attribute FrozenArray? children; [Default] object toJSON(); diff --git a/test/fixtures/wpt/interfaces/interest-invokers.tentative.idl b/test/fixtures/wpt/interfaces/interest-invokers.tentative.idl index f89af4d7341..fe164e23575 100644 --- a/test/fixtures/wpt/interfaces/interest-invokers.tentative.idl +++ b/test/fixtures/wpt/interfaces/interest-invokers.tentative.idl @@ -2,6 +2,17 @@ interface mixin InterestInvokerElement { [CEReactions,Reflect=interesttarget] attribute Element? interestTargetElement; }; -HTMLInputElement includes InterestInvokerElement; +HTMLAnchorElement includes InterestInvokerElement; +HTMLAreaElement includes InterestInvokerElement; HTMLButtonElement includes InterestInvokerElement; -HTMLAnchorElement includes InterestInvokerElement; \ No newline at end of file +SVGAElement includes InterestInvokerElement; + +[Exposed=Window] +interface InterestEvent : Event { + constructor(DOMString type, optional InterestEventInit eventInitDict = {}); + readonly attribute Element? source; +}; + +dictionary InterestEventInit : EventInit { + Element? source = null; +}; diff --git a/test/fixtures/wpt/interfaces/media-capabilities.idl b/test/fixtures/wpt/interfaces/media-capabilities.idl index 94339b6d1dd..7bd8aca90c3 100644 --- a/test/fixtures/wpt/interfaces/media-capabilities.idl +++ b/test/fixtures/wpt/interfaces/media-capabilities.idl @@ -110,6 +110,8 @@ partial interface WorkerNavigator { [Exposed=(Window, Worker)] interface MediaCapabilities { - [NewObject] Promise decodingInfo(MediaDecodingConfiguration configuration); - [NewObject] Promise encodingInfo(MediaEncodingConfiguration configuration); + [NewObject] Promise decodingInfo( + MediaDecodingConfiguration configuration); + [NewObject] Promise encodingInfo( + MediaEncodingConfiguration configuration); }; diff --git a/test/fixtures/wpt/interfaces/mediacapture-surface-control.idl b/test/fixtures/wpt/interfaces/mediacapture-surface-control.idl index 964f662da7f..357b5c516f6 100644 --- a/test/fixtures/wpt/interfaces/mediacapture-surface-control.idl +++ b/test/fixtures/wpt/interfaces/mediacapture-surface-control.idl @@ -6,8 +6,10 @@ partial interface CaptureController { sequence getSupportedZoomLevels(); long getZoomLevel(); - Promise setZoomLevel(long zoomLevel); - attribute EventHandler oncapturedzoomlevelchange; + Promise increaseZoomLevel(); + Promise decreaseZoomLevel(); + Promise resetZoomLevel(); + attribute EventHandler onzoomlevelchange; }; partial interface CaptureController { diff --git a/test/fixtures/wpt/interfaces/observable.idl b/test/fixtures/wpt/interfaces/observable.idl index 5aa8332ca9b..b1ab3c3c5b1 100644 --- a/test/fixtures/wpt/interfaces/observable.idl +++ b/test/fixtures/wpt/interfaces/observable.idl @@ -74,7 +74,7 @@ interface Observable { // // takeUntil() can consume promises, iterables, async iterables, and other // observables. - Observable takeUntil(any notifier); + Observable takeUntil(any value); Observable map(Mapper mapper); Observable filter(Predicate predicate); Observable take(unsigned long long amount); diff --git a/test/fixtures/wpt/interfaces/permissions-policy.idl b/test/fixtures/wpt/interfaces/permissions-policy.idl index 806d2eb80f6..b17304de8d0 100644 --- a/test/fixtures/wpt/interfaces/permissions-policy.idl +++ b/test/fixtures/wpt/interfaces/permissions-policy.idl @@ -28,4 +28,5 @@ interface PermissionsPolicyViolationReportBody : ReportBody { readonly attribute long? columnNumber; readonly attribute DOMString disposition; readonly attribute DOMString? allowAttribute; + readonly attribute DOMString? srcAttribute; }; diff --git a/test/fixtures/wpt/interfaces/shared-storage.idl b/test/fixtures/wpt/interfaces/shared-storage.idl index 6f38d673952..941c7414c4f 100644 --- a/test/fixtures/wpt/interfaces/shared-storage.idl +++ b/test/fixtures/wpt/interfaces/shared-storage.idl @@ -114,6 +114,7 @@ dictionary SharedStoragePrivateAggregationConfig { USVString aggregationCoordinatorOrigin; USVString contextId; [EnforceRange] unsigned long long filteringIdMaxBytes; + [EnforceRange] unsigned long long maxContributions; }; dictionary SharedStorageRunOperationMethodOptions { diff --git a/test/fixtures/wpt/interfaces/speech-api.idl b/test/fixtures/wpt/interfaces/speech-api.idl index f3967b873ff..025f9424f01 100644 --- a/test/fixtures/wpt/interfaces/speech-api.idl +++ b/test/fixtures/wpt/interfaces/speech-api.idl @@ -8,16 +8,19 @@ interface SpeechRecognition : EventTarget { constructor(); // recognition parameters - attribute SpeechGrammarList grammars; attribute DOMString lang; attribute boolean continuous; attribute boolean interimResults; attribute unsigned long maxAlternatives; + attribute SpeechRecognitionMode mode; // methods to drive the speech interaction undefined start(); + undefined start(MediaStreamTrack audioTrack); undefined stop(); undefined abort(); + boolean onDeviceWebSpeechAvailable(DOMString lang); + boolean installOnDeviceSpeechRecognition(DOMString lang); // event methods attribute EventHandler onaudiostart; @@ -40,10 +43,15 @@ enum SpeechRecognitionErrorCode { "network", "not-allowed", "service-not-allowed", - "bad-grammar", "language-not-supported" }; +enum SpeechRecognitionMode { + "ondevice-preferred", // On-device speech recognition if available, otherwise use Cloud speech recognition as a fallback. + "ondevice-only", // On-device speech recognition only. Returns an error if on-device speech recognition is not available. + "cloud-only", // Cloud speech recognition only. +}; + [Exposed=Window] interface SpeechRecognitionErrorEvent : Event { constructor(DOMString type, SpeechRecognitionErrorEventInit eventInitDict); @@ -91,25 +99,6 @@ dictionary SpeechRecognitionEventInit : EventInit { required SpeechRecognitionResultList results; }; -// The object representing a speech grammar -[Exposed=Window] -interface SpeechGrammar { - attribute DOMString src; - attribute float weight; -}; - -// The object representing a speech grammar collection -[Exposed=Window] -interface SpeechGrammarList { - constructor(); - readonly attribute unsigned long length; - getter SpeechGrammar item(unsigned long index); - undefined addFromURI(DOMString src, - optional float weight = 1.0); - undefined addFromString(DOMString string, - optional float weight = 1.0); -}; - [Exposed=Window] interface SpeechSynthesis : EventTarget { readonly attribute boolean pending; diff --git a/test/fixtures/wpt/interfaces/turtledove.idl b/test/fixtures/wpt/interfaces/turtledove.idl index 05072974ec9..2f34d763722 100644 --- a/test/fixtures/wpt/interfaces/turtledove.idl +++ b/test/fixtures/wpt/interfaces/turtledove.idl @@ -151,17 +151,31 @@ partial interface Navigator { [SecureContext] partial interface Navigator { - Promise getInterestGroupAdAuctionData(AdAuctionDataConfig config); + Promise getInterestGroupAdAuctionData(optional AdAuctionDataConfig config = {}); +}; + +dictionary AdAuctionPerSellerData { + required USVString seller; + Uint8Array request; + DOMString error; }; dictionary AdAuctionData { - required Uint8Array request; required USVString requestId; + Uint8Array request; + sequence requests; }; -dictionary AdAuctionDataConfig { +dictionary AdAuctionOneSeller { required USVString seller; - required USVString coordinatorOrigin; + USVString coordinatorOrigin; +}; + +dictionary AdAuctionDataConfig { + USVString seller; + USVString coordinatorOrigin; + sequence sellers; + unsigned long requestSize; record perBuyerConfig; }; diff --git a/test/fixtures/wpt/interfaces/web-animations-2.idl b/test/fixtures/wpt/interfaces/web-animations-2.idl index 97a0d3f6c6b..c4a0c2532d9 100644 --- a/test/fixtures/wpt/interfaces/web-animations-2.idl +++ b/test/fixtures/wpt/interfaces/web-animations-2.idl @@ -14,6 +14,7 @@ partial interface AnimationTimeline { partial interface Animation { attribute CSSNumberish? startTime; attribute CSSNumberish? currentTime; + attribute AnimationTrigger? trigger; readonly attribute double? overallProgress; }; @@ -98,6 +99,7 @@ dictionary TimelineRangeOffset { partial dictionary KeyframeAnimationOptions { (TimelineRangeOffset or CSSNumericValue or CSSKeywordValue or DOMString) rangeStart = "normal"; (TimelineRangeOffset or CSSNumericValue or CSSKeywordValue or DOMString) rangeEnd = "normal"; + AnimationTrigger? trigger; }; [Exposed=Window] @@ -111,3 +113,25 @@ dictionary AnimationPlaybackEventInit : EventInit { CSSNumberish? currentTime = null; CSSNumberish? timelineTime = null; }; + +[Exposed=Window] +interface AnimationTrigger { + constructor(optional AnimationTriggerOptions options = {}); + attribute AnimationTimeline timeline; + attribute AnimationTriggerType type; + attribute any rangeStart; + attribute any rangeEnd; + attribute any exitRangeStart; + attribute any exitRangeEnd; +}; + +dictionary AnimationTriggerOptions { + AnimationTimeline? timeline; + AnimationTriggerType? type = "once"; + (TimelineRangeOffset or CSSNumericValue or CSSKeywordValue or DOMString) rangeStart = "normal"; + (TimelineRangeOffset or CSSNumericValue or CSSKeywordValue or DOMString) rangeEnd = "normal"; + (TimelineRangeOffset or CSSNumericValue or CSSKeywordValue or DOMString) exitRangeStart = "auto"; + (TimelineRangeOffset or CSSNumericValue or CSSKeywordValue or DOMString) exitRangeEnd = "auto"; +}; + +enum AnimationTriggerType { "once", "repeat", "alternate", "state" }; diff --git a/test/fixtures/wpt/interfaces/webcodecs.idl b/test/fixtures/wpt/interfaces/webcodecs.idl index 5cd4adf9348..274ef96578a 100644 --- a/test/fixtures/wpt/interfaces/webcodecs.idl +++ b/test/fixtures/wpt/interfaces/webcodecs.idl @@ -154,6 +154,8 @@ dictionary VideoDecoderConfig { VideoColorSpaceInit colorSpace; HardwareAcceleration hardwareAcceleration = "no-preference"; boolean optimizeForLatency; + double rotation = 0; + boolean flip = false; }; dictionary AudioEncoderConfig { diff --git a/test/fixtures/wpt/interfaces/webgpu.idl b/test/fixtures/wpt/interfaces/webgpu.idl index 401d2ec69b5..d91a6a710b1 100644 --- a/test/fixtures/wpt/interfaces/webgpu.idl +++ b/test/fixtures/wpt/interfaces/webgpu.idl @@ -62,6 +62,8 @@ interface GPUAdapterInfo { readonly attribute DOMString architecture; readonly attribute DOMString device; readonly attribute DOMString description; + readonly attribute unsigned long subgroupMinSize; + readonly attribute unsigned long subgroupMaxSize; }; interface mixin NavigatorGPU { @@ -123,6 +125,7 @@ enum GPUFeatureName { "float32-blendable", "clip-distances", "dual-source-blending", + "subgroups", }; [Exposed=(Window, Worker), SecureContext] diff --git a/test/fixtures/wpt/resources/chromium/webxr-test.js b/test/fixtures/wpt/resources/chromium/webxr-test.js index 49938dc3ec1..15d015debb1 100644 --- a/test/fixtures/wpt/resources/chromium/webxr-test.js +++ b/test/fixtures/wpt/resources/chromium/webxr-test.js @@ -5,14 +5,14 @@ import {GamepadHand, GamepadMapping} from '/gen/device/gamepad/public/mojom/game // This polyfill library implements the WebXR Test API as specified here: // https://github.com/immersive-web/webxr-test-api -const defaultMojoFromFloor = { +const defaultMojoFromStage = { matrix: [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, -1.65, 0, 1] }; const default_stage_parameters = { - mojoFromFloor: defaultMojoFromFloor, + mojoFromStage: defaultMojoFromStage, bounds: null }; @@ -512,8 +512,8 @@ class MockRuntime { this.stageParameters_.bounds = this.bounds_; } - // floorOrigin is passed in as mojoFromFloor. - this.stageParameters_.mojoFromFloor = + // floorOrigin is passed in as mojoFromStage. + this.stageParameters_.mojoFromStage = {matrix: getMatrixFromTransform(floorOrigin)}; this._onStageParametersUpdated(); @@ -928,15 +928,17 @@ class MockRuntime { } const frameData = { - mojoFromViewer: this.pose_, - views: frame_views, + renderInfo: { + frameId: this.next_frame_id_, + mojoFromViewer: this.pose_, + views: frame_views + }, mojoSpaceReset: mojo_space_reset, inputState: input_state, timeDelta: { // window.performance.now() is in milliseconds, so convert to microseconds. microseconds: BigInt(Math.floor(window.performance.now() * 1000)), }, - frameId: this.next_frame_id_, bufferHolder: null, cameraImageSize: this.cameraImage_ ? { width: this.cameraImage_.width, @@ -1640,11 +1642,11 @@ class MockRuntime { case vrMojom.XRReferenceSpaceType.kLocal: return XRMathHelper.identity(); case vrMojom.XRReferenceSpaceType.kLocalFloor: - if (this.stageParameters_ == null || this.stageParameters_.mojoFromFloor == null) { + if (this.stageParameters_ == null || this.stageParameters_.mojoFromStage == null) { console.warn("Standing transform not available."); return null; } - return this.stageParameters_.mojoFromFloor.matrix; + return this.stageParameters_.mojoFromStage.matrix; case vrMojom.XRReferenceSpaceType.kViewer: return mojo_from_viewer; case vrMojom.XRReferenceSpaceType.kBoundedFloor: diff --git a/test/fixtures/wpt/service-workers/service-worker/resources/router-rules.js b/test/fixtures/wpt/service-workers/service-worker/resources/router-rules.js index 71c09e7079c..58c0e5cbbaf 100644 --- a/test/fixtures/wpt/service-workers/service-worker/resources/router-rules.js +++ b/test/fixtures/wpt/service-workers/service-worker/resources/router-rules.js @@ -53,10 +53,18 @@ const routerRules = { 'condition-lack-of-source': [{ condition: {requestMode: 'no-cors'}, }], - 'condition-invalid-request-method': [{ + 'condition-invalid-bytestring-request-method': [{ condition: {requestMethod: String.fromCodePoint(0x3042)}, source: 'network' }], + 'condition-invalid-http-request-method': [{ + condition: {requestMethod: '(GET|POST)'}, + source: 'network' + }], + 'condition-forbidden-request-method': [{ + condition: {requestMethod: 'connect'}, + source: 'network' + }], 'condition-request-destination-script-network': [{condition: {requestDestination: 'script'}, source: 'network'}], 'condition-or-source-network': [{ diff --git a/test/fixtures/wpt/service-workers/service-worker/static-router-invalid-rules.https.html b/test/fixtures/wpt/service-workers/service-worker/static-router-invalid-rules.https.html index 05f39f2c9dc..616f85bd618 100644 --- a/test/fixtures/wpt/service-workers/service-worker/static-router-invalid-rules.https.html +++ b/test/fixtures/wpt/service-workers/service-worker/static-router-invalid-rules.https.html @@ -13,19 +13,37 @@ diff --git a/test/fixtures/wpt/websockets/constants.sub.js b/test/fixtures/wpt/websockets/constants.sub.js index fd3c3b84b96..78601ce877a 100644 --- a/test/fixtures/wpt/websockets/constants.sub.js +++ b/test/fixtures/wpt/websockets/constants.sub.js @@ -15,6 +15,7 @@ if (url_has_flag('h2')) { } const SCHEME_DOMAIN_PORT = __SCHEME + '://' + __SERVER__NAME + ':' + __PORT; +const SCHEME_CROSSDOMAIN_PORT = __SCHEME + '://' + '{{hosts[alt][www]}}' + ':' + __PORT; function url_has_variant(variant) { const params = new URLSearchParams(location.search); diff --git a/test/fixtures/wpt/websockets/cookies/cross-origin-cookie-set.html b/test/fixtures/wpt/websockets/cookies/cross-origin-cookie-set.html new file mode 100644 index 00000000000..f5ccbd1a334 --- /dev/null +++ b/test/fixtures/wpt/websockets/cookies/cross-origin-cookie-set.html @@ -0,0 +1,40 @@ + +Include credentials in cross-origin websocket requests + + + + + + +

WebSocket - Set Cross-origin cookie

+
+ diff --git a/test/fixtures/wpt/websockets/opening-handshake/received-301-code.html b/test/fixtures/wpt/websockets/opening-handshake/received-301-code.html new file mode 100644 index 00000000000..41ca295d833 --- /dev/null +++ b/test/fixtures/wpt/websockets/opening-handshake/received-301-code.html @@ -0,0 +1,23 @@ + +WebSockets: 301 Code Failed + + + + +
+ diff --git a/test/fixtures/wpt/websockets/opening-handshake/resources/redirect_response.py b/test/fixtures/wpt/websockets/opening-handshake/resources/redirect_response.py new file mode 100644 index 00000000000..d6055860405 --- /dev/null +++ b/test/fixtures/wpt/websockets/opening-handshake/resources/redirect_response.py @@ -0,0 +1,3 @@ +def main(request, response): + headers = [('Location', '/echo')] + return (301, "moved"), headers, '' From 782f06b1ec44043f858b8ad1f76225919ecd55db Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 25 Feb 2025 08:50:36 +0100 Subject: [PATCH 15/17] chore: update WPT (#4062) Co-authored-by: Uzlopak <5059100+Uzlopak@users.noreply.github.com> --- .../command-and-commandfor.tentative.idl | 15 -- test/fixtures/wpt/interfaces/fedcm.idl | 2 +- .../wpt/interfaces/gamepad-extensions.idl | 9 - test/fixtures/wpt/interfaces/gamepad.idl | 8 + test/fixtures/wpt/interfaces/html.idl | 21 ++- .../wpt/interfaces/media-capabilities.idl | 2 +- .../mediacapture-surface-control.idl | 2 +- test/fixtures/wpt/interfaces/ppa.idl | 66 +++++++ .../fixtures/wpt/interfaces/sanitizer-api.idl | 2 +- .../interfaces/sanitizer-api.tentative.idl | 66 +++++-- test/fixtures/wpt/interfaces/speech-api.idl | 4 +- test/fixtures/wpt/interfaces/webnn.idl | 7 - test/fixtures/wpt/resources/testdriver.js | 164 ++++++++++++++++++ .../service-worker/resources/router-rules.js | 31 ++++ .../static-router-invalid-rules.https.html | 29 ++++ 15 files changed, 377 insertions(+), 51 deletions(-) delete mode 100644 test/fixtures/wpt/interfaces/command-and-commandfor.tentative.idl create mode 100644 test/fixtures/wpt/interfaces/ppa.idl diff --git a/test/fixtures/wpt/interfaces/command-and-commandfor.tentative.idl b/test/fixtures/wpt/interfaces/command-and-commandfor.tentative.idl deleted file mode 100644 index 046a365939c..00000000000 --- a/test/fixtures/wpt/interfaces/command-and-commandfor.tentative.idl +++ /dev/null @@ -1,15 +0,0 @@ -interface mixin CommandElement { - [CEReactions,Reflect=commandfor] attribute Element? commandForElement; - [CEReactions,Reflect=command] attribute DOMString command; -}; - -interface CommandEvent : Event { - constructor(DOMString type, optional CommandEventInit eventInitDict = {}); - readonly attribute Element? source; - readonly attribute DOMString command; -}; - -dictionary CommandEventInit : EventInit { - Element? source = null; - DOMString command = ""; -}; diff --git a/test/fixtures/wpt/interfaces/fedcm.idl b/test/fixtures/wpt/interfaces/fedcm.idl index 07f7955ff64..f7038a6fee1 100644 --- a/test/fixtures/wpt/interfaces/fedcm.idl +++ b/test/fixtures/wpt/interfaces/fedcm.idl @@ -9,7 +9,7 @@ dictionary IdentityCredentialDisconnectOptions : IdentityProviderConfig { [Exposed=Window, SecureContext] interface IdentityCredential : Credential { - static Promise disconnect(optional IdentityCredentialDisconnectOptions options = {}); + static Promise disconnect(IdentityCredentialDisconnectOptions options); readonly attribute USVString? token; readonly attribute boolean isAutoSelected; }; diff --git a/test/fixtures/wpt/interfaces/gamepad-extensions.idl b/test/fixtures/wpt/interfaces/gamepad-extensions.idl index 81776a46ec9..330267d93b8 100644 --- a/test/fixtures/wpt/interfaces/gamepad-extensions.idl +++ b/test/fixtures/wpt/interfaces/gamepad-extensions.idl @@ -22,19 +22,10 @@ interface GamepadPose { readonly attribute Float32Array? angularAcceleration; }; -[Exposed=Window, SecureContext] -interface GamepadTouch { - readonly attribute unsigned long touchId; - readonly attribute octet surfaceId; - readonly attribute Float32Array position; - readonly attribute Uint32Array? surfaceDimensions; -}; - partial interface Gamepad { readonly attribute GamepadHand hand; readonly attribute FrozenArray hapticActuators; readonly attribute GamepadPose? pose; - readonly attribute FrozenArray? touchEvents; }; [Exposed=Window] diff --git a/test/fixtures/wpt/interfaces/gamepad.idl b/test/fixtures/wpt/interfaces/gamepad.idl index d922d7b80b0..200947968e3 100644 --- a/test/fixtures/wpt/interfaces/gamepad.idl +++ b/test/fixtures/wpt/interfaces/gamepad.idl @@ -12,6 +12,7 @@ interface Gamepad { readonly attribute GamepadMappingType mapping; readonly attribute FrozenArray axes; readonly attribute FrozenArray buttons; + readonly attribute FrozenArray touches; [SameObject] readonly attribute GamepadHapticActuator vibrationActuator; }; @@ -22,6 +23,13 @@ interface GamepadButton { readonly attribute double value; }; +dictionary GamepadTouch { + unsigned long touchId; + octet surfaceId; + DOMPointReadOnly position; + DOMRectReadOnly? surfaceDimensions; +}; + enum GamepadMappingType { "", "standard", diff --git a/test/fixtures/wpt/interfaces/html.idl b/test/fixtures/wpt/interfaces/html.idl index f10bb72e913..f48fd370281 100644 --- a/test/fixtures/wpt/interfaces/html.idl +++ b/test/fixtures/wpt/interfaces/html.idl @@ -957,6 +957,8 @@ HTMLInputElement includes PopoverInvokerElement; interface HTMLButtonElement : HTMLElement { [HTMLConstructor] constructor(); + [CEReactions] attribute DOMString command; + [CEReactions] attribute Element? commandForElement; [CEReactions] attribute boolean disabled; readonly attribute HTMLFormElement? form; [CEReactions] attribute USVString formAction; @@ -1450,10 +1452,10 @@ interface mixin CanvasDrawImage { interface mixin CanvasImageData { // pixel manipulation ImageData createImageData([EnforceRange] long sw, [EnforceRange] long sh, optional ImageDataSettings settings = {}); - ImageData createImageData(ImageData imagedata); + ImageData createImageData(ImageData imageData); ImageData getImageData([EnforceRange] long sx, [EnforceRange] long sy, [EnforceRange] long sw, [EnforceRange] long sh, optional ImageDataSettings settings = {}); - undefined putImageData(ImageData imagedata, [EnforceRange] long dx, [EnforceRange] long dy); - undefined putImageData(ImageData imagedata, [EnforceRange] long dx, [EnforceRange] long dy, [EnforceRange] long dirtyX, [EnforceRange] long dirtyY, [EnforceRange] long dirtyWidth, [EnforceRange] long dirtyHeight); + undefined putImageData(ImageData imageData, [EnforceRange] long dx, [EnforceRange] long dy); + undefined putImageData(ImageData imageData, [EnforceRange] long dx, [EnforceRange] long dy, [EnforceRange] long dirtyX, [EnforceRange] long dirtyY, [EnforceRange] long dirtyWidth, [EnforceRange] long dirtyHeight); }; enum CanvasLineCap { "butt", "round", "square" }; @@ -1710,6 +1712,18 @@ dictionary ToggleEventInit : EventInit { DOMString newState = ""; }; +[Exposed=Window] +interface CommandEvent : Event { + constructor(DOMString type, optional CommandEventInit eventInitDict = {}); + readonly attribute Element? source; + readonly attribute DOMString command; +}; + +dictionary CommandEventInit : EventInit { + Element? source = null; + DOMString command = ""; +}; + dictionary FocusOptions { boolean preventScroll = false; boolean focusVisible; @@ -2195,6 +2209,7 @@ interface mixin GlobalEventHandlers { attribute EventHandler onchange; attribute EventHandler onclick; attribute EventHandler onclose; + attribute EventHandler oncommand; attribute EventHandler oncontextlost; attribute EventHandler oncontextmenu; attribute EventHandler oncontextrestored; diff --git a/test/fixtures/wpt/interfaces/media-capabilities.idl b/test/fixtures/wpt/interfaces/media-capabilities.idl index 7bd8aca90c3..68ab0a8d0d1 100644 --- a/test/fixtures/wpt/interfaces/media-capabilities.idl +++ b/test/fixtures/wpt/interfaces/media-capabilities.idl @@ -90,7 +90,7 @@ dictionary MediaCapabilitiesInfo { }; dictionary MediaCapabilitiesDecodingInfo : MediaCapabilitiesInfo { - required MediaKeySystemAccess keySystemAccess; + required MediaKeySystemAccess? keySystemAccess; MediaDecodingConfiguration configuration; }; diff --git a/test/fixtures/wpt/interfaces/mediacapture-surface-control.idl b/test/fixtures/wpt/interfaces/mediacapture-surface-control.idl index 357b5c516f6..3a0ccf5faaf 100644 --- a/test/fixtures/wpt/interfaces/mediacapture-surface-control.idl +++ b/test/fixtures/wpt/interfaces/mediacapture-surface-control.idl @@ -5,7 +5,7 @@ partial interface CaptureController { sequence getSupportedZoomLevels(); - long getZoomLevel(); + readonly attribute long? zoomLevel; Promise increaseZoomLevel(); Promise decreaseZoomLevel(); Promise resetZoomLevel(); diff --git a/test/fixtures/wpt/interfaces/ppa.idl b/test/fixtures/wpt/interfaces/ppa.idl new file mode 100644 index 00000000000..a00d2deba87 --- /dev/null +++ b/test/fixtures/wpt/interfaces/ppa.idl @@ -0,0 +1,66 @@ +// GENERATED CONTENT - DO NOT EDIT +// Content was automatically extracted by Reffy into webref +// (https://github.com/w3c/webref) +// Source: Privacy-Preserving Attribution: Level 1 (https://w3c.github.io/ppa/) + +partial interface Navigator { + [SecureContext, SameObject] readonly attribute PrivateAttribution privateAttribution; +}; + +enum PrivateAttributionProtocol { "dap-12-histogram", "tee-00" }; + +dictionary PrivateAttributionAggregationService { + required DOMString url; + required DOMString protocol; +}; + +[SecureContext, Exposed=Window] +interface PrivateAttributionAggregationServices { + readonly setlike; +}; + +[SecureContext, Exposed=Window] +interface PrivateAttribution { + readonly attribute PrivateAttributionAggregationServices aggregationServices; +}; + +dictionary PrivateAttributionImpressionOptions { + required unsigned long histogramIndex; + unsigned long filterData = 0; + required DOMString conversionSite; + unsigned long lifetimeDays = 30; +}; + +[SecureContext, Exposed=Window] +partial interface PrivateAttribution { + undefined saveImpression(PrivateAttributionImpressionOptions options); +}; + +dictionary PrivateAttributionConversionOptions { + required DOMString aggregationService; + double epsilon = 1.0; + + required unsigned long histogramSize; + + PrivateAttributionLogic logic = "last-touch"; + unsigned long value = 1; + unsigned long maxValue = 1; + + unsigned long lookbackDays; + unsigned long filterData; + sequence impressionSites = []; + sequence intermediarySites = []; +}; + +dictionary PrivateAttributionConversionResult { + required Uint8Array report; +}; + +[SecureContext, Exposed=Window] +partial interface PrivateAttribution { + Promise measureConversion(PrivateAttributionConversionOptions options); +}; + +enum PrivateAttributionLogic { + "last-touch", +}; diff --git a/test/fixtures/wpt/interfaces/sanitizer-api.idl b/test/fixtures/wpt/interfaces/sanitizer-api.idl index 86ec7875f49..70412cced28 100644 --- a/test/fixtures/wpt/interfaces/sanitizer-api.idl +++ b/test/fixtures/wpt/interfaces/sanitizer-api.idl @@ -11,7 +11,7 @@ dictionary SetHTMLUnsafeOptions { (Sanitizer or SanitizerConfig or SanitizerPresets) sanitizer = {}; }; -[Exposed=(Window,Worker)] +[Exposed=Window] interface Sanitizer { constructor(optional (SanitizerConfig or SanitizerPresets) configuration = "default"); diff --git a/test/fixtures/wpt/interfaces/sanitizer-api.tentative.idl b/test/fixtures/wpt/interfaces/sanitizer-api.tentative.idl index 3e843d8eb0c..4e597aeec7b 100644 --- a/test/fixtures/wpt/interfaces/sanitizer-api.tentative.idl +++ b/test/fixtures/wpt/interfaces/sanitizer-api.tentative.idl @@ -1,17 +1,61 @@ // https://wicg.github.io/sanitizer-api/ -[ - Exposed=Window, - SecureContext -] interface Sanitizer { - constructor(optional SanitizerConfig sanitizerConfig = {}); - DocumentFragment sanitize((DocumentFragment or Document) input); +enum SanitizerPresets { "default" }; +dictionary SetHTMLOptions { + (Sanitizer or SanitizerConfig or SanitizerPresets) sanitizer = "default"; +}; +dictionary SetHTMLUnsafeOptions { + (Sanitizer or SanitizerConfig or SanitizerPresets) sanitizer = {}; +}; + +[Exposed=Window] +interface Sanitizer { + constructor(optional (SanitizerConfig or SanitizerPresets) configuration = "default"); + + // Query configuration: + SanitizerConfig get(); + + // Modify a Sanitizer’s lists and fields: + undefined allowElement(SanitizerElementWithAttributes element); + undefined removeElement(SanitizerElement element); + undefined replaceElementWithChildren(SanitizerElement element); + undefined allowAttribute(SanitizerAttribute attribute); + undefined removeAttribute(SanitizerAttribute attribute); + undefined setComments(boolean allow); + undefined setDataAttributes(boolean allow); + + // Remove markup that executes script. May modify multiple lists: + undefined removeUnsafe(); +}; + +dictionary SanitizerElementNamespace { + required DOMString name; + DOMString? _namespace = "http://www.w3.org/1999/xhtml"; }; +// Used by "elements" +dictionary SanitizerElementNamespaceWithAttributes : SanitizerElementNamespace { + sequence attributes; + sequence removeAttributes; +}; + +typedef (DOMString or SanitizerElementNamespace) SanitizerElement; +typedef (DOMString or SanitizerElementNamespaceWithAttributes) SanitizerElementWithAttributes; + +dictionary SanitizerAttributeNamespace { + required DOMString name; + DOMString? _namespace = null; +}; +typedef (DOMString or SanitizerAttributeNamespace) SanitizerAttribute; + dictionary SanitizerConfig { - sequence allowElements; - sequence blockElements; - sequence dropElements; - sequence allowAttributes; - sequence dropAttributes; + sequence elements; + sequence removeElements; + sequence replaceWithChildrenElements; + + sequence attributes; + sequence removeAttributes; + + boolean comments; + boolean dataAttributes; }; diff --git a/test/fixtures/wpt/interfaces/speech-api.idl b/test/fixtures/wpt/interfaces/speech-api.idl index 025f9424f01..bc0635fa3f2 100644 --- a/test/fixtures/wpt/interfaces/speech-api.idl +++ b/test/fixtures/wpt/interfaces/speech-api.idl @@ -19,8 +19,8 @@ interface SpeechRecognition : EventTarget { undefined start(MediaStreamTrack audioTrack); undefined stop(); undefined abort(); - boolean onDeviceWebSpeechAvailable(DOMString lang); - boolean installOnDeviceSpeechRecognition(DOMString lang); + static Promise availableOnDevice(DOMString lang); + static Promise installOnDevice(DOMString lang); // event methods attribute EventHandler onaudiostart; diff --git a/test/fixtures/wpt/interfaces/webnn.idl b/test/fixtures/wpt/interfaces/webnn.idl index 3e1d9a9f440..63554edbffd 100644 --- a/test/fixtures/wpt/interfaces/webnn.idl +++ b/test/fixtures/wpt/interfaces/webnn.idl @@ -9,12 +9,6 @@ interface mixin NavigatorML { Navigator includes NavigatorML; WorkerNavigator includes NavigatorML; -enum MLDeviceType { - "cpu", - "gpu", - "npu" -}; - enum MLPowerPreference { "default", "high-performance", @@ -22,7 +16,6 @@ enum MLPowerPreference { }; dictionary MLContextOptions { - MLDeviceType deviceType = "cpu"; MLPowerPreference powerPreference = "default"; }; diff --git a/test/fixtures/wpt/resources/testdriver.js b/test/fixtures/wpt/resources/testdriver.js index aee319f5757..05301bf5589 100644 --- a/test/fixtures/wpt/resources/testdriver.js +++ b/test/fixtures/wpt/resources/testdriver.js @@ -82,6 +82,42 @@ * `bluetooth `_ module. */ bluetooth: { + /** + * Handle a bluetooth device prompt with the given params. Matches the + * `bluetooth.handleRequestDevicePrompt + * `_ + * WebDriver BiDi command. + * + * @example + * await test_driver.bidi.bluetooth.handleRequestDevicePrompt({ + * prompt: "pmt-e0a234b", + * accept: true, + * device: "dvc-9b3b872" + * }); + * + * @param {object} params - Parameters for the command. + * @param {string} params.prompt - The id of a bluetooth device prompt. + * Matches the + * `bluetooth.HandleRequestDevicePromptParameters:prompt `_ + * value. + * @param {bool} params.accept - Whether to accept a bluetooth device prompt. + * Matches the + * `bluetooth.HandleRequestDevicePromptAcceptParameters:accept `_ + * value. + * @param {string} params.device - The device id from a bluetooth device + * prompt to be accepted. Matches the + * `bluetooth.HandleRequestDevicePromptAcceptParameters:device `_ + * value. + * @param {Context} [params.context] The optional context parameter specifies in + * which browsing context the bluetooth device prompt should be handled. If not + * provided, the current browsing context is used. + * @returns {Promise} fulfilled after the bluetooth device prompt + * is handled, or rejected if the operation fails. + */ + handle_request_device_prompt: function(params) { + return window.test_driver_internal.bidi.bluetooth + .handle_request_device_prompt(params); + }, /** * Creates a simulated bluetooth adapter with the given params. Matches the * `bluetooth.simulateAdapter `_ @@ -105,6 +141,116 @@ */ simulate_adapter: function (params) { return window.test_driver_internal.bidi.bluetooth.simulate_adapter(params); + }, + /** + * Creates a simulated bluetooth peripheral with the given params. + * Matches the + * `bluetooth.simulatePreconnectedPeripheral `_ + * WebDriver BiDi command. + * + * @example + * await test_driver.bidi.bluetooth.simulatePreconnectedPeripheral({ + * "address": "09:09:09:09:09:09", + * "name": "Some Device", + * "manufacturerData": [{key: 17, data: "AP8BAX8="}], + * "knownServiceUuids": [ + * "12345678-1234-5678-9abc-def123456789", + * ], + * }); + * + * @param {object} params - Parameters for the command. + * @param {string} params.address - The address of the simulated + * bluetooth peripheral. Matches the + * `bluetooth.SimulatePreconnectedPeripheralParameters:address `_ + * value. + * @param {string} params.name - The name of the simulated bluetooth + * peripheral. Matches the + * `bluetooth.SimulatePreconnectedPeripheralParameters:name `_ + * value. + * @param {Array.ManufacturerData} params.manufacturerData - The manufacturerData of the + * simulated bluetooth peripheral. Matches the + * `bluetooth.SimulatePreconnectedPeripheralParameters:manufacturerData `_ + * value. + * @param {string} params.knownServiceUuids - The knownServiceUuids of + * the simulated bluetooth peripheral. Matches the + * `bluetooth.SimulatePreconnectedPeripheralParameters:knownServiceUuids `_ + * value. + * @param {Context} [params.context] The optional context parameter + * specifies in which browsing context the simulated bluetooth peripheral should be + * set. If not provided, the current browsing context is used. + * @returns {Promise} fulfilled after the simulated bluetooth peripheral is created + * and set, or rejected if the operation fails. + */ + simulate_preconnected_peripheral: function(params) { + return window.test_driver_internal.bidi.bluetooth + .simulate_preconnected_peripheral(params); + }, + /** + * `bluetooth.RequestDevicePromptUpdatedParameters `_ + * event. + */ + request_device_prompt_updated: { + /** + * @typedef {object} RequestDevicePromptUpdated + * `bluetooth.RequestDevicePromptUpdatedParameters `_ + * event. + */ + + /** + * Subscribes to the event. Events will be emitted only if + * there is a subscription for the event. This method does + * not add actual listeners. To listen to the event, use the + * `on` or `once` methods. The buffered events will be + * emitted before the command promise is resolved. + * + * @param {object} [params] Parameters for the subscription. + * @param {null|Array.<(Context)>} [params.contexts] The + * optional contexts parameter specifies which browsing + * contexts to subscribe to the event on. It should be + * either an array of Context objects, or null. If null, the + * event will be subscribed to globally. If omitted, the + * event will be subscribed to on the current browsing + * context. + * @returns {Promise} Resolves when the subscription + * is successfully done. + */ + subscribe: async function(params = {}) { + assertBidiIsEnabled(); + return window.test_driver_internal.bidi.bluetooth + .request_device_prompt_updated.subscribe(params); + }, + /** + * Adds an event listener for the event. + * + * @param {function(RequestDevicePromptUpdated): void} callback The + * callback to be called when the event is emitted. The + * callback is called with the event object as a parameter. + * @returns {function(): void} A function that removes the + * added event listener when called. + */ + on: function(callback) { + assertBidiIsEnabled(); + return window.test_driver_internal.bidi.bluetooth + .request_device_prompt_updated.on(callback); + }, + /** + * Adds an event listener for the event that is only called + * once and removed afterward. + * + * @return {Promise} The promise which + * is resolved with the event object when the event is emitted. + */ + once: function() { + assertBidiIsEnabled(); + return new Promise(resolve => { + const remove_handler = + window.test_driver_internal.bidi.bluetooth + .request_device_prompt_updated.on(event => { + resolve(event); + remove_handler(); + }); + }); + }, } }, /** @@ -1340,9 +1486,27 @@ bidi: { bluetooth: { + handle_request_device_prompt: function() { + throw new Error( + 'bidi.bluetooth.handle_request_device_prompt is not implemented by testdriver-vendor.js'); + }, simulate_adapter: function () { throw new Error( "bidi.bluetooth.simulate_adapter is not implemented by testdriver-vendor.js"); + }, + simulate_preconnected_peripheral: function() { + throw new Error( + 'bidi.bluetooth.simulate_preconnected_peripheral is not implemented by testdriver-vendor.js'); + }, + request_device_prompt_updated: { + async subscribe() { + throw new Error( + 'bidi.bluetooth.request_device_prompt_updated.subscribe is not implemented by testdriver-vendor.js'); + }, + on() { + throw new Error( + 'bidi.bluetooth.request_device_prompt_updated.on is not implemented by testdriver-vendor.js'); + } } }, log: { diff --git a/test/fixtures/wpt/service-workers/service-worker/resources/router-rules.js b/test/fixtures/wpt/service-workers/service-worker/resources/router-rules.js index 58c0e5cbbaf..27462b6c1d7 100644 --- a/test/fixtures/wpt/service-workers/service-worker/resources/router-rules.js +++ b/test/fixtures/wpt/service-workers/service-worker/resources/router-rules.js @@ -1,4 +1,7 @@ const TEST_CACHE_NAME = 'v1'; +// https://w3c.github.io/ServiceWorker/#check-router-registration-limit-algorithm +const CONDITION_MAX_RECURSION_DEPTH = 10; +const CONDITION_MAX_COUNT = 1024; const routerRules = { 'condition-urlpattern-constructed-source-network': [{ @@ -65,6 +68,34 @@ const routerRules = { condition: {requestMethod: 'connect'}, source: 'network' }], + 'condition-invalid-or-condition-depth': (() => { + const addOrCondition = (depth) => { + if (depth > CONDITION_MAX_RECURSION_DEPTH + 1) { + return {urlPattern: '/foo'}; + } + return { + or: [addOrCondition(depth + 1)] + }; + }; + return {condition: addOrCondition(1), source: 'network'}; + })(), + 'condition-invalid-not-condition-depth': (() => { + const generateNotCondition = (depth) => { + if (depth > CONDITION_MAX_RECURSION_DEPTH + 1) { + return { + urlPattern: '/**/example.txt', + }; + } + return {not: generateNotCondition(depth + 1)}; + }; + return {condition: generateNotCondition(1), source: 'network'}; + })(), + 'condition-invalid-router-size': [...Array(CONDITION_MAX_COUNT + 1)].map((val, i) => { + return { + condition: {urlPattern: `/foo-${i}`}, + source: 'network' + }; + }), 'condition-request-destination-script-network': [{condition: {requestDestination: 'script'}, source: 'network'}], 'condition-or-source-network': [{ diff --git a/test/fixtures/wpt/service-workers/service-worker/static-router-invalid-rules.https.html b/test/fixtures/wpt/service-workers/service-worker/static-router-invalid-rules.https.html index 616f85bd618..958d74374d8 100644 --- a/test/fixtures/wpt/service-workers/service-worker/static-router-invalid-rules.https.html +++ b/test/fixtures/wpt/service-workers/service-worker/static-router-invalid-rules.https.html @@ -19,6 +19,14 @@ 'condition-invalid-http-request-method'; const ROUTER_RULE_KEY_FORBIDDEN_REQUEST_METHOD = 'condition-invalid-forbidden-method'; +const ROUTER_RULE_KEY_INVALID_REQUEST_METHOD = + 'condition-invalid-request-method'; +const ROUTER_RULE_KEY_INVALID_OR_CONDITION_DEPTH = + 'condition-invalid-or-condition-depth'; +const ROUTER_RULE_KEY_INVALID_NOT_CONDITION_DEPTH = + 'condition-invalid-not-condition-depth'; +const ROUTER_RULE_KEY_INVALID_ROUTER_SIZE = + 'condition-invalid-router-size'; const ROUTER_RULE_KEY_LACK_OF_CONDITION = 'condition-lack-of-condition'; const ROUTER_RULE_KEY_LACK_OF_SOURCE = @@ -45,6 +53,27 @@ assert_equals(errors.length, 1); }, 'addRoutes should raise for forbidden request method.'); +promise_test(async t => { + const worker = await registerAndActivate(t, ROUTER_RULE_KEY_INVALID_OR_CONDITION_DEPTH); + t.add_cleanup(() => {reset_info_in_worker(worker)}); + const {errors} = await get_info_from_worker(worker); + assert_equals(errors.length, 1); +}, 'addRoutes should raise if or condition exceeds the depth limit'); + +promise_test(async t => { + const worker = await registerAndActivate(t, ROUTER_RULE_KEY_INVALID_NOT_CONDITION_DEPTH); + t.add_cleanup(() => {reset_info_in_worker(worker)}); + const {errors} = await get_info_from_worker(worker); + assert_equals(errors.length, 1); +}, 'addRoutes should raise if not condition exceeds the depth limit'); + +promise_test(async t => { + const worker = await registerAndActivate(t, ROUTER_RULE_KEY_INVALID_ROUTER_SIZE); + t.add_cleanup(() => {reset_info_in_worker(worker)}); + const {errors} = await get_info_from_worker(worker); + assert_equals(errors.length, 1); +}, 'addRoutes should raise if the number of router rules exceeds the length limit'); + promise_test(async t => { const worker = await registerAndActivate(t, ROUTER_RULE_KEY_LACK_OF_CONDITION); t.add_cleanup(() => {reset_info_in_worker(worker)}); From a217002369c403f5a84a870520294435632ef6eb Mon Sep 17 00:00:00 2001 From: Joyee Cheung Date: Wed, 26 Feb 2025 23:00:49 +0800 Subject: [PATCH 16/17] fix: fix EnvHttpProxyAgent for the Node.js bundle (#4064) * fix: fix EnvHttpProxyAgent for the Node.js bundle The Dispatcher needs some methods from lib/api for EnvHttpProxyAgent, otherwise it's incomplete. * fixup! fix: fix EnvHttpProxyAgent for the Node.js bundle --- index-fetch.js | 3 + test/env-http-proxy-agent-nodejs-bundle.js | 75 ++++++++++++++++++++++ 2 files changed, 78 insertions(+) create mode 100644 test/env-http-proxy-agent-nodejs-bundle.js diff --git a/index-fetch.js b/index-fetch.js index 01df32d2fb4..711d7e8a1e4 100644 --- a/index-fetch.js +++ b/index-fetch.js @@ -26,6 +26,9 @@ module.exports.createFastMessageEvent = createFastMessageEvent module.exports.EventSource = require('./lib/web/eventsource/eventsource').EventSource +const api = require('./lib/api') +const Dispatcher = require('./lib/dispatcher/dispatcher') +Object.assign(Dispatcher.prototype, api) // Expose the fetch implementation to be enabled in Node.js core via a flag module.exports.EnvHttpProxyAgent = EnvHttpProxyAgent module.exports.getGlobalDispatcher = getGlobalDispatcher diff --git a/test/env-http-proxy-agent-nodejs-bundle.js b/test/env-http-proxy-agent-nodejs-bundle.js new file mode 100644 index 00000000000..43746a318a5 --- /dev/null +++ b/test/env-http-proxy-agent-nodejs-bundle.js @@ -0,0 +1,75 @@ +'use strict' + +const { tspl } = require('@matteo.collina/tspl') +const { describe, test, after, before } = require('node:test') +const { EnvHttpProxyAgent, setGlobalDispatcher } = require('../index-fetch') +const http = require('node:http') +const net = require('node:net') +const { once } = require('node:events') + +const env = { ...process.env } + +describe('EnvHttpProxyAgent and setGlobalDispatcher', () => { + before(() => { + ['HTTP_PROXY', 'http_proxy', 'HTTPS_PROXY', 'https_proxy', 'NO_PROXY', 'no_proxy'].forEach((varname) => { + delete process.env[varname] + }) + }) + + after(() => { + process.env = { ...env } + }) + + test('should work together from the Node.js bundle', async (t) => { + const { strictEqual } = tspl(t, { plan: 3 }) + + // Instead of using mocks, start a real server and a minimal proxy server + // in order to exercise the actual paths in EnvHttpProxyAgent from the + // Node.js bundle. + const server = http.createServer((req, res) => { res.end('Hello world') }) + server.on('error', err => { console.log('Server error', err) }) + server.listen(0) + await once(server, 'listening') + t.after(() => { server.close() }) + + const proxy = http.createServer() + proxy.on('connect', (req, clientSocket, head) => { + // Check that the proxy is actually used to tunnel the request sent below. + const [hostname, port] = req.url.split(':') + strictEqual(hostname, 'localhost') + strictEqual(port, server.address().port.toString()) + + const serverSocket = net.connect(port, hostname, () => { + clientSocket.write( + 'HTTP/1.1 200 Connection Established\r\n' + + 'Proxy-agent: Node.js-Proxy\r\n' + + '\r\n' + ) + serverSocket.write(head) + clientSocket.pipe(serverSocket) + serverSocket.pipe(clientSocket) + }) + + serverSocket.on('error', () => { + clientSocket.write('HTTP/1.1 500 Connection Error\r\n\r\n') + clientSocket.end() + }) + }) + + proxy.on('error', (err) => { console.log('Proxy error', err) }) + + proxy.listen(0) + await once(proxy, 'listening') + t.after(() => { proxy.close() }) + + // Use setGlobalDispatcher and EnvHttpProxyAgent from Node.js + // and make sure that they work together. + const proxyAddress = `http://localhost:${proxy.address().port}` + const serverAddress = `http://localhost:${server.address().port}` + process.env.http_proxy = proxyAddress + setGlobalDispatcher(new EnvHttpProxyAgent()) + + const res = await fetch(serverAddress) + strictEqual(await res.text(), 'Hello world') + }) +}) From 6bb527e9d768c5ca5b98765c28cf403ad8b250fb Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 28 Feb 2025 02:45:15 -0800 Subject: [PATCH 17/17] Bumped v7.4.0 (#4071) Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index e90a4dbfbf6..fe79f32f8af 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "undici", - "version": "7.3.0", + "version": "7.4.0", "description": "An HTTP/1.1 client, written from scratch for Node.js", "homepage": "https://undici.nodejs.org", "bugs": {