diff --git a/html/semantics/scripting-1/the-script-element/module/import-meta/import-meta-object.any.js b/html/semantics/scripting-1/the-script-element/module/import-meta/import-meta-object.any.js new file mode 100644 index 00000000000000..494e1681023c2b --- /dev/null +++ b/html/semantics/scripting-1/the-script-element/module/import-meta/import-meta-object.any.js @@ -0,0 +1,20 @@ +// META: global=dedicatedworker-module,sharedworker-module,serviceworker-module + +test(() => { + assert_equals(typeof import.meta, "object"); + assert_not_equals(import.meta, null); +}, "import.meta is an object"); + +test(() => { + import.meta.newProperty = 1; + assert_true(Object.isExtensible(import.meta)); +}, "import.meta is extensible"); + +test(() => { + for (const name of Reflect.ownKeys(import.meta)) { + const desc = Object.getOwnPropertyDescriptor(import.meta, name); + assert_equals(desc.writable, true); + assert_equals(desc.enumerable, true); + assert_equals(desc.configurable, true); + } +}, "import.meta's properties are writable, configurable, and enumerable"); diff --git a/html/semantics/scripting-1/the-script-element/module/import-meta/import-meta-resolve-importmap.html b/html/semantics/scripting-1/the-script-element/module/import-meta/import-meta-resolve-importmap.html new file mode 100644 index 00000000000000..214b9bb59c3a73 --- /dev/null +++ b/html/semantics/scripting-1/the-script-element/module/import-meta/import-meta-resolve-importmap.html @@ -0,0 +1,57 @@ +<!DOCTYPE html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> + +<!-- + More extensive tests of import maps and import.meta.resolve() will be + located in the import maps test suite. This contains some basic tests plus + tests some tricky parts of the import.meta.resolve() algorithm around string + conversion which are only testable with import maps. +--> + +<script type="importmap"> +{ + "imports": { + "bare": "https://example.com/", + "https://example.com/rewrite": "https://example.com/rewritten", + + "1": "https://example.com/PASS-1", + "null": "https://example.com/PASS-null", + "undefined": "https://example.com/PASS-undefined", + "[object Object]": "https://example.com/PASS-object", + + "./start": "./resources/export-1.mjs", + "./resources/export-1.mjs": "./resources/export-2.mjs" + } +} +</script> + +<script type="module"> +test(() => { + assert_equals(import.meta.resolve("bare"), "https://example.com/"); +}, "import.meta.resolve() given an import mapped bare specifier"); + +test(() => { + assert_equals(import.meta.resolve("https://example.com/rewrite"), "https://example.com/rewritten"); +}, "import.meta.resolve() given an import mapped URL-like specifier"); + +test(() => { + assert_equals(import.meta.resolve(), "https://example.com/PASS-undefined", "no-arg case"); + + assert_equals(import.meta.resolve(1), "https://example.com/PASS-1"); + assert_equals(import.meta.resolve(null), "https://example.com/PASS-null"); + assert_equals(import.meta.resolve(undefined), "https://example.com/PASS-undefined"); + + // Only toString() methods are consulted by ToString, not valueOf() ones. + // So this becomes "[object Object]". + assert_equals(import.meta.resolve({ valueOf() { return "./x"; } }), "https://example.com/PASS-object"); +}, "Testing the ToString() step of import.meta.resolve() via import maps"); + +promise_test(async () => { + const one = (await import("./start")).default; + assert_equals(one, 1); + + const two = (await import(import.meta.resolve("./start"))).default; + assert_equals(two, 2); +}, "import(import.meta.resolve(x)) can be different from import(x)"); +</script> diff --git a/html/semantics/scripting-1/the-script-element/module/import-meta/import-meta-resolve-multiple-scripts.html b/html/semantics/scripting-1/the-script-element/module/import-meta/import-meta-resolve-multiple-scripts.html new file mode 100644 index 00000000000000..d2e0f185e0b370 --- /dev/null +++ b/html/semantics/scripting-1/the-script-element/module/import-meta/import-meta-resolve-multiple-scripts.html @@ -0,0 +1,39 @@ +<!DOCTYPE html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> + +<iframe src="resources/store-import-meta.html"></iframe> + +<script type="module"> +import * as otherImportMeta from "./resources/export-import-meta.mjs"; +setup({ explicit_done: true }); + +window.onload = () => { + test(() => { + assert_not_equals(frames[0].importMetaURL, import.meta.url, + "Precondition check: we've set things up so that the other script has a different import.meta.url"); + + const expected = (new URL("resources/x", location.href)).href; + assert_equals(frames[0].importMetaResolve("./x"), expected); + }, "import.meta.resolve resolves URLs relative to the import.meta.url, not relative to the active script when it is called: another global's inline script"); + + test(() => { + const otherFrameImportMetaResolve = frames[0].importMetaResolve; + + document.querySelector("iframe").remove(); + + const expected = (new URL("resources/x", location.href)).href; + assert_equals(otherFrameImportMetaResolve("./x"), expected); + }, "import.meta.resolve still works if its global has been destroyed (by detaching the iframe)"); + + test(() => { + assert_not_equals(otherImportMeta.url, import.meta.url, + "Precondition check: we've set things up so that the other script has a different import.meta.url"); + + const expected = (new URL("resources/x", location.href)).href; + assert_equals(otherImportMeta.resolve("./x"), expected); + }, "import.meta.resolve resolves URLs relative to the import.meta.url, not relative to the active script when it is called: another module script"); + + done(); +}; +</script> diff --git a/html/semantics/scripting-1/the-script-element/module/import-meta/import-meta-resolve.any.js b/html/semantics/scripting-1/the-script-element/module/import-meta/import-meta-resolve.any.js new file mode 100644 index 00000000000000..5b8a84efaf9cd6 --- /dev/null +++ b/html/semantics/scripting-1/the-script-element/module/import-meta/import-meta-resolve.any.js @@ -0,0 +1,77 @@ +// META: global=dedicatedworker-module,sharedworker-module,serviceworker-module + +import { importMetaOnRootModule, importMetaOnDependentModule } + from "./import-meta-root.js"; + +test(() => { + assert_equals(typeof import.meta.resolve, "function"); + assert_equals(import.meta.resolve.name, "resolve"); + assert_equals(import.meta.resolve.length, 1); + assert_equals(Object.getPrototypeOf(import.meta.resolve), Function.prototype); +}, "import.meta.resolve is a function with the right properties"); + +test(() => { + assert_false(isConstructor(import.meta.resolve)); + + assert_throws_js(TypeError, () => new import.meta.resolve("./x")); +}, "import.meta.resolve is not a constructor"); + +test(() => { + // See also tests in ./import-meta-resolve-importmap.html. + + assert_equals(import.meta.resolve({ toString() { return "./x"; } }), resolveURL("x")); + assert_throws_js(TypeError, () => import.meta.resolve(Symbol("./x")), + "symbol"); + assert_throws_js(TypeError, () => import.meta.resolve(), + "no argument (which is treated like \"undefined\")"); +}, "import.meta.resolve ToString()s its argument"); + +test(() => { + assert_equals(import.meta.resolve("./x"), resolveURL("x"), + "current module import.meta"); + assert_equals(importMetaOnRootModule.resolve("./x"), resolveURL("x"), + "sibling module import.meta"); + assert_equals(importMetaOnDependentModule.resolve("./x"), resolveURL("x"), + "dependency module import.meta"); +}, "Relative URL-like specifier resolution"); + +test(() => { + assert_equals(import.meta.resolve("https://example.com/"), "https://example.com/", + "current module import.meta"); + assert_equals(importMetaOnRootModule.resolve("https://example.com/"), "https://example.com/", + "sibling module import.meta"); + assert_equals(importMetaOnDependentModule.resolve("https://example.com/"), "https://example.com/", + "dependency module import.meta"); +}, "Absolute URL-like specifier resolution"); + +test(() => { + const invalidSpecifiers = [ + "https://eggplant:b/c", + "pumpkins.js", + ".tomato", + "..zuccini.mjs", + ".\\yam.es" + ]; + + for (const specifier of invalidSpecifiers) { + assert_throws_js(TypeError, () => import.meta.resolve(specifier), specifier); + } +}, "Invalid module specifiers"); + +test(() => { + const { resolve } = import.meta; + assert_equals(resolve("https://example.com/"), "https://example.com/", "current module import.meta"); +}, "Works fine with no this value"); + +function resolveURL(urlRelativeToThisTest) { + return (new URL(urlRelativeToThisTest, location.href)).href; +} + +function isConstructor(o) { + try { + new (new Proxy(o, { construct: () => ({}) })); + return true; + } catch { + return false; + } +} diff --git a/html/semantics/scripting-1/the-script-element/module/import-meta/import-meta-url.any.js b/html/semantics/scripting-1/the-script-element/module/import-meta/import-meta-url.any.js index 82982b4d93cbf2..61d96f35af344a 100644 --- a/html/semantics/scripting-1/the-script-element/module/import-meta/import-meta-url.any.js +++ b/html/semantics/scripting-1/the-script-element/module/import-meta/import-meta-url.any.js @@ -15,26 +15,6 @@ test(() => { base + "/import-meta-dependent.js"); }, "import.meta.url in a dependent external script"); -test(() => { - assert_equals(typeof importMetaOnRootModule, "object"); - assert_not_equals(importMetaOnRootModule, null); -}, "import.meta is an object"); - -test(() => { - importMetaOnRootModule.newProperty = 1; - assert_true(Object.isExtensible(importMetaOnRootModule)); -}, "import.meta is extensible"); - -test(() => { - const names = new Set(Reflect.ownKeys(importMetaOnRootModule)); - for (const name of names) { - var desc = Object.getOwnPropertyDescriptor(importMetaOnRootModule, name); - assert_equals(desc.writable, true); - assert_equals(desc.enumerable, true); - assert_equals(desc.configurable, true); - } -}, "import.meta's properties are writable, configurable, and enumerable"); - import { importMetaOnRootModule as hashedImportMetaOnRootModule1, importMetaOnDependentModule as hashedImportMetaOnDependentModule1 } diff --git a/html/semantics/scripting-1/the-script-element/module/import-meta/resources/export-1.mjs b/html/semantics/scripting-1/the-script-element/module/import-meta/resources/export-1.mjs new file mode 100644 index 00000000000000..aef22247d75263 --- /dev/null +++ b/html/semantics/scripting-1/the-script-element/module/import-meta/resources/export-1.mjs @@ -0,0 +1 @@ +export default 1; diff --git a/html/semantics/scripting-1/the-script-element/module/import-meta/resources/export-2.mjs b/html/semantics/scripting-1/the-script-element/module/import-meta/resources/export-2.mjs new file mode 100644 index 00000000000000..842e368a0a26cd --- /dev/null +++ b/html/semantics/scripting-1/the-script-element/module/import-meta/resources/export-2.mjs @@ -0,0 +1 @@ +export default 2; diff --git a/html/semantics/scripting-1/the-script-element/module/import-meta/resources/export-import-meta.mjs b/html/semantics/scripting-1/the-script-element/module/import-meta/resources/export-import-meta.mjs new file mode 100644 index 00000000000000..488ca74c935115 --- /dev/null +++ b/html/semantics/scripting-1/the-script-element/module/import-meta/resources/export-import-meta.mjs @@ -0,0 +1,2 @@ +export const url = import.meta.url; +export const resolve = import.meta.resolve; diff --git a/html/semantics/scripting-1/the-script-element/module/import-meta/resources/store-import-meta.html b/html/semantics/scripting-1/the-script-element/module/import-meta/resources/store-import-meta.html new file mode 100644 index 00000000000000..c9751da408b1fd --- /dev/null +++ b/html/semantics/scripting-1/the-script-element/module/import-meta/resources/store-import-meta.html @@ -0,0 +1,5 @@ +<!DOCTYPE html> +<script type="module"> +window.importMetaURL = import.meta.url; +window.importMetaResolve = import.meta.resolve; +</script>