tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

commit d7286727c11c5c69972d2f509a3f2e015b87a98c
parent 89de974d040ca6f1cf4267ae62541e8deda9fb2f
Author: Kurt Catti-Schmidt <kschmi@microsoft.com>
Date:   Tue, 14 Oct 2025 22:23:57 +0000

Bug 1993204 [wpt PR 55291] - [CSS Modules] Adding WPT's for Import Map statefulness, a=testonly

Automatic update from web-platform-tests
[CSS Modules] Adding WPT's for Import Map statefulness

For CSS Modules (https://github.com/whatwg/html/issues/10673), Domenic
suggested offline to follow the static behaviors of Import Maps.

Specifically that they can't be modified into classic scripts once the
Already Started flag is set, that they don't update the underlying
import map when innerText changes, and that import map scripts can be
created dynamically as long as the Already Started flag has not been
set.

There are no WPT's specifically testing these behaviors, so this CL adds
them.

Bug: 449621064

Change-Id: I84ad6a03f4b2f9c5fa80e9d642fb14d7f303d2b9
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7004710
Reviewed-by: Shunya Shishido <sisidovski@chromium.org>
Commit-Queue: Kurt Catti-Schmidt <kschmi@microsoft.com>
Cr-Commit-Position: refs/heads/main@{#1526624}

--

wpt-commits: a5677a5cf5423c03a8126747688fe832e5356288
wpt-pr: 55291

Diffstat:
Atesting/web-platform/tests/import-maps/dynamic-module-map-key.html | 86+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mtesting/web-platform/tests/import-maps/not-as-classic-script.html | 127++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-----
Atesting/web-platform/tests/import-maps/static-after-innerText.html | 27+++++++++++++++++++++++++++
3 files changed, 233 insertions(+), 7 deletions(-)

diff --git a/testing/web-platform/tests/import-maps/dynamic-module-map-key.html b/testing/web-platform/tests/import-maps/dynamic-module-map-key.html @@ -0,0 +1,86 @@ +<!DOCTYPE html> +<html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script> + const dynamicScript = document.createElement("script"); + dynamicScript.type = "importmap"; + dynamicScript.innerText = "{ \"imports\": { \"./resources/log.js?pipe=sub&name=A\": \"./resources/log.js?pipe=sub&name=B\" } }"; + document.head.appendChild(dynamicScript); +</script> + +<script> +let log = []; + +promise_test(() => { + return import("./resources/log.js?pipe=sub&name=A") + .then(() => import("./resources/log.js?pipe=sub&name=B")) + .then(() => assert_array_equals(log, ["log:B"])) + }, + "Module map's key is the URL after import map resolution"); +</script> + +<script> + // Ensure that the Already Started flag is passed through to clones, + // ensuring the type cannot be changed after it is set to true. + const dynamicScriptTypeStatic = document.createElement("script"); + dynamicScriptTypeStatic.type = "text/javascript"; + dynamicScriptTypeStatic.type = "importmap"; + dynamicScriptTypeStatic.innerText = "{ \"imports\": { \"./resources/log.js?pipe=sub&name=C\": \"./resources/log.js?pipe=sub&name=D\" } }"; + document.head.appendChild(dynamicScriptTypeStatic); + + // Because the contents aren't empty, once it's connected as an import + // map, the Already Started flag is set to true and it can't be changed + // into a classic script. + document.head.removeChild(dynamicScriptTypeStatic); + dynamicScriptTypeStatic.type = "text/javascript"; + dynamicScriptTypeStatic.innerText = "test(function() { assert_unreached('Script should not be able to execute after Already Started is set.'); }"; + document.head.appendChild(dynamicScriptTypeStatic); + + // The Already Started flag is copied through to clones, even though + // `type` is set to "text/javascript" when it is connected. + const clonedScriptNode = dynamicScriptTypeStatic.cloneNode(/*deep=*/true); + test(function() { + assert_equals(clonedScriptNode.type, "text/javascript"); + assert_equals(clonedScriptNode.innerText, "test(function() { assert_unreached('Script should not be able to execute after Already Started is set.'); }"); + }, "Cloned script node copies attribute and text content."); + document.head.appendChild(clonedScriptNode); + + // Script tags with empty contents do not set Already Started to true, + // so the type can be changed later and on clones, even if they have + // been connected. + const dynamicScriptEmpty = document.createElement("script"); + dynamicScriptEmpty.type = "importmap"; + document.head.appendChild(dynamicScriptEmpty); + document.head.removeChild(dynamicScriptEmpty); + + // The Already Started flag is copied onto clones. + dynamicScriptEmpty.type = "text/javascript"; + const clonedEmptyScript = dynamicScriptEmpty.cloneNode(/*deep=*/true); + test(function() { + assert_equals(clonedEmptyScript.type, "text/javascript"); + assert_equals(clonedEmptyScript.innerText, ""); + }, "Cloned script node copies attributes and text content."); + + // Because Already Started is false, The clone can be set to a different + // type than the original element and both can be inserted as their + // respective types. + clonedEmptyScript.setAttribute("type", "importmap"); + clonedEmptyScript.innerText = "{ \"imports\": { \"./resources/log.js?pipe=sub&name=E\": \"./resources/log.js?pipe=sub&name=F\" } }"; + document.head.appendChild(clonedEmptyScript); + + const t_evaluate = async_test("The Already Started flag is set when a non-empty <script> tag is connected."); + dynamicScriptEmpty.innerText = "t_evaluate.done();"; + document.head.appendChild(dynamicScriptEmpty); +</script> + +<script> +promise_test(() => { + return import("./resources/log.js?pipe=sub&name=C") + .then(() => import("./resources/log.js?pipe=sub&name=D")) + .then(() => import("./resources/log.js?pipe=sub&name=E")) + .then(() => assert_array_equals(log, ["log:B", "log:D", "log:F"])) + }, + "The script tag's Already Started flag is passed to clones."); +</script> +</html> diff --git a/testing/web-platform/tests/import-maps/not-as-classic-script.html b/testing/web-platform/tests/import-maps/not-as-classic-script.html @@ -6,16 +6,19 @@ setup({allow_uncaught_exception : true}); const t_parse = async_test("Import maps shouldn't be parsed as scripts"); +const t_already_started = async_test("The Already Started flag is passed through cloneNode"); const t_evaluate = async_test("Import maps shouldn't be executed as scripts"); const t_external = async_test( "External import maps shouldn't be executed as scripts"); +const t_change_type = async_test( + "Import maps shouldn't be executed as scripts after changing type"); -const errorHandler = event => { +const parseErrorHandler = event => { event.preventDefault(); t_parse.unreached_func("An import map is parsed as a classic script")(); }; -window.addEventListener("error", errorHandler, {once: true}); +window.addEventListener("error", parseErrorHandler, {once: true}); </script> <!-- This import map causes a parse error when parsed as a classic script. --> @@ -27,19 +30,129 @@ window.addEventListener("error", errorHandler, {once: true}); </script> <script> -// Remove error handler, because the following import map can causes parse -// error. -window.removeEventListener("error", errorHandler); +window.removeEventListener("error", parseErrorHandler); +t_parse.done(); + +const alreadyStartedErrorHandler = event => { + event.preventDefault(); + t_already_started.unreached_func("An import map is evaluated as a classic script")(); +}; + +window.addEventListener("error", alreadyStartedErrorHandler, {once: true}); +</script> + +<script> +// Once Already Started is true on a classic script, it cannot be +// converted into an import map. +const dynamicScriptClassic = document.createElement("script"); +dynamicScriptClassic.type = "text/javascript"; +dynamicScriptClassic.innerText = "t_already_started.step(function() { assert_true(true, 'Classic script connected dynamically.');});"; + +// Inserting a <script> with contents sets Already Started to true. +document.head.appendChild(dynamicScriptClassic); +document.head.removeChild(dynamicScriptClassic); + +// The existing innerText would be a parse error if parsed as an import +// map, but is valid as a classic script. +dynamicScriptClassic.type = "importmap"; +document.head.appendChild(dynamicScriptClassic); + +// The Already Started flag on a script is persisted upon cloning, so +// the clone won't be parsed as an import map even though its `type` +// was set to "importmap" when it was connected. +const clonedClassicScript = dynamicScriptClassic.cloneNode(/*deep=*/true); +t_already_started.step(function() { + assert_equals(clonedClassicScript.type, "importmap"); + assert_equals(clonedClassicScript.innerText, dynamicScriptClassic.innerText); +}); +document.head.appendChild(clonedClassicScript); + +// Creating an empty <script> tag does not set Already Started to true, +// so the type can be changed later. +const dynamicScriptEmpty = document.createElement("script"); +dynamicScriptEmpty.type = "text/javascript"; +document.head.appendChild(dynamicScriptEmpty); + +// The Already Started flag is copied onto clones, so the type can be +// changed later. +const clonedDynamicScriptEmpty = dynamicScriptEmpty.cloneNode(/*deep=*/true); +t_already_started.step(function() { + assert_equals(clonedDynamicScriptEmpty.type, "text/javascript"); +}); +clonedDynamicScriptEmpty.setAttribute("type", "importmap"); +t_already_started.step(function() { + assert_equals(clonedDynamicScriptEmpty.type, "importmap"); +}); + +// Since the contents are not empty, connecting the clone will set the +// Already Started flag to true and lock the type into an import map. +document.head.appendChild(clonedDynamicScriptEmpty); +document.head.removeChild(clonedDynamicScriptEmpty); + +// This would cause a parse error when parsed as a classic script. +clonedDynamicScriptEmpty.innerText = "{ \"imports\": { } }"; +document.head.appendChild(clonedDynamicScriptEmpty); + +// The original element can have its contents set and will executed +// as a classic script. Ensure this is executed by completing the test. +dynamicScriptEmpty.innerText = "t_already_started.done();"; +document.head.appendChild(dynamicScriptEmpty); </script> +<script> +// Remove prior error handler, because the following import maps can +// causes parse errors. +window.removeEventListener("error", alreadyStartedErrorHandler); +</script> + +<script> +// A dynamically created import map isn't parsed as a classic script. +const dynamicScript = document.createElement("script"); +dynamicScript.type = "importmap"; +t_evaluate.step(function() { + assert_equals(dynamicScript.type, "importmap"); +}); +dynamicScript.innerText = "t_evaluate.unreached_func('A dynamically-created import map is evaluated')();"; +document.head.appendChild(dynamicScript); + +// Changing the type shouldn't execute script. +dynamicScript.type = "text/javascript"; +t_evaluate.step(function() { + assert_equals(dynamicScript.type, "text/javascript"); +}); + +// Nor should removing it and re-inserting it. +document.head.removeChild(dynamicScript); +document.head.appendChild(dynamicScript); +</script> + +<!-- Test the same with markup. --> <script type="importmap"> t_evaluate.unreached_func("An import map is evaluated")(); </script> +<script> +t_evaluate.done(); +</script> + +<script type="importmap" id="changeType"> +t_change_type.unreached_func("An import map is evaluated after changing type")(); +</script> + +<script> +// Change the type from "importmap" to other valid values and ensure +// script never executes. +let changeType = document.getElementById("changeType"); +changeType.setAttribute("type", "text/javascript"); +changeType.setAttribute("type", "module"); +changeType.setAttribute("type", ""); +changeType.removeAttribute("type"); +t_change_type.done(); +</script> + +<!-- Import Maps do not execute external scripts. --> <script type="importmap" src="data:text/javascript,t_external.unreached_func('An external import map is evaluated')();"></script> <script> -t_parse.done(); -t_evaluate.done(); t_external.done(); </script> diff --git a/testing/web-platform/tests/import-maps/static-after-innerText.html b/testing/web-platform/tests/import-maps/static-after-innerText.html @@ -0,0 +1,27 @@ +<!DOCTYPE html> +<html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script type="importmap" id="importMap"> +{ + "imports": { + "./resources/log.js?pipe=sub&name=A": "./resources/log.js?pipe=sub&name=B" + } +} +</script> +<script> +const log = []; + +promise_test(() => { + // Import maps are static. Changing their innerText does not change the import + // map entry. + const importMap = document.getElementById("importMap"); + importMap.innerText = ""; + + // The following should succeed as if the above lines did nothing. + return import("./resources/log.js?pipe=sub&name=A") + .then(() => import("./resources/log.js?pipe=sub&name=B")) + .then(() => assert_array_equals(log, ["log:B"])) + }, + "Module map's key is the URL after import map resolution"); +</script>