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>