Auto merge of #25878 - servo-wpt-sync:wpt_update_02-03-2020, r=servo-wpt-sync

Sync WPT with upstream (02-03-2020)

Automated downstream sync of changes from upstream as of 02-03-2020.
[no-wpt-sync]
r? @servo-wpt-sync
This commit is contained in:
bors-servo 2020-03-02 15:01:06 -05:00 committed by GitHub
commit eef3899fb1
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
43 changed files with 475 additions and 1628 deletions

View file

@ -230368,23 +230368,6 @@
}
}
},
"std-toast": {
"ref-tests": {
"toast-slotting.html": [
"4d6f5038f2d6ffa80ebdfa12798c8c4202b39670",
[
null,
[
[
"/std-toast/ref-tests/toast-slotting-expected.html",
"=="
]
],
{}
]
]
}
},
"svg": {
"coordinate-systems": {
"abspos.html": [
@ -239750,7 +239733,7 @@
[]
],
"beacon-common.sub.js": [
"ae2f169f272e9efbbea3b7464ea77c34fe65c6e1",
"3635da73b75400ed3699576e2eac6329c39bba1b",
[]
],
"headers": {
@ -239761,7 +239744,7 @@
},
"resources": {
"beacon.py": [
"5f2553d3c4d506f7e292cfb73d81930a83a12d76",
"34b55b1a95b9fb6b925b71541a090f23a4aba21d",
[]
],
"content-type.py": [
@ -315646,7 +315629,7 @@
[]
],
"postback.html.headers": [
"6604450991a122e3e241e40b1b9e0516c525389d",
"4e798cd9f5d3f756df077a43ce9a1a6f9b41fd28",
[]
]
}
@ -319657,7 +319640,7 @@
[]
],
"echo-iframe.html.headers": [
"6604450991a122e3e241e40b1b9e0516c525389d",
"4e798cd9f5d3f756df077a43ce9a1a6f9b41fd28",
[]
],
"echo-worker.js": [
@ -341080,24 +341063,6 @@
[]
]
},
"std-toast": {
"META.yml": [
"c54357a03f214572481fc08be473cc308d50097b",
[]
],
"ref-tests": {
"toast-slotting-expected.html": [
"b4b365c7a9dfa25363acfb3123b1c019c3ca08d0",
[]
]
},
"resources": {
"helpers.js": [
"256f0c880408c3343d36392f8b4cf64d9bbbfbe6",
[]
]
}
},
"storage": {
"META.yml": [
"2aad1fb513710e839ad86c923bf208dae604d3d2",
@ -373249,7 +373214,7 @@
]
],
"beacon-cors.sub.window.js": [
"4a8e4dfbc6570e58ae7d57e409d4fbbc72a47964",
"5543bddc5f66b3a09c54937c0cf665d2d25ea8d6",
[
"beacon/beacon-cors.sub.window.html",
{
@ -373299,7 +373264,7 @@
]
],
"beacon-preflight-failure.sub.window.js": [
"27e6905a9bd95a78621fe86d07d3671f95af111e",
"c5a2d813f176adc42d3ce3b6feeea2b6e34e9c69",
[
"beacon/beacon-preflight-failure.sub.window.html",
{
@ -373323,10 +373288,10 @@
{}
]
],
"beacon-redirect.window.js": [
"3a8aef3c742b68348fa2c27303e297e8386768b3",
"beacon-redirect.sub.window.js": [
"fd23a45f86badfc1d540de414cb351c4e5ef06bb",
[
"beacon/beacon-redirect.window.html",
"beacon/beacon-redirect.sub.window.html",
{
"script_metadata": [
[
@ -431060,7 +431025,7 @@
]
],
"none.https.html": [
"e603753084cf2c3c85b0187a7a8f66b3de8cf401",
"548525968352147dc14ceeeab1ad6b6a93b2729a",
[
null,
{
@ -529186,82 +529151,6 @@
]
]
},
"std-toast": {
"actions.html": [
"6a80def09985f27867f2c41bbb138b1a40399641",
[
null,
{}
]
],
"attributes.html": [
"9b87280b6688b3847401ea7b97ed63f6164bf70a",
[
null,
{}
]
],
"closebutton.html": [
"d53c78aca5da605bece2433c3796d8b1197e848e",
[
null,
{}
]
],
"events-open.html": [
"a49414d2fce2061bcc1399e222cd6b2b5ccdc872",
[
null,
{}
]
],
"events-showhide.html": [
"547f742f713cbf4c46a82ae1df353aa317b2d2df",
[
null,
{}
]
],
"methods.html": [
"981b9fba4341e5798c7456e02da53e97bd1369f3",
[
null,
{}
]
],
"options.html": [
"0383d71f9ae9ff9c2f682abd8158b9c2e31e245b",
[
null,
{
"timeout": "long"
}
]
],
"reflection.html": [
"36cbaf652287dbbde5a871b398d313cf70d1b275",
[
null,
{
"timeout": "long"
}
]
],
"show-toast.html": [
"50cdc2c06ddbf34b16119f9c42e3a7cbaf5ec258",
[
null,
{}
]
],
"styles.html": [
"1db8620485dc459577649e0c2689194d106dcc51",
[
null,
{}
]
]
},
"storage": {
"estimate-indexeddb.https.any.js": [
"c62f5a96c1a2c22142988eabd5b59ce3d3409f95",
@ -551948,7 +551837,7 @@
]
],
"RTCDTMFSender-insertDTMF.https.html": [
"ac27c5139ccb12715d51cbd8f5ecdd83c40c23d1",
"71cfe70171f7ff4aeddd01483fce6edbf29e59b9",
[
null,
{}
@ -552252,6 +552141,13 @@
{}
]
],
"RTCPeerConnection-operations.https.html": [
"e88c8b28172c1c0edbfaef4dda10a306470ad96f",
[
null,
{}
]
],
"RTCPeerConnection-remote-track-mute.https.html": [
"c280a7d44d8d2a477f2600c741ed68903e516651",
[
@ -552304,7 +552200,7 @@
]
],
"RTCPeerConnection-setLocalDescription-parameterless.https.html": [
"0cce78bfbca161016721c4e2b122382c4be868f7",
"5a7a76319a0571961d7dacc3d1562327089587bd",
[
null,
{}

View file

@ -1,4 +0,0 @@
[hit-test-floats-004.html]
[Miss float below something else]
expected: FAIL

View file

@ -1,2 +1,2 @@
[no-transition-from-ua-to-blocking-stylesheet.html]
expected: TIMEOUT
expected: FAIL

View file

@ -1,4 +0,0 @@
[CaretPosition-001.html]
[Element at (400, 100)]
expected: FAIL

View file

@ -2,3 +2,6 @@
[listeners are called when <iframe> is resized]
expected: FAIL
[listeners are called correct number of times]
expected: FAIL

View file

@ -21,6 +21,3 @@
[test the top of layer]
expected: FAIL
[test some point of the element: top left corner]
expected: FAIL

View file

@ -312,18 +312,9 @@
[fetch(): separate response Content-Type: text/plain ]
expected: NOTRUN
[<iframe>: combined response Content-Type: text/html */*;charset=gbk]
expected: FAIL
[<iframe>: combined response Content-Type: text/html */*]
expected: FAIL
[<iframe>: combined response Content-Type: text/html;" text/plain]
expected: FAIL
[<iframe>: separate response Content-Type: text/html */*;charset=gbk]
expected: FAIL
[<iframe>: separate response Content-Type: text/html;" \\" text/plain]
expected: FAIL
[<iframe>: separate response Content-Type: text/html;" text/plain]
expected: FAIL

View file

@ -53,9 +53,9 @@
[combined text/javascript ]
expected: FAIL
[separate text/javascript x/x]
expected: FAIL
[separate text/javascript;charset=windows-1252 text/javascript]
expected: FAIL
[separate text/javascript error]
expected: FAIL

View file

@ -11,6 +11,6 @@
[X-Content-Type-Options%3A%20nosniff%2C%2C%40%23%24%23%25%25%26%5E%26%5E*()()11!]
expected: FAIL
[X-Content-Type-Options%3A%20%40%23%24%23%25%25%26%5E%26%5E*()()11!%2Cnosniff]
[Content-Type-Options%3A%20nosniff]
expected: FAIL

View file

@ -0,0 +1,4 @@
[traverse_the_history_1.html]
[Multiple history traversals from the same task]
expected: FAIL

View file

@ -1,4 +0,0 @@
[traverse_the_history_4.html]
[Multiple history traversals, last would be aborted]
expected: FAIL

View file

@ -1,4 +1,4 @@
[traverse_the_history_3.html]
[traverse_the_history_5.html]
[Multiple history traversals, last would be aborted]
expected: FAIL

View file

@ -1,6 +1,6 @@
[iframe_sandbox_popups_nonescaping-3.html]
type: testharness
expected: TIMEOUT
expected: CRASH
[Check that popups from a sandboxed iframe do not escape the sandbox]
expected: NOTRUN

View file

@ -1,4 +1,5 @@
[realtimeanalyser-fft-scaling.html]
expected: TIMEOUT
[X 2048-point FFT peak position is not equal to 64. Got 0.]
expected: FAIL

View file

@ -23,3 +23,6 @@
[X Rendered audio for channel 5 does not equal [0,0.0626220703125,0.125030517578125,0.18695068359375,0.24810791015625,0.308319091796875,0.3673095703125,0.42486572265625,0.480743408203125,0.53472900390625,0.58660888671875,0.636199951171875,0.68328857421875,0.727691650390625,0.76922607421875,0.8077392578125...\] with an element-wise tolerance of {"absoluteThreshold":0.000030517578125,"relativeThreshold":0}.\n\tIndex\tActual\t\t\tExpected\t\tAbsError\t\tRelError\t\tTest threshold\n\t[1\]\t3.6732959747314453e-1\t6.2622070312500000e-2\t3.0470752716064453e-1\t4.8658168859649127e+0\t3.0517578125000000e-5\n\t[2\]\t6.8329977989196777e-1\t1.2503051757812500e-1\t5.5826926231384277e-1\t4.4650639949963384e+0\t3.0517578125000000e-5\n\t[3\]\t9.0373212099075317e-1\t1.8695068359375000e-1\t7.1678143739700317e-1\t3.8340669508039502e+0\t3.0517578125000000e-5\n\t[4\]\t9.9780619144439697e-1\t2.4810791015625000e-1\t7.4969828128814697e-1\t3.0216621502152523e+0\t3.0517578125000000e-5\n\t[5\]\t9.5236867666244507e-1\t3.0831909179687500e-1\t6.4404958486557007e-1\t2.0889059484187866e+0\t3.0517578125000000e-5\n\t...and 44048 more errors.\n\tMax AbsError of 1.9986916780471802e+0 at index of 29020.\n\t[29020\]\t9.9994289875030518e-1\t-9.9874877929687500e-1\t1.9986916780471802e+0\t2.0011956154322119e+0\t3.0517578125000000e-5\n\tMax RelError of Infinity at index of 10584.\n\t[10584\]\t-5.8778524398803711e-1\t0.0000000000000000e+0\t5.8778524398803711e-1\tInfinity\t3.0517578125000000e-5\n]
expected: FAIL
[X Rendered audio for channel 5 does not equal [0,0.0626220703125,0.125030517578125,0.18695068359375,0.24810791015625,0.308319091796875,0.3673095703125,0.42486572265625,0.480743408203125,0.53472900390625,0.58660888671875,0.636199951171875,0.68328857421875,0.727691650390625,0.76922607421875,0.8077392578125...\] with an element-wise tolerance of {"absoluteThreshold":0.000030517578125,"relativeThreshold":0}.\n\tIndex\tActual\t\t\tExpected\t\tAbsError\t\tRelError\t\tTest threshold\n\t[1\]\t3.6732959747314453e-1\t6.2622070312500000e-2\t3.0470752716064453e-1\t4.8658168859649127e+0\t3.0517578125000000e-5\n\t[2\]\t6.8329977989196777e-1\t1.2503051757812500e-1\t5.5826926231384277e-1\t4.4650639949963384e+0\t3.0517578125000000e-5\n\t[3\]\t9.0373212099075317e-1\t1.8695068359375000e-1\t7.1678143739700317e-1\t3.8340669508039502e+0\t3.0517578125000000e-5\n\t[4\]\t9.9780619144439697e-1\t2.4810791015625000e-1\t7.4969828128814697e-1\t3.0216621502152523e+0\t3.0517578125000000e-5\n\t[5\]\t9.5236867666244507e-1\t3.0831909179687500e-1\t6.4404958486557007e-1\t2.0889059484187866e+0\t3.0517578125000000e-5\n\t...and 44056 more errors.\n\tMax AbsError of 1.9999977350234985e+0 at index of 10361.\n\t[10361\]\t9.9999773502349854e-1\t-1.0000000000000000e+0\t1.9999977350234985e+0\t1.9999977350234985e+0\t3.0517578125000000e-5\n\tMax RelError of Infinity at index of 7056.\n\t[7056\]\t5.8778524398803711e-1\t0.0000000000000000e+0\t5.8778524398803711e-1\tInfinity\t3.0517578125000000e-5\n]
expected: FAIL

View file

@ -152,3 +152,9 @@
[X Stitched sine-wave buffers at sample rate 43800 does not equal [0,0.06264832615852356,0.12505052983760834,0.18696144223213196,0.24813786149024963,0.308339387178421,0.36732959747314453,0.4248766601085663,0.480754554271698,0.5347436666488647,0.5866320133209229,0.6362156271934509,0.6832997798919678,0.7276994585990906,0.7692402601242065,0.8077589869499207...\] with an element-wise tolerance of {"absoluteThreshold":0.0038986,"relativeThreshold":0}.\n\tIndex\tActual\t\t\tExpected\t\tAbsError\t\tRelError\t\tTest threshold\n\t[19030\]\t-6.4853054482227890e-8\t-7.3546999692916870e-1\t7.3546993207611422e-1\t9.9999991182093795e-1\t3.8985999999999999e-3\n\t[19031\]\t-3.6017334461212158e-1\t-6.9157749414443970e-1\t3.3140414953231812e-1\t4.7920030992665957e-1\t3.8985999999999999e-3\n\t[38059\]\t-1.5015864107681409e-7\t-9.8956179618835449e-1\t9.8956164602971342e-1\t9.9999984825743915e-1\t3.8985999999999999e-3\n\t[38060\]\t-8.8409073650836945e-2\t-9.9664616584777832e-1\t9.0823709219694138e-1\t9.1129341918891205e-1\t3.8985999999999999e-3\n\tMax AbsError of 9.8956164602971342e-1 at index of 38059.\n\tMax RelError of 9.9999991182093795e-1 at index of 19030.\n]
expected: FAIL
[X SNR (39.5689338967812 dB) is not greater than or equal to 65.737. Got 39.5689338967812.]
expected: FAIL
[X Stitched sine-wave buffers at sample rate 43800 does not equal [0,0.06264832615852356,0.12505052983760834,0.18696144223213196,0.24813786149024963,0.308339387178421,0.36732959747314453,0.4248766601085663,0.480754554271698,0.5347436666488647,0.5866320133209229,0.6362156271934509,0.6832997798919678,0.7276994585990906,0.7692402601242065,0.8077589869499207...\] with an element-wise tolerance of {"absoluteThreshold":0.0038986,"relativeThreshold":0}.\n\tIndex\tActual\t\t\tExpected\t\tAbsError\t\tRelError\t\tTest threshold\n\t[19030\]\t-4.2493608780205250e-3\t-7.3546999692916870e-1\t7.3122063605114818e-1\t9.9422225121927066e-1\t3.8985999999999999e-3\n\t[19031\]\t-3.6017334461212158e-1\t-6.9157749414443970e-1\t3.3140414953231812e-1\t4.7920030992665957e-1\t3.8985999999999999e-3\n\t[38059\]\t-9.8401503637433052e-3\t-9.8956179618835449e-1\t9.7972164582461119e-1\t9.9005605268751673e-1\t3.8985999999999999e-3\n\t[38060\]\t-8.8409073650836945e-2\t-9.9664616584777832e-1\t9.0823709219694138e-1\t9.1129341918891205e-1\t3.8985999999999999e-3\n\tMax AbsError of 9.7972164582461119e-1 at index of 38059.\n\tMax RelError of 9.9422225121927066e-1 at index of 19030.\n]
expected: FAIL

View file

@ -1,5 +0,0 @@
[018.html]
expected: TIMEOUT
[origin of the script that invoked the method, javascript:]
expected: TIMEOUT

View file

@ -16,33 +16,33 @@ var largePayload = largePayloadSize + ":" + Array(largePayloadSize).fill('*').jo
var maxPayload = (maxPayloadSize - 6) + ":" + Array(maxPayloadSize - 6).fill('*').join("")
// Test case definitions.
// id: String containing the unique name of the test case.
// name: String containing the unique name of the test case.
// data: Payload object to send through sendbeacon.
var noDataTest = { id: "NoData" };
var nullDataTest = { id: "NullData", data: null };
var undefinedDataTest = { id: "UndefinedData", data: undefined };
var smallStringTest = { id: "SmallString", data: smallPayload };
var mediumStringTest = { id: "MediumString", data: mediumPayload };
var largeStringTest = { id: "LargeString", data: largePayload };
var maxStringTest = { id: "MaxString", data: maxPayload };
var emptyBlobTest = { id: "EmptyBlob", data: new Blob() };
var smallBlobTest = { id: "SmallBlob", data: new Blob([smallPayload]) };
var mediumBlobTest = { id: "MediumBlob", data: new Blob([mediumPayload]) };
var largeBlobTest = { id: "LargeBlob", data: new Blob([largePayload]) };
var maxBlobTest = { id: "MaxBlob", data: new Blob([maxPayload]) };
var emptyBufferSourceTest = { id: "EmptyBufferSource", data: new Uint8Array() };
var smallBufferSourceTest = { id: "SmallBufferSource", data: CreateArrayBufferFromPayload(smallPayload) };
var mediumBufferSourceTest = { id: "MediumBufferSource", data: CreateArrayBufferFromPayload(mediumPayload) };
var largeBufferSourceTest = { id: "LargeBufferSource", data: CreateArrayBufferFromPayload(largePayload) };
var maxBufferSourceTest = { id: "MaxBufferSource", data: CreateArrayBufferFromPayload(maxPayload) };
var emptyFormDataTest = { id: "EmptyFormData", data: CreateEmptyFormDataPayload() };
var smallFormDataTest = { id: "SmallFormData", data: CreateFormDataFromPayload(smallPayload) };
var mediumFormDataTest = { id: "MediumFormData", data: CreateFormDataFromPayload(mediumPayload) };
var largeFormDataTest = { id: "LargeFormData", data: CreateFormDataFromPayload(largePayload) };
var smallSafeContentTypeEncodedTest = { id: "SmallSafeContentTypeEncoded", data: new Blob([smallPayload], { type: 'application/x-www-form-urlencoded' }) };
var smallSafeContentTypeFormTest = { id: "SmallSafeContentTypeForm", data: new FormData() };
var smallSafeContentTypeTextTest = { id: "SmallSafeContentTypeText", data: new Blob([smallPayload], { type: 'text/plain' }) };
var smallCORSContentTypeTextTest = { id: "SmallCORSContentTypeText", data: new Blob([smallPayload], { type: 'text/html' }) };
var noDataTest = { name: "NoData" };
var nullDataTest = { name: "NullData", data: null };
var undefinedDataTest = { name: "UndefinedData", data: undefined };
var smallStringTest = { name: "SmallString", data: smallPayload };
var mediumStringTest = { name: "MediumString", data: mediumPayload };
var largeStringTest = { name: "LargeString", data: largePayload };
var maxStringTest = { name: "MaxString", data: maxPayload };
var emptyBlobTest = { name: "EmptyBlob", data: new Blob() };
var smallBlobTest = { name: "SmallBlob", data: new Blob([smallPayload]) };
var mediumBlobTest = { name: "MediumBlob", data: new Blob([mediumPayload]) };
var largeBlobTest = { name: "LargeBlob", data: new Blob([largePayload]) };
var maxBlobTest = { name: "MaxBlob", data: new Blob([maxPayload]) };
var emptyBufferSourceTest = { name: "EmptyBufferSource", data: new Uint8Array() };
var smallBufferSourceTest = { name: "SmallBufferSource", data: CreateArrayBufferFromPayload(smallPayload) };
var mediumBufferSourceTest = { name: "MediumBufferSource", data: CreateArrayBufferFromPayload(mediumPayload) };
var largeBufferSourceTest = { name: "LargeBufferSource", data: CreateArrayBufferFromPayload(largePayload) };
var maxBufferSourceTest = { name: "MaxBufferSource", data: CreateArrayBufferFromPayload(maxPayload) };
var emptyFormDataTest = { name: "EmptyFormData", data: CreateEmptyFormDataPayload() };
var smallFormDataTest = { name: "SmallFormData", data: CreateFormDataFromPayload(smallPayload) };
var mediumFormDataTest = { name: "MediumFormData", data: CreateFormDataFromPayload(mediumPayload) };
var largeFormDataTest = { name: "LargeFormData", data: CreateFormDataFromPayload(largePayload) };
var smallSafeContentTypeEncodedTest = { name: "SmallSafeContentTypeEncoded", data: new Blob([smallPayload], { type: 'application/x-www-form-urlencoded' }) };
var smallSafeContentTypeFormTest = { name: "SmallSafeContentTypeForm", data: new FormData() };
var smallSafeContentTypeTextTest = { name: "SmallSafeContentTypeText", data: new Blob([smallPayload], { type: 'text/plain' }) };
var smallCORSContentTypeTextTest = { name: "SmallCORSContentTypeText", data: new Blob([smallPayload], { type: 'text/html' }) };
// We don't test maxFormData because the extra multipart separators make it difficult to
// calculate a maxPayload.
@ -66,13 +66,6 @@ var sampleTests = [noDataTest, nullDataTest, undefinedDataTest, smallStringTest,
var preflightTests = [smallCORSContentTypeTextTest];
// Build a test lookup table, which is useful when instructing a web worker or an iframe
// to run a test, so that we don't have to marshal the entire test case across a process boundary.
var testLookup = {};
allTests.forEach(function(testCase) {
testLookup[testCase.id] = testCase;
});
// Helper function to create an ArrayBuffer representation of a string.
function CreateArrayBufferFromPayload(payload) {
var length = payload.length;
@ -105,76 +98,27 @@ function CreateFormDataFromPayload(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.
// Parameters:
// testCases: An array of test cases.
// suffix [optional]: A string used for the suffix for each test case name.
// buildUrl [optional]: A function that returns a beacon URL given an id.
// sendData [optional]: A function that sends the beacon with given a URL and payload.
function runTests(testCases, suffix = '', buildUrl = self.buildUrl, sendData = self.sendData) {
for (const testCase of testCases) {
const id = token();
async_test((test) => {
const url = buildUrl(id);
assert_true(sendData(url, testCase.data), 'sendBeacon should succeed');
waitForResult(id).then(() => test.done(), test.step_func((e) => {throw e;}));
}, `Verify 'navigator.sendbeacon()' successfully sends for variant: ${testCase.name}${suffix}`);
};
}
// Schedules async_test's for each of the test cases, treating them as a single session,
// and wires up the continueAfterSendingBeacon() and waitForResults() calls.
// The method looks for several "extension" functions in the global scope:
// - self.buildBaseUrl: if present, can change the base URL of a beacon target URL (this
// is the scheme, hostname, and port).
// - self.buildTargetUrl: if present, can modify a beacon target URL (for example wrap it).
// Parameters:
// testCases: An array of test cases.
// sendData [optional]: A function that sends the beacon.
function runTests(testCases, sendData = self.sendData) {
const session = initSession(testCases);
testCases.forEach(function(testCase, testIndex) {
// Make a copy of the test case as we'll be storing some metadata on it,
// such as which session it belongs to.
const testCaseCopy = Object.assign({ session: session }, testCase);
testCaseCopy.index = testIndex;
async_test((test) => {
// Save the testharness.js 'test' object, so that we only have one object
// to pass around.
testCaseCopy.test = test;
// Extension point: generate the beacon URL.
var baseUrl = "http://{{host}}:{{ports[http][0]}}";
if (self.buildBaseUrl) {
baseUrl = self.buildBaseUrl(baseUrl);
}
var targetUrl = `${baseUrl}/beacon/resources/beacon.py?cmd=store&sid=${session.id}&tid=${testCaseCopy.id}&tidx=${testIndex}`;
if (self.buildTargetUrl) {
targetUrl = self.buildTargetUrl(targetUrl);
}
// Attach the URL to the test object for debugging purposes.
testCaseCopy.url = targetUrl;
assert_true(sendData(testCaseCopy), 'sendBeacon should succeed');
waitForResult(testCaseCopy).then(() => test.done(), test.step_func((e) => {throw e;}));
}, `Verify 'navigator.sendbeacon()' successfully sends for variant: ${testCaseCopy.id}`);
});
function buildUrl(id) {
const baseUrl = "http://{{host}}:{{ports[http][0]}}";
return `${baseUrl}/beacon/resources/beacon.py?cmd=store&id=${id}`;
}
// Sends the beacon for a single test. This step is factored into its own function so that
@ -183,15 +127,13 @@ function runTests(testCases, sendData = self.sendData) {
// full testharness.js test context. Instead return 'false', and the main scope will fail
// the test.
// Returns the result of the 'sendbeacon()' function call, true or false.
function sendData(testCase) {
return self.navigator.sendBeacon(testCase.url, testCase.data);
function sendData(url, payload) {
return self.navigator.sendBeacon(url, payload);
}
// Poll the server for the test result.
async function waitForResult(testCase) {
const session = testCase.session;
const index = testCase.index;
const url = `resources/beacon.py?cmd=stat&sid=${session.id}&tidx_min=${index}&tidx_max=${index}`;
async function waitForResult(id) {
const url = `resources/beacon.py?cmd=stat&id=${id}`;
for (let i = 0; i < 30; ++i) {
const response = await fetch(url);
const text = await response.text();
@ -218,16 +160,10 @@ function runSendInIframeAndNavigateTests() {
iframe.onload = function() {
// Clear our onload handler to prevent re-running the tests as we navigate away.
iframe.onload = null;
function sendData(testCase) {
return iframe.contentWindow.navigator.sendBeacon(testCase.url, testCase.data);
function sendData(url, payload) {
return iframe.contentWindow.navigator.sendBeacon(url, payload);
}
const tests = [];
for (const test of sampleTests) {
const copy = Object.assign({}, test);
copy.id = `${test.id}-NAVIGATE`;
tests.push(copy);
}
runTests(tests, sendData);
runTests(sampleTests, '-NAVIGATE', self.buildUrl, sendData);
// Now navigate ourselves.
iframe.contentWindow.location = "http://{{host}}:{{ports[http][0]}}/";
};

View file

@ -8,15 +8,8 @@
// the beacon handler will return CORS headers. This test ensures that the
// sendBeacon() succeeds in either case.
[true, false].forEach(function(allowCors) {
// Implement the self.buildBaseUrl and self.buildTargetUrl extensions
// to change the target URL to use a cross-origin domain name.
self.buildBaseUrl = function(baseUrl) {
return "http://{{domains[www]}}:{{ports[http][0]}}";
};
// Implement the self.buildTargetUrl extension to append a directive
// to the handler, that it should return CORS headers, if 'allowCors'
// is true.
self.buildTargetUrl = function(targetUrl) {
function buildUrl(id) {
const baseUrl = "http://{{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
@ -27,16 +20,10 @@
// value of the sendBeacon() call, because the underlying fetch is asynchronous.
// The "Beacon CORS" tests are merely testing that sendBeacon() to a cross-
// origin URL *will* work regardless.
return allowCors ? `${targetUrl}&origin=http://{{host}}:{{ports[http][0]}}&credentials=true` : targetUrl;
const additionalQuery = allowCors ? "&origin=http://{{host}}:{{ports[http][0]}}&credentials=true" : "";
return `${baseUrl}/beacon/resources/beacon.py?cmd=store&id=${id}${additionalQuery}`
}
const tests = [];
for (const test of sampleTests) {
const copy = Object.assign({}, test);
copy.id = `${test.id}-${allowCors ? "CORS-ALLOW" : "CORS-FORBID"}`;
tests.push(copy);
}
runTests(tests);
runTests(sampleTests, allowCors ? "-CORS-ALLOW" : "-CORS-FORBID", buildUrl);
});
// Now test a cross-origin request that doesn't use a safelisted Content-Type and ensure
@ -44,24 +31,12 @@
// header is used there should be a preflight/options request and we should only succeed
// send the payload if the proper CORS headers are used.
{
// Implement the self.buildBaseUrl and self.buildTargetUrl extensions
// to change the target URL to use a cross-origin domain name.
self.buildBaseUrl = function (baseUrl) {
return "http://{{domains[www]}}:{{ports[http][0]}}";
};
// Implement the self.buildTargetUrl extension to append a directive
// to the handler, that it should return CORS headers for the preflight we expect.
self.buildTargetUrl = function (targetUrl) {
return `${targetUrl}&origin=http://{{host}}:{{ports[http][0]}}&credentials=true&preflightExpected=true`;
function buildUrl(id) {
const baseUrl = "http://{{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}`
}
const tests = [];
for (const test of preflightTests) {
const copy = Object.assign({}, test);
copy.id = `${test.id}-PREFLIGHT-ALLOW`;
tests.push(copy);
}
runTests(tests);
runTests(preflightTests, "-PREFLIGHT-ALLOW", buildUrl);
}
done();

View file

@ -2,13 +2,10 @@
// META: script=/common/get-host-info.sub.js
promise_test(async (test) => {
const sid = token();
const tid = token();
const origin = get_host_info().REMOTE_ORIGIN;
const store =
`${origin}/beacon/resources/beacon.py?cmd=store&sid=${sid}&tid=${tid}&tidx=0`;
const monitor =
`/beacon/resources/beacon.py?cmd=stat&sid=${sid}&tidx_min=0&tidx_max=0`;
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'})));
@ -25,7 +22,7 @@ promise_test(async (test) => {
}
const expected =
JSON.stringify([{id: tid, error: 'Preflight not expected.'}]);
JSON.stringify([{error: 'Preflight not expected.'}]);
assert_equals(actual, expected);
});

View file

@ -8,18 +8,13 @@
// Note that status codes 307 and 308 are the only codes that will maintain POST data
// through a redirect.
[307, 308].forEach(function(status) {
// Implement the self.buildTargetUrl extension to inject a redirect to
// the sendBeacon target.
self.buildTargetUrl = function(targetUrl) {
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)}`;
};
const tests = [];
for (const test of sampleTests) {
const copy = Object.assign({}, test);
copy.id = `${test.id}-${status}`;
tests.push(copy);
}
runTests(tests);
runTests(sampleTests, `-${status}`, buildUrl);
});
done();

View file

@ -1,7 +1,5 @@
import json
def build_stash_key(session_id, test_num):
return "%s_%s" % (session_id, test_num)
def main(request, response):
"""Helper handler for Beacon tests.
@ -9,63 +7,50 @@ def main(request, response):
It handles two forms of requests:
STORE:
A URL with a query string of the form 'cmd=store&sid=<token>&tidx=<test_index>&tid=<test_name>'.
A URL with a query string of the form 'cmd=store&id=<token>'.
Stores the receipt of a sendBeacon() request along with its validation result, returning HTTP 200 OK.
Stores the receipt of a sendBeacon() request along with its validation
result, returning HTTP 200 OK.
Parameters:
tidx - the integer index of the test.
tid - a friendly identifier or name for the test, used when returning results.
if "preflightExpected" exists in the query, this handler responds to
CORS preflights.
STAT:
A URL with a query string of the form 'cmd=stat&sid=<token>&tidx_min=<min_test_index>&tidx_max=<max_test_index>'.
A URL with a query string of the form 'cmd=stat&id=<token>'.
Retrieves the results of test with indices [min_test_index, max_test_index] and returns them as
a JSON array and HTTP 200 OK status code. Due to the eventual read-once nature of the stash, results for a given test
are only guaranteed to be returned once, though they may be returned multiple times.
Retrieves the results of test for the given id and returns them as a
JSON array and HTTP 200 OK status code. Due to the eventual read-once
nature of the stash, results for a given test are only guaranteed to be
returned once, though they may be returned multiple times.
Parameters:
tidx_min - the lower-bounding integer test index.
tidx_max - the upper-bounding integer test index.
Example response body:
[{"id": "Test1", error: null}, {"id": "Test2", error: "some validation details"}]
Example response bodies:
- [{error: null}]
- [{error: "some validation details"}]
- []
Common parameters:
cmd - the command, 'store' or 'stat'.
sid - session id used to provide isolation to a test run comprising multiple sendBeacon()
tests.
id - the unique identifier of the test.
"""
session_id = request.GET.first("sid");
command = request.GET.first("cmd").lower();
# Workaround to circumvent the limitation that cache keys
# can only be UUID's.
def wrap_key(key, path):
return (str(path), str(key))
request.server.stash._wrap_key = wrap_key
id = request.GET.first("id")
command = request.GET.first("cmd").lower()
# Append CORS headers if needed.
if "origin" in request.GET:
response.headers.set("Access-Control-Allow-Origin", request.GET.first("origin"))
response.headers.set("Access-Control-Allow-Origin",
request.GET.first("origin"))
if "credentials" in request.GET:
response.headers.set("Access-Control-Allow-Credentials", request.GET.first("credentials"))
response.headers.set("Access-Control-Allow-Credentials",
request.GET.first("credentials"))
# Handle the 'store' and 'stat' commands.
if command == "store":
# The test id is just used to make the results more human-readable.
test_id = request.GET.first("tid")
# The test index is used to build a predictable stash key, together
# with the unique session id, in order to retrieve a range of results
# later knowing the index range.
test_idx = request.GET.first("tidx")
test_data = { "id": test_id, "error": None }
error = None
# Only store the actual POST requests, not any preflight/OPTIONS requests we may get.
# Only store the actual POST requests, not any preflight/OPTIONS
# requests we may get.
if request.method == "POST":
test_data_key = build_stash_key(session_id, test_idx)
payload = ""
if "Content-Type" in request.headers and \
"form-data" in request.headers["Content-Type"]:
@ -83,42 +68,38 @@ def main(request, response):
if len(payload_parts) > 0:
payload_size = int(payload_parts[0])
# Confirm the payload size sent matches with the number of characters sent.
# Confirm the payload size sent matches with the number of
# characters sent.
if payload_size != len(payload_parts[1]):
test_data["error"] = "expected %d characters but got %d" % (payload_size, len(payload_parts[1]))
error = "expected %d characters but got %d" % (
payload_size, len(payload_parts[1]))
else:
# Confirm the payload contains the correct characters.
for i in range(0, payload_size):
if payload_parts[1][i] != "*":
test_data["error"] = "expected '*' at index %d but got '%s''" % (i, payload_parts[1][i])
error = "expected '*' at index %d but got '%s''" % (
i, payload_parts[1][i])
break
# Store the result in the stash so that it can be retrieved
# later with a 'stat' command.
request.server.stash.put(test_data_key, test_data)
request.server.stash.put(id, {"error": error})
elif request.method == "OPTIONS":
# If we expect a preflight, then add the cors headers we expect, otherwise log an error as we shouldn't
# send a preflight for all requests.
# If we expect a preflight, then add the cors headers we expect,
# otherwise log an error as we shouldn't send a preflight for all
# requests.
if "preflightExpected" in request.GET:
response.headers.set("Access-Control-Allow-Headers", "content-type")
response.headers.set("Access-Control-Allow-Headers",
"content-type")
response.headers.set("Access-Control-Allow-Methods", "POST")
else:
test_data_key = build_stash_key(session_id, test_idx)
test_data["error"] = "Preflight not expected."
request.server.stash.put(test_data_key, test_data)
error = "Preflight not expected."
request.server.stash.put(id, {"error": error})
elif command == "stat":
test_idx_min = int(request.GET.first("tidx_min"))
test_idx_max = int(request.GET.first("tidx_max"))
# For each result that has come in, append it to the response.
results = []
for test_idx in range(test_idx_min, test_idx_max+1): # +1 because end is exclusive
test_data_key = build_stash_key(session_id, test_idx)
test_data = request.server.stash.take(test_data_key)
if test_data:
results.append(test_data)
test_data = request.server.stash.take(id)
results = [test_data] if test_data else []
response.headers.set("Content-Type", "text/plain")
response.content = json.dumps(results)
else:
response.status = 400 # BadRequest
response.status = 400 # BadRequest

View file

@ -84,6 +84,8 @@ async_test(t => {
assert_equals(win, null);
}, `"require-corp" top-level noopener popup: navigating to "none" should succeed`);
// CORP is checked because COEP of the frame is "require-corp". The parent
// frame's COEP value doesn't matter.
async_test(t => {
const frame = document.createElement("iframe");
const id = token();
@ -94,8 +96,33 @@ async_test(t => {
t.done();
}
}));
// REMOTE_ORIGIN is cross-origin, same-site.
frame.src = `${HOST.HTTPS_REMOTE_ORIGIN}${BASE}/navigate-require-corp-same-site.sub.html?token=${id}`;
document.body.append(frame);
}, 'CORP: same-site is checked and allowed.');
// CORP is checked because COEP of the frame is "require-corp". The parent
// frame's COEP value doesn't matter.
async_test(t => {
const frame = document.createElement("iframe");
const id = token();
t.add_cleanup(() => frame.remove());
let loaded = false;
window.addEventListener('message', t.step_func((e) => {
if (e.data === id) {
loaded = true;
}
}));
t.step_timeout(() => {
// Make sure the iframe didn't load. See https://github.com/whatwg/html/issues/125 for why a
// timeout is used here. Long term all network error handling should be similar and have a
// reliable event.
assert_false(loaded);
t.done();
}, 2000);
// NOTESAMESITE_ORIGIN is cross-origin, cross-site.
frame.src = `${HOST.HTTPS_NOTSAMESITE_ORIGIN}${BASE}/navigate-require-corp-same-site.sub.html?token=${id}`;
document.body.append(frame);
}, 'CORP: same-site is not checked.');
}, 'CORP: same-site is checked and blocked.');
</script>

View file

@ -1 +1,2 @@
Cross-Origin-Embedder-Policy: require-corp
Cross-Origin-Resource-Policy: cross-origin

View file

@ -1 +1,2 @@
Cross-Origin-Embedder-Policy: require-corp
Cross-Origin-Resource-Policy: cross-origin

View file

@ -1,5 +0,0 @@
spec: https://github.com/jackbsteinberg/std-toast
suggested_reviewers:
- domenic
- fergald
- jackbsteinberg

View file

@ -1,190 +0,0 @@
<!DOCTYPE html>
<meta charset="utf-8">
<title>Toast: action tests</title>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<main></main>
<script type="module">
import { testActionToast, testToastElement, assertActionButtonOnToast } from './resources/helpers.js';
import { showToast } from 'std:elements/toast';
testActionToast((toast) => {
assert_equals(toast.action.textContent, 'action');
}, 'the action element gets properly captured with this.action');
testActionToast((toast) => {
toast.innerHTML = `<button slot='action'>new action</button>`
assert_equals(toast.action.textContent, 'new action');
}, 'changing the action button changes this.action');
testToastElement((toast) => {
assert_equals(toast.action, null);
}, 'the action property of a toast without an action is null');
testToastElement((toast) => {
toast.innerHTML = `<button slot="action" id="first">first</button>
<button slot="action" id="second">second</button>`;
assert_equals(toast.action, toast.querySelector('#first'));
}, 'toast action returns the first item with the action slot');
test(() => {
const toast = showToast('Message', {action: 'action'});
const actionButton = toast.querySelector('button');
assertActionButtonOnToast(actionButton, toast);
}, 'passing an action via showToast creates a button');
test(() => {
const actionMarkup = '<b>strong text</b>';
const toast = showToast('Message', {action: actionMarkup});
const actionButton = toast.querySelector('button');
assert_equals(actionButton.textContent, actionMarkup);
assert_equals(toast.querySelector('b'), null);
}, 'passing markup to the action option represents as text');
test(() => {
const toast = document.createElement('std-toast');
toast.textContent = 'Message';
toast.show({action: 'action'});
const actionButton = toast.querySelector('button');
assert_equals(actionButton, null);
}, 'passing action option to show does not create a button');
test(() => {
const toast = showToast('Message', {action: null});
const actionButton = toast.querySelector('button');
assertActionButtonOnToast(actionButton, toast);
assert_equals(actionButton.textContent, 'null');
}, 'passing non-string (null) as action option stringifies it and creates an action button');
test(() => {
const toast = showToast('Message', {action: false});
const actionButton = toast.querySelector('button');
assertActionButtonOnToast(actionButton, toast);
assert_equals(actionButton.textContent, 'false');
}, 'passing non-string (false) as action option stringifies it and creates an action button');
test(() => {
const toast = showToast('Message', {action: 0});
const actionButton = toast.querySelector('button');
assertActionButtonOnToast(actionButton, toast);
assert_equals(actionButton.textContent, '0');
}, 'passing non-string (0) as action option stringifies it and creates an action button');
test(() => {
const toast = showToast('Message', {action: 1});
const actionButton = toast.querySelector('button');
assertActionButtonOnToast(actionButton, toast);
assert_equals(actionButton.textContent, '1');
}, 'passing non-string (1) as action option stringifies it and creates an action button');
test(() => {
const toast = showToast('Message', {action: {field: 'value'}});
const actionButton = toast.querySelector('button');
assertActionButtonOnToast(actionButton, toast);
assert_equals(actionButton.textContent, '[object Object]');
}, 'passing non-string ({field: value}) as action option stringifies it and creates an action button');
test(() => {
const toast = showToast('Message', {});
const actionButton = toast.querySelector('button');
assert_equals(actionButton, null);
}, 'passing non-string (undefined) as action option does not create an action button');
testToastElement((toast) => {
const actionButton = document.createElement('button');
actionButton.textContent = 'action';
toast.action = actionButton;
assertActionButtonOnToast(actionButton, toast);
}, 'setting the action on an actionless toast inserts the element into the slot');
testActionToast((toast, action) => {
const actionButton = document.createElement('button');
actionButton.textContent = 'replacement';
toast.action = actionButton;
assert_false(document.contains(action));
assertActionButtonOnToast(actionButton, toast);
}, 'resetting the action on an action toast changes the action element');
testToastElement((toast) => {
const text = document.createTextNode('some text');
assert_throws_js(TypeError, () => {
toast.action = text;
});
}, 'setting the action to an invalid type (Text node) throws an error');
testToastElement((toast) => {
const text = 'some text';
assert_throws_js(TypeError, () => {
toast.action = text;
});
}, 'setting the action to an invalid type (string) throws an error');
test(() => {
const actionButton = document.createElement('button');
actionButton.textContent = 'action';
const toast = showToast('Message', {action: actionButton});
assertActionButtonOnToast(actionButton, toast);
}, 'showToast can take an Element as the action parameter');
testActionToast((toast, action) => {
toast.action = null;
assert_not_equals(toast.action, action);
assert_equals(toast.querySelector('button'), null);
}, 'setting toast.action to null removes the action from the toast');
testActionToast((toast, action) => {
const wrongAction = document.createElement('button');
wrongAction.textContent = 'wrong';
wrongAction.setAttribute('slot', 'action');
toast.appendChild(wrongAction);
const correctAction = document.createElement('button');
correctAction.textContent = 'correct';
toast.action = correctAction;
assertActionButtonOnToast(correctAction, toast);
}, 'resetting toast.action on a toast with multiple actions slotted sets properly');
test(() => {
try {
Object.defineProperty(Element, Symbol.hasInstance, {
value: () => true,
configurable: true
});
const fakeElement = {};
const toast = showToast('Message');
assert_throws_js(TypeError, () => toast.action = fakeElement);
} finally {
delete Element[Symbol.hasInstance];
}
}, 'spoofing element instance will not register as element to action setter');
test(() => {
const iframe = document.createElement('iframe');
document.body.append(iframe);
iframe.contentDocument.body.innerHTML = '<div></div>';
const elementFromAnotherFrame = iframe.contentDocument.querySelector('div');
// Should not throw:
const toast = showToast('Message');
toast.action = elementFromAnotherFrame;
}, 'element from iframe instance will pass correctly to action without throwing an error');
</script>

View file

@ -1,127 +0,0 @@
<!DOCTYPE html>
<meta charset="utf-8">
<title>Toast: attribute tests</title>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<main></main>
<script type="module">
import { testToastElement, assertToastShown, assertToastNotShown, testToastElementAsync } from './resources/helpers.js';
testToastElement((toast) => {
toast.setAttribute('open', '');
assertToastShown(toast);
}, 'setting `open` on a hidden toast shows the toast');
testToastElement((toast) => {
toast.setAttribute('open', false);
assertToastShown(toast);
}, 'setting `open` to false on a hidden toast shows the toast, because of string conversion');
testToastElement((toast) => {
toast.show();
toast.setAttribute('open', 'test');
assertToastShown(toast);
}, 'setting `open` on a shown toast does nothing');
testToastElement((toast) => {
toast.show();
toast.setAttribute('open', 'test');
toast.setAttribute('open', 'test');
assertToastShown(toast);
}, 'resetting `open` on a shown toast does nothing');
testToastElement((toast) => {
toast.show();
toast.setAttribute('open', false);
assertToastShown(toast);
}, 'setting `open` to false on a shown toast does nothing, because of string conversion');
testToastElement((toast) => {
toast.show();
toast.removeAttribute('open');
assertToastNotShown(toast);
}, 'removing `open` hides the toast');
testToastElement((toast) => {
toast.show();
assert_true(toast.hasAttribute('open'));
}, 'showing the toast adds open attribute');
testToastElement((toast) => {
toast.show();
toast.hide();
assert_false(toast.hasAttribute('open'));
}, 'hiding the toast removes open attribute');
testToastElement((toast) => {
toast.toggleAttribute('open');
assert_true(toast.hasAttribute('open'));
}, 'toggling `open` on a hidden toast sets the open attribute');
testToastElement((toast) => {
toast.toggleAttribute('open');
toast.toggleAttribute('open');
assert_false(toast.hasAttribute('open'));
}, 'toggling `open` twice leaves the toast with no open attribute');
testToastElement((toast) => {
assert_false(toast.open);
}, 'the `toast.open` boolean is false for a hidden toast');
testToastElement((toast) => {
toast.show();
assert_true(toast.open);
}, 'the `toast.open` boolean is true for a shown toast');
testToastElement((toast) => {
toast.open = true;
assertToastShown(toast);
assert_equals(toast.getAttribute('open'), '');
}, 'setting `toast.open` to true on a hidden toast will show the toast');
testToastElement((toast) => {
toast.show();
toast.open = false;
assertToastNotShown(toast);
}, 'setting `toast.open` to false on a shown toast will hide the toast');
testToastElement((toast) => {
toast.open = 'truthy!';
assertToastShown(toast);
assert_equals(toast.getAttribute('open'), '');
}, 'setting `toast.open` to some truthy value on a hidden toast will show the toast');
testToastElement((toast) => {
toast.show();
toast.open = '';
assertToastNotShown(toast);
}, 'setting `toast.open` to some falsy value on a shown toast will hide the toast');
testToastElementAsync((t, toast) => {
toast.toggleAttribute('open', true);
t.step_timeout(() => {
assertToastShown(toast);
t.done();
}, 2000);
}, 'toggling open attribute does not start timeout');
testToastElement((toast) => {
const permitted_properties = ['constructor', 'show', 'hide', 'toggle', 'open', 'action', 'closeButton', 'type'];
assert_array_equals(permitted_properties.sort(), Object.getOwnPropertyNames(toast.__proto__).sort());
}, 'toast only exposes certain properties');
testToastElement((toast) => {
assert_false(toast.hasAttribute('type'));
assert_equals(toast.type, '');
}, 'default type is empty string without attribute present');
testToastElement((toast) => {
toast.type = 'info';
assert_equals(toast.type, '');
assert_equals(toast.getAttribute('type'), 'info');
}, 'info was briefly a valid type, but no longer is, so it will return empty string');
</script>

View file

@ -1,124 +0,0 @@
<!DOCTYPE html>
<meta charset="utf-8">
<title>Toast: closebutton tests</title>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<main></main>
<script type="module">
import { showToast } from 'std:elements/toast';
import { testToastElement } from './resources/helpers.js';
testToastElement((toast) => {
toast.setAttribute('closebutton', '');
assert_true(toast.closeButton);
}, 'the closeButton property returns true with an empty attribute');
testToastElement((toast) => {
toast.setAttribute('closebutton', 'dismiss');
assert_equals(toast.closeButton, 'dismiss');
}, 'the closeButton property returns the set attribute value');
testToastElement((toast) => {
assert_false(toast.closeButton);
}, 'the closeButton property returns false with no attribute');
testToastElement((toast) => {
toast.setAttribute('closebutton', '');
assert_true(toast.closeButton);
toast.setAttribute('closebutton', 'dismiss');
assert_equals(toast.closeButton, 'dismiss');
toast.removeAttribute('closebutton');
assert_false(toast.closeButton);
}, 'the closeButton property changes when the attribute changes');
testToastElement((toast) => {
toast.closeButton = 'dismiss';
assert_equals(toast.getAttribute('closebutton'), 'dismiss');
}, 'setting the closeButton property to any string changes the attribute to that string');
testToastElement((toast) => {
toast.closeButton = '';
assert_equals(toast.getAttribute('closebutton'), '');
}, 'setting the closeButton property to empty string changes the attribute to empty string');
testToastElement((toast) => {
toast.closeButton = true;
assert_equals(toast.getAttribute('closebutton'), '');
}, 'setting the closeButton property to true changes the attribute to empty string');
testToastElement((toast) => {
toast.closeButton = false;
assert_false(toast.hasAttribute('closebutton'));
}, 'setting the closeButton property to false removes the attribute');
testToastElement((toast) => {
toast.closeButton = undefined;
assert_equals(toast.getAttribute('closebutton'), 'undefined');
}, 'setting the closeButton property to undefined stringifies and sets to that');
testToastElement((toast) => {
toast.closeButton = null;
assert_equals(toast.getAttribute('closebutton'), 'null');
}, 'setting the closeButton property to null stringifies and sets to that');
testToastElement((toast) => {
toast.closeButton = {};
assert_equals(toast.getAttribute('closebutton'), '[object Object]');
}, 'setting the closeButton property to {} stringifies and sets to [object Object]');
test(() => {
const toast = showToast('Message', { closeButton: true });
assert_equals(toast.getAttribute('closebutton'), '');
}, 'setting the showToast closeButton option to true sets the closebutton attribute to empty string');
test(() => {
const toast = showToast('Message', { closeButton: 'dismiss' });
assert_equals(toast.getAttribute('closebutton'), 'dismiss');
}, 'setting the showToast closeButton option to some string sets that string as the closebutton attribute');
test(() => {
const toast = showToast('Message', { closeButton: '' });
assert_equals(toast.getAttribute('closebutton'), '');
}, 'setting the showToast closeButton option to empty string sets the closebutton attribute to empty string');
test(() => {
const toast = showToast('Message', { closeButton: {} });
assert_equals(toast.getAttribute('closebutton'), '[object Object]');
}, 'setting the showToast closeButton option to {} sets the closebutton attribute to [object Object]');
test(() => {
const toast = showToast('Message', { closeButton: document.createElement('span') });
assert_equals(toast.getAttribute('closebutton'), '[object HTMLSpanElement]');
}, 'passing an HTML element into the closeButton option of showToast stringifies and sets it to the closebutton attribute');
test(() => {
const toast = showToast('Message', { closeButton: false });
assert_false(toast.hasAttribute('closebutton'));
}, 'setting the showToast closeButton option to false does not put a close button on the toast');
test(() => {
const toast = showToast('Message');
assert_false(toast.hasAttribute('closebutton'));
}, 'calling showToast without the closeButton option does not put a closebutton on the toast');
</script>

View file

@ -1,71 +0,0 @@
<!DOCTYPE html>
<meta charset="utf-8">
<title>Toast: event (open) tests</title>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<main></main>
<script type="module">
import { testToastElement, EventCollector } from './resources/helpers.js';
testToastElement((toast) => {
const counter = new EventCollector();
toast.addEventListener('show', counter.getCallback());
toast.open = true;
assert_equals(counter.getCount(), 1);
}, 'setting open to true on a hidden toast triggers the `show` event');
testToastElement((toast) => {
const counter = new EventCollector();
toast.open = true;
toast.addEventListener('show', counter.getCallback());
toast.open = true;
assert_equals(counter.getCount(), 0);
}, 'setting open to true on a shown toast does not trigger the `show` event');
testToastElement((toast) => {
const counter = new EventCollector();
toast.addEventListener('hide', counter.getCallback());
toast.open = false;
assert_equals(counter.getCount(), 0);
}, 'setting open to false on a hidden toast does not trigger the `hide` event');
testToastElement((toast) => {
const counter = new EventCollector();
toast.show();
toast.addEventListener('hide', counter.getCallback());
toast.open = false;
assert_equals(counter.getCount(), 1);
}, 'setting open to false on a shown toast triggers the `hide` event');
testToastElement((toast) => {
const counter = new EventCollector();
toast.addEventListener('show', counter.getCallback());
toast.open = true;
toast.open = true;
assert_equals(counter.getCount(), 1);
}, 'setting open to true twice only triggers the `show` event once');
testToastElement((toast) => {
const counter = new EventCollector();
toast.show();
toast.addEventListener('hide', counter.getCallback());
toast.open = false;
toast.open = false;
assert_equals(counter.getCount(), 1);
}, 'setting open to false twice only triggers the `hide` event once');
</script>

View file

@ -1,98 +0,0 @@
<!DOCTYPE html>
<meta charset="utf-8">
<title>Toast: event (show/hide) tests</title>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<main></main>
<script type="module">
import { testToastElement, EventCollector } from './resources/helpers.js';
testToastElement((toast) => {
const counter = new EventCollector();
toast.addEventListener('show', counter.getCallback());
toast.show();
assert_equals(counter.getCount(), 1);
}, 'calling `show()` on a hidden toast triggers the `show` event');
testToastElement((toast) => {
const counter = new EventCollector();
toast.show();
toast.addEventListener('show', counter.getCallback());
toast.show();
assert_equals(counter.getCount(), 0);
}, 'calling `show()` on a shown toast does not trigger the `show` event');
testToastElement((toast) => {
const counter = new EventCollector();
toast.addEventListener('hide', counter.getCallback());
toast.hide();
assert_equals(counter.getCount(), 0);
}, 'calling `hide()` on a hidden toast does not trigger the `hide` event');
testToastElement((toast) => {
const counter = new EventCollector();
toast.show();
toast.addEventListener('hide', counter.getCallback());
toast.hide();
assert_equals(counter.getCount(), 1);
}, 'calling `hide()` on a shown toast triggers the `hide` event');
testToastElement((toast) => {
const counter = new EventCollector();
toast.addEventListener('show', counter.getCallback());
toast.show();
toast.show();
assert_equals(counter.getCount(), 1);
}, 'calling `show()` twice only triggers the `show` event once');
testToastElement((toast) => {
const counter = new EventCollector();
toast.show();
toast.addEventListener('hide', counter.getCallback());
toast.hide();
toast.hide();
assert_equals(counter.getCount(), 1);
}, 'calling `hide()` twice only triggers the `hide` event once');
testToastElement((toast) => {
const events = new EventCollector();
toast.addEventListener('show', events.getCallback());
toast.show();
toast.hide();
toast.show();
assert_equals(events.getCount(), 2);
assert_not_equals(events.getEvents()[0], events.getEvents()[1]);
}, "separate openings trigger different `show` events");
testToastElement((toast) => {
const events = new EventCollector();
toast.addEventListener('hide', events.getCallback());
toast.show();
toast.hide();
toast.show();
toast.hide();
assert_equals(events.getCount(), 2);
assert_not_equals(events.getEvents()[0], events.getEvents()[1]);
}, "separate closings trigger different `hide` events");
</script>

View file

@ -1,126 +0,0 @@
<!DOCTYPE html>
<meta charset="utf-8">
<title>Toast: method tests</title>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<main></main>
<div></div>
<script type="module">
import { testToastElement, assertToastShown, assertToastNotShown, testToastElementAsync } from './resources/helpers.js';
testToastElement((toast) => {
toast.show();
assertToastShown(toast);
}, 'calling `show()` on a hidden toast opens and displays it');
testToastElement((toast) => {
toast.toggleAttribute('open');
toast.show();
assertToastShown(toast);
}, 'calling `show()` on a shown toast does nothing');
testToastElement((toast) => {
toast.toggleAttribute('open');
toast.hide();
assertToastNotShown(toast);
}, 'calling `hide()` on a shown toast hides the toast');
testToastElement((toast) => {
toast.hide();
assertToastNotShown(toast);
}, 'calling `hide()` on a hidden toast does nothing');
testToastElement((toast) => {
toast.toggle();
assertToastShown(toast);
}, 'calling `toggle()` on a hidden toast shows the toast');
testToastElement((toast) => {
toast.show();
toast.toggle();
assertToastNotShown(toast);
}, 'calling `toggle()` on a shown toast hides the toast');
testToastElement((toast) => {
toast.toggle(true);
assertToastShown(toast);
}, 'calling `toggle()` with `force` parameter set to true on a hidden opens the toast');
testToastElement((toast) => {
toast.show();
toast.toggle(true);
assertToastShown(toast);
}, 'calling `toggle()` with `force` parameter set to true on a shown toast does not close the toast');
testToastElement((toast) => {
toast.toggle(false);
assertToastNotShown(toast);
}, 'calling `toggle()` with `force` parameter set to false on a hidden toast does not open the toast');
testToastElement((toast) => {
toast.show();
toast.toggle(false);
assertToastNotShown(toast);
}, 'calling `toggle()` with `force` parameter set to false on a shown toast closes the toast');
testToastElementAsync((t, toast) => {
toast.show({duration: 50});
// time = 49
t.step_timeout(() => {
assertToastShown(toast);
toast.show({duration: 50});
// time = 98
t.step_timeout(() => {
assertToastShown(toast);
// time = 99
t.step_timeout(() => {
assertToastNotShown(toast);
t.done();
}, 1000);
}, 49);
}, 49);
}, 'calling `show()` twice resets the timeout');
testToastElementAsync((t, toast) => {
toast.show({duration: 50});
// time = 48
t.step_timeout(() => {
assertToastShown(toast);
toast.hide();
// time = 49
t.step_timeout(() => {
assertToastNotShown(toast);
// time = 50
t.step_timeout(() => {
toast.show({duration: 2});
// time = 51
t.step_timeout(() => {
assertToastShown(toast);
toast.hide();
t.done();
}, 1);
}, 1);
}, 1);
}, 48);
}, 'calling `hide()` clears the timeout');
</script>

View file

@ -1,129 +0,0 @@
<!DOCTYPE html>
<meta charset="utf-8">
<meta name="timeout" content="long">
<title>Toast: option tests</title>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<body>
</body>
<script type="module">
import { showToast, StdToastElement } from 'std:elements/toast';
import { assertToastNotShown, assertToastShown } from './resources/helpers.js';
// message
test(() => {
const toast = new StdToastElement('test');
document.body.appendChild(toast);
assert_equals(toast.textContent, 'test');
}, 'passing test string as message sets the message properly');
test(() => {
const toast = new StdToastElement('<p>rich text</p>');
document.body.appendChild(toast);
assert_equals(toast.textContent, '<p>rich text</p>');
assert_equals(toast.querySelector('p'), null);
}, 'passing markup to the constructor does not pass through the markup behaviors');
test(() => {
const toast = new StdToastElement(false);
document.body.appendChild(toast);
assert_equals(toast.textContent, 'false');
}), 'passing false as message converts to the string `false`';
test(() => {
const toast = new StdToastElement();
document.body.appendChild(toast);
assert_equals(toast.textContent, '');
}, 'passing nothing as message sets the message to the empty string');
test(() => {
const toast = new StdToastElement(undefined);
document.body.appendChild(toast);
assert_equals(toast.textContent, '');
}, 'passing `undefined` as message sets the message to the empty string');
test(() => {
const toast = new StdToastElement('');
document.body.appendChild(toast);
assert_equals(toast.textContent, '');
}, 'passing empty string as message sets the message to the empty string');
test(() => {
const toastString = '<std-toast id="test">test</std-toast>';
document.body.innerHTML = toastString;
const toast = document.body.querySelector('#test');
assert_equals(toast.textContent, 'test');
}, 'HTML created toast has `test` as its text content');
// duration
async_test(t => {
const toast = showToast('message');
t.step_timeout(() => {
assertToastShown(toast);
}, 2999);
t.step_timeout(() => {
assertToastNotShown(toast);
t.done();
}, 3000);
t.add_cleanup(function() {
toast.remove();
});
}, 'showToast closes after default duration of 3000ms');
// FIXME: find a way to virtualize time instead of waiting 3000ms
// BUG: https://github.com/web-platform-tests/wpt/issues/17489
async_test(t => {
const toast = showToast('message', {duration: 50});
t.step_timeout(() => {
assertToastShown(toast);
}, 49);
t.step_timeout(() => {
assertToastNotShown(toast);
t.done();
}, 50);
t.add_cleanup(function() {
toast.remove();
});
}, 'showToast closes after user specified 50ms');
async_test(t => {
const toast = showToast('message', {duration: Infinity});
t.step_timeout(() => {
assertToastShown(toast);
t.done();
}, 50);
t.add_cleanup(function() {
toast.remove();
});
}, 'passing Infinity as the duration leaves the toast open for at least 50ms');
test(() => {
assert_throws_js(RangeError, () => {
const toast = showToast('Message', {duration: 0});
});
}, 'setting the duration to 0 throws a RangeError');
test(() => {
assert_throws_js(RangeError, () => {
const toast = showToast('Message', {duration: -5});
});
}, 'setting the duration to a negative number throws a RangeError');
</script>

View file

@ -1,8 +0,0 @@
<!DOCTYPE html>
<meta charset="utf-8">
<title>Toast: slotting test reference</title>
<script type="module">
import 'std:elements/toast';
</script>
<p>Pass if the toast is displayed with the action button last.</p>
<std-toast open>First. Second. <button>Last.</button></std-toast>

View file

@ -1,11 +0,0 @@
<!DOCTYPE html>
<meta charset="utf-8">
<title>Toast: slotting test</title>
<link rel="help" href="https://github.com/jackbsteinberg/std-toast">
<meta name="assert" content="Toast slots action button behind any text">
<link rel="match" href="toast-slotting-expected.html">
<script type="module">
import 'std:elements/toast';
</script>
<p>Pass if the toast is displayed with the action button last.</p>
<std-toast open>First. <button slot="action">Last.</button> Second. </std-toast>

View file

@ -1,24 +0,0 @@
<!doctype html>
<title>HTML5 reflection tests: std-toast</title>
<meta name=timeout content=long>
<div id=log></div>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="/html/dom/original-harness.js"></script>
<script src="/html/dom/new-harness.js"></script>
<script type="module">
import 'std:elements/toast';
const toastElement = {
'std-toast': {
open: 'boolean',
type: {type: 'enum', keywords: ['success', 'error', 'warning']},
},
};
mergeElements(toastElement);
</script>
<script src="/html/dom/reflection.js" defer></script>

View file

@ -1,101 +0,0 @@
import { showToast, StdToastElement } from 'std:elements/toast';
// helper functions to keep tests from bleeding into each other
const runTest = (testFn, name, toast, action) => {
try {
test(() => {
testFn(toast, action);
}, name);
} finally {
toast.remove();
}
};
const runTestAsync = (testFn, name, toast) => {
async_test(t => {
testFn(t, toast);
t.add_cleanup(() => {
toast.remove();
});
}, name);
};
export const testToastElement = (testFn, name) => {
const toast = new StdToastElement('Message', {});
document.querySelector('main').appendChild(toast);
runTest(testFn, name, toast);
};
export const testToastElementAsync = (testFn, name) => {
const toast = new StdToastElement('Message', {});
document.querySelector('main').appendChild(toast);
runTestAsync(testFn, name, toast);
};
export const testShowToast = (testFn, name) => {
const toast = showToast("message");
runTest(testFn, name, toast);
};
export const testActionToast = (testFn, name) => {
const toast = new StdToastElement('Message', {});
const action = document.createElement('button');
action.setAttribute('slot', 'action');
action.textContent = 'action';
toast.appendChild(action);
document.querySelector('main').appendChild(toast);
runTest(testFn, name, toast, action);
};
export const assertToastShown = (toast) => {
assert_not_equals(window.getComputedStyle(toast).display, 'none');
assert_true(toast.hasAttribute('open'));
assert_true(toast.open);
};
export const assertToastNotShown = (toast) => {
assert_equals(window.getComputedStyle(toast).display, 'none');
assert_false(toast.hasAttribute('open'));
assert_false(toast.open);
};
export const assertActionButtonOnToast = (action, toast) => {
assert_equals(toast.action, action);
assert_equals(action.getAttribute('slot'), 'action');
assert_equals(action, toast.querySelector('button'));
};
export const assertComputedStyleMapsEqual = (element1, element2) => {
assert_greater_than(element1.computedStyleMap().size, 0);
for (const [styleProperty, baseStyleValues] of element1.computedStyleMap()) {
const refStyleValues = element2.computedStyleMap().getAll(styleProperty);
assert_equals(baseStyleValues.length, refStyleValues.length, `${styleProperty} length`);
for (let i = 0; i < baseStyleValues.length; ++i) {
const baseStyleValue = baseStyleValues[i];
const refStyleValue = refStyleValues[i];
assert_equals(baseStyleValue.toString(), refStyleValue.toString(), `diff at value ${styleProperty}`);
}
}
}
export class EventCollector {
events = [];
getCallback() {
return (e) => {this.events.push(e)};
}
getCount() {
return this.events.length;
}
getEvents() {
return this.events;
}
}

View file

@ -1,63 +0,0 @@
<!DOCTYPE html>
<meta charset="utf-8">
<title>Toast: showToast tests</title>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<body>
</body>
<script type="module">
import { showToast, StdToastElement } from 'std:elements/toast';
import { testShowToast, assertToastNotShown, assertToastShown } from './resources/helpers.js';
testShowToast((toast) => {
assert_true(toast != null);
assert_true(toast instanceof StdToastElement);
}, 'showToast creates and returns a toast');
testShowToast((toast) => {
assert_true(document.querySelector('std-toast') === toast);
}, 'showToast puts the toast in the DOM');
testShowToast((toast) => {
assertToastShown(toast);
}, 'showToast displays the toast by default');
testShowToast((toast) => {
toast.hide();
assertToastNotShown(toast);
}, 'hiding showToast immediately does not display it');
testShowToast((toast) => {
toast.show();
assertToastShown(toast);
}, 'calling show after showToast does nothing');
testShowToast((toast) => {
let toast2;
try {
toast2 = showToast('message2');
assert_not_equals(toast, toast2);
}
finally {
toast2.remove();
}
}, 'calling showToast multiple times creates multiple different toasts');
test(() => {
const toast = showToast('test');
assert_equals(toast.textContent, 'test');
}, 'showToast created toast has `test` as its text content');
test(() => {
const toast = showToast('<p>rich text</p>');
assert_equals(toast.textContent, '<p>rich text</p>');
assert_equals(toast.querySelector('p'), null);
}, 'passing markup to showToast does not pass through the markup behaviors');
</script>

View file

@ -1,100 +0,0 @@
<!DOCTYPE html>
<meta charset="utf-8">
<title>Toast: style tests</title>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<main>
</main>
<script type="module">
import { testToastElement, assertComputedStyleMapsEqual } from './resources/helpers.js';
testToastElement((toast) => {
toast.open = true;
const mockToast = document.createElement('span');
mockToast.id = 'mock-toast-open';
mockToast.textContent = 'Message';
const mockStyler = document.createElement('style');
mockStyler.textContent = `
#mock-toast-open {
position: fixed;
bottom: 1em;
right: 1em;
border: solid;
padding: 1em;
background: white;
color: black;
z-index: 1;
}`;
document.querySelector('main').appendChild(mockStyler);
document.querySelector('main').appendChild(mockToast);
assertComputedStyleMapsEqual(toast, mockToast);
}, 'the computed style map of an open unstyled toast is the same as a span given toast defaults');
testToastElement((toast) => {
const mockToast = document.createElement('span');
mockToast.id = 'mock-toast-hidden';
mockToast.textContent = 'Message';
const mockStyler = document.createElement('style');
mockStyler.textContent = `
#mock-toast-hidden {
position: fixed;
bottom: 1em;
right: 1em;
border: solid;
padding: 1em;
background: white;
color: black;
z-index: 1;
display: none;
}`;
document.querySelector('main').appendChild(mockStyler);
document.querySelector('main').appendChild(mockToast);
assertComputedStyleMapsEqual(toast, mockToast);
}, 'the computed style map of a closed unstyled toast is the same as a span given toast defaults');
testToastElement((toast) => {
toast.type = 'error';
const styles = window.getComputedStyle(toast);
assert_equals(styles.borderColor, 'rgb(255, 0, 0)');
}, 'changing type to error changes the border color to red');
testToastElement((toast) => {
toast.type = 'warning';
const styles = window.getComputedStyle(toast);
assert_equals(styles.borderColor, 'rgb(255, 165, 0)');
}, 'changing type to warning changes the border color to orange');
testToastElement((toast) => {
toast.type = 'success';
const styles = window.getComputedStyle(toast);
assert_equals(styles.borderColor, 'rgb(0, 128, 0)');
}, 'changing type to success changes the border color to green');
testToastElement((toast) => {
const styler = document.createElement('style');
styler.append(`
[type=error i] {
border-color: pink;
}
`);
document.querySelector('main').appendChild(styler);
toast.type = 'error';
const styles = window.getComputedStyle(toast);
assert_equals(styles.borderColor, 'rgb(255, 192, 203)');
}, 'outside styles can set type styles');
</script>

View file

@ -53,17 +53,15 @@
All other characters (and only those other characters) MUST be considered
unrecognized.
*/
promise_test(t => {
return createDtmfSender()
.then(dtmfSender => {
dtmfSender.insertDTMF('');
dtmfSender.insertDTMF('012345689');
dtmfSender.insertDTMF('ABCD');
dtmfSender.insertDTMF('abcd');
dtmfSender.insertDTMF('#*');
dtmfSender.insertDTMF(',');
dtmfSender.insertDTMF('0123456789ABCDabcd#*,');
});
promise_test(async t => {
const dtmfSender = await createDtmfSender();
dtmfSender.insertDTMF('');
dtmfSender.insertDTMF('012345689');
dtmfSender.insertDTMF('ABCD');
dtmfSender.insertDTMF('abcd');
dtmfSender.insertDTMF('#*');
dtmfSender.insertDTMF(',');
dtmfSender.insertDTMF('0123456789ABCDabcd#*,');
}, 'insertDTMF() should succeed if tones contains valid DTMF characters');
@ -72,21 +70,19 @@
6. If tones contains any unrecognized characters, throw an
InvalidCharacterError.
*/
promise_test(t => {
return createDtmfSender()
.then(dtmfSender => {
assert_throws_dom('InvalidCharacterError', () =>
// 'F' is invalid
dtmfSender.insertDTMF('123FFABC'));
promise_test(async t => {
const dtmfSender = await createDtmfSender();
assert_throws_dom('InvalidCharacterError', () =>
// 'F' is invalid
dtmfSender.insertDTMF('123FFABC'));
assert_throws_dom('InvalidCharacterError', () =>
// 'E' is invalid
dtmfSender.insertDTMF('E'));
assert_throws_dom('InvalidCharacterError', () =>
// 'E' is invalid
dtmfSender.insertDTMF('E'));
assert_throws_dom('InvalidCharacterError', () =>
// ' ' is invalid
dtmfSender.insertDTMF('# *'));
});
assert_throws_dom('InvalidCharacterError', () =>
// ' ' is invalid
dtmfSender.insertDTMF('# *'));
}, 'insertDTMF() should throw InvalidCharacterError if tones contains invalid DTMF characters');
/*
@ -152,37 +148,29 @@
7. Set the object's toneBuffer attribute to tones.
*/
promise_test(t => {
return createDtmfSender()
.then(dtmfSender => {
dtmfSender.insertDTMF('123');
assert_equals(dtmfSender.toneBuffer, '123');
promise_test(async t => {
const dtmfSender = await createDtmfSender();
dtmfSender.insertDTMF('123');
assert_equals(dtmfSender.toneBuffer, '123');
dtmfSender.insertDTMF('ABC');
assert_equals(dtmfSender.toneBuffer, 'ABC');
dtmfSender.insertDTMF('ABC');
assert_equals(dtmfSender.toneBuffer, 'ABC');
dtmfSender.insertDTMF('bcd');
assert_equals(dtmfSender.toneBuffer, 'BCD');
});
dtmfSender.insertDTMF('bcd');
assert_equals(dtmfSender.toneBuffer, 'BCD');
}, 'insertDTMF() should set toneBuffer to provided tones normalized, with old tones overridden');
promise_test(t => {
let dtmfSender;
let sender;
let pc = new RTCPeerConnection();
promise_test(async t => {
const pc = new RTCPeerConnection();
t.add_cleanup(() => pc.close());
return getTrackFromUserMedia('audio')
.then(([track, mediaStream]) => {
sender = pc.addTrack(track, mediaStream);
return pc.createOffer();
}).then(offer => {
pc.setLocalDescription(offer);
dtmfSender = sender.dtmf;
pc.removeTrack(sender);
pc.close();
assert_throws_dom('InvalidStateError', () =>
dtmfSender.insertDTMF('123'));
});
const [track, mediaStream] = await getTrackFromUserMedia('audio');
const sender = pc.addTrack(track, mediaStream);
await pc.setLocalDescription(await pc.createOffer());
const dtmfSender = sender.dtmf;
pc.removeTrack(sender);
pc.close();
assert_throws_dom('InvalidStateError', () =>
dtmfSender.insertDTMF('123'));
}, 'insertDTMF() after remove and close should reject');
</script>

View file

@ -0,0 +1,211 @@
<!doctype html>
<meta charset=utf-8>
<title></title>
<script src=/resources/testharness.js></script>
<script src=/resources/testharnessreport.js></script>
<script>
'use strict';
promise_test(async t => {
const pc1 = new RTCPeerConnection();
t.add_cleanup(() => pc1.close());
const pc2 = new RTCPeerConnection();
t.add_cleanup(() => pc2.close());
pc1.addTransceiver("video");
const offer = await pc1.createOffer();
await pc1.setLocalDescription(offer);
const {candidate} = await new Promise(r => pc1.onicecandidate = r);
try {
await pc2.addIceCandidate(candidate);
assert_unreached("Control. Must not succeed");
} catch (e) {
assert_equals(e.name, "InvalidStateError");
}
const p = pc2.setRemoteDescription(offer);
await pc2.addIceCandidate(candidate);
await p;
}, "addIceCandidate chains onto SRD, fails before");
promise_test(async t => {
const pc1 = new RTCPeerConnection();
t.add_cleanup(() => pc1.close());
const pc2 = new RTCPeerConnection();
t.add_cleanup(() => pc2.close());
pc1.addTransceiver("video");
await Promise.all([
pc1.createOffer(),
pc1.setLocalDescription({type: "offer"})
]);
await Promise.all([
pc2.setRemoteDescription(pc1.localDescription),
pc2.createAnswer(),
pc2.setLocalDescription({type: "answer"})
]);
await pc1.setRemoteDescription(pc2.localDescription);
}, "Pack operations queue with implicit offer and answer");
promise_test(async t => {
const pc1 = new RTCPeerConnection();
t.add_cleanup(() => pc1.close());
const pc2 = new RTCPeerConnection();
t.add_cleanup(() => pc2.close());
const state = (pc, s) => new Promise(r => pc.onsignalingstatechange =
() => pc.signalingState == s && r());
pc1.addTransceiver("video");
pc1.createOffer();
pc1.setLocalDescription({type: "offer"});
await state(pc1, "have-local-offer");
pc2.setRemoteDescription(pc1.localDescription);
pc2.createAnswer();
pc2.setLocalDescription({type: "answer"});
await state(pc2, "stable");
await pc1.setRemoteDescription(pc2.localDescription);
}, "Negotiate solely by operations queue and signaling state");
// Helpers to test APIs "return a promise rejected with a newly created" error.
// Strictly speaking this means already-rejected upon return.
function promiseState(p) {
const t = {};
return Promise.race([p, t])
.then(v => (v === t)? "pending" : "fulfilled", () => "rejected");
}
// However, to allow promises to be used in implementations, this helper adds
// some slack: returning a pending promise will pass, provided it is rejected
// before the end of the current run of the event loop (i.e. on microtask queue
// before next task).
async function promiseStateFinal(p) {
for (let i = 0; i < 20; i++) {
await promiseState(p);
}
return promiseState(p);
}
promise_test(async t => {
assert_equals(await promiseState(Promise.resolve()), "fulfilled");
assert_equals(await promiseState(Promise.reject()), "rejected");
assert_equals(await promiseState(new Promise(() => {})), "pending");
}, "promiseState helper works");
promise_test(async t => {
const pc = new RTCPeerConnection();
t.add_cleanup(() => pc.close());
const p = pc.setLocalDescription({type: "offer", sdp: "blah"});
const haveState = promiseStateFinal(p);
try {
await p;
assert_unreached("Control. Must not succeed");
} catch (e) {
assert_equals(e.name, "InvalidModificationError");
}
assert_equals(await haveState, "rejected", "promise rejected on same task");
}, "Operations chain must run first operation's sync part synchronously");
// Helper builds on above test to check if operations queue is empty or not.
async function isOperationsChainEmpty(pc) {
const type = pc.signalingState == "have-remote-offer" ? "answer" : "offer";
const p = pc.setLocalDescription({type, sdp: "blah"});
const state = await promiseStateFinal(p);
try {
await p;
assert_unreached("Control. Must not succeed");
} catch (e) {
assert_equals(e.name, "InvalidModificationError", "isOperationsChainEmpty");
}
return state == "rejected";
}
promise_test(async t => {
const pc = new RTCPeerConnection();
t.add_cleanup(() => pc.close());
assert_true(await isOperationsChainEmpty(pc), "Empty to start");
}, "isOperationsChainEmpty detects empty");
promise_test(async t => {
const pc = new RTCPeerConnection();
t.add_cleanup(() => pc.close());
pc.createDataChannel("dummy");
const p = await pc.createOffer();
try {
await pc.setLocalDescription({type: "offer", sdp: "blah"});
assert_unreached("Control. Must not succeed");
} catch (e) {
assert_equals(e.name, "InvalidModificationError");
}
}, "SLD(offer) must validate against LastCreatedOffer early (prerequisite)");
promise_test(async t => {
const pc = new RTCPeerConnection();
t.add_cleanup(() => pc.close());
pc.createDataChannel("dummy");
await pc.setRemoteDescription(await pc.createOffer());
try {
await pc.setLocalDescription({type: "offer", sdp: "blah"});
assert_unreached("Control. Must not succeed");
} catch (e) {
assert_equals(e.name, "InvalidModificationError");
}
}, "SLD(offer) must validate input before signaling state (prerequisite)");
promise_test(async t => {
const pc = new RTCPeerConnection();
t.add_cleanup(() => pc.close());
pc.createDataChannel("dummy");
const p = pc.createOffer();
assert_true(!await isOperationsChainEmpty(pc), "Non-empty queue");
await p;
}, "createOffer uses operations chain");
promise_test(async t => {
const pc = new RTCPeerConnection();
t.add_cleanup(() => pc.close());
pc.createDataChannel("dummy");
await pc.setRemoteDescription(await pc.createOffer());
const p = pc.createAnswer();
assert_true(!await isOperationsChainEmpty(pc), "Non-empty queue");
await p;
}, "createAnswer uses operations chain");
promise_test(async t => {
const pc = new RTCPeerConnection();
t.add_cleanup(() => pc.close());
pc.createDataChannel("dummy");
const offer = await pc.createOffer();
const p = pc.setLocalDescription(offer);
assert_true(!await isOperationsChainEmpty(pc), "Non-empty queue");
await p;
}, "setLocalDescription uses operations chain");
promise_test(async t => {
const pc = new RTCPeerConnection();
t.add_cleanup(() => pc.close());
pc.createDataChannel("dummy");
const offer = await pc.createOffer();
assert_true(await isOperationsChainEmpty(pc), "Empty after settled");
const p = pc.setRemoteDescription(offer);
assert_true(!await isOperationsChainEmpty(pc), "Non-empty queue");
await p;
}, "setRemoteDescription uses operations chain");
promise_test(async t => {
const pc1 = new RTCPeerConnection();
t.add_cleanup(() => pc1.close());
const pc2 = new RTCPeerConnection();
t.add_cleanup(() => pc2.close());
pc1.addTransceiver("video");
const offer = await pc1.createOffer();
await pc1.setLocalDescription(offer);
const {candidate} = await new Promise(r => pc1.onicecandidate = r);
await pc2.setRemoteDescription(offer);
const p = pc2.addIceCandidate(candidate);
assert_true(!await isOperationsChainEmpty(pc2), "Non-empty queue");
await p;
}, "addIceCandidate uses operations chain");
</script>

View file

@ -7,7 +7,7 @@
<script>
"use strict";
const kSmallTimeoutMs = 10;
const kSmallTimeoutMs = 100;
promise_test(async t => {
const offerer = new RTCPeerConnection();
@ -34,6 +34,7 @@ promise_test(async t => {
t.add_cleanup(() => offerer.close());
const transceiver = offerer.addTransceiver('audio');
assert_equals(transceiver.mid, null);
await offerer.setLocalDescription();
assert_not_equals(transceiver.mid, null);
}, "Parameterless SLD() in 'stable' assigns transceiver.mid");
@ -110,20 +111,25 @@ promise_test(async t => {
promise_test(async t => {
const offerer = new RTCPeerConnection();
offerer.close();
offerer.setLocalDescription().then(t.step_func(() => assert_not_reached()));
await new Promise(resolve => t.step_timeout(resolve, kSmallTimeoutMs));
}, "Parameterless SLD() never resolves if already closed");
try {
await offerer.setLocalDescription();
assert_not_reached();
} catch (e) {
assert_equals(e.name, "InvalidStateError");
}
}, "Parameterless SLD() rejects with InvalidStateError if already closed");
promise_test(async t => {
const offerer = new RTCPeerConnection();
t.add_cleanup(() => offerer.close());
offerer.setLocalDescription().then(t.step_func(() => assert_not_reached()));
const p = Promise.race([
offerer.setLocalDescription(),
new Promise(r => t.step_timeout(() => r("timeout"), kSmallTimeoutMs))
]);
offerer.close();
await new Promise(resolve => t.step_timeout(resolve, kSmallTimeoutMs));
}, "Parameterless SLD() never resolves if closed while pending");
assert_equals(await p, "timeout");
}, "Parameterless SLD() never settles if closed while pending");
promise_test(async t => {
const offerer = new RTCPeerConnection();
@ -139,4 +145,26 @@ promise_test(async t => {
await offerer.setRemoteDescription(answerer.currentLocalDescription);
}, "Parameterless SLD() in a full O/A exchange succeeds");
</script>
promise_test(async t => {
const answerer = new RTCPeerConnection();
try {
await answerer.setRemoteDescription();
assert_not_reached();
} catch (e) {
assert_equals(e.name, "TypeError");
}
}, "Parameterless SRD() rejects with TypeError.");
promise_test(async t => {
const offerer = new RTCPeerConnection();
const {sdp} = await offerer.createOffer();
new RTCSessionDescription({type: "offer", sdp});
try {
new RTCSessionDescription({sdp});
assert_not_reached();
} catch (e) {
assert_equals(e.name, "TypeError");
}
}, "RTCSessionDescription constructed without type throws TypeError");
</script>