summaryrefslogtreecommitdiffstats
path: root/testing/web-platform/tests/workers/modules
diff options
context:
space:
mode:
Diffstat (limited to 'testing/web-platform/tests/workers/modules')
-rw-r--r--testing/web-platform/tests/workers/modules/dedicated-worker-import-blob-url.any.js26
-rw-r--r--testing/web-platform/tests/workers/modules/dedicated-worker-import-csp.html120
-rw-r--r--testing/web-platform/tests/workers/modules/dedicated-worker-import-data-url-cross-origin.html43
-rw-r--r--testing/web-platform/tests/workers/modules/dedicated-worker-import-data-url.any.js22
-rw-r--r--testing/web-platform/tests/workers/modules/dedicated-worker-import-failure.html54
-rw-r--r--testing/web-platform/tests/workers/modules/dedicated-worker-import-meta.html59
-rw-r--r--testing/web-platform/tests/workers/modules/dedicated-worker-import-referrer.html244
-rw-r--r--testing/web-platform/tests/workers/modules/dedicated-worker-import.any.js22
-rw-r--r--testing/web-platform/tests/workers/modules/dedicated-worker-options-credentials.html304
-rw-r--r--testing/web-platform/tests/workers/modules/dedicated-worker-options-credentials.html.headers2
-rw-r--r--testing/web-platform/tests/workers/modules/dedicated-worker-options-type.html46
-rw-r--r--testing/web-platform/tests/workers/modules/dedicated-worker-parse-error-failure.html51
-rw-r--r--testing/web-platform/tests/workers/modules/resources/dynamic-import-and-then-static-import-worker.js32
-rw-r--r--testing/web-platform/tests/workers/modules/resources/dynamic-import-data-url-block-cross-origin.js24
-rw-r--r--testing/web-platform/tests/workers/modules/resources/dynamic-import-given-url-worker.js21
-rw-r--r--testing/web-platform/tests/workers/modules/resources/dynamic-import-remote-origin-credentials-checker-worker.sub.js15
-rw-r--r--testing/web-platform/tests/workers/modules/resources/dynamic-import-remote-origin-referrer-checker-worker.sub.js16
-rw-r--r--testing/web-platform/tests/workers/modules/resources/dynamic-import-remote-origin-script-worker.sub.js17
-rw-r--r--testing/web-platform/tests/workers/modules/resources/dynamic-import-same-origin-credentials-checker-worker.js13
-rw-r--r--testing/web-platform/tests/workers/modules/resources/dynamic-import-same-origin-referrer-checker-worker.js15
-rw-r--r--testing/web-platform/tests/workers/modules/resources/dynamic-import-script-block-cross-origin.js24
-rw-r--r--testing/web-platform/tests/workers/modules/resources/dynamic-import-worker.js32
-rw-r--r--testing/web-platform/tests/workers/modules/resources/empty-worker.js1
-rw-r--r--testing/web-platform/tests/workers/modules/resources/eval-dynamic-import-worker.js29
-rw-r--r--testing/web-platform/tests/workers/modules/resources/export-block-cross-origin.js1
-rw-r--r--testing/web-platform/tests/workers/modules/resources/export-credentials.py15
-rw-r--r--testing/web-platform/tests/workers/modules/resources/export-on-dynamic-import-script.js8
-rw-r--r--testing/web-platform/tests/workers/modules/resources/export-on-dynamic-import-script.js.headers1
-rw-r--r--testing/web-platform/tests/workers/modules/resources/export-on-load-script.js1
-rw-r--r--testing/web-platform/tests/workers/modules/resources/export-on-load-script.js.headers1
-rw-r--r--testing/web-platform/tests/workers/modules/resources/export-on-load-script.py15
-rw-r--r--testing/web-platform/tests/workers/modules/resources/export-on-static-import-script.js3
-rw-r--r--testing/web-platform/tests/workers/modules/resources/export-on-static-import-script.js.headers1
-rw-r--r--testing/web-platform/tests/workers/modules/resources/export-referrer-checker.py9
-rw-r--r--testing/web-platform/tests/workers/modules/resources/import-meta-url-export.js1
-rw-r--r--testing/web-platform/tests/workers/modules/resources/import-meta-url-worker.js10
-rw-r--r--testing/web-platform/tests/workers/modules/resources/import-scripts-worker.js33
-rw-r--r--testing/web-platform/tests/workers/modules/resources/import-test-cases.js59
-rw-r--r--testing/web-platform/tests/workers/modules/resources/nested-dynamic-import-worker.js34
-rw-r--r--testing/web-platform/tests/workers/modules/resources/nested-static-import-worker.js21
-rw-r--r--testing/web-platform/tests/workers/modules/resources/new-shared-worker-window.html19
-rw-r--r--testing/web-platform/tests/workers/modules/resources/new-worker-window.html19
-rw-r--r--testing/web-platform/tests/workers/modules/resources/post-message-on-load-worker.js10
-rw-r--r--testing/web-platform/tests/workers/modules/resources/postmessage-credentials.py22
-rw-r--r--testing/web-platform/tests/workers/modules/resources/postmessage-referrer-checker.py16
-rw-r--r--testing/web-platform/tests/workers/modules/resources/redirect.py8
-rw-r--r--testing/web-platform/tests/workers/modules/resources/static-import-and-then-dynamic-import-worker.js34
-rw-r--r--testing/web-platform/tests/workers/modules/resources/static-import-data-url-block-cross-origin.js14
-rw-r--r--testing/web-platform/tests/workers/modules/resources/static-import-non-existent-script-worker.js1
-rw-r--r--testing/web-platform/tests/workers/modules/resources/static-import-redirect-worker.js21
-rw-r--r--testing/web-platform/tests/workers/modules/resources/static-import-remote-origin-credentials-checker-worker.sub.js12
-rw-r--r--testing/web-platform/tests/workers/modules/resources/static-import-remote-origin-referrer-checker-worker.sub.js12
-rw-r--r--testing/web-platform/tests/workers/modules/resources/static-import-remote-origin-script-worker.sub.js20
-rw-r--r--testing/web-platform/tests/workers/modules/resources/static-import-same-origin-credentials-checker-worker.js11
-rw-r--r--testing/web-platform/tests/workers/modules/resources/static-import-same-origin-referrer-checker-worker.js11
-rw-r--r--testing/web-platform/tests/workers/modules/resources/static-import-script-block-cross-origin.js14
-rw-r--r--testing/web-platform/tests/workers/modules/resources/static-import-syntax-error.js1
-rw-r--r--testing/web-platform/tests/workers/modules/resources/static-import-worker.js21
-rw-r--r--testing/web-platform/tests/workers/modules/resources/syntax-error.js1
-rw-r--r--testing/web-platform/tests/workers/modules/resources/throw.js1
-rw-r--r--testing/web-platform/tests/workers/modules/shared-worker-import-blob-url.window.js26
-rw-r--r--testing/web-platform/tests/workers/modules/shared-worker-import-csp.html128
-rw-r--r--testing/web-platform/tests/workers/modules/shared-worker-import-data-url-cross-origin.html43
-rw-r--r--testing/web-platform/tests/workers/modules/shared-worker-import-data-url.window.js23
-rw-r--r--testing/web-platform/tests/workers/modules/shared-worker-import-failure.html67
-rw-r--r--testing/web-platform/tests/workers/modules/shared-worker-import-meta.html49
-rw-r--r--testing/web-platform/tests/workers/modules/shared-worker-import-referrer.html259
-rw-r--r--testing/web-platform/tests/workers/modules/shared-worker-import.window.js22
-rw-r--r--testing/web-platform/tests/workers/modules/shared-worker-options-credentials.html309
-rw-r--r--testing/web-platform/tests/workers/modules/shared-worker-options-credentials.html.headers2
-rw-r--r--testing/web-platform/tests/workers/modules/shared-worker-options-type.html48
-rw-r--r--testing/web-platform/tests/workers/modules/shared-worker-parse-error-failure.html49
72 files changed, 2738 insertions, 0 deletions
diff --git a/testing/web-platform/tests/workers/modules/dedicated-worker-import-blob-url.any.js b/testing/web-platform/tests/workers/modules/dedicated-worker-import-blob-url.any.js
new file mode 100644
index 0000000000..e5d79add73
--- /dev/null
+++ b/testing/web-platform/tests/workers/modules/dedicated-worker-import-blob-url.any.js
@@ -0,0 +1,26 @@
+// META: script=/workers/modules/resources/import-test-cases.js
+
+// Imports |testCase.scriptURL| on a dedicated worker loaded from a blob URL,
+// and waits until the list of imported modules is sent from the worker. Passes
+// if the list is equal to |testCase.expectation|.
+function import_blob_url_test(testCase) {
+ promise_test(async () => {
+ const importURL = new URL(testCase.scriptURL, location.href);
+ const blob = new Blob([`import "${importURL}";`],
+ { type: 'text/javascript' });
+ const blobURL = URL.createObjectURL(blob);
+ const worker = new Worker(blobURL, { type: 'module'});
+ worker.postMessage('Send message for tests from main script.');
+ const msgEvent = await new Promise((resolve, reject) => {
+ worker.onmessage = resolve;
+ worker.onerror = error => {
+ const msg = error instanceof ErrorEvent ? error.message
+ : 'unknown error';
+ reject(msg);
+ };
+ });
+ assert_array_equals(msgEvent.data, testCase.expectation);
+ }, testCase.description);
+}
+
+testCases.forEach(import_blob_url_test);
diff --git a/testing/web-platform/tests/workers/modules/dedicated-worker-import-csp.html b/testing/web-platform/tests/workers/modules/dedicated-worker-import-csp.html
new file mode 100644
index 0000000000..20b03a7276
--- /dev/null
+++ b/testing/web-platform/tests/workers/modules/dedicated-worker-import-csp.html
@@ -0,0 +1,120 @@
+<!DOCTYPE html>
+<title>DedicatedWorker: CSP for ES Modules</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+
+async function openWindow(url) {
+ const win = window.open(url, '_blank');
+ add_result_callback(() => win.close());
+ const msg_event = await new Promise(resolve => window.onmessage = resolve);
+ assert_equals(msg_event.data, 'LOADED');
+ return win;
+}
+
+function import_csp_test(
+ cspHeader, importType, expectedImportedModules, description) {
+ // Append CSP header to windowURL for static import tests since static import
+ // scripts should obey Window's CSP.
+ const windowURL = `resources/new-worker-window.html` +
+ `${importType === 'static'
+ ? '?pipe=header(Content-Security-Policy, ' + cspHeader + ')'
+ : ''}`;
+ // Append CSP header to scriptURL for dynamic import tests since dynamic
+ // import scripts should obey Worker script's response's CSP.
+ const scriptURL = `${importType}-import-remote-origin-script-worker.sub.js` +
+ `${importType === 'dynamic'
+ ? '?pipe=header(Content-Security-Policy, ' + cspHeader + ')'
+ : ''}`;
+ promise_test(async () => {
+ const win = await openWindow(windowURL);
+ // Ask the window to start a dedicated worker.
+ win.postMessage(scriptURL, '*');
+ const msg_event = await new Promise(resolve => window.onmessage = resolve);
+ assert_array_equals(msg_event.data, expectedImportedModules);
+ }, description);
+}
+
+// Tests for static import.
+//
+// Static import should obey the worker-src directive and the script-src
+// directive. If the both directives are specified, the worker-src directive
+// should be prioritized.
+//
+// Step 1: "If the result of executing 6.6.1.11 Get the effective directive for
+// request on request is "worker-src", and policy contains a directive whose
+// name is "worker-src", return "Allowed"."
+// "Note: If worker-src is present, we’ll defer to it when handling worker
+// requests."
+// https://w3c.github.io/webappsec-csp/#script-src-pre-request
+
+import_csp_test(
+ "worker-src 'self' 'unsafe-inline'",
+ "static",
+ ['ERROR'],
+ "worker-src 'self' directive should disallow cross origin static import.");
+
+import_csp_test(
+ "worker-src * 'unsafe-inline'",
+ "static",
+ ["export-on-load-script.js"],
+ "worker-src * directive should allow cross origin static import.")
+
+import_csp_test(
+ "script-src 'self' 'unsafe-inline'",
+ "static",
+ ['ERROR'],
+ "script-src 'self' directive should disallow cross origin static import.");
+
+import_csp_test(
+ "script-src * 'unsafe-inline'",
+ "static",
+ ["export-on-load-script.js"],
+ "script-src * directive should allow cross origin static import.")
+
+import_csp_test(
+ "worker-src *; script-src 'self' 'unsafe-inline'",
+ "static",
+ ["export-on-load-script.js"],
+ "worker-src * directive should override script-src 'self' directive and " +
+ "allow cross origin static import.");
+
+import_csp_test(
+ "worker-src 'self'; script-src * 'unsafe-inline'",
+ "static",
+ ['ERROR'],
+ "worker-src 'self' directive should override script-src * directive and " +
+ "disallow cross origin static import.");
+
+// Tests for dynamic import.
+//
+// Dynamic import should obey the script-src directive instead of the worker-src
+// directive according to the specs:
+//
+// Dynamic import has the "script" destination.
+// Step 2.4: "Fetch a module script graph given url, ..., "script", ..."
+// https://html.spec.whatwg.org/multipage/webappapis.html#hostimportmoduledynamically(referencingscriptormodule,-specifier,-promisecapability)
+//
+// The "script" destination should obey the script-src CSP directive.
+// Step 2: "If request's destination is script-like:"
+// https://w3c.github.io/webappsec-csp/#script-src-pre-request
+
+import_csp_test(
+ "script-src 'self' 'unsafe-inline'",
+ "dynamic",
+ ['ERROR'],
+ "script-src 'self' directive should disallow cross origin dynamic import.");
+
+import_csp_test(
+ "script-src * 'unsafe-inline'",
+ "dynamic",
+ ["export-on-load-script.js"],
+ "script-src * directive should allow cross origin dynamic import.")
+
+import_csp_test(
+ "worker-src 'self' 'unsafe-inline'",
+ "dynamic",
+ ["export-on-load-script.js"],
+ "worker-src 'self' directive should not take effect on dynamic import.");
+
+</script>
diff --git a/testing/web-platform/tests/workers/modules/dedicated-worker-import-data-url-cross-origin.html b/testing/web-platform/tests/workers/modules/dedicated-worker-import-data-url-cross-origin.html
new file mode 100644
index 0000000000..37390947b6
--- /dev/null
+++ b/testing/web-platform/tests/workers/modules/dedicated-worker-import-data-url-cross-origin.html
@@ -0,0 +1,43 @@
+<!DOCTYPE html>
+<title>DedicatedWorker: ES modules for data URL workers</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+
+const import_from_data_url_worker_test = (importType, isDataURL, expectation) => {
+ promise_test(async () => {
+ const importURL = new URL(`resources/${importType}-import-` +
+ `${isDataURL ? 'data-url' : 'script'}-block-cross-origin.js`,
+ location.href) + '?pipe=header(Access-Control-Allow-Origin, *)';
+ const dataURL = `data:text/javascript,import "${importURL}";`;
+ const worker = new Worker(dataURL, { type: 'module' });
+ worker.postMessage('Send message for tests from main script.');
+ const msgEvent =
+ await new Promise(resolve => worker.onmessage = resolve);
+ assert_array_equals(msgEvent.data,
+ expectation === 'blocked' ? ['ERROR']
+ : ['export-block-cross-origin.js']);
+ }, `${importType} import ${isDataURL ? 'data url' : 'script'} from data: ` +
+ `URL should be ${expectation}.`);
+}
+
+// Static import should obey the outside settings.
+// SecurityOrigin of the outside settings is decided by Window.
+import_from_data_url_worker_test('static', true, 'allowed');
+import_from_data_url_worker_test('static', false, 'allowed');
+
+
+// Dynamic import should obey the inside settings.
+// SecurityOrigin of the inside settings is a unique opaque origin.
+//
+// Data url script is cross-origin to the inside settings' SecurityOrigin, but
+// dynamic importing it is allowed.
+// https://fetch.spec.whatwg.org/#concept-main-fetch
+// Step 5: request’s current URL’s scheme is "data" [spec text]
+import_from_data_url_worker_test('dynamic', true, 'allowed');
+
+// Non-data url script is cross-origin to the inside settings' SecurityOrigin.
+// It should be blocked.
+import_from_data_url_worker_test('dynamic', false, 'blocked');
+
+</script>
diff --git a/testing/web-platform/tests/workers/modules/dedicated-worker-import-data-url.any.js b/testing/web-platform/tests/workers/modules/dedicated-worker-import-data-url.any.js
new file mode 100644
index 0000000000..0d8510da0c
--- /dev/null
+++ b/testing/web-platform/tests/workers/modules/dedicated-worker-import-data-url.any.js
@@ -0,0 +1,22 @@
+// META: script=/workers/modules/resources/import-test-cases.js
+
+// Imports |testCase.scriptURL| on a dedicated worker loaded from a data URL,
+// and waits until the list of imported modules is sent from the worker. Passes
+// if the list is equal to |testCase.expectation|.
+function import_data_url_test(testCase) {
+ promise_test(async () => {
+ // The Access-Control-Allow-Origin header is necessary because a worker
+ // loaded from a data URL has a null origin and import() on the worker
+ // without the header is blocked.
+ const importURL = new URL(testCase.scriptURL, location.href) +
+ '?pipe=header(Access-Control-Allow-Origin, *)';
+ const dataURL = `data:text/javascript,import "${importURL}";`;
+
+ const worker = new Worker(dataURL, { type: 'module'});
+ worker.postMessage('Send message for tests from main script.');
+ const msgEvent = await new Promise(resolve => worker.onmessage = resolve);
+ assert_array_equals(msgEvent.data, testCase.expectation);
+ }, testCase.description);
+}
+
+testCases.forEach(import_data_url_test);
diff --git a/testing/web-platform/tests/workers/modules/dedicated-worker-import-failure.html b/testing/web-platform/tests/workers/modules/dedicated-worker-import-failure.html
new file mode 100644
index 0000000000..4c705e7325
--- /dev/null
+++ b/testing/web-platform/tests/workers/modules/dedicated-worker-import-failure.html
@@ -0,0 +1,54 @@
+<!DOCTYPE html>
+<title>DedicatedWorker: import failure</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script> setup({allow_uncaught_exception: true}); </script>
+<script>
+
+promise_test(async () => {
+ const scriptURL = 'resources/import-scripts-worker.js';
+ const worker = new Worker(scriptURL, { type: 'module' });
+ const msg_event = await new Promise(resolve => worker.onmessage = resolve);
+ assert_equals(msg_event.data, 'TypeError');
+}, 'importScripts() on module worker should throw an exception.');
+
+promise_test(() => {
+ const scriptURL = 'resources/non-existent-worker.js';
+ const worker = new Worker(scriptURL, { type: 'module' });
+ return new Promise(resolve => worker.onerror = resolve);
+}, 'Worker construction for non-existent script should dispatch an ' +
+ 'ErrorEvent.');
+
+promise_test(() => {
+ const scriptURL = 'resources/static-import-non-existent-script-worker.js';
+ const worker = new Worker(scriptURL, { type: 'module' });
+ return new Promise(resolve => worker.onerror = resolve);
+}, 'Static import for non-existent script should dispatch an ErrorEvent.');
+
+promise_test(async () => {
+ const scriptURL = './non-existent-worker.js';
+ const worker = new Worker('resources/dynamic-import-given-url-worker.js',
+ { type: 'module' });
+ worker.postMessage(scriptURL);
+ const msg_event = await new Promise((resolve, reject) => {
+ worker.onmessage = resolve;
+ worker.onerror = error => {
+ const msg = error instanceof ErrorEvent ? error.message
+ : 'unknown error';
+ reject(msg);
+ };
+ });
+ assert_equals(msg_event.data, 'TypeError');
+}, 'Dynamic import for non-existent script should throw an exception.');
+
+test(() => {
+ const scriptURL = 'http://invalid:123$';
+ assert_throws_dom('SyntaxError', () => new Worker(scriptURL, { type: 'module' }));
+}, 'Worker construction for an invalid URL should throw an exception.');
+
+test(() => {
+ const scriptURL = 'file:///static-import-worker.js';
+ assert_throws_dom('SecurityError', () => new Worker(scriptURL, { type: 'module' }));
+}, 'Worker construction for a file URL should throw an exception.');
+
+</script>
diff --git a/testing/web-platform/tests/workers/modules/dedicated-worker-import-meta.html b/testing/web-platform/tests/workers/modules/dedicated-worker-import-meta.html
new file mode 100644
index 0000000000..97a5da870f
--- /dev/null
+++ b/testing/web-platform/tests/workers/modules/dedicated-worker-import-meta.html
@@ -0,0 +1,59 @@
+<!DOCTYPE html>
+<title>DedicatedWorker: import.meta</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+
+promise_test(() => {
+ const script_url = 'resources/import-meta-url-worker.js';
+ const worker = new Worker(script_url, { type: 'module' });
+ return new Promise((resolve, reject) => {
+ worker.onmessage = resolve;
+ worker.onerror = error => {
+ const msg = error instanceof ErrorEvent ? error.message
+ : 'unknown error';
+ reject(msg);
+ };
+ })
+ .then(msg_event => assert_true(msg_event.data.endsWith(script_url)));
+}, 'Test import.meta.url on the top-level module script.');
+
+promise_test(() => {
+ const script_url = 'import-meta-url-export.js';
+ const worker = new Worker('resources/dynamic-import-given-url-worker.js',
+ { type: 'module' });
+ worker.postMessage('./' + script_url);
+ return new Promise((resolve, reject) => {
+ worker.onmessage = resolve;
+ worker.onerror = error => {
+ const msg = error instanceof ErrorEvent ? error.message
+ : 'unknown error';
+ reject(msg);
+ };
+ })
+ .then(msg_event => assert_true(msg_event.data.endsWith(script_url)));
+}, 'Test import.meta.url on the imported module script.');
+
+promise_test(() => {
+ const script_url = 'import-meta-url-export.js';
+ const worker = new Worker('resources/dynamic-import-given-url-worker.js',
+ { type: 'module' });
+ worker.postMessage('./' + script_url);
+
+ return new Promise((resolve, reject) => {
+ worker.onmessage = resolve;
+ worker.onerror = error => {
+ const msg = error instanceof ErrorEvent ? error.message
+ : 'unknown error';
+ reject(msg);
+ };
+ })
+ .then(msg_event => assert_true(msg_event.data.endsWith(script_url)))
+ .then(() => {
+ worker.postMessage('./' + script_url + '#1');
+ return new Promise(resolve => worker.onmessage = resolve);
+ })
+ .then(msg_event => assert_true(msg_event.data.endsWith(script_url + "#1")));
+}, 'Test import.meta.url on the imported module script with a fragment.');
+
+</script>
diff --git a/testing/web-platform/tests/workers/modules/dedicated-worker-import-referrer.html b/testing/web-platform/tests/workers/modules/dedicated-worker-import-referrer.html
new file mode 100644
index 0000000000..855df62194
--- /dev/null
+++ b/testing/web-platform/tests/workers/modules/dedicated-worker-import-referrer.html
@@ -0,0 +1,244 @@
+<!DOCTYPE html>
+<title>DedicatedWorker: Referrer</title>
+<meta name="timeout" content="long">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+
+async function openWindow(url) {
+ const win = window.open(url, '_blank');
+ add_result_callback(() => win.close());
+ const msg_event = await new Promise(resolve => window.onmessage = resolve);
+ assert_equals(msg_event.data, 'LOADED');
+ return win;
+}
+
+// Removes URL parameters from the given URL string and returns it.
+function removeParams(url_string) {
+ if (!url_string)
+ return url_string;
+ let url = new URL(url_string);
+ for (var key of url.searchParams.keys())
+ url.searchParams.delete(key);
+ return url.href;
+}
+
+// Generates a referrer given a fetchType, referrer policy, and a request URL
+// (used to determine whether request is remote-origin or not). This function
+// is used to generate expected referrers.
+function generateExpectedReferrer(referrerURL, referrerPolicy, requestURL) {
+ let generatedReferrer = "";
+ if (referrerPolicy === 'no-referrer')
+ generatedReferrer = "";
+ else if (referrerPolicy === 'origin')
+ generatedReferrer = new URL('resources/' + referrerURL, location.href).origin + '/';
+ else if (referrerPolicy === 'same-origin')
+ generatedReferrer = requestURL.includes('remote') ? "" : new URL('resources/' + referrerURL, location.href).href;
+ else
+ generatedReferrer = new URL('resources/' + referrerURL, location.href).href;
+
+ return generatedReferrer;
+}
+
+// Runs a referrer policy test with the given settings. This opens a new window
+// that starts a dedicated worker.
+//
+// |settings| has options as follows:
+//
+// settings = {
+// scriptURL: 'resources/referrer-checker.sub.js',
+// windowReferrerPolicy: 'no-referrer',
+// workerReferrerPolicy: 'same-origin',
+// fetchType: 'top-level' or 'descendant-static' or 'descendant-dynamic'
+// };
+//
+// - |scriptURL| is used for starting a new worker.
+// - |windowReferrerPolicy| is to set the ReferrerPolicy HTTP header of the
+// window. This policy should be applied to top-level worker module script
+// loading and static imports.
+// - |workerReferrerPolicy| is to set the ReferrerPolicy HTTP header of the
+// worker. This policy should be applied to dynamic imports.
+// - |fetchType| indicates a script whose referrer will be tested.
+function import_referrer_test(settings, description) {
+ promise_test(async () => {
+ let windowURL = 'resources/new-worker-window.html';
+ if (settings.windowReferrerPolicy) {
+ windowURL +=
+ `?pipe=header(Referrer-Policy, ${settings.windowReferrerPolicy})`;
+ }
+
+ let scriptURL = settings.scriptURL;
+ if (settings.workerReferrerPolicy) {
+ // 'sub' is necessary even if the |scriptURL| contains the '.sub'
+ // extension because the extension doesn't work with the 'pipe' parameter.
+ // See an issue on the WPT's repository:
+ // https://github.com/web-platform-tests/wpt/issues/9345
+ scriptURL +=
+ `?pipe=sub|header(Referrer-Policy, ${settings.workerReferrerPolicy})`;
+ }
+
+ const win = await openWindow(windowURL);
+ win.postMessage(scriptURL, '*');
+ const msgEvent = await new Promise(resolve => window.onmessage = resolve);
+
+ // Generate the expected referrer, given:
+ // - The fetchType of the test
+ // - Referrer URL to be used
+ // - Relevant referrer policy
+ // - Request URL
+ let expectedReferrer;
+ if (settings.fetchType === 'top-level') {
+ // Top-level worker requests have their outgoing referrers set given their
+ // containing window's URL and referrer policy.
+ expectedReferrer = generateExpectedReferrer('new-worker-window.html', settings.windowReferrerPolicy, settings.scriptURL);
+ } else if (settings.fetchType === 'descendant-static') {
+ // Static descendant script requests from a worker have their outgoing
+ // referrer set given their containing script's URL and containing
+ // window's referrer policy.
+
+ // In the below cases, the referrer URL and the request URLs are the same
+ // because the initial request (for the top-level worker script) should
+ // act as the referrer for future descendant requests.
+ expectedReferrer = generateExpectedReferrer(settings.scriptURL, settings.windowReferrerPolicy, settings.scriptURL);
+ } else if (settings.fetchType === 'descendant-dynamic') {
+ // Dynamic descendant script requests from a worker have their outgoing
+ // referrer set given their containing script's URL and referrer policy.
+ expectedReferrer = generateExpectedReferrer(settings.scriptURL, settings.workerReferrerPolicy, settings.scriptURL);
+ }
+
+ // Delete query parameters from the actual referrer to make it easy to match
+ // it with the expected referrer.
+ const actualReferrer = removeParams(msgEvent.data);
+
+ assert_equals(actualReferrer, expectedReferrer);
+ }, description);
+}
+
+// Tests for top-level worker module script loading.
+//
+// Top-level worker module scripts should inherit the containing window's
+// referrer policy when using the containing window's URL as the referrer.
+//
+// [Current document]
+// --(open)--> [Window] whose referrer policy is |windowReferrerPolicy|.
+// --(new Worker)--> [Worker] should respect [windowReferrerPolicy| when
+// using [Window]'s URL as the referrer.
+
+import_referrer_test(
+ { scriptURL: 'postmessage-referrer-checker.py',
+ windowReferrerPolicy: 'no-referrer',
+ fetchType: 'top-level' },
+ 'Same-origin top-level module script loading with "no-referrer" referrer ' +
+ 'policy');
+
+import_referrer_test(
+ { scriptURL: 'postmessage-referrer-checker.py',
+ windowReferrerPolicy: 'origin',
+ fetchType: 'top-level' },
+ 'Same-origin top-level module script loading with "origin" referrer ' +
+ 'policy');
+
+import_referrer_test(
+ { scriptURL: 'postmessage-referrer-checker.py',
+ windowReferrerPolicy: 'same-origin',
+ fetchType: 'top-level' },
+ 'Same-origin top-level module script loading with "same-origin" referrer ' +
+ 'policy');
+
+// Tests for static imports.
+//
+// Static imports should inherit the containing window's referrer policy when
+// using the containing module script's URL as the referrer.
+// Note: This is subject to change in the future; see
+// https://github.com/w3c/webappsec-referrer-policy/issues/111.
+//
+// [Current document]
+// --(open)--> [Window] whose referrer policy is |windowReferrerPolicy|.
+// --(new Worker)--> [Worker]
+// --(static import)--> [Script] should respect |windowReferrerPolicy| when
+// using [Worker]'s URL as the referrer.
+
+import_referrer_test(
+ { scriptURL: 'static-import-same-origin-referrer-checker-worker.js',
+ windowReferrerPolicy: 'no-referrer',
+ fetchType: 'descendant-static' },
+ 'Same-origin static import with "no-referrer" referrer policy.');
+
+import_referrer_test(
+ { scriptURL: 'static-import-same-origin-referrer-checker-worker.js',
+ windowReferrerPolicy: 'origin',
+ fetchType: 'descendant-static' },
+ 'Same-origin static import with "origin" referrer policy.');
+
+import_referrer_test(
+ { scriptURL: 'static-import-same-origin-referrer-checker-worker.js',
+ windowReferrerPolicy: 'same-origin',
+ fetchType: 'descendant-static' },
+ 'Same-origin static import with "same-origin" referrer policy.');
+
+import_referrer_test(
+ { scriptURL: 'static-import-remote-origin-referrer-checker-worker.sub.js',
+ windowReferrerPolicy: 'no-referrer',
+ fetchType: 'descendant-static' },
+ 'Cross-origin static import with "no-referrer" referrer policy.');
+
+import_referrer_test(
+ { scriptURL: 'static-import-remote-origin-referrer-checker-worker.sub.js',
+ windowReferrerPolicy: 'origin',
+ fetchType: 'descendant-static' },
+ 'Cross-origin static import with "origin" referrer policy.');
+
+import_referrer_test(
+ { scriptURL: 'static-import-remote-origin-referrer-checker-worker.sub.js',
+ windowReferrerPolicy: 'same-origin',
+ fetchType: 'descendant-static' },
+ 'Cross-origin static import with "same-origin" referrer policy.');
+
+// Tests for dynamic imports.
+//
+// Dynamic imports should inherit the containing script's referrer policy if
+// set, and use the default referrer policy otherwise, when using the
+// containing script's URL as the referrer.
+//
+// [Current document]
+// --(open)--> [Window]
+// --(new Worker)--> [Worker] whose referrer policy is |workerReferrerPolicy|.
+// --(dynamic import)--> [Script] should respect |workerReferrerPolicy| when
+// using [Worker]'s URL as the referrer.
+
+import_referrer_test(
+ { scriptURL: 'dynamic-import-same-origin-referrer-checker-worker.js',
+ workerReferrerPolicy: 'no-referrer',
+ fetchType: 'descendant-dynamic' },
+ 'Same-origin dynamic import with "no-referrer" referrer policy.');
+
+import_referrer_test(
+ { scriptURL: 'dynamic-import-same-origin-referrer-checker-worker.js',
+ workerReferrerPolicy: 'origin',
+ fetchType: 'descendant-dynamic' },
+ 'Same-origin dynamic import with "origin" referrer policy.');
+
+import_referrer_test(
+ { scriptURL: 'dynamic-import-same-origin-referrer-checker-worker.js',
+ workerReferrerPolicy: 'same-origin',
+ fetchType: 'descendant-dynamic' },
+ 'Same-origin dynamic import with "same-origin" referrer policy.');
+
+import_referrer_test(
+ { scriptURL: 'dynamic-import-remote-origin-referrer-checker-worker.sub.js',
+ workerReferrerPolicy: 'no-referrer',
+ fetchType: 'descendant-dynamic' },
+ 'Cross-origin dynamic import with "no-referrer" referrer policy.');
+
+import_referrer_test(
+ { scriptURL: 'dynamic-import-remote-origin-referrer-checker-worker.sub.js',
+ workerReferrerPolicy: 'origin',
+ fetchType: 'descendant-dynamic' },
+ 'Cross-origin dynamic import with "origin" referrer policy.');
+
+import_referrer_test(
+ { scriptURL: 'dynamic-import-remote-origin-referrer-checker-worker.sub.js',
+ workerReferrerPolicy: 'same-origin',
+ fetchType: 'descendant-dynamic' },
+ 'Cross-origin dynamic import with "same-origin" referrer policy.');
+</script>
diff --git a/testing/web-platform/tests/workers/modules/dedicated-worker-import.any.js b/testing/web-platform/tests/workers/modules/dedicated-worker-import.any.js
new file mode 100644
index 0000000000..d5bb6cccbe
--- /dev/null
+++ b/testing/web-platform/tests/workers/modules/dedicated-worker-import.any.js
@@ -0,0 +1,22 @@
+// META: script=/workers/modules/resources/import-test-cases.js
+
+// Starts a dedicated worker for |testCase.scriptURL| and waits until the list
+// of imported modules is sent from the worker. Passes if the list is equal to
+// |testCase.expectation|.
+function import_test(testCase) {
+ promise_test(async () => {
+ const worker = new Worker(testCase.scriptURL, { type: 'module' });
+ worker.postMessage('Send message for tests from main script.');
+ const msgEvent = await new Promise((resolve, reject) => {
+ worker.onmessage = resolve;
+ worker.onerror = error => {
+ const msg = error instanceof ErrorEvent ? error.message
+ : 'unknown error';
+ reject(msg);
+ };
+ });
+ assert_array_equals(msgEvent.data, testCase.expectation);
+ }, testCase.description);
+}
+
+testCases.forEach(import_test);
diff --git a/testing/web-platform/tests/workers/modules/dedicated-worker-options-credentials.html b/testing/web-platform/tests/workers/modules/dedicated-worker-options-credentials.html
new file mode 100644
index 0000000000..94916d7ff5
--- /dev/null
+++ b/testing/web-platform/tests/workers/modules/dedicated-worker-options-credentials.html
@@ -0,0 +1,304 @@
+<!DOCTYPE html>
+<title>DedicatedWorker: WorkerOptions 'credentials'</title>
+<meta name="timeout" content="long">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/get-host-info.sub.js"></script>
+<script>
+host_info = get_host_info();
+
+// Determines the expected cookie value to be reported by a dedicated worker
+// based on the given option. The worker reports an empty string as the actual
+// cookie value if the cookie wasn't sent to the server. Otherwise, it's the
+// value set by the headers file:
+// "dedicated-worker-options-credentials.html.headers"
+function DetermineExpectedCookieValue(options, config) {
+ // Valid WorkerOptions and test config checking.
+ if (config.origin !== 'same' && config.origin !== 'remote')
+ assert_unreached('Invalid config.origin was specified: ' + config.origin);
+ if (options.credentials && options.credentials !== 'omit' &&
+ options.credentials !== 'same-origin' &&
+ options.credentials !== 'include') {
+ assert_unreached('Invalid credentials option was specified: ' +
+ options.credentials);
+ }
+ if (options.type !== 'classic' && options.type !== 'module')
+ assert_unreached('Invalid type option was specified: ' + options.type);
+
+ if (options.type === 'classic')
+ return (config.origin === 'same') ? '1' : '';
+
+ if (options.credentials === 'omit')
+ return '';
+ else if (options.credentials === 'include')
+ return '1';
+ else
+ return (config.origin === 'same') ? '1' : '';
+}
+
+// Runs a credentials test with the given WorkerOptions.
+//
+// |options| is a WorkerOptions dict.
+// |config| has options as follows:
+//
+// config = {
+// fetchType: 'top-level' or 'descendant-static' or 'descendant-dynamic'
+// origin: 'remote' or 'same'
+// };
+//
+// - |config.fetchType| indicates the type of script to load for the test.
+// - |config.origin| indicates same-origin-ness of the script to load.
+function credentials_test(options, config, description) {
+ promise_test(async () => {
+ let workerURL, origin = config.origin;
+ if (config.fetchType === 'top-level') {
+ workerURL = 'resources/postmessage-credentials.py';
+ } else if (config.fetchType === 'descendant-static') {
+ workerURL =
+ `resources/static-import-${origin}-origin-credentials-checker-worker.${origin === 'same' ? '' : 'sub.'}js`;
+ } else if (config.fetchType === 'descendant-dynamic') {
+ workerURL =
+ `resources/dynamic-import-${origin}-origin-credentials-checker-worker.${origin === 'same' ? '' : 'sub.'}js`;
+ } else {
+ assert_unreached('Invalid config.fetchType: ' + config.fetchType);
+ }
+
+ const worker = new Worker(workerURL, options);
+
+ // Wait until the worker sends the actual cookie value.
+ const msg_event = await new Promise(resolve => worker.onmessage = resolve);
+
+ const expectedCookieValue = DetermineExpectedCookieValue(options, config);
+ assert_equals(msg_event.data, expectedCookieValue);
+ }, description);
+}
+
+function init() {
+ // Same-origin cookie is set up in the .headers file in this directory.
+ promise_test(async () => {
+ return fetch(
+ `${host_info.HTTP_REMOTE_ORIGIN}/cookies/resources/set-cookie.py?name=COOKIE_NAME&path=/workers/modules/`,
+ {
+ mode: 'no-cors',
+ credentials: 'include'
+ });
+ }, 'Test initialization: setting up cross-origin cookie');
+}
+
+init();
+
+// Tests for module workers.
+
+credentials_test(
+ { type: 'module' },
+ { fetchType: 'top-level', origin: 'same' },
+ 'new Worker() with type=module and default credentials option should ' +
+ 'behave as credentials=same-origin and send the credentials');
+
+credentials_test(
+ { credentials: 'omit', type: 'module' },
+ { fetchType: 'top-level', origin: 'same' },
+ 'new Worker() with type=module and credentials=omit should not send the ' +
+ 'credentials');
+
+credentials_test(
+ { credentials: 'same-origin', type: 'module' },
+ { fetchType: 'top-level', origin: 'same' },
+ 'new Worker() with type=module and credentials=same-origin should send ' +
+ 'the credentials');
+
+credentials_test(
+ { credentials: 'include', type: 'module' },
+ { fetchType: 'top-level', origin: 'same' },
+ 'new Worker() with type=module and credentials=include should send the ' +
+ 'credentials');
+
+// Tests for module worker static imports.
+
+credentials_test(
+ { type: 'module' },
+ { fetchType: 'descendant-static', origin: 'same' },
+ 'new Worker() with type=module and default credentials option should ' +
+ 'behave as credentials=same-origin and send the credentials for ' +
+ 'same-origin static imports');
+
+credentials_test(
+ { credentials: 'omit', type: 'module' },
+ { fetchType: 'descendant-static', origin: 'same' },
+ 'new Worker() with type=module and credentials=omit should not send the ' +
+ 'credentials for same-origin static imports');
+
+credentials_test(
+ { credentials: 'same-origin', type: 'module' },
+ { fetchType: 'descendant-static', origin: 'same' },
+ 'new Worker() with type=module and credentials=same-origin should send ' +
+ 'the credentials for same-origin static imports');
+
+credentials_test(
+ { credentials: 'include', type: 'module' },
+ { fetchType: 'descendant-static', origin: 'same' },
+ 'new Worker() with type=module and credentials=include should send the ' +
+ 'credentials for same-origin static imports');
+
+credentials_test(
+ { type: 'module' },
+ { fetchType: 'descendant-static', origin: 'remote' },
+ 'new Worker() with type=module and default credentials option should ' +
+ 'behave as credentials=same-origin and not send the credentials for ' +
+ 'cross-origin static imports');
+
+credentials_test(
+ { credentials: 'omit', type: 'module' },
+ { fetchType: 'descendant-static', origin: 'remote' },
+ 'new Worker() with type-module credentials=omit should not send the ' +
+ 'credentials for cross-origin static imports');
+
+credentials_test(
+ { credentials: 'same-origin', type: 'module' },
+ { fetchType: 'descendant-static', origin: 'remote' },
+ 'new Worker() with type=module and credentials=same-origin should not ' +
+ 'send the credentials for cross-origin static imports');
+
+credentials_test(
+ { credentials: 'include', type: 'module' },
+ { fetchType: 'descendant-static', origin: 'remote' },
+ 'new Worker() with type=module and credentials=include should send the ' +
+ 'credentials for cross-origin static imports');
+
+// Tests for module worker dynamic imports.
+
+credentials_test(
+ { type: 'module' },
+ { fetchType: 'descendant-dynamic', origin: 'same' },
+ 'new Worker() with type=module and default credentials option should ' +
+ 'behave as credentials=same-origin and send the credentials for ' +
+ 'same-origin dynamic imports');
+
+credentials_test(
+ { credentials: 'omit', type: 'module' },
+ { fetchType: 'descendant-dynamic', origin: 'same' },
+ 'new Worker() with type=module and credentials=omit should not send the ' +
+ 'credentials for same-origin dynamic imports');
+
+credentials_test(
+ { credentials: 'same-origin', type: 'module' },
+ { fetchType: 'descendant-dynamic', origin: 'same' },
+ 'new Worker() with type=module and credentials=same-origin should send ' +
+ 'the credentials for same-origin dynamic imports');
+
+credentials_test(
+ { credentials: 'include', type: 'module' },
+ { fetchType: 'descendant-dynamic', origin: 'same' },
+ 'new Worker() with type=module and credentials=include should send the ' +
+ 'credentials for same-origin dynamic imports');
+
+credentials_test(
+ { type: 'module'},
+ { fetchType: 'descendant-dynamic', origin: 'remote' },
+ 'new Worker() with type=module and default credentials option should ' +
+ 'behave as credentials=same-origin and not send the credentials for ' +
+ 'cross-origin dynamic imports');
+
+credentials_test(
+ { credentials: 'omit', type: 'module' },
+ { fetchType: 'descendant-dynamic', origin: 'remote' },
+ 'new Worker() with type-module credentials=omit should not send the ' +
+ 'credentials for cross-origin dynamic imports');
+
+credentials_test(
+ { credentials: 'same-origin', type: 'module' },
+ { fetchType: 'descendant-dynamic', origin: 'remote' },
+ 'new Worker() with type=module and credentials=same-origin should not ' +
+ 'send the credentials for cross-origin dynamic imports');
+
+credentials_test(
+ { credentials: 'include', type: 'module' },
+ { fetchType: 'descendant-dynamic', origin: 'remote' },
+ 'new Worker() with type=module and credentials=include should send the ' +
+ 'credentials for cross-origin dynamic imports');
+
+// Tests for classic workers.
+// TODO(domfarolino): Maybe move classic worker tests up a directory?
+
+credentials_test(
+ { type: 'classic' },
+ { fetchType: 'top-level', origin: 'same' },
+ 'new Worker() with type=classic should always send the credentials ' +
+ 'regardless of the credentials option (default).');
+
+credentials_test(
+ { credentials: 'omit', type: 'classic' },
+ { fetchType: 'top-level', origin: 'same' },
+ 'new Worker() with type=classic should always send the credentials ' +
+ 'regardless of the credentials option (omit).');
+
+credentials_test(
+ { credentials: 'same-origin', type: 'classic' },
+ { fetchType: 'top-level', origin: 'same' },
+ 'new Worker() with type=classic should always send the credentials ' +
+ 'regardless of the credentials option (same-origin).');
+
+credentials_test(
+ { credentials: 'include', type: 'classic' },
+ { fetchType: 'top-level', origin: 'same' },
+ 'new Worker() with type=classic should always send the credentials ' +
+ 'regardless of the credentials option (include).');
+
+// Tests for classic worker dynamic imports.
+
+credentials_test(
+ { type: 'classic' },
+ { fetchType: 'descendant-dynamic', origin: 'same' },
+ 'new Worker() with type=classic should always send the credentials for ' +
+ 'same-origin dynamic imports regardless of the credentials option ' +
+ '(default).');
+
+credentials_test(
+ { credentials: 'omit', type: 'classic' },
+ { fetchType: 'descendant-dynamic', origin: 'same' },
+ 'new Worker() with type=classic should always send the credentials for ' +
+ 'same-origin dynamic imports regardless of the credentials option (omit).');
+
+credentials_test(
+ { credentials: 'same-origin', type: 'classic' },
+ { fetchType: 'descendant-dynamic', origin: 'same' },
+ 'new Worker() with type=classic should always send the credentials for ' +
+ 'same-origin dynamic imports regardless of the credentials option ' +
+ '(same-origin).');
+
+credentials_test(
+ { credentials: 'include', type: 'classic' },
+ { fetchType: 'descendant-dynamic', origin: 'same' },
+ 'new Worker() with type=classic should always send the credentials for ' +
+ 'same-origin dynamic imports regardless of the credentials option ' +
+ '(include).');
+
+credentials_test(
+ { type: 'classic' },
+ { fetchType: 'descendant-dynamic', origin: 'remote' },
+ 'new Worker() with type=classic should never send the credentials for ' +
+ 'cross-origin dynamic imports regardless of the credentials option ' +
+ '(default).');
+
+credentials_test(
+ { credentials: 'omit', type: 'classic' },
+ { fetchType: 'descendant-dynamic', origin: 'remote' },
+ 'new Worker() with type=classic should never send the credentials for ' +
+ 'cross-origin dynamic imports regardless of the credentials option ' +
+ '(omit).');
+
+credentials_test(
+ { credentials: 'same-origin', type: 'classic' },
+ { fetchType: 'descendant-dynamic', origin: 'remote' },
+ 'new Worker() with type=classic should never send the credentials for ' +
+ 'cross-origin dynamic imports regardless of the credentials option ' +
+ '(same-origin).');
+
+credentials_test(
+ { credentials: 'include', type: 'classic' },
+ { fetchType: 'descendant-dynamic', origin: 'remote' },
+ 'new Worker() with type=classic should never send the credentials for ' +
+ 'cross-origin dynamic imports regardless of the credentials option ' +
+ '(include).');
+
+</script>
diff --git a/testing/web-platform/tests/workers/modules/dedicated-worker-options-credentials.html.headers b/testing/web-platform/tests/workers/modules/dedicated-worker-options-credentials.html.headers
new file mode 100644
index 0000000000..8da851ab73
--- /dev/null
+++ b/testing/web-platform/tests/workers/modules/dedicated-worker-options-credentials.html.headers
@@ -0,0 +1,2 @@
+Set-Cookie: COOKIE_NAME=1
+Access-Control-Allow-Credentials: true
diff --git a/testing/web-platform/tests/workers/modules/dedicated-worker-options-type.html b/testing/web-platform/tests/workers/modules/dedicated-worker-options-type.html
new file mode 100644
index 0000000000..bb37a18f2c
--- /dev/null
+++ b/testing/web-platform/tests/workers/modules/dedicated-worker-options-type.html
@@ -0,0 +1,46 @@
+<!DOCTYPE html>
+<title>DedicatedWorker: WorkerOptions 'type'</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+
+promise_test(() => {
+ const worker = new Worker('resources/post-message-on-load-worker.js');
+ return new Promise(resolve => worker.onmessage = resolve)
+ .then(msg_event => assert_equals(msg_event.data, 'LOADED'));
+}, 'Test worker construction with the default worker type.');
+
+promise_test(() => {
+ const worker = new Worker('resources/post-message-on-load-worker.js',
+ { type: 'classic' });
+ return new Promise(resolve => worker.onmessage = resolve)
+ .then(msg_event => assert_equals(msg_event.data, 'LOADED'));
+}, 'Test worker construction with the "classic" worker type.');
+
+promise_test(() => {
+ const worker = new Worker('resources/post-message-on-load-worker.js',
+ { type: 'module' });
+ return new Promise(resolve => worker.onmessage = resolve)
+ .then(msg_event => assert_equals(msg_event.data, 'LOADED'));
+}, 'Test worker construction with the "module" worker type.');
+
+test(() => {
+ assert_throws_js(
+ TypeError,
+ () => {
+ new Worker('resources/post-message-on-load-worker.js', { type: '' });
+ },
+ 'Worker construction with an empty type should throw an exception');
+}, 'Test worker construction with an empty worker type.');
+
+test(() => {
+ assert_throws_js(
+ TypeError,
+ () => {
+ new Worker('resources/post-message-on-load-worker.js',
+ { type: 'unknown' });
+ },
+ 'Worker construction with an unknown type should throw an exception');
+}, 'Test worker construction with an unknown worker type.');
+
+</script>
diff --git a/testing/web-platform/tests/workers/modules/dedicated-worker-parse-error-failure.html b/testing/web-platform/tests/workers/modules/dedicated-worker-parse-error-failure.html
new file mode 100644
index 0000000000..343bfe289f
--- /dev/null
+++ b/testing/web-platform/tests/workers/modules/dedicated-worker-parse-error-failure.html
@@ -0,0 +1,51 @@
+<!DOCTYPE html>
+<title>DedicatedWorker: parse error failure</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script> setup({allow_uncaught_exception: true}); </script>
+<script src="../support/check-error-arguments.js"></script>
+<script>
+
+// Check if module scripts are supported on dedicated workers. There is no
+// direct way to detect it, so we assume it's supported when static import is
+// available on the workers.
+//
+// This check is necessary to appropriately test parse error handling because
+// we need to distinguish the parse error invoked by unsupported "import" in
+// the top-level script from the parse error invoked by the statically imported
+// script which is the one we want to check in this test.
+promise_setup(async () => {
+ const scriptURL = 'resources/static-import-worker.js';
+ const worker = new Worker(scriptURL, { type: 'module' });
+ worker.postMessage('Send message for tests from main script.');
+ const supportsModuleWorkers = await new Promise((resolve, reject) => {
+ worker.onmessage = e => {
+ resolve(e.data.length == 1 && e.data[0] == 'export-on-load-script.js');
+ };
+ worker.onerror = () => resolve(false);
+ });
+ assert_implements(
+ supportsModuleWorkers,
+ "Static import must be supported on module dedicated worker to run this test."
+ );
+});
+
+promise_test(async () => {
+ const scriptURL = 'resources/syntax-error.js';
+ const worker = new Worker(scriptURL, { type: 'module' });
+ const args = await new Promise(resolve =>
+ worker.onerror = (...args) => resolve(args));
+ window.checkErrorArguments(args);
+}, 'Module worker construction for script with syntax error should dispatch ' +
+ 'an event named error.');
+
+promise_test(async () => {
+ const scriptURL = 'resources/static-import-syntax-error.js';
+ const worker = new Worker(scriptURL, { type: 'module' });
+ const args = await new Promise(resolve =>
+ worker.onerror = (...args) => resolve(args));
+ window.checkErrorArguments(args);
+}, 'Static import on module worker for script with syntax error should ' +
+ 'dispatch an event named error.');
+
+</script>
diff --git a/testing/web-platform/tests/workers/modules/resources/dynamic-import-and-then-static-import-worker.js b/testing/web-platform/tests/workers/modules/resources/dynamic-import-and-then-static-import-worker.js
new file mode 100644
index 0000000000..527702f551
--- /dev/null
+++ b/testing/web-platform/tests/workers/modules/resources/dynamic-import-and-then-static-import-worker.js
@@ -0,0 +1,32 @@
+// This script is meant to be imported by a module worker. It receives a
+// message from the worker and responds with the list of imported modules.
+const sourcePromise = new Promise(resolve => {
+ if ('DedicatedWorkerGlobalScope' in self &&
+ self instanceof DedicatedWorkerGlobalScope) {
+ self.onmessage = e => {
+ resolve(e.target);
+ };
+ } else if (
+ 'SharedWorkerGlobalScope' in self &&
+ self instanceof SharedWorkerGlobalScope) {
+ self.onconnect = e => {
+ resolve(e.ports[0]);
+ };
+ } else if (
+ 'ServiceWorkerGlobalScope' in self &&
+ self instanceof ServiceWorkerGlobalScope) {
+ self.onmessage = e => {
+ resolve(e.source);
+ };
+ }
+});
+
+const importedModulesPromise =
+ import('./export-on-static-import-script.js')
+ .then(module => module.importedModules)
+ .catch(error => `Failed to do dynamic import: ${error}`);
+
+Promise.all([sourcePromise, importedModulesPromise]).then(results => {
+ const [source, importedModules] = results;
+ source.postMessage(importedModules);
+});
diff --git a/testing/web-platform/tests/workers/modules/resources/dynamic-import-data-url-block-cross-origin.js b/testing/web-platform/tests/workers/modules/resources/dynamic-import-data-url-block-cross-origin.js
new file mode 100644
index 0000000000..caa15319c5
--- /dev/null
+++ b/testing/web-platform/tests/workers/modules/resources/dynamic-import-data-url-block-cross-origin.js
@@ -0,0 +1,24 @@
+const sourcePromise = new Promise(resolve => {
+ if ('DedicatedWorkerGlobalScope' in self &&
+ self instanceof DedicatedWorkerGlobalScope) {
+ self.onmessage = e => {
+ resolve(e.target);
+ };
+ } else if (
+ 'SharedWorkerGlobalScope' in self &&
+ self instanceof SharedWorkerGlobalScope) {
+ self.onconnect = e => {
+ resolve(e.ports[0]);
+ };
+ }
+});
+
+const importedModulesPromise =
+ import("data:text/javascript, export const importedModules = ['export-block-cross-origin.js'];")
+ .then(module => module.importedModules)
+ .catch(() => ['ERROR']);
+
+Promise.all([sourcePromise, importedModulesPromise]).then(results => {
+ const [source, importedModules] = results;
+ source.postMessage(importedModules);
+});
diff --git a/testing/web-platform/tests/workers/modules/resources/dynamic-import-given-url-worker.js b/testing/web-platform/tests/workers/modules/resources/dynamic-import-given-url-worker.js
new file mode 100644
index 0000000000..5510275935
--- /dev/null
+++ b/testing/web-platform/tests/workers/modules/resources/dynamic-import-given-url-worker.js
@@ -0,0 +1,21 @@
+// This worker dynamically imports the script URL sent by postMessage(), and
+// sends back an error name if the dynamic import fails.
+if ('DedicatedWorkerGlobalScope' in self &&
+ self instanceof DedicatedWorkerGlobalScope) {
+ self.onmessage = msg_event => {
+ import(msg_event.data)
+ .then(module => postMessage(module.meta_url))
+ .catch(e => postMessage(e.name));
+ };
+} else if (
+ 'SharedWorkerGlobalScope' in self &&
+ self instanceof SharedWorkerGlobalScope) {
+ self.onconnect = connect_event => {
+ const port = connect_event.ports[0];
+ port.onmessage = msg_event => {
+ import(msg_event.data)
+ .then(module => port.postMessage(module.meta_url))
+ .catch(e => port.postMessage(e.name));
+ };
+ };
+}
diff --git a/testing/web-platform/tests/workers/modules/resources/dynamic-import-remote-origin-credentials-checker-worker.sub.js b/testing/web-platform/tests/workers/modules/resources/dynamic-import-remote-origin-credentials-checker-worker.sub.js
new file mode 100644
index 0000000000..3cfbe7c6e4
--- /dev/null
+++ b/testing/web-platform/tests/workers/modules/resources/dynamic-import-remote-origin-credentials-checker-worker.sub.js
@@ -0,0 +1,15 @@
+// Import a remote origin script.
+const import_url =
+ 'http://{{domains[www1]}}:{{ports[http][0]}}/workers/modules/resources/export-credentials.py';
+if ('DedicatedWorkerGlobalScope' in self &&
+ self instanceof DedicatedWorkerGlobalScope) {
+ import(import_url)
+ .then(module => postMessage(module.cookie));
+} else if (
+ 'SharedWorkerGlobalScope' in self &&
+ self instanceof SharedWorkerGlobalScope) {
+ onconnect = e => {
+ import(import_url)
+ .then(module => e.ports[0].postMessage(module.cookie));
+ };
+}
diff --git a/testing/web-platform/tests/workers/modules/resources/dynamic-import-remote-origin-referrer-checker-worker.sub.js b/testing/web-platform/tests/workers/modules/resources/dynamic-import-remote-origin-referrer-checker-worker.sub.js
new file mode 100644
index 0000000000..3f281faa7c
--- /dev/null
+++ b/testing/web-platform/tests/workers/modules/resources/dynamic-import-remote-origin-referrer-checker-worker.sub.js
@@ -0,0 +1,16 @@
+// Import a remote origin script.
+const import_url = 'https://{{domains[www1]}}:{{ports[https][0]}}/workers/modules/resources/export-referrer-checker.py';
+if ('DedicatedWorkerGlobalScope' in self &&
+ self instanceof DedicatedWorkerGlobalScope) {
+ import(import_url)
+ .then(module => postMessage(module.referrer))
+ .catch(error => postMessage(`Import failed: ${error}`));
+} else if (
+ 'SharedWorkerGlobalScope' in self &&
+ self instanceof SharedWorkerGlobalScope) {
+ onconnect = e => {
+ import(import_url)
+ .then(module => e.ports[0].postMessage(module.referrer))
+ .catch(error => e.ports[0].postMessage(`Import failed: ${error}`));
+ };
+}
diff --git a/testing/web-platform/tests/workers/modules/resources/dynamic-import-remote-origin-script-worker.sub.js b/testing/web-platform/tests/workers/modules/resources/dynamic-import-remote-origin-script-worker.sub.js
new file mode 100644
index 0000000000..7ed6543890
--- /dev/null
+++ b/testing/web-platform/tests/workers/modules/resources/dynamic-import-remote-origin-script-worker.sub.js
@@ -0,0 +1,17 @@
+// Import a remote origin script.
+const importUrl =
+ 'https://{{domains[www1]}}:{{ports[https][0]}}/workers/modules/resources/export-on-load-script.js';
+if ('DedicatedWorkerGlobalScope' in self &&
+ self instanceof DedicatedWorkerGlobalScope) {
+ import(importUrl)
+ .then(module => postMessage(module.importedModules))
+ .catch(e => postMessage(['ERROR']));
+} else if (
+ 'SharedWorkerGlobalScope' in self &&
+ self instanceof SharedWorkerGlobalScope) {
+ onconnect = e => {
+ import(importUrl)
+ .then(module => e.ports[0].postMessage(module.importedModules))
+ .catch(error => e.ports[0].postMessage(['ERROR']));
+ };
+}
diff --git a/testing/web-platform/tests/workers/modules/resources/dynamic-import-same-origin-credentials-checker-worker.js b/testing/web-platform/tests/workers/modules/resources/dynamic-import-same-origin-credentials-checker-worker.js
new file mode 100644
index 0000000000..24dbd55128
--- /dev/null
+++ b/testing/web-platform/tests/workers/modules/resources/dynamic-import-same-origin-credentials-checker-worker.js
@@ -0,0 +1,13 @@
+const import_url = './export-credentials.py';
+if ('DedicatedWorkerGlobalScope' in self &&
+ self instanceof DedicatedWorkerGlobalScope) {
+ import(import_url)
+ .then(module => postMessage(module.cookie));
+} else if (
+ 'SharedWorkerGlobalScope' in self &&
+ self instanceof SharedWorkerGlobalScope) {
+ onconnect = e => {
+ import(import_url)
+ .then(module => e.ports[0].postMessage(module.cookie));
+ };
+}
diff --git a/testing/web-platform/tests/workers/modules/resources/dynamic-import-same-origin-referrer-checker-worker.js b/testing/web-platform/tests/workers/modules/resources/dynamic-import-same-origin-referrer-checker-worker.js
new file mode 100644
index 0000000000..56f8a6ffe8
--- /dev/null
+++ b/testing/web-platform/tests/workers/modules/resources/dynamic-import-same-origin-referrer-checker-worker.js
@@ -0,0 +1,15 @@
+const import_url = './export-referrer-checker.py';
+if ('DedicatedWorkerGlobalScope' in self &&
+ self instanceof DedicatedWorkerGlobalScope) {
+ import(import_url)
+ .then(module => postMessage(module.referrer))
+ .catch(error => postMessage(`Import failed: ${error}`));
+} else if (
+ 'SharedWorkerGlobalScope' in self &&
+ self instanceof SharedWorkerGlobalScope) {
+ onconnect = e => {
+ import(import_url)
+ .then(module => e.ports[0].postMessage(module.referrer))
+ .catch(error => e.ports[0].postMessage(`Import failed: ${error}`));
+ };
+}
diff --git a/testing/web-platform/tests/workers/modules/resources/dynamic-import-script-block-cross-origin.js b/testing/web-platform/tests/workers/modules/resources/dynamic-import-script-block-cross-origin.js
new file mode 100644
index 0000000000..52ec530663
--- /dev/null
+++ b/testing/web-platform/tests/workers/modules/resources/dynamic-import-script-block-cross-origin.js
@@ -0,0 +1,24 @@
+const sourcePromise = new Promise(resolve => {
+ if ('DedicatedWorkerGlobalScope' in self &&
+ self instanceof DedicatedWorkerGlobalScope) {
+ self.onmessage = e => {
+ resolve(e.target);
+ };
+ } else if (
+ 'SharedWorkerGlobalScope' in self &&
+ self instanceof SharedWorkerGlobalScope) {
+ self.onconnect = e => {
+ resolve(e.ports[0]);
+ };
+ }
+});
+
+const importedModulesPromise =
+ import('./export-block-cross-origin.js')
+ .then(module => module.importedModules)
+ .catch(() => ['ERROR']);
+
+Promise.all([sourcePromise, importedModulesPromise]).then(results => {
+ const [source, importedModules] = results;
+ source.postMessage(importedModules);
+});
diff --git a/testing/web-platform/tests/workers/modules/resources/dynamic-import-worker.js b/testing/web-platform/tests/workers/modules/resources/dynamic-import-worker.js
new file mode 100644
index 0000000000..2e756fe055
--- /dev/null
+++ b/testing/web-platform/tests/workers/modules/resources/dynamic-import-worker.js
@@ -0,0 +1,32 @@
+// This script is meant to be imported by a module worker. It receives a
+// message from the worker and responds with the list of imported modules.
+const sourcePromise = new Promise(resolve => {
+ if ('DedicatedWorkerGlobalScope' in self &&
+ self instanceof DedicatedWorkerGlobalScope) {
+ self.onmessage = e => {
+ resolve(e.target);
+ };
+ } else if (
+ 'SharedWorkerGlobalScope' in self &&
+ self instanceof SharedWorkerGlobalScope) {
+ self.onconnect = e => {
+ resolve(e.ports[0]);
+ };
+ } else if (
+ 'ServiceWorkerGlobalScope' in self &&
+ self instanceof ServiceWorkerGlobalScope) {
+ self.onmessage = e => {
+ resolve(e.source);
+ };
+ }
+});
+
+const importedModulesPromise =
+ import('./export-on-load-script.js')
+ .then(module => module.importedModules)
+ .catch(error => `Failed to do dynamic import: ${error}`);
+
+Promise.all([sourcePromise, importedModulesPromise]).then(results => {
+ const [source, importedModules] = results;
+ source.postMessage(importedModules);
+});
diff --git a/testing/web-platform/tests/workers/modules/resources/empty-worker.js b/testing/web-platform/tests/workers/modules/resources/empty-worker.js
new file mode 100644
index 0000000000..49ceb2648a
--- /dev/null
+++ b/testing/web-platform/tests/workers/modules/resources/empty-worker.js
@@ -0,0 +1 @@
+// Do nothing.
diff --git a/testing/web-platform/tests/workers/modules/resources/eval-dynamic-import-worker.js b/testing/web-platform/tests/workers/modules/resources/eval-dynamic-import-worker.js
new file mode 100644
index 0000000000..60080d4292
--- /dev/null
+++ b/testing/web-platform/tests/workers/modules/resources/eval-dynamic-import-worker.js
@@ -0,0 +1,29 @@
+// This script is meant to be imported by a module worker. It receives a
+// message from the worker and responds with the list of imported modules.
+const code = 'const sourcePromise = new Promise(resolve => {' +
+ ' if (\'DedicatedWorkerGlobalScope\' in self &&' +
+ ' self instanceof DedicatedWorkerGlobalScope) {' +
+ ' self.onmessage = e => {' +
+ ' resolve(e.target);' +
+ ' };' +
+ ' } else if (\'SharedWorkerGlobalScope\' in self &&' +
+ ' self instanceof SharedWorkerGlobalScope) {' +
+ ' self.onconnect = e => {' +
+ ' resolve(e.ports[0]);' +
+ ' };' +
+ ' } else if (\'ServiceWorkerGlobalScope\' in self &&' +
+ ' self instanceof ServiceWorkerGlobalScope) {' +
+ ' self.onmessage = e => {' +
+ ' resolve(e.source);' +
+ ' };' +
+ ' }' +
+ '});' +
+ 'const importedModulesPromise =' +
+ ' import(\'./export-on-load-script.js\')' +
+ ' .then(module => module.importedModules)' +
+ ' .catch(error => `Failed to do dynamic import: ${error}`);' +
+ 'Promise.all([sourcePromise, importedModulesPromise]).then(results => {' +
+ ' const [source, importedModules] = results;' +
+ ' source.postMessage(importedModules);' +
+ '});';
+eval(code);
diff --git a/testing/web-platform/tests/workers/modules/resources/export-block-cross-origin.js b/testing/web-platform/tests/workers/modules/resources/export-block-cross-origin.js
new file mode 100644
index 0000000000..a4b513d280
--- /dev/null
+++ b/testing/web-platform/tests/workers/modules/resources/export-block-cross-origin.js
@@ -0,0 +1 @@
+export const importedModules = ['export-block-cross-origin.js'];
diff --git a/testing/web-platform/tests/workers/modules/resources/export-credentials.py b/testing/web-platform/tests/workers/modules/resources/export-credentials.py
new file mode 100644
index 0000000000..3d0ba778d1
--- /dev/null
+++ b/testing/web-platform/tests/workers/modules/resources/export-credentials.py
@@ -0,0 +1,15 @@
+def main(request, response):
+ cookie = request.cookies.first(b"COOKIE_NAME", None)
+
+ response_headers = [(b"Content-Type", b"text/javascript"),
+ (b"Access-Control-Allow-Credentials", b"true")]
+
+ origin = request.headers.get(b"Origin", None)
+ if origin:
+ response_headers.append((b"Access-Control-Allow-Origin", origin))
+
+ cookie_value = b'';
+ if cookie:
+ cookie_value = cookie.value;
+ return (200, response_headers,
+ b"export const cookie = '"+cookie_value+b"';")
diff --git a/testing/web-platform/tests/workers/modules/resources/export-on-dynamic-import-script.js b/testing/web-platform/tests/workers/modules/resources/export-on-dynamic-import-script.js
new file mode 100644
index 0000000000..bab7cb48d2
--- /dev/null
+++ b/testing/web-platform/tests/workers/modules/resources/export-on-dynamic-import-script.js
@@ -0,0 +1,8 @@
+// Export the list of imported modules. It's available after the |ready| promise
+// is resolved.
+export let importedModules = ['export-on-dynamic-import-script.js'];
+export let ready = import('./export-on-load-script.js')
+ .then(module => {
+ Array.prototype.push.apply(importedModules, module.importedModules);
+ return importedModules;
+ });
diff --git a/testing/web-platform/tests/workers/modules/resources/export-on-dynamic-import-script.js.headers b/testing/web-platform/tests/workers/modules/resources/export-on-dynamic-import-script.js.headers
new file mode 100644
index 0000000000..cb762eff80
--- /dev/null
+++ b/testing/web-platform/tests/workers/modules/resources/export-on-dynamic-import-script.js.headers
@@ -0,0 +1 @@
+Access-Control-Allow-Origin: *
diff --git a/testing/web-platform/tests/workers/modules/resources/export-on-load-script.js b/testing/web-platform/tests/workers/modules/resources/export-on-load-script.js
new file mode 100644
index 0000000000..7d1b122c32
--- /dev/null
+++ b/testing/web-platform/tests/workers/modules/resources/export-on-load-script.js
@@ -0,0 +1 @@
+export const importedModules = ['export-on-load-script.js'];
diff --git a/testing/web-platform/tests/workers/modules/resources/export-on-load-script.js.headers b/testing/web-platform/tests/workers/modules/resources/export-on-load-script.js.headers
new file mode 100644
index 0000000000..cb762eff80
--- /dev/null
+++ b/testing/web-platform/tests/workers/modules/resources/export-on-load-script.js.headers
@@ -0,0 +1 @@
+Access-Control-Allow-Origin: *
diff --git a/testing/web-platform/tests/workers/modules/resources/export-on-load-script.py b/testing/web-platform/tests/workers/modules/resources/export-on-load-script.py
new file mode 100644
index 0000000000..9014341915
--- /dev/null
+++ b/testing/web-platform/tests/workers/modules/resources/export-on-load-script.py
@@ -0,0 +1,15 @@
+import datetime
+
+def main(request, response):
+ # This script serves both preflight and main GET request for cross-origin
+ # static imports from module service workers.
+ # According to https://w3c.github.io/ServiceWorker/#update-algorithm,
+ # `Service-Worker: script` request header is added, which triggers CORS
+ # preflight.
+ response_headers = [(b"Content-Type", b"text/javascript"),
+ (b"Access-Control-Allow-Origin", b"*"),
+ (b"Access-Control-Allow-Headers", b"Service-Worker")]
+
+ body = b"export const importedModules = ['export-on-load-script.js'];"
+ body += b"// %d" % datetime.datetime.now().timestamp()
+ return (200, response_headers, body)
diff --git a/testing/web-platform/tests/workers/modules/resources/export-on-static-import-script.js b/testing/web-platform/tests/workers/modules/resources/export-on-static-import-script.js
new file mode 100644
index 0000000000..3f7174eef0
--- /dev/null
+++ b/testing/web-platform/tests/workers/modules/resources/export-on-static-import-script.js
@@ -0,0 +1,3 @@
+import * as module from './export-on-load-script.js';
+const filename = 'export-on-static-import-script.js';
+export const importedModules = [filename].concat(module.importedModules);
diff --git a/testing/web-platform/tests/workers/modules/resources/export-on-static-import-script.js.headers b/testing/web-platform/tests/workers/modules/resources/export-on-static-import-script.js.headers
new file mode 100644
index 0000000000..cb762eff80
--- /dev/null
+++ b/testing/web-platform/tests/workers/modules/resources/export-on-static-import-script.js.headers
@@ -0,0 +1 @@
+Access-Control-Allow-Origin: *
diff --git a/testing/web-platform/tests/workers/modules/resources/export-referrer-checker.py b/testing/web-platform/tests/workers/modules/resources/export-referrer-checker.py
new file mode 100644
index 0000000000..3b0fda1aa6
--- /dev/null
+++ b/testing/web-platform/tests/workers/modules/resources/export-referrer-checker.py
@@ -0,0 +1,9 @@
+# Returns a worker script that posts the request's referrer header.
+def main(request, response):
+ referrer = request.headers.get(b"referer", b"")
+
+ response_headers = [(b"Content-Type", b"text/javascript"),
+ (b"Access-Control-Allow-Origin", b"*")]
+
+ return (200, response_headers,
+ b"export const referrer = '"+referrer+b"';")
diff --git a/testing/web-platform/tests/workers/modules/resources/import-meta-url-export.js b/testing/web-platform/tests/workers/modules/resources/import-meta-url-export.js
new file mode 100644
index 0000000000..5287b2e9e8
--- /dev/null
+++ b/testing/web-platform/tests/workers/modules/resources/import-meta-url-export.js
@@ -0,0 +1 @@
+export const meta_url = import.meta.url;
diff --git a/testing/web-platform/tests/workers/modules/resources/import-meta-url-worker.js b/testing/web-platform/tests/workers/modules/resources/import-meta-url-worker.js
new file mode 100644
index 0000000000..7887836a6c
--- /dev/null
+++ b/testing/web-platform/tests/workers/modules/resources/import-meta-url-worker.js
@@ -0,0 +1,10 @@
+if ('DedicatedWorkerGlobalScope' in self &&
+ self instanceof DedicatedWorkerGlobalScope) {
+ postMessage(import.meta.url);
+} else if (
+ 'SharedWorkerGlobalScope' in self &&
+ self instanceof SharedWorkerGlobalScope) {
+ self.onconnect = e => {
+ e.ports[0].postMessage(import.meta.url);
+ };
+}
diff --git a/testing/web-platform/tests/workers/modules/resources/import-scripts-worker.js b/testing/web-platform/tests/workers/modules/resources/import-scripts-worker.js
new file mode 100644
index 0000000000..d85f573ffe
--- /dev/null
+++ b/testing/web-platform/tests/workers/modules/resources/import-scripts-worker.js
@@ -0,0 +1,33 @@
+try {
+ importScripts('empty-worker.js');
+ if ('DedicatedWorkerGlobalScope' in self &&
+ self instanceof DedicatedWorkerGlobalScope) {
+ postMessage('LOADED');
+ } else if (
+ 'SharedWorkerGlobalScope' in self &&
+ self instanceof SharedWorkerGlobalScope) {
+ onconnect = e => {
+ e.ports[0].postMessage('LOADED');
+ };
+ }
+} catch (e) {
+ // Post a message instead of propagating an ErrorEvent to the page because
+ // propagated event loses an error name.
+ //
+ // Step 1. "Let notHandled be the result of firing an event named error at the
+ // Worker object associated with the worker, using ErrorEvent, with the
+ // cancelable attribute initialized to true, the message, filename, lineno,
+ // and colno attributes initialized appropriately, and the error attribute
+ // initialized to null."
+ // https://html.spec.whatwg.org/multipage/workers.html#runtime-script-errors-2
+ if ('DedicatedWorkerGlobalScope' in self &&
+ self instanceof DedicatedWorkerGlobalScope) {
+ postMessage(e.name);
+ } else if (
+ 'SharedWorkerGlobalScope' in self &&
+ self instanceof SharedWorkerGlobalScope) {
+ onconnect = connectEvent => {
+ connectEvent.ports[0].postMessage(e.name);
+ };
+ }
+}
diff --git a/testing/web-platform/tests/workers/modules/resources/import-test-cases.js b/testing/web-platform/tests/workers/modules/resources/import-test-cases.js
new file mode 100644
index 0000000000..8ad89e8fda
--- /dev/null
+++ b/testing/web-platform/tests/workers/modules/resources/import-test-cases.js
@@ -0,0 +1,59 @@
+const testCases = [
+ {
+ scriptURL: '/workers/modules/resources/static-import-worker.js',
+ expectation: ['export-on-load-script.js'],
+ description: 'Static import.'
+ },
+ {
+ scriptURL: '/workers/modules/resources/static-import-remote-origin-script-worker.sub.js',
+ expectation: ['export-on-load-script.js'],
+ description: 'Static import (cross-origin).'
+ },
+ {
+ scriptURL: '/workers/modules/resources/static-import-redirect-worker.js',
+ expectation: ['export-on-load-script.js'],
+ description: 'Static import (redirect).'
+ },
+ {
+ scriptURL: '/workers/modules/resources/nested-static-import-worker.js',
+ expectation: [
+ 'export-on-static-import-script.js',
+ 'export-on-load-script.js'
+ ],
+ description: 'Nested static import.'
+ },
+ {
+ scriptURL: '/workers/modules/resources/static-import-and-then-dynamic-import-worker.js',
+ expectation: [
+ 'export-on-dynamic-import-script.js',
+ 'export-on-load-script.js'
+ ],
+ description: 'Static import and then dynamic import.'
+ },
+ {
+ scriptURL: '/workers/modules/resources/dynamic-import-worker.js',
+ expectation: ['export-on-load-script.js'],
+ description: 'Dynamic import.'
+ },
+ {
+ scriptURL: '/workers/modules/resources/nested-dynamic-import-worker.js',
+ expectation: [
+ 'export-on-dynamic-import-script.js',
+ 'export-on-load-script.js'
+ ],
+ description: 'Nested dynamic import.'
+ },
+ {
+ scriptURL: '/workers/modules/resources/dynamic-import-and-then-static-import-worker.js',
+ expectation: [
+ 'export-on-static-import-script.js',
+ 'export-on-load-script.js'
+ ],
+ description: 'Dynamic import and then static import.'
+ },
+ {
+ scriptURL: '/workers/modules/resources/eval-dynamic-import-worker.js',
+ expectation: ['export-on-load-script.js'],
+ description: 'eval(import()).'
+ }
+];
diff --git a/testing/web-platform/tests/workers/modules/resources/nested-dynamic-import-worker.js b/testing/web-platform/tests/workers/modules/resources/nested-dynamic-import-worker.js
new file mode 100644
index 0000000000..00da43c5f0
--- /dev/null
+++ b/testing/web-platform/tests/workers/modules/resources/nested-dynamic-import-worker.js
@@ -0,0 +1,34 @@
+// This script is meant to be imported by a module worker. It receives a
+// message from the worker and responds with the list of imported modules.
+
+const sourcePromise = new Promise(resolve => {
+ if ('DedicatedWorkerGlobalScope' in self &&
+ self instanceof DedicatedWorkerGlobalScope) {
+ self.onmessage = e => {
+ resolve(e.target);
+ };
+ } else if (
+ 'SharedWorkerGlobalScope' in self &&
+ self instanceof SharedWorkerGlobalScope) {
+ self.onconnect = e => {
+ resolve(e.ports[0]);
+ };
+ } else if (
+ 'ServiceWorkerGlobalScope' in self &&
+ self instanceof ServiceWorkerGlobalScope) {
+ self.onmessage = e => {
+ resolve(e.source);
+ };
+ }
+});
+
+const importedModulesPromise =
+ import('./export-on-dynamic-import-script.js')
+ .then(module => module.ready)
+ .then(importedModules => importedModules)
+ .catch(error => `Failed to do dynamic import: ${error}`);
+
+Promise.all([sourcePromise, importedModulesPromise]).then(results => {
+ const [source, importedModules] = results;
+ source.postMessage(importedModules);
+});
diff --git a/testing/web-platform/tests/workers/modules/resources/nested-static-import-worker.js b/testing/web-platform/tests/workers/modules/resources/nested-static-import-worker.js
new file mode 100644
index 0000000000..c3a9ff0b79
--- /dev/null
+++ b/testing/web-platform/tests/workers/modules/resources/nested-static-import-worker.js
@@ -0,0 +1,21 @@
+// This script is meant to be imported by a module worker. It receives a
+// message from the worker and responds with the list of imported modules.
+import * as module from './export-on-static-import-script.js';
+if ('DedicatedWorkerGlobalScope' in self &&
+ self instanceof DedicatedWorkerGlobalScope) {
+ self.onmessage = e => {
+ e.target.postMessage(module.importedModules);
+ };
+} else if (
+ 'SharedWorkerGlobalScope' in self &&
+ self instanceof SharedWorkerGlobalScope) {
+ self.onconnect = e => {
+ e.ports[0].postMessage(module.importedModules);
+ };
+} else if (
+ 'ServiceWorkerGlobalScope' in self &&
+ self instanceof ServiceWorkerGlobalScope) {
+ self.onmessage = e => {
+ e.source.postMessage(module.importedModules);
+ };
+}
diff --git a/testing/web-platform/tests/workers/modules/resources/new-shared-worker-window.html b/testing/web-platform/tests/workers/modules/resources/new-shared-worker-window.html
new file mode 100644
index 0000000000..84564fd7b6
--- /dev/null
+++ b/testing/web-platform/tests/workers/modules/resources/new-shared-worker-window.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<title>SharedWorker: new SharedWorker()</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+let worker;
+
+// Create a new shared worker for a given script url.
+window.onmessage = e => {
+ worker = new SharedWorker(e.data.scriptURL,
+ { name: e.data.name, type: 'module' });
+ worker.port.onmessage = msg => window.opener.postMessage(msg.data, '*');
+ worker.onerror = err => {
+ window.opener.postMessage(['ERROR'], '*');
+ err.preventDefault();
+ };
+}
+window.opener.postMessage('LOADED', '*');
+</script>
diff --git a/testing/web-platform/tests/workers/modules/resources/new-worker-window.html b/testing/web-platform/tests/workers/modules/resources/new-worker-window.html
new file mode 100644
index 0000000000..32a89fae0e
--- /dev/null
+++ b/testing/web-platform/tests/workers/modules/resources/new-worker-window.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<title>DedicatedWorker: new Worker()</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+let worker;
+
+// Creates a new dedicated worker for a given script url.
+window.onmessage = e => {
+ worker = new Worker(e.data, { type: 'module' });
+ worker.postMessage('start');
+ worker.onmessage = msg => window.opener.postMessage(msg.data, '*');
+ worker.onerror = err => {
+ window.opener.postMessage(['ERROR'], '*');
+ err.preventDefault();
+ };
+};
+window.opener.postMessage('LOADED', '*');
+</script>
diff --git a/testing/web-platform/tests/workers/modules/resources/post-message-on-load-worker.js b/testing/web-platform/tests/workers/modules/resources/post-message-on-load-worker.js
new file mode 100644
index 0000000000..e1c547ab6a
--- /dev/null
+++ b/testing/web-platform/tests/workers/modules/resources/post-message-on-load-worker.js
@@ -0,0 +1,10 @@
+if ('DedicatedWorkerGlobalScope' in self &&
+ self instanceof DedicatedWorkerGlobalScope) {
+ postMessage('LOADED');
+} else if (
+ 'SharedWorkerGlobalScope' in self &&
+ self instanceof SharedWorkerGlobalScope) {
+ self.onconnect = e => {
+ e.ports[0].postMessage('LOADED');
+ };
+}
diff --git a/testing/web-platform/tests/workers/modules/resources/postmessage-credentials.py b/testing/web-platform/tests/workers/modules/resources/postmessage-credentials.py
new file mode 100644
index 0000000000..a054a1a923
--- /dev/null
+++ b/testing/web-platform/tests/workers/modules/resources/postmessage-credentials.py
@@ -0,0 +1,22 @@
+def main(request, response):
+ cookie = request.cookies.first(b"COOKIE_NAME", None)
+
+ response_headers = [(b"Content-Type", b"text/javascript"),
+ (b"Access-Control-Allow-Credentials", b"true")]
+
+ origin = request.headers.get(b"Origin", None)
+ if origin:
+ response_headers.append((b"Access-Control-Allow-Origin", origin))
+
+ cookie_value = b'';
+ if cookie:
+ cookie_value = cookie.value;
+ return (200, response_headers,
+ b"if ('DedicatedWorkerGlobalScope' in self &&" +
+ b" self instanceof DedicatedWorkerGlobalScope) {" +
+ b" postMessage('"+cookie_value+b"');" +
+ b"} else if (" +
+ b" 'SharedWorkerGlobalScope' in self &&" +
+ b" self instanceof SharedWorkerGlobalScope) {" +
+ b" onconnect = e => e.ports[0].postMessage('"+cookie_value+b"');" +
+ b"}")
diff --git a/testing/web-platform/tests/workers/modules/resources/postmessage-referrer-checker.py b/testing/web-platform/tests/workers/modules/resources/postmessage-referrer-checker.py
new file mode 100644
index 0000000000..699af684a2
--- /dev/null
+++ b/testing/web-platform/tests/workers/modules/resources/postmessage-referrer-checker.py
@@ -0,0 +1,16 @@
+# Returns a worker script that posts the request's referrer header.
+def main(request, response):
+ referrer = request.headers.get(b"referer", b"")
+
+ response_headers = [(b"Content-Type", b"text/javascript"),
+ (b"Access-Control-Allow-Origin", b"*")]
+
+ return (200, response_headers,
+ b"if ('DedicatedWorkerGlobalScope' in self &&" +
+ b" self instanceof DedicatedWorkerGlobalScope) {" +
+ b" postMessage('"+referrer+b"');" +
+ b"} else if (" +
+ b" 'SharedWorkerGlobalScope' in self &&" +
+ b" self instanceof SharedWorkerGlobalScope) {" +
+ b" onconnect = e => e.ports[0].postMessage('"+referrer+b"');" +
+ b"}")
diff --git a/testing/web-platform/tests/workers/modules/resources/redirect.py b/testing/web-platform/tests/workers/modules/resources/redirect.py
new file mode 100644
index 0000000000..396bd4c4b2
--- /dev/null
+++ b/testing/web-platform/tests/workers/modules/resources/redirect.py
@@ -0,0 +1,8 @@
+def main(request, response):
+ """Simple handler that causes redirection.
+ This is placed here to stay within the same directory during redirects,
+ to avoid issues like https://crbug.com/1136775.
+ """
+ response.status = 302
+ location = request.GET.first(b"location")
+ response.headers.set(b"Location", location)
diff --git a/testing/web-platform/tests/workers/modules/resources/static-import-and-then-dynamic-import-worker.js b/testing/web-platform/tests/workers/modules/resources/static-import-and-then-dynamic-import-worker.js
new file mode 100644
index 0000000000..2d857a2e90
--- /dev/null
+++ b/testing/web-platform/tests/workers/modules/resources/static-import-and-then-dynamic-import-worker.js
@@ -0,0 +1,34 @@
+// This script is meant to be imported by a module worker. It receives a
+// message from the worker and responds with the list of imported modules.
+import * as module from './export-on-dynamic-import-script.js';
+
+const sourcePromise = new Promise(resolve => {
+ if ('DedicatedWorkerGlobalScope' in self &&
+ self instanceof DedicatedWorkerGlobalScope) {
+ self.onmessage = e => {
+ resolve(e.target);
+ };
+ } else if (
+ 'SharedWorkerGlobalScope' in self &&
+ self instanceof SharedWorkerGlobalScope) {
+ self.onconnect = e => {
+ resolve(e.ports[0]);
+ };
+ } else if (
+ 'ServiceWorkerGlobalScope' in self &&
+ self instanceof ServiceWorkerGlobalScope) {
+ self.onmessage = e => {
+ resolve(e.source);
+ };
+ }
+});
+
+export let importedModules = ['export-on-dynamic-import-script.js'];
+const importedModulesPromise = module.ready
+ .then(importedModules => importedModules)
+ .catch(error => `Failed to do dynamic import: ${error}`);
+
+Promise.all([sourcePromise, importedModulesPromise]).then(results => {
+ const [source, importedModules] = results;
+ source.postMessage(importedModules);
+});
diff --git a/testing/web-platform/tests/workers/modules/resources/static-import-data-url-block-cross-origin.js b/testing/web-platform/tests/workers/modules/resources/static-import-data-url-block-cross-origin.js
new file mode 100644
index 0000000000..833348d66c
--- /dev/null
+++ b/testing/web-platform/tests/workers/modules/resources/static-import-data-url-block-cross-origin.js
@@ -0,0 +1,14 @@
+import * as module from "data:text/javascript, export const importedModules = ['export-block-cross-origin.js'];";
+
+if ('DedicatedWorkerGlobalScope' in self &&
+ self instanceof DedicatedWorkerGlobalScope) {
+ self.onmessage = e => {
+ e.target.postMessage(module.importedModules);
+ };
+} else if (
+ 'SharedWorkerGlobalScope' in self &&
+ self instanceof SharedWorkerGlobalScope) {
+ self.onconnect = e => {
+ e.ports[0].postMessage(module.importedModules);
+ };
+}
diff --git a/testing/web-platform/tests/workers/modules/resources/static-import-non-existent-script-worker.js b/testing/web-platform/tests/workers/modules/resources/static-import-non-existent-script-worker.js
new file mode 100644
index 0000000000..16f70e9daf
--- /dev/null
+++ b/testing/web-platform/tests/workers/modules/resources/static-import-non-existent-script-worker.js
@@ -0,0 +1 @@
+import './non-existent-script.js';
diff --git a/testing/web-platform/tests/workers/modules/resources/static-import-redirect-worker.js b/testing/web-platform/tests/workers/modules/resources/static-import-redirect-worker.js
new file mode 100644
index 0000000000..8434c1a34e
--- /dev/null
+++ b/testing/web-platform/tests/workers/modules/resources/static-import-redirect-worker.js
@@ -0,0 +1,21 @@
+// This script is meant to be imported by a module worker. It receives a
+// message from the worker and responds with the list of imported modules.
+import * as module from './redirect.py?location=/workers/modules/resources/export-on-load-script.js';
+if ('DedicatedWorkerGlobalScope' in self &&
+ self instanceof DedicatedWorkerGlobalScope) {
+ self.onmessage = e => {
+ e.target.postMessage(module.importedModules);
+ };
+} else if (
+ 'SharedWorkerGlobalScope' in self &&
+ self instanceof SharedWorkerGlobalScope) {
+ self.onconnect = e => {
+ e.ports[0].postMessage(module.importedModules);
+ };
+} else if (
+ 'ServiceWorkerGlobalScope' in self &&
+ self instanceof ServiceWorkerGlobalScope) {
+ self.onmessage = e => {
+ e.source.postMessage(module.importedModules);
+ };
+}
diff --git a/testing/web-platform/tests/workers/modules/resources/static-import-remote-origin-credentials-checker-worker.sub.js b/testing/web-platform/tests/workers/modules/resources/static-import-remote-origin-credentials-checker-worker.sub.js
new file mode 100644
index 0000000000..c543cb4961
--- /dev/null
+++ b/testing/web-platform/tests/workers/modules/resources/static-import-remote-origin-credentials-checker-worker.sub.js
@@ -0,0 +1,12 @@
+// Import a remote origin script.
+import * as module from 'http://{{domains[www1]}}:{{ports[http][0]}}/workers/modules/resources/export-credentials.py';
+if ('DedicatedWorkerGlobalScope' in self &&
+ self instanceof DedicatedWorkerGlobalScope) {
+ postMessage(module.cookie);
+} else if (
+ 'SharedWorkerGlobalScope' in self &&
+ self instanceof SharedWorkerGlobalScope) {
+ onconnect = e => {
+ e.ports[0].postMessage(module.cookie);
+ };
+}
diff --git a/testing/web-platform/tests/workers/modules/resources/static-import-remote-origin-referrer-checker-worker.sub.js b/testing/web-platform/tests/workers/modules/resources/static-import-remote-origin-referrer-checker-worker.sub.js
new file mode 100644
index 0000000000..45fc5493c9
--- /dev/null
+++ b/testing/web-platform/tests/workers/modules/resources/static-import-remote-origin-referrer-checker-worker.sub.js
@@ -0,0 +1,12 @@
+// Import a remote origin script.
+import * as module from 'https://{{domains[www1]}}:{{ports[https][0]}}/workers/modules/resources/export-referrer-checker.py';
+if ('DedicatedWorkerGlobalScope' in self &&
+ self instanceof DedicatedWorkerGlobalScope) {
+ postMessage(module.referrer);
+} else if (
+ 'SharedWorkerGlobalScope' in self &&
+ self instanceof SharedWorkerGlobalScope) {
+ onconnect = e => {
+ e.ports[0].postMessage(module.referrer);
+ };
+}
diff --git a/testing/web-platform/tests/workers/modules/resources/static-import-remote-origin-script-worker.sub.js b/testing/web-platform/tests/workers/modules/resources/static-import-remote-origin-script-worker.sub.js
new file mode 100644
index 0000000000..6432dd5d80
--- /dev/null
+++ b/testing/web-platform/tests/workers/modules/resources/static-import-remote-origin-script-worker.sub.js
@@ -0,0 +1,20 @@
+// Import a remote origin script.
+import * as module from 'https://{{domains[www1]}}:{{ports[https][0]}}/workers/modules/resources/export-on-load-script.py';
+if ('DedicatedWorkerGlobalScope' in self &&
+ self instanceof DedicatedWorkerGlobalScope) {
+ self.onmessage = e => {
+ e.target.postMessage(module.importedModules);
+ };
+} else if (
+ 'SharedWorkerGlobalScope' in self &&
+ self instanceof SharedWorkerGlobalScope) {
+ self.onconnect = e => {
+ e.ports[0].postMessage(module.importedModules);
+ };
+} else if (
+ 'ServiceWorkerGlobalScope' in self &&
+ self instanceof ServiceWorkerGlobalScope) {
+ self.onmessage = e => {
+ e.source.postMessage(module.importedModules);
+ };
+}
diff --git a/testing/web-platform/tests/workers/modules/resources/static-import-same-origin-credentials-checker-worker.js b/testing/web-platform/tests/workers/modules/resources/static-import-same-origin-credentials-checker-worker.js
new file mode 100644
index 0000000000..e83dc92070
--- /dev/null
+++ b/testing/web-platform/tests/workers/modules/resources/static-import-same-origin-credentials-checker-worker.js
@@ -0,0 +1,11 @@
+import * as module from './export-credentials.py';
+if ('DedicatedWorkerGlobalScope' in self &&
+ self instanceof DedicatedWorkerGlobalScope) {
+ postMessage(module.cookie);
+} else if (
+ 'SharedWorkerGlobalScope' in self &&
+ self instanceof SharedWorkerGlobalScope) {
+ onconnect = e => {
+ e.ports[0].postMessage(module.cookie);
+ };
+}
diff --git a/testing/web-platform/tests/workers/modules/resources/static-import-same-origin-referrer-checker-worker.js b/testing/web-platform/tests/workers/modules/resources/static-import-same-origin-referrer-checker-worker.js
new file mode 100644
index 0000000000..ffcab9e45f
--- /dev/null
+++ b/testing/web-platform/tests/workers/modules/resources/static-import-same-origin-referrer-checker-worker.js
@@ -0,0 +1,11 @@
+import * as module from './export-referrer-checker.py';
+if ('DedicatedWorkerGlobalScope' in self &&
+ self instanceof DedicatedWorkerGlobalScope) {
+ postMessage(module.referrer);
+} else if (
+ 'SharedWorkerGlobalScope' in self &&
+ self instanceof SharedWorkerGlobalScope) {
+ onconnect = e => {
+ e.ports[0].postMessage(module.referrer);
+ };
+}
diff --git a/testing/web-platform/tests/workers/modules/resources/static-import-script-block-cross-origin.js b/testing/web-platform/tests/workers/modules/resources/static-import-script-block-cross-origin.js
new file mode 100644
index 0000000000..82be8e7ce7
--- /dev/null
+++ b/testing/web-platform/tests/workers/modules/resources/static-import-script-block-cross-origin.js
@@ -0,0 +1,14 @@
+import * as module from './export-block-cross-origin.js';
+
+if ('DedicatedWorkerGlobalScope' in self &&
+ self instanceof DedicatedWorkerGlobalScope) {
+ self.onmessage = e => {
+ e.target.postMessage(module.importedModules);
+ };
+} else if (
+ 'SharedWorkerGlobalScope' in self &&
+ self instanceof SharedWorkerGlobalScope) {
+ self.onconnect = e => {
+ e.ports[0].postMessage(module.importedModules);
+ };
+}
diff --git a/testing/web-platform/tests/workers/modules/resources/static-import-syntax-error.js b/testing/web-platform/tests/workers/modules/resources/static-import-syntax-error.js
new file mode 100644
index 0000000000..3a20e792c4
--- /dev/null
+++ b/testing/web-platform/tests/workers/modules/resources/static-import-syntax-error.js
@@ -0,0 +1 @@
+import * as module from './syntax-error.js';
diff --git a/testing/web-platform/tests/workers/modules/resources/static-import-worker.js b/testing/web-platform/tests/workers/modules/resources/static-import-worker.js
new file mode 100644
index 0000000000..19a347999d
--- /dev/null
+++ b/testing/web-platform/tests/workers/modules/resources/static-import-worker.js
@@ -0,0 +1,21 @@
+// This script is meant to be imported by a module worker. It receives a
+// message from the worker and responds with the list of imported modules.
+import * as module from './export-on-load-script.js';
+if ('DedicatedWorkerGlobalScope' in self &&
+ self instanceof DedicatedWorkerGlobalScope) {
+ self.onmessage = e => {
+ e.target.postMessage(module.importedModules);
+ };
+} else if (
+ 'SharedWorkerGlobalScope' in self &&
+ self instanceof SharedWorkerGlobalScope) {
+ self.onconnect = e => {
+ e.ports[0].postMessage(module.importedModules);
+ };
+} else if (
+ 'ServiceWorkerGlobalScope' in self &&
+ self instanceof ServiceWorkerGlobalScope) {
+ self.onmessage = e => {
+ e.source.postMessage(module.importedModules);
+ };
+}
diff --git a/testing/web-platform/tests/workers/modules/resources/syntax-error.js b/testing/web-platform/tests/workers/modules/resources/syntax-error.js
new file mode 100644
index 0000000000..8c5c4df671
--- /dev/null
+++ b/testing/web-platform/tests/workers/modules/resources/syntax-error.js
@@ -0,0 +1 @@
+1 + ;
diff --git a/testing/web-platform/tests/workers/modules/resources/throw.js b/testing/web-platform/tests/workers/modules/resources/throw.js
new file mode 100644
index 0000000000..3d876d43d9
--- /dev/null
+++ b/testing/web-platform/tests/workers/modules/resources/throw.js
@@ -0,0 +1 @@
+throw new Error('custom message');
diff --git a/testing/web-platform/tests/workers/modules/shared-worker-import-blob-url.window.js b/testing/web-platform/tests/workers/modules/shared-worker-import-blob-url.window.js
new file mode 100644
index 0000000000..a79d5725ad
--- /dev/null
+++ b/testing/web-platform/tests/workers/modules/shared-worker-import-blob-url.window.js
@@ -0,0 +1,26 @@
+// META: script=/workers/modules/resources/import-test-cases.js
+
+// Imports |testCase.scriptURL| on a shared worker loaded from a blob URL,
+// and waits until the list of imported modules is sent from the worker. Passes
+// if the list is equal to |testCase.expectation|.
+function import_blob_url_test(testCase) {
+ promise_test(async () => {
+ const importURL = new URL(testCase.scriptURL, location.href);
+ const blob =
+ new Blob([`import "${importURL}";`], {type: 'text/javascript'});
+ const blobURL = URL.createObjectURL(blob);
+ const worker = new SharedWorker(blobURL, {type: 'module'});
+ worker.port.postMessage('Send message for tests from main script.');
+ const msgEvent = await new Promise((resolve, reject) => {
+ worker.port.onmessage = resolve;
+ worker.onerror = error => {
+ const msg = error instanceof ErrorEvent ? error.message
+ : 'unknown error';
+ reject(msg);
+ };
+ });
+ assert_array_equals(msgEvent.data, testCase.expectation);
+ }, testCase.description);
+}
+
+testCases.forEach(import_blob_url_test);
diff --git a/testing/web-platform/tests/workers/modules/shared-worker-import-csp.html b/testing/web-platform/tests/workers/modules/shared-worker-import-csp.html
new file mode 100644
index 0000000000..c7ca37557a
--- /dev/null
+++ b/testing/web-platform/tests/workers/modules/shared-worker-import-csp.html
@@ -0,0 +1,128 @@
+<!DOCTYPE html>
+<title>SharedWorker: CSP for ES Modules</title>
+<meta name="timeout" content="long">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+
+// This Set is for checking a shared worker in each test is newly created.
+const existingWorkers = new Set();
+
+async function openWindow(url) {
+ const win = window.open(url, '_blank');
+ add_result_callback(() => win.close());
+ const msgEvent = await new Promise(resolve => window.onmessage = resolve);
+ assert_equals(msgEvent.data, 'LOADED');
+ return win;
+}
+
+function import_csp_test(
+ cspHeader, importType, expectedImportedModules, description) {
+ // Append CSP header to windowURL for static import tests since static import
+ // scripts should obey Window's CSP.
+ const windowURL = `resources/new-shared-worker-window.html` +
+ `${importType === 'static'
+ ? '?pipe=header(Content-Security-Policy, ' + cspHeader + ')'
+ : ''}`;
+ // Append CSP header to scriptURL for dynamic import tests since dynamic
+ // import scripts should obey SharedWorker script's responce's CSP.
+ const scriptURL = `${importType}-import-remote-origin-script-worker.sub.js` +
+ `${importType === 'dynamic'
+ ? '?pipe=header(Content-Security-Policy, ' + cspHeader + ')'
+ : ''}`;
+ promise_test(async () => {
+ // Open a window that has the given CSP header.
+ const win = await openWindow(windowURL);
+ // Construct a unique name for SharedWorker.
+ const name = `${cspHeader}_${importType}`;
+ const workerProperties = { scriptURL, name };
+ // Check if this shared worker is newly created.
+ assert_false(existingWorkers.has(workerProperties));
+ existingWorkers.add(workerProperties);
+
+ // Ask the window to start a shared worker with the given CSP header.
+ // The shared worker doesn't inherits the window's CSP header.
+ // https://w3c.github.io/webappsec-csp/#initialize-global-object-csp
+ win.postMessage(workerProperties, '*');
+ const msg_event = await new Promise(resolve => window.onmessage = resolve);
+ assert_array_equals(msg_event.data, expectedImportedModules);
+ }, description);
+}
+
+// Tests for static import.
+//
+// Static import should obey the worker-src directive and the script-src
+// directive. If the both directives are specified, the worker-src directive
+// should be prioritized.
+//
+// "The script-src directive acts as a default fallback for all script-like
+// destinations (including worker-specific destinations if worker-src is not
+// present)."
+// https://w3c.github.io/webappsec-csp/#directive-script-src
+
+import_csp_test(
+ "worker-src 'self' 'unsafe-inline'", "static",
+ ['ERROR'],
+ "worker-src 'self' directive should disallow cross origin static import.");
+
+import_csp_test(
+ "worker-src * 'unsafe-inline'", "static",
+ ["export-on-load-script.js"],
+ "worker-src * directive should allow cross origin static import.");
+
+import_csp_test(
+ "script-src 'self' 'unsafe-inline'", "static",
+ ['ERROR'],
+ "script-src 'self' directive should disallow cross origin static import.");
+
+import_csp_test(
+ "script-src * 'unsafe-inline'", "static",
+ ["export-on-load-script.js"],
+ "script-src * directive should allow cross origin static import.");
+
+import_csp_test(
+ "worker-src *; script-src 'self' 'unsafe-inline'", "static",
+ ["export-on-load-script.js"],
+ "worker-src * directive should override script-src 'self' directive and " +
+ "allow cross origin static import.");
+
+import_csp_test(
+ "worker-src 'self'; script-src * 'unsafe-inline'", "static",
+ ['ERROR'],
+ "worker-src 'self' directive should override script-src * directive and " +
+ "disallow cross origin static import.");
+
+// Tests for dynamic import.
+//
+// Dynamic import should obey SharedWorker script's CSP instead of parent
+// Window's CSP.
+//
+// Dynamic import should obey the script-src directive instead of the worker-src
+// directive according to the specs:
+//
+// Dynamic import has the "script" destination.
+// Step 3: "Fetch a single module script graph given url, ..., "script", ..."
+// https://html.spec.whatwg.org/multipage/webappapis.html#fetch-an-import()-module-script-graph
+//
+// The "script" destination should obey the script-src CSP directive.
+// "The script-src directive acts as a default fallback for all script-like
+// destinations (including worker-specific destinations if worker-src is not
+// present)."
+// https://w3c.github.io/webappsec-csp/#directive-script-src
+
+import_csp_test(
+ "script-src 'self' 'unsafe-inline'", "dynamic",
+ ['ERROR'],
+ "script-src 'self' directive should disallow cross origin dynamic import.");
+
+import_csp_test(
+ "script-src * 'unsafe-inline'", "dynamic",
+ ["export-on-load-script.js"],
+ "script-src * directive should allow cross origin dynamic import.");
+
+import_csp_test(
+ "worker-src 'self' 'unsafe-inline'", "dynamic",
+ ["export-on-load-script.js"],
+ "worker-src 'self' directive should not take effect on dynamic import.");
+
+</script>
diff --git a/testing/web-platform/tests/workers/modules/shared-worker-import-data-url-cross-origin.html b/testing/web-platform/tests/workers/modules/shared-worker-import-data-url-cross-origin.html
new file mode 100644
index 0000000000..3f22c5094d
--- /dev/null
+++ b/testing/web-platform/tests/workers/modules/shared-worker-import-data-url-cross-origin.html
@@ -0,0 +1,43 @@
+<!DOCTYPE html>
+<title>SharedWorker: ES modules for data URL workers</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+
+const import_from_data_url_worker_test = (importType, isDataURL, expectation) => {
+ promise_test(async () => {
+ const importURL = new URL(`resources/${importType}-import-` +
+ `${isDataURL ? 'data-url' : 'script'}-block-cross-origin.js`,
+ location.href) + '?pipe=header(Access-Control-Allow-Origin, *)';
+ const dataURL = `data:text/javascript,import "${importURL}";`;
+ const worker = new SharedWorker(dataURL, { type: 'module' });
+ worker.port.postMessage('Send message for tests from main script.');
+ const msgEvent =
+ await new Promise(resolve => worker.port.onmessage = resolve);
+ assert_array_equals(msgEvent.data,
+ expectation === 'blocked' ? ['ERROR']
+ : ['export-block-cross-origin.js']);
+ }, `${importType} import ${isDataURL ? 'data url' : 'script'} from data: ` +
+ `URL should be ${expectation}.`);
+}
+
+// Static import should obey the outside settings.
+// SecurityOrigin of the outside settings is decided by Window.
+import_from_data_url_worker_test('static', true, 'allowed');
+import_from_data_url_worker_test('static', false, 'allowed');
+
+
+// Dynamic import should obey the inside settings.
+// SecurityOrigin of the inside settings is a unique opaque origin.
+//
+// Data url script is cross-origin to the inside settings' SecurityOrigin, but
+// dynamic importing it is allowed.
+// https://fetch.spec.whatwg.org/#concept-main-fetch
+// Step 5: request’s current URL’s scheme is "data" [spec text]
+import_from_data_url_worker_test('dynamic', true, 'allowed');
+
+// Non-data url script is cross-origin to the inside settings' SecurityOrigin.
+// It should be blocked.
+import_from_data_url_worker_test('dynamic', false, 'blocked');
+
+</script>
diff --git a/testing/web-platform/tests/workers/modules/shared-worker-import-data-url.window.js b/testing/web-platform/tests/workers/modules/shared-worker-import-data-url.window.js
new file mode 100644
index 0000000000..6d54c0e232
--- /dev/null
+++ b/testing/web-platform/tests/workers/modules/shared-worker-import-data-url.window.js
@@ -0,0 +1,23 @@
+// META: script=/workers/modules/resources/import-test-cases.js
+
+// Imports |testCase.scriptURL| on a shared worker loaded from a data URL,
+// and waits until the list of imported modules is sent from the worker. Passes
+// if the list is equal to |testCase.expectation|.
+function import_data_url_test(testCase) {
+ promise_test(async () => {
+ // The Access-Control-Allow-Origin header is necessary because a worker
+ // loaded from a data URL has a null origin and import() on the worker
+ // without the header is blocked.
+ const importURL = new URL(testCase.scriptURL, location.href) +
+ '?pipe=header(Access-Control-Allow-Origin, *)';
+ const dataURL = `data:text/javascript,import "${importURL}";`;
+
+ const worker = new SharedWorker(dataURL, { type: 'module'});
+ worker.port.postMessage('Send message for tests from main script.');
+ const msgEvent =
+ await new Promise(resolve => worker.port.onmessage = resolve);
+ assert_array_equals(msgEvent.data, testCase.expectation);
+ }, testCase.description);
+}
+
+testCases.forEach(import_data_url_test);
diff --git a/testing/web-platform/tests/workers/modules/shared-worker-import-failure.html b/testing/web-platform/tests/workers/modules/shared-worker-import-failure.html
new file mode 100644
index 0000000000..14579ba762
--- /dev/null
+++ b/testing/web-platform/tests/workers/modules/shared-worker-import-failure.html
@@ -0,0 +1,67 @@
+<!DOCTYPE html>
+<title>SharedWorker: import failure</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script> setup({allow_uncaught_exception: true}); </script>
+<script>
+
+// TODO: Factor out the test cases into a separate file.
+// (like import-test-cases.js)
+
+// SharedWorkers doesn't fire error events when runtime script errors occur.
+//
+// "For shared workers, if the error is still not handled afterwards, the error
+// may be reported to a developer console." [spec text]
+// https://html.spec.whatwg.org/C/#runtime-script-errors-2
+
+promise_test(async () => {
+ const scriptURL = 'resources/import-scripts-worker.js';
+ const worker = new SharedWorker(scriptURL, { type: 'module' });
+ const msg_event =
+ await new Promise(resolve => worker.port.onmessage = resolve);
+ assert_equals(msg_event.data, 'TypeError');
+}, 'importScripts() on module worker should throw an exception.');
+
+promise_test(() => {
+ const scriptURL = 'resources/non-existent-worker.js';
+ const worker = new SharedWorker(scriptURL, { type: 'module' });
+ return new Promise(resolve => worker.onerror = resolve);
+}, 'Worker construction for non-existent script should dispatch an ' +
+ 'ErrorEvent.');
+
+promise_test(() => {
+ const scriptURL = 'resources/static-import-non-existent-script-worker.js';
+ const worker = new SharedWorker(scriptURL, { type: 'module' });
+ return new Promise(resolve => worker.onerror = resolve);
+}, 'Static import for non-existent script should dispatch an ErrorEvent.');
+
+promise_test(async () => {
+ const scriptURL = './non-existent-worker.js';
+ const worker =
+ new SharedWorker('resources/dynamic-import-given-url-worker.js',
+ { type: 'module' });
+ worker.port.postMessage(scriptURL);
+ const msg_event = await new Promise((resolve, reject) => {
+ worker.port.onmessage = resolve;
+ worker.onerror = error => {
+ const msg = error instanceof ErrorEvent ? error.message
+ : 'unknown error';
+ reject(msg);
+ };
+ });
+ assert_equals(msg_event.data, 'TypeError');
+}, 'Dynamic import for non-existent script should throw an exception.');
+
+test(() => {
+ const scriptURL = 'http://invalid:123$';
+ assert_throws_dom('SyntaxError',
+ () => new SharedWorker(scriptURL, { type: 'module' }));
+}, 'SharedWorker construction for an invalid URL should throw an exception.');
+
+test(() => {
+ const scriptURL = 'file:///static-import-worker.js';
+ assert_throws_dom('SecurityError',
+ () => new SharedWorker(scriptURL, { type: 'module' }));
+}, 'SharedWorker construction for a file URL should throw an exception.');
+
+</script>
diff --git a/testing/web-platform/tests/workers/modules/shared-worker-import-meta.html b/testing/web-platform/tests/workers/modules/shared-worker-import-meta.html
new file mode 100644
index 0000000000..e8e9b51f6a
--- /dev/null
+++ b/testing/web-platform/tests/workers/modules/shared-worker-import-meta.html
@@ -0,0 +1,49 @@
+<!DOCTYPE html>
+<title>SharedWorker: import.meta</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+
+promise_test(() => {
+ const script_url = 'resources/import-meta-url-worker.js';
+ const worker = new SharedWorker(script_url, { type: 'module' });
+ return new Promise((resolve, reject) => {
+ worker.port.onmessage = resolve;
+ worker.onerror = (error) => reject(error && error.message);
+ })
+ .then(msg_event => assert_true(msg_event.data.endsWith(script_url)));
+}, 'Test import.meta.url on the top-level module script.');
+
+promise_test(() => {
+ const script_url = 'import-meta-url-export.js';
+ const worker = new SharedWorker(
+ 'resources/dynamic-import-given-url-worker.js',
+ { type: 'module' });
+ worker.port.postMessage('./' + script_url);
+ return new Promise((resolve, reject) => {
+ worker.port.onmessage = resolve;
+ worker.onerror = (error) => reject(error && error.message);
+ })
+ .then(msg_event => assert_true(msg_event.data.endsWith(script_url)));
+}, 'Test import.meta.url on the imported module script.');
+
+promise_test(() => {
+ const script_url = 'import-meta-url-export.js';
+ const worker = new SharedWorker(
+ 'resources/dynamic-import-given-url-worker.js',
+ { type: 'module' });
+ worker.port.postMessage('./' + script_url);
+
+ return new Promise((resolve, reject) => {
+ worker.port.onmessage = resolve;
+ worker.onerror = (error) => reject(error && error.message);
+ })
+ .then(msg_event => assert_true(msg_event.data.endsWith(script_url)))
+ .then(() => {
+ worker.port.postMessage('./' + script_url + '#1');
+ return new Promise(resolve => worker.port.onmessage = resolve);
+ })
+ .then(msg_event => assert_true(msg_event.data.endsWith(script_url + "#1")));
+}, 'Test import.meta.url on the imported module script with a fragment.');
+
+</script>
diff --git a/testing/web-platform/tests/workers/modules/shared-worker-import-referrer.html b/testing/web-platform/tests/workers/modules/shared-worker-import-referrer.html
new file mode 100644
index 0000000000..1096fd2ad8
--- /dev/null
+++ b/testing/web-platform/tests/workers/modules/shared-worker-import-referrer.html
@@ -0,0 +1,259 @@
+<!DOCTYPE html>
+<title>SharedWorker: Referrer</title>
+<meta name="timeout" content="long" />
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+
+// This Set is for checking a shared worker in each test is newly created.
+const existingWorkers = new Set();
+
+async function openWindow(url) {
+ const win = window.open(url, '_blank');
+ add_result_callback(() => win.close());
+ const msg_event = await new Promise(resolve => window.onmessage = resolve);
+ assert_equals(msg_event.data, 'LOADED');
+ return win;
+}
+
+// Removes URL parameters from the given URL string and returns it.
+function removeParams(url_string) {
+ if (!url_string)
+ return url_string;
+ let url = new URL(url_string);
+ for (var key of url.searchParams.keys())
+ url.searchParams.delete(key);
+ return url.href;
+}
+
+// Generates a referrer given a fetchType, referrer policy, and a request URL
+// (used to determine whether request is remote-origin or not). This function
+// is used to generate expected referrers.
+function generateExpectedReferrer(referrerURL, referrerPolicy, requestURL) {
+ let generatedReferrer = "";
+ if (referrerPolicy === 'no-referrer')
+ generatedReferrer = "";
+ else if (referrerPolicy === 'origin')
+ generatedReferrer = new URL('resources/' + referrerURL, location.href).origin + '/';
+ else if (referrerPolicy === 'same-origin')
+ generatedReferrer = requestURL.includes('remote') ? "" : new URL('resources/' + referrerURL, location.href).href;
+ else
+ generatedReferrer = new URL('resources/' + referrerURL, location.href).href;
+
+ return generatedReferrer;
+}
+
+// Runs a referrer policy test with the given settings. This opens a new window
+// that starts a shared worker.
+//
+// |settings| has options as follows:
+//
+// settings = {
+// scriptURL: 'resources/referrer-checker.sub.js',
+// windowReferrerPolicy: 'no-referrer',
+// workerReferrerPolicy: 'same-origin',
+// fetchType: 'top-level' or 'descendant-static' or 'descendant-dynamic'
+// };
+//
+// - |scriptURL| is used for starting a new worker.
+// - |windowReferrerPolicy| is to set the ReferrerPolicy HTTP header of the
+// window. This policy should be applied to top-level worker module script
+// loading and static imports.
+// - |workerReferrerPolicy| is to set the ReferrerPolicy HTTP header of the
+// worker. This policy should be applied to dynamic imports.
+// - |fetchType| indicates a script whose referrer will be tested.
+function import_referrer_test(settings, description) {
+ promise_test(async () => {
+ let windowURL = 'resources/new-shared-worker-window.html';
+ if (settings.windowReferrerPolicy) {
+ windowURL +=
+ `?pipe=header(Referrer-Policy, ${settings.windowReferrerPolicy})`;
+ }
+
+ let scriptURL = settings.scriptURL;
+ if (settings.workerReferrerPolicy) {
+ // 'sub' is necessary even if the |scriptURL| contains the '.sub'
+ // extension because the extension doesn't work with the 'pipe' parameter.
+ // See an issue on the WPT's repository:
+ // https://github.com/web-platform-tests/wpt/issues/9345
+ scriptURL +=
+ `?pipe=sub|header(Referrer-Policy, ${settings.workerReferrerPolicy})`;
+ }
+
+ const win = await openWindow(windowURL);
+ // Construct a unique name for SharedWorker.
+ const name = `${settings.scriptURL}_` +
+ `${settings.windowReferrerPolicy ? settings.windowReferrerPolicy
+ : settings.workerReferrerPolicy}` +
+ `_${settings.fetchType}`;
+ const workerProperties = { scriptURL, name };
+ // Check if this shared worker is newly created.
+ assert_false(existingWorkers.has(workerProperties));
+ existingWorkers.add(workerProperties);
+
+ win.postMessage(workerProperties, '*');
+ const msgEvent = await new Promise(resolve => window.onmessage = resolve);
+
+ // Generate the expected referrer, given:
+ // - The fetchType of the test
+ // - Referrer URL to be used
+ // - Relevant referrer policy
+ // - Request URL
+ let expectedReferrer;
+ if (settings.fetchType === 'top-level') {
+ // Top-level worker requests have their outgoing referrers set given their
+ // containing window's URL and referrer policy.
+ expectedReferrer = generateExpectedReferrer('new-shared-worker-window.html', settings.windowReferrerPolicy, settings.scriptURL);
+ } else if (settings.fetchType === 'descendant-static') {
+ // Static descendant script requests from a worker have their outgoing
+ // referrer set given their containing script's URL and containing
+ // window's referrer policy.
+
+ // In the below cases, the referrer URL and the request URLs are the same
+ // because the initial request (for the top-level worker script) should
+ // act as the referrer for future descendant requests.
+ expectedReferrer = generateExpectedReferrer(settings.scriptURL, settings.windowReferrerPolicy, settings.scriptURL);
+ } else if (settings.fetchType === 'descendant-dynamic') {
+ // Dynamic descendant script requests from a worker have their outgoing
+ // referrer set given their containing script's URL and referrer policy.
+ expectedReferrer = generateExpectedReferrer(settings.scriptURL, settings.workerReferrerPolicy, settings.scriptURL);
+ }
+
+ // Delete query parameters from the actual referrer to make it easy to match
+ // it with the expected referrer.
+ const actualReferrer = removeParams(msgEvent.data);
+
+ assert_equals(actualReferrer, expectedReferrer);
+ }, description);
+}
+
+// Tests for top-level worker module script loading.
+//
+// Top-level worker module scripts should inherit the containing window's
+// referrer policy when using the containing window's URL as the referrer.
+//
+// [Current document]
+// --(open)--> [Window] whose referrer policy is |windowReferrerPolicy|.
+// --(new SharedWorker)--> [SharedWorker] should respect [windowReferrerPolicy|
+// when using [Window]'s URL as the referrer.
+
+import_referrer_test(
+ { scriptURL: 'postmessage-referrer-checker.py',
+ windowReferrerPolicy: 'no-referrer',
+ fetchType: 'top-level' },
+ 'Same-origin top-level module script loading with "no-referrer" referrer ' +
+ 'policy');
+
+import_referrer_test(
+ { scriptURL: 'postmessage-referrer-checker.py',
+ windowReferrerPolicy: 'origin',
+ fetchType: 'top-level' },
+ 'Same-origin top-level module script loading with "origin" referrer ' +
+ 'policy');
+
+import_referrer_test(
+ { scriptURL: 'postmessage-referrer-checker.py',
+ windowReferrerPolicy: 'same-origin',
+ fetchType: 'top-level' },
+ 'Same-origin top-level module script loading with "same-origin" referrer ' +
+ 'policy');
+
+// Tests for static imports.
+//
+// Static imports should inherit the containing window's referrer policy when
+// using the containing module script's URL as the referrer.
+// Note: This is subject to change in the future; see
+// https://github.com/w3c/webappsec-referrer-policy/issues/111.
+//
+// [Current document]
+// --(open)--> [Window] whose referrer policy is |windowReferrerPolicy|.
+// --(new SharedWorker)--> [SharedWorker]
+// --(static import)--> [Script] should respect |windowReferrerPolicy| when
+// using [SharedWorker]'s URL as the referrer.
+
+import_referrer_test(
+ { scriptURL: 'static-import-same-origin-referrer-checker-worker.js',
+ windowReferrerPolicy: 'no-referrer',
+ fetchType: 'descendant-static' },
+ 'Same-origin static import with "no-referrer" referrer policy.');
+
+import_referrer_test(
+ { scriptURL: 'static-import-same-origin-referrer-checker-worker.js',
+ windowReferrerPolicy: 'origin',
+ fetchType: 'descendant-static' },
+ 'Same-origin static import with "origin" referrer policy.');
+
+import_referrer_test(
+ { scriptURL: 'static-import-same-origin-referrer-checker-worker.js',
+ windowReferrerPolicy: 'same-origin',
+ fetchType: 'descendant-static' },
+ 'Same-origin static import with "same-origin" referrer policy.');
+
+import_referrer_test(
+ { scriptURL: 'static-import-remote-origin-referrer-checker-worker.sub.js',
+ windowReferrerPolicy: 'no-referrer',
+ fetchType: 'descendant-static' },
+ 'Cross-origin static import with "no-referrer" referrer policy.');
+
+import_referrer_test(
+ { scriptURL: 'static-import-remote-origin-referrer-checker-worker.sub.js',
+ windowReferrerPolicy: 'origin',
+ fetchType: 'descendant-static' },
+ 'Cross-origin static import with "origin" referrer policy.');
+
+import_referrer_test(
+ { scriptURL: 'static-import-remote-origin-referrer-checker-worker.sub.js',
+ windowReferrerPolicy: 'same-origin',
+ fetchType: 'descendant-static' },
+ 'Cross-origin static import with "same-origin" referrer policy.');
+
+// Tests for dynamic imports.
+//
+// Dynamic imports should inherit the containing script's referrer policy if
+// set, and use the default referrer policy otherwise, when using the
+// containing script's URL as the referrer.
+//
+// [Current document]
+// --(open)--> [Window]
+// --(new SharedWorker)--> [SharedWorker] whose referrer policy is
+// |workerReferrerPolicy|.
+// --(dynamic import)--> [Script] should respect |workerReferrerPolicy| when
+// using [SharedWorker]'s URL as the referrer.
+
+import_referrer_test(
+ { scriptURL: 'dynamic-import-same-origin-referrer-checker-worker.js',
+ workerReferrerPolicy: 'no-referrer',
+ fetchType: 'descendant-dynamic' },
+ 'Same-origin dynamic import with "no-referrer" referrer policy.');
+
+import_referrer_test(
+ { scriptURL: 'dynamic-import-same-origin-referrer-checker-worker.js',
+ workerReferrerPolicy: 'origin',
+ fetchType: 'descendant-dynamic' },
+ 'Same-origin dynamic import with "origin" referrer policy.');
+
+import_referrer_test(
+ { scriptURL: 'dynamic-import-same-origin-referrer-checker-worker.js',
+ workerReferrerPolicy: 'same-origin',
+ fetchType: 'descendant-dynamic' },
+ 'Same-origin dynamic import with "same-origin" referrer policy.');
+
+import_referrer_test(
+ { scriptURL: 'dynamic-import-remote-origin-referrer-checker-worker.sub.js',
+ workerReferrerPolicy: 'no-referrer',
+ fetchType: 'descendant-dynamic' },
+ 'Cross-origin dynamic import with "no-referrer" referrer policy.');
+
+import_referrer_test(
+ { scriptURL: 'dynamic-import-remote-origin-referrer-checker-worker.sub.js',
+ workerReferrerPolicy: 'origin',
+ fetchType: 'descendant-dynamic' },
+ 'Cross-origin dynamic import with "origin" referrer policy.');
+
+import_referrer_test(
+ { scriptURL: 'dynamic-import-remote-origin-referrer-checker-worker.sub.js',
+ workerReferrerPolicy: 'same-origin',
+ fetchType: 'descendant-dynamic' },
+ 'Cross-origin dynamic import with "same-origin" referrer policy.');
+
+</script>
diff --git a/testing/web-platform/tests/workers/modules/shared-worker-import.window.js b/testing/web-platform/tests/workers/modules/shared-worker-import.window.js
new file mode 100644
index 0000000000..5be49ec125
--- /dev/null
+++ b/testing/web-platform/tests/workers/modules/shared-worker-import.window.js
@@ -0,0 +1,22 @@
+// META: script=/workers/modules/resources/import-test-cases.js
+
+// Starts a shared worker for |testCase.scriptURL| and waits until the list
+// of imported modules is sent from the worker. Passes if the list is equal to
+// |testCase.expectation|.
+function import_test(testCase) {
+ promise_test(async () => {
+ const worker = new SharedWorker(testCase.scriptURL, {type: 'module'});
+ worker.port.postMessage('Send message for tests from main script.');
+ const msgEvent = await new Promise((resolve, reject) => {
+ worker.port.onmessage = resolve;
+ worker.onerror = error => {
+ const msg = error instanceof ErrorEvent ? error.message
+ : 'unknown error';
+ reject(msg);
+ };
+ });
+ assert_array_equals(msgEvent.data, testCase.expectation);
+ }, testCase.description);
+}
+
+testCases.forEach(import_test);
diff --git a/testing/web-platform/tests/workers/modules/shared-worker-options-credentials.html b/testing/web-platform/tests/workers/modules/shared-worker-options-credentials.html
new file mode 100644
index 0000000000..221dd01a37
--- /dev/null
+++ b/testing/web-platform/tests/workers/modules/shared-worker-options-credentials.html
@@ -0,0 +1,309 @@
+<!DOCTYPE html>
+<title>SharedWorker: WorkerOptions 'credentials'</title>
+<meta name="timeout" content="long">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/get-host-info.sub.js"></script>
+<script>
+host_info = get_host_info();
+
+// Determines the expected cookie value to be reported by a shared worker
+// based on the given option. The worker reports an empty string as the actual
+// cookie value if the cookie wasn't sent to the server. Otherwise, it's the
+// value set by the headers file:
+// "shared-worker-options-credentials.html.headers"
+function DetermineExpectedCookieValue(options, config) {
+ // Valid WorkerOptions and test config checking.
+ if (config.origin !== 'same' && config.origin !== 'remote')
+ assert_unreached('Invalid config.origin was specified: ' + config.origin);
+ if (options.credentials && options.credentials !== 'omit' &&
+ options.credentials !== 'same-origin' &&
+ options.credentials !== 'include') {
+ assert_unreached('Invalid credentials option was specified: ' +
+ options.credentials);
+ }
+ if (options.type !== 'classic' && options.type !== 'module')
+ assert_unreached('Invalid type option was specified: ' + options.type);
+
+ if (options.type === 'classic')
+ return (config.origin === 'same') ? '1' : '';
+
+ if (options.credentials === 'omit')
+ return '';
+ else if (options.credentials === 'include')
+ return '1';
+ else
+ return (config.origin === 'same') ? '1' : '';
+}
+
+// Runs a credentials test with the given WorkerOptions.
+//
+// |options| is a WorkerOptions dict.
+// |config| has options as follows:
+//
+// config = {
+// fetchType: 'top-level' or 'descendant-static' or 'descendant-dynamic'
+// origin: 'remote' or 'same'
+// };
+//
+// - |config.fetchType| indicates the type of script to load for the test.
+// - |config.origin| indicates same-origin-ness of the script to load.
+function credentials_test(options, config, description) {
+ promise_test(async () => {
+ let workerURL, origin = config.origin;
+ if (config.fetchType === 'top-level') {
+ workerURL = 'resources/postmessage-credentials.py';
+ } else if (config.fetchType === 'descendant-static') {
+ workerURL =
+ `resources/static-import-${origin}-origin-credentials-checker-worker.${origin === 'same' ? '' : 'sub.'}js`;
+ } else if (config.fetchType === 'descendant-dynamic') {
+ workerURL =
+ `resources/dynamic-import-${origin}-origin-credentials-checker-worker.${origin === 'same' ? '' : 'sub.'}js`;
+ } else {
+ assert_unreached('Invalid config.fetchType: ' + config.fetchType);
+ }
+
+ // Name idetically for each test cases so that it connects to the shared
+ // worker with specified type and credentials.
+ options.name = `${options.type}_${options.credentials || 'default'}_${config.fetchType}_${config.origin}`;
+
+ const worker = new SharedWorker(workerURL, options);
+
+ // Wait until the worker sends the actual cookie value.
+ const msg_event = await new Promise(resolve => worker.port.onmessage = resolve);
+
+ const expectedCookieValue = DetermineExpectedCookieValue(options, config);
+ assert_equals(msg_event.data, expectedCookieValue);
+ }, description);
+}
+
+function init() {
+ // Same-origin cookie is set up in the .headers file in this directory.
+ promise_test(async () => {
+ return fetch(
+ `${host_info.HTTP_REMOTE_ORIGIN}/cookies/resources/set-cookie.py?name=COOKIE_NAME&path=/workers/modules/`,
+ {
+ mode: 'no-cors',
+ credentials: 'include'
+ });
+ }, 'Test initialization: setting up cross-origin cookie');
+}
+
+init();
+
+// Tests for module workers.
+
+credentials_test(
+ { type: 'module' },
+ { fetchType: 'top-level', origin: 'same' },
+ 'new SharedWorker() with type=module and default credentials option ' +
+ 'should behave as credentials=same-origin and send the credentials');
+
+credentials_test(
+ { credentials: 'omit', type: 'module' },
+ { fetchType: 'top-level', origin: 'same' },
+ 'new SharedWorker() with type=module and credentials=omit should not ' +
+ 'send the credentials');
+
+credentials_test(
+ { credentials: 'same-origin', type: 'module' },
+ { fetchType: 'top-level', origin: 'same' },
+ 'new SharedWorker() with type=module and credentials=same-origin should ' +
+ 'send the credentials');
+
+credentials_test(
+ { credentials: 'include', type: 'module' },
+ { fetchType: 'top-level', origin: 'same' },
+ 'new SharedWorker() with type=module and credentials=include should send ' +
+ 'the credentials');
+
+// Tests for module worker static imports.
+
+credentials_test(
+ { type: 'module' },
+ { fetchType: 'descendant-static', origin: 'same' },
+ 'new SharedWorker() with type=module and default credentials option ' +
+ 'should behave as credentials=same-origin and send the credentials for ' +
+ 'same-origin static imports');
+
+credentials_test(
+ { credentials: 'omit', type: 'module' },
+ { fetchType: 'descendant-static', origin: 'same' },
+ 'new SharedWorker() with type=module and credentials=omit should not ' +
+ 'send the credentials for same-origin static imports');
+
+credentials_test(
+ { credentials: 'same-origin', type: 'module' },
+ { fetchType: 'descendant-static', origin: 'same' },
+ 'new SharedWorker() with type=module and credentials=same-origin should ' +
+ 'send the credentials for same-origin static imports');
+
+credentials_test(
+ { credentials: 'include', type: 'module' },
+ { fetchType: 'descendant-static', origin: 'same' },
+ 'new SharedWorker() with type=module and credentials=include should send ' +
+ 'the credentials for same-origin static imports');
+
+credentials_test(
+ { type: 'module' },
+ { fetchType: 'descendant-static', origin: 'remote' },
+ 'new SharedWorker() with type=module and default credentials option ' +
+ 'should behave as credentials=same-origin and not send the credentials ' +
+ 'for cross-origin static imports');
+
+credentials_test(
+ { credentials: 'omit', type: 'module' },
+ { fetchType: 'descendant-static', origin: 'remote' },
+ 'new SharedWorker() with type=module and credentials=omit should not ' +
+ 'send the credentials for cross-origin static imports');
+
+credentials_test(
+ { credentials: 'same-origin', type: 'module' },
+ { fetchType: 'descendant-static', origin: 'remote' },
+ 'new SharedWorker() with type=module and credentials=same-origin should ' +
+ 'not send the credentials for cross-origin static imports');
+
+credentials_test(
+ { credentials: 'include', type: 'module' },
+ { fetchType: 'descendant-static', origin: 'remote' },
+ 'new SharedWorker() with type=module and credentials=include should send ' +
+ 'the credentials for cross-origin static imports');
+
+// Tests for module worker dynamic imports.
+
+credentials_test(
+ { type: 'module' },
+ { fetchType: 'descendant-dynamic', origin: 'same' },
+ 'new SharedWorker() with type=module and default credentials option ' +
+ 'should behave as credentials=same-origin and send the credentials for ' +
+ 'same-origin dynamic imports');
+
+credentials_test(
+ { credentials: 'omit', type: 'module' },
+ { fetchType: 'descendant-dynamic', origin: 'same' },
+ 'new SharedWorker() with type=module and credentials=omit should not ' +
+ 'send the credentials for same-origin dynamic imports');
+
+credentials_test(
+ { credentials: 'same-origin', type: 'module' },
+ { fetchType: 'descendant-dynamic', origin: 'same' },
+ 'new SharedWorker() with type=module and credentials=same-origin should ' +
+ 'send the credentials for same-origin dynamic imports');
+
+credentials_test(
+ { credentials: 'include', type: 'module' },
+ { fetchType: 'descendant-dynamic', origin: 'same' },
+ 'new SharedWorker() with type=module and credentials=include should send ' +
+ 'the credentials for same-origin dynamic imports');
+
+credentials_test(
+ { type: 'module'},
+ { fetchType: 'descendant-dynamic', origin: 'remote' },
+ 'new SharedWorker() with type=module and default credentials option ' +
+ 'should behave as credentials=same-origin and not send the credentials ' +
+ 'for cross-origin dynamic imports');
+
+credentials_test(
+ { credentials: 'omit', type: 'module' },
+ { fetchType: 'descendant-dynamic', origin: 'remote' },
+ 'new SharedWorker() with type=module and credentials=omit should not ' +
+ 'send the credentials for cross-origin dynamic imports');
+
+credentials_test(
+ { credentials: 'same-origin', type: 'module' },
+ { fetchType: 'descendant-dynamic', origin: 'remote' },
+ 'new SharedWorker() with type=module and credentials=same-origin should ' +
+ 'not send the credentials for cross-origin dynamic imports');
+
+credentials_test(
+ { credentials: 'include', type: 'module' },
+ { fetchType: 'descendant-dynamic', origin: 'remote' },
+ 'new SharedWorker() with type=module and credentials=include should send ' +
+ 'the credentials for cross-origin dynamic imports');
+
+// Tests for classic workers.
+// TODO(domfarolino): Maybe move classic worker tests up a directory?
+
+credentials_test(
+ { type: 'classic' },
+ { fetchType: 'top-level', origin: 'same' },
+ 'new SharedWorker() with type=classic should always send the credentials ' +
+ 'regardless of the credentials option (default).');
+
+credentials_test(
+ { credentials: 'omit', type: 'classic' },
+ { fetchType: 'top-level', origin: 'same' },
+ 'new SharedWorker() with type=classic should always send the credentials ' +
+ 'regardless of the credentials option (omit).');
+
+credentials_test(
+ { credentials: 'same-origin', type: 'classic' },
+ { fetchType: 'top-level', origin: 'same' },
+ 'new SharedWorker() with type=classic should always send the credentials ' +
+ 'regardless of the credentials option (same-origin).');
+
+credentials_test(
+ { credentials: 'include', type: 'classic' },
+ { fetchType: 'top-level', origin: 'same' },
+ 'new SharedWorker() with type=classic should always send the credentials ' +
+ 'regardless of the credentials option (include).');
+
+// Tests for classic worker dynamic imports.
+
+credentials_test(
+ { type: 'classic' },
+ { fetchType: 'descendant-dynamic', origin: 'same' },
+ 'new SharedWorker() with type=classic should always send the credentials ' +
+ 'for same-origin dynamic imports regardless of the credentials option ' +
+ '(default).');
+
+credentials_test(
+ { credentials: 'omit', type: 'classic' },
+ { fetchType: 'descendant-dynamic', origin: 'same' },
+ 'new SharedWorker() with type=classic should always send the credentials ' +
+ 'for same-origin dynamic imports regardless of the credentials option ' +
+ '(omit).');
+
+credentials_test(
+ { credentials: 'same-origin', type: 'classic' },
+ { fetchType: 'descendant-dynamic', origin: 'same' },
+ 'new SharedWorker() with type=classic should always send the credentials ' +
+ 'for same-origin dynamic imports regardless of the credentials option ' +
+ '(same-origin).');
+
+credentials_test(
+ { credentials: 'include', type: 'classic' },
+ { fetchType: 'descendant-dynamic', origin: 'same' },
+ 'new SharedWorker() with type=classic should always send the credentials ' +
+ 'for same-origin dynamic imports regardless of the credentials option ' +
+ '(include).');
+
+credentials_test(
+ { type: 'classic' },
+ { fetchType: 'descendant-dynamic', origin: 'remote' },
+ 'new SharedWorker() with type=classic should never send the credentials ' +
+ 'for cross-origin dynamic imports regardless of the credentials option ' +
+ '(default).');
+
+credentials_test(
+ { credentials: 'omit', type: 'classic' },
+ { fetchType: 'descendant-dynamic', origin: 'remote' },
+ 'new SharedWorker() with type=classic should never send the credentials ' +
+ 'for cross-origin dynamic imports regardless of the credentials option ' +
+ '(omit).');
+
+credentials_test(
+ { credentials: 'same-origin', type: 'classic' },
+ { fetchType: 'descendant-dynamic', origin: 'remote' },
+ 'new SharedWorker() with type=classic should never send the credentials ' +
+ 'for cross-origin dynamic imports regardless of the credentials option ' +
+ '(same-origin).');
+
+credentials_test(
+ { credentials: 'include', type: 'classic' },
+ { fetchType: 'descendant-dynamic', origin: 'remote' },
+ 'new SharedWorker() with type=classic should never send the credentials ' +
+ 'for cross-origin dynamic imports regardless of the credentials option ' +
+ '(include).');
+
+</script>
diff --git a/testing/web-platform/tests/workers/modules/shared-worker-options-credentials.html.headers b/testing/web-platform/tests/workers/modules/shared-worker-options-credentials.html.headers
new file mode 100644
index 0000000000..8da851ab73
--- /dev/null
+++ b/testing/web-platform/tests/workers/modules/shared-worker-options-credentials.html.headers
@@ -0,0 +1,2 @@
+Set-Cookie: COOKIE_NAME=1
+Access-Control-Allow-Credentials: true
diff --git a/testing/web-platform/tests/workers/modules/shared-worker-options-type.html b/testing/web-platform/tests/workers/modules/shared-worker-options-type.html
new file mode 100644
index 0000000000..f62eeeb76b
--- /dev/null
+++ b/testing/web-platform/tests/workers/modules/shared-worker-options-type.html
@@ -0,0 +1,48 @@
+<!DOCTYPE html>
+<title>SharedWorker: WorkerOptions 'type'</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+
+promise_test(() => {
+ const worker = new SharedWorker('resources/post-message-on-load-worker.js',
+ 'default');
+ return new Promise(resolve => worker.port.onmessage = resolve)
+ .then(msg_event => assert_equals(msg_event.data, 'LOADED'));
+}, 'Test worker construction with the default worker type.');
+
+promise_test(() => {
+ const worker = new SharedWorker('resources/post-message-on-load-worker.js',
+ { name: 'classic', type: 'classic' });
+ return new Promise(resolve => worker.port.onmessage = resolve)
+ .then(msg_event => assert_equals(msg_event.data, 'LOADED'));
+}, 'Test worker construction with the "classic" worker type.');
+
+promise_test(() => {
+ const worker = new SharedWorker('resources/post-message-on-load-worker.js',
+ { name: 'module', type: 'module' });
+ return new Promise(resolve => worker.port.onmessage = resolve)
+ .then(msg_event => assert_equals(msg_event.data, 'LOADED'));
+}, 'Test worker construction with the "module" worker type.');
+
+test(() => {
+ assert_throws_js(
+ TypeError,
+ () => {
+ new SharedWorker('resources/post-message-on-load-worker.js',
+ { name: 'blank', type : '' });
+ },
+ 'Worker construction with an empty type should throw an exception');
+}, 'Test worker construction with an empty worker type.');
+
+test(() => {
+ assert_throws_js(
+ TypeError,
+ () => {
+ new SharedWorker('resources/post-message-on-load-worker.js',
+ { name: 'unknown', type : 'unknown' });
+ },
+ 'Worker construction with an unknown type should throw an exception');
+}, 'Test worker construction with an unknown worker type.');
+
+</script>
diff --git a/testing/web-platform/tests/workers/modules/shared-worker-parse-error-failure.html b/testing/web-platform/tests/workers/modules/shared-worker-parse-error-failure.html
new file mode 100644
index 0000000000..8f63d5f37a
--- /dev/null
+++ b/testing/web-platform/tests/workers/modules/shared-worker-parse-error-failure.html
@@ -0,0 +1,49 @@
+<!DOCTYPE html>
+<title>SharedWorker: parse error failure</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../support/check-error-arguments.js"></script>
+<script>
+
+// Check if module shared worker is supported.
+// In this test scope, we only use simple non-nested static import as a feature
+// of module shared worker, so we only check if static import is supported.
+//
+// This check is necessary to appropriately test parse error handling because
+// we need to distingusih the parse error invoked by unsupported "import" in
+// the top-level script from the parse error invoked by the statically imported
+// script which is the one we want to check in this test.
+promise_setup(async () => {
+ const scriptURL = 'resources/static-import-worker.js';
+ const worker = new SharedWorker(scriptURL, { type: 'module' });
+ const supportsModuleWorkers = await new Promise((resolve, reject) => {
+ worker.port.onmessage = e => {
+ resolve(e.data.length == 1 && e.data[0] == 'export-on-load-script.js');
+ };
+ worker.onerror = () => resolve(false);
+ });
+ assert_implements(
+ supportsModuleWorkers,
+ "Static import must be supported on module shared worker to run this test."
+ );
+});
+
+promise_test(async () => {
+ const scriptURL = 'resources/syntax-error.js';
+ const worker = new SharedWorker(scriptURL, { type: 'module' });
+ const args = await new Promise(resolve =>
+ worker.onerror = (...args) => resolve(args));
+ window.checkErrorArguments(args);
+}, 'Module shared worker construction for script with syntax error should ' +
+ 'dispatch an event named error.');
+
+promise_test(async () => {
+ const scriptURL = 'resources/static-import-syntax-error.js';
+ const worker = new SharedWorker(scriptURL, { type: 'module' });
+ const args = await new Promise(resolve =>
+ worker.onerror = (...args) => resolve(args));
+ window.checkErrorArguments(args);
+}, 'Static import on module shared worker for script with syntax error ' +
+ 'should dispatch an event named error.');
+
+</script>