mirror of
https://github.com/servo/servo.git
synced 2025-08-03 12:40:06 +01:00
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:
commit
eef3899fb1
43 changed files with 475 additions and 1628 deletions
|
@ -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,
|
||||
{}
|
||||
|
|
|
@ -1,4 +0,0 @@
|
|||
[hit-test-floats-004.html]
|
||||
[Miss float below something else]
|
||||
expected: FAIL
|
||||
|
|
@ -1,2 +1,2 @@
|
|||
[no-transition-from-ua-to-blocking-stylesheet.html]
|
||||
expected: TIMEOUT
|
||||
expected: FAIL
|
||||
|
|
|
@ -1,4 +0,0 @@
|
|||
[CaretPosition-001.html]
|
||||
[Element at (400, 100)]
|
||||
expected: FAIL
|
||||
|
|
@ -2,3 +2,6 @@
|
|||
[listeners are called when <iframe> is resized]
|
||||
expected: FAIL
|
||||
|
||||
[listeners are called correct number of times]
|
||||
expected: FAIL
|
||||
|
||||
|
|
|
@ -21,6 +21,3 @@
|
|||
[test the top of layer]
|
||||
expected: FAIL
|
||||
|
||||
[test some point of the element: top left corner]
|
||||
expected: FAIL
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
[traverse_the_history_1.html]
|
||||
[Multiple history traversals from the same task]
|
||||
expected: FAIL
|
||||
|
|
@ -1,4 +0,0 @@
|
|||
[traverse_the_history_4.html]
|
||||
[Multiple history traversals, last would be aborted]
|
||||
expected: FAIL
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
[traverse_the_history_3.html]
|
||||
[traverse_the_history_5.html]
|
||||
[Multiple history traversals, last would be aborted]
|
||||
expected: FAIL
|
||||
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -1,5 +0,0 @@
|
|||
[018.html]
|
||||
expected: TIMEOUT
|
||||
[origin of the script that invoked the method, javascript:]
|
||||
expected: TIMEOUT
|
||||
|
|
@ -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]}}/";
|
||||
};
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
|
|
|
@ -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();
|
|
@ -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
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -1 +1,2 @@
|
|||
Cross-Origin-Embedder-Policy: require-corp
|
||||
Cross-Origin-Resource-Policy: cross-origin
|
||||
|
|
|
@ -1 +1,2 @@
|
|||
Cross-Origin-Embedder-Policy: require-corp
|
||||
Cross-Origin-Resource-Policy: cross-origin
|
||||
|
|
|
@ -1,5 +0,0 @@
|
|||
spec: https://github.com/jackbsteinberg/std-toast
|
||||
suggested_reviewers:
|
||||
- domenic
|
||||
- fergald
|
||||
- jackbsteinberg
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
|
@ -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>
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue