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:
Yutaka Hirano 2021-02-11 16:47:56 +00:00 коммит произвёл moz-wptsync-bot
Родитель 8df20530ed
Коммит cc0f2153f8
20 изменённых файлов: 406 добавлений и 471 удалений

Просмотреть файл

@ -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