Update web-platform-tests to revision ad99a9e949c6006cc0f609501de77a0edb1e593a

This commit is contained in:
WPT Sync Bot 2020-08-05 08:22:29 +00:00
parent 6e28d7b3ec
commit f1ca6b4be4
89 changed files with 2406 additions and 267 deletions

View file

@ -1,4 +0,0 @@
[hit-test-floats-002.html]
[Hit test float]
expected: FAIL

View file

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

View file

@ -324,15 +324,9 @@
[<iframe>: separate response Content-Type: text/html */*;charset=gbk]
expected: FAIL
[<iframe>: separate response Content-Type: text/html */*]
expected: FAIL
[<iframe>: separate response Content-Type: text/plain */*]
expected: FAIL
[<iframe>: combined response Content-Type: text/html */*;charset=gbk]
expected: FAIL
[<iframe>: separate response Content-Type: text/html;" \\" text/plain]
[<iframe>: combined response Content-Type: text/html;charset=gbk text/plain text/html]
expected: FAIL

View file

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

View file

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

View file

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

View file

@ -0,0 +1,4 @@
[creating_browsing_context_test_01.html]
[first argument: absolute url]
expected: FAIL

View file

@ -0,0 +1,5 @@
[skip-another-top-level-browsing-context.html]
expected: TIMEOUT
[Autofocus elements queued in another top-level browsing context's documents should be skipped.]
expected: TIMEOUT

View file

@ -1,16 +1,20 @@
[supported-elements.html]
expected: TIMEOUT
[Contenteditable element should support autofocus]
expected: FAIL
[Host element with delegatesFocus including no focusable descendants should be skipped]
expected: FAIL
expected: NOTRUN
[Element with tabindex should support autofocus]
expected: FAIL
[Area element should support autofocus]
expected: FAIL
expected: NOTRUN
[Host element with delegatesFocus should support autofocus]
expected: FAIL
expected: NOTRUN
[Non-HTMLElement should not support autofocus]
expected: TIMEOUT

View file

@ -1,5 +1,5 @@
[iframe_sandbox_popups_escaping-1.html]
expected: TIMEOUT
expected: CRASH
[Check that popups from a sandboxed iframe escape the sandbox if\n allow-popups-to-escape-sandbox is used]
expected: TIMEOUT

View file

@ -1,4 +1,5 @@
[iframe_sandbox_popups_escaping-2.html]
expected: CRASH
[Check that popups from a sandboxed iframe escape the sandbox if\n allow-popups-to-escape-sandbox is used]
expected: FAIL

View file

@ -1,5 +1,4 @@
[iframe_sandbox_popups_escaping-3.html]
expected: TIMEOUT
[Check that popups from a sandboxed iframe escape the sandbox if\n allow-popups-to-escape-sandbox is used]
expected: TIMEOUT
expected: FAIL

View file

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

View file

@ -1,5 +1,4 @@
[iframe_sandbox_popups_nonescaping-2.html]
expected: CRASH
[Check that popups from a sandboxed iframe do not escape the sandbox]
expected: FAIL

View file

@ -1,5 +1,4 @@
[iframe_sandbox_popups_nonescaping-3.html]
expected: TIMEOUT
[Check that popups from a sandboxed iframe do not escape the sandbox]
expected: NOTRUN
expected: FAIL

View file

@ -1,4 +0,0 @@
[form-double-submit-3.html]
[<button> should have the same double-submit protection as <input type=submit>]
expected: FAIL

View file

@ -0,0 +1,7 @@
[base-url-worker-importScripts.html]
[Relative URL-like from same-origin importScripts()]
expected: FAIL
[Relative URL-like from cross-origin importScripts()]
expected: FAIL

View file

@ -0,0 +1,7 @@
[base-url.sub.html]
[Relative URL-like from cross origin classic <script> without crossorigin attribute]
expected: FAIL
[Relative URL-like from cross origin module <script>]
expected: FAIL

View file

@ -1,4 +0,0 @@
[iframe_005.html]
[document.write external script into iframe write back into parent]
expected: FAIL

View file

@ -1,9 +1,10 @@
[promise-job-entry.html]
expected: TIMEOUT
[Fulfillment handler on fulfilled promise]
expected: FAIL
[Rejection handler on pending-then-rejected promise]
expected: FAIL
expected: TIMEOUT
[Sanity check: this all works as expected with no promises involved]
expected: FAIL
@ -15,5 +16,5 @@
expected: FAIL
[Fulfillment handler on pending-then-fulfilled promise]
expected: FAIL
expected: TIMEOUT

View file

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

View file

@ -1,7 +1,8 @@
[shared-worker-in-data-url-context.window.html]
expected: TIMEOUT
[Create a shared worker in a data url frame]
expected: FAIL
[Create a data url shared worker in a data url frame]
expected: FAIL
expected: TIMEOUT

View file

@ -17086,6 +17086,41 @@
null,
{}
]
],
"protocol-handler-fragment-manual.https.html": [
"15617865691878af2069dfbbf4d397bf3a442600",
[
null,
{}
]
],
"protocol-handler-fragment-nosw-manual.https.html": [
"be3a6be6665246ccffa1d60f0e11f376b3280cb5",
[
null,
{}
]
],
"protocol-handler-path-manual.https.html": [
"085c5723ec412d027ef0ccca13855839477ffe70",
[
null,
{}
]
],
"protocol-handler-query-manual.https.html": [
"8ce65a5bad8826326dc4b5c85c6476174bbd5954",
[
null,
{}
]
],
"protocol-handler-query-nosw-manual.https.html": [
"9b4473fb8952ba93c79d17b71725b97c6bf2b208",
[
null,
{}
]
]
}
},
@ -270664,6 +270699,14 @@
"6d1eedb1fcbfda2bf27f74af1b34763adc62d599",
[]
],
"not-embeddable-frame.html": [
"e69de29bb2d1d6434b8b29ae775ad8c2e48c5391",
[]
],
"not-embeddable-frame.html.sub.headers": [
"beecdb765cd02cc6f15700d773e66e8844b941da",
[]
],
"redirect-throw-function.sub.py": [
"1bc89abf7176bbada8f0c8c35fd699f52bb0e8a9",
[]
@ -270710,6 +270753,10 @@
},
"sandbox": {
"support": {
"post-origin-on-load-worker.js": [
"21ce5748ab8b1edbfd04c8f77a3fba54739a73d5",
[]
],
"sandboxed-data-iframe.sub.html": [
"fafd4dc7707ab0d26ed2c67f34c26920649795f8",
[]
@ -322644,7 +322691,7 @@
[]
],
"from-local-system.md": [
"8c71e535baa907f1ef86641ee6211adb7d0c2423",
"91823444d5d4813afb020d00b2fca188f4a22f21",
[]
],
"from-web.md": [
@ -337409,6 +337456,36 @@
[]
],
"dynamic-import": {
"alpha": {
"import.js": [
"b2ac52df2a18d4bdcf49310352c2bafbeb0269c4",
[]
],
"worker-importScripts.sub.js": [
"904d32f9bfd7110c1cb1c7104f54118c86aba30e",
[]
]
},
"beta": {
"import.js": [
"7de1c68182571b80afabd79661f3fed025a2a34f",
[]
],
"redirect.py": [
"f2fd1ebd51d4ad5f4ef0582510600eb3731fd2c7",
[]
]
},
"gamma": {
"base-url.sub.js": [
"ec7784983d1182831d3ab910bca7eb54322eec62",
[]
],
"import.js": [
"435c1369be2f7a214b003ee8efa734ba8478cc7b",
[]
]
},
"propagate-nonce-external.js": [
"3b97d2f40e914af5d22ecc338913dcb0591927ba",
[]
@ -338885,6 +338962,24 @@
[]
]
},
"system-state-and-capabilities": {
"the-navigator-object": {
"resources": {
"handler-sw.js": [
"5fd915d17f98d8fa9c3c81451f46d71bc6cd024e",
[]
],
"handler-tools.js": [
"073287265cfe5f5b219ad6f61ffed89556a1e5cb",
[]
],
"handler.html": [
"552e5417842e67bb5b917268e0250a951614267c",
[]
]
}
}
},
"the-windoworworkerglobalscope-mixin": {
"README.md": [
"10ae3e5f03601a67e247f771cf73fdfddfdda12f",
@ -340437,7 +340532,7 @@
[]
],
"web-share.idl": [
"c29a29d0b4ede94153a75860344ab4824e91b6d5",
"b66bcd81fe9c951c2e70a58596380353b7046d97",
[]
],
"webaudio.idl": [
@ -350532,7 +350627,7 @@
[]
],
"taskcluster-run.py": [
"61d05689287a33c3470089e929d0c8871b6a46ef",
"d228e21990a1960bd19b3a1ea16207cc3fcfad55",
[]
],
"tc": {
@ -350545,13 +350640,17 @@
[]
],
"decision.py": [
"0820a6798b5b233581eb561a973cb97ff2af44e1",
"f52f5b093f0f41632613578f449188f82bc03ab9",
[]
],
"download.py": [
"359ec33405048c134ddcfac7a564ea9a17ac1b14",
[]
],
"github_checks_output.py": [
"d799be911660f566701be5ebc3b90caf117075e2",
[]
],
"sink_task.py": [
"ba76d27640c86197736d7edc9e8ed86fe8ec4bd0",
[]
@ -350562,7 +350661,7 @@
],
"tasks": {
"test.yml": [
"dd14fc71b915e90f0625dc1120aed3c86a787e53",
"2fc11e0b27b853c5af507581ebd44772741d20d0",
[]
]
},
@ -357563,7 +357662,7 @@
[]
],
"stability.py": [
"3f8989729fde0ee45cf783bc698307e38c844d90",
"605b40281a1b559e47d6aa795bb5cbd4b8715177",
[]
],
"testdriver-extra.js": [
@ -357697,7 +357796,7 @@
[]
],
"wptcommandline.py": [
"f322d77fbddefabc8fd187ebbc8cadaea8ea1086",
"ea586825616132b410127124ad2152ae88554cde",
[]
],
"wptlogging.py": [
@ -384835,6 +384934,13 @@
}
]
],
"report-frame-ancestors.sub.html": [
"a5aa1661c1085a61bedfe88b41ee389f62f577d2",
[
null,
{}
]
],
"report-multiple-violations-01.html": [
"7a92f1b955639eb26bfd4a737ee1a930fdec6592",
[
@ -385004,6 +385110,13 @@
{}
]
],
"meta-element.sub.html": [
"cd8da8f14c4ad775b9034131315867dec08cd2e8",
[
null,
{}
]
],
"sandbox-allow-scripts-subframe.sub.html": [
"1d6db3cde71cddb66f5536720a632b537465cbff",
[
@ -457945,7 +458058,7 @@
]
],
"reporting-redirect-with-same-origin-allow-popups.https.html": [
"7dba76c4ef5434d49d7800da14eb980f79728f44",
"bb76df811da5949c2d6f5196428a4a09d8bd9a56",
[
null,
{
@ -469554,6 +469667,29 @@
]
],
"dynamic-import": {
"alpha": {
"base-url-worker-importScripts.html": [
"817cf6d5ddfdc186454de0e3a672a865e2ced416",
[
null,
{}
]
],
"base-url-worker.sub.html": [
"a12204281cc43e64c7f1f4f7cdaf37958567b516",
[
null,
{}
]
],
"base-url.sub.html": [
"f7d4927a104c78e2f3d41e0899c7deadb5ca3d1c",
[
null,
{}
]
]
},
"dynamic-imports-credentials.sub.html": [
"b939a3ef1639db8027519ac004cd3197e254c0b9",
[
@ -476172,6 +476308,24 @@
{}
]
],
"input-events-get-target-ranges-backspace.tentative.html": [
"cf512269b07e0177214f7d9059150a77b85eb225",
[
null,
{
"testdriver": true
}
]
],
"input-events-get-target-ranges-forwarddelete.tentative.html": [
"3780324cf92bfa0c712045d89e43e903e3a1c9c6",
[
null,
{
"testdriver": true
}
]
],
"input-events-typing.html": [
"a894beea9bd381313b999e45abe0b4b8f61c804b",
[
@ -477048,7 +477202,7 @@
]
],
"invisible-images-composited-1.html": [
"495645ab41eccb80464a2ceb7de83a2136ecfbf7",
"7723d2f2bea5b622395e09ae1bd7fd4bb6cf87bc",
[
null,
{}
@ -477226,20 +477380,6 @@
{}
]
],
"inline-flow-shift-vertical-rl.html": [
"06bc34c37dd0559bfe6bedcc956ec2e2734c14b3",
[
null,
{}
]
],
"inline-flow-shift.html": [
"39550da6588eeb7aca7001ab592ee0177d26ca56",
[
null,
{}
]
],
"local-shift-without-viewport-shift.html": [
"37729f1c13c298b5a2d95c46b7a4f199d8943022",
[
@ -477261,13 +477401,6 @@
{}
]
],
"outline.html": [
"1fed8e92f5e4b60cd83646405b6bd77e673c076f",
[
null,
{}
]
],
"partially-clipped-visual-rect.html": [
"3b18b98dd93312c37b9e2f25918df50266a09243",
[
@ -516250,6 +516383,17 @@
{}
]
],
"case-sensitivity.any.js": [
"1c0b0dcac361fe02eefc1dd5035955d10dc37151",
[
"user-timing/case-sensitivity.any.html",
{}
],
[
"user-timing/case-sensitivity.any.worker.html",
{}
]
],
"clearMarks.html": [
"92c60a3bbb856bd05bf13f12bde09dac7eefb6e6",
[
@ -561705,7 +561849,7 @@
]
],
"response.py": [
"dc1328998810faacf74f1b94b54ed59f48e84e4d",
"cbe9e7d7fb14680f027d6fdc15d409ac19a7320c",
[
null,
{}

View file

@ -1,4 +0,0 @@
[hit-test-floats-002.html]
[Hit test float]
expected: FAIL

View file

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

View file

@ -324,15 +324,9 @@
[<iframe>: separate response Content-Type: text/html */*;charset=gbk]
expected: FAIL
[<iframe>: separate response Content-Type: text/html */*]
expected: FAIL
[<iframe>: separate response Content-Type: text/plain */*]
expected: FAIL
[<iframe>: combined response Content-Type: text/html */*;charset=gbk]
expected: FAIL
[<iframe>: separate response Content-Type: text/html;" \\" text/plain]
[<iframe>: combined response Content-Type: text/html;charset=gbk text/plain text/html]
expected: FAIL

View file

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

View file

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

View file

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

View file

@ -0,0 +1,4 @@
[creating_browsing_context_test_01.html]
[first argument: absolute url]
expected: FAIL

View file

@ -0,0 +1,5 @@
[skip-another-top-level-browsing-context.html]
expected: TIMEOUT
[Autofocus elements queued in another top-level browsing context's documents should be skipped.]
expected: TIMEOUT

View file

@ -1,4 +1,5 @@
[supported-elements.html]
expected: TIMEOUT
[Contenteditable element should support autofocus]
expected: FAIL
@ -6,11 +7,14 @@
expected: FAIL
[Host element with delegatesFocus including no focusable descendants should be skipped]
expected: FAIL
expected: NOTRUN
[Area element should support autofocus]
expected: FAIL
expected: NOTRUN
[Host element with delegatesFocus should support autofocus]
expected: FAIL
expected: NOTRUN
[Non-HTMLElement should not support autofocus]
expected: TIMEOUT

View file

@ -1,6 +1,6 @@
[iframe_sandbox_popups_escaping-1.html]
type: testharness
expected: TIMEOUT
expected: CRASH
[Check that popups from a sandboxed iframe escape the sandbox if\n allow-popups-to-escape-sandbox is used]
expected: TIMEOUT

View file

@ -1,4 +1,5 @@
[iframe_sandbox_popups_escaping-2.html]
expected: CRASH
[Check that popups from a sandboxed iframe escape the sandbox if\n allow-popups-to-escape-sandbox is used]
expected: FAIL

View file

@ -1,6 +1,5 @@
[iframe_sandbox_popups_escaping-3.html]
type: testharness
expected: TIMEOUT
[Check that popups from a sandboxed iframe escape the sandbox if\n allow-popups-to-escape-sandbox is used]
expected: TIMEOUT
expected: FAIL

View file

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

View file

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

View file

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

View file

@ -1,4 +0,0 @@
[form-double-submit-3.html]
[<button> should have the same double-submit protection as <input type=submit>]
expected: FAIL

View file

@ -0,0 +1,7 @@
[base-url-worker-importScripts.html]
[Relative URL-like from same-origin importScripts()]
expected: FAIL
[Relative URL-like from cross-origin importScripts()]
expected: FAIL

View file

@ -0,0 +1,7 @@
[base-url.sub.html]
[Relative URL-like from cross origin classic <script> without crossorigin attribute]
expected: FAIL
[Relative URL-like from cross origin module <script>]
expected: FAIL

View file

@ -1,4 +0,0 @@
[iframe_005.html]
[document.write external script into iframe write back into parent]
expected: FAIL

View file

@ -1,9 +1,10 @@
[promise-job-entry.html]
expected: TIMEOUT
[Fulfillment handler on fulfilled promise]
expected: FAIL
[Rejection handler on pending-then-rejected promise]
expected: FAIL
expected: TIMEOUT
[Sanity check: this all works as expected with no promises involved]
expected: FAIL
@ -15,5 +16,5 @@
expected: FAIL
[Fulfillment handler on pending-then-fulfilled promise]
expected: FAIL
expected: TIMEOUT

View file

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

View file

@ -1,7 +1,8 @@
[shared-worker-in-data-url-context.window.html]
expected: TIMEOUT
[Create a shared worker in a data url frame]
expected: FAIL
[Create a data url shared worker in a data url frame]
expected: FAIL
expected: TIMEOUT

View file

@ -1,4 +0,0 @@
[hit_test_multiple_sc.html]
[Hit testing works for following stacking contexts]
expected: FAIL

View file

@ -1,4 +0,0 @@
[hit-test-background.html]
[Hit testing backgrounds of content should report the same element as the content]
expected: FAIL

View file

@ -1,4 +0,0 @@
[hit_test_multiple_sc.html]
[Hit testing works for following stacking contexts]
expected: FAIL

View file

@ -1,4 +0,0 @@
[hit_test_pos_fixed.html]
[Hit-test of an element with position: fixed should discard scroll offset]
expected: FAIL

View file

@ -0,0 +1,12 @@
<!DOCTYPE html>
<html>
<head>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<title>Reporting works with frame-ancestors</title>
</head>
<body>
<iframe src="./support/not-embeddable-frame.html?reportID={{$id:uuid()}}"></iframe>
<script async defer src='../support/checkReport.sub.js?reportField=violated-directive&reportValue=frame-ancestors&reportID={{$id}}'></script>
</body>
</html>

View file

@ -0,0 +1,5 @@
Expires: Mon, 26 Jul 1997 05:00:00 GMT
Cache-Control: no-store, no-cache, must-revalidate
Cache-Control: post-check=0, pre-check=0, false
Pragma: no-cache
Content-Security-Policy: frame-ancestors 'none'; report-uri ../../support/report.py?op=put&reportID={{GET[reportID]}}

View file

@ -0,0 +1,46 @@
<!DOCTYPE html>
<meta charset="utf-8">
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<meta content="sandbox allow-scripts" http-equiv="Content-Security-Policy">
<body>
<iframe id="iframe"></iframe>
<script>
// According to
// https://html.spec.whatwg.org/multipage/semantics.html#attr-meta-http-equiv-content-security-policy
// `sandbox` directives must be ignored when delivered via `<meta>`.
test(() => {
assert_equals(location.origin, "{{location[scheme]}}://{{location[host]}}");
}, "Document shouldn't be sandboxed by <meta>");
// Note: sandbox directive for workers are not yet specified.
// https://github.com/w3c/webappsec-csp/issues/279
// Anyway workers shouldn't be affected by sandbox directives in `<meta>`.
async_test(t => {
const worker = new Worker("support/post-origin-on-load-worker.js");
worker.onerror = t.unreached_func("Worker construction failed");
worker.onmessage = t.step_func_done(e => {
assert_equals(e.data, "{{location[scheme]}}://{{location[host]}}");
});
}, "Worker shouldn't be sandboxed by inheriting <meta>");
parent.async_test(t => {
// Although <iframe about:blank> should inherit parent's CSP,
// sandbox directives in <meta> should be ignored in the first place,
// so workers created from such <iframe>s shouldn't also be sandboxed.
const iframeDocument = document.querySelector("#iframe").contentDocument;
const script = iframeDocument.createElement("script");
script.innerText = `
const worker = new Worker("support/post-origin-on-load-worker.js");
worker.onerror = () => parent.postMessage("onerror", "*");
worker.onmessage = (e) => parent.postMessage(e.data, "*");
`;
iframeDocument.body.appendChild(script);
// Receive message from <iframe>.
onmessage = t.step_func_done(e => {
assert_equals(e.data, "{{location[scheme]}}://{{location[host]}}");
});
}, "Worker shouldn't be sandboxed when created <iframe> inheriting parent's CSP with sandbox <meta>");
</script>
</body>

View file

@ -0,0 +1 @@
postMessage(self.origin);

View file

@ -39,9 +39,6 @@ wherever you currently set your PATH.
See also [additional setup required to run Safari](safari.md).
### Windows Setup
**Note:** In general, Windows Subsystem for Linux will provide the smoothest
user experience for running web-platform-tests on Windows, where installation
and usage are similar to Linux.
Download and install [Python 2.7](https://www.python.org/downloads). The
installer includes `pip` by default.
@ -63,6 +60,15 @@ started using:
python wpt serve
```
#### Windows Subsystem for Linux
Optionally on Windows you can use the [Windows Subsystem for
Linux](https://docs.microsoft.com/en-us/windows/wsl/about) (WSL). If doing so,
installation and usage are similar to the Linux instructions. Be aware that WSL
may attempt to override `/etc/hosts` each time it is launched, which would then
require you to re-run [`hosts` File Setup](#hosts-file-setup). This behavior
[can be configured](https://docs.microsoft.com/en-us/windows/wsl/wsl-config#network).
### `hosts` File Setup
To get the tests running, you need to set up the test domains in your

View file

@ -8,7 +8,6 @@
<script src=/common/get-host-info.sub.js></script>
<script src="/common/utils.js"></script>
<script src="../resources/dispatcher.js"></script>
<script src="../resources/try-access.js"></script>
<script>
const directory = "/html/cross-origin-opener-policy/reporting";

View file

@ -0,0 +1,7 @@
<!DOCTYPE html>
<title>Base URLs used in resolving specifiers in dynamic imports from importScripts()</title>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script>
fetch_tests_from_worker(new Worker("./worker-importScripts.sub.js"));
</script>

View file

@ -0,0 +1,8 @@
<!DOCTYPE html>
<title>Base URLs used in resolving specifiers in dynamic imports from workers</title>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script>
fetch_tests_from_worker(new Worker(
"../beta/redirect.py?location=http://{{host}}:{{ports[http][0]}}/html/semantics/scripting-1/the-script-element/module/dynamic-import/gamma/base-url.sub.js"));
</script>

View file

@ -0,0 +1,28 @@
<!DOCTYPE html>
<title>Base URLs used in resolving specifiers in dynamic imports</title>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script>
self.testName = "same origin classic <script>";
self.baseUrlSanitized = false;
</script>
<script src="../beta/redirect.py?location=http://{{host}}:{{ports[http][0]}}/html/semantics/scripting-1/the-script-element/module/dynamic-import/gamma/base-url.sub.js"></script>
<script>
self.testName = "cross origin classic <script> without crossorigin attribute";
self.baseUrlSanitized = true;
</script>
<script src="../beta/redirect.py?location=http://{{hosts[alt][]}}:{{ports[http][0]}}/html/semantics/scripting-1/the-script-element/module/dynamic-import/gamma/base-url.sub.js"></script>
<script>
self.testName = "cross origin classic <script> with crossorigin attribute";
self.baseUrlSanitized = false;
</script>
<script src="../beta/redirect.py?location=http://{{hosts[alt][]}}:{{ports[http][0]}}/html/semantics/scripting-1/the-script-element/module/dynamic-import/gamma/base-url.sub.js%3Fpipe=header(Access-Control-Allow-Origin,*)" crossorigin></script>
<script>
self.testName = "cross origin module <script>";
self.baseUrlSanitized = false;
</script>
<script src="../beta/redirect.py?location=http://{{hosts[alt][]}}:{{ports[http][0]}}/html/semantics/scripting-1/the-script-element/module/dynamic-import/gamma/base-url.sub.js%3Fpipe=header(Access-Control-Allow-Origin,*)" type="module"></script>

View file

@ -0,0 +1 @@
export const A = { "from": "alpha/import.js" };

View file

@ -0,0 +1,15 @@
"use strict";
importScripts("/resources/testharness.js");
// CORS-same-origin
self.testName = "same-origin importScripts()";
self.baseUrlSanitized = false;
importScripts("../beta/redirect.py?location=http://{{host}}:{{ports[http][0]}}/html/semantics/scripting-1/the-script-element/module/dynamic-import/gamma/base-url.sub.js");
// CORS-cross-origin
self.testName = "cross-origin importScripts()";
self.baseUrlSanitized = true;
importScripts("../beta/redirect.py?location=http://{{hosts[alt][]}}:{{ports[http][0]}}/html/semantics/scripting-1/the-script-element/module/dynamic-import/gamma/base-url.sub.js");
done();

View file

@ -0,0 +1 @@
export const A = { "from": "beta/import.js" };

View file

@ -0,0 +1,19 @@
def main(request, response):
"""Simple handler that causes redirection.
The request should typically have two query parameters:
status - The status to use for the redirection. Defaults to 302.
location - The resource to redirect to.
"""
status = 302
if b"status" in request.GET:
try:
status = int(request.GET.first(b"status"))
except ValueError:
pass
response.status = status
location = request.GET.first(b"location")
response.headers.set(b"Location", location)

View file

@ -0,0 +1,58 @@
"use strict";
// This script triggers import(), and thus the base URL of this script
// (either loaded by `<script>` or `importScripts()`) is used as the base URL
// of resolving relative URL-like specifiers in `import()`.
// The following fields should be set by the callers of this script
// (unless loaded as the worker top-level script):
// - self.testName (string)
// - self.baseUrlSanitized (boolean)
// When this script is loaded as the worker top-level script:
if ('DedicatedWorkerGlobalScope' in self &&
self instanceof DedicatedWorkerGlobalScope &&
!self.testName) {
importScripts("/resources/testharness.js");
self.testName = 'worker top-level script';
// Worker top-level scripts are always same-origin.
self.baseUrlSanitized = false;
}
{
// This could change by the time the test is executed, so we save it now.
// As this script is loaded multiple times, savedBaseUrlSanitized is scoped.
const savedBaseUrlSanitized = self.baseUrlSanitized;
promise_test(() => {
const promise = import("./import.js?pipe=header(Access-Control-Allow-Origin,*)&label=relative-" + self.testName);
if (savedBaseUrlSanitized) {
// The base URL is "about:blank" and thus import() here should fail.
return promise.then(module => {
// This code should be unreached, but assert_equals() is used here
// to log `module.A["from"]` in case of unexpected resolution.
assert_equals(module.A["from"], "(unreached)",
"Relative URL-like specifier resolution should fail");
assert_unreached();
},
() => {});
} else {
// The base URL is the response URL of this script, i.e.
// `.../gamma/base-url.sub.js`.
return promise.then(module => {
assert_equals(module.A["from"], "gamma/import.js");
});
}
},
"Relative URL-like from " + self.testName);
}
promise_test(() => {
return import("http://{{hosts[alt][]}}:{{ports[http][0]}}/html/semantics/scripting-1/the-script-element/module/dynamic-import/gamma/import.js?pipe=header(Access-Control-Allow-Origin,*)&label=absolute-" + self.testName)
.then(module => {
assert_equals(module.A["from"], "gamma/import.js");
})
},
"Absolute URL-like from " + self.testName);
done();

View file

@ -0,0 +1 @@
export const A = { "from": "gamma/import.js" };

View file

@ -0,0 +1,20 @@
<!doctype html>
<!-- Use a non-UTF-8 encoding to see how the handler URL is parsed -->
<meta charset=windows-1254>
<meta name=timeout content=long>
<title>registerProtocolHandler() and a handler with %s in the fragment</title>
<script src=/resources/testharness.js></script>
<script src=/resources/testharnessreport.js></script>
<script src=/service-workers/service-worker/resources/test-helpers.sub.js></script>
<script>
// Configure expectations for individual test
window.type = "fragment";
window.noSW = false;
</script>
<script src=resources/handler-tools.js></script>
<ol>
<li><p>First, register the handler: <button onclick='register()'>Register</button>.
<li><p>Then, run the test: <button onclick='runTest()'>Run</button>.
<li><p>Or, run the test with U+0000 NULL: <button onclick='runTest({ includeNull: true })'>Run NULL</button>.
</ol>
<div id=log></div>

View file

@ -0,0 +1,20 @@
<!doctype html>
<!-- Use a non-UTF-8 encoding to see how the handler URL is parsed -->
<meta charset=windows-1254>
<meta name=timeout content=long>
<title>registerProtocolHandler() and a handler with %s in the fragment (does not use a service worker)</title>
<script src=/resources/testharness.js></script>
<script src=/resources/testharnessreport.js></script>
<script src=/service-workers/service-worker/resources/test-helpers.sub.js></script>
<script>
// Configure expectations for individual test
window.type = "fragment";
window.noSW = true;
</script>
<script src=resources/handler-tools.js></script>
<ol>
<li><p>First, register the handler: <button onclick='register()'>Register</button>.
<li><p>Then, run the test: <button onclick='runTest()'>Run</button>.
<li><p>Or, run the test with U+0000 NULL: <button onclick='runTest({ includeNull: true })'>Run NULL</button>.
</ol>
<div id=log></div>

View file

@ -0,0 +1,20 @@
<!doctype html>
<!-- Use a non-UTF-8 encoding to see how the handler URL is parsed -->
<meta charset=windows-1254>
<meta name=timeout content=long>
<title>registerProtocolHandler() and a handler with %s in the path</title>
<script src=/resources/testharness.js></script>
<script src=/resources/testharnessreport.js></script>
<script src=/service-workers/service-worker/resources/test-helpers.sub.js></script>
<script>
// Configure expectations for individual test
window.type = "path";
window.noSW = false;
</script>
<script src=resources/handler-tools.js></script>
<ol>
<li><p>First, register the handler: <button onclick='register()'>Register</button>.
<li><p>Then, run the test: <button onclick='runTest()'>Run</button>.
<li><p>Or, run the test with U+0000 NULL: <button onclick='runTest({ includeNull: true })'>Run NULL</button>.
</ol>
<div id=log></div>

View file

@ -0,0 +1,20 @@
<!doctype html>
<!-- Use a non-UTF-8 encoding to see how the handler URL is parsed -->
<meta charset=windows-1254>
<meta name=timeout content=long>
<title>registerProtocolHandler() and a handler with %s in the query</title>
<script src=/resources/testharness.js></script>
<script src=/resources/testharnessreport.js></script>
<script src=/service-workers/service-worker/resources/test-helpers.sub.js></script>
<script>
// Configure expectations for individual test
window.type = "query";
window.noSW = false;
</script>
<script src=resources/handler-tools.js></script>
<ol>
<li><p>First, register the handler: <button onclick='register()'>Register</button>.
<li><p>Then, run the test: <button onclick='runTest()'>Run</button>.
<li><p>Or, run the test with U+0000 NULL: <button onclick='runTest({ includeNull: true })'>Run NULL</button>.
</ol>
<div id=log></div>

View file

@ -0,0 +1,20 @@
<!doctype html>
<!-- Use a non-UTF-8 encoding to see how the handler URL is parsed -->
<meta charset=windows-1254>
<meta name=timeout content=long>
<title>registerProtocolHandler() and a handler with %s in the query (does not use a service worker)</title>
<script src=/resources/testharness.js></script>
<script src=/resources/testharnessreport.js></script>
<script src=/service-workers/service-worker/resources/test-helpers.sub.js></script>
<script>
// Configure expectations for individual test
window.type = "query";
window.noSW = true;
</script>
<script src=resources/handler-tools.js></script>
<ol>
<li><p>First, register the handler: <button onclick='register()'>Register</button>.
<li><p>Then, run the test: <button onclick='runTest()'>Run</button>.
<li><p>Or, run the test with U+0000 NULL: <button onclick='runTest({ includeNull: true })'>Run NULL</button>.
</ol>
<div id=log></div>

View file

@ -0,0 +1,3 @@
onfetch = e => {
e.respondWith(fetch("handler.html"));
}

View file

@ -0,0 +1,53 @@
// These can be used in an environment that has these global variables defined:
// * type (one of "path", "query", or "fragment")
// * noSW (a boolean)
if (type === "path" && noSW) {
throw new Error("There is no support for a path handler without a service worker.");
}
const swString = noSW ? "" : "sw";
const handler = {
"path": "PSS%sPSE/?QES\u2020QEE#FES\u2020FEE",
"query": "?QES\u2020QEEPSS%sPSE#FES\u2020FEE",
"fragment": "?QES\u2020QEE#FES\u2020FEEPSS%sPSE"
}[type];
const scheme = `web+wpt${type}${swString}`;
function register() {
const handlerURL = noSW ? `resources/handler.html${handler}${type}` : `resources/handler/${type}/${handler}`;
navigator.registerProtocolHandler(scheme, handlerURL, `WPT ${type} handler${noSW ? ", without service worker" : ""}`);
}
function runTest({ includeNull = false } = {}) {
promise_test(async t => {
const bc = new BroadcastChannel(`protocol-handler-${type}${swString}`);
if (!noSW) {
const reg = await service_worker_unregister_and_register(t, "resources/handler-sw.js", "resources/handler/");
t.add_cleanup(async () => await reg.unregister());
await wait_for_state(t, reg.installing, 'activated');
}
const a = document.body.appendChild(document.createElement("a"));
const codePoints = [];
let i = includeNull ? 0 : 1;
for (; i < 0x82; i++) {
codePoints.push(String.fromCharCode(i));
}
a.href = `${scheme}:${codePoints.join("")}`;
a.target = "_blank";
a.click();
await new Promise(resolve => {
bc.onmessage = t.step_func(e => {
resultingURL = e.data;
assert_equals(stringBetweenMarkers(resultingURL, "QES", "QEE"), "%86", "query baseline");
assert_equals(stringBetweenMarkers(resultingURL, "FES", "FEE"), "%E2%80%A0", "fragment baseline");
assert_equals(stringBetweenMarkers(resultingURL, "PSS", "PSE"), `${encodeURIComponent(scheme)}%3A${includeNull ? "%2500" : ""}%2501%2502%2503%2504%2505%2506%2507%2508%250B%250C%250E%250F%2510%2511%2512%2513%2514%2515%2516%2517%2518%2519%251A%251B%251C%251D%251E%251F%20!%22%23%24%25%26${type === "query" ? "%27" : "'"}()*%2B%2C-.%2F0123456789%3A%3B%3C%3D%3E%3F%40ABCDEFGHIJKLMNOPQRSTUVWXYZ%5B%5C%5D%5E_%60abcdefghijklmnopqrstuvwxyz%7B%7C%7D~%257F%25C2%2580%25C2%2581`, "actual test");
resolve();
});
});
});
}
function stringBetweenMarkers(string, start, end) {
return string.substring(string.indexOf(start) + start.length, string.indexOf(end));
}

View file

@ -0,0 +1,23 @@
<!doctype html>
<p>This popup can be closed if it does not close itself.
<p>
<script>
// This resource either gets navigated to through a service worker as a result of a URL that looks
// like:
// https://.../html/webappapis/system-state-and-capabilities/the-navigator-object/resources/handler/{type}/...
// (the host is excluded to not upset the lint tool)
// or it gets navigated to directly with the type appended to the end of the URL. In that case type
// can only be fragment or query.
let type = null;
let swString = null;
if (new URL(document.URL).pathname.endsWith("handler.html")) {
swString = "";
type = (document.URL.endsWith("fragment")) ? "fragment" : "query";
} else {
type = document.URL.split("/")[9];
swString = "sw";
}
new BroadcastChannel(`protocol-handler-${type}${swString}`).postMessage(document.URL);
window.close();
</script>

View file

@ -0,0 +1,811 @@
<!DOCTYPE html>
<meta charset="utf-8">
<title>InputEvent.getTargetRanges() at Backspace</title>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="/resources/testdriver.js"></script>
<script src="/resources/testdriver-vendor.js"></script>
<script src="/resources/testdriver-actions.js"></script>
<div contenteditable></div>
<script>
const kBackspaceKey = "\uE003";
const kShift = "\uE008";
const kMeta = "\uE03d";
const kControl = "\uE009";
const kAlt = "\uE00A";
let selection = getSelection();
let editor = document.querySelector("div[contenteditable]");
let beforeinput = [];
let input = [];
editor.addEventListener("beforeinput", (e) => {
// NOTE: Blink makes `getTargetRanges()` return empty range after propagaion,
// but this test wants to check the result during propagation.
// Therefore, we need to cache the result, but will assert if
// `getTargetRanges()` returns different ranges after checking the
// cached ranges.
e.cachedRanges = e.getTargetRanges();
beforeinput.push(e);
});
editor.addEventListener("input", (e) => {
e.cachedRanges = e.getTargetRanges();
input.push(e);
});
function reset() {
editor.focus();
beforeinput = [];
input = [];
}
function getRangeDescription(range) {
function getNodeDescription(node) {
if (!node) {
return "null";
}
switch (node.nodeType) {
case Node.TEXT_NODE:
case Node.COMMENT_NODE:
case Node.CDATA_SECTION_NODE:
return `${node.nodeName} "${node.data}"`;
case Node.ELEMENT_NODE:
return `<${node.nodeName.toLowerCase()}>`;
default:
return `${node.nodeName}`;
}
}
if (range === null) {
return "null";
}
if (range === undefined) {
return "undefined";
}
return range.startContainer == range.endContainer && range.startOffset == range.endOffset
? `(${getNodeDescription(range.startContainer)}, ${range.startOffset})`
: `(${getNodeDescription(range.startContainer)}, ${range.startOffset}) - (${getNodeDescription(range.endContainer)}, ${range.endOffset})`;
}
function getArrayOfRangesDescription(arrayOfRanges) {
if (arrayOfRanges === null) {
return "null";
}
if (arrayOfRanges === undefined) {
return "undefined";
}
if (!Array.isArray(arrayOfRanges)) {
return "Unknown Object";
}
if (arrayOfRanges.length === 0) {
return "[]";
}
let result = "[";
for (let range of arrayOfRanges) {
result += `{${getRangeDescription(range)}},`;
}
result += "]";
return result;
}
function sendBackspaceKey(modifier) {
if (!modifier) {
return new test_driver.Actions()
.keyDown(kBackspaceKey)
.keyUp(kBackspaceKey)
.send();
}
return new test_driver.Actions()
.keyDown(modifier)
.keyDown(kBackspaceKey)
.keyUp(kBackspaceKey)
.keyUp(modifier)
.send();
}
function checkGetTargetRangesKeepReturningSameValue(event) {
// https://github.com/w3c/input-events/issues/114
assert_equals(getArrayOfRangesDescription(event.getTargetRanges()),
getArrayOfRangesDescription(event.cachedRanges),
`${event.type}.getTargetRanges() should keep returning the same array of ranges even after its propagation finished`);
}
function checkGetTargetRangesOfBeforeinputOnDeleteSomething(expectedRange) {
assert_equals(beforeinput.length, 1,
"One beforeinput event should be fired if the key operation deletes something");
assert_true(Array.isArray(beforeinput[0].cachedRanges),
"beforeinput[0].getTargetRanges() should return an array of StaticRange instances during propagation");
// Before checking the length of array of ranges, we should check first range
// first because the first range data is more important than whether there
// are additional unexpected ranges.
if (beforeinput[0].cachedRanges.length > 0) {
assert_equals(
getRangeDescription(beforeinput[0].cachedRanges[0]),
getRangeDescription(expectedRange),
`beforeinput.getTargetRanges() should return expected range (inputType is "${beforeinput[0].inputType}")`);
assert_equals(beforeinput[0].cachedRanges.length, 1,
"beforeinput.getTargetRanges() should return one range within an array");
}
assert_equals(beforeinput[0].cachedRanges, 1,
"One range should be returned from getTargetRanges() when the key operation deletes something");
checkGetTargetRangesKeepReturningSameValue(beforeinput[0]);
}
function checkGetTargetRangesOfInputOnDeleteSomething() {
assert_equals(input.length, 1,
"One input event should be fired if the key operation deletes something");
// https://github.com/w3c/input-events/issues/113
assert_true(Array.isArray(input[0].cachedRanges),
"input[0].getTargetRanges() should return an array of StaticRange instances during propagation");
assert_equals(input[0].cachedRanges.length, 0,
"input[0].getTargetRanges() should return empty array during propagation");
checkGetTargetRangesKeepReturningSameValue(input[0]);
}
function checkBeforeinputAndInputEventsOnNOOP() {
assert_equals(beforeinput.length, 0,
"beforeinput event shouldn't be fired when the key operation does not cause modifying the DOM tree");
assert_equals(input.length, 0,
"input event shouldn't be fired when the key operation does not cause modifying the DOM tree");
}
// Simply deletes the previous ASCII character of caret position.
promise_test(async () => {
reset();
editor.innerHTML = "<p>abc</p>";
selection.collapse(editor.firstChild.firstChild, 1);
await sendBackspaceKey();
assert_equals(editor.innerHTML, "<p>bc</p>");
checkGetTargetRangesOfBeforeinputOnDeleteSomething({
startContainer: editor.firstChild.firstChild,
startOffset: 0,
endContainer: editor.firstChild.firstChild,
endOffset: 1,
});
checkGetTargetRangesOfInput();
}, 'Backspace at "<p>a[]bc</p>"');
// Simply deletes the previous ASCII character of caret position.
promise_test(async () => {
reset();
editor.innerHTML = "<p>abc</p>";
selection.collapse(editor.firstChild.firstChild, 2);
await sendBackspaceKey();
assert_equals(editor.innerHTML, "<p>ac</p>");
checkGetTargetRangesOfBeforeinputOnDeleteSomething({
startContainer: editor.firstChild.firstChild,
startOffset: 1,
endContainer: editor.firstChild.firstChild,
endOffset: 2,
});
checkGetTargetRangesOfInput();
}, 'Backspace at "<p>ab[]c</p>"');
// Simply deletes the previous ASCII character of caret position.
promise_test(async () => {
reset();
editor.innerHTML = "<p>abc</p>";
selection.collapse(editor.firstChild.firstChild, 3);
await sendBackspaceKey();
assert_equals(editor.innerHTML, "<p>ab</p>");
checkGetTargetRangesOfBeforeinputOnDeleteSomething({
startContainer: editor.firstChild.firstChild,
startOffset: 2,
endContainer: editor.firstChild.firstChild,
endOffset: 3,
});
checkGetTargetRangesOfInput();
}, 'Backspace at "<p>abc[]</p>"');
// Should delete the `<span>` element becase it becomes empty.
// However, we need discussion whether the `<span>` element should be
// contained by a range of `getTargetRanges()`.
// https://github.com/w3c/input-events/issues/112
promise_test(async () => {
reset();
editor.innerHTML = "<p>a<span>b</span>c</p>";
let c = editor.querySelector("span").nextSibling;
selection.collapse(c, 0);
await sendBackspaceKey();
assert_equals(editor.innerHTML, "<p>ac</p>");
checkGetTargetRangesOfBeforeinputOnDeleteSomething({
startContainer: editor.firstChild,
startOffset: 1,
endContainer: c,
endOffset: 0,
});
checkGetTargetRangesOfInput();
}, 'Backspace at "<p>a<span>b</span>[]c</p>"');
// Should delete the `<span>` element becase it becomes empty.
// However, we need discussion whether the `<span>` element should be
// contained by a range of `getTargetRanges()`.
// https://github.com/w3c/input-events/issues/112
promise_test(async () => {
reset();
editor.innerHTML = "<p>a<span>b</span>c</p>";
let b = editor.querySelector("span").firstChild;
selection.collapse(b, 1);
await sendBackspaceKey();
assert_equals(editor.innerHTML, "<p>ac</p>");
checkGetTargetRangesOfBeforeinputOnDeleteSomething({
startContainer: editor.firstChild,
startOffset: 1,
endContainer: editor.firstChild,
endOffset: 2,
});
checkGetTargetRangesOfInput();
}, 'Backspace at "<p>a<span>b[]</span>c</p>"');
// Invisible leading white-space may be deleted when the first visible
// character is deleted. If it's deleted, it should be contained by
// the range of `getTargetRanges()`, but needs discussion.
// https://github.com/w3c/input-events/issues/112
promise_test(async () => {
reset();
editor.innerHTML = "<p> abc</p>";
selection.collapse(editor.firstChild.firstChild, 2);
await sendBackspaceKey();
assert_in_array(editor.innerHTML, ["<p>bc</p>", "<p> bc</p>"]);
checkGetTargetRangesOfBeforeinputOnDeleteSomething({
startContainer: editor.firstChild.firstChild,
startOffset: editor.firstChild.firstChild.length == 2 ? 0 : 1,
endContainer: editor.firstChild.firstChild,
endOffset: 2,
});
checkGetTargetRangesOfInput();
}, 'Backspace at "<p> a[]bc</p>"');
// Invisible leading white-spaces in current block and invisible trailing
// white-spaces in the previous block should be deleted for avoiding they
// becoming visible when the blocks are joined. Perhaps, they should be
// contained by the range of `getTargetRanges()`, but needs discussion.
// https://github.com/w3c/input-events/issues/112
promise_test(async () => {
reset();
editor.innerHTML = "<p>abc </p><p> def</p>";
let p1 = editor.firstChild;
let abc = p1.firstChild;
let p2 = p1.nextSibling;
let def = p2.firstChild;
selection.collapse(def, 3);
await sendBackspaceKey();
assert_equals(editor.innerHTML, "<p>abcdef</p>");
checkGetTargetRangesOfBeforeinputOnDeleteSomething({
startContainer: abc,
startOffset: 3,
endContainer: def,
endOffset: 3,
});
checkGetTargetRangesOfInput();
}, 'Backspace at "<p>abc </p><p> []def</p>"');
// Invisible leading white-spaces in current block and invisible trailing
// white-spaces in the previous block should be deleted for avoiding they
// becoming visible when the blocks are joined. Perhaps, they should be
// contained by the range of `getTargetRanges()`, but needs discussion.
// https://github.com/w3c/input-events/issues/112
promise_test(async () => {
reset();
editor.innerHTML = "<p>abc </p><p> def</p>";
let p1 = editor.firstChild;
let abc = p1.firstChild;
let p2 = p1.nextSibling;
let def = p2.firstChild;
selection.collapse(def, 2);
await sendBackspaceKey();
assert_equals(editor.innerHTML, "<p>abcdef</p>");
checkGetTargetRangesOfBeforeinputOnDeleteSomething({
startContainer: abc,
startOffset: 3,
endContainer: def,
endOffset: 3,
});
checkGetTargetRangesOfInput();
}, 'Backspace at "<p>abc </p><p> [] def</p>"');
// Invisible leading white-spaces in current block and invisible trailing
// white-spaces in the previous block should be deleted for avoiding they
// becoming visible when the blocks are joined. Perhaps, they should be
// contained by the range of `getTargetRanges()`, but needs discussion.
// https://github.com/w3c/input-events/issues/112
promise_test(async () => {
reset();
editor.innerHTML = "<p>abc </p><p> def</p>";
let p1 = editor.firstChild;
let abc = p1.firstChild;
let p2 = p1.nextSibling;
let def = p2.firstChild;
selection.collapse(def, 1);
await sendBackspaceKey();
assert_equals(editor.innerHTML, "<p>abcdef</p>");
checkGetTargetRangesOfBeforeinputOnDeleteSomething({
startContainer: abc,
startOffset: 3,
endContainer: def,
endOffset: 3,
});
checkGetTargetRangesOfInput();
}, 'Backspace at "<p>abc </p><p> [] def</p>"');
// Invisible leading white-spaces in current block and invisible trailing
// white-spaces in the previous block should be deleted for avoiding they
// becoming visible when the blocks are joined. Perhaps, they should be
// contained by the range of `getTargetRanges()`, but needs discussion.
// https://github.com/w3c/input-events/issues/112
promise_test(async () => {
reset();
editor.innerHTML = "<p>abc </p><p> def</p>";
let p1 = editor.firstChild;
let abc = p1.firstChild;
let p2 = p1.nextSibling;
let def = p2.firstChild;
selection.collapse(def, 0);
await sendBackspaceKey();
assert_equals(editor.innerHTML, "<p>abcdef</p>");
checkGetTargetRangesOfBeforeinputOnDeleteSomething({
startContainer: abc,
startOffset: 3,
endContainer: def,
endOffset: 3,
});
checkGetTargetRangesOfInput();
}, 'Backspace at "<p>abc </p><p>[] def</p>"');
// Invisible leading white-spaces in current block and invisible trailing
// white-spaces in the previous block should be deleted for avoiding they
// becoming visible when the blocks are joined. Perhaps, they should be
// contained by the range of `getTargetRanges()`, but needs discussion.
// https://github.com/w3c/input-events/issues/112
promise_test(async () => {
reset();
editor.innerHTML = "<p>abc </p><p> def</p>";
let p1 = editor.firstChild;
let abc = p1.firstChild;
let p2 = p1.nextSibling;
let def = p2.firstChild;
selection.setBaseAndExtent(abc, 6, def, 0);
await sendBackspaceKey();
assert_equals(editor.innerHTML, "<p>abcdef</p>");
checkGetTargetRangesOfBeforeinputOnDeleteSomething({
startContainer: abc,
startOffset: 3,
endContainer: def,
endOffset: 3,
});
checkGetTargetRangesOfInput();
}, 'Backspace at "<p>abc [</p><p>] def</p>"');
// Invisible leading white-spaces in the current block should be deleted
// for avoiding they becoming visible when the blocks are joined, but
// preformatted trailing white-spaces in the first block shouldn't be
// deleted. Perhaps, the invisible white-spaces should be contained by
// the range of `getTargetRanges()`, but needs discussion.
// https://github.com/w3c/input-events/issues/112
promise_test(async () => {
reset();
editor.innerHTML = "<pre>abc </pre><p> def</p>";
let pre = editor.firstChild;
let abc = pre.firstChild;
let p = pre.nextSibling;
let def = p.firstChild;
selection.collapse(def, 3);
await sendBackspaceKey();
// https://github.com/w3c/input-events/issues/112
// Shouldn't make the invisible white-spaces visible.
assert_equals(editor.innerHTML, "<pre>abc def</pre>");
checkGetTargetRangesOfBeforeinputOnDeleteSomething({
startContainer: abc,
startOffset: 6,
endContainer: def,
endOffset: 3,
});
checkGetTargetRangesOfInput();
}, 'Backspace at "<pre>abc </pre><p> []def</p>"');
// Invisible leading/trailing white-spaces in the current block should be
// deleted for avoiding they becoming visible when the blocks are joined, but
// preformatted trailing white-spaces in the first block shouldn't be
// deleted. Perhaps, the invisible leading white-spaces should be contained
// by the range of `getTargetRanges()`, but needs discussion.
// And also not sure whether the trailing white-spaces should be contained
// by additional range of `getTargetRanges()` or not because of the
// implementation cost and runtime cost. Needs discuss.
// https://github.com/w3c/input-events/issues/112
promise_test(async () => {
reset();
editor.innerHTML = "<pre>abc </pre><p> def </p>";
let pre = editor.firstChild;
let abc = pre.firstChild;
let p = pre.nextSibling;
let def = p.firstChild;
selection.collapse(def, 3);
await sendBackspaceKey();
assert_equals(editor.innerHTML, "<pre>abc def </pre>");
checkGetTargetRangesOfBeforeinputOnDeleteSomething({
startContainer: abc,
startOffset: 6,
endContainer: def,
endOffset: 3,
});
checkGetTargetRangesOfInput();
}, 'Backspace at "<pre>abc </pre><p> []def </p>"');
// Invisible trailing white-spaces in the first block should be deleted
// when the block is joined with the preformated following block, but
// the leading white-spaces in the preformatted block shouldn't be
// removed. So, in this case, the invisible trailing white-spaces should
// be in the range of `getTargetRanges()`, but not so for the preformatted
// visible leading white-spaces. But needs discussion.
// https://github.com/w3c/input-events/issues/112
promise_test(async () => {
reset();
editor.innerHTML = "<p>abc </p><pre> def</pre>";
let p = editor.firstChild;
let abc = p.firstChild;
let pre = p.nextSibling;
let def = pre.firstChild;
selection.collapse(def, 0);
await sendBackspaceKey();
assert_in_array(editor.innerHTML, ["<p>abc &nbsp; def</p>",
"<p>abc&nbsp;&nbsp; def</p>",
"<p>abc&nbsp; &nbsp;def</p>",
"<p>abc &nbsp;&nbsp;def</p>"]);
checkGetTargetRangesOfBeforeinputOnDeleteSomething({
startContainer: abc,
startOffset: 6,
endContainer: def,
endOffset: 0,
});
checkGetTargetRangesOfInput();
}, 'Backspace at "<p>abc </p><pre>[] def</pre>"');
// If the first block has invisible `<br>` element and joining it with
// the following block, the invisible trailing `<br>` element should be
// deleted and join the blocks. Therefore, the target range should contain
// the `<br>` element and block boundaries. But maybe needs discussion.
// https://github.com/w3c/input-events/issues/112
promise_test(async () => {
reset();
editor.innerHTML = "<p>abc<br></p><p>def</p>";
let p1 = editor.firstChild;
let abc = p1.firstChild;
let p2 = p1.nextSibling;
let def = p2.firstChild;
selection.collapse(def, 0);
await sendBackspaceKey();
assert_equals(editor.innerHTML, "<p>abcdef</p>");
checkGetTargetRangesOfBeforeinputOnDeleteSomething({
startContainer: p1,
startOffset: 1,
endContainer: def,
endOffset: 0,
});
checkGetTargetRangesOfInput();
}, 'Backspace at "<p>abc<br></p><p>[]def</p>"');
// If the first block has invisible `<br>` element for empty last line and
// joining it with the following block, the invisible trailing `<br>` element
// should be deleted and join the blocks. Therefore, the target range should
// contain the `<br>` element and block boundaries. But maybe needs discussion.
// https://github.com/w3c/input-events/issues/112
promise_test(async () => {
reset();
editor.innerHTML = "<p>abc<br><br></p><p>def</p>";
let p1 = editor.firstChild;
let abc = p1.firstChild;
let p2 = p1.nextSibling;
let def = p2.firstChild;
selection.collapse(def, 0);
await sendBackspaceKey();
assert_equals(editor.innerHTML, "<p>abc<br>def</p>");
checkGetTargetRangesOfBeforeinputOnDeleteSomething({
startContainer: p1,
startOffset: 2,
endContainer: def,
endOffset: 0,
});
checkGetTargetRangesOfInput();
}, 'Backspace at "<p>abc<br><br></p><p>[]def</p>"');
// Deleting visible `<br>` element should be contained by a range of
// `getTargetRanges()`.
promise_test(async () => {
reset();
editor.innerHTML = "<p>abc<br>def</p>";
let def = editor.querySelector("br").nextSibling;
selection.collapse(def, 0);
await sendBackspaceKey();
assert_equals(editor.innerHTML, "<p>abcdef</p>");
checkGetTargetRangesOfBeforeinputOnDeleteSomething({
startContainer: editor.firstChild,
startOffset: 1,
endContainer: def,
endOffset: 0,
});
checkGetTargetRangesOfInput();
}, 'Backspace at "<p>abc<br>[]def</p>"');
// Deleting visible `<br>` element should be contained by a range of
// `getTargetRanges()`. However, when only the `<br>` element is selected,
// the range shouldn't start from nor end by surrounding text nodes?
// https://github.com/w3c/input-events/issues/112
promise_test(async () => {
reset();
editor.innerHTML = "<p>abc<br>def</p>";
selection.setBaseAndExtent(editor.firstChild, 1, editor.firstChild, 2);
await sendBackspaceKey();
assert_equals(editor.innerHTML, "<p>abcdef</p>");
checkGetTargetRangesOfBeforeinputOnDeleteSomething({
startContainer: editor.firstChild,
startOffset: 1,
endContainer: editor.firstChild,
endOffset: 2,
});
checkGetTargetRangesOfInput();
}, 'Backspace at "<p>abc{<br>}def</p>"');
// Joining parent block and child block should remove invisible preceding
// white-spaces of the child block and invisible leading white-spaces in
// the child block, and they should be contained in a range of
// `getTargetRanges()`, but maybe needs discussion.
// https://github.com/w3c/input-events/issues/112
promise_test(async () => {
reset();
editor.innerHTML = "<div>abc <p> def<br>ghi</p></div>";
let p = editor.querySelector("p");
let def = p.firstChild;
let abc = editor.firstChild.firstChild;
selection.collapse(def, 3);
await sendBackspaceKey();
assert_equals(editor.innerHTML, "<div>abcdef<p>ghi</p></div>");
checkGetTargetRangesOfBeforeinputOnDeleteSomething({
startContainer: abc,
startOffset: 3,
endContainer: def,
endOffset: 3,
});
checkGetTargetRangesOfInput();
}, 'Backspace at "<div>abc <p> []def<br>ghi</p></div>"');
// Joining child block and parent block should remove invisible trailing
// white-spaces of the child block and invisible following white-spaces
// in the parent block, and they should be contained by a range of
// `getTaregetRanges()`, but maybe needs discussion.
// https://github.com/w3c/input-events/issues/112
promise_test(async () => {
reset();
editor.innerHTML = "<div><p>abc </p> def</div>";
let abc = editor.querySelector("p").firstChild;
let def = editor.querySelector("p").nextSibling;
selection.collapse(def, 3);
await sendBackspaceKey();
assert_equals(editor.innerHTML, "<div><p>abcdef</p></div>");
checkGetTargetRangesOfBeforeinputOnDeleteSomething({
startContainer: abc,
startOffset: 3,
endContainer: def,
endOffset: 3,
});
checkGetTargetRangesOfInput();
}, 'Backspace at "<div><p>abc </p> []def</div>"');
// The following tests check whether the range returned from
// `beforeinput[0].getTargetRanges()` is modified or different range is
// modified instead. I.e., they don't test which type of deletion should
// occur. Therefore, their result depends on browser's key bindings,
// system settings and running OS.
function getFirstDifferentOffset(currentString, originalString) {
for (let i = 0; i < currentString.length; i++) {
if (currentString.charAt(i) !== originalString.charAt(i) &&
(originalString.charAt(i) !== " " || !currentString.charAt("\u00A0"))) {
return i;
}
}
return currentString.length;
}
promise_test(async () => {
reset();
const kText = "abc def ghi";
editor.innerHTML = `<p>${kText}</p>`;
let p = editor.querySelector("p");
selection.collapse(p.firstChild, "abc def".length);
await sendBackspaceKey(kShift);
let startOffset = getFirstDifferentOffset(p.firstChild.data, kText);
let length = kText.length - p.firstChild.data.length;
assert_equals(editor.innerHTML.replace(/&nbsp;/g, " "),
`<p>${kText.substr(0, startOffset) + kText.substr(startOffset + length)}</p>`);
if (startOffset === kText.length) {
checkBeforeinputAndInputEventsOnNOOP();
return;
}
checkGetTargetRangesOfBeforeinputOnDeleteSomething({
startContainer: p.firstChild,
startOffset: startOffset,
endContainer: p.firstChild,
endOffset: startOffset + length,
});
checkGetTargetRangesOfInput();
}, 'Shift + Backspace at "<p>abc def[] ghi</p>"');
promise_test(async () => {
reset();
const kText = "abc def ghi";
editor.innerHTML = `<p>${kText}</p>`;
let p = editor.querySelector("p");
selection.collapse(p.firstChild, "abc def".length);
await sendBackspaceKey(kControl);
let startOffset = getFirstDifferentOffset(p.firstChild.data, kText);
let length = kText.length - p.firstChild.data.length;
assert_equals(editor.innerHTML.replace(/&nbsp;/g, " "),
`<p>${kText.substr(0, startOffset) + kText.substr(startOffset + length)}</p>`);
if (startOffset === kText.length) {
checkBeforeinputAndInputEventsOnNOOP();
return;
}
checkGetTargetRangesOfBeforeinputOnDeleteSomething({
startContainer: p.firstChild,
startOffset: startOffset,
endContainer: p.firstChild,
endOffset: startOffset + length,
});
checkGetTargetRangesOfInput();
}, 'Control + Backspace at "<p>abc def[] ghi</p>"');
promise_test(async () => {
reset();
const kText = "abc def ghi";
editor.innerHTML = `<p>${kText}</p>`;
let p = editor.querySelector("p");
selection.collapse(p.firstChild, "abc def".length);
await sendBackspaceKey(kAlt);
let startOffset = getFirstDifferentOffset(p.firstChild.data, kText);
let length = kText.length - p.firstChild.data.length;
assert_equals(editor.innerHTML.replace(/&nbsp;/g, " "),
`<p>${kText.substr(0, startOffset) + kText.substr(startOffset + length)}</p>`);
if (startOffset === kText.length) {
checkBeforeinputAndInputEventsOnNOOP();
return;
}
checkGetTargetRangesOfBeforeinputOnDeleteSomething({
startContainer: p.firstChild,
startOffset: startOffset,
endContainer: p.firstChild,
endOffset: startOffset + length,
});
checkGetTargetRangesOfInput();
}, 'Alt + Backspace at "<p>abc def[] ghi</p>"');
promise_test(async () => {
reset();
const kText = "abc def ghi";
editor.innerHTML = `<p>${kText}</p>`;
let p = editor.querySelector("p");
selection.collapse(p.firstChild, "abc def".length);
await sendBackspaceKey(kMeta);
let startOffset = getFirstDifferentOffset(p.firstChild.data, kText);
let length = kText.length - p.firstChild.data.length;
assert_equals(editor.innerHTML.replace(/&nbsp;/g, " "),
`<p>${kText.substr(0, startOffset) + kText.substr(startOffset + length)}</p>`);
if (startOffset === kText.length) {
checkBeforeinputAndInputEventsOnNOOP();
return;
}
checkGetTargetRangesOfBeforeinputOnDeleteSomething({
startContainer: p.firstChild,
startOffset: startOffset,
endContainer: p.firstChild,
endOffset: startOffset + length,
});
checkGetTargetRangesOfInput();
}, 'Meta + Backspace at "<p>abc def[] ghi</p>"');
promise_test(async () => {
reset();
const kText = "abc def";
editor.innerHTML = `<p> ${kText}</p>`;
let p = editor.querySelector("p");
selection.collapse(p.firstChild, "abc".length);
await sendBackspaceKey(kShift);
let visibleText = p.firstChild.data.replace(/^\s+/, "");
let invisibleWhiteSpaces = " ".repeat(p.firstChild.data.length - visibleText.length);
let startOffset = invisibleWhiteSpaces.length + getFirstDifferentOffset(visibleText, kText);
let length = kText.length + 3 - p.firstChild.data.length;
// If invisible white-spaces are deleted, they should be contained in the target range.
assert_equals(editor.innerHTML.replace(/&nbsp;/g, " "),
`<p>${invisibleWhiteSpaces + kText.substr(0, startOffset) + kText.substr(startOffset + length)}</p>`);
if (startOffset === kText.length) {
checkBeforeinputAndInputEventsOnNOOP();
return;
}
checkGetTargetRangesOfBeforeinputOnDeleteSomething({
startContainer: p.firstChild,
startOffset: startOffset,
endContainer: p.firstChild,
endOffset: startOffset + length,
});
checkGetTargetRangesOfInput();
}, 'Shift + Backspace at "<p> abc[] def</p>"');
promise_test(async () => {
reset();
const kText = "abc def";
editor.innerHTML = `<p> ${kText}</p>`;
let p = editor.querySelector("p");
selection.collapse(p.firstChild, "abc".length);
await sendBackspaceKey(kControl);
let visibleText = p.firstChild.data.replace(/^\s+/, "");
let invisibleWhiteSpaces = " ".repeat(p.firstChild.data.length - visibleText.length);
let startOffset = invisibleWhiteSpaces.length + getFirstDifferentOffset(visibleText, kText);
let length = kText.length + 3 - p.firstChild.data.length;
// If invisible white-spaces are deleted, they should be contained in the target range.
assert_equals(editor.innerHTML.replace(/&nbsp;/g, " "),
`<p>${invisibleWhiteSpaces + kText.substr(0, startOffset) + kText.substr(startOffset + length)}</p>`);
if (startOffset === kText.length) {
checkBeforeinputAndInputEventsOnNOOP();
return;
}
checkGetTargetRangesOfBeforeinputOnDeleteSomething({
startContainer: p.firstChild,
startOffset: startOffset,
endContainer: p.firstChild,
endOffset: startOffset + length,
});
checkGetTargetRangesOfInput();
}, 'Control + Backspace at "<p> abc[] def</p>"');
promise_test(async () => {
reset();
const kText = "abc def";
editor.innerHTML = `<p> ${kText}</p>`;
let p = editor.querySelector("p");
selection.collapse(p.firstChild, "abc".length);
await sendBackspaceKey(kAlt);
let visibleText = p.firstChild.data.replace(/^\s+/, "");
let invisibleWhiteSpaces = " ".repeat(p.firstChild.data.length - visibleText.length);
let startOffset = invisibleWhiteSpaces.length + getFirstDifferentOffset(visibleText, kText);
let length = kText.length + 3 - p.firstChild.data.length;
// If invisible white-spaces are deleted, they should be contained in the target range.
assert_equals(editor.innerHTML.replace(/&nbsp;/g, " "),
`<p>${invisibleWhiteSpaces + kText.substr(0, startOffset) + kText.substr(startOffset + length)}</p>`);
if (startOffset === kText.length) {
checkBeforeinputAndInputEventsOnNOOP();
return;
}
checkGetTargetRangesOfBeforeinputOnDeleteSomething({
startContainer: p.firstChild,
startOffset: startOffset,
endContainer: p.firstChild,
endOffset: startOffset + length,
});
checkGetTargetRangesOfInput();
}, 'Alt + Backspace at "<p> abc[] def</p>"');
promise_test(async () => {
reset();
const kText = "abc def";
editor.innerHTML = `<p> ${kText}</p>`;
let p = editor.querySelector("p");
selection.collapse(p.firstChild, "abc".length);
await sendBackspaceKey(kMeta);
let visibleText = p.firstChild.data.replace(/^\s+/, "");
let invisibleWhiteSpaces = " ".repeat(p.firstChild.data.length - visibleText.length);
let startOffset = invisibleWhiteSpaces.length + getFirstDifferentOffset(visibleText, kText);
let length = kText.length + 3 - p.firstChild.data.length;
// If invisible white-spaces are deleted, they should be contained in the target range.
assert_equals(editor.innerHTML.replace(/&nbsp;/g, " "),
`<p>${invisibleWhiteSpaces + kText.substr(0, startOffset) + kText.substr(startOffset + length)}</p>`);
if (startOffset === kText.length) {
checkBeforeinputAndInputEventsOnNOOP();
return;
}
checkGetTargetRangesOfBeforeinputOnDeleteSomething({
startContainer: p.firstChild,
startOffset: startOffset,
endContainer: p.firstChild,
endOffset: startOffset + length,
});
checkGetTargetRangesOfInput();
}, 'Meta + Backspace at "<p> abc[] def</p>"');
</script>

View file

@ -0,0 +1,808 @@
<!DOCTYPE html>
<meta charset="utf-8">
<title>InputEvent.getTargetRanges() at Delete (forward delete)</title>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="/resources/testdriver.js"></script>
<script src="/resources/testdriver-vendor.js"></script>
<script src="/resources/testdriver-actions.js"></script>
<div contenteditable></div>
<script>
const kDeleteKey = "\uE017";
const kShift = "\uE008";
const kMeta = "\uE03d";
const kControl = "\uE009";
const kAlt = "\uE00A";
let selection = getSelection();
let editor = document.querySelector("div[contenteditable]");
let beforeinput = [];
let input = [];
editor.addEventListener("beforeinput", (e) => {
// NOTE: Blink makes `getTargetRanges()` return empty range after propagaion,
// but this test wants to check the result during propagation.
// Therefore, we need to cache the result, but will assert if
// `getTargetRanges()` returns different ranges after checking the
// cached ranges.
e.cachedRanges = e.getTargetRanges();
beforeinput.push(e);
});
editor.addEventListener("input", (e) => {
e.cachedRanges = e.getTargetRanges();
input.push(e);
});
function reset() {
editor.focus();
beforeinput = [];
input = [];
}
function getRangeDescription(range) {
function getNodeDescription(node) {
if (!node) {
return "null";
}
switch (node.nodeType) {
case Node.TEXT_NODE:
case Node.COMMENT_NODE:
case Node.CDATA_SECTION_NODE:
return `${node.nodeName} "${node.data}"`;
case Node.ELEMENT_NODE:
return `<${node.nodeName.toLowerCase()}>`;
default:
return `${node.nodeName}`;
}
}
if (range === null) {
return "null";
}
if (range === undefined) {
return "undefined";
}
return range.startContainer == range.endContainer && range.startOffset == range.endOffset
? `(${getNodeDescription(range.startContainer)}, ${range.startOffset})`
: `(${getNodeDescription(range.startContainer)}, ${range.startOffset}) - (${getNodeDescription(range.endContainer)}, ${range.endOffset})`;
}
function getArrayOfRangesDescription(arrayOfRanges) {
if (arrayOfRanges === null) {
return "null";
}
if (arrayOfRanges === undefined) {
return "undefined";
}
if (!Array.isArray(arrayOfRanges)) {
return "Unknown Object";
}
if (arrayOfRanges.length === 0) {
return "[]";
}
let result = "[";
for (let range of arrayOfRanges) {
result += `{${getRangeDescription(range)}},`;
}
result += "]";
return result;
}
function sendDeleteKey(modifier) {
if (!modifier) {
return new test_driver.Actions()
.keyDown(kDeleteKey)
.keyUp(kDeleteKey)
.send();
}
return new test_driver.Actions()
.keyDown(modifier)
.keyDown(kDeleteKey)
.keyUp(kDeleteKey)
.keyUp(modifier)
.send();
}
function checkGetTargetRangesKeepReturningSameValue(event) {
// https://github.com/w3c/input-events/issues/114
assert_equals(getArrayOfRangesDescription(event.getTargetRanges()),
getArrayOfRangesDescription(event.cachedRanges),
`${event.type}.getTargetRanges() should keep returning the same array of ranges even after its propagation finished`);
}
function checkGetTargetRangesOfBeforeinputOnDeleteSomething(expectedRange) {
assert_equals(beforeinput.length, 1,
"One beforeinput event should be fired if the key operation deletes something");
assert_true(Array.isArray(beforeinput[0].cachedRanges),
"beforeinput[0].getTargetRanges() should return an array of StaticRange instances during propagation");
// Before checking the length of array of ranges, we should check the first
// range first because the first range data is more important than whether
// there are additional unexpected ranges.
if (beforeinput[0].cachedRanges.length > 0) {
assert_equals(
getRangeDescription(beforeinput[0].cachedRanges[0]),
getRangeDescription(expectedRange),
`beforeinput.getTargetRanges() should return expected range (inputType is "${beforeinput[0].inputType}")`);
assert_equals(beforeinput[0].cachedRanges.length, 1,
"beforeinput.getTargetRanges() should return one range within an array");
}
assert_equals(beforeinput[0].cachedRanges, 1,
"One range should be returned from getTargetRanges() when the key operation deletes something");
checkGetTargetRangesKeepReturningSameValue(beforeinput[0]);
}
function checkGetTargetRangesOfInputOnDeleteSomething() {
assert_equals(input.length, 1,
"One input event should be fired if the key operation deletes something");
// https://github.com/w3c/input-events/issues/113
assert_true(Array.isArray(input[0].cachedRanges),
"input[0].getTargetRanges() should return an array of StaticRange instances during propagation");
assert_equals(input[0].cachedRanges.length, 0,
"input[0].getTargetRanges() should return empty array during propagation");
checkGetTargetRangesKeepReturningSameValue(input[0]);
}
function checkBeforeinputAndInputEventsOnNOOP() {
assert_equals(beforeinput.length, 0,
"beforeinput event shouldn't be fired when the key operation does not cause modifying the DOM tree");
assert_equals(input.length, 0,
"input event shouldn't be fired when the key operation does not cause modifying the DOM tree");
}
// Simply deletes the next ASCII character of caret position.
promise_test(async () => {
reset();
editor.innerHTML = "<p>abc</p>";
selection.collapse(editor.firstChild.firstChild, 2);
await sendDeleteKey();
assert_equals(editor.innerHTML, "<p>ab</p>");
checkGetTargetRangesOfBeforeinputOnDeleteSomething({
startContainer: editor.firstChild.firstChild,
startOffset: 2,
endContainer: editor.firstChild.firstChild,
endOffset: 3,
});
checkGetTargetRangesOfInputOnDeleteSomething();
}, 'Delete at "<p>ab[]c</p>"');
// Simply deletes the next ASCII character of caret position.
promise_test(async () => {
reset();
editor.innerHTML = "<p>abc</p>";
selection.collapse(editor.firstChild.firstChild, 1);
await sendDeleteKey();
assert_equals(editor.innerHTML, "<p>ac</p>");
checkGetTargetRangesOfBeforeinputOnDeleteSomething({
startContainer: editor.firstChild.firstChild,
startOffset: 1,
endContainer: editor.firstChild.firstChild,
endOffset: 2,
});
checkGetTargetRangesOfInputOnDeleteSomething();
}, 'Delete at "<p>a[]bc</p>"');
// Simply deletes the next ASCII character of caret position.
promise_test(async () => {
reset();
editor.innerHTML = "<p>abc</p>";
selection.collapse(editor.firstChild.firstChild, 0);
await sendDeleteKey();
assert_equals(editor.innerHTML, "<p>bc</p>");
checkGetTargetRangesOfBeforeinputOnDeleteSomething({
startContainer: editor.firstChild.firstChild,
startOffset: 0,
endContainer: editor.firstChild.firstChild,
endOffset: 1,
});
checkGetTargetRangesOfInputOnDeleteSomething();
}, 'Delete at "<p>[]abc</p>"');
// Should delete the `<span>` element becase it becomes empty.
// However, we need discussion whether the `<span>` element should be
// contained by a range of `getTargetRanges()`.
// https://github.com/w3c/input-events/issues/112
promise_test(async () => {
reset();
editor.innerHTML = "<p>a<span>b</span>c</p>";
let a = editor.querySelector("span").previousSibling;
selection.collapse(a, 1);
await sendDeleteKey();
assert_equals(editor.innerHTML, "<p>ac</p>");
checkGetTargetRangesOfBeforeinputOnDeleteSomething({
startContainer: a,
startOffset: 1,
endContainer: editor.firstChild,
endOffset: 2,
});
checkGetTargetRangesOfInputOnDeleteSomething();
}, 'Delete at "<p>a[]<span>b</span>c</p>"');
// Should delete the `<span>` element becase it becomes empty.
// However, we need discussion whether the `<span>` element should be
// contained by a range of `getTargetRanges()`.
// https://github.com/w3c/input-events/issues/112
promise_test(async () => {
reset();
editor.innerHTML = "<p>a<span>b</span>c</p>";
let b = editor.querySelector("span").firstChild;
selection.collapse(b, 0);
await sendDeleteKey();
assert_equals(editor.innerHTML, "<p>ac</p>");
checkGetTargetRangesOfBeforeinputOnDeleteSomething({
startContainer: editor.firstChild,
startOffset: 1,
endContainer: editor.firstChild,
endOffset: 2,
});
checkGetTargetRangesOfInputOnDeleteSomething();
}, 'Delete at "<p>a<span>[]b</span>c</p>"');
// Invisible trailing white-space may be deleted when the last visible
// character is deleted. If it's deleted, it should be contained by
// the range of `getTargetRanges()`, but needs discussion.
// https://github.com/w3c/input-events/issues/112
promise_test(async () => {
reset();
editor.innerHTML = "<p>abc </p>";
selection.collapse(editor.firstChild.firstChild, 2);
await sendDeleteKey();
assert_in_array(editor.innerHTML, ["<p>ab</p>", "<p>ab </p>"]);
checkGetTargetRangesOfBeforeinputOnDeleteSomething({
startContainer: editor.firstChild.firstChild,
startOffset: 2,
endContainer: editor.firstChild.firstChild,
endOffset: editor.firstChild.firstChild.data.length == 2 ? 4 : 3,
});
checkGetTargetRangesOfInputOnDeleteSomething();
}, 'Delete at "<p>ab[]c </p>"');
// Invisible trailing white-spaces in current block and invisible leading
// white-spaces in the following block should be deleted for avoiding they
// becoming visible when the blocks are joined. Perhaps, they should be
// contained by the range of `getTargetRanges()`, but needs discussion.
// https://github.com/w3c/input-events/issues/112
promise_test(async () => {
reset();
editor.innerHTML = "<p>abc </p><p> def</p>";
let p1 = editor.firstChild;
let abc = p1.firstChild;
let p2 = p1.nextSibling;
let def = p2.firstChild;
selection.collapse(abc, 3);
await sendDeleteKey();
assert_equals(editor.innerHTML, "<p>abcdef</p>");
checkGetTargetRangesOfBeforeinputOnDeleteSomething({
startContainer: abc,
startOffset: 3,
endContainer: def,
endOffset: 3,
});
checkGetTargetRangesOfInputOnDeleteSomething();
}, 'Delete at "<p>abc[] </p><p> def</p>"');
// Invisible trailing white-spaces in current block and invisible leading
// white-spaces in the following block should be deleted for avoiding they
// becoming visible when the blocks are joined. Perhaps, they should be
// contained by the range of `getTargetRanges()`, but needs discussion.
// https://github.com/w3c/input-events/issues/112
promise_test(async () => {
reset();
editor.innerHTML = "<p>abc </p><p> def</p>";
let p1 = editor.firstChild;
let abc = p1.firstChild;
let p2 = p1.nextSibling;
let def = p2.firstChild;
selection.collapse(abc, 4);
await sendDeleteKey();
assert_equals(editor.innerHTML, "<p>abcdef</p>");
checkGetTargetRangesOfBeforeinputOnDeleteSomething({
startContainer: abc,
startOffset: 3,
endContainer: def,
endOffset: 3,
});
checkGetTargetRangesOfInputOnDeleteSomething();
}, 'Delete at "<p>abc [] </p><p> def</p>"');
// Invisible trailing white-spaces in current block and invisible leading
// white-spaces in the following block should be deleted for avoiding they
// becoming visible when the blocks are joined. Perhaps, they should be
// contained by the range of `getTargetRanges()`, but needs discussion.
// https://github.com/w3c/input-events/issues/112
promise_test(async () => {
reset();
editor.innerHTML = "<p>abc </p><p> def</p>";
let p1 = editor.firstChild;
let abc = p1.firstChild;
let p2 = p1.nextSibling;
let def = p2.firstChild;
selection.collapse(abc, 5);
await sendDeleteKey();
assert_equals(editor.innerHTML, "<p>abcdef</p>");
checkGetTargetRangesOfBeforeinputOnDeleteSomething({
startContainer: abc,
startOffset: 3,
endContainer: def,
endOffset: 3,
});
checkGetTargetRangesOfInputOnDeleteSomething();
}, 'Delete at "<p>abc [] </p><p> def</p>"');
// Invisible trailing white-spaces in current block and invisible leading
// white-spaces in the following block should be deleted for avoiding they
// becoming visible when the blocks are joined. Perhaps, they should be
// contained by the range of `getTargetRanges()`, but needs discussion.
// https://github.com/w3c/input-events/issues/112
promise_test(async () => {
reset();
editor.innerHTML = "<p>abc </p><p> def</p>";
let p1 = editor.firstChild;
let abc = p1.firstChild;
let p2 = p1.nextSibling;
let def = p2.firstChild;
selection.collapse(abc, 6);
await sendDeleteKey();
assert_equals(editor.innerHTML, "<p>abcdef</p>");
checkGetTargetRangesOfBeforeinputOnDeleteSomething({
startContainer: abc,
startOffset: 3,
endContainer: def,
endOffset: 3,
});
checkGetTargetRangesOfInputOnDeleteSomething();
}, 'Delete at "<p>abc []</p><p> def</p>"');
// Invisible trailing white-spaces in current block and invisible leading
// white-spaces in the following block should be deleted for avoiding they
// becoming visible when the blocks are joined. Perhaps, they should be
// contained by the range of `getTargetRanges()`, but needs discussion.
promise_test(async () => {
reset();
editor.innerHTML = "<p>abc </p><p> def</p>";
let p1 = editor.firstChild;
let abc = p1.firstChild;
let p2 = p1.nextSibling;
let def = p2.firstChild;
selection.setBaseAndExtent(abc, 6, def, 0);
await sendDeleteKey();
assert_equals(editor.innerHTML, "<p>abcdef</p>");
checkGetTargetRangesOfBeforeinputOnDeleteSomething({
startContainer: abc,
startOffset: 3,
endContainer: def,
endOffset: 3,
});
checkGetTargetRangesOfInputOnDeleteSomething();
}, 'Delete at "<p>abc [</p><p>] def</p>"');
// Invisible leading white-spaces in the following block should be deleted
// for avoiding they becoming visible when the blocks are joined, but
// preformatted trailing white-spaces in the first block shouldn't be
// deleted. Perhaps, the invisible white-spaces should be contained by
// the range of `getTargetRanges()`, but needs discussion.
// https://github.com/w3c/input-events/issues/112
promise_test(async () => {
reset();
editor.innerHTML = "<pre>abc </pre><p> def</p>";
let pre = editor.firstChild;
let abc = pre.firstChild;
let p = pre.nextSibling;
let def = p.firstChild;
selection.collapse(abc, 6);
await sendDeleteKey();
assert_equals(editor.innerHTML, "<pre>abc def</pre>");
checkGetTargetRangesOfBeforeinputOnDeleteSomething({
startContainer: abc,
startOffset: 6,
endContainer: def,
endOffset: 3,
});
checkGetTargetRangesOfInputOnDeleteSomething();
}, 'Delete at "<pre>abc []</pre><p> def</p>"');
// Invisible leading/trailing white-spaces in the following block should be
// deleted for avoiding they becoming visible when the blocks are joined, but
// preformatted trailing white-spaces in the first block shouldn't be
// deleted. Perhaps, the invisible leading white-spaces should be contained
// by the range of `getTargetRanges()`, but needs discussion.
// And also not sure whether the trailing white-spaces should be contained
// by additional range of `getTargetRanges()` or not because of the
// implementation cost and runtime cost. Needs discuss.
// https://github.com/w3c/input-events/issues/112
promise_test(async () => {
reset();
editor.innerHTML = "<pre>abc </pre><p> def</p>";
let pre = editor.firstChild;
let abc = pre.firstChild;
let p = pre.nextSibling;
let def = p.firstChild;
selection.collapse(abc, 6);
await sendDeleteKey();
assert_equals(editor.innerHTML, "<pre>abc def</pre>");
checkGetTargetRangesOfBeforeinputOnDeleteSomething({
startContainer: abc,
startOffset: 6,
endContainer: def,
endOffset: 3,
});
checkGetTargetRangesOfInputOnDeleteSomething();
}, 'Delete at "<pre>abc []</pre><p> def </p>"');
// Invisible trailing white-spaces in the first block should be deleted
// when the block is joined with the preformated following block, but
// the leading white-spaces in the preformatted block shouldn't be
// removed. So, in this case, the invisible trailing white-spaces should
// be in the range of `getTargetRanges()`, but not so for the preformatted
// visible leading white-spaces. But needs discussion.
// https://github.com/w3c/input-events/issues/112
promise_test(async () => {
reset();
editor.innerHTML = "<p>abc </p><pre> def</pre>";
let p = editor.firstChild;
let abc = p.firstChild;
let pre = p.nextSibling;
let def = pre.firstChild;
selection.collapse(abc, 3);
await sendDeleteKey();
assert_in_array(editor.innerHTML, ["<p>abc &nbsp; def</p>",
"<p>abc&nbsp;&nbsp; def</p>",
"<p>abc&nbsp; &nbsp;def</p>",
"<p>abc &nbsp;&nbsp;def</p>"]);
checkGetTargetRangesOfBeforeinputOnDeleteSomething({
startContainer: abc,
startOffset: 3,
endContainer: def,
endOffset: 0,
});
checkGetTargetRangesOfInputOnDeleteSomething();
}, 'Delete at "<p>abc[] </p><pre> def</pre>"');
// Deleting from before invisible trailing `<br>` element of a block
// should delete the `<br>` element and join the blocks. Therefore,
// the target range should contain the `<br>` element and block boundaries.
// But maybe needs discussion.
// https://github.com/w3c/input-events/issues/112
promise_test(async () => {
reset();
editor.innerHTML = "<p>abc<br></p><p>def</p>";
let p1 = editor.firstChild;
let abc = p1.firstChild;
let p2 = p1.nextSibling;
let def = p2.firstChild;
selection.collapse(abc, 3);
await sendDeleteKey();
assert_equals(editor.innerHTML, "<p>abcdef</p>");
checkGetTargetRangesOfBeforeinputOnDeleteSomething({
startContainer: abc,
startOffset: 3,
endContainer: def,
endOffset: 0,
});
checkGetTargetRangesOfInputOnDeleteSomething();
}, 'Delete at "<p>abc[]<br></p><p>def</p>"');
// Deleting from last empty line in the first block should delete the
// invisible `<br>` element for the last empty line and join the blocks.
// In this case, the invisible `<br>` element should be contained in the
// range of `getTargetRanges()`, but needs discussion.
// https://github.com/w3c/input-events/issues/112
promise_test(async () => {
reset();
editor.innerHTML = "<p>abc<br><br></p><p>def</p>";
let p1 = editor.firstChild;
let abc = p1.firstChild;
let p2 = p1.nextSibling;
let def = p2.firstChild;
selection.collapse(p1, 2);
await sendDeleteKey();
assert_equals(editor.innerHTML, "<p>abc<br>def</p>");
checkGetTargetRangesOfBeforeinputOnDeleteSomething({
startContainer: p1,
startOffset: 2,
endContainer: def,
endOffset: 0,
});
checkGetTargetRangesOfInputOnDeleteSomething();
}, 'Delete at "<p>abc<br>{}<br></p><p>def</p>"');
// Deleting visible `<br>` element should be contained by a range of
// `getTargetRanges()`.
promise_test(async () => {
reset();
editor.innerHTML = "<p>abc<br>def</p>";
let abc = editor.firstChild.firstChild;
selection.collapse(abc, 3);
await sendDeleteKey();
assert_equals(editor.innerHTML, "<p>abcdef</p>");
checkGetTargetRangesOfBeforeinputOnDeleteSomething({
startContainer: abc,
startOffset: 3,
endContainer: editor.querySelector("p"),
endOffset: 2,
});
checkGetTargetRangesOfInputOnDeleteSomething();
}, 'Delete at "<p>abc[]<br>def</p>"');
// Deleting visible `<br>` element should be contained by a range of
// `getTargetRanges()`. However, when only the `<br>` element is selected,
// the range shouldn't start from nor end by surrounding text nodes?
// https://github.com/w3c/input-events/issues/112
promise_test(async () => {
reset();
editor.innerHTML = "<p>abc<br>def</p>";
selection.setBaseAndExtent(editor.firstChild, 1, editor.firstChild, 2);
await sendDeleteKey();
assert_equals(editor.innerHTML, "<p>abcdef</p>");
checkGetTargetRangesOfBeforeinputOnDeleteSomething({
startContainer: editor.firstChild,
startOffset: 1,
endContainer: editor.firstChild,
endOffset: 2,
});
checkGetTargetRangesOfInputOnDeleteSomething();
}, 'Delete at "<p>abc{<br>}def</p>"');
// Joining parent block and child block should remove invisible preceding
// white-spaces of the child block and invisible leading white-spaces in
// the child block, and they should be contained in a range of
// `getTargetRanges()`, but maybe needs discussion.
// https://github.com/w3c/input-events/issues/112
promise_test(async () => {
reset();
editor.innerHTML = "<div>abc <p> def<br>ghi</p></div>";
let p = editor.querySelector("p");
let def = p.firstChild;
let abc = editor.firstChild.firstChild;
selection.collapse(abc, 3);
await sendDeleteKey();
assert_equals(editor.innerHTML, "<div>abcdef<p>ghi</p></div>");
checkGetTargetRangesOfBeforeinputOnDeleteSomething({
startContainer: abc,
startOffset: 3,
endContainer: def,
endOffset: 3,
});
checkGetTargetRangesOfInputOnDeleteSomething();
}, 'Delete at "<div>abc[] <p> def<br>ghi</p></div>"');
// Joining child block and parent block should remove invisible trailing
// white-spaces of the child block and invisible following white-spaces
// in the parent block, and they should be contained by a range of
// `getTaregetRanges()`, but maybe needs discussion.
// https://github.com/w3c/input-events/issues/112
promise_test(async () => {
reset();
editor.innerHTML = "<div><p>abc </p> def</div>";
let abc = editor.querySelector("p").firstChild;
let def = editor.querySelector("p").nextSibling;
selection.collapse(abc, 3);
await sendDeleteKey();
assert_equals(editor.innerHTML, "<div><p>abcdef</p></div>");
checkGetTargetRangesOfBeforeinputOnDeleteSomething({
startContainer: abc,
startOffset: 3,
endContainer: def,
endOffset: 3,
});
checkGetTargetRangesOfInputOnDeleteSomething();
}, 'Delete at "<div><p>abc[] </p> def</div>"');
// The following tests check whether the range returned from
// `beforeinput[0].getTargetRanges()` is modified or different range is
// modified instead. I.e., they don't test which type of deletion should
// occur. Therefore, their result depends on browser's key bindings,
// system settings and running OS.
function getFirstDifferentOffset(currentString, originalString) {
for (let i = 0; i < currentString.length; i++) {
if (currentString.charAt(i) !== originalString.charAt(i) &&
(originalString.charAt(i) !== " " || !currentString.charAt("\u00A0"))) {
return i;
}
}
return currentString.length;
}
promise_test(async () => {
reset();
const kText = "abc def ghi";
editor.innerHTML = `<p>${kText}</p>`;
let p = editor.querySelector("p");
selection.collapse(p.firstChild, "abc ".length);
await sendDeleteKey(kShift);
let startOffset = getFirstDifferentOffset(p.firstChild.data, kText);
let length = kText.length - p.firstChild.data.length;
assert_equals(editor.innerHTML.replace(/&nbsp;/g, " "),
`<p>${kText.substr(0, startOffset) + kText.substr(startOffset + length)}</p>`);
if (startOffset === kText.length) {
checkBeforeinputAndInputEventsOnNOOP();
return;
}
checkGetTargetRangesOfBeforeinputOnDeleteSomething({
startContainer: p.firstChild,
startOffset: startOffset,
endContainer: p.firstChild,
endOffset: startOffset + length,
});
checkGetTargetRangesOfInputOnDeleteSomething();
}, 'Shift + Delete at "<p>abc []def ghi</p>"');
promise_test(async () => {
reset();
const kText = "abc def ghi";
editor.innerHTML = `<p>${kText}</p>`;
let p = editor.querySelector("p");
selection.collapse(p.firstChild, "abc ".length);
await sendDeleteKey(kControl);
let startOffset = getFirstDifferentOffset(p.firstChild.data, kText);
let length = kText.length - p.firstChild.data.length;
assert_equals(editor.innerHTML.replace(/&nbsp;/g, " "),
`<p>${kText.substr(0, startOffset) + kText.substr(startOffset + length)}</p>`);
if (startOffset === kText.length) {
checkBeforeinputAndInputEventsOnNOOP();
return;
}
checkGetTargetRangesOfBeforeinputOnDeleteSomething({
startContainer: p.firstChild,
startOffset: startOffset,
endContainer: p.firstChild,
endOffset: startOffset + length,
});
checkGetTargetRangesOfInputOnDeleteSomething();
}, 'Control + Delete at "<p>abc []def ghi</p>"');
promise_test(async () => {
reset();
const kText = "abc def ghi";
editor.innerHTML = `<p>${kText}</p>`;
let p = editor.querySelector("p");
selection.collapse(p.firstChild, "abc ".length);
await sendDeleteKey(kAlt);
let startOffset = getFirstDifferentOffset(p.firstChild.data, kText);
let length = kText.length - p.firstChild.data.length;
assert_equals(editor.innerHTML.replace(/&nbsp;/g, " "),
`<p>${kText.substr(0, startOffset) + kText.substr(startOffset + length)}</p>`);
if (startOffset === kText.length) {
checkBeforeinputAndInputEventsOnNOOP();
return;
}
checkGetTargetRangesOfBeforeinputOnDeleteSomething({
startContainer: p.firstChild,
startOffset: startOffset,
endContainer: p.firstChild,
endOffset: startOffset + length,
});
checkGetTargetRangesOfInputOnDeleteSomething();
}, 'Alt + Delete at "<p>abc []def ghi</p>"');
promise_test(async () => {
reset();
const kText = "abc def ghi";
editor.innerHTML = `<p>${kText}</p>`;
let p = editor.querySelector("p");
selection.collapse(p.firstChild, "abc ".length);
await sendDeleteKey(kMeta);
let startOffset = getFirstDifferentOffset(p.firstChild.data, kText);
let length = kText.length - p.firstChild.data.length;
assert_equals(editor.innerHTML.replace(/&nbsp;/g, " "),
`<p>${kText.substr(0, startOffset) + kText.substr(startOffset + length)}</p>`);
if (startOffset === kText.length) {
checkBeforeinputAndInputEventsOnNOOP();
return;
}
checkGetTargetRangesOfBeforeinputOnDeleteSomething({
startContainer: p.firstChild,
startOffset: startOffset,
endContainer: p.firstChild,
endOffset: startOffset + length,
});
checkGetTargetRangesOfInputOnDeleteSomething();
}, 'Meta + Delete at "<p>abc []def ghi</p>"');
promise_test(async () => {
reset();
const kText = "abc def";
editor.innerHTML = `<p>${kText} </p>`;
let p = editor.querySelector("p");
selection.collapse(p.firstChild, "abc ".length);
await sendDeleteKey(kShift);
let visibleText = p.firstChild.data.replace(/%s+$/, "");
let invisibleWhiteSpaces = " ".repeat(p.firstChild.data.length - visibleText.length);
let startOffset = invisibleWhiteSpaces.length + getFirstDifferentOffset(visibleText, kText);
let length = kText.length + 3 - p.firstChild.data.length;
// If invisible white-spaces are deleted, they should be contained in the target range.
assert_equals(editor.innerHTML.replace(/&nbsp;/g, " "),
`<p>${kText.substr(0, startOffset) + kText.substr(startOffset + length) + invisibleWhiteSpaces}</p>`);
if (startOffset === kText.length) {
checkBeforeinputAndInputEventsOnNOOP();
return;
}
checkGetTargetRangesOfBeforeinputOnDeleteSomething({
startContainer: p.firstChild,
startOffset: startOffset,
endContainer: p.firstChild,
endOffset: startOffset + length,
});
checkGetTargetRangesOfInputOnDeleteSomething();
}, 'Shift + Delete at "<p>abc []def </p>"');
promise_test(async () => {
reset();
const kText = "abc def";
editor.innerHTML = `<p>${kText} </p>`;
let p = editor.querySelector("p");
selection.collapse(p.firstChild, "abc ".length);
await sendDeleteKey(kControl);
let visibleText = p.firstChild.data.replace(/%s+$/, "");
let invisibleWhiteSpaces = " ".repeat(p.firstChild.data.length - visibleText.length);
let startOffset = invisibleWhiteSpaces.length + getFirstDifferentOffset(visibleText, kText);
let length = kText.length + 3 - p.firstChild.data.length;
// If invisible white-spaces are deleted, they should be contained in the target range.
assert_equals(editor.innerHTML.replace(/&nbsp;/g, " "),
`<p>${kText.substr(0, startOffset) + kText.substr(startOffset + length) + invisibleWhiteSpaces}</p>`);
if (startOffset === kText.length) {
checkBeforeinputAndInputEventsOnNOOP();
return;
}
checkGetTargetRangesOfBeforeinputOnDeleteSomething({
startContainer: p.firstChild,
startOffset: startOffset,
endContainer: p.firstChild,
endOffset: startOffset + length,
});
checkGetTargetRangesOfInputOnDeleteSomething();
}, 'Control + Delete at "<p>abc []def </p>"');
promise_test(async () => {
reset();
const kText = "abc def";
editor.innerHTML = `<p>${kText} </p>`;
let p = editor.querySelector("p");
selection.collapse(p.firstChild, "abc ".length);
await sendDeleteKey(kAlt);
let visibleText = p.firstChild.data.replace(/%s+$/, "");
let invisibleWhiteSpaces = " ".repeat(p.firstChild.data.length - visibleText.length);
let startOffset = invisibleWhiteSpaces.length + getFirstDifferentOffset(visibleText, kText);
let length = kText.length + 3 - p.firstChild.data.length;
// If invisible white-spaces are deleted, they should be contained in the target range.
assert_equals(editor.innerHTML.replace(/&nbsp;/g, " "),
`<p>${kText.substr(0, startOffset) + kText.substr(startOffset + length) + invisibleWhiteSpaces}</p>`);
if (startOffset === kText.length) {
checkBeforeinputAndInputEventsOnNOOP();
return;
}
checkGetTargetRangesOfBeforeinputOnDeleteSomething({
startContainer: p.firstChild,
startOffset: startOffset,
endContainer: p.firstChild,
endOffset: startOffset + length,
});
checkGetTargetRangesOfInputOnDeleteSomething();
}, 'Alt + Delete at "<p>abc []def </p>"');
promise_test(async () => {
reset();
const kText = "abc def";
editor.innerHTML = `<p>${kText} s</p>`;
let p = editor.querySelector("p");
selection.collapse(p.firstChild, "abc ".length);
await sendDeleteKey(kMeta);
let visibleText = p.firstChild.data.replace(/%s+$/, "");
let invisibleWhiteSpaces = " ".repeat(p.firstChild.data.length - visibleText.length);
let startOffset = invisibleWhiteSpaces.length + getFirstDifferentOffset(visibleText, kText);
let length = kText.length + 3 - p.firstChild.data.length;
// If invisible white-spaces are deleted, they should be contained in the target range.
assert_equals(editor.innerHTML.replace(/&nbsp;/g, " "),
`<p>${kText.substr(0, startOffset) + kText.substr(startOffset + length) + invisibleWhiteSpaces}</p>`);
if (startOffset === kText.length) {
checkBeforeinputAndInputEventsOnNOOP();
return;
}
checkGetTargetRangesOfBeforeinputOnDeleteSomething({
startContainer: p.firstChild,
startOffset: startOffset,
endContainer: p.firstChild,
endOffset: startOffset + length,
});
checkGetTargetRangesOfInputOnDeleteSomething();
}, 'Meta + Delete at "<p>abc []def</p>"');
</script>

View file

@ -8,7 +8,7 @@ partial interface Navigator {
};
dictionary ShareData {
FrozenArray<File> files;
sequence<File> files;
USVString title;
USVString text;
USVString url;

View file

@ -13,13 +13,17 @@
.displayNone {
display: none;
}
.composited {
.willChangeTransform {
will-change: transform;
}
.willChangeOpacity {
will-change: opacity;
}
</style>
<img src='/images/blue.png' class='opacity0 composited' id='opacity0'/>
<img src='/images/green.png' class='visibilityHidden composited' id='visibilityHidden'/>
<img src='/images/red.png' class='displayNone composited' id='displayNone'/>
<img src='/images/blue.png' class='opacity0 willChangeTransform' id='opacity0-willChangeTransform'/>
<img src='/images/green.png' class='visibilityHidden willChangeTransform' id='visibilityHidden'/>
<img src='/images/red.png' class='displayNone willChangeTransform' id='displayNone'/>
<img src='/images/blue.png' class='opacity0 willChangeOpacity' id='opacity0-willChangeOpacity'/>
<div class='opacity0 composited'><img src='/images/yellow.png' id='divOpacity0'/></div>
<div class='visibilityHidden composited'><img src='/images/yellow.png' id='divVisibilityHidden'/></div>
<div class='displayNone composited'><img src='/images/yellow.png' id='divDisplayNone'/></div>

View file

@ -1,44 +0,0 @@
<!DOCTYPE html>
<title>Layout Instability: simple block movement is detected</title>
<link rel="help" href="https://wicg.github.io/layout-instability/" />
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="resources/util.js"></script>
<body style="writing-mode: vertical-rl">
<div style="height: 200px; font-size: 20px; line-height: 25px">
1AAAAAAA<br>
2AAAAAAA<br>
3AAAAAAA<br>
<div id="inline-block" style="display: inline-block; width: 50px">4AAAAAAA</div><br>
5AAAAAAA<br>
6AAAAAAA<br>
7AAAAAAA<br>
</div>
<script>
promise_test(async () => {
const watcher = new ScoreWatcher;
// Wait for the initial render to complete.
await waitForAnimationFrames(2);
// Modify the position of the div.
const inline_block = document.querySelector("#inline-block");
inline_block.style.width = '100px';
// The lines below the inline-block are shifted down by 50px.
// The implementation may measure the real width of the shifted text
// or use the available width (i.e. width of the containing block).
// Also tolerate extra 10% error.
const text_width = inline_block.offsetWidth;
const expectedScoreMin = computeExpectedScore(text_width * (30 * 3 + 50), 50) * 0.9;
const expectedScoreMax = computeExpectedScore(200 * (30 * 3 + 50), 50) * 1.1;
// Observer fires after the frame is painted.
assert_equals(watcher.score, 0);
await watcher.promise;
assert_between_exclusive(watcher.score, expectedScoreMin, expectedScoreMax);
}, 'Inline flow movement.');
</script>
</body>

View file

@ -1,42 +0,0 @@
<!DOCTYPE html>
<title>Layout Instability: simple block movement is detected</title>
<link rel="help" href="https://wicg.github.io/layout-instability/" />
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="resources/util.js"></script>
<div style="width: 200px; font-size: 20px; line-height: 25px">
1AAAAAAA<br>
2AAAAAAA<br>
3AAAAAAA<br>
<div id="inline-block" style="display: inline-block; height: 50px">4AAAAAAA</div><br>
5AAAAAAA<br>
6AAAAAAA<br>
7AAAAAAA<br>
</div>
<script>
promise_test(async () => {
const watcher = new ScoreWatcher;
// Wait for the initial render to complete.
await waitForAnimationFrames(2);
// Modify the position of the div.
const inline_block = document.querySelector("#inline-block");
inline_block.style.height = '100px';
// The lines below the inline-block are shifted down by 50px.
// The implementation may measure the real width of the shifted text
// or use the available width (i.e. width of the containing block).
// Also tolerate extra 10% error.
const text_width = inline_block.offsetWidth;
const expectedScoreMin = computeExpectedScore(text_width * (30 * 3 + 50), 50) * 0.9;
const expectedScoreMax = computeExpectedScore(200 * (30 * 3 + 50), 50) * 1.1;
// Observer fires after the frame is painted.
assert_equals(watcher.score, 0);
await watcher.promise;
assert_between_exclusive(watcher.score, expectedScoreMin, expectedScoreMax);
}, 'Inline flow movement.');
</script>

View file

@ -1,21 +0,0 @@
<!DOCTYPE html>
<title>Layout Instability: outline doesn't contribute to layout shift</title>
<link rel="help" href="https://wicg.github.io/layout-instability/" />
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="resources/util.js"></script>
<div id="target" style="width: 300px; height: 300px"></div>
<script>
promise_test(async () => {
const watcher = new ScoreWatcher;
// Wait for the initial render to complete.
await waitForAnimationFrames(2);
// Add outline for target. This should not generate a shift.
target.style.outline = "10px solid blue";
await waitForAnimationFrames(3);
assert_equals(watcher.score, 0);
}, "Outline.");
</script>

View file

@ -84,6 +84,9 @@ def main(product, commit_range, wpt_args):
command = ["python", "./wpt", "run"] + wpt_args + [product]
logger.info("Executing command: %s" % " ".join(command))
with open("/home/test/artifacts/checkrun.md", "a") as f:
f.write("\n**WPT Command:** `%s`\n\n" % " ".join(command))
retcode = subprocess.call(command, env=dict(os.environ, TERM="dumb"))
if retcode != 0:
sys.exit(retcode)

View file

@ -241,6 +241,8 @@ def create_tc_task(event, task, taskgroup_id, depends_on_ids, env_extra=None):
},
"routes": ["checks"]
}
if "extra" in task:
task_data["extra"].update(task["extra"])
if env_extra:
task_data["payload"]["env"].update(env_extra)
if depends_on_ids:

View file

@ -0,0 +1,29 @@
class GitHubChecksOutputter(object):
"""Provides a method to output data to be shown in the GitHub Checks UI.
This can be useful to provide a summary of a given check (e.g. the lint)
to enable developers to quickly understand what has gone wrong. The output
supports markdown format.
See https://docs.taskcluster.net/docs/reference/integrations/github/checks#custom-text-output-in-checks
"""
def __init__(self, path):
self.path = path
def output(self, line):
with open(self.path, 'a') as f:
f.write(line)
f.write('\n')
__outputter = None
def get_gh_checks_outputter(kwargs):
"""Return the outputter for GitHub Checks output, if enabled.
:param kwargs: The arguments passed to the program (to look for the
--github_checks_text_file flag)
"""
global __outputter
if kwargs['github_checks_text_file'] and __outputter is None:
__outputter = GitHubChecksOutputter(kwargs['github_checks_text_file'])
return __outputter

View file

@ -10,6 +10,13 @@ components:
public/results:
path: /home/test/artifacts
type: directory
extra:
github:
customCheckRun:
# We set both textArtifactName and annotationsArtifactName due
# to https://github.com/taskcluster/taskcluster/issues/3191
textArtifactName: public/results/checkrun.md
annotationsArtifactName: public/results/checkrun.md
wpt-testharness:
chunks: 16
@ -296,6 +303,7 @@ tasks:
--channel=${vars.channel}
--verify
--verify-no-chaos-mode
--github-checks-text-file="/home/test/artifacts/checkrun.md"
- wpt-${vars.browser}-${vars.channel}-results:
use:

View file

@ -13,6 +13,7 @@ from mozlog.handlers import BaseHandler, StreamHandler, LogLevelFilter
here = os.path.dirname(__file__)
localpaths = imp.load_source("localpaths", os.path.abspath(os.path.join(here, os.pardir, os.pardir, "localpaths.py")))
from ci.tc.github_checks_output import get_gh_checks_outputter
from wpt.markdown import markdown_adjust, table
@ -178,8 +179,32 @@ def err_string(results_dict, iterations):
return rv
def write_github_checks_summary_inconsistent(log, inconsistent, iterations):
"""Outputs a summary of inconsistent tests for GitHub Checks."""
log("Some affected tests had inconsistent (flaky) results:\n")
write_inconsistent(log, inconsistent, iterations)
log("\n")
log("These may be pre-existing or new flakes. Please try to reproduce (see "
"the above WPT command, though some flags may not be needed when "
"running locally) and determine if your change introduced the flake. "
"If you are unable to reproduce the problem, please tag "
"`@web-platform-tests/wpt-core-team` in a comment for help.\n")
def write_github_checks_summary_slow_tests(log, slow):
"""Outputs a summary of slow tests for GitHub Checks."""
log("Some affected tests had slow results:\n")
write_slow_tests(log, slow)
log("\n")
log("These may be pre-existing or newly slow tests. Slow tests indicate "
"that a test ran very close to the test timeout limit and so may "
"become TIMEOUT-flaky in the future. Consider speeding up the test or "
"breaking it into multiple tests. For help, please tag "
"`@web-platform-tests/wpt-core-team` in a comment.\n")
def write_inconsistent(log, inconsistent, iterations):
"""Output inconsistent tests to logger.error."""
"""Output inconsistent tests to the passed in logging function."""
log("## Unstable results ##\n")
strings = [(
"`%s`" % markdown_adjust(test),
@ -191,6 +216,7 @@ def write_inconsistent(log, inconsistent, iterations):
def write_slow_tests(log, slow):
"""Output slow tests to the passed in logging function."""
log("## Slow tests ##\n")
strings = [(
"`%s`" % markdown_adjust(test),
@ -321,6 +347,8 @@ def check_stability(logger, repeat_loop=10, repeat_restart=5, chaos_mode=True, m
start_time = datetime.now()
step_results = []
github_checks_outputter = get_gh_checks_outputter(kwargs)
for desc, step_func in steps:
if max_time and datetime.now() - start_time > max_time:
logger.info("::: Test verification is taking too long: Giving up!")
@ -337,12 +365,16 @@ def check_stability(logger, repeat_loop=10, repeat_restart=5, chaos_mode=True, m
if inconsistent:
step_results.append((desc, "FAIL"))
if github_checks_outputter:
write_github_checks_summary_inconsistent(github_checks_outputter.output, inconsistent, iterations)
write_inconsistent(logger.info, inconsistent, iterations)
write_summary(logger, step_results, "FAIL")
return 1
if slow:
step_results.append((desc, "FAIL"))
if github_checks_outputter:
write_github_checks_summary_slow_tests(github_checks_outputter.output, slow)
write_slow_tests(logger.info, slow)
write_summary(logger, step_results, "FAIL")
return 1

View file

@ -348,6 +348,11 @@ scheme host and port.""")
help="Command-line argument to forward to the "
"Sauce Connect binary (repeatable)")
taskcluster_group = parser.add_argument_group("Taskcluster-specific")
taskcluster_group.add_argument("--github-checks-text-file",
dest="github_checks_text_file",
help="Path to GitHub checks output file")
webkit_group = parser.add_argument_group("WebKit-specific")
webkit_group.add_argument("--webkit-port", dest="webkit_port",
help="WebKit port")

View file

@ -0,0 +1,25 @@
test(function () {
assert_equals(typeof self.performance, "object");
assert_equals(typeof self.performance.getEntriesByType, "function");
self.performance.mark("mark1");
self.performance.measure("measure1");
const type = [
'mark',
'measure',
];
type.forEach(function(entryType) {
if (PerformanceObserver.supportedEntryTypes.includes(entryType)) {
const entryTypeUpperCased = entryType.toUpperCase();
const entryTypeCapitalized = entryType[0].toUpperCase() + entryType.substring(1);
const lowerList = self.performance.getEntriesByType(entryType);
const upperList = self.performance.getEntriesByType(entryTypeUpperCased);
const mixedList = self.performance.getEntriesByType(entryTypeCapitalized);
assert_greater_than(lowerList.length, 0, "Entries exist");
assert_equals(upperList.length, 0, "getEntriesByType('" + entryTypeCapitalized + "').length");
assert_equals(mixedList.length, 0, "getEntriesByType('" + entryTypeCapitalized + "').length");
}
});
}, "getEntriesByType values are case sensitive");

View file

@ -1,7 +1,7 @@
import uuid
import pytest
from six import text_type
from six import string_types
from tests.support.asserts import assert_success
@ -9,21 +9,21 @@ from tests.support.asserts import assert_success
def test_sessionid(new_session, add_browser_capabilities):
response, _ = new_session({"capabilities": {"alwaysMatch": add_browser_capabilities({})}})
value = assert_success(response)
assert isinstance(value["sessionId"], text_type)
assert isinstance(value["sessionId"], string_types)
uuid.UUID(hex=value["sessionId"])
@pytest.mark.parametrize("capability, type", [
("browserName", text_type),
("browserVersion", text_type),
("platformName", text_type),
("browserName", string_types),
("browserVersion", string_types),
("platformName", string_types),
("acceptInsecureCerts", bool),
("pageLoadStrategy", text_type),
("pageLoadStrategy", string_types),
("proxy", dict),
("setWindowRect", bool),
("timeouts", dict),
("strictFileInteractability", bool),
("unhandledPromptBehavior", text_type),
("unhandledPromptBehavior", string_types),
])
def test_capability_type(session, capability, type):
assert isinstance(session.capabilities, dict)

View file

@ -209,3 +209,12 @@
[WebGL test #44: attachment 7 should be 0,255,0,255\nat (0, 0) expected: 0,255,0,255 was 0,0,255,0]
expected: FAIL
[WebGL test #43: attachment 6 should be 0,255,0,255\nat (0, 0) expected: 0,255,0,255 was 0,0,0,255]
expected: FAIL
[WebGL test #44: attachment 7 should be 0,255,0,255\nat (0, 0) expected: 0,255,0,255 was 255,0,0,255]
expected: FAIL
[WebGL test #52: attachment 7 should be 0,255,0,255\nat (0, 0) expected: 0,255,0,255 was 255,0,0,255]
expected: FAIL