forked from web-platform-tests/wpt
-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathbeacon-common.sub.js
237 lines (211 loc) · 11.4 KB
/
beacon-common.sub.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
"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);
}