Remove BroadcastChannel dependency from speculation-rules (#33032)

diff --git a/speculation-rules/prerender/about-blank-iframes.html b/speculation-rules/prerender/about-blank-iframes.html
index a35d188..9cc0ab3 100644
--- a/speculation-rules/prerender/about-blank-iframes.html
+++ b/speculation-rules/prerender/about-blank-iframes.html
@@ -7,6 +7,7 @@
 <meta name="timeout" content="long">
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
+<script src="/common/utils.js"></script>
 <script src="resources/utils.js"></script>
 <body>
 <script>
@@ -14,7 +15,8 @@
 setup(() => assertSpeculationRulesIsSupported());
 
 promise_test(async t => {
-  const channel = new BroadcastChannel('test-channel');
+  const uid = token();
+  const channel = new PrerenderChannel('test-channel', uid);
 
   const gotMessage = new Promise(resolve => {
     channel.addEventListener('message', e => {
@@ -23,7 +25,7 @@
   });
 
   // Make the window to start the prerender.
-  const url = `resources/about-blank-iframes.html`;
+  const url = `resources/about-blank-iframes.html?uid=${uid}`;
   window.open(url, '_blank', 'noopener');
 
   const msg = await gotMessage;
diff --git a/speculation-rules/prerender/activation-start.html b/speculation-rules/prerender/activation-start.html
index 773a99b..fda6de1 100644
--- a/speculation-rules/prerender/activation-start.html
+++ b/speculation-rules/prerender/activation-start.html
@@ -3,21 +3,22 @@
 <meta name="timeout" content="long">
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
+<script src="/common/utils.js"></script>
 <script src="resources/utils.js"></script>
 <body>
 <script>
 
 setup(() => assertSpeculationRulesIsSupported());
-
 promise_test(async t => {
-  const testChannel = new BroadcastChannel('test-channel');
+  const uid = token();
+  const testChannel = new PrerenderChannel('test-channel', uid);
   t.add_cleanup(() => {
     testChannel.close();
   });
   const gotMessage = new Promise(resolve => {
     testChannel.addEventListener('message', e => resolve(e.data), {once: true});
   });
-  window.open('resources/activation-start.html', '_blank', 'noopener');
+  window.open(`resources/activation-start.html?uid=${uid}`, '_blank', 'noopener');
   assert_equals(await gotMessage, 'Done');
 }, 'PerformanceNavigationTiming.activationStart in prerendered page');
 
diff --git a/speculation-rules/prerender/cross-origin-isolated.https.html b/speculation-rules/prerender/cross-origin-isolated.https.html
index ba41ced..01dafe0 100644
--- a/speculation-rules/prerender/cross-origin-isolated.https.html
+++ b/speculation-rules/prerender/cross-origin-isolated.https.html
@@ -3,6 +3,7 @@
 <meta name="timeout" content="long">
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
+<script src="/common/utils.js"></script>
 <script src="resources/utils.js"></script>
 <body>
 <script>
@@ -10,7 +11,8 @@
 setup(() => assertSpeculationRulesIsSupported());
 
 promise_test(async t => {
-  const testChannel = new BroadcastChannel('test-channel');
+  const uid = token();
+  const testChannel = new PrerenderChannel('test-channel', uid);
   t.add_cleanup(() => {
     testChannel.close();
   });
@@ -18,7 +20,7 @@
     testChannel.addEventListener('message', e => resolve(e.data), {once: true});
   });
 
-  startPrerendering('resources/cross-origin-isolated.https.html');
+  startPrerendering(`resources/cross-origin-isolated.https.html?uid=${uid}`);
   assert_true(await gotMessage);
 }, 'Allow crossOriginIsolated in prerendered page');
 
diff --git a/speculation-rules/prerender/iframe-added-post-activation.html b/speculation-rules/prerender/iframe-added-post-activation.html
index aeec5fc..c4263db 100644
--- a/speculation-rules/prerender/iframe-added-post-activation.html
+++ b/speculation-rules/prerender/iframe-added-post-activation.html
@@ -7,6 +7,7 @@
 <meta name="timeout" content="long">
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
+<script src="/common/utils.js"></script>
 <script src="resources/utils.js"></script>
 <body>
 <script>
@@ -14,12 +15,13 @@
 setup(() => assertSpeculationRulesIsSupported());
 
 promise_test(async t => {
-  const channel = new BroadcastChannel('test-channel');
+  const uid = token();
+  const channel = new PrerenderChannel('test-channel', uid);
   const messageQueue = new BroadcastMessageQueue(channel);
   t.add_cleanup(_ => channel.close());
 
   // Make the window to start the prerender.
-  const url = `resources/iframe-added-post-activation.html`;
+  const url = `resources/iframe-added-post-activation.html?uid=${uid}`;
   window.open(url, '_blank', 'noopener');
 
   // Wait for done.
diff --git a/speculation-rules/prerender/resources/about-blank-iframes.html b/speculation-rules/prerender/resources/about-blank-iframes.html
index 4cac6d6..db99d58 100644
--- a/speculation-rules/prerender/resources/about-blank-iframes.html
+++ b/speculation-rules/prerender/resources/about-blank-iframes.html
@@ -85,7 +85,7 @@
   });
 
   // Ask to activate.
-  const prerenderChannel = new BroadcastChannel('prerender-channel');
+  const prerenderChannel = new PrerenderChannel('prerender-channel');
   prerenderChannel.postMessage('readyToActivate');
 
   // Test document.prerendering post-activation for each document.
@@ -104,7 +104,7 @@
 } else {
   // For the prerendering page, run main() then message the test page with the
   // result.
-  const testChannel = new BroadcastChannel('test-channel');
+  const testChannel = new PrerenderChannel('test-channel');
   main().then(() => {
     testChannel.postMessage('PASS');
   }).catch((e) => {
diff --git a/speculation-rules/prerender/resources/activation-start.html b/speculation-rules/prerender/resources/activation-start.html
index 262a1ae..456f15f 100644
--- a/speculation-rules/prerender/resources/activation-start.html
+++ b/speculation-rules/prerender/resources/activation-start.html
@@ -20,7 +20,7 @@
 }
 
 (async () => {
-  const testChannel = new BroadcastChannel('test-channel');
+  const testChannel = new PrerenderChannel('test-channel');
   const params = new URLSearchParams(location.search);
   const isPrerenderingMainFrame = params.has('prerendering') &&
                                   !params.has('iframe');
@@ -36,9 +36,9 @@
   // 3. The prerendering document opens the iframe document (isIframe).
   try {
     if (isInitiator) {
-      // We use a BroadcastChannel to receive a message from the prerendering
+      // We use a PrerenderChannel to receive a message from the prerendering
       // iframe.
-      const prerenderChannel = new BroadcastChannel('prerender-channel');
+      const prerenderChannel = new PrerenderChannel('prerender-channel');
       const prerendering_url = new URL(document.URL);
       prerendering_url.searchParams.set('prerendering', '');
       startPrerendering(prerendering_url);
@@ -80,7 +80,7 @@
       // Finishes the test.
       testChannel.postMessage('Done');
     } else if (isIframe) {
-      const prerenderChannel = new BroadcastChannel('prerender-channel');
+      const prerenderChannel = new PrerenderChannel('prerender-channel');
       const initial_value = getActivationStart();
       const activation_start_promise = new Promise(resolve => {
         document.addEventListener('prerenderingchange', () => {
diff --git a/speculation-rules/prerender/resources/cross-origin-isolated.https.html b/speculation-rules/prerender/resources/cross-origin-isolated.https.html
index 1118b99..4fdc6c9 100644
--- a/speculation-rules/prerender/resources/cross-origin-isolated.https.html
+++ b/speculation-rules/prerender/resources/cross-origin-isolated.https.html
@@ -8,7 +8,7 @@
   assert_equals(e.data.name, 'crossOriginIsolated');
   assert_true(e.data.value);
 
-  const testChannel = new BroadcastChannel('test-channel');
+  const testChannel = new PrerenderChannel('test-channel');
   testChannel.postMessage(self.crossOriginIsolated);
   testChannel.close();
 };
diff --git a/speculation-rules/prerender/resources/deferred-promise-utils.js b/speculation-rules/prerender/resources/deferred-promise-utils.js
index 02bd7ec..9a5cfb1 100644
--- a/speculation-rules/prerender/resources/deferred-promise-utils.js
+++ b/speculation-rules/prerender/resources/deferred-promise-utils.js
@@ -23,6 +23,9 @@
 class PrerenderEventCollector {
   constructor() {
     this.eventsSeen_ = [];
+    new PrerenderChannel('close').addEventListener('message', () => {
+      window.close();
+    });
   }
 
   // Adds an event to `eventsSeen_` along with the prerendering state of the
@@ -49,11 +52,9 @@
             })
         .finally(() => {
           // Used to communicate with the main test page.
-          const testChannel = new BroadcastChannel('test-channel');
+          const testChannel = new PrerenderChannel('test-channel');
           // Send the observed events back to the main test page.
           testChannel.postMessage(this.eventsSeen_);
-          testChannel.close();
-          window.close();
         });
     document.addEventListener('prerenderingchange', () => {
       this.addEvent('prerendering change');
@@ -63,10 +64,9 @@
     // resolves a promise without waiting for activation.
     setTimeout(() => {
       // Used to communicate with the initiator page.
-      const prerenderChannel = new BroadcastChannel('prerender-channel');
+      const prerenderChannel = new PrerenderChannel('prerender-channel');
       // Inform the initiator page that this page is ready to be activated.
       prerenderChannel.postMessage('readyToActivate');
-      prerenderChannel.close();
     }, 0);
   }
 }
diff --git a/speculation-rules/prerender/resources/deprecated-broadcast-channel.py b/speculation-rules/prerender/resources/deprecated-broadcast-channel.py
new file mode 100644
index 0000000..62631d9
--- /dev/null
+++ b/speculation-rules/prerender/resources/deprecated-broadcast-channel.py
@@ -0,0 +1,28 @@
+import json
+import time
+def main(request, response):
+    uid = request.GET.first(b"uid")
+    name = request.GET.first(b"name")
+    time.sleep(0.1)
+
+    messagesByName = []
+    if request.method == 'POST':
+        with request.server.stash.lock:
+            messages = request.server.stash.take(uid) or {}
+            if name in messages:
+                messagesByName = messages[name]
+
+            messagesByName.append(json.loads(request.body))
+            messages[name] = messagesByName
+            request.server.stash.put(uid, messages)
+        response.status = 204
+    else:
+        with request.server.stash.lock:
+            messages = request.server.stash.take(uid) or {}
+            if name in messages:
+                messagesByName = messages[name]
+
+            request.server.stash.put(uid, messages)
+            response.status = 200
+            response.headers['Content-Type'] = 'application/json'
+            response.content = json.dumps(messagesByName)
diff --git a/speculation-rules/prerender/resources/iframe-added-post-activation.html b/speculation-rules/prerender/resources/iframe-added-post-activation.html
index fb7aaa8..10a48df 100644
--- a/speculation-rules/prerender/resources/iframe-added-post-activation.html
+++ b/speculation-rules/prerender/resources/iframe-added-post-activation.html
@@ -29,7 +29,7 @@
   });
 
   // Ask to activate.
-  const prerenderChannel = new BroadcastChannel('prerender-channel');
+  const prerenderChannel = new PrerenderChannel('prerender-channel');
   prerenderChannel.postMessage('readyToActivate');
 
   // Check that document.prerendering is false in the iframe.
@@ -46,7 +46,7 @@
 } else {
   // For the prerendering page, run main() then message the test page with the
   // result.
-  const testChannel = new BroadcastChannel('test-channel');
+  const testChannel = new PrerenderChannel('test-channel');
   main().then(() => {
     testChannel.postMessage('PASS');
   }).catch((e) => {
diff --git a/speculation-rules/prerender/resources/notification.html b/speculation-rules/prerender/resources/notification.html
index f9502dd..03e36f0 100644
--- a/speculation-rules/prerender/resources/notification.html
+++ b/speculation-rules/prerender/resources/notification.html
@@ -13,9 +13,9 @@
   loadInitiatorPage();
 } else {
   // Used to communicate with the initiator page.
-  const prerenderChannel = new BroadcastChannel('prerender-channel');
+  const prerenderChannel = new PrerenderChannel('prerender-channel');
   // Used to communicate with the main test page.
-  const testChannel = new BroadcastChannel('test-channel');
+  const testChannel = new PrerenderChannel('test-channel');
 
   window.addEventListener('load', () => {
     // Inform the initiator page that this page is ready to be activated.
diff --git a/speculation-rules/prerender/resources/presentation-request.html b/speculation-rules/prerender/resources/presentation-request.html
index 5dc8d6a..6282955 100644
--- a/speculation-rules/prerender/resources/presentation-request.html
+++ b/speculation-rules/prerender/resources/presentation-request.html
@@ -6,7 +6,7 @@
 assert_true(document.prerendering);
 
 async function startPresentationRequest() {
-  const bc = new BroadcastChannel('prerender-channel');
+  const bc = new PrerenderChannel('prerender-channel');
   const presentationRequest = new PresentationRequest(
       'https://example.com/presentation.html');
 
diff --git a/speculation-rules/prerender/resources/storage-foundation-access.https.html b/speculation-rules/prerender/resources/storage-foundation-access.https.html
index 4ab8b06..db965fa9 100644
--- a/speculation-rules/prerender/resources/storage-foundation-access.https.html
+++ b/speculation-rules/prerender/resources/storage-foundation-access.https.html
@@ -20,7 +20,7 @@
 }
 
 async function readWriteTest() {
-  const bc = new BroadcastChannel('prerender-channel');
+  const bc = new PrerenderChannel('prerender-channel');
   assert_true(document.prerendering);
 
   let f = await storageFoundation.open('test_file');
diff --git a/speculation-rules/prerender/resources/utils.js b/speculation-rules/prerender/resources/utils.js
index 38aaff7..bbb9448 100644
--- a/speculation-rules/prerender/resources/utils.js
+++ b/speculation-rules/prerender/resources/utils.js
@@ -22,6 +22,41 @@
   document.head.appendChild(script);
 }
 
+class PrerenderChannel extends EventTarget {
+  #ids = new Set();
+  #url;
+  #active = true;
+
+  constructor(name, uid = new URLSearchParams(location.search).get('uid')) {
+    super();
+    this.#url = `/speculation-rules/prerender/resources/deprecated-broadcast-channel.py?name=${name}&uid=${uid}`;
+    (async() => {
+      while (this.#active) {
+        const messages = await (await fetch(this.#url)).json();
+        for (const {data, id} of messages) {
+          if (!this.#ids.has(id))
+            this.dispatchEvent(new MessageEvent('message', {data}));
+          this.#ids.add(id);
+        }
+      }
+    })();
+  }
+
+  close() {
+    this.#active = false;
+  }
+
+  set onmessage(m) {
+    this.addEventListener('message', m)
+  }
+
+  async postMessage(data) {
+    const id = new Date().valueOf();
+    this.#ids.add(id);
+    await fetch(this.#url, {method: 'POST', body: JSON.stringify({data, id})});
+  }
+}
+
 // Reads the value specified by `key` from the key-value store on the server.
 async function readValueFromServer(key) {
   const serverUrl = `${STORE_URL}?key=${key}`;
@@ -63,7 +98,7 @@
 // receives the 'readyToActivate' message.
 function loadInitiatorPage() {
   // Used to communicate with the prerendering page.
-  const prerenderChannel = new BroadcastChannel('prerender-channel');
+  const prerenderChannel = new PrerenderChannel('prerender-channel');
   window.addEventListener('unload', () => {
     prerenderChannel.close();
   });
@@ -90,7 +125,7 @@
   readyToActivate.then(() => {
     window.location = url.toString();
   }).catch(e => {
-    const testChannel = new BroadcastChannel('test-channel');
+    const testChannel = new PrerenderChannel('test-channel');
     testChannel.postMessage(
         `Failed to navigate the prerendered page: ${e.toString()}`);
     testChannel.close();
@@ -98,21 +133,21 @@
   });
 }
 
-// Returns messages received from the given BroadcastChannel
+// Returns messages received from the given PrerenderChannel
 // so that callers do not need to add their own event listeners.
 // nextMessage() returns a promise which resolves with the next message.
 //
 // Usage:
-//   const channel = new BroadcastChannel('channel-name');
+//   const channel = new PrerenderChannel('channel-name');
 //   const messageQueue = new BroadcastMessageQueue(channel);
 //   const message1 = await messageQueue.nextMessage();
 //   const message2 = await messageQueue.nextMessage();
 //   message1 and message2 are the messages received.
 class BroadcastMessageQueue {
-  constructor(broadcastChannel) {
+  constructor(c) {
     this.messages = [];
     this.resolveFunctions = [];
-    this.channel = broadcastChannel;
+    this.channel = c;
     this.channel.addEventListener('message', e => {
       if (this.resolveFunctions.length > 0) {
         const fn = this.resolveFunctions.shift();
diff --git a/speculation-rules/prerender/resources/web-database-access.html b/speculation-rules/prerender/resources/web-database-access.html
index 9974190..fb00852 100644
--- a/speculation-rules/prerender/resources/web-database-access.html
+++ b/speculation-rules/prerender/resources/web-database-access.html
@@ -1,9 +1,10 @@
 <!DOCTYPE html>
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
+<script src="utils.js"></script>
 <script>
 
-const bc = new BroadcastChannel('prerender-channel');
+const bc = new PrerenderChannel('prerender-channel');
 assert_true(document.prerendering);
 
 let result = "Success";
diff --git a/speculation-rules/prerender/resources/window-move.html b/speculation-rules/prerender/resources/window-move.html
index fdb1294..0c5888c 100644
--- a/speculation-rules/prerender/resources/window-move.html
+++ b/speculation-rules/prerender/resources/window-move.html
@@ -15,7 +15,7 @@
   try {
     func();
   } catch (e) {
-    const testChannel = new BroadcastChannel('test-channel');
+    const testChannel = new PrerenderChannel('test-channel');
     testChannel.postMessage({status: 'FAIL: ' + e});
   }
 }
@@ -50,7 +50,7 @@
       }
   );
 
-  const bc = new BroadcastChannel('test-channel');
+  const bc = new PrerenderChannel('test-channel');
   bc.postMessage({
     'status': 'PASS',
     'prevPosition': prevPosition,
diff --git a/speculation-rules/prerender/resources/window-open-during-prerendering.html b/speculation-rules/prerender/resources/window-open-during-prerendering.html
index 3a6ece5..a314d50 100644
--- a/speculation-rules/prerender/resources/window-open-during-prerendering.html
+++ b/speculation-rules/prerender/resources/window-open-during-prerendering.html
@@ -9,10 +9,10 @@
 
 function runAsTriggerPage() {
   assert_false(document.prerendering);
-  startPrerendering(location.href + '?prerendering');
+  startPrerendering(location.href + '&prerendering=true');
 
   // Close this window for cleanup after the prerendering page runs the test.
-  const bc = new BroadcastChannel('result');
+  const bc = new PrerenderChannel('result');
   bc.onmessage = e => window.close();
 }
 
@@ -23,7 +23,7 @@
   const win = window.open('empty.html', '_blank');
 
   // Send the result to the test runner page.
-  const bc = new BroadcastChannel('result');
+  const bc = new PrerenderChannel('result');
   if (win) {
     bc.postMessage('opened');
     win.close();
@@ -32,7 +32,7 @@
   }
 }
 
-if (location.search === '?prerendering') {
+if (new URLSearchParams(location.search).has('prerendering')) {
   runAsPrerenderingPage();
 } else {
   runAsTriggerPage();
diff --git a/speculation-rules/prerender/resources/window-open-in-prerenderingchange.html b/speculation-rules/prerender/resources/window-open-in-prerenderingchange.html
index 485b878..f32126f 100644
--- a/speculation-rules/prerender/resources/window-open-in-prerenderingchange.html
+++ b/speculation-rules/prerender/resources/window-open-in-prerenderingchange.html
@@ -14,12 +14,12 @@
   assert_false(document.prerendering);
 
   // Start prerendering.
-  const prerendering_url = location.href + '?prerendering';
+  const prerendering_url = location.href + '&prerendering=true';
   startPrerendering(prerendering_url);
 
   // Activate the prerendering page once it gets ready.
-  const bc = new BroadcastChannel('activation-ready');
-  bc.onmessage = () => window.location = prerendering_url;
+  const bc = new PrerenderChannel('activation-ready');
+  bc.onmessage = () => { window.location = prerendering_url };
 }
 
 // Runs as prerendeirng page. First this page waits for the load event and
@@ -32,10 +32,13 @@
     assert_true(document.prerendering);
 
     // Notify the trigger page of activation ready.
-    const bc = new BroadcastChannel('activation-ready');
+    const bc = new PrerenderChannel('activation-ready');
     bc.postMessage('ready for activation');
   }
 
+  new PrerenderChannel('close').addEventListener('message', () => {
+    window.close();
+  });
   document.onprerenderingchange = () => {
     assert_false(document.prerendering);
 
@@ -43,20 +46,17 @@
     const win = window.open('empty.html', '_blank');
 
     // Send the result to the test runner page.
-    const bc = new BroadcastChannel('result');
+    const bc = new PrerenderChannel('result');
     if (win) {
       bc.postMessage('opened');
       win.close();
     } else {
       bc.postMessage('failed to open');
     }
-
-    // Close this window for cleanup.
-    window.close();
   };
 }
 
-if (location.search === '?prerendering') {
+if (new URLSearchParams(location.search).has('prerendering')) {
   runAsPrerenderingPage();
 } else {
   runAsTriggerPage();
diff --git a/speculation-rules/prerender/resources/window-resize.html b/speculation-rules/prerender/resources/window-resize.html
index d8ae493..8b6172e 100644
--- a/speculation-rules/prerender/resources/window-resize.html
+++ b/speculation-rules/prerender/resources/window-resize.html
@@ -8,7 +8,7 @@
   try {
     func();
   } catch (e) {
-    const testChannel = new BroadcastChannel('test-channel');
+    const testChannel = new PrerenderChannel('test-channel');
     testChannel.postMessage({status: 'FAIL: ' + e});
   }
 }
@@ -52,7 +52,7 @@
     }
   });
 
-  const bc = new BroadcastChannel('test-channel');
+  const bc = new PrerenderChannel('test-channel');
   bc.postMessage({
     'status': 'PASS',
     'prevRect': prevRect,
diff --git a/speculation-rules/prerender/restriction-focus.html b/speculation-rules/prerender/restriction-focus.html
index 10df5b8..1149b8b 100644
--- a/speculation-rules/prerender/restriction-focus.html
+++ b/speculation-rules/prerender/restriction-focus.html
@@ -13,8 +13,6 @@
 setup(() => assertSpeculationRulesIsSupported());
 
 promise_test(async t => {
-  const bc = new BroadcastChannel('prerender-channel');
-
   document.getElementById('prerenderTextField').focus();
   assert_true(
       document.hasFocus(),
diff --git a/speculation-rules/prerender/restriction-notification.https.html b/speculation-rules/prerender/restriction-notification.https.html
index 964c408..e9d3ba2 100644
--- a/speculation-rules/prerender/restriction-notification.https.html
+++ b/speculation-rules/prerender/restriction-notification.https.html
@@ -11,6 +11,7 @@
 <script src="/resources/testharnessreport.js"></script>
 <script src="/resources/testdriver.js"></script>
 <script src="/resources/testdriver-vendor.js"></script>
+<script src="/common/utils.js"></script>
 <script src="resources/utils.js"></script>
 <body>
 <script>
@@ -18,7 +19,8 @@
 setup(() => assertSpeculationRulesIsSupported());
 
 promise_test(async t => {
-  const bc = new BroadcastChannel('test-channel');
+  const uid = token();
+  const bc = new PrerenderChannel('test-channel', uid);
   t.add_cleanup(_ => bc.close());
 
   await test_driver.set_permission({name: 'notifications'}, 'granted', true);
@@ -29,7 +31,7 @@
       once: true
     });
   });
-  const url = `resources/notification.html`;
+  const url = `resources/notification.html?uid=${uid}`;
   window.open(url, '_blank', 'noopener');
 
   const result = await gotMessage;
diff --git a/speculation-rules/prerender/restriction-presentation-request.https.html b/speculation-rules/prerender/restriction-presentation-request.https.html
index 75e55e2..5f77422 100644
--- a/speculation-rules/prerender/restriction-presentation-request.https.html
+++ b/speculation-rules/prerender/restriction-presentation-request.https.html
@@ -3,6 +3,7 @@
 <meta name="timeout" content="long">
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
+<script src="/common/utils.js"></script>
 <script src="resources/utils.js"></script>
 <body>
 <script>
@@ -10,7 +11,8 @@
 setup(() => assertSpeculationRulesIsSupported());
 
 promise_test(async t => {
-  const bc = new BroadcastChannel('prerender-channel');
+  const uid = token();
+  const bc = new PrerenderChannel('prerender-channel', uid);
 
   const gotMessage = new Promise(resolve => {
     bc.addEventListener('message', e => {
@@ -21,7 +23,7 @@
   });
 
   // Start prerendering a page that attempts to start presentation.
-  startPrerendering(`resources/presentation-request.html`);
+  startPrerendering(`resources/presentation-request.html?uid=${uid}`);
 
   const result = await gotMessage;
   assert_equals(result, 'request failed');
diff --git a/speculation-rules/prerender/restriction-window-move.html b/speculation-rules/prerender/restriction-window-move.html
index fca3b53..e801131 100644
--- a/speculation-rules/prerender/restriction-window-move.html
+++ b/speculation-rules/prerender/restriction-window-move.html
@@ -2,6 +2,7 @@
 <meta name="timeout" content="long">
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
+<script src="/common/utils.js"></script>
 <script src="resources/utils.js"></script>
 <body>
 <script>
@@ -12,7 +13,8 @@
 // See https://github.com/jeremyroman/alternate-loading-modes/issues/73.
 ['moveTo', 'moveBy'].forEach(moveFunc => {
   promise_test(async t => {
-    const bc = new BroadcastChannel('test-channel');
+    const uid = token();
+    const bc = new PrerenderChannel('test-channel', uid);
     t.add_cleanup(_ => bc.close());
 
     const gotMessage = new Promise(resolve => {
@@ -21,7 +23,7 @@
       }, {once: true});
     });
 
-    const url = `resources/window-move.html?move=${moveFunc}`;
+    const url = `resources/window-move.html?move=${moveFunc}&uid=${uid}`;
 
     // We have to open a new window to run the test, since a window that was
     // not created by window.open() cannot be moved.
diff --git a/speculation-rules/prerender/restriction-window-open.html b/speculation-rules/prerender/restriction-window-open.html
index 33ec5b1..5de23ef 100644
--- a/speculation-rules/prerender/restriction-window-open.html
+++ b/speculation-rules/prerender/restriction-window-open.html
@@ -2,6 +2,7 @@
 <meta name="timeout" content="long">
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
+<script src="/common/utils.js"></script>
 <script src="resources/utils.js"></script>
 <body>
 <script>
@@ -10,15 +11,17 @@
 
 function runTest(test_file, expectation, description) {
   promise_test(async t => {
+    const uid = token();
     // Run test in a new window for test isolation.
-    window.open(test_file, '_blank', 'noopener');
+    window.open(`${test_file}?uid=${uid}`, '_blank', 'noopener');
 
     // Wait until the prerendered page sends the result.
-    const bc = new BroadcastChannel('result');
-    const gotMessage = new Promise(r => bc.onmessage = e => r(e.data));
-    const result = await gotMessage;
-
-    assert_equals(await gotMessage, expectation);
+    const bc = new PrerenderChannel('result', uid);
+    t.add_cleanup(() => {
+      new PrerenderChannel('close', uid).postMessage('close');
+    })
+    const result = await new Promise(r => bc.addEventListener('message', e => r(e.data)));
+    assert_equals(result, expectation);
   }, description);
 }
 
diff --git a/speculation-rules/prerender/restriction-window-resize.html b/speculation-rules/prerender/restriction-window-resize.html
index 5f02ac6..20a71b4 100644
--- a/speculation-rules/prerender/restriction-window-resize.html
+++ b/speculation-rules/prerender/restriction-window-resize.html
@@ -2,6 +2,7 @@
 <meta name="timeout" content="long">
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
+<script src="/common/utils.js"></script>
 <script src="resources/utils.js"></script>
 <body>
 <script>
@@ -13,7 +14,8 @@
 ['resizeTo', 'resizeBy'].forEach(resizeFunc => {
   promise_test(
       async t => {
-        const bc = new BroadcastChannel('test-channel');
+        const uid = token();
+        const bc = new PrerenderChannel('test-channel', uid);
         t.add_cleanup(_ => bc.close());
 
         const gotMessage = new Promise(resolve => {
@@ -22,7 +24,7 @@
           }, {once: true});
         });
 
-        const url = `resources/window-resize.html?resize=${resizeFunc}`;
+        const url = `resources/window-resize.html?resize=${resizeFunc}&uid=${uid}`;
 
         // We have to open a new window to run the test, since a window that was
         // not created by window.open() cannot be resized.
diff --git a/speculation-rules/prerender/storage-foundation.https.html b/speculation-rules/prerender/storage-foundation.https.html
index ca1b936..6be77ba 100644
--- a/speculation-rules/prerender/storage-foundation.https.html
+++ b/speculation-rules/prerender/storage-foundation.https.html
@@ -3,6 +3,7 @@
 <meta name="timeout" content="long">
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
+<script src="/common/utils.js"></script>
 <script src="resources/utils.js"></script>
 <body>
 <script>
@@ -32,7 +33,8 @@
 }
 
 promise_test(async t => {
-  const bc = new BroadcastChannel('prerender-channel');
+  const uid = token();
+  const bc = new PrerenderChannel('prerender-channel', uid);
 
   const gotMessage = new Promise(resolve => {
     bc.addEventListener('message', e => {
diff --git a/speculation-rules/prerender/visibility-state.html b/speculation-rules/prerender/visibility-state.html
index 1affe54..f8d3ccd 100644
--- a/speculation-rules/prerender/visibility-state.html
+++ b/speculation-rules/prerender/visibility-state.html
@@ -3,6 +3,7 @@
 <meta name="timeout" content="long">
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
+<script src="/common/utils.js"></script>
 <script src="resources/utils.js"></script>
 <body>
 <script>
@@ -10,7 +11,8 @@
 setup(() => assertSpeculationRulesIsSupported());
 
 promise_test(async t => {
-  const bc = new BroadcastChannel('test-channel');
+  const uid = token();
+  const bc = new PrerenderChannel('test-channel', uid);
 
   const gotMessage = new Promise(resolve => {
     bc.addEventListener('message', e => {
@@ -19,7 +21,7 @@
       once: true
     });
   });
-  const url = `resources/visibility-state-check.html`;
+  const url = `resources/visibility-state-check.html?uid=${uid}`;
   window.open(url, '_blank', 'noopener');
 
   const result = await gotMessage;
@@ -47,6 +49,8 @@
     assert_equals(result[i].prerendering, expected[i].prerendering,
       `prerendering${i}`);
   }
+
+  new PrerenderChannel('close', uid).postMessage('')
 }, 'The visibilityState must be updated after prerendering.');
 
 </script>
diff --git a/speculation-rules/prerender/web-database.html b/speculation-rules/prerender/web-database.html
index 3e59c3c..3ef1141 100644
--- a/speculation-rules/prerender/web-database.html
+++ b/speculation-rules/prerender/web-database.html
@@ -3,6 +3,7 @@
 <meta name="timeout" content="long">
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
+<script src="/common/utils.js"></script>
 <script src="resources/utils.js"></script>
 <body>
 <script>
@@ -38,7 +39,8 @@
 }
 
 promise_test(async t => {
-  const bc = new BroadcastChannel('prerender-channel');
+  const uid = token();
+  const bc = new PrerenderChannel('prerender-channel', uid);
 
   const gotMessage = new Promise(resolve => {
     bc.addEventListener('message', e => {
@@ -53,7 +55,7 @@
         'primary page should be able to execute statements from Web Database.');
 
   // Start prerendering a page that attempts to access Web Database.
-  startPrerendering('resources/web-database-access.html');
+  startPrerendering(`resources/web-database-access.html?uid=${uid}`);
   const result = await gotMessage;
 
   assert_equals(