Update web-platform-tests/beacon from upstream
https://bugs.webkit.org/show_bug.cgi?id=213663

Reviewed by Darin Adler.

Update web-platform-tests/beacon from upstream b076c305a256e7fb7.

* web-platform-tests/beacon/*: Updated.


git-svn-id: http://svn.webkit.org/repository/webkit/trunk@263595 268f45cc-cd09-0410-ab3c-d52691b4dbfc
diff --git a/LayoutTests/imported/w3c/ChangeLog b/LayoutTests/imported/w3c/ChangeLog
index 048e5c9..5731f67 100644
--- a/LayoutTests/imported/w3c/ChangeLog
+++ b/LayoutTests/imported/w3c/ChangeLog
@@ -1,3 +1,14 @@
+2020-06-26  Chris Dumez  <cdumez@apple.com>
+
+        Update web-platform-tests/beacon from upstream
+        https://bugs.webkit.org/show_bug.cgi?id=213663
+
+        Reviewed by Darin Adler.
+
+        Update web-platform-tests/beacon from upstream b076c305a256e7fb7.
+
+        * web-platform-tests/beacon/*: Updated.
+
 2020-06-24  Sergio Villar Senin  <svillar@igalia.com>
 
         [WebXR] Unreviewed gardening. Updated expectations and new passing tests.
diff --git a/LayoutTests/imported/w3c/web-platform-tests/beacon/beacon-common.sub.js b/LayoutTests/imported/w3c/web-platform-tests/beacon/beacon-common.sub.js
index ae2f169..3635da7 100644
--- a/LayoutTests/imported/w3c/web-platform-tests/beacon/beacon-common.sub.js
+++ b/LayoutTests/imported/w3c/web-platform-tests/beacon/beacon-common.sub.js
@@ -16,33 +16,33 @@
 var maxPayload = (maxPayloadSize - 6) + ":" + Array(maxPayloadSize - 6).fill('*').join("")
 
 // Test case definitions.
-//      id: String containing the unique name of the test case.
+//      name: String containing the unique name of the test case.
 //      data: Payload object to send through sendbeacon.
-var noDataTest = { id: "NoData" };
-var nullDataTest = { id: "NullData", data: null };
-var undefinedDataTest = { id: "UndefinedData", data: undefined };
-var smallStringTest = { id: "SmallString", data: smallPayload };
-var mediumStringTest = { id: "MediumString", data: mediumPayload };
-var largeStringTest = { id: "LargeString", data: largePayload };
-var maxStringTest = { id: "MaxString", data: maxPayload };
-var emptyBlobTest = { id: "EmptyBlob", data: new Blob() };
-var smallBlobTest = { id: "SmallBlob", data: new Blob([smallPayload]) };
-var mediumBlobTest = { id: "MediumBlob", data: new Blob([mediumPayload]) };
-var largeBlobTest = { id: "LargeBlob", data: new Blob([largePayload]) };
-var maxBlobTest = { id: "MaxBlob", data: new Blob([maxPayload]) };
-var emptyBufferSourceTest = { id: "EmptyBufferSource", data: new Uint8Array() };
-var smallBufferSourceTest = { id: "SmallBufferSource", data: CreateArrayBufferFromPayload(smallPayload) };
-var mediumBufferSourceTest = { id: "MediumBufferSource", data: CreateArrayBufferFromPayload(mediumPayload) };
-var largeBufferSourceTest = { id: "LargeBufferSource", data: CreateArrayBufferFromPayload(largePayload) };
-var maxBufferSourceTest = { id: "MaxBufferSource", data: CreateArrayBufferFromPayload(maxPayload) };
-var emptyFormDataTest = { id: "EmptyFormData", data: CreateEmptyFormDataPayload() };
-var smallFormDataTest = { id: "SmallFormData", data: CreateFormDataFromPayload(smallPayload) };
-var mediumFormDataTest = { id: "MediumFormData", data: CreateFormDataFromPayload(mediumPayload) };
-var largeFormDataTest = { id: "LargeFormData", data: CreateFormDataFromPayload(largePayload) };
-var smallSafeContentTypeEncodedTest = { id: "SmallSafeContentTypeEncoded", data: new Blob([smallPayload], { type: 'application/x-www-form-urlencoded' }) };
-var smallSafeContentTypeFormTest = { id: "SmallSafeContentTypeForm", data: new FormData() };
-var smallSafeContentTypeTextTest = { id: "SmallSafeContentTypeText", data: new Blob([smallPayload], { type: 'text/plain' }) };
-var smallCORSContentTypeTextTest = { id: "SmallCORSContentTypeText", data: new Blob([smallPayload], { type: 'text/html' }) };
+var noDataTest = { name: "NoData" };
+var nullDataTest = { name: "NullData", data: null };
+var undefinedDataTest = { name: "UndefinedData", data: undefined };
+var smallStringTest = { name: "SmallString", data: smallPayload };
+var mediumStringTest = { name: "MediumString", data: mediumPayload };
+var largeStringTest = { name: "LargeString", data: largePayload };
+var maxStringTest = { name: "MaxString", data: maxPayload };
+var emptyBlobTest = { name: "EmptyBlob", data: new Blob() };
+var smallBlobTest = { name: "SmallBlob", data: new Blob([smallPayload]) };
+var mediumBlobTest = { name: "MediumBlob", data: new Blob([mediumPayload]) };
+var largeBlobTest = { name: "LargeBlob", data: new Blob([largePayload]) };
+var maxBlobTest = { name: "MaxBlob", data: new Blob([maxPayload]) };
+var emptyBufferSourceTest = { name: "EmptyBufferSource", data: new Uint8Array() };
+var smallBufferSourceTest = { name: "SmallBufferSource", data: CreateArrayBufferFromPayload(smallPayload) };
+var mediumBufferSourceTest = { name: "MediumBufferSource", data: CreateArrayBufferFromPayload(mediumPayload) };
+var largeBufferSourceTest = { name: "LargeBufferSource", data: CreateArrayBufferFromPayload(largePayload) };
+var maxBufferSourceTest = { name: "MaxBufferSource", data: CreateArrayBufferFromPayload(maxPayload) };
+var emptyFormDataTest = { name: "EmptyFormData", data: CreateEmptyFormDataPayload() };
+var smallFormDataTest = { name: "SmallFormData", data: CreateFormDataFromPayload(smallPayload) };
+var mediumFormDataTest = { name: "MediumFormData", data: CreateFormDataFromPayload(mediumPayload) };
+var largeFormDataTest = { name: "LargeFormData", data: CreateFormDataFromPayload(largePayload) };
+var smallSafeContentTypeEncodedTest = { name: "SmallSafeContentTypeEncoded", data: new Blob([smallPayload], { type: 'application/x-www-form-urlencoded' }) };
+var smallSafeContentTypeFormTest = { name: "SmallSafeContentTypeForm", data: new FormData() };
+var smallSafeContentTypeTextTest = { name: "SmallSafeContentTypeText", data: new Blob([smallPayload], { type: 'text/plain' }) };
+var smallCORSContentTypeTextTest = { name: "SmallCORSContentTypeText", data: new Blob([smallPayload], { type: 'text/html' }) };
 // We don't test maxFormData because the extra multipart separators make it difficult to
 // calculate a maxPayload.
 
@@ -66,13 +66,6 @@
 
 var preflightTests = [smallCORSContentTypeTextTest];
 
-// Build a test lookup table, which is useful when instructing a web worker or an iframe
-// to run a test, so that we don't have to marshal the entire test case across a process boundary.
-var testLookup = {};
-allTests.forEach(function(testCase) {
-    testLookup[testCase.id] = testCase;
-});
-
 // Helper function to create an ArrayBuffer representation of a string.
 function CreateArrayBufferFromPayload(payload) {
     var length = payload.length;
@@ -105,76 +98,27 @@
     return formData;
 }
 
-// Initializes a session with a client-generated SID.
-// A "session" is a run of one or more tests. It is used to batch several beacon
-// tests in a way that isolates the server-side session state and makes it easy
-// to poll the results of the tests in one request.
-//     testCases: The array of test cases participating in the session.
-function initSession(testCases) {
-    return {
-        // Provides a unique session identifier to prevent mixing server-side data
-        // with other sessions.
-        id: self.token(),
-        // Dictionary of test name to live testCase object.
-        testCaseLookup: {},
-        // Array of testCase objects for iteration.
-        testCases: [],
-        // Tracks the total number of tests in the session.
-        totalCount: testCases.length,
-        // Tracks the number of tests for which we have sent the beacon.
-        // When it reaches totalCount, we will start polling for results.
-        sentCount: 0,
-        // Tracks the number of tests for which we have verified the results.
-        // When it reaches sentCount, we will stop polling for results.
-        doneCount: 0,
-        // Helper to add a testCase to the session.
-        add: function add(testCase) {
-            this.testCases.push(testCase);
-            this.testCaseLookup[testCase.id] = testCase;
-        }
+// Schedules async_test's for each of the test cases, treating them as a single session,
+// and wires up the continueAfterSendingBeacon() and waitForResults() calls.
+// Parameters:
+//     testCases: An array of test cases.
+//     suffix [optional]: A string used for the suffix for each test case name.
+//     buildUrl [optional]: A function that returns a beacon URL given an id.
+//     sendData [optional]: A function that sends the beacon with given a URL and payload.
+function runTests(testCases, suffix = '', buildUrl = self.buildUrl, sendData = self.sendData) {
+    for (const testCase of testCases) {
+        const id = token();
+        async_test((test) => {
+            const url = buildUrl(id);
+            assert_true(sendData(url, testCase.data), 'sendBeacon should succeed');
+            waitForResult(id).then(() => test.done(), test.step_func((e) => {throw e;}));
+        }, `Verify 'navigator.sendbeacon()' successfully sends for variant: ${testCase.name}${suffix}`);
     };
 }
 
-// Schedules async_test's for each of the test cases, treating them as a single session,
-// and wires up the continueAfterSendingBeacon() and waitForResults() calls.
-// The method looks for several "extension" functions in the global scope:
-//   - self.buildBaseUrl: if present, can change the base URL of a beacon target URL (this
-//     is the scheme, hostname, and port).
-//   - self.buildTargetUrl: if present, can modify a beacon target URL (for example wrap it).
-// Parameters:
-//     testCases: An array of test cases.
-//     sendData [optional]: A function that sends the beacon.
-function runTests(testCases, sendData = self.sendData) {
-    const session = initSession(testCases);
-
-    testCases.forEach(function(testCase, testIndex) {
-        // Make a copy of the test case as we'll be storing some metadata on it,
-        // such as which session it belongs to.
-        const testCaseCopy = Object.assign({ session: session }, testCase);
-
-        testCaseCopy.index = testIndex;
-
-        async_test((test) => {
-            // Save the testharness.js 'test' object, so that we only have one object
-            // to pass around.
-            testCaseCopy.test = test;
-
-            // Extension point: generate the beacon URL.
-            var baseUrl = "http://{{host}}:{{ports[http][0]}}";
-            if (self.buildBaseUrl) {
-                baseUrl = self.buildBaseUrl(baseUrl);
-            }
-            var targetUrl = `${baseUrl}/beacon/resources/beacon.py?cmd=store&sid=${session.id}&tid=${testCaseCopy.id}&tidx=${testIndex}`;
-            if (self.buildTargetUrl) {
-                targetUrl = self.buildTargetUrl(targetUrl);
-            }
-            // Attach the URL to the test object for debugging purposes.
-            testCaseCopy.url = targetUrl;
-
-            assert_true(sendData(testCaseCopy), 'sendBeacon should succeed');
-            waitForResult(testCaseCopy).then(() => test.done(), test.step_func((e) => {throw e;}));
-        }, `Verify 'navigator.sendbeacon()' successfully sends for variant: ${testCaseCopy.id}`);
-    });
+function buildUrl(id) {
+    const baseUrl = "http://{{host}}:{{ports[http][0]}}";
+    return `${baseUrl}/beacon/resources/beacon.py?cmd=store&id=${id}`;
 }
 
 // Sends the beacon for a single test. This step is factored into its own function so that
@@ -183,15 +127,13 @@
 // full testharness.js test context. Instead return 'false', and the main scope will fail
 // the test.
 // Returns the result of the 'sendbeacon()' function call, true or false.
-function sendData(testCase) {
-    return self.navigator.sendBeacon(testCase.url, testCase.data);
+function sendData(url, payload) {
+    return self.navigator.sendBeacon(url, payload);
 }
 
 // Poll the server for the test result.
-async function waitForResult(testCase) {
-    const session = testCase.session;
-    const index = testCase.index;
-    const url = `resources/beacon.py?cmd=stat&sid=${session.id}&tidx_min=${index}&tidx_max=${index}`;
+async function waitForResult(id) {
+    const url = `resources/beacon.py?cmd=stat&id=${id}`;
     for (let i = 0; i < 30; ++i) {
         const response = await fetch(url);
         const text = await response.text();
@@ -218,16 +160,10 @@
     iframe.onload = function() {
         // Clear our onload handler to prevent re-running the tests as we navigate away.
         iframe.onload = null;
-        function sendData(testCase) {
-            return iframe.contentWindow.navigator.sendBeacon(testCase.url, testCase.data);
+        function sendData(url, payload) {
+            return iframe.contentWindow.navigator.sendBeacon(url, payload);
         }
-        const tests = [];
-        for (const test of sampleTests) {
-            const copy = Object.assign({}, test);
-            copy.id = `${test.id}-NAVIGATE`;
-            tests.push(copy);
-        }
-        runTests(tests, sendData);
+        runTests(sampleTests, '-NAVIGATE', self.buildUrl, sendData);
         // Now navigate ourselves.
         iframe.contentWindow.location = "http://{{host}}:{{ports[http][0]}}/";
     };
diff --git a/LayoutTests/imported/w3c/web-platform-tests/beacon/beacon-cors.sub.window.js b/LayoutTests/imported/w3c/web-platform-tests/beacon/beacon-cors.sub.window.js
index 79acde1..9a95acb 100644
--- a/LayoutTests/imported/w3c/web-platform-tests/beacon/beacon-cors.sub.window.js
+++ b/LayoutTests/imported/w3c/web-platform-tests/beacon/beacon-cors.sub.window.js
@@ -8,15 +8,8 @@
 // the beacon handler will return CORS headers. This test ensures that the
 // sendBeacon() succeeds in either case.
 [true, false].forEach(function(allowCors) {
-    // Implement the self.buildBaseUrl and self.buildTargetUrl extensions
-    // to change the target URL to use a cross-origin domain name.
-    self.buildBaseUrl = function(baseUrl) {
-        return "http://{{hosts[alt][]}}:{{ports[http][0]}}";
-    };
-    // Implement the self.buildTargetUrl extension to append a directive
-    // to the handler, that it should return CORS headers, if 'allowCors'
-    // is true.
-    self.buildTargetUrl = function(targetUrl) {
+    function buildUrl(id) {
+        const baseUrl = "http://{{hosts[alt][]}}:{{ports[http][0]}}";
         // Note that 'allowCors=true' is not necessary for the sendBeacon() to reach
         // the server. Beacons use the HTTP POST method, which is a CORS-safelisted
         // method, and thus they do not trigger preflight. If the server does not
@@ -27,16 +20,10 @@
         // value of the sendBeacon() call, because the underlying fetch is asynchronous.
         // The "Beacon CORS" tests are merely testing that sendBeacon() to a cross-
         // origin URL *will* work regardless.
-        return allowCors ? `${targetUrl}&origin=http://{{host}}:{{ports[http][0]}}&credentials=true` : targetUrl;
+        const additionalQuery = allowCors ? "&origin=http://{{host}}:{{ports[http][0]}}&credentials=true" : "";
+        return `${baseUrl}/beacon/resources/beacon.py?cmd=store&id=${id}${additionalQuery}`
     }
-
-    const tests = [];
-    for (const test of sampleTests) {
-        const copy = Object.assign({}, test);
-        copy.id = `${test.id}-${allowCors ? "CORS-ALLOW" : "CORS-FORBID"}`;
-        tests.push(copy);
-    }
-    runTests(tests);
+    runTests(sampleTests, allowCors ? "-CORS-ALLOW" : "-CORS-FORBID", buildUrl);
 });
 
 // Now test a cross-origin request that doesn't use a safelisted Content-Type and ensure
@@ -44,24 +31,12 @@
 // header is used there should be a preflight/options request and we should only succeed
 // send the payload if the proper CORS headers are used.
 {
-    // Implement the self.buildBaseUrl and self.buildTargetUrl extensions
-    // to change the target URL to use a cross-origin domain name.
-    self.buildBaseUrl = function (baseUrl) {
-        return "http://{{hosts[alt][]}}:{{ports[http][0]}}";
-    };
-
-    // Implement the self.buildTargetUrl extension to append a directive
-    // to the handler, that it should return CORS headers for the preflight we expect.
-    self.buildTargetUrl = function (targetUrl) {
-        return `${targetUrl}&origin=http://{{host}}:{{ports[http][0]}}&credentials=true&preflightExpected=true`;
+    function buildUrl(id) {
+        const baseUrl = "http://{{hosts[alt][]}}:{{ports[http][0]}}";
+        const additionalQuery = "&origin=http://{{host}}:{{ports[http][0]}}&credentials=true&preflightExpected=true";
+        return `${baseUrl}/beacon/resources/beacon.py?cmd=store&id=${id}${additionalQuery}`
     }
-    const tests = [];
-    for (const test of preflightTests) {
-        const copy = Object.assign({}, test);
-        copy.id = `${test.id}-PREFLIGHT-ALLOW`;
-        tests.push(copy);
-    }
-    runTests(tests);
+    runTests(preflightTests, "-PREFLIGHT-ALLOW", buildUrl);
 }
 
 done();
diff --git a/LayoutTests/imported/w3c/web-platform-tests/beacon/beacon-error.sub.window.js b/LayoutTests/imported/w3c/web-platform-tests/beacon/beacon-error.sub.window.js
index f2c5e95..499fa3b 100644
--- a/LayoutTests/imported/w3c/web-platform-tests/beacon/beacon-error.sub.window.js
+++ b/LayoutTests/imported/w3c/web-platform-tests/beacon/beacon-error.sub.window.js
@@ -13,13 +13,13 @@
 
 test(function() {
     var invalidUrl = "http://invalid:url";
-    assert_throws(new TypeError(), function() { navigator.sendBeacon(invalidUrl, smallPayload); },
+    assert_throws_js(TypeError, function() { navigator.sendBeacon(invalidUrl, smallPayload); },
         `calling 'navigator.sendBeacon()' with an invalid URL '${invalidUrl}' must throw a TypeError`);
 }, "Verify calling 'navigator.sendBeacon()' with an invalid URL throws an exception.");
 
 test(function() {
     var invalidUrl = "nothttp://invalid.url";
-    assert_throws(new TypeError(), function() { navigator.sendBeacon(invalidUrl, smallPayload); },
+    assert_throws_js(TypeError, function() { navigator.sendBeacon(invalidUrl, smallPayload); },
          `calling 'navigator.sendBeacon()' with a non-http(s) URL '${invalidUrl}' must throw a TypeError`);
 }, "Verify calling 'navigator.sendBeacon()' with a URL that is not a http(s) scheme throws an exception.");
 
diff --git a/LayoutTests/imported/w3c/web-platform-tests/beacon/beacon-preflight-failure.sub.window-expected.txt b/LayoutTests/imported/w3c/web-platform-tests/beacon/beacon-preflight-failure.sub.window-expected.txt
new file mode 100644
index 0000000..0ebe369
--- /dev/null
+++ b/LayoutTests/imported/w3c/web-platform-tests/beacon/beacon-preflight-failure.sub.window-expected.txt
@@ -0,0 +1,3 @@
+
+PASS beacon-preflight-failure 
+
diff --git a/LayoutTests/imported/w3c/web-platform-tests/beacon/beacon-preflight-failure.sub.window.html b/LayoutTests/imported/w3c/web-platform-tests/beacon/beacon-preflight-failure.sub.window.html
new file mode 100644
index 0000000..2382913
--- /dev/null
+++ b/LayoutTests/imported/w3c/web-platform-tests/beacon/beacon-preflight-failure.sub.window.html
@@ -0,0 +1 @@
+<!-- This file is required for WebKit test infrastructure to run the templated test -->
\ No newline at end of file
diff --git a/LayoutTests/imported/w3c/web-platform-tests/beacon/beacon-preflight-failure.sub.window.js b/LayoutTests/imported/w3c/web-platform-tests/beacon/beacon-preflight-failure.sub.window.js
new file mode 100644
index 0000000..c5a2d81
--- /dev/null
+++ b/LayoutTests/imported/w3c/web-platform-tests/beacon/beacon-preflight-failure.sub.window.js
@@ -0,0 +1,28 @@
+// META: script=/common/utils.js
+// META: script=/common/get-host-info.sub.js
+
+promise_test(async (test) => {
+  const origin = get_host_info().REMOTE_ORIGIN;
+  const id = token();
+  const store = `${origin}/beacon/resources/beacon.py?cmd=store&id=${id}`;
+  const monitor = `/beacon/resources/beacon.py?cmd=stat&id=${id}`;
+
+  assert_true(navigator.sendBeacon(store, new Blob([], {type: 'x/y'})));
+
+  let actual;
+  for (let i = 0; i < 30; ++i) {
+    await new Promise(resolve => test.step_timeout(resolve, 10));
+
+    const response = await fetch(monitor);
+    const obj = await response.json();
+    if (obj.length > 0) {
+      actual = JSON.stringify(obj);
+      break;
+    }
+  }
+
+  const expected =
+    JSON.stringify([{error: 'Preflight not expected.'}]);
+
+  assert_equals(actual, expected);
+});
diff --git a/LayoutTests/imported/w3c/web-platform-tests/beacon/beacon-readablestream.window.js b/LayoutTests/imported/w3c/web-platform-tests/beacon/beacon-readablestream.window.js
index fc7f81f..46e30fc 100644
--- a/LayoutTests/imported/w3c/web-platform-tests/beacon/beacon-readablestream.window.js
+++ b/LayoutTests/imported/w3c/web-platform-tests/beacon/beacon-readablestream.window.js
@@ -1,3 +1,3 @@
 test(() => {
-  assert_throws(new TypeError(), () => navigator.sendBeacon("...", new ReadableStream()));
+  assert_throws_js(TypeError, () => navigator.sendBeacon("...", new ReadableStream()));
 }, "sendBeacon() with a stream does not work due to the keepalive flag being set");
diff --git a/LayoutTests/imported/w3c/web-platform-tests/beacon/beacon-redirect.sub.window-expected.txt b/LayoutTests/imported/w3c/web-platform-tests/beacon/beacon-redirect.sub.window-expected.txt
new file mode 100644
index 0000000..db0204e
--- /dev/null
+++ b/LayoutTests/imported/w3c/web-platform-tests/beacon/beacon-redirect.sub.window-expected.txt
@@ -0,0 +1,22 @@
+
+PASS Verify 'navigator.sendbeacon()' successfully sends for variant: NoData-307 
+PASS Verify 'navigator.sendbeacon()' successfully sends for variant: NullData-307 
+PASS Verify 'navigator.sendbeacon()' successfully sends for variant: UndefinedData-307 
+PASS Verify 'navigator.sendbeacon()' successfully sends for variant: SmallString-307 
+PASS Verify 'navigator.sendbeacon()' successfully sends for variant: SmallBlob-307 
+PASS Verify 'navigator.sendbeacon()' successfully sends for variant: SmallBufferSource-307 
+PASS Verify 'navigator.sendbeacon()' successfully sends for variant: SmallFormData-307 
+PASS Verify 'navigator.sendbeacon()' successfully sends for variant: SmallSafeContentTypeEncoded-307 
+PASS Verify 'navigator.sendbeacon()' successfully sends for variant: SmallSafeContentTypeForm-307 
+PASS Verify 'navigator.sendbeacon()' successfully sends for variant: SmallSafeContentTypeText-307 
+PASS Verify 'navigator.sendbeacon()' successfully sends for variant: NoData-308 
+PASS Verify 'navigator.sendbeacon()' successfully sends for variant: NullData-308 
+PASS Verify 'navigator.sendbeacon()' successfully sends for variant: UndefinedData-308 
+PASS Verify 'navigator.sendbeacon()' successfully sends for variant: SmallString-308 
+PASS Verify 'navigator.sendbeacon()' successfully sends for variant: SmallBlob-308 
+PASS Verify 'navigator.sendbeacon()' successfully sends for variant: SmallBufferSource-308 
+PASS Verify 'navigator.sendbeacon()' successfully sends for variant: SmallFormData-308 
+PASS Verify 'navigator.sendbeacon()' successfully sends for variant: SmallSafeContentTypeEncoded-308 
+PASS Verify 'navigator.sendbeacon()' successfully sends for variant: SmallSafeContentTypeForm-308 
+PASS Verify 'navigator.sendbeacon()' successfully sends for variant: SmallSafeContentTypeText-308 
+
diff --git a/LayoutTests/imported/w3c/web-platform-tests/beacon/beacon-redirect.sub.window.html b/LayoutTests/imported/w3c/web-platform-tests/beacon/beacon-redirect.sub.window.html
new file mode 100644
index 0000000..2382913
--- /dev/null
+++ b/LayoutTests/imported/w3c/web-platform-tests/beacon/beacon-redirect.sub.window.html
@@ -0,0 +1 @@
+<!-- This file is required for WebKit test infrastructure to run the templated test -->
\ No newline at end of file
diff --git a/LayoutTests/imported/w3c/web-platform-tests/beacon/beacon-redirect.sub.window.js b/LayoutTests/imported/w3c/web-platform-tests/beacon/beacon-redirect.sub.window.js
new file mode 100644
index 0000000..fd23a45
--- /dev/null
+++ b/LayoutTests/imported/w3c/web-platform-tests/beacon/beacon-redirect.sub.window.js
@@ -0,0 +1,20 @@
+// META: timeout=long
+// META: script=/common/utils.js
+// META: script=beacon-common.sub.js
+
+"use strict";
+
+// Execute each sample test per redirect status code.
+// Note that status codes 307 and 308 are the only codes that will maintain POST data
+// through a redirect.
+[307, 308].forEach(function(status) {
+    function buildUrl(id) {
+        const baseUrl = "http://{{host}}:{{ports[http][0]}}";
+        const targetUrl = `${baseUrl}/beacon/resources/beacon.py?cmd=store&id=${id}`;
+
+        return `/common/redirect.py?status=${status}&location=${encodeURIComponent(targetUrl)}`;
+    }
+    runTests(sampleTests, `-${status}`, buildUrl);
+});
+
+done();
diff --git a/LayoutTests/imported/w3c/web-platform-tests/beacon/beacon-redirect.window.js b/LayoutTests/imported/w3c/web-platform-tests/beacon/beacon-redirect.window.js
deleted file mode 100644
index 3a8aef3..0000000
--- a/LayoutTests/imported/w3c/web-platform-tests/beacon/beacon-redirect.window.js
+++ /dev/null
@@ -1,25 +0,0 @@
-// META: timeout=long
-// META: script=/common/utils.js
-// META: script=beacon-common.sub.js
-
-"use strict";
-
-// Execute each sample test per redirect status code.
-// Note that status codes 307 and 308 are the only codes that will maintain POST data
-// through a redirect.
-[307, 308].forEach(function(status) {
-    // Implement the self.buildTargetUrl extension to inject a redirect to
-    // the sendBeacon target.
-    self.buildTargetUrl = function(targetUrl) {
-        return `/common/redirect.py?status=${status}&location=${encodeURIComponent(targetUrl)}`;
-    };
-    const tests = [];
-    for (const test of sampleTests) {
-        const copy = Object.assign({}, test);
-        copy.id = `${test.id}-${status}`;
-        tests.push(copy);
-    }
-    runTests(tests);
-});
-
-done();
diff --git a/LayoutTests/imported/w3c/web-platform-tests/beacon/headers/header-referrer-no-referrer-when-downgrade.https.html b/LayoutTests/imported/w3c/web-platform-tests/beacon/headers/header-referrer-no-referrer-when-downgrade.https.html
index c65b9fd..d09d4ea 100644
--- a/LayoutTests/imported/w3c/web-platform-tests/beacon/headers/header-referrer-no-referrer-when-downgrade.https.html
+++ b/LayoutTests/imported/w3c/web-platform-tests/beacon/headers/header-referrer-no-referrer-when-downgrade.https.html
@@ -15,7 +15,7 @@
       var testBase = get_host_info().HTTPS_ORIGIN + RESOURCES_DIR;
       testReferrerHeader(testBase, referrerUrl);
       testBase = get_host_info().HTTP_ORIGIN + RESOURCES_DIR;
-      testReferrerHeader(testBase, "");
+      testReferrerHeader(testBase, "", true);
     </script>
   </body>
 </html>
diff --git a/LayoutTests/imported/w3c/web-platform-tests/beacon/resources/beacon.py b/LayoutTests/imported/w3c/web-platform-tests/beacon/resources/beacon.py
index 5f2553d..f5caaf4 100644
--- a/LayoutTests/imported/w3c/web-platform-tests/beacon/resources/beacon.py
+++ b/LayoutTests/imported/w3c/web-platform-tests/beacon/resources/beacon.py
@@ -1,7 +1,6 @@
 import json
 
-def build_stash_key(session_id, test_num):
-    return "%s_%s" % (session_id, test_num)
+from wptserve.utils import isomorphic_decode
 
 def main(request, response):
     """Helper handler for Beacon tests.
@@ -9,69 +8,56 @@
     It handles two forms of requests:
 
     STORE:
-        A URL with a query string of the form 'cmd=store&sid=<token>&tidx=<test_index>&tid=<test_name>'.
+        A URL with a query string of the form 'cmd=store&id=<token>'.
 
-        Stores the receipt of a sendBeacon() request along with its validation result, returning HTTP 200 OK.
+        Stores the receipt of a sendBeacon() request along with its validation
+        result, returning HTTP 200 OK.
 
-        Parameters:
-            tidx - the integer index of the test.
-            tid - a friendly identifier or name for the test, used when returning results.
+        if "preflightExpected"  exists in the query, this handler responds to
+        CORS preflights.
 
     STAT:
-        A URL with a query string of the form 'cmd=stat&sid=<token>&tidx_min=<min_test_index>&tidx_max=<max_test_index>'.
+        A URL with a query string of the form 'cmd=stat&id=<token>'.
 
-        Retrieves the results of test with indices [min_test_index, max_test_index] and returns them as
-        a JSON array and HTTP 200 OK status code. Due to the eventual read-once nature of the stash, results for a given test
-        are only guaranteed to be returned once, though they may be returned multiple times.
+        Retrieves the results of test for the given id and returns them as a
+        JSON array and HTTP 200 OK status code. Due to the eventual read-once
+        nature of the stash, results for a given test are only guaranteed to be
+        returned once, though they may be returned multiple times.
 
-        Parameters:
-            tidx_min - the lower-bounding integer test index.
-            tidx_max - the upper-bounding integer test index.
-
-        Example response body:
-            [{"id": "Test1", error: null}, {"id": "Test2", error: "some validation details"}]
+        Example response bodies:
+            - [{error: null}]
+            - [{error: "some validation details"}]
+            - []
 
     Common parameters:
         cmd - the command, 'store' or 'stat'.
-        sid - session id used to provide isolation to a test run comprising multiple sendBeacon()
-              tests.
+        id - the unique identifier of the test.
     """
 
-    session_id = request.GET.first("sid");
-    command = request.GET.first("cmd").lower();
-
-    # Workaround to circumvent the limitation that cache keys
-    # can only be UUID's.
-    def wrap_key(key, path):
-        return (str(path), str(key))
-    request.server.stash._wrap_key = wrap_key
+    id = request.GET.first(b"id")
+    command = request.GET.first(b"cmd").lower()
 
     # Append CORS headers if needed.
-    if "origin" in request.GET:
-        response.headers.set("Access-Control-Allow-Origin", request.GET.first("origin"))
-    if "credentials" in request.GET:
-        response.headers.set("Access-Control-Allow-Credentials", request.GET.first("credentials"))
+    if b"origin" in request.GET:
+        response.headers.set(b"Access-Control-Allow-Origin",
+                             request.GET.first(b"origin"))
+    if b"credentials" in request.GET:
+        response.headers.set(b"Access-Control-Allow-Credentials",
+                             request.GET.first(b"credentials"))
 
     # Handle the 'store' and 'stat' commands.
-    if command == "store":
-        # The test id is just used to make the results more human-readable.
-        test_id = request.GET.first("tid")
-        # The test index is used to build a predictable stash key, together
-        # with the unique session id, in order to retrieve a range of results
-        # later knowing the index range.
-        test_idx = request.GET.first("tidx")
-        test_data = { "id": test_id, "error": None }
+    if command == b"store":
+        error = None
 
-        # Only store the actual POST requests, not any preflight/OPTIONS requests we may get.
-        if request.method == "POST":
-            test_data_key = build_stash_key(session_id, test_idx)
-
-            payload = ""
-            if "Content-Type" in request.headers and \
-               "form-data" in request.headers["Content-Type"]:
-                if "payload" in request.POST:
+        # Only store the actual POST requests, not any preflight/OPTIONS
+        # requests we may get.
+        if request.method == u"POST":
+            payload = b""
+            if b"Content-Type" in request.headers and \
+               b"form-data" in request.headers[b"Content-Type"]:
+                if b"payload" in request.POST:
                     # The payload was sent as a FormData.
-                    payload = request.POST.first("payload")
+                    payload = request.POST.first(b"payload")
                 else:
                     # A FormData was sent with an empty payload.
                     pass
@@ -79,46 +65,42 @@
                 # The payload was sent as either a string, Blob, or BufferSource.
                 payload = request.body
 
-            payload_parts = filter(None, payload.split(":"))
+            payload_parts = list(filter(None, payload.split(b":")))
             if len(payload_parts) > 0:
                 payload_size = int(payload_parts[0])
 
-                # Confirm the payload size sent matches with the number of characters sent.
+                # Confirm the payload size sent matches with the number of
+                # characters sent.
                 if payload_size != len(payload_parts[1]):
-                    test_data["error"] = "expected %d characters but got %d" % (payload_size, len(payload_parts[1]))
+                    error = u"expected %d characters but got %d" % (
+                        payload_size, len(payload_parts[1]))
                 else:
                     # Confirm the payload contains the correct characters.
                     for i in range(0, payload_size):
-                        if payload_parts[1][i] != "*":
-                            test_data["error"] = "expected '*' at index %d but got '%s''" % (i, payload_parts[1][i])
+                        if payload_parts[1][i:i+1] != b"*":
+                            error = u"expected '*' at index %d but got '%s''" % (
+                                i, isomorphic_decode(payload_parts[1][i:i+1]))
                             break
 
             # Store the result in the stash so that it can be retrieved
             # later with a 'stat' command.
-            request.server.stash.put(test_data_key, test_data)
-        elif request.method == "OPTIONS":
-            # If we expect a preflight, then add the cors headers we expect, otherwise log an error as we shouldn't
-            # send a preflight for all requests.
-            if "preflightExpected" in request.GET:
-                response.headers.set("Access-Control-Allow-Headers", "content-type")
-                response.headers.set("Access-Control-Allow-Methods", "POST")
+            request.server.stash.put(id, {u"error": error})
+        elif request.method == u"OPTIONS":
+            # If we expect a preflight, then add the cors headers we expect,
+            # otherwise log an error as we shouldn't send a preflight for all
+            # requests.
+            if b"preflightExpected" in request.GET:
+                response.headers.set(b"Access-Control-Allow-Headers",
+                                     b"content-type")
+                response.headers.set(b"Access-Control-Allow-Methods", b"POST")
             else:
-                test_data_key = build_stash_key(session_id, test_idx)
-                test_data["error"] = "Preflight not expected."
-                request.server.stash.put(test_data_key, test_data)
-    elif command == "stat":
-        test_idx_min = int(request.GET.first("tidx_min"))
-        test_idx_max = int(request.GET.first("tidx_max"))
+                error = u"Preflight not expected."
+                request.server.stash.put(id, {u"error": error})
+    elif command == b"stat":
+        test_data = request.server.stash.take(id)
+        results = [test_data] if test_data else []
 
-        # For each result that has come in, append it to the response.
-        results = []
-        for test_idx in range(test_idx_min, test_idx_max+1): # +1 because end is exclusive
-            test_data_key = build_stash_key(session_id, test_idx)
-            test_data = request.server.stash.take(test_data_key)
-            if test_data:
-                results.append(test_data)
-
-        response.headers.set("Content-Type", "text/plain")
+        response.headers.set(b"Content-Type", b"text/plain")
         response.content = json.dumps(results)
     else:
-        response.status = 400 # BadRequest
\ No newline at end of file
+        response.status = 400  # BadRequest
diff --git a/LayoutTests/imported/w3c/web-platform-tests/beacon/resources/content-type.py b/LayoutTests/imported/w3c/web-platform-tests/beacon/resources/content-type.py
index 2ea18d5..d3be7d4 100644
--- a/LayoutTests/imported/w3c/web-platform-tests/beacon/resources/content-type.py
+++ b/LayoutTests/imported/w3c/web-platform-tests/beacon/resources/content-type.py
@@ -1,14 +1,14 @@
 def main(request, response):
-  command = request.GET.first("cmd").lower();
-  test_id = request.GET.first("id")
-  if command == "put":
-    request.server.stash.put(test_id, request.headers.get("Content-Type", ""))
-    return [("Content-Type", "text/plain")], ""
+    command = request.GET.first(b"cmd").lower()
+    test_id = request.GET.first(b"id")
+    if command == b"put":
+        request.server.stash.put(test_id, request.headers.get(b"Content-Type", b""))
+        return [(b"Content-Type", b"text/plain")], u""
 
-  if command == "get":
-    stashed_header = request.server.stash.take(test_id)
-    if stashed_header is not None:
-      return [("Content-Type", "text/plain")], stashed_header
+    if command == b"get":
+        stashed_header = request.server.stash.take(test_id)
+        if stashed_header is not None:
+            return [(b"Content-Type", b"text/plain")], stashed_header
 
-  response.set_error(400, "Bad Command")
-  return "ERROR: Bad Command!"
+    response.set_error(400, u"Bad Command")
+    return u"ERROR: Bad Command!"
diff --git a/LayoutTests/imported/w3c/web-platform-tests/beacon/resources/inspect-header.py b/LayoutTests/imported/w3c/web-platform-tests/beacon/resources/inspect-header.py
index e0b0e92..f926ed4 100644
--- a/LayoutTests/imported/w3c/web-platform-tests/beacon/resources/inspect-header.py
+++ b/LayoutTests/imported/w3c/web-platform-tests/beacon/resources/inspect-header.py
@@ -1,18 +1,18 @@
 def main(request, response):
-    headers = [("Content-Type", "text/plain")]
-    command = request.GET.first("cmd").lower();
-    test_id = request.GET.first("id")
-    header = request.GET.first("header")
-    if command == "put":
-        request.server.stash.put(test_id, request.headers.get(header, ""))
+    headers = [(b"Content-Type", b"text/plain")]
+    command = request.GET.first(b"cmd").lower()
+    test_id = request.GET.first(b"id")
+    header = request.GET.first(b"header")
+    if command == b"put":
+        request.server.stash.put(test_id, request.headers.get(header, b""))
 
-    elif command == "get":
+    elif command == b"get":
         stashed_header = request.server.stash.take(test_id)
         if stashed_header is not None:
-            headers.append(("x-request-" + header, stashed_header ))
+            headers.append((b"x-request-" + header, stashed_header))
 
     else:
-        response.set_error(400, "Bad Command")
-        return "ERROR: Bad Command!"
+        response.set_error(400, u"Bad Command")
+        return u"ERROR: Bad Command!"
 
-    return headers, ""
+    return headers, u""
diff --git a/LayoutTests/imported/w3c/web-platform-tests/beacon/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/beacon/w3c-import.log
index 0749e19..7ff0578 100644
--- a/LayoutTests/imported/w3c/web-platform-tests/beacon/w3c-import.log
+++ b/LayoutTests/imported/w3c/web-platform-tests/beacon/w3c-import.log
@@ -27,6 +27,7 @@
 /LayoutTests/imported/w3c/web-platform-tests/beacon/beacon-cors.sub.window.js
 /LayoutTests/imported/w3c/web-platform-tests/beacon/beacon-error.sub.window.js
 /LayoutTests/imported/w3c/web-platform-tests/beacon/beacon-navigate.html
+/LayoutTests/imported/w3c/web-platform-tests/beacon/beacon-preflight-failure.sub.window.js
 /LayoutTests/imported/w3c/web-platform-tests/beacon/beacon-readablestream.window.js
-/LayoutTests/imported/w3c/web-platform-tests/beacon/beacon-redirect.window.js
+/LayoutTests/imported/w3c/web-platform-tests/beacon/beacon-redirect.sub.window.js
 /LayoutTests/imported/w3c/web-platform-tests/beacon/idlharness.any.js