diff options
Diffstat (limited to 'testing/web-platform/tests/workers/modules')
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> |