зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1691099 [wpt PR 27512] - Reorganize and augment beacon wpt, a=testonly
Automatic update from web-platform-tests Reorganize and augment beacon wpt - Run tests on secure contexts. I'm planning to use the sec-fetch-mode header once WebKit supports it, which is only available on secure contexts. - Replace html files with window.js files. - Merge many beacon-basic-* files into beacon-basic.https.window.js. This is doable because the payload size limit is per context, not per page. - Merge beacon-readablestream.js and beacon-error.sub.window.js into beacon-basic.https.window.js. - Merge beacon-preflight-failure.sub.window.js into beacon-cors.https.window.js. - Add more cross origin tests in beacon-cors.https.window.js. - Slim down beacon-common.sub.js. Also remove some test files in http/tests/sendbeacon, because they are covered by wpt/beacon. Bug: 720303 Change-Id: Id65c70d290753428d5d17bc8e9e783969ff8bc16 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2677465 Commit-Queue: Yutaka Hirano <yhirano@chromium.org> Reviewed-by: Dominic Farolino <dom@chromium.org> Cr-Commit-Position: refs/heads/master@{#852515} -- wpt-commits: a341862f7bf55470eb456fbdbb20e465508e7cb9 wpt-pr: 27512
This commit is contained in:
Родитель
8df20530ed
Коммит
cc0f2153f8
|
@ -1,18 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>W3C Beacon Basic Blob Test</title>
|
||||
<meta name="timeout" content="long">
|
||||
<meta name="author" title="Microsoft Edge" href="https://www.microsoft.com">
|
||||
<script src="/resources/testharness.js"></script>
|
||||
<script src="/resources/testharnessreport.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<script src="/common/utils.js"></script>
|
||||
<script src="beacon-common.sub.js"></script>
|
||||
<script>
|
||||
"use strict";
|
||||
runTests(blobTests);
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
|
@ -1,18 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>W3C Beacon Basic Blob Test - MaxSize</title>
|
||||
<meta name="timeout" content="long">
|
||||
<meta name="author" title="Microsoft Edge" href="https://www.microsoft.com">
|
||||
<script src="/resources/testharness.js"></script>
|
||||
<script src="/resources/testharnessreport.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<script src="/common/utils.js"></script>
|
||||
<script src="beacon-common.sub.js"></script>
|
||||
<script>
|
||||
"use strict";
|
||||
runTests(blobMaxTest);
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
|
@ -1,18 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>W3C Beacon Basic BufferSource Test</title>
|
||||
<meta name="timeout" content="long">
|
||||
<meta name="author" title="Microsoft Edge" href="https://www.microsoft.com">
|
||||
<script src="/resources/testharness.js"></script>
|
||||
<script src="/resources/testharnessreport.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<script src="/common/utils.js"></script>
|
||||
<script src="beacon-common.sub.js"></script>
|
||||
<script>
|
||||
"use strict";
|
||||
runTests(bufferSourceTests);
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
|
@ -1,18 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>W3C Beacon Basic BufferSource Test - MaxSize</title>
|
||||
<meta name="timeout" content="long">
|
||||
<meta name="author" title="Microsoft Edge" href="https://www.microsoft.com">
|
||||
<script src="/resources/testharness.js"></script>
|
||||
<script src="/resources/testharnessreport.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<script src="/common/utils.js"></script>
|
||||
<script src="beacon-common.sub.js"></script>
|
||||
<script>
|
||||
"use strict";
|
||||
runTests(bufferSourceMaxTest);
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
|
@ -1,18 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>W3C Beacon Basic FormData Test</title>
|
||||
<meta name="timeout" content="long">
|
||||
<meta name="author" title="Microsoft Edge" href="https://www.microsoft.com">
|
||||
<script src="/resources/testharness.js"></script>
|
||||
<script src="/resources/testharnessreport.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<script src="/common/utils.js"></script>
|
||||
<script src="beacon-common.sub.js"></script>
|
||||
<script>
|
||||
"use strict";
|
||||
runTests(formDataTests);
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
|
@ -1,18 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>W3C Beacon Basic FormData Test</title>
|
||||
<meta name="timeout" content="long">
|
||||
<meta name="author" title="Microsoft Edge" href="https://www.microsoft.com">
|
||||
<script src="/resources/testharness.js"></script>
|
||||
<script src="/resources/testharnessreport.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<script src="/common/utils.js"></script>
|
||||
<script src="beacon-common.sub.js"></script>
|
||||
<script>
|
||||
"use strict";
|
||||
runTests(formDataMaxTest);
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
|
@ -1,18 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>W3C Beacon Basic String Test</title>
|
||||
<meta name="timeout" content="long">
|
||||
<meta name="author" title="Microsoft Edge" href="https://www.microsoft.com">
|
||||
<script src="/resources/testharness.js"></script>
|
||||
<script src="/resources/testharnessreport.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<script src="/common/utils.js"></script>
|
||||
<script src="beacon-common.sub.js"></script>
|
||||
<script>
|
||||
"use strict";
|
||||
runTests(stringTests);
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
|
@ -1,18 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>W3C Beacon Basic String Test - MaxSize</title>
|
||||
<meta name="timeout" content="long">
|
||||
<meta name="author" title="Microsoft Edge" href="https://www.microsoft.com">
|
||||
<script src="/resources/testharness.js"></script>
|
||||
<script src="/resources/testharnessreport.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<script src="/common/utils.js"></script>
|
||||
<script src="beacon-common.sub.js"></script>
|
||||
<script>
|
||||
"use strict";
|
||||
runTests(stringMaxTest);
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,98 @@
|
|||
// META: timeout=long
|
||||
// META: script=/common/utils.js
|
||||
// META: script=beacon-common.sub.js
|
||||
|
||||
'use strict';
|
||||
|
||||
// TODO(yhirano): Check the sec-fetch-mode request header once WebKit supports
|
||||
// the feature.
|
||||
|
||||
parallelPromiseTest(async (t) => {
|
||||
const iframe = document.createElement('iframe');
|
||||
document.body.appendChild(iframe);
|
||||
t.add_cleanup(() => iframe.remove());
|
||||
|
||||
const id = token();
|
||||
const url = `/beacon/resources/beacon.py?cmd=store&id=${id}`;
|
||||
assert_true(iframe.contentWindow.navigator.sendBeacon(url));
|
||||
iframe.remove();
|
||||
|
||||
const result = await waitForResult(id);
|
||||
assert_equals(result.type, '(missing)', 'content-type');
|
||||
}, `simple case: with no payload`);
|
||||
|
||||
parallelPromiseTest(async (t) => {
|
||||
const iframe = document.createElement('iframe');
|
||||
document.body.appendChild(iframe);
|
||||
t.add_cleanup(() => iframe.remove());
|
||||
|
||||
const id = token();
|
||||
const url = `/beacon/resources/beacon.py?cmd=store&id=${id}`;
|
||||
assert_true(iframe.contentWindow.navigator.sendBeacon(url, null));
|
||||
iframe.remove();
|
||||
|
||||
const result = await waitForResult(id);
|
||||
assert_equals(result.type, '(missing)', 'content-type');
|
||||
}, `simple case: with null payload`);
|
||||
|
||||
for (const size of [EMPTY, SMALL, LARGE, MAX]) {
|
||||
for (const type of [STRING, ARRAYBUFFER, FORM, BLOB]) {
|
||||
if (size === MAX && type === FORM) {
|
||||
// It is difficult to estimate the size of a form accurately, so we cannot
|
||||
// test this case.
|
||||
continue;
|
||||
}
|
||||
parallelPromiseTest(async (t) => {
|
||||
const iframe = document.createElement('iframe');
|
||||
document.body.appendChild(iframe);
|
||||
t.add_cleanup(() => iframe.remove());
|
||||
|
||||
const payload = makePayload(size, type);
|
||||
const id = token();
|
||||
const url = `/beacon/resources/beacon.py?cmd=store&id=${id}`;
|
||||
assert_true(iframe.contentWindow.navigator.sendBeacon(url, payload));
|
||||
iframe.remove();
|
||||
|
||||
const result = await waitForResult(id);
|
||||
if (getContentType(type) === null) {
|
||||
assert_equals(result.type, '(missing)', 'content-type');
|
||||
} else {
|
||||
assert_true(result.type.includes(getContentType(type)), 'content-type');
|
||||
}
|
||||
}, `simple case: type = ${type} and size = ${size}`);
|
||||
}
|
||||
}
|
||||
|
||||
for (const type of [STRING, ARRAYBUFFER, FORM, BLOB]) {
|
||||
parallelPromiseTest(async (t) => {
|
||||
const iframe = document.createElement('iframe');
|
||||
document.body.appendChild(iframe);
|
||||
t.add_cleanup(() => iframe.remove());
|
||||
|
||||
const payload = makePayload(TOOLARGE, type);
|
||||
const id = token();
|
||||
const url = `/beacon/resources/beacon.py?cmd=store&id=${id}`;
|
||||
assert_false(iframe.contentWindow.navigator.sendBeacon(url, payload));
|
||||
}, `Too large payload should be rejected: type = ${type}`);
|
||||
}
|
||||
|
||||
for (const type of [STRING, ARRAYBUFFER, BLOB]) {
|
||||
parallelPromiseTest(async (t) => {
|
||||
const iframe = document.createElement('iframe');
|
||||
document.body.appendChild(iframe);
|
||||
t.add_cleanup(() => iframe.remove());
|
||||
|
||||
assert_true(iframe.contentWindow.navigator.sendBeacon(
|
||||
`/beacon/resources/beacon.py?cmd=store&id=${token()}`,
|
||||
makePayload(MAX, type)));
|
||||
assert_true(iframe.contentWindow.navigator.sendBeacon(
|
||||
`/beacon/resources/beacon.py?cmd=store&id=${token()}`, ''));
|
||||
assert_false(iframe.contentWindow.navigator.sendBeacon(
|
||||
`/beacon/resources/beacon.py?cmd=store&id=${token()}`, 'x'));
|
||||
}, `Payload size restriction should be accumulated: type = ${type}`);
|
||||
}
|
||||
|
||||
test(() => {
|
||||
assert_throws_js(
|
||||
TypeError, () => navigator.sendBeacon('...', new ReadableStream()));
|
||||
}, 'sendBeacon() with a stream does not work due to the keepalive flag being set');
|
|
@ -1,172 +1,111 @@
|
|||
"use strict";
|
||||
'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.
|
||||
const EMPTY = 'empty';
|
||||
const SMALL = 'small';
|
||||
const LARGE = 'large';
|
||||
const MAX = 'max';
|
||||
const TOOLARGE = 'toolarge';
|
||||
|
||||
// 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("")
|
||||
const STRING = 'string';
|
||||
const ARRAYBUFFER = 'arraybuffer';
|
||||
const FORM = 'form';
|
||||
const BLOB = 'blob';
|
||||
|
||||
// Test case definitions.
|
||||
// name: String containing the unique name of the test case.
|
||||
// data: Payload object to send through sendbeacon.
|
||||
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.
|
||||
|
||||
// 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];
|
||||
|
||||
// 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;
|
||||
function getContentType(type) {
|
||||
switch (type) {
|
||||
case STRING:
|
||||
return 'text/plain;charset=UTF-8';
|
||||
case ARRAYBUFFER:
|
||||
return null;
|
||||
case FORM:
|
||||
return 'multipart/form-data';
|
||||
case BLOB:
|
||||
return null;
|
||||
default:
|
||||
throw Error(`invalid type: ${type}`);
|
||||
}
|
||||
}
|
||||
|
||||
// Helper function to create an empty FormData object.
|
||||
function CreateEmptyFormDataPayload() {
|
||||
if (self.document === undefined) {
|
||||
return null;
|
||||
}
|
||||
// Create a payload with the given size and type.
|
||||
// `sizeString` must be one of EMPTY, SMALL, LARGE, MAX, TOOLARGE.
|
||||
// `type` must be one of STRING, ARRAYBUFFER, FORM, BLOB.
|
||||
// `contentType` is effective only if `type` is BLOB.
|
||||
function makePayload(sizeString, type, contentType) {
|
||||
let size = 0;
|
||||
switch (sizeString) {
|
||||
case EMPTY:
|
||||
size = 0;
|
||||
break;
|
||||
case SMALL:
|
||||
size = 10;
|
||||
break;
|
||||
case LARGE:
|
||||
size = 10 * 1000;
|
||||
break;
|
||||
case MAX:
|
||||
if (type === FORM) {
|
||||
throw Error('Not supported');
|
||||
}
|
||||
size = 65536;
|
||||
break;
|
||||
case TOOLARGE:
|
||||
size = 65537;
|
||||
break;
|
||||
default:
|
||||
throw Error('invalid size');
|
||||
}
|
||||
|
||||
return new FormData();
|
||||
let data = '';
|
||||
if (size > 0) {
|
||||
const prefix = String(size) + ':';
|
||||
data = prefix + Array(size - prefix.length).fill('*').join('');
|
||||
}
|
||||
|
||||
switch (type) {
|
||||
case STRING:
|
||||
return data;
|
||||
case ARRAYBUFFER:
|
||||
return new TextEncoder().encode(data).buffer;
|
||||
case FORM:
|
||||
const formData = new FormData();
|
||||
if (size > 0) {
|
||||
formData.append('payload', data);
|
||||
}
|
||||
return formData;
|
||||
case BLOB:
|
||||
const options = contentType ? {type: contentType} : undefined;
|
||||
const blob = new Blob([data], options);
|
||||
return blob;
|
||||
default:
|
||||
throw Error('invalid type');
|
||||
}
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
// Schedules promise_test's for each of the test cases.
|
||||
// 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();
|
||||
promise_test((test) => {
|
||||
const url = buildUrl(id);
|
||||
assert_true(sendData(url, testCase.data), 'sendBeacon should succeed');
|
||||
return waitForResult(id);
|
||||
}, `Verify 'navigator.sendbeacon()' successfully sends for variant: ${testCase.name}${suffix}`);
|
||||
};
|
||||
}
|
||||
|
||||
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
|
||||
// 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(url, payload) {
|
||||
return self.navigator.sendBeacon(url, payload);
|
||||
function parallelPromiseTest(func, description) {
|
||||
async_test((t) => {
|
||||
Promise.resolve(func(t)).then(() => t.done()).catch(t.step_func((e) => {
|
||||
throw e;
|
||||
}));
|
||||
}, description);
|
||||
}
|
||||
|
||||
// Poll the server for the test result.
|
||||
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();
|
||||
const results = JSON.parse(text);
|
||||
async function waitForResult(id, expectedError = null) {
|
||||
const url = `/beacon/resources/beacon.py?cmd=stat&id=${id}`;
|
||||
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;
|
||||
if (results.length === 0) {
|
||||
await new Promise(resolve => step_timeout(resolve, 100));
|
||||
continue;
|
||||
}
|
||||
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(url, payload) {
|
||||
return iframe.contentWindow.navigator.sendBeacon(url, payload);
|
||||
}
|
||||
runTests(sampleTests, '-NAVIGATE', self.buildUrl, sendData);
|
||||
// Now navigate ourselves.
|
||||
iframe.contentWindow.location = "http://{{host}}:{{ports[http][0]}}/";
|
||||
};
|
||||
|
||||
iframe.srcdoc = '<html></html>';
|
||||
document.body.appendChild(iframe);
|
||||
assert_equals(results.length, 1, `bad response: '${text}'`);
|
||||
const result = results[0];
|
||||
// null JSON values parse as null, not undefined
|
||||
assert_equals(result.error, expectedError, 'error recorded in stash');
|
||||
return result;
|
||||
}
|
||||
assert_true(false, 'timeout');
|
||||
}
|
||||
|
|
|
@ -0,0 +1,132 @@
|
|||
// META: timeout=long
|
||||
// META: script=/common/get-host-info.sub.js
|
||||
// META: script=/common/utils.js
|
||||
// META: script=beacon-common.sub.js
|
||||
|
||||
'use strict';
|
||||
|
||||
const {HTTPS_ORIGIN, ORIGIN, HTTPS_REMOTE_ORIGIN} = get_host_info();
|
||||
|
||||
// As /common/redirect.py is not under our control, let's make sure that
|
||||
// it doesn't support CORS.
|
||||
parallelPromiseTest(async (t) => {
|
||||
const destination = `${HTTPS_REMOTE_ORIGIN}/common/text-plain.txt` +
|
||||
`?pipe=header(access-control-allow-origin,*)`;
|
||||
const redirect = `${HTTPS_REMOTE_ORIGIN}/common/redirect.py` +
|
||||
`?location=${encodeURIComponent(destination)}`;
|
||||
|
||||
// Fetching `destination` is fine because it supports CORS.
|
||||
await fetch(destination);
|
||||
|
||||
// Fetching redirect.py should fail because it doesn't support CORS.
|
||||
await promise_rejects_js(t, TypeError, fetch(redirect));
|
||||
}, '/common/redirect.py does not support CORS');
|
||||
|
||||
for (const type of [STRING, ARRAYBUFFER, FORM, BLOB]) {
|
||||
parallelPromiseTest(async (t) => {
|
||||
const iframe = document.createElement('iframe');
|
||||
document.body.appendChild(iframe);
|
||||
t.add_cleanup(() => iframe.remove());
|
||||
|
||||
const payload = makePayload(SMALL, type);
|
||||
const id = token();
|
||||
// As we use "no-cors" for CORS-safelisted requests, the redirect is
|
||||
// processed without an error while the request is cross-origin and the
|
||||
// redirect handler doesn't support CORS.
|
||||
const destination =
|
||||
`${HTTPS_REMOTE_ORIGIN}/beacon/resources/beacon.py?cmd=store&id=${id}`;
|
||||
const url = `${HTTPS_REMOTE_ORIGIN}/common/redirect.py` +
|
||||
`?status=307&location=${encodeURIComponent(destination)}`;
|
||||
|
||||
assert_true(iframe.contentWindow.navigator.sendBeacon(url, payload));
|
||||
iframe.remove();
|
||||
|
||||
await waitForResult(id);
|
||||
}, `cross-origin, CORS-safelisted: type = ${type}`);
|
||||
}
|
||||
|
||||
parallelPromiseTest(async (t) => {
|
||||
const iframe = document.createElement('iframe');
|
||||
document.body.appendChild(iframe);
|
||||
t.add_cleanup(() => iframe.remove());
|
||||
|
||||
const payload = makePayload(SMALL, BLOB, 'application/octet-stream');
|
||||
const id = token();
|
||||
const destination =
|
||||
`${HTTPS_REMOTE_ORIGIN}/beacon/resources/beacon.py?cmd=store&id=${id}`;
|
||||
const url = `${HTTPS_REMOTE_ORIGIN}/common/redirect.py` +
|
||||
`?status=307&location=${encodeURIComponent(destination)}`;
|
||||
assert_true(iframe.contentWindow.navigator.sendBeacon(url, payload));
|
||||
iframe.remove();
|
||||
|
||||
// The beacon is rejected during redirect handling because /common/redirect.py
|
||||
// doesn't support CORS.
|
||||
|
||||
await new Promise((resolve) => step_timeout(resolve, 3000));
|
||||
const res = await fetch(`/beacon/resources/beacon.py?cmd=stat&id=${id}`);
|
||||
assert_equals((await res.json()).length, 0);
|
||||
}, `cross-origin, non-CORS-safelisted: failure case (with redirect)`);
|
||||
|
||||
parallelPromiseTest(async (t) => {
|
||||
const iframe = document.createElement('iframe');
|
||||
document.body.appendChild(iframe);
|
||||
t.add_cleanup(() => iframe.remove());
|
||||
|
||||
const payload = makePayload(SMALL, BLOB, 'application/octet-stream');
|
||||
const id = token();
|
||||
const url =
|
||||
`${HTTPS_REMOTE_ORIGIN}/beacon/resources/beacon.py?cmd=store&id=${id}`;
|
||||
assert_true(iframe.contentWindow.navigator.sendBeacon(url, payload));
|
||||
iframe.remove();
|
||||
|
||||
// The beacon is rejected during preflight handling.
|
||||
await waitForResult(id, /*expectedError=*/ 'Preflight not expected.');
|
||||
}, `cross-origin, non-CORS-safelisted: failure case (without redirect)`);
|
||||
|
||||
for (const credentials of [false, true]) {
|
||||
parallelPromiseTest(async (t) => {
|
||||
const iframe = document.createElement('iframe');
|
||||
document.body.appendChild(iframe);
|
||||
t.add_cleanup(() => iframe.remove());
|
||||
|
||||
const payload = makePayload(SMALL, BLOB, 'application/octet-stream');
|
||||
const id = token();
|
||||
let url = `${HTTPS_REMOTE_ORIGIN}/beacon/resources/beacon.py` +
|
||||
`?cmd=store&id=${id}&preflightExpected&origin=${ORIGIN}`;
|
||||
if (credentials) {
|
||||
url += `&credentials=true`;
|
||||
}
|
||||
assert_true(iframe.contentWindow.navigator.sendBeacon(url, payload));
|
||||
iframe.remove();
|
||||
|
||||
// We need access-control-allow-credentials in the preflight response. This
|
||||
// shows that the request's credentials mode is 'include'.
|
||||
if (credentials) {
|
||||
const result = await waitForResult(id);
|
||||
assert_equals(result.type, 'application/octet-stream');
|
||||
} else {
|
||||
await new Promise((resolve) => step_timeout(resolve, 3000));
|
||||
const res = await fetch(`/beacon/resources/beacon.py?cmd=stat&id=${id}`);
|
||||
assert_equals((await res.json()).length, 0);
|
||||
}
|
||||
}, `cross-origin, non-CORS-safelisted[credentials=${credentials}]`);
|
||||
}
|
||||
|
||||
parallelPromiseTest(async (t) => {
|
||||
const iframe = document.createElement('iframe');
|
||||
document.body.appendChild(iframe);
|
||||
t.add_cleanup(() => iframe.remove());
|
||||
|
||||
const payload = makePayload(SMALL, BLOB, 'application/octet-stream');
|
||||
const id = token();
|
||||
const destination = `${HTTPS_REMOTE_ORIGIN}/beacon/resources/beacon.py` +
|
||||
`?cmd=store&id=${id}&preflightExpected&origin=${ORIGIN}&credentials=true`;
|
||||
const url = `${HTTPS_REMOTE_ORIGIN}/fetch/api/resources/redirect.py` +
|
||||
`?redirect_status=307&allow_headers=content-type` +
|
||||
`&location=${encodeURIComponent(destination)}`;
|
||||
assert_true(iframe.contentWindow.navigator.sendBeacon(url, payload));
|
||||
iframe.remove();
|
||||
|
||||
const result = await waitForResult(id);
|
||||
assert_equals(result.type, 'application/octet-stream');
|
||||
}, `cross-origin, non-CORS-safelisted success-case (with redirect)`);
|
|
@ -1,42 +0,0 @@
|
|||
// META: timeout=long
|
||||
// META: script=/common/utils.js
|
||||
// META: script=beacon-common.sub.js
|
||||
|
||||
"use strict";
|
||||
|
||||
// Execute each sample test with a cross-origin URL. If allowCors is 'true'
|
||||
// the beacon handler will return CORS headers. This test ensures that the
|
||||
// sendBeacon() succeeds in either case.
|
||||
[true, false].forEach(function(allowCors) {
|
||||
function buildUrl(id) {
|
||||
const baseUrl = "http://{{domains[www]}}:{{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
|
||||
// respond with Access-Control-Allow-Origin and Access-Control-Allow-Credentials
|
||||
// headers, an error will be printed to the console, but the request will
|
||||
// already have reached the server. Since beacons are fire-and-forget, the
|
||||
// error will not affect any client script, either -- not even the return
|
||||
// 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.
|
||||
const additionalQuery = allowCors ? "&origin=http://{{host}}:{{ports[http][0]}}&credentials=true" : "";
|
||||
return `${baseUrl}/beacon/resources/beacon.py?cmd=store&id=${id}${additionalQuery}`
|
||||
}
|
||||
runTests(sampleTests, allowCors ? "-CORS-ALLOW" : "-CORS-FORBID", buildUrl);
|
||||
});
|
||||
|
||||
// Now test a cross-origin request that doesn't use a safelisted Content-Type and ensure
|
||||
// we are applying the proper restrictions. Since a non-safelisted Content-Type request
|
||||
// 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.
|
||||
{
|
||||
function buildUrl(id) {
|
||||
const baseUrl = "http://{{domains[www]}}:{{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}`
|
||||
}
|
||||
runTests(preflightTests, "-PREFLIGHT-ALLOW", buildUrl);
|
||||
}
|
||||
|
||||
done();
|
|
@ -1,48 +0,0 @@
|
|||
// META: script=/common/utils.js
|
||||
// META: script=beacon-common.sub.js
|
||||
|
||||
"use strict";
|
||||
|
||||
test(function() {
|
||||
// Payload that should cause sendBeacon to return false because it exceeds the maximum payload size.
|
||||
var exceedPayload = Array(maxPayloadSize + 1).fill('z').join("");
|
||||
|
||||
var success = navigator.sendBeacon("http://{{hosts[][nonexistent]}}", exceedPayload);
|
||||
assert_false(success, "calling 'navigator.sendBeacon()' with payload size exceeding the maximum size must fail");
|
||||
}, "Verify calling 'navigator.sendBeacon()' with a large payload returns 'false'.");
|
||||
|
||||
test(function() {
|
||||
var invalidUrl = "http://invalid:url";
|
||||
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_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.");
|
||||
|
||||
// We'll validate that we can send one beacon that uses our entire Quota and then fail to send one that is just one char.
|
||||
promise_test(async () => {
|
||||
function wait(ms) {
|
||||
return new Promise(res => step_timeout(res, ms));
|
||||
}
|
||||
const url = '/fetch/api/resources/trickle.py?count=1&ms=0';
|
||||
assert_true(navigator.sendBeacon(url, maxPayload),
|
||||
"calling 'navigator.sendBeacon()' with our max payload size should succeed.");
|
||||
|
||||
// Now we'll send just one character.
|
||||
assert_false(navigator.sendBeacon(url, '1'),
|
||||
"calling 'navigator.sendBeacon()' with just one char should fail while our Quota is used up.");
|
||||
|
||||
for (let i = 0; i < 20; ++i) {
|
||||
await wait(100);
|
||||
if (navigator.sendBeacon(url, maxPayload)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
assert_unreached('The quota should recover after fetching.');
|
||||
}, "Verify the behavior after the quota is exhausted.");
|
||||
|
||||
done();
|
|
@ -1,19 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>W3C Beacon Outliving Navigation Test</title>
|
||||
<meta name="timeout" content="long">
|
||||
<meta name="author" title="Microsoft Edge" href="https://www.microsoft.com">
|
||||
<script src="/resources/testharness.js"></script>
|
||||
<script src="/resources/testharnessreport.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<script src="/common/utils.js"></script>
|
||||
<script src="beacon-common.sub.js"></script>
|
||||
<script>
|
||||
"use strict";
|
||||
|
||||
runSendInIframeAndNavigateTests("beacon");
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,23 @@
|
|||
// META: timeout=long
|
||||
// META: script=/common/utils.js
|
||||
// META: script=/common/get-host-info.sub.js
|
||||
// META: script=beacon-common.sub.js
|
||||
|
||||
'use strict';
|
||||
|
||||
const {HTTP_REMOTE_ORIGIN} = get_host_info();
|
||||
|
||||
for (const type of [STRING, ARRAYBUFFER, FORM, BLOB]) {
|
||||
parallelPromiseTest(async (t) => {
|
||||
const iframe = document.createElement('iframe');
|
||||
document.body.appendChild(iframe);
|
||||
t.add_cleanup(() => iframe.remove());
|
||||
|
||||
const payload = makePayload(SMALL, type);
|
||||
const id = token();
|
||||
const url = `/beacon/resources/beacon.py?cmd=store&id=${id}`;
|
||||
assert_true(iframe.contentWindow.navigator.sendBeacon(url, payload));
|
||||
|
||||
iframe.src = `${HTTP_REMOTE_ORIGIN}/common/blank.html`;
|
||||
}, `The frame navigates away after calling sendBeacon[type = ${type}].`);
|
||||
}
|
|
@ -1,28 +0,0 @@
|
|||
// 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);
|
||||
});
|
|
@ -1,3 +0,0 @@
|
|||
test(() => {
|
||||
assert_throws_js(TypeError, () => navigator.sendBeacon("...", new ReadableStream()));
|
||||
}, "sendBeacon() with a stream does not work due to the keepalive flag being set");
|
|
@ -0,0 +1,35 @@
|
|||
// META: timeout=long
|
||||
// META: script=/common/get-host-info.sub.js
|
||||
// META: script=/common/utils.js
|
||||
// META: script=beacon-common.sub.js
|
||||
|
||||
'use strict';
|
||||
|
||||
const {ORIGIN} = get_host_info();
|
||||
|
||||
// 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.
|
||||
for (const status of [307, 308]) {
|
||||
for (const type of [STRING, ARRAYBUFFER, FORM, BLOB]) {
|
||||
parallelPromiseTest(async (t) => {
|
||||
const iframe = document.createElement('iframe');
|
||||
document.body.appendChild(iframe);
|
||||
t.add_cleanup(() => iframe.remove());
|
||||
|
||||
const payload = makePayload(SMALL, type);
|
||||
const id = token();
|
||||
const destination =
|
||||
`${ORIGIN}/beacon/resources/beacon.py?cmd=store&id=${id}`;
|
||||
const url = `${ORIGIN}/common/redirect.py` +
|
||||
`?status=${status}&location=${encodeURIComponent(destination)}`;
|
||||
|
||||
assert_true(iframe.contentWindow.navigator.sendBeacon(url, payload));
|
||||
iframe.remove();
|
||||
|
||||
await waitForResult(id);
|
||||
}, `cross-origin, CORS-safelisted: status = ${status}, type = ${type}`);
|
||||
}
|
||||
};
|
||||
|
||||
done();
|
|
@ -1,20 +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) {
|
||||
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();
|
|
@ -24,8 +24,13 @@ def main(request, response):
|
|||
nature of the stash, results for a given test are only guaranteed to be
|
||||
returned once, though they may be returned multiple times.
|
||||
|
||||
An entry may contain following members.
|
||||
- error: An error string. null if there is no error.
|
||||
- type: The content-type header of the request "(missing)" if there
|
||||
is no content-type header in the request.
|
||||
|
||||
Example response bodies:
|
||||
- [{error: null}]
|
||||
- [{error: null, type: "text/plain;charset=UTF8"}]
|
||||
- [{error: "some validation details"}]
|
||||
- []
|
||||
|
||||
|
@ -53,8 +58,9 @@ def main(request, response):
|
|||
# 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"]:
|
||||
contentType = request.headers[b"Content-Type"] \
|
||||
if b"Content-Type" in request.headers else b"(missing)"
|
||||
if b"form-data" in contentType:
|
||||
if b"payload" in request.POST:
|
||||
# The payload was sent as a FormData.
|
||||
payload = request.POST.first(b"payload")
|
||||
|
@ -71,20 +77,26 @@ def main(request, response):
|
|||
|
||||
# Confirm the payload size sent matches with the number of
|
||||
# characters sent.
|
||||
if payload_size != len(payload_parts[1]):
|
||||
if payload_size != len(payload):
|
||||
error = u"expected %d characters but got %d" % (
|
||||
payload_size, len(payload_parts[1]))
|
||||
payload_size, len(payload))
|
||||
else:
|
||||
# Confirm the payload contains the correct characters.
|
||||
for i in range(0, payload_size):
|
||||
if payload_parts[1][i:i+1] != b"*":
|
||||
for i in range(len(payload)):
|
||||
if i <= len(payload_parts[0]):
|
||||
continue
|
||||
c = payload[i:i+1]
|
||||
if c != b"*":
|
||||
error = u"expected '*' at index %d but got '%s''" % (
|
||||
i, isomorphic_decode(payload_parts[1][i:i+1]))
|
||||
i, isomorphic_decode(c))
|
||||
break
|
||||
|
||||
# Store the result in the stash so that it can be retrieved
|
||||
# later with a 'stat' command.
|
||||
request.server.stash.put(id, {u"error": error})
|
||||
request.server.stash.put(id, {
|
||||
u"error": error,
|
||||
u"type": isomorphic_decode(contentType)
|
||||
})
|
||||
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
|
||||
|
|
Загрузка…
Ссылка в новой задаче