summaryrefslogtreecommitdiffstats
path: root/dom/tests/mochitest/general
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-28 14:29:10 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-28 14:29:10 +0000
commit2aa4a82499d4becd2284cdb482213d541b8804dd (patch)
treeb80bf8bf13c3766139fbacc530efd0dd9d54394c /dom/tests/mochitest/general
parentInitial commit. (diff)
downloadfirefox-2aa4a82499d4becd2284cdb482213d541b8804dd.tar.xz
firefox-2aa4a82499d4becd2284cdb482213d541b8804dd.zip
Adding upstream version 86.0.1.upstream/86.0.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'dom/tests/mochitest/general')
-rw-r--r--dom/tests/mochitest/general/497633.html35
-rw-r--r--dom/tests/mochitest/general/chrome.ini12
-rw-r--r--dom/tests/mochitest/general/cssA.css0
-rw-r--r--dom/tests/mochitest/general/cssB.css2
-rw-r--r--dom/tests/mochitest/general/cssC.css0
-rw-r--r--dom/tests/mochitest/general/emptyCssFile2.css1
-rw-r--r--dom/tests/mochitest/general/fail.pngbin0 -> 91 bytes
-rw-r--r--dom/tests/mochitest/general/file_bug628069.html16
-rw-r--r--dom/tests/mochitest/general/file_clonewrapper.html35
-rw-r--r--dom/tests/mochitest/general/file_domWindowUtils_scrollbarSize.html7
-rw-r--r--dom/tests/mochitest/general/file_focusrings.html5
-rw-r--r--dom/tests/mochitest/general/file_frameElementWrapping.html32
-rw-r--r--dom/tests/mochitest/general/file_moving_nodeList.html31
-rw-r--r--dom/tests/mochitest/general/file_moving_xhr.html28
-rw-r--r--dom/tests/mochitest/general/file_resource_timing_nocors.html190
-rw-r--r--dom/tests/mochitest/general/frameSelectEvents.html485
-rw-r--r--dom/tests/mochitest/general/frameStorageAllowed.html22
-rw-r--r--dom/tests/mochitest/general/frameStorageChrome.html20
-rw-r--r--dom/tests/mochitest/general/frameStorageNullprincipal.sjs33
-rw-r--r--dom/tests/mochitest/general/frameStoragePrevented.html47
-rw-r--r--dom/tests/mochitest/general/generateCss.sjs42
-rw-r--r--dom/tests/mochitest/general/historyframes.html179
-rw-r--r--dom/tests/mochitest/general/image_100.pngbin0 -> 91 bytes
-rw-r--r--dom/tests/mochitest/general/image_200.pngbin0 -> 100 bytes
-rw-r--r--dom/tests/mochitest/general/image_50.pngbin0 -> 85 bytes
-rw-r--r--dom/tests/mochitest/general/importsSameAndCrossOrigin.css3
-rw-r--r--dom/tests/mochitest/general/mochitest.ini139
-rw-r--r--dom/tests/mochitest/general/navigation_timing.html100
-rw-r--r--dom/tests/mochitest/general/pass.apngbin0 -> 188 bytes
-rw-r--r--dom/tests/mochitest/general/performance_timeline_main_test.html105
-rw-r--r--dom/tests/mochitest/general/res0.resource0
-rw-r--r--dom/tests/mochitest/general/res1.resource0
-rw-r--r--dom/tests/mochitest/general/res1.resource^headers^2
-rw-r--r--dom/tests/mochitest/general/res2.resource0
-rw-r--r--dom/tests/mochitest/general/res2.resource^headers^2
-rw-r--r--dom/tests/mochitest/general/res3.resource0
-rw-r--r--dom/tests/mochitest/general/res3.resource^headers^2
-rw-r--r--dom/tests/mochitest/general/res4.resource0
-rw-r--r--dom/tests/mochitest/general/res4.resource^headers^3
-rw-r--r--dom/tests/mochitest/general/res5.resource0
-rw-r--r--dom/tests/mochitest/general/res5.resource^headers^2
-rw-r--r--dom/tests/mochitest/general/res6.resource0
-rw-r--r--dom/tests/mochitest/general/res6.resource^headers^2
-rw-r--r--dom/tests/mochitest/general/res7.resource0
-rw-r--r--dom/tests/mochitest/general/res7.resource^headers^2
-rw-r--r--dom/tests/mochitest/general/res8.resource0
-rw-r--r--dom/tests/mochitest/general/res8.resource^headers^2
-rw-r--r--dom/tests/mochitest/general/resource_timing.js0
-rw-r--r--dom/tests/mochitest/general/resource_timing_cross_origin.html187
-rw-r--r--dom/tests/mochitest/general/resource_timing_iframe.html50
-rw-r--r--dom/tests/mochitest/general/resource_timing_main_test.html289
-rw-r--r--dom/tests/mochitest/general/resource_timing_nocors.html88
-rw-r--r--dom/tests/mochitest/general/start_historyframe.html1
-rw-r--r--dom/tests/mochitest/general/storagePermissionsUtils.js282
-rw-r--r--dom/tests/mochitest/general/test-data.json1
-rw-r--r--dom/tests/mochitest/general/test-data2.json1
-rw-r--r--dom/tests/mochitest/general/test_497898.html38
-rw-r--r--dom/tests/mochitest/general/test_CCW_optimization.html50
-rw-r--r--dom/tests/mochitest/general/test_DOMMatrix.html717
-rw-r--r--dom/tests/mochitest/general/test_WebKitCSSMatrix.html339
-rw-r--r--dom/tests/mochitest/general/test_bug1161721.html32
-rw-r--r--dom/tests/mochitest/general/test_bug1170911.html90
-rw-r--r--dom/tests/mochitest/general/test_bug1208217.html31
-rw-r--r--dom/tests/mochitest/general/test_bug1313753.html63
-rw-r--r--dom/tests/mochitest/general/test_bug1434273.html36
-rw-r--r--dom/tests/mochitest/general/test_bug504220.html65
-rw-r--r--dom/tests/mochitest/general/test_bug628069_1.html50
-rw-r--r--dom/tests/mochitest/general/test_bug628069_2.html41
-rw-r--r--dom/tests/mochitest/general/test_bug631440.html39
-rw-r--r--dom/tests/mochitest/general/test_bug653364.html39
-rw-r--r--dom/tests/mochitest/general/test_bug861217.html115
-rw-r--r--dom/tests/mochitest/general/test_clientRects.html127
-rw-r--r--dom/tests/mochitest/general/test_clipboard_disallowed.html68
-rw-r--r--dom/tests/mochitest/general/test_clipboard_events.html39
-rw-r--r--dom/tests/mochitest/general/test_consoleAPI.html66
-rw-r--r--dom/tests/mochitest/general/test_contentViewer_overrideDPPX.html422
-rw-r--r--dom/tests/mochitest/general/test_devicePixelRatio_with_zoom.html84
-rw-r--r--dom/tests/mochitest/general/test_domWindowUtils.html183
-rw-r--r--dom/tests/mochitest/general/test_domWindowUtils_scrollXY.html90
-rw-r--r--dom/tests/mochitest/general/test_domWindowUtils_scrollbarSize.html65
-rw-r--r--dom/tests/mochitest/general/test_donottrack.html72
-rw-r--r--dom/tests/mochitest/general/test_focus_legend_noparent.html36
-rw-r--r--dom/tests/mochitest/general/test_focus_scrollchildframe.html24
-rw-r--r--dom/tests/mochitest/general/test_focusrings.xhtml203
-rw-r--r--dom/tests/mochitest/general/test_for_of.html25
-rw-r--r--dom/tests/mochitest/general/test_frameElementWrapping.html44
-rw-r--r--dom/tests/mochitest/general/test_framedhistoryframes.html31
-rw-r--r--dom/tests/mochitest/general/test_img_mutations.html236
-rw-r--r--dom/tests/mochitest/general/test_innerScreen.xhtml85
-rw-r--r--dom/tests/mochitest/general/test_interfaces.html25
-rw-r--r--dom/tests/mochitest/general/test_interfaces.js1521
-rw-r--r--dom/tests/mochitest/general/test_interfaces_secureContext.html25
-rw-r--r--dom/tests/mochitest/general/test_media_queries_with_zoom.html52
-rw-r--r--dom/tests/mochitest/general/test_navigation_timing.html36
-rw-r--r--dom/tests/mochitest/general/test_network_events.html72
-rw-r--r--dom/tests/mochitest/general/test_nodeAdoption_chrome_boundary.xhtml28
-rw-r--r--dom/tests/mochitest/general/test_offsets.css3
-rw-r--r--dom/tests/mochitest/general/test_offsets.html100
-rw-r--r--dom/tests/mochitest/general/test_offsets.js336
-rw-r--r--dom/tests/mochitest/general/test_offsets.xhtml99
-rw-r--r--dom/tests/mochitest/general/test_outerHTML.html74
-rw-r--r--dom/tests/mochitest/general/test_outerHTML.xhtml75
-rw-r--r--dom/tests/mochitest/general/test_paste_selection.html122
-rw-r--r--dom/tests/mochitest/general/test_performance_nav_timing_before_onload.html30
-rw-r--r--dom/tests/mochitest/general/test_performance_now.html60
-rw-r--r--dom/tests/mochitest/general/test_performance_timeline.html35
-rw-r--r--dom/tests/mochitest/general/test_picture_apng.html77
-rw-r--r--dom/tests/mochitest/general/test_picture_mutations.html311
-rw-r--r--dom/tests/mochitest/general/test_pointerPreserves3D.html25
-rw-r--r--dom/tests/mochitest/general/test_pointerPreserves3DClip.html55
-rw-r--r--dom/tests/mochitest/general/test_pointerPreserves3DPerspective.html29
-rw-r--r--dom/tests/mochitest/general/test_resizeby.html64
-rw-r--r--dom/tests/mochitest/general/test_resource_timing.html40
-rw-r--r--dom/tests/mochitest/general/test_resource_timing_cross_origin.html47
-rw-r--r--dom/tests/mochitest/general/test_resource_timing_frameset.html29
-rw-r--r--dom/tests/mochitest/general/test_resource_timing_nocors.html37
-rw-r--r--dom/tests/mochitest/general/test_selectevents.html32
-rw-r--r--dom/tests/mochitest/general/test_showModalDialog_removed.html31
-rw-r--r--dom/tests/mochitest/general/test_spacetopagedown.html75
-rw-r--r--dom/tests/mochitest/general/test_storagePermissionsAccept.html44
-rw-r--r--dom/tests/mochitest/general/test_storagePermissionsLimitForeign.html46
-rw-r--r--dom/tests/mochitest/general/test_storagePermissionsReject.html44
-rw-r--r--dom/tests/mochitest/general/test_storagePermissionsRejectForeign.html44
-rw-r--r--dom/tests/mochitest/general/test_stylesheetPI.html37
-rw-r--r--dom/tests/mochitest/general/test_toggling_performance_navigation_timing.html47
-rw-r--r--dom/tests/mochitest/general/test_vibrator.html93
-rw-r--r--dom/tests/mochitest/general/test_windowProperties.html28
-rw-r--r--dom/tests/mochitest/general/test_windowedhistoryframes.html32
-rw-r--r--dom/tests/mochitest/general/url1_historyframe.html1
-rw-r--r--dom/tests/mochitest/general/url2_historyframe.html1
-rw-r--r--dom/tests/mochitest/general/window_clipboard_events.html1239
-rw-r--r--dom/tests/mochitest/general/window_storagePermissions.html38
-rw-r--r--dom/tests/mochitest/general/workerStorageAllowed.js74
-rw-r--r--dom/tests/mochitest/general/workerStoragePrevented.js71
134 files changed, 11514 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/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
new file mode 100644
index 0000000000..db812bd7d5
--- /dev/null
+++ b/dom/tests/mochitest/general/fail.png
Binary files differ
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..6f8ff6e66d
--- /dev/null
+++ b/dom/tests/mochitest/general/file_resource_timing_nocors.html
@@ -0,0 +1,190 @@
+<!--
+ 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?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..78805408ef
--- /dev/null
+++ b/dom/tests/mochitest/general/frameSelectEvents.html
@@ -0,0 +1,485 @@
+<!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)
+ });
+ });
+ }
+
+ // 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(aEnabled) {
+ await SpecialPowers.pushPrefEnv({'set': [['dom.select_events.textcontrols.enabled', aEnabled]]});
+ }
+ await UpdateSelectEventsOntextControlsPref(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) {
+ is(aEvent.originalTarget, document,
+ "The original target of selectionchange should be the document");
+ 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++;
+ });
+ async function mouseAction(aElement, aOffset, aType,
+ aSelStart, aSelChng, aISelChng, aTSelChng,
+ aYOffset)
+ {
+ if (aType == "click") { // You can simulate a click event by sending undefined
+ aType = undefined;
+ }
+ if (!aYOffset) {
+ aYOffset = 10;
+ }
+ synthesizeMouse(aElement, aOffset, aYOffset, { type: aType });
+ await spin();
+
+ is(selectstart, aSelStart,
+ "SelStart Mouse Action (" + aOffset + " - " + aType + ")");
+ is(selectionchange, aSelChng,
+ "SelChng Mouse Action (" + aOffset + " - " + aType + ")");
+ is(inputSelectionchange, aISelChng || 0,
+ "ISelChng Mouse Action (" + aOffset + " - " + aType + ")");
+ is(textareaSelectionchange, aTSelChng || 0,
+ "TSelChng Mouse Action (" + aOffset + " - " + aType + ")");
+ reset();
+ }
+
+ async function keyAction(aKey, aShift, aAccel,
+ aSelStart, aSelChng, aISelChng, aTSelChng)
+ {
+ synthesizeKey(aKey, { shiftKey: aShift, accelKey: aAccel });
+ await spin();
+ is(selectstart, aSelStart,
+ "SelStart Key Action (" + aKey + " - " + aShift + " - " + aAccel + ")");
+ is(selectionchange, aSelChng,
+ "SelChng Key Action (" + aKey + " - " + aShift + " - " + aAccel + ")");
+ is(inputSelectionchange, aISelChng || 0,
+ "ISelChng Key Action (" + aKey + " - " + aShift + " - " + aAccel + ")");
+ is(textareaSelectionchange, aTSelChng || 0,
+ "TSelChng Key Action (" + aKey + " - " + aShift + " - " + aAccel + ")");
+ reset();
+ }
+
+ async function contentEditableAction(aElement, aContentEditable,
+ aSelStart, aSelChng,
+ aISelChng, aTSelChng)
+ {
+ aElement.setAttribute("contenteditable",
+ aContentEditable ? "true" : "false");
+ await spin();
+
+ is(selectstart, aSelStart,
+ "SelStart ContentEditable Action");
+ is(selectionchange, aSelChng,
+ "SelStart ContentEditable Action");
+ is(inputSelectionchange, aISelChng || 0,
+ "SelStart ContentEditable Action");
+ is(textareaSelectionchange, aTSelChng || 0,
+ "SelStart ContentEditable Action");
+ 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 contentEditableAction(elt("ce"), true, 0, 0, 0, 0);
+
+ // Make sure setting the element to not be contentEditable doesn't cause any selectionchange events
+ await contentEditableAction(elt("ce"), false, 0, 0, 0, 0);
+
+ // Now make the div contentEditable and proceed with the test
+ await contentEditableAction(elt("ce"), true, 0, 0, 0, 0);
+
+ // Focus the contenteditable text
+ await mouseAction(elt("ce"), 100, "click", 0, 1);
+ isCollapsed();
+
+ // Move the selection to the right, this should only fire selectstart once
+ selectstartTarget = elt("ce").firstChild;
+ await keyAction("VK_RIGHT", true, false, 1, 1);
+ isNotCollapsed();
+ await keyAction("VK_RIGHT", true, false, 0, 1);
+ isNotCollapsed();
+
+ // Move it back so that the selection is empty again
+ await keyAction("VK_LEFT", true, false, 0, 1);
+ isNotCollapsed();
+ await keyAction("VK_LEFT", true, false, 0, 1);
+ isCollapsed();
+
+ // Going from empty to non-empty should fire selectstart again
+ selectstartTarget = elt("ce").firstChild;
+ await keyAction("VK_LEFT", true, false, 1, 1);
+ isNotCollapsed();
+
+ async function mouseMoves(aElement, aTarget) {
+ // Select a region
+ await mouseAction(aElement, 50, "mousedown", 0, 1);
+ isCollapsed();
+
+ selectstartTarget = aTarget;
+ await mouseAction(aElement, 100, "mousemove", 1, 1);
+ isNotCollapsed();
+
+ // Moving it more shouldn't trigger a start (move back to empty)
+ await mouseAction(aElement, 75, "mousemove", 0, 1);
+ isNotCollapsed();
+ await mouseAction(aElement, 50, "mousemove", 0, 1);
+ isCollapsed();
+
+ // Wiggling the mouse a little such that it doesn't select any
+ // characters shouldn't trigger a selection
+ await mouseAction(aElement, 50, "mousemove", 0, 0, 0, 0, 11);
+ isCollapsed();
+
+ // Moving the mouse again from an empty selection should trigger a
+ // selectstart
+ selectstartTarget = aTarget;
+ await mouseAction(aElement, 25, "mousemove", 1, 1);
+ isNotCollapsed();
+
+ // Releasing the mouse shouldn't do anything
+ await mouseAction(aElement, 25, "mouseup", 0, 0);
+ isNotCollapsed();
+
+ // And neither should moving your mouse around when the mouse
+ // button isn't pressed
+ await mouseAction(aElement, 50, "mousemove", 0, 0);
+ isNotCollapsed();
+
+ // Clicking in an random location should move the selection, but not perform a
+ // selectstart
+ await mouseAction(aElement, 50, "click", 0, 1);
+ isCollapsed();
+
+ // Clicking there again should do nothing
+ await mouseAction(aElement, 50, "click", 0, 0);
+ isCollapsed();
+
+ // Selecting a region, and canceling the selectstart should mean that the
+ // selection remains collapsed
+ await mouseAction(aElement, 75, "mousedown", 0, 1);
+ isCollapsed();
+ cancel = true;
+ selectstartTarget = aTarget;
+ await mouseAction(aElement, 100, "mousemove", 1, 1);
+ isCollapsed();
+ await mouseAction(aElement, 100, "mouseup", 0, 0);
+ isCollapsed();
+ }
+
+ // Should work both on normal
+ await mouseMoves(elt("inner"), elt("inner").firstChild);
+ // and contenteditable fields
+ await mouseMoves(elt("ce"), elt("ce").firstChild);
+ // and fields with elements in them
+ await mouseMoves(elt("normal"), elt("inner").firstChild);
+
+ await mouseAction(elt("inner"), 50, "click", 0, 1);
+ isCollapsed();
+
+ reset();
+ // Select all should fire both selectstart and change
+ selectstartTarget = document.body;
+ await keyAction("A", false, true, 1, 1);
+ isNotCollapsed();
+
+ // Clear the selection
+ await mouseAction(elt("inner"), 50, "click", 0, 1);
+ isCollapsed();
+
+ // Even if we already have a selection
+ await mouseAction(elt("inner"), 75, "mousedown", 0, 1);
+ isCollapsed();
+ selectstartTarget = elt("inner").firstChild;
+ await mouseAction(elt("inner"), 100, "mousemove", 1, 1);
+ isNotCollapsed();
+ await mouseAction(elt("inner"), 100, "mouseup", 0, 0);
+ isNotCollapsed();
+
+ selectstartTarget = document.body;
+ await keyAction("A", false, true, 1, 1);
+ isNotCollapsed();
+
+ // Clear the selection
+ await mouseAction(elt("inner"), 50, "click", 0, 1);
+ isCollapsed();
+
+ // Make sure that a synthesized selection change doesn't fire selectstart
+ var s = document.getSelection();
+ s.removeAllRanges();
+ await spin();
+ is(selectstart, 0, "Synthesized range removals shouldn't fire selectstart");
+ is(selectionchange, 1, "Synthesized range removals should change selectionchange");
+ reset();
+ isCollapsed();
+
+ var range = document.createRange();
+ range.selectNode(elt("inner"));
+ s.addRange(range);
+ await spin();
+ is(selectstart, 0, "Synthesized range additions shouldn't fire selectstart");
+ is(selectionchange, 1, "Synthesized range additions should change selectionchange");
+ reset();
+ isNotCollapsed();
+
+ // Change the range, without replacing
+ range.selectNode(elt("ce"));
+ await spin();
+ is(selectstart, 0, "Synthesized range mutations shouldn't fire selectstart");
+ is(selectionchange, 1, "Synthesized range mutations should change selectionchange");
+ reset();
+ isNotCollapsed();
+
+ // Remove the range
+ s.removeAllRanges();
+ await spin();
+ is(selectstart, 0, "Synthesized range removal");
+ is(selectionchange, 1, "Synthesized range removal");
+ reset();
+ isCollapsed();
+
+
+ /*
+ Selection events mouse move on input type=text
+ */
+
+ // Select a region
+
+ // Without the dom.select_events.textcontrols.enabled pref,
+ // pressing the mouse shouldn't do anything.
+ await mouseAction(elt("input"), 50, "mousedown", 0, 1, 0, 0);
+
+ // Releasing the mouse shouldn't do anything
+ await mouseAction(elt("input"), 50, "mouseup", 0, 0, 0, 0);
+
+ await UpdateSelectEventsOntextControlsPref(true);
+
+ await mouseAction(elt("input"), 50, "mousedown", 0, 0, 0, 0);
+
+ selectstartTarget = elt("input");
+ await mouseAction(elt("input"), 100, "mousemove", 1, 0, 1, 0);
+
+ // Moving it more shouldn't trigger a start (move back to empty)
+ await mouseAction(elt("input"), 75, "mousemove", 0, 0, 1, 0);
+ await mouseAction(elt("input"), 50, "mousemove", 0, 0, 1, 0);
+
+ // Wiggling the mouse a little such that it doesn't select any
+ // characters shouldn't trigger a selection
+ await mouseAction(elt("input"), 50, "mousemove", 0, 0, 0, 0, 11);
+
+ // Moving the mouse again from an empty selection should trigger a
+ // selectstart
+ selectstartTarget = elt("input");
+ await mouseAction(elt("input"), 25, "mousemove", 1, 0, 1, 0);
+
+ // Releasing the mouse shouldn't do anything
+ await mouseAction(elt("input"), 25, "mouseup", 0, 0, 0, 0);
+
+ // And neither should moving your mouse around when the mouse
+ // button isn't pressed
+ await mouseAction(elt("input"), 50, "mousemove", 0, 0, 0, 0);
+
+ // Clicking in an random location should move the selection, but
+ // not perform a selectstart
+ await mouseAction(elt("input"), 50, "click", 0, 0, 1, 0);
+
+ // Clicking there again should do nothing
+ await mouseAction(elt("input"), 50, "click", 0, 0, 0, 0);
+
+ // Selecting a region, and canceling the selectstart should mean that the
+ // selection remains collapsed
+ await mouseAction(elt("input"), 75, "mousedown", 0, 0, 1, 0);
+ cancel = true;
+ selectstartTarget = elt("input");
+ await mouseAction(elt("input"), 100, "mousemove", 1, 0, 1, 0);
+ await mouseAction(elt("input"), 100, "mouseup", 0, 0, 0, 0);
+
+
+ await UpdateSelectEventsOntextControlsPref(false);
+
+ // Without the dom.select_events.textcontrols.enabled pref,
+ // pressing the mouse shouldn't do anything.
+ // XXX For some reason we fire 2 selectchange events on the body
+ // when switching from the input to the text area.
+ await mouseAction(elt("textarea"), 50, "mousedown", 0, 2, 0, 0);
+
+ // Releasing the mouse shouldn't do anything
+ await mouseAction(elt("textarea"), 50, "mouseup", 0, 0, 0, 0);
+
+ await UpdateSelectEventsOntextControlsPref(true);
+
+ // Select a region
+ await mouseAction(elt("textarea"), 50, "mousedown", 0, 0, 0, 0);
+
+ selectstartTarget = elt("textarea");
+ await mouseAction(elt("textarea"), 100, "mousemove", 1, 0, 0, 1);
+
+ // Moving it more shouldn't trigger a start (move back to empty)
+ await mouseAction(elt("textarea"), 75, "mousemove", 0, 0, 0, 1);
+ await mouseAction(elt("textarea"), 50, "mousemove", 0, 0, 0, 1);
+
+ // Wiggling the mouse a little such that it doesn't select any
+ // characters shouldn't trigger a selection
+ await mouseAction(elt("textarea"), 50, "mousemove", 0, 0, 0, 0, 11);
+
+ // Moving the mouse again from an empty selection should trigger a
+ // selectstart
+ selectstartTarget = elt("textarea");
+ await mouseAction(elt("textarea"), 25, "mousemove", 1, 0, 0, 1);
+
+ // Releasing the mouse shouldn't do anything
+ await mouseAction(elt("textarea"), 25, "mouseup", 0, 0, 0, 0);
+
+ // And neither should moving your mouse around when the mouse
+ // button isn't pressed
+ await mouseAction(elt("textarea"), 50, "mousemove", 0, 0, 0, 0);
+
+ // Clicking in an random location should move the selection, but not perform a
+ // selectstart
+ await mouseAction(elt("textarea"), 50, "click", 0, 0, 0, 1);
+
+ // Clicking there again should do nothing
+ await mouseAction(elt("textarea"), 50, "click", 0, 0, 0, 0);
+
+ // Selecting a region, and canceling the selectstart should mean that the
+ // selection remains collapsed
+ await mouseAction(elt("textarea"), 75, "mousedown", 0, 0, 0, 1);
+ cancel = true;
+ selectstartTarget = elt("textarea");
+ await mouseAction(elt("textarea"), 100, "mousemove", 1, 0, 0, 1);
+ await mouseAction(elt("textarea"), 100, "mouseup", 0, 0, 0, 0);
+
+ // 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
+ elt("input").setAttribute("style", "display: none;");
+ await spin();
+ is(selectstart, 0, "idn - ss 1");
+ is(selectionchange, 0, "idn - sc 1");
+ is(inputSelectionchange, 0, "idn - isc 1");
+ is(textareaSelectionchange, 0, "idn - tsc 1");
+ reset();
+
+ elt("input").setAttribute("style", "");
+ await spin();
+ is(selectstart, 0, "idn - ss 2");
+ is(selectionchange, 0, "idn - sc 2");
+ is(inputSelectionchange, 0, "idn - isc 2");
+ is(textareaSelectionchange, 0, "idn - tsc 2");
+ reset();
+
+ elt("textarea").setAttribute("style", "display: none;");
+ await spin();
+ is(selectstart, 0, "tdn - ss 1");
+ is(selectionchange, 0, "tdn - sc 1");
+ is(inputSelectionchange, 0, "tdn - isc 1");
+ is(textareaSelectionchange, 0, "tdn - tsc 1");
+ reset();
+
+ elt("textarea").setAttribute("style", "");
+ await spin();
+ is(selectstart, 0, "tdn - ss 2");
+ is(selectionchange, 0, "tdn - sc 2");
+ is(inputSelectionchange, 0, "tdn - isc 2");
+ is(textareaSelectionchange, 0, "tdn - tsc 2");
+ reset();
+
+ // When selection is at the end of contentEditable's content,
+ // clearing the content should trigger selection events.
+ var savedContent = elt("ce").innerHTML;
+ document.getSelection().setBaseAndExtent(elt("ce"), 1, elt("ce"), 1);
+ await spin();
+ reset();
+
+ elt("ce").firstChild.remove();
+ await spin();
+ is(selectstart, 0, "clear ce - ss");
+ is(selectionchange, 1, "clear ce - sc");
+ is(inputSelectionchange, 0, "clear ce - isc");
+ is(textareaSelectionchange, 0, "clear ce - tsc");
+
+ 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..4f58a296f6
--- /dev/null
+++ b/dom/tests/mochitest/general/frameStorageNullprincipal.sjs
@@ -0,0 +1,33 @@
+// 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..d72ae1c07e
--- /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
new file mode 100644
index 0000000000..df421453c2
--- /dev/null
+++ b/dom/tests/mochitest/general/image_100.png
Binary files differ
diff --git a/dom/tests/mochitest/general/image_200.png b/dom/tests/mochitest/general/image_200.png
new file mode 100644
index 0000000000..6f76d44387
--- /dev/null
+++ b/dom/tests/mochitest/general/image_200.png
Binary files differ
diff --git a/dom/tests/mochitest/general/image_50.png b/dom/tests/mochitest/general/image_50.png
new file mode 100644
index 0000000000..144a2f0b93
--- /dev/null
+++ b/dom/tests/mochitest/general/image_50.png
Binary files differ
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..56a1267d0d
--- /dev/null
+++ b/dom/tests/mochitest/general/mochitest.ini
@@ -0,0 +1,139 @@
+[DEFAULT]
+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
+
+[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]
+[test_domWindowUtils_scrollXY.html]
+[test_donottrack.html]
+[test_focus_scrollchildframe.html]
+[test_focus_legend_noparent.html]
+[test_for_of.html]
+[test_framedhistoryframes.html]
+[test_frameElementWrapping.html]
+[test_img_mutations.html]
+skip-if = verify
+[test_interfaces.html]
+[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
+[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
+[test_resource_timing_cross_origin.html]
+[test_resource_timing_frameset.html]
+[test_selectevents.html]
+skip-if = toolkit == 'android' # bug 1627523
+[test_showModalDialog_removed.html]
+[test_storagePermissionsAccept.html]
+[test_storagePermissionsLimitForeign.html]
+[test_storagePermissionsReject.html]
+[test_storagePermissionsRejectForeign.html]
+[test_stylesheetPI.html]
+[test_toggling_performance_navigation_timing.html]
+[test_vibrator.html]
+fail-if = xorigin
+[test_WebKitCSSMatrix.html]
+[test_windowedhistoryframes.html]
+[test_windowProperties.html]
+[test_resource_timing_nocors.html]
+[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
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
new file mode 100644
index 0000000000..6e78a9eef4
--- /dev/null
+++ b/dom/tests/mochitest/general/pass.apng
Binary files differ
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..21d33c115e
--- /dev/null
+++ b/dom/tests/mochitest/general/performance_timeline_main_test.html
@@ -0,0 +1,105 @@
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+-->
+
+<!DOCTYPE html>
+<html>
+<head>
+ <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/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..6a82885a74
--- /dev/null
+++ b/dom/tests/mochitest/general/resource_timing_cross_origin.html
@@ -0,0 +1,187 @@
+<!--
+ 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 are 0.
+function checkRedirectCrossOriginResourceSameOrigin(entry) {
+ const checks = { "redirectStart": 0, "redirectEnd": 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_main_test.html b/dom/tests/mochitest/general/resource_timing_main_test.html
new file mode 100644
index 0000000000..d2efd36ab6
--- /dev/null
+++ b/dom/tests/mochitest/general/resource_timing_main_test.html
@@ -0,0 +1,289 @@
+<!--
+ 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>
+ <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_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/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..920197aba8
--- /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(false, "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(false, "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..7bd0cdaf3b
--- /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..7bd0cdaf3b
--- /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..368a96126d
--- /dev/null
+++ b/dom/tests/mochitest/general/test_497898.html
@@ -0,0 +1,38 @@
+<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></head>
+ <body>
+ <button id='a' onfocus='parent.switchFocus()' onblur='window.frameElement.parentNode.removeChild(window.frameElement)'>Switching focus to a different program should not crash Mozilla</button>
+ <script>
+document.getElementById('a').focus();
+ </script>
+ </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..034ad48bf5
--- /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 > 0 ? 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..13c47bdb79
--- /dev/null
+++ b/dom/tests/mochitest/general/test_clipboard_disallowed.html
@@ -0,0 +1,68 @@
+<!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 exception;
+ try {
+ clipboardData.setData("application/x-moz-file", "Test");
+ } catch(ex) {
+ exception = ex;
+ }
+ is(String(exception).indexOf("SecurityError"), 0, "Cannot set file");
+
+ exception = null;
+ try {
+ clipboardData.setData("application/x-moz-file-promise", "Test");
+ } catch(ex) {
+ exception = ex;
+ }
+ is(String(exception).indexOf("SecurityError"), 0, "Cannot set file promise");
+
+ exception = null;
+ try {
+ clipboardData.setData("text/x-moz-place", "Test");
+ } catch(ex) {
+ exception = ex;
+ }
+ is(String(exception).indexOf("SecurityError"), 0, "Cannot set place");
+ exception = null;
+ try {
+ clipboardData.setData("text/x-moz-place-container", "Test");
+ } catch(ex) {
+ exception = ex;
+ }
+ is(String(exception).indexOf("SecurityError"), 0, "Cannot set place container");
+
+ exception = null;
+ try {
+ clipboardData.setData("application/something", "This is data");
+ } catch(ex) {
+ exception = ex;
+ }
+ is(exception, null, "Can set custom data to a string");
+ 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..9a91aaf4c3
--- /dev/null
+++ b/dom/tests/mochitest/general/test_clipboard_events.html
@@ -0,0 +1,39 @@
+<!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],
+ // GlobalEventHandlers.onbeforeinput is required.
+ ["dom.input_events.beforeinput.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..66788728e0
--- /dev/null
+++ b/dom/tests/mochitest/general/test_contentViewer_overrideDPPX.html
@@ -0,0 +1,422 @@
+<!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`);
+ }
+
+ SpecialPowers.setOverrideDPPX(window, value);
+}
+
+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": (done) => {
+ assertValuesAreInitial();
+
+ setOverrideDPPX(dppx);
+
+ is(window.devicePixelRatio, dppx,
+ "devicePixelRatio overridden.");
+ is(frameWindow.devicePixelRatio, dppx,
+ "frame's devicePixelRatio overridden.");
+
+ 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": (done) => {
+ assertValuesAreInitial();
+
+ setFullZoom(zoom);
+ setOverrideDPPX(dppx);
+
+ is(window.devicePixelRatio, dppx,
+ "devicePixelRatio overridden; fullZoom ignored");
+ is(frameWindow.devicePixelRatio, dppx,
+ "frame's devicePixelRatio overridden; fullZoom ignored");
+
+ 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": (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");
+
+ 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.");
+
+ 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": (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);
+ 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.");
+
+ 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": (done) => {
+ assertValuesAreInitial();
+
+ let promises = [
+ waitForMediaQueryListEvent(
+ window.matchMedia(`(resolution: ${dppx}dppx)`)),
+ waitForMediaQueryListEvent(
+ window.matchMedia(`(resolution: ${originalDPR * zoom}dppx)`)),
+ ];
+
+ 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": (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");
+
+ 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", 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();
+
+ 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);
+ setOverrideDPPX(8);
+ await loaded;
+
+ is(image.currentSrc, imageSets["8x"],
+ "Image is properly set for 8dppx.");
+
+ loaded = new Promise(resolve => image.onload = resolve);
+ setOverrideDPPX(1);
+ await loaded;
+
+ is(image.currentSrc, imageSets["1x"],
+ "Image url is properly set for 1dppx.");
+
+ loaded = new Promise(resolve => image.onload = resolve);
+ 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);
+ 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);
+ setOverrideDPPX(8);
+ await loaded;
+
+ is(image.currentSrc, imageSets["8x"],
+ "Image is properly set for 8dppx.");
+
+ loaded = new Promise(resolve => image.onload = resolve);
+ 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..300c3b17b9
--- /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 == 0) {
+ // 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..2895004d62
--- /dev/null
+++ b/dom/tests/mochitest/general/test_focusrings.xhtml
@@ -0,0 +1,203 @@
+<?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; }
+#l1:-moz-focusring, #l3:-moz-focusring, #b1:-moz-focusring { outline: 2px solid red; }
+#l2:focus, #b2:focus { outline: 2px solid red; }
+</html:style>
+
+<script>
+<![CDATA[
+
+SimpleTest.waitForExplicitFinish();
+
+const kFocusVisibleEnabled = SpecialPowers.getBoolPref("layout.css.focus-visible.enabled");
+
+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");
+}
+
+function initTest()
+{
+ SpecialPowers.pushPrefEnv({"set": [['accessibility.tabfocus', 7]]}, runTest);
+}
+
+function runTest()
+{
+ var isMac = (navigator.platform.includes("Mac"));
+ 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 (navigator.platform.includes("Mac")) {
+ var focusedButton = $("b3");
+ ok(compareSnapshots(snapShot(focusedButton), snapShot($("b2")), true)[0], "unfocused shows no ring");
+ focusedButton.focus();
+ ok(compareSnapshots(snapShot(focusedButton), snapShot($("b2")), false)[0], "focus shows ring");
+ }
+
+ 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, isMac, 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"), expectedVisible && !kFocusVisibleEnabled, "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"), !isMac && expectedVisible && !kFocusVisibleEnabled, "appearance on button after mouse focus with :moz-focusring");
+ if (navigator.platform.includes("Mac")) {
+ ok(compareSnapshots(snapShot($("b1")), snapShot($("b2")), false)[0], "focus after mouse shows no ring");
+ }
+
+ synthesizeMouse($("b2"), 4, 4, { });
+ checkFocus($("b2"), !isMac, "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 (isMac) {
+ SpecialPowers.pushPrefEnv({"set": [['accessibility.mouse_focuses_formcontrol', true]]}, testMacFocusesFormControl);
+ }
+ else {
+ SimpleTest.finish();
+ }
+}
+
+function testMacFocusesFormControl()
+{
+ testHTMLElements(htmlElementsMacPrefSet, true, false);
+ SimpleTest.finish();
+}
+
+var htmlElements = [
+ "<button id='elem'>Button</button>",
+ "<input id='elem' class='canfocus'>",
+ "<input id='elem' type='password' class='canfocus'>",
+ "<input id='elem' type='button'>",
+ "<input id='elem' type='checkbox'>",
+ "<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, isMac, 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 = !isMac || (elem.className == "canfocus");
+ var ringSize = (shouldFocus ? (expectedNoRingsOnWin ? 2 : 1) : 0) + "px";
+
+ var expectedMatchWithMouse = shouldFocus && !expectedNoRingsOnWin;
+ var mouseRingSize = ringSize;
+ if (shouldFocus && kFocusVisibleEnabled) {
+ 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 && !isMac;
+ }
+
+ 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 = (elem.localName == "a" ? "0" : (expectedNoRingsOnWin ? 2 : 1)) + "px";
+
+ 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(runTest);
+
+]]>
+</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..e8d1268057
--- /dev/null
+++ b/dom/tests/mochitest/general/test_innerScreen.xhtml
@@ -0,0 +1,85 @@
+<?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 domWindowUtils = window.windowUtils;
+ var devPxPerCSSPx = domWindowUtils.screenPixelsPerCSSPixel;
+
+ 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;
+ var frameDomWindowUtils = f.contentWindow.windowUtils;
+ is(frameDomWindowUtils.screenPixelsPerCSSPixel, 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..02380c7170
--- /dev/null
+++ b/dom/tests/mochitest/general/test_interfaces.html
@@ -0,0 +1,25 @@
+<!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>
+<pre id="test">
+<script>
+ ok(!self.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..8dec7f5e44
--- /dev/null
+++ b/dom/tests/mochitest/general/test_interfaces.js
@@ -0,0 +1,1521 @@
+/** 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.
+
+// 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.Cu.import(
+ "resource://gre/modules/AppConstants.jsm",
+ {}
+);
+
+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!
+var 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: "ByteLengthQueuingStrategy", insecureContext: true },
+ { name: "CountQueuingStrategy", 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: "ReadableStream", 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 },
+ {
+ name: "WebAssembly",
+ insecureContext: true,
+ disabled: !SpecialPowers.Cu.getJSTestingFunctions().wasmIsSupportedByHardware(),
+ },
+];
+// 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,
+// except to remove items from it!
+//
+// This is a list of interfaces that were prefixed with 'moz' instead of 'Moz'.
+// We should never to that again, interfaces in the DOM start with an uppercase
+// letter. If you think you need to add an interface here, DON'T. Rename your
+// interface.
+var legacyMozPrefixedInterfaces = [
+ "mozContact",
+ "mozRTCIceCandidate",
+ "mozRTCPeerConnection",
+ "mozRTCSessionDescription",
+];
+// IMPORTANT: Do not change the list above without review from a DOM peer,
+// except to remove items from it!
+
+// IMPORTANT: Do not change the list below without review from a DOM peer!
+var 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: "Cache", insecureContext: true },
+ // IMPORTANT: Do not change this list without review from a DOM peer!
+ { name: "CacheStorage", insecureContext: true },
+ // 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: "ConstantSourceNode", insecureContext: 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: "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: "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: "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: "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: "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: "DeprecationReportBody", insecureContext: true, nightly: 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, nightly: 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: "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: "FeaturePolicyViolationReportBody",
+ insecureContext: true,
+ nightly: 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: "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: "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: true },
+ // IMPORTANT: Do not change this list without review from a DOM peer!
+ { name: "GamepadAxisMoveEvent", insecureContext: true },
+ // IMPORTANT: Do not change this list without review from a DOM peer!
+ { name: "GamepadButtonEvent", insecureContext: true },
+ // IMPORTANT: Do not change this list without review from a DOM peer!
+ { name: "GamepadButton", insecureContext: true },
+ // IMPORTANT: Do not change this list without review from a DOM peer!
+ { name: "GamepadEvent", insecureContext: true },
+ // IMPORTANT: Do not change this list without review from a DOM peer!
+ { name: "GamepadHapticActuator", insecureContext: true },
+ // 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: true },
+ // 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: "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, nightly: 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: "IDBFileHandle", insecureContext: true },
+ // IMPORTANT: Do not change this list without review from a DOM peer!
+ { name: "IDBFileRequest", 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: "IDBMutableFile", 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 },
+ // 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!
+ { 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", disabled: true },
+ // IMPORTANT: Do not change this list without review from a DOM peer!
+ { name: "MIDIConnectionEvent", disabled: true },
+ // IMPORTANT: Do not change this list without review from a DOM peer!
+ { name: "MIDIInputMap", disabled: true },
+ // IMPORTANT: Do not change this list without review from a DOM peer!
+ { name: "MIDIInput", disabled: true },
+ // IMPORTANT: Do not change this list without review from a DOM peer!
+ { name: "MIDIMessageEvent", disabled: true },
+ // IMPORTANT: Do not change this list without review from a DOM peer!
+ { name: "MIDIOutputMap", disabled: true },
+ // IMPORTANT: Do not change this list without review from a DOM peer!
+ { name: "MIDIOutput", disabled: true },
+ // IMPORTANT: Do not change this list without review from a DOM peer!
+ { name: "MIDIPort", disabled: true },
+ // 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: "mozRTCIceCandidate", insecureContext: true },
+ // IMPORTANT: Do not change this list without review from a DOM peer!
+ { name: "mozRTCPeerConnection", insecureContext: true },
+ // IMPORTANT: Do not change this list without review from a DOM peer!
+ { name: "mozRTCSessionDescription", 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!
+ { name: "Navigator", insecureContext: true },
+ // IMPORTANT: Do not change this list without review from a DOM peer!
+ { name: "NetworkInformation", insecureContext: true, desktop: false },
+ // 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: "OffscreenCanvas", insecureContext: true, disabled: 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: "OfflineResourceList", insecureContext: false },
+ // 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: "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: "Presentation",
+ insecureContext: true,
+ desktop: false,
+ release: false,
+ },
+ // IMPORTANT: Do not change this list without review from a DOM peer!
+ {
+ name: "PresentationAvailability",
+ insecureContext: true,
+ desktop: false,
+ release: false,
+ },
+ // IMPORTANT: Do not change this list without review from a DOM peer!
+ {
+ name: "PresentationConnection",
+ insecureContext: true,
+ desktop: false,
+ release: false,
+ },
+ // IMPORTANT: Do not change this list without review from a DOM peer!
+ {
+ name: "PresentationConnectionAvailableEvent",
+ insecureContext: true,
+ desktop: false,
+ release: false,
+ },
+ // IMPORTANT: Do not change this list without review from a DOM peer!
+ {
+ name: "PresentationConnectionCloseEvent",
+ insecureContext: true,
+ desktop: false,
+ release: false,
+ },
+ // IMPORTANT: Do not change this list without review from a DOM peer!
+ {
+ name: "PresentationConnectionList",
+ insecureContext: true,
+ desktop: false,
+ release: false,
+ },
+ // IMPORTANT: Do not change this list without review from a DOM peer!
+ {
+ name: "PresentationReceiver",
+ insecureContext: true,
+ desktop: false,
+ release: false,
+ },
+ // IMPORTANT: Do not change this list without review from a DOM peer!
+ {
+ name: "PresentationRequest",
+ insecureContext: true,
+ desktop: false,
+ release: false,
+ },
+ // 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!
+ { name: "PushManager", insecureContext: true },
+ // IMPORTANT: Do not change this list without review from a DOM peer!
+ { name: "PushSubscription", insecureContext: true },
+ // IMPORTANT: Do not change this list without review from a DOM peer!
+ {
+ name: "PushSubscriptionOptions",
+ insecureContext: true,
+ },
+ // 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: "Report", insecureContext: true, nightly: true },
+ // IMPORTANT: Do not change this list without review from a DOM peer!
+ { name: "ReportBody", insecureContext: true, nightly: true },
+ // IMPORTANT: Do not change this list without review from a DOM peer!
+ { name: "ReportingObserver", insecureContext: true, nightly: 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: "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: "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!
+ { name: "ServiceWorker", insecureContext: true },
+ // IMPORTANT: Do not change this list without review from a DOM peer!
+ { name: "ServiceWorkerContainer", insecureContext: false },
+ // IMPORTANT: Do not change this list without review from a DOM peer!
+ { name: "ServiceWorkerRegistration", insecureContext: true },
+ // 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: "SimpleTest", 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: "SVGPathSegList", 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: "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: "TextEncoder", 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: "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, android: false },
+ // 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: "VRDisplay", releaseNonWindowsAndMac: false },
+ // IMPORTANT: Do not change this list without review from a DOM peer!
+ {
+ name: "VRDisplayCapabilities",
+ releaseNonWindowsAndMac: false,
+ },
+ // IMPORTANT: Do not change this list without review from a DOM peer!
+ {
+ name: "VRDisplayEvent",
+ releaseNonWindowsAndMac: false,
+ },
+ // IMPORTANT: Do not change this list without review from a DOM peer!
+ {
+ name: "VREyeParameters",
+ releaseNonWindowsAndMac: false,
+ },
+ // IMPORTANT: Do not change this list without review from a DOM peer!
+ {
+ name: "VRFieldOfView",
+ releaseNonWindowsAndMac: false,
+ },
+ // IMPORTANT: Do not change this list without review from a DOM peer!
+ {
+ name: "VRFrameData",
+ releaseNonWindowsAndMac: false,
+ },
+ // IMPORTANT: Do not change this list without review from a DOM peer!
+ { name: "VRPose", releaseNonWindowsAndMac: false },
+ // IMPORTANT: Do not change this list without review from a DOM peer!
+ {
+ name: "VRStageParameters",
+ releaseNonWindowsAndMac: false,
+ },
+ // 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: "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: "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!
+];
+// IMPORTANT: Do not change the list above without review from a DOM peer!
+
+function createInterfaceMap() {
+ var interfaceMap = {};
+
+ function addInterfaces(interfaces) {
+ for (var entry of interfaces) {
+ if (typeof entry === "string") {
+ interfaceMap[entry] = !isInsecureContext;
+ } else {
+ ok(!("pref" in entry), "Bogus pref annotation for " + entry.name);
+ if (
+ 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.releaseNonWindowsAndMac ===
+ !(isRelease && !isWindows && !isMac) ||
+ 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
+ ) {
+ interfaceMap[entry.name] = false;
+ } else {
+ interfaceMap[entry.name] = true;
+ }
+ }
+ }
+ }
+
+ addInterfaces(ecmaGlobals);
+ addInterfaces(interfaceNamesInGlobalScope);
+
+ return interfaceMap;
+}
+
+function runTest() {
+ var interfaceMap = createInterfaceMap();
+ for (var name of Object.getOwnPropertyNames(window)) {
+ // An interface name should start with an upper case character.
+ // However, we have a couple of legacy interfaces that start with 'moz', so
+ // we want to allow those until we can remove them.
+ if (!/^[A-Z]/.test(name) && !legacyMozPrefixedInterfaces.includes(name)) {
+ continue;
+ }
+ 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 the window? 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 window,
+ `${name} is exposed as an own property on the window but tests false for "in" in the global scope`
+ );
+ ok(
+ Object.getOwnPropertyDescriptor(window, name),
+ `${name} is exposed as an own property on the window but has no property descriptor in the global scope`
+ );
+
+ delete interfaceMap[name];
+ }
+ for (var name of Object.keys(interfaceMap)) {
+ ok(
+ name in window === interfaceMap[name],
+ name +
+ " should " +
+ (interfaceMap[name] ? "" : " NOT") +
+ " be defined on the global scope"
+ );
+ if (!interfaceMap[name]) {
+ delete interfaceMap[name];
+ }
+ }
+ is(
+ Object.keys(interfaceMap).length,
+ 0,
+ "The following interface(s) are not enumerated: " +
+ Object.keys(interfaceMap).join(", ")
+ );
+}
+
+runTest();
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..50b216a4c4
--- /dev/null
+++ b/dom/tests/mochitest/general/test_interfaces_secureContext.html
@@ -0,0 +1,25 @@
+<!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>
+<pre id="test">
+<script>
+ ok(self.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..24cb7a9d1a
--- /dev/null
+++ b/dom/tests/mochitest/general/test_offsets.js
@@ -0,0 +1,336 @@
+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") {
+ var lastchild = $("lastline");
+ scrollWidth =
+ lastchild.getBoundingClientRect().width + paddingLeft + paddingRight;
+ var top = element.firstChild.getBoundingClientRect().top;
+ var bottom = element.lastChild.getBoundingClientRect().bottom;
+ var contentsHeight = bottom - top;
+ scrollHeight = contentsHeight + paddingTop + paddingBottom;
+ 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) {
+ ok(
+ Math.abs(element[type] - Math.round(val)) <= fuzz,
+ testname + " " + type
+ );
+ }
+}
+
+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..886bf56b0b
--- /dev/null
+++ b/dom/tests/mochitest/general/test_offsets.xhtml
@@ -0,0 +1,99 @@
+<?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" width="140" height="120"/>
+ </vbox>
+ <hbox align="start">
+ <vbox id="scrollbox" style="overflow: scroll; padding: 2px; margin: 3px; border: 4px solid green;"
+ maxwidth="66" maxheight="56">
+ <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..3b38c40d49
--- /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/unicode", SpecialPowers.Ci.nsIClipboard.kSelectionClipboard) == "COPY TEXT") &&
+ SpecialPowers.getClipboardData("text/unicode", 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_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..e3c79ce3e5
--- /dev/null
+++ b/dom/tests/mochitest/general/test_selectevents.html
@@ -0,0 +1,32 @@
+<!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.enabled', true],
+ ['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..88f7154286
--- /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 (resistFingerprinting)
+ 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)
+ 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..3188b62e56
--- /dev/null
+++ b/dom/tests/mochitest/general/test_vibrator.html
@@ -0,0 +1,93 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for Vibrator</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>
+
+<!-- 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">
+SimpleTest.waitForExplicitFinish();
+var result;
+function expectFailure(param) {
+ result = navigator.vibrate(param);
+ is(result, false, 'vibrate(' + param + ') should have failed.');
+}
+
+function expectSuccess(param) {
+ result = navigator.vibrate(param);
+ is(result, true, 'vibrate(' + param + ') must succeed.');
+}
+
+function tests(aEnabled) {
+ // Some edge cases that the bindings should handle for us.
+ expectSuccess(null);
+ expectSuccess(undefined);
+ // -1 will be converted to the highest unsigned long then clamped.
+ expectSuccess(-1);
+ expectSuccess('a');
+ // -1 will be converted to the highest unsigned long then clamped.
+ expectSuccess([100, -1]);
+ expectSuccess([100, 'a']);
+
+ var maxVibrateMs = SpecialPowers.getIntPref('dom.vibrator.max_vibrate_ms');
+ var maxVibrateListLen = SpecialPowers.getIntPref('dom.vibrator.max_vibrate_list_len');
+
+ // 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.
+ expectSuccess(maxVibrateMs + 1);
+ expectSuccess([maxVibrateMs + 1]);
+
+ var arr = [];
+ for (var i = 0; i < maxVibrateListLen + 1; i++) {
+ arr[i] = 0;
+ }
+ // The array will be truncated to have a length equal to dom.vibrator.max_vibrate_list_len.
+ expectSuccess(arr);
+
+
+ expectSuccess(0);
+ expectSuccess([]);
+ expectSuccess('1000');
+ expectSuccess(1000);
+ expectSuccess(1000.1);
+ expectSuccess([0, 0, 0]);
+ expectSuccess(['1000', 1000]);
+ expectSuccess([1000, 1000]);
+ expectSuccess([1000, 1000.1]);
+
+ // 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.");
+ if(!aEnabled)
+ SimpleTest.finish();
+}
+
+SpecialPowers.pushPermissions([
+ {type: 'vibration', allow: true, context: document}
+ ], function() {
+ // Test with the vibrator pref enabled.
+ SpecialPowers.pushPrefEnv({"set": [['dom.vibrator.enabled', true]]}, function() {
+ tests(true);
+ SpecialPowers.pushPrefEnv({"set": [['dom.vibrator.enabled', false]]}, tests(false));
+ });
+ // Everything should be the same when the vibrator is disabled -- in
+ // particular, a disabled vibrator shouldn't eat failures we'd otherwise
+ // observe.
+ }
+);
+
+</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..08c404f7c6
--- /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/unicode");
+}
+
+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 > 0) {
+ 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 > 0) {
+ 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 > 0) {
+ 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 > 0) {
+ 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 > 0) {
+ 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 > 0) {
+ 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..87f87befa8
--- /dev/null
+++ b/dom/tests/mochitest/general/workerStorageAllowed.js
@@ -0,0 +1,74 @@
+// 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(false, "WORKER getting caches should not have thrown");
+}
+
+// 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..0818513a61
--- /dev/null
+++ b/dom/tests/mochitest/general/workerStoragePrevented.js
@@ -0,0 +1,71 @@
+// 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(false, "WORKER getting caches should not have thrown");
+}
+
+// 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)"
+ );
+ });
+}