diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 00:47:55 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 00:47:55 +0000 |
commit | 26a029d407be480d791972afb5975cf62c9360a6 (patch) | |
tree | f435a8308119effd964b339f76abb83a57c29483 /testing/web-platform/mozilla/tests/dom | |
parent | Initial commit. (diff) | |
download | firefox-26a029d407be480d791972afb5975cf62c9360a6.tar.xz firefox-26a029d407be480d791972afb5975cf62c9360a6.zip |
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'testing/web-platform/mozilla/tests/dom')
20 files changed, 1176 insertions, 0 deletions
diff --git a/testing/web-platform/mozilla/tests/dom/classList.html b/testing/web-platform/mozilla/tests/dom/classList.html new file mode 100644 index 0000000000..21d79f49e3 --- /dev/null +++ b/testing/web-platform/mozilla/tests/dom/classList.html @@ -0,0 +1,526 @@ +<!doctype html> +<meta charset=utf-8> +<title>Test for the classList element attribute</title> +<script src=/resources/testharness.js></script> +<script src=/resources/testharnessreport.js></script> +<div id="content"></div> +<script> +// This should be the same as dom/nodes/Element-classlist.html in the upstream +// tests! We have a couple of bits changed, which are marked with comments +// "LOCAL MODIFICATION". Do not change this without changing the upstream test +// as well! Merging upstream changes here occasionally might also be nice. + +const SVG_NS = "http://www.w3.org/2000/svg"; +const XHTML_NS = "http://www.w3.org/1999/xhtml" +const MATHML_NS = "http://www.w3.org/1998/Math/MathML"; + +// BEGIN LOCAL MODIFICATION: The spec does not have the onAttrModified event +// and does not want it, but we still support it. +var gMutationEvents = []; + +function onAttrModified(event) { + assert_equals(event.attrName, "class", "mutation on unexpected attribute"); + + gMutationEvents.push({ + attrChange: event.attrChange, + prevValue: event.prevValue, + newValue: event.newValue, + }); +} +// END LOCAL MODIFICATION + +function setClass(e, newVal) { + if (newVal === null) { + e.removeAttribute("class"); + } else { + e.setAttribute("class", newVal); + } +} + +function checkModification(e, funcName, args, expectedRes, before, after, + expectedException, desc) { + if (!Array.isArray(args)) { + args = [args]; + } + + test(function() { + var shouldThrow = typeof(expectedException) === "string"; + if (shouldThrow) { + // If an exception is thrown, the class attribute shouldn't change. + after = before; + } + setClass(e, before); + + // BEGIN LOCAL MODIFICATION + gMutationEvents = []; + e.addEventListener("DOMAttrModified", onAttrModified); + // END LOCAL MODIFICATION + var obs; + // If we have MutationObservers available, do some checks to make + // sure attribute sets are happening at sane times. + if (self.MutationObserver) { + obs = new MutationObserver(() => {}); + obs.observe(e, { attributes: true }); + } + if (shouldThrow) { + assert_throws_dom(expectedException, function() { + var list = e.classList; + var res = list[funcName].apply(list, args); + }); + } else { + var list = e.classList; + var res = list[funcName].apply(list, args); + } + if (obs) { + var mutationRecords = obs.takeRecords(); + obs.disconnect(); + if (shouldThrow) { + assert_equals(mutationRecords.length, 0, + "There should have been no mutation"); + } else if (funcName == "replace") { + assert_equals(mutationRecords.length == 1, + expectedRes, + "Should have a mutation exactly when replace() returns true"); + } else { + // For other functions, would need to check when exactly + // mutations are supposed to happen. + } + } + // BEGIN LOCAL MODIFICATION + e.removeEventListener("DOMAttrModified", onAttrModified); + // END LOCAL MODIFICATION + if (!shouldThrow) { + assert_equals(res, expectedRes, "wrong return value"); + } + + var expectedAfter = after; + + assert_equals(e.getAttribute("class"), expectedAfter, + "wrong class after modification"); + // BEGIN LOCAL MODIFICATION + var expectedMutation = before != after; + assert_equals(gMutationEvents.length, expectedMutation ? 1 : 0, + "unexpected mutation event count"); + if (expectedMutation && gMutationEvents.length) { + assert_equals(gMutationEvents[0].attrChange, + before == null ? MutationEvent.ADDITION + : MutationEvent.MODIFICATION, + "wrong type of attribute change"); + // If there wasn't any previous attribute, prevValue will return an empty + // string. + var expectedPrevValue = before === null ? "" : before; + assert_equals(gMutationEvents[0].prevValue, expectedPrevValue, + "wrong previous value"); + assert_equals(gMutationEvents[0].newValue, after, "wrong new value"); + } + // END LOCAL MODIFICATION + }, "classList." + funcName + "(" + args.map(format_value).join(", ") + + ") with attribute value " + format_value(before) + desc); +} + +function assignToClassListStrict(e) { + "use strict"; + e.classList = "foo"; + e.removeAttribute("class"); +} + +function assignToClassList(e) { + var expect = e.classList; + e.classList = "foo"; + assert_equals(e.classList, expect, + "classList should be unchanged after assignment"); + e.removeAttribute("class"); +} + +function testClassList(e, desc) { + + // assignment + + test(function() { + assignToClassListStrict(e); + assignToClassList(e); + }, "Assigning to classList" + desc); + + // supports + test(function() { + assert_throws_js(TypeError, function() { + e.classList.supports("a"); + }) + }, ".supports() must throw TypeError" + desc); + + // length attribute + + function checkLength(value, length) { + test(function() { + setClass(e, value); + assert_equals(e.classList.length, length); + }, "classList.length when " + + (value === null ? "removed" : "set to " + format_value(value)) + desc); + } + + checkLength(null, 0); + checkLength("", 0); + checkLength(" \t \f", 0); + checkLength("a", 1); + checkLength("a A", 2); + checkLength("\r\na\t\f", 1); + checkLength("a a", 1); + checkLength("a a a a a a", 1); + checkLength("a a b b", 2); + checkLength("a A B b", 4); + checkLength("a b c c b a a b c c", 3); + checkLength(" a a b", 2); + checkLength("a\tb\nc\fd\re f", 6); + + // [Stringifies] + + function checkStringifier(value, expected) { + test(function() { + setClass(e, value); + assert_equals(e.classList.toString(), expected); + }, "classList.toString() when " + + (value === null ? "removed" : "set to " + format_value(value)) + desc); + } + + checkStringifier(null, ""); + checkStringifier("foo", "foo"); + checkStringifier(" a a b", " a a b"); + + // item() method + + function checkItems(attributeValue, expectedValues) { + function checkItemFunction(index, expected) { + assert_equals(e.classList.item(index), expected, + "classList.item(" + index + ")"); + } + + function checkItemArray(index, expected) { + assert_equals(e.classList[index], expected, "classList[" + index + "]"); + } + + test(function() { + setClass(e, attributeValue); + + checkItemFunction(-1, null); + checkItemArray(-1, undefined); + + var i = 0; + while (i < expectedValues.length) { + checkItemFunction(i, expectedValues[i]); + checkItemArray(i, expectedValues[i]); + i++; + } + + checkItemFunction(i, null); + checkItemArray(i, undefined); + + checkItemFunction(0xffffffff, null); + checkItemArray(0xffffffff, undefined); + + checkItemFunction(0xfffffffe, null); + checkItemArray(0xfffffffe, undefined); + }, "classList.item() when set to " + format_value(attributeValue) + desc); + } + + checkItems(null, []); + checkItems("a", ["a"]); + checkItems("aa AA aa", ["aa", "AA"]); + checkItems("a b", ["a", "b"]); + checkItems(" a a b", ["a", "b"]); + checkItems("\t\n\f\r a\t\n\f\r b\t\n\f\r ", ["a", "b"]); + + // contains() method + + function checkContains(attributeValue, args, expectedRes) { + if (!Array.isArray(expectedRes)) { + expectedRes = Array(args.length).fill(expectedRes); + } + setClass(e, attributeValue); + for (var i = 0; i < args.length; i++) { + test(function() { + assert_equals(e.classList.contains(args[i]), expectedRes[i], + "classList.contains(\"" + args[i] + "\")"); + }, "classList.contains(" + format_value(args[i]) + ") when set to " + + format_value(attributeValue) + desc); + } + } + + checkContains(null, ["a", "", " "], false); + checkContains("", ["a"], false); + + checkContains("a", ["a"], true); + checkContains("a", ["aa", "b", "A", "a.", "a)",, "a'", 'a"', "a$", "a~", + "a?", "a\\"], false); + + // All "ASCII whitespace" per spec, before and after + checkContains("a", ["a\t", "\ta", "a\n", "\na", "a\f", "\fa", "a\r", "\ra", + "a ", " a"], false); + + checkContains("aa AA", ["aa", "AA", "aA"], [true, true, false]); + checkContains("a a a", ["a", "aa", "b"], [true, false, false]); + checkContains("a b c", ["a", "b"], true); + + checkContains("null undefined", [null, undefined], true); + checkContains("\t\n\f\r a\t\n\f\r b\t\n\f\r ", ["a", "b"], true); + + // add() method + + function checkAdd(before, argument, after, expectedException) { + checkModification(e, "add", argument, undefined, before, after, + expectedException, desc); + // Also check force toggle + // XXX https://github.com/whatwg/dom/issues/443 + //if (!Array.isArray(argument)) { + // checkModification(e, "toggle", [argument, true], true, before, after, + // expectedException); + //} + } + + checkAdd(null, "", null, "SyntaxError"); + checkAdd(null, ["a", ""], null, "SyntaxError"); + checkAdd(null, " ", null, "InvalidCharacterError"); + checkAdd(null, "\ta", null, "InvalidCharacterError"); + checkAdd(null, "a\t", null, "InvalidCharacterError"); + checkAdd(null, "\na", null, "InvalidCharacterError"); + checkAdd(null, "a\n", null, "InvalidCharacterError"); + checkAdd(null, "\fa", null, "InvalidCharacterError"); + checkAdd(null, "a\f", null, "InvalidCharacterError"); + checkAdd(null, "\ra", null, "InvalidCharacterError"); + checkAdd(null, "a\r", null, "InvalidCharacterError"); + checkAdd(null, " a", null, "InvalidCharacterError"); + checkAdd(null, "a ", null, "InvalidCharacterError"); + checkAdd(null, ["a", " "], null, "InvalidCharacterError"); + checkAdd(null, ["a", "aa "], null, "InvalidCharacterError"); + + checkAdd("a", "a", "a"); + checkAdd("aa", "AA", "aa AA"); + checkAdd("a b c", "a", "a b c"); + checkAdd("a a a b", "a", "a b"); + checkAdd(null, "a", "a"); + checkAdd("", "a", "a"); + checkAdd(" ", "a", "a"); + checkAdd(" \f", "a", "a"); + checkAdd("a", "b", "a b"); + checkAdd("a b c", "d", "a b c d"); + checkAdd("a b c ", "d", "a b c d"); + checkAdd(" a a b", "c", "a b c"); + checkAdd(" a a b", "a", "a b"); + checkAdd("\t\n\f\r a\t\n\f\r b\t\n\f\r ", "c", "a b c"); + + // multiple add + checkAdd("a b c ", ["d", "e"], "a b c d e"); + checkAdd("a b c ", ["a", "a"], "a b c"); + checkAdd("a b c ", ["d", "d"], "a b c d"); + checkAdd("a b c a ", [], "a b c"); + checkAdd(null, ["a", "b"], "a b"); + checkAdd("", ["a", "b"], "a b"); + + checkAdd(null, null, "null"); + checkAdd(null, undefined, "undefined"); + + // remove() method + + function checkRemove(before, argument, after, expectedException) { + checkModification(e, "remove", argument, undefined, before, after, + expectedException, desc); + // Also check force toggle + // XXX https://github.com/whatwg/dom/issues/443 + //if (!Array.isArray(argument)) { + // checkModification(e, "toggle", [argument, false], false, before, after, + // expectedException); + //} + } + + checkRemove(null, "", null, "SyntaxError"); + checkRemove(null, " ", null, "InvalidCharacterError"); + checkRemove("\ta", "\ta", "\ta", "InvalidCharacterError"); + checkRemove("a\t", "a\t", "a\t", "InvalidCharacterError"); + checkRemove("\na", "\na", "\na", "InvalidCharacterError"); + checkRemove("a\n", "a\n", "a\n", "InvalidCharacterError"); + checkRemove("\fa", "\fa", "\fa", "InvalidCharacterError"); + checkRemove("a\f", "a\f", "a\f", "InvalidCharacterError"); + checkRemove("\ra", "\ra", "\ra", "InvalidCharacterError"); + checkRemove("a\r", "a\r", "a\r", "InvalidCharacterError"); + checkRemove(" a", " a", " a", "InvalidCharacterError"); + checkRemove("a ", "a ", "a ", "InvalidCharacterError"); + checkRemove("aa ", "aa ", null, "InvalidCharacterError"); + + checkRemove(null, "a", null); + checkRemove("", "a", ""); + checkRemove("a b c", "d", "a b c"); + checkRemove("a b c", "A", "a b c"); + checkRemove(" a a a ", "a", ""); + checkRemove("a b", "a", "b"); + checkRemove("a b ", "a", "b"); + checkRemove("a a b", "a", "b"); + checkRemove("aa aa bb", "aa", "bb"); + checkRemove("a a b a a c a a", "a", "b c"); + + checkRemove("a b c", "b", "a c"); + checkRemove("aaa bbb ccc", "bbb", "aaa ccc"); + checkRemove(" a b c ", "b", "a c"); + checkRemove("a b b b c", "b", "a c"); + + checkRemove("a b c", "c", "a b"); + checkRemove(" a b c ", "c", "a b"); + checkRemove("a b c c c", "c", "a b"); + + checkRemove("a b a c a d a", "a", "b c d"); + checkRemove("AA BB aa CC AA dd aa", "AA", "BB aa CC dd"); + + checkRemove("\ra\na\ta\f", "a", ""); + checkRemove("\t\n\f\r a\t\n\f\r b\t\n\f\r ", "a", "b"); + + // multiple remove + checkRemove("a b c ", ["d", "e"], "a b c"); + checkRemove("a b c ", ["a", "b"], "c"); + checkRemove("a b c ", ["a", "c"], "b"); + checkRemove("a b c ", ["a", "a"], "b c"); + checkRemove("a b c ", ["d", "d"], "a b c"); + checkRemove("a b c ", [], "a b c"); + checkRemove(null, ["a", "b"], null); + checkRemove("", ["a", "b"], ""); + checkRemove("a a", [], "a"); + + checkRemove("null", null, ""); + checkRemove("undefined", undefined, ""); + + // toggle() method + + function checkToggle(before, argument, expectedRes, after, expectedException) { + checkModification(e, "toggle", argument, expectedRes, before, after, + expectedException, desc); + } + + checkToggle(null, "", null, null, "SyntaxError"); + checkToggle(null, "aa ", null, null, "InvalidCharacterError"); + + checkToggle(null, "a", true, "a"); + checkToggle("", "a", true, "a"); + checkToggle(" ", "a", true, "a"); + checkToggle(" \f", "a", true, "a"); + checkToggle("a", "b", true, "a b"); + checkToggle("a", "A", true, "a A"); + checkToggle("a b c", "d", true, "a b c d"); + checkToggle(" a a b", "d", true, "a b d"); + + checkToggle("a", "a", false, ""); + checkToggle(" a a a ", "a", false, ""); + checkToggle(" A A A ", "a", true, "A a"); + checkToggle(" a b c ", "b", false, "a c"); + checkToggle(" a b c b b", "b", false, "a c"); + checkToggle(" a b c ", "c", false, "a b"); + checkToggle(" a b c ", "a", false, "b c"); + checkToggle(" a a b", "b", false, "a"); + checkToggle("\t\n\f\r a\t\n\f\r b\t\n\f\r ", "a", false, "b"); + checkToggle("\t\n\f\r a\t\n\f\r b\t\n\f\r ", "c", true, "a b c"); + + checkToggle("null", null, false, ""); + checkToggle("", null, true, "null"); + checkToggle("undefined", undefined, false, ""); + checkToggle("", undefined, true, "undefined"); + + + // tests for the force argument handling + // XXX Remove these if https://github.com/whatwg/dom/issues/443 is fixed + + function checkForceToggle(before, argument, force, expectedRes, after, expectedException) { + checkModification(e, "toggle", [argument, force], expectedRes, before, + after, expectedException, desc); + } + + checkForceToggle("", "a", true, true, "a"); + checkForceToggle("a", "a", true, true, "a"); + checkForceToggle("a", "b", true, true, "a b"); + checkForceToggle("a b", "b", true, true, "a b"); + checkForceToggle("", "a", false, false, ""); + checkForceToggle("a", "a", false, false, ""); + checkForceToggle("a", "b", false, false, "a"); + checkForceToggle("a b", "b", false, false, "a"); + + + // replace() method + function checkReplace(before, token, newToken, expectedRes, after, expectedException) { + checkModification(e, "replace", [token, newToken], expectedRes, before, + after, expectedException, desc); + } + + checkReplace(null, "", "a", null, null, "SyntaxError"); + checkReplace(null, "", " ", null, null, "SyntaxError"); + checkReplace(null, " ", "a", null, null, "InvalidCharacterError"); + checkReplace(null, "\ta", "b", null, null, "InvalidCharacterError"); + checkReplace(null, "a\t", "b", null, null, "InvalidCharacterError"); + checkReplace(null, "\na", "b", null, null, "InvalidCharacterError"); + checkReplace(null, "a\n", "b", null, null, "InvalidCharacterError"); + checkReplace(null, "\fa", "b", null, null, "InvalidCharacterError"); + checkReplace(null, "a\f", "b", null, null, "InvalidCharacterError"); + checkReplace(null, "\ra", "b", null, null, "InvalidCharacterError"); + checkReplace(null, "a\r", "b", null, null, "InvalidCharacterError"); + checkReplace(null, " a", "b", null, null, "InvalidCharacterError"); + checkReplace(null, "a ", "b", null, null, "InvalidCharacterError"); + + checkReplace(null, "a", "", null, null, "SyntaxError"); + checkReplace(null, " ", "", null, null, "SyntaxError"); + checkReplace(null, "a", " ", null, null, "InvalidCharacterError"); + checkReplace(null, "b", "\ta", null, null, "InvalidCharacterError"); + checkReplace(null, "b", "a\t", null, null, "InvalidCharacterError"); + checkReplace(null, "b", "\na", null, null, "InvalidCharacterError"); + checkReplace(null, "b", "a\n", null, null, "InvalidCharacterError"); + checkReplace(null, "b", "\fa", null, null, "InvalidCharacterError"); + checkReplace(null, "b", "a\f", null, null, "InvalidCharacterError"); + checkReplace(null, "b", "\ra", null, null, "InvalidCharacterError"); + checkReplace(null, "b", "a\r", null, null, "InvalidCharacterError"); + checkReplace(null, "b", " a", null, null, "InvalidCharacterError"); + checkReplace(null, "b", "a ", null, null, "InvalidCharacterError"); + + checkReplace("a", "a", "a", true, "a"); + checkReplace("a", "a", "b", true, "b"); + checkReplace("a", "A", "b", false, "a"); + checkReplace("a b", "b", "A", true, "a A"); + checkReplace("a b", "c", "a", false, "a b"); + checkReplace("a b c", "d", "e", false, "a b c"); + // https://github.com/whatwg/dom/issues/443 + checkReplace("a a a b", "a", "a", true, "a b"); + checkReplace("a a a b", "c", "d", false, "a a a b"); + checkReplace(null, "a", "b", false, null); + checkReplace("", "a", "b", false, ""); + checkReplace(" ", "a", "b", false, " "); + checkReplace(" a \f", "a", "b", true, "b"); + checkReplace("a b c", "b", "d", true, "a d c"); + checkReplace("a b c", "c", "a", true, "a b"); + checkReplace("c b a", "c", "a", true, "a b"); + checkReplace("a b a", "a", "c", true, "c b"); + checkReplace("a b a", "b", "c", true, "a c"); + checkReplace(" a a b", "a", "c", true, "c b"); + checkReplace(" a a b", "b", "c", true, "a c"); + checkReplace("\t\n\f\r a\t\n\f\r b\t\n\f\r ", "a", "c", true, "c b"); + checkReplace("\t\n\f\r a\t\n\f\r b\t\n\f\r ", "b", "c", true, "a c"); + + checkReplace("a null", null, "b", true, "a b"); + checkReplace("a b", "a", null, true, "null b"); + checkReplace("a undefined", undefined, "b", true, "a b"); + checkReplace("a b", "a", undefined, true, "undefined b"); +} + +var content = document.getElementById("content"); + +var htmlNode = document.createElement("div"); +content.appendChild(htmlNode); +testClassList(htmlNode, " (HTML node)"); + +var xhtmlNode = document.createElementNS(XHTML_NS, "div"); +content.appendChild(xhtmlNode); +testClassList(xhtmlNode, " (XHTML node)"); + +var mathMLNode = document.createElementNS(MATHML_NS, "math"); +content.appendChild(mathMLNode); +testClassList(mathMLNode, " (MathML node)"); + +var xmlNode = document.createElementNS(null, "foo"); +content.appendChild(xmlNode); +testClassList(xmlNode, " (XML node with null namespace)"); + +var fooNode = document.createElementNS("http://example.org/foo", "foo"); +content.appendChild(fooNode); +testClassList(fooNode, " (foo node)"); +</script> diff --git a/testing/web-platform/mozilla/tests/dom/delayed_window_print.html b/testing/web-platform/mozilla/tests/dom/delayed_window_print.html new file mode 100644 index 0000000000..0bb9977184 --- /dev/null +++ b/testing/web-platform/mozilla/tests/dom/delayed_window_print.html @@ -0,0 +1,39 @@ +<!doctype html> +<meta charset=utf-8> +<title>Test for delaying window.print() before load</title> +<script src=/resources/testharness.js></script> +<script src=/resources/testharnessreport.js></script> +<body> +<script> +let t = async_test("Delayed print before load"); +let beforePrintCalled = false; +window.addEventListener("beforeprint", t.step_func(function() { + assert_false(beforePrintCalled, "Should only call beforeprint once"); + beforePrintCalled = true; + assert_true( + !!document.getElementById("before-load"), + "Should show contents that get added before load" + ); + assert_true( + !!document.getElementById("during-load"), + "Should show contents that get added during load" + ); + setTimeout(function() { t.done(); }, 0); +})); + +t.step(function() { + window.print(); + + let div = document.createElement("div"); + div.id = "before-load"; + document.body.appendChild(div); +}); + +window.addEventListener("load", t.step_func(function() { + window.print(); + + let div = document.createElement("div"); + div.id = "during-load"; + document.body.appendChild(div); +})); +</script> diff --git a/testing/web-platform/mozilla/tests/dom/dispatch_select_event.html b/testing/web-platform/mozilla/tests/dom/dispatch_select_event.html new file mode 100644 index 0000000000..1fb70aa5b1 --- /dev/null +++ b/testing/web-platform/mozilla/tests/dom/dispatch_select_event.html @@ -0,0 +1,35 @@ +<!-- See also Bug 1679427. +Ensure `select` event is only fired once when tab-ing to an `<input>` element. +--> +<!doctype html> +<html> +<head> + <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> +</head> +<body> + <button>Press this button and Press Tab</button><input value="abc"> + <script> + promise_test(async t => { + await new Promise(resolve => { window.onload = resolve; }); + const button = document.querySelector("button"); + const input = document.querySelector("input"); + + let countSelectEvent = 0; + input.addEventListener("select", event => { + countSelectEvent++; + }); + + button.focus(); + const tabKey = "\uE004"; + await test_driver.send_keys(button, tabKey); + await new Promise(resolve => requestAnimationFrame( + () => requestAnimationFrame(resolve) + )); + assert_equals(countSelectEvent, 1, "Select event was fired more than once!"); + }, "Select event should only be fired once."); + </script> +</body> +</html> diff --git a/testing/web-platform/mozilla/tests/dom/focus-invalid-uri-link.html b/testing/web-platform/mozilla/tests/dom/focus-invalid-uri-link.html new file mode 100644 index 0000000000..5de81c866f --- /dev/null +++ b/testing/web-platform/mozilla/tests/dom/focus-invalid-uri-link.html @@ -0,0 +1,63 @@ +<!doctype html> +<script src=/resources/testharness.js></script> +<script src=/resources/testharnessreport.js></script> +<script src=/resources/testdriver.js></script> +<script src=/resources/testdriver-actions.js></script> +<script src=/resources/testdriver-vendor.js></script> +<script> +function abs(uri) { + return new URL(uri, window.location.href).href; +} +const CHILD_DOC = ` +<!doctype html> +<script src="${abs("/resources/testdriver.js")}"></` + `script> +<script src="${abs("/resources/testdriver-actions.js")}"></` + `script> +<script src="${abs("/resources/testdriver-vendor.js")}"></` + `script> +<script> + test_driver.set_test_context(opener); +</` + `script> + +<a href>To link or not to link</a> + +<script> +onload = async function() { + let link = document.querySelector("a"); + link.focus(); + let focused = document.activeElement == link; + let clicked = new Promise(resolve => { + link.addEventListener("click", resolve, { once: true }); + }); + const enterKey = '\\uE007'; + await test_driver.send_keys(link, enterKey); + await clicked; + test_driver.message_test({ testResult: true, focused, clicked: true }); +}; +</` + `script> +` + +promise_test(async function(t) { + await new Promise(resolve => { + window.onload = resolve; + }) + + let messagePromise = new Promise(resolve => { + addEventListener("message", function(msg) { + if (msg.data.testResult) { + resolve(msg); + } + }); + }); + + let win = window.open("data:text/html," + escape(CHILD_DOC)); + + assert_true(true, "Window opened"); + let message = await messagePromise; + assert_true(true, "message: " + JSON.stringify(message)); + + let { focused, clicked } = message.data; + assert_true(focused, "Link should be focusable"); + assert_true(clicked, "Link should be keyboard activatable"); + + win.close(); +}, "Link to invalid URI should be focusable and keyboard activatable"); +</script> diff --git a/testing/web-platform/mozilla/tests/dom/fs/fs-writable_unlocked_on_tab_close.https.window.js b/testing/web-platform/mozilla/tests/dom/fs/fs-writable_unlocked_on_tab_close.https.window.js new file mode 100644 index 0000000000..6e8192aaef --- /dev/null +++ b/testing/web-platform/mozilla/tests/dom/fs/fs-writable_unlocked_on_tab_close.https.window.js @@ -0,0 +1,70 @@ +// META: title=Origin private file system used from multiple tabs +// META: script=support/testHelpers.js + +promise_test(async t => { + const bc = new BroadcastChannel("Coordinate writables"); + + function firstReady(win) { + return new Promise(resolve => { + // Blur triggers after the unload event and after window.closed is set + win.onblur = () => { + // Closing the low-level file handle may get stuck, but the unlocking + // request can only be sent to the parent after the handle is closed. + // There is no guarantee when or if the closing will be complete. + // + // Therefore, the content process shutdown does not wait for the + // completion but sets window.closed and calls the unload listeners + // while actually still holding onto some resources. + // + // Since in this test we mainly want to ensure that a file + // does not remain locked indefinitely, we wait for a reasonable amount + // of time before creating a new writable, corresponding roughly to + // a 500ms closing delay. + const timeoutMs = 400; + setTimeout(() => { + resolve(win); + }, timeoutMs); + }; + }); + } + + const firstTabName = "support/fs-open_writable_then_close_tab.sub.html"; + const firstTab = await firstReady(window.open(firstTabName)); + assert_true(firstTab.closed, "Is the first tab already closed?"); + + function secondReady(win) { + return new Promise(resolve => { + bc.onmessage = e => { + if (e.data === "Second window ready!") { + resolve(win); + } + }; + }); + } + + const secondTabName = "support/fs-open_writable_after_trigger.sub.html"; + const secondTab = await secondReady(window.open(secondTabName)); + + let isDone = false; + let childStatus = "Success"; + + const secondSucceeded = new Promise(resolve => { + bc.onmessage = e => { + isDone = true; + if (e.data !== "Success") { + childStatus = e.data; + } + + resolve(); + }; + }); + + bc.postMessage("Create writable in the second window"); + + await secondSucceeded; + assert_true(isDone, "Did the second tab respond?"); + + await fetch_tests_from_window(secondTab); + + assert_equals(childStatus, "Success"); +}, `writable available for other tabs after one tab is closed`); diff --git a/testing/web-platform/mozilla/tests/dom/fs/support/fs-open_writable_after_trigger.sub.html b/testing/web-platform/mozilla/tests/dom/fs/support/fs-open_writable_after_trigger.sub.html new file mode 100644 index 0000000000..b64d97083f --- /dev/null +++ b/testing/web-platform/mozilla/tests/dom/fs/support/fs-open_writable_after_trigger.sub.html @@ -0,0 +1,45 @@ +<!DOCTYPE html> +<html> +<title>Child context test(s)</title> +<head> + <script src="/resources/testharness.js"></script> + <script src="testHelpers.js"></script> +</head> +<body> + <div id="log"></div> + <script> + const channel = new BroadcastChannel("Coordinate writables"); + + let triggered = false; + + channel.onmessage = e => { + if ("Create writable in the second window" === e.data) { + triggered = true; + } + }; + + channel.postMessage("Second window ready!"); + + promise_test(async t => { + try { + const maxWaitMs = 2000; + await waitUntil(() => { return triggered; }, maxWaitMs); + assert_true(triggered, "Did we receive a trigger?"); + + const dir = await navigator.storage.getDirectory(); + const opts = { create: true }; + const file = await dir.getFileHandle('funky-file-handle', opts); + let writable = await file.createWritable({}); + t.add_cleanup(async () => { await writable.close(); }); + assert_true(!!writable, "Did we receive a writable?"); + + channel.postMessage("Success"); + } catch(err) { + channel.postMessage(err.message); + + throw err; + } + }); + </script> +</body> +</html> diff --git a/testing/web-platform/mozilla/tests/dom/fs/support/fs-open_writable_then_close_tab.sub.html b/testing/web-platform/mozilla/tests/dom/fs/support/fs-open_writable_then_close_tab.sub.html new file mode 100644 index 0000000000..098fe83b1f --- /dev/null +++ b/testing/web-platform/mozilla/tests/dom/fs/support/fs-open_writable_then_close_tab.sub.html @@ -0,0 +1,20 @@ +<!DOCTYPE html> +<html> +<title>Child context test(s)</title> +<head> + <script src="/resources/testharness.js"></script> +</head> +<body> + <div id="log"></div> + <script> + window.addEventListener('load', async () => { + const dir = await navigator.storage.getDirectory(); + const opts = { create: true }; + const file = await dir.getFileHandle('funky-file-handle', opts); + const writable = await file.createWritable({}); + + window.close(); + }); + </script> +</body> +</html> diff --git a/testing/web-platform/mozilla/tests/dom/fs/support/testHelpers.js b/testing/web-platform/mozilla/tests/dom/fs/support/testHelpers.js new file mode 100644 index 0000000000..5cf47435c1 --- /dev/null +++ b/testing/web-platform/mozilla/tests/dom/fs/support/testHelpers.js @@ -0,0 +1,15 @@ +async function waitUntil(isWaitDone, untilMs, stepMs = 25) { + const startMs = Date.now(); + + return new Promise((resolve, reject) => { + const areWeDoneYet = setInterval(async function() { + if (await isWaitDone()) { + clearInterval(areWeDoneYet); + resolve(); + } else if (Date.now() > startMs + untilMs) { + clearInterval(areWeDoneYet); + reject(new Error("Timed out after " + untilMs + "ms")); + } + }, stepMs); + }); +} diff --git a/testing/web-platform/mozilla/tests/dom/range-in-two-selections.html b/testing/web-platform/mozilla/tests/dom/range-in-two-selections.html new file mode 100644 index 0000000000..a37464d55a --- /dev/null +++ b/testing/web-platform/mozilla/tests/dom/range-in-two-selections.html @@ -0,0 +1,34 @@ +<!DOCTYPE html> +<html> + +<head> +<script src=/resources/testharness.js></script> +<script src=/resources/testharnessreport.js></script> +</head> + +<body> +<span>One two</span> +<script> +promise_test(async function (t) { + await new Promise(resolve => { + window.onload = resolve; + }) + const range = document.createRange(); + range.setStart(document.body, 0); + range.setEnd(document.body, 1); + const highlight = new Highlight(range); + CSS.highlights.set("foo", highlight); + document.getSelection().addRange(range); + + const highlightRange = highlight.entries().next().value[0]; + const selectionRange = document.getSelection().getRangeAt(0); + assert_equals( + highlightRange, + selectionRange, + "The same range must be present in the highlight and the Selection." + ); +}, "Range is shared between a custom highlight and the document's Selection."); +</script> +</body> + +</html> diff --git a/testing/web-platform/mozilla/tests/dom/throttling/resources/test.html b/testing/web-platform/mozilla/tests/dom/throttling/resources/test.html new file mode 100644 index 0000000000..7eb9dd1e40 --- /dev/null +++ b/testing/web-platform/mozilla/tests/dom/throttling/resources/test.html @@ -0,0 +1,5 @@ +<!doctype html> +<meta charset=utf-8> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="throttling.js"></script> diff --git a/testing/web-platform/mozilla/tests/dom/throttling/resources/throttling.js b/testing/web-platform/mozilla/tests/dom/throttling/resources/throttling.js new file mode 100644 index 0000000000..280b0adc0d --- /dev/null +++ b/testing/web-platform/mozilla/tests/dom/throttling/resources/throttling.js @@ -0,0 +1,136 @@ +function waitForLoad() { + return new Promise(resolve => addEventListener('load', resolve)) + .then(() => delay(10)); +} + +function delay(timeout) { + return new Promise(resolve => step_timeout(() => resolve(), 10)); +} + +function busy(work) { + return delay(10).then(() => new Promise(resolve => { + step_timeout(() => { + let end = performance.now() + work; + while (performance.now() < end) { + + } + + resolve(); + }, 1); + })); +} + +function getThrottlingRate(delay) { + return new Promise(resolve => { + let start = performance.now(); + setTimeout(() => { + let rate = Math.floor((performance.now() - start) / delay); + resolve(rate); + }, delay); + }); +} + +function addElement(t, element, src) { + return new Promise((resolve, reject) => { + let e = document.createElement(element); + e.addEventListener('load', () => resolve(e)); + if (src) { + e.src = src; + } + document.body.appendChild(e); + t.add_cleanup(() => e.remove()); + }); +} + +function inFrame(t) { + return addElement(t, "iframe", "resources/test.html") + .then(frame => delay(10).then(() => Promise.resolve(frame.contentWindow))); +} + +function addWebSocket(t, url) { + return new Promise((resolve, reject) => { + let socket = new WebSocket(url); + socket.onopen = () => { + t.add_cleanup(() => socket.close()); + resolve(); + }; + socket.onerror = reject; + }); +} + +function addRTCPeerConnection(t) { + return new Promise((resolve, reject) => { + let connection = new RTCPeerConnection(); + t.add_cleanup(() => { + connection.close() + }); + + resolve(); + }); +} + +function addIndexedDB(t) { + return new Promise((resolve, reject) => { + let iDBState = { + running: false, + db: null + }; + + let req = indexedDB.open("testDB", 1); + + req.onupgradeneeded = e => { + let db = e.target.result; + let store = db.createObjectStore("testOS", {keyPath: "id"}); + let index = store.createIndex("index", ["col"]); + }; + + req.onsuccess = e => { + let db = iDBState.db = e.target.result; + let store = db.transaction("testOS", "readwrite").objectStore("testOS"); + let ctr = 0; + + iDBState.running = true; + + function putLoop() { + if (!iDBState.running) { + return; + } + + let req = store.put({id: ctr++, col: "foo"}); + req.onsuccess = putLoop; + + if (!iDBState.request) { + iDBState.request = req; + } + } + + putLoop(); + resolve(); + }; + + t.add_cleanup(() => { + iDBState.running = false; + iDBState.db && iDBState.db.close(); + iDBState.db = null; + }); + }); +} + +function addWebAudio(t) { + return new Promise(resolve => { + let context = new (window.AudioContext || window.webkitAudioContext)(); + context.onstatechange = () => (context.state === "running") && resolve(); + + let gain = context.createGain(); + gain.gain.value = 0.1; + gain.connect(context.destination); + + let webaudionode = context.createOscillator(); + webaudionode.type = 'square'; + webaudionode.frequency.value = 440; // value in hertz + webaudionode.connect(gain); + webaudionode.start(); + + t.add_cleanup(() => webaudionode.stop()); + }); +} diff --git a/testing/web-platform/mozilla/tests/dom/throttling/resources/ws.sub.js b/testing/web-platform/mozilla/tests/dom/throttling/resources/ws.sub.js new file mode 100644 index 0000000000..a1ac273a54 --- /dev/null +++ b/testing/web-platform/mozilla/tests/dom/throttling/resources/ws.sub.js @@ -0,0 +1,3 @@ +var __SERVER__NAME = "{{host}}"; +var __PORT = "{{ports[ws][0]}}"; +var __PATH = "echo"; diff --git a/testing/web-platform/mozilla/tests/dom/throttling/throttling-1.window.js b/testing/web-platform/mozilla/tests/dom/throttling/throttling-1.window.js new file mode 100644 index 0000000000..86cefc8a81 --- /dev/null +++ b/testing/web-platform/mozilla/tests/dom/throttling/throttling-1.window.js @@ -0,0 +1,10 @@ +// META: script=resources/throttling.js + +setup(() => waitForLoad() + .then(() => "setup done")); + +promise_test(t => busy(100) + .then(() => getThrottlingRate(100)) + .then(rate => { + assert_greater_than(rate, 10, "Timeout wasn't throttled"); + }), "Throttle when all budget has been used."); diff --git a/testing/web-platform/mozilla/tests/dom/throttling/throttling-2.window.js b/testing/web-platform/mozilla/tests/dom/throttling/throttling-2.window.js new file mode 100644 index 0000000000..3ccb35dc08 --- /dev/null +++ b/testing/web-platform/mozilla/tests/dom/throttling/throttling-2.window.js @@ -0,0 +1,11 @@ +// META: script=resources/throttling.js + +setup(() => waitForLoad() + .then(() => "setup done")); + +promise_test(t => inFrame(t) + .then(win => win.busy(100) + .then(() => win.getThrottlingRate(100))) + .then(rate => { + assert_greater_than(rate, 10, "Timeout wasn't throttled"); + }), "Throttle iframe when all budget has been used"); diff --git a/testing/web-platform/mozilla/tests/dom/throttling/throttling-3.window.js b/testing/web-platform/mozilla/tests/dom/throttling/throttling-3.window.js new file mode 100644 index 0000000000..d1c38bcc12 --- /dev/null +++ b/testing/web-platform/mozilla/tests/dom/throttling/throttling-3.window.js @@ -0,0 +1,11 @@ +// META: script=resources/throttling.js + +setup(() => waitForLoad() + .then(() => "setup done")); + +promise_test(t => inFrame(t) + .then(win => busy(100) + .then(() => win.getThrottlingRate(100))) + .then(rate => { + assert_less_than(rate, 10, "Timeout was throttled"); + }), "Don't throttle iframe when all budget in parent has been used"); diff --git a/testing/web-platform/mozilla/tests/dom/throttling/throttling-4.window.js b/testing/web-platform/mozilla/tests/dom/throttling/throttling-4.window.js new file mode 100644 index 0000000000..b072b51809 --- /dev/null +++ b/testing/web-platform/mozilla/tests/dom/throttling/throttling-4.window.js @@ -0,0 +1,11 @@ +// META: script=resources/throttling.js + +setup(() => waitForLoad() + .then(() => "setup done")); + +promise_test(t => inFrame(t) + .then(win => win.busy(100)) + .then(() => getThrottlingRate(100)) + .then(rate => { + assert_less_than(rate, 10, "Timeout was throttled"); + }), "Don't throttle parent when all budget in iframe has been used"); diff --git a/testing/web-platform/mozilla/tests/dom/throttling/throttling-indexeddb.window.js b/testing/web-platform/mozilla/tests/dom/throttling/throttling-indexeddb.window.js new file mode 100644 index 0000000000..73c734a584 --- /dev/null +++ b/testing/web-platform/mozilla/tests/dom/throttling/throttling-indexeddb.window.js @@ -0,0 +1,35 @@ +// META: script=resources/throttling.js + +setup(() => waitForLoad() + .then(() => "setup done")); + +promise_test(t => addIndexedDB(t) + .then(() => busy(100)) + .then(() => getThrottlingRate(100)) + .then(rate => { + assert_less_than(rate, 10, "Timeout was throttled"); + }), "Don't throttle when there are open IndexedDB transactions."); + +promise_test(t => inFrame(t) + .then(win => win.addIndexedDB(t)) + .then(() => busy(100)) + .then(() => getThrottlingRate(100)) + .then(rate => { + assert_less_than(rate, 10, "Timeout was throttled"); + }), "Don't throttle when there are open IndexedDB transactions in iframe."); + +promise_test(t => inFrame(t) + .then(win => addIndexedDB(t) + .then(() => win.busy(100)) + .then(() => win.getThrottlingRate(100))) + .then(rate => { + assert_less_than(rate, 10, "Timeout was throttled"); + }), "Don't throttle iframe when there are open IndexedDB transactions in parent."); + +promise_test(t => inFrame(t) + .then(win => win.addIndexedDB(t) + .then(() => win.busy(100)) + .then(() => win.getThrottlingRate(100))) + .then(rate => { + assert_less_than(rate, 10, "Timeout was throttled"); + }), "Don't throttle iframe when there are open IndexedDB transactions in iframe."); diff --git a/testing/web-platform/mozilla/tests/dom/throttling/throttling-webaudio.window.js b/testing/web-platform/mozilla/tests/dom/throttling/throttling-webaudio.window.js new file mode 100644 index 0000000000..5cd7193788 --- /dev/null +++ b/testing/web-platform/mozilla/tests/dom/throttling/throttling-webaudio.window.js @@ -0,0 +1,35 @@ +// META: script=resources/throttling.js + +setup(() => waitForLoad() + .then(() => "setup done")); + +promise_test(t => addWebAudio(t) + .then(() => busy(100)) + .then(() => getThrottlingRate(100)) + .then(rate => { + assert_less_than(rate, 10, "Timeout was throttled"); + }), "Don't throttle when there is active WebAudio."); + +promise_test(t => inFrame(t) + .then(win => win.addWebAudio(t)) + .then(() => busy(100)) + .then(() => getThrottlingRate(100)) + .then(rate => { + assert_less_than(rate, 10, "Timeout was throttled"); + }), "Don't throttle when there is active WebAudio in iframe."); + +promise_test(t => inFrame(t) + .then(win => addWebAudio(t) + .then(() => win.busy(100)) + .then(() => win.getThrottlingRate(100))) + .then(rate => { + assert_less_than(rate, 10, "Timeout was throttled"); + }), "Don't throttle iframe when there is active WebAudio in parent."); + +promise_test(t => inFrame(t) + .then(win => win.addWebAudio(t) + .then(() => win.busy(100)) + .then(() => win.getThrottlingRate(100))) + .then(rate => { + assert_less_than(rate, 10, "Timeout was throttled"); + }), "Don't throttle iframe when there is active WebAudio in iframe."); diff --git a/testing/web-platform/mozilla/tests/dom/throttling/throttling-webrtc.window.js b/testing/web-platform/mozilla/tests/dom/throttling/throttling-webrtc.window.js new file mode 100644 index 0000000000..2842f77e44 --- /dev/null +++ b/testing/web-platform/mozilla/tests/dom/throttling/throttling-webrtc.window.js @@ -0,0 +1,35 @@ +// META: script=resources/throttling.js + +setup(() => waitForLoad() + .then(() => "setup done")); + +promise_test(t => addRTCPeerConnection(t) + .then(() => busy(100)) + .then(() => getThrottlingRate(100)) + .then(rate => { + assert_less_than(rate, 10, "Timeout was throttled"); + }), "Don't throttle when there are open RTCPeerConnections."); + +promise_test(t => inFrame(t) + .then(win => win.addRTCPeerConnection(t)) + .then(() => busy(100)) + .then(() => getThrottlingRate(100)) + .then(rate => { + assert_less_than(rate, 10, "Timeout was throttled"); + }), "Don't throttle when there are open RTCPeerConnections in iframe."); + +promise_test(t => inFrame(t) + .then(win => addRTCPeerConnection(t) + .then(() => win.busy(100)) + .then(() => win.getThrottlingRate(100))) + .then(rate => { + assert_less_than(rate, 10, "Timeout was throttled"); + }), "Don't throttle iframe when there are open RTCPeerConnections in parent."); + +promise_test(t => inFrame(t) + .then(win => win.addRTCPeerConnection(t) + .then(() => win.busy(100)) + .then(() => win.getThrottlingRate(100))) + .then(rate => { + assert_less_than(rate, 10, "Timeout was throttled"); + }), "Don't throttle iframe when there are open RTCPeerConnections in iframe."); diff --git a/testing/web-platform/mozilla/tests/dom/throttling/throttling-ws.window.js b/testing/web-platform/mozilla/tests/dom/throttling/throttling-ws.window.js new file mode 100644 index 0000000000..185654e04d --- /dev/null +++ b/testing/web-platform/mozilla/tests/dom/throttling/throttling-ws.window.js @@ -0,0 +1,37 @@ +// META: script=resources/ws.sub.js +// META: script=resources/throttling.js +let server = "ws://" + __SERVER__NAME + ":" + __PORT + "/" + __PATH; + +setup(() => waitForLoad() + .then(() => "setup done")); + +promise_test(t => addWebSocket(t, server) + .then(() => busy(100)) + .then(() => getThrottlingRate(100)) + .then(rate => { + assert_less_than(rate, 10, "Timeout was throttled"); + }), "Don't throttle when there are open WebSockets."); + +promise_test(t => inFrame(t) + .then(win => win.addWebSocket(t, server)) + .then(() => busy(100)) + .then(() => getThrottlingRate(100)) + .then(rate => { + assert_less_than(rate, 10, "Timeout was throttled"); + }), "Don't throttle when there are open WebSockets in iframe."); + +promise_test(t => inFrame(t) + .then(win => addWebSocket(t, server) + .then(() => win.busy(100)) + .then(() => win.getThrottlingRate(100))) + .then(rate => { + assert_less_than(rate, 10, "Timeout was throttled"); + }), "Don't throttle iframe when there are open WebSockets in parent."); + +promise_test(t => inFrame(t) + .then(win => win.addWebSocket(t, server) + .then(() => win.busy(100)) + .then(() => win.getThrottlingRate(100))) + .then(rate => { + assert_less_than(rate, 10, "Timeout was throttled"); + }), "Don't throttle iframe when there are open WebSockets in iframe."); |