зеркало из https://github.com/mozilla/gecko-dev.git
238 строки
11 KiB
JavaScript
238 строки
11 KiB
JavaScript
"use strict";
|
|
|
|
// Different sizes of payloads to test.
|
|
var smallPayloadSize = 10;
|
|
var mediumPayloadSize = 10000;
|
|
var largePayloadSize = 50000;
|
|
var maxPayloadSize = 65536; // The maximum payload size allowed for a beacon request.
|
|
|
|
// String payloads of various sizes sent by sendbeacon. The format of the payloads is a string:
|
|
// <numberOfCharacters>:<numberOfCharacters *'s>
|
|
// ex. "10:**********"
|
|
var smallPayload = smallPayloadSize + ":" + Array(smallPayloadSize).fill('*').join("");
|
|
var mediumPayload = mediumPayloadSize + ":" + Array(mediumPayloadSize).fill('*').join("");
|
|
var largePayload = largePayloadSize + ":" + Array(largePayloadSize).fill('*').join("");
|
|
// Subtract 6 from maxPayloadSize because 65536 is 5 digits, plus 1 more for the ':'
|
|
var maxPayload = (maxPayloadSize - 6) + ":" + Array(maxPayloadSize - 6).fill('*').join("")
|
|
|
|
// Test case definitions.
|
|
// id: 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' }) };
|
|
// We don't test maxFormData because the extra multipart separators make it difficult to
|
|
// calculate a maxPayload.
|
|
|
|
// Test case suites.
|
|
// Due to quota limits we split the max payload tests into their own bucket.
|
|
var stringTests = [noDataTest, nullDataTest, undefinedDataTest, smallStringTest, mediumStringTest, largeStringTest];
|
|
var stringMaxTest = [maxStringTest];
|
|
var blobTests = [emptyBlobTest, smallBlobTest, mediumBlobTest, largeBlobTest];
|
|
var blobMaxTest = [maxBlobTest];
|
|
var bufferSourceTests = [emptyBufferSourceTest, smallBufferSourceTest, mediumBufferSourceTest, largeBufferSourceTest];
|
|
var bufferSourceMaxTest = [maxBufferSourceTest];
|
|
var formDataTests = [emptyFormDataTest, smallFormDataTest, mediumFormDataTest, largeFormDataTest];
|
|
var formDataMaxTest = [largeFormDataTest];
|
|
var contentTypeTests = [smallSafeContentTypeEncodedTest,smallSafeContentTypeFormTest,smallSafeContentTypeTextTest,smallCORSContentTypeTextTest];
|
|
var allTests = [].concat(stringTests, stringMaxTest, blobTests, blobMaxTest, bufferSourceTests, bufferSourceMaxTest, formDataTests, formDataMaxTest, contentTypeTests);
|
|
|
|
// This special cross section of test cases is meant to provide a slimmer but reasonably-
|
|
// representative set of tests for parameterization across variables (e.g. redirect codes,
|
|
// cors modes, etc.)
|
|
var sampleTests = [noDataTest, nullDataTest, undefinedDataTest, smallStringTest, smallBlobTest, smallBufferSourceTest, smallFormDataTest, smallSafeContentTypeEncodedTest, smallSafeContentTypeFormTest, smallSafeContentTypeTextTest];
|
|
|
|
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;
|
|
var buffer = new Uint8Array(length);
|
|
|
|
for (var i = 0; i < length; i++) {
|
|
buffer[i] = payload.charCodeAt(i);
|
|
}
|
|
|
|
return buffer;
|
|
}
|
|
|
|
// Helper function to create an empty FormData object.
|
|
function CreateEmptyFormDataPayload() {
|
|
if (self.document === undefined) {
|
|
return null;
|
|
}
|
|
|
|
return new FormData();
|
|
}
|
|
|
|
// Helper function to create a FormData representation of a string.
|
|
function CreateFormDataFromPayload(payload) {
|
|
if (self.document === undefined) {
|
|
return null;
|
|
}
|
|
|
|
var formData = new FormData();
|
|
formData.append("payload", payload);
|
|
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.
|
|
// 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}`);
|
|
});
|
|
}
|
|
|
|
// Sends the beacon for a single test. This step is factored into its own function so that
|
|
// it can be called from a web worker. It does not check for results.
|
|
// Note: do not assert from this method, as when called from a worker, we won't have the
|
|
// 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);
|
|
}
|
|
|
|
// 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}`;
|
|
for (let i = 0; i < 30; ++i) {
|
|
const response = await fetch(url);
|
|
const text = await response.text();
|
|
const results = JSON.parse(text);
|
|
|
|
if (results.length === 0) {
|
|
await new Promise(resolve => step_timeout(resolve, 100));
|
|
continue;
|
|
}
|
|
assert_equals(results.length, 1, `bad response: '${text}'`);;
|
|
// null JSON values parse as null, not undefined
|
|
assert_equals(results[0].error, null, "'sendbeacon' data must not fail validation");
|
|
return;
|
|
}
|
|
assert_true(false, 'timeout');
|
|
}
|
|
|
|
// Creates an iframe on the document's body and runs the sample tests from the iframe.
|
|
// The iframe is navigated immediately after it sends the data, and the window verifies
|
|
// that the data is still successfully sent.
|
|
function runSendInIframeAndNavigateTests() {
|
|
var iframe = document.createElement("iframe");
|
|
iframe.id = "iframe";
|
|
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);
|
|
}
|
|
const tests = [];
|
|
for (const test of sampleTests) {
|
|
const copy = Object.assign({}, test);
|
|
copy.id = `${test.id}-NAVIGATE`;
|
|
tests.push(copy);
|
|
}
|
|
runTests(tests, sendData);
|
|
// Now navigate ourselves.
|
|
iframe.contentWindow.location = "http://{{host}}:{{ports[http][0]}}/";
|
|
};
|
|
|
|
iframe.srcdoc = '<html></html>';
|
|
document.body.appendChild(iframe);
|
|
}
|