SharedWorker: Add shared-worker-import-referrer.html WPT

This CL adds shared-worker-import-referrer.html for web-platform-test.

Bug: 824646
Change-Id: I3031e34f1181303f7a0455974b3c614340d939f8
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2037275
Commit-Queue: Eriko Kurimoto <elkurin@google.com>
Reviewed-by: Hiroki Nakagawa <nhiroki@chromium.org>
Cr-Commit-Position: refs/heads/master@{#738474}
diff --git a/workers/modules/dedicated-worker-import-referrer.html b/workers/modules/dedicated-worker-import-referrer.html
index e13c0e2..32d3b3f 100644
--- a/workers/modules/dedicated-worker-import-referrer.html
+++ b/workers/modules/dedicated-worker-import-referrer.html
@@ -124,21 +124,21 @@
 //                     using [Window]'s URL as the referrer.
 
 import_referrer_test(
-    { scriptURL: 'referrer-checker.py',
+    { 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: 'referrer-checker.py',
+    { 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: 'referrer-checker.py',
+    { scriptURL: 'postmessage-referrer-checker.py',
       windowReferrerPolicy: 'same-origin',
       fetchType: 'top-level' },
     'Same-origin top-level module script loading with "same-origin" referrer ' +
diff --git a/workers/modules/resources/dynamic-import-remote-origin-referrer-checker-worker.sub.js b/workers/modules/resources/dynamic-import-remote-origin-referrer-checker-worker.sub.js
index e8f7b0a..3f281fa 100644
--- a/workers/modules/resources/dynamic-import-remote-origin-referrer-checker-worker.sub.js
+++ b/workers/modules/resources/dynamic-import-remote-origin-referrer-checker-worker.sub.js
@@ -1,3 +1,16 @@
 // Import a remote origin script.
-import('https://{{domains[www1]}}:{{ports[https][0]}}/workers/modules/resources/referrer-checker.py')
-    .catch(error => postMessage(`Import failed: ${error}`));
+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/workers/modules/resources/dynamic-import-same-origin-referrer-checker-worker.js b/workers/modules/resources/dynamic-import-same-origin-referrer-checker-worker.js
index a1c4eea..56f8a6f 100644
--- a/workers/modules/resources/dynamic-import-same-origin-referrer-checker-worker.js
+++ b/workers/modules/resources/dynamic-import-same-origin-referrer-checker-worker.js
@@ -1,2 +1,15 @@
-import('./referrer-checker.py')
-    .catch(error => postMessage(`Import failed: ${error}`));
+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/workers/modules/resources/referrer-checker.py b/workers/modules/resources/export-referrer-checker.py
similarity index 84%
rename from workers/modules/resources/referrer-checker.py
rename to workers/modules/resources/export-referrer-checker.py
index 83a9003..2928d28 100644
--- a/workers/modules/resources/referrer-checker.py
+++ b/workers/modules/resources/export-referrer-checker.py
@@ -6,4 +6,4 @@
                         ("Access-Control-Allow-Origin", "*")]
 
     return (200, response_headers,
-            "postMessage('"+referrer+"')")
+            "export const referrer = '"+referrer+"';")
diff --git a/workers/modules/resources/new-shared-worker-window.html b/workers/modules/resources/new-shared-worker-window.html
new file mode 100644
index 0000000..a7fee4d
--- /dev/null
+++ b/workers/modules/resources/new-shared-worker-window.html
@@ -0,0 +1,18 @@
+<!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, { 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/workers/modules/resources/postmessage-referrer-checker.py b/workers/modules/resources/postmessage-referrer-checker.py
new file mode 100644
index 0000000..f926834
--- /dev/null
+++ b/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("referer", "")
+
+    response_headers = [("Content-Type", "text/javascript"),
+                        ("Access-Control-Allow-Origin", "*")]
+
+    return (200, response_headers,
+            "if ('DedicatedWorkerGlobalScope' in self &&" +
+            "    self instanceof DedicatedWorkerGlobalScope) {" +
+            "  postMessage('"+referrer+"');" +
+            "} else if (" +
+            "    'SharedWorkerGlobalScope' in self &&" +
+            "    self instanceof SharedWorkerGlobalScope) {" +
+            "  onconnect = e => e.ports[0].postMessage('"+referrer+"');" +
+            "}")
diff --git a/workers/modules/resources/static-import-remote-origin-referrer-checker-worker.sub.js b/workers/modules/resources/static-import-remote-origin-referrer-checker-worker.sub.js
index 1722fc9..45fc549 100644
--- a/workers/modules/resources/static-import-remote-origin-referrer-checker-worker.sub.js
+++ b/workers/modules/resources/static-import-remote-origin-referrer-checker-worker.sub.js
@@ -1,2 +1,12 @@
 // Import a remote origin script.
-import 'https://{{domains[www1]}}:{{ports[https][0]}}/workers/modules/resources/referrer-checker.py';
+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/workers/modules/resources/static-import-same-origin-referrer-checker-worker.js b/workers/modules/resources/static-import-same-origin-referrer-checker-worker.js
index 7d564f9..ffcab9e 100644
--- a/workers/modules/resources/static-import-same-origin-referrer-checker-worker.js
+++ b/workers/modules/resources/static-import-same-origin-referrer-checker-worker.js
@@ -1 +1,11 @@
-import './referrer-checker.py';
+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/workers/modules/shared-worker-import-referrer.html b/workers/modules/shared-worker-import-referrer.html
new file mode 100644
index 0000000..abd4b41
--- /dev/null
+++ b/workers/modules/shared-worker-import-referrer.html
@@ -0,0 +1,245 @@
+<!DOCTYPE html>
+<title>SharedWorker: Referrer</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;
+}
+
+// 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);
+    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-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>