diff options
Diffstat (limited to 'dom/tests/mochitest/general')
141 files changed, 12742 insertions, 0 deletions
diff --git a/dom/tests/mochitest/general/497633.html b/dom/tests/mochitest/general/497633.html new file mode 100644 index 0000000000..da011f54c8 --- /dev/null +++ b/dom/tests/mochitest/general/497633.html @@ -0,0 +1,35 @@ +<html> +<head> +</head> +<body> +<script> +var t = 0; +function doe() { +setTimeout(function() { + if (t == 1) { + window.close(); + window.opener.done(); + } + else { + window.frames[0].location.reload(); + t++; + } +}, 300); +} +</script> + +<iframe srcdoc="<html xmlns='http://www.w3.org/1999/xhtml'> + <iframe/> + <frameset onblur='window.frameElement.parentNode.removeChild(window.frameElement)' id='frame'/> + + <script> +function doe(i){ + document.getElementById('frame').focus(); + document.getElementsByTagName('*')[1].focus(); +} +top.opener.SimpleTest.waitForFocus(function () setTimeout(doe, 100), top); + </script> + </html>" onload="doe()" id="content"></iframe> +</body> +</html> + diff --git a/dom/tests/mochitest/general/chrome.ini b/dom/tests/mochitest/general/chrome.ini new file mode 100644 index 0000000000..9910eb7a9b --- /dev/null +++ b/dom/tests/mochitest/general/chrome.ini @@ -0,0 +1,12 @@ +[DEFAULT] +skip-if = os == 'android' + +[test_innerScreen.xhtml] +[test_offsets.xhtml] +support-files = test_offsets.css test_offsets.js +skip-if = (os == "mac" && debug) #leaks Bug 1571583 +[test_spacetopagedown.html] +[test_nodeAdoption_chrome_boundary.xhtml] +[test_focusrings.xhtml] +support-files = file_focusrings.html +skip-if = toolkit == 'android' #TIMED_OUT diff --git a/dom/tests/mochitest/general/cssA.css b/dom/tests/mochitest/general/cssA.css new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/dom/tests/mochitest/general/cssA.css diff --git a/dom/tests/mochitest/general/cssB.css b/dom/tests/mochitest/general/cssB.css new file mode 100644 index 0000000000..8d44ebd00f --- /dev/null +++ b/dom/tests/mochitest/general/cssB.css @@ -0,0 +1,2 @@ +@import 'cssC.css'; +@import url('http://example.org/tests/dom/tests/mochitest/general/cssC.css'); diff --git a/dom/tests/mochitest/general/cssC.css b/dom/tests/mochitest/general/cssC.css new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/dom/tests/mochitest/general/cssC.css diff --git a/dom/tests/mochitest/general/embed_navigate.html b/dom/tests/mochitest/general/embed_navigate.html new file mode 100644 index 0000000000..93641f026a --- /dev/null +++ b/dom/tests/mochitest/general/embed_navigate.html @@ -0,0 +1 @@ +<script>location="https://test2.example.org/tests/dom/tests/mochitest/general/postback.html"</script> diff --git a/dom/tests/mochitest/general/emptyCssFile2.css b/dom/tests/mochitest/general/emptyCssFile2.css new file mode 100644 index 0000000000..64f97ae687 --- /dev/null +++ b/dom/tests/mochitest/general/emptyCssFile2.css @@ -0,0 +1 @@ +@import url('http://example.org/tests/dom/tests/mochitest/general/cross.css');
\ No newline at end of file diff --git a/dom/tests/mochitest/general/fail.png b/dom/tests/mochitest/general/fail.png Binary files differnew file mode 100644 index 0000000000..db812bd7d5 --- /dev/null +++ b/dom/tests/mochitest/general/fail.png diff --git a/dom/tests/mochitest/general/file_bug628069.html b/dom/tests/mochitest/general/file_bug628069.html new file mode 100644 index 0000000000..9fd28888c9 --- /dev/null +++ b/dom/tests/mochitest/general/file_bug628069.html @@ -0,0 +1,16 @@ +<html> +<body onhashchange='hashchange(event)' onload='load()'> + +<script> +function hashchange(e) { + (opener || parent).childHashchange(e); +} + +function load() { + (opener || parent).childLoad(); +} +</script> + +Just a shell of a page. +</body> +</html> diff --git a/dom/tests/mochitest/general/file_clonewrapper.html b/dom/tests/mochitest/general/file_clonewrapper.html new file mode 100644 index 0000000000..18e0505d86 --- /dev/null +++ b/dom/tests/mochitest/general/file_clonewrapper.html @@ -0,0 +1,35 @@ +<!doctype html> +<html> +<head> +<script type="application/javascript"> + + function waitForMessage() { + return new Promise(function(resolve) { + window.addEventListener('message', function(evt) { + resolve(evt.data); + }, {once: true}); + }); + } + + // Set up the objects for cloning. + function setup() { + window.testObject = { myNumber: 42, + myString: "hello", + myImageData: new ImageData(10, 10) }; + } + + // Called by the chrome parent window. + function tryToClone(obj, shouldSucceed, message) { + var success = false; + try { window.postMessage(obj, '*'); success = true; } + catch (e) { message = message + ' (threw: ' + e.message + ')'; } + is(success, shouldSucceed, message); + return (success && shouldSucceed) ? waitForMessage() : Promise.resolve(); + } + +</script> +</head> +<body onload="setup()"> +<input id="fileinput" type="file"></input> +</body> +</html> diff --git a/dom/tests/mochitest/general/file_domWindowUtils_scrollbarSize.html b/dom/tests/mochitest/general/file_domWindowUtils_scrollbarSize.html new file mode 100644 index 0000000000..1959fc4f68 --- /dev/null +++ b/dom/tests/mochitest/general/file_domWindowUtils_scrollbarSize.html @@ -0,0 +1,7 @@ +<!DOCTYPE HTML> +<html> +<body style='width: 100000px; overflow: hidden;'></body> + <div id="float" style="float: left; overflow: scroll;"> + <div style="width: 200px;"></div> + </div> +</html> diff --git a/dom/tests/mochitest/general/file_focusrings.html b/dom/tests/mochitest/general/file_focusrings.html new file mode 100644 index 0000000000..74f3a563ed --- /dev/null +++ b/dom/tests/mochitest/general/file_focusrings.html @@ -0,0 +1,5 @@ +<html><style> +* { outline: none; -moz-appearance: none; min-width:10px; min-height:10px; } +#elem:focus { outline: 2px solid red; } +#elem:-moz-focusring { outline: 1px solid blue; } +</style><div id='container'></html> diff --git a/dom/tests/mochitest/general/file_frameElementWrapping.html b/dom/tests/mochitest/general/file_frameElementWrapping.html new file mode 100644 index 0000000000..44237f7e04 --- /dev/null +++ b/dom/tests/mochitest/general/file_frameElementWrapping.html @@ -0,0 +1,32 @@ +<html> + <script> + function check(elt, expectAccess, prop) { + var access = false; + try { + elt[prop]; + access = true; + } + catch (e) {} + return access === expectAccess; + } + + function sendMessage(success, sameOrigin, prop) { + var result = success ? 'PASS' : 'FAIL'; + var message; + if (sameOrigin) + message = 'Can access |' + prop + '| if same origin'; + else + message = 'Cannot access |' + prop + '| if not same origin'; + parent.postMessage(result + ',' + message, '*'); + } + + var sameOrigin = location.host !== 'example.org'; + var pass = check(frameElement, sameOrigin, 'src'); + if (!pass) { + sendMessage(false, sameOrigin, 'src'); + } else { + pass = check(parent.location, sameOrigin, 'href'); + sendMessage(pass, sameOrigin, 'href'); + } + </script> +</html> diff --git a/dom/tests/mochitest/general/file_moving_nodeList.html b/dom/tests/mochitest/general/file_moving_nodeList.html new file mode 100644 index 0000000000..2456c6e689 --- /dev/null +++ b/dom/tests/mochitest/general/file_moving_nodeList.html @@ -0,0 +1,31 @@ +<html> + <head> + <script> + document.childNodes.expando = "foo"; + + function getNodeList() { + return document.childNodes; + } + function getOptions() { + return document.createElement("select").options; + } + + function tryToUseNodeList(nodeList, ok) { + function expectException(op, reason) { + try { + var result = op(); + ok(false, "should have thrown an exception, got: " + result); + } catch (e) { + ok(/Permission denied/.test(e.toString()), reason); + } + } + + expectException(function() { nodeList.length = 2; }, "should not be able to set attributes"); + expectException(function() { nodeList.item(0); }, "should not have access to any functions"); + expectException(function() { nodeList.foo = "foo"; }, "should not be able to add expandos"); + } + </script> + </head> + <body> + </body> +</html> diff --git a/dom/tests/mochitest/general/file_moving_xhr.html b/dom/tests/mochitest/general/file_moving_xhr.html new file mode 100644 index 0000000000..ee09c2bd10 --- /dev/null +++ b/dom/tests/mochitest/general/file_moving_xhr.html @@ -0,0 +1,28 @@ +<html> + <head> + <script> + function createXHR() { + var xhr = new XMLHttpRequest(); + xhr.expando = "foo"; + return xhr; + } + + function tryToUseXHR(xhr, ok) { + function expectException(op, reason) { + try { + var result = op(); + ok(false, "should have thrown an exception, got: " + result); + } catch (e) { + ok(/Permission denied/.test(e.toString()), reason); + } + } + + expectException(function() { xhr.open(); }, "should not have access to any functions"); + expectException(function() { xhr.foo = "foo"; }, "should not be able to add expandos"); + expectException(function() { xhr.withCredentials = true; }, "should not be able to set attributes"); + } + </script> + </head> + <body> + </body> +</html> diff --git a/dom/tests/mochitest/general/file_resource_timing_nocors.html b/dom/tests/mochitest/general/file_resource_timing_nocors.html new file mode 100644 index 0000000000..631251c2b1 --- /dev/null +++ b/dom/tests/mochitest/general/file_resource_timing_nocors.html @@ -0,0 +1,191 @@ +<!-- + Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ +--> + +<!DOCTYPE html> +<html> +<head> + <meta charset="utf-8"> + <link rel="stylesheet" type="text/css" href="http://mochi.test:8888/tests/SimpleTest/test.css?resource-timing-nocors"/> + + <!-- + This document has the origin http://mochi.test:8888. + + The resource timing of any all sub-resources should be reported, except for + any sub-resources of any cross-origin resources that are loaded. + + Note that the resource timing of the original cross-origin resource should + itself be reported. The goal here is to not reveal which sub-resources are + loaded by any cross-origin resources (the fact that any given cross-origin + resource itself is loaded is known to the original origin). + + In the comments below, the following annotations apply: + + [r] - this resource should be reported by performance.getEntries() + [!] - this resource should be excluded from performance.getEntries() + --> + + + <!-- 1. [r] http://mochi.test:8888 , generateCss.sjs?A + [r] http://mochi.test:8888 , generateCss.sjs?B + --> + <link rel="stylesheet" type="text/css" href="generateCss.sjs?A"/> + + + <!-- 2. [r] http://example.com , generateCss.sjs?C + [!] http://example.com , generateCss.sjs?D + --> + <link rel="stylesheet" type="text/css" href="http://example.com/tests/dom/tests/mochitest/general/generateCss.sjs?C"/> + + + <!-- 3. [r] http://example.com , generateCss.sjs?E + [!] http://mochi.test:8888 , generateCss.sjs?F + --> + <link rel="stylesheet" type="text/css" href="http://example.com/tests/dom/tests/mochitest/general/generateCss.sjs?E"/> + + + <!-- 4. [r] http://mochi.test:8888 , generateCss.sjs?G + [r] http://mochi.test:8888 , generateCss.sjs?H + [r] http://example.com , generateCss.sjs?I + [!] http://example.com , generateCss.sjs?J + [r] http://example.org , generateCss.sjs?K + [!] http://example.org , generateCss.sjs?L + [!] http://example.org , generateCss.sjs?M + --> + <link rel="stylesheet" type="text/css" href="generateCss.sjs?G"/> + + + <!-- 5. background-image: -moz-image-rect() + [r] http://example.net , generateCss.sjs?N + [!] http://example.net , blue.png + --> + <link rel="stylesheet" type="text/css" href="http://example.net/tests/dom/tests/mochitest/general/generateCss.sjs?N"/> + + + <!-- 6. background-image: url() + [r] http://example.net , generateCss.sjs?O + [!] http://example.net , red.png + --> + <link rel="stylesheet" type="text/css" href="http://example.net/tests/dom/tests/mochitest/general/generateCss.sjs?O"/> + + + <!-- 7. @font-face + [r] http://example.net , generateCss.sjs?P + [!] http://example.net , Ahem.tff + --> + <link rel="stylesheet" type="text/css" href="http://example.net/tests/dom/tests/mochitest/general/generateCss.sjs?P"/> + + + <!-- 8. cursor: url() + [r] http://example.net , generateCss.sjs?Q + [!] http://example.net , over.png + --> + <link rel="stylesheet" type="text/css" href="http://example.net/tests/dom/tests/mochitest/general/generateCss.sjs?Q"/> + + + <!-- 9. mask: url(res.svg#mask) + TODO: I don't think this is working properly. Must fix. + [r] http://example.net , generateCss.sjs?R + [!] http://example.net , file_use_counter_svg_fill_pattern_data.svg + --> + <link rel="stylesheet" type="text/css" href="http://example.net/tests/dom/tests/mochitest/general/generateCss.sjs?R"/> + + + <!-- TODO: add test that we _do_ include subresources if the cross-origin sheet was fetched via CORS --> + + + <script type="application/javascript"> + +function ok(cond, message) { + window.opener.ok(cond, message) +} + +function is(received, expected, message) { + window.opener.is(received, expected, message); +} + +function isnot(received, notExpected, message) { + window.opener.isnot(received, notExpected, message); +} + +var allResources = { + "http://mochi.test:8888/tests/SimpleTest/test.css?resource-timing-nocors" : "link", + + // 1 + "http://mochi.test:8888/tests/dom/tests/mochitest/general/generateCss.sjs?A" : "link", + "http://mochi.test:8888/tests/dom/tests/mochitest/general/generateCss.sjs?B" : "css", + + // 2 + "http://example.com/tests/dom/tests/mochitest/general/generateCss.sjs?C" : "link", + + // 3 + "http://example.com/tests/dom/tests/mochitest/general/generateCss.sjs?E" : "link", + + // 4 + "http://mochi.test:8888/tests/dom/tests/mochitest/general/generateCss.sjs?G" : "link", + "http://mochi.test:8888/tests/dom/tests/mochitest/general/generateCss.sjs?H" : "css", + "http://example.com/tests/dom/tests/mochitest/general/generateCss.sjs?I" : "css", + "http://example.org/tests/dom/tests/mochitest/general/generateCss.sjs?K" : "css", + + // 5 + "http://example.net/tests/dom/tests/mochitest/general/generateCss.sjs?N" : "link", + + // 6 + "http://example.net/tests/dom/tests/mochitest/general/generateCss.sjs?O" : "link", + + // 7 + "http://example.net/tests/dom/tests/mochitest/general/generateCss.sjs?P" : "link", + + // 8 + "http://example.net/tests/dom/tests/mochitest/general/generateCss.sjs?Q" : "link", + + // 9 + "http://example.net/tests/dom/tests/mochitest/general/generateCss.sjs?R" : "link", +}; + +window.onload = function() { + let entries = performance.getEntriesByType('resource'); + for (let entry of entries) { + //dump(entry.name + " || "+ entry.initiatorType+ "\n"); + if (!(entry.name in allResources)) { + if (entry.name.substr(-4) == ".ttf") { + // TODO: fix hiding of font files + continue; + } + ok(false, "Did not expect timing for resource: " + entry.name); + continue; + } + let resType = allResources[entry.name]; + is(entry.initiatorType, resType, + "Expected matching initiatorType for: " + entry.name); + delete allResources[entry.name]; + } + + for (let res in allResources) { + ok(false, "Expect timing for resource: " + res); + } + + window.opener.finishTests(); +} + +</script> +</head> +<body> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=1180145" + title="Resource timing NO-CORS CSS"> + Bug #1180145 - Resource Timing NO-CORS CSS + </a> + <p id="display"></p> + <div id="content"> + </div> + + <div class="c1"> BLUE </div> + <div class="c2"> RED </div> + <div class="c3"> Font </div> + <div class="c4"> CURSOR </div> + <div class="c5"> <img id="image" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAHWSURBVHjaYvz//z8DJQAggJiQOe/fv2fv7Oz8rays/N+VkfG/iYnJfyD/1+rVq7ffu3dPFpsBAAHEAHIBCJ85c8bN2Nj4vwsDw/8zQLwKiO8CcRoQu0DxqlWrdsHUwzBAAIGJmTNnPgYa9j8UqhFElwPxf2MIDeIrKSn9FwSJoRkAEEAM0DD4DzMAyPi/G+QKY4hh5WAXGf8PDQ0FGwJ22d27CjADAAIIrLmjo+MXA9R2kAHvGBA2wwx6B8W7od6CeQcggKCmCEL8bgwxYCbUIGTDVkHDBia+CuotgACCueD3TDQN75D4xmAvCoK9ARMHBzAw0AECiBHkAlC0Mdy7x9ABNA3obAZXIAa6iKEcGlMVQHwWyjYuL2d4v2cPg8vZswx7gHyAAAK7AOif7SAbOqCmn4Ha3AHFsIDtgPq/vLz8P4MSkJ2W9h8ggBjevXvHDo4FQUQg/kdypqCg4H8lUIACnQ/SOBMYI8bAsAJFPcj1AAEEjwVQqLpAbXmH5BJjqI0gi9DTAAgDBBCcAVLkgmQ7yKCZxpCQxqUZhAECCJ4XgMl493ug21ZD+aDAXH0WLM4A9MZPXJkJIIAwTAR5pQMalaCABQUULttBGCCAGCnNzgABBgAMJ5THwGvJLAAAAABJRU5ErkJggg=="> </div> + <div class="c6"> </div> +</body> +</html> diff --git a/dom/tests/mochitest/general/frameSelectEvents.html b/dom/tests/mochitest/general/frameSelectEvents.html new file mode 100644 index 0000000000..21995c86ef --- /dev/null +++ b/dom/tests/mochitest/general/frameSelectEvents.html @@ -0,0 +1,929 @@ +<!doctype html> +<html> + <head> + <title>Testing Selection Events</title> + <script src="/tests/SimpleTest/EventUtils.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> + </head> + + <body> + <div id="normal"> + <span id="inner">A bunch of text in a span inside of a div which should be selected</span> + </div> + + <div id="ce"> + This is a random block of text + </div> + + <input type="text" id="input" value="XXXXXXXXXXXXXXXXXXX" width="200"> <br> + + <textarea id="textarea" width="200">XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX</textarea> + + <script> + // Call the testing methods from the parent window + var is = parent.is; + var ok = parent.ok; + + // spin() spins the event loop for two cycles, giving time for + // selectionchange events to be fired, and handled by our listeners. + function spin() { + return new Promise(function(a) { + parent.SimpleTest.executeSoon(function() { + parent.SimpleTest.executeSoon(a) + }); + }); + } + + /** + * @param {Node} node + */ + function isProperSelectionChangeTarget(node) { + return node === document || node === input || node === textarea; + } + + // The main test + parent.add_task(async function() { + await spin(); + + var selectstart = 0; + var selectionchange = 0; + var inputSelectionchange = 0; + var textareaSelectionchange = 0; + + var cancel = false; + var selectstartTarget = null; + + async function UpdateSelectEventsOnTextControlsPref({ selectstart, selectionchange }) { + await SpecialPowers.pushPrefEnv({ + 'set': [ + ['dom.select_events.textcontrols.selectstart.enabled', !!selectstart], + ['dom.select_events.textcontrols.selectionchange.enabled', !!selectionchange], + ] + }); + } + await UpdateSelectEventsOnTextControlsPref({ + selectstart: false, + selectionchange: false, + }); + + document.addEventListener('selectstart', function(aEvent) { + console.log("originaltarget", aEvent.originalTarget, "new", selectstartTarget); + is(aEvent.originalTarget, selectstartTarget, + "The original target of selectstart"); + selectstartTarget = null; + + console.log(selectstart); + selectstart++; + + if (cancel) { + aEvent.preventDefault(); + } + }); + document.addEventListener('selectionchange', function(aEvent) { + ok(isProperSelectionChangeTarget(aEvent.target), + "The target of selectionchange should be one of document, input, or textarea"); + console.log(selectionchange); + selectionchange++; + }); + + function elt(aId) { return document.getElementById(aId); } + function reset() { + selectstart = 0; + selectionchange = 0; + inputSelectionchange = 0; + textareaSelectionchange = 0; + cancel = false; + } + + elt("input").addEventListener('selectionchange', function(aEvent) { + is (aEvent.originalTarget, elt("input"), + "The original target of selectionchange should be the input"); + console.log(inputSelectionchange); + inputSelectionchange++; + }); + elt("textarea").addEventListener('selectionchange', function(aEvent) { + is (aEvent.originalTarget, elt("textarea"), + "The original target of selectionchange should be the textarea"); + console.log(textareaSelectionchange); + textareaSelectionchange++; + }); + + function checkEventCounts( + aTestDescription, + aSituationDescription, + aExpectedEventCounts + ) { + let { + selectstartOnDocument = 0, + selectionchangeOnDocument = 0, + selectionchangeOnInput = 0, + selectionchangeOnTextarea = 0, + } = aExpectedEventCounts; + + is( + selectstart, + selectstartOnDocument, + `${ + aTestDescription + }: "selectstart" event on the document node should be fired ${ + selectstartOnDocument + } times ${aSituationDescription}` + ); + is( + selectionchange, + selectionchangeOnDocument, + `${ + aTestDescription + }: "selectionchange" event on the document node should be fired ${ + selectionchangeOnDocument + } times ${aSituationDescription}` + ); + is( + inputSelectionchange, + selectionchangeOnInput, + `${ + aTestDescription + }: "selectionchange" event on the <input> should be fired ${ + selectionchangeOnInput + } times ${aSituationDescription}` + ); + is( + textareaSelectionchange, + selectionchangeOnTextarea, + `${ + aTestDescription + }: "selectionchange" event on the <textarea> should be fired ${ + selectionchangeOnTextarea + } times ${aSituationDescription}` + ); + } + + async function testWithSynthesizingMouse( + aDescription, + aElement, + aOffset, + aType, + aExpectedEventCounts + ) { + const eventObject = aType == "click" ? {} : { type: aType }; + if (aOffset.y === undefined || aOffset.y === null) { + aOffset.y = 10; + } + synthesizeMouse(aElement, aOffset.x, aOffset.y, eventObject); + await spin(); + + checkEventCounts( + aDescription, + `after synthesizing ${aType} at ${aOffset.x}, ${aOffset.y}`, + aExpectedEventCounts + ); + reset(); + } + + async function testWithSynthesizingKey( + aDescription, + aKey, + aEventObject, + aExpectedEventCounts + ) { + synthesizeKey(aKey, aEventObject); + await spin(); + + checkEventCounts( + aDescription, + `after synthesizing a key press of "${aKey}"`, + aExpectedEventCounts + ); + reset(); + } + + async function testWithSettingContentEditableAttribute( + aDescription, + aElement, + aContentEditableValue, + aExpectedEventCounts + ) { + aElement.setAttribute("contenteditable", + aContentEditableValue ? "true" : "false"); + await spin(); + + checkEventCounts( + aDescription, + `after setting contenteditable attribute to ${ + aElement.getAttribute("contenteditable") + }`, + aExpectedEventCounts + ); + reset(); + } + + var selection = document.getSelection(); + function isCollapsed() { + is(selection.isCollapsed, true, "Selection is collapsed"); + } + function isNotCollapsed() { + is(selection.isCollapsed, false, "Selection is not collapsed"); + } + + // Make sure setting the element to contentEditable doesn't cause any selectionchange events + await testWithSettingContentEditableAttribute( + "Setting contenteditable attribute to true of <div> should not change selection", + elt("ce"), + true, + {} + ); + + // Make sure setting the element to not be contentEditable doesn't cause any selectionchange events + await testWithSettingContentEditableAttribute( + 'Setting contenteditable attribute to false of <div contenteditable="true"> should not change selection', + elt("ce"), + false, + {} + ); + + // Now make the div contentEditable and proceed with the test + await testWithSettingContentEditableAttribute( + 'Setting contenteditable attribute to true of <div contenteditable="false"> should not change selection', + elt("ce"), + true, + {} + ); + + // Focus the contenteditable text + await testWithSynthesizingMouse( + 'Clicking in <div contenteditable="true"> should change selection', + elt("ce"), + { x: 100 }, + "click", + { selectionchangeOnDocument: 1 } + ); + isCollapsed(); + + // Move the selection to the right, this should only fire selectstart once + selectstartTarget = elt("ce").firstChild; + await testWithSynthesizingKey( + 'Synthesizing Shift-ArrowRight to select a character in the text node of <div contenteditable="true"> should start to select again and change selection', + "KEY_ArrowRight", + { shiftKey: true }, + { selectstartOnDocument: 1, selectionchangeOnDocument: 1 } + ); + isNotCollapsed(); + await testWithSynthesizingKey( + 'Synthesizing Shift-ArrowRight again to select 2 characters in the text node of <div contenteditable="true"> should change selection', + "KEY_ArrowRight", + { shiftKey: true }, + { selectionchangeOnDocument: 1 } + ); + isNotCollapsed(); + + // Move it back so that the selection is empty again + await testWithSynthesizingKey( + 'Synthesizing Shift-ArrowLeft to shrink selection in the text node of <div contenteditable="true"> should change selection', + "KEY_ArrowLeft", + { shiftKey: true }, + { selectionchangeOnDocument: 1 } + ); + isNotCollapsed(); + await testWithSynthesizingKey( + 'Synthesizing Shift-ArrowLeft again to collapse selection in the text node of <div contenteditable="true"> should change selection', + "KEY_ArrowLeft", + { shiftKey: true }, + { selectionchangeOnDocument: 1 } + ); + isCollapsed(); + + // Going from empty to non-empty should fire selectstart again + selectstartTarget = elt("ce").firstChild; + await testWithSynthesizingKey( + 'Synthesizing Shift-ArrowLeft again to select a character on the other side in the text node of <div contenteditable="true"> should start to select and change selection', + "KEY_ArrowLeft", + { shiftKey: true }, + { selectstartOnDocument: 1, selectionchangeOnDocument: 1 } + ); + isNotCollapsed(); + + async function testWithSynthesizingMouseDrag( + aDescription, + aElement, + aSelectstartTarget + ) { + // Select a region + await testWithSynthesizingMouse( + `Pressing left mouse button ${ + aDescription + } should not start to select but should change selection`, + aElement, + { x: 50 }, + "mousedown", + { selectionchangeOnDocument: 1 } + ); + isCollapsed(); + + selectstartTarget = aSelectstartTarget; + await testWithSynthesizingMouse( + `Dragging mouse to right to extend selection ${ + aDescription + } should start to select and change selection`, + aElement, + { x: 100 }, + "mousemove", + { selectstartOnDocument: 1, selectionchangeOnDocument: 1 } + ); + isNotCollapsed(); + + // Moving it more shouldn't trigger a start (move back to empty) + await testWithSynthesizingMouse( + `Dragging mouse to left to shrink selection ${ + aDescription + } should change selection`, + aElement, + { x: 75 }, + "mousemove", + { selectionchangeOnDocument: 1 } + ); + isNotCollapsed(); + await testWithSynthesizingMouse( + `Dragging mouse to left to collapse selection ${ + aDescription + } should change selection`, + aElement, + { x: 50 }, + "mousemove", + { selectionchangeOnDocument: 1 } + ); + isCollapsed(); + + // Wiggling the mouse a little such that it doesn't select any + // characters shouldn't trigger a selection + await testWithSynthesizingMouse( + `Dragging mouse to bottom a bit ${ + aDescription + } should not cause selection change`, + aElement, + { x: 50, y: 11 }, + "mousemove", + {} + ); + isCollapsed(); + + // Moving the mouse again from an empty selection should trigger a + // selectstart + selectstartTarget = aSelectstartTarget; + await testWithSynthesizingMouse( + `Dragging mouse to left to extend selection ${ + aDescription + } should start to select and change selection`, + aElement, + { x: 25 }, + "mousemove", + { selectstartOnDocument: 1, selectionchangeOnDocument: 1 } + ); + isNotCollapsed(); + + // Releasing the mouse shouldn't do anything + await testWithSynthesizingMouse( + `Releasing left mouse button to stop dragging ${ + aDescription + } should not change selection`, + aElement, + { x: 25 }, + "mouseup", + {} + ); + isNotCollapsed(); + + // And neither should moving your mouse around when the mouse + // button isn't pressed + await testWithSynthesizingMouse( + `Just moving mouse to right ${ + aDescription + } should not start to select nor change selection`, + aElement, + { x: 50 }, + "mousemove", + {} + ); + isNotCollapsed(); + + // Clicking in an random location should move the selection, but not perform a + // selectstart + await testWithSynthesizingMouse( + `Clicking to collapse selection ${ + aDescription + } should cause only selection change`, + aElement, + { x: 50 }, + "click", + { selectionchangeOnDocument: 1 } + ); + isCollapsed(); + + // Clicking there again should do nothing + await testWithSynthesizingMouse( + `Clicking same position again ${ + aDescription + } should not change selection`, + aElement, + { x: 50 }, + "click", + {} + ); + isCollapsed(); + + // Selecting a region, and canceling the selectstart should mean that the + // selection remains collapsed + await testWithSynthesizingMouse( + `Pressing left mouse button on different character to move caret ${ + aDescription + } should cause only selection change`, + aElement, + { x: 75 }, + "mousedown", + { selectionchangeOnDocument: 1 } + ); + isCollapsed(); + cancel = true; + selectstartTarget = aSelectstartTarget; + await testWithSynthesizingMouse( + `Moving mouse to right to extend selection but selectstart event will be prevented default ${ + aDescription + } should start to select and change selection`, + aElement, + { x: 100 }, + "mousemove", + { selectstartOnDocument: 1, selectionchangeOnDocument: 1 } + ); + isCollapsed(); + await testWithSynthesizingMouse( + `Releasing the left mouse button after dragging but selectstart was prevented the default ${ + aDescription + } should not change selection`, + aElement, + { x: 100 }, + "mouseup", + {} + ); + isCollapsed(); + } + + // Should work both on normal + await testWithSynthesizingMouseDrag( + "on the text node in the non-editable <div>", + elt("inner"), + elt("inner").firstChild + ); + // and contenteditable fields + await testWithSynthesizingMouseDrag( + 'on the text node in the editable <div contenteditable="true">', + elt("ce"), + elt("ce").firstChild + ); + // and fields with elements in them + await testWithSynthesizingMouseDrag( + "on the text node in the non-editable <div>'s child", + elt("normal"), + elt("inner").firstChild + ); + + await testWithSynthesizingMouse( + "Clicking in the text node in the `<div>` should change selection", + elt("inner"), + { x: 50 }, + "click", + { selectionchangeOnDocument: 1 } + ); + isCollapsed(); + + reset(); + // Select all should fire both selectstart and change + selectstartTarget = document.body; + await testWithSynthesizingKey( + "Select All when no editor has focus should start to select and select all content", + "a", { accelKey: true }, + { selectstartOnDocument: 1, selectionchangeOnDocument: 1 } + ); + isNotCollapsed(); + + // Clear the selection + await testWithSynthesizingMouse( + "Clicking in the non-editable <div> should clear selection", + elt("inner"), + { x: 50 }, + "click", + { selectionchangeOnDocument: 1 } + ); + isCollapsed(); + + // Even if we already have a selection + await testWithSynthesizingMouse( + "Pressing the left mouse button in non-editable <div> should change selection", + elt("inner"), + { x: 75 }, + "mousedown", + { selectionchangeOnDocument: 1 } + ); + isCollapsed(); + selectstartTarget = elt("inner").firstChild; + await testWithSynthesizingMouse( + "Dragging mouse to right to extend selection should start and change selection", + elt("inner"), + { x: 100 }, + "mousemove", + { selectstartOnDocument: 1, selectionchangeOnDocument: 1 } + ); + isNotCollapsed(); + await testWithSynthesizingMouse( + "Releasing the left mouse button should not change selection", + elt("inner"), + { x: 100 }, + "mouseup", + {} + ); + isNotCollapsed(); + + selectstartTarget = document.body; + await testWithSynthesizingKey( + "Select All when no editor has focus should start to select and select all content (again)", + "a", + { accelKey: true }, + { selectstartOnDocument: 1, selectionchangeOnDocument: 1 } + ); + isNotCollapsed(); + + // Clear the selection + await testWithSynthesizingMouse( + "Clicking in the non-editable <div> should clear selection (again)", + elt("inner"), + { x: 50 }, + "click", + {selectionchangeOnDocument: 1 } + ); + isCollapsed(); + + // Make sure that a synthesized selection change doesn't fire selectstart + getSelection().removeAllRanges(); + await spin(); + is( + selectstart, + 0, + "Selection.removeAllRanges() should not cause selectstart event" + ); + is( + selectionchange, + 1, + "Selection.removeAllRanges() should cause selectionchange event" + ); + reset(); + isCollapsed(); + + await (async function test_Selection_selectNode() { + const range = document.createRange(); + range.selectNode(elt("inner")); + getSelection().addRange(range); + await spin(); + is( + selectstart, + 0, + "Selection.addRange() should not cause selectstart event" + ); + is( + selectionchange, + 1, + "Selection.addRange() should cause selectionchange event" + ); + reset(); + isNotCollapsed(); + })(); + + // Change the range, without replacing + await (async function test_Selection_getRangeAt_selectNode() { + getSelection().getRangeAt(0).selectNode(elt("ce")); + await spin(); + is( + selectstart, + 0, + "Selection.getRangeAt(0).selectNode() should not cause selectstart event" + ); + is( + selectionchange, + 1, + "Selection.getRangeAt(0).selectNode() should cause selectionchange event" + ); + reset(); + isNotCollapsed(); + })(); + + // Remove the range + getSelection().removeAllRanges(); + await spin(); + is( + selectstart, + 0, + "Selection.removeAllRanges() should not cause selectstart event (again)" + ); + is( + selectionchange, + 1, + "Selection.removeAllRanges() should cause selectionchange event (again)" + ); + reset(); + isCollapsed(); + + for (const textControl of [elt("input"), elt("textarea")]) { + await UpdateSelectEventsOnTextControlsPref({ + selectstart: false, + selectionchange: false, + }); + + // Without the dom.select_events.textcontrols.enabled pref, + // pressing the mouse shouldn't do anything. + await testWithSynthesizingMouse( + `Pressing the left mouse button in <${ + textControl.tagName.toLocaleLowerCase() + }> should change selection of the document`, + textControl, + { x: 50 }, + "mousedown", + { + // XXX Bug 1721287: For some reason we fire 2 selectchange events + // on the body when switching from the <input> to the <textarea>. + selectionchangeOnDocument: + document.activeElement != textControl && + (document.activeElement.tagName.toLocaleLowerCase() == "input" || + document.activeElement.tagName.toLocaleLowerCase() == "textarea") + ? 2 + : 1, + } + ); + + // Releasing the mouse shouldn't do anything + await testWithSynthesizingMouse( + `Releasing the left mouse button in <${ + textControl.tagName.toLocaleLowerCase() + }> should not change any selection`, + textControl, + { x: 50 }, + "mouseup", + {} + ); + + for (const selectstart of [1, 0]) { + await UpdateSelectEventsOnTextControlsPref({ + selectstart, + selectionchange: true, + }); + + const selectstartEventSetting = `selectstart in text controls is ${ + selectstart ? "enabled" : "disabled" + }`; + + const isInput = textControl.tagName.toLocaleLowerCase() == "input"; + + await testWithSynthesizingMouse( + `Pressing the left mouse button in <${ + textControl.tagName.toLocaleLowerCase() + }> should change selection (${selectstartEventSetting})`, + textControl, + { x: 40 }, + "mousedown", + { + selectionchangeOnDocument: 1, + selectionchangeOnInput: isInput ? 1 : 0, + selectionchangeOnTextarea: isInput ? 0 : 1, + } + ); + + selectstartTarget = textControl; + await testWithSynthesizingMouse( + `Dragging mouse to right to extend selection in <${ + textControl.tagName.toLocaleLowerCase() + }> should start to select and change selection (${ + selectstartEventSetting + })`, + textControl, + { x: 100 }, + "mousemove", + { + selectstartOnDocument: selectstart, + selectionchangeOnDocument: 1, + selectionchangeOnInput: isInput ? 1 : 0, + selectionchangeOnTextarea: isInput ? 0 : 1, + } + ); + + // Moving it more shouldn't trigger a start (move back to empty) + await testWithSynthesizingMouse( + `Dragging mouse to left to shrink selection in <${ + textControl.tagName.toLocaleLowerCase() + }> should change selection (${selectstartEventSetting})`, + textControl, + { x: 75 }, + "mousemove", + { + selectionchangeOnDocument: 1, + selectionchangeOnInput: isInput ? 1 : 0, + selectionchangeOnTextarea: isInput ? 0 : 1, + } + ); + await testWithSynthesizingMouse( + `Dragging mouse to left to collapse selection in <${ + textControl.tagName.toLocaleLowerCase() + }> should change selection (${selectstartEventSetting})`, + textControl, + { x: 40 }, + "mousemove", + { + selectionchangeOnDocument: 1, + selectionchangeOnInput: isInput ? 1 : 0, + selectionchangeOnTextarea: isInput ? 0 : 1, + } + ); + + // Wiggling the mouse a little such that it doesn't select any + // characters shouldn't trigger a selection + await testWithSynthesizingMouse( + `Pressing the left mouse button at caret in <${ + textControl.tagName.toLocaleLowerCase() + }> should not change selection (${selectstartEventSetting})`, + textControl, + { + x: 40, + y: 11, + }, + "mousemove", + {} + ); + + // Moving the mouse again from an empty selection should trigger a + // selectstart + selectstartTarget = textControl; + await testWithSynthesizingMouse( + `Dragging mouse to left to extend selection in <${ + textControl.tagName.toLocaleLowerCase() + }> should start to select and change selection (${ + selectstartEventSetting + })`, + textControl, + { x: 25 }, + "mousemove", + { + selectstartOnDocument: selectstart, + selectionchangeOnDocument: 1, + selectionchangeOnInput: isInput ? 1 : 0, + selectionchangeOnTextarea: isInput ? 0 : 1, + } + ); + + // Releasing the mouse shouldn't do anything + await testWithSynthesizingMouse( + `Releasing the left mouse button in <${ + textControl.tagName.toLocaleLowerCase() + }> should not change selection (${selectstartEventSetting})`, + textControl, + { x: 25 }, + "mouseup", + {} + ); + + // And neither should moving your mouse around when the mouse + // button isn't pressed + await testWithSynthesizingMouse( + `Just moving mouse to right in <${ + textControl.tagName.toLocaleLowerCase() + }> should not start to select nor change selection (${ + selectstartEventSetting + })`, + textControl, + { x: 50 }, + "mousemove", + {} + ); + + // Clicking in an random location should move the selection, but + // not perform a selectstart + await testWithSynthesizingMouse( + `Clicking in <${ + textControl.tagName.toLocaleLowerCase() + }> should change selection, but should not start selection (${ + selectstartEventSetting + })`, + textControl, + { x: 50 }, + "click", + { + selectionchangeOnDocument: 1, + selectionchangeOnInput: isInput ? 1 : 0, + selectionchangeOnTextarea: isInput ? 0 : 1, + } + ); + + // Clicking there again should do nothing + await testWithSynthesizingMouse( + `Clicking at caret in <${ + textControl.tagName.toLocaleLowerCase() + }> should not change selection (${selectstartEventSetting})`, + textControl, + { x: 50 }, + "click", + {} + ); + + // Selecting a region, and canceling the selectstart should mean that the + // selection remains collapsed + await testWithSynthesizingMouse( + `Pressing the left mouse button at different character in <${ + textControl.tagName.toLocaleLowerCase() + }> should change selection (${selectstartEventSetting})`, + textControl, + { x: 75 }, + "mousedown", + { + selectionchangeOnDocument: 1, + selectionchangeOnInput: isInput ? 1 : 0, + selectionchangeOnTextarea: isInput ? 0 : 1, + } + ); + cancel = true; + selectstartTarget = textControl; + await testWithSynthesizingMouse( + `Dragging mouse to right to extend selection in <${ + textControl.tagName.toLocaleLowerCase() + }> but the default of selectstart is prevented should cause selectstart and selectionchange events (${ + selectstartEventSetting + })`, + textControl, + { x: 100 }, + "mousemove", + { + selectstartOnDocument: selectstart, + selectionchangeOnDocument: 1, + selectionchangeOnInput: isInput ? 1 : 0, + selectionchangeOnTextarea: isInput ? 0 : 1, + } + ); + await testWithSynthesizingMouse( + `Releasing the left mouse button in <${ + textControl.tagName.toLocaleLowerCase() + }> should not cause changing selection (${selectstartEventSetting})`, + textControl, + { x: 100 }, + "mouseup", + {} + ); + } + } + + // Marking the input and textarea as display: none and then as visible again + // shouldn't trigger any changes, although the nodes will be re-framed + for (const textControl of [elt("input"), elt("textarea")]) { + await (async function test_set_display_of_text_control_to_none() { + textControl.setAttribute("style", "display: none;"); + await spin(); + checkEventCounts( + `Setting display of <${ + textControl.tagName.toLocaleLowerCase() + }> to none`, + "", + {} + ); + reset(); + })(); + + await (async function test_remove_display_none_of_text_control() { + textControl.setAttribute("style", ""); + await spin(); + checkEventCounts( + `Removing display:none of <${ + textControl.tagName.toLocaleLowerCase() + }>`, + "", + {} + ); + reset(); + })(); + } + + // When selection is at the end of contentEditable's content, + // clearing the content should trigger selection events. + await (async function test_removing_contenteditable() { + const savedContent = elt("ce").innerHTML; + document.getSelection().setBaseAndExtent(elt("ce"), 1, elt("ce"), 1); + await spin(); + reset(); + + elt("ce").firstChild.remove(); + await spin(); + checkEventCounts( + 'Removing <div contenteditable="true"> from the DOM tree', + "", + { selectionchangeOnDocument: 1 } + ); + + elt("ce").innerHTML = savedContent; + await spin(); + reset(); + })(); + }); + </script> + </body> +</html> diff --git a/dom/tests/mochitest/general/frameStorageAllowed.html b/dom/tests/mochitest/general/frameStorageAllowed.html new file mode 100644 index 0000000000..bb96392398 --- /dev/null +++ b/dom/tests/mochitest/general/frameStorageAllowed.html @@ -0,0 +1,22 @@ +<html xmlns="http://www.w3.org/1999/xhtml"> +<head> +<title>frame for storage allowed test</title> + +<script type="text/javascript" src="https://example.com/tests/dom/tests/mochitest/general/storagePermissionsUtils.js"></script> +<script type="text/javascript"> + + task(async function() { + // We should be able to access storage + await storageAllowed(); + + // We should be able to run a web worker which can access storage + await runWorker("workerStorageAllowed.js"); + }); + +</script> + +</head> + +<body> +</body> +</html> diff --git a/dom/tests/mochitest/general/frameStorageChrome.html b/dom/tests/mochitest/general/frameStorageChrome.html new file mode 100644 index 0000000000..51f4fa85e6 --- /dev/null +++ b/dom/tests/mochitest/general/frameStorageChrome.html @@ -0,0 +1,20 @@ +<html xmlns="http://www.w3.org/1999/xhtml"> +<head> +<title>frame for storage allowed test</title> + +<script type="text/javascript" src="https://example.com/tests/dom/tests/mochitest/general/storagePermissionsUtils.js"></script> +<script type="text/javascript"> + + task(async function() { + // We just check if we can access storage as chrome using special powers! + var params = new URLSearchParams(location.search.substr(1)); + await chromePower(params.get('allowed') == 'yes', params.get('blockSessionStorage') == 'yes'); + }); + +</script> + +</head> + +<body> +</body> +</html> diff --git a/dom/tests/mochitest/general/frameStorageNullprincipal.sjs b/dom/tests/mochitest/general/frameStorageNullprincipal.sjs new file mode 100644 index 0000000000..7dfdc32d1e --- /dev/null +++ b/dom/tests/mochitest/general/frameStorageNullprincipal.sjs @@ -0,0 +1,40 @@ +// This is a sjs file which reads in frameStoragePrevented.html, and writes it out as a data: URI, which this page redirects to. +// This produces a URI with the null principal, which should be unable to access storage. +// We append the #nullprincipal hash to the end of the data: URI to tell the script that it shouldn't try to spawn a webworker, +// as it won't be allowed to, as it has a null principal. + +function handleRequest(request, response) { + // Get the nsIFile for frameStoragePrevented.html + var file; + getObjectState("SERVER_ROOT", function (serverRoot) { + file = serverRoot.getFile( + "/tests/dom/tests/mochitest/general/frameStoragePrevented.html" + ); + }); + + // Set up the file streams to read in the file as UTF-8 + let fstream = Components.classes[ + "@mozilla.org/network/file-input-stream;1" + ].createInstance(Components.interfaces.nsIFileInputStream); + fstream.init(file, -1, 0, 0); + let cstream = Components.classes[ + "@mozilla.org/intl/converter-input-stream;1" + ].createInstance(Components.interfaces.nsIConverterInputStream); + cstream.init(fstream, "UTF-8", 0, 0); + + // Read in the file, and concatenate it onto the data string + let data = ""; + let str = {}; + let read = 0; + do { + read = cstream.readString(0xffffffff, str); + data += str.value; + } while (read != 0); + + // Write out the file as a data: URI, and redirect to it + response.setStatusLine("1.1", 302, "Found"); + response.setHeader( + "Location", + "data:text/html," + encodeURIComponent(data) + "#nullprincipal" + ); +} diff --git a/dom/tests/mochitest/general/frameStoragePrevented.html b/dom/tests/mochitest/general/frameStoragePrevented.html new file mode 100644 index 0000000000..693beff5a4 --- /dev/null +++ b/dom/tests/mochitest/general/frameStoragePrevented.html @@ -0,0 +1,47 @@ + +<html xmlns="http://www.w3.org/1999/xhtml"> +<head> +<title>frame for storage prevented test</title> + +<script type="text/javascript" src="https://example.com/tests/dom/tests/mochitest/general/storagePermissionsUtils.js"></script> +<script type="text/javascript"> + + task(async function() { + // We shouldn't be able to access storage + await storagePrevented(); + + // This hash of the URI is set to #nullprincipal by the test if the current page has a null principal, + // and thus attempting to create a dedicated worker will throw + if (location.hash == "#nullprincipal") { + function createWorker() { + return new Promise((resolve, reject) => { + var w; + try { + w = new Worker("workerStoragePrevented.js"); + } catch (e) { + ok(true, "Running workers was prevented"); + resolve(); + } + + w.onerror = function() { + ok(true, "Running workers was prevented"); + resolve(); + } + }); + } + + await createWorker(); + return; + } + + // Try to run a worker, which shouldn't be able to access storage + await runWorker("workerStoragePrevented.js"); + }); + +</script> + +</head> + +<body> +</body> +</html> diff --git a/dom/tests/mochitest/general/generateCss.sjs b/dom/tests/mochitest/general/generateCss.sjs new file mode 100644 index 0000000000..d6b4348aa3 --- /dev/null +++ b/dom/tests/mochitest/general/generateCss.sjs @@ -0,0 +1,42 @@ +function handleRequest(request, response) { + response.setHeader("Content-Type", "text/css", false); + response.write(gResponses[request.queryString]); +} + +let gResponses = { + // 1 + A: "@import 'generateCss.sjs?B';", + B: "", + + // 2 + C: "@import 'generateCss.sjs?D';", + D: "", + + // 3 + E: "@import 'generateCss.sjs?F';", + F: "", + + // 4 + G: "@import 'generateCss.sjs?H'; @import 'http://example.org/tests/dom/tests/mochitest/general/generateCss.sjs?K';", + H: "@import 'http://example.com/tests/dom/tests/mochitest/general/generateCss.sjs?I';", + I: "@import 'generateCss.sjs?J", + J: "", + K: "@import 'generateCss.sjs?L';", + L: "@import 'generateCss.sjs?M", + M: "", + + // 5 + N: ".c1 { background-image: -moz-image-rect(url('/image/test/mochitest/blue.png'), 0, 0, 200, 200);}", + + // 6 + O: ".c2 { background-image: url('/image/test/mochitest/red.png');}", + + // 7 + P: "@font-face { font-family: Ahem; src: url('/tests/dom/base/test/Ahem.ttf'); } .c3 { font-family: Ahem; font-size: 20px; }", + + // 8 + Q: ".c4 { cursor: url('/image/test/mochitest/over.png') 2 2, auto; } ", + + // 9 + R: "#image { mask: url('/tests/dom/base/test/file_use_counter_svg_fill_pattern_data.svg'); }", +}; diff --git a/dom/tests/mochitest/general/historyframes.html b/dom/tests/mochitest/general/historyframes.html new file mode 100644 index 0000000000..f0f7e934ae --- /dev/null +++ b/dom/tests/mochitest/general/historyframes.html @@ -0,0 +1,179 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=602256 +--> +<head> + <title>Test for Bug 602256</title> +</head> +<body onload="SimpleTest.executeSoon(run_test)"> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=602256">Mozilla Bug 602256</a> +<div id="content"> + <iframe id="iframe" src="start_historyframe.html"></iframe> +</div> +<pre id="test"> +<script type="application/javascript"> + +/** Test for Bug 602256 **/ + +var testWin = window.opener ? window.opener : window.parent; + +var SimpleTest = testWin.SimpleTest; +function is() { testWin.is.apply(testWin, arguments); } + +var gFrame = null; + +var gState = null; + +window.addEventListener("popstate", function(aEvent) { + gState = aEvent.state; +}); + +function waitForLoad() { + function listener() { + gFrame.removeEventListener("load", listener); + SimpleTest.executeSoon(continue_test); + } + + gFrame.addEventListener("load", listener); +} + +function loadContent(aURL) { + waitForLoad(); + + gFrame.src = aURL; +} + +function getURL() { + return gFrame.contentDocument.documentURI; +} + +function getContent() { + return gFrame.contentDocument.getElementById("text").textContent; +} + +var BASE_URI = "http://mochi.test:8888/tests/dom/tests/mochitest/general/"; +var START = BASE_URI + "start_historyframe.html"; +var URL1 = BASE_URI + "url1_historyframe.html"; +var URL2 = BASE_URI + "url2_historyframe.html"; + +function run_test() { + window.history.pushState("START", window.location); + + gFrame = document.getElementById("iframe"); + + continue_test(); +} + +function* test_body() +{ + yield* test_basic_inner_navigation(); + yield* test_state_navigation(); +} + +var gTestContinuation = null; + +function continue_test() { + if (!gTestContinuation) { + gTestContinuation = test_body(); + } + var ret = gTestContinuation.next(); + if (ret.done) { + testWin.done(); + } +} + +var gTestContinuation = null; +function continueAsync() { + setTimeout(function() { gTestContinuation.next(); }) +} + +function continueOnPopState() { + // Use continueAsync to avoid recursion within sync popstate listener. + window.addEventListener("popstate", continueAsync, { once: true }); +} + +function* test_basic_inner_navigation() { + // Navigate the inner frame a few times + yield loadContent(URL1); + is(getURL(), URL1, "URL should be correct"); + is(getContent(), "Test1", "Page should be correct"); + + yield loadContent(URL2); + + is(getURL(), URL2, "URL should be correct"); + is(getContent(), "Test2", "Page should be correct"); + + // Test that history is working + window.history.back(); + yield waitForLoad(); + is(getURL(), URL1, "URL should be correct"); + is(getContent(), "Test1", "Page should be correct"); + + window.history.forward(); + yield waitForLoad(); + is(getURL(), URL2, "URL should be correct"); + is(getContent(), "Test2", "Page should be correct"); +} + +function* test_state_navigation() { + window.history.pushState("STATE1", window.location); + + is(getURL(), URL2, "URL should be correct"); + is(getContent(), "Test2", "Page should be correct"); + + window.history.pushState("STATE2", window.location); + + is(getURL(), URL2, "URL should be correct"); + is(getContent(), "Test2", "Page should be correct"); + + window.history.back(); + continueOnPopState(); + yield; + + is(gState, "STATE1", "State should be correct"); + is(getURL(), URL2, "URL should be correct"); + is(getContent(), "Test2", "Page should be correct"); + + window.history.forward(); + continueOnPopState(); + yield; + + is(gState, "STATE2", "State should be correct"); + is(getURL(), URL2, "URL should be correct"); + is(getContent(), "Test2", "Page should be correct"); + + window.history.back(); + continueOnPopState(); + yield; + window.history.back(); + continueOnPopState(); + yield; + + is(gState, "START", "State should be correct"); + is(getURL(), URL2, "URL should be correct"); + is(getContent(), "Test2", "Page should be correct"); + + window.history.back(); + continueAsync(); + yield; + is(gState, "START", "State should be correct"); + yield waitForLoad(); + + is(getURL(), URL1, "URL should be correct"); + is(getContent(), "Test1", "Page should be correct"); + + window.history.back(); + continueAsync(); + yield; + is(gState, "START", "State should be correct after going back twice"); + yield waitForLoad(); + + is(gState, "START", "State should be correct"); + is(getURL(), START, "URL should be correct"); + is(getContent(), "Start", "Page should be correct"); +} +</script> +</pre> +</body> +</html> diff --git a/dom/tests/mochitest/general/image_100.png b/dom/tests/mochitest/general/image_100.png Binary files differnew file mode 100644 index 0000000000..df421453c2 --- /dev/null +++ b/dom/tests/mochitest/general/image_100.png diff --git a/dom/tests/mochitest/general/image_200.png b/dom/tests/mochitest/general/image_200.png Binary files differnew file mode 100644 index 0000000000..6f76d44387 --- /dev/null +++ b/dom/tests/mochitest/general/image_200.png diff --git a/dom/tests/mochitest/general/image_50.png b/dom/tests/mochitest/general/image_50.png Binary files differnew file mode 100644 index 0000000000..144a2f0b93 --- /dev/null +++ b/dom/tests/mochitest/general/image_50.png diff --git a/dom/tests/mochitest/general/importsSameAndCrossOrigin.css b/dom/tests/mochitest/general/importsSameAndCrossOrigin.css new file mode 100644 index 0000000000..69e239c063 --- /dev/null +++ b/dom/tests/mochitest/general/importsSameAndCrossOrigin.css @@ -0,0 +1,3 @@ +@import 'emptyCssFile2.css'; +@import url('http://example.org/tests/dom/tests/mochitest/general/emptyCssFile2.css'); + diff --git a/dom/tests/mochitest/general/mochitest.ini b/dom/tests/mochitest/general/mochitest.ini new file mode 100644 index 0000000000..1fe4ccbaec --- /dev/null +++ b/dom/tests/mochitest/general/mochitest.ini @@ -0,0 +1,182 @@ +[DEFAULT] +prefs = + dom.svg.pathSeg.enabled=false +support-files = + 497633.html + fail.png + file_bug628069.html + file_clonewrapper.html + file_domWindowUtils_scrollbarSize.html + file_frameElementWrapping.html + file_moving_nodeList.html + file_moving_xhr.html + file_resource_timing_nocors.html + generateCss.sjs + historyframes.html + start_historyframe.html + url1_historyframe.html + url2_historyframe.html + image_50.png + image_100.png + image_200.png + pass.apng + performance_timeline_main_test.html + resource_timing_iframe.html + resource_timing_main_test.html + resource_timing_cross_origin.html + res0.resource + res1.resource + res1.resource^headers^ + res2.resource + res2.resource^headers^ + res3.resource + res3.resource^headers^ + res4.resource + res4.resource^headers^ + res5.resource + res5.resource^headers^ + res6.resource + res6.resource^headers^ + res7.resource + res7.resource^headers^ + res8.resource + res8.resource^headers^ + resource_timing.js + navigation_timing.html + test_interfaces.js + frameStorageAllowed.html + frameStoragePrevented.html + frameStorageChrome.html + frameStorageNullprincipal.sjs + workerStorageAllowed.js + workerStoragePrevented.js + storagePermissionsUtils.js + window_storagePermissions.html + frameSelectEvents.html + !/image/test/mochitest/big.png + !/image/test/mochitest/blue.png + !/image/test/mochitest/clear.png + !/image/test/mochitest/damon.jpg + !/image/test/mochitest/over.png + !/image/test/mochitest/red.png + !/dom/base/test/Ahem.ttf + !/dom/base/test/file_empty.html + !/dom/base/test/file_use_counter_svg_fill_pattern_data.svg + resource_timing_location_navigate.html + resource_timing_meta_refresh.html + resource_timing_redirect.html + resource_timing_redirect.html^headers^ + embed_navigate.html + postback.html + +[test_497898.html] +[test_bug504220.html] +[test_bug628069_1.html] +[test_bug628069_2.html] +[test_bug631440.html] +[test_bug653364.html] +[test_bug861217.html] +[test_bug1161721.html] +[test_bug1170911.html] +[test_bug1208217.html] +[test_bug1313753.html] +[test_bug1434273.html] +[test_clientRects.html] +[test_clipboard_disallowed.html] +[test_clipboard_events.html] +support-files = window_clipboard_events.html +skip-if = headless # bug 1403542 +[test_consoleAPI.html] +[test_contentViewer_overrideDPPX.html] +[test_CCW_optimization.html] +[test_devicePixelRatio_with_zoom.html] +[test_DOMMatrix.html] +[test_domWindowUtils.html] +[test_domWindowUtils_scrollbarSize.html] +skip-if = + http3 +[test_domWindowUtils_scrollXY.html] +[test_donottrack.html] +[test_focus_scrollchildframe.html] +[test_focus_legend_noparent.html] +[test_for_of.html] +[test_framedhistoryframes.html] +skip-if = + http3 +[test_frameElementWrapping.html] +skip-if = + http3 +[test_img_mutations.html] +[test_interfaces.html] +skip-if = + http3 +[test_interfaces_secureContext.html] +scheme = https +[test_media_queries_with_zoom.html] +[test_navigation_timing.html] +[test_network_events.html] +skip-if = true +# Disable this test until bug 795711 is fixed. +[test_offsets.html] +support-files = test_offsets.js +[test_outerHTML.html] +[test_outerHTML.xhtml] +[test_paste_selection.html] +[test_performance_now.html] +[test_performance_timeline.html] +skip-if = + verify + http3 +[test_performance_nav_timing_before_onload.html] +[test_picture_apng.html] +[test_picture_mutations.html] +[test_pointerPreserves3D.html] +[test_pointerPreserves3DClip.html] +[test_pointerPreserves3DPerspective.html] +[test_resource_timing.html] +skip-if = + verify + http3 +[test_resource_timing_cross_origin.html] +skip-if = + http3 +[test_resource_timing_frameset.html] +skip-if = + http3 +[test_selectevents.html] +skip-if = toolkit == 'android' # bug 1627523 +[test_showModalDialog_removed.html] +[test_storagePermissionsAccept.html] +skip-if = + http3 +[test_storagePermissionsLimitForeign.html] +skip-if = + http3 +[test_storagePermissionsReject.html] +skip-if = + http3 +[test_storagePermissionsRejectForeign.html] +skip-if = + http3 +[test_stylesheetPI.html] +[test_toggling_performance_navigation_timing.html] +skip-if = + os == 'win' && bits == 64 && !debug # Bug 1730152 + os == 'mac' && bits == 64 && !debug # Bug 1730152 +[test_vibrator.html] +fail-if = xorigin +skip-if = + http3 +[test_WebKitCSSMatrix.html] +[test_windowedhistoryframes.html] +skip-if = + http3 +[test_windowProperties.html] +[test_resource_timing_nocors.html] +skip-if = + http3 +[test_resizeby.html] +skip-if = (toolkit == 'android') || (devedition && os == 'win' && bits == 32) || (os == 'linux' && bits == 64) # Window sizes cannot be controled on android; Windows: bug 1540554; Bug 1604152 +[test_resource_timing_cross_origin_navigate.html] +skip-if = + http3 diff --git a/dom/tests/mochitest/general/navigation_timing.html b/dom/tests/mochitest/general/navigation_timing.html new file mode 100644 index 0000000000..72ce60fb73 --- /dev/null +++ b/dom/tests/mochitest/general/navigation_timing.html @@ -0,0 +1,100 @@ +<!-- + Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ +--> + +<!DOCTYPE html> +<html> +<head> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> + <script type="application/javascript"> + +var timingParams = [ + "navigationStart", + "unloadEventStart", + "unloadEventEnd", + "redirectStart", + "redirectEnd", + "fetchStart", + "domainLookupStart", + "domainLookupEnd", + "connectStart", + "connectEnd", + "requestStart", + "responseStart", + "responseEnd", + "domLoading", + "domInteractive", + "domContentLoadedEventStart", + "domContentLoadedEventEnd", + "domComplete", + "loadEventStart", + "loadEventEnd" + ]; + +function is(received, expected, message) { + window.opener.is(received, expected, message); +} + +function isnot(received, notExpected, message) { + window.opener.isnot(received, notExpected, message); +} + +window.onload = function() { + if (location.href.includes("_blank")) { + test_blank(); + return; + } + + if (location.href.includes("_self")) { + test_self(); + return; + } +} + +function checkTimingValues(expectedValues) { + for (var name of timingParams) { + if (name in expectedValues) { + is(window.performance.timing[name], expectedValues[name], name+" should be "+expectedValues[name]); + } else { + isnot(window.performance.timing[name], 0, name+" should not be 0"); + } + } +} + +function test_blank() { + // We set a timeout to make sure this is run after onload is called + setTimeout(function(){ + // When loading the page in _blank, unloadEvent and redirect timestamps should be 0 + var expectedValues = { "unloadEventStart": 0, "unloadEventEnd": 0, "redirectStart": 0, "redirectEnd": 0 }; + checkTimingValues(expectedValues); + + // change location in order to test a _self load + window.location.href = "navigation_timing.html?x=1#_self"; + }, 0); +} + +function test_self() { + // We set a timeout to make sure this is run after onload is called + setTimeout(function(){ + // When simply loading in _self, redirect timestamps should be 0 (unloadEventStart/End != 0) + var expectedValues = { "redirectStart": 0, "redirectEnd": 0 }; + checkTimingValues(expectedValues); + + window.opener.finishTests(); + }, 0); +} + +</script> + +</script> +</head> +<body> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=1099092" + title="Navigation timing"> + Bug #1099092 - Navigation Timing has incorrect values when page is load via link with target=_blank attribute + </a> + <p id="display"></p> +</body> +</html> diff --git a/dom/tests/mochitest/general/pass.apng b/dom/tests/mochitest/general/pass.apng Binary files differnew file mode 100644 index 0000000000..6e78a9eef4 --- /dev/null +++ b/dom/tests/mochitest/general/pass.apng diff --git a/dom/tests/mochitest/general/performance_timeline_main_test.html b/dom/tests/mochitest/general/performance_timeline_main_test.html new file mode 100644 index 0000000000..f8094998eb --- /dev/null +++ b/dom/tests/mochitest/general/performance_timeline_main_test.html @@ -0,0 +1,106 @@ +<!-- + Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ +--> + +<!DOCTYPE html> +<html> +<head> + <meta charset="utf-8"> + <link rel="stylesheet" href="/tests/SimpleTest/test.css?performance-timeline-main-test"/> + <script type="application/javascript"> + +function ok(cond, message) { + window.opener.ok(cond, message) +} + +function is(received, expected, message) { + window.opener.is(received, expected, message); +} + +function isnot(received, notExpected, message) { + window.opener.isnot(received, notExpected, message); +} + +var receivedBufferFullEvents = 0; +window.performance.onresourcetimingbufferfull = () => { + receivedBufferFullEvents++; +} + +window.onload = () => { + // Here, we should have 4 entries (1 css, 3 png) since the image was loaded. + var nEntries = window.performance.getEntries().length; + ok(nEntries >= 4, "Performance.getEntries() returned wrong number of entries."); + + window.performance.setResourceTimingBufferSize(5); + window.performance.mark("test-start"); + window.performance.mark("test-end"); + + // The URI should be the address of a resource will be loaded later to be used on getEntriesByName. + window.performance.measure("http://mochi.test:8888/tests/dom/tests/mochitest/general/test-data2.json", + "test-start", "test-end"); + + is(window.performance.getEntries().length, nEntries + 3, "User Timing APIs should never be affected by setResourceTimingBufferSize."); + is(window.performance.getEntriesByType("resource").length, 4, "The number of PerformanceResourceTiming should be 4."); + is(window.performance.getEntriesByType("mark").length, 2, "The number of PerformanceMark entries should be 2."); + is(window.performance.getEntriesByType("measure").length, 1, "The number of PerformanceMeasure entries should be 1."); + + is(receivedBufferFullEvents, 0, "onresourcetimingbufferfull should never be called."); + + makeXhr("test-data2.json", firstCheck); +} + +function makeXhr(aUrl, aCallback) { + var xmlhttp = new XMLHttpRequest(); + xmlhttp.onload = aCallback; + xmlhttp.open("get", aUrl, true); + xmlhttp.send(); +} + +function firstCheck() { + SpecialPowers.executeSoon(() => { + is(window.performance.getEntriesByType("resource").length, 5, + "The number of PerformanceResourceTiming should be 5."); + is(receivedBufferFullEvents, 0, + "onresourcetimingbufferfull should not have been called yet."); + makeXhr("test-data2.json", secondCheck); + }, window); +} + +function secondCheck() { + SpecialPowers.executeSoon(() => { + is(window.performance.getEntriesByType("resource").length, 5, "The number of PerformanceResourceTiming should be 5."); + is(receivedBufferFullEvents, 1, "onresourcetimingbufferfull should have been called since the last call."); + checkOrder(window.performance.getEntries(), "All PerformanceEntry"); + checkOrder(window.performance.getEntriesByType("resource"), "PerformanceResourceTiming"); + checkOrder(window.performance.getEntriesByType("mark"), "PerformanceMark"); + checkOrder(window.performance.getEntriesByType("measure"), "PerformanceMeasure"); + + is(window.performance.getEntriesByName("http://mochi.test:8888/tests/dom/tests/mochitest/general/test-data2.json").length, 2, "Both PerformanceMeasure and XMLHttpRequest resource should be included."); + checkOrder(window.performance.getEntriesByName("http://mochi.test:8888/tests/dom/tests/mochitest/general/test-data2.json"), "Entry with performance.getEntrieByName()"); + window.opener.finishTests(); + }, window); +} + +function checkOrder(entries, name) { + for (var i = 0; i < entries.length - 1; i++) { + ok(entries[i].startTime <= entries[i + 1].startTime, name + " should be sorted by startTime."); + } +} + +</script> +</head> +<body> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=1158731" + title="Buffer for Performance APIs (Resource Timing, User Timing) should be separeted"> + Bug #1158731 - Buffer for Performance APIs (Resource Timing, User Timing) should be separeted + </a> + <p id="display"></p> + <div id="content"> + <img src="http://mochi.test:8888/tests/image/test/mochitest/over.png"> + <object data="http://mochi.test:8888/tests/image/test/mochitest/clear.png" type="image/png"></object> + <embed src="http://mochi.test:8888/tests/image/test/mochitest/green.png" type="image/png"/> + </div> +</body> +</html> diff --git a/dom/tests/mochitest/general/postback.html b/dom/tests/mochitest/general/postback.html new file mode 100644 index 0000000000..c45cf5ad34 --- /dev/null +++ b/dom/tests/mochitest/general/postback.html @@ -0,0 +1,16 @@ +<!DOCTYPE html> +<html> +<head> + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width, initial-scale=1"> + <title></title> +</head> +<body> +<script type="text/javascript"> + window.addEventListener("load", () => { + window.parent.postMessage("loaded", "*"); + }); +</script> + +</body> +</html> diff --git a/dom/tests/mochitest/general/res0.resource b/dom/tests/mochitest/general/res0.resource new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/dom/tests/mochitest/general/res0.resource diff --git a/dom/tests/mochitest/general/res1.resource b/dom/tests/mochitest/general/res1.resource new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/dom/tests/mochitest/general/res1.resource diff --git a/dom/tests/mochitest/general/res1.resource^headers^ b/dom/tests/mochitest/general/res1.resource^headers^ new file mode 100644 index 0000000000..2e5d8ea17a --- /dev/null +++ b/dom/tests/mochitest/general/res1.resource^headers^ @@ -0,0 +1,2 @@ +HTTP 200 +Timing-Allow-Origin: * diff --git a/dom/tests/mochitest/general/res2.resource b/dom/tests/mochitest/general/res2.resource new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/dom/tests/mochitest/general/res2.resource diff --git a/dom/tests/mochitest/general/res2.resource^headers^ b/dom/tests/mochitest/general/res2.resource^headers^ new file mode 100644 index 0000000000..f19c20d3ec --- /dev/null +++ b/dom/tests/mochitest/general/res2.resource^headers^ @@ -0,0 +1,2 @@ +HTTP 302 Moved +Location: http://test2.example.com/tests/image/test/mochitest/red.png diff --git a/dom/tests/mochitest/general/res3.resource b/dom/tests/mochitest/general/res3.resource new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/dom/tests/mochitest/general/res3.resource diff --git a/dom/tests/mochitest/general/res3.resource^headers^ b/dom/tests/mochitest/general/res3.resource^headers^ new file mode 100644 index 0000000000..391a442227 --- /dev/null +++ b/dom/tests/mochitest/general/res3.resource^headers^ @@ -0,0 +1,2 @@ +HTTP 200 +Timing-Allow-Origin: http://mochi.test:8888 diff --git a/dom/tests/mochitest/general/res4.resource b/dom/tests/mochitest/general/res4.resource new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/dom/tests/mochitest/general/res4.resource diff --git a/dom/tests/mochitest/general/res4.resource^headers^ b/dom/tests/mochitest/general/res4.resource^headers^ new file mode 100644 index 0000000000..fbf4a9de7f --- /dev/null +++ b/dom/tests/mochitest/general/res4.resource^headers^ @@ -0,0 +1,3 @@ +HTTP 302 Moved +Timing-Allow-Origin: * +Location: http://mochi.test:8888/tests/dom/tests/mochitest/general/res1.resource diff --git a/dom/tests/mochitest/general/res5.resource b/dom/tests/mochitest/general/res5.resource new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/dom/tests/mochitest/general/res5.resource diff --git a/dom/tests/mochitest/general/res5.resource^headers^ b/dom/tests/mochitest/general/res5.resource^headers^ new file mode 100644 index 0000000000..1be24a555c --- /dev/null +++ b/dom/tests/mochitest/general/res5.resource^headers^ @@ -0,0 +1,2 @@ +HTTP 200 +Timing-Allow-Origin: http://mochi.test:8889 diff --git a/dom/tests/mochitest/general/res6.resource b/dom/tests/mochitest/general/res6.resource new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/dom/tests/mochitest/general/res6.resource diff --git a/dom/tests/mochitest/general/res6.resource^headers^ b/dom/tests/mochitest/general/res6.resource^headers^ new file mode 100644 index 0000000000..4a570ae678 --- /dev/null +++ b/dom/tests/mochitest/general/res6.resource^headers^ @@ -0,0 +1,2 @@ +HTTP 200 +Timing-Allow-Origin: diff --git a/dom/tests/mochitest/general/res7.resource b/dom/tests/mochitest/general/res7.resource new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/dom/tests/mochitest/general/res7.resource diff --git a/dom/tests/mochitest/general/res7.resource^headers^ b/dom/tests/mochitest/general/res7.resource^headers^ new file mode 100644 index 0000000000..d9bbf9462b --- /dev/null +++ b/dom/tests/mochitest/general/res7.resource^headers^ @@ -0,0 +1,2 @@ +HTTP 200 +Timing-Allow-Origin: http://mochi.test:8888 http://test1.com diff --git a/dom/tests/mochitest/general/res8.resource b/dom/tests/mochitest/general/res8.resource new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/dom/tests/mochitest/general/res8.resource diff --git a/dom/tests/mochitest/general/res8.resource^headers^ b/dom/tests/mochitest/general/res8.resource^headers^ new file mode 100644 index 0000000000..21f490fd32 --- /dev/null +++ b/dom/tests/mochitest/general/res8.resource^headers^ @@ -0,0 +1,2 @@ +HTTP 302 Moved +Location: http://test1.example.com/tests/dom/tests/mochitest/general/res4.resource diff --git a/dom/tests/mochitest/general/resource_timing.js b/dom/tests/mochitest/general/resource_timing.js new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/dom/tests/mochitest/general/resource_timing.js diff --git a/dom/tests/mochitest/general/resource_timing_cross_origin.html b/dom/tests/mochitest/general/resource_timing_cross_origin.html new file mode 100644 index 0000000000..d608cdce9a --- /dev/null +++ b/dom/tests/mochitest/general/resource_timing_cross_origin.html @@ -0,0 +1,191 @@ +<!-- + Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ +--> + +<!DOCTYPE HTML> +<html> +<head> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> + <script type="application/javascript"> + +function ok(cond, message) { + window.opener.ok(cond, message) +} + +function is(received, expected, message) { + window.opener.is(received, expected, message); +} + +function isnot(received, notExpected, message) { + window.opener.isnot(received, notExpected, message); +} + +var bufferFullCounter = 0; +const expectedBufferFullEvents = 0; + +const properties = ["startTime", "redirectStart", "redirectEnd", "fetchStart", + "domainLookupStart", "domainLookupEnd", "connectStart", + "connectEnd", "requestStart", "responseStart", "responseEnd"]; + +window.onload = function() { + ok(!!window.performance, "Performance object should exist"); + ok(!!window.performance.getEntries, "Performance.getEntries() should exist"); + ok(!!window.performance.getEntriesByName, "Performance.getEntriesByName() should exist"); + ok(!!window.performance.getEntriesByType, "Performance.getEntriesByType() should exist"); + + window.performance.onresourcetimingbufferfull = function() { + bufferFullCounter += 1; + } + + makeXhr("http://mochi.test:8888/tests/dom/tests/mochitest/general/test-data.json", firstCheck); +}; + +function firstCheck() { + var entries = window.performance.getEntriesByName("http://mochi.test:8888/tests/dom/tests/mochitest/general/test-data.json"); + ok(!!entries[0], "same origin test-data.json is missing from entries"); + checkSameOrigin(entries[0]); + + var entries = window.performance.getEntriesByName("http://mochi.test:8888/tests/dom/tests/mochitest/general/res0.resource"); + ok(!!entries[0], "same origin res0.resource is missing from entries"); + checkSameOrigin(entries[0]); + + entries = window.performance.getEntriesByName("http://test1.example.com/tests/dom/tests/mochitest/general/res0.resource"); + ok(!!entries[0], "cross origin res0.resource is missing from entries"); + checkCrossOrigin(entries[0]); + + entries = window.performance.getEntriesByName("http://test1.example.com/tests/dom/tests/mochitest/general/res1.resource"); + ok(!!entries[0], "res1.resource is missing from entries"); + checkSameOrigin(entries[0]); + + entries = window.performance.getEntriesByName("http://test1.example.com/tests/dom/tests/mochitest/general/res2.resource"); + ok(!!entries[0], "redirected res2.resource is missing from entries"); + checkRedirectedCrossOrigin(entries[0]); + + entries = window.performance.getEntriesByName("http://test1.example.com/tests/dom/tests/mochitest/general/res3.resource"); + ok(!!entries[0], "cross origin res3.resource is missing from entries"); + checkSameOrigin(entries[0]); + + entries = window.performance.getEntriesByName("http://test1.example.com/tests/dom/tests/mochitest/general/res4.resource"); + ok(!!entries[0], "redirected res4.resource is missing from entries"); + checkRedirectedSameOrigin(entries[0]); + + entries = window.performance.getEntriesByName("http://test1.example.com/tests/dom/tests/mochitest/general/res5.resource"); + ok(!!entries[0], "cross origin res5.resource is missing from entries"); + checkCrossOrigin(entries[0]); + + entries = window.performance.getEntriesByName("http://test1.example.com/tests/dom/tests/mochitest/general/res6.resource"); + ok(!!entries[0], "cross origin res6.resource is missing from entries"); + checkCrossOrigin(entries[0]); + + entries = window.performance.getEntriesByName("http://test1.example.com/tests/dom/tests/mochitest/general/res7.resource"); + ok(!!entries[0], "cross origin res7.resource is missing from entries"); + checkCrossOrigin(entries[0]); + + entries = window.performance.getEntriesByName("http://test1.example.com/tests/dom/tests/mochitest/general/res8.resource"); + ok(!!entries[0], "redirected res8.resource is missing from entries"); + checkRedirectCrossOriginResourceSameOrigin(entries[0]); + + entries = window.performance.getEntriesByName("http://mochi.test:8888/tests/dom/tests/mochitest/general/resource_timing.js"); + ok(!!entries[0], "same origin resource_timing.js is missing from entries"); + checkSameOrigin(entries[0]); + + is(bufferFullCounter, expectedBufferFullEvents, "Buffer full was called"); + finishTests(); +} + +function checkEntry(entry, checks) { + // If the entry is undefined, we return early so we don't get a JS error + if (entry == undefined) + return; + + for (var j = 1; j < properties.length; ++j) { + var prop = properties[j]; + if (checks[prop] != undefined) { + is(entry[prop], checks[prop], "Wrong value for prop " + prop + " for resource " + entry.name); + } else { + isnot(entry[prop], 0, "Wrong value for prop " + prop + " for resource " + entry.name); + } + } +} + +// No redirects have occured so RedirectStart/End are 0 +function checkSameOrigin(entry) { + const checks = { "redirectStart": 0, "redirectEnd": 0 }; + checkEntry(entry, checks); +} + +// This is a cross-origin resource that doesn't pass the check +// All of these attributes are 0. No redirects +function checkCrossOrigin(entry) { + const checks = { "redirectStart": 0, "redirectEnd": 0, + "domainLookupStart": 0, "domainLookupEnd": 0, + "connectStart": 0, "connectEnd": 0, + "requestStart": 0, "responseStart": 0 }; + checkEntry(entry, checks); +} + +// A cross-origin redirect has occured. RedirectStart/End and the rest of the +// attributes are 0. +function checkRedirectedCrossOrigin(entry) { + const checks = { "redirectStart": 0, "redirectEnd": 0, + "domainLookupStart": 0, "domainLookupEnd": 0, + "connectStart": 0, "connectEnd": 0, + "requestStart": 0, "responseStart": 0 }; + checkEntry(entry, checks); +} + +// The redirect is to the same origin as the initial document, +// so no entries are 0. +function checkRedirectedSameOrigin(entry) { + const checks = { }; + checkEntry(entry, checks); +} + +// The final entry passes the timing-allow-check, +// but one of the redirects does not. redirectStart/End and the rest of the +// attributes are 0 because all redirects need to pass TAO check. +function checkRedirectCrossOriginResourceSameOrigin(entry) { + const checks = { "redirectStart": 0, "redirectEnd": 0, + "domainLookupStart": 0, "domainLookupEnd": 0, + "connectStart": 0, "connectEnd": 0, + "requestStart": 0, "responseStart": 0 }; + checkEntry(entry, checks); +} + +function makeXhr(aUrl, aCallback) { + var xmlhttp = new XMLHttpRequest(); + xmlhttp.onload = aCallback; + xmlhttp.open("get", aUrl, true); + xmlhttp.send(); +} + +function finishTests() { + window.opener.finishTests(); +} + +</script> + +<body> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=822480" + title="Add resource timing API."> + Bug #822480 - Add in the Resource Timing API + </a> + <p id="display"></p> + <div id="content"> + <object data="http://mochi.test:8888/tests/dom/tests/mochitest/general/res0.resource"></object> <!-- same origin, no header --> + <object data="http://test1.example.com/tests/dom/tests/mochitest/general/res0.resource"></object> <!-- cross origin, no header --> + <object data="http://test1.example.com/tests/dom/tests/mochitest/general/res1.resource"></object> <!-- cross origin, Timing-Allow-Origin: * header --> + <object data="http://test1.example.com/tests/dom/tests/mochitest/general/res2.resource"></object> <!-- cross origin redirect to test2.example.com, no header --> + <object data="http://test1.example.com/tests/dom/tests/mochitest/general/res3.resource"></object> <!-- cross origin, Timing-Allow-Origin: http://mochi.test:8888 header --> + <object data="http://test1.example.com/tests/dom/tests/mochitest/general/res4.resource"></object> <!-- cross origin redirect to mochi.test:8888/.../res1.resource, Timing-Allow-Origin: * --> + <object data="http://test1.example.com/tests/dom/tests/mochitest/general/res5.resource"></object> <!-- cross origin, Timing-Allow-Origin: http://mochi.test:8889 --> + <object data="http://test1.example.com/tests/dom/tests/mochitest/general/res6.resource"></object> <!-- cross origin, Timing-Allow-Origin: "" (empty string) --> + <object data="http://test1.example.com/tests/dom/tests/mochitest/general/res7.resource"></object> <!-- cross origin, Timing-Allow-Origin: http://mochi.test:8888 http://test1.com header --> + <object data="http://test1.example.com/tests/dom/tests/mochitest/general/res8.resource"></object> <!-- double cross origin redirect --> + <script type="text/javascript" src="http://mochi.test:8888/tests/dom/tests/mochitest/general/resource_timing.js"></script> <!-- same origin script --> + </div> +</body> + +</html> diff --git a/dom/tests/mochitest/general/resource_timing_iframe.html b/dom/tests/mochitest/general/resource_timing_iframe.html new file mode 100644 index 0000000000..2d50427d92 --- /dev/null +++ b/dom/tests/mochitest/general/resource_timing_iframe.html @@ -0,0 +1,50 @@ +<!-- + Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ +--> + +<!-- + This file is a sub-test file for the Resource Timing and Performance Timeline + APIs. + These tests are focused on the iframe corner case. + The first step is to check that the image from this document was added as + an entry to this window.performance object. + The second step is to check that this iframe was not added as an entry to its + own window.performance object. + As a final step, we do a double checking: no ifrmes were added as entries + to this window.performance object. +--> + +<!DOCTYPE HTML> +<html> +<head> + <meta charset="utf-8"> + <title>Test for Bug 822480 - Add in the Resource Timing API</title> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> +<script> +function doTest() { + window.parent.ok(!!window.performance.getEntriesByName( + "http://mochi.test:8888/tests/image/test/mochitest/damon.jpg").length, + "http://mochi.test:8888/tests/image/test/mochitest/damon.jpg should be a valid entry name"); + let entries = window.performance.getEntriesByName( + "http://mochi.test:8888/tests/dom/tests/mochitest/general/resource_timing_iframe.html"); + window.parent.is(entries.length, 1, "This iframe should have only 1 entry with its URL"); + window.parent.is(entries[0].type, "navigate", "This iframe should have its navigation entry"); + + // Check that there are no iframes added as entries + var resources = window.performance.getEntriesByType("resource"); + for (var i = 0 ; i < resources.length; i++) { + var entry = resources[i]; + if (entry.initiatorType === "iframe") { + ok(false, "unexpected iframe " + entry.name); + } + } + + window.parent.iframeTestsCompleted(); +} +</script> +<body onLoad="doTest()"> + <img src="http://mochi.test:8888/tests/image/test/mochitest/damon.jpg"/> +</body> +</html> diff --git a/dom/tests/mochitest/general/resource_timing_location_navigate.html b/dom/tests/mochitest/general/resource_timing_location_navigate.html new file mode 100644 index 0000000000..94d0d0512e --- /dev/null +++ b/dom/tests/mochitest/general/resource_timing_location_navigate.html @@ -0,0 +1,3 @@ +<script type="text/javascript"> + document.location = "https://test1.example.org/tests/dom/tests/mochitest/general/postback.html" +</script> diff --git a/dom/tests/mochitest/general/resource_timing_main_test.html b/dom/tests/mochitest/general/resource_timing_main_test.html new file mode 100644 index 0000000000..88b44f76dd --- /dev/null +++ b/dom/tests/mochitest/general/resource_timing_main_test.html @@ -0,0 +1,290 @@ +<!-- + Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ +--> + +<!-- + This file contains test for the Resource Timing and Performance Timeline APIs. + The test starts by checking that all the entries were added to the performance + object. + The next step is to check that the "performance" object and its "getEntries()" + methods are available. We check all the 3 methods: getEntries, + getEntriesByName() and getEntriesByType(). + + As a next step, we check that the entries contain the correct information + ("checkEntries()" method). + The test checks that the entries contain all the required members, that the + timings are sorted properly and that the entries were returned in + chronological order with respect to startTime. In "checkEntries()", it is also + checked if the order of the entries is the expected order (the expected order + is hard-coded here). + The last test from the "checkEntries()" method will verify the iframe case: + the iframe must be added as an entry to this window's performance object, + while the image from the iframe should not be added here. + + Next tests will check the Performance API extensions introduced by the + resource timing: window.performance.setResourceTimingBufferSize(1) and + window.performance.clearResourceTimings(); + + The last tests will verify that the xhr resources are also added as entries + to our performance object. + + Meanwhile, the iframe from the page will get loaded + (resource_timing_iframe.html). + The iframe contains a second script that will do some tests, as well, plus + an image - its own resource. + The script from the iframe will check that the iframe itself was not added + as an entry (to itself). Also, it will check that its image was added as + entry to the iframe's performance object. + The last check is a double check: check that no subdocuments were added as + entries for this iframe's performance object. + The parent's (this window) "ok_wrapper()" method will be called once the tests + are completed. +--> + +<!DOCTYPE html> +<html> +<head> + <meta charset="utf-8"> + <link rel="stylesheet" href="/tests/SimpleTest/test.css?resource-timing-main-test"/> + <script type="application/javascript"> + +var mainWindowTestsDone = false; +var iframeTestsDone = false; + +function ok(cond, message) { + window.opener.ok(cond, message) +} + +function is(received, expected, message) { + window.opener.is(received, expected, message); +} + +function isnot(received, notExpected, message) { + window.opener.isnot(received, notExpected, message); +} + +var bufferFullCounter = 0; +const expectedBufferFullEvents = 1; + +var allResources = { + "http://mochi.test:8888/tests/SimpleTest/test.css?resource-timing-main-test": "link", + "http://mochi.test:8888/tests/image/test/mochitest/blue.png" : "img", + "http://mochi.test:8888/tests/image/test/mochitest/red.png" : "object", + "http://mochi.test:8888/tests/image/test/mochitest/big.png" : "embed", + "http://mochi.test:8888/tests/dom/tests/mochitest/general/resource_timing_iframe.html" : "iframe"}; + +window.onload = function() { + ok(!!window.performance, "Performance object should exist"); + ok(!!window.performance.getEntries, "Performance.getEntries() should exist"); + ok(!!window.performance.getEntriesByName, "Performance.getEntriesByName() should exist"); + ok(!!window.performance.getEntriesByType, "Performance.getEntriesByType() should exist"); + + window.performance.onresourcetimingbufferfull = function() { + bufferFullCounter += 1; + } + + is(window.performance.getEntriesByType("resource").length, Object.keys(allResources).length, "Performance.getEntriesByType() returned wrong number of entries."); + + checkStringify(window.performance.getEntriesByType("resource")[0]); + + ok(!!window.performance.getEntriesByType("resource").length, + "Performance.getEntriesByType() should return some results"); + ok(!!window.performance.getEntriesByName("http://mochi.test:8888/tests/image/test/mochitest/blue.png").length, + "Performance.getEntriesByName() should return some results"); + + // Checks that two calls for "getEntriesByType()" return a different array with the same + // entries. + isnot(window.performance.getEntriesByType("resource"), window.performance.getEntriesByType("resource"), + "getEntriesByType() should return a different array object every time."); + ok(function (array1, array2) { + if (array1.length != array2.length) { + return false; + } + for (var i = 0 ; i < array1.length ; i++) { + if (array1[i] !== array2[i]) { + return false; + } + } + return true; + }(window.performance.getEntriesByType("resource"), window.performance.getEntriesByType("resource")), + "The arrays should have the same entries."); + + checkEntries(window.performance.getEntriesByType("resource")); + + window.performance.setResourceTimingBufferSize(1); + is(window.performance.getEntriesByType("resource").length, Object.keys(allResources).length, "No entries should be " + + "removed when setResourceTimingBufferSize is called."); + + window.performance.setResourceTimingBufferSize(4); + is(window.performance.getEntriesByType("resource").length, Object.keys(allResources).length, "No entries should be " + + "removed when setResourceTimingBufferSize is called."); + + window.performance.setResourceTimingBufferSize(1); + window.performance.clearResourceTimings(); + is(window.performance.getEntriesByType("resource").length, 0, "All the entries should " + + "be removed when when clearResourceTimings is being called."); + + makeXhr("test-data.json", firstCheck); +} + +function checkStringify(entry) { + var object = JSON.parse(JSON.stringify(entry)); + var keys = ["initiatorType","redirectStart","redirectEnd","fetchStart", + "domainLookupStart","domainLookupEnd","connectStart","connectEnd", + "secureConnectionStart","requestStart","responseStart","responseEnd", + "name","entryType","startTime","duration"]; + for (var i in keys) { + ok(keys[i] in object, "The serialization should contain key: "+keys[i]); + } +} + +function checkEntries(anEntryList) { + // Check that all the entries have all the properties. + for (var i = 0 ; i < anEntryList.length ; i++) { + var entry = anEntryList[i]; + + ok(!!entry, "PerformanceEntry should not be null"); + ok(!!entry.name, "PerformanceEntry.name should be valid."); + ok(entry.startTime > 0, "PerformanceEntry.startTime should be grater than 0"); + + // The entries list should be in chronological order with respect to startTime + if (i > 0) { + ok(anEntryList[i - 1].startTime <= anEntryList[i].startTime, + "Entries list should be in chronological order with respect to startTime."); + } + + // Check that each entry has all the properties and that the timings were + // returned in the expected order. + if ("initiatorType" in entry) { + ok("redirectStart" in entry, "PerformanceEntry.redirectStart should be part of PerformanceEntry"); + ok("redirectEnd" in entry, "PerformanceEntry.redirectEnd should be part of PerformanceEntry"); + ok("fetchStart" in entry, "PerformanceEntry.fetchStart should be part of PerformanceEntry"); + ok("domainLookupStart" in entry, "PerformanceEntry.domainLookupStart should be part of PerformanceEntry"); + ok("domainLookupEnd" in entry, "PerformanceEntry.domainLookupEnd should be part of PerformanceEntry"); + ok("connectStart" in entry, "PerformanceEntry.connectStart should be part of PerformanceEntry"); + ok("connectEnd" in entry, "PerformanceEntry.connectEnd should be part of PerformanceEntry"); + ok("secureConnectionStart" in entry, "PerformanceEntry.secureConnectionStart should be part of PerformanceEntry"); + ok("requestStart" in entry, "PerformanceEntry.requestStart should be part of PerformanceEntry"); + ok("responseStart" in entry, "PerformanceEntry.responseStart should be part of PerformanceEntry"); + ok("responseEnd" in entry, "PerformanceEntry.responseEnd should be part of PerformanceEntry"); + + // Check that timings are in proper order + sequence = ['startTime', 'redirectStart', 'redirectEnd', 'fetchStart', + 'domainLookupStart', 'domainLookupEnd', 'connectStart', + 'connectEnd', 'requestStart', 'responseStart', 'responseEnd']; + for (var j = 1; j < sequence.length; ++j) { + var prop = sequence[j]; + var prevProp = sequence[j-1]; + if (prop == 'redirectStart' && entry[prop] == 0) + continue; + if (prop == 'redirectEnd' && entry[prop] == 0) + continue; + ok(entry[prevProp] <= entry[prop], + ['Expected ', prevProp, ' to happen before ', prop, + ', got ', prevProp, ' = ', entry[prevProp], + ', ', prop, ' = ', entry[prop]].join('')); + } + } + } + + // Check that the entries have the expected initiator type. We can't check + // the order (the order might depend on the platform the tests are running). + for (resourceName in allResources) { + // Check that we have a resource with the specific name. + namedEntries = window.performance.getEntriesByName(resourceName); + ok (!!namedEntries && (namedEntries.length == 1), + "An entry with the name '" + resourceName + "' should be available"); + + if (!namedEntries.length) { + continue; + } + + // Double check for the entry name. + is (namedEntries[0].name, resourceName, "The resource name is invalid"); + + // Check the initiator type. + is (namedEntries[0].initiatorType, allResources[resourceName], + "The initiator type for " + resourceName + " is invalid"); + } + + // Check that the iframe's image was NOT added as an entry to this window's performance entry. + ok(!window.performance.getEntriesByName("http://mochi.test:8888/tests/image/test/mochitest/damon.jpg").length, + "http://mochi.test:8888/tests/image/test/mochitest/damon.jpg should be a valid entry name"); +} + +function firstCheck() { + is(window.performance.getEntriesByType("resource").length, 1, "The first xhr entry was not added."); + is(window.performance.getEntriesByType("resource")[0].initiatorType, "xmlhttprequest", + "The initiatorType is incorrect for this entry"); + makeXhr("test-data2.json", secondCheck); +} + +function secondCheck() { + // Since the buffer max size was set to '1', 'peformance.getEntriesByType()' should + // return only '1' entry (first xhr results). + is(window.performance.getEntriesByType("resource").length, 1, "The second xhr entry should not be " + + "returned since the buffer size was set to 1."); + isnot(window.performance.getEntriesByType("resource")[0].name, "http://mochi.test:8888/tests/dom/tests/mochitest/general/test-data2.json", + "We returned the second xhr instead of the first one"); + finishTest(); +} + +function finishTest() { + // Check if all the tests are completed. + SpecialPowers.executeSoon(function () { + if (iframeTestsDone) { + is(bufferFullCounter, expectedBufferFullEvents, "onresourcetimingbufferfull called a wrong number of times"); + window.opener.finishTests(); + } else { + mainWindowTestsDone = true; + } + }, window); +} + +function makeXhr(aUrl, aCallback) { + var xmlhttp = new XMLHttpRequest(); + xmlhttp.onload = aCallback; + xmlhttp.open("get", aUrl, true); + xmlhttp.send(); +} + +function checkArraysHaveSameElementsInSameOrder(array1, array2) { + if (array1.length != array2.length) { + return false; + } + for (var i = 0 ; i < array1.length ; i++) { + if (array1[i] !== array2[i]) { + return false; + } + } + return true; +} + +function iframeTestsCompleted() { + if (mainWindowTestsDone) { + is(bufferFullCounter, expectedBufferFullEvents, "onresourcetimingbufferfull called a wrong number of times"); + window.opener.finishTests(); + } + else { + iframeTestsDone = true; + } +} + +</script> +</head> +<body> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=822480" + title="Add resource timing API."> + Bug #822480 - Add in the Resource Timing API + </a> + <p id="display"></p> + <div id="content"> + <img src="http://mochi.test:8888/tests/image/test/mochitest/blue.png"> + <object data="http://mochi.test:8888/tests/image/test/mochitest/red.png" type="image/png"></object> + <embed src="http://mochi.test:8888/tests/image/test/mochitest/big.png" type="image/png"/> + <iframe sandbox="allow-same-origin allow-scripts" id="if_2" src="resource_timing_iframe.html" height="10" width="10"></iframe> + </div> +</body> +</html> diff --git a/dom/tests/mochitest/general/resource_timing_meta_refresh.html b/dom/tests/mochitest/general/resource_timing_meta_refresh.html new file mode 100644 index 0000000000..dbbcd65413 --- /dev/null +++ b/dom/tests/mochitest/general/resource_timing_meta_refresh.html @@ -0,0 +1,10 @@ +<!DOCTYPE html> +<html> +<head> + <meta charset="utf-8"> + <meta http-equiv="refresh" content="0; url=https://sub1.test1.example.org/tests/dom/tests/mochitest/general/postback.html"> +</head> +<body> + +</body> +</html> diff --git a/dom/tests/mochitest/general/resource_timing_nocors.html b/dom/tests/mochitest/general/resource_timing_nocors.html new file mode 100644 index 0000000000..39b34950fc --- /dev/null +++ b/dom/tests/mochitest/general/resource_timing_nocors.html @@ -0,0 +1,88 @@ +<!-- + Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ +--> + +<!DOCTYPE html> +<html> +<head> + <link rel="stylesheet" type="text/css" href="http://mochi.test:8888/tests/SimpleTest/test.css"/> + <link rel="stylesheet" type="text/css" href="http://mochi.test:8888/tests/dom/tests/mochitest/general/linkA.css"/> + <link rel="stylesheet" type="text/css" href="http://example.com/tests/dom/tests/mochitest/general/linkB.css"/> + <link rel="stylesheet" type="text/css" href="http://example.net/tests/dom/tests/mochitest/general/linkC.css"/> + +<!-- + + Resources fetched by a cross-origin stylesheet loaded with a no-cors mode should not be reported. + Resources marked with a ^ should be reported in performance.getEntries() + + (mochi.test:8888 | linkA.css)^ -> (mochi.test:8888 | cssA.css)^ + -> (mochi.test:8888 | cssB.css)^ -> (mochi.test:8888 | cssC.css)^ + -> (example.org | cssC.css)^ + (example.com | linkB.css)^ -> (example.com | cssA.css) + -> (mochi.test:8888 | cssA.css) + -> (test2.examp.org | cssB.css) -> (test2.examp.org | cssC.css) + -> (example.org | cssC.css) + -> (example.net | cssC.css) + + (example.net | linkC.css)^ -> (example.net | cssA.css) + [WITH Allow-* HEADERS] + +--> + + + <script type="application/javascript"> + +function ok(cond, message) { + window.opener.ok(cond, message) +} + +function is(received, expected, message) { + window.opener.is(received, expected, message); +} + +function isnot(received, notExpected, message) { + window.opener.isnot(received, notExpected, message); +} + +var allResources = { + "http://mochi.test:8888/tests/SimpleTest/test.css" : "link", + "http://mochi.test:8888/tests/dom/tests/mochitest/general/linkA.css" : "link", + "http://example.com/tests/dom/tests/mochitest/general/linkB.css" : "link", + "http://example.net/tests/dom/tests/mochitest/general/linkC.css" : "link", + "http://mochi.test:8888/tests/dom/tests/mochitest/general/cssA.css" : "css", + "http://mochi.test:8888/tests/dom/tests/mochitest/general/cssB.css" : "css", + "http://mochi.test:8888/tests/dom/tests/mochitest/general/cssC.css" : "css", + "http://example.org/tests/dom/tests/mochitest/general/cssC.css" : "css", +}; + +window.onload = function() { + let entries = performance.getEntries(); + for (let entry of entries) { + let type = allResources[entry.name]; + if (!type) { + ok(false, "Did not expect to find resource: "+entry.name); + continue; + } + + is(entry.initiatorType, type, "Expected initiatorType does not match"); + } + + is(entries.length, Object.keys(allResources).length, "Found wrong number of resources"); + + window.opener.finishTests(); +} + +</script> +</head> +<body> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=1180145" + title="Resource timing NO-CORS CSS"> + Bug #1180145 - Resource Timing NO-CORS CSS + </a> + <p id="display"></p> + <div id="content"> + </div> +</body> +</html> diff --git a/dom/tests/mochitest/general/resource_timing_redirect.html b/dom/tests/mochitest/general/resource_timing_redirect.html new file mode 100644 index 0000000000..5417098d72 --- /dev/null +++ b/dom/tests/mochitest/general/resource_timing_redirect.html @@ -0,0 +1,11 @@ +<!DOCTYPE html> +<html> +<head> + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width, initial-scale=1"> + <title></title> +</head> +<body> + +</body> +</html> diff --git a/dom/tests/mochitest/general/resource_timing_redirect.html^headers^ b/dom/tests/mochitest/general/resource_timing_redirect.html^headers^ new file mode 100644 index 0000000000..aed575c918 --- /dev/null +++ b/dom/tests/mochitest/general/resource_timing_redirect.html^headers^ @@ -0,0 +1,2 @@ +HTTP 302 Found +Location: https://example.org/tests/dom/tests/mochitest/general/postback.html diff --git a/dom/tests/mochitest/general/start_historyframe.html b/dom/tests/mochitest/general/start_historyframe.html new file mode 100644 index 0000000000..a791af4e64 --- /dev/null +++ b/dom/tests/mochitest/general/start_historyframe.html @@ -0,0 +1 @@ +<p id='text'>Start</p> diff --git a/dom/tests/mochitest/general/storagePermissionsUtils.js b/dom/tests/mochitest/general/storagePermissionsUtils.js new file mode 100644 index 0000000000..d4a7190c82 --- /dev/null +++ b/dom/tests/mochitest/general/storagePermissionsUtils.js @@ -0,0 +1,282 @@ +const BEHAVIOR_ACCEPT = 0; +const BEHAVIOR_REJECT_FOREIGN = 1; +const BEHAVIOR_REJECT = 2; +const BEHAVIOR_LIMIT_FOREIGN = 3; + +const kPrefName = "network.cookie.cookieBehavior"; + +// Check if we are in frame, and declare ok and finishTest appropriately +const inFrame = ("" + location).match(/frame/); +if (inFrame) { + ok = function (a, message) { + if (!a) { + parent.postMessage("FAILURE: " + message, "http://mochi.test:8888"); + } else { + parent.postMessage(message, "http://mochi.test:8888"); + } + }; + + finishTest = function () { + parent.postMessage("done", "http://mochi.test:8888"); + }; +} else { + finishTest = function () { + SimpleTest.finish(); + }; +} + +function setCookieBehavior(behavior) { + return SpecialPowers.pushPrefEnv({ set: [[kPrefName, behavior]] }); +} + +function runIFrame(url) { + return new Promise((resolve, reject) => { + function onMessage(e) { + if (e.data == "done") { + resolve(); + window.removeEventListener("message", onMessage); + return; + } + + ok(!e.data.match(/^FAILURE/), e.data + " (IFRAME = " + url + ")"); + } + window.addEventListener("message", onMessage); + + document.querySelector("iframe").src = url; + }); +} + +function runWorker(url) { + return new Promise((resolve, reject) => { + var worker = new Worker(url); + worker.addEventListener("message", function (e) { + if (e.data == "done") { + resolve(); + return; + } + + ok(!e.data.match(/^FAILURE/), e.data + " (WORKER = " + url + ")"); + }); + }); +} + +function chromePower(allowed, blockSessionStorage) { + // localStorage is affected by storage policy. + try { + SpecialPowers.wrap(window).localStorage.getItem("X"); + ok(allowed, "getting localStorage from chrome didn't throw"); + } catch (e) { + ok(!allowed, "getting localStorage from chrome threw"); + } + + // sessionStorage is not. See bug 1183968. + try { + SpecialPowers.wrap(window).sessionStorage.getItem("X"); + ok(!blockSessionStorage, "getting sessionStorage from chrome didn't throw"); + } catch (e) { + ok(blockSessionStorage, "getting sessionStorage from chrome threw"); + } + + // indexedDB is affected by storage policy. + try { + SpecialPowers.wrap(window).indexedDB; + ok(allowed, "getting indexedDB from chrome didn't throw"); + } catch (e) { + ok(!allowed, "getting indexedDB from chrome threw"); + } + + // Same with caches, along with the additional https-only requirement. + try { + var shouldResolve = allowed && location.protocol == "https:"; + var promise = SpecialPowers.wrap(window).caches.keys(); + ok(true, "getting caches from chrome should never throw"); + return new Promise((resolve, reject) => { + promise.then( + function () { + ok(shouldResolve, "The promise was resolved for chrome"); + resolve(); + }, + function (e) { + ok(!shouldResolve, "The promise was rejected for chrome: " + e); + resolve(); + } + ); + }); + } catch (e) { + ok(false, "getting caches from chrome threw"); + } +} + +function storageAllowed() { + try { + localStorage.getItem("X"); + ok(true, "getting localStorage didn't throw"); + } catch (e) { + ok(false, "getting localStorage should not throw"); + } + + try { + sessionStorage.getItem("X"); + ok(true, "getting sessionStorage didn't throw"); + } catch (e) { + ok(false, "getting sessionStorage should not throw"); + } + + try { + indexedDB; + ok(true, "getting indexedDB didn't throw"); + } catch (e) { + ok(false, "getting indexedDB should not throw"); + } + + try { + var promise = caches.keys(); + ok(true, "getting caches didn't throw"); + + return new Promise((resolve, reject) => { + promise.then( + function () { + ok(location.protocol == "https:", "The promise was not rejected"); + resolve(); + }, + function () { + ok( + location.protocol !== "https:", + "The promise should not have been rejected" + ); + resolve(); + } + ); + }); + } catch (e) { + ok(location.protocol !== "https:", "getting caches should not have thrown"); + return Promise.resolve(); + } +} + +function storagePrevented() { + try { + localStorage.getItem("X"); + ok(false, "getting localStorage should have thrown"); + } catch (e) { + ok(true, "getting localStorage threw"); + } + + if (location.hash == "#thirdparty") { + // No matter what the user's preferences are, we don't block + // sessionStorage in 3rd-party iframes. We do block them everywhere + // else however. + try { + sessionStorage.getItem("X"); + ok(true, "getting sessionStorage didn't throw"); + } catch (e) { + ok(false, "getting sessionStorage should not have thrown"); + } + } else { + try { + sessionStorage.getItem("X"); + ok(false, "getting sessionStorage should have thrown"); + } catch (e) { + ok(true, "getting sessionStorage threw"); + } + } + + try { + indexedDB; + ok(false, "getting indexedDB should have thrown"); + } catch (e) { + ok(true, "getting indexedDB threw"); + } + + try { + var promise = caches.keys(); + ok(true, "getting caches didn't throw"); + + return new Promise((resolve, reject) => { + promise.then( + function () { + ok(false, "The promise should have rejected"); + resolve(); + }, + function () { + ok(true, "The promise was rejected"); + resolve(); + } + ); + }); + } catch (e) { + ok(location.protocol !== "https:", "getting caches should not have thrown"); + + return Promise.resolve(); + } +} + +function task(fn) { + if (!inFrame) { + SimpleTest.waitForExplicitFinish(); + } + + var gen = fn(); + + function next_step(val, e) { + var it; + try { + if (typeof e !== "undefined") { + it = gen.throw(e); + } else { + it = gen.next(val); + } + } catch (e) { + ok(false, "An error was thrown while stepping: " + e); + ok(false, "Stack: " + e.stack); + finishTest(); + } + + if (it.done) { + finishTest(); + return; + } + it.value.then(next_step, e => next_step(null, e)); + } + + if (!gen.then) { + next_step(); + } else { + gen.then(finishTest, e => { + ok(false, "An error was thrown while stepping: " + e); + ok(false, "Stack: " + e.stack); + finishTest(); + }); + } +} + +// The test will run on a separate window in order to apply the new cookie jar settings. +async function runTestInWindow(test) { + let w = window.open("window_storagePermissions.html"); + await new Promise(resolve => { + w.onload = e => { + resolve(); + }; + }); + + await new Promise(resolve => { + onmessage = e => { + if (e.data.type == "finish") { + w.close(); + resolve(); + return; + } + + if (e.data.type == "check") { + ok(e.data.test, e.data.msg); + return; + } + + ok(false, "Unknown message"); + }; + + w.postMessage(test.toString(), "*"); + }); +} + +var thirdparty = "https://example.com/tests/dom/tests/mochitest/general/"; diff --git a/dom/tests/mochitest/general/test-data.json b/dom/tests/mochitest/general/test-data.json new file mode 100644 index 0000000000..797ab7a9dc --- /dev/null +++ b/dom/tests/mochitest/general/test-data.json @@ -0,0 +1 @@ +{ "id": "test JSON data", "myArray": ["foo", "bar", "baz", "biff"] } diff --git a/dom/tests/mochitest/general/test-data2.json b/dom/tests/mochitest/general/test-data2.json new file mode 100644 index 0000000000..797ab7a9dc --- /dev/null +++ b/dom/tests/mochitest/general/test-data2.json @@ -0,0 +1 @@ +{ "id": "test JSON data", "myArray": ["foo", "bar", "baz", "biff"] } diff --git a/dom/tests/mochitest/general/test_497898.html b/dom/tests/mochitest/general/test_497898.html new file mode 100644 index 0000000000..c26c67af86 --- /dev/null +++ b/dom/tests/mochitest/general/test_497898.html @@ -0,0 +1,41 @@ +<html> +<head> +<title>Crash [@ nsFocusManager::SendFocusOrBlurEvent] after switching focus to a different window in this case</title> +<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"> +<script src="/tests/SimpleTest/SimpleTest.js"></script> +<script> +SimpleTest.waitForExplicitFinish(); + +function done() +{ + is("passed", "passed", "test passed without crashing"); + SimpleTest.finish(); +} + +function switchFocus() +{ + setTimeout(() => window.open('497633.html', '_new', 'width=300,height=300'), 0); +} +</script> +</head> +<body> + <iframe srcdoc="<html><meta charset='utf-8'> + <head> + <script> + function run() { + setTimeout(() => document.getElementById('a').focus(), 0); + } + </script> + </head> + <body onload='run()'> + <button id='a' onfocus='parent.switchFocus()' onblur='window.frameElement.parentNode.removeChild(window.frameElement)'>Switching focus to a different program should not crash Mozilla</button> + </body> + </html>"> + </iframe> + +<p id="display"></p> +<div id="content" style="display: none"></div> + +</body> +</html> + diff --git a/dom/tests/mochitest/general/test_CCW_optimization.html b/dom/tests/mochitest/general/test_CCW_optimization.html new file mode 100644 index 0000000000..ca3a2e096c --- /dev/null +++ b/dom/tests/mochitest/general/test_CCW_optimization.html @@ -0,0 +1,50 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1319087 +--> +<head> + <title>Test for Bug 1319087</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <script src="/tests/SimpleTest/EventUtils.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1319087">Mozilla Bug 1319087</a> +<p id="display"></p> +<div id="content"> + <iframe></iframe> + <iframe></iframe> +</div> +<pre id="test"> +<script type="text/javascript"> + +function WrapperToOwnCompartment() { + var iframe = new frames[0].Object(); + var obj = iframe.obj = new Object(); + obj.x = 123; + + for (var i = 0; i < 50; i++) { + is(iframe.obj, obj); + is(iframe.obj.x, 123); + } +} + +function WrapperToYetAnotherCompartment() { + var iframe = new frames[0].Object(); + // Obj points to an object in a third compartment. + var obj = iframe.obj = new frames[1].Object(); + obj.x = 42; + + for (var i = 0; i < 50; i++) { + is(iframe.obj, obj); + is(iframe.obj.x, 42); + } +} + +WrapperToOwnCompartment(); +WrapperToYetAnotherCompartment(); + +</script> +</body> +</html> diff --git a/dom/tests/mochitest/general/test_DOMMatrix.html b/dom/tests/mochitest/general/test_DOMMatrix.html new file mode 100644 index 0000000000..81bf68d71c --- /dev/null +++ b/dom/tests/mochitest/general/test_DOMMatrix.html @@ -0,0 +1,717 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test DOMMatrix behavior</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"> +</head> +<script> +function createMatrix(a, b, c, d, e, f) +{ + var m = new DOMMatrix(); + m.a = a; + m.b = b; + m.c = c; + m.d = d; + m.e = e; + m.f = f; + return m; +} + +function create3DMatrix(a, b, c, d, e, f) +{ + var m = new DOMMatrix(); + m.a = a; + m.b = b; + m.c = c; + m.d = d; + m.e = e; + m.f = f; + m.m13 = 0; + return m; +} + +function cmpMatrix(a, b, msg) +{ + if (Array.isArray(a)) + a = new DOMMatrix(a); + if (Array.isArray(b)) + b = new DOMMatrix(b); + + ok(CompareDOMMatrix(a, b), + msg + " - got " + formatMatrix(a) + + ", expected " + formatMatrix(b)); +} + +function roughCmpMatrix(a, b, msg) +{ + if (Array.isArray(a)) + a = new DOMMatrix(a); + if (Array.isArray(b)) + b = new DOMMatrix(b); + + ok(RoughCompareDOMMatrix(a, b), + msg + " - got " + formatMatrix(a) + + ", expected " + formatMatrix(b)); +} + +function formatMatrix(m) +{ + m = new DOMMatrix(m); + + if (m.is2D) + return "(" + [m.a, m.b, m.c, m.d, m.e, m.f].join(', ') + ")"; + else + return "(" + [m.m11, m.m12, m.m13, m.m14, + m.m21, m.m22, m.m23, m.m24, + m.m31, m.m32, m.m33, m.m34, + m.m41, m.m42, m.m43, m.m44,].join(', ') + ")"; +} + +function CompareMatrix(dm, m) +{ + var ma = m.toFloat32Array(); + for (var x = 0; x < ma.length; x++) { + if (Math.abs(ma[x] - dm.m[x]) > 0.000001) + return false; + } + + return true; +} + +function CompareDOMMatrix(dm1, dm2) +{ + var m1 = dm1.toFloat32Array(); + var m2 = dm2.toFloat32Array(); + + if (m1.length != m2.length) + return false; + + for (var x = 0; x < m1.length; x++) { + if (Math.abs(m1[x] - m2[x]) > 0.000001) + return false; + } + + return true; +} + +function RoughCompareDOMMatrix(dm1, dm2) +{ + var m1 = dm1.toFloat32Array(); + var m2 = dm2.toFloat32Array(); + + if (m1.length != m2.length) + return false; + + const tolerance = 1 / 65535; + for (var x = 0; x < m1.length; x++) { + if (Math.abs(m1[x] - m2[x]) > tolerance) + return false; + } + + return true; +} + +SimpleTest.waitForExplicitFinish(); + +function main() +{ + var tests = [ + testCreateMatrix, + testMultiply, + testInverse, + testTranslate, + testScale, + testScaleNonUniform, + testRotate, + testRotateFromVector, + testFlipX, + testFlipY, + testSkewX, + testSkewY, + testMultiplyInPlace, + testInverseInPlace, + testTranslateInPlace, + testScaleInPlace, + testRotateInPlace, + testRotateFromVectorInPlace, + testSkewXInPlace, + testSkewYInPlace, + testCreateMatrix3D, + testMultiply3D, + testInverse3D, + testTranslate3D, + testScale3D, + test3D, + testParsing, + testStringify + ]; + for (var i = 0; i < tests.length; i++) { + try{ + tests[i](); + } catch (e) { + ok(false, "uncaught exception in test " + i + ": " + e.name); + } + } + SimpleTest.finish(); +} + +function testCreateMatrix() +{ + var m = new DOMMatrix(); + + // Should be initialised to identity + cmpMatrix(m, [1, 0, 0, 1, 0, 0], + "DOMMatrix should produce identity matrix"); + + m = new DOMMatrix([1,2,3,4,5,6]); + cmpMatrix(m, [1,2,3,4,5,6], + "DOMMatrix should produce the same matrix"); + ok(m.is2D, "Failed to mark matrix as 2D."); + + m = new DOMMatrix([1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16]); + cmpMatrix(m, [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16], + "DOMMatrix should produce the same matrix"); + ok(!m.is2D, "Failed to mark matrix as 3D."); + + var n = new DOMMatrix(m.toFloat32Array()); + cmpMatrix(n, [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16], + "DOMMatrix should produce the same matrix with float32array constructor"); + ok(!n.is2D, "Failed to mark matrix as 3D."); + + var n = new DOMMatrix(m.toFloat64Array()); + cmpMatrix(n, [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16], + "DOMMatrix should produce the same matrix with float64array constructor"); + ok(!n.is2D, "Failed to mark matrix as 3D."); + + var exn = null; + try { + m = new DOMMatrix([0]); + } catch (e) { + exn = e; + } + ok(exn, "did throw exception with bad DOMMatrix constructor with 1 parameter"); + + exn = null; + try { + m = new DOMMatrix([1,2,3,4,5,6,7,8,9]); + } catch (e) { + exn = e; + } + ok(exn, "did throw exception with bad DOMMatrix constructor with 9 parameters"); + + exn = null; + try { + m = new DOMMatrix([1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17]); + } catch (e) { + exn = e; + } + ok(exn, "did throw exception with bad DOMMatrix constructor with 17 parameters"); +} + +// DOMMatrix multiply(in DOMMatrix secondMatrix); +function testMultiply() +{ + var m1 = createMatrix(1, 0, 0, 1, 50, 90); + var m2 = createMatrix(Math.SQRT1_2, -Math.SQRT1_2, Math.SQRT1_2, Math.SQRT1_2, 0, 0); + var m3 = createMatrix(1, 0, 0, 1, 130, 160); + var result = m1.multiply(m2).multiply(m3); + roughCmpMatrix(result, [Math.SQRT1_2, -Math.SQRT1_2, Math.SQRT1_2, Math.SQRT1_2, 255.060974, 111.213203], + "Unexpected result after multiplying matrices"); + + // Check orig matrices are unchanged + cmpMatrix(m1, [1, 0, 0, 1, 50, 90], "Matrix changed after multiplication"); + roughCmpMatrix(m2, [Math.SQRT1_2, -Math.SQRT1_2, Math.SQRT1_2, Math.SQRT1_2, 0, 0], + "Matrix changed after multiplication"); + cmpMatrix(m3, [1, 0, 0, 1, 130, 160], "Matrix changed after multiplication"); +} + +// DOMMatrix inverse() raises(SVGException); +function testInverse() +{ + // Test inversion + var m = createMatrix(2, 0, 0, 4, 110, -50); + roughCmpMatrix(m.inverse(), [0.5, 0, 0, 0.25, -55, 12.5], + "Unexpected result after inverting matrix"); + + // Test non-invertable + m = createMatrix(0, 0, 1, 0, 0, 0); + m = m.inverse(); + ok(isNaN(m.a), "Failed to invalidate inverted singular matrix, got " + m.a); + ok(!m.is2D, "Failed to mark invalidated inverted singular matrix as 3D."); +} + +// DOMMatrix translate(in float x, in float y); +function testTranslate() +{ + var m = createMatrix(2, 0, 0, 1, 120, 100); + roughCmpMatrix(m.translate(100, -50), [2, 0, 0, 1, 320, 50], + "Unexpected result after translate"); +} + +// DOMMatrix scale(in float scaleFactor); +function testScale() +{ + var m = createMatrix(2, 0, 0, 1, 120, 100); + roughCmpMatrix(m.scale(0.5), [1, 0, 0, 0.5, 120, 100], + "Unexpected result after scale"); +} + +// DOMMatrix scaleNonUniform(in float scaleFactorX, in float scaleFactorY); +function testScaleNonUniform() +{ + var m = createMatrix(2, 0, 0, 1, 120, 100); + roughCmpMatrix(m.scaleNonUniform(0.5, -3), [1, 0, 0, -3, 120, 100], + "Unexpected result after scaleNonUniform"); +} + +// DOMMatrix rotate(in float angle); +function testRotate() +{ + var m = createMatrix(2, 0, 0, 1, 120, 100); + roughCmpMatrix(m.rotate(45), + [2*Math.cos(Math.PI/4), Math.sin(Math.PI/4), + 2*-Math.sin(Math.PI/4), Math.cos(Math.PI/4), + 120, 100], + "Unexpected result after rotate"); +} + +// DOMMatrix rotateFromVector(in float x, in float y) raises(SVGException); +function testRotateFromVector() +{ + var m = createMatrix(2, 0, 0, 1, 120, 100); + // Make a 150 degree angle + var result = m.rotateFromVector(-2, 1.1547); + roughCmpMatrix(result, + [2*Math.cos(5*Math.PI/6), Math.sin(5*Math.PI/6), + 2*-Math.sin(5*Math.PI/6), Math.cos(5*Math.PI/6), + 120, 100], + "Unexpected result after rotateFromVector"); + + // Test bad input (1) + var exn = null; + try { + m.rotateFromVector(1, 0); + } catch (e) { + exn = e; + } + is(exn, null, "did not throw exception with zero coord for rotateFromVector"); + + // Test bad input (2) + exn = null; + try { + m.rotateFromVector(0, 1); + } catch (e) { + exn = e; + } + is(exn, null, "did not throw exception with zero coord for rotateFromVector"); +} + +// DOMMatrix flipX(); +function testFlipX() +{ + var m = createMatrix(1, 2, 3, 4, 5, 6); + cmpMatrix(m.flipX(), [-1, -2, 3, 4, 5, 6], "Unexpected result after flipX"); +} + +// DOMMatrix flipY(); +function testFlipY() +{ + var m = createMatrix(1, 2, 3, 4, 5, 6); + cmpMatrix(m.flipY(), [1, 2, -3, -4, 5, 6], "Unexpected result after flipY"); +} + +// DOMMatrix skewX(in float angle); +function testSkewX() +{ + var m = createMatrix(2, 0, 0, 1, 120, 100); + roughCmpMatrix(m.skewX(30), [2, 0, 2*Math.tan(Math.PI/6), 1, 120, 100], + "Unexpected result after skewX"); +} + +// DOMMatrix skewY(in float angle); +function testSkewY() +{ + var m = createMatrix(2, 0, 0, 1, 120, 100); + roughCmpMatrix(m.skewY(30), [2, Math.tan(Math.PI/6), 0, 1, 120, 100], + "Unexpected result after skewY"); +} + +// DOMMatrix multiply(in DOMMatrix secondMatrix); +function testMultiplyInPlace() +{ + var m1 = createMatrix(1, 0, 0, 1, 50, 90); + var m2 = createMatrix(Math.SQRT1_2, -Math.SQRT1_2, Math.SQRT1_2, Math.SQRT1_2, 0, 0); + var m3 = createMatrix(1, 0, 0, 1, 130, 160); + m1.multiplySelf(m2).multiplySelf(m3); + roughCmpMatrix(m1, [Math.SQRT1_2, -Math.SQRT1_2, Math.SQRT1_2, Math.SQRT1_2, 255.060974, 111.213203], + "Unexpected result after multiplying matrices"); + + // Check orig matrices are unchanged + roughCmpMatrix(m2, [Math.SQRT1_2, -Math.SQRT1_2, Math.SQRT1_2, Math.SQRT1_2, 0, 0], + "Matrix changed after multiplication"); + cmpMatrix(m3, [1, 0, 0, 1, 130, 160], "Matrix changed after multiplication"); +} + +// DOMMatrix inverse() raises(SVGException); +function testInverseInPlace() +{ + // Test inversion + var m = createMatrix(2, 0, 0, 4, 110, -50); + m.invertSelf(); + roughCmpMatrix(m, [0.5, 0, 0, 0.25, -55, 12.5], + "Unexpected result after inverting matrix"); + + // Test non-invertable + m = createMatrix(0, 0, 1, 0, 0, 0); + m.invertSelf(); + ok(isNaN(m.a), "Failed to invalidate inverted singular matrix, got " + m.a); + ok(!m.is2D, "Failed to mark invalidated inverted singular matrix as 3D."); +} + +// DOMMatrix translate(in float x, in float y); +function testTranslateInPlace() +{ + var m = createMatrix(2, 0, 0, 1, 120, 100); + m.translateSelf(100, -50) + roughCmpMatrix(m, [2, 0, 0, 1, 320, 50], + "Unexpected result after translate"); +} + +// DOMMatrix scale(in float scaleFactor); +function testScaleInPlace() +{ + var m = createMatrix(2, 0, 0, 1, 120, 100); + m.scaleSelf(0.5); + roughCmpMatrix(m, [1, 0, 0, 0.5, 120, 100], + "Unexpected result after scale"); +} + +// DOMMatrix rotate(in float angle); +function testRotateInPlace() +{ + var m = createMatrix(2, 0, 0, 1, 120, 100); + m.rotateSelf(45); + roughCmpMatrix(m, + [2*Math.cos(Math.PI/4), Math.sin(Math.PI/4), + 2*-Math.sin(Math.PI/4), Math.cos(Math.PI/4), + 120, 100], + "Unexpected result after rotate"); +} + +// DOMMatrix rotateFromVector(in float x, in float y) raises(SVGException); +function testRotateFromVectorInPlace() +{ + var m = createMatrix(2, 0, 0, 1, 120, 100); + // Make a 150 degree angle + m.rotateFromVectorSelf(-2, 1.1547); + roughCmpMatrix(m, + [2*Math.cos(5*Math.PI/6), Math.sin(5*Math.PI/6), + 2*-Math.sin(5*Math.PI/6), Math.cos(5*Math.PI/6), + 120, 100], + "Unexpected result after rotateFromVector"); + + // Test bad input (1) + try { + m.rotateFromVectorSelf(1, 0); + ok(true, "did not throw exception with zero coord for rotateFromVector"); + } catch (e) { + ok(false, + "Got unexpected exception " + e + ", expected NotSupportedError"); + } + + // Test bad input (2) + try { + m.rotateFromVectorSelf(0, 1); + ok(true, "did not throw exception with zero coord for rotateFromVector"); + } catch (e) { } +} + +// DOMMatrix skewX(in float angle); +function testSkewXInPlace() +{ + var m = createMatrix(2, 0, 0, 1, 120, 100); + m.skewXSelf(30); + roughCmpMatrix(m, [2, 0, 2*Math.tan(Math.PI/6), 1, 120, 100], + "Unexpected result after skewX"); +} + +// DOMMatrix skewY(in float angle); +function testSkewYInPlace() +{ + var m = createMatrix(2, 0, 0, 1, 120, 100); + m.skewYSelf(30); + roughCmpMatrix(m, [2, Math.tan(Math.PI/6), 0, 1, 120, 100], + "Unexpected result after skewY"); +} + +function testCreateMatrix3D() +{ + var m = new DOMMatrix([1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1]); + + // Should be initialised to identity + cmpMatrix(m, [1, 0, 0, 1, 0, 0], + "DOMMatrix should produce identity matrix"); + is(m.is2D, false, "should produce 3d matrix"); + + m = new DOMMatrix([1, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0]); + + // Should be initialised to identity + cmpMatrix(m, [1, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0], + "DOMMatrix should produce identity matrix"); + is(m.is2D, false, "should produce 3d matrix"); +} + +// DOMMatrix multiply(in DOMMatrix secondMatrix); +function testMultiply3D() +{ + var m1 = createMatrix(1, 0, 0, 1, 50, 90); + var m2 = create3DMatrix(Math.SQRT1_2, -Math.SQRT1_2, Math.SQRT1_2, Math.SQRT1_2, 0, 0); + var m3 = create3DMatrix(1, 0, 0, 1, 130, 160); + var result = m1.multiply(m2).multiply(m3); + ok(m1.is2D == true, "should produce 3d matrix"); + roughCmpMatrix(result, [Math.SQRT1_2, -Math.SQRT1_2, Math.SQRT1_2, Math.SQRT1_2, 255.060974, 111.213203], + "Unexpected result after multiplying matrices"); + + // Check orig matrices are unchanged + cmpMatrix(m1, [1, 0, 0, 1, 50, 90], "Matrix changed after multiplication"); + roughCmpMatrix(m2, [Math.SQRT1_2, -Math.SQRT1_2, Math.SQRT1_2, Math.SQRT1_2, 0, 0], + "Matrix changed after multiplication"); + cmpMatrix(m3, [1, 0, 0, 1, 130, 160], "Matrix changed after multiplication"); +} + +// DOMMatrix inverse() raises(SVGException); +function testInverse3D() +{ + // Test inversion + var m = create3DMatrix(2, 0, 0, 4, 110, -50); + roughCmpMatrix(m.inverse(), [0.5, 0, 0, 0.25, -55, 12.5], + "Unexpected result after inverting matrix"); + + // Test non-invertable + m = createMatrix(0, 0, 1, 0, 0, 0); + m = m.inverse(); + ok(isNaN(m.a), "Failed to invalidate inverted singular matrix, got " + m.a); + ok(!m.is2D, "Failed to mark invalidated inverted singular matrix as 3D."); +} + +// DOMMatrix translate(in float x, in float y); +function testTranslate3D() +{ + var m = create3DMatrix(2, 0, 0, 1, 120, 100); + roughCmpMatrix(m.translate(100, -50), [2, 0, 0, 1, 320, 50], + "Unexpected result after translate"); +} + +// DOMMatrix scale(in float scaleFactor); +function testScale3D() +{ + var m = create3DMatrix(2, 0, 0, 1, 120, 100); + roughCmpMatrix(m.scale(0.5), [1, 0, 0, 0.5, 120, 100], + "Unexpected result after scale"); +} + +function Matrix3D() { + this.m = new Float32Array([ + 1, 0, 0, 0, + 0, 1, 0, 0, + 0, 0, 1, 0, + 0, 0, 0, 1 + ]); +} + +Matrix3D.prototype = { + translate(x, y, z, result) { + result = result || new Matrix3D(); + var m = result.m; + + m[0] = 1; + m[1] = 0; + m[2] = 0; + m[3] = x; + + m[4] = 0; + m[5] = 1; + m[6] = 0; + m[7] = y; + + m[8] = 0; + m[9] = 0; + m[10] = 1; + m[11] = z; + + m[12] = 0; + m[13] = 0; + m[14] = 0; + m[15] = 1; + + return result; + }, + inverse(matrix, result) { + result = result || new Matrix3D(); + var m = matrix.m, r = result.m; + + r[0] = m[5]*m[10]*m[15] - m[5]*m[14]*m[11] - m[6]*m[9]*m[15] + m[6]*m[13]*m[11] + m[7]*m[9]*m[14] - m[7]*m[13]*m[10]; + r[1] = -m[1]*m[10]*m[15] + m[1]*m[14]*m[11] + m[2]*m[9]*m[15] - m[2]*m[13]*m[11] - m[3]*m[9]*m[14] + m[3]*m[13]*m[10]; + r[2] = m[1]*m[6]*m[15] - m[1]*m[14]*m[7] - m[2]*m[5]*m[15] + m[2]*m[13]*m[7] + m[3]*m[5]*m[14] - m[3]*m[13]*m[6]; + r[3] = -m[1]*m[6]*m[11] + m[1]*m[10]*m[7] + m[2]*m[5]*m[11] - m[2]*m[9]*m[7] - m[3]*m[5]*m[10] + m[3]*m[9]*m[6]; + + r[4] = -m[4]*m[10]*m[15] + m[4]*m[14]*m[11] + m[6]*m[8]*m[15] - m[6]*m[12]*m[11] - m[7]*m[8]*m[14] + m[7]*m[12]*m[10]; + r[5] = m[0]*m[10]*m[15] - m[0]*m[14]*m[11] - m[2]*m[8]*m[15] + m[2]*m[12]*m[11] + m[3]*m[8]*m[14] - m[3]*m[12]*m[10]; + r[6] = -m[0]*m[6]*m[15] + m[0]*m[14]*m[7] + m[2]*m[4]*m[15] - m[2]*m[12]*m[7] - m[3]*m[4]*m[14] + m[3]*m[12]*m[6]; + r[7] = m[0]*m[6]*m[11] - m[0]*m[10]*m[7] - m[2]*m[4]*m[11] + m[2]*m[8]*m[7] + m[3]*m[4]*m[10] - m[3]*m[8]*m[6]; + + r[8] = m[4]*m[9]*m[15] - m[4]*m[13]*m[11] - m[5]*m[8]*m[15] + m[5]*m[12]*m[11] + m[7]*m[8]*m[13] - m[7]*m[12]*m[9]; + r[9] = -m[0]*m[9]*m[15] + m[0]*m[13]*m[11] + m[1]*m[8]*m[15] - m[1]*m[12]*m[11] - m[3]*m[8]*m[13] + m[3]*m[12]*m[9]; + r[10] = m[0]*m[5]*m[15] - m[0]*m[13]*m[7] - m[1]*m[4]*m[15] + m[1]*m[12]*m[7] + m[3]*m[4]*m[13] - m[3]*m[12]*m[5]; + r[11] = -m[0]*m[5]*m[11] + m[0]*m[9]*m[7] + m[1]*m[4]*m[11] - m[1]*m[8]*m[7] - m[3]*m[4]*m[9] + m[3]*m[8]*m[5]; + + r[12] = -m[4]*m[9]*m[14] + m[4]*m[13]*m[10] + m[5]*m[8]*m[14] - m[5]*m[12]*m[10] - m[6]*m[8]*m[13] + m[6]*m[12]*m[9]; + r[13] = m[0]*m[9]*m[14] - m[0]*m[13]*m[10] - m[1]*m[8]*m[14] + m[1]*m[12]*m[10] + m[2]*m[8]*m[13] - m[2]*m[12]*m[9]; + r[14] = -m[0]*m[5]*m[14] + m[0]*m[13]*m[6] + m[1]*m[4]*m[14] - m[1]*m[12]*m[6] - m[2]*m[4]*m[13] + m[2]*m[12]*m[5]; + r[15] = m[0]*m[5]*m[10] - m[0]*m[9]*m[6] - m[1]*m[4]*m[10] + m[1]*m[8]*m[6] + m[2]*m[4]*m[9] - m[2]*m[8]*m[5]; + + var det = m[0]*r[0] + m[1]*r[4] + m[2]*r[8] + m[3]*r[12]; + for (var i = 0; i < 16; i++) r[i] /= det; + return result; + }, + multiply(left, result) { + result = result || new Matrix3D(); + var a = this.m, b = left.m, r = result.m; + + r[0] = a[0] * b[0] + a[1] * b[4] + a[2] * b[8] + a[3] * b[12]; + r[1] = a[0] * b[1] + a[1] * b[5] + a[2] * b[9] + a[3] * b[13]; + r[2] = a[0] * b[2] + a[1] * b[6] + a[2] * b[10] + a[3] * b[14]; + r[3] = a[0] * b[3] + a[1] * b[7] + a[2] * b[11] + a[3] * b[15]; + + r[4] = a[4] * b[0] + a[5] * b[4] + a[6] * b[8] + a[7] * b[12]; + r[5] = a[4] * b[1] + a[5] * b[5] + a[6] * b[9] + a[7] * b[13]; + r[6] = a[4] * b[2] + a[5] * b[6] + a[6] * b[10] + a[7] * b[14]; + r[7] = a[4] * b[3] + a[5] * b[7] + a[6] * b[11] + a[7] * b[15]; + + r[8] = a[8] * b[0] + a[9] * b[4] + a[10] * b[8] + a[11] * b[12]; + r[9] = a[8] * b[1] + a[9] * b[5] + a[10] * b[9] + a[11] * b[13]; + r[10] = a[8] * b[2] + a[9] * b[6] + a[10] * b[10] + a[11] * b[14]; + r[11] = a[8] * b[3] + a[9] * b[7] + a[10] * b[11] + a[11] * b[15]; + + r[12] = a[12] * b[0] + a[13] * b[4] + a[14] * b[8] + a[15] * b[12]; + r[13] = a[12] * b[1] + a[13] * b[5] + a[14] * b[9] + a[15] * b[13]; + r[14] = a[12] * b[2] + a[13] * b[6] + a[14] * b[10] + a[15] * b[14]; + r[15] = a[12] * b[3] + a[13] * b[7] + a[14] * b[11] + a[15] * b[15]; + + return result; + }, + scale(x, y, z, result) { + result = result || new Matrix3D(); + var m = result.m; + + m[0] = x; + m[1] = 0; + m[2] = 0; + m[3] = 0; + + m[4] = 0; + m[5] = y; + m[6] = 0; + m[7] = 0; + + m[8] = 0; + m[9] = 0; + m[10] = z; + m[11] = 0; + + m[12] = 0; + m[13] = 0; + m[14] = 0; + m[15] = 1; + + return result; + }, + rotate(a, x, y, z, result) { + result = result || new Matrix3D(); + var m = result.m; + + var d = Math.sqrt(x*x + y*y + z*z); + a *= Math.PI / 180; x /= d; y /= d; z /= d; + var c = Math.cos(a), s = Math.sin(a), t = 1 - c; + + m[0] = x * x * t + c; + m[1] = x * y * t - z * s; + m[2] = x * z * t + y * s; + m[3] = 0; + + m[4] = y * x * t + z * s; + m[5] = y * y * t + c; + m[6] = y * z * t - x * s; + m[7] = 0; + + m[8] = z * x * t - y * s; + m[9] = z * y * t + x * s; + m[10] = z * z * t + c; + m[11] = 0; + + m[12] = 0; + m[13] = 0; + m[14] = 0; + m[15] = 1; + + return result; + }, + swap(result) { + result = result || new Matrix3D(); + for (var x = 0; x < 16; x++) + result.m[x] = this.m[Math.floor(x/4) + (x%4)*4]; + + return result; + } +}; + + +function test3D() +{ + var m = new DOMMatrix() + var m2 = new Matrix3D(); + + m.translateSelf(2,3,4).scaleSelf(1.2, 2.3, 3.4, 0, 0, 0); + m2 = m2.multiply(m2.translate(2,3,4)).multiply(m2.scale(1.2, 2.3, 3.4)).swap(); + + ok(CompareMatrix(m2, m), "translate + scale in 3d didn't match, expected: " + formatMatrix(m2.m) + ", got: " + formatMatrix(m)); + + m.invertSelf(); + m2 = new Matrix3D(); + m2 = m2.multiply(m2.translate(2,3,4)).multiply(m2.scale(1.2, 2.3, 3.4)); + m2 = m2.inverse(m2).swap(); + ok(CompareMatrix(m2, m), "translate + scale in inverted 3d didn't match, expected: " + formatMatrix(m2.m) + ", got: " + formatMatrix(m)); +} + +function testParsing() +{ + var m = new DOMMatrix("translate(10px, 20px) scale(.5, 2) rotate(45deg)"); + var m2 = new DOMMatrix(); + m2.translateSelf(10, 20).scaleSelf(.5,2).rotateSelf(45); + ok(CompareDOMMatrix(m2, m), "string parsing didn't match"); + + m = new DOMMatrix(); + m.setMatrixValue("translate(10px, 20px) scale(.5, 2) rotate(45deg)"); + ok(CompareDOMMatrix(m2, m), "string parsing didn't match"); +} + + +function testStringify() { + var m = new DOMMatrix(); + var s = "" + m; + ok(s == "matrix" + formatMatrix(m), "stringifier 1 produced wrong result: " + s); + m.a = 100; + s = "" + m; + ok(s == "matrix" + formatMatrix(m), "stringifier 2 produced wrong result: " + s); + m.m43 = 200; + s = "" + m; + ok(s == "matrix3d" + formatMatrix(m), "stringifier 3 produced wrong result:" + s); +} + +window.addEventListener("load", main); + +</script> +</pre> +</body> +</html> diff --git a/dom/tests/mochitest/general/test_WebKitCSSMatrix.html b/dom/tests/mochitest/general/test_WebKitCSSMatrix.html new file mode 100644 index 0000000000..690a7d2c63 --- /dev/null +++ b/dom/tests/mochitest/general/test_WebKitCSSMatrix.html @@ -0,0 +1,339 @@ +<!DOCTYPE html> +<meta charset=utf-8> +<title>Test for WebKitCSSMatrix</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<div id="log"></div> +<script> +function RoughCompareMatrix(dm1, dm2) +{ + var m1 = dm1.toFloat32Array(); + var m2 = dm2.toFloat32Array(); + + if (m1.length != m2.length) { + return false; + } + + const tolerance = 1 / 65535; + for (var x = 0; x < m1.length; x++) { + if (Math.abs(m1[x] - m2[x]) > tolerance) { + return false; + } + } + + return true; +} + +function CompareMatrix(dm1, dm2) +{ + var m1 = dm1.toFloat32Array(); + var m2 = dm2.toFloat32Array(); + + if (m1.length != m2.length) { + return false; + } + + for (var x = 0; x < m1.length; x++) { + if (m1[x] != m2[x] && !(Number.isNaN(m1[x]) && Number.isNaN(m2[x]))) { + return false; + } + } + + return true; +} + +test(function() { + var m = new WebKitCSSMatrix(); + + assert_equals(m.m11, 1, "m11 should be 1"); + assert_equals(m.m22, 1, "m22 should be 1"); + assert_equals(m.m33, 1, "m33 should be 1"); + assert_equals(m.m44, 1, "m44 should be 1"); + assert_equals(m.m12, 0, "m12 should be 0"); + assert_equals(m.m13, 0, "m13 should be 0"); + assert_equals(m.m14, 0, "m14 should be 0"); + assert_equals(m.m21, 0, "m21 should be 0"); + assert_equals(m.m23, 0, "m23 should be 0"); + assert_equals(m.m24, 0, "m24 should be 0"); + assert_equals(m.m31, 0, "m31 should be 0"); + assert_equals(m.m32, 0, "m32 should be 0"); + assert_equals(m.m34, 0, "m34 should be 0"); + assert_equals(m.m41, 0, "m41 should be 0"); + assert_equals(m.m42, 0, "m42 should be 0"); + assert_equals(m.m43, 0, "m43 should be 0"); +}, "Test constructor with no arguments."); + +test(function() { + var m = new WebKitCSSMatrix("matrix(1,2,3,4,5,6)"); + + assert_equals(m.m11, 1, "m11 should be 1"); + assert_equals(m.m12, 2, "m12 should be 2"); + assert_equals(m.m21, 3, "m21 should be 3"); + assert_equals(m.m22, 4, "m22 should be 4"); + assert_equals(m.m41, 5, "m41 should be 5"); + assert_equals(m.m42, 6, "m42 should be 6"); +}, "Test constructor with transform list."); + +test(function() { + var m1 = new WebKitCSSMatrix("matrix(1,2,3,4,5,6)"); + var m2 = new WebKitCSSMatrix(m1); + + assert_true(RoughCompareMatrix(m1, m2), "Matrix should be equal."); +}, "Test constructor with other matrix."); + +test(function() { + var m = new WebKitCSSMatrix(); + var mr = m.setMatrixValue("matrix(1,2,3,4,5,6)"); + + assert_equals(m.m11, 1, "m11 should be 1"); + assert_equals(m.m12, 2, "m12 should be 2"); + assert_equals(m.m21, 3, "m21 should be 3"); + assert_equals(m.m22, 4, "m22 should be 4"); + assert_equals(m.m41, 5, "m41 should be 5"); + assert_equals(m.m42, 6, "m42 should be 6"); + + assert_equals(m, mr, "Return value of setMatrixValue should be the same matrix."); +}, "Test setMatrixValue."); + +test(function() { + var m1 = new WebKitCSSMatrix("matrix(1,2,3,4,5,6)"); + var m2 = new DOMMatrix("matrix(1,2,3,4,5,6)"); + var m3 = new WebKitCSSMatrix("matrix(6,5,4,3,2,1)"); + var m4 = new WebKitCSSMatrix("matrix(1,2,3,4,5,6)"); + + var m1r = m1.multiply(m3); + var m2r = m2.multiply(m3); + + assert_true(RoughCompareMatrix(m1r, m2r), "multiply should return the same result as DOMMatrixReadOnly."); + assert_true(CompareMatrix(m1, m4), "Multiply should not mutate original matrix."); +}, "Test multiply."); + +test(function() { + var m1 = new WebKitCSSMatrix("matrix(1,2,3,4,5,6)"); + var m2 = new DOMMatrix("matrix(1,2,3,4,5,6)"); + var m3 = new WebKitCSSMatrix("matrix(1,2,3,4,5,6)"); + + var m1r = m1.inverse(); + var m2r = m2.inverse(); + + assert_true(RoughCompareMatrix(m1r, m2r), "inverse should return the same result as DOMMatrixReadOnly."); + assert_true(CompareMatrix(m1, m3), "inverse should not mutate original matrix."); +}, "Test inverse."); + +test(function() { + var m1 = new WebKitCSSMatrix("matrix(1,2,3,4,5,6)"); + var m2 = new DOMMatrix("matrix(1,2,3,4,5,6)"); + var m3 = new WebKitCSSMatrix("matrix(1,2,3,4,5,6)"); + + var m1r = m1.translate(2, 3, 4); + var m2r = m2.translate(2, 3, 4); + + assert_true(RoughCompareMatrix(m1r, m2r), "translate should return the same result as DOMMatrixReadOnly."); + assert_true(CompareMatrix(m1, m3), "translate should not mutate original matrix."); +}, "Test translate."); + +test(function() { + var m1 = new WebKitCSSMatrix("matrix(1,2,3,4,5,6)"); + var m2 = new DOMMatrix("matrix(1,2,3,4,5,6)"); + var m3 = new WebKitCSSMatrix("matrix(1,2,3,4,5,6)"); + + var m1r = m1.scale(2); + var m2r = m2.scale(2, 2, 1); + + assert_true(RoughCompareMatrix(m1r, m2r), "scale should return the same result as DOMMatrixReadOnly."); + assert_true(CompareMatrix(m1, m3), "scale should not mutate original matrix."); +}, "Test scale with 1 argument."); + +test(function() { + var m1 = new WebKitCSSMatrix("matrix(1,2,3,4,5,6)"); + var m2 = new DOMMatrix("matrix(1,2,3,4,5,6)"); + var m3 = new WebKitCSSMatrix("matrix(1,2,3,4,5,6)"); + + var m1r = m1.scale(2, 3); + var m2r = m2.scale(2, 3, 1); + + assert_true(RoughCompareMatrix(m1r, m2r), "scale should return the same result as DOMMatrixReadOnly."); + assert_true(CompareMatrix(m1, m3), "scale should not mutate original matrix."); +}, "Test scale with 2 arguments."); + +test(function() { + var m1 = new WebKitCSSMatrix("matrix(1,2,3,4,5,6)"); + var m2 = new DOMMatrix("matrix(1,2,3,4,5,6)"); + var m3 = new WebKitCSSMatrix("matrix(1,2,3,4,5,6)"); + + var m1r = m1.scale(2, 3, 4); + var m2r = m2.scale(2, 3, 4); + + assert_true(RoughCompareMatrix(m1r, m2r), "scale should return the same result as DOMMatrixReadOnly."); + assert_true(CompareMatrix(m1, m3), "scale should not mutate original matrix."); +}, "Test scale with 3 arguments."); + +test(function() { + var m1 = new WebKitCSSMatrix("matrix(1,2,3,4,5,6)"); + var m2 = new DOMMatrix("matrix(1,2,3,4,5,6)"); + var m3 = new WebKitCSSMatrix("matrix(1,2,3,4,5,6)"); + + var m1r = m1.scale(undefined, 3, 4); + var m2r = m2.scale(1, 3, 4); + + assert_true(RoughCompareMatrix(m1r, m2r), "scale should return the same result as DOMMatrixReadOnly."); + assert_true(CompareMatrix(m1, m3), "scale should not mutate original matrix."); +}, "Test scale with undefined scaleX argument."); + +test(function() { + var m1 = new WebKitCSSMatrix("matrix(1,2,3,4,5,6)"); + var m2 = new DOMMatrix("matrix(1,2,3,4,5,6)"); + var m3 = new WebKitCSSMatrix("matrix(1,2,3,4,5,6)"); + + var m1r = m1.scale(2, undefined, 4); + var m2r = m2.scale(2, 2, 4); + + assert_true(RoughCompareMatrix(m1r, m2r), "scale should return the same result as DOMMatrixReadOnly."); + assert_true(CompareMatrix(m1, m3), "scale should not mutate original matrix."); +}, "Test scale with undefined scaleY argument."); + +test(function() { + var m1 = new WebKitCSSMatrix("matrix(1,2,3,4,5,6)"); + var m2 = new DOMMatrix("matrix(1,2,3,4,5,6)"); + var m3 = new WebKitCSSMatrix("matrix(1,2,3,4,5,6)"); + + var m1r = m1.scale(2, 3, undefined); + var m2r = m2.scale(2, 3, 1); + + assert_true(RoughCompareMatrix(m1r, m2r), "scale should return the same result as DOMMatrixReadOnly."); + assert_true(CompareMatrix(m1, m3), "scale should not mutate original matrix."); +}, "Test scale with undefined scaleZ argument."); + +test(function() { + var m1 = new WebKitCSSMatrix("matrix(1,2,3,4,5,6)"); + var m2 = new DOMMatrix("matrix(1,2,3,4,5,6)"); + var m3 = new WebKitCSSMatrix("matrix(1,2,3,4,5,6)"); + + var m1r = m1.rotate(2); + var m2r = m2.rotateAxisAngle(0, 0, 1, 2); // Rotate around unit vector on z-axis. + + assert_true(RoughCompareMatrix(m1r, m2r)); + assert_true(CompareMatrix(m1, m3), "rotate should not mutate original matrix."); +}, "Test rotate with 1 argument."); + +test(function() { + var m1 = new WebKitCSSMatrix("matrix(1,2,3,4,5,6)"); + var m2 = new DOMMatrix("matrix(1,2,3,4,5,6)"); + var m3 = new WebKitCSSMatrix("matrix(1,2,3,4,5,6)"); + + var m1r = m1.rotate(2, 3); + var m2r = m2.rotateAxisAngle(0, 1, 0, 3); // Rotate around unit vector on x-axis. + m2r = m2r.rotateAxisAngle(1, 0, 0, 2); // Rotate around unit vector on y-axis. + + assert_true(RoughCompareMatrix(m1r, m2r)); + assert_true(CompareMatrix(m1, m3), "rotate should not mutate original matrix."); +}, "Test rotate with 2 arguments."); + +test(function() { + var m1 = new WebKitCSSMatrix("matrix(1,2,3,4,5,6)"); + var m2 = new DOMMatrix("matrix(1,2,3,4,5,6)"); + var m3 = new WebKitCSSMatrix("matrix(1,2,3,4,5,6)"); + + var m1r = m1.rotate(2, 3, 4); + var m2r = m2.rotateAxisAngle(0, 0, 1, 4); // Rotate around unit vector on z-axis. + m2r = m2r.rotateAxisAngle(0, 1, 0, 3); // Rotate around unit vector on y-axis. + m2r = m2r.rotateAxisAngle(1, 0, 0, 2); // Rotate around unit vector on x-axis. + + assert_true(RoughCompareMatrix(m1r, m2r)); + assert_true(CompareMatrix(m1, m3), "rotate should not mutate original matrix."); +}, "Test rotate with 3 arguments."); + +test(function() { + var m1 = new WebKitCSSMatrix("matrix(1,2,3,4,5,6)"); + var m2 = new DOMMatrix("matrix(1,2,3,4,5,6)"); + var m3 = new WebKitCSSMatrix("matrix(1,2,3,4,5,6)"); + + var m1r = m1.rotate(2, undefined, undefined); + var m2r = m2.rotateAxisAngle(0, 0, 1, 2); // Rotate around unit vector on z-axis. + + assert_true(RoughCompareMatrix(m1r, m2r)); + assert_true(CompareMatrix(m1, m3), "rotate should not mutate original matrix."); +}, "Test rotate with rotY and rotZ as undefined."); + +test(function() { + var m1 = new WebKitCSSMatrix("matrix(1,2,3,4,5,6)"); + var m2 = new DOMMatrix("matrix(1,2,3,4,5,6)"); + var m3 = new WebKitCSSMatrix("matrix(1,2,3,4,5,6)"); + + var m1r = m1.rotate(undefined, 3, 4); + var m2r = m2.rotateAxisAngle(0, 0, 1, 4); // Rotate around unit vector on z-axis. + m2r = m2r.rotateAxisAngle(0, 1, 0, 3); // Rotate around unit vector on y-axis. + + assert_true(RoughCompareMatrix(m1r, m2r)); + assert_true(CompareMatrix(m1, m3), "rotate should not mutate original matrix."); +}, "Test rotate with rotX as undefined."); + +test(function() { + var m1 = new WebKitCSSMatrix("matrix(1,2,3,4,5,6)"); + var m2 = new DOMMatrix("matrix(1,2,3,4,5,6)"); + var m3 = new WebKitCSSMatrix("matrix(1,2,3,4,5,6)"); + + var m1r = m1.rotateAxisAngle(2, 3, 4, 5); + var m2r = m2.rotateAxisAngle(2, 3, 4, 5); + + assert_true(RoughCompareMatrix(m1r, m2r), "rotateAxisAngle should return the same result as DOMMatrixReadOnly."); + assert_true(CompareMatrix(m1, m3), "rotateAxisAngle should not mutate original matrix."); +}, "Test rotateAxisAngle."); + +test(function() { + var m1 = new WebKitCSSMatrix("matrix(1,2,3,4,5,6)"); + var m2 = new DOMMatrix("matrix(1,2,3,4,5,6)"); + var m3 = new WebKitCSSMatrix("matrix(1,2,3,4,5,6)"); + + var m1r = m1.skewX(2); + var m2r = m2.skewX(2); + + assert_true(RoughCompareMatrix(m1r, m2r), "skewX should return the same result as DOMMatrixReadOnly."); + assert_true(CompareMatrix(m1, m3), "skewX should not mutate original matrix."); +}, "Test skewX."); + +test(function() { + var m1 = new WebKitCSSMatrix("matrix(1,2,3,4,5,6)"); + var m2 = new DOMMatrix("matrix(1,2,3,4,5,6)"); + var m3 = new WebKitCSSMatrix("matrix(1,2,3,4,5,6)"); + + var m1r = m1.skewY(2); + var m2r = m2.skewY(2); + + assert_true(RoughCompareMatrix(m1r, m2r), "skewY should return the same result as DOMMatrixReadOnly."); + assert_true(CompareMatrix(m1, m3), "skewY should not mutate original matrix."); +}, "Test skewY."); + +test(function() { + var m1 = new WebKitCSSMatrix("matrix(1,0,0,0,0,0)"); + m1 = m1.inverse(); + + var m2 = new DOMMatrix(new Array(16).fill(NaN)); + assert_true(CompareMatrix(m1, m2), "Inverting a non-invertible matrix should set all attributes to NaN.") +}, "Test that inverting an invertible matrix throws."); + +test(function() { + var m1 = new WebKitCSSMatrix("translate(10px, 10px)"); + var m2 = new DOMMatrix(); + m2.translateSelf(10, 10); + assert_true(RoughCompareMatrix(m1, m2), "translate in constructor should result in translated matrix"); + + assert_throws("SyntaxError", function() { new WebKitCSSMatrix("translate(10em, 10em)"); }, "Transform function may not contain relative units.") + assert_throws("SyntaxError", function() { new WebKitCSSMatrix("translate(10%, 10%)"); }, "Transform function may not contain percentage.") +}, "Test constructor with translate"); + +test(function() { + assert_throws("SyntaxError", function() { new WebKitCSSMatrix("initial"); }, "initial is not a valid constructor argument.") + assert_throws("SyntaxError", function() { new WebKitCSSMatrix("inherit"); }, "inherit is not a valid constructor arugment.") +}, "Test invalid constructor arguments."); + +test(function() { + var m1 = new WebKitCSSMatrix(); + m1 = m1.rotateAxisAngle(0, 0, 1, 45); + + var m2 = new WebKitCSSMatrix(); + m2 = m2.rotateAxisAngle(0, 0, 3, 45); + + assert_true(RoughCompareMatrix(m1, m2), "rotateAxisAngle should normalize vector to unit vector."); +}, "Test normalization of vector for rotateAxisAngle"); +</script> diff --git a/dom/tests/mochitest/general/test_bug1161721.html b/dom/tests/mochitest/general/test_bug1161721.html new file mode 100644 index 0000000000..ab609bf046 --- /dev/null +++ b/dom/tests/mochitest/general/test_bug1161721.html @@ -0,0 +1,32 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1161721 +--> +<head> + <title>Test for Bug 1161721</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <script src="/tests/SimpleTest/EventUtils.js"></script> + <script src="/tests/SimpleTest/WindowSnapshot.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1161721">Mozilla Bug 1161721</a> +<p id="display"></p> + +<div id="content"> +</div> + +<pre id="test"> + <script type="application/javascript"> + ok(!document.queryCommandSupported("paste"), "Paste isn't supported in non-privilged JavaScript"); + ok(document.queryCommandSupported("copy"), "Copy is supported in non-privilged JavaScript"); + ok(document.queryCommandSupported("cut"), "Cut is supported in non-privilged JavaScript"); + + ok(SpecialPowers.wrap(document).queryCommandSupported("paste"), "Paste is supported in privilged JavaScript"); + ok(SpecialPowers.wrap(document).queryCommandSupported("copy"), "Copy is supported in privilged JavaScript"); + ok(SpecialPowers.wrap(document).queryCommandSupported("cut"), "Cut is supported in privilged JavaScript"); + </script> +</pre> +</body> +</html> diff --git a/dom/tests/mochitest/general/test_bug1170911.html b/dom/tests/mochitest/general/test_bug1170911.html new file mode 100644 index 0000000000..af0e71576f --- /dev/null +++ b/dom/tests/mochitest/general/test_bug1170911.html @@ -0,0 +1,90 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1012662 +--> +<head> + <title>Test for Bug 1170911</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <script src="/tests/SimpleTest/EventUtils.js"></script> + <script src="/tests/SimpleTest/WindowSnapshot.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1170911">Mozilla Bug 1170911</a> +<p id="display"></p> + +<div id="content"> + <textarea>textarea text</textarea> +</div> + +<pre id="test"> +<script> +const TEXTAREA = document.querySelector('textarea'); +const TEXTAREA_VALUE = TEXTAREA.value; + +function doTest() { + is(document.queryCommandSupported("copy"), false, + "Copy support should have been disabled"); + is(document.queryCommandSupported("cut"), false, + "Cut support should have been disabled"); + + document.addEventListener("keydown", tryCopy); + sendString("Q"); +} + +function tryCopy(evt) { + evt.preventDefault(); + document.removeEventListener("keydown", tryCopy); + TEXTAREA.setSelectionRange(0, TEXTAREA_VALUE.length); + TEXTAREA.focus(); + + SimpleTest.waitForClipboard(null, function () { + is(document.queryCommandEnabled("copy"), false, + "Copy should not be allowed when dom.allow_cut_copy is off"); + is(document.execCommand("copy"), false, + "Copy should not be executed when dom.allow_cut_copy is off"); + is(TEXTAREA.value, TEXTAREA_VALUE, + "Content in the textarea shouldn't be changed"); + TEXTAREA.value = TEXTAREA_VALUE; + }, + /* success fn */ SimpleTest.finish, + /* failure fn */ function () { + document.addEventListener("keydown", tryCut); + sendString("Q"); + }, + /* flavor */ undefined, + /* timeout */ undefined, + /* expect failure */ true); +} + +function tryCut(evt) { + evt.preventDefault(); + document.removeEventListener("keydown", tryCut); + TEXTAREA.setSelectionRange(0, TEXTAREA_VALUE.length); + TEXTAREA.focus(); + + SimpleTest.waitForClipboard(null, function () { + is(document.queryCommandEnabled("cut"), false, + "Cut should not be allowed when dom.allow_cut_copy is off"); + is(document.execCommand("cut"), false, + "Cut should not be executed when dom.allow_cut_copy is off"); + is(TEXTAREA.value, TEXTAREA_VALUE, + "Content in the textarea shouldn't be changed"); + }, + /* success fn */ SimpleTest.finish, + /* failure fn */ SimpleTest.finish, + /* flavor */ undefined, + /* timeout */ undefined, + /* expect failure */ true); +} + +SimpleTest.waitForExplicitFinish(); +SimpleTest.waitForFocus(() => { + SpecialPowers.pushPrefEnv({"set": [["dom.allow_cut_copy", false]]}, doTest); +}); + +</script> +</pre> +</body> +</html> diff --git a/dom/tests/mochitest/general/test_bug1208217.html b/dom/tests/mochitest/general/test_bug1208217.html new file mode 100644 index 0000000000..1f76ba34de --- /dev/null +++ b/dom/tests/mochitest/general/test_bug1208217.html @@ -0,0 +1,31 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1208217 +--> +<head> + <title>Test for Bug 1208217</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <script src="/tests/SimpleTest/EventUtils.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1208217">Mozilla Bug 1208217</a> +<script type="application/javascript"> + + add_task(async function() { + let pasteCount = 0; + document.addEventListener('paste', function() { + pasteCount++; + }); + + is(pasteCount, 0); + + synthesizeKey("V", {accelKey: true}); + + is(pasteCount, 1); + }); + +</script> +</body> +</html> diff --git a/dom/tests/mochitest/general/test_bug1313753.html b/dom/tests/mochitest/general/test_bug1313753.html new file mode 100644 index 0000000000..e684f29136 --- /dev/null +++ b/dom/tests/mochitest/general/test_bug1313753.html @@ -0,0 +1,63 @@ +<!DOCTYPE html> +<meta charset=utf-8> +<title>Test for bug 1313753</title> +<script src="/tests/SimpleTest/SimpleTest.js"></script> +<div id="log"></div> +<script> +function runTest() { + // Change visible region of |closure| element. + document.getElementById("target").classList.add("rotate"); + window.setTimeout(function() { + var target = document.getElementById("target"); + var bounds = target.getBoundingClientRect(); + var x = bounds.x + bounds.width / 2; + var y = bounds.y + bounds.height / 2; + is(document.elementFromPoint(x, y).id, target.id, + "it should be |target| element if visible regions of closure is correct"); + SimpleTest.finish(); + }, 0); +} + +SimpleTest.waitForExplicitFinish(); +SimpleTest.waitForFocus(runTest); +</script> + +<style> +.panel { + transform: rotateX(-150deg); + backface-visibility: hidden; + transform-origin: 0px 0px; + position: absolute; + display: block; + width: 100px; + height: 100px; + background-color: green; +} +#closure .rotate { + transform: rotateX(0deg); +} +#closure { + perspective: 100px; + width: 200px; + z-index: 1; +} +#outer { + height: 400px; + width: 200px; +} +</style> +<div id="outer"> + <div id="closure"> + <div style="transform-style: preserve-3d;"> + <div style="transform-style: preserve-3d; background-color: blue;"> + <ul style="transform-style: preserve-3d;"> + <li style="transform-style:preserve-3d;"> + <div style="display: contents"> + <div id="target" class="panel"></div> + </div> + </li> + </ul> + </div> + </div> + </div> +</div> diff --git a/dom/tests/mochitest/general/test_bug1434273.html b/dom/tests/mochitest/general/test_bug1434273.html new file mode 100644 index 0000000000..89556d1a0d --- /dev/null +++ b/dom/tests/mochitest/general/test_bug1434273.html @@ -0,0 +1,36 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1434273 +--> +<head> + <meta charset="utf-8"> + <title>Test for Bug 1434273</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <script src="/tests/SimpleTest/EventUtils.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> + <style> + #test::before { content: url(image_200.png); } + </style> + <script type="application/javascript"> + SimpleTest.waitForExplicitFinish(); + + /** Test for Bug 1434273 **/ + addLoadEvent(function() { + synthesizeMouse($("test"), 100, 100, {}); + ok(true, "We did not crash!"); + SimpleTest.finish(); + }); + + </script> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1434273">Mozilla Bug 1434273</a> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +</pre> +</body> +</html> diff --git a/dom/tests/mochitest/general/test_bug504220.html b/dom/tests/mochitest/general/test_bug504220.html new file mode 100644 index 0000000000..7223c67d91 --- /dev/null +++ b/dom/tests/mochitest/general/test_bug504220.html @@ -0,0 +1,65 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=504220 +--> +<head> + <title>Test for Bug 504220</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <script src="/tests/SimpleTest/EventUtils.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> +<body onload="run_test();"> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=504220">Mozilla Bug 504220</a> +<p id="display"></p> +<div id="content"> + <iframe id="frame" style="height:100px; width:100px; border:0"></iframe> + <div id="status" style="display: none"></div> +</div> +<pre id="test"> +<script type="application/javascript"> + +/** Test for Bug 504220 **/ + +function run_test() { + ok("onhashchange" in document.body, + "document.body should contain 'onhashchange'."); + + ok("onhashchange" in window, "window should contain 'onhashchange'."); + + // window.onhashchange should mirror document.body.onhashchange. + var func = function() { return 42; }; + document.body.onhashchange = func; + is(window.onhashchange, func); + + // Likewise, document.body.hashchange should mirror window.onhashchange + numEvents = 0; + var func2 = function() { numEvents++; gGen.next() }; + window.onhashchange = func2; + is(document.body.onhashchange, func2); + + SimpleTest.waitForExplicitFinish(); + + function* waitForHashchange() { + // Change the document's hash. If we've been running this test manually, + // the hash might already be "#foo", so we need to check in order to be + // sure we trigger a hashchange. + if (location.hash != "#foo") + location.hash = "#foo"; + else + location.hash = "#bar"; + + yield undefined; + + is(numEvents, 1, "Exactly one hashchange should have been fired."); + SimpleTest.finish(); + } + + var gGen = waitForHashchange(); + gGen.next(); +} + +</script> +</pre> +</body> +</html> diff --git a/dom/tests/mochitest/general/test_bug628069_1.html b/dom/tests/mochitest/general/test_bug628069_1.html new file mode 100644 index 0000000000..fef2307a45 --- /dev/null +++ b/dom/tests/mochitest/general/test_bug628069_1.html @@ -0,0 +1,50 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=628069 +--> +<head> + <title>Test for Bug 628069</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <script src="/tests/SimpleTest/EventUtils.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=628069">Mozilla Bug 628069</a> +<p id="display"></p> +<div id="content"> + <iframe id="frame" style="height:100px; width:100px; border:0"></iframe> + <div id="status" style="display: none"></div> +</div> +<pre id="test"> +<script type="application/javascript"> + +/** Test for Bug 628069 **/ + +SimpleTest.waitForExplicitFinish(); + +popup = window.open('file_bug628069.html'); + +// Control flows into childLoad, once the popup loads. + +gOrigURL = null; +function childLoad() { + gOrigURL = popup.location + ''; + + popup.location.hash = '#hash'; + + // This should trigger a hashchange, so control should flow down to + // childHashchange. +} + +function childHashchange(e) { + is(e.oldURL, gOrigURL, 'event.oldURL'); + is(e.newURL, gOrigURL + '#hash', 'event.newURL'); + is(e.isTrusted, true, 'Hashchange event should be trusted.'); + popup.close(); + SimpleTest.finish(); +} + +</script> +</body> +</html> diff --git a/dom/tests/mochitest/general/test_bug628069_2.html b/dom/tests/mochitest/general/test_bug628069_2.html new file mode 100644 index 0000000000..e9c5b623eb --- /dev/null +++ b/dom/tests/mochitest/general/test_bug628069_2.html @@ -0,0 +1,41 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=628069 +--> +<head> + <title>Test for Bug 628069</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <script src="/tests/SimpleTest/EventUtils.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=628069">Mozilla Bug 628069</a> +<p id="display"></p> +<div id="content"> + <iframe id="frame" style="height:100px; width:100px; border:0"></iframe> + <div id="status" style="display: none"></div> +</div> +<pre id="test"> +<script type="application/javascript"> + +/** Test for Bug 628069 **/ + +gotHashChange = 0; +document.addEventListener("hashChange", function(e) { + gotHashChange = 1; + is(e.oldURL, "oldURL"); + is(e.newURL, "newURL"); + is(e.isTrusted, false, "Hashchange event shouldn't be trusted."); +}, true); + +let hc = new HashChangeEvent("hashChange", { bubbles: true, + cancelable: false, + oldURL: "oldURL", + newURL: "newURL" }); +document.documentElement.dispatchEvent(hc); +is(gotHashChange, 1, 'Document received hashchange event.'); + +</script> +</body> +</html> diff --git a/dom/tests/mochitest/general/test_bug631440.html b/dom/tests/mochitest/general/test_bug631440.html new file mode 100644 index 0000000000..8228a0af99 --- /dev/null +++ b/dom/tests/mochitest/general/test_bug631440.html @@ -0,0 +1,39 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=631440 +--> +<head> + <title>Test for Bug 631440</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=631440">Mozilla Bug 631440</a> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +<script type="application/javascript"> + +SimpleTest.waitForExplicitFinish(); + +var w = window.open("about:blank"); +w.addEventListener("load", function() { + w.close(); + SimpleTest.finish(); +}); + +try { + w.history.pushState(null, "title", "pushState.html"); + ok(false, "Should have thrown a security exception"); +} +catch (e) { + ok(true, "Should have thrown a security exception"); +} + +</script> +</pre> +</body> +</html> diff --git a/dom/tests/mochitest/general/test_bug653364.html b/dom/tests/mochitest/general/test_bug653364.html new file mode 100644 index 0000000000..2db81254eb --- /dev/null +++ b/dom/tests/mochitest/general/test_bug653364.html @@ -0,0 +1,39 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=653364 +--> +<head> + <title>Test for Bug 653364</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <script src="/tests/SimpleTest/EventUtils.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=653364">Mozilla Bug 653364</a> +<p id="display"></p> +<div id="content"> + <iframe id="frame" style="height:100px; width:100px; border:0"></iframe> + <div id="status" style="display: none"></div> +</div> +<pre id="test"> +<script type="application/javascript"> + +/** Test for Bug 653364 **/ + +gotPopState = 0; +document.addEventListener("popState", function(e) { + gotPopState = 1; + is(e.state.foo, 'bar', "PopState event should have state we set."); + is(e.isTrusted, false, "PopState event shouldn't be trusted."); +}, true); + +let ps = new PopStateEvent("popState", { bubbles: true, + cancelable: false, + state: {'foo': 'bar'} }); +document.documentElement.dispatchEvent(ps); +is(gotPopState, 1, 'Document received PopState event.'); + +</script> +</body> +</html> diff --git a/dom/tests/mochitest/general/test_bug861217.html b/dom/tests/mochitest/general/test_bug861217.html new file mode 100644 index 0000000000..1af3f58ff6 --- /dev/null +++ b/dom/tests/mochitest/general/test_bug861217.html @@ -0,0 +1,115 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=861217 +--> +<head> + <title>Test for Bug 861217</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"> +</head> +<body onload="runTest()"> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=861217">Mozilla Bug 861217</a> +<p id="display"></p> +<div id="content"> + <table border="0" cellpadding="0" cellspacing="0" style="table-layout: fixed; width: 50px"> + <tbody> + <tr> + <td id="tableCell1" style="overflow: hidden"><div style="width: 100px; height: 100px; background-color: DodgerBlue">1</div></td> + </tr> + <tr> + <td id="tableCell2" style="overflow: hidden"><div style="margin-top: 5px; margin-left: 7px; width: 100px; height: 100px; background-color: SkyBlue">2</div></td> + </tr> + <tr> + <td id="tableCell3" style="overflow: hidden"><div style="display: inline-block; margin-right: 8px; margin-bottom: 10px; width: 100px; height: 100px; background-color: Khaki">3</div></td> + </tr> + <tr> + <td id="tableCell4" style="overflow: hidden"><div style="display: inline-block; margin-right: 3px; margin-left: 1px; box-sizing: border-box; width: 100px; height: 100px; border-left: 6px solid black; border-bottom: 2px solid black; background-color: LightCoral">4</div></td> + </tr> + <tr> + <td id="tableCell5" style="overflow: hidden"><div style="display: inline-block; border-right: 9px solid black; width: 100px; height: 100px; background-color: LightSeaGreen">5</div></td> + </tr> + <tr> + <td id="tableCell6" style="overflow: hidden"><div style="box-sizing: border-box; width: 100px; height: 100px; padding-top: 3px; padding-right: 13px; background-color: Orange">6</div></td> + </tr> + <tr> + <td id="tableCell7" style="overflow: hidden"><div style="display: inline-block; margin-right: 11px; margin-left: 4px; box-sizing: border-box; width: 100px; height: 100px; border-right: 6px solid black; border-bottom: 8px solid black; padding-top: 5px; padding-right: 9px; padding-bottom: 8px; padding-left: 7px; background-color: Silver">7</div></td> + </tr> + <tr> + <td id="tableCell8" style="overflow: hidden"><div style="display: inline-block; margin-top: 7px; margin-bottom: 1px; border-right: 6px solid black; border-bottom: 8px solid black; padding-top: 5px; padding-right: 9px; padding-bottom: 8px; padding-left: 7px; width: 100px; height: 100px; background-color: Turquoise">8</div></td> + </tr> + </tbody> + </table> + <div id="status" style="display: none"></div> +</div> +<pre id="test"> +<script class="testbody" type="text/javascript"> + +SimpleTest.waitForExplicitFinish(); + +/** Test for Bug 861217 **/ +function runTest() { + var tableCell1 = document.getElementById("tableCell1"), + bcr1 = tableCell1.getBoundingClientRect(), + tableCell2 = document.getElementById("tableCell2"), + bcr2 = tableCell2.getBoundingClientRect(), + tableCell3 = document.getElementById("tableCell3"), + bcr3 = tableCell3.getBoundingClientRect(), + tableCell4 = document.getElementById("tableCell4"), + bcr4 = tableCell4.getBoundingClientRect(), + tableCell5 = document.getElementById("tableCell5"), + bcr5 = tableCell5.getBoundingClientRect(), + tableCell6 = document.getElementById("tableCell6"), + bcr6 = tableCell6.getBoundingClientRect(), + tableCell7 = document.getElementById("tableCell7"), + bcr7 = tableCell7.getBoundingClientRect(), + tableCell8 = document.getElementById("tableCell8"), + bcr8 = tableCell8.getBoundingClientRect(); + + is(bcr1.width, 50, "Width of bounding client rect of #tableCell1"); + is(tableCell1.scrollWidth, 100, "scrollWidth of #tableCell1"); + is(bcr1.height, 100, "Height of bounding client rect of #tableCell1"); + is(tableCell1.scrollHeight, 100, "scrollHeight of #tableCell1"); + + is(bcr2.width, 50, "Width of bounding client rect of #tableCell2"); + is(tableCell2.scrollWidth, 107, "scrollWidth of #tableCell2"); + is(bcr2.height, 105, "Height of bounding client rect of #tableCell2"); + is(tableCell2.scrollHeight, 105, "scrollHeight of #tableCell2"); + + is(bcr3.width, 50, "Width of bounding client rect of #tableCell3"); + is(tableCell3.scrollWidth, 108, "scrollWidth of #tableCell3"); + is(bcr3.height, 110, "Height of bounding client rect of #tableCell3"); + is(tableCell3.scrollHeight, 110, "scrollHeight of #tableCell3"); + + is(bcr4.width, 50, "Width of bounding client rect of #tableCell4"); + is(tableCell4.scrollWidth, 104, "scrollWidth of #tableCell4"); + is(bcr4.height, 100, "Height of bounding client rect of #tableCell4"); + is(tableCell4.scrollHeight, 100, "scrollHeight of #tableCell4"); + + is(bcr5.width, 50, "Width of bounding client rect of #tableCell5"); + is(tableCell5.scrollWidth, 109, "scrollWidth of #tableCell5"); + is(bcr5.height, 100, "Height of bounding client rect of #tableCell5"); + is(tableCell5.scrollHeight, 100, "scrollHeight of #tableCell5"); + + is(bcr6.width, 50, "Width of bounding client rect of #tableCell6"); + is(tableCell6.scrollWidth, 100, "scrollWidth of #tableCell6"); + is(bcr6.height, 100, "Height of bounding client rect of #tableCell6"); + is(tableCell6.scrollHeight, 100, "scrollHeight of #tableCell6"); + + is(bcr7.width, 50, "Width of bounding client rect of #tableCell7"); + is(tableCell7.scrollWidth, 115, "scrollWidth of #tableCell7"); + is(bcr7.height, 100, "Height of bounding client rect of #tableCell7"); + is(tableCell7.scrollHeight, 100, "scrollHeight of #tableCell7"); + + is(bcr8.width, 50, "Width of bounding client rect of #tableCell8"); + is(tableCell8.scrollWidth, 122, "scrollWidth of #tableCell8"); + is(bcr8.height, 129, "Height of bounding client rect of #tableCell8"); + is(tableCell8.scrollHeight, 129, "scrollHeight of #tableCell8"); + + SimpleTest.finish(); +} + +</script> +</pre> +</body> +</html> diff --git a/dom/tests/mochitest/general/test_clientRects.html b/dom/tests/mochitest/general/test_clientRects.html new file mode 100644 index 0000000000..bcfe1ee909 --- /dev/null +++ b/dom/tests/mochitest/general/test_clientRects.html @@ -0,0 +1,127 @@ +<!DOCTYPE HTML> +<html id="d9" style="width:800px; height:1000px"> +<head> + <title>Tests for getClientRects/getBoundingClientRect</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"> +</head> +<body style="margin:0" onload="doTest()"> + +<script> +function isWithinEps(v1, v2, eps, msg) { + if (eps) { + ok(Math.abs(v1 - v2) < eps, msg + " (within " + eps + "); got " + v1 + ", expected " + v2); + } else { + is(v1, v2, msg); + } +} +function checkRect(clientRect, r, eps, exprMsg, restMsg) { + isWithinEps(clientRect.left, r[0], eps, exprMsg + ".left" + restMsg); + isWithinEps(clientRect.top, r[1], eps, exprMsg + ".top" + restMsg); + isWithinEps(clientRect.right, r[2], eps, exprMsg + ".right" + restMsg); + isWithinEps(clientRect.bottom, r[3], eps, exprMsg + ".bottom" + restMsg); + isWithinEps(clientRect.width, r[2] - r[0], eps, exprMsg + ".width" + restMsg); + isWithinEps(clientRect.height, r[3] - r[1], eps, exprMsg + ".height" + restMsg); +} +function doc(id) { + return document.getElementById(id).contentDocument; +} +function checkElement(id, list, eps, doc) { + var e = (doc || document).getElementById(id); + var clientRects = e.getClientRects(); + ok(!(clientRects instanceof e.ownerDocument.defaultView.Array), + "getClientRects retval should not have Array.prototype on its proto chain"); + is(clientRects.length, list.length, "getClientRects().length for element '" + id + "'"); + var bounds = list.length ? list[0] : [0,0,0,0]; + for (var i = 0; i < clientRects.length && i < list.length; ++i) { + var r = list[i]; + r[2] += r[0]; + r[3] += r[1]; + checkRect(clientRects[i], r, eps, "getClientRects()[" + i + "]", " for element '" + id + "'"); + if (r[2] != r[0] && r[3] != r[1]) { + bounds[0] = Math.min(bounds[0], r[0]); + bounds[1] = Math.min(bounds[1], r[1]); + bounds[2] = Math.max(bounds[2], r[2]); + bounds[3] = Math.max(bounds[3], r[3]); + } + } + checkRect(e.getBoundingClientRect(), bounds, eps, "getBoundingClientRect()", " for element '" + id + "'"); +} +</script> + +<!-- Simple case --> +<div id="d1" style="position:absolute; left:50px; top:50px; width:20px; height:30px; background:pink;"></div> +<!-- Multiple boxes --> +<div style="position:absolute; left:50px; top:100px; width:400px; height:100px; column-count:2; column-gap:0"> + <div id="d2"> + <div style="width:200px; height:100px; background:yellow"></div> + <div style="width:200px; height:100px; background:lime"></div> + </div> +</div> +<!-- No boxes --> +<div id="d3" style="display:none"></div> +<!-- Element in transform --> +<div style="-moz-transform:translate(50px, 50px); transform:translate(50px,50px); position:absolute; left:0; top:200px"> + <div id="d4" style="width:50px; height:50px; background:blue;"></div> +</div> +<svg style="position:absolute; left:50px; top:300px; width:100px; height:100px;"> + <!-- Element in SVG foreignobject --> + <foreignObject x="20" y="30" width="40" height="40"> + <div id="d5" style="width:40px; height:40px; background:pink;"></div> + </foreignObject> + <!-- SVG Element --> + <circle id="s1" cx="60" cy="60" r="10" fill="yellow"/> +</svg> +<!-- Element in transform with bounding-box --> +<div style="-moz-transform:rotate(45deg); transform:rotate(45deg); position:absolute; left:50px; top:450px; width:100px; height:100px;"> + <div id="d6" style="width:100px; height:100px; background:orange;"></div> +</div> +<!-- Element in two transforms; we should combine transforms instead of taking bounding-box twice --> +<div style="-moz-transform:rotate(45deg); transform:rotate(45deg); position:absolute; left:50px; top:550px; width:100px; height:100px;"> + <div style="-moz-transform:rotate(-45deg); transform:rotate(-45deg); width:100px; height:100px;"> + <div id="d7" style="width:100px; height:100px; background:lime;"></div> + </div> +</div> +<!-- Fixed-pos element --> +<div id="d8" style="position:fixed; left:50px; top:700px; width:100px; height:100px; background:gray;"></div> +<!-- Root element; see d9 --> +<!-- Element in iframe --> +<iframe id="f1" style="position:absolute; left:300px; top:0; width:100px; height:200px; border:none" + srcdoc="<div id='d10' style='position:absolute; left:0; top:25px; width:100px; height:100px; background:cyan'>"> +</iframe> +<!-- Root element in iframe --> +<iframe id="f2" style="position:absolute; left:300px; top:250px; width:100px; height:200px; border:none" + srcdoc="<html id='d11' style='width:100px; height:100px; background:magenta'>"> +</iframe> +<!-- Fixed-pos element in iframe --> +<iframe id="f3" style="position:absolute; left:300px; top:400px; border:none" + srcdoc="<div id='d12' style='position:fixed; left:0; top:0; width:100px; height:100px;'>"></iframe> + +<script> +function doTest() { + checkElement("d1", [[50,50,20,30]]); + checkElement("d2", [[50,100,200,100],[250,100,200,100]]); + checkElement("d3", []); + checkElement("d4", [[50,250,50,50]]); + checkElement("d5", [[70,330,40,40]]); + checkElement("s1", [[100,350,20,20]], 0.1); + var sqrt2 = Math.sqrt(2); + checkElement("d6", [[100 - 50*sqrt2,500 - 50*sqrt2,100*sqrt2,100*sqrt2]], 0.1); + checkElement("d7", [[50,550,100,100]]); + checkElement("d8", [[50,700,100,100]]); + checkElement("d9", [[0,0,800,1000]]); + checkElement("d10", [[0,25,100,100]], 0, doc("f1")); + checkElement("d11", [[0,0,100,100]], 0, doc("f2")); + checkElement("d12", [[0,0,100,100]], 0, doc("f3")); + SimpleTest.finish(); +} +SimpleTest.waitForExplicitFinish(); +</script> + +<p id="display"></p> +<div id="content" style="display: none"> + +</div> + +</body> +</html> diff --git a/dom/tests/mochitest/general/test_clipboard_disallowed.html b/dom/tests/mochitest/general/test_clipboard_disallowed.html new file mode 100644 index 0000000000..ce331ee253 --- /dev/null +++ b/dom/tests/mochitest/general/test_clipboard_disallowed.html @@ -0,0 +1,69 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test for Clipboard Events</title> + <script type="text/javascript" src="/MochiKit/MochiKit.js"></script> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <script src="/tests/SimpleTest/EventUtils.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<p id="display"></p> +<input id="input" value="INPUT TEXT" oncopy="checkAllowed(event)"> + +<script> +function doTest() +{ + document.getElementById("input").focus(); + synthesizeKey("c", {accelKey: 1}); +} + +function checkAllowed(event) +{ + let clipboardData = event.clipboardData; + + let disallowedTypes = [ + "application/x-moz-file", + "application/x-moz-file-promise", + "application/x-moz-file-promise-url", + "application/x-moz-file-promise-dest-filename", + "application/x-moz-file-promise-dir", + "application/x-moz-nativeimage", + "text/x-moz-requestmime", + "text/x-moz-place", + "text/x-moz-place-container", + "text/x-moz-some-made-up-type", + ]; + + for (let type of disallowedTypes) { + let exception; + try { + clipboardData.setData(type, "Test"); + } catch(ex) { + exception = ex; + } + is(String(exception).indexOf("SecurityError"), 0, "Cannot set " + type); + } + + let allowedTypes = [ + "application/something", + "x-moz-/something", + "something/ax-moz-", + ]; + + for (let type of allowedTypes) { + let exception = null; + try { + clipboardData.setData(type, "This is data"); + } catch(ex) { + exception = ex; + } + is(exception, null, "Can set " + type); + } + + SimpleTest.finish(); +} + +SimpleTest.waitForExplicitFinish(); +SimpleTest.waitForFocus(doTest); +</script> diff --git a/dom/tests/mochitest/general/test_clipboard_events.html b/dom/tests/mochitest/general/test_clipboard_events.html new file mode 100644 index 0000000000..aa99010b89 --- /dev/null +++ b/dom/tests/mochitest/general/test_clipboard_events.html @@ -0,0 +1,37 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test for Clipboard Events</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<p id="display"></p> +<script> +"use strict"; + +// The clipboard event tests require `GlobalEventHandlers.onbeforeinput` +// attribute which is available only when `beforeinput` event is enabled. +// For ensuring it's available with any element in the document, we need +// to enable it in this window and then, create elements in the new +// document in a child window. + +SimpleTest.waitForExplicitFinish(); +SimpleTest.waitForFocus(async () => { + await SpecialPowers.pushPrefEnv({ + set: [ + // NOTE: The tests operate under the assumption that the protected mode of + // DataTransfer is enabled. + ["dom.events.dataTransfer.protected.enabled", true], + ] + }); + let childWindow = + window.open("window_clipboard_events.html", "_blank", "width=500,height=800"); + ok(childWindow, "A child window should've been opened"); + childWindow.onclose = () => { + SimpleTest.finish(); + }; +}); +</script> +</body> +</html> diff --git a/dom/tests/mochitest/general/test_consoleAPI.html b/dom/tests/mochitest/general/test_consoleAPI.html new file mode 100644 index 0000000000..506fc7f160 --- /dev/null +++ b/dom/tests/mochitest/general/test_consoleAPI.html @@ -0,0 +1,66 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>window.console test</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"> +</head> + +<body id="body"> + +<script type="application/javascript"> + +function doTest() { + ok(window.console, "console exists"); + + try { + ok(!console.foo, "random property doesn't throw"); + } catch (ex) { + ok(false, "random property threw: " + ex); + } + + var expectedProps = { + "log": "function", + "info": "function", + "warn": "function", + "error": "function", + "exception": "function", + "debug": "function", + "trace": "function", + "dir": "function", + "group": "function", + "groupCollapsed": "function", + "groupEnd": "function", + "time": "function", + "timeLog": "function", + "timeEnd": "function", + "profile": "function", + "profileEnd": "function", + "assert": "function", + "count": "function", + "countReset": "function", + "table": "function", + "clear": "function", + "dirxml": "function", + "timeStamp": "function", + }; + + var foundProps = 0; + for (var prop in console) { + foundProps++; + is(typeof(console[prop]), expectedProps[prop], "expect console prop " + prop + " exists"); + } + is(foundProps, Object.keys(expectedProps).length, "found correct number of properties"); + + SimpleTest.finish(); +} + +SimpleTest.waitForExplicitFinish(); +addLoadEvent(doTest); + +</script> + +<p id="display"></p> + +</body> +</html> diff --git a/dom/tests/mochitest/general/test_contentViewer_overrideDPPX.html b/dom/tests/mochitest/general/test_contentViewer_overrideDPPX.html new file mode 100644 index 0000000000..af3f46b81b --- /dev/null +++ b/dom/tests/mochitest/general/test_contentViewer_overrideDPPX.html @@ -0,0 +1,428 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>nsIContentViewer::overrideDPPX test</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <script src="/tests/SimpleTest/EventUtils.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"> +</head> + +<body> + +<iframe></iframe> +<img> + +<script type="application/javascript"> + +SimpleTest.waitForExplicitFinish(); + +const frameWindow = document.querySelector("iframe").contentWindow; +const image = document.querySelector("img"); + +const originalDPR = window.devicePixelRatio; +const originalZoom = SpecialPowers.getFullZoom(window); +const dppx = originalDPR * 1.5; +const zoom = originalZoom * 0.5; + +const imageSets = { + "1x" : "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAA" + + "GUlEQVQ4jWP4z8DwnxLMMGrAqAGjBgwXAwAwxP4QWURl4wAAAABJRU5ErkJggg==", + "1.5x": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAA" + + "GElEQVQ4jWNgaGD4TxEeNWDUgFEDhosBAOsIfxAZ/CYXAAAAAElFTkSuQmCC", + "2x" : "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAA" + + "GElEQVQ4jWNgYPj/nzI8asCoAaMGDBMDADKm/hBZaHKGAAAAAElFTkSuQmCC", + "8x" : "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAA" + + "GklEQVQ4jWP4f+D0f0oww6gBowaMGjBcDAAAskKJLhIvZvgAAAAASUVORK5CYII=", +}; + +const setOverrideDPPX = (value) => { + if (value > 0) { + info(`override window's dppx to ${value}`); + } else { + info(`restore window's dppx to default value`); + } + + return SpecialPowers.spawnChrome([value], dppx => { + browsingContext.top.overrideDPPX = dppx; + }); +}; + +const setFullZoom = (value) => { + info(`set window's fullZoom to ${value}`); + SpecialPowers.setFullZoom(window, value); +} + +const createFontStyleForDPPX = (doc, value, size) => { + info(`adding a stylesheet that set font size to ${size}px for ${value}dppx`); + + let style = doc.createElement("style"); + + style.setAttribute("media", `(resolution: ${value}dppx)`); + + doc.head.appendChild(style); + + style.sheet.insertRule(`body {font-size: ${size}px}`, 0); + + return style; +} + +const getBodyFontSize = (w) => w.getComputedStyle(w.document.body).fontSize; + +const assertValuesAreInitial = () => { + is(window.devicePixelRatio, originalDPR, + "devicePixelRatio has the original value."); + is(SpecialPowers.getFullZoom(window), originalZoom, + "fullZoom has the original value."); + + is(frameWindow.devicePixelRatio, originalDPR, + "devicePixelRatio has the original value."); + is(SpecialPowers.getFullZoom(frameWindow), originalZoom, + "fullZoom has the original value."); +} + +const waitForMediaQueryListEvent = (mediaQueryList) => { + return new Promise(resolve => { + mediaQueryList.addListener(function listener() { + ok(true, "MediaQueryList's listener invoked for " + mediaQueryList.media); + mediaQueryList.removeListener(listener); + // We need to evacuate a media query list event to avoid changing any + // media features inside this callback (and microtasks for the callbacks), + // because we currently dispatch media query list events during flush + // pending styles, and we want to make sure there is no pending media + // feature changes after the event handling. + // This workaround should be dropped in bug 1437688. + setTimeout(resolve, 0); + }); + }); +} + +const gTests = { + "test overrideDPPX with devicePixelRatio": async (done) => { + assertValuesAreInitial(); + + await setOverrideDPPX(dppx); + + is(window.devicePixelRatio, dppx, + "devicePixelRatio overridden."); + is(frameWindow.devicePixelRatio, dppx, + "frame's devicePixelRatio overridden."); + + await setOverrideDPPX(0); + + is(window.devicePixelRatio, originalDPR, + "devicePixelRatio back to default."); + is(frameWindow.devicePixelRatio, originalDPR, + "frame's devicePixelRatio back to default."); + + done(); + }, + "test overrideDPPX with devicePixelRatio and fullZoom": async (done) => { + assertValuesAreInitial(); + + setFullZoom(zoom); + await setOverrideDPPX(dppx); + + is(window.devicePixelRatio, dppx, + "devicePixelRatio overridden; fullZoom ignored"); + is(SpecialPowers.wrap(window).devicePixelRatio, originalDPR * zoom, + "devicePixelRatio is right for privileged code"); + is(frameWindow.devicePixelRatio, dppx, + "frame's devicePixelRatio overridden; fullZoom ignored"); + is(SpecialPowers.wrap(frameWindow).devicePixelRatio, originalDPR * zoom, + "frame's devicePixelRatio is right for privileged code"); + + await setOverrideDPPX(0); + + is(window.devicePixelRatio, originalDPR * zoom, + "devicePixelRatio now is affected by fullZoom"); + is(frameWindow.devicePixelRatio, originalDPR * zoom, + "frame's devicePixelRatio now is affected by fullZoom"); + isnot(dppx, originalDPR * zoom, + "test is no longer testing what it should be"); + + setFullZoom(originalZoom); + + is(window.devicePixelRatio, originalDPR, + "devicePixelRatio back to default."); + is(frameWindow.devicePixelRatio, originalDPR, + "frame's devicePixelRatio back to default."); + + done(); + + }, + "test overrideDPPX with media queries": async (done) => { + assertValuesAreInitial(); + + let frameDoc = frameWindow.document; + + let originalFontSize = getBodyFontSize(window); + let frameOriginalFontSize = getBodyFontSize(frameWindow); + + let style = createFontStyleForDPPX(document, dppx, "32"); + let frameStyle = createFontStyleForDPPX(frameDoc, dppx, "32"); + + let currentFontSize = getBodyFontSize(window); + let frameCurrentFontSize = getBodyFontSize(frameWindow); + + is(currentFontSize, originalFontSize, + "media queries are not applied yet"); + is(frameCurrentFontSize, frameOriginalFontSize, + "frame's media queries are not applied yet"); + + await setOverrideDPPX(dppx); + + currentFontSize = getBodyFontSize(window); + frameCurrentFontSize = getBodyFontSize(frameWindow); + + isnot(currentFontSize, originalFontSize, + "media queries are applied."); + isnot(frameCurrentFontSize, frameOriginalFontSize, + "frame's media queries are applied."); + + is(currentFontSize, "32px", + "font size has the expected value."); + is(frameCurrentFontSize, "32px", + "frame's font size has the expected value."); + + await setOverrideDPPX(0); + + currentFontSize = getBodyFontSize(window); + frameCurrentFontSize = getBodyFontSize(frameWindow); + + is(currentFontSize, originalFontSize, + "media queries are not applied anymore."); + is(frameCurrentFontSize, frameOriginalFontSize, + "media queries are not applied anymore."); + + style.remove(); + frameStyle.remove(); + + done(); + }, + "test overrideDPPX with media queries and fullZoom": async (done) => { + assertValuesAreInitial(); + + let frameDoc = frameWindow.document; + + let styles = [ + createFontStyleForDPPX(document, originalDPR, "23"), + createFontStyleForDPPX(document, dppx, "32"), + createFontStyleForDPPX(document, originalDPR * zoom, "48"), + createFontStyleForDPPX(frameDoc, originalDPR, "23"), + createFontStyleForDPPX(frameDoc, dppx, "32"), + createFontStyleForDPPX(frameDoc, originalDPR * zoom, "48") + ]; + + let currentFontSize = getBodyFontSize(window); + let frameCurrentFontSize = getBodyFontSize(frameWindow); + is(currentFontSize, "23px", + "media queries are not applied yet"); + is(frameCurrentFontSize, "23px", + "frame's media queries are not applied yet"); + + setFullZoom(zoom); + await setOverrideDPPX(dppx); + + currentFontSize = getBodyFontSize(window); + frameCurrentFontSize = getBodyFontSize(frameWindow); + is(currentFontSize, "32px", + "media queries are applied for overridden DDPX; fullZoom ignored."); + is(frameCurrentFontSize, "32px", + "frame's media queries are applied for overridden DDPX; fullZoom ignored."); + + await setOverrideDPPX(0); + + currentFontSize = getBodyFontSize(window); + frameCurrentFontSize = getBodyFontSize(frameWindow); + is(currentFontSize, "48px", + "media queries are applied for fullZoom."); + is(frameCurrentFontSize, "48px", + "frame's media queries are applied for fullZoom."); + + setFullZoom(originalZoom); + + currentFontSize = getBodyFontSize(window); + frameCurrentFontSize = getBodyFontSize(frameWindow); + is(currentFontSize, "23px", + "media queries are not applied anymore."); + is(frameCurrentFontSize, "23px", + "frame's media queries are not applied anymore."); + + styles.forEach(style => style.remove()); + + done(); + }, + "test OverrideDPPX with MediaQueryList": (done) => { + assertValuesAreInitial(); + + let promises = [ + waitForMediaQueryListEvent( + window.matchMedia(`(resolution: ${dppx}dppx)`)), + waitForMediaQueryListEvent( + frameWindow.matchMedia(`(resolution: ${dppx}dppx)`)), + ]; + + Promise.all(promises) + .then(() => setOverrideDPPX(0)) + .then(done, e => {throw e}); + + setOverrideDPPX(dppx); + }, + "test OverrideDPPX with MediaQueryList and fullZoom": async (done) => { + assertValuesAreInitial(); + + let promises = [ + waitForMediaQueryListEvent( + window.matchMedia(`(resolution: ${dppx}dppx)`)), + waitForMediaQueryListEvent( + window.matchMedia(`(resolution: ${originalDPR * zoom}dppx)`)), + ]; + + await setOverrideDPPX(dppx); + setFullZoom(zoom); + + promises[0] + .then(() => setOverrideDPPX(0)) + .then(promises[1]) + .then(() => setFullZoom(originalZoom)) + .then(done, e => {throw e}); + }, + "test OverrideDPPX is kept on document navigation": async (done) => { + assertValuesAreInitial(); + + let frameOriginalFontSize = getBodyFontSize(frameWindow); + let frameStyle = createFontStyleForDPPX(frameWindow.document, dppx, "32"); + let frameCurrentFontSize = getBodyFontSize(frameWindow); + + is(frameCurrentFontSize, frameOriginalFontSize, + "frame's media queries are not applied yet"); + + await setOverrideDPPX(dppx); + + frameCurrentFontSize = getBodyFontSize(frameWindow); + + is(frameWindow.devicePixelRatio, dppx, + "frame's devicePixelRatio overridden."); + isnot(frameCurrentFontSize, frameOriginalFontSize, + "frame's media queries are applied."); + is(frameCurrentFontSize, "32px", + "frame's font size has the expected value."); + + frameWindow.frameElement.addEventListener("load", async function() { + frameStyle = createFontStyleForDPPX(frameWindow.document, dppx, "32"); + + frameCurrentFontSize = getBodyFontSize(frameWindow); + + is(frameWindow.devicePixelRatio, dppx, + "frame's devicePixelRatio is still overridden."); + isnot(frameCurrentFontSize, frameOriginalFontSize, + "frame's media queries are still applied."); + is(frameCurrentFontSize, "32px", + "frame's font size has still the expected value."); + + frameStyle.remove(); + + await setOverrideDPPX(0); + + done(); + }, {once: true}); + + frameWindow.location.reload(true); + }, + + "test overrideDPPX with srcset": async function (done) { + assertValuesAreInitial(); + + let loaded; + + // Set the image srcset and default src. This is delayed until this test so + // that dppx overrides in other test blocks don't trigger load event on the + // image. + loaded = new Promise(resolve => image.onload = resolve); + image.srcset = Object.entries(imageSets).map(v => v[1] + " " + v[0]).join(", "); + image.src = imageSets["1x"]; + await loaded; + + let originalSrc = image.currentSrc; + + // Make sure to test some very large value that can't be the default density + // so that the first override will always trigger a load. + isnot(originalDPR, 8, "originalDPR differs from final test value"); + loaded = new Promise(resolve => image.onload = resolve); + await setOverrideDPPX(8); + await loaded; + + is(image.currentSrc, imageSets["8x"], + "Image is properly set for 8dppx."); + + loaded = new Promise(resolve => image.onload = resolve); + await setOverrideDPPX(1); + await loaded; + + is(image.currentSrc, imageSets["1x"], + "Image url is properly set for 1dppx."); + + loaded = new Promise(resolve => image.onload = resolve); + await setOverrideDPPX(1.5); + await loaded; + + is(image.currentSrc, imageSets["1.5x"], + "Image url is properly set for 1.5dppx."); + + loaded = new Promise(resolve => image.onload = resolve); + await setOverrideDPPX(2); + await loaded; + + is(image.currentSrc, imageSets["2x"], + "Image is properly set for 2dppx."); + + // Make sure to test some very large value that can't be the default density + // so that resetting to the default will always trigger a load. + isnot(originalDPR, 8, "originalDPR differs from final test value"); + loaded = new Promise(resolve => image.onload = resolve); + await setOverrideDPPX(8); + await loaded; + + is(image.currentSrc, imageSets["8x"], + "Image is properly set for 8dppx."); + + loaded = new Promise(resolve => image.onload = resolve); + await setOverrideDPPX(0); + await loaded; + + is(image.currentSrc, originalSrc, + "Image is properly restored to the default value."); + + // Clear sources so any future dppx test blocks don't trigger image loads. + image.srcset = ""; + image.src = ""; + + done(); + } +}; + +function* runner(tests) { + for (let name of Object.keys(tests)) { + info(name); + tests[name](next); + yield undefined; + } +}; + +const gTestRunner = runner(gTests); + +function next() { + SimpleTest.executeSoon(function() { + if (gTestRunner.next().done) { + SimpleTest.finish(); + } + }); +} + +// Run the tests +addLoadEvent(next); + +</script> + +</body> +</html> diff --git a/dom/tests/mochitest/general/test_devicePixelRatio_with_zoom.html b/dom/tests/mochitest/general/test_devicePixelRatio_with_zoom.html new file mode 100644 index 0000000000..710af8fe43 --- /dev/null +++ b/dom/tests/mochitest/general/test_devicePixelRatio_with_zoom.html @@ -0,0 +1,84 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>DevicePixelRatios with Zoom</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"> +</head> + +<body> + +<div>Testing devicePixelRatio with different zoom levels</div> + +<script type="application/javascript"> + +// We're creating a table of zooms and expected devicePixelRatio (DPR) strings. +// The values in the table are specific to the "native" DPR at zoom level 100%. +// If we don't have a table for a native DPR value, we'll trivially report +// a successful test with a note that we didn't have a table. +// For the moment, we only have a table for native DPR of 2. +let zoomsAndDPRsToTest; + +const originalZoom = SpecialPowers.getFullZoom(window); +const zoom = 1; +SpecialPowers.setFullZoom(window, zoom); + +const dprAtZoom1 = window.devicePixelRatio; +if (dprAtZoom1 == 1) { + zoomsAndDPRsToTest = [ + [300, "3"], + [250, "2.5"], + [200, "2"], + [167, "1.6666666666666667"], + [150, "1.5"], + [133, "1.3333333333333333"], + [120, "1.2"], + [110, "1.0909090909090908"], + [100, "1"], + [90, "0.8955223880597015"], + [80, "0.8"], + [67, "0.6666666666666666"], + [50, "0.5"], + ]; +} else if (dprAtZoom1 == 2) { + zoomsAndDPRsToTest = [ + [300, "6"], + [250, "5"], + [200, "4"], + [167, "3.3333333333333335"], + [150, "3"], + [133, "2.608695652173913"], // there's a trailing 0 here unreported by JS + [120, "2.4"], + [110, "2.2222222222222223"], + [100, "2"], + [90, "1.8181818181818181"], + [80, "1.5789473684210527"], + [67, "1.3333333333333333"], + [50, "1"], + ]; +} + +if (!zoomsAndDPRsToTest.length) { + // Need to run at least one test function to keep mochitest harness happy. + ok(true, `No table of data for devicePixelRatio of ${dprAtZoom1} at zoom level 100%.`); +} + +for (let i = 0; i < zoomsAndDPRsToTest.length; ++i) { + let data = zoomsAndDPRsToTest[i]; + let zoomPercent = data[0]; + let targetDPR = data[1]; + + let relativeZoom = zoom * zoomPercent / 100; + SpecialPowers.setFullZoom(window, relativeZoom); + + // Force conversion to string for string comparison to targetDPR. + let actualDPR = window.devicePixelRatio + ""; + is(actualDPR, targetDPR, `At ${zoomPercent}% zoom, window.devicePixelRatio is rounded correctly.`); +} + +// Reset the zoom when the test is done. +SpecialPowers.setFullZoom(window, originalZoom); +</script> + +</body> +</html> diff --git a/dom/tests/mochitest/general/test_domWindowUtils.html b/dom/tests/mochitest/general/test_domWindowUtils.html new file mode 100644 index 0000000000..2b0bcc9086 --- /dev/null +++ b/dom/tests/mochitest/general/test_domWindowUtils.html @@ -0,0 +1,183 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test nsIDOMWindowUtils</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <script src="/tests/SimpleTest/EventUtils.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"> + <style> + html, body, div { + padding: 0; + margin: 0; + } + + div.test { + position: absolute; + height: 10px; + width: 10px; + } + </style> +</head> + +<body id="body"> + +<div class="test" id="onscreen" style="top: 100px; background-color: red;"></div> +<div class="test" id="offscreen" style="top: 100000px; background-color: green;"></div> + +<script type="application/javascript"> + +SimpleTest.waitForExplicitFinish(); + +var domWindowUtils = SpecialPowers.getDOMWindowUtils(window); + +var gTests = [ +/* + Element elementFromPoint(in long aX, + in long aY, + in boolean aIgnoreRootScrollFrame, + in boolean aFlushLayout); +*/ +async function testElementFromPoint() { + let onscreen = document.getElementById("onscreen"); + let offscreen = document.getElementById("offscreen"); + let htmldoc = document.documentElement; + ok(onscreen, "on screen element exists"); + ok(offscreen, "off screen element exists"); + ok(htmldoc, "htmldoc element exists"); + + let testData = [ + // default behavior is to return null for items outside the viewport + [[0, 100], null, onscreen], + [[9, 109], null, onscreen], + [[0, 100000], null, null], + [[9, 100009], null, null], + + // ignore scroll frame + [[0, 100, true, false], null, onscreen], + [[9, 109, true, false], null, onscreen], + [[0, 100000, true, false], null, offscreen], + [[9, 100009, true, false], null, offscreen], + + // layout flush tests + // test moving element 10px to the left and down, and flushing layout + [[10, 110, false, true], [[10, 110], onscreen], onscreen], + // test moving element back, not flushing layout + // (will get the html document instead) + [[0, 100, false, false], [[0, 100], onscreen], htmldoc], + // test element at same position, flushing layout + [[0, 100, false, true], [[0, 100], onscreen], onscreen], + + // same tests repeated for offscreen element + [[10, 100010, true, true], [[10, 100010], offscreen], offscreen], + [[0, 100000, true, false], [[0, 100000], offscreen], htmldoc], + [[0, 100000, true, true], [[0, 100000], offscreen], offscreen], + ]; + + for (let i = 0; i < testData.length; ++i) { + let [x, y, ignoreScroll, flushLayout] = testData[i][0]; + let moveData = testData[i][1]; + let expected = testData[i][2]; + + if (moveData) { + let moveEl = moveData[1]; + let [moveX, moveY] = moveData[0]; + + moveEl.style.left = moveX + "px"; + moveEl.style.top = moveY + "px"; + } + let found = SpecialPowers.unwrap(domWindowUtils.elementFromPoint( + x, y, ignoreScroll, flushLayout)); + is(found, expected, "at index " + i + " for data " + JSON.stringify(testData[i][0])); + } +}, + +/** + * Test .isHandlingUserInput attribute. + */ +async function testHandlingUserInput() { + ok('isHandlingUserInput' in domWindowUtils, + "isHandlingUserInput should be present"); + + is(domWindowUtils.isHandlingUserInput, false, + "isHandlingUserInput should return false if nothing is happening"); + + var data = [ + { + eventName: "click", + result: true, + }, + { + eventName: "auxclick", + button: 1, + result: true, + }, + { + eventName: "mousemove", + result: false, + }, + { + eventName: "mouseup", + result: true, + }, + { + eventName: "mousedown", + result: true, + }, + { + eventName: "keydown", + result: true, + }, + { + eventName: "keyup", + result: true, + }, + ]; + + for (const {eventName, result, button} of data) { + let eventPromise = new Promise(resolve => { + document.addEventListener(eventName, function() { + is(domWindowUtils.isHandlingUserInput, result, + `isHandlingUserInput should be ${result} for ${eventName}`); + + SimpleTest.executeSoon(resolve); + }, {once: true}); + }); + + SimpleTest.executeSoon(function() { + if (eventName == "click") { + synthesizeMouseAtCenter(document.body, {}); + } else if (eventName == "auxclick" && button) { + synthesizeMouseAtCenter(document.body, { button }); + } else if (eventName.startsWith("key")) { + synthesizeKey("VK_A", { type: eventName }); + } else { + synthesizeMouseAtCenter(document.body, { type: eventName }); + } + }); + + await eventPromise; + } +}, +]; + +async function runner() { + for (let i=0; i<gTests.length; ++i) { + if (i > 0) { + await new Promise(r => SimpleTest.executeSoon(r)); + } + await gTests[i](); + } + + SimpleTest.finish(); +}; + +// Run the test from onload, since the onscreen and offscreen divs should be in +// the right places by then. +addLoadEvent(runner); + +</script> + +<p id="display"></p> + +</body> +</html> diff --git a/dom/tests/mochitest/general/test_domWindowUtils_scrollXY.html b/dom/tests/mochitest/general/test_domWindowUtils_scrollXY.html new file mode 100644 index 0000000000..6bf29f46e4 --- /dev/null +++ b/dom/tests/mochitest/general/test_domWindowUtils_scrollXY.html @@ -0,0 +1,90 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>nsIDOMWindowUtils::elementFromPoint test</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"> + <style> + body { + /* Make room for scrolling */ + } + </style> +</head> + +<body id="body"> + <script type="application/javascript"> + /* + void getScrollXY(in boolean aFlushLayout, out long aScrollX, out long aScrollY); + */ + function doTests() { + testScrollXY(); + testHiddenIframe(); + + SimpleTest.finish(); + } + + function testScrollXY() { + let iframe = document.getElementById("iframe"); + let cwindow = iframe.contentWindow; + let domWindowUtils = SpecialPowers.getDOMWindowUtils(cwindow); + + function checkGetScrollXYState(flush, vals, testName) { + let scrollX = {}, scrollY = {}; + domWindowUtils.getScrollXY(flush, scrollX, scrollY); + is(Math.round(scrollX.value), vals[0], "getScrollXY x for test: " + testName); + is(Math.round(scrollY.value), vals[1], "getScrollXY y for test: " + testName); + } + + function checkWindowScrollState(vals, testName) { + is(Math.round(cwindow.scrollX), vals[0], "scrollX for test: " + testName); + is(Math.round(cwindow.scrollY), vals[1], "scrollY for test: " + testName); + } + + // Check initial state (0, 0) + checkGetScrollXYState(false, [0, 0], "initial getScrollXY state"); + checkGetScrollXYState(true, [0, 0], "initial getScrollXY state+flush"); + checkWindowScrollState([0, 0], "initial window.scroll* state"); + + // scroll + cwindow.scrollTo(900, 1000); + checkGetScrollXYState(false, [900, 1000], "after scroll getScrollXY state"); + checkGetScrollXYState(true, [900, 1000], "after scroll getScrollXY state+flush"); + checkWindowScrollState([900, 1000], "after scroll window.scroll* state"); + + // ensure flush=false works + cwindow.document.body.style.width = 'auto'; + cwindow.document.body.style.height = 'auto'; + checkGetScrollXYState(false, [900, 1000], "didn't flush layout for getScrollXY"); + checkGetScrollXYState(true, [0, 0], "flushed layout for getScrollXY"); + } + + function testHiddenIframe() { + let iframe = document.getElementById("hidden-iframe"); + let cwindow = iframe.contentWindow; + let domWindowUtils = SpecialPowers.getDOMWindowUtils(cwindow); + + // make sure getScrollXY doesn't throw + let scrollX = {}, scrollY = {}; + domWindowUtils.getScrollXY(false, scrollX, scrollY); + + is(Math.round(scrollX.value), 0, "scrollX is zero for display:none iframe"); + is(Math.round(scrollY.value), 0, "scrollY is zero for display:none iframe"); + } + + SimpleTest.waitForExplicitFinish(); + </script> + + <!-- can't run this in the test document, since it potentially runs in a + scrolling="no" test harness iframe, and that causes a failure for some + reason --> + <iframe srcdoc="<body style='width: 100000px; height: 100000px;'><p>top</p></body>" + id="iframe" + onload="doTests();"> + </iframe> + + <iframe id="hidden-iframe" style="display: none;"></iframe> + + <p id="display"></p> + +</body> +</html> diff --git a/dom/tests/mochitest/general/test_domWindowUtils_scrollbarSize.html b/dom/tests/mochitest/general/test_domWindowUtils_scrollbarSize.html new file mode 100644 index 0000000000..2822a5d93c --- /dev/null +++ b/dom/tests/mochitest/general/test_domWindowUtils_scrollbarSize.html @@ -0,0 +1,65 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>nsIDOMWindowUtils::getScrollbarSize test</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"> +</head> + +<body id="body"> + <script type="application/javascript"> + function doTests() { + let iframe = document.getElementById("iframe"); + let cwindow = iframe.contentWindow; + let utils = SpecialPowers.getDOMWindowUtils(cwindow); + let doc = cwindow.document; + + function haveNonFloatingScrollbars() { + return doc.getElementById("float").offsetWidth > 200; + } + + checkScrollbarSizeFlush(utils, (w, h) => w == 0 && h == 0, + "[overflow=hidden] corrrect scrollbar size after flushing"); + + // Some platforms (esp. mobile) may have floating scrollbars that don't + // affect layout. Thus getScrollbarSize() would always return zeros. + if (haveNonFloatingScrollbars()) { + let body = doc.querySelector("body"); + body.style.overflowY = "scroll"; + + checkScrollbarSize(utils, (w, h) => w == 0 && h == 0, + "[overflowY=scroll] correct scrollbar size w/o flushing"); + + checkScrollbarSizeFlush(utils, (w, h) => w > 0 && h == 0, + "[overflowY=scroll] correct scrollbar size after flushing"); + + body.style.overflowX = "scroll"; + checkScrollbarSize(utils, (w, h) => w > 0 && h == 0, + "[overflowXY=scroll] correct scrollbar size w/o flushing"); + + checkScrollbarSizeFlush(utils, (w, h) => w > 0 && h > 0, + "[overflowXY=scroll] correct scrollbar size after flushing"); + } + + SimpleTest.finish(); + } + + function checkScrollbarSize(utils, check, msg, flush = false) { + let width = {}, height = {}; + utils.getScrollbarSize(flush, width, height); + ok(check(width.value, height.value), msg); + } + + function checkScrollbarSizeFlush(utils, check, msg) { + checkScrollbarSize(utils, check, msg, true); + } + + SimpleTest.waitForExplicitFinish(); + </script> + + <iframe src="http://mochi.test:8888/tests/dom/tests/mochitest/general/file_domWindowUtils_scrollbarSize.html" + id="iframe" onload="doTests();"> + </iframe> + +</body> +</html> diff --git a/dom/tests/mochitest/general/test_donottrack.html b/dom/tests/mochitest/general/test_donottrack.html new file mode 100644 index 0000000000..84bd383a1d --- /dev/null +++ b/dom/tests/mochitest/general/test_donottrack.html @@ -0,0 +1,72 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=629535 +--> +<head> + <title>Test for Bug 629535</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=629535">Mozilla Bug 629535</a> + +<script type="application/javascript"> + +const dntPref = 'privacy.donottrackheader.enabled'; + +SimpleTest.waitForExplicitFinish(); + +var currentTestIdx = -1; +var tests = []; +function nextTest() { + currentTestIdx++; + if (currentTestIdx >= tests.length) { + SimpleTest.finish(); + return; + } + + tests[currentTestIdx](); +} + +tests.push(function testDefaultValues() { + // The default pref values depend on the OS it seems. + var isAndroid = !!navigator.userAgent.includes("Android"); + var isB2G = !isAndroid && /Mobile|Tablet/.test(navigator.userAgent); + + is(SpecialPowers.getBoolPref(dntPref), false, + 'DNT should be disabled by default'); + is(navigator.doNotTrack, 'unspecified', + 'navigator.doNotTrack should initially be "unspecified".'); + + nextTest(); +}); + +tests.push(function clearedEnabled() { + SpecialPowers.pushPrefEnv({"clear": [[dntPref]]}, function() { + is(navigator.doNotTrack, "unspecified", 'after clearing pref'); + nextTest(); + }); +}); + +tests.push(function setEnabled() { + SpecialPowers.pushPrefEnv({"set": [[dntPref, true]]}, function() { + is(navigator.doNotTrack, "1", 'after setting pref to true'); + nextTest(); + }); +}); + +tests.push(function setDisabled() { + SpecialPowers.pushPrefEnv({"set": [[dntPref, false]]}, function() { + is(navigator.doNotTrack, "unspecified", 'after setting pref to false'); + nextTest(); + }); +}); + +nextTest(); + +</script> + +</body> +</html> + diff --git a/dom/tests/mochitest/general/test_focus_legend_noparent.html b/dom/tests/mochitest/general/test_focus_legend_noparent.html new file mode 100644 index 0000000000..99bc9d9f22 --- /dev/null +++ b/dom/tests/mochitest/general/test_focus_legend_noparent.html @@ -0,0 +1,36 @@ +<html> +<head> +<script src="/tests/SimpleTest/SimpleTest.js"></script> +<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"> +<script type="text/javascript"> + +SimpleTest.waitForExplicitFinish(); +SimpleTest.requestFlakyTimeout("untriaged"); +SimpleTest.requestFlakyTimeout("untriaged"); + +function runTest() +{ + window.focus(); + var g = document.createElementNS("http://www.w3.org/1999/xhtml", "legend"); + document.body.appendChild(g); + setTimeout(g.focus.bind(g), 0); + setTimeout(done, 10); +} + +function done() +{ + ok(document.hasFocus(), "document is still focused"); + is(document.activeElement, document.body, "document has no focused element") + SimpleTest.finish(); +} + +SimpleTest.waitForFocus(runTest); +</script> +</head> + +<p id="display"></p> +<div id="content" style="display: none"> +</div> + +<body onblur="dump('blurred window!\n')"></body> +</html> diff --git a/dom/tests/mochitest/general/test_focus_scrollchildframe.html b/dom/tests/mochitest/general/test_focus_scrollchildframe.html new file mode 100644 index 0000000000..91688a13a7 --- /dev/null +++ b/dom/tests/mochitest/general/test_focus_scrollchildframe.html @@ -0,0 +1,24 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Tests for for-of loops</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <script src="/tests/SimpleTest/EventUtils.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"> +</head> +<body onload="doTest()"> +<p id="display"></p> +<div id="content" style="display: none"></div> +<script> +function doTest() { + document.getElementById("button").focus(); + is(window.scrollY, 0, "Scrolled position initially 0"); + synthesizeKey("KEY_Tab"), + ok(window.scrollY > 200, "Scrolled child frame into view"); + SimpleTest.finish(); +} +SimpleTest.waitForExplicitFinish(); +</script> +<button id="button">B</button><br><iframe style="margin-top:10000px;height:400px"></iframe> +</body> +</html> diff --git a/dom/tests/mochitest/general/test_focusrings.xhtml b/dom/tests/mochitest/general/test_focusrings.xhtml new file mode 100644 index 0000000000..38882ad99c --- /dev/null +++ b/dom/tests/mochitest/general/test_focusrings.xhtml @@ -0,0 +1,217 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?> +<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + +<script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> +<script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> +<script src="chrome://mochikit/content/tests/SimpleTest/WindowSnapshot.js"></script> + +<html:style xmlns:html="http://www.w3.org/1999/xhtml" type="text/css"> +* { outline: none; } +#b3:focus-visible { outline: auto } +#l1:focus-visible, #l3:focus-visible, #b1:focus-visible { outline: 2px solid red; } +#l2:focus, #b2:focus { outline: 2px solid red; } +</html:style> + +<script> +<![CDATA[ + +SimpleTest.waitForExplicitFinish(); + +const kIsMac = navigator.platform.includes("Mac"); + +function snapShot(element) { + var rect = element.getBoundingClientRect(); + adjustedRect = { left: rect.left - 6, top: rect.top - 6, + width: rect.width + 12, height: rect.height + 12 } + return SpecialPowers.snapshotRect(window, adjustedRect, "transparent"); +} + +async function initTest() { + if (kIsMac) { + // FIXME: Historically this pref was only respected on mac, + // and the test uses that assumption. + // + // Ideally it should be refactored to test with the different + // values of the pref, independently of the platform. + await SpecialPowers.pushPrefEnv({ + "set": [ + ['accessibility.mouse_focuses_formcontrol', 0], + ] + }); + } + + runTest(); +} + +function runTest() +{ + var isWinOrLinux = navigator.platform.includes("Win") || navigator.platform.includes("Linux"); + + function checkFocus(element, visible, testid) + { + var outline = getComputedStyle(element, "").outlineWidth; + is(outline, visible ? "2px" : "0px", testid); + } + + // make sure that a focus ring appears on the focused button + if (kIsMac) { + var focusedButton = $("b3"); + const retBefore = compareSnapshots(snapShot(focusedButton), snapShot($("b2")), true); + ok(retBefore[0], `unfocused shows no ring,\nRESULT:\n${retBefore[1]}, \nREFERENCE:\n${retBefore[2]}`); + focusedButton.focus(); + const retAfter = compareSnapshots(snapShot(focusedButton), snapShot($("b2")), false); + ok(retAfter[0], `focus shows ring,\nRESULT:\n${retAfter[1]}, \nREFERENCE:\n${retAfter[2]}`); + } + + checkFocus($("l1"), false, "initial appearance"); + + // we can't really test the situation on Windows where a dialog doesn't show + // focus rings until a key is pressed, as the default state depends on what + // kind of real user input, mouse or key, was last entered. But we can handle + // the test regardless of which user input last occurred. + $("l1").focus(); + var expectedVisible = (!isWinOrLinux || getComputedStyle($("l1"), "").outlineWidth == "2px"); + testHTMLElements(htmlElements, isWinOrLinux && !expectedVisible); + + $("l1").focus(); + checkFocus($("l1"), expectedVisible, "appearance on list after focus() with :moz-focusring"); + $("l2").focus(); + + checkFocus($("l2"), true, "appearance on list after focus() with :focus"); + + is(getComputedStyle($("l1"), "").outlineWidth, "0px", "appearance on previous list after focus() with :focus"); + + synthesizeMouse($("l1"), 4, 4, { }); + checkFocus($("l1"), false, "appearance on list after mouse focus with :moz-focusring"); + synthesizeMouse($("l2"), 4, 4, { }); + checkFocus($("l2"), true, "appearance on list after mouse focus with :focus"); + + synthesizeMouse($("b1"), 4, 4, { }); + checkFocus($("b1"), false, "appearance on button after mouse focus with :moz-focusring"); + + synthesizeMouse($("b2"), 4, 4, { }); + checkFocus($("b2"), !kIsMac, "appearance on button after mouse focus with :focus"); + + // after a key is pressed, the focus ring will always be visible + $("l2").focus(); + synthesizeKey("KEY_Tab"); + checkFocus($("l3"), true, "appearance on list after tab focus"); + + if (kIsMac) { + SpecialPowers.pushPrefEnv({"set": [['accessibility.mouse_focuses_formcontrol', 1]]}, testMacFocusesFormControl); + } + else { + SimpleTest.finish(); + } +} + +async function testMacFocusesFormControl() +{ + await SimpleTest.promiseFocus(window); + testHTMLElements(htmlElementsMacPrefSet, false); + SimpleTest.finish(); +} + +var htmlElements = [ + "<button id='elem'>Button</button>", + "<input id='elem' type='button'>", + "<input id='elem' type='checkbox'>", + "<input id='elem' class='canfocus'>", + "<input id='elem' type='password' class='canfocus'>", + "<textarea id='elem' class='canfocus'></textarea>", + "<select id='elem' class='canfocus'><option>One</select>", + "<select id='elem' rows='5' class='canfocus'><option>One</select>", + "<div id='elem' tabindex='0' class='canfocus' style='width: 10px; height: 10px;'></div>", + "<a href='about:blank' class='canfocus' onclick='return false;'>about:blank</a>", +]; + +var htmlElementsMacPrefSet = [ + "<button id='elem' class='canfocus'>Button</button>", + "<input id='elem' class='canfocus'>", + "<input id='elem' type='button' class='canfocus'>", + "<input id='elem' type='checkbox' class='canfocus'>", +]; + +function createElement(str) { + let doc = new DOMParser().parseFromSafeString(`<html><body>${str}</body></html>`, "text/html"); + return doc.body.firstChild; +} + +function testHTMLElements(list, expectedNoRingsOnWin) { + var childwin = frames[0]; + var childdoc = childwin.document; + var container = childdoc.getElementById("container"); + for (var e = 0; e < list.length; e++) { + // Using innerHTML would be sanitized, so use the DOM parser instead to + // create the elements. + var elem = childdoc.adoptNode(createElement(list[e])); + container.appendChild(elem); + + var shouldFocus = !kIsMac || (elem.className == "canfocus"); + var ringSize = (shouldFocus ? (expectedNoRingsOnWin ? 2 : 1) : 0) + "px"; + + var expectedMatchWithMouse = shouldFocus && !expectedNoRingsOnWin; + var mouseRingSize = ringSize; + if (shouldFocus) { + var textControl = (function() { + if (elem.localName == "textarea") + return true; + if (elem.localName == "input") + return elem.type == "text" || elem.type == "password"; + return false; + }()); + expectedMatchWithMouse = textControl; + mouseRingSize = textControl ? "1px" : "2px"; + } + + if (elem.localName == "a") { + mouseRingSize = ringSize = "0px"; + expectedMatchWithMouse = expectedMatchWithMouse && !kIsMac; + } + + synthesizeMouse(elem, 8, 8, { }, childwin); + is(childdoc.activeElement, shouldFocus ? elem : childdoc.body, "mouse click on " + list[e]); + is(childwin.getComputedStyle(elem).outlineWidth, mouseRingSize, "mouse click on " + list[e] + " ring"); + is(elem.matches(":-moz-focusring"), expectedMatchWithMouse, "mouse click on " + list[e] + " selector"); + + if (childdoc.activeElement) + childdoc.activeElement.blur(); + + ringSize = mouseRingSize; + if (kIsMac && !shouldFocus && elem.localName != "a") { + ringSize = "1px"; + } + + elem.focus(); + is(childdoc.activeElement, elem, "focus() on " + list[e]); + is(childwin.getComputedStyle(elem).outlineWidth, ringSize, + "focus() on " + list[e] + " ring"); + + childdoc.activeElement.blur(); + + // Clear out the container for the next test. + while (container.firstChild) { + container.firstChild.remove(); + } + } +} + +SimpleTest.waitForFocus(initTest); + +]]> +</script> + +<richlistbox id="l1" class="plain" height="20"/> +<richlistbox id="l2" class="plain" height="20"/> +<richlistbox id="l3" class="plain" height="20"/> +<button id="b1" label="Button"/> +<button id="b2" label="Button"/> +<button id="b3" label="Button"/> + +<iframe id="child" src="file_focusrings.html"/> + +<body xmlns="http://www.w3.org/1999/xhtml" style="height: 300px; overflow: auto;"/> + +</window> diff --git a/dom/tests/mochitest/general/test_for_of.html b/dom/tests/mochitest/general/test_for_of.html new file mode 100644 index 0000000000..d5284f10bb --- /dev/null +++ b/dom/tests/mochitest/general/test_for_of.html @@ -0,0 +1,25 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Tests for for-of loops</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"> +</head> +<body onload="doTest()"> +<p id="display"></p> +<div id="content" style="display: none"></div> +<script> +function doTest() { + // DOM NodeLists are iterable. + var a = []; + for (var e of document.body.childNodes) + if (e.nodeType === 1) + a.push(e.tagName); + is("P DIV SCRIPT", a.join(" "), "for-of should see each element in the body"); + + SimpleTest.finish(); +} +SimpleTest.waitForExplicitFinish(); +</script> +</body> +</html> diff --git a/dom/tests/mochitest/general/test_frameElementWrapping.html b/dom/tests/mochitest/general/test_frameElementWrapping.html new file mode 100644 index 0000000000..a85f0ed394 --- /dev/null +++ b/dom/tests/mochitest/general/test_frameElementWrapping.html @@ -0,0 +1,44 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test for same-origin and cross-origin wrapping of frameElement</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<iframe id="ifr" src="file_frameElementWrapping.html"></iframe> +<pre id="test"> +<script class="testbody" type="text/javascript"> + +// +// This test has sort of morphed over time to become less and less useful. +// In the past, we had special security policy for frameElement, but that's +// more or less gone away with compartment/proxy wrapping. So we just go +// through the motions to make sure that, indeed, frameElement is subject +// to the same-origin policy. +// + +SimpleTest.waitForExplicitFinish(); + +var count = 0; + +function runTest(result, message) { + ok(result === 'PASS', message); + + if (++count === 2) + SimpleTest.finish(); + else + $('ifr').contentWindow.location = 'http://example.org/tests/dom/tests/mochitest/general/file_frameElementWrapping.html'; +} + +window.addEventListener("message", + function(event) { runTest.apply(null, event.data.split(',')) }); + +</script> +</pre> +</body> +</html> diff --git a/dom/tests/mochitest/general/test_framedhistoryframes.html b/dom/tests/mochitest/general/test_framedhistoryframes.html new file mode 100644 index 0000000000..a756d38115 --- /dev/null +++ b/dom/tests/mochitest/general/test_framedhistoryframes.html @@ -0,0 +1,31 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=602256 +--> +<head> + <title>Test for Bug 602256</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=602256">Mozilla Bug 602256</a> +<p id="display"></p> +<div id="content"> + <iframe id="iframe" src="historyframes.html"></iframe> +</div> +<pre id="test"> +<script type="application/javascript"> + +/** Test for Bug 602256 **/ + +SimpleTest.waitForExplicitFinish(); + +function done() { + SimpleTest.finish(); +} + +</script> +</pre> +</body> +</html> diff --git a/dom/tests/mochitest/general/test_img_mutations.html b/dom/tests/mochitest/general/test_img_mutations.html new file mode 100644 index 0000000000..54b02acad6 --- /dev/null +++ b/dom/tests/mochitest/general/test_img_mutations.html @@ -0,0 +1,236 @@ +<!DOCTYPE HTML> +<html> +<head> + <meta charset="utf-8"> + <title>Image srcset mutations</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> +<body> + <script type="application/javascript"> + "use strict"; + + // Tests the relevant mutations part of the spec for img src and srcset + // and that img.src still behaves by the older spec. (Bug 1076583) + // https://html.spec.whatwg.org/#relevant-mutations + SimpleTest.waitForExplicitFinish(); + + // 50x50 png + var testPNG50 = new URL("image_50.png?noCache=" + Math.random(), location).href; + // 100x100 png + var testPNG100 = new URL("image_100.png?noCache=" + Math.random(), location).href; + // 200x200 png + var testPNG200 = new URL("image_200.png?noCache=" + Math.random(), location).href; + + var tests = []; + var img; + var expectingErrors = 0; + var expectingLoads = 0; + var afterExpectCallback; + + function onImgLoad() { + ok(expectingLoads > 0, "expected load"); + if (expectingLoads > 0) { + expectingLoads--; + } + if (!expectingLoads && !expectingErrors && afterExpectCallback) { + setTimeout(afterExpectCallback, 0); + afterExpectCallback = null; + } + } + function onImgError() { + ok(expectingErrors > 0, "expected error"); + if (expectingErrors > 0) { + expectingErrors--; + } + if (!expectingLoads && !expectingErrors && afterExpectCallback) { + setTimeout(afterExpectCallback, 0); + afterExpectCallback = null; + } + } + function expectEvents(loads, errors, callback) { + if (!loads && !errors) { + setTimeout(callback, 0); + } else { + expectingLoads += loads; + expectingErrors += errors; + info("Waiting for " + expectingLoads + " load and " + expectingErrors + " error events"); + afterExpectCallback = callback; + } + } + + // + // Test that img.src still does some work synchronously per the older spec (bug 1076583) + // + tests.push(function test1() { + info("test 1"); + img.src = testPNG50; + is(img.currentSrc, testPNG50, "Should have synchronously selected source"); + + // Assigning a wrong URL should not trigger error event (bug 1321300). + img.src = '//:0'; // Wrong URL + + img.src = "non_existent_image.404"; + ok(img.currentSrc.endsWith("non_existent_image.404"), "Should have synchronously selected source"); + + img.removeAttribute("src"); + is(img.currentSrc, '', "Should have dropped currentSrc"); + + // Load another image while previous load is still pending + img.src = testPNG200; + is(img.currentSrc, testPNG200, "Should have synchronously selected source"); + + // No events should have fired synchronously, now we should get just one load (and no 404 error) + expectEvents(1, 0, nextTest); + }); + + + // Setting srcset should be async + tests.push(function () { + info("test 2"); + img.srcset = testPNG100; + is(img.currentSrc, testPNG200, "Should still have testPNG200 as current request"); + + expectEvents(1, 0, function() { + is(img.currentSrc, testPNG100, "Should now have testPNG100 as current request"); + nextTest(); + }); + }); + + // Setting srcset, even to no ultimate effect, should trigger a reload + tests.push(function () { + info("test 3"); + img.srcset = testPNG100 + " 1x, " + testPNG200 + " 2x"; + is(img.currentSrc, testPNG100, "Should still have testPNG100 as current request"); + + expectEvents(1, 0, function() { + is(img.currentSrc, testPNG100, "Should still have testPNG100 as current request"); + nextTest(); + }); + }); + + // Should switch to src as 1x source + tests.push(function () { + info("test 4"); + img.srcset = testPNG50 + " 2x"; + is(img.currentSrc, testPNG100, "Should still have testPNG100 as current request"); + + expectEvents(1, 0, function() { + is(img.currentSrc, testPNG200, "Should now have testPNG200 as current request"); + nextTest(); + }); + }); + + // Changing src while we have responsive attributes should not be sync + tests.push(function () { + info("test 5"); + img.src = testPNG100; + is(img.currentSrc, testPNG200, "Should still have testPNG200 as current request"); + + expectEvents(1, 0, function() { + is(img.currentSrc, testPNG100, "Should now have testPNG100 as current request"); + + // Switch to using srcset again for next test + img.srcset = testPNG100; + expectEvents(1, 0, nextTest); + }); + }); + + // img.src = img.src should trigger an async event even in responsive mode + tests.push(function () { + info("test 6"); + is(img.currentSrc, testPNG100, "Should now have testPNG100 as current request"); + img.src = img.src; + is(img.currentSrc, testPNG100, "Should still have testPNG100 as current request"); + + expectEvents(1, 0, nextTest); + }); + + // img.srcset = img.srcset should be a no-op + tests.push(function () { + info("test 7"); + img.srcset = img.srcset; + is(img.currentSrc, testPNG100, "Should still have testPNG100 as current request"); + + expectEvents(0, 0, nextTest); + }); + + // re-binding the image to the document should be a no-op + tests.push(function () { + info("test 8"); + document.body.appendChild(img); + is(img.currentSrc, testPNG100, "Should still have testPNG100 as current request"); + + expectEvents(0, 0, nextTest); + }); + + // We should re-run our selection algorithm when any load event occurs + tests.push(function () { + info("test 9"); + img.srcset = testPNG50 + " 1x, " + testPNG200 + " 2x"; + is(img.currentSrc, testPNG100, "Should still have testPNG100 as current request"); + + expectEvents(1, 0, function() { + is(img.currentSrc, testPNG50, "Should now have testPNG50 as current request"); + + // The preference change will trigger a load, as the image will change + SpecialPowers.pushPrefEnv({'set': [ ["layout.css.devPixelsPerPx", "2.0"] ] }); + expectEvents(1, 0, function() { + is(img.currentSrc, testPNG200, "Should now have testPNG200 as current request"); + img.src = img.src; + is(img.currentSrc, testPNG200, "Should still have testPNG200 as current request"); + // img.src = img.src is special-cased by the spec. It should always + // trigger an load event + expectEvents(1, 0, function() { + is(img.currentSrc, testPNG200, "Should still have testPNG200 as current request"); + expectEvents(0, 0, nextTest); + }); + }) + }); + }); + + // Removing srcset attr should async switch back to src + tests.push(function () { + info("test 10"); + is(img.currentSrc, testPNG200, "Should have testPNG200 as current request"); + + img.removeAttribute("srcset"); + is(img.currentSrc, testPNG200, "Should still have testPNG200 as current request"); + + expectEvents(1, 0, function() { + is(img.currentSrc, testPNG100, "Should now have testPNG100 as current request"); + + expectEvents(0, 0, nextTest); + }); + }); + + function nextTest() { + if (tests.length) { + // Spin event loop to make sure no unexpected image events are + // pending (unexpected events will assert in the handlers) + setTimeout(function() { + (tests.shift())(); + }, 0); + } else { + // Remove the event listeners to prevent the prefenv being popped from + // causing test failures. + img.removeEventListener("load", onImgLoad); + img.removeEventListener("error", onImgError); + SimpleTest.finish(); + } + } + + addEventListener("load", function() { + SpecialPowers.pushPrefEnv({'set': [["layout.css.devPixelsPerPx", "1.0"]] }, + function() { + // Create this after the pref is set, as it is guarding webIDL attributes + img = document.createElement("img"); + img.addEventListener("load", onImgLoad); + img.addEventListener("error", onImgError); + document.body.appendChild(img); + setTimeout(nextTest, 0); + }); + }); + </script> +</body> +</html> diff --git a/dom/tests/mochitest/general/test_innerScreen.xhtml b/dom/tests/mochitest/general/test_innerScreen.xhtml new file mode 100644 index 0000000000..7e30bce63b --- /dev/null +++ b/dom/tests/mochitest/general/test_innerScreen.xhtml @@ -0,0 +1,83 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?> +<!-- + Tests for mozInnerScreenX/Y properties + --> +<window title="Test mozInnerScreenX/Y Properties" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/> + + <!-- test resuls are displayed in the html:body --> + <body xmlns="http://www.w3.org/1999/xhtml" + style="height: 400px; position:relative; overflow: auto;"> + <iframe id="f" + style="position:absolute; left:100px; + top:200px; width:200px; height:200px; border:none;"></iframe> + </body> + + <!-- test code goes here --> + <script type="application/javascript"><![CDATA[ + +function isRounded(a, b, msg) { + ok(Math.round(a) == Math.round(b), + msg + " (rounded), got " + a + ", expected " + b); +} + +function doTests() +{ + var readable = false; + try + { + mozScreenPixelsPerCSSPixel; + readable = true; + } + catch(ex) { } + ok(!readable, "window pixels per css pixel shouldn't be readable to content"); + + var devPxPerCSSPx = window.devicePixelRatio; + + is(window.devicePixelRatio, devPxPerCSSPx, "window.devicePixelRatio"); + + var rootElement = document.documentElement; + isRounded(window.mozInnerScreenX*devPxPerCSSPx, rootElement.screenX, + "window screen X"); + isRounded(window.mozInnerScreenY*devPxPerCSSPx, rootElement.screenY, + "window screen Y"); + + var f = document.getElementById("f"); + var fBounds = f.getBoundingClientRect(); + + var fbc = f.contentWindow.docShell.browsingContext; + + isRounded(f.contentWindow.mozInnerScreenX, + window.mozInnerScreenX + fBounds.left, + "frame screen X"); + isRounded(f.contentWindow.mozInnerScreenY, + window.mozInnerScreenY + fBounds.top, + "frame screen Y"); + + fbc.fullZoom *= 2; + is(f.contentWindow.devicePixelRatio, 2*devPxPerCSSPx, + "frame screen pixels per CSS pixel"); + + is(f.contentWindow.devicePixelRatio, 2*devPxPerCSSPx, "frame devicePixelRatio"); + + isRounded(f.contentWindow.mozInnerScreenX*2, + window.mozInnerScreenX + fBounds.left, + "zoomed frame screen X"); + isRounded(f.contentWindow.mozInnerScreenY*2, + window.mozInnerScreenY + fBounds.top, + "zoomed frame screen Y"); + fbc.fullZoom = 1.0; + + SimpleTest.finish(); +} + +addLoadEvent(doTests); +SimpleTest.waitForExplicitFinish(); + +]]> +</script> + +</window> diff --git a/dom/tests/mochitest/general/test_interfaces.html b/dom/tests/mochitest/general/test_interfaces.html new file mode 100644 index 0000000000..214552ed04 --- /dev/null +++ b/dom/tests/mochitest/general/test_interfaces.html @@ -0,0 +1,28 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=766694 +--> +<head> + <meta charset="utf-8"> + <title>Test for Bug 766694</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=766694">Mozilla Bug 766694</a> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<iframe id="testframe" src="about:blank"></iframe> +<pre id="test"> +<script> + ok(!self.isSecureContext, "The test should not be running in a secure context"); + ok(!document.getElementById("testframe").contentWindow.isSecureContext, + "The test should not be running in a secure context"); +</script> +<script type="text/javascript" src="test_interfaces.js"></script> +</pre> +</body> +</html> diff --git a/dom/tests/mochitest/general/test_interfaces.js b/dom/tests/mochitest/general/test_interfaces.js new file mode 100644 index 0000000000..1de681009e --- /dev/null +++ b/dom/tests/mochitest/general/test_interfaces.js @@ -0,0 +1,2049 @@ +/** Test for Bug 766694 **/ + +// This is a list of all interfaces that are exposed to every webpage. +// Please only add things to this list with great care and proper review +// from the associated module peers. +// +// The test is supposed to check whether our actual exposure behavior +// matches what we expect, with the latter expressed in terms of outside +// observables like type of build (nightly, release), platform, secure +// context, etc. Testing based on prefs is thus the wrong check, as this +// means we'd also have to test whether the pref value matches what we +// expect in terms of outside observables. + +// This file lists global interfaces we want exposed and verifies they +// are what we intend. Each entry in the arrays below can either be a +// simple string with the interface name, or an object with a 'name' +// property giving the interface name as a string, and additional +// properties which qualify the exposure of that interface. For example: +// +// [ +// "AGlobalInterface", +// {name: "ExperimentalThing", release: false}, +// {name: "ReallyExperimentalThing", nightly: true}, +// {name: "DesktopOnlyThing", desktop: true}, +// {name: "DisabledEverywhere", disabled: true}, +// ]; +// +// See createInterfaceMap() below for a complete list of properties. +// +// The values of the properties need to be either literal true/false +// (e.g. indicating whether something is enabled on a particular +// channel/OS) or one of the is* constants below (in cases when +// exposure is affected by channel or OS in a nontrivial way). + +const { AppConstants } = SpecialPowers.ChromeUtils.importESModule( + "resource://gre/modules/AppConstants.sys.mjs" +); + +const isNightly = AppConstants.NIGHTLY_BUILD; +const isEarlyBetaOrEarlier = AppConstants.EARLY_BETA_OR_EARLIER; +const isRelease = AppConstants.RELEASE_OR_BETA; +const isDesktop = !/Mobile|Tablet/.test(navigator.userAgent); +const isMac = AppConstants.platform == "macosx"; +const isWindows = AppConstants.platform == "win"; +const isAndroid = AppConstants.platform == "android"; +const isLinux = AppConstants.platform == "linux"; +const isInsecureContext = !window.isSecureContext; +// Currently, MOZ_APP_NAME is always "fennec" for all mobile builds, so we can't use AppConstants for this +const isFennec = + isAndroid && + SpecialPowers.Cc["@mozilla.org/android/bridge;1"].getService( + SpecialPowers.Ci.nsIAndroidBridge + ).isFennec; +const isCrossOriginIsolated = window.crossOriginIsolated; + +// IMPORTANT: Do not change this list without review from +// a JavaScript Engine peer! +let wasmGlobalEntry = { + name: "WebAssembly", + insecureContext: true, + disabled: + !SpecialPowers.Cu.getJSTestingFunctions().wasmIsSupportedByHardware(), +}; +let wasmGlobalInterfaces = [ + { name: "Module", insecureContext: true }, + { name: "Instance", insecureContext: true }, + { name: "Memory", insecureContext: true }, + { name: "Table", insecureContext: true }, + { name: "Global", insecureContext: true }, + { name: "CompileError", insecureContext: true }, + { name: "LinkError", insecureContext: true }, + { name: "RuntimeError", insecureContext: true }, + { name: "Function", insecureContext: true, nightly: true }, + { name: "Exception", insecureContext: true }, + { name: "Tag", insecureContext: true }, + { name: "compile", insecureContext: true }, + { name: "compileStreaming", insecureContext: true }, + { name: "instantiate", insecureContext: true }, + { name: "instantiateStreaming", insecureContext: true }, + { name: "validate", insecureContext: true }, +]; +// IMPORTANT: Do not change this list without review from +// a JavaScript Engine peer! +let ecmaGlobals = [ + { name: "AggregateError", insecureContext: true }, + { name: "Array", insecureContext: true }, + { name: "ArrayBuffer", insecureContext: true }, + { name: "Atomics", insecureContext: true }, + { name: "BigInt", insecureContext: true }, + { name: "BigInt64Array", insecureContext: true }, + { name: "BigUint64Array", insecureContext: true }, + { name: "Boolean", insecureContext: true }, + { name: "DataView", insecureContext: true }, + { name: "Date", insecureContext: true }, + { name: "Error", insecureContext: true }, + { name: "EvalError", insecureContext: true }, + { name: "FinalizationRegistry", insecureContext: true }, + { name: "Float32Array", insecureContext: true }, + { name: "Float64Array", insecureContext: true }, + { name: "Function", insecureContext: true }, + { name: "Infinity", insecureContext: true }, + { name: "Int16Array", insecureContext: true }, + { name: "Int32Array", insecureContext: true }, + { name: "Int8Array", insecureContext: true }, + { name: "InternalError", insecureContext: true }, + { name: "Intl", insecureContext: true }, + { name: "JSON", insecureContext: true }, + { name: "Map", insecureContext: true }, + { name: "Math", insecureContext: true }, + { name: "NaN", insecureContext: true }, + { name: "Number", insecureContext: true }, + { name: "Object", insecureContext: true }, + { name: "Promise", insecureContext: true }, + { name: "Proxy", insecureContext: true }, + { name: "RangeError", insecureContext: true }, + { name: "ReferenceError", insecureContext: true }, + { name: "Reflect", insecureContext: true }, + { name: "RegExp", insecureContext: true }, + { name: "Set", insecureContext: true }, + { + name: "SharedArrayBuffer", + insecureContext: true, + crossOriginIsolated: true, + }, + { name: "String", insecureContext: true }, + { name: "Symbol", insecureContext: true }, + { name: "SyntaxError", insecureContext: true }, + { name: "TypeError", insecureContext: true }, + { name: "Uint16Array", insecureContext: true }, + { name: "Uint32Array", insecureContext: true }, + { name: "Uint8Array", insecureContext: true }, + { name: "Uint8ClampedArray", insecureContext: true }, + { name: "URIError", insecureContext: true }, + { name: "WeakMap", insecureContext: true }, + { name: "WeakRef", insecureContext: true }, + { name: "WeakSet", insecureContext: true }, + wasmGlobalEntry, + { name: "decodeURI", insecureContext: true }, + { name: "decodeURIComponent", insecureContext: true }, + { name: "encodeURI", insecureContext: true }, + { name: "encodeURIComponent", insecureContext: true }, + { name: "escape", insecureContext: true }, + { name: "eval", insecureContext: true }, + { name: "globalThis", insecureContext: true }, + { name: "isFinite", insecureContext: true }, + { name: "isNaN", insecureContext: true }, + { name: "parseFloat", insecureContext: true }, + { name: "parseInt", insecureContext: true }, + { name: "undefined", insecureContext: true }, + { name: "unescape", insecureContext: true }, +]; +// IMPORTANT: Do not change the list above without review from +// a JavaScript Engine peer! + +// IMPORTANT: Do not change the list below without review from a DOM peer! +// (You can request review on Phabricator via r=#webidl) +let interfaceNamesInGlobalScope = [ + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "AbortController", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "AbortSignal", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "AbstractRange", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "AnalyserNode", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "Animation", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "AnimationEffect", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "AnimationEvent", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "AnimationPlaybackEvent", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "AnimationTimeline", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "Attr", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "Audio", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "AudioBuffer", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "AudioContext", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "AudioBufferSourceNode", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "AudioDestinationNode", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "AudioListener", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "AudioNode", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "AudioParam", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "AudioParamMap", insecureContext: false }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "AudioProcessingEvent", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "AudioScheduledSourceNode", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "AudioWorklet", insecureContext: false }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "AudioWorkletNode", insecureContext: false }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "AuthenticatorAssertionResponse" }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "AuthenticatorAttestationResponse" }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "AuthenticatorResponse" }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "BarProp", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "BaseAudioContext", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "BeforeUnloadEvent", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "BiquadFilterNode", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "Blob", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "BlobEvent", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "BroadcastChannel", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "ByteLengthQueuingStrategy", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + "Cache", + // IMPORTANT: Do not change this list without review from a DOM peer! + "CacheStorage", + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "CanvasCaptureMediaStream", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "CanvasGradient", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "CanvasPattern", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "CanvasRenderingContext2D", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "CaretPosition", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "CDATASection", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "ChannelMergerNode", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "ChannelSplitterNode", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "CharacterData", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "Clipboard" }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "ClipboardEvent", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "CloseEvent", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "Comment", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "CompositionEvent", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "CompressionStream", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "ConstantSourceNode", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { + name: "ContentVisibilityAutoStateChangeEvent", + insecureContext: true, + nightly: true, + }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "ConvolverNode", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "CountQueuingStrategy", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "Credential" }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "CredentialsContainer" }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "Crypto", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "CryptoKey" }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "CSS", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "CSS2Properties", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "CSSAnimation", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "CSSConditionRule", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "CSSContainerRule", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "CSSCounterStyleRule", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "CSSFontFaceRule", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "CSSFontFeatureValuesRule", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "CSSFontPaletteValuesRule", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "CSSGroupingRule", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "CSSImportRule", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "CSSKeyframeRule", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "CSSKeyframesRule", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "CSSLayerBlockRule", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "CSSLayerStatementRule", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "CSSMediaRule", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "CSSMozDocumentRule", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "CSSNamespaceRule", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "CSSPageRule", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "CSSPseudoElement", insecureContext: true, disabled: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "CSSRule", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "CSSRuleList", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "CSSStyleDeclaration", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "CSSStyleRule", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "CSSStyleSheet", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "CSSSupportsRule", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "CSSTransition", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "CustomElementRegistry", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "CustomEvent", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "DecompressionStream", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "DataTransfer", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "DataTransferItem", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "DataTransferItemList", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "DelayNode", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "DeviceLightEvent", insecureContext: true, disabled: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "DeviceMotionEvent", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "DeviceOrientationEvent", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "DeviceProximityEvent", insecureContext: true, disabled: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "Directory", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "Document", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "DocumentFragment", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "DocumentTimeline", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "DocumentType", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "DOMException", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "DOMImplementation", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "DOMMatrix", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "DOMMatrixReadOnly", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "DOMParser", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "DOMPoint", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "DOMPointReadOnly", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "DOMQuad", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "DOMRect", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "DOMRectList", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "DOMRectReadOnly", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "DOMRequest", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "DOMStringList", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "DOMStringMap", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "DOMTokenList", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "DragEvent", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "DynamicsCompressorNode", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "Element", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "ElementInternals", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "ErrorEvent", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "Event", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "EventCounts", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "EventSource", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "EventTarget", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "File", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "FileList", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "FileReader", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "FileSystem", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "FileSystemDirectoryEntry", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "FileSystemDirectoryHandle" }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "FileSystemDirectoryReader", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "FileSystemEntry", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "FileSystemFileEntry", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "FileSystemFileHandle" }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "FileSystemHandle" }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "FileSystemWritableFileStream" }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "FocusEvent", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "FormData", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "FormDataEvent", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "FontFace", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "FontFaceSet", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "FontFaceSetLoadEvent", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "GainNode", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "Gamepad", insecureContext: false }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "GamepadAxisMoveEvent", insecureContext: false }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "GamepadButtonEvent", insecureContext: false }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "GamepadButton", insecureContext: false }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "GamepadEvent", insecureContext: false }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "GamepadHapticActuator", insecureContext: false }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "GamepadLightIndicator", insecureContext: false, disabled: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "GamepadPose", insecureContext: false }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "GamepadTouch", insecureContext: false, disabled: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "Geolocation", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "GeolocationCoordinates", insecureContext: false }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "GeolocationPosition", insecureContext: false }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "GeolocationPositionError", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "GPU", nightly: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "GPUAdapter", nightly: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "GPUAdapterInfo", nightly: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "GPUBindGroup", nightly: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "GPUBindGroupLayout", nightly: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "GPUBuffer", nightly: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "GPUBufferUsage", nightly: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "GPUCanvasContext", nightly: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "GPUColorWrite", nightly: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "GPUCommandBuffer", nightly: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "GPUCommandEncoder", nightly: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "GPUCompilationInfo", nightly: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "GPUCompilationMessage", nightly: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "GPUComputePassEncoder", nightly: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "GPUComputePipeline", nightly: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "GPUDevice", nightly: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "GPUDeviceLostInfo", nightly: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "GPUMapMode", nightly: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "GPUOutOfMemoryError", nightly: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "GPUPipelineLayout", nightly: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "GPUQuerySet", nightly: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "GPUQueue", nightly: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "GPURenderBundle", nightly: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "GPURenderBundleEncoder", nightly: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "GPURenderPassEncoder", nightly: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "GPURenderPipeline", nightly: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "GPUSampler", nightly: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "GPUShaderModule", nightly: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "GPUShaderStage", nightly: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "GPUSupportedFeatures", nightly: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "GPUSupportedLimits", nightly: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "GPUTexture", nightly: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "GPUTextureUsage", nightly: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "GPUTextureView", nightly: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "GPUUncapturedErrorEvent", nightly: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "GPUValidationError", nightly: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "HashChangeEvent", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "Headers", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "History", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "HTMLAllCollection", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "HTMLAnchorElement", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "HTMLAreaElement", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "HTMLAudioElement", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "HTMLBaseElement", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "HTMLBodyElement", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "HTMLBRElement", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "HTMLButtonElement", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "HTMLCanvasElement", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "HTMLCollection", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "HTMLDataElement", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "HTMLDataListElement", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "HTMLDetailsElement", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "HTMLDialogElement", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "HTMLDirectoryElement", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "HTMLDivElement", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "HTMLDListElement", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "HTMLDocument", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "HTMLElement", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "HTMLEmbedElement", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "HTMLFieldSetElement", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "HTMLFontElement", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "HTMLFormControlsCollection", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "HTMLFormElement", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "HTMLFrameElement", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "HTMLFrameSetElement", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "HTMLHeadElement", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "HTMLHeadingElement", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "HTMLHRElement", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "HTMLHtmlElement", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "HTMLIFrameElement", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "HTMLImageElement", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "HTMLInputElement", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "HTMLLabelElement", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "HTMLLegendElement", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "HTMLLIElement", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "HTMLLinkElement", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "HTMLMapElement", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "HTMLMarqueeElement", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "HTMLMediaElement", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "HTMLMenuElement", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "HTMLMetaElement", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "HTMLMeterElement", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "HTMLModElement", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "HTMLObjectElement", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "HTMLOListElement", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "HTMLOptGroupElement", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "HTMLOptionElement", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "HTMLOptionsCollection", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "HTMLOutputElement", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "HTMLParagraphElement", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "HTMLParamElement", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "HTMLPreElement", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "HTMLPictureElement", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "HTMLProgressElement", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "HTMLQuoteElement", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "HTMLScriptElement", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "HTMLSelectElement", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "HTMLSlotElement", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "HTMLSourceElement", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "HTMLSpanElement", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "HTMLStyleElement", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "HTMLTableCaptionElement", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "HTMLTableCellElement", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "HTMLTableColElement", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "HTMLTableElement", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "HTMLTableRowElement", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "HTMLTableSectionElement", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "HTMLTemplateElement", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "HTMLTextAreaElement", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "HTMLTimeElement", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "HTMLTitleElement", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "HTMLTrackElement", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "HTMLUListElement", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "HTMLUnknownElement", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "HTMLVideoElement", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "IdleDeadline", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "IDBCursor", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "IDBCursorWithValue", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "IDBDatabase", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "IDBFactory", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "IDBIndex", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "IDBKeyRange", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "IDBObjectStore", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "IDBOpenDBRequest", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "IDBRequest", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "IDBTransaction", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "IDBVersionChangeEvent", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "IIRFilterNode", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "Image", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "ImageBitmap", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "ImageBitmapRenderingContext", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "ImageCapture", insecureContext: true, disabled: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "ImageCaptureErrorEvent", insecureContext: true, disabled: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "ImageData", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "InputEvent", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { + name: "InstallTrigger", + insecureContext: true, + disabled: isEarlyBetaOrEarlier, + }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "IntersectionObserver", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "IntersectionObserverEntry", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "KeyEvent", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "KeyboardEvent", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "KeyframeEffect", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "Location", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + "Lock", + // IMPORTANT: Do not change this list without review from a DOM peer! + "LockManager", + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "MathMLElement", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "MediaCapabilities", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "MediaCapabilitiesInfo", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "MediaDeviceInfo", insecureContext: false }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "MediaDevices", insecureContext: false }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "MediaElementAudioSourceNode", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "MediaError", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "MediaKeyError", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "MediaEncryptedEvent", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "MediaKeys", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "MediaKeySession", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "MediaKeySystemAccess", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "MediaKeyMessageEvent", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "MediaKeyStatusMap", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "MediaList", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "MediaMetadata", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "MediaQueryList", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "MediaQueryListEvent", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "MediaRecorder", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "MediaRecorderErrorEvent", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "MediaSession", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "MediaSource", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "MediaStream", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "MediaStreamAudioDestinationNode", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "MediaStreamAudioSourceNode", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "MediaStreamTrackAudioSourceNode", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "MediaStreamEvent", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "MediaStreamTrackEvent", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "MediaStreamTrack", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { + name: "MerchantValidationEvent", + insecureContext: false, + desktop: true, + nightly: true, + linux: false, + disabled: true, + }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "MessageChannel", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "MessageEvent", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "MessagePort", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "MIDIAccess", android: false }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "MIDIConnectionEvent", android: false }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "MIDIInputMap", android: false }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "MIDIInput", android: false }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "MIDIMessageEvent", android: false }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "MIDIOutputMap", android: false }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "MIDIOutput", android: false }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "MIDIPort", android: false }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "MimeType", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "MimeTypeArray", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "MouseEvent", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "MouseScrollEvent", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "MutationEvent", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "MutationObserver", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "MutationRecord", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "NamedNodeMap", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + "NavigationPreloadManager", + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "Navigator", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "NetworkInformation", insecureContext: true, disabled: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "Node", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "NodeFilter", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "NodeIterator", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "NodeList", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "Notification", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "OfflineAudioCompletionEvent", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "OfflineAudioContext", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "OffscreenCanvas", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "OffscreenCanvasRenderingContext2D", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "Option", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "OscillatorNode", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "PageTransitionEvent", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "PaintRequest", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "PaintRequestList", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "PannerNode", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "Path2D", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { + name: "PaymentAddress", + insecureContext: false, + desktop: true, + nightly: true, + linux: false, + disabled: true, + }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { + name: "PaymentMethodChangeEvent", + insecureContext: false, + desktop: true, + nightly: true, + linux: false, + disabled: true, + }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { + name: "PaymentRequest", + insecureContext: false, + desktop: true, + nightly: true, + linux: false, + disabled: true, + }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { + name: "PaymentRequestUpdateEvent", + insecureContext: false, + desktop: true, + nightly: true, + linux: false, + disabled: true, + }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { + name: "PaymentResponse", + insecureContext: false, + desktop: true, + nightly: true, + linux: false, + disabled: true, + }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "Performance", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "PerformanceEntry", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "PerformanceEventTiming", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "PerformanceMark", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "PerformanceMeasure", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "PerformanceNavigation", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "PerformanceNavigationTiming", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "PerformanceObserver", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "PerformanceObserverEntryList", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "PerformancePaintTiming", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "PerformanceResourceTiming", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "PerformanceServerTiming", insecureContext: false }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "PerformanceTiming", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "PeriodicWave", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "Permissions", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "PermissionStatus", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "Plugin", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "PluginArray", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "PointerEvent", insecureContext: true, fennec: false }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "PopStateEvent", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "PopupBlockedEvent", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "ProcessingInstruction", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "ProgressEvent", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "PromiseRejectionEvent", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "PublicKeyCredential" }, + // IMPORTANT: Do not change this list without review from a DOM peer! + "PushManager", + // IMPORTANT: Do not change this list without review from a DOM peer! + "PushSubscription", + // IMPORTANT: Do not change this list without review from a DOM peer! + "PushSubscriptionOptions", + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "RadioNodeList", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "Range", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "ReadableByteStreamController", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "ReadableStream", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "ReadableStreamBYOBReader", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "ReadableStreamBYOBRequest", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "ReadableStreamDefaultController", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "ReadableStreamDefaultReader", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "Request", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "ResizeObserver", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "ResizeObserverEntry", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "ResizeObserverSize", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "Response", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "RTCCertificate", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "RTCDataChannel", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "RTCDataChannelEvent", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "RTCDtlsTransport", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "RTCDTMFSender", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "RTCDTMFToneChangeEvent", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "RTCIceCandidate", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "RTCPeerConnection", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "RTCPeerConnectionIceEvent", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "RTCRtpReceiver", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "RTCRtpSender", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "RTCRtpTransceiver", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "RTCSctpTransport", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "RTCSessionDescription", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "RTCStatsReport", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "RTCTrackEvent", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "Sanitizer", disabled: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "Scheduler", insecureContext: true, nightly: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "Screen", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "ScreenOrientation", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "ScriptProcessorNode", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "ScrollAreaEvent", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "SecurityPolicyViolationEvent", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "Selection", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + "ServiceWorker", + // IMPORTANT: Do not change this list without review from a DOM peer! + "ServiceWorkerContainer", + // IMPORTANT: Do not change this list without review from a DOM peer! + "ServiceWorkerRegistration", + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "ScopedCredential", insecureContext: true, disabled: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "ScopedCredentialInfo", insecureContext: true, disabled: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "ShadowRoot", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "SharedWorker", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "SourceBuffer", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "SourceBufferList", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "SpeechSynthesisErrorEvent", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "SpeechSynthesisEvent", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "SpeechSynthesis", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "SpeechSynthesisUtterance", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "SpeechSynthesisVoice", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "SpecialPowers", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "StaticRange", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "StereoPannerNode", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "Storage", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "StorageEvent", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "StorageManager", fennec: false }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "StyleSheet", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "StyleSheetList", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "SubtleCrypto" }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "SubmitEvent", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "SVGAElement", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "SVGAngle", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "SVGAnimatedAngle", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "SVGAnimatedBoolean", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "SVGAnimatedEnumeration", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "SVGAnimatedInteger", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "SVGAnimatedLength", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "SVGAnimatedLengthList", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "SVGAnimatedNumber", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "SVGAnimatedNumberList", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "SVGAnimatedPreserveAspectRatio", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "SVGAnimatedRect", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "SVGAnimatedString", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "SVGAnimatedTransformList", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "SVGAnimateElement", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "SVGAnimateMotionElement", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "SVGAnimateTransformElement", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "SVGAnimationElement", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "SVGCircleElement", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "SVGClipPathElement", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "SVGComponentTransferFunctionElement", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "SVGDefsElement", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "SVGDescElement", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "SVGElement", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "SVGEllipseElement", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "SVGFEBlendElement", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "SVGFEColorMatrixElement", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "SVGFEComponentTransferElement", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "SVGFECompositeElement", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "SVGFEConvolveMatrixElement", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "SVGFEDiffuseLightingElement", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "SVGFEDisplacementMapElement", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "SVGFEDistantLightElement", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "SVGFEDropShadowElement", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "SVGFEFloodElement", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "SVGFEFuncAElement", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "SVGFEFuncBElement", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "SVGFEFuncGElement", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "SVGFEFuncRElement", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "SVGFEGaussianBlurElement", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "SVGFEImageElement", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "SVGFEMergeElement", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "SVGFEMergeNodeElement", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "SVGFEMorphologyElement", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "SVGFEOffsetElement", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "SVGFEPointLightElement", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "SVGFESpecularLightingElement", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "SVGFESpotLightElement", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "SVGFETileElement", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "SVGFETurbulenceElement", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "SVGFilterElement", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "SVGForeignObjectElement", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "SVGGElement", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "SVGGeometryElement", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "SVGGradientElement", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "SVGGraphicsElement", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "SVGImageElement", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "SVGLength", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "SVGLengthList", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "SVGLinearGradientElement", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "SVGLineElement", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "SVGMarkerElement", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "SVGMaskElement", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "SVGMatrix", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "SVGMetadataElement", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "SVGMPathElement", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "SVGNumber", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "SVGNumberList", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "SVGPathElement", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "SVGPatternElement", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "SVGPoint", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "SVGPointList", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "SVGPolygonElement", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "SVGPolylineElement", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "SVGPreserveAspectRatio", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "SVGRadialGradientElement", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "SVGRect", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "SVGRectElement", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "SVGScriptElement", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "SVGSetElement", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "SVGStopElement", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "SVGStringList", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "SVGStyleElement", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "SVGSVGElement", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "SVGSwitchElement", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "SVGSymbolElement", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "SVGTextContentElement", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "SVGTextElement", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "SVGTextPathElement", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "SVGTextPositioningElement", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "SVGTitleElement", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "SVGTransform", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "SVGTransformList", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "SVGTSpanElement", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "SVGUnitTypes", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "SVGUseElement", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "SVGViewElement", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "TaskController", insecureContext: true, nightly: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "TaskPriorityChangeEvent", insecureContext: true, nightly: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "TaskSignal", insecureContext: true, nightly: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "Text", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "TextDecoder", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "TextDecoderStream", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "TextEncoder", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "TextEncoderStream", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "TextMetrics", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "TextTrack", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "TextTrackCue", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "TextTrackCueList", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "TextTrackList", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "TimeEvent", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "TimeRanges", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "Touch", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "TouchEvent", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "TouchList", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "TrackEvent", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "TransformStream", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { + name: "TransformStreamDefaultController", + insecureContext: true, + }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "TransitionEvent", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "TreeWalker", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "U2F", insecureContext: false, disabled: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "UIEvent", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "URL", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "URLSearchParams", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "UserProximityEvent", insecureContext: true, disabled: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "ValidityState", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "VideoPlaybackQuality", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "VisualViewport", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "VTTCue", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "VTTRegion", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "WaveShaperNode", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "WebAuthnAssertion", insecureContext: true, disabled: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "WebAuthnAttestation", insecureContext: true, disabled: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "WebAuthentication", insecureContext: true, disabled: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "WebGLActiveInfo", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "WebGLBuffer", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "WebGLContextEvent", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "WebGLFramebuffer", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "WebGLProgram", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "WebGLQuery", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "WebGLRenderbuffer", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "WebGLRenderingContext", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "WebGL2RenderingContext", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "WebGLSampler", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "WebGLShader", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "WebGLShaderPrecisionFormat", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "WebGLSync", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "WebGLTexture", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "WebGLTransformFeedback", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "WebGLUniformLocation", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "WebGLVertexArrayObject", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "WebKitCSSMatrix", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "WebSocket", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "WebTransport", insecureContext: false }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "WebTransportBidirectionalStream", insecureContext: false }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "WebTransportDatagramDuplexStream", insecureContext: false }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "WebTransportError", insecureContext: false }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "WebTransportReceiveStream", insecureContext: false }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "WebTransportSendStream", insecureContext: false }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "WheelEvent", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "Window", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "Worker", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "Worklet", insecureContext: false }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "WritableStream", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "WritableStreamDefaultController", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "WritableStreamDefaultWriter", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "XMLDocument", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "XMLHttpRequest", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "XMLHttpRequestEventTarget", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "XMLHttpRequestUpload", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "XMLSerializer", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "XPathEvaluator", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "XPathExpression", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "XPathResult", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "XSLTProcessor", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "alert", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "atob", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "blur", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "btoa", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "caches", insecureContext: false }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "cancelAnimationFrame", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "cancelIdleCallback", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "captureEvents", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "clearInterval", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "clearTimeout", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "clientInformation", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "close", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "closed", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "confirm", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "console", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "createImageBitmap", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "crossOriginIsolated", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "crypto", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "customElements", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "devicePixelRatio", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "document", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "dump", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "event", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "external", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "fetch", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "find", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "focus", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "frameElement", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "frames", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "fullScreen", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "getComputedStyle", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "getDefaultComputedStyle", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "getSelection", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "history", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "indexedDB", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "innerHeight", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "innerWidth", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "isSecureContext", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "length", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "localStorage", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "location", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "locationbar", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "matchMedia", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "menubar", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "moveBy", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "moveTo", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "mozInnerScreenX", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "mozInnerScreenY", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "name", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "navigator", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "netscape", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "onabort", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "ondeviceorientationabsolute", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "onafterprint", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "onanimationcancel", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "onanimationend", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "onanimationiteration", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "onanimationstart", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "onauxclick", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "onbeforeinput", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "onbeforeprint", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "onbeforeunload", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "onblur", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "oncanplay", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "oncanplaythrough", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "onchange", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "onclick", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "onclose", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "oncontextmenu", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "oncopy", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "oncuechange", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "oncut", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "ondblclick", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "ondevicemotion", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "ondeviceorientation", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "ondrag", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "ondragend", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "ondragenter", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "ondragexit", insecureContext: true, nightly: false }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "ondragleave", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "ondragover", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "ondragstart", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "ondrop", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "ondurationchange", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "onemptied", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "onended", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "onerror", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "onfocus", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "onformdata", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "ongamepadconnected", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "ongamepaddisconnected", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "ongotpointercapture", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "onhashchange", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "oninput", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "oninvalid", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "onkeydown", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "onkeypress", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "onkeyup", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "onlanguagechange", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "onload", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "onloadeddata", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "onloadedmetadata", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "onloadstart", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "onlostpointercapture", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "onmessage", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "onmessageerror", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "onmousedown", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "onmouseenter", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "onmouseleave", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "onmousemove", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "onmouseout", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "onmouseover", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "onmouseup", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "onmozfullscreenchange", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "onmozfullscreenerror", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "onoffline", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "ononline", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "onorientationchange", insecureContext: true, android: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "onpagehide", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "onpageshow", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "onpaste", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "onpause", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "onplay", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "onplaying", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "onpointercancel", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "onpointerdown", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "onpointerenter", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "onpointerleave", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "onpointermove", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "onpointerout", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "onpointerover", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "onpointerup", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "onpopstate", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "onprogress", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "onratechange", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "onrejectionhandled", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "onreset", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "onresize", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "onscroll", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "onscrollend", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "onsecuritypolicyviolation", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "onseeked", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "onseeking", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "onselect", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "onselectionchange", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "onselectstart", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "onslotchange", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "onstalled", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "onstorage", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "onsubmit", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "onsuspend", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "ontimeupdate", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "ontoggle", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "ontouchcancel", insecureContext: true, android: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "ontouchend", insecureContext: true, android: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "ontouchmove", insecureContext: true, android: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "ontouchstart", insecureContext: true, android: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "ontransitioncancel", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "ontransitionend", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "ontransitionrun", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "ontransitionstart", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "onunhandledrejection", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "onunload", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "onvolumechange", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "onwaiting", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "onwebkitanimationend", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "onwebkitanimationiteration", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "onwebkitanimationstart", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "onwebkittransitionend", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "onwheel", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "open", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "opener", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "orientation", insecureContext: true, android: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "origin", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "outerHeight", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "outerWidth", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "pageXOffset", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "pageYOffset", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "parent", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "performance", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "personalbar", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "postMessage", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "print", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "prompt", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "queueMicrotask", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "releaseEvents", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "reportError", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "requestAnimationFrame", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "requestIdleCallback", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "resizeBy", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "resizeTo", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "scheduler", insecureContext: true, nightly: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "screen", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "screenLeft", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "screenTop", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "screenX", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "screenY", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "scroll", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "scrollBy", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "scrollByLines", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "scrollByPages", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "scrollMaxX", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "scrollMaxY", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "scrollTo", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "scrollX", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "scrollY", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "scrollbars", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "self", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "sessionStorage", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "setInterval", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "setResizable", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "setTimeout", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "sizeToContent", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "speechSynthesis", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "status", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "statusbar", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "stop", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "structuredClone", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "toolbar", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "top", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "u2f", insecureContext: false, disabled: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "updateCommands", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "visualViewport", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "webkitURL", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "window", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! +]; +// IMPORTANT: Do not change the list above without review from a DOM peer! + +function entryDisabled(entry) { + return ( + entry.nightly === !isNightly || + (entry.nightlyAndroid === !(isAndroid && isNightly) && isAndroid) || + entry.desktop === !isDesktop || + entry.windows === !isWindows || + entry.mac === !isMac || + entry.linux === !isLinux || + (entry.android === !isAndroid && !entry.nightlyAndroid) || + entry.fennecOrDesktop === (isAndroid && !isFennec) || + entry.fennec === !isFennec || + entry.release === !isRelease || + entry.releaseNonWindows === !(isRelease && !isWindows) || + // The insecureContext test is very purposefully converting + // entry.insecureContext to boolean, so undefined will convert to + // false. That way entries without an insecureContext annotation + // will get treated as "insecureContext: false", which means exposed + // only in secure contexts. + (isInsecureContext && !entry.insecureContext) || + entry.earlyBetaOrEarlier === !isEarlyBetaOrEarlier || + entry.crossOriginIsolated === !isCrossOriginIsolated || + entry.disabled + ); +} + +function createInterfaceMap(...interfaceGroups) { + var interfaceMap = {}; + + function addInterfaces(interfaces) { + for (var entry of interfaces) { + if (typeof entry === "string") { + ok(!(entry in interfaceMap), "duplicate entry for " + entry); + interfaceMap[entry] = !isInsecureContext; + } else { + ok(!(entry.name in interfaceMap), "duplicate entry for " + entry.name); + ok(!("pref" in entry), "Bogus pref annotation for " + entry.name); + interfaceMap[entry.name] = !entryDisabled(entry); + } + } + } + + for (let interfaceGroup of interfaceGroups) { + addInterfaces(interfaceGroup); + } + + return interfaceMap; +} + +function runTest(parentName, parent, ...interfaceGroups) { + var interfaceMap = createInterfaceMap(...interfaceGroups); + for (var name of Object.getOwnPropertyNames(parent)) { + ok( + interfaceMap[name], + "If this is failing: DANGER, are you sure you want to expose the new interface " + + name + + " to all webpages as a property on '" + + parentName + + "'? Do not make a change to this file without a " + + " review from a DOM peer for that specific change!!! (or a JS peer for changes to ecmaGlobals)" + ); + + ok( + name in parent, + `${name} is exposed as an own property on '${parentName}' but tests false for "in" in the global scope` + ); + ok( + Object.getOwnPropertyDescriptor(parent, name), + `${name} is exposed as an own property on '${parentName}' but has no property descriptor in the global scope` + ); + + delete interfaceMap[name]; + } + for (var name of Object.keys(interfaceMap)) { + ok( + name in parent === interfaceMap[name], + name + + " should " + + (interfaceMap[name] ? "" : " NOT") + + " be defined on '" + + parentName + + "' scope" + ); + if (!interfaceMap[name]) { + delete interfaceMap[name]; + } + } + is( + Object.keys(interfaceMap).length, + 0, + "The following interface(s) are not enumerated: " + + Object.keys(interfaceMap).join(", ") + ); +} + +// Use an iframe because the test harness pollutes the global object with a lot +// of functions. +let iframeWindow = document.getElementById("testframe").contentWindow; +is( + window.isSecureContext, + iframeWindow.isSecureContext, + "iframe isSecureContext must match" +); +runTest("window", iframeWindow, ecmaGlobals, interfaceNamesInGlobalScope); + +if (window.WebAssembly && !entryDisabled(wasmGlobalEntry)) { + runTest("WebAssembly", window.WebAssembly, wasmGlobalInterfaces); +} diff --git a/dom/tests/mochitest/general/test_interfaces_secureContext.html b/dom/tests/mochitest/general/test_interfaces_secureContext.html new file mode 100644 index 0000000000..e133c6fdd4 --- /dev/null +++ b/dom/tests/mochitest/general/test_interfaces_secureContext.html @@ -0,0 +1,28 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=766694 +--> +<head> + <meta charset="utf-8"> + <title>Test for Bug 766694</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=766694">Mozilla Bug 766694</a> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<iframe id="testframe" src="about:blank"></iframe> +<pre id="test"> +<script> + ok(self.isSecureContext, "The test should be running in a secure context"); + ok(document.getElementById("testframe").contentWindow.isSecureContext, + "The test should be running in a secure context"); +</script> +<script type="text/javascript" src="test_interfaces.js"></script> +</pre> +</body> +</html> diff --git a/dom/tests/mochitest/general/test_media_queries_with_zoom.html b/dom/tests/mochitest/general/test_media_queries_with_zoom.html new file mode 100644 index 0000000000..c89d7e4e95 --- /dev/null +++ b/dom/tests/mochitest/general/test_media_queries_with_zoom.html @@ -0,0 +1,52 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Media Queries with Zoom</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"> +</head> + +<body> + +<div>Testing media queries with different zoom levels</div> + +<script type="application/javascript"> + +const originalDPR = window.devicePixelRatio; +const originalZoom = SpecialPowers.getFullZoom(window); + +const zoomsToTest = [ +300, +240, +200, +170, +150, +133, +120, +110, +100, +90, +80, +67, +50, +30, +]; + +for (let i = 0; i < zoomsToTest.length; ++i) { + let zoomPercent = zoomsToTest[i]; + + let relativeZoom = originalZoom * zoomPercent / 100; + SpecialPowers.setFullZoom(window, relativeZoom); + let actualZoom = SpecialPowers.getDeviceFullZoom(window); + let targetDPR = (originalDPR * actualZoom); + let actualDPR = window.devicePixelRatio; + let mql = window.matchMedia(`(resolution: ${targetDPR}dppx)`); + ok(mql.matches, `At ${zoomPercent}% zoom, target ${targetDPR}dppx matches ${actualDPR}dppx.`); +} + +// Reset the zoom when the test is done. +SpecialPowers.setFullZoom(window, originalZoom); +</script> + +</body> +</html> diff --git a/dom/tests/mochitest/general/test_navigation_timing.html b/dom/tests/mochitest/general/test_navigation_timing.html new file mode 100644 index 0000000000..c0aabd1647 --- /dev/null +++ b/dom/tests/mochitest/general/test_navigation_timing.html @@ -0,0 +1,36 @@ +<!-- + Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ +--> + +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=822480 +--> +<head> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> +<body> + +<pre id="test"> +<script type="application/javascript"> + +var subwindow = null; + +window.onload = function() { + SimpleTest.waitForExplicitFinish(); + subwindow = window.open("navigation_timing.html?x=0#_blank", "_blank"); +} + +function finishTests() { + subwindow.close(); + SimpleTest.finish(); +} + +</script> +</pre> + +</body> +</html> diff --git a/dom/tests/mochitest/general/test_network_events.html b/dom/tests/mochitest/general/test_network_events.html new file mode 100644 index 0000000000..5763cf1fff --- /dev/null +++ b/dom/tests/mochitest/general/test_network_events.html @@ -0,0 +1,72 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=795136 +--> +<head> + <meta charset="utf-8"> + <title>Test for moznetworkupload and moznetworkdownload</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=795136">Mozilla Bug 795136</a> +<p id="display"></p> +<div id="content"> + +</div> +<pre id="test"> +<script type="application/javascript"> + +/** Test for Bug 795136 **/ + +SimpleTest.waitForExplicitFinish(); + +SpecialPowers.addPermission("network-events", true, document); + +var gNetworkUpload = false; +var gNetworkDownload = false; + +function isFinished() { + return gNetworkUpload && gNetworkDownload; +} + +function finish() { + removeEventListener('moznetworkupload', uploadHandler); + removeEventListener('moznetworkdownload', downloadHandler); + + SpecialPowers.removePermission("network-events", document); + + SimpleTest.finish(); +} + +function uploadHandler() { + ok(true, 'got a network upload event'); + gNetworkUpload = true; + + if (isFinished()) { + finish(); + } +} + +function downloadHandler() { + ok(true, 'got a network download event'); + gNetworkDownload = true; + + if (isFinished()) { + finish(); + } +} + +addEventListener('moznetworkupload', uploadHandler); +addEventListener('moznetworkdownload', downloadHandler); + +var iframe = document.createElement('iframe'); +iframe.src = 'http://mozilla.org'; + +document.getElementById('content').appendChild(iframe); + +</script> +</pre> +</body> +</html> diff --git a/dom/tests/mochitest/general/test_nodeAdoption_chrome_boundary.xhtml b/dom/tests/mochitest/general/test_nodeAdoption_chrome_boundary.xhtml new file mode 100644 index 0000000000..eafe3cb714 --- /dev/null +++ b/dom/tests/mochitest/general/test_nodeAdoption_chrome_boundary.xhtml @@ -0,0 +1,28 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?> + +<window title="Cross chrome and content node adoption test" + onload="setTimeout(runTest, 0);" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/> + <browser xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" id="content" type="content" src="about:blank"/> + +<script> + +SimpleTest.waitForExplicitFinish(); +function runTest() +{ + let browserElement = document.getElementById("content"); + try { + document.adoptNode(browserElement.contentDocument.documentElement); + SimpleTest.ok(false, "Cross chrome and content node adoption should fail"); + } catch (SecurityError) { + SimpleTest.ok(true, "Cross chrome and content node adoption fails as expected"); + } + SimpleTest.finish(); +} +</script> +</window> + diff --git a/dom/tests/mochitest/general/test_offsets.css b/dom/tests/mochitest/general/test_offsets.css new file mode 100644 index 0000000000..18231c4d9b --- /dev/null +++ b/dom/tests/mochitest/general/test_offsets.css @@ -0,0 +1,3 @@ + button, vbox, menu, menuitem, menupopup { + box-sizing: content-box; + } diff --git a/dom/tests/mochitest/general/test_offsets.html b/dom/tests/mochitest/general/test_offsets.html new file mode 100644 index 0000000000..4bad588913 --- /dev/null +++ b/dom/tests/mochitest/general/test_offsets.html @@ -0,0 +1,100 @@ +<!DOCTYPE HTML> +<html style="margin: 5px; border: 0; padding: 1px;"> +<head> + <title>HTML Tests for offset/client/scroll properties</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="test_offsets.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"> + +<style> + input { + box-sizing: content-box; + } +</style> +</head> + +<!-- We set a transform on the body element so that it creates a reference frame. + This makes sure that snapping of scrolled areas for the contained elements + is not influenced by offsets outside of this document. --> +<body id="body" onload="setTimeout(testElements, 0, 'testelements', SimpleTest.finish);" + style="margin: 1px; border: 2px solid black; padding: 4px; transform: translateY(1px);"> + +<div id="testelements" style="margin: 0; border: 0; padding: 0;"> + <div id="div1" style="margin: 0; margin-left: 6px; margin-top: 2px; border: 1px solid green; padding: 6px; width: 50px; height: 20px" + _offsetLeft="13" _offsetTop="9" _offsetWidth="64" _offsetHeight="34" + _scrollWidth="62" _scrollHeight="32" + _clientLeft="1" _clientTop="1" _clientWidth="62" _clientHeight="32"></div> + <div id="noscroll" style="margin: 2px; border: 1px solid blue; padding: 3px;" + _offsetLeft="10" _offsetTop="12" _offsetWidth="64" _offsetHeight="34" + _scrollWidth="62" _scrollHeight="32" + _clientLeft="1" _clientTop="1" _clientWidth="62" _clientHeight="32"> + <div id="inner">Inner Text</div> + </div> + + <div id="absolute" style="position: absolute; margin: 5px; border: 2px solid blue; padding: 0;"> + <div id="absolute-block" _offsetParent="absolute"> + <div id="absolute-replaced" _offsetParent="absolute" style="margin: 1px; border: 0; padding: 3px;"></div> + </div> + </div> + + <div id="absolutelr" style="position: absolute; margin: 5px; border: 2px solid blue; padding: 0; left: 90px; top: 130px;"> + This is some absolute positioned text. + <div id="absolutelr-block" _offsetParent="absolutelr"> + <div id="absolutelr-replaced" _offsetParent="absolutelr" style="margin: 1px; border: 0; padding: 3px;"></div> + </div> + </div> + + <div id="relative" style="position: relative; margin: 2px; border: 1px solid orange; padding: 7px; left: 10px; top: 5px;"> + This is some relative positioned text. + <div id="relative-block" _offsetParent="relative"> + <div id="relative-replaced" _offsetParent="relative" style="margin: 1px; border: 0; padding: 3px;"></div> + </div> + </div> + + <div id="fixed" style="position: fixed; margin: 2px; border: 1px solid orange; padding: 7px; left: 87px; top: 12px;"> + This is some fixed positioned text. + <div id="fixed-block" _offsetParent="fixed"> + <div id="fixed-replaced" _offsetParent="fixed" style="margin: 1px; border: 0; padding: 3px;"></div> + </div> + </div> + + <div id="scrollbox" + style="overflow: scroll; padding-left: 0px; margin: 3px; border: 4px solid green; max-width: 80px; max-height: 70px" + _scrollWidth="62" _scrollHeight="32" + _clientLeft="1" _clientTop="1" _clientWidth="62" _clientHeight="32"><p id="p1" style="margin: 0; padding: 0;">One</p> + <p id="p2">Two</p> + <p id="scrollchild">Three</p> + <p id="lastlinebox" style="margin: 0; padding: 0;"><input id="lastline" type="button" + style="margin: 0px; border: 2px solid red;" + value="This button is much longer than the others"> + </p></div> + + <div id="overflow-visible" style="width:100px; height:100px;"> + <div id="overflow-visible-1" style="width:200px; height:1px; background:yellow;"></div> + <div id="overflow-visible-2" style="height:200px; background:lime;"></div> + </div> + + <div id="div-displaynone" style="display: none; border: 0; padding: 0;" + _offsetParent="null"></div> + <p id="p3" style="margin: 2px; border: 0; padding: 1px;" + _offsetLeft="9" _offsetTop="9" _offsetWidth="64" _offsetHeight="34" + _scrollWidth="62" _scrollHeight="32" + _clientLeft="1" _clientTop="1" _clientWidth="62" _clientHeight="32"> + <div id="div-nosize" style="width: 0; height: 0; margin: 0; border: 0; padding: 0;"></div> + </p> + +</div> + +<div id="scrollbox-test" style="float: left; overflow: scroll; margin: 0; border: 0; padding: 0"></div> + +<script type="application/javascript"> +SimpleTest.waitForExplicitFinish(); +</script> + +<p id="display"></p> +<div id="content" style="display: none"> + +</div> + +</body> +</html> diff --git a/dom/tests/mochitest/general/test_offsets.js b/dom/tests/mochitest/general/test_offsets.js new file mode 100644 index 0000000000..d89b575876 --- /dev/null +++ b/dom/tests/mochitest/general/test_offsets.js @@ -0,0 +1,332 @@ +var scrollbarWidth = 17, + scrollbarHeight = 17; + +function testElements(baseid, callback) { + scrollbarWidth = scrollbarHeight = gcs($("scrollbox-test"), "width"); + + var elements = $(baseid).getElementsByTagName("*"); + for (var t = 0; t < elements.length; t++) { + var element = elements[t]; + + // Ignore presentational content inside menus + if ( + element.closest("menu, menuitem") && + element.closest("[aria-hidden=true]") + ) { + continue; + } + + // Ignore content inside a <button> This can be removed if/when + // button switches to use shadow DOM. + let buttonParent = element.closest("button"); + if (buttonParent && buttonParent !== element) { + continue; + } + + testElement(element); + } + + var nonappended = document.createElementNS( + "http://www.w3.org/1999/xhtml", + "div" + ); + nonappended.id = "nonappended"; + nonappended.setAttribute("_offsetParent", "null"); + testElement(nonappended); + + checkScrolledElement($("scrollbox"), $("scrollchild")); + + var div = $("noscroll"); + div.scrollLeft = 10; + div.scrollTop = 10; + is(element.scrollLeft, 0, element.id + " scrollLeft after nonscroll"); + is(element.scrollTop, 0, element.id + " scrollTop after nonscroll"); + + callback(); +} + +function usesSVGLayout(e) { + return e instanceof SVGElement && !(e instanceof SVGSVGElement); +} + +function toNearestAppunit(v) { + // 60 appunits per CSS pixel; round result to the nearest appunit + return Math.round(v * 60) / 60; +} + +function isEqualAppunits(a, b, msg) { + is(toNearestAppunit(a), toNearestAppunit(b), msg); +} + +function testElement(element) { + var offsetParent = element.getAttribute("_offsetParent"); + offsetParent = $( + offsetParent == "null" ? null : offsetParent ? offsetParent : "body" + ); + + var borderLeft = gcs(element, "borderLeftWidth"); + var borderTop = gcs(element, "borderTopWidth"); + var borderRight = gcs(element, "borderRightWidth"); + var borderBottom = gcs(element, "borderBottomWidth"); + var paddingLeft = gcs(element, "paddingLeft"); + var paddingTop = gcs(element, "paddingTop"); + var paddingRight = gcs(element, "paddingRight"); + var paddingBottom = gcs(element, "paddingBottom"); + var width = gcs(element, "width"); + var height = gcs(element, "height"); + + if (element instanceof HTMLElement) { + checkOffsetState( + element, + -10000, + -10000, + borderLeft + paddingLeft + width + paddingRight + borderRight, + borderTop + paddingTop + height + paddingBottom + borderBottom, + offsetParent, + element.id + ); + } + + var scrollWidth, scrollHeight, clientWidth, clientHeight; + var doScrollCheck = true; + { + if (element.id == "scrollbox") { + clientWidth = paddingLeft + width + paddingRight - scrollbarWidth; + clientHeight = paddingTop + height + paddingBottom - scrollbarHeight; + } else { + clientWidth = paddingLeft + width + paddingRight; + clientHeight = paddingTop + height + paddingBottom; + } + if (element.id == "overflow-visible") { + scrollWidth = 200; + scrollHeight = 201; + } else if ( + element.scrollWidth > clientWidth || + element.scrollHeight > clientHeight + ) { + // The element overflows. Don't check scrollWidth/scrollHeight since the + // above calculation is not correct. + doScrollCheck = false; + } else { + scrollWidth = clientWidth; + scrollHeight = clientHeight; + } + } + + if (doScrollCheck) { + if (usesSVGLayout(element)) { + checkScrollState(element, 0, 0, 0, 0, element.id); + } else { + checkScrollState(element, 0, 0, scrollWidth, scrollHeight, element.id); + } + } + + if (usesSVGLayout(element)) { + checkClientState(element, 0, 0, 0, 0, element.id); + } else { + checkClientState( + element, + borderLeft, + borderTop, + clientWidth, + clientHeight, + element.id + ); + } + + var boundingrect = element.getBoundingClientRect(); + isEqualAppunits( + boundingrect.width, + borderLeft + paddingLeft + width + paddingRight + borderRight, + element.id + " bounding rect width" + ); + isEqualAppunits( + boundingrect.height, + borderTop + paddingTop + height + paddingBottom + borderBottom, + element.id + " bounding rect height" + ); + isEqualAppunits( + boundingrect.right - boundingrect.left, + boundingrect.width, + element.id + " bounding rect right" + ); + isEqualAppunits( + boundingrect.bottom - boundingrect.top, + boundingrect.height, + element.id + " bounding rect bottom" + ); + + var rects = element.getClientRects(); + if (element.id == "div-displaynone" || element.id == "nonappended") { + is(rects.length, 0, element.id + " getClientRects empty"); + } else { + is(rects[0].left, boundingrect.left, element.id + " getClientRects left"); + is(rects[0].top, boundingrect.top, element.id + " getClientRects top"); + is( + rects[0].right, + boundingrect.right, + element.id + " getClientRects right" + ); + is( + rects[0].bottom, + boundingrect.bottom, + element.id + " getClientRects bottom" + ); + } +} + +function checkScrolledElement(element, child) { + var elemrect = element.getBoundingClientRect(); + var childrect = child.getBoundingClientRect(); + + var topdiff = childrect.top - elemrect.top; + + element.scrollTop = 20; + is(element.scrollLeft, 0, element.id + " scrollLeft after vertical scroll"); + is(element.scrollTop, 20, element.id + " scrollTop after vertical scroll"); + // If the viewport has been transformed, then we might have scrolled to a subpixel value + // that's slightly different from what we requested. After rounding, however, it should + // be the same. + is( + Math.round(childrect.top - child.getBoundingClientRect().top), + 20, + "child position after vertical scroll" + ); + + element.scrollTop = 0; + is( + element.scrollLeft, + 0, + element.id + " scrollLeft after vertical scroll reset" + ); + is( + element.scrollTop, + 0, + element.id + " scrollTop after vertical scroll reset" + ); + // Scrolling back to the top should work precisely. + is( + child.getBoundingClientRect().top, + childrect.top, + "child position after vertical scroll reset" + ); + + element.scrollTop = 10; + element.scrollTop = -30; + is( + element.scrollLeft, + 0, + element.id + " scrollLeft after vertical scroll negative" + ); + is( + element.scrollTop, + 0, + element.id + " scrollTop after vertical scroll negative" + ); + is( + child.getBoundingClientRect().top, + childrect.top, + "child position after vertical scroll negative" + ); + + element.scrollLeft = 18; + is( + element.scrollLeft, + 18, + element.id + " scrollLeft after horizontal scroll" + ); + is(element.scrollTop, 0, element.id + " scrollTop after horizontal scroll"); + is( + Math.round(childrect.left - child.getBoundingClientRect().left), + 18, + "child position after horizontal scroll" + ); + + element.scrollLeft = -30; + is( + element.scrollLeft, + 0, + element.id + " scrollLeft after horizontal scroll reset" + ); + is( + element.scrollTop, + 0, + element.id + " scrollTop after horizontal scroll reset" + ); + is( + child.getBoundingClientRect().left, + childrect.left, + "child position after horizontal scroll reset" + ); +} + +function checkOffsetState(element, left, top, width, height, parent, testname) { + checkCoords(element, "offset", left, top, width, height, testname); + is(element.offsetParent, parent, testname + " offsetParent"); +} + +function checkScrollState(element, left, top, width, height, testname) { + checkCoords(element, "scroll", left, top, width, height, testname); +} + +function checkClientState(element, left, top, width, height, testname) { + checkCoords(element, "client", left, top, width, height, testname); +} + +function checkCoord(element, type, val, testname) { + if (val != -10000) { + is(element[type], Math.round(val), testname + " " + type); + } +} + +function checkCoordFuzzy(element, type, val, fuzz, testname) { + if (val != -10000) { + let v = element[type]; + ok( + Math.abs(v - Math.round(val)) <= fuzz, + `${testname} ${type}: ${v} vs. ${val}` + ); + } +} + +function checkCoords(element, type, left, top, width, height, testname) { + checkCoord(element, type + "Left", left, testname); + checkCoord(element, type + "Top", top, testname); + + if (type == "scroll") { + // scrollWidth and scrollHeight can deviate by 1 pixel due to snapping. + checkCoordFuzzy(element, type + "Width", width, 1, testname); + checkCoordFuzzy(element, type + "Height", height, 1, testname); + } else { + checkCoord(element, type + "Width", width, testname); + checkCoord(element, type + "Height", height, testname); + } + + if (usesSVGLayout(element)) { + return; + } + + if (element.id == "outerpopup" && !element.parentNode.open) { + // closed popup + return; + } + + if (element.id == "div-displaynone" || element.id == "nonappended") { + // hidden elements + ok( + element[type + "Width"] == 0 && element[type + "Height"] == 0, + element.id + " has zero " + type + " width and height" + ); + } +} + +function gcs(element, prop) { + var propVal = + usesSVGLayout(element) && (prop == "width" || prop == "height") + ? element.getAttribute(prop) + : getComputedStyle(element, "")[prop]; + if (propVal == "auto" || element.id == "nonappended") { + return 0; + } + return parseFloat(propVal); +} diff --git a/dom/tests/mochitest/general/test_offsets.xhtml b/dom/tests/mochitest/general/test_offsets.xhtml new file mode 100644 index 0000000000..ec789c8db6 --- /dev/null +++ b/dom/tests/mochitest/general/test_offsets.xhtml @@ -0,0 +1,98 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?> +<?xml-stylesheet href="test_offsets.css" type="text/css"?> +<!-- + XUL Tests for client/scroll properties + --> +<window title="Test Offset/Client/Scroll Properties" width="500" height="600" + style="margin: 1px !important" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/> + <script type="text/javascript" src="test_offsets.js"/> + +<vbox id="testelements" style="margin: 0; padding: 0; border: 0;"> +<vbox id="vbox" style="margin: 5px 0 0 2px;"> + <vbox id="noscroll" align="start"> + <button id="button1" label="Button One" style="margin: 0px; padding: 0; border: 0;"/> + <button id="button2" label="Button Two" style="width: 140px; height: 120px;"/> + </vbox> + <hbox align="start"> + <vbox id="scrollbox" style="overflow: scroll; padding: 2px; margin: 3px; border: 4px solid green; max-width: 66px; max-height: 56px"> + <label value="One" style="margin: 0"/> + <label id="scrollchild" value="Two"/> + <label value="Three"/> + <label id="lastline" value="This fourth label is much longer than the others" + style="margin: 0; padding: 0; border: 0;"/> + </vbox> + <vbox id="scrollbox-test"> + <scrollbar orient="vertical" style="border: 0; padding: 0;"/> + </vbox> + </hbox> +</vbox> + +<!-- wrap svg in a div so that it can take its intrinsic width --> +<div> +<svg:svg id="svgbase" width="45" height="20" xmlns:svg="http://www.w3.org/2000/svg"> + <svg:rect id="svgrect" x="3" y="5" width="45" height="20" fill="red"/> +</svg:svg> +</div> + +</vbox> + +<button id="outermenu" type="menu" label="Menu"> + <menupopup id="outerpopup" + style="margin-left: 5px; padding-left: 3px; padding: 0;" + onpopupshown="this.firstChild.open = true" + onpopuphidden="if (event.target == this) SimpleTest.finish();"> + <menu id="innermenu" label="Open" + style="margin: 0; padding: 0; border: 2px black solid; -moz-appearance: none;"> + <menupopup style="margin: 0; padding: 0; border: 1px black solid; -moz-appearance: none;" + onpopupshown="testElements('outermenu', doneTests)"> + <menuitem label="Document"/> + <menuitem id="innermenuitem" style="margin: 2px; padding: 3px;" label="Page"/> + </menupopup> + </menu> + <menuitem id="outermenuitem" label="Close"/> + </menupopup> +</button> + + <!-- test results are displayed in the html:body --> + <body xmlns="http://www.w3.org/1999/xhtml" style="height: 300px; overflow: auto;"/> + + <!-- test code goes here --> + <script type="application/javascript"><![CDATA[ + +var gTestSet = "box"; + +var whichpopup = "outer"; + +SimpleTest.waitForExplicitFinish(); + +function startTests() +{ + testElements('testelements', doneTests); +} + +function doneTests() +{ + if (gTestSet == "box") { + gTestSet = "popup"; + // only test this on Mac for now + if (navigator.platform.includes("Mac")) { + checkScrollState($("outerpopup"), 0, 0, 0, 0, "popup before open"); + checkClientState($("outerpopup"), 0, 0, 0, 0, "popup before open"); + } + $("outermenu").open = true; + } + else { + $("outermenu").open = false; + } +} + +SimpleTest.waitForFocus(startTests); + +]]> +</script> + +</window> diff --git a/dom/tests/mochitest/general/test_outerHTML.html b/dom/tests/mochitest/general/test_outerHTML.html new file mode 100644 index 0000000000..b4ebf4e866 --- /dev/null +++ b/dom/tests/mochitest/general/test_outerHTML.html @@ -0,0 +1,74 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=92264 +--> +<head> + <title>Test for Bug 92264</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> +<body onload="runTest();"> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=92264">Mozilla Bug 92264</a> +<p id="display"></p> +<div id="content" style="display: none"> +<div id="wrap"><dl></dl><p id="thep">foo<span>bar</span></p><ol></ol></div> +<table id="thetable"><tbody><tr><td>1</td></tr><tr id="thetr"><td>2</td></tr><tr><td>3</td></tr></tbody></table> +<iframe></iframe> +<div id="fragmentwrap"></div> +</div> +<pre id="test"> +<script type="application/javascript"> + +/** Test for Bug 92264 **/ + +SimpleTest.waitForExplicitFinish(); + +function runTest() { + + var thep = document.getElementById("thep"); + var wrap = document.getElementById("wrap"); + is(thep.outerHTML, '<p id="thep">foo<span>bar</span></p>', "Unexpected thep outerHTML"); + thep.outerHTML = "<ul></ul><tr></tr><p></p>"; + is(wrap.innerHTML, "<dl></dl><ul></ul><p></p><ol></ol>", "Bad outerHTML parsing inside wrap"); + + var thetr = document.getElementById("thetr"); + thetr.outerHTML = "<tr><td>a</td></tr><div></div><tr><td>b</td></tr>"; + var thetable = document.getElementById("thetable"); + is(thetable.innerHTML, "<tbody><tr><td>1</td></tr><tr><td>a</td></tr><div></div><tr><td>b</td></tr><tr><td>3</td></tr></tbody>", "Wrong outerHTML parsing inside table"); + + var iframe = document.getElementsByTagName("iframe")[0]; + var oldbody = iframe.contentDocument.body; + iframe.contentDocument.body.outerHTML = "<body></body>"; + isnot(oldbody, iframe.contentDocument.body, "Failed to replace body"); + is(iframe.contentDocument.getElementsByTagName("body").length, 1, "Should have gotten one body"); + // Yes, two heads per spec. Also Ragnarök and Chrome produce two heads. + is(iframe.contentDocument.getElementsByTagName("head").length, 2, "Should have gotten two heads"); + + try { + document.documentElement.outerHTML = "<html></html>"; + ok(false, "Should have thrown an exception"); + } catch(e) { + is(e.name, "NoModificationAllowedError", "outerHTML should throw NoModificationAllowedError"); + is(e.code, 7, "outerHTML should throw NO_MODIFICATION_ALLOWED_ERR"); + } + + var f = document.createDocumentFragment(); + var dl = document.createElement("dl"); + var p = document.createElement("p"); + var ol = document.createElement("ol"); + f.appendChild(dl); + f.appendChild(p); + f.appendChild(ol); + p.outerHTML = "<ul></ul><tr></tr><body></body><p></p>"; + var fragmentwrap = document.getElementById("fragmentwrap"); + fragmentwrap.appendChild(f); + is(fragmentwrap.innerHTML, "<dl></dl><ul></ul><p></p><ol></ol>", "Bad outerHTML parsing in fragment"); + + SimpleTest.finish(); +} + +</script> +</pre> +</body> +</html> diff --git a/dom/tests/mochitest/general/test_outerHTML.xhtml b/dom/tests/mochitest/general/test_outerHTML.xhtml new file mode 100644 index 0000000000..45d7a629b1 --- /dev/null +++ b/dom/tests/mochitest/general/test_outerHTML.xhtml @@ -0,0 +1,75 @@ +<!DOCTYPE HTML> +<html xmlns="http://www.w3.org/1999/xhtml"> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=92264 +--> +<head> + <title>Test for Bug 92264</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> +<body onload="runTest();"> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=92264">Mozilla Bug 92264</a> +<p id="display"></p> +<div id="content" style="display: none"> +<div id="wrap"><dl></dl><p id="thep">foo<span>bar</span></p><ol></ol></div> +<table id="thetable"><tbody><tr><td>1</td></tr><tr id="thetr"><td>2</td></tr><tr><td>3</td></tr></tbody></table> +<iframe></iframe> +<div id="fragmentwrap"></div> +</div> +<pre id="test"> +<script type="application/javascript"> +<![CDATA[ + +/** Test for Bug 92264 **/ + +SimpleTest.waitForExplicitFinish(); + +function runTest() { + + var thep = document.getElementById("thep"); + var wrap = document.getElementById("wrap"); + is(thep.outerHTML, '<p xmlns="http://www.w3.org/1999/xhtml" id="thep">foo<span>bar</span></p>', "Unexpected thep outerHTML"); + thep.outerHTML = "<ul></ul><tr></tr><p></p>"; + is(wrap.innerHTML, '<dl xmlns="http://www.w3.org/1999/xhtml"></dl><ul xmlns="http://www.w3.org/1999/xhtml"></ul><tr xmlns="http://www.w3.org/1999/xhtml"></tr><p xmlns="http://www.w3.org/1999/xhtml"></p><ol xmlns="http://www.w3.org/1999/xhtml"></ol>', "Bad outerHTML parsing inside wrap"); + + var thetr = document.getElementById("thetr"); + thetr.outerHTML = "<tr><td>a</td></tr><div></div><tr><td>b</td></tr>"; + var thetable = document.getElementById("thetable"); + is(thetable.innerHTML, '<tbody xmlns="http://www.w3.org/1999/xhtml"><tr><td>1</td></tr><tr><td>a</td></tr><div></div><tr><td>b</td></tr><tr><td>3</td></tr></tbody>', "Wrong outerHTML parsing inside table"); + + var iframe = document.getElementsByTagName("iframe")[0]; + var oldbody = iframe.contentDocument.body; + iframe.contentDocument.body.outerHTML = "<body></body>"; + isnot(oldbody, iframe.contentDocument.body, "Failed to replace body"); + is(iframe.contentDocument.getElementsByTagName("body").length, 1, "Should have gotten one body"); + // Yes, two heads per spec. Also Ragnarök and Chrome produce two heads. + is(iframe.contentDocument.getElementsByTagName("head").length, 2, "Should have gotten two heads"); + + try { + document.documentElement.outerHTML = "<html></html>"; + ok(false, "Should have thrown an exception"); + } catch(e) { + is(e.name, "NoModificationAllowedError", "outerHTML should throw NoModificationAllowedError"); + is(e.code, 7, "outerHTML should throw NO_MODIFICATION_ALLOWED_ERR"); + } + + var f = document.createDocumentFragment(); + var dl = document.createElement("dl"); + var p = document.createElement("p"); + var ol = document.createElement("ol"); + f.appendChild(dl); + f.appendChild(p); + f.appendChild(ol); + p.outerHTML = "<ul></ul><tr></tr><body></body><p></p>"; + var fragmentwrap = document.getElementById("fragmentwrap"); + fragmentwrap.appendChild(f); + is(fragmentwrap.innerHTML, '<dl xmlns="http://www.w3.org/1999/xhtml"></dl><ul xmlns="http://www.w3.org/1999/xhtml"></ul><tr xmlns="http://www.w3.org/1999/xhtml"></tr><body xmlns="http://www.w3.org/1999/xhtml"></body><p xmlns="http://www.w3.org/1999/xhtml"></p><ol xmlns="http://www.w3.org/1999/xhtml"></ol>', "Bad outerHTML parsing in fragment"); + + SimpleTest.finish(); +} +]]> +</script> +</pre> +</body> +</html> diff --git a/dom/tests/mochitest/general/test_paste_selection.html b/dom/tests/mochitest/general/test_paste_selection.html new file mode 100644 index 0000000000..b39915d165 --- /dev/null +++ b/dom/tests/mochitest/general/test_paste_selection.html @@ -0,0 +1,122 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test for middle-click to paste selection with paste events</title> + <script type="text/javascript" src="/MochiKit/MochiKit.js"></script> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <script src="/tests/SimpleTest/EventUtils.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<p id="display"></p> +<input id="copy-area" value="CLIPBOARD"> +<input id="paste-selection-area" value="" onpaste="pastedSelection(event)"> +<input id="paste-global-area" value="" onpaste="pastedGlobal(event)"> + +<script> + +var supportsSelectionClipboard = SpecialPowers.supportsSelectionClipboard(); + +function checkSelectionClipboardText(count) +{ + if ((!supportsSelectionClipboard || + SpecialPowers.getClipboardData("text/plain", SpecialPowers.Ci.nsIClipboard.kSelectionClipboard) == "COPY TEXT") && + SpecialPowers.getClipboardData("text/plain", SpecialPowers.Ci.nsIClipboard.kGlobalClipboard) == "CLIPBOARD") { + pasteArea(); + return; + } + + if (count > 10) { + ok(false, "could not set clipboards"); + pasteArea(); + SimpleTest.finish(); + return; + } + + setTimeout(checkSelectionClipboardText, 5, count + 1); +} + +function selectArea() +{ + var copyArea = document.getElementById("copy-area"); + copyArea.focus(); + copyArea.select(); + synthesizeKey("x", { accelKey: true }); + + if (supportsSelectionClipboard) { + var clipboardHelper = SpecialPowers.Cc["@mozilla.org/widget/clipboardhelper;1"] + .getService(SpecialPowers.Ci.nsIClipboardHelper); + clipboardHelper.copyStringToClipboard("COPY TEXT", + SpecialPowers.Ci.nsIClipboard.kSelectionClipboard); + } + + setTimeout(checkSelectionClipboardText, 50, 0); +} + +var selectionPasted = false; +var globalPasted = false; + +function pasteArea() +{ + var pasteArea = document.getElementById("paste-selection-area"); + var pasteAreaRect = pasteArea.getBoundingClientRect(); + var pasteAreaCenterX = pasteAreaRect.left + pasteAreaRect.width/2; + var pasteAreaCenterY = pasteAreaRect.top + pasteAreaRect.height/2; + var util = SpecialPowers.getDOMWindowUtils(window); + + pasteArea.focus(); + util.sendMouseEventToWindow("mousedown", pasteAreaCenterX, pasteAreaCenterY, 1, 1, 0, true); + util.sendMouseEventToWindow("mouseup", pasteAreaCenterX, pasteAreaCenterY, 1, 1, 0, true); + + var usesMouseButtonPaste = SpecialPowers.getBoolPref("middlemouse.paste"); + if (usesMouseButtonPaste) { + // The data transfer should contain the selection data when the selection clipboard is supported, + // not the global clipboard data. + var expectedText = supportsSelectionClipboard ? "COPY TEXT" : "CLIPBOARD"; + is(document.getElementById("paste-selection-area").value, expectedText, "In pasteArea(): data pasted properly from selection"); + ok(selectionPasted, "selection event fired"); + } + else { + is(pasteArea.value, "", "data not pasted when middle click not supported"); + } + + var pasteArea = document.getElementById("paste-global-area"); + pasteArea.focus(); + synthesizeKey("v", { accelKey: true }); + + ok(globalPasted, "global event fired"); + is(document.getElementById("paste-global-area").value, "CLIPBOARD", "data pasted properly from global clipboard"); + SimpleTest.finish(); +} + +function pastedSelection(event) +{ + ok(SpecialPowers.getBoolPref("middlemouse.paste"), "paste on middle click is valid"); + + // Mac and Windows shouldn't get here as the middle mouse preference is false by default + ok(!navigator.platform.includes("Mac") && !navigator.platform.includes("Win"), "middle click enabled on right platforms"); + + var expectedText = supportsSelectionClipboard ? "COPY TEXT" : "CLIPBOARD"; + is(event.clipboardData.getData("text/plain"), expectedText, "In pastedSelection(): data pasted properly from selection"); + + selectionPasted = true; +} + +function pastedGlobal(event) +{ + // The data transfer should contain the global data. + is(event.clipboardData.getData("text/plain"), "CLIPBOARD", "data correct in global clipboard data transfer"); + globalPasted = true; +} + +SimpleTest.waitForExplicitFinish(); +SimpleTest.requestFlakyTimeout("untriaged"); +SimpleTest.waitForFocus(function() { + SpecialPowers.pushPrefEnv({"set": [["general.autoScroll", false]]}, selectArea); +}); +</script> + +</pre> +</body> +</html> + diff --git a/dom/tests/mochitest/general/test_performance_nav_timing_before_onload.html b/dom/tests/mochitest/general/test_performance_nav_timing_before_onload.html new file mode 100644 index 0000000000..af85a6edab --- /dev/null +++ b/dom/tests/mochitest/general/test_performance_nav_timing_before_onload.html @@ -0,0 +1,30 @@ +<!-- + Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ +--> + +<!DOCTYPE HTML> +<html> +<head> + <meta charset="utf-8"> + <title>Test for Bug 1405961 - Using PerformanceNavigationTiming before onload</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> +<body> + <div id="content"> </div> + <script> + SimpleTest.waitForExplicitFinish(); + + SpecialPowers.pushPrefEnv({"set": [["dom.enable_performance_navigation_timing", true]]}, start); + + function start() { + var p = performance.getEntriesByName(window.location.href)[0]; + ok(!!p, "There should be an entry for the document"); + document.getElementById("content").textContent += JSON.stringify(p); + + SimpleTest.finish(); + } + </script> +</body> +</html> diff --git a/dom/tests/mochitest/general/test_performance_now.html b/dom/tests/mochitest/general/test_performance_now.html new file mode 100644 index 0000000000..23f5f45969 --- /dev/null +++ b/dom/tests/mochitest/general/test_performance_now.html @@ -0,0 +1,60 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test for High Resolution Timer</title> + <script type="text/javascript" src="/MochiKit/MochiKit.js"></script> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> + <script> + ok(window.performance, "Performance object should exist."); + ok(typeof window.performance.now == 'function', "Performance object should have a 'now' method."); + var n = window.performance.now(), d = Date.now(); + ok(n >= 0, "The value of now() should be equal to or greater than 0."); + ok(window.performance.now() >= n, "The value of now() should monotonically increase."); + + SimpleTest.waitForExplicitFinish(); + SimpleTest.requestFlakyTimeout("using setTimeout() to measure performance.now()"); + + // Spin on setTimeout() until performance.now() increases. Due to recent + // security developments, the hr-time working group has not yet reached + // consensus on what the recommend minimum clock resolution should be: + // https://w3c.github.io/hr-time/#clock-resolution + // Since setTimeout might return too early/late, our goal is for + // performance.now() to increase before a 2 ms deadline rather than specific + // number of setTimeout(N) invocations. + // See bug 749894 (intermittent failures of this test) + var checks = 0; + + function checkAfterTimeout() { + checks++; + var d2 = Date.now(); + var n2 = window.performance.now(); + + // Spin on setTimeout() until performance.now() increases. Abort the + // test if it runs for more than 2 ms or 50 timeouts. + let elapsedTime = d2 - d; + let elapsedPerf = n2 - n; + if (elapsedPerf == 0 && elapsedTime < 2 && checks < 50) { + setTimeout(checkAfterTimeout, 1); + return; + } + + // Our implementation provides 1 ms resolution (bug 1451790). + ok(elapsedPerf >= 1, + `Loose - the value of now() should increase by no less than 1 ms ` + + `after 2 ms. delta now(): ${elapsedPerf} ms`); + + // If we need more than 1 iteration, then either performance.now() + // resolution is shorter than 1 ms or setTimeout() is returning too early. + ok(checks == 1, + `Strict - the value of now() should increase after one setTimeout. ` + + `iters: ${checks}, dt: ${elapsedTime}, now(): ${n2}`); + + SimpleTest.finish(); + }; + setTimeout(checkAfterTimeout, 1); + </script> +</body> +</html> diff --git a/dom/tests/mochitest/general/test_performance_timeline.html b/dom/tests/mochitest/general/test_performance_timeline.html new file mode 100644 index 0000000000..e0ec357ab7 --- /dev/null +++ b/dom/tests/mochitest/general/test_performance_timeline.html @@ -0,0 +1,35 @@ +<!-- + Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ +--> + +<!DOCTYPE HTML> +<html> +<head> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> +<body> + +<pre id="test"> +<script type="application/javascript"> + +SimpleTest.waitForExplicitFinish(); + +// Resource timing is prefed off by default, so we had to use this workaround +SpecialPowers.pushPrefEnv({"set": [["dom.enable_resource_timing", true]]}, start); +var subwindow = null; + +function start() { + subwindow = window.open("performance_timeline_main_test.html"); +} + +function finishTests() { + subwindow.close(); + SimpleTest.finish(); +} +</script> +</pre> + +</body> +</html> diff --git a/dom/tests/mochitest/general/test_picture_apng.html b/dom/tests/mochitest/general/test_picture_apng.html new file mode 100644 index 0000000000..8fc37c04c2 --- /dev/null +++ b/dom/tests/mochitest/general/test_picture_apng.html @@ -0,0 +1,77 @@ +<!DOCTYPE HTML> +<html> + +<head> + <meta charset="utf-8"> + <title>Image srcset mutations</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<script type="application/javascript"> +"use strict"; +window.onload = function() { + // Smoke test, check picture working as expected + const t0 = document.querySelector("#test0 img"); + ok(t0.currentSrc.endsWith("apng"), `t0: expected pass.apng, got '${t0.currentSrc}'`); + + // Test that the apng is selected over bogus types. + const t1 = document.querySelector("#test1 img"); + ok(t1.currentSrc.endsWith("apng"), `t1: expected pass.apng, got '${t1.currentSrc}'`); + + // Test that tree order precedence applies + const t2 = document.querySelector("#test2 img"); + ok(t2.currentSrc.endsWith("apng"), `t2: expected pass.apng, got '${t2.currentSrc}'`); + + // Test that apng doesn't alway win + const t3 = document.querySelector("#test3 img"); + ok(t3.currentSrc.endsWith("apng"), `t3: expected pass.apng, got '${t3.currentSrc}'`); + + // Test dynamically constructed picture, where apng is selected over a bogus + // source or the img src attribute + const pic = document.createElement("picture"); + pic.id = "test4"; + const t4 = document.createElement("img"); + const bogusSource = document.createElement("source"); + bogusSource.type = "bogus/bogus"; + bogusSource.srcset = "fail.png"; + const legitSource = document.createElement("source"); + legitSource.type = "image/apng"; + legitSource.srcset = "pass.apng"; + pic.appendChild(bogusSource); + pic.appendChild(legitSource); + pic.appendChild(t4); + t4.src = "fail.png"; + document.body.appendChild(pic); + t4.onload = ()=>{ + ok(t4.currentSrc.endsWith("apng"), `t4: Expected pass.apng, got '${t4.currentSrc}'`); + SimpleTest.finish(); + } +}; +SimpleTest.waitForExplicitFinish(); +</script> + +<body> + <picture id="test0"> + <source> + <img src="pass.apng"> + </picture> + <picture id="test1"> + <source type="bogus/type" srcset="fail.png"> + <source type="image/apng" srcset="pass.apng"> + <source type="image/jpeg" srcset="fail.png"> + <img src="fail-fallback"> + </picture> + <picture id="test2"> + <source type="image/png" srcset="pass.apng"> + <source srcset="fail.png"> + <source type="bogus/type" srcset="fail.png"> + <img src="fail-fallback"> + </picture> + <picture id="test3"> + <source type="image/jpeg" srcset="pass.apng"> + <source type="image/apng" srcset="fail.png"> + <img src="fail-fallback"> + </picture> +</body> + +</html> diff --git a/dom/tests/mochitest/general/test_picture_mutations.html b/dom/tests/mochitest/general/test_picture_mutations.html new file mode 100644 index 0000000000..b311ffc8a2 --- /dev/null +++ b/dom/tests/mochitest/general/test_picture_mutations.html @@ -0,0 +1,311 @@ +<!DOCTYPE HTML> +<html> +<head> + <meta charset="utf-8"> + <title>Image srcset mutations</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> +<body> + <script type="application/javascript"> + "use strict"; + + // Tests the relevant mutations part of the spec for <img> inside <picture> tags + // https://html.spec.whatwg.org/#relevant-mutations + SimpleTest.waitForExplicitFinish(); + + // 50x50 png + var testPNG50 = new URL("image_50.png", location).href; + // 100x100 png + var testPNG100 = new URL("image_100.png", location).href; + // 200x200 png + var testPNG200 = new URL("image_200.png", location).href; + + var tests = []; + var img; + var picture; + var source1; + var source2; + var source3; + var expectingErrors = 0; + var expectingLoads = 0; + var afterExpectCallback; + + function onImgLoad() { + ok(expectingLoads > 0, "expected load"); + if (expectingLoads > 0) { + expectingLoads--; + } + if (!expectingLoads && !expectingErrors) { + setTimeout(afterExpectCallback, 0); + } + } + function onImgError() { + ok(expectingErrors > 0, "expected error"); + if (expectingErrors > 0) { + expectingErrors--; + } + if (!expectingLoads && !expectingErrors) { + setTimeout(afterExpectCallback, 0); + } + } + function expectEvents(loads, errors, callback) { + if (!loads && !errors) { + setTimeout(callback, 0); + } else { + expectingLoads += loads; + expectingErrors += errors; + info("Waiting for " + expectingLoads + " load and " + expectingErrors + " error events"); + afterExpectCallback = callback; + } + } + + // Setup image outside the tree dom, make sure it loads + tests.push(function() { + info("test 1"); + img.srcset = testPNG100; + img.src = testPNG50; + is(img.currentSrc, '', "Should not have synchronously selected source"); + + // No events should have fired synchronously, now we should get just one load (and no 404 error) + expectEvents(1, 0, nextTest); + }); + + // Binding to an empty picture should trigger an event, even if source doesn't change + tests.push(function() { + info("test 2"); + is(img.currentSrc, testPNG100, "Should have loaded testPNG100"); + document.body.appendChild(picture); + picture.appendChild(img); + is(img.currentSrc, testPNG100, "Should still have testPNG100"); + + expectEvents(1, 0, nextTest); + }); + + // inserting and removing an empty source before the image should both trigger a no-op reload + tests.push(function() { + info("test 3"); + is(img.currentSrc, testPNG100, "Should still have testPNG100"); + picture.insertBefore(source1, img); + is(img.currentSrc, testPNG100, "Should still have testPNG100"); + + // should fire one event, not change source + expectEvents(1, 0, function() { + is(img.currentSrc, testPNG100, "Should still have testPNG100"); + picture.removeChild(source1); + is(img.currentSrc, testPNG100, "Should still have testPNG100"); + + // Should also no-op fire + expectEvents(1, 0, nextTest); + }); + }); + + // insert and remove valid source before + tests.push(function() { + info("test 4"); + is(img.currentSrc, testPNG100, "Should still have testPNG100"); + + // Insert source1 before img with valid candidate + source1.srcset = testPNG50; + picture.insertBefore(source1, img); + is(img.currentSrc, testPNG100, "Should still have testPNG100"); + + // should fire one event, change to the source + expectEvents(1, 0, function() { + is(img.currentSrc, testPNG50, "Should have switched to testPNG50"); + picture.removeChild(source1); + is(img.currentSrc, testPNG50, "Should still have testPNG50"); + + // Should also no-op fire + expectEvents(1, 0, function() { + is(img.currentSrc, testPNG100, "Should have returned to testPNG100"); + nextTest(); + }); + }); + }); + + // insert and remove valid source after + tests.push(function() { + info("test 5"); + is(img.currentSrc, testPNG100, "Should still have testPNG100"); + + // Insert source1 before img with valid candidate + source1.srcset = testPNG50; + picture.appendChild(source1); + is(img.currentSrc, testPNG100, "Should still have testPNG100"); + + // should fire nothing, no action + expectEvents(0, 0, function() { + is(img.currentSrc, testPNG100, "Should still have testPNG100"); + + // Same with removing + picture.removeChild(source1); + expectEvents(0, 0, function() { + is(img.currentSrc, testPNG100, "Should still have testPNG100"); + nextTest(); + }); + }); + }); + + // Should re-consider earlier sources when a load event occurs. + tests.push(function() { + info("test 6"); + + // Insert two valid sources, with MQ causing us to select the second + source1.srcset = testPNG50 + " 1x"; + source1.media = "(min-resolution: 2dppx)"; // Wont match, test starts at 1x + source2.srcset = testPNG200; + picture.insertBefore(source1, img); + picture.insertBefore(source2, img); + is(img.currentSrc, testPNG100, "Should still have testPNG100"); + + // should get one load, selecting source2 + expectEvents(1, 0, function() { + is(img.currentSrc, testPNG200, "Should have selected testPNG200"); + + expectEvents(1, 0, function() { + is(img.currentSrc, testPNG50, "Should have switched to testPNG50"); + + // Now add a source *also* wanting that DPI *just before* the + // selected source. Properly re-running the algorithm should + // re-consider all sources and thus go back to the first + // source, not just the valid source just inserted before us. + source3.media = source1.media; + source3.srcset = testPNG100; + picture.insertBefore(source3, source2); + // This should trigger a reload, but we should re-consider + // source1 and remain with that, not just the newly added source2 + expectEvents(1, 0, function() { + is(img.currentSrc, testPNG50, "Should have remained on testPNG50"); + expectEvents(0, 0, nextTest); + }); + }); + + // Switch DPI to match the first source. + SpecialPowers.pushPrefEnv({'set': [ ["layout.css.devPixelsPerPx", "2.0"] ] }); + }); + }); + + // insert and remove valid source after our current source should + // trigger a reload, but not switch source + tests.push(function() { + info("test 7"); + // Should be using source1 from last test + is(img.currentSrc, testPNG50, "Should still have testPNG50"); + + // Remove source2, should trigger an event even though we would + // not switch + + picture.removeChild(source2); + expectEvents(1, 0, function() { + is(img.currentSrc, testPNG50, "Should still have testPNG50"); + + // Same with re-adding + picture.insertBefore(source2, img); + expectEvents(1, 0, function() { + is(img.currentSrc, testPNG50, "Should still have testPNG50"); + expectEvents(0, 0, nextTest); + }); + }); + }); + + // Changing source attributes should trigger updates + tests.push(function() { + info("test 8"); + // Should be using source1 from last test + is(img.currentSrc, testPNG50, "Should still have testPNG50"); + + // Reconfigure source1 to have empty srcset. Should switch to + // next source due to becoming invalid. + source1.srcset = ""; + is(img.currentSrc, testPNG50, "Should still have testPNG50"); + + expectEvents(1, 0, function() { + is(img.currentSrc, testPNG100, "Should have switched to testPNG100"); + + // Give source1 valid sizes. Should trigger an event but not switch anywhere. + source1.sizes = "100vw"; + + expectEvents(1, 0, function() { + is(img.currentSrc, testPNG100, "Should still have testPNG100"); + + // And a valid MQ + source1.media = "(min-resolution: 1dppx)"; + expectEvents(1, 0, function() { + // And a valid type... + source1.type = "image/png"; + expectEvents(1, 0, function() { + // Finally restore srcset, should trigger load and re-consider source1 which is valid again + source1.srcset = testPNG50; + expectEvents(1, 0, function() { + is(img.currentSrc, testPNG50, "Should have selected testPNG50"); + expectEvents(0, 0, nextTest); + }); + }); + }); + }); + }); + }); + + // Inserting a source after <img> and touching its attributes should all be no-ops + tests.push(function() { + info("test 9"); + // Setup: source2 + picture.removeChild(source2); + + expectEvents(1, 0, function() { + is(img.currentSrc, testPNG50, "Should still have testPNG50"); + + source2.srcset = testPNG200; + source2.media = ""; + source2.sizes = "100vw"; + source2.type = "image/png"; + // Append valid source + picture.appendChild(source2); + // Touch all the things (but keep it valid) + source2.srcset = testPNG100; + source2.media = "(min-resolution: 2dppx)"; + source2.sizes = "50vw"; + source2.type = "image/png"; + + // No event should fire. Source should not change. + expectEvents(0, 0, function() { + is(img.currentSrc, testPNG50, "Should still have testPNG50"); + expectEvents(0, 0, nextTest); + }); + }); + }); + + function nextTest() { + if (tests.length) { + // Spin event loop to make sure no unexpected image events are + // pending (unexpected events will assert in the handlers) + setTimeout(function() { + (tests.shift())(); + }, 0); + } else { + // We'll get a flood of load events due to prefs being popped while cleaning up. + // Ignore it all. + img.removeEventListener("load", onImgLoad); + img.removeEventListener("error", onImgError); + SimpleTest.finish(); + } + } + + addEventListener("load", function() { + SpecialPowers.pushPrefEnv({'set': [["layout.css.devPixelsPerPx", "1.0" ]] }, + function() { + // Create these after the pref is set, as it is guarding webIDL attributes + img = document.createElement("img"); + img.addEventListener("load", onImgLoad); + img.addEventListener("error", onImgError); + picture = document.createElement("picture"); + source1 = document.createElement("source"); + source2 = document.createElement("source"); + source3 = document.createElement("source"); + setTimeout(nextTest, 0); + }); + }); + </script> +</body> +</html> diff --git a/dom/tests/mochitest/general/test_pointerPreserves3D.html b/dom/tests/mochitest/general/test_pointerPreserves3D.html new file mode 100644 index 0000000000..d99403002d --- /dev/null +++ b/dom/tests/mochitest/general/test_pointerPreserves3D.html @@ -0,0 +1,25 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test for pointer events with preserve-3d</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<div> + <div> + <div style="transform-style: preserve-3d; transform: rotateX(90deg);"> + <div id="color" style="transform: rotateX(-90deg); display: block; background-color: blue; width: 200px; height: 200px;"></div> + </div> + </div> +</div> +<script class="testbody" type="text/javascript"> +function runTest() { + var target = document.elementFromPoint(100, 110); + ok(target == document.getElementById("color"), "Find the right target."); +} + +runTest(); +</script> +</body> +</html> diff --git a/dom/tests/mochitest/general/test_pointerPreserves3DClip.html b/dom/tests/mochitest/general/test_pointerPreserves3DClip.html new file mode 100644 index 0000000000..82f64f5969 --- /dev/null +++ b/dom/tests/mochitest/general/test_pointerPreserves3DClip.html @@ -0,0 +1,55 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test for pointer events with preserve-3d and clips</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> + <style type="text/css"> + .outer { + transform-style: preserve-3d; + } + .container { + overflow-y: scroll; + overflow-x: hidden; + width: 200px; + height: 300px; + } + .content { + width: 200px; + height: 1000px; + transform-style: preserve-3d; + } + #container1 { + background-color: green; + transform: translateZ(2px); + } + #container2 { + height: 100px; + transform: translateY(-200px) translateZ(10px); + background-color: red; + } + </style> + </head> + <body onload="runTest();"> + <div class="outer" id="outer"> + <div class="container" id="container1"> + <div class="content"></div> + </div> + <div class="container" id="container2"> + <div class="content"></div> + </div> + </div> +<script class="testbody" type="text/javascript"> +function runTest() { + var outer = document.getElementById("outer"); + var x = outer.offsetLeft; + var y = outer.offsetTop; + var target = document.elementFromPoint(x + 100, y + 250); + ok(target.parentNode == document.getElementById("container1"), "Find the right target."); + SimpleTest.finish(); +} + +SimpleTest.waitForExplicitFinish(); +</script> + </body> +</html> diff --git a/dom/tests/mochitest/general/test_pointerPreserves3DPerspective.html b/dom/tests/mochitest/general/test_pointerPreserves3DPerspective.html new file mode 100644 index 0000000000..4feaf03551 --- /dev/null +++ b/dom/tests/mochitest/general/test_pointerPreserves3DPerspective.html @@ -0,0 +1,29 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test for pointer events with preserve-3d</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<div> + <div> + <div style="perspective: 100px; transform-style:preserve-3d; transform: translateX(100px)"> + <div style="display:inline"> + <div id="color" style="transform-style:flat; transform: translateX(-100px); display: block; background-color: blue; width: 200px; height: 200px;"></div> + </div> + </div> + </div> +</div> +<script class="testbody" type="text/javascript"> +function runTest() { + var target = document.elementFromPoint(200, 200); + ok(target == document.getElementById("color"), "Find the right target."); + var target = document.elementFromPoint(16, 16); + ok(target == document.getElementById("color"), "Find the right target."); +} + +runTest(); +</script> +</body> +</html> diff --git a/dom/tests/mochitest/general/test_resizeby.html b/dom/tests/mochitest/general/test_resizeby.html new file mode 100644 index 0000000000..4826e538a0 --- /dev/null +++ b/dom/tests/mochitest/general/test_resizeby.html @@ -0,0 +1,64 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test for Bug 1369627</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <script src="/tests/SimpleTest/EventUtils.js"></script> + + + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1369627">Mozilla Bug 1369627</a> +<p id="display"></p> +<div id="content"> + <button id="clicky">clicky</button> +</div> +<pre id="test"> +</pre> + +<script> + /** Test for Bug 1369627 **/ + add_task(async function resizeby() { + await SimpleTest.promiseFocus(); + + let clicky = document.querySelector("#clicky"); + + let popupPromise = new Promise(resolve => { + let linkclick = () => { + clicky.removeEventListener('click', linkclick); + let popup = window.open("about:blank", "_blank", "width=500,height=500"); + function loaded() { + is(popup.innerHeight, 500, "starting width is 500"); + is(popup.innerWidth, 500, "starting height in 500"); + + popup.resizeBy(50, 0); + + // We resized synchronously. If this happened, we sometimes won't fire + // an resize event, so we resolve immediately. + if (popup.innerWidth == 550) { + resolve(popup); + } else { + let popupresize = () => { + popup.removeEventListener('resize', popupresize); + resolve(popup); + }; + popup.addEventListener('resize', popupresize); + } + }; + popup.addEventListener("load", loaded, { once: true }) + }; + + clicky.addEventListener('click', linkclick); + }); + + synthesizeMouseAtCenter(clicky, {}, window); + + let popup = await popupPromise; + is(popup.innerHeight, 500, "ending height is 500"); + is(popup.innerWidth, 550, "ending width is 550"); + popup.close(); + }); +</script> +</body> +</html> diff --git a/dom/tests/mochitest/general/test_resource_timing.html b/dom/tests/mochitest/general/test_resource_timing.html new file mode 100644 index 0000000000..4b45d69941 --- /dev/null +++ b/dom/tests/mochitest/general/test_resource_timing.html @@ -0,0 +1,40 @@ +<!-- + Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ +--> + +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=822480 +--> +<head> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> +<body> + +<pre id="test"> +<script type="application/javascript"> + +SimpleTest.waitForExplicitFinish(); + +// Resource timing is prefed off by default, so we had to use this workaround +SpecialPowers.pushPrefEnv({"set": [ + ["dom.enable_resource_timing", true], + ["privacy.reduceTimerPrecision", false]]}, start); +var subwindow = null; + +function start() { + subwindow = window.open("resource_timing_main_test.html"); +} + +function finishTests() { + subwindow.close(); + SimpleTest.finish(); +} +</script> +</pre> + +</body> +</html> diff --git a/dom/tests/mochitest/general/test_resource_timing_cross_origin.html b/dom/tests/mochitest/general/test_resource_timing_cross_origin.html new file mode 100644 index 0000000000..bef092abde --- /dev/null +++ b/dom/tests/mochitest/general/test_resource_timing_cross_origin.html @@ -0,0 +1,47 @@ +<!-- + Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ +--> + +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=822480 +--> +<head> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> +<body> + +<pre id="test"> +<script type="application/javascript"> + +SimpleTest.waitForExplicitFinish(); + +// Resource timing is prefed off by default, so we had to use this workaround +var subwindow = null; +SpecialPowers.pushPrefEnv({"set": [ + ["dom.enable_resource_timing", true], + ["privacy.reduceTimerPrecision", false]]}, start); + +function start() { + subwindow = window.open("resource_timing_cross_origin.html"); +} + +function finishTests() { + subwindow.close(); + SpecialPowers.pushPrefEnv({"clear":[["dom.enable_resource_timing"]]}, SimpleTest.finish); +} + +</script> +</pre> + +<a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=936814" + title="Cross origin resource timing"> + Bug #936814 - Implement the "timing allow check algorithm" for cross-origin Resource Timing +</a> + +</body> +</html> diff --git a/dom/tests/mochitest/general/test_resource_timing_cross_origin_navigate.html b/dom/tests/mochitest/general/test_resource_timing_cross_origin_navigate.html new file mode 100644 index 0000000000..133e9a0c8a --- /dev/null +++ b/dom/tests/mochitest/general/test_resource_timing_cross_origin_navigate.html @@ -0,0 +1,73 @@ +<!-- + Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ +--> + +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1789128 +--> +<head> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> +<body> + +<pre id="test"> +<script type="application/javascript"> + +</script> +</pre> + +<a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=1789128" + title="Cross origin resource timing"> + Bug #1789128 - Cross-Origin URL Steal is possible using performance.getEntries() +</a> + +<script type="text/javascript"> + +SimpleTest.waitForExplicitFinish(); + +let domains = [ + // resource_timing_location_navigate.html navigates via document.location + "https://example.org", + // resource_timing_meta_refresh.html redirects via meta refresh + "https://test2.example.org", + // resource_timing_redirect.html redirects via 302 redirect + "https://test1.example.org", + // embed_navigate.html navigates via document.location + "https://sub1.test1.example.org", + ]; + + +let redirectResolves = {}; + +window.addEventListener("message", (event) => { + console.log("message", event); + redirectResolves[event.origin](); +}); + +// Wait for all iframes to navigate. +Promise.all(domains.map(domain => { + return new Promise(resolve => { + redirectResolves[domain] = resolve; + }) +})).then(() => { + // Check resource timing for iframes. + for (let e of performance.getEntries()) { + ok(!e.name.includes("example.org"), `${e.name} cross origin should not be present in resource timing`) + } + SimpleTest.finish(); +}); + +</script> + +<iframe src="resource_timing_location_navigate.html"></iframe> +<iframe src="resource_timing_meta_refresh.html"></iframe> +<iframe src="resource_timing_redirect.html"></iframe> +<embed src="embed_navigate.html"> + +</body> +</html> diff --git a/dom/tests/mochitest/general/test_resource_timing_frameset.html b/dom/tests/mochitest/general/test_resource_timing_frameset.html new file mode 100644 index 0000000000..245bd382ee --- /dev/null +++ b/dom/tests/mochitest/general/test_resource_timing_frameset.html @@ -0,0 +1,29 @@ +<!-- + Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ +--> + +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Frameset//EN"> +<html lang="en"> + <head> + <meta charset="utf-8"> + <title>browser_frametree_sample_frameset.html</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> + + <script type="text/javascript"> + SimpleTest.waitForExplicitFinish(); + window.addEventListener("load", function() { + var frameEntries = performance.getEntriesByName("http://mochi.test:8888/tests/dom/base/test/file_empty.html"); + + is(frameEntries.length, 2, "correct number"); + is(frameEntries[0].initiatorType, "frame", "correct type"); + SimpleTest.finish(); + }); + </script> + </head> + <frameset id="frames" rows="50%, 50%"> + <frame src="http://mochi.test:8888/tests/dom/base/test/file_empty.html"> + <frame src="http://mochi.test:8888/tests/dom/base/test/file_empty.html"> + </frameset> +</html> diff --git a/dom/tests/mochitest/general/test_resource_timing_nocors.html b/dom/tests/mochitest/general/test_resource_timing_nocors.html new file mode 100644 index 0000000000..388b8f9837 --- /dev/null +++ b/dom/tests/mochitest/general/test_resource_timing_nocors.html @@ -0,0 +1,37 @@ +<!-- + Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ +--> + +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1180145 +--> +<head> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> +<body> + +<pre id="test"> +<script type="application/javascript"> + +SimpleTest.waitForExplicitFinish(); + +SpecialPowers.pushPrefEnv({"set": [["dom.enable_resource_timing", true]]}, start); +var subwindow = null; + +function start() { + subwindow = window.open("file_resource_timing_nocors.html"); +} + +function finishTests() { + subwindow.close(); + SimpleTest.finish(); +} +</script> +</pre> + +</body> +</html> diff --git a/dom/tests/mochitest/general/test_selectevents.html b/dom/tests/mochitest/general/test_selectevents.html new file mode 100644 index 0000000000..bcc3526492 --- /dev/null +++ b/dom/tests/mochitest/general/test_selectevents.html @@ -0,0 +1,31 @@ +<!doctype html> +<html> + <head> + <title>Testing Selection Events</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <script src="/tests/SimpleTest/EventUtils.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> + </head> + + <body> + <iframe width="500"></iframe> + <script> + add_task(async function() { + // Push the correct preferences for the test + await SpecialPowers.pushPrefEnv({'set': [ + ['dom.select_events.textcontrols.enabled', true], + ]}); + + // Start the actual test + await new Promise((done) => { + var iframe = document.querySelector('iframe'); + iframe.addEventListener('load', done); + iframe.setAttribute('src', 'frameSelectEvents.html'); + }); + + // The child iframe will call add_task before we reach this point, + // and will handle the rest of the test. + }); + </script> + </body> +</html> diff --git a/dom/tests/mochitest/general/test_showModalDialog_removed.html b/dom/tests/mochitest/general/test_showModalDialog_removed.html new file mode 100644 index 0000000000..a5f85ffc8e --- /dev/null +++ b/dom/tests/mochitest/general/test_showModalDialog_removed.html @@ -0,0 +1,31 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1077002 +--> +<head> + <title>Test for showModalDialog unavailability in e10s</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <script src="/tests/SimpleTest/EventUtils.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> +<body> +<a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=1077002">Mozilla Bug 1077002</a> +<p id="display"></p> +<div id="content"> + <iframe id="frame" style="height:100px; width:100px; border:0"></iframe> + <div id="status" style="display: none"></div> +</div> +<pre id="test"> +<script type="application/javascript"> + +/** Test for showModalDialog unavailability in Firefox. **/ + +// showModalDialog was removed in bug 981796. +ok(!window.showModalDialog, "showModalDialog should not exist"); + +</script> +</pre> +</body> +</html> diff --git a/dom/tests/mochitest/general/test_spacetopagedown.html b/dom/tests/mochitest/general/test_spacetopagedown.html new file mode 100644 index 0000000000..39b02bc0fa --- /dev/null +++ b/dom/tests/mochitest/general/test_spacetopagedown.html @@ -0,0 +1,75 @@ +<html> +<head> + <meta charset="utf-8"> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> + <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"/> + <script type="application/javascript"> + +SimpleTest.waitForExplicitFinish(); + + +var windowUtils = SpecialPowers.getDOMWindowUtils(window); + +function pressKey(isShift) +{ + return new Promise(resolve => { + synthesizeKey(" ", { shiftKey: isShift }); + windowUtils.advanceTimeAndRefresh(100); + SimpleTest.executeSoon(resolve); + }); +} + +function initTest() +{ + SpecialPowers.pushPrefEnv({"set":[["general.smoothScroll", false]]}, runTest); +} + +function runTest() +{ + (async function() { + await pressKey(false); + + ok(window.scrollY > 0, "Space with no focus" + window.scrollY); + await pressKey(true); + is(window.scrollY, 0, "Shift+Space with no focus"); + + let checkbox = document.getElementById("checkbox"); + checkbox.focus(); + await pressKey(false); + + is(window.scrollY, 0, "Space with checkbox focused"); + ok(checkbox.checked, "Space with checkbox focused, checked"); + await pressKey(true); + is(window.scrollY, 0, "Shift+Space with checkbox focused"); + ok(!checkbox.checked, "Space with checkbox focused, unchecked"); + + let input = document.getElementById("input"); + input.focus(); + await pressKey(false); + is(window.scrollY, 0, "Space with input focused"); + is(input.value, " ", "Space with input focused, value"); + await pressKey(true); + is(window.scrollY, 0, "Shift+Space with input focused"); + is(input.value, " ", "Space with input focused, value"); + + windowUtils.restoreNormalRefresh(); + SimpleTest.finish(); + })(); +} + + </script> +</head> +<body onload="SimpleTest.waitForFocus(initTest)"> + +<input id="checkbox" type="checkbox">Checkbox +<input id="input"> +<p style="height: 4000px">Text</p> + +<p id="display"></p> +<div id="content" style="display: none"> +</div> +<pre id="test"> +</pre> +</body> +</html> diff --git a/dom/tests/mochitest/general/test_storagePermissionsAccept.html b/dom/tests/mochitest/general/test_storagePermissionsAccept.html new file mode 100644 index 0000000000..ddb33de9c3 --- /dev/null +++ b/dom/tests/mochitest/general/test_storagePermissionsAccept.html @@ -0,0 +1,44 @@ +<html xmlns="http://www.w3.org/1999/xhtml"> + <head> + <title>Storage Permission Restrictions</title> + + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="storagePermissionsUtils.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> + </head> + <body> + <iframe></iframe> + + <script type="text/javascript"> + +task(async function() { + await setCookieBehavior(BEHAVIOR_ACCEPT); + + await runTestInWindow(async function() { + // We should be able to access storage + await storageAllowed(); + + // Same origin iframes should be allowed, unless they redirect to a URI with the null principal + await runIFrame("frameStorageAllowed.html"); + await runIFrame("frameStorageNullprincipal.sjs"); + await runIFrame("frameStorageChrome.html?allowed=yes"); + + // Sandboxed iframes should have the null principal, and thus can't access storage + document.querySelector('iframe').setAttribute('sandbox', 'allow-scripts'); + await runIFrame("frameStoragePrevented.html#nullprincipal"); + await runIFrame("frameStorageNullprincipal.sjs"); + document.querySelector('iframe').removeAttribute('sandbox'); + + // Thirdparty iframes should be allowed, unless they redirect to a URI with the null principal + await runIFrame(thirdparty + "frameStorageAllowed.html"); + await runIFrame(thirdparty + "frameStorageNullprincipal.sjs"); + await runIFrame(thirdparty + "frameStorageChrome.html?allowed=yes"); + + // Workers should be able to access storage + await runWorker("workerStorageAllowed.js"); + }); +}); + + </script> + </body> +</html> diff --git a/dom/tests/mochitest/general/test_storagePermissionsLimitForeign.html b/dom/tests/mochitest/general/test_storagePermissionsLimitForeign.html new file mode 100644 index 0000000000..e2b4b93798 --- /dev/null +++ b/dom/tests/mochitest/general/test_storagePermissionsLimitForeign.html @@ -0,0 +1,46 @@ +<html xmlns="http://www.w3.org/1999/xhtml"> + <head> + <title>Storage Permission Restrictions</title> + + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="storagePermissionsUtils.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> + </head> + <body> + <iframe></iframe> + + <script type="text/javascript"> + +task(async function() { + await setCookieBehavior(BEHAVIOR_LIMIT_FOREIGN); + + await runTestInWindow(async function() { + // We should be able to access storage + await storageAllowed(); + + // Same origin iframes should be allowed. + await runIFrame("frameStorageAllowed.html"); + await runIFrame("frameStorageChrome.html?allowed=yes"); + + // Null principal iframes should not. + await runIFrame("frameStorageNullprincipal.sjs"); + + // Sandboxed iframes should have the null principal, and thus can't access storage + document.querySelector('iframe').setAttribute('sandbox', 'allow-scripts'); + await runIFrame("frameStoragePrevented.html#nullprincipal"); + await runIFrame("frameStorageNullprincipal.sjs"); + document.querySelector('iframe').removeAttribute('sandbox'); + + // Thirdparty iframes should be blocked, even when accessed from chrome over Xrays. + await runIFrame(thirdparty + "frameStoragePrevented.html#thirdparty"); + await runIFrame(thirdparty + "frameStorageNullprincipal.sjs"); + await runIFrame(thirdparty + "frameStorageChrome.html?allowed=no"); + + // Workers should be unable to access storage + await runWorker("workerStorageAllowed.js"); + }); +}); + + </script> + </body> +</html> diff --git a/dom/tests/mochitest/general/test_storagePermissionsReject.html b/dom/tests/mochitest/general/test_storagePermissionsReject.html new file mode 100644 index 0000000000..70dbe856d9 --- /dev/null +++ b/dom/tests/mochitest/general/test_storagePermissionsReject.html @@ -0,0 +1,44 @@ +<html xmlns="http://www.w3.org/1999/xhtml"> + <head> + <title>Storage Permission Restrictions</title> + + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="storagePermissionsUtils.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> + </head> + <body> + <iframe></iframe> + + <script type="text/javascript"> + +task(async function() { + await setCookieBehavior(BEHAVIOR_REJECT); + + await runTestInWindow(async function() { + // We should be unable to access storage + await storagePrevented(); + + // Same origin iframes should be blocked. + await runIFrame("frameStoragePrevented.html"); + await runIFrame("frameStorageNullprincipal.sjs"); + await runIFrame("frameStorageChrome.html?allowed=no&blockSessionStorage=yes"); + + // Sandboxed iframes should have the null principal, and thus can't access storage + document.querySelector('iframe').setAttribute('sandbox', 'allow-scripts'); + await runIFrame("frameStoragePrevented.html#nullprincipal"); + await runIFrame("frameStorageNullprincipal.sjs"); + document.querySelector('iframe').removeAttribute('sandbox'); + + // thirdparty iframes should be blocked. + await runIFrame(thirdparty + "frameStoragePrevented.html"); + await runIFrame(thirdparty + "frameStorageNullprincipal.sjs"); + await runIFrame(thirdparty + "frameStorageChrome.html?allowed=no&blockSessionStorage=yes"); + + // Workers should be unable to access storage + await runWorker("workerStoragePrevented.js"); + }); +}); + + </script> + </body> +</html> diff --git a/dom/tests/mochitest/general/test_storagePermissionsRejectForeign.html b/dom/tests/mochitest/general/test_storagePermissionsRejectForeign.html new file mode 100644 index 0000000000..f188d46cb2 --- /dev/null +++ b/dom/tests/mochitest/general/test_storagePermissionsRejectForeign.html @@ -0,0 +1,44 @@ +<html xmlns="http://www.w3.org/1999/xhtml"> + <head> + <title>Storage Permission Restrictions</title> + + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="storagePermissionsUtils.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> + </head> + <body> + <iframe></iframe> + + <script type="text/javascript"> + +task(async function() { + await setCookieBehavior(BEHAVIOR_REJECT_FOREIGN); + + await runTestInWindow(async function() { + // We should be able to access storage + await storageAllowed(); + + // Same origin iframes should be allowed, unless they redirect to a URI with the null principal + await runIFrame("frameStorageAllowed.html"); + await runIFrame("frameStorageNullprincipal.sjs"); + await runIFrame("frameStorageChrome.html?allowed=yes"); + + // Sandboxed iframes should have the null principal, and thus can't access storage + document.querySelector('iframe').setAttribute('sandbox', 'allow-scripts'); + await runIFrame("frameStoragePrevented.html#nullprincipal"); + await runIFrame("frameStorageNullprincipal.sjs"); + document.querySelector('iframe').removeAttribute('sandbox'); + + // thirdparty iframes should be blocked. + await runIFrame(thirdparty + "frameStoragePrevented.html#thirdparty"); + await runIFrame(thirdparty + "frameStorageNullprincipal.sjs"); + await runIFrame(thirdparty + "frameStorageChrome.html?allowed=no"); + + // Workers should be able to access storage + await runWorker("workerStorageAllowed.js"); + }); +}); + + </script> + </body> +</html> diff --git a/dom/tests/mochitest/general/test_stylesheetPI.html b/dom/tests/mochitest/general/test_stylesheetPI.html new file mode 100644 index 0000000000..2b242597d2 --- /dev/null +++ b/dom/tests/mochitest/general/test_stylesheetPI.html @@ -0,0 +1,37 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=836809 +--> +<head> + <meta charset="utf-8"> + <title>Test for Bug 836809</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> + <script type="application/javascript"> + + /** Test for Bug 836809 **/ + + var pi = document.createProcessingInstruction('xml-stylesheet', 'href="/tests/SimpleTest/test.css" type="text/css"'); + + function checkSheet(e) + { + ok("sheet" in pi, "XMLStyleSheetProcessingInstruction should have a sheet property"); + ok(pi.sheet, "XMLStyleSheetProcessingInstruction should have a sheet property"); + } + + pi.addEventListener("load", checkSheet); + document.insertBefore(pi, document.documentElement); + + </script> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=836809">Mozilla Bug 836809</a> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +</pre> +</body> +</html> diff --git a/dom/tests/mochitest/general/test_toggling_performance_navigation_timing.html b/dom/tests/mochitest/general/test_toggling_performance_navigation_timing.html new file mode 100644 index 0000000000..6e6d94dca7 --- /dev/null +++ b/dom/tests/mochitest/general/test_toggling_performance_navigation_timing.html @@ -0,0 +1,47 @@ +<!DOCTYPE HTML> +<html> +<head> + <meta charset="utf-8"> + <title>Test for Bug 1511941 - Don't expose PerformanceNavigationTiming when it is disabled</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> +<body> + <div id="content"> </div> + <script type="application/javascript"> + async function testWhetherExposed(resistFingerprinting, enable_performance_navigation_timing) { + await SpecialPowers.pushPrefEnv({ + "set": [["privacy.resistFingerprinting", resistFingerprinting], + ["dom.enable_performance_navigation_timing", enable_performance_navigation_timing]], + }); + var iframe = document.createElement("iframe"); + document.body.append(iframe); + var p = iframe.contentWindow.PerformanceNavigationTiming; + if (enable_performance_navigation_timing && resistFingerprinting) + isnot(p, undefined, "window.PerformanceNavigationTiming should be exposed when" + + " dom.enable_performance_navigation_timing=" + enable_performance_navigation_timing + + " and privacy.resistFingerprinting="+ resistFingerprinting +"."); + if (!enable_performance_navigation_timing) + is(p, undefined, "window.PerformanceNavigationTiming should not be exposed when" + + " dom.enable_performance_navigation_timing=" + enable_performance_navigation_timing + + " and privacy.resistFingerprinting="+ resistFingerprinting +"."); + if (enable_performance_navigation_timing && !resistFingerprinting) { + isnot(p, undefined, "window.PerformanceNavigationTiming should be exposed when" + + " dom.enable_performance_navigation_timing=" + enable_performance_navigation_timing + + " and privacy.resistFingerprinting="+ resistFingerprinting +"."); + } + } + + async function start() { + await testWhetherExposed(true,true); + await testWhetherExposed(true,false); + await testWhetherExposed(false,true); + await testWhetherExposed(false,false); + SimpleTest.finish(); + } + + SimpleTest.waitForExplicitFinish(); + start(); + </script> +</body> +</html> diff --git a/dom/tests/mochitest/general/test_vibrator.html b/dom/tests/mochitest/general/test_vibrator.html new file mode 100644 index 0000000000..e7085fc987 --- /dev/null +++ b/dom/tests/mochitest/general/test_vibrator.html @@ -0,0 +1,148 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test for Vibrator</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> + +<!-- Although we can't test that the vibrator works properly, we can test that + navigator.vibrate throws an exception where appropriate. --> + +<script class="testbody" type="text/javascript"> +function testNavigatorVibrate(testCase) { + result = navigator.vibrate(testCase.value); + is(result, true, `vibrate(${testCase.value}) must succeed.`); +} + +function testNotificationVibrate(testCase) { + var notification = new Notification('Test notification', { + body: 'test vibrate', + vibrate: testCase.value, + }); + + isDeeply(notification.vibrate, testCase.expected, `vibrate = ${testCase.value} should be accepted.`); +} + +const MAX_VIBRATE_MS = SpecialPowers.getIntPref('dom.vibrator.max_vibrate_ms'); +const MAX_VIBRATE_LIST_LEN = SpecialPowers.getIntPref('dom.vibrator.max_vibrate_list_len'); +const TESTCASES = [ + { + value: null, + expected: [0], + },{ + value: undefined, + expected: [], + },{ + // -1 will be converted to the highest unsigned long then clamped. + value: -1, + expected: [MAX_VIBRATE_MS], + },{ + value: 'a', + expected: [0], + },{ + // -1 will be converted to the highest unsigned long then clamped. + value: [100, -1], + expected: [100, MAX_VIBRATE_MS], + },{ + value: [100, 'a'], + expected: [100, 0], + },{ + // If we pass a vibration pattern with a value higher than max_vibrate_ms or a + // pattern longer than max_vibrate_list_len, the call should succeed but the + // pattern should be modified to match the restrictions. + + // Values will be clamped to dom.vibrator.max_vibrate_ms. + value: MAX_VIBRATE_MS + 1, + expected: [MAX_VIBRATE_MS], + },{ + value: [MAX_VIBRATE_MS + 1], + expected: [MAX_VIBRATE_MS], + },{ + // The array will be truncated to have a length equal to dom.vibrator.max_vibrate_list_len. + value: new Array(MAX_VIBRATE_LIST_LEN + 1).fill(0), + expected: new Array(MAX_VIBRATE_LIST_LEN).fill(0), + },{ + value: 0, + expected: [0], + },{ + value: [], + expected: [], + },{ + value: '1000', + expected: [1000], + },{ + value: 1000, + expected: [1000], + },{ + value: 1000.1, + expected: [1000], + },{ + value: [0, 0, 0], + expected: [0, 0, 0], + },{ + value: ['1000', 1000], + expected: [1000, 1000], + },{ + value: [1000, 1000], + expected: [1000, 1000], + },{ + value: [1000, 1000.1], + expected: [1000, 1000], + } +]; + +function testWith(tester) { + for (let testCase of TESTCASES) { + tester(testCase); + } +} + +add_task(async function test_notification_vibrate_enabled() { + await SpecialPowers.pushPrefEnv({"set": [['dom.webnotifications.vibrate.enabled', true]]}); + + testWith(testNotificationVibrate); +}); + +add_task(async function test_vibrator_vibrate() { + await SpecialPowers.pushPermissions([{type: 'vibration', allow: true, context: document}]); + await SpecialPowers.pushPrefEnv({"set": [['dom.vibrator.enabled', true]]}); + + testWith(testNavigatorVibrate); + + await SpecialPowers.pushPrefEnv({"set": [['dom.vibrator.enabled', false]]}); + + testWith(testNavigatorVibrate); +}); + +add_task(async function test_vibrate_many_times() { + await SpecialPowers.pushPermissions([{type: 'vibration', allow: true, context: document}]); + await SpecialPowers.pushPrefEnv({"set": [['dom.vibrator.enabled', true]]}); + + // The following loop shouldn't cause us to crash. See bug 701716. + for (var i = 0; i < 10000; i++) { + navigator.vibrate([100, 100]); + } + ok(true, "Didn't crash after issuing a lot of vibrate() calls."); +}); + +add_task(async function test_notification_vibrate_silent() { + await SpecialPowers.pushPrefEnv({"set": [['dom.webnotifications.vibrate.enabled', true], + ['dom.webnotifications.silent.enabled', true]]}); + + try { + var notification = new Notification('Test notification', { + body: 'test vibrate', + vibrate: [100, 100], + silent: true, + }); + ok(false, "The above should throw if vibrate is enabled"); + } catch (error) { + is(error.name, "TypeError", "A silent notification with a vibrate param should throw a TypeError"); + } +}); + +</script> +</body> +</html> diff --git a/dom/tests/mochitest/general/test_windowProperties.html b/dom/tests/mochitest/general/test_windowProperties.html new file mode 100644 index 0000000000..3fe4c9ee9e --- /dev/null +++ b/dom/tests/mochitest/general/test_windowProperties.html @@ -0,0 +1,28 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test that all window properties are accessible to nonprivileged code</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"> +</head> + +<body id="body"> + +<script type="application/javascript"> +var lastProp; +try { + var propVals = []; + for (var prop in window) { + lastProp = prop; + propVals.push(window[prop]); + } + ok(true, "Read all " + propVals.length + " window properties"); +} catch (ex) { + ok(false, "Exception occurred reading window." + lastProp); +} +</script> + +<p id="display"></p> + +</body> +</html> diff --git a/dom/tests/mochitest/general/test_windowedhistoryframes.html b/dom/tests/mochitest/general/test_windowedhistoryframes.html new file mode 100644 index 0000000000..c2c148b838 --- /dev/null +++ b/dom/tests/mochitest/general/test_windowedhistoryframes.html @@ -0,0 +1,32 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=602256 +--> +<head> + <title>Test for Bug 602256</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=602256">Mozilla Bug 602256</a> +<p id="display"></p> +<div id="content" style="display: none"></div> +<pre id="test"> +<script type="application/javascript"> + +/** Test for Bug 602256 **/ + +SimpleTest.waitForExplicitFinish(); + +function done() { + subWin.close(); + SimpleTest.finish(); +} + +var subWin = window.open("historyframes.html", "_blank"); + +</script> +</pre> +</body> +</html> diff --git a/dom/tests/mochitest/general/url1_historyframe.html b/dom/tests/mochitest/general/url1_historyframe.html new file mode 100644 index 0000000000..b86af4b3fa --- /dev/null +++ b/dom/tests/mochitest/general/url1_historyframe.html @@ -0,0 +1 @@ +<p id='text'>Test1</p> diff --git a/dom/tests/mochitest/general/url2_historyframe.html b/dom/tests/mochitest/general/url2_historyframe.html new file mode 100644 index 0000000000..24374d1a5b --- /dev/null +++ b/dom/tests/mochitest/general/url2_historyframe.html @@ -0,0 +1 @@ +<p id='text'>Test2</p> diff --git a/dom/tests/mochitest/general/window_clipboard_events.html b/dom/tests/mochitest/general/window_clipboard_events.html new file mode 100644 index 0000000000..ee88cc5fa0 --- /dev/null +++ b/dom/tests/mochitest/general/window_clipboard_events.html @@ -0,0 +1,1239 @@ +<!DOCTYPE HTML> +<html> +<head> + <meta charset="utf-8"> + <title>Test for Clipboard Events</title> + <script> + var SimpleTest = opener.SimpleTest; + var SpecialPowers = opener.SpecialPowers; + var ok = opener.ok; + var is = opener.is; + var isnot = opener.isnot; + var todo = opener.todo; + var todo_is = opener.todo_is; + var add_task = opener.add_task; + </script> + <script src="/tests/SimpleTest/EventUtils.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"> +</head> +<body> +<p id="display"></p> +<div id="content" style="border: 3px solid black; padding: 3em;">CONTENT TEXT<input id="content-input" value="INPUT TEXT"></div> +<img id="image" src="image_50.png"> +<button id="button">Button</button> + +<div id="syntheticSpot" oncut="compareSynthetic(event, 'cut')" + oncopy="compareSynthetic(event, 'copy')" + onpaste="compareSynthetic(event, 'paste')">Spot</div> + +<div id="contenteditableContainer"></div> + +<pre id="test"> +<script class="testbody" type="text/javascript"> +var content = document.getElementById("content"); +var contentInput = document.getElementById("content-input"); +var contenteditableContainer = document.getElementById("contenteditableContainer"); +var clipboardInitialValue = "empty"; + +var cachedCutData, cachedCopyData, cachedPasteData; + +ok(SpecialPowers.getBoolPref("dom.events.dataTransfer.protected.enabled"), + "The following require dom.events.dataTransfer.protected.enabled is enabled"); +isnot(typeof window.onbeforeinput, "undefined", + "The following tests require onbeforeinput attribute"); + +// Before each test function is run, the clipboard is initialized +// to clipboardInitialValue, and the contents of div#content are +// set as the window's selection. + +add_task(async function initialize_for_tests() { + disableNonTestMouseEvents(true); + + await SimpleTest.promiseFocus(window); + + // Test that clearing and reading the clipboard works. A random number + // is used to make sure that leftover clipboard values from a previous + // test run don't cause a false-positive test. + try { + var cb_text = "empty_" + Math.random(); + await putOnClipboard(cb_text, () => { setClipboardText(cb_text) }, + "Failed to initial set/get clipboard text"); + } catch (e) { + ok(false, e.toString()); + } +}); + +var eventListeners = []; + +// Note that don't use EventTarget.addEventListener directly in this file +// because if it's remained in next test, it makes developers harder to +// investigate such oranges. addEventListenerTo cleans up when `reset()` +// is called by first of next test and then, all event listeners including +// marked as "once" are removed automatically. +function addEventListenerTo(aEventTarget, aEventType, aListener, aOptions) { + eventListeners.push({ + target: aEventTarget, + type: aEventType, + listener: aListener, + options: aOptions + }); + aEventTarget.addEventListener(aEventType, aListener, aOptions); +} + +async function reset() { + [content, contentInput, document, document.documentElement].forEach(eventTarget => { + ["oncut", "oncopy", "onpaste", "oninput", "onbeforeinput"].forEach(attr => { + eventTarget[attr] = null; + }); + }); + eventListeners.forEach(data => { + data.target.removeEventListener(data.type, data.listener, data.options); + }); + eventListeners = []; + + // Init clipboard + await putOnClipboard(clipboardInitialValue, + () => { setClipboardText(clipboardInitialValue) }, + "reset clipboard"); + + // Reset value of editors. + contentInput.value = "INPUT TEXT"; + contenteditableContainer.innerHTML = ""; +} + +function getClipboardText() { + return SpecialPowers.getClipboardData("text/plain"); +} + +function getHTMLEditor() { + let editingSession = SpecialPowers.wrap(window).docShell.editingSession; + if (!editingSession) { + return null; + } + let editor = editingSession.getEditorForWindow(window); + if (!editor) { + return null; + } + return editor.QueryInterface(SpecialPowers.Ci.nsIHTMLEditor); +} + +async function putOnClipboard(expected, operationFn, desc, type) { + try { + await SimpleTest.promiseClipboardChange(expected, operationFn, type, 1000); + } catch (e) { + throw `Failed "${desc}" due to "${e.toString()}"` + } +} + +async function wontPutOnClipboard(unexpectedData, operationFn, desc, type) { + try { + // SimpleTest.promiseClipboardChange() doesn't throw exception when + // it unexpectedly succeeds to copy something. Therefore, we need + // to throw an exception by ourselves. + await SimpleTest.promiseClipboardChange(null, operationFn, type, 300, true, false) + .then(aData => { + if (aData == unexpectedData) { + throw `Failed "${desc}", the clipboard data is modified to "${aData.toString()}"`; + } + }); + } catch (e) { + throw `Failed "${desc}" due to "${e.toString()}"` + } +} + +function setClipboardText(text) { + var helper = SpecialPowers.Cc["@mozilla.org/widget/clipboardhelper;1"] + .getService(SpecialPowers.Ci.nsIClipboardHelper); + helper.copyString(text); +} + +function selectContentDiv() { + // Set selection + var selection = window.getSelection(); + selection.removeAllRanges(); + selection.selectAllChildren(content); +} + +function selectContentInput() { + contentInput.focus(); + contentInput.select(); +} + +add_task(async function test_dom_oncopy() { + try { + await reset(); + } catch (e) { + ok(false, `Failed to reset (${e.toString()})`); + return; + } + + // Setup an oncopy event handler, fire copy. Ensure that the event + // handler was called, and the clipboard contents have set to CONTENT TEXT. + // Test firing oncopy event on ctrl-c: + selectContentDiv(); + + var oncopy_fired = false; + content.oncopy = function() { oncopy_fired = true; }; + try { + await putOnClipboard("CONTENT TEXT", () => { + synthesizeKey("c", {accelKey: 1}); + }, "copy on DOM element set clipboard correctly"); + ok(oncopy_fired, "copy event firing on DOM element"); + } catch (e) { + ok(false, e.toString()); + } +}); + +add_task(async function test_dom_oncut() { + try { + await reset(); + } catch (e) { + ok(false, `Failed to reset (${e.toString()})`); + return; + } + + // Setup an oncut event handler, fire cut. Ensure that the event handler + // was called. The <div> doesn't handle a cut, so ensure that the + // clipboard text shouldn't be "CONTENT TEXT". + selectContentDiv(); + var oncut_fired = false; + content.oncut = function() { oncut_fired = true; }; + try { + await wontPutOnClipboard("CONTENT TEXT", () => { + synthesizeKey("x", {accelKey: 1}); + }, "cut on DOM element set clipboard correctly"); + ok(oncut_fired, "cut event firing on DOM element") + } catch (e) { + ok(false, e.toString()); + } +}); + +add_task(async function test_dom_onpaste() { + try { + await reset(); + } catch (e) { + ok(false, `Failed to reset (${e.toString()})`); + return; + } + + // Setup an onpaste event handler, fire paste. Ensure that the event + // handler was called. + selectContentDiv(); + var onpaste_fired = false; + content.onpaste = function() { onpaste_fired = true; }; + synthesizeKey("v", {accelKey: 1}); + ok(onpaste_fired, "paste event firing on DOM element"); +}); + +add_task(async function test_dom_oncopy_abort() { + try { + await reset(); + } catch (e) { + ok(false, `Failed to reset (${e.toString()})`); + return; + } + + // Setup an oncopy event handler that aborts the copy, and fire the copy + // event. Ensure that the event handler was fired, and the clipboard + // contents have not been modified. + selectContentDiv(); + var oncopy_fired = false; + content.oncopy = function() { oncopy_fired = true; return false; }; + try { + await wontPutOnClipboard("CONTENT TEXT", () => { + synthesizeKey("c", {accelKey: 1}); + }, "aborted copy on DOM element did not modify clipboard"); + ok(oncopy_fired, "copy event (to-be-cancelled) firing on DOM element"); + } catch (e) { + ok(false, e.toString()); + } +}); + +add_task(async function test_input_oncopy() { + try { + await reset(); + } catch (e) { + ok(false, `Failed to reset (${e.toString()})`); + return; + } + + // Setup an oncopy event handler, fire copy. Ensure that the event + // handler was called, and the clipboard contents have been set to 'PUT TE', + // which is the part that is selected below. + selectContentInput(); + contentInput.focus(); + contentInput.setSelectionRange(2, 8); + + let oncopy_fired = false; + let onbeforeinput_fired = false; + let oninput_fired = false; + contentInput.oncopy = () => { oncopy_fired = true; }; + contentInput.onbeforeinput = () => { onbeforeinput = true; }; + contentInput.oninput = () => { oninput_fired = true; }; + try { + await putOnClipboard("PUT TE", () => { + synthesizeKey("c", {accelKey: 1}); + }, "copy on plaintext editor set clipboard correctly"); + ok(oncopy_fired, "copy event firing on plaintext editor"); + ok(!onbeforeinput_fired, "beforeinput event shouldn't be fired on plaintext editor by copy"); + ok(!oninput_fired, "input event shouldn't be fired on plaintext editor by copy"); + } catch (e) { + ok(false, e.toString()); + } +}); + +add_task(async function test_input_oncut() { + try { + await reset(); + } catch (e) { + ok(false, `Failed to reset (${e.toString()})`); + return; + } + + // Setup an oncut event handler, and fire cut. Ensure that the event + // handler was fired, the clipboard contains the INPUT TEXT, and + // that the input itself is empty. + selectContentInput(); + let oncut_fired = false; + let beforeInputEvents = []; + let inputEvents = []; + contentInput.oncut = () => { oncut_fired = true; }; + contentInput.onbeforeinput = (aEvent) => { beforeInputEvents.push(aEvent); } + contentInput.oninput = (aEvent) => { inputEvents.push(aEvent); } + try { + await putOnClipboard("INPUT TEXT", () => { + synthesizeKey("x", {accelKey: 1}); + }, "cut on plaintext editor set clipboard correctly"); + ok(oncut_fired, "cut event firing on plaintext editor"); + is(beforeInputEvents.length, 1, '"beforeinput" event should be fired once by cut'); + if (beforeInputEvents.length) { + is(beforeInputEvents[0].inputType, "deleteByCut", '"inputType" of "beforeinput" event should be "deleteByCut"'); + is(beforeInputEvents[0].cancelable, true, '"beforeinput" event for "deleteByCut" should be cancelable'); + is(beforeInputEvents[0].data, null, '"data" of "beforeinput" event for "deleteByCut" should be null'); + is(beforeInputEvents[0].dataTransfer, null, '"dataTransfer" of "beforeinput" event for "deleteByCut" should be null'); + is(beforeInputEvents[0].getTargetRanges().length, 0, 'getTargetRanges() of "beforeinput" event for "deleteByCut" should return empty array'); + } + is(inputEvents.length, 1, '"input" event should be fired once by cut'); + if (inputEvents.length) { + is(inputEvents[0].inputType, "deleteByCut", '"inputType" of "input" event should be "deleteByCut"'); + is(inputEvents[0].cancelable, false, '"input" event for "deleteByCut" should not be cancelable'); + is(inputEvents[0].data, null, '"data" of "input" event for "deleteByCut" should be null'); + is(inputEvents[0].dataTransfer, null, '"dataTransfer" of "input" event for "deleteByCut" should be null'); + is(inputEvents[0].getTargetRanges().length, 0, 'getTargetRanges() of "input" event for "deleteByCut" should return empty array'); + } + is(contentInput.value, "", + "cut on plaintext editor emptied editor"); + } catch (e) { + ok(false, e.toString()); + } +}); + +add_task(async function test_input_onpaste() { + try { + await reset(); + } catch (e) { + ok(false, `Failed to reset (${e.toString()})`); + return; + } + + // Setup an onpaste event handler, and fire paste. Ensure that the event + // handler was fired, the clipboard contents didn't change, and that the + // input value did change (ie. paste succeeded). + selectContentInput(); + let onpaste_fired = false; + let beforeInputEvents = []; + let inputEvents = []; + contentInput.onpaste = () => { onpaste_fired = true; }; + contentInput.onbeforeinput = (aEvent) => { beforeInputEvents.push(aEvent); } + contentInput.oninput = (aEvent) => { inputEvents.push(aEvent); } + + synthesizeKey("v", {accelKey: 1}); + ok(onpaste_fired, "paste event firing on plaintext editor"); + is(getClipboardText(), clipboardInitialValue, + "paste on plaintext editor did not modify clipboard contents"); + is(beforeInputEvents.length, 1, '"beforeinput" event should be fired once by paste'); + if (beforeInputEvents.length) { + is(beforeInputEvents[0].inputType, "insertFromPaste", '"inputType" of "beforeinput" event should be "insertFromPaste"'); + is(beforeInputEvents[0].cancelable, true, '"beforeinput" event for "insertFromPaste" should be cancelable'); + is(beforeInputEvents[0].data, clipboardInitialValue, `"data" of "beforeinput" event for "insertFromPaste" should be "${clipboardInitialValue}"`); + is(beforeInputEvents[0].dataTransfer, null, '"dataTransfer" of "beforeinput" event for "insertFromPaste" should be null'); + is(beforeInputEvents[0].getTargetRanges().length, 0, 'getTargetRanges() of "beforeinput" event for "insertFromPaste" should return empty array'); + } + is(inputEvents.length, 1, '"input" event should be fired once by paste'); + if (inputEvents.length) { + is(inputEvents[0].inputType, "insertFromPaste", '"inputType" of "input" event should be "insertFromPaste"'); + is(inputEvents[0].cancelable, false, '"input" event for "insertFromPaste" should not be cancelable'); + is(inputEvents[0].data, clipboardInitialValue, `"data" of "input" event for "insertFromPaste" should be "${clipboardInitialValue}"`); + is(inputEvents[0].dataTransfer, null, '"dataTransfer" of "input" event for "insertFromPaste" should be null'); + is(inputEvents[0].getTargetRanges().length, 0, 'getTargetRanges() of "input" event for "insertFromPaste" should return empty array'); + } + is(contentInput.value, clipboardInitialValue, + "paste on plaintext editor did modify editor value"); +}); + +add_task(async function test_input_oncopy_abort() { + try { + await reset(); + } catch (e) { + ok(false, `Failed to reset (${e.toString()})`); + return; + } + + // Setup an oncopy event handler, fire copy. Ensure that the event + // handler was called, and that the clipboard value did NOT change. + selectContentInput(); + let oncopy_fired = false; + contentInput.oncopy = () => { oncopy_fired = true; return false; }; + contentInput.onbeforeinput = () => { + ok(false, '"beforeinput" event should not be fired by copy but canceled'); + }; + contentInput.oninput = function() { + ok(false, '"input" event should not be fired by copy but canceled'); + }; + try { + await wontPutOnClipboard("CONTENT TEXT", () => { + synthesizeKey("c", {accelKey: 1}); + }, "aborted copy on plaintext editor did not modify clipboard"); + ok(oncopy_fired, "copy event (to-be-cancelled) firing on plaintext editor"); + } catch (e) { + ok(false, e.toString()); + } +}); + +add_task(async function test_input_oncut_abort() { + try { + await reset(); + } catch (e) { + ok(false, `Failed to reset (${e.toString()})`); + return; + } + + // Setup an oncut event handler, and fire cut. Ensure that the event + // handler was fired, the clipboard contains the INPUT TEXT, and + // that the input itself is empty. + selectContentInput(); + let oncut_fired = false; + contentInput.oncut = () => { oncut_fired = true; return false; }; + contentInput.onbeforeinput = () => { + ok(false, '"beforeinput" event should not be fired by cut but canceled by "cut" event listener'); + }; + contentInput.oninput = () => { + ok(false, '"input" event should not be fired by cut but canceled by "cut" event listener'); + }; + try { + await wontPutOnClipboard("CONTENT TEXT", () => { + synthesizeKey("x", {accelKey: 1}); + }, "aborted cut on plaintext editor did not modify clipboard"); + ok(oncut_fired, "cut event (to-be-cancelled) firing on plaintext editor"); + is(contentInput.value, "INPUT TEXT", + "aborted cut on plaintext editor did not modify editor contents"); + } catch (e) { + ok(false, e.toString()); + } +}); + +add_task(async function test_input_oncut_beforeinput_abort() { + try { + await reset(); + } catch (e) { + ok(false, `Failed to reset (${e.toString()})`); + return; + } + + // Setup an oncut event handler, and fire cut. Ensure that the event + // handler was fired, the clipboard contains the INPUT TEXT, and + // that the input itself is empty. + selectContentInput(); + let oncut_fired = false; + let beforeInputEvents = []; + let inputEvents = []; + contentInput.oncut = () => { oncut_fired = true; }; + contentInput.onbeforeinput = (aEvent) => { beforeInputEvents.push(aEvent); aEvent.preventDefault(); } + contentInput.oninput = (aEvent) => { inputEvents.push(aEvent); } + try { + await putOnClipboard("INPUT TEXT", () => { + synthesizeKey("x", {accelKey: 1}); + }, "cut on plaintext editor set clipboard correctly"); + ok(oncut_fired, "cut event firing on plaintext editor"); + is(beforeInputEvents.length, 1, '"beforeinput" event should be fired once by cut'); + if (beforeInputEvents.length) { + is(beforeInputEvents[0].inputType, "deleteByCut", '"inputType" of "beforeinput" event should be "deleteByCut"'); + is(beforeInputEvents[0].cancelable, true, '"beforeinput" event for "deleteByCut" should be cancelable'); + is(beforeInputEvents[0].data, null, '"data" of "beforeinput" event for "deleteByCut" should be null'); + is(beforeInputEvents[0].dataTransfer, null, '"dataTransfer" of "beforeinput" event for "deleteByCut" should be null'); + is(beforeInputEvents[0].getTargetRanges().length, 0, 'getTargetRanges() of "beforeinput" event for "deleteByCut" should return empty array'); + } + is(inputEvents.length, 0, '"input" event should not be fired by cut if "beforeinput" event is canceled'); + is(contentInput.value, "INPUT TEXT", + 'cut on plaintext editor should not change editor since "beforeinput" event was canceled'); + } catch (e) { + ok(false, e.toString()); + } +}); + +add_task(async function test_input_onpaste_abort() { + try { + await reset(); + } catch (e) { + ok(false, `Failed to reset (${e.toString()})`); + return; + } + + // Setup an onpaste event handler, and fire paste. Ensure that the event + // handler was fired, the clipboard contents didn't change, and that the + // input value did change (ie. paste succeeded). + selectContentInput(); + let onpaste_fired = false; + contentInput.onpaste = () => { onpaste_fired = true; return false; }; + contentInput.onbeforeinput = () => { + ok(false, '"beforeinput" event should not be fired by paste but canceled'); + }; + contentInput.oninput = () => { + ok(false, '"input" event should not be fired by paste but canceled'); + }; + synthesizeKey("v", {accelKey: 1}); + ok(onpaste_fired, + "paste event (to-be-cancelled) firing on plaintext editor"); + is(getClipboardText(), clipboardInitialValue, + "aborted paste on plaintext editor did not modify clipboard"); + is(contentInput.value, "INPUT TEXT", + "aborted paste on plaintext editor did not modify modified editor value"); +}); + +add_task(async function test_input_onpaste_beforeinput_abort() { + try { + await reset(); + } catch (e) { + ok(false, `Failed to reset (${e.toString()})`); + return; + } + + // Setup an onpaste event handler, and fire paste. Ensure that the event + // handler was fired, the clipboard contents didn't change, and that the + // input value did change (ie. paste succeeded). + selectContentInput(); + let onpaste_fired = false; + let beforeInputEvents = []; + let inputEvents = []; + contentInput.onpaste = () => { onpaste_fired = true; }; + contentInput.onbeforeinput = (aEvent) => { beforeInputEvents.push(aEvent); aEvent.preventDefault(); } + contentInput.oninput = (aEvent) => { inputEvents.push(aEvent); } + + synthesizeKey("v", {accelKey: 1}); + ok(onpaste_fired, "paste event firing on plaintext editor"); + is(getClipboardText(), clipboardInitialValue, + "paste on plaintext editor did not modify clipboard contents"); + is(beforeInputEvents.length, 1, '"beforeinput" event should be fired once by paste'); + if (beforeInputEvents.length) { + is(beforeInputEvents[0].inputType, "insertFromPaste", '"inputType" of "beforeinput" event should be "insertFromPaste"'); + is(beforeInputEvents[0].cancelable, true, '"beforeinput" event for "insertFromPaste" should be cancelable'); + is(beforeInputEvents[0].data, clipboardInitialValue, `"data" of "beforeinput" event for "insertFromPaste" should be "${clipboardInitialValue}"`); + is(beforeInputEvents[0].dataTransfer, null, '"dataTransfer" of "beforeinput" event for "insertFromPaste" should be null'); + is(beforeInputEvents[0].getTargetRanges().length, 0, 'getTargetRanges() of "beforeinput" event for "insertFromPaste" should return empty array'); + } + is(inputEvents.length, 0, '"input" event should not be fired by paste when "beforeinput" is canceled'); + is(contentInput.value, "INPUT TEXT", + "paste on plaintext editor did modify editor value"); +}); + +add_task(async function test_input_cut_dataTransfer() { + try { + await reset(); + } catch (e) { + ok(false, `Failed to reset (${e.toString()})`); + return; + } + + // Cut using event.dataTransfer. The event is not cancelled so the default + // cut should occur + selectContentInput(); + contentInput.oncut = function(event) { + ok(event instanceof ClipboardEvent, "cut event is a ClipboardEvent"); + ok(event.clipboardData instanceof DataTransfer, "cut event dataTransfer is a DataTransfer"); + is(event.target, contentInput, "cut event target"); + is(SpecialPowers.wrap(event.clipboardData).mozItemCount, 0, "cut event mozItemCount"); + is(event.clipboardData.getData("text/plain"), "", "cut event getData"); + event.clipboardData.setData("text/plain", "This is some dataTransfer text"); + cachedCutData = event.clipboardData; + }; + try { + await putOnClipboard("INPUT TEXT", () => { + synthesizeKey("x", {accelKey: 1}); + }, "cut using dataTransfer on plaintext editor set clipboard correctly"); + is(contentInput.value, "", + "cut using dataTransfer on plaintext editor cleared input"); + } catch (e) { + ok(false, e.toString()); + } +}); + +add_task(async function test_input_cut_abort_dataTransfer() { + try { + await reset(); + } catch (e) { + ok(false, `Failed to reset (${e.toString()})`); + return; + } + + // Cut using event.dataTransfer but cancel the event. The data should be + // put on the clipboard but since we don't modify the input value, the input + // should have the same value. + selectContentInput(); + contentInput.oncut = function(event) { + event.clipboardData.setData("text/plain", "Cut dataTransfer text"); + return false; + }; + try { + await putOnClipboard("Cut dataTransfer text", () => { + synthesizeKey("x", {accelKey: 1}); + }, "aborted cut using dataTransfer on plaintext editor set clipboard correctly"); + is(contentInput.value, "INPUT TEXT", + "aborted cut using dataTransfer on plaintext editor did not modify input"); + } catch (e) { + ok(false, e.toString()); + } +}); + +add_task(async function test_input_copy_dataTransfer() { + try { + await reset(); + } catch (e) { + ok(false, `Failed to reset (${e.toString()})`); + return; + } + + // Copy using event.dataTransfer + selectContentInput(); + contentInput.oncopy = function(event) { + ok(event instanceof ClipboardEvent, "copy event is a ClipboardEvent"); + ok(event.clipboardData instanceof DataTransfer, "copy event dataTransfer is a DataTransfer"); + is(event.target, contentInput, "copy event target"); + is(SpecialPowers.wrap(event.clipboardData).mozItemCount, 0, "copy event mozItemCount"); + is(event.clipboardData.getData("text/plain"), "", "copy event getData"); + event.clipboardData.setData("text/plain", "Copied dataTransfer text"); + cachedCopyData = event.clipboardData; + }; + try { + await putOnClipboard("INPUT TEXT", () => { + synthesizeKey("c", {accelKey: 1}); + }, "copy using dataTransfer on plaintext editor set clipboard correctly"); + is(contentInput.value, "INPUT TEXT", + "copy using dataTransfer on plaintext editor did not modify input"); + } catch (e) { + ok(false, e.toString()); + } +}); + +add_task(async function test_input_copy_abort_dataTransfer() { + try { + await reset(); + } catch (e) { + ok(false, `Failed to reset (${e.toString()})`); + return; + } + + // Copy using event.dataTransfer but cancel the event. + selectContentInput(); + contentInput.oncopy = function(event) { + event.clipboardData.setData("text/plain", "Copy dataTransfer text"); + return false; + }; + try { + await putOnClipboard("Copy dataTransfer text", () => { + synthesizeKey("c", {accelKey: 1}); + }, "aborted copy using dataTransfer on plaintext editor set clipboard correctly"); + is(contentInput.value, "INPUT TEXT", + "aborted copy using dataTransfer on plaintext editor did not modify input"); + } catch (e) { + ok(false, e.toString()); + } +}); + +add_task(async function test_input_paste_dataTransfer() { + try { + await reset(); + } catch (e) { + ok(false, `Failed to reset (${e.toString()})`); + return; + } + + // Paste using event.dataTransfer + selectContentInput(); + contentInput.onpaste = function(event) { + ok(event instanceof ClipboardEvent, "paste event is an ClipboardEvent"); + ok(event.clipboardData instanceof DataTransfer, "paste event dataTransfer is a DataTransfer"); + is(event.target, contentInput, "paste event target"); + is(SpecialPowers.wrap(event.clipboardData).mozItemCount, 1, "paste event mozItemCount"); + is(event.clipboardData.getData("text/plain"), clipboardInitialValue, "paste event getData"); + cachedPasteData = event.clipboardData; + }; + synthesizeKey("v", {accelKey: 1}); + is(getClipboardText(), clipboardInitialValue, + "paste using dataTransfer on plaintext editor did not modify clipboard contents"); + is(contentInput.value, clipboardInitialValue, + "paste using dataTransfer on plaintext editor modified input"); +}); + +add_task(async function test_input_paste_abort_dataTransfer() { + try { + await reset(); + } catch (e) { + ok(false, `Failed to reset (${e.toString()})`); + return; + } + + // Paste using event.dataTransfer but cancel the event + selectContentInput(); + contentInput.onpaste = function(event) { + is(event.clipboardData.getData("text/plain"), clipboardInitialValue, "get data on aborted paste"); + contentInput.value = "Alternate Paste"; + return false; + }; + synthesizeKey("v", {accelKey: 1}); + is(getClipboardText(), clipboardInitialValue, + "aborted paste using dataTransfer on plaintext editor did not modify clipboard contents"); + is(contentInput.value, "Alternate Paste", + "aborted paste using dataTransfer on plaintext editor modified input"); +}); + +add_task(async function test_input_copypaste_dataTransfer_multiple() { + try { + await reset(); + } catch (e) { + ok(false, `Failed to reset (${e.toString()})`); + return; + } + + // Cut several types of data and paste it again + contentInput.value = "This is a line of text"; + contentInput.oncopy = function(event) { + var cd = event.clipboardData; + cd.setData("text/plain", "would be a phrase"); + + var exh = false; + try { SpecialPowers.wrap(cd).mozSetDataAt("text/plain", "Text", 1); } catch (ex) { exh = true; } + ok(exh, "exception occured mozSetDataAt 1"); + exh = false; + try { SpecialPowers.wrap(cd).mozTypesAt(1); } catch (ex) { exh = true; } + ok(exh, "exception occured mozTypesAt 1"); + exh = false; + try { SpecialPowers.wrap(cd).mozGetDataAt("text/plain", 1); } catch (ex) { exh = true; } + ok(exh, "exception occured mozGetDataAt 1"); + exh = false; + try { cd.mozClearDataAt("text/plain", 1); } catch (ex) { exh = true; } + ok(exh, "exception occured mozClearDataAt 1"); + + cd.setData("text/x-moz-url", "http://www.mozilla.org"); + SpecialPowers.wrap(cd).mozSetDataAt("text/x-custom", "Custom Text with \u0000 null", 0); + is(SpecialPowers.wrap(cd).mozItemCount, 1, "mozItemCount after set multiple types"); + return false; + }; + + try { + selectContentInput(); + + await putOnClipboard("would be a phrase", () => { + synthesizeKey("c", {accelKey: 1}); + }, "copy multiple types text"); + contentInput.oncopy = null; // XXX Not sure why this is required... + } catch (e) { + ok(false, e.toString()); + return; + } + + contentInput.setSelectionRange(5, 14); + + contentInput.onpaste = function(event) { + var cd = event.clipboardData; + is(SpecialPowers.wrap(cd).mozItemCount, 1, "paste after copy multiple types mozItemCount"); + is(cd.getData("text/plain"), "would be a phrase", "paste text/plain multiple types"); + + // Firefox for Android's clipboard code doesn't handle x-moz-url. Therefore + // disabling the following test. Enable this once bug #840101 is fixed. + if (!navigator.appVersion.includes("Android")) { + is(cd.getData("text/x-moz-url"), "http://www.mozilla.org", "paste text/x-moz-url multiple types"); + is(cd.getData("text/x-custom"), "Custom Text with \u0000 null", "paste text/custom multiple types"); + } else { + is(cd.getData("text/x-custom"), "", "paste text/custom multiple types"); + } + + is(cd.getData("application/x-moz-custom-clipdata"), "", "application/x-moz-custom-clipdata is not present"); + + exh = false; + try { cd.setData("application/x-moz-custom-clipdata", "Some Data"); } catch (ex) { exh = true; } + ok(exh, "exception occured setData with application/x-moz-custom-clipdata"); + + exh = false; + try { cd.setData("text/plain", "Text on Paste"); } catch (ex) { exh = true; } + ok(exh, "exception occured setData on paste"); + + is(cd.getData("text/plain"), "would be a phrase", "text/plain data unchanged"); + }; + synthesizeKey("v", {accelKey: 1}); + is(contentInput.value, "This would be a phrase of text", + "default paste after copy multiple types"); +}); + +add_task(async function test_input_copy_button_dataTransfer() { + try { + await reset(); + } catch (e) { + ok(false, `Failed to reset (${e.toString()})`); + return; + } + + // Copy using event.dataTransfer when a button is focused. + var button = document.getElementById("button"); + button.focus(); + button.oncopy = function(event) { + ok(false, "should not be firing copy event on button"); + return false; + }; + try { + // copy should not occur here because buttons don't have any controller + // for the copy command + await wontPutOnClipboard("", () => { + synthesizeKey("c", {accelKey: 1}); + }, "Accel-C on the `<button>` shouldn't modify the clipboard data"); + ok(true, "Accel-C on the <button> shouldn't modify the clipboard data"); + } catch (e) { + ok(false, e.toString()); + } + + try { + selectContentDiv(); + + await putOnClipboard("CONTENT TEXT", () => { + synthesizeKey("c", {accelKey: 1}); + }, "Accel-C with selecting the content <div> should modify the clipboard data with text in it"); + } catch (e) { + ok(false, e.toString()); + } +}); + +add_task(async function test_eventspref_disabled() { + try { + await reset(); + } catch (e) { + ok(false, `Failed to reset (${e.toString()})`); + return; + } + + // Disable clipboard events + try { + await SpecialPowers.pushPrefEnv({ + set: [['dom.event.clipboardevents.enabled', false]] + }); + + var event_fired = false; + var input_data = undefined; + contentInput.oncut = function() { event_fired = true; }; + contentInput.oncopy = function() { event_fired = true; }; + contentInput.onpaste = function() { event_fired = true; }; + contentInput.oninput = function(event) { input_data = event.data; }; + + selectContentInput(); + contentInput.setSelectionRange(1, 4); + + await putOnClipboard("NPU", () => { + synthesizeKey("x", {accelKey: 1}); + }, "cut changed clipboard when preference is disabled"); + is(contentInput.value, "IT TEXT", "cut changed text when preference is disabled"); + ok(!event_fired, "cut event did not fire when preference is disabled"); + is(input_data, null, "cut should cause input event whose data value is null"); + + event_fired = false; + input_data = undefined; + contentInput.setSelectionRange(3, 6); + await putOnClipboard("TEX", () => { + synthesizeKey("c", {accelKey: 1}); + }, "copy changed clipboard when preference is disabled"); + ok(!event_fired, "copy event did not fire when preference is disabled") + is(input_data, undefined, "copy shouldn't cause input event"); + + event_fired = false; + contentInput.setSelectionRange(0, 2); + synthesizeKey("v", {accelKey: 1}); + is(contentInput.value, "TEX TEXT", "paste changed text when preference is disabled"); + ok(!event_fired, "paste event did not fire when preference is disabled"); + is(input_data, "", + "paste should cause input event but whose data value should be empty string if clipboard event is disabled"); + } catch (e) { + ok(false, e.toString()); + } finally { + await SpecialPowers.popPrefEnv(); + } +}); + +let expectedData = []; + +// Check to make that synthetic events do not change the clipboard +add_task(async function test_synthetic_events() { + try { + await reset(); + } catch (e) { + ok(false, `Failed to reset (${e.toString()})`); + return; + } + + let syntheticSpot = document.getElementById("syntheticSpot"); + + // No dataType specified + let event = new ClipboardEvent("cut", { data: "something" }); + expectedData = { type: "cut", data: null } + compareSynthetic(event, "before"); + syntheticSpot.dispatchEvent(event); + ok(expectedData.eventFired, "cut event fired"); + compareSynthetic(event, "after"); + + event = new ClipboardEvent("cut", { dataType: "text/plain", data: "something" }); + expectedData = { type: "cut", dataType: "text/plain", data: "something" } + compareSynthetic(event, "before"); + syntheticSpot.dispatchEvent(event); + ok(expectedData.eventFired, "cut event fired"); + compareSynthetic(event, "after"); + + event = new ClipboardEvent("copy", { dataType: "text/plain", data: "something" }); + expectedData = { type: "copy", dataType: "text/plain", data: "something" } + compareSynthetic(event, "before"); + syntheticSpot.dispatchEvent(event); + ok(expectedData.eventFired, "copy event fired"); + compareSynthetic(event, "after"); + + event = new ClipboardEvent("copy", { dataType: "text/plain" }); + expectedData = { type: "copy", dataType: "text/plain", data: "" } + compareSynthetic(event, "before"); + syntheticSpot.dispatchEvent(event); + ok(expectedData.eventFired, "copy event fired"); + compareSynthetic(event, "after"); + + event = new ClipboardEvent("paste", { dataType: "text/plain", data: "something" }); + expectedData = { type: "paste", dataType: "text/plain", data: "something" } + compareSynthetic(event, "before"); + syntheticSpot.dispatchEvent(event); + ok(expectedData.eventFired, "paste event fired"); + compareSynthetic(event, "after"); + + event = new ClipboardEvent("paste", { dataType: "application/unknown", data: "unknown" }); + expectedData = { type: "paste", dataType: "application/unknown", data: "unknown" } + compareSynthetic(event, "before"); + syntheticSpot.dispatchEvent(event); + ok(expectedData.eventFired, "paste event fired"); + compareSynthetic(event, "after"); +}); + +function compareSynthetic(event, eventtype) { + let step = (eventtype == "cut" || eventtype == "copy" || eventtype == "paste") ? "during" : eventtype; + if (step == "during") { + is(eventtype, expectedData.type, "synthetic " + eventtype + " event fired"); + } + + ok(event.clipboardData instanceof DataTransfer, "clipboardData is assigned"); + + is(event.type, expectedData.type, "synthetic " + eventtype + " event type"); + if (expectedData.data === null) { + is(SpecialPowers.wrap(event.clipboardData).mozItemCount, 0, "synthetic " + eventtype + " empty data"); + } + else { + is(SpecialPowers.wrap(event.clipboardData).mozItemCount, 1, "synthetic " + eventtype + " item count"); + is(event.clipboardData.types.length, 1, "synthetic " + eventtype + " types length"); + is(event.clipboardData.getData(expectedData.dataType), expectedData.data, + "synthetic " + eventtype + " data"); + } + + is(getClipboardText(), "empty", "event does not change the clipboard " + step + " dispatch"); + + if (step == "during") { + expectedData.eventFired = true; + } +} + +async function checkCachedDataTransfer(cd, eventtype) { + var testprefix = "cached " + eventtype + " dataTransfer"; + + try { + await putOnClipboard("Some Clipboard Text", () => { setClipboardText("Some Clipboard Text") }, + "change clipboard outside of event"); + } catch (e) { + ok(false, e.toString()); + return; + } + + var oldtext = cd.getData("text/plain"); + ok(!oldtext, "clipboard get using " + testprefix); + + try { + SpecialPowers.wrap(cd).mozSetDataAt("text/plain", "Test Cache Data", 0); + } catch (ex) {} + ok(!cd.getData("text/plain"), "clipboard set using " + testprefix); + + is(getClipboardText(), "Some Clipboard Text", "clipboard not changed using " + testprefix); + + try { + cd.mozClearDataAt("text/plain", 0); + } catch (ex) {} + ok(!cd.getData("text/plain"), "clipboard clear using " + testprefix); + + is(getClipboardText(), "Some Clipboard Text", "clipboard not changed using " + testprefix); +} + +add_task(async function test_modify_datatransfer_outofevent() { + try { + await reset(); + } catch (e) { + ok(false, `Failed to reset (${e.toString()})`); + return; + } + + // Check if the cached clipboard data can be accessed or modified + // and whether it modifies the real clipboard + + // XXX Depends on test_input_cut_dataTransfer() + if (cachedCutData) { + await checkCachedDataTransfer(cachedCutData, "cut"); + } else { + todo(false, "test_input_cut_dataTransfer must have been failed, skipping tests with its dataTransfer"); + } + // XXX Depends on test_input_copy_dataTransfer() + if (cachedCopyData) { + await checkCachedDataTransfer(cachedCopyData, "copy"); + } else { + todo(false, "test_input_copy_dataTransfer must have been failed, skipping tests with its dataTransfer"); + } + // XXX Depends on test_input_paste_dataTransfer() + if (cachedPasteData) { + await checkCachedDataTransfer(cachedPasteData, "paste"); + } else { + todo(false, "test_input_paste_dataTransfer must have been failed, skipping tests with its dataTransfer"); + } +}); + +add_task(async function test_input_cut_disallowed_types_dataTransfer() { + try { + await reset(); + } catch (e) { + ok(false, `Failed to reset (${e.toString()})`); + return; + } + + selectContentInput(); + let oncutExecuted = false; + contentInput.oncut = function(event) { + // Setting an arbitrary type should be OK + try { + event.clipboardData.setData("apple/cider", "Anything your heart desires"); + ok(true, "We should have successfully executed the setData call"); + } catch(e) { + ok(false, "We should not have gotten an exception for trying to set that data"); + } + + // Unless that type happens to be application/x-moz-custom-clipdata + try { + event.clipboardData.setData("application/x-moz-custom-clipdata", "Anything your heart desires"); + ok(false, "We should not have successfully executed the setData call"); + } catch(e) { + is(e.name, "NotSupportedError", + "We should have gotten an NotSupportedError exception for trying to set that data"); + } + oncutExecuted = true; + }; + + try { + await putOnClipboard("INPUT TEXT", () => { + synthesizeKey("x", {accelKey: 1}); + }, "The oncut handler should have been executed data"); + ok(oncutExecuted, "The oncut handler should have been executed"); + } catch (e) { + ok(false, "Failed to copy the data given by the oncut"); + } +}); + +// Try copying an image to the clipboard and make sure that it looks correct when pasting it. +add_task(async function test_image_dataTransfer() { + // cmd_copyImageContents errors on Android (bug 1299578). + if (navigator.userAgent.includes("Android")) { + return; + } + + try { + await reset(); + } catch (e) { + ok(false, `Failed to reset (${e.toString()})`); + return; + } + + // Copy the image's data to the clipboard + try { + await putOnClipboard("", () => { + SpecialPowers.setCommandNode(window, document.getElementById("image")); + SpecialPowers.doCommand(window, "cmd_copyImageContents"); + }, "copy changed clipboard when preference is disabled"); + } catch (e) { + ok(false, e.toString()); + } + + let onpasteCalled = false; + document.onpaste = function(event) { + ok(event instanceof ClipboardEvent, "paste event is an ClipboardEvent"); + ok(event.clipboardData instanceof DataTransfer, "paste event dataTransfer is a DataTransfer"); + let items = event.clipboardData.items; + let foundData = false; + for (let i = 0; i < items.length; ++i) { + if (items[i].kind == "file") { + foundData = true; + is(items[i].type, "image/png", "The type of the data must be image/png"); + is(items[i].getAsFile().type, "image/png", "The attached file must be image/png"); + } + } + ok(foundData, "Should have found a file entry in the DataTransferItemList"); + let files = event.clipboardData.files; + is(files.length, 1, "There should only be one file on the DataTransfer"); + is(files[0].type, "image/png", "The only file should be an image/png"); + onpasteCalled = true; + } + + synthesizeKey("v", {accelKey: 1}); + ok(onpasteCalled, "The paste event listener must have been called"); +}); + +add_task(async function test_event_target() { + try { + await reset(); + } catch (e) { + ok(false, `Failed to reset (${e.toString()})`); + return; + } + + let copyTarget = null; + addEventListenerTo(document, "copy", (event) => { copyTarget = event.target; }, {once: true}); + + if (document.activeElement) { + document.activeElement.blur(); + } + + let selection = document.getSelection(); + selection.setBaseAndExtent(content.firstChild, "CONTENT ".length, + content.firstChild, "CONTENT TEXT".length); + + try { + await putOnClipboard("TEXT", () => { + synthesizeKey("c", {accelKey: 1}); + }, "copy text from non-editable element"); + } catch (e) { + ok(false, e.toString()); + } + + is(copyTarget.getAttribute("id"), "content", "Copy event's target should be always an element"); + + // Create a contenteditable element to check complicated event target. + contenteditableContainer.innerHTML = '<div contenteditable><p id="p1">foo</p><p id="p2">bar</p></div>'; + contenteditableContainer.firstChild.focus(); + + let p1 = document.getElementById("p1"); + let p2 = document.getElementById("p2"); + selection.setBaseAndExtent(p1.firstChild, 1, p2.firstChild, 1); + + let pasteTarget = null; + let pasteEventCount = 0; + function pasteEventLogger(event) { + pasteTarget = event.target; + pasteEventCount++; + } + addEventListenerTo(document, "paste", pasteEventLogger); + synthesizeKey("v", {accelKey: 1}); + is(pasteTarget.getAttribute("id"), "p1", + "'paste' event's target should be always an element which includes start container of the first Selection range"); + is(pasteEventCount, 1, + "'paste' event should be fired only once when Accel+'v' is pressed"); +}); + +add_task(async function test_paste_event_for_middle_click_without_HTMLEditor() { + await SpecialPowers.pushPrefEnv({"set": [["middlemouse.paste", true], + ["middlemouse.contentLoadURL", false]]}); + + try { + await reset(); + } catch (e) { + ok(false, `Failed to reset (${e.toString()})`); + return; + } + + contenteditableContainer.innerHTML = '<div id="non-editable-target">non-editable</div>'; + let noneditableDiv = document.getElementById("non-editable-target"); + + ok(!getHTMLEditor(), "There should not be HTMLEditor"); + + let selection = document.getSelection(); + selection.setBaseAndExtent(content.firstChild, 0, + content.firstChild, "CONTENT".length); + + try { + await putOnClipboard("CONTENT", () => { + synthesizeKey("c", {accelKey: 1}); + }, "copy text from non-editable element"); + } catch (e) { + ok(false, e.toString()); + return; + } + + let auxclickFired = false; + function onAuxClick(event) { + auxclickFired = true; + } + addEventListenerTo(document, "auxclick", onAuxClick); + + let pasteEventCount = 0; + function onPaste(event) { + pasteEventCount++; + ok(auxclickFired, "'auxclick' event should be fired before 'paste' event"); + is(event.target, noneditableDiv, + "'paste' event should be fired on the clicked element"); + } + addEventListenerTo(document, "paste", onPaste); + + synthesizeMouseAtCenter(noneditableDiv, {button: 1}); + is(pasteEventCount, 1, "'paste' event should be fired just once"); + + pasteEventCount = 0; + auxclickFired = false; + addEventListenerTo(document, "mouseup", (event) => { event.preventDefault(); }, {once: true}); + synthesizeMouseAtCenter(noneditableDiv, {button: 1}); + is(pasteEventCount, 1, + "Even if 'mouseup' event is consumed, 'paste' event should be fired"); + + pasteEventCount = 0; + auxclickFired = false; + addEventListenerTo(document, "auxclick", (event) => { event.preventDefault(); }, {once: true, capture: true}); + synthesizeMouseAtCenter(noneditableDiv, {button: 1}); + ok(auxclickFired, "'auxclickFired' fired"); + is(pasteEventCount, 0, + "If 'auxclick' event is consumed at capturing phase at the document node, 'paste' event should not be fired"); + + pasteEventCount = 0; + auxclickFired = false; + addEventListenerTo(noneditableDiv, "auxclick", (event) => { event.preventDefault(); }, {once: true}); + synthesizeMouseAtCenter(noneditableDiv, {button: 1}); + ok(auxclickFired, "'auxclick' fired"); + is(pasteEventCount, 0, + "If 'auxclick' event listener is added to the click event target, 'paste' event should not be fired"); + + pasteEventCount = 0; + auxclickFired = false; + addEventListenerTo(document, "auxclick", (event) => { event.preventDefault(); }, {once: true}); + synthesizeMouseAtCenter(noneditableDiv, {button: 1}); + ok(auxclickFired, "'auxclick' fired"); + is(pasteEventCount, 0, + "If 'auxclick' event is consumed, 'paste' event should be not be fired"); +}); + +add_task(function cleaning_up() { + try { + disableNonTestMouseEvents(false); + } finally { + window.close(); + } +}); +</script> +</pre> +</body> +</html> diff --git a/dom/tests/mochitest/general/window_storagePermissions.html b/dom/tests/mochitest/general/window_storagePermissions.html new file mode 100644 index 0000000000..3bab23c13b --- /dev/null +++ b/dom/tests/mochitest/general/window_storagePermissions.html @@ -0,0 +1,38 @@ +<html xmlns="http://www.w3.org/1999/xhtml"> + <head> + <title>Storage Permission Restrictions</title> + <script type="text/javascript" src="storagePermissionsUtils.js"></script> + </head> + <body> + <iframe></iframe> + + <script type="text/javascript"> + +function ok(a, msg) { + opener.postMessage({type: "check", test: !!a, msg }, "*"); +} + +function is(a, b, msg) { + ok(a === b , msg); +} + +let init = false; +onmessage = e => { + if (!init) { + init = true; + + let runnableStr = `(() => {return (${e.data});})();`; + let runnable = eval(runnableStr); // eslint-disable-line no-eval + runnable.call(this).then(_ => { + opener.postMessage({ type: "finish" }, "*"); + }); + + return; + } + + parent.postMessage(e.data, "*"); +} + + </script> + </body> +</html> diff --git a/dom/tests/mochitest/general/workerStorageAllowed.js b/dom/tests/mochitest/general/workerStorageAllowed.js new file mode 100644 index 0000000000..89e0a4b9ce --- /dev/null +++ b/dom/tests/mochitest/general/workerStorageAllowed.js @@ -0,0 +1,78 @@ +// Unfortunately, workers can't share the code from storagePermissionsUtils. +// These are basic mechanisms for communicating to the test runner. + +function ok(condition, text) { + if (!condition) { + self.postMessage("FAILURE: " + text); + } else { + self.postMessage(text); + } +} + +function finishTest() { + self.postMessage("done"); + self.close(); +} + +// Workers don't have access to localstorage or sessionstorage +ok(typeof self.localStorage == "undefined", "localStorage should be undefined"); +ok( + typeof self.sessionStorage == "undefined", + "sessionStorage should be undefined" +); + +// Make sure that we can access indexedDB +try { + indexedDB; + ok(true, "WORKER getting indexedDB didn't throw"); +} catch (e) { + ok(false, "WORKER getting indexedDB should not throw"); +} + +// Make sure that we can access caches +try { + var promise = caches.keys(); + ok(true, "WORKER getting caches didn't throw"); + + promise.then( + function () { + ok(location.protocol == "https:", "WORKER The promise was not rejected"); + workerTest(); + }, + function () { + ok( + location.protocol !== "https:", + "WORKER The promise should not have been rejected" + ); + workerTest(); + } + ); +} catch (e) { + ok( + location.protocol !== "https:", + "WORKER getting caches should not have thrown" + ); + workerTest(); +} + +// Try to spawn an inner worker, and make sure that it can also access storage +function workerTest() { + if (location.hash == "#inner") { + // Don't recurse infinitely, if we are the inner worker, don't spawn another + finishTest(); + return; + } + // Create the inner worker, and listen for test messages from it + var worker = new Worker("workerStorageAllowed.js#inner"); + worker.addEventListener("message", function (e) { + if (e.data == "done") { + finishTest(); + return; + } + + ok( + !e.data.match(/^FAILURE/), + e.data + " (WORKER = workerStorageAllowed.js#inner)" + ); + }); +} diff --git a/dom/tests/mochitest/general/workerStoragePrevented.js b/dom/tests/mochitest/general/workerStoragePrevented.js new file mode 100644 index 0000000000..467cc09113 --- /dev/null +++ b/dom/tests/mochitest/general/workerStoragePrevented.js @@ -0,0 +1,75 @@ +// Unfortunately, workers can't share the code from storagePermissionsUtils. +// These are basic mechanisms for communicating to the test runner. + +function ok(condition, text) { + if (!condition) { + self.postMessage("FAILURE: " + text); + } else { + self.postMessage(text); + } +} + +function finishTest() { + self.postMessage("done"); + self.close(); +} + +// Workers don't have access to localstorage or sessionstorage +ok(typeof self.localStorage == "undefined", "localStorage should be undefined"); +ok( + typeof self.sessionStorage == "undefined", + "sessionStorage should be undefined" +); + +// Make sure that we can't access indexedDB +try { + indexedDB; + ok(false, "WORKER getting indexedDB should have thrown"); +} catch (e) { + ok(true, "WORKER getting indexedDB threw"); +} + +// Make sure that we can't access caches +try { + var promise = caches.keys(); + ok(true, "WORKER getting caches didn't throw"); + + promise.then( + function () { + ok(false, "WORKER The promise should have rejected"); + workerTest(); + }, + function () { + ok(true, "WORKER The promise was rejected"); + workerTest(); + } + ); +} catch (e) { + ok( + location.protocol !== "https:", + "WORKER getting caches should not have thrown" + ); + workerTest(); +} + +// Try to spawn an inner worker, and make sure that it also can't access storage +function workerTest() { + if (location.hash == "#inner") { + // Don't recurse infinitely, if we are the inner worker, don't spawn another + finishTest(); + return; + } + // Create the inner worker, and listen for test messages from it + var worker = new Worker("workerStoragePrevented.js#inner"); + worker.addEventListener("message", function (e) { + if (e.data == "done") { + finishTest(); + return; + } + + ok( + !e.data.match(/^FAILURE/), + e.data + " (WORKER = workerStoragePrevented.js#inner)" + ); + }); +} |