Bug 1527643 [wpt PR 15197] - Add tentative WPT tests for Built-in Module Infra and Import Maps, a=testonly
authorHiroshige Hayashizaki <hiroshige@chromium.org>
Tue, 05 Mar 2019 11:11:07 +0000
changeset 464514 c20dbdb5add0
parent 464513 3ff51976c179
child 464515 5ed7c1d98107
push id35717
push useraciure@mozilla.com
push dateSun, 17 Mar 2019 09:45:26 +0000
treeherdermozilla-central@e0861be8d6c0 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerstestonly
bugs1527643, 15197, 848607, 927477, 928435, 1449072, 631546
milestone67.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1527643 [wpt PR 15197] - Add tentative WPT tests for Built-in Module Infra and Import Maps, a=testonly Automatic update from web-platform-tests Add tentative WPT tests for Built-in Module Infra and Import Maps Bug: 848607, 927477, 928435 Change-Id: Id0573736a762c26df28f32da0716d94a566cfce3 Reviewed-on: https://chromium-review.googlesource.com/c/1449072 Commit-Queue: Hiroshige Hayashizaki <hiroshige@chromium.org> Reviewed-by: Kouhei Ueno <kouhei@chromium.org> Cr-Commit-Position: refs/heads/master@{#631546} -- wpt-commits: 896ca12d8a73e7679ef3a4c7402301a5603b5faa wpt-pr: 15197
testing/web-platform/tests/import-maps/@std/__dir__.headers
testing/web-platform/tests/import-maps/@std/blank
testing/web-platform/tests/import-maps/@std/none
testing/web-platform/tests/import-maps/README.md
testing/web-platform/tests/import-maps/acquire-import-maps-flag/dynamic-import/success.tentative.html
testing/web-platform/tests/import-maps/acquire-import-maps-flag/script-tag/success.tentative.html
testing/web-platform/tests/import-maps/acquire-import-maps-flag/worker-request/success.tentative.html
testing/web-platform/tests/import-maps/bare.sub.tentative.html
testing/web-platform/tests/import-maps/bare/__dir__.headers
testing/web-platform/tests/import-maps/bare/bare
testing/web-platform/tests/import-maps/bare/blank
testing/web-platform/tests/import-maps/bare/cross-origin-bare
testing/web-platform/tests/import-maps/bare/none
testing/web-platform/tests/import-maps/bare/std-blank
testing/web-platform/tests/import-maps/bare/std-none
testing/web-platform/tests/import-maps/bare/to-bare
testing/web-platform/tests/import-maps/bare/to-data
testing/web-platform/tests/import-maps/builtin-import-scheme.tentative.html
testing/web-platform/tests/import-maps/builtin.tentative.html
testing/web-platform/tests/import-maps/data.sub.tentative.html
testing/web-platform/tests/import-maps/fallback-disallowed.sub.tentative.html
testing/web-platform/tests/import-maps/fallback.sub.tentative.html
testing/web-platform/tests/import-maps/http.sub.tentative.html
testing/web-platform/tests/import-maps/module-map-key.tentative.html
testing/web-platform/tests/import-maps/resolving.tentative.html
testing/web-platform/tests/import-maps/resources/empty.js
testing/web-platform/tests/import-maps/resources/log.js
testing/web-platform/tests/import-maps/resources/log.js.headers
testing/web-platform/tests/import-maps/resources/resolving.js
testing/web-platform/tests/import-maps/resources/test-helper.js
testing/web-platform/tests/import-maps/static-import.js
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/import-maps/@std/__dir__.headers
@@ -0,0 +1,1 @@
+Content-Type: text/javascript
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/import-maps/@std/blank
@@ -0,0 +1,1 @@
+log.push("relative:@std/blank");
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/import-maps/@std/none
@@ -0,0 +1,1 @@
+log.push("relative:@std/none");
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/import-maps/README.md
@@ -0,0 +1,9 @@
+Tests for [Import Maps](https://github.com/WICG/import-maps).
+
+Because the spec itself is still under development and there are ongoing spec
+discussions, the tests are all tentative.
+
+Also, some tests are based on Chromium's behavior which reflects an older
+version of import maps spec ("package name maps" around May 2018), and have
+dependency to Chromium's implementation (internals.resolveModuleSpecifier).
+These dependencies should be removed, once the spec matures.
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/import-maps/acquire-import-maps-flag/dynamic-import/success.tentative.html
@@ -0,0 +1,31 @@
+<!DOCTYPE html>
+<html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+// https://github.com/WICG/import-maps/blob/master/spec.md#when-import-maps-can-be-encountered
+const t = async_test(
+  'After dynamic imports, import maps should fire error events');
+const log = [];
+// To ensure we are testing that the flag is cleared at the beginning of module
+// script loading unconditionally, not at the end of loading or not at the
+// first attempt to resolve a module specifier, trickle(d1) is used to ensure
+// the following import map is added after module loading is triggered but
+// before the first module script is parsed.
+promise_test(() => import('../../resources/empty.js?pipe=trickle(d1)'),
+             "A dynamic import succeeds");
+</script>
+<script type="importmap" onload="t.assert_unreached('onload')" onerror="t.done()">
+{
+  "imports": {
+    "../../resources/log.js?pipe=sub&name=A": "../../resources/log.js?pipe=sub&name=B"
+  }
+}
+</script>
+<script>
+promise_test(() => {
+  return import("../../resources/log.js?pipe=sub&name=A")
+    .then(() => assert_array_equals(log, ["log:A"]))
+  },
+  'After a dynamic import(), import maps are not effective');
+</script>
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/import-maps/acquire-import-maps-flag/script-tag/success.tentative.html
@@ -0,0 +1,25 @@
+<!DOCTYPE html>
+<html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+// https://github.com/WICG/import-maps/blob/master/spec.md#when-import-maps-can-be-encountered
+const t = async_test(
+  'After <script type="module"> import maps should fire error events');
+const log = [];
+</script>
+<script type="module" src="../../resources/empty.js?pipe=trickle(d1)"></script>
+<script type="importmap" onerror="t.done()">
+{
+  "imports": {
+    "../../resources/log.js?pipe=sub&name=A": "../../resources/log.js?pipe=sub&name=B"
+  }
+}
+</script>
+<script>
+promise_test(() => {
+  return import("../../resources/log.js?pipe=sub&name=A")
+    .then(() => assert_array_equals(log, ["log:A"]))
+  },
+  'After <script type="module"> import maps are not effective');
+</script>
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/import-maps/acquire-import-maps-flag/worker-request/success.tentative.html
@@ -0,0 +1,25 @@
+<!DOCTYPE html>
+<html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+// https://github.com/WICG/import-maps/blob/master/spec.md#when-import-maps-can-be-encountered
+const t = async_test(
+  'After module worker creation, import maps should fire error events');
+const log = [];
+new Worker('../../resources/empty.js?pipe=trickle(d1)', {type: "module"});
+</script>
+<script type="importmap" onerror="t.done()">
+{
+  "imports": {
+    "../../resources/log.js?pipe=sub&name=A": "../../resources/log.js?pipe=sub&name=B"
+  }
+}
+</script>
+<script>
+promise_test(() => {
+  return import("../../resources/log.js?pipe=sub&name=A")
+    .then(() => assert_array_equals(log, ["A"]))
+  },
+  'After module worker creation import maps are not effective');
+</script>
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/import-maps/bare.sub.tentative.html
@@ -0,0 +1,70 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/test-helper.js"></script>
+
+<script>
+// "bare/..." (i.e. without leading "./") are bare specifiers
+// (not relative paths).
+//
+// Discussions about notations for builtin modules are ongoing, e.g.
+// https://github.com/tc39/proposal-javascript-standard-library/issues/12
+// Currently the tests expects two notations are accepted.
+// TODO: Once the discussions converge, update the tests.
+const importMap = `
+{
+  "imports": {
+    "bare/bare": "./resources/log.js?pipe=sub&name=bare",
+    "bare/cross-origin-bare": "https://{{domains[www1]}}:{{ports[https][0]}}/import-maps/resources/log.js?pipe=sub&name=cross-origin-bare",
+    "bare/to-data": "data:text/javascript,log.push('dataURL')",
+
+    "bare/std-blank": "std:blank",
+    "bare/blank": "@std/blank",
+    "bare/std-none": "std:none",
+    "bare/none": "@std/none",
+
+    "bare/to-bare": "bare/bare"
+  }
+}
+`;
+
+const tests = {
+  // Arrays of expected results for:
+  // - <script src type="module">,
+  // - <script src> (classic script),
+  // - static import, and
+  // - dynamic import.
+
+  // Currently, Chromium's implementation resolves import maps as a part of
+  // specifier resolution, and thus failure in import map resolution causes
+  // a parse error, not fetch error. Therefore, we use Result.PARSE_ERROR
+  // below. https://crbug.com/928435
+
+  // Bare to HTTP(S).
+  "bare/bare":
+    [Result.URL, Result.URL, "log:bare", "log:bare"],
+  "bare/cross-origin-bare":
+    [Result.URL, Result.URL, "log:cross-origin-bare", "log:cross-origin-bare"],
+
+  // Bare to data:
+  "bare/to-data":
+    [Result.URL, Result.URL, "dataURL", "dataURL"],
+
+  // Bare to built-in.
+  "bare/std-blank":
+    [Result.URL, Result.URL, Result.BUILTIN, Result.BUILTIN],
+  "bare/blank":
+    [Result.URL, Result.URL, Result.BUILTIN, Result.BUILTIN],
+  "bare/std-none":
+    [Result.URL, Result.URL, Result.PARSE_ERROR, Result.PARSE_ERROR],
+  "bare/none":
+    [Result.URL, Result.URL, Result.PARSE_ERROR, Result.PARSE_ERROR],
+
+  // Bare to bare mapping is disabled.
+  "bare/to-bare":
+    [Result.URL, Result.URL, Result.PARSE_ERROR, Result.PARSE_ERROR],
+};
+
+doTests(importMap, null, tests);
+</script>
+<body>
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/import-maps/bare/__dir__.headers
@@ -0,0 +1,1 @@
+Content-Type: text/javascript
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/import-maps/bare/bare
@@ -0,0 +1,1 @@
+log.push("relative:bare/bare");
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/import-maps/bare/blank
@@ -0,0 +1,1 @@
+log.push("relative:bare/blank");
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/import-maps/bare/cross-origin-bare
@@ -0,0 +1,1 @@
+log.push("relative:bare/cross-origin-bare");
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/import-maps/bare/none
@@ -0,0 +1,1 @@
+log.push("relative:bare/none");
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/import-maps/bare/std-blank
@@ -0,0 +1,1 @@
+log.push("relative:bare/std-blank");
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/import-maps/bare/std-none
@@ -0,0 +1,1 @@
+log.push("relative:bare/std-none");
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/import-maps/bare/to-bare
@@ -0,0 +1,1 @@
+log.push("relative:bare/to-bare");
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/import-maps/bare/to-data
@@ -0,0 +1,1 @@
+log.push("relative:bare/to-data");
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/import-maps/builtin-import-scheme.tentative.html
@@ -0,0 +1,27 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/test-helper.js"></script>
+
+<script>
+const tests = {
+  // Arrays of expected results for:
+  // - <script src type="module">,
+  // - <script src> (classic script),
+  // - static import, and
+  // - dynamic import.
+
+  // Currently direct use of import: URLs are disabled.
+  "import:std:blank":
+    [Result.FETCH_ERROR, Result.FETCH_ERROR, Result.FETCH_ERROR, Result.FETCH_ERROR],
+  "import:@std/blank":
+    [Result.FETCH_ERROR, Result.FETCH_ERROR, Result.FETCH_ERROR, Result.FETCH_ERROR],
+  "import:std:none":
+    [Result.FETCH_ERROR, Result.FETCH_ERROR, Result.FETCH_ERROR, Result.FETCH_ERROR],
+  "import:@std/none":
+    [Result.FETCH_ERROR, Result.FETCH_ERROR, Result.FETCH_ERROR, Result.FETCH_ERROR],
+};
+
+doTests(null, null, tests);
+</script>
+<body>
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/import-maps/builtin.tentative.html
@@ -0,0 +1,30 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/test-helper.js"></script>
+
+<script>
+const tests = {
+  // Arrays of expected results for:
+  // - <script src type="module">,
+  // - <script src> (classic script),
+  // - static import, and
+  // - dynamic import.
+
+  // Discussions about notations are ongoing, e.g.
+  // https://github.com/tc39/proposal-javascript-standard-library/issues/12
+  // Currently the tests expects two notations are accepted.
+  // TODO: Once the discussions converge, update this and related tests.
+  "std:blank":
+    [Result.BUILTIN, Result.FETCH_ERROR, Result.BUILTIN, Result.BUILTIN],
+  "@std/blank":
+    [Result.URL, Result.URL, Result.BUILTIN, Result.BUILTIN],
+  "std:none":
+    [Result.FETCH_ERROR, Result.FETCH_ERROR, Result.FETCH_ERROR, Result.FETCH_ERROR],
+  "@std/none":
+    [Result.URL, Result.URL, Result.FETCH_ERROR, Result.FETCH_ERROR],
+};
+
+doTests(null, null, tests);
+</script>
+<body>
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/import-maps/data.sub.tentative.html
@@ -0,0 +1,72 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/test-helper.js"></script>
+
+<script>
+// "bare/..." (i.e. without leading "./") are bare specifiers
+// (not relative paths).
+//
+// Discussions about notations for builtin modules are ongoing, e.g.
+// https://github.com/tc39/proposal-javascript-standard-library/issues/12
+// Currently the tests expects two notations are accepted.
+// TODO: Once the discussions converge, update the tests.
+const importMap = `
+{
+  "imports": {
+    "bare": "./resources/log.js?pipe=sub&name=bare",
+
+    "data:text/javascript,log.push('data:foo')": "./resources/log.js?pipe=sub&name=foo",
+    "data:text/javascript,log.push('data:cross-origin-foo')": "https://{{domains[www1]}}:{{ports[https][0]}}/import-maps/resources/log.js?pipe=sub&name=cross-origin-foo",
+    "data:text/javascript,log.push('data:to-data')": "data:text/javascript,log.push('dataURL')",
+
+    "data:text/javascript,log.push('data:std-blank')": "std:blank",
+    "data:text/javascript,log.push('data:blank')": "@std/blank",
+    "data:text/javascript,log.push('data:std-none')": "std:none",
+    "data:text/javascript,log.push('data:none')": "@std/none",
+
+    "data:text/javascript,log.push('data:to-bare')": "bare"
+  }
+}
+`;
+
+const tests = {
+  // Arrays of expected results for:
+  // - <script src type="module">,
+  // - <script src> (classic script),
+  // - static import, and
+  // - dynamic import.
+
+  // Currently, Chromium's implementation resolves import maps as a part of
+  // specifier resolution, and thus failure in import map resolution causes
+  // a parse error, not fetch error. Therefore, we use Result.PARSE_ERROR
+  // below. https://crbug.com/928435
+
+  // data: to HTTP(S).
+  "data:text/javascript,log.push('data:foo')":
+    [Result.URL, Result.URL, "log:foo", "log:foo"],
+  "data:text/javascript,log.push('data:cross-origin-foo')":
+    [Result.URL, Result.URL, "log:cross-origin-foo", "log:cross-origin-foo"],
+
+  // data: to data:
+  "data:text/javascript,log.push('data:to-data')":
+    [Result.URL, Result.URL, "dataURL", "dataURL"],
+
+  // data: to built-in.
+  "data:text/javascript,log.push('data:std-blank')":
+    [Result.URL, Result.URL, Result.BUILTIN, Result.BUILTIN],
+  "data:text/javascript,log.push('data:blank')":
+    [Result.URL, Result.URL, Result.BUILTIN, Result.BUILTIN],
+  "data:text/javascript,log.push('data:std-none')":
+    [Result.URL, Result.URL, Result.PARSE_ERROR, Result.PARSE_ERROR],
+  "data:text/javascript,log.push('data:none')":
+    [Result.URL, Result.URL, Result.PARSE_ERROR, Result.PARSE_ERROR],
+
+  // data: to bare mapping is disabled.
+  "data:text/javascript,log.push('data:to-bare')":
+    [Result.URL, Result.URL, Result.PARSE_ERROR, Result.PARSE_ERROR],
+};
+
+doTests(importMap, null, tests);
+</script>
+<body>
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/import-maps/fallback-disallowed.sub.tentative.html
@@ -0,0 +1,79 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/test-helper.js"></script>
+
+<script>
+// Fallbacks from external URLs (such as HTTPS URLs) are
+// blocked by ongoing spec discussions, for example
+// https://github.com/WICG/import-maps/issues/76.
+// https://crbug.com/928435
+//
+// This test, as well as Chromium's implementation, rejects broader range of
+// fallbacks (not only those from HTTPS), to avoid potential spec and
+// interoperability issues.
+// The only allowed fallback pattern is fallbacks from bare specifiers with
+// two elements, which are listed in fallback.sub.tentative.html.
+const importMap = `
+{
+  "imports": {
+    "bare": "./resources/log.js?pipe=sub&name=bare",
+
+    "./resources/log.js?pipe=sub&name=http-to-builtin": [
+      "./resources/log.js?pipe=sub&name=http-to-builtin",
+      "@std/blank"
+    ],
+
+    "./resources/log.js?pipe=sub&name=fallback-to-different-url-1": [
+      "@std/blank",
+      "./resources/log.js?pipe=sub&name=something-different"
+    ],
+    "./resources/log.js?pipe=sub&name=fallback-to-different-url-2": [
+      "@std/none",
+      "./resources/log.js?pipe=sub&name=something-different2"
+    ],
+    "./resources/log.js?pipe=sub&name=fallback-to-different-origin-1": [
+      "@std/blank",
+      "https://{{domains[www1]}}:{{ports[https][0]}}/import-maps/resources/log.js?pipe=sub&name=fallback-to-different-origin-1"
+    ],
+    "./resources/log.js?pipe=sub&name=fallback-to-different-origin-2": [
+      "@std/none",
+      "https://{{domains[www1]}}:{{ports[https][0]}}/import-maps/resources/log.js?pipe=sub&name=fallback-to-different-origin-2"
+    ],
+
+    "./resources/log.js?pipe=sub&name=more-than-two-values-1": [
+      "@std/none",
+      "@std/blank",
+      "./resources/log.js?pipe=sub&name=more-than-two-values-1"
+    ],
+    "./resources/log.js?pipe=sub&name=more-than-two-values-2": [
+      "@std/none",
+      "./resources/log.js?pipe=sub&name=more-than-two-values-2",
+      "@std/blank"
+    ],
+    "./resources/log.js?pipe=sub&name=fallback-from-http": [
+      "./resources/log.js?pipe=sub&name=non-built-in",
+      "./resources/log.js?pipe=sub&name=fallback-from-http"
+    ],
+    "./resources/log.js?pipe=sub&name=fallback-from-data-1": [
+      "data:text/plain,",
+      "./resources/log.js?pipe=sub&name=fallback-from-http"
+    ],
+    "./resources/log.js?pipe=sub&name=fallback-from-data-2": [
+      "data:text/javascript,log.push('dataURL')",
+      "./resources/log.js?pipe=sub&name=fallback-from-http"
+    ]
+  }
+}
+`;
+const tests = {};
+for (const key in JSON.parse(importMap).imports) {
+  if (key === "bare") {
+    continue;
+  }
+  tests[key] =
+    [Result.URL, Result.URL, Result.PARSE_ERROR, Result.PARSE_ERROR];
+}
+doTests(importMap, null, tests);
+</script>
+<body>
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/import-maps/fallback.sub.tentative.html
@@ -0,0 +1,84 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/test-helper.js"></script>
+
+<script>
+// This tests is for fallbacks with the pattern of
+// `"https://some.external/url": ["@std/x", "https://some.external/url"]`
+// which maps "https://some.external/url" to "@std/x" if "@std/x" is
+// implemented, or leaves it unmodified otherwise.
+//
+// This is the primary use case where fallback should work.
+// Some other patterns of fallbacks are intentionally blocked due to ongoing
+// spec issues. See fallback-disallowed.sub.tentative.html.
+const importMap = `
+{
+  "imports": {
+    "./resources/log.js?pipe=sub&name=blank": [
+      "@std/blank",
+      "./resources/log.js?pipe=sub&name=blank"
+    ],
+    "./resources/log.js?pipe=sub&name=none": [
+      "@std/none",
+      "./resources/log.js?pipe=sub&name=none"
+    ],
+    "https://{{domains[www1]}}:{{ports[https][0]}}/import-maps/resources/log.js?pipe=sub&name=cross-origin-blank": [
+      "@std/blank",
+      "https://{{domains[www1]}}:{{ports[https][0]}}/import-maps/resources/log.js?pipe=sub&name=cross-origin-blank"
+    ],
+    "https://{{domains[www1]}}:{{ports[https][0]}}/import-maps/resources/log.js?pipe=sub&name=cross-origin-none": [
+      "@std/none",
+      "https://{{domains[www1]}}:{{ports[https][0]}}/import-maps/resources/log.js?pipe=sub&name=cross-origin-none"
+    ],
+
+    "./resources/log.js?pipe=sub&name=std-blank": [
+      "std:blank",
+      "./resources/log.js?pipe=sub&name=std-blank"
+    ],
+    "./resources/log.js?pipe=sub&name=std-none": [
+      "std:none",
+      "./resources/log.js?pipe=sub&name=std-none"
+    ],
+    "https://{{domains[www1]}}:{{ports[https][0]}}/import-maps/resources/log.js?pipe=sub&name=std-cross-origin-blank": [
+      "std:blank",
+      "https://{{domains[www1]}}:{{ports[https][0]}}/import-maps/resources/log.js?pipe=sub&name=std-cross-origin-blank"
+    ],
+    "https://{{domains[www1]}}:{{ports[https][0]}}/import-maps/resources/log.js?pipe=sub&name=std-cross-origin-none": [
+      "std:none",
+      "https://{{domains[www1]}}:{{ports[https][0]}}/import-maps/resources/log.js?pipe=sub&name=std-cross-origin-none"
+    ]
+
+  }
+}
+`;
+const tests = {
+  // Arrays of expected results for:
+  // - <script src type="module">,
+  // - <script src> (classic script),
+  // - static import, and
+  // - dynamic import.
+  // Result.URL indicates that the specifier was not re-mapped by import maps,
+  // i.e. either considered as a relative path, or fallback occured.
+  "./resources/log.js?pipe=sub&name=blank":
+    [Result.URL, Result.URL, Result.BUILTIN, Result.BUILTIN],
+  "./resources/log.js?pipe=sub&name=none":
+    [Result.URL, Result.URL, Result.URL, Result.URL],
+  "https://{{domains[www1]}}:{{ports[https][0]}}/import-maps/resources/log.js?pipe=sub&name=cross-origin-blank":
+    [Result.URL, Result.URL, Result.BUILTIN, Result.BUILTIN],
+  "https://{{domains[www1]}}:{{ports[https][0]}}/import-maps/resources/log.js?pipe=sub&name=cross-origin-none":
+    [Result.URL, Result.URL, Result.URL, Result.URL],
+
+  "./resources/log.js?pipe=sub&name=std-blank":
+    [Result.URL, Result.URL, Result.BUILTIN, Result.BUILTIN],
+  "./resources/log.js?pipe=sub&name=std-none":
+    [Result.URL, Result.URL, Result.URL, Result.URL],
+  "https://{{domains[www1]}}:{{ports[https][0]}}/import-maps/resources/log.js?pipe=sub&name=std-cross-origin-blank":
+    [Result.URL, Result.URL, Result.BUILTIN, Result.BUILTIN],
+  "https://{{domains[www1]}}:{{ports[https][0]}}/import-maps/resources/log.js?pipe=sub&name=std-cross-origin-none":
+    [Result.URL, Result.URL, Result.URL, Result.URL],
+};
+
+doTests(importMap, null, tests);
+</script>
+<body>
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/import-maps/http.sub.tentative.html
@@ -0,0 +1,72 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/test-helper.js"></script>
+
+<script>
+// "bare/..." (i.e. without leading "./") are bare specifiers
+// (not relative paths).
+//
+// Discussions about notations for builtin modules are ongoing, e.g.
+// https://github.com/tc39/proposal-javascript-standard-library/issues/12
+// Currently the tests expects two notations are accepted.
+// TODO: Once the discussions converge, update the tests.
+const importMap = `
+{
+  "imports": {
+    "bare": "./resources/log.js?pipe=sub&name=bare",
+
+    "./resources/log.js?pipe=sub&name=foo": "./resources/log.js?pipe=sub&name=bar",
+    "./resources/log.js?pipe=sub&name=cross-origin-foo": "https://{{domains[www1]}}:{{ports[https][0]}}/import-maps/resources/log.js?pipe=sub&name=cross-origin-bar",
+    "./resources/log.js?pipe=sub&name=to-data": "data:text/javascript,log.push('dataURL')",
+
+    "./resources/log.js?pipe=sub&name=std-blank": "std:blank",
+    "./resources/log.js?pipe=sub&name=blank": "@std/blank",
+    "./resources/log.js?pipe=sub&name=std-none": "std:none",
+    "./resources/log.js?pipe=sub&name=none": "@std/none",
+
+    "./resources/log.js?pipe=sub&name=to-bare": "bare"
+  }
+}
+`;
+
+const tests = {
+  // Arrays of expected results for:
+  // - <script src type="module">,
+  // - <script src> (classic script),
+  // - static import, and
+  // - dynamic import.
+
+  // Currently, Chromium's implementation resolves import maps as a part of
+  // specifier resolution, and thus failure in import map resolution causes
+  // a parse error, not fetch error. Therefore, we use Result.PARSE_ERROR
+  // below. https://crbug.com/928435
+
+  // HTTP(S) to HTTP(S).
+  "{{location[server]}}/import-maps/resources/log.js?pipe=sub&name=foo":
+    [Result.URL, Result.URL, "log:bar", "log:bar"],
+  "{{location[server]}}/import-maps/resources/log.js?pipe=sub&name=cross-origin-foo":
+    [Result.URL, Result.URL, "log:cross-origin-bar", "log:cross-origin-bar"],
+
+  // HTTP(S) to data:
+  "{{location[server]}}/import-maps/resources/log.js?pipe=sub&name=to-data":
+    [Result.URL, Result.URL, "dataURL", "dataURL"],
+
+  // HTTP(S) to built-in.
+  "{{location[server]}}/import-maps/resources/log.js?pipe=sub&name=std-blank":
+    [Result.URL, Result.URL, Result.BUILTIN, Result.BUILTIN],
+  "{{location[server]}}/import-maps/resources/log.js?pipe=sub&name=blank":
+    [Result.URL, Result.URL, Result.BUILTIN, Result.BUILTIN],
+  "{{location[server]}}/import-maps/resources/log.js?pipe=sub&name=std-none":
+    [Result.URL, Result.URL, Result.PARSE_ERROR, Result.PARSE_ERROR],
+  "{{location[server]}}/import-maps/resources/log.js?pipe=sub&name=none":
+    [Result.URL, Result.URL, Result.PARSE_ERROR, Result.PARSE_ERROR],
+
+  // HTTP(S) to bare mapping is disabled.
+  "{{location[server]}}/import-maps/resources/log.js?pipe=sub&name=to-bare":
+    [Result.URL, Result.URL, Result.PARSE_ERROR, Result.PARSE_ERROR],
+};
+
+doTests(importMap, null, tests);
+</script>
+<body>
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/import-maps/module-map-key.tentative.html
@@ -0,0 +1,25 @@
+<!DOCTYPE html>
+<html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script type="importmap">
+{
+  "imports": {
+    "./resources/log.js?pipe=sub&name=A": "./resources/log.js?pipe=sub&name=B"
+  }
+}
+</script>
+<script>
+const log = [];
+
+// This test reflects the Chromium's current implementation.
+// If the import map resolution is moved into the fetch spec, the module map's
+// key will become the URL/specifier BEFORE import map resolution.
+// https://crbug.com/928435
+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>
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/import-maps/resolving.tentative.html
@@ -0,0 +1,81 @@
+<!DOCTYPE html>
+<html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+setup({allow_uncaught_exception : true});
+
+// Hacky glue code to run Jest-based tests as WPT tests.
+// Only supports resolving.js.
+function require(name) {
+  return {
+    'URL': URL,
+    'parseFromString': parseFromString,
+    'resolve': resolve
+  };
+}
+
+function expect(v) {
+  return {
+    toMatchURL: expected => assert_equals(v, expected),
+    toThrow: expected => assert_throws(expected(), v)
+  };
+}
+
+let current_message = '';
+function describe(message, f) {
+  const old = current_message;
+  if (current_message !== '') {
+    current_message += ' / ';
+  }
+  current_message += message;
+  f();
+  current_message = old;
+}
+function it(message, f) {
+  const old = current_message;
+  if (current_message !== '') {
+    current_message += ' / ';
+  }
+  current_message += message;
+  test(t => t.step_func(f)(), current_message);
+  current_message = old;
+}
+
+// Creates a new Document (via <iframe>) and add an inline import map.
+// Currently document.write() is used to make everything synchronous, which
+// is just needed for running the existing Jest-based tests easily.
+function parseFromString(mapString, mapBaseURL) {
+  const iframe = document.createElement('iframe');
+  document.body.appendChild(iframe);
+  iframe.contentDocument.write(`
+    <base href="${mapBaseURL}">
+    <script>
+    let isError = false;
+    function onError() {
+      isError = true;
+    }
+    </sc` + `ript>
+    <script type="importmap" onerror="onError()">
+    ${mapString}
+    </sc` + `ript>
+  `);
+  iframe.contentDocument.close();
+  return iframe;
+}
+
+// URL resolution is tested using Chromium's `internals`.
+// TODO(hiroshige): Remove the Chromium-specific dependency.
+function resolve(specifier, map, baseURL) {
+  return internals.resolveModuleSpecifier(specifier,
+                                          baseURL,
+                                          map.contentDocument);
+}
+
+</script>
+
+<!--
+resolving.js is
+https://github.com/WICG/import-maps/blob/master/reference-implementation/__tests__/resolving.js
+-->
+<script type="module" src="resources/resolving.js"></script>
new file mode 100644
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/import-maps/resources/log.js
@@ -0,0 +1,1 @@
+log.push("log:{{GET[name]}}");
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/import-maps/resources/log.js.headers
@@ -0,0 +1,1 @@
+Access-Control-Allow-Origin: *
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/import-maps/resources/resolving.js
@@ -0,0 +1,206 @@
+'use strict';
+const { URL } = require('url');
+const { parseFromString } = require('../lib/parser.js');
+const { resolve } = require('../lib/resolver.js');
+
+const mapBaseURL = new URL('https://example.com/app/index.html');
+const scriptURL = new URL('https://example.com/js/app.mjs');
+
+function makeResolveUnderTest(mapString) {
+  const map = parseFromString(mapString, mapBaseURL);
+  return specifier => resolve(specifier, map, scriptURL);
+}
+
+describe('Unmapped', () => {
+  const resolveUnderTest = makeResolveUnderTest(`{}`);
+
+  it('should resolve ./ specifiers as URLs', () => {
+    expect(resolveUnderTest('./foo')).toMatchURL('https://example.com/js/foo');
+    expect(resolveUnderTest('./foo/bar')).toMatchURL('https://example.com/js/foo/bar');
+    expect(resolveUnderTest('./foo/../bar')).toMatchURL('https://example.com/js/bar');
+    expect(resolveUnderTest('./foo/../../bar')).toMatchURL('https://example.com/bar');
+  });
+
+  it('should resolve ../ specifiers as URLs', () => {
+    expect(resolveUnderTest('../foo')).toMatchURL('https://example.com/foo');
+    expect(resolveUnderTest('../foo/bar')).toMatchURL('https://example.com/foo/bar');
+    expect(resolveUnderTest('../../../foo/bar')).toMatchURL('https://example.com/foo/bar');
+  });
+
+  it('should resolve / specifiers as URLs', () => {
+    expect(resolveUnderTest('/foo')).toMatchURL('https://example.com/foo');
+    expect(resolveUnderTest('/foo/bar')).toMatchURL('https://example.com/foo/bar');
+    expect(resolveUnderTest('/../../foo/bar')).toMatchURL('https://example.com/foo/bar');
+    expect(resolveUnderTest('/../foo/../bar')).toMatchURL('https://example.com/bar');
+  });
+
+  it('should parse absolute fetch-scheme URLs', () => {
+    expect(resolveUnderTest('about:good')).toMatchURL('about:good');
+    expect(resolveUnderTest('https://example.net')).toMatchURL('https://example.net/');
+    expect(resolveUnderTest('https://ex%41mple.com/')).toMatchURL('https://example.com/');
+    expect(resolveUnderTest('https:example.org')).toMatchURL('https://example.org/');
+    expect(resolveUnderTest('https://///example.com///')).toMatchURL('https://example.com///');
+  });
+
+  it('should fail for absolute non-fetch-scheme URLs', () => {
+    expect(() => resolveUnderTest('mailto:bad')).toThrow(TypeError);
+    expect(() => resolveUnderTest('import:bad')).toThrow(TypeError);
+    expect(() => resolveUnderTest('javascript:bad')).toThrow(TypeError);
+    expect(() => resolveUnderTest('wss:bad')).toThrow(TypeError);
+  });
+
+  it('should fail for strings not parseable as absolute URLs and not starting with ./ ../ or /', () => {
+    expect(() => resolveUnderTest('foo')).toThrow(TypeError);
+    expect(() => resolveUnderTest('\\foo')).toThrow(TypeError);
+    expect(() => resolveUnderTest(':foo')).toThrow(TypeError);
+    expect(() => resolveUnderTest('@foo')).toThrow(TypeError);
+    expect(() => resolveUnderTest('%2E/foo')).toThrow(TypeError);
+    expect(() => resolveUnderTest('%2E%2E/foo')).toThrow(TypeError);
+    expect(() => resolveUnderTest('.%2Ffoo')).toThrow(TypeError);
+    expect(() => resolveUnderTest('https://ex ample.org/')).toThrow(TypeError);
+    expect(() => resolveUnderTest('https://example.com:demo')).toThrow(TypeError);
+    expect(() => resolveUnderTest('http://[www.example.com]/')).toThrow(TypeError);
+  });
+});
+
+describe('Mapped using the "imports" key only (no scopes)', () => {
+  it('should fail when the mapping is to an empty array', () => {
+    const resolveUnderTest = makeResolveUnderTest(`{
+      "imports": {
+        "moment": null,
+        "lodash": []
+      }
+    }`);
+
+    expect(() => resolveUnderTest('moment')).toThrow(TypeError);
+    expect(() => resolveUnderTest('lodash')).toThrow(TypeError);
+  });
+
+  describe('Package-like scenarios', () => {
+    const resolveUnderTest = makeResolveUnderTest(`{
+      "imports": {
+        "moment": "/node_modules/moment/src/moment.js",
+        "moment/": "/node_modules/moment/src/",
+        "lodash-dot": "./node_modules/lodash-es/lodash.js",
+        "lodash-dot/": "./node_modules/lodash-es/",
+        "lodash-dotdot": "../node_modules/lodash-es/lodash.js",
+        "lodash-dotdot/": "../node_modules/lodash-es/"
+      }
+    }`);
+
+    it('should work for package main modules', () => {
+      expect(resolveUnderTest('moment')).toMatchURL('https://example.com/node_modules/moment/src/moment.js');
+      expect(resolveUnderTest('lodash-dot')).toMatchURL('https://example.com/app/node_modules/lodash-es/lodash.js');
+      expect(resolveUnderTest('lodash-dotdot')).toMatchURL('https://example.com/node_modules/lodash-es/lodash.js');
+    });
+
+    it('should work for package submodules', () => {
+      expect(resolveUnderTest('moment/foo')).toMatchURL('https://example.com/node_modules/moment/src/foo');
+      expect(resolveUnderTest('lodash-dot/foo')).toMatchURL('https://example.com/app/node_modules/lodash-es/foo');
+      expect(resolveUnderTest('lodash-dotdot/foo')).toMatchURL('https://example.com/node_modules/lodash-es/foo');
+    });
+
+    it('should work for package names that end in a slash by just passing through', () => {
+      // TODO: is this the right behavior, or should we throw?
+      expect(resolveUnderTest('moment/')).toMatchURL('https://example.com/node_modules/moment/src/');
+    });
+
+    it('should still fail for package modules that are not declared', () => {
+      expect(() => resolveUnderTest('underscore/')).toThrow(TypeError);
+      expect(() => resolveUnderTest('underscore/foo')).toThrow(TypeError);
+    });
+  });
+
+  describe('Tricky specifiers', () => {
+    const resolveUnderTest = makeResolveUnderTest(`{
+      "imports": {
+        "package/withslash": "/node_modules/package-with-slash/index.mjs",
+        "not-a-package": "/lib/not-a-package.mjs",
+        ".": "/lib/dot.mjs",
+        "..": "/lib/dotdot.mjs",
+        "..\\\\": "/lib/dotdotbackslash.mjs",
+        "%2E": "/lib/percent2e.mjs",
+        "%2F": "/lib/percent2f.mjs"
+      }
+    }`);
+
+    it('should work for explicitly-mapped specifiers that happen to have a slash', () => {
+      expect(resolveUnderTest('package/withslash')).toMatchURL('https://example.com/node_modules/package-with-slash/index.mjs');
+    });
+
+    it('should work when the specifier has punctuation', () => {
+      expect(resolveUnderTest('.')).toMatchURL('https://example.com/lib/dot.mjs');
+      expect(resolveUnderTest('..')).toMatchURL('https://example.com/lib/dotdot.mjs');
+      expect(resolveUnderTest('..\\')).toMatchURL('https://example.com/lib/dotdotbackslash.mjs');
+      expect(resolveUnderTest('%2E')).toMatchURL('https://example.com/lib/percent2e.mjs');
+      expect(resolveUnderTest('%2F')).toMatchURL('https://example.com/lib/percent2f.mjs');
+    });
+
+    it('should fail for attempting to get a submodule of something not declared with a trailing slash', () => {
+      expect(() => resolveUnderTest('not-a-package/foo')).toThrow(TypeError);
+    });
+  });
+
+  describe('URL-like specifiers', () => {
+    const resolveUnderTest = makeResolveUnderTest(`{
+      "imports": {
+        "/node_modules/als-polyfill/index.mjs": "@std/kv-storage",
+
+        "/lib/foo.mjs": "./more/bar.mjs",
+        "./dotrelative/foo.mjs": "/lib/dot.mjs",
+        "../dotdotrelative/foo.mjs": "/lib/dotdot.mjs",
+
+        "/lib/no.mjs": null,
+        "./dotrelative/no.mjs": [],
+
+        "/": "/lib/slash-only.mjs",
+        "./": "/lib/dotslash-only.mjs",
+
+        "/test": "/lib/test1.mjs",
+        "../test": "/lib/test2.mjs"
+      }
+    }`);
+
+    it('should remap to built-in modules', () => {
+      expect(resolveUnderTest('/node_modules/als-polyfill/index.mjs')).toMatchURL('import:@std/kv-storage');
+      expect(resolveUnderTest('https://example.com/node_modules/als-polyfill/index.mjs')).toMatchURL('import:@std/kv-storage');
+      expect(resolveUnderTest('https://///example.com/node_modules/als-polyfill/index.mjs')).toMatchURL('import:@std/kv-storage');
+    });
+
+    it('should remap to other URLs', () => {
+      expect(resolveUnderTest('https://example.com/lib/foo.mjs')).toMatchURL('https://example.com/app/more/bar.mjs');
+      expect(resolveUnderTest('https://///example.com/lib/foo.mjs')).toMatchURL('https://example.com/app/more/bar.mjs');
+      expect(resolveUnderTest('/lib/foo.mjs')).toMatchURL('https://example.com/app/more/bar.mjs');
+
+      expect(resolveUnderTest('https://example.com/app/dotrelative/foo.mjs')).toMatchURL('https://example.com/lib/dot.mjs');
+      expect(resolveUnderTest('../app/dotrelative/foo.mjs')).toMatchURL('https://example.com/lib/dot.mjs');
+
+      expect(resolveUnderTest('https://example.com/dotdotrelative/foo.mjs')).toMatchURL('https://example.com/lib/dotdot.mjs');
+      expect(resolveUnderTest('../dotdotrelative/foo.mjs')).toMatchURL('https://example.com/lib/dotdot.mjs');
+    });
+
+    it('should fail for URLs that remap to empty arrays', () => {
+      expect(() => resolveUnderTest('https://example.com/lib/no.mjs')).toThrow(TypeError);
+      expect(() => resolveUnderTest('/lib/no.mjs')).toThrow(TypeError);
+      expect(() => resolveUnderTest('../lib/no.mjs')).toThrow(TypeError);
+
+      expect(() => resolveUnderTest('https://example.com/app/dotrelative/no.mjs')).toThrow(TypeError);
+      expect(() => resolveUnderTest('/app/dotrelative/no.mjs')).toThrow(TypeError);
+      expect(() => resolveUnderTest('../app/dotrelative/no.mjs')).toThrow(TypeError);
+    });
+
+    it('should remap URLs that are just composed from / and .', () => {
+      expect(resolveUnderTest('https://example.com/')).toMatchURL('https://example.com/lib/slash-only.mjs');
+      expect(resolveUnderTest('/')).toMatchURL('https://example.com/lib/slash-only.mjs');
+      expect(resolveUnderTest('../')).toMatchURL('https://example.com/lib/slash-only.mjs');
+
+      expect(resolveUnderTest('https://example.com/app/')).toMatchURL('https://example.com/lib/dotslash-only.mjs');
+      expect(resolveUnderTest('/app/')).toMatchURL('https://example.com/lib/dotslash-only.mjs');
+      expect(resolveUnderTest('../app/')).toMatchURL('https://example.com/lib/dotslash-only.mjs');
+    });
+
+    it('should use the last entry\'s address when URL-like specifiers parse to the same absolute URL', () => {
+      expect(resolveUnderTest('/test')).toMatchURL('https://example.com/lib/test2.mjs');
+    });
+  });
+});
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/import-maps/resources/test-helper.js
@@ -0,0 +1,205 @@
+let log = [];
+
+function expect_log(test, expected_log) {
+  test.step_func_done(() => {
+    const actual_log = log;
+    log = [];
+    assert_array_equals(actual_log, expected_log, 'fallback log');
+  })();
+}
+
+// Results of resolving a specifier using import maps.
+const Result = {
+  // A built-in module (std:blank) is loaded.
+  BUILTIN: "builtin",
+
+  // A failure considered as a fetch error in a module script tree.
+  // <script>'s error event is fired.
+  FETCH_ERROR: "fetch_error",
+
+  // A failure considered as a parse error in a module script tree.
+  // Window's error event is fired.
+  PARSE_ERROR: "parse_error",
+
+  // The specifier is considered as a relative or absolute URL.
+  // Specifier                 Expected log
+  // ------------------------- ----------------------
+  // ...?name=foo              log:foo
+  // data:...log('foo')        foo
+  // Others, e.g. @std/blank   relative:@std/blank
+  // ------------------------- ----------------------
+  // (The last case assumes a file `@std/blank` that logs `relative:@std/blank`
+  // exists)
+  URL: "URL",
+};
+
+const Handler = {
+  // Handlers for <script> element cases.
+  // Note that on a parse error both WindowErrorEvent and ScriptLoadEvent are
+  // called.
+  ScriptLoadEvent: "<script> element's load event handler",
+  ScriptErrorEvent: "<script> element's error event handler",
+  WindowErrorEvent: "window's error event handler",
+
+  // Handlers for dynamic imports.
+  DynamicImportResolve: "dynamic import resolve",
+  DynamicImportReject: "dynamic import reject",
+};
+
+// Returns a map with Handler.* as the keys.
+function getHandlers(t, specifier, expected) {
+  let handlers = {};
+  handlers[Handler.ScriptLoadEvent] = t.unreached_func("Shouldn't load");
+  handlers[Handler.ScriptErrorEvent] =
+      t.unreached_func("script's error event shouldn't be fired");
+  handlers[Handler.WindowErrorEvent] =
+      t.unreached_func("window's error event shouldn't be fired");
+  handlers[Handler.DynamicImportResolve] =
+    t.unreached_func("dynamic import promise shouldn't be resolved");
+  handlers[Handler.DynamicImportReject] =
+    t.unreached_func("dynamic import promise shouldn't be rejected");
+
+  if (expected === Result.FETCH_ERROR) {
+    handlers[Handler.ScriptErrorEvent] = () => expect_log(t, []);
+    handlers[Handler.DynamicImportReject] = () => expect_log(t, []);
+  } else if (expected === Result.PARSE_ERROR) {
+    let error_occurred = false;
+    handlers[Handler.WindowErrorEvent] = () => { error_occurred = true; };
+    handlers[Handler.ScriptLoadEvent] = t.step_func(() => {
+      // Even if a parse error occurs, load event is fired (after
+      // window.onerror is called), so trigger the load handler only if
+      // there was no previous window.onerror call.
+      assert_true(error_occurred, "window.onerror should be fired");
+      expect_log(t, []);
+    });
+    handlers[Handler.DynamicImportReject] = t.step_func(() => {
+      assert_false(error_occurred,
+        "window.onerror shouldn't be fired for dynamic imports");
+      expect_log(t, []);
+    });
+  } else {
+    let expected_log;
+    if (expected === Result.BUILTIN) {
+      expected_log = [];
+    } else if (expected === Result.URL) {
+      const match_data_url = specifier.match(/data:.*log\.push\('(.*)'\)/);
+      const match_log_js = specifier.match(/name=(.*)/);
+      if (match_data_url) {
+        expected_log = [match_data_url[1]];
+      } else if (match_log_js) {
+        expected_log = ["log:" + match_log_js[1]];
+      } else {
+        expected_log = ["relative:" + specifier];
+      }
+    } else {
+      expected_log = [expected];
+    }
+    handlers[Handler.ScriptLoadEvent] = () => expect_log(t, expected_log);
+    handlers[Handler.DynamicImportResolve] = () => expect_log(t, expected_log);
+  }
+  return handlers;
+}
+
+// Creates an <iframe> and run a test inside the <iframe>
+// to separate the module maps and import maps in each test.
+function testInIframe(importMapString, importMapBaseURL, testScript) {
+  const iframe = document.createElement('iframe');
+  document.body.appendChild(iframe);
+  if (!importMapBaseURL) {
+    importMapBaseURL = document.baseURI;
+  }
+  let content = `
+    <script src="/resources/testharness.js"></script>
+    <script src="/import-maps/resources/test-helper.js"></script>
+    <base href="${importMapBaseURL}">
+  `;
+  if (importMapString) {
+    content += `
+      <script type="importmap">
+      ${importMapString}
+      </sc` + `ript>
+    `;
+  }
+  content += `
+    <body>
+    <script>
+    setup({ allow_uncaught_exception: true });
+    ${testScript}
+    </sc` + `ript>
+  `;
+  iframe.contentDocument.write(content);
+  iframe.contentDocument.close();
+  fetch_tests_from_window(iframe.contentWindow);
+}
+
+function testScriptElement(importMapString, importMapBaseURL, specifier, expected, type) {
+  testInIframe(importMapString, importMapBaseURL, `
+    const t = async_test("${specifier}: <script src type=${type}>");
+    const handlers = getHandlers(t, "${specifier}", "${expected}");
+    const script = document.createElement("script");
+    script.setAttribute("type", "${type}");
+    script.setAttribute("src", "${specifier}");
+    script.addEventListener("load", handlers[Handler.ScriptLoadEvent]);
+    script.addEventListener("error", handlers[Handler.ScriptErrorEvent]);
+    window.addEventListener("error", handlers[Handler.WindowErrorEvent]);
+    document.body.appendChild(script);
+  `);
+}
+
+function testStaticImport(importMapString, importMapBaseURL, specifier, expected) {
+  testInIframe(importMapString, importMapBaseURL, `
+    const t = async_test("${specifier}: static import");
+    const handlers = getHandlers(t, "${specifier}", "${expected}");
+    const script = document.createElement("script");
+    script.setAttribute("type", "module");
+    script.setAttribute("src",
+        "/import-maps/static-import.js?pipe=sub(none)&url=" +
+        encodeURIComponent("${specifier}"));
+    script.addEventListener("load", handlers[Handler.ScriptLoadEvent]);
+    script.addEventListener("error", handlers[Handler.ScriptErrorEvent]);
+    window.addEventListener("error", handlers[Handler.WindowErrorEvent]);
+    document.body.appendChild(script);
+  `);
+}
+
+function testDynamicImport(importMapString, importMapBaseURL, specifier, expected, type) {
+  testInIframe(importMapString, importMapBaseURL, `
+    const t = async_test("${specifier}: dynamic import (from ${type})");
+    const handlers = getHandlers(t, "${specifier}", "${expected}");
+    const script = document.createElement("script");
+    script.setAttribute("type", "${type}");
+    script.innerText =
+        "import(\\"${specifier}\\")" +
+        ".then(handlers[Handler.DynamicImportResolve], " +
+        "handlers[Handler.DynamicImportReject]);";
+    script.addEventListener("error",
+        t.unreached_func("top-level inline script shouldn't error"));
+    document.body.appendChild(script);
+  `);
+}
+
+function doTests(importMapString, importMapBaseURL, tests) {
+  window.addEventListener("load", () => {
+    for (const specifier in tests) {
+      // <script src> (module scripts)
+      testScriptElement(importMapString, importMapBaseURL, specifier,
+        tests[specifier][0], "module");
+
+      // <script src> (classic scripts)
+      testScriptElement(importMapString, importMapBaseURL, specifier,
+        tests[specifier][1], "text/javascript");
+
+      // static imports.
+      testStaticImport(importMapString, importMapBaseURL, specifier,
+        tests[specifier][2]);
+
+      // dynamic imports from a module script.
+      testDynamicImport(importMapString, importMapBaseURL, specifier,
+        tests[specifier][3], "module");
+
+      // dynamic imports from a classic script.
+      testDynamicImport(importMapString, importMapBaseURL, specifier,
+        tests[specifier][3], "text/javascript");
+    }
+  });
+}
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/import-maps/static-import.js
@@ -0,0 +1,1 @@
+import "{{GET[url]}}";