summaryrefslogtreecommitdiffstats
path: root/devtools/server/tests/chrome
diff options
context:
space:
mode:
Diffstat (limited to 'devtools/server/tests/chrome')
-rw-r--r--devtools/server/tests/chrome/.eslintrc.js6
-rw-r--r--devtools/server/tests/chrome/Debugger.Source.prototype.element-2.js4
-rw-r--r--devtools/server/tests/chrome/Debugger.Source.prototype.element.html25
-rw-r--r--devtools/server/tests/chrome/Debugger.Source.prototype.element.js7
-rw-r--r--devtools/server/tests/chrome/chrome.ini105
-rw-r--r--devtools/server/tests/chrome/doc_Debugger.Source.prototype.introductionType.xhtml7
-rw-r--r--devtools/server/tests/chrome/framerate-helpers.js63
-rw-r--r--devtools/server/tests/chrome/hello-actor.js28
-rw-r--r--devtools/server/tests/chrome/iframe1_makeGlobalObjectReference.html1
-rw-r--r--devtools/server/tests/chrome/iframe2_makeGlobalObjectReference.html1
-rw-r--r--devtools/server/tests/chrome/inactive-property-helper/.eslintrc.js5
-rw-r--r--devtools/server/tests/chrome/inactive-property-helper/align-content.js93
-rw-r--r--devtools/server/tests/chrome/inactive-property-helper/flex-grid-item-properties.js229
-rw-r--r--devtools/server/tests/chrome/inactive-property-helper/float.js76
-rw-r--r--devtools/server/tests/chrome/inactive-property-helper/gap.js98
-rw-r--r--devtools/server/tests/chrome/inactive-property-helper/grid-with-absolute-properties.js71
-rw-r--r--devtools/server/tests/chrome/inactive-property-helper/margin-padding.js260
-rw-r--r--devtools/server/tests/chrome/inactive-property-helper/max-min-width-height.js367
-rw-r--r--devtools/server/tests/chrome/inactive-property-helper/outline-radius.js28
-rw-r--r--devtools/server/tests/chrome/inactive-property-helper/place-items-content.js159
-rw-r--r--devtools/server/tests/chrome/inactive-property-helper/positioned.js84
-rw-r--r--devtools/server/tests/chrome/inactive-property-helper/text-overflow.js44
-rw-r--r--devtools/server/tests/chrome/inactive-property-helper/vertical-align.js56
-rw-r--r--devtools/server/tests/chrome/inspector-delay-image-response.sjs40
-rw-r--r--devtools/server/tests/chrome/inspector-eyedropper.html20
-rw-r--r--devtools/server/tests/chrome/inspector-helpers.js146
-rw-r--r--devtools/server/tests/chrome/inspector-search-data.html54
-rw-r--r--devtools/server/tests/chrome/inspector-styles-data.css3
-rw-r--r--devtools/server/tests/chrome/inspector-styles-data.html85
-rw-r--r--devtools/server/tests/chrome/inspector-template.html17
-rw-r--r--devtools/server/tests/chrome/inspector-traversal-data.html103
-rw-r--r--devtools/server/tests/chrome/inspector_css-properties.html12
-rw-r--r--devtools/server/tests/chrome/inspector_display-type.html17
-rw-r--r--devtools/server/tests/chrome/inspector_getImageData.html23
-rw-r--r--devtools/server/tests/chrome/inspector_getOffsetParent.html18
-rw-r--r--devtools/server/tests/chrome/large-image.jpgbin0 -> 793541 bytes
-rw-r--r--devtools/server/tests/chrome/memory-helpers.js53
-rw-r--r--devtools/server/tests/chrome/nonchrome_unsafeDereference.html10
-rw-r--r--devtools/server/tests/chrome/small-image.gifbin0 -> 510655 bytes
-rw-r--r--devtools/server/tests/chrome/suspendTimeouts_content.html1
-rw-r--r--devtools/server/tests/chrome/suspendTimeouts_content.js73
-rw-r--r--devtools/server/tests/chrome/suspendTimeouts_worker.js12
-rw-r--r--devtools/server/tests/chrome/test_Debugger.Script.prototype.global.html48
-rw-r--r--devtools/server/tests/chrome/test_Debugger.Source.prototype.element.html182
-rw-r--r--devtools/server/tests/chrome/test_Debugger.Source.prototype.introductionScript.html97
-rw-r--r--devtools/server/tests/chrome/test_Debugger.Source.prototype.introductionType.html169
-rw-r--r--devtools/server/tests/chrome/test_animation-type-longhand.html42
-rw-r--r--devtools/server/tests/chrome/test_css-logic-media-queries.html58
-rw-r--r--devtools/server/tests/chrome/test_css-logic-specificity.html82
-rw-r--r--devtools/server/tests/chrome/test_css-logic.html73
-rw-r--r--devtools/server/tests/chrome/test_css-properties.html72
-rw-r--r--devtools/server/tests/chrome/test_device.html80
-rw-r--r--devtools/server/tests/chrome/test_executeInGlobal-outerized_this.html73
-rw-r--r--devtools/server/tests/chrome/test_framerate_01.html70
-rw-r--r--devtools/server/tests/chrome/test_framerate_02.html48
-rw-r--r--devtools/server/tests/chrome/test_framerate_03.html51
-rw-r--r--devtools/server/tests/chrome/test_framerate_04.html66
-rw-r--r--devtools/server/tests/chrome/test_framerate_05.html47
-rw-r--r--devtools/server/tests/chrome/test_framerate_06.html87
-rw-r--r--devtools/server/tests/chrome/test_highlighter_paused_debugger.html88
-rw-r--r--devtools/server/tests/chrome/test_inspector-changeattrs.html90
-rw-r--r--devtools/server/tests/chrome/test_inspector-changevalue.html68
-rw-r--r--devtools/server/tests/chrome/test_inspector-dead-nodes.html332
-rw-r--r--devtools/server/tests/chrome/test_inspector-display-type.html81
-rw-r--r--devtools/server/tests/chrome/test_inspector-duplicate-node.html61
-rw-r--r--devtools/server/tests/chrome/test_inspector-hide.html71
-rw-r--r--devtools/server/tests/chrome/test_inspector-inactive-property-helper.html104
-rw-r--r--devtools/server/tests/chrome/test_inspector-mutations-attr.html168
-rw-r--r--devtools/server/tests/chrome/test_inspector-mutations-events.html187
-rw-r--r--devtools/server/tests/chrome/test_inspector-mutations-value.html162
-rw-r--r--devtools/server/tests/chrome/test_inspector-pick-color.html91
-rw-r--r--devtools/server/tests/chrome/test_inspector-pseudoclass-lock.html187
-rw-r--r--devtools/server/tests/chrome/test_inspector-reload.html86
-rw-r--r--devtools/server/tests/chrome/test_inspector-resize.html69
-rw-r--r--devtools/server/tests/chrome/test_inspector-resolve-url.html87
-rw-r--r--devtools/server/tests/chrome/test_inspector-scroll-into-view.html76
-rw-r--r--devtools/server/tests/chrome/test_inspector-search-front.html172
-rw-r--r--devtools/server/tests/chrome/test_inspector-template.html66
-rw-r--r--devtools/server/tests/chrome/test_inspector_getImageData-wait-for-load.html133
-rw-r--r--devtools/server/tests/chrome/test_inspector_getImageData.html166
-rw-r--r--devtools/server/tests/chrome/test_inspector_getImageDataFromURL.html116
-rw-r--r--devtools/server/tests/chrome/test_inspector_getNodeFromActor.html84
-rw-r--r--devtools/server/tests/chrome/test_inspector_getOffsetParent.html129
-rw-r--r--devtools/server/tests/chrome/test_makeGlobalObjectReference.html96
-rw-r--r--devtools/server/tests/chrome/test_memory.html39
-rw-r--r--devtools/server/tests/chrome/test_memory_allocations_02.html80
-rw-r--r--devtools/server/tests/chrome/test_memory_allocations_03.html80
-rw-r--r--devtools/server/tests/chrome/test_memory_allocations_04.html62
-rw-r--r--devtools/server/tests/chrome/test_memory_allocations_05.html93
-rw-r--r--devtools/server/tests/chrome/test_memory_allocations_06.html51
-rw-r--r--devtools/server/tests/chrome/test_memory_allocations_07.html58
-rw-r--r--devtools/server/tests/chrome/test_memory_attach_01.html33
-rw-r--r--devtools/server/tests/chrome/test_memory_attach_02.html49
-rw-r--r--devtools/server/tests/chrome/test_memory_census.html35
-rw-r--r--devtools/server/tests/chrome/test_memory_gc_01.html50
-rw-r--r--devtools/server/tests/chrome/test_memory_gc_events.html44
-rw-r--r--devtools/server/tests/chrome/test_overflowing-body.html42
-rw-r--r--devtools/server/tests/chrome/test_overflowing-children.html131
-rw-r--r--devtools/server/tests/chrome/test_preference.html129
-rw-r--r--devtools/server/tests/chrome/test_styles-applied.html143
-rw-r--r--devtools/server/tests/chrome/test_styles-computed.html130
-rw-r--r--devtools/server/tests/chrome/test_styles-layout.html110
-rw-r--r--devtools/server/tests/chrome/test_styles-matched.html103
-rw-r--r--devtools/server/tests/chrome/test_styles-modify.html110
-rw-r--r--devtools/server/tests/chrome/test_styles-svg.html61
-rw-r--r--devtools/server/tests/chrome/test_suspendTimeouts.html20
-rw-r--r--devtools/server/tests/chrome/test_suspendTimeouts.js139
-rw-r--r--devtools/server/tests/chrome/test_unsafeDereference.html53
-rw-r--r--devtools/server/tests/chrome/test_webconsole-node-grip.html68
-rw-r--r--devtools/server/tests/chrome/test_webextension-addon-debugging-connect.html80
-rw-r--r--devtools/server/tests/chrome/test_webextension-addon-debugging-reload.html122
-rw-r--r--devtools/server/tests/chrome/webconsole-helpers.js58
-rw-r--r--devtools/server/tests/chrome/webextension-helpers.js138
113 files changed, 9130 insertions, 0 deletions
diff --git a/devtools/server/tests/chrome/.eslintrc.js b/devtools/server/tests/chrome/.eslintrc.js
new file mode 100644
index 0000000000..e563d6bea6
--- /dev/null
+++ b/devtools/server/tests/chrome/.eslintrc.js
@@ -0,0 +1,6 @@
+"use strict";
+
+module.exports = {
+ // Extend from the common devtools mochitest eslintrc config.
+ extends: "../../../.eslintrc.mochitests.js",
+};
diff --git a/devtools/server/tests/chrome/Debugger.Source.prototype.element-2.js b/devtools/server/tests/chrome/Debugger.Source.prototype.element-2.js
new file mode 100644
index 0000000000..7260431428
--- /dev/null
+++ b/devtools/server/tests/chrome/Debugger.Source.prototype.element-2.js
@@ -0,0 +1,4 @@
+"use strict";
+
+// eslint-disable-next-line no-debugger
+debugger;
diff --git a/devtools/server/tests/chrome/Debugger.Source.prototype.element.html b/devtools/server/tests/chrome/Debugger.Source.prototype.element.html
new file mode 100644
index 0000000000..6959ad970d
--- /dev/null
+++ b/devtools/server/tests/chrome/Debugger.Source.prototype.element.html
@@ -0,0 +1,25 @@
+<head>
+ <!-- Static (not dynamically inserted) inline script. -->
+ <script id='franz'>
+ /* exported franz */
+ "use strict";
+
+ function franz() {
+ // eslint-disable-next-line no-debugger
+ debugger;
+ }
+ </script>
+
+ <!-- Static out-of-line script element. -->
+ <script id='heinrich' src='Debugger.Source.prototype.element.js'></script>
+</head>
+
+<!-- HTML requires some body element onfoo attributes to add handlers to the
+ *window*, not the element --- but Debugger.Source.prototype.element should
+ return the element. Here, that rule should apply to the body's 'onresize'
+ handler. (For the reason for the 'cancelable' check, see the code that
+ sends the event.) -->
+<body onresize='if (event.cancelable) debugger;'>
+ <!-- Ordinary content element with event handler. -->
+ <div id='heidi' onclick='heinrichFun();'>Heidi</div>
+</body>
diff --git a/devtools/server/tests/chrome/Debugger.Source.prototype.element.js b/devtools/server/tests/chrome/Debugger.Source.prototype.element.js
new file mode 100644
index 0000000000..095398ddad
--- /dev/null
+++ b/devtools/server/tests/chrome/Debugger.Source.prototype.element.js
@@ -0,0 +1,7 @@
+/* exported heinrichFun */
+/* global franz */
+"use strict";
+
+function heinrichFun() {
+ franz();
+}
diff --git a/devtools/server/tests/chrome/chrome.ini b/devtools/server/tests/chrome/chrome.ini
new file mode 100644
index 0000000000..2ef1d8385b
--- /dev/null
+++ b/devtools/server/tests/chrome/chrome.ini
@@ -0,0 +1,105 @@
+[DEFAULT]
+tags = devtools
+skip-if = os == 'android'
+support-files =
+ doc_Debugger.Source.prototype.introductionType.xhtml
+ Debugger.Source.prototype.element.js
+ Debugger.Source.prototype.element-2.js
+ Debugger.Source.prototype.element.html
+ framerate-helpers.js
+ hello-actor.js
+ iframe1_makeGlobalObjectReference.html
+ iframe2_makeGlobalObjectReference.html
+ inspector_css-properties.html
+ inspector_display-type.html
+ inspector_getImageData.html
+ inspector_getOffsetParent.html
+ inspector-delay-image-response.sjs
+ inspector-eyedropper.html
+ inspector-helpers.js
+ inspector-search-data.html
+ inspector-styles-data.css
+ inspector-styles-data.html
+ inspector-template.html
+ inspector-traversal-data.html
+ large-image.jpg
+ memory-helpers.js
+ nonchrome_unsafeDereference.html
+ suspendTimeouts_content.html
+ suspendTimeouts_content.js
+ suspendTimeouts_worker.js
+ small-image.gif
+ test_suspendTimeouts.js
+ webconsole-helpers.js
+ webextension-helpers.js
+ inactive-property-helper/*.js
+[test_animation-type-longhand.html]
+[test_css-logic.html]
+[test_css-logic-media-queries.html]
+[test_css-logic-specificity.html]
+[test_css-properties.html]
+[test_Debugger.Source.prototype.introductionScript.html]
+[test_Debugger.Source.prototype.introductionType.html]
+[test_Debugger.Source.prototype.element.html]
+[test_Debugger.Script.prototype.global.html]
+[test_device.html]
+[test_executeInGlobal-outerized_this.html]
+[test_framerate_01.html]
+[test_framerate_02.html]
+[test_framerate_03.html]
+[test_framerate_04.html]
+[test_framerate_05.html]
+[test_framerate_06.html]
+[test_highlighter_paused_debugger.html]
+[test_inspector-changeattrs.html]
+[test_inspector-changevalue.html]
+[test_inspector-dead-nodes.html]
+[test_inspector-display-type.html]
+[test_inspector-duplicate-node.html]
+[test_inspector_getImageData.html]
+[test_inspector_getImageDataFromURL.html]
+[test_inspector_getImageData-wait-for-load.html]
+[test_inspector_getNodeFromActor.html]
+[test_inspector_getOffsetParent.html]
+[test_inspector-hide.html]
+[test_inspector-inactive-property-helper.html]
+[test_inspector-mutations-attr.html]
+[test_inspector-mutations-events.html]
+[test_inspector-mutations-value.html]
+[test_inspector-pick-color.html]
+[test_inspector-pseudoclass-lock.html]
+[test_inspector-reload.html]
+[test_inspector-resize.html]
+[test_inspector-resolve-url.html]
+[test_inspector-search-front.html]
+[test_inspector-scroll-into-view.html]
+[test_inspector-template.html]
+[test_makeGlobalObjectReference.html]
+[test_memory.html]
+[test_memory_allocations_02.html]
+[test_memory_allocations_03.html]
+[test_memory_allocations_04.html]
+[test_memory_allocations_05.html]
+[test_memory_allocations_06.html]
+[test_memory_allocations_07.html]
+[test_memory_attach_01.html]
+[test_memory_attach_02.html]
+[test_memory_census.html]
+[test_memory_gc_01.html]
+[test_memory_gc_events.html]
+[test_overflowing-children.html]
+[test_overflowing-body.html]
+[test_preference.html]
+[test_styles-applied.html]
+[test_styles-computed.html]
+[test_styles-layout.html]
+[test_styles-matched.html]
+[test_styles-modify.html]
+[test_styles-svg.html]
+[test_unsafeDereference.html]
+[test_webconsole-node-grip.html]
+[test_webextension-addon-debugging-connect.html]
+disabled = true # Bug 1467313: broken, needs repair or removal
+[test_webextension-addon-debugging-reload.html]
+disabled = true # Bug 1467313: broken, needs repair or removal
+[test_suspendTimeouts.html]
diff --git a/devtools/server/tests/chrome/doc_Debugger.Source.prototype.introductionType.xhtml b/devtools/server/tests/chrome/doc_Debugger.Source.prototype.introductionType.xhtml
new file mode 100644
index 0000000000..b037190c9a
--- /dev/null
+++ b/devtools/server/tests/chrome/doc_Debugger.Source.prototype.introductionType.xhtml
@@ -0,0 +1,7 @@
+<?xml version="1.0"?>
+<window xmlns='http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul'>
+<script id='xulie'>
+/* eslint-disable strict, no-unused-vars, no-debugger */
+function xulScriptFunc() { debugger; }
+</script>
+</window>
diff --git a/devtools/server/tests/chrome/framerate-helpers.js b/devtools/server/tests/chrome/framerate-helpers.js
new file mode 100644
index 0000000000..1539e8b517
--- /dev/null
+++ b/devtools/server/tests/chrome/framerate-helpers.js
@@ -0,0 +1,63 @@
+/* exported getTargetForSelectedTab, waitFor, plotFPS */
+"use strict";
+const { require } = ChromeUtils.import("resource://devtools/shared/Loader.jsm");
+const { TargetFactory } = require("devtools/client/framework/target");
+const Services = require("Services");
+
+// Always log packets when running tests.
+Services.prefs.setBoolPref("devtools.debugger.log", true);
+SimpleTest.registerCleanupFunction(function() {
+ Services.prefs.clearUserPref("devtools.debugger.log");
+});
+
+SimpleTest.waitForExplicitFinish();
+
+/**
+ * Add a new test tab in the browser and load the given url.
+ * @return Promise a promise that resolves to the new target representing
+ * the page currently opened.
+ */
+function getTargetForSelectedTab() {
+ // Get the target and get the necessary front
+ const { gBrowser } = Services.wm.getMostRecentWindow("navigator:browser");
+ return TargetFactory.forTab(gBrowser.selectedTab);
+}
+
+function waitFor(time) {
+ return new Promise(resolve => setTimeout(resolve, time));
+}
+
+function plotFPS(ticks, interval = 100, clamp = 60) {
+ const timeline = [];
+ const totalTicks = ticks.length;
+
+ // If the refresh driver didn't get a chance to tick before the
+ // recording was stopped, assume framerate was 0.
+ if (totalTicks == 0) {
+ timeline.push({ delta: 0, value: 0 });
+ timeline.push({ delta: interval, value: 0 });
+ return timeline;
+ }
+
+ let frameCount = 0;
+ let prevTime = ticks[0];
+
+ for (let i = 1; i < totalTicks; i++) {
+ const currTime = ticks[i];
+ frameCount++;
+
+ const elapsedTime = currTime - prevTime;
+ if (elapsedTime < interval) {
+ continue;
+ }
+
+ const framerate = Math.min(1000 / (elapsedTime / frameCount), clamp);
+ timeline.push({ delta: prevTime, value: framerate });
+ timeline.push({ delta: currTime, value: framerate });
+
+ frameCount = 0;
+ prevTime = currTime;
+ }
+
+ return timeline;
+}
diff --git a/devtools/server/tests/chrome/hello-actor.js b/devtools/server/tests/chrome/hello-actor.js
new file mode 100644
index 0000000000..a0ccbf2727
--- /dev/null
+++ b/devtools/server/tests/chrome/hello-actor.js
@@ -0,0 +1,28 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+/* exported HelloActor */
+"use strict";
+
+const protocol = require("devtools/shared/protocol");
+
+const helloSpec = protocol.generateActorSpec({
+ typeName: "helloActor",
+
+ methods: {
+ count: {
+ request: {},
+ response: { count: protocol.RetVal("number") },
+ },
+ },
+});
+
+var HelloActor = protocol.ActorClassWithSpec(helloSpec, {
+ initialize: function() {
+ protocol.Actor.prototype.initialize.apply(this, arguments);
+ this.counter = 0;
+ },
+
+ count: function() {
+ return ++this.counter;
+ },
+});
diff --git a/devtools/server/tests/chrome/iframe1_makeGlobalObjectReference.html b/devtools/server/tests/chrome/iframe1_makeGlobalObjectReference.html
new file mode 100644
index 0000000000..bab5a70765
--- /dev/null
+++ b/devtools/server/tests/chrome/iframe1_makeGlobalObjectReference.html
@@ -0,0 +1 @@
+<html>The word 'smorgasbord' spoken by an adorably plump child, symbolizing prosperity</html>
diff --git a/devtools/server/tests/chrome/iframe2_makeGlobalObjectReference.html b/devtools/server/tests/chrome/iframe2_makeGlobalObjectReference.html
new file mode 100644
index 0000000000..b297ca8a2b
--- /dev/null
+++ b/devtools/server/tests/chrome/iframe2_makeGlobalObjectReference.html
@@ -0,0 +1 @@
+<html>Her retrospection, in hindsight, was prescient.</html>
diff --git a/devtools/server/tests/chrome/inactive-property-helper/.eslintrc.js b/devtools/server/tests/chrome/inactive-property-helper/.eslintrc.js
new file mode 100644
index 0000000000..1514b14fde
--- /dev/null
+++ b/devtools/server/tests/chrome/inactive-property-helper/.eslintrc.js
@@ -0,0 +1,5 @@
+module.exports = {
+ parserOptions: {
+ sourceType: "module",
+ },
+};
diff --git a/devtools/server/tests/chrome/inactive-property-helper/align-content.js b/devtools/server/tests/chrome/inactive-property-helper/align-content.js
new file mode 100644
index 0000000000..4a5f4837ff
--- /dev/null
+++ b/devtools/server/tests/chrome/inactive-property-helper/align-content.js
@@ -0,0 +1,93 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// InactivePropertyHelper `align-content` test cases.
+
+export default [
+ {
+ info:
+ "align-content is inactive on block elements (until bug 1105571 is fixed)",
+ property: "align-content",
+ tagName: "div",
+ rules: ["div { align-content: center; }"],
+ isActive: false,
+ },
+ {
+ info: "align-content is active on flex containers",
+ property: "align-content",
+ tagName: "div",
+ rules: ["div { align-content: center; display: flex; }"],
+ isActive: true,
+ },
+ {
+ info: "align-content is active on grid containers",
+ property: "align-content",
+ tagName: "div",
+ rules: ["div { align-content: center; display: grid; }"],
+ isActive: true,
+ },
+ {
+ info: "align-content is inactive on flex items",
+ property: "align-content",
+ createTestElement: rootNode => {
+ const container = document.createElement("div");
+ const element = document.createElement("span");
+ container.append(element);
+ rootNode.append(container);
+ return element;
+ },
+ rules: ["div { display: flex; }", "span { align-content: center; }"],
+ ruleIndex: 1,
+ isActive: false,
+ },
+ {
+ info: "align-content is inactive on grid items",
+ property: "align-content",
+ createTestElement: rootNode => {
+ const container = document.createElement("div");
+ const element = document.createElement("span");
+ container.append(element);
+ rootNode.append(container);
+ return element;
+ },
+ rules: ["div { display: grid; }", "span { align-content: center; }"],
+ ruleIndex: 1,
+ isActive: false,
+ },
+ {
+ info: "align-content:baseline is active on flex items",
+ property: "align-content",
+ createTestElement: rootNode => {
+ const container = document.createElement("div");
+ const element = document.createElement("span");
+ container.append(element);
+ rootNode.append(container);
+ return element;
+ },
+ rules: ["div { display: flex; }", "span { align-content: baseline; }"],
+ ruleIndex: 1,
+ isActive: true,
+ },
+ {
+ info: "align-content:baseline is active on grid items",
+ property: "align-content",
+ createTestElement: rootNode => {
+ const container = document.createElement("div");
+ const element = document.createElement("span");
+ container.append(element);
+ rootNode.append(container);
+ return element;
+ },
+ rules: ["div { display: grid; }", "span { align-content: baseline; }"],
+ ruleIndex: 1,
+ isActive: true,
+ },
+ {
+ info: "align-content:baseline is active on table cells",
+ property: "align-content",
+ tagName: "div",
+ rules: ["div { display: table-cell; align-content: baseline; }"],
+ isActive: true,
+ },
+];
diff --git a/devtools/server/tests/chrome/inactive-property-helper/flex-grid-item-properties.js b/devtools/server/tests/chrome/inactive-property-helper/flex-grid-item-properties.js
new file mode 100644
index 0000000000..79c587798a
--- /dev/null
+++ b/devtools/server/tests/chrome/inactive-property-helper/flex-grid-item-properties.js
@@ -0,0 +1,229 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// InactivePropertyHelper `align-self`, `place-self`, and `order` test cases.
+export default [
+ {
+ info: "align-self is inactive on block element",
+ property: "align-self",
+ tagName: "div",
+ rules: ["div { align-self: center; }"],
+ isActive: false,
+ },
+ {
+ info: "align-self is inactive on flex container",
+ property: "align-self",
+ tagName: "div",
+ rules: ["div { align-self: center; display: flex;}"],
+ isActive: false,
+ },
+ {
+ info: "align-self is inactive on inline-flex container",
+ property: "align-self",
+ tagName: "div",
+ rules: ["div { align-self: center; display: inline-flex;}"],
+ isActive: false,
+ },
+ {
+ info: "align-self is inactive on grid container",
+ property: "align-self",
+ tagName: "div",
+ rules: ["div { align-self: center; display: grid;}"],
+ isActive: false,
+ },
+ {
+ info: "align-self is inactive on inline grid container",
+ property: "align-self",
+ tagName: "div",
+ rules: ["div { align-self: center; display: inline-grid;}"],
+ isActive: false,
+ },
+ {
+ info: "align-self is inactive on inline element",
+ property: "align-self",
+ tagName: "span",
+ rules: ["span { align-self: center; }"],
+ isActive: false,
+ },
+ {
+ info: "align-self is active on flex item",
+ property: "align-self",
+ createTestElement: rootNode => {
+ const container = document.createElement("div");
+ const element = document.createElement("span");
+ container.append(element);
+ rootNode.append(container);
+ return element;
+ },
+ rules: [
+ "div { display: flex; align-items: start; }",
+ "span { align-self: center; }",
+ ],
+ ruleIndex: 1,
+ isActive: true,
+ },
+ {
+ info: "align-self is active on grid item",
+ property: "align-self",
+ createTestElement: rootNode => {
+ const container = document.createElement("div");
+ const element = document.createElement("span");
+ container.append(element);
+ rootNode.append(container);
+ return element;
+ },
+ rules: [
+ "div { display: grid; align-items: start; }",
+ "span { align-self: center; }",
+ ],
+ ruleIndex: 1,
+ isActive: true,
+ },
+ {
+ info: "place-self is inactive on block element",
+ property: "place-self",
+ tagName: "div",
+ rules: ["div { place-self: center; }"],
+ isActive: false,
+ },
+ {
+ info: "place-self is inactive on flex container",
+ property: "place-self",
+ tagName: "div",
+ rules: ["div { place-self: center; display: flex;}"],
+ isActive: false,
+ },
+ {
+ info: "place-self is inactive on inline-flex container",
+ property: "place-self",
+ tagName: "div",
+ rules: ["div { place-self: center; display: inline-flex;}"],
+ isActive: false,
+ },
+ {
+ info: "place-self is inactive on grid container",
+ property: "place-self",
+ tagName: "div",
+ rules: ["div { place-self: center; display: grid;}"],
+ isActive: false,
+ },
+ {
+ info: "place-self is inactive on inline grid container",
+ property: "place-self",
+ tagName: "div",
+ rules: ["div { place-self: center; display: inline-grid;}"],
+ isActive: false,
+ },
+ {
+ info: "place-self is inactive on inline element",
+ property: "place-self",
+ tagName: "span",
+ rules: ["span { place-self: center; }"],
+ isActive: false,
+ },
+ {
+ info: "place-self is active on flex item",
+ property: "place-self",
+ createTestElement: rootNode => {
+ const container = document.createElement("div");
+ const element = document.createElement("span");
+ container.append(element);
+ rootNode.append(container);
+ return element;
+ },
+ rules: [
+ "div { display: flex; align-items: start; }",
+ "span { place-self: center; }",
+ ],
+ ruleIndex: 1,
+ isActive: true,
+ },
+ {
+ info: "place-self is active on grid item",
+ property: "place-self",
+ createTestElement: rootNode => {
+ const container = document.createElement("div");
+ const element = document.createElement("span");
+ container.append(element);
+ rootNode.append(container);
+ return element;
+ },
+ rules: [
+ "div { display: grid; align-items: start; }",
+ "span { place-self: center; }",
+ ],
+ ruleIndex: 1,
+ isActive: true,
+ },
+ {
+ info: "order is inactive on block element",
+ property: "order",
+ tagName: "div",
+ rules: ["div { order: 1; }"],
+ isActive: false,
+ },
+ {
+ info: "order is inactive on flex container",
+ property: "order",
+ tagName: "div",
+ rules: ["div { order: 1; display: flex;}"],
+ isActive: false,
+ },
+ {
+ info: "order is inactive on inline-flex container",
+ property: "order",
+ tagName: "div",
+ rules: ["div { order: 1; display: inline-flex;}"],
+ isActive: false,
+ },
+ {
+ info: "order is inactive on grid container",
+ property: "order",
+ tagName: "div",
+ rules: ["div { order: 1; display: grid;}"],
+ isActive: false,
+ },
+ {
+ info: "order is inactive on inline grid container",
+ property: "order",
+ tagName: "div",
+ rules: ["div { order: 1; display: inline-grid;}"],
+ isActive: false,
+ },
+ {
+ info: "order is inactive on inline element",
+ property: "order",
+ tagName: "span",
+ rules: ["span { order: 1; }"],
+ isActive: false,
+ },
+ {
+ info: "order is active on flex item",
+ property: "order",
+ createTestElement: rootNode => {
+ const container = document.createElement("div");
+ const element = document.createElement("span");
+ container.append(element);
+ rootNode.append(container);
+ return element;
+ },
+ rules: ["div { display: flex; }", "span { order: 1; }"],
+ ruleIndex: 1,
+ isActive: true,
+ },
+ {
+ info: "order is active on grid item",
+ property: "order",
+ createTestElement: rootNode => {
+ const container = document.createElement("div");
+ const element = document.createElement("span");
+ container.append(element);
+ rootNode.append(container);
+ return element;
+ },
+ rules: ["div { display: grid; }", "span { order: 1; }"],
+ ruleIndex: 1,
+ isActive: true,
+ },
+];
diff --git a/devtools/server/tests/chrome/inactive-property-helper/float.js b/devtools/server/tests/chrome/inactive-property-helper/float.js
new file mode 100644
index 0000000000..4c502e3cca
--- /dev/null
+++ b/devtools/server/tests/chrome/inactive-property-helper/float.js
@@ -0,0 +1,76 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// InactivePropertyHelper `float` test cases.
+export default [
+ {
+ info: "display: inline is inactive on a floated element",
+ property: "display",
+ tagName: "div",
+ rules: ["div { display: inline; float: right; }"],
+ isActive: false,
+ },
+ {
+ info: "display: block is active on a floated element",
+ property: "display",
+ tagName: "div",
+ rules: ["div { display: block; float: right;}"],
+ isActive: true,
+ },
+ {
+ info: "display: inline-grid is inactive on a floated element",
+ property: "display",
+ createTestElement: rootNode => {
+ const container = document.createElement("div");
+ container.classList.add("test");
+ rootNode.append(container);
+ return container;
+ },
+ rules: [
+ "div { float: left; display:block; }",
+ ".test { display: inline-grid ;}",
+ ],
+ isActive: false,
+ },
+ {
+ info: "display: table-footer-group is inactive on a floated element",
+ property: "display",
+ createTestElement: rootNode => {
+ const container = document.createElement("div");
+ container.style.display = "table";
+ const footer = document.createElement("div");
+ footer.classList.add("table-footer");
+ container.append(footer);
+ rootNode.append(container);
+ return footer;
+ },
+ rules: [".table-footer { display: table-footer-group; float: left;}"],
+ isActive: false,
+ },
+ createGridPlacementOnFloatedItemTest("grid-row"),
+ createGridPlacementOnFloatedItemTest("grid-column"),
+ createGridPlacementOnFloatedItemTest("grid-area", "foo"),
+];
+
+function createGridPlacementOnFloatedItemTest(property, value = "2") {
+ return {
+ info: `grid placement property ${property} is active on a floated grid item`,
+ property,
+ createTestElement: rootNode => {
+ const grid = document.createElement("div");
+ grid.style.display = "grid";
+ grid.style.gridTemplateRows = "repeat(5, 1fr)";
+ grid.style.gridTemplateColumns = "repeat(5, 1fr)";
+ grid.style.gridTemplateAreas = "'foo foo foo'";
+ rootNode.appendChild(grid);
+
+ const item = document.createElement("span");
+ grid.appendChild(item);
+
+ return item;
+ },
+ rules: [`span { ${property}: ${value}; float: left; }`],
+ isActive: true,
+ };
+}
diff --git a/devtools/server/tests/chrome/inactive-property-helper/gap.js b/devtools/server/tests/chrome/inactive-property-helper/gap.js
new file mode 100644
index 0000000000..e2dd0b5bbb
--- /dev/null
+++ b/devtools/server/tests/chrome/inactive-property-helper/gap.js
@@ -0,0 +1,98 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// InactivePropertyHelper `gap` test cases.
+export default [
+ {
+ info: "column-gap is inactive on non-grid and non-flex container",
+ property: "column-gap",
+ tagName: "div",
+ rules: ["div { column-gap: 10px; display: block; }"],
+ isActive: false,
+ },
+ {
+ info: "column-gap is active on grid container",
+ property: "column-gap",
+ tagName: "div",
+ rules: ["div { column-gap: 10px; display: grid; }"],
+ isActive: true,
+ },
+ {
+ info: "column-gap is active on flex container",
+ property: "column-gap",
+ tagName: "div",
+ rules: ["div { column-gap: 10px; display: flex; }"],
+ isActive: true,
+ },
+ {
+ info: "column-gap is inactive on non-multi-col container",
+ property: "column-gap",
+ tagName: "div",
+ rules: ["div { column-gap: 10px; column-count: auto; }"],
+ isActive: false,
+ },
+ {
+ info: "column-gap is active on multi-column container",
+ property: "column-gap",
+ tagName: "div",
+ rules: ["div { column-gap: 10px; column-count: 2; }"],
+ isActive: true,
+ },
+ {
+ info: "row-gap is inactive on non-grid and non-flex container",
+ property: "row-gap",
+ tagName: "div",
+ rules: ["div { row-gap: 10px; display: block; }"],
+ isActive: false,
+ },
+ {
+ info: "row-gap is active on grid container",
+ property: "row-gap",
+ tagName: "div",
+ rules: ["div { row-gap: 10px; display: grid; }"],
+ isActive: true,
+ },
+ {
+ info: "row-gap is active on flex container",
+ property: "row-gap",
+ tagName: "div",
+ rules: ["div { row-gap: 10px; display: flex; }"],
+ isActive: true,
+ },
+ {
+ info: "gap is inactive on non-grid and non-flex container",
+ property: "gap",
+ tagName: "div",
+ rules: ["div { gap: 10px; display: block; }"],
+ isActive: false,
+ },
+ {
+ info: "gap is active on flex container",
+ property: "gap",
+ tagName: "div",
+ rules: ["div { gap: 10px; display: flex; }"],
+ isActive: true,
+ },
+ {
+ info: "gap is active on grid container",
+ property: "gap",
+ tagName: "div",
+ rules: ["div { gap: 10px; display: grid; }"],
+ isActive: true,
+ },
+ {
+ info: "gap is inactive on non-multi-col container",
+ property: "gap",
+ tagName: "div",
+ rules: ["div { gap: 10px; column-count: auto; }"],
+ isActive: false,
+ },
+ {
+ info: "gap is active on multi-col container",
+ property: "gap",
+ tagName: "div",
+ rules: ["div { gap: 10px; column-count: 2; }"],
+ isActive: true,
+ },
+];
diff --git a/devtools/server/tests/chrome/inactive-property-helper/grid-with-absolute-properties.js b/devtools/server/tests/chrome/inactive-property-helper/grid-with-absolute-properties.js
new file mode 100644
index 0000000000..fd963e0d3b
--- /dev/null
+++ b/devtools/server/tests/chrome/inactive-property-helper/grid-with-absolute-properties.js
@@ -0,0 +1,71 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// InactivePropertyHelper test cases:
+// `grid-area`, `grid-column`, `grid-column-end`, `grid-column-start`,
+// `grid-row`, `grid-row-end`, `grid-row-start`, `justify-self`, `align-self`
+// and `place-self`.
+let tests = [];
+
+for (const { propertyName, propertyValue } of [
+ { propertyName: "grid-area", propertyValue: "2 / 1 / span 2 / span 3" },
+ { propertyName: "grid-column", propertyValue: 2 },
+ { propertyName: "grid-column-end", propertyValue: "span 3" },
+ { propertyName: "grid-column-start", propertyValue: 2 },
+ { propertyName: "grid-row", propertyValue: "1 / span 2" },
+ { propertyName: "grid-row-end", propertyValue: "span 3" },
+ { propertyName: "grid-row-start", propertyValue: 2 },
+ { propertyName: "justify-self", propertyValue: "start" },
+ { propertyName: "align-self", propertyValue: "auto" },
+ { propertyName: "place-self", propertyValue: "auto center" },
+]) {
+ tests = tests.concat(createTestsForProp(propertyName, propertyValue));
+}
+
+function createTestsForProp(propertyName, propertyValue) {
+ return [
+ {
+ info: `${propertyName} is active on a grid item`,
+ property: `${propertyName}`,
+ createTestElement,
+ rules: [
+ `#grid-container { display:grid; grid:auto/100px 100px; }`,
+ `#grid-item { ${propertyName}: ${propertyValue}; }`,
+ ],
+ ruleIndex: 1,
+ isActive: true,
+ },
+ {
+ info: `${propertyName} is active on an absolutely positioned grid item`,
+ property: `${propertyName}`,
+ createTestElement,
+ rules: [
+ `#grid-container { display:grid; grid:auto/100px 100px; position: relative }`,
+ `#grid-item { ${propertyName}: ${propertyValue}; position: absolute; }`,
+ ],
+ ruleIndex: 1,
+ isActive: true,
+ },
+ {
+ info: `${propertyName} is inactive on a non-grid item`,
+ property: `${propertyName}`,
+ tagName: `div`,
+ rules: [`div { ${propertyName}: ${propertyValue}; }`],
+ isActive: false,
+ },
+ ];
+}
+
+function createTestElement(rootNode) {
+ const container = document.createElement("div");
+ container.id = "grid-container";
+ const element = document.createElement("div");
+ element.id = "grid-item";
+ container.append(element);
+ rootNode.append(container);
+
+ return element;
+}
+
+export default tests;
diff --git a/devtools/server/tests/chrome/inactive-property-helper/margin-padding.js b/devtools/server/tests/chrome/inactive-property-helper/margin-padding.js
new file mode 100644
index 0000000000..7c1d348512
--- /dev/null
+++ b/devtools/server/tests/chrome/inactive-property-helper/margin-padding.js
@@ -0,0 +1,260 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// InactivePropertyHelper `align-content` test cases.
+
+export default [
+ {
+ info: "margin is active on block containers",
+ property: "margin",
+ tagName: "div",
+ rules: ["div { margin: 10px; }"],
+ isActive: true,
+ },
+ {
+ info: "margin is active on flex containers",
+ property: "margin",
+ tagName: "div",
+ rules: ["div { display: flex; margin: 10px; }"],
+ isActive: true,
+ },
+ {
+ info: "margin is active on grid containers",
+ property: "margin",
+ tagName: "div",
+ rules: ["div { display: grid; margin: 10px; }"],
+ isActive: true,
+ },
+ {
+ info: "margin is active on tables",
+ property: "margin",
+ tagName: "div",
+ rules: ["div { display: table; margin: 10px; }"],
+ isActive: true,
+ },
+ {
+ info: "margin is active on inline tables",
+ property: "margin",
+ tagName: "div",
+ rules: ["div { display: inline-table; margin: 10px; }"],
+ isActive: true,
+ },
+ {
+ info: "margin is active on table captions",
+ property: "margin",
+ tagName: "div",
+ rules: ["div { display: table-caption; margin: 10px; }"],
+ isActive: true,
+ },
+ {
+ info: "margin is inactive on table cells",
+ property: "margin",
+ tagName: "div",
+ rules: ["div { display: table-cell; margin: 10px; }"],
+ isActive: false,
+ },
+ {
+ info: "margin-block is inactive on table cells",
+ property: "margin-block",
+ tagName: "div",
+ rules: ["div { display: table-cell; margin-block: 10px; }"],
+ isActive: false,
+ },
+ {
+ info: "margin-block-start is inactive on table cells",
+ property: "margin-block-start",
+ tagName: "div",
+ rules: ["div { display: table-cell; margin-block-start: 10px; }"],
+ isActive: false,
+ },
+ {
+ info: "margin-block-end is inactive on table cells",
+ property: "margin-block-end",
+ tagName: "div",
+ rules: ["div { display: table-cell; margin-block-end: 10px; }"],
+ isActive: false,
+ },
+ {
+ info: "margin-block is inactive on table cells",
+ property: "margin-block",
+ tagName: "div",
+ rules: ["div { display: table-cell; margin-block: 10px; }"],
+ isActive: false,
+ },
+ {
+ info: "margin-bottom is inactive on table rows",
+ property: "margin-bottom",
+ tagName: "div",
+ rules: ["div { display: table-row; margin-bottom: 10px; }"],
+ isActive: false,
+ },
+ {
+ info: "margin-inline-start is inactive on table rows",
+ property: "margin-inline-start",
+ tagName: "div",
+ rules: ["div { display: table-row; margin-inline-start: 10px; }"],
+ isActive: false,
+ },
+ {
+ info: "margin-inline-end is inactive on table rows",
+ property: "margin-inline-end",
+ tagName: "div",
+ rules: ["div { display: table-row; margin-inline-end: 10px; }"],
+ isActive: false,
+ },
+ {
+ info: "margin-inline is inactive on table rows",
+ property: "margin-inline",
+ tagName: "div",
+ rules: ["div { display: table-row; margin-inline: 10px; }"],
+ isActive: false,
+ },
+ {
+ info: "margin-left is inactive on table columns",
+ property: "margin-left",
+ tagName: "div",
+ rules: ["div { display: table-column; margin-left: 10px; }"],
+ isActive: false,
+ },
+ {
+ info: "margin-right is inactive on table row groups",
+ property: "margin-right",
+ tagName: "div",
+ rules: ["div { display: table-row-group; margin-right: 10px; }"],
+ isActive: false,
+ },
+ {
+ info: "margin-top is inactive on table column groups",
+ property: "margin-top",
+ tagName: "div",
+ rules: ["div { display: table-column-group; margin-top: 10px; }"],
+ isActive: false,
+ },
+ {
+ info: "padding is active on block containers",
+ property: "padding",
+ tagName: "div",
+ rules: ["div { padding: 10px; }"],
+ isActive: true,
+ },
+ {
+ info: "padding is active on flex containers",
+ property: "padding",
+ tagName: "div",
+ rules: ["div { display: flex; padding: 10px; }"],
+ isActive: true,
+ },
+ {
+ info: "padding is active on grid containers",
+ property: "padding",
+ tagName: "div",
+ rules: ["div { display: grid; padding: 10px; }"],
+ isActive: true,
+ },
+ {
+ info: "padding is active on tables",
+ property: "padding",
+ tagName: "div",
+ rules: ["div { display: table; padding: 10px; }"],
+ isActive: true,
+ },
+ {
+ info: "padding is active on inline tables",
+ property: "padding",
+ tagName: "div",
+ rules: ["div { display: inline-table; padding: 10px; }"],
+ isActive: true,
+ },
+ {
+ info: "padding is active on table captions",
+ property: "padding",
+ tagName: "div",
+ rules: ["div { display: table-caption; padding: 10px; }"],
+ isActive: true,
+ },
+ {
+ info: "padding is active on table cells",
+ property: "padding",
+ tagName: "div",
+ rules: ["div { display: table-cell; padding: 10px; }"],
+ isActive: true,
+ },
+ {
+ info: "padding-block is active on table cells",
+ property: "padding-block",
+ tagName: "div",
+ rules: ["div { display: table-cell; padding-block: 10px; }"],
+ isActive: true,
+ },
+ {
+ info: "padding-block-start is active on table cells",
+ property: "padding-block-start",
+ tagName: "div",
+ rules: ["div { display: table-cell; padding-block-start: 10px; }"],
+ isActive: true,
+ },
+ {
+ info: "padding-block-end is active on table cells",
+ property: "padding-block-end",
+ tagName: "div",
+ rules: ["div { display: table-cell; padding-block-end: 10px; }"],
+ isActive: true,
+ },
+ {
+ info: "padding-block is active on table cells",
+ property: "padding-block",
+ tagName: "div",
+ rules: ["div { display: table-cell; padding-block: 10px; }"],
+ isActive: true,
+ },
+ {
+ info: "padding-bottom is inactive on table rows",
+ property: "padding-bottom",
+ tagName: "div",
+ rules: ["div { display: table-row; padding-bottom: 10px; }"],
+ isActive: false,
+ },
+ {
+ info: "padding-inline-start is inactive on table rows",
+ property: "padding-inline-start",
+ tagName: "div",
+ rules: ["div { display: table-row; padding-inline-start: 10px; }"],
+ isActive: false,
+ },
+ {
+ info: "padding-inline-end is inactive on table rows",
+ property: "padding-inline-end",
+ tagName: "div",
+ rules: ["div { display: table-row; padding-inline-end: 10px; }"],
+ isActive: false,
+ },
+ {
+ info: "padding-inline is inactive on table rows",
+ property: "padding-inline",
+ tagName: "div",
+ rules: ["div { display: table-row; padding-inline: 10px; }"],
+ isActive: false,
+ },
+ {
+ info: "padding-left is inactive on table columns",
+ property: "padding-left",
+ tagName: "div",
+ rules: ["div { display: table-column; padding-left: 10px; }"],
+ isActive: false,
+ },
+ {
+ info: "padding-right is inactive on table row groups",
+ property: "padding-right",
+ tagName: "div",
+ rules: ["div { display: table-row-group; padding-right: 10px; }"],
+ isActive: false,
+ },
+ {
+ info: "padding-top is inactive on table column groups",
+ property: "padding-top",
+ tagName: "div",
+ rules: ["div { display: table-column-group; padding-top: 10px; }"],
+ isActive: false,
+ },
+];
diff --git a/devtools/server/tests/chrome/inactive-property-helper/max-min-width-height.js b/devtools/server/tests/chrome/inactive-property-helper/max-min-width-height.js
new file mode 100644
index 0000000000..e0ce4d24a5
--- /dev/null
+++ b/devtools/server/tests/chrome/inactive-property-helper/max-min-width-height.js
@@ -0,0 +1,367 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// InactivePropertyHelper `width`, `min-width`, `max-width`, `height`, `min-height`,
+// `max-height` test cases.
+export default [
+ {
+ info: "width is inactive on a non-replaced inline element",
+ property: "width",
+ tagName: "span",
+ rules: ["span { width: 500px; }"],
+ isActive: false,
+ },
+ {
+ info: "min-width is inactive on a non-replaced inline element",
+ property: "min-width",
+ tagName: "span",
+ rules: ["span { min-width: 500px; }"],
+ isActive: false,
+ },
+ {
+ info: "max-width is inactive on a non-replaced inline element",
+ property: "max-width",
+ tagName: "span",
+ rules: ["span { max-width: 500px; }"],
+ isActive: false,
+ },
+ {
+ info: "width is inactive on an tr element",
+ property: "width",
+ tagName: "tr",
+ rules: ["tr { width: 500px; }"],
+ isActive: false,
+ },
+ {
+ info: "min-width is inactive on an tr element",
+ property: "min-width",
+ tagName: "tr",
+ rules: ["tr { min-width: 500px; }"],
+ isActive: false,
+ },
+ {
+ info: "max-width is inactive on an tr element",
+ property: "max-width",
+ tagName: "tr",
+ rules: ["tr { max-width: 500px; }"],
+ isActive: false,
+ },
+ {
+ info: "width is inactive on an thead element",
+ property: "width",
+ tagName: "thead",
+ rules: ["thead { width: 500px; }"],
+ isActive: false,
+ },
+ {
+ info: "min-width is inactive on an thead element",
+ property: "min-width",
+ tagName: "thead",
+ rules: ["thead { min-width: 500px; }"],
+ isActive: false,
+ },
+ {
+ info: "max-width is inactive on an thead element",
+ property: "max-width",
+ tagName: "thead",
+ rules: ["thead { max-width: 500px; }"],
+ isActive: false,
+ },
+ {
+ info: "width is inactive on an tfoot element",
+ property: "width",
+ tagName: "tfoot",
+ rules: ["tfoot { width: 500px; }"],
+ isActive: false,
+ },
+ {
+ info: "min-width is inactive on an tfoot element",
+ property: "min-width",
+ tagName: "tfoot",
+ rules: ["tfoot { min-width: 500px; }"],
+ isActive: false,
+ },
+ {
+ info: "max-width is inactive on an tfoot element",
+ property: "max-width",
+ tagName: "tfoot",
+ rules: ["tfoot { max-width: 500px; }"],
+ isActive: false,
+ },
+ {
+ info: "width is active on a replaced inline element",
+ property: "width",
+ tagName: "img",
+ rules: ["img { width: 500px; }"],
+ isActive: true,
+ },
+ {
+ info: "width is active on an inline input element",
+ property: "width",
+ tagName: "input",
+ rules: ["input { display: inline; width: 500px; }"],
+ isActive: true,
+ },
+ {
+ info: "width is active on an inline select element",
+ property: "width",
+ tagName: "select",
+ rules: ["select { display: inline; width: 500px; }"],
+ isActive: true,
+ },
+ {
+ info: "width is active on a textarea element",
+ property: "width",
+ tagName: "textarea",
+ rules: ["textarea { width: 500px; }"],
+ isActive: true,
+ },
+ {
+ info: "min-width is active on a replaced inline element",
+ property: "min-width",
+ tagName: "img",
+ rules: ["img { min-width: 500px; }"],
+ isActive: true,
+ },
+ {
+ info: "max-width is active on a replaced inline element",
+ property: "max-width",
+ tagName: "img",
+ rules: ["img { max-width: 500px; }"],
+ isActive: true,
+ },
+ {
+ info: "width is active on a block element",
+ property: "width",
+ tagName: "div",
+ rules: ["div { width: 500px; }"],
+ isActive: true,
+ },
+ {
+ info: "min-width is active on a block element",
+ property: "min-width",
+ tagName: "div",
+ rules: ["div { min-width: 500px; }"],
+ isActive: true,
+ },
+ {
+ info: "max-width is active on a block element",
+ property: "max-width",
+ tagName: "div",
+ rules: ["div { max-width: 500px; }"],
+ isActive: true,
+ },
+ {
+ info: "height is inactive on a non-replaced inline element",
+ property: "height",
+ tagName: "span",
+ rules: ["span { height: 500px; }"],
+ isActive: false,
+ },
+ {
+ info: "min-height is inactive on a non-replaced inline element",
+ property: "min-height",
+ tagName: "span",
+ rules: ["span { min-height: 500px; }"],
+ isActive: false,
+ },
+ {
+ info: "max-height is inactive on a non-replaced inline element",
+ property: "max-height",
+ tagName: "span",
+ rules: ["span { max-height: 500px; }"],
+ isActive: false,
+ },
+ {
+ info: "height is inactive on colgroup element",
+ property: "height",
+ tagName: "colgroup",
+ rules: ["colgroup { height: 500px; }"],
+ isActive: false,
+ },
+ {
+ info: "min-height is inactive on colgroup element",
+ property: "min-height",
+ tagName: "colgroup",
+ rules: ["colgroup { min-height: 500px; }"],
+ isActive: false,
+ },
+ {
+ info: "max-height is inactive on colgroup element",
+ property: "max-height",
+ tagName: "colgroup",
+ rules: ["colgroup { max-height: 500px; }"],
+ isActive: false,
+ },
+ {
+ info: "height is inactive on col element",
+ property: "height",
+ tagName: "col",
+ rules: ["col { height: 500px; }"],
+ isActive: false,
+ },
+ {
+ info: "min-height is inactive on col element",
+ property: "min-height",
+ tagName: "col",
+ rules: ["col { min-height: 500px; }"],
+ isActive: false,
+ },
+ {
+ info: "max-height is inactive on col element",
+ property: "max-height",
+ tagName: "col",
+ rules: ["col { max-height: 500px; }"],
+ isActive: false,
+ },
+ {
+ info: "height is active on a replaced inline element",
+ property: "height",
+ tagName: "img",
+ rules: ["img { height: 500px; }"],
+ isActive: true,
+ },
+ {
+ info: "height is active on an inline input element",
+ property: "height",
+ tagName: "input",
+ rules: ["input { display: inline; height: 500px; }"],
+ isActive: true,
+ },
+ {
+ info: "height is active on an inline select element",
+ property: "height",
+ tagName: "select",
+ rules: ["select { display: inline; height: 500px; }"],
+ isActive: true,
+ },
+ {
+ info: "height is active on a textarea element",
+ property: "height",
+ tagName: "textarea",
+ rules: ["textarea { height: 500px; }"],
+ isActive: true,
+ },
+ {
+ info: "min-height is active on a replaced inline element",
+ property: "min-height",
+ tagName: "img",
+ rules: ["img { min-height: 500px; }"],
+ isActive: true,
+ },
+ {
+ info: "max-height is active on a replaced inline element",
+ property: "max-height",
+ tagName: "img",
+ rules: ["img { max-height: 500px; }"],
+ isActive: true,
+ },
+ {
+ info: "height is active on a block element",
+ property: "height",
+ tagName: "div",
+ rules: ["div { height: 500px; }"],
+ isActive: true,
+ },
+ {
+ info: "min-height is active on a block element",
+ property: "min-height",
+ tagName: "div",
+ rules: ["div { min-height: 500px; }"],
+ isActive: true,
+ },
+ {
+ info: "max-height is active on a block element",
+ property: "max-height",
+ tagName: "div",
+ rules: ["div { max-height: 500px; }"],
+ isActive: true,
+ },
+ {
+ info: "height is active on an svg <rect> element.",
+ property: "height",
+ createTestElement: main => {
+ main.innerHTML = `
+ <svg width=100 height=100>
+ <rect width=100 fill=green></rect>
+ </svg>
+ `;
+ return main.querySelector("rect");
+ },
+ rules: ["rect { height: 100px; }"],
+ isActive: true,
+ },
+ createTableElementTestCase("width", false, "table-row"),
+ createTableElementTestCase("width", false, "table-row-group"),
+ createTableElementTestCase("width", true, "table-column"),
+ createTableElementTestCase("width", true, "table-column-group"),
+ createTableElementTestCase("height", false, "table-column"),
+ createTableElementTestCase("height", false, "table-column-group"),
+ createTableElementTestCase("height", true, "table-row"),
+ createTableElementTestCase("height", true, "table-row-group"),
+ createVerticalTableElementTestCase("width", true, "table-row"),
+ createVerticalTableElementTestCase("width", true, "table-row-group"),
+ createVerticalTableElementTestCase("width", false, "table-column"),
+ createVerticalTableElementTestCase("width", false, "table-column-group"),
+ createVerticalTableElementTestCase("height", true, "table-column"),
+ createVerticalTableElementTestCase("height", true, "table-column-group"),
+ createVerticalTableElementTestCase("height", false, "table-row"),
+ createVerticalTableElementTestCase("height", false, "table-row-group"),
+ {
+ info:
+ "width's inactivity status for a row takes the table's writing mode into account",
+ property: "width",
+ createTestElement: rootNode => {
+ const table = document.createElement("table");
+ table.style.writingMode = "vertical-lr";
+ rootNode.appendChild(table);
+
+ const tbody = document.createElement("tbody");
+ table.appendChild(tbody);
+
+ const tr = document.createElement("tr");
+ tbody.appendChild(tr);
+
+ const td = document.createElement("td");
+ tr.appendChild(td);
+
+ return tr;
+ },
+ rules: ["tr { writing-mode: horizontal-tb; width: 360px; }"],
+ isActive: true,
+ },
+];
+
+function createTableElementTestCase(property, isActive, displayType) {
+ return {
+ info: `${property} is ${
+ isActive ? "active" : "inactive"
+ } on a ${displayType}`,
+ property,
+ tagName: "div",
+ rules: [`div { display: ${displayType}; ${property}: 100px; }`],
+ isActive,
+ };
+}
+
+function createVerticalTableElementTestCase(property, isActive, displayType) {
+ return {
+ info: `${property} is ${
+ isActive ? "active" : "inactive"
+ } on a vertical ${displayType}`,
+ property,
+ createTestElement: rootNode => {
+ const container = document.createElement("div");
+ container.style.writingMode = "vertical-lr";
+ rootNode.append(container);
+
+ const element = document.createElement("span");
+ container.append(element);
+
+ return element;
+ },
+ rules: [`span { display: ${displayType}; ${property}: 100px; }`],
+ isActive,
+ };
+}
diff --git a/devtools/server/tests/chrome/inactive-property-helper/outline-radius.js b/devtools/server/tests/chrome/inactive-property-helper/outline-radius.js
new file mode 100644
index 0000000000..8c4985437f
--- /dev/null
+++ b/devtools/server/tests/chrome/inactive-property-helper/outline-radius.js
@@ -0,0 +1,28 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// InactivePropertyHelper `outline-radius` test cases.
+export default [
+ {
+ info: "-moz-outline-radius is inactive when outline-style is auto",
+ property: "-moz-outline-radius",
+ tagName: "div",
+ rules: ["div { outline: 10px auto; -moz-outline-radius: 10px; }"],
+ isActive: false,
+ },
+ {
+ info: "-moz-outline-radius is inactive when outline-style is none",
+ property: "-moz-outline-radius",
+ tagName: "div",
+ rules: ["div { -moz-outline-radius: 10px; }"],
+ isActive: false,
+ },
+ {
+ info: "-moz-outline-radius is active when outline-style is not auto",
+ property: "-moz-outline-radius",
+ tagName: "div",
+ rules: ["div { outline: 10px solid; -moz-outline-radius: 10px; }"],
+ isActive: true,
+ },
+];
diff --git a/devtools/server/tests/chrome/inactive-property-helper/place-items-content.js b/devtools/server/tests/chrome/inactive-property-helper/place-items-content.js
new file mode 100644
index 0000000000..f554a785a7
--- /dev/null
+++ b/devtools/server/tests/chrome/inactive-property-helper/place-items-content.js
@@ -0,0 +1,159 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// InactivePropertyHelper `place-items` and `place-content` test cases.
+export default [
+ {
+ info: "place-items is inactive on block element",
+ property: "place-items",
+ tagName: "div",
+ rules: ["div { place-items: center; }"],
+ isActive: false,
+ },
+ {
+ info: "place-items is inactive on inline element",
+ property: "place-items",
+ tagName: "span",
+ rules: ["span { place-items: center; }"],
+ isActive: false,
+ },
+ {
+ info: "place-items is inactive on flex item",
+ property: "place-items",
+ createTestElement: rootNode => {
+ const container = document.createElement("div");
+ const element = document.createElement("span");
+ container.append(element);
+ rootNode.append(container);
+ return element;
+ },
+ rules: [
+ "div { display: flex; align-items: start; }",
+ "span { place-items: center; }",
+ ],
+ ruleIndex: 1,
+ isActive: false,
+ },
+ {
+ info: "place-items is inactive on grid item",
+ property: "place-items",
+ createTestElement: rootNode => {
+ const container = document.createElement("div");
+ const element = document.createElement("span");
+ container.append(element);
+ rootNode.append(container);
+ return element;
+ },
+ rules: [
+ "div { display: grid; align-items: start; }",
+ "span { place-items: center; }",
+ ],
+ ruleIndex: 1,
+ isActive: false,
+ },
+ {
+ info: "place-items is active on flex container",
+ property: "place-items",
+ tagName: "div",
+ rules: ["div { place-items: center; display: flex;}"],
+ isActive: true,
+ },
+ {
+ info: "place-items is active on inline-flex container",
+ property: "place-items",
+ tagName: "div",
+ rules: ["div { place-items: center; display: inline-flex;}"],
+ isActive: true,
+ },
+ {
+ info: "place-items is active on grid container",
+ property: "place-items",
+ tagName: "div",
+ rules: ["div { place-items: center; display: grid;}"],
+ isActive: true,
+ },
+ {
+ info: "place-items is active on inline grid container",
+ property: "place-items",
+ tagName: "div",
+ rules: ["div { place-items: center; display: inline-grid;}"],
+ isActive: true,
+ },
+ {
+ info: "place-content is inactive on block element",
+ property: "place-content",
+ tagName: "div",
+ rules: ["div { place-content: center; }"],
+ isActive: false,
+ },
+ {
+ info: "place-content is inactive on inline element",
+ property: "place-content",
+ tagName: "span",
+ rules: ["span { place-content: center; }"],
+ isActive: false,
+ },
+ {
+ info: "place-content is inactive on flex item",
+ property: "place-content",
+ createTestElement: rootNode => {
+ const container = document.createElement("div");
+ const element = document.createElement("span");
+ container.append(element);
+ rootNode.append(container);
+ return element;
+ },
+ rules: [
+ "div { display: flex; align-items: start; }",
+ "span { place-content: center; }",
+ ],
+ ruleIndex: 1,
+ isActive: false,
+ },
+ {
+ info: "place-content is inactive on grid item",
+ property: "place-content",
+ createTestElement: rootNode => {
+ const container = document.createElement("div");
+ const element = document.createElement("span");
+ container.append(element);
+ rootNode.append(container);
+ return element;
+ },
+ rules: [
+ "div { display: grid; align-items: start; }",
+ "span { place-content: center; }",
+ ],
+ ruleIndex: 1,
+ isActive: false,
+ },
+ {
+ info: "place-content is active on flex container",
+ property: "place-content",
+ tagName: "div",
+ rules: ["div { place-content: center; display: flex;}"],
+ isActive: true,
+ },
+ {
+ info: "place-content is active on inline-flex container",
+ property: "place-content",
+ tagName: "div",
+ rules: ["div { place-content: center; display: inline-flex;}"],
+ isActive: true,
+ },
+ {
+ info: "place-content is active on grid container",
+ property: "place-content",
+ tagName: "div",
+ rules: ["div { place-content: center; display: grid;}"],
+ isActive: true,
+ },
+ {
+ info: "place-content is active on inline grid container",
+ property: "place-content",
+ tagName: "div",
+ rules: ["div { place-content: center; display: inline-grid;}"],
+ isActive: true,
+ },
+];
diff --git a/devtools/server/tests/chrome/inactive-property-helper/positioned.js b/devtools/server/tests/chrome/inactive-property-helper/positioned.js
new file mode 100644
index 0000000000..b9ebe5aaee
--- /dev/null
+++ b/devtools/server/tests/chrome/inactive-property-helper/positioned.js
@@ -0,0 +1,84 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// InactivePropertyHelper positioned elements test cases.
+
+// These are the properties we care about, those that are inactive when the element isn't
+// positioned.
+const PROPERTIES = [
+ { property: "z-index", value: "2" },
+ { property: "top", value: "20px" },
+ { property: "right", value: "20px" },
+ { property: "bottom", value: "20px" },
+ { property: "left", value: "20px" },
+];
+
+// These are all of the different position values and whether the above properties are
+// active or not for each.
+const POSITIONS = [
+ { position: "unset", isActive: false },
+ { position: "initial", isActive: false },
+ { position: "inherit", isActive: false },
+ { position: "static", isActive: false },
+ { position: "absolute", isActive: true },
+ { position: "relative", isActive: true },
+ { position: "fixed", isActive: true },
+ { position: "sticky", isActive: true },
+];
+
+function makeTestCase(property, value, position, isActive) {
+ return {
+ info: `${property} is ${
+ isActive ? "" : "in"
+ }active when position is ${position}`,
+ property,
+ tagName: "div",
+ rules: [`div { ${property}: ${value}; position: ${position}; }`],
+ isActive,
+ };
+}
+
+// Make the test cases for all the combinations of PROPERTIES and POSITIONS
+const mainTests = [];
+
+for (const { property, value } of PROPERTIES) {
+ for (const { position, isActive } of POSITIONS) {
+ mainTests.push(makeTestCase(property, value, position, isActive));
+ }
+}
+
+// Add a few test cases to check that z-index actually works inside grids and flexboxes.
+mainTests.push({
+ info:
+ "z-index is active even on unpositioned elements if they are grid items",
+ property: "z-index",
+ createTestElement: rootNode => {
+ const container = document.createElement("div");
+ const element = document.createElement("span");
+ container.append(element);
+ rootNode.append(container);
+ return element;
+ },
+ rules: ["div { display: grid; }", "span { z-index: 3; }"],
+ ruleIndex: 1,
+ isActive: true,
+});
+
+mainTests.push({
+ info:
+ "z-index is active even on unpositioned elements if they are flex items",
+ property: "z-index",
+ createTestElement: rootNode => {
+ const container = document.createElement("div");
+ const element = document.createElement("span");
+ container.append(element);
+ rootNode.append(container);
+ return element;
+ },
+ rules: ["div { display: flex; }", "span { z-index: 3; }"],
+ ruleIndex: 1,
+ isActive: true,
+});
+
+export default mainTests;
diff --git a/devtools/server/tests/chrome/inactive-property-helper/text-overflow.js b/devtools/server/tests/chrome/inactive-property-helper/text-overflow.js
new file mode 100644
index 0000000000..21bc180458
--- /dev/null
+++ b/devtools/server/tests/chrome/inactive-property-helper/text-overflow.js
@@ -0,0 +1,44 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// InactivePropertyHelper `text-overflow` test cases.
+export default [
+ {
+ info: "text-overflow is inactive when overflow is not set",
+ property: "text-overflow",
+ tagName: "div",
+ rules: ["div { text-overflow: ellipsis; }"],
+ isActive: false,
+ },
+ {
+ info: "text-overflow is active when overflow is set to hidden",
+ property: "text-overflow",
+ tagName: "div",
+ rules: ["div { text-overflow: ellipsis; overflow: hidden; }"],
+ isActive: true,
+ },
+ {
+ info: "text-overflow is inactive when overflow is set to visible",
+ property: "text-overflow",
+ tagName: "div",
+ rules: ["div { text-overflow: ellipsis; overflow: visible; }"],
+ isActive: false,
+ },
+ {
+ info:
+ "as soon as overflow:hidden is set, text-overflow is active whatever the box type",
+ property: "text-overflow",
+ tagName: "span",
+ rules: ["span { text-overflow: ellipsis; overflow: hidden; }"],
+ isActive: true,
+ },
+ {
+ info:
+ "as soon as overflow:hidden is set, text-overflow is active whatever the box type",
+ property: "text-overflow",
+ tagName: "legend",
+ rules: ["legend { text-overflow: ellipsis; overflow: hidden; }"],
+ isActive: true,
+ },
+];
diff --git a/devtools/server/tests/chrome/inactive-property-helper/vertical-align.js b/devtools/server/tests/chrome/inactive-property-helper/vertical-align.js
new file mode 100644
index 0000000000..e9873d4865
--- /dev/null
+++ b/devtools/server/tests/chrome/inactive-property-helper/vertical-align.js
@@ -0,0 +1,56 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// InactivePropertyHelper `vertical-align` test cases.
+export default [
+ {
+ info: "vertical-align is inactive on a block element",
+ property: "vertical-align",
+ tagName: "div",
+ rules: ["div { vertical-align: top; }"],
+ isActive: false,
+ },
+ {
+ info: "vertical-align is inactive on a span with display block",
+ property: "vertical-align",
+ tagName: "span",
+ rules: ["span { vertical-align: top; display: block;}"],
+ isActive: false,
+ },
+ {
+ info: "vertical-align is active on a div with display inline-block",
+ property: "vertical-align",
+ tagName: "div",
+ rules: ["div { vertical-align: top; display: inline-block;}"],
+ isActive: true,
+ },
+ {
+ info: "vertical-align is active on a table-cell",
+ property: "vertical-align",
+ tagName: "div",
+ rules: ["div { vertical-align: top; display: table-cell;}"],
+ isActive: true,
+ },
+ {
+ info: "vertical-align is active on a block element ::first-letter",
+ property: "vertical-align",
+ tagName: "div",
+ rules: ["div::first-letter { vertical-align: top; }"],
+ isActive: true,
+ },
+ {
+ info: "vertical-align is active on a block element ::first-line",
+ property: "vertical-align",
+ tagName: "div",
+ rules: ["div::first-line { vertical-align: top; }"],
+ isActive: true,
+ },
+ {
+ info: "vertical-align is active on an inline element",
+ property: "vertical-align",
+ tagName: "span",
+ rules: ["span { vertical-align: top; }"],
+ isActive: true,
+ },
+];
diff --git a/devtools/server/tests/chrome/inspector-delay-image-response.sjs b/devtools/server/tests/chrome/inspector-delay-image-response.sjs
new file mode 100644
index 0000000000..d8de857751
--- /dev/null
+++ b/devtools/server/tests/chrome/inspector-delay-image-response.sjs
@@ -0,0 +1,40 @@
+/**
+ * Adapted from https://searchfox.org/mozilla-central/source/layout/reftests/backgrounds/delay-image-response.sjs
+ */
+"use strict";
+
+// A 1x1 PNG image.
+// Source: https://commons.wikimedia.org/wiki/File:1x1.png (Public Domain)
+const IMAGE = atob("iVBORw0KGgoAAAANSUhEUgAAAAEAAAABAQMAAAAl21bKAAAAA1BMVEUAA" +
+ "ACnej3aAAAAAXRSTlMAQObYZgAAAApJREFUCNdjYAAAAAIAAeIhvDMAAAAASUVORK5CYII=");
+
+// To avoid GC.
+let timer = null;
+
+function handleRequest(request, response) {
+ let query = {};
+ request.queryString.split("&").forEach(function(val) {
+ let [name, value] = val.split("=");
+ query[name] = unescape(value);
+ });
+
+ response.setStatusLine(request.httpVersion, 200, "OK");
+ response.setHeader("Content-Type", "image/png", false);
+
+ // If there is no delay, we write the image and leave.
+ if (!("delay" in query)) {
+ response.write(IMAGE);
+ return;
+ }
+
+ // If there is a delay, we create a timer which, when it fires, will write
+ // image and leave.
+ response.processAsync();
+ const nsITimer = Components.interfaces.nsITimer;
+
+ timer = Components.classes["@mozilla.org/timer;1"].createInstance(nsITimer);
+ timer.initWithCallback(function() {
+ response.write(IMAGE);
+ response.finish();
+ }, query.delay, nsITimer.TYPE_ONE_SHOT);
+}
diff --git a/devtools/server/tests/chrome/inspector-eyedropper.html b/devtools/server/tests/chrome/inspector-eyedropper.html
new file mode 100644
index 0000000000..f5bd3a1cb8
--- /dev/null
+++ b/devtools/server/tests/chrome/inspector-eyedropper.html
@@ -0,0 +1,20 @@
+<html>
+<head>
+ <meta charset="UTF-8">
+ <title>Inspector Eyedropper tests</title>
+ <style>
+ html {
+ background: black;
+ }
+ </style>
+ <script type="text/javascript">
+ "use strict";
+
+ window.onload = function() {
+ window.opener.postMessage("ready", "*");
+ };
+ </script>
+</head>
+</body>
+</body>
+</html>
diff --git a/devtools/server/tests/chrome/inspector-helpers.js b/devtools/server/tests/chrome/inspector-helpers.js
new file mode 100644
index 0000000000..bd436c313e
--- /dev/null
+++ b/devtools/server/tests/chrome/inspector-helpers.js
@@ -0,0 +1,146 @@
+/* exported attachURL, promiseDone,
+ promiseOnce,
+ addTest, addAsyncTest,
+ runNextTest, _documentWalker,
+ createResourceWatcher */
+"use strict";
+
+const { require } = ChromeUtils.import("resource://devtools/shared/Loader.jsm");
+const { TargetFactory } = require("devtools/client/framework/target");
+const { DevToolsServer } = require("devtools/server/devtools-server");
+const {
+ BrowserTestUtils,
+} = require("resource://testing-common/BrowserTestUtils.jsm");
+const {
+ DocumentWalker: _documentWalker,
+} = require("devtools/server/actors/inspector/document-walker");
+
+const { TargetList } = require("devtools/shared/resources/target-list");
+const {
+ ResourceWatcher,
+} = require("devtools/shared/resources/resource-watcher");
+
+const Services = require("Services");
+
+// Always log packets when running tests.
+Services.prefs.setBoolPref("devtools.debugger.log", true);
+SimpleTest.registerCleanupFunction(function() {
+ Services.prefs.clearUserPref("devtools.debugger.log");
+});
+
+if (!DevToolsServer.initialized) {
+ DevToolsServer.init();
+ DevToolsServer.registerAllActors();
+ SimpleTest.registerCleanupFunction(function() {
+ DevToolsServer.destroy();
+ });
+}
+
+var gAttachCleanups = [];
+
+SimpleTest.registerCleanupFunction(function() {
+ for (const cleanup of gAttachCleanups) {
+ cleanup();
+ }
+});
+
+/**
+ * Add a new test tab in the browser and load the given url.
+ * @return Promise a promise that resolves to the new target representing
+ * the page currently opened.
+ */
+
+async function getTargetForSelectedTab(gBrowser) {
+ const selectedTab = gBrowser.selectedTab;
+ await BrowserTestUtils.browserLoaded(selectedTab.linkedBrowser);
+ return TargetFactory.forTab(selectedTab);
+}
+
+/**
+ * Open a tab, load the url, wait for it to signal its readiness,
+ * find the tab with the devtools server, and call the callback.
+ *
+ * Returns a function which can be called to close the opened ta
+ * and disconnect its devtools client.
+ */
+async function attachURL(url) {
+ // Get the current browser window
+ const gBrowser = Services.wm.getMostRecentWindow("navigator:browser")
+ .gBrowser;
+
+ // open the url in a new tab, save a reference to the new inner window global object
+ // and wait for it to load. The tests rely on this window object to send a "ready"
+ // event to its opener (the test page). This window reference is used within
+ // the test tab, to reference the webpage being tested against, which is in another
+ // tab.
+ const windowOpened = BrowserTestUtils.waitForNewTab(gBrowser, url);
+ const win = window.open(url, "_blank");
+ await windowOpened;
+
+ const target = await getTargetForSelectedTab(gBrowser);
+ await target.attach();
+
+ const cleanup = async function() {
+ await target.destroy();
+ if (win) {
+ win.close();
+ }
+ };
+
+ gAttachCleanups.push(cleanup);
+ return { target, doc: win.document };
+}
+
+function promiseOnce(target, event) {
+ return new Promise(resolve => {
+ target.on(event, (...args) => {
+ if (args.length === 1) {
+ resolve(args[0]);
+ } else {
+ resolve(args);
+ }
+ });
+ });
+}
+
+function promiseDone(currentPromise) {
+ currentPromise.catch(err => {
+ ok(false, "Promise failed: " + err);
+ if (err.stack) {
+ dump(err.stack);
+ }
+ SimpleTest.finish();
+ });
+}
+
+var _tests = [];
+function addTest(test) {
+ _tests.push(test);
+}
+
+function addAsyncTest(generator) {
+ _tests.push(() => generator().catch(ok.bind(null, false)));
+}
+
+function runNextTest() {
+ if (_tests.length == 0) {
+ SimpleTest.finish();
+ return;
+ }
+ const fn = _tests.shift();
+ try {
+ fn();
+ } catch (ex) {
+ info(
+ "Test function " +
+ (fn.name ? "'" + fn.name + "' " : "") +
+ "threw an exception: " +
+ ex
+ );
+ }
+}
+
+function createResourceWatcher(target) {
+ const targetList = new TargetList(target.client.mainRoot, target);
+ return new ResourceWatcher(targetList);
+}
diff --git a/devtools/server/tests/chrome/inspector-search-data.html b/devtools/server/tests/chrome/inspector-search-data.html
new file mode 100644
index 0000000000..784dcb7c9b
--- /dev/null
+++ b/devtools/server/tests/chrome/inspector-search-data.html
@@ -0,0 +1,54 @@
+<html>
+<head>
+ <meta charset="UTF-8">
+ <title>Inspector Search Test Data</title>
+ <style>
+ #pseudo {
+ display: block;
+ margin: 0;
+ }
+ #pseudo:before {
+ content: "before element";
+ }
+ #pseudo:after {
+ content: "after element";
+ }
+ </style>
+ <script type="text/javascript">
+ "use strict";
+
+ window.onload = function() {
+ window.opener.postMessage("ready", "*");
+ };
+ </script>
+</head>
+</body>
+ <!-- A comment
+ spread across multiple lines -->
+
+ <img width="100" height="100" src="large-image.jpg" />
+
+ <h1 id="pseudo">Heading 1</h1>
+ <p>A p tag with the text 'h1' inside of it.
+ <strong>A strong h1 result</strong>
+ </p>
+
+ <div id="arrows" northwest="↖" northeast="↗" southeast="↘" southwest="↙">
+ Unicode arrows
+ </div>
+
+ <h2>Heading 2</h2>
+ <h2>Heading 2</h2>
+ <h2>Heading 2</h2>
+
+ <h3>Heading 3</h3>
+ <h3>Heading 3</h3>
+ <h3>Heading 3</h3>
+
+ <h4>Heading 4</h4>
+ <h4>Heading 4</h4>
+ <h4>Heading 4</h4>
+
+ <div class="💩" id="💩" 💩="💩"></div>
+</body>
+</html>
diff --git a/devtools/server/tests/chrome/inspector-styles-data.css b/devtools/server/tests/chrome/inspector-styles-data.css
new file mode 100644
index 0000000000..5c3652f522
--- /dev/null
+++ b/devtools/server/tests/chrome/inspector-styles-data.css
@@ -0,0 +1,3 @@
+.external-rule {
+ cursor: crosshair;
+}
diff --git a/devtools/server/tests/chrome/inspector-styles-data.html b/devtools/server/tests/chrome/inspector-styles-data.html
new file mode 100644
index 0000000000..334b268bfd
--- /dev/null
+++ b/devtools/server/tests/chrome/inspector-styles-data.html
@@ -0,0 +1,85 @@
+<html>
+<script>
+ "use strict";
+
+ window.onload = () => {
+ window.opener.postMessage("ready", "*");
+ };
+</script>
+<style>
+ .inheritable-rule {
+ font-size: 15px;
+ }
+ /* Has to be on one line, is such for test */
+ .column-rule { font-size: 20px; } .column-rule { font-size: 25px; }
+ .uninheritable-rule {
+ background-color: #f06;
+ }
+ @media screen {
+ #mediaqueried {
+ background-color: #f06;
+ }
+ }
+ #svgcontent rect {
+ fill: rgb(1,2,3);
+ }
+
+ #layout-element,
+ #layout-auto-margin-element {
+ width: 50px;
+ height: 50px;
+ padding: 3px 5px 7px 5px;
+ border: 5px solid red;
+ margin: 10px 20px 30px 0;
+ box-sizing: border-box;
+ position: absolute;
+ z-index: 2;
+ }
+
+ #layout-auto-margin-element {
+ margin: 10px auto;
+ }
+</style>
+<link type="text/css" rel="stylesheet" href="inspector-styles-data.css"></link>
+<body>
+ <h1>Style Actor Tests</h1>
+ <!-- Inheritance checks -->
+ <div id="inheritable-rule-uninheritable-style" class="inheritable-rule" style="background-color: purple">
+ <div id="inheritable-rule-inheritable-style" class="inheritable-rule" style="color: blue">
+ <div id="uninheritable-rule-uninheritable-style" class="uninheritable-rule" style="background-color: green">
+ <div id="uninheritable-rule-inheritable-style" class="uninheritable-rule" style="color: red">
+ <div id="test-node">
+ Here is the test node.
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+
+ <!-- Computed checks -->
+ <div id="computed-parent" class="external-rule inheritable-rule uninheritable-rule" style="color: red;">
+ <div id="computed-test-node" class="external-rule">
+ Here is the test node.
+ </div>
+ </div>
+
+ <!-- Matched checks -->
+ <div id="matched-parent" class="external-rule inheritable-rule column-rule uninheritable-rule" style="color: red;">
+ <div id="matched-test-node" style="font-size: 10px" class="external-rule">
+ Here is the test node.
+ </div>
+ </div>
+
+ <div id="mediaqueried">
+ Screen mediaqueried.
+ </div>
+
+ <div id="svgcontent">
+ <svg><rect></rect></svg>
+ </div>
+
+ <div id="layout-element">I can has layout</div>
+ <div id="layout-auto-margin-element">I can has layout too</div>
+
+</body>
+</html>
diff --git a/devtools/server/tests/chrome/inspector-template.html b/devtools/server/tests/chrome/inspector-template.html
new file mode 100644
index 0000000000..13c9d5c7d3
--- /dev/null
+++ b/devtools/server/tests/chrome/inspector-template.html
@@ -0,0 +1,17 @@
+<html>
+<body>
+ <template>
+ <p>template content</p>
+ </template>
+ <div></div>
+ <script>
+ "use strict";
+
+ const template = document.querySelector("template");
+ const clone = document.importNode(template.content, true);
+ document.querySelector("div").appendChild(clone);
+
+ window.opener.postMessage("ready", "*");
+ </script>
+</body>
+</html>
diff --git a/devtools/server/tests/chrome/inspector-traversal-data.html b/devtools/server/tests/chrome/inspector-traversal-data.html
new file mode 100644
index 0000000000..e294796467
--- /dev/null
+++ b/devtools/server/tests/chrome/inspector-traversal-data.html
@@ -0,0 +1,103 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="UTF-8">
+ <title>Inspector Traversal Test Data</title>
+ <style type="text/css">
+ #pseudo::before {
+ content: "before";
+ }
+ #pseudo::after {
+ content: "after";
+ }
+ #pseudo-empty::before {
+ content: "before an empty element";
+ }
+ #shadow::before {
+ content: "Testing ::before on a shadow host";
+ }
+ </style>
+ <script type="text/javascript">
+ "use strict";
+
+ window.onload = function() {
+ // Set up a basic shadow DOM
+ const host = document.querySelector("#shadow");
+ if (host.attachShadow) {
+ const root = host.attachShadow({ mode: "open" });
+
+ const h3 = document.createElement("h3");
+ h3.append("Shadow ");
+
+ const em = document.createElement("em");
+ em.append("DOM");
+
+ const select = document.createElement("select");
+ select.setAttribute("multiple", "");
+ h3.appendChild(em);
+ root.appendChild(h3);
+ root.appendChild(select);
+ }
+
+ // Put a copy of the body in an iframe to test frame traversal.
+ const body = document.querySelector("body");
+ const data = "data:text/html,<html>" + body.outerHTML + "<html>";
+ const iframe = document.createElement("iframe");
+ iframe.setAttribute("id", "childFrame");
+ iframe.onload = function() {
+ window.opener.postMessage("ready", "*");
+ };
+ iframe.src = data;
+ body.appendChild(iframe);
+ };
+ </script>
+</head>
+<body style="background-color:white">
+ <h1>Inspector Actor Tests</h1>
+ <span id="longstring">longlonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglong</span>
+ <span id="shortstring">short</span>
+ <span id="empty"></span>
+ <div id="longlist" data-test="exists">
+ <div id="a">a</div>
+ <div id="b">b</div>
+ <div id="c">c</div>
+ <div id="d">d</div>
+ <div id="e">e</div>
+ <div id="f">f</div>
+ <div id="g">g</div>
+ <div id="h">h</div>
+ <div id="i">i</div>
+ <div id="j">j</div>
+ <div id="k">k</div>
+ <div id="l">l</div>
+ <div id="m">m</div>
+ <div id="n">n</div>
+ <div id="o">o</div>
+ <div id="p">p</div>
+ <div id="q">q</div>
+ <div id="r">r</div>
+ <div id="s">s</div>
+ <div id="t">t</div>
+ <div id="u">u</div>
+ <div id="v">v</div>
+ <div id="w">w</div>
+ <div id="x">x</div>
+ <div id="y">y</div>
+ <div id="z">z</div>
+ </div>
+ <div id="longlist-sibling">
+ <div id="longlist-sibling-firstchild"></div>
+ </div>
+ <p id="edit-html"></p>
+
+ <select multiple><option>one</option><option>two</option></select>
+ <div id="pseudo"><span>middle</span></div>
+ <div id="pseudo-empty"></div>
+ <div id="shadow">light dom</div>
+ <object>
+ <div id="1"></div>
+ </object>
+ <div class="node-to-duplicate"></div>
+ <div id="scroll-into-view" style="margin-top: 1000px;">scroll</div>
+</body>
+</html>
diff --git a/devtools/server/tests/chrome/inspector_css-properties.html b/devtools/server/tests/chrome/inspector_css-properties.html
new file mode 100644
index 0000000000..8cc6368cd1
--- /dev/null
+++ b/devtools/server/tests/chrome/inspector_css-properties.html
@@ -0,0 +1,12 @@
+<html>
+<head>
+<body>
+ <script type="text/javascript">
+ "use strict";
+
+ window.onload = function() {
+ window.opener.postMessage("ready", "*");
+ };
+ </script>
+</body>
+</html>
diff --git a/devtools/server/tests/chrome/inspector_display-type.html b/devtools/server/tests/chrome/inspector_display-type.html
new file mode 100644
index 0000000000..7bd0da6709
--- /dev/null
+++ b/devtools/server/tests/chrome/inspector_display-type.html
@@ -0,0 +1,17 @@
+<html>
+<head>
+<body>
+ <div id="inline-block" style="display: inline-block">
+ HELLO WORLD
+ </div>
+ <div id="grid" style="display: grid"></div>
+ <div id="block" style="position: fixed"></div>
+ <script>
+ "use strict";
+
+ window.onload = () => {
+ window.opener.postMessage("ready", "*");
+ };
+ </script>
+</body>
+</html>
diff --git a/devtools/server/tests/chrome/inspector_getImageData.html b/devtools/server/tests/chrome/inspector_getImageData.html
new file mode 100644
index 0000000000..754798df44
--- /dev/null
+++ b/devtools/server/tests/chrome/inspector_getImageData.html
@@ -0,0 +1,23 @@
+<html>
+<head>
+<body>
+ <img class="custom">
+ <img class="big-horizontal" src="large-image.jpg" style="width:500px;">
+ <canvas class="big-vertical" style="width:500px;"></canvas>
+ <img class="small" src="small-image.gif">
+ <img class="data" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABwAAAAcCAYAAAByDd+UAAAAJklEQVRIie3NMREAAAgAoe9fWls4eAzMVM0xoVAoFAqFQqFQ+C9chp4NHvu+4Q4AAAAASUVORK5CYII=">
+ <script>
+ "use strict";
+
+ window.onload = () => {
+ const canvas = document.querySelector("canvas"), ctx = canvas.getContext("2d");
+ canvas.width = 1000;
+ canvas.height = 2000;
+ ctx.fillStyle = "red";
+ ctx.fillRect(0, 0, 1000, 2000);
+
+ window.opener.postMessage("ready", "*");
+ };
+ </script>
+</body>
+</html>
diff --git a/devtools/server/tests/chrome/inspector_getOffsetParent.html b/devtools/server/tests/chrome/inspector_getOffsetParent.html
new file mode 100644
index 0000000000..72aac5f70b
--- /dev/null
+++ b/devtools/server/tests/chrome/inspector_getOffsetParent.html
@@ -0,0 +1,18 @@
+<html>
+<head>
+<body>
+ <div id="relative_parent" style="position: relative">
+ <div id="absolute_child" style="position: absolute"></div>
+ </div>
+ <div id="static"></div>
+ <div id="no_parent" style="position: absolute"></div>
+ <div id="fixed" style="position: fixed"></div>
+ <script>
+ "use strict";
+
+ window.onload = () => {
+ window.opener.postMessage("ready", "*");
+ };
+ </script>
+</body>
+</html>
diff --git a/devtools/server/tests/chrome/large-image.jpg b/devtools/server/tests/chrome/large-image.jpg
new file mode 100644
index 0000000000..bda383e594
--- /dev/null
+++ b/devtools/server/tests/chrome/large-image.jpg
Binary files differ
diff --git a/devtools/server/tests/chrome/memory-helpers.js b/devtools/server/tests/chrome/memory-helpers.js
new file mode 100644
index 0000000000..f94e1f1a9e
--- /dev/null
+++ b/devtools/server/tests/chrome/memory-helpers.js
@@ -0,0 +1,53 @@
+/* exported Task, startServerAndGetSelectedTabMemory, destroyServerAndFinish,
+ waitForTime, waitUntil */
+"use strict";
+
+const { require } = ChromeUtils.import("resource://devtools/shared/Loader.jsm");
+const Services = require("Services");
+const { TargetFactory } = require("devtools/client/framework/target");
+
+// Always log packets when running tests.
+Services.prefs.setBoolPref("devtools.debugger.log", true);
+var gReduceTimePrecision = Services.prefs.getBoolPref(
+ "privacy.reduceTimerPrecision"
+);
+Services.prefs.setBoolPref("privacy.reduceTimerPrecision", false);
+SimpleTest.registerCleanupFunction(function() {
+ Services.prefs.clearUserPref("devtools.debugger.log");
+ Services.prefs.setBoolPref(
+ "privacy.reduceTimerPrecision",
+ gReduceTimePrecision
+ );
+});
+
+async function getTargetForSelectedTab() {
+ const browserWindow = Services.wm.getMostRecentWindow("navigator:browser");
+ const target = await TargetFactory.forTab(browserWindow.gBrowser.selectedTab);
+ return target;
+}
+
+async function startServerAndGetSelectedTabMemory() {
+ const target = await getTargetForSelectedTab();
+ const memory = await target.getFront("memory");
+ return { memory, target };
+}
+
+async function destroyServerAndFinish(target) {
+ await target.destroy();
+ SimpleTest.finish();
+}
+
+function waitForTime(ms) {
+ return new Promise((resolve, reject) => {
+ setTimeout(resolve, ms);
+ });
+}
+
+function waitUntil(predicate) {
+ if (predicate()) {
+ return Promise.resolve(true);
+ }
+ return new Promise(resolve =>
+ setTimeout(() => waitUntil(predicate).then(() => resolve(true)), 10)
+ );
+}
diff --git a/devtools/server/tests/chrome/nonchrome_unsafeDereference.html b/devtools/server/tests/chrome/nonchrome_unsafeDereference.html
new file mode 100644
index 0000000000..15e9fd9160
--- /dev/null
+++ b/devtools/server/tests/chrome/nonchrome_unsafeDereference.html
@@ -0,0 +1,10 @@
+<!DOCTYPE HTML>
+<html>
+<script>
+"use strict";
+
+var xhr = new XMLHttpRequest();
+xhr.timeout = 1742;
+xhr.expando = "Expando!";
+</script>
+</html>
diff --git a/devtools/server/tests/chrome/small-image.gif b/devtools/server/tests/chrome/small-image.gif
new file mode 100644
index 0000000000..e702427a53
--- /dev/null
+++ b/devtools/server/tests/chrome/small-image.gif
Binary files differ
diff --git a/devtools/server/tests/chrome/suspendTimeouts_content.html b/devtools/server/tests/chrome/suspendTimeouts_content.html
new file mode 100644
index 0000000000..f3969fc10c
--- /dev/null
+++ b/devtools/server/tests/chrome/suspendTimeouts_content.html
@@ -0,0 +1 @@
+<script src='suspendTimeouts_content.js'></script>
diff --git a/devtools/server/tests/chrome/suspendTimeouts_content.js b/devtools/server/tests/chrome/suspendTimeouts_content.js
new file mode 100644
index 0000000000..0e0cc19788
--- /dev/null
+++ b/devtools/server/tests/chrome/suspendTimeouts_content.js
@@ -0,0 +1,73 @@
+"use strict";
+
+// To make it easier to follow, this code is arranged so that the functions are
+// arranged in the order they are called.
+
+const worker = new Worker("suspendTimeouts_worker.js");
+worker.onerror = error => {
+ const message = `error from worker: ${error.filename}:${error.lineno}: ${error.message}`;
+ throw new Error(message);
+};
+
+// Create a message channel. Send one end to the worker, and return the other to
+// the mochitest.
+/* exported create_channel */
+function create_channel() {
+ const { port1, port2 } = new MessageChannel();
+ info(`sending port to worker`);
+ worker.postMessage({ mochitestPort: port1 }, [port1]);
+ return port2;
+}
+
+// Provoke the worker into sending us a message, and then refuse to receive said
+// message, causing it to be delayed for later delivery.
+//
+// The worker will also post a message to the MessagePort we sent it earlier.
+// That message should not be delayed, as it is handled by the mochitest window,
+// not the content window. Its receipt signals that the test can assume that the
+// runnable for step 2) is in the main thread's event queue, so the test can
+// prepare for step 3).
+/* exported start_worker */
+function start_worker() {
+ worker.onmessage = handle_echo;
+
+ // This should prevent worker.onmessage from being called, until
+ // resumeTimeouts is called.
+ //
+ // This function is provided by test_suspendTimeouts.js.
+ // eslint-disable-next-line no-undef
+ suspendTimeouts();
+
+ // The worker should echo this message back to us and to the mochitest.
+ worker.postMessage("HALLOOOOOO"); // suitable message for echoing
+ info(`posted message to worker`);
+}
+
+var resumeTimeouts_has_returned = false;
+
+// Resume timeouts. After this call, the worker's message should not be
+// delivered to our onmessage handler until control returns to the event loop.
+/* exported resume_timeouts */
+function resume_timeouts() {
+ // This function is provided by test_suspendTimeouts.js.
+ // eslint-disable-next-line no-undef
+ resumeTimeouts(); // onmessage handlers should not run from this call.
+
+ resumeTimeouts_has_returned = true;
+
+ // When this JavaScript invocation returns to the main thread's event loop,
+ // only then should onmessage handlers be invoked.
+}
+
+// The buggy code calls this handler from the resumeTimeouts call, before the
+// main thread returns to the event loop. The correct code calls this only once
+// the JavaScript invocation that called resumeTimeouts has run to completion.
+function handle_echo({ data }) {
+ ok(
+ resumeTimeouts_has_returned,
+ "worker message delivered from main event loop"
+ );
+
+ // Finish the mochitest.
+ finish();
+}
diff --git a/devtools/server/tests/chrome/suspendTimeouts_worker.js b/devtools/server/tests/chrome/suspendTimeouts_worker.js
new file mode 100644
index 0000000000..e008f7d0d3
--- /dev/null
+++ b/devtools/server/tests/chrome/suspendTimeouts_worker.js
@@ -0,0 +1,12 @@
+"use strict";
+
+// Once content sends us a port connected to the mochitest, we simply echo every
+// message we receive back to content and the mochitest.
+onmessage = ({ data: { mochitestPort } }) => {
+ onmessage = ({ data }) => {
+ // Send a message to both content and the mochitest, which the main thread's
+ // event loop will attempt to deliver as step 2).
+ postMessage(`worker echo to content: ${data}`);
+ mochitestPort.postMessage(`worker echo to port: ${data}`);
+ };
+};
diff --git a/devtools/server/tests/chrome/test_Debugger.Script.prototype.global.html b/devtools/server/tests/chrome/test_Debugger.Script.prototype.global.html
new file mode 100644
index 0000000000..7009f9eae2
--- /dev/null
+++ b/devtools/server/tests/chrome/test_Debugger.Script.prototype.global.html
@@ -0,0 +1,48 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=958646
+
+Debugger.Script.prototype.global should return innerize globals, not WindowProxies.
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Debugger.Script.prototype.global should return inner windows</title>
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
+</head>
+<body>
+<pre id="test">
+<script>
+"use strict";
+
+const {addDebuggerToGlobal} = ChromeUtils.import("resource://gre/modules/jsdebugger.jsm");
+addDebuggerToGlobal(this);
+
+window.onload = function() {
+ SimpleTest.waitForExplicitFinish();
+
+ const iframe = document.createElement("iframe");
+ iframe.src = "data:text/html,<script>function glorp() { }<\/script>";
+ iframe.onload = firstOnLoadHandler;
+ document.body.appendChild(iframe);
+
+ function firstOnLoadHandler() {
+ const dbg = new Debugger();
+ const iframeDO = dbg.addDebuggee(iframe.contentWindow);
+
+ // For sanity: check that the debuggee global is the inner window,
+ // and that the outer window gets a distinct D.O.
+ const iframeWindowProxyDO = iframeDO.makeDebuggeeValue(iframe.contentWindow);
+ ok(iframeDO !== iframeWindowProxyDO);
+
+ // The real test: Debugger.Script.prototype.global returns inner windows.
+ ok(iframeDO.getOwnPropertyDescriptor("glorp").value.script.global === iframeDO);
+
+ SimpleTest.finish();
+ }
+};
+</script>
+</pre>
+</body>
+</html>
diff --git a/devtools/server/tests/chrome/test_Debugger.Source.prototype.element.html b/devtools/server/tests/chrome/test_Debugger.Source.prototype.element.html
new file mode 100644
index 0000000000..6244bcb0de
--- /dev/null
+++ b/devtools/server/tests/chrome/test_Debugger.Source.prototype.element.html
@@ -0,0 +1,182 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=941876
+
+Debugger.Source.prototype.element and .elementAttributeName should report the DOM
+element to which code is attached (if any), and how.
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Debugger.Source.prototype.element should return owning element</title>
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
+</head>
+<body>
+<pre id="test">
+<script>
+"use strict";
+
+const {addSandboxedDebuggerToGlobal} = ChromeUtils.import("resource://gre/modules/jsdebugger.jsm");
+addSandboxedDebuggerToGlobal(this);
+
+window.onload = function() {
+ SimpleTest.waitForExplicitFinish();
+
+ let log = "";
+ let doc, dieter, ulrich, isolde, albrecht;
+ let dbg, iframeDO, DOFor;
+
+ // Create an iframe to debug.
+ // We can't use a data: URL here, because we want to test script elements
+ // that refer to the JavaScript via 'src' attributes, and data: documents
+ // can't refer to those. So we use a separate HTML document.
+ const iframe = document.createElement("iframe");
+ iframe.src = "Debugger.Source.prototype.element.html";
+ iframe.onload = onLoadHandler;
+ document.body.appendChild(iframe);
+
+ function onLoadHandler() {
+ log += "l";
+
+ // Now that the iframe's window has been created, we can add
+ // it as a debuggee.
+ dbg = new Debugger();
+ dbg.onDebuggerStatement = franzDebuggerHandler;
+ iframeDO = dbg.addDebuggee(iframe.contentWindow);
+ DOFor = iframeDO.makeDebuggeeValue.bind(iframeDO);
+
+ // Send a click event to heidi.
+ doc = iframe.contentWindow.document;
+ doc.getElementById("heidi").dispatchEvent(new Event("click"));
+ }
+
+ function franzDebuggerHandler(frame) {
+ log += "f";
+
+ // The top stack frame should be franz, belonging to the script element.
+ ok(frame.callee.displayName === "franz", "top frame is franz");
+ ok(frame.script.source.element === DOFor(doc.getElementById("franz")),
+ "top frame source belongs to element franz");
+ ok(frame.script.source.elementAttributeName === undefined,
+ "top frame source doesn't belong to an attribute");
+
+ // The second stack frame should belong to heinrich.
+ ok(frame.older.script.source.element === DOFor(doc.getElementById("heinrich")),
+ "second frame source belongs to element heinrich");
+ ok(frame.older.script.source.elementAttributeName === undefined,
+ "second frame source doesn't belong to an attribute");
+
+ // The next stack frame should belong to heidi's onclick handler.
+ ok(frame.older.older.script.source.element === DOFor(doc.getElementById("heidi")),
+ "third frame source belongs to element heidi");
+ ok(frame.older.older.script.source.elementAttributeName === "onclick",
+ "third frame source belongs to 'onclick' attribute");
+
+ // Try a dynamically inserted inline script element.
+ ulrich = doc.createElement("script");
+ ulrich.text = "debugger;";
+ dbg.onDebuggerStatement = ulrichDebuggerHandler;
+ doc.body.appendChild(ulrich);
+ }
+
+ function ulrichDebuggerHandler(frame) {
+ log += "u";
+
+ // The top frame should be ulrich's text.
+ ok(frame.script.source.element === DOFor(ulrich),
+ "top frame belongs to ulrich");
+ ok(frame.script.source.elementAttributeName === undefined,
+ "top frame is not on an attribute of ulrich");
+
+ // Try a dynamically inserted out-of-line script element.
+ isolde = doc.createElement("script");
+ isolde.setAttribute("src", "Debugger.Source.prototype.element-2.js");
+ isolde.setAttribute("id", "idolde, my dear");
+ dbg.onDebuggerStatement = isoldeDebuggerHandler;
+ doc.body.appendChild(isolde);
+ }
+
+ function isoldeDebuggerHandler(frame) {
+ log += "i";
+
+ // The top frame should belong to isolde.
+ ok(frame.script.source.element === DOFor(isolde),
+ "top frame belongs to isolde");
+ info("frame.script.source.element is: " + uneval(frame.script.source.element));
+ if (typeof frame.script.source.element.unsafeDereference() == "object") {
+ info(" toString: " + frame.script.source.element.unsafeDereference());
+ info(" id: " + frame.script.source.element.unsafeDereference().id);
+ }
+
+ ok(frame.script.source.elementAttributeName === undefined,
+ "top frame source is not an attribute of isolde");
+ info("frame.script.source.elementAttributeName is: " +
+ uneval(frame.script.source.elementAttributeName));
+
+ // Try a dynamically created div element with a handler.
+ dieter = doc.createElement("div");
+ dieter.setAttribute("id", "dieter");
+ dieter.setAttribute("ondrag", "debugger;");
+ dbg.onDebuggerStatement = dieterDebuggerHandler;
+ dieter.dispatchEvent(new Event("drag"));
+ }
+
+ function dieterDebuggerHandler(frame) {
+ log += "d";
+
+ // The top frame should belong to dieter's ondrag handler.
+ ok(frame.script.source.element === DOFor(dieter),
+ "second event's handler belongs to dieter");
+ ok(frame.script.source.elementAttributeName === "ondrag",
+ "second event's handler is on dieter's 'ondrag' element");
+
+ // Try sending an 'onresize' event to the window.
+ //
+ // Note that we only want Debugger to see the events we send, not any
+ // genuine resize events accidentally generated by the test harness (see bug
+ // 1162067). So we mark our events as cancelable; that seems to be the only
+ // bit chrome can fiddle on an Event that content code will see and that
+ // won't affect propagation. Then, the content event only runs its
+ // 'debugger' statement when the event is cancelable. It's a kludge.
+ dbg.onDebuggerStatement = resizeDebuggerHandler;
+ iframe.contentWindow.dispatchEvent(new Event("resize", { cancelable: true }));
+ }
+
+ function resizeDebuggerHandler(frame) {
+ log += "e";
+
+ // The top frame should belong to the body's 'onresize' handler, even
+ // though we sent the message to the window and it was handled.
+ ok(frame.script.source.element === DOFor(doc.body),
+ "onresize event handler belongs to body element");
+ ok(frame.script.source.elementAttributeName === "onresize",
+ "onresize event handler is on body element's 'onresize' attribute");
+
+ // In SVG, the event and the attribute that holds that event's handler
+ // have different names. Debugger.Source.prototype.elementAttributeName
+ // should report (as one might infer) the attribute name, not the event
+ // name.
+ albrecht = doc.createElementNS("http://www.w3.org/2000/svg", "svg");
+ albrecht.setAttribute("onload", "debugger;");
+ dbg.onDebuggerStatement = SVGLoadHandler;
+ albrecht.dispatchEvent(new Event("SVGLoad"));
+ }
+
+ function SVGLoadHandler(frame) {
+ log += "s";
+
+ // The top frame's source should be on albrecht's 'onload' attribute.
+ ok(frame.script.source.element === DOFor(albrecht),
+ "SVGLoad event handler belongs to albrecht");
+ ok(frame.script.source.elementAttributeName === "onload",
+ "SVGLoad event handler is on albrecht's 'onload' attribute");
+
+ ok(log === "lfuides", "all tests actually ran");
+ SimpleTest.finish();
+ }
+};
+</script>
+</pre>
+</body>
+</html>
diff --git a/devtools/server/tests/chrome/test_Debugger.Source.prototype.introductionScript.html b/devtools/server/tests/chrome/test_Debugger.Source.prototype.introductionScript.html
new file mode 100644
index 0000000000..31a0c9efc7
--- /dev/null
+++ b/devtools/server/tests/chrome/test_Debugger.Source.prototype.introductionScript.html
@@ -0,0 +1,97 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=969786
+
+Debugger.Source.prototype.introductionScript and .introductionOffset should
+behave when 'eval' is called with no scripted frames active at all.
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Debugger.Source.prototype.introductionScript with no caller</title>
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
+</head>
+<body>
+<pre id="test">
+<script>
+"use strict";
+
+const {addDebuggerToGlobal} = ChromeUtils.import("resource://gre/modules/jsdebugger.jsm");
+addDebuggerToGlobal(this);
+
+window.onload = function() {
+ SimpleTest.waitForExplicitFinish();
+
+ let dbg, iframeDO, doc, script2DO;
+
+ // Create an iframe to debug.
+ const iframe = document.createElement("iframe");
+ iframe.src = "data:text/html,<div>Hi!</div>";
+ iframe.onload = onLoadHandler;
+ document.body.appendChild(iframe);
+
+ function onLoadHandler() {
+ // Now that the iframe's window has been created, we can add
+ // it as a debuggee.
+ dbg = new Debugger();
+ iframeDO = dbg.addDebuggee(iframe.contentWindow);
+
+ doc = iframe.contentWindow.document;
+ const script = doc.createElement("script");
+ script.text = "setTimeout(eval.bind(null, 'debugger;'), 0);";
+ dbg.onDebuggerStatement = timerHandler;
+ doc.body.appendChild(script);
+ }
+
+ function timerHandler(frame) {
+ // The top stack frame's source should have an undefined
+ // introduction script and introduction offset.
+ const source = frame.script.source;
+ ok(source.introductionScript === undefined,
+ "setTimeout eval introductionScript is undefined");
+ ok(source.introductionOffset === undefined,
+ "setTimeout eval introductionOffset is undefined");
+
+ // Check that the above isn't just some quirk of iframes, or the
+ // browser milieu destroying information: an eval script should indeed
+ // have proper introduction information.
+ const script2 = doc.createElement("script");
+ script2.text = "eval('debugger;');";
+ script2DO = iframeDO.makeDebuggeeValue(script2);
+ dbg.onDebuggerStatement = evalHandler;
+ doc.body.appendChild(script2);
+ }
+
+ function evalHandler(frame) {
+ // The top stack frame's source should be introduced by the script that
+ // called eval.
+ const source = frame.script.source;
+ const frame2 = frame.older;
+ const frame3 = frame2.older;
+
+ ok(source.introductionType === "eval",
+ "top frame's source was introduced by 'eval'");
+ ok(source.introductionScript === frame2.script,
+ "eval frame's introduction script is the older frame's script");
+ ok(source.introductionOffset === frame2.offset,
+ "eval frame's introduction offset is current offset in older frame");
+ ok(source.introductionScript.source.element === script2DO,
+ "eval frame's introducer belongs to script2 element");
+
+ // The frame that called eval, in turn, was introduced at the call that
+ // inserted the script element into the document.
+ ok(frame2.script.source.introductionType === "injectedScript",
+ "older frame has no introduction type");
+ ok(frame2.script.source.introductionScript === frame3.script,
+ "older frame has introduction script");
+ ok(frame2.script.source.introductionOffset === frame3.offset,
+ "older frame has introduction offset");
+
+ SimpleTest.finish();
+ }
+};
+</script>
+</pre>
+</body>
+</html>
diff --git a/devtools/server/tests/chrome/test_Debugger.Source.prototype.introductionType.html b/devtools/server/tests/chrome/test_Debugger.Source.prototype.introductionType.html
new file mode 100644
index 0000000000..1a5ec2d5cb
--- /dev/null
+++ b/devtools/server/tests/chrome/test_Debugger.Source.prototype.introductionType.html
@@ -0,0 +1,169 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=935203
+
+Debugger.Source.prototype.introductionType should return 'eventHandler' for
+JavaScrip appearing in an inline event handler attribute.
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Debugger.Source.prototype.introductionType should identify event handlers</title>
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript" src="inspector-helpers.js"></script>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
+</head>
+<body>
+<pre id="test">
+<script>
+"use strict";
+
+const {addSandboxedDebuggerToGlobal} = ChromeUtils.import("resource://gre/modules/jsdebugger.jsm");
+addSandboxedDebuggerToGlobal(this);
+
+let dbg;
+let iframeDO, doc;
+let Tootles, TootlesDO;
+
+window.onload = function() {
+ SimpleTest.waitForExplicitFinish();
+ runNextTest();
+};
+
+addTest(function setup() {
+ // Create an iframe to debug.
+ const iframe = document.createElement("iframe");
+ iframe.srcdoc = "<div id='Tootles' onclick='debugger;'>I'm a DIV!</div>" +
+ "<script id='Auddie'>function auddie() { debugger; }<\/script>";
+ iframe.onload = onLoadHandler;
+ document.body.appendChild(iframe);
+
+ function onLoadHandler() {
+ // Now that the iframe's window has been created, we can add
+ // it as a debuggee.
+ dbg = new Debugger();
+ iframeDO = dbg.addDebuggee(iframe.contentWindow);
+ doc = iframe.contentWindow.document;
+ Tootles = doc.getElementById("Tootles");
+ TootlesDO = iframeDO.makeDebuggeeValue(Tootles);
+
+ runNextTest();
+ }
+});
+
+// Check the introduction type of in-markup event handler code.
+// Send a click event to Tootles, whose handler has a 'debugger' statement,
+// and check that script's introduction type.
+addTest(function ClickOnTootles() {
+ dbg.onDebuggerStatement = TootlesClickDebugger;
+ Tootles.dispatchEvent(new Event("click"));
+
+ function TootlesClickDebugger(frame) {
+ // some sanity checks
+ ok(frame.script.source.element === TootlesDO,
+ "top frame source belongs to element 'Tootles'");
+ is(frame.script.source.elementAttributeName, "onclick",
+ "top frame source belongs to 'onclick' attribute");
+
+ // And, the actual point of this test:
+ is(frame.script.source.introductionType, "eventHandler",
+ "top frame source's introductionType is 'eventHandler'");
+
+ runNextTest();
+ }
+});
+
+// Check the introduction type of dynamically added event handler code.
+// Add a drag event handler to Tootles as a string, and then send
+// Tootles a drag event.
+addTest(function DragTootles() {
+ dbg.onDebuggerStatement = TootlesDragDebugger;
+ Tootles.setAttribute("ondrag", "debugger;");
+ Tootles.dispatchEvent(new Event("drag"));
+
+ function TootlesDragDebugger(frame) {
+ // sanity checks
+ ok(frame.script.source.element === TootlesDO,
+ "top frame source belongs to element 'Tootles'");
+ is(frame.script.source.elementAttributeName, "ondrag",
+ "top frame source belongs to 'ondrag' attribute");
+
+ // And, the actual point of this test:
+ is(frame.script.source.introductionType, "eventHandler",
+ "top frame source's introductionType is 'eventHandler'");
+
+ runNextTest();
+ }
+});
+
+// Check the introduction type of an in-markup script element.
+addTest(function checkAuddie() {
+ const fnDO = iframeDO.getOwnPropertyDescriptor("auddie").value;
+ const AuddieDO = iframeDO.makeDebuggeeValue(doc.getElementById("Auddie"));
+
+ is(fnDO.class, "Function",
+ "Script element 'Auddie' defined function 'auddie'.");
+ ok(fnDO.script.source.element === AuddieDO,
+ "Function auddie's script belongs to script element 'Auddie'");
+ is(fnDO.script.source.elementAttributeName, undefined,
+ "Function auddie's script doesn't belong to any attribute of 'Auddie'");
+ is(fnDO.script.source.introductionType, "inlineScript",
+ "Function auddie's script's source was introduced by a script element");
+
+ runNextTest();
+});
+
+// Check the introduction type of a dynamically inserted script element.
+addTest(function InsertRover() {
+ dbg.onDebuggerStatement = RoverDebugger;
+ const rover = doc.createElement("script");
+ const roverDO = iframeDO.makeDebuggeeValue(rover);
+ rover.text = "debugger;";
+ doc.body.appendChild(rover);
+
+ function RoverDebugger(frame) {
+ // sanity checks
+ ok(frame.script.source.element === roverDO,
+ "Rover script belongs to Rover");
+ ok(frame.script.source.elementAttributeName === undefined,
+ "Rover script doesn't belong to an attribute of Rover");
+
+ // Check the introduction type.
+ ok(frame.script.source.introductionType === "injectedScript",
+ "Rover script's introduction type is 'injectedScript'");
+
+ runNextTest();
+ }
+});
+
+// Creates a chrome document with a XUL script element, and check its introduction type.
+addTest(function XULDocumentScript() {
+ const frame = document.createElement("iframe");
+ frame.src = "doc_Debugger.Source.prototype.introductionType.xhtml";
+ frame.onload = docLoaded;
+ info("Appending iframe containing a document with a XUL script tag");
+ document.body.appendChild(frame);
+
+ function docLoaded() {
+ info("Loaded chrome document");
+ const xulFrameDO = dbg.addDebuggee(frame.contentWindow);
+ const xulFnDO = xulFrameDO.getOwnPropertyDescriptor("xulScriptFunc").value;
+ is(typeof xulFnDO, "object", "XUL script element defined 'xulScriptFunc'");
+ is(xulFnDO.class, "Function",
+ "XUL global 'xulScriptFunc' is indeed a function");
+
+ // A XUL script elements' code gets shared amongst all
+ // instantiations of the document, so there's no specific DOM element
+ // we can attribute the code to.
+ is(xulFnDO.script.source.element, undefined,
+ "XUL script code should not be attributed to any individual element");
+
+ is(xulFnDO.script.source.introductionType, "inlineScript",
+ "xulScriptFunc's introduction type is 'inlineScript'");
+ runNextTest();
+ }
+});
+</script>
+</pre>
+</body>
+</html>
diff --git a/devtools/server/tests/chrome/test_animation-type-longhand.html b/devtools/server/tests/chrome/test_animation-type-longhand.html
new file mode 100644
index 0000000000..98ae0ffe9d
--- /dev/null
+++ b/devtools/server/tests/chrome/test_animation-type-longhand.html
@@ -0,0 +1,42 @@
+<!DOCTYPE html>
+<meta charset="UTF-8">
+<title>Test animation-type-longhand</title>
+<script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+<body>
+<script>
+ "use strict";
+
+ // This test checks the content of animation type for longhands table that
+ // * every longhand property is included
+ // * nothing else is included
+ // * no property is mapped to more than one animation type
+ window.onload = function() {
+ const {require} = ChromeUtils.import("resource://devtools/shared/Loader.jsm");
+ const { ANIMATION_TYPE_FOR_LONGHANDS } =
+ require("devtools/server/actors/animation-type-longhand");
+ const InspectorUtils = SpecialPowers.InspectorUtils;
+
+ const all_longhands = InspectorUtils.getCSSPropertyNames({
+ includeShorthands: false,
+ includeExperimentals: true,
+ });
+
+ const unseen_longhands = new Set(all_longhands);
+ const seen_longhands = new Set();
+ for (const [, names] of ANIMATION_TYPE_FOR_LONGHANDS) {
+ for (const name of names) {
+ ok(!seen_longhands.has(name),
+ `${name} should have only one animation type`);
+ ok(unseen_longhands.has(name),
+ `${name} is an unseen longhand property`);
+ unseen_longhands.delete(name);
+ seen_longhands.add(name);
+ }
+ }
+ is(unseen_longhands.size, 0,
+ "All longhands should be mapped to some animation type");
+
+ SimpleTest.finish();
+ };
+</script>
+</body>
diff --git a/devtools/server/tests/chrome/test_css-logic-media-queries.html b/devtools/server/tests/chrome/test_css-logic-media-queries.html
new file mode 100644
index 0000000000..947062c9cb
--- /dev/null
+++ b/devtools/server/tests/chrome/test_css-logic-media-queries.html
@@ -0,0 +1,58 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+Test that css-logic handles media-queries correctly
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test css-logic media-queries</title>
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
+ <style>
+ div {
+ width: 1000px;
+ height: 100px;
+ background-color: #f00;
+ }
+
+ @media screen and (min-width: 1px) {
+ div {
+ width: 200px;
+ }
+ }
+ </style>
+</head>
+<body>
+ <div></div>
+ <script type="application/javascript">
+ "use strict";
+
+ window.onload = function() {
+ const {require} = ChromeUtils.import("resource://devtools/shared/Loader.jsm");
+ const Services = require("Services");
+ const {CssLogic} = require("devtools/server/actors/inspector/css-logic");
+
+ SimpleTest.waitForExplicitFinish();
+
+ const div = document.querySelector("div");
+ const cssLogic = new CssLogic();
+ cssLogic.highlight(div);
+ cssLogic.processMatchedSelectors();
+
+ const _strings = Services.strings
+ .createBundle("chrome://devtools-shared/locale/styleinspector.properties");
+
+ const inline = _strings.GetStringFromName("rule.sourceInline");
+
+ const source1 = inline + ":12";
+ const source2 = inline + ":19 @media screen and (min-width: 1px)";
+ is(cssLogic._matchedRules[0][0].source, source1,
+ "rule.source gives correct output for rule 1");
+ is(cssLogic._matchedRules[1][0].source, source2,
+ "rule.source gives correct output for rule 2");
+
+ SimpleTest.finish();
+ };
+ </script>
+</body>
+</html>
diff --git a/devtools/server/tests/chrome/test_css-logic-specificity.html b/devtools/server/tests/chrome/test_css-logic-specificity.html
new file mode 100644
index 0000000000..e4ec21aeda
--- /dev/null
+++ b/devtools/server/tests/chrome/test_css-logic-specificity.html
@@ -0,0 +1,82 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+Test that css-logic calculates CSS specificity properly
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test css-logic specificity</title>
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+</head>
+<body style="background:blue;">
+ <script type="application/javascript">
+ "use strict";
+
+ window.onload = function() {
+ const {require} = ChromeUtils.import("resource://devtools/shared/Loader.jsm");
+ const {CssLogic, CssSelector} = require("devtools/server/actors/inspector/css-logic");
+ const InspectorUtils = SpecialPowers.InspectorUtils;
+
+ const TEST_DATA = [
+ {text: "*", expected: 0},
+ {text: "LI", expected: 1},
+ {text: "UL LI", expected: 2},
+ {text: "UL OL + LI", expected: 3},
+ {text: "H1 + [REL=\"up\"]", expected: 1025},
+ {text: "UL OL LI.red", expected: 1027},
+ {text: "LI.red.level", expected: 2049},
+ {text: ".red .level", expected: 2048},
+ {text: "#x34y", expected: 1048576},
+ {text: "#s12:not(FOO)", expected: 1048577},
+ {text: "body#home div#warning p.message", expected: 2098179},
+ {text: "* body#home div#warning p.message", expected: 2098179},
+ {text: "#footer :not(nav) li", expected: 1048578},
+ {text: "bar:nth-child(n)", expected: 1025},
+ {text: "li::marker", expected: 2},
+ {text: "a:hover", expected: 1025},
+ ];
+
+ function createDocument() {
+ let text = TEST_DATA.map(i=>i.text).join(",");
+ text = '<style type="text/css">' + text + " {color:red;}</style>";
+ // eslint-disable-next-line no-unsanitized/property
+ document.body.innerHTML = text;
+ }
+
+ function getExpectedSpecificity(selectorText) {
+ return TEST_DATA.filter(i => i.text === selectorText)[0].expected;
+ }
+
+ SimpleTest.waitForExplicitFinish();
+
+ createDocument();
+ const cssLogic = new CssLogic();
+
+ cssLogic.highlight(document.body);
+ const cssSheet = cssLogic.sheets[0];
+ const cssRule = cssSheet.domSheet.cssRules[0];
+ const selectors = CssLogic.getSelectors(cssRule);
+
+ info("Iterating over the test selectors");
+ for (let i = 0; i < selectors.length; i++) {
+ const selectorText = selectors[i];
+ info("Testing selector " + selectorText);
+
+ const selector = new CssSelector(cssRule, selectorText, i);
+ const expected = getExpectedSpecificity(selectorText);
+ const specificity = InspectorUtils.getSpecificity(selector.cssRule,
+ selector.selectorIndex);
+ is(specificity, expected,
+ 'Selector "' + selectorText + '" has a specificity of ' + expected);
+ }
+
+ info("Testing specificity of element.style");
+ const colorProp = cssLogic.getPropertyInfo("background");
+ is(colorProp.matchedSelectors[0].specificity, 0x40000000,
+ "Element styles have specificity of 0x40000000 (1073741824).");
+
+ SimpleTest.finish();
+ };
+ </script>
+</body>
+</html>
diff --git a/devtools/server/tests/chrome/test_css-logic.html b/devtools/server/tests/chrome/test_css-logic.html
new file mode 100644
index 0000000000..3bbc571e7d
--- /dev/null
+++ b/devtools/server/tests/chrome/test_css-logic.html
@@ -0,0 +1,73 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug </title>
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
+ <script type="application/javascript" src="inspector-helpers.js"></script>
+ <script type="application/javascript">
+"use strict";
+
+const {CssLogic} = require("devtools/server/actors/inspector/css-logic");
+
+window.onload = function() {
+ SimpleTest.waitForExplicitFinish();
+ runNextTest();
+};
+
+addTest(function getComputedStyle() {
+ const node = document.querySelector("#computed-style");
+ is(CssLogic.getComputedStyle(node).getPropertyValue("width"),
+ "50px", "Computed style on a normal node works (width)");
+ is(CssLogic.getComputedStyle(node).getPropertyValue("height"),
+ "10px", "Computed style on a normal node works (height)");
+
+ const firstChild = new _documentWalker(node, window).firstChild();
+ is(CssLogic.getComputedStyle(firstChild).getPropertyValue("content"),
+ "\"before\"", "Computed style on a ::before node works (content)");
+ const lastChild = new _documentWalker(node, window).lastChild();
+ is(CssLogic.getComputedStyle(lastChild).getPropertyValue("content"),
+ "\"after\"", "Computed style on a ::after node works (content)");
+
+ runNextTest();
+});
+
+addTest(function getBindingElementAndPseudo() {
+ const node = document.querySelector("#computed-style");
+ let {bindingElement, pseudo} = CssLogic.getBindingElementAndPseudo(node);
+
+ is(bindingElement, node,
+ "Binding element is the node itself for a normal node");
+ ok(!pseudo, "Pseudo is null for a normal node");
+
+ const firstChild = new _documentWalker(node, window).firstChild();
+ ({ bindingElement, pseudo } = CssLogic.getBindingElementAndPseudo(firstChild));
+ is(bindingElement, node,
+ "Binding element is the parent for a pseudo node");
+ is(pseudo, ":before", "Pseudo is correct for a ::before node");
+
+ const lastChild = new _documentWalker(node, window).lastChild();
+ ({ bindingElement, pseudo } = CssLogic.getBindingElementAndPseudo(lastChild));
+ is(bindingElement, node,
+ "Binding element is the parent for a pseudo node");
+ is(pseudo, ":after", "Pseudo is correct for a ::after node");
+
+ runNextTest();
+});
+
+ </script>
+</head>
+<body>
+ <style type="text/css">
+ #computed-style { width: 50px; height: 10px; }
+ #computed-style::before { content: "before"; }
+ #computed-style::after { content: "after"; }
+ </style>
+ <div id="computed-style"></div>
+</body>
+</html>
diff --git a/devtools/server/tests/chrome/test_css-properties.html b/devtools/server/tests/chrome/test_css-properties.html
new file mode 100644
index 0000000000..8d1dc9022f
--- /dev/null
+++ b/devtools/server/tests/chrome/test_css-properties.html
@@ -0,0 +1,72 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+Bug 1265798 - Replace inIDOMUtils.cssPropertyIsShorthand
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test CSS Properties Actor</title>
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
+ <script type="application/javascript" src="inspector-helpers.js"></script>
+ <script type="application/javascript">
+"use strict";
+
+window.onload = function() {
+ function toSortedString(array) {
+ return JSON.stringify(array.sort());
+ }
+
+ const runCssPropertiesTests = async function(url) {
+ info(`Opening tab with CssPropertiesActor support.`);
+ // Open a new tab. The only property we are interested in is `target`.
+ const { target } = await attachURL(url);
+ const { cssProperties } = await target.getFront("cssProperties");
+
+ ok(cssProperties.isKnown("border"),
+ "The `border` shorthand property is known.");
+ ok(cssProperties.isKnown("display"),
+ "The `display` property is known.");
+ ok(!cssProperties.isKnown("foobar"),
+ "A fake property is not known.");
+ ok(cssProperties.isKnown("--foobar"),
+ "A CSS variable properly evaluates.");
+ ok(cssProperties.isKnown("--foob\\{ar"),
+ "A CSS variable with escaped character properly evaluates.");
+ ok(cssProperties.isKnown("--fübar"),
+ "A CSS variable unicode properly evaluates.");
+ ok(!cssProperties.isKnown("--foo bar"),
+ "A CSS variable with spaces fails");
+
+ is(toSortedString(cssProperties.getValues("margin")),
+ toSortedString(["auto", "inherit", "initial", "unset", "revert"]),
+ "Can get values for the CSS margin.");
+ is(cssProperties.getValues("foobar").length, 0,
+ "Unknown values return an empty array.");
+
+ const bgColorValues = cssProperties.getValues("background-color");
+ ok(bgColorValues.includes("blanchedalmond"),
+ "A property with color values includes blanchedalmond.");
+ ok(bgColorValues.includes("papayawhip"),
+ "A property with color values includes papayawhip.");
+ ok(bgColorValues.includes("rgb"),
+ "A property with color values includes non-colors.");
+ };
+
+ addAsyncTest(async function setup() {
+ const url = document.getElementById("cssProperties").href;
+ await runCssPropertiesTests(url);
+
+ runNextTest();
+ });
+
+ SimpleTest.waitForExplicitFinish();
+ runNextTest();
+};
+ </script>
+</head>
+<body>
+ <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1265798">Mozilla Bug 1265798</a>
+ <a id="cssProperties" target="_blank" href="inspector_css-properties.html">Test Document</a>
+</body>
+</html>
diff --git a/devtools/server/tests/chrome/test_device.html b/devtools/server/tests/chrome/test_device.html
new file mode 100644
index 0000000000..4567636b47
--- /dev/null
+++ b/devtools/server/tests/chrome/test_device.html
@@ -0,0 +1,80 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+Bug 895360 - [app manager] Device meta data actor
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Mozilla Bug</title>
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
+</head>
+<body>
+<pre id="test">
+<script>
+"use strict";
+
+window.onload = function() {
+ const {require} = ChromeUtils.import("resource://devtools/shared/Loader.jsm");
+ const {DevToolsClient} = require("devtools/client/devtools-client");
+ const {DevToolsServer} = require("devtools/server/devtools-server");
+ const Services = require("Services");
+
+ SimpleTest.waitForExplicitFinish();
+
+ DevToolsServer.init();
+ DevToolsServer.registerAllActors();
+
+ const client = new DevToolsClient(DevToolsServer.connectPipe());
+ client.connect().then(function onConnect() {
+ return client.mainRoot.getFront("device");
+ }).then(function(d) {
+ let desc;
+ const appInfo = Services.appinfo;
+ const utils = window.windowUtils;
+
+ const localDesc = {
+ appid: appInfo.ID,
+ vendor: appInfo.vendor,
+ name: appInfo.name,
+ version: appInfo.version,
+ appbuildid: appInfo.appBuildID,
+ platformbuildid: appInfo.platformBuildID,
+ platformversion: appInfo.platformVersion,
+ geckobuildid: appInfo.platformBuildID,
+ geckoversion: appInfo.platformVersion,
+ useragent: window.navigator.userAgent,
+ locale: Services.locale.appLocaleAsBCP47,
+ os: appInfo.OS,
+ processor: appInfo.XPCOMABI.split("-")[0],
+ compiler: appInfo.XPCOMABI.split("-")[1],
+ dpi: utils.displayDPI,
+ width: window.screen.width,
+ height: window.screen.height,
+ };
+
+ function checkValues() {
+ for (const key in localDesc) {
+ is(desc[key], localDesc[key], "valid field (" + key + ")");
+ }
+
+ const currProfD = Services.dirsvc.get("ProfD", Ci.nsIFile);
+ const profileDir = currProfD.path;
+ ok(profileDir.includes(desc.profile.length > 0 && desc.profile),
+ "valid profile name");
+
+ client.close().then(() => {
+ DevToolsServer.destroy();
+ SimpleTest.finish();
+ });
+ }
+
+ d.getDescription().then(function(v) {
+ desc = v;
+ }).then(checkValues);
+ });
+};
+</script>
+</pre>
+</body>
+</html>
diff --git a/devtools/server/tests/chrome/test_executeInGlobal-outerized_this.html b/devtools/server/tests/chrome/test_executeInGlobal-outerized_this.html
new file mode 100644
index 0000000000..f16ab33a77
--- /dev/null
+++ b/devtools/server/tests/chrome/test_executeInGlobal-outerized_this.html
@@ -0,0 +1,73 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=837060
+
+When we use Debugger.Object.prototype.executeInGlobal, the 'this' value seen
+by the evaluated code should be the WindowProxy, not the inner window
+object.
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Mozilla Bug 837060</title>
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
+</head>
+<body>
+<pre id="test">
+<script>
+"use strict";
+
+const {addDebuggerToGlobal} = ChromeUtils.import("resource://gre/modules/jsdebugger.jsm");
+addDebuggerToGlobal(this);
+
+window.onload = function() {
+ SimpleTest.waitForExplicitFinish();
+
+ const iframe = document.createElement("iframe");
+ iframe.src = "data:text/html,<script>var me = 'page 1';<\/script>";
+ iframe.onload = firstOnLoadHandler;
+ document.body.appendChild(iframe);
+
+ function firstOnLoadHandler() {
+ const dbg = new Debugger();
+ const page1DO = dbg.addDebuggee(iframe.contentWindow);
+ iframe.src = "data:text/html,<script>var me = 'page 2';<\/script>";
+ iframe.onload = function() {
+ const page2DO = dbg.addDebuggee(iframe.contentWindow);
+ ok(page1DO !== page2DO, "the two pages' globals get distinct D.O's");
+ ok(page1DO.unsafeDereference() === page2DO.unsafeDereference(),
+ "unwrapping page1DO and page2DO outerizes both, yielding the same outer window");
+
+ is(page1DO.executeInGlobal("me").return,
+ "page 1", "page1DO continues to refer to original page");
+ is(page2DO.executeInGlobal("me").return, "page 2",
+ "page2DO refers to current page");
+
+ is(page1DO.executeInGlobal("this === window").return, true,
+ "page 1: Debugger.Object.prototype.executeInGlobal should outerize 'this'");
+ is(page1DO.executeInGlobalWithBindings("this === window", {x: 2}).return, true,
+ "page 1: Debugger.Object.prototype.executeInGlobal should outerize 'this'");
+
+ is(page2DO.executeInGlobal("this === window").return, true,
+ "page 2: Debugger.Object.prototype.executeInGlobal should outerize 'this'");
+ is(page2DO.executeInGlobalWithBindings("this === window", {x: 2}).return, true,
+ "page 2: Debugger.Object.prototype.executeInGlobal should outerize 'this'");
+
+ // Debugger doesn't let one use outer windows as globals. You have to innerize.
+ const outerDO = page1DO.makeDebuggeeValue(page1DO.unsafeDereference());
+ ok(outerDO !== page1DO,
+ "outer window gets its own D.O, distinct from page 1's global");
+ ok(outerDO !== page2DO,
+ "outer window gets its own D.O, distinct from page 2's global");
+ SimpleTest.doesThrow(() => outerDO.executeInGlobal("me"),
+ "outer window D.Os can't be used as globals");
+
+ SimpleTest.finish();
+ };
+ }
+};
+</script>
+</pre>
+</body>
+</html>
diff --git a/devtools/server/tests/chrome/test_framerate_01.html b/devtools/server/tests/chrome/test_framerate_01.html
new file mode 100644
index 0000000000..a306eb7ea5
--- /dev/null
+++ b/devtools/server/tests/chrome/test_framerate_01.html
@@ -0,0 +1,70 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+Bug 1007200 - Create a framerate actor
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Framerate actor test</title>
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript" src="framerate-helpers.js"></script>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
+</head>
+<body>
+<pre id="test">
+<script>
+"use strict";
+
+window.onload = async function() {
+ const target = await getTargetForSelectedTab();
+ const front = await target.getFront("framerate");
+ const TICK = 1000;
+
+ await waitFor(TICK);
+ await front.startRecording();
+ await waitFor(TICK);
+ const rawData = await front.stopRecording();
+ await onRecordingStopped(front, rawData);
+ await target.destroy();
+ SimpleTest.finish();
+};
+
+// Local Helpers
+async function onRecordingStopped(front, rawData) {
+ ok(rawData, "There should be a recording available.");
+
+ const timeline = plotFPS(rawData);
+ ok(timeline.length >= 2,
+ "There should be at least one measurement available, with two entries.");
+
+ let prevTimeStart = timeline[0].delta;
+
+ for (let i = 0; i < timeline.length; i += 2) {
+ const currTimeStart = timeline[i].delta;
+ const currTimeEnd = timeline[i + 1].delta;
+ info("Testing delta: " + currTimeStart + " vs. " + currTimeEnd);
+
+ ok(currTimeStart < currTimeEnd,
+ "The start and end time deltas should be consecutive.");
+ is(currTimeStart, prevTimeStart,
+ "There should be two time deltas for each framerate value.");
+
+ prevTimeStart = currTimeEnd;
+ }
+
+ for (let i = 0; i < timeline.length; i += 2) {
+ const currFramerateStart = timeline[i].value;
+ const currFramerateEnd = timeline[i + 1].value;
+ info("Testing framerate: " + currFramerateStart);
+
+ is(currFramerateStart, currFramerateEnd,
+ "The start and end framerate values should be equal.");
+
+ is(typeof currFramerateStart, "number", "All values should be numbers.");
+ ok(currFramerateStart <= 60, "All values were correctly clamped.");
+ }
+}
+</script>
+</pre>
+</body>
+</html>
diff --git a/devtools/server/tests/chrome/test_framerate_02.html b/devtools/server/tests/chrome/test_framerate_02.html
new file mode 100644
index 0000000000..346ba0b22c
--- /dev/null
+++ b/devtools/server/tests/chrome/test_framerate_02.html
@@ -0,0 +1,48 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+Bug 1007200 - Create a framerate actor
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Framerate actor test</title>
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript" src="framerate-helpers.js"></script>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
+</head>
+<body>
+<pre id="test">
+<script>
+"use strict";
+
+window.onload = async function() {
+ const target = await getTargetForSelectedTab();
+ const front = await target.getFront("framerate");
+
+ const rawData = await front.stopRecording();
+ ok(rawData, "There should be a recording available.");
+ is(rawData.length, 0, "...but it should be empty.");
+
+ const timeline = plotFPS(rawData);
+ is(timeline.length, 2,
+ "There should be one measurement plotted, with two entries.");
+
+ info("The framerate should be assumed to be 0 if the recording is empty.");
+
+ is(timeline[0].delta, 0,
+ "The first time delta should be 0.");
+ is(timeline[0].value, 0,
+ "The first framerate value should be 0.");
+
+ is(timeline[1].delta, 100,
+ "The last time delta should be 100 (the default interval value).");
+ is(timeline[1].value, 0,
+ "The last framerate value should be 0.");
+
+ await target.destroy();
+ SimpleTest.finish();
+};
+</script>
+</pre>
+</body>
+</html>
diff --git a/devtools/server/tests/chrome/test_framerate_03.html b/devtools/server/tests/chrome/test_framerate_03.html
new file mode 100644
index 0000000000..b8eca6c428
--- /dev/null
+++ b/devtools/server/tests/chrome/test_framerate_03.html
@@ -0,0 +1,51 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+Bug 1023018 - Tests whether or not the framerate actor can handle time ranges.
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Framerate actor test</title>
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript" src="framerate-helpers.js"></script>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
+</head>
+<body>
+<pre id="test">
+<script>
+"use strict";
+
+const START_TICK = 2000;
+const STOP_TICK = 3000;
+const TOTAL_TIME = 5000;
+
+window.onload = async function() {
+ const target = await getTargetForSelectedTab();
+ const front = await target.getFront("framerate");
+
+ await front.startRecording();
+ await waitFor(TOTAL_TIME);
+ const rawData = await front.stopRecording(START_TICK, STOP_TICK);
+ await onRecordingStopped(front, rawData);
+ await target.destroy();
+ SimpleTest.finish();
+};
+
+// Local Helper Functions
+async function onRecordingStopped(front, rawData) {
+ ok(rawData, "There should be a recording available.");
+
+ ok(!rawData.find(e => e < START_TICK),
+ "There should be no tick before 2000ms.");
+ ok(!rawData.find(e => e > STOP_TICK),
+ "There should be no tick after 3000ms.");
+
+ for (const tick of rawData) {
+ info("Testing tick: " + tick);
+ is(typeof tick, "number", "All values should be numbers.");
+ }
+}
+</script>
+</pre>
+</body>
+</html>
diff --git a/devtools/server/tests/chrome/test_framerate_04.html b/devtools/server/tests/chrome/test_framerate_04.html
new file mode 100644
index 0000000000..6fbc0e6969
--- /dev/null
+++ b/devtools/server/tests/chrome/test_framerate_04.html
@@ -0,0 +1,66 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+Bug 1023018 - Tests if the framerate actor keeps recording after navigations.
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Framerate actor test</title>
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript" src="inspector-helpers.js"></script>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
+</head>
+<body>
+<pre id="test">
+<script>
+"use strict";
+
+window.onload = async function() {
+ // inspector-helpers doesnt wait for explicit finish
+ SimpleTest.waitForExplicitFinish();
+
+ const TICK = 1000;
+ const url = document.getElementById("testContent").href;
+ const { target, doc } = await attachURL(url);
+ const contentWin = doc.defaultView;
+ const front = await target.getFront("framerate");
+
+ await front.startRecording();
+ await waitFor(TICK);
+ const firstBatch = await front.getPendingTicks();
+ await waitFor(TICK);
+
+ const onWillNavigate = target.once("will-navigate");
+ contentWin.location.reload();
+ await onWillNavigate;
+
+ await waitFor(TICK);
+ const secondBatch = await front.stopRecording();
+ await onRecordingStopped(firstBatch, secondBatch);
+ target.destroy();
+ SimpleTest.finish();
+};
+
+// Local Helpers
+function waitFor(time) {
+ return new Promise(resolve => setTimeout(resolve, time));
+}
+
+function onRecordingStopped(firstBatch, secondBatch) {
+ ok(firstBatch, "There should be a first batch recording available.");
+ ok(secondBatch, "There should be a second batch recording available.");
+
+ const diff = secondBatch.length - firstBatch.length;
+ info("Difference in ticks: " + diff);
+ ok(diff > 0, "More ticks should be recorded in the second batch.");
+
+ ok(firstBatch.every((e) => secondBatch.includes(e)),
+ "All the ticks in the first batch should be in the second batch as well.");
+ ok(secondBatch.every((e, i, array) => i < array.length - 1 ? e < array[i + 1] : true),
+ "All the ticks in the final batch should be ascending in value.");
+}
+</script>
+</pre>
+<a id="testContent" target="_blank" href="inspector_getImageData.html">Test Document</a>
+</body>
+</html>
diff --git a/devtools/server/tests/chrome/test_framerate_05.html b/devtools/server/tests/chrome/test_framerate_05.html
new file mode 100644
index 0000000000..574b86719a
--- /dev/null
+++ b/devtools/server/tests/chrome/test_framerate_05.html
@@ -0,0 +1,47 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+Bug 1034648 - Tests whether a framerate recording can be cancelled.
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Framerate actor test</title>
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript" src="framerate-helpers.js"></script>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
+</head>
+<body>
+<pre id="test">
+<script>
+"use strict";
+
+window.onload = async function() {
+ const target = await getTargetForSelectedTab();
+ const front = await target.getFront("framerate");
+ const TICK = 1000;
+
+ await front.startRecording();
+ await waitFor(TICK);
+
+ await front.cancelRecording();
+ await waitFor(TICK);
+
+ const rawTicks = await front.getPendingTicks();
+ ok(rawTicks,
+ "The returned pending ticks should be empty (1).");
+ is(rawTicks.length, 0,
+ "The returned pending ticks should be empty (2).");
+
+ const newRawData = await front.stopRecording();
+ ok(newRawData,
+ "The returned raw data should be an empty array (1).");
+ is(newRawData.length, 0,
+ "The returned raw data should be an empty array (2).");
+
+ await target.destroy();
+ SimpleTest.finish();
+};
+</script>
+</pre>
+</body>
+</html>
diff --git a/devtools/server/tests/chrome/test_framerate_06.html b/devtools/server/tests/chrome/test_framerate_06.html
new file mode 100644
index 0000000000..6c8040c381
--- /dev/null
+++ b/devtools/server/tests/chrome/test_framerate_06.html
@@ -0,0 +1,87 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+Bug 1171489 - Tests if the framerate actor does not record timestamps from multiple frames.
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Framerate actor test</title>
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript" src="inspector-helpers.js"></script>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
+</head>
+<body>
+<pre id="test">
+<script>
+"use strict";
+
+window.onload = async function() {
+ // inspector-helpers doesnt wait for explicit finish
+ SimpleTest.waitForExplicitFinish();
+
+ const TICK = 1000;
+ const url = document.getElementById("testContent").href;
+ const { target, doc } = await attachURL(url);
+ const contentWin = doc.defaultView;
+
+ const front = await target.getFront("framerate");
+
+ await front.startRecording();
+ await waitFor(TICK);
+ const onWindowReady = waitForWindowReady();
+ contentWin.location.reload();
+
+ // Wait for the iframe to be loaded again
+ await onWindowReady;
+ await waitFor(TICK);
+ const ticks = await front.stopRecording();
+ await onRecordingStopped(ticks);
+
+ await target.destroy();
+ SimpleTest.finish();
+};
+
+// Local Helpers
+function waitFor(time) {
+ return new Promise(resolve => setTimeout(resolve, time));
+}
+
+function waitForWindowReady() {
+ return new Promise(resolve => {
+ window.addEventListener("message", function loaded(event) {
+ if (event.data === "ready") {
+ window.removeEventListener("message", loaded);
+ resolve();
+ }
+ });
+ });
+}
+
+function onRecordingStopped(ticks) {
+ const diffs = [];
+
+ info(`Got ${ticks.length} ticks.`);
+
+ for (let i = 1; i < ticks.length; i++) {
+ const prev = ticks[i - 1];
+ const curr = ticks[i];
+ diffs.push(curr - prev);
+ info(curr + " - " + (curr - prev));
+ }
+
+ // 1000 / 60 => 16.666... so we shouldn't get more than diffs of 16.66.. but
+ // when we get ticks from other frames they're usually at diffs of < 1. Sometimes
+ // ticks can still be less than 16ms even on one frame (usually following a very slow
+ // frame), so use a low number (2) to be our threshold
+ const THRESHOLD = 2;
+ ok(ticks.length >= 20,
+ "we should have atleast 20 ticks over the course of two seconds.");
+ const belowThreshold = diffs.filter(v => v <= THRESHOLD);
+ ok(belowThreshold.length <= 10,
+ "we should have very few frames less than the threshold");
+}
+</script>
+</pre>
+<a id="testContent" target="_blank" href="inspector-traversal-data.html">Test Document</a>
+</body>
+</html>
diff --git a/devtools/server/tests/chrome/test_highlighter_paused_debugger.html b/devtools/server/tests/chrome/test_highlighter_paused_debugger.html
new file mode 100644
index 0000000000..ad8239a9e1
--- /dev/null
+++ b/devtools/server/tests/chrome/test_highlighter_paused_debugger.html
@@ -0,0 +1,88 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+Test the PausedDebuggerOverlay highlighter.
+-->
+<head>
+ <meta charset="utf-8">
+ <title>PausedDebuggerOverlay test</title>
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
+</head>
+<body>
+<pre id="test">
+<script>
+"use strict";
+
+window.onload = async function() {
+ SimpleTest.waitForExplicitFinish();
+
+ const {require} = ChromeUtils.import("resource://devtools/shared/Loader.jsm");
+ require("devtools/server/actors/inspector/inspector");
+ const {HighlighterEnvironment} = require("devtools/server/actors/highlighters");
+ const {PausedDebuggerOverlay} = require("devtools/server/actors/highlighters/paused-debugger");
+
+ const env = new HighlighterEnvironment();
+ env.initFromWindow(window);
+
+ const highlighter = new PausedDebuggerOverlay(env);
+ await highlighter.isReady;
+ const anonymousContent = highlighter.markup.content;
+
+ const id = elementID => `${highlighter.ID_CLASS_PREFIX}${elementID}`;
+
+ function isHidden(elementID) {
+ const attr = anonymousContent.getAttributeForElement(id(elementID), "hidden");
+ return typeof attr === "string" && attr == "true";
+ }
+
+ function getReason() {
+ return anonymousContent.getTextContentForElement(id("reason"));
+ }
+
+ function isOverlayShown() {
+ const attr = anonymousContent.getAttributeForElement(id("root"), "overlay");
+ return typeof attr === "string" && attr == "true";
+ }
+
+ info("Test that the various elements with IDs exist");
+ ok(highlighter.getElement("root"), "The root wrapper element exists");
+ ok(highlighter.getElement("toolbar"), "The toolbar element exists");
+ ok(highlighter.getElement("reason"), "The reason label element exists");
+
+ info("Test that the highlighter is hidden by default");
+ ok(isHidden("root"), "The highlighter is hidden");
+
+ info("Show the highlighter with overlay and toolbar");
+ let didShow = highlighter.show("breakpoint");
+ ok(didShow, "Calling show returned true");
+ ok(!isHidden("root"), "The highlighter is shown");
+ ok(isOverlayShown(), "The overlay is shown");
+ is(
+ getReason(),
+ "Paused on breakpoint",
+ "The reason displayed in the toolbar is correct"
+ );
+
+ info("Call show again with another reason");
+ didShow = highlighter.show("debuggerStatement");
+ ok(didShow, "Calling show returned true too");
+ ok(!isHidden("root"), "The highlighter is still shown");
+ is(getReason(), "Paused on debugger statement",
+ "The reason displayed in the toolbar is correct again");
+ ok(isOverlayShown(), "The overlay is still shown too");
+
+ info("Call show again but with no reason");
+ highlighter.show();
+ ok(isOverlayShown(), "The overlay is shown however");
+
+ info("Hide the highlighter");
+ highlighter.hide();
+ ok(isHidden("root"), "The highlighter is now hidden");
+
+ SimpleTest.finish();
+};
+</script>
+</pre>
+</body>
+</html>
diff --git a/devtools/server/tests/chrome/test_inspector-changeattrs.html b/devtools/server/tests/chrome/test_inspector-changeattrs.html
new file mode 100644
index 0000000000..94c4c3dc1b
--- /dev/null
+++ b/devtools/server/tests/chrome/test_inspector-changeattrs.html
@@ -0,0 +1,90 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug </title>
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
+ <script type="application/javascript" src="inspector-helpers.js"></script>
+ <script type="application/javascript">
+"use strict";
+
+window.onload = function() {
+ SimpleTest.waitForExplicitFinish();
+ runNextTest();
+};
+
+let gInspectee = null;
+let gWalker = null;
+
+addTest(async function setup() {
+ const url = document.getElementById("inspectorContent").href;
+ const { target, doc } = await attachURL(url);
+ gInspectee = doc;
+ const inspector = await target.getFront("inspector");
+ gWalker = inspector.walker;
+ runNextTest();
+});
+
+addTest(function testChangeAttrs() {
+ const attrNode = gInspectee.querySelector("#a");
+ let attrFront;
+ promiseDone(gWalker.querySelector(gWalker.rootNode, "#a").then(front => {
+ attrFront = front;
+ dump("attrFront is: " + attrFront + "\n");
+ // Add a few attributes.
+ const list = attrFront.startModifyingAttributes();
+ list.setAttribute("data-newattr", "newvalue");
+ list.setAttribute("data-newattr2", "newvalue");
+ return list.apply();
+ }).then(() => {
+ // We're only going to test that the change hit the document.
+ // There are other tests that make sure changes are propagated
+ // to the client.
+ is(attrNode.getAttribute("data-newattr"), "newvalue",
+ "Node should have the first new attribute");
+ is(attrNode.getAttribute("data-newattr2"), "newvalue",
+ "Node should have the second new attribute.");
+ }).then(() => {
+ // Change an attribute.
+ const list = attrFront.startModifyingAttributes();
+ list.setAttribute("data-newattr", "changedvalue");
+ return list.apply();
+ }).then(() => {
+ is(attrNode.getAttribute("data-newattr"), "changedvalue",
+ "Node should have the changed first value.");
+ is(attrNode.getAttribute("data-newattr2"), "newvalue",
+ "Second value should remain unchanged.");
+ }).then(() => {
+ const list = attrFront.startModifyingAttributes();
+ list.removeAttribute("data-newattr2");
+ return list.apply();
+ }).then(() => {
+ is(attrNode.getAttribute("data-newattr"), "changedvalue",
+ "Node should have the changed first value.");
+ ok(!attrNode.hasAttribute("data-newattr2"), "Second value should be removed.");
+ }).then(runNextTest));
+});
+
+addTest(function cleanup() {
+ gWalker = null;
+ gInspectee = null;
+ runNextTest();
+});
+ </script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=">Mozilla Bug </a>
+<a id="inspectorContent" target="_blank" href="inspector-traversal-data.html">Test Document</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+</body>
+</html>
diff --git a/devtools/server/tests/chrome/test_inspector-changevalue.html b/devtools/server/tests/chrome/test_inspector-changevalue.html
new file mode 100644
index 0000000000..f5aee52881
--- /dev/null
+++ b/devtools/server/tests/chrome/test_inspector-changevalue.html
@@ -0,0 +1,68 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug </title>
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
+ <script type="application/javascript" src="inspector-helpers.js"></script>
+ <script type="application/javascript">
+"use strict";
+
+window.onload = function() {
+ SimpleTest.waitForExplicitFinish();
+ runNextTest();
+};
+
+let gInspectee = null;
+let gWalker = null;
+
+addTest(async function setup() {
+ const url = document.getElementById("inspectorContent").href;
+ const { target, doc } = await attachURL(url);
+ gInspectee = doc;
+ const inspector = await target.getFront("inspector");
+ gWalker = inspector.walker;
+ runNextTest();
+});
+
+addTest(function testChangeValue() {
+ const contentNode = gInspectee.querySelector("#a").firstChild;
+ let nodeFront;
+ promiseDone(gWalker.querySelector(gWalker.rootNode, "#a").then(front => {
+ // Get the text child
+ return gWalker.children(front, { maxNodes: 1 });
+ }).then(children => {
+ nodeFront = children.nodes[0];
+ is(nodeFront.nodeType, Node.TEXT_NODE);
+ return nodeFront.setNodeValue("newvalue");
+ }).then(() => {
+ // We're only going to test that the change hit the document.
+ // There are other tests that make sure changes are propagated
+ // to the client.
+ is(contentNode.nodeValue, "newvalue", "Node should have a new value.");
+ }).then(runNextTest));
+});
+
+addTest(function cleanup() {
+ gWalker = null;
+ gInspectee = null;
+ runNextTest();
+});
+ </script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=">Mozilla Bug </a>
+<a id="inspectorContent" target="_blank" href="inspector-traversal-data.html">Test Document</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+</body>
+</html>
diff --git a/devtools/server/tests/chrome/test_inspector-dead-nodes.html b/devtools/server/tests/chrome/test_inspector-dead-nodes.html
new file mode 100644
index 0000000000..53c8b7eb3d
--- /dev/null
+++ b/devtools/server/tests/chrome/test_inspector-dead-nodes.html
@@ -0,0 +1,332 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1121528
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1121528</title>
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
+ <script type="application/javascript" src="inspector-helpers.js"></script>
+ <script type="application/javascript">
+"use strict";
+
+window.onload = function() {
+ SimpleTest.waitForExplicitFinish();
+ runNextTest();
+};
+
+let gWalker = null;
+let gDoc = null;
+let gResourceWatcher = null;
+let gRootNodeResolve = null;
+
+async function reloadTarget() {
+ const rootNodePromise = new Promise(r => (gRootNodeResolve = r));
+ gDoc.defaultView.location.reload();
+ await rootNodePromise;
+}
+
+addAsyncTest(async function() {
+ const url = document.getElementById("inspectorContent").href;
+ const { target, doc } = await attachURL(url);
+ gDoc = doc;
+ const inspector = await target.getFront("inspector");
+ gWalker = inspector.walker;
+ gResourceWatcher = createResourceWatcher(target);
+
+ info("Start watching for root nodes and wait for the initial root node");
+ const rootNodePromise = new Promise(r => (gRootNodeResolve = r));
+ const onAvailable = rootNodeFront => gRootNodeResolve(rootNodeFront);
+ await gResourceWatcher.watchResources([gResourceWatcher.TYPES.ROOT_NODE], {
+ onAvailable,
+ });
+ await rootNodePromise;
+
+ runNextTest();
+});
+
+addAsyncTest(async function() {
+ info("Getting a nodeFront, reloading the page, and calling " +
+ "walker.children(nodeFront) before the load completes shouldn't fail");
+
+ const nodeFront = await gWalker.querySelector(gWalker.rootNode, "body");
+
+ await reloadTarget();
+ await gWalker.children(nodeFront);
+
+ ok(true, "The call to walker.children() didn't fail");
+ runNextTest();
+});
+
+addAsyncTest(async function() {
+ info("Getting a nodeFront, reloading the page, and calling " +
+ "walker.nextSibling(nodeFront) before the load completes shouldn't fail");
+
+ const nodeFront = await gWalker.querySelector(gWalker.rootNode, "h1");
+ await reloadTarget();
+ await gWalker.nextSibling(nodeFront);
+
+ ok(true, "The call to walker.nextSibling() didn't fail");
+ runNextTest();
+});
+
+addAsyncTest(async function() {
+ info("Getting a nodeFront, reloading the page, and calling " +
+ "walker.previousSibling(nodeFront) before the load completes shouldn't fail");
+
+ const nodeFront = await gWalker.querySelector(gWalker.rootNode, "h1");
+ await reloadTarget();
+ await gWalker.previousSibling(nodeFront);
+
+ ok(true, "The call to walker.previousSibling() didn't fail");
+ runNextTest();
+});
+
+addAsyncTest(async function() {
+ info("Getting a nodeFront, reloading the page, and calling " +
+ "walker.addPseudoClassLock(nodeFront) before the load completes " +
+ "shouldn't fail");
+
+ const nodeFront = await gWalker.querySelector(gWalker.rootNode, "h1");
+ await reloadTarget();
+ await gWalker.addPseudoClassLock(nodeFront, ":hover");
+
+ ok(true, "The call to walker.addPseudoClassLock() didn't fail");
+ runNextTest();
+});
+
+addAsyncTest(async function() {
+ info("Getting a nodeFront, reloading the page, and calling " +
+ "walker.removePseudoClassLock(nodeFront) before the load completes " +
+ "shouldn't fail");
+
+ const nodeFront = await gWalker.querySelector(gWalker.rootNode, "h1");
+ await reloadTarget();
+ await gWalker.removePseudoClassLock(nodeFront, ":hover");
+
+ ok(true, "The call to walker.removePseudoClassLock() didn't fail");
+ runNextTest();
+});
+
+addAsyncTest(async function() {
+ info("Getting a nodeFront, reloading the page, and calling " +
+ "walker.clearPseudoClassLocks(nodeFront) before the load completes " +
+ "shouldn't fail");
+
+ const nodeFront = await gWalker.querySelector(gWalker.rootNode, "h1");
+ await reloadTarget();
+ await gWalker.clearPseudoClassLocks(nodeFront);
+
+ ok(true, "The call to walker.clearPseudoClassLocks() didn't fail");
+ runNextTest();
+});
+
+addAsyncTest(async function() {
+ info("Getting a nodeFront, reloading the page, and calling " +
+ "walker.innerHTML(nodeFront) before the load completes shouldn't fail");
+
+ const nodeFront = await gWalker.querySelector(gWalker.rootNode, "h1");
+ await reloadTarget();
+ await gWalker.innerHTML(nodeFront);
+
+ ok(true, "The call to walker.innerHTML() didn't fail");
+ runNextTest();
+});
+
+addAsyncTest(async function() {
+ info("Getting a nodeFront, reloading the page, and calling " +
+ "walker.setInnerHTML(nodeFront) before the load completes shouldn't fail");
+
+ const nodeFront = await gWalker.querySelector(gWalker.rootNode, "h1");
+ await reloadTarget();
+ await gWalker.setInnerHTML(nodeFront, "<span>innerHTML changed</span>");
+
+ ok(true, "The call to walker.setInnerHTML() didn't fail");
+ runNextTest();
+});
+
+addAsyncTest(async function() {
+ info("Getting a nodeFront, reloading the page, and calling " +
+ "walker.outerHTML(nodeFront) before the load completes shouldn't fail");
+
+ const nodeFront = await gWalker.querySelector(gWalker.rootNode, "h1");
+ await reloadTarget();
+ await gWalker.outerHTML(nodeFront);
+
+ ok(true, "The call to walker.outerHTML() didn't fail");
+ runNextTest();
+});
+
+addAsyncTest(async function() {
+ info("Getting a nodeFront, reloading the page, and calling " +
+ "walker.setOuterHTML(nodeFront) before the load completes shouldn't fail");
+
+ const nodeFront = await gWalker.querySelector(gWalker.rootNode, "h1");
+ await reloadTarget();
+ await gWalker.setOuterHTML(nodeFront, "<h1><span>innerHTML changed</span></h1>");
+
+ ok(true, "The call to walker.setOuterHTML() didn't fail");
+ runNextTest();
+});
+
+addAsyncTest(async function() {
+ info("Getting a nodeFront, reloading the page, and calling " +
+ "walker.insertAdjacentHTML(nodeFront) before the load completes shouldn't " +
+ "fail");
+
+ const nodeFront = await gWalker.querySelector(gWalker.rootNode, "h1");
+ await reloadTarget();
+ await gWalker.insertAdjacentHTML(nodeFront, "afterEnd",
+ "<span>new adjacent HTML</span>");
+
+ ok(true, "The call to walker.insertAdjacentHTML() didn't fail");
+ runNextTest();
+});
+
+addAsyncTest(async function() {
+ info("Getting a nodeFront, reloading the page, and calling " +
+ "walker.removeNode(nodeFront) before the load completes should throw");
+
+ const nodeFront = await gWalker.querySelector(gWalker.rootNode, "h1");
+ await reloadTarget();
+ let hasThrown = false;
+ try {
+ await gWalker.removeNode(nodeFront);
+ } catch (e) {
+ hasThrown = true;
+ }
+
+ ok(hasThrown, "The call to walker.removeNode() threw");
+ runNextTest();
+});
+
+addAsyncTest(async function() {
+ info("Getting a nodeFront, reloading the page, and calling " +
+ "walker.removeNodes([nodeFront]) before the load completes should throw");
+
+ const nodeFront1 = await gWalker.querySelector(gWalker.rootNode, "h1");
+ const nodeFront2 = await gWalker.querySelector(gWalker.rootNode, "#longstring");
+ const nodeFront3 = await gWalker.querySelector(gWalker.rootNode, "#shortstring");
+ await reloadTarget();
+ let hasThrown = false;
+ try {
+ await gWalker.removeNodes([nodeFront1, nodeFront2, nodeFront3]);
+ } catch (e) {
+ hasThrown = true;
+ }
+
+ ok(hasThrown, "The call to walker.removeNodes() threw");
+ runNextTest();
+});
+
+addAsyncTest(async function() {
+ info("Getting a nodeFront, reloading the page, and calling " +
+ "walker.insertBefore(nodeFront, parent, null) before the load completes " +
+ "shouldn't fail");
+
+ const nodeFront = await gWalker.querySelector(gWalker.rootNode, "h1");
+ const newParentFront = await gWalker.querySelector(gWalker.rootNode, "#longlist");
+ await reloadTarget();
+ await gWalker.insertBefore(nodeFront, newParentFront);
+
+ ok(true, "The call to walker.insertBefore() didn't fail");
+ runNextTest();
+});
+
+addAsyncTest(async function() {
+ info("Getting a nodeFront, reloading the page, and calling " +
+ "walker.insertBefore(nodeFront, parent, sibling) before the load completes " +
+ "shouldn't fail");
+
+ const nodeFront = await gWalker.querySelector(gWalker.rootNode, "h1");
+ const newParentFront = await gWalker.querySelector(gWalker.rootNode, "#longlist");
+ const siblingFront = await gWalker.querySelector(gWalker.rootNode, "#b");
+ await reloadTarget();
+ await gWalker.insertBefore(nodeFront, newParentFront, siblingFront);
+
+ ok(true, "The call to walker.insertBefore() didn't fail");
+ runNextTest();
+});
+
+addAsyncTest(async function() {
+ info("Getting a nodeFront, reloading the page, and calling " +
+ "walker.editTagName(nodeFront) before the load completes shouldn't fail");
+
+ const nodeFront = await gWalker.querySelector(gWalker.rootNode, "h1");
+ await reloadTarget();
+ await gWalker.editTagName(nodeFront, "h2");
+
+ ok(true, "The call to walker.editTagName() didn't fail");
+ runNextTest();
+});
+
+addAsyncTest(async function() {
+ info("Getting a nodeFront, reloading the page, and calling " +
+ "walker.hideNode(nodeFront) before the load completes shouldn't fail");
+
+ const nodeFront = await gWalker.querySelector(gWalker.rootNode, "h1");
+ await reloadTarget();
+ await gWalker.hideNode(nodeFront);
+
+ ok(true, "The call to walker.hideNode() didn't fail");
+ runNextTest();
+});
+
+addAsyncTest(async function() {
+ info("Getting a nodeFront, reloading the page, and calling " +
+ "walker.unhideNode(nodeFront) before the load completes shouldn't fail");
+
+ const nodeFront = await gWalker.querySelector(gWalker.rootNode, "h1");
+ await reloadTarget();
+ await gWalker.unhideNode(nodeFront);
+
+ ok(true, "The call to walker.unhideNode() didn't fail");
+ runNextTest();
+});
+
+addAsyncTest(async function() {
+ info("Getting a nodeFront, reloading the page, and calling " +
+ "walker.releaseNode(nodeFront) before the load completes shouldn't fail");
+
+ const nodeFront = await gWalker.querySelector(gWalker.rootNode, "h1");
+ await reloadTarget();
+ await gWalker.releaseNode(nodeFront);
+
+ ok(true, "The call to walker.releaseNode() didn't fail");
+ runNextTest();
+});
+
+addAsyncTest(async function() {
+ info("Getting a nodeFront, reloading the page, and calling " +
+ "walker.querySelector(nodeFront) before the load completes shouldn't fail");
+
+ const nodeFront = await gWalker.querySelector(gWalker.rootNode, "body");
+ await reloadTarget();
+ await gWalker.querySelector(nodeFront, "h1");
+
+ ok(true, "The call to walker.querySelector() didn't fail");
+ runNextTest();
+});
+
+addTest(function cleanup() {
+ gWalker = null;
+ gDoc = null;
+ gResourceWatcher = null;
+ runNextTest();
+});
+ </script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=932937">Mozilla Bug 1121528</a>
+<a id="inspectorContent" target="_blank" href="inspector-traversal-data.html">Test Document</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+</body>
+</html>
diff --git a/devtools/server/tests/chrome/test_inspector-display-type.html b/devtools/server/tests/chrome/test_inspector-display-type.html
new file mode 100644
index 0000000000..a8bbedc22a
--- /dev/null
+++ b/devtools/server/tests/chrome/test_inspector-display-type.html
@@ -0,0 +1,81 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1431900
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1431900</title>
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
+ <script type="application/javascript" src="inspector-helpers.js"></script>
+ <script type="application/javascript">
+"use strict";
+
+window.onload = function() {
+ SimpleTest.waitForExplicitFinish();
+ runNextTest();
+};
+
+var gWalker;
+
+addTest(async function setup() {
+ const url = document.getElementById("inspectorContent").href;
+ const { target } = await attachURL(url);
+ const inspector = await target.getFront("inspector");
+ gWalker = inspector.walker;
+ runNextTest();
+});
+
+addAsyncTest(async function testInlineBlockDisplayType() {
+ info("Test getting the display type of an inline block element.");
+ const node = await gWalker.querySelector(gWalker.rootNode, "#inline-block");
+ const displayType = node.displayType;
+ is(displayType, "inline-block", "The node has a display type of 'inline-block'.");
+ runNextTest();
+});
+
+addAsyncTest(async function testInlineTextChildDisplayType() {
+ info("Test getting the display type of an inline text child.");
+ const node = await gWalker.querySelector(gWalker.rootNode, "#inline-block");
+ const children = await gWalker.children(node);
+ const inlineTextChild = children.nodes[0];
+ const displayType = inlineTextChild.displayType;
+ ok(!displayType, "No display type for inline text child.");
+ runNextTest();
+});
+
+addAsyncTest(async function testGridDisplayType() {
+ info("Test getting the display type of an grid container.");
+ const node = await gWalker.querySelector(gWalker.rootNode, "#grid");
+ const displayType = node.displayType;
+ is(displayType, "grid", "The node has a display type of 'grid'.");
+ runNextTest();
+});
+
+addAsyncTest(async function testBlockDisplayType() {
+ info("Test getting the display type of a block element.");
+ const node = await gWalker.querySelector(gWalker.rootNode, "#block");
+ const displayType = await node.displayType;
+ is(displayType, "block", "The node has a display type of 'block'.");
+ runNextTest();
+});
+
+addTest(function() {
+ gWalker = null;
+ runNextTest();
+});
+ </script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1431900">Mozilla Bug 1431900</a>
+<a id="inspectorContent" target="_blank" href="inspector_display-type.html">Test Document</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+</body>
+</html>
diff --git a/devtools/server/tests/chrome/test_inspector-duplicate-node.html b/devtools/server/tests/chrome/test_inspector-duplicate-node.html
new file mode 100644
index 0000000000..205e11629e
--- /dev/null
+++ b/devtools/server/tests/chrome/test_inspector-duplicate-node.html
@@ -0,0 +1,61 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1208864
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1208864</title>
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
+ <script type="application/javascript" src="inspector-helpers.js"></script>
+ <script type="application/javascript">
+"use strict";
+
+window.onload = function() {
+ SimpleTest.waitForExplicitFinish();
+ runNextTest();
+};
+
+let gWalker = null;
+
+addTest(async function setup() {
+ const url = document.getElementById("inspectorContent").href;
+ const { target } = await attachURL(url);
+ const inspector = await target.getFront("inspector");
+ gWalker = inspector.walker;
+ runNextTest();
+});
+
+addTest(async function testDuplicateNode() {
+ const className = ".node-to-duplicate";
+ let matches = await gWalker.querySelectorAll(gWalker.rootNode, className);
+ is(matches.length, 1, "There should initially be one node to duplicate.");
+
+ const nodeFront = await gWalker.querySelector(gWalker.rootNode, className);
+ await gWalker.duplicateNode(nodeFront);
+
+ matches = await gWalker.querySelectorAll(gWalker.rootNode, className);
+ is(matches.length, 2, "The node should now be duplicated.");
+
+ runNextTest();
+});
+
+addTest(function cleanup() {
+ gWalker = null;
+ runNextTest();
+});
+ </script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1208864">Mozilla Bug 1208864</a>
+<a id="inspectorContent" target="_blank" href="inspector-traversal-data.html">Test Document</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+</body>
+</html>
diff --git a/devtools/server/tests/chrome/test_inspector-hide.html b/devtools/server/tests/chrome/test_inspector-hide.html
new file mode 100644
index 0000000000..e699400ee0
--- /dev/null
+++ b/devtools/server/tests/chrome/test_inspector-hide.html
@@ -0,0 +1,71 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug </title>
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
+ <script type="application/javascript" src="inspector-helpers.js"></script>
+ <script type="application/javascript">
+"use strict";
+
+window.onload = function() {
+ SimpleTest.waitForExplicitFinish();
+ runNextTest();
+};
+
+let gWalker = null;
+let gInspectee = null;
+
+addTest(async function setup() {
+ const url = document.getElementById("inspectorContent").href;
+ const { target, doc } = await attachURL(url);
+ const inspector = await target.getFront("inspector");
+ gInspectee = doc;
+ gWalker = inspector.walker;
+ runNextTest();
+});
+
+addTest(function testRearrange() {
+ let listFront = null;
+ const listNode = gInspectee.querySelector("#longlist");
+
+ promiseDone(gWalker.querySelector(gWalker.rootNode, "#longlist").then(front => {
+ listFront = front;
+ }).then(() => {
+ const computed = gInspectee.defaultView.getComputedStyle(listNode);
+ is(computed.visibility, "visible", "Node should be visible to start with");
+ return gWalker.hideNode(listFront);
+ }).then(response => {
+ const computed = gInspectee.defaultView.getComputedStyle(listNode);
+ is(computed.visibility, "hidden", "Node should be hidden");
+ return gWalker.unhideNode(listFront);
+ }).then(() => {
+ const computed = gInspectee.defaultView.getComputedStyle(listNode);
+ is(computed.visibility, "visible", "Node should be visible again.");
+ }).then(runNextTest));
+});
+
+addTest(function cleanup() {
+ gWalker = null;
+ gInspectee = null;
+ runNextTest();
+});
+
+ </script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=">Mozilla Bug </a>
+<a id="inspectorContent" target="_blank" href="inspector-traversal-data.html">Test Document</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+</body>
+</html>
diff --git a/devtools/server/tests/chrome/test_inspector-inactive-property-helper.html b/devtools/server/tests/chrome/test_inspector-inactive-property-helper.html
new file mode 100644
index 0000000000..168a752e9c
--- /dev/null
+++ b/devtools/server/tests/chrome/test_inspector-inactive-property-helper.html
@@ -0,0 +1,104 @@
+<!DOCTYPE HTML>
+<html>
+ <head>
+ <meta charset="utf-8">
+ <title>Test for InactivePropertyHelper</title>
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
+ <script type="application/javascript">
+"use strict";
+SimpleTest.waitForExplicitFinish();
+
+(async function() {
+ const { require } = ChromeUtils.import("resource://devtools/shared/Loader.jsm");
+ const Services = require("Services");
+ const { inactivePropertyHelper } = require("devtools/server/actors/utils/inactive-property-helper");
+ let { isPropertyUsed } = inactivePropertyHelper;
+ isPropertyUsed = isPropertyUsed.bind(inactivePropertyHelper);
+
+ const INACTIVE_CSS_PREF = "devtools.inspector.inactive.css.enabled";
+ Services.prefs.setBoolPref(INACTIVE_CSS_PREF, true);
+ SimpleTest.registerCleanupFunction(() => {
+ Services.prefs.clearUserPref(INACTIVE_CSS_PREF);
+ });
+
+ const FOLDER = "./inactive-property-helper";
+
+ // Each file should `export default` an array of objects, representing each test case.
+ // A single test case is an object of the following shape:
+ // - {String} info: a summary of the test case
+ // - {String} property: the CSS property that should be tested
+ // - {String|undefined} tagName: the tagName of the element we're going to test.
+ // Optional only if there's a createTestElement property.
+ // - {Function|undefined} createTestElement: A function that takes a node as a parameter
+ // where elements used for the test case will
+ // be appended. The function should return the
+ // element that will be passed to
+ // isPropertyUsed.
+ // Optional only if there's a tagName property
+ // - {Array<String>} rules: An array of the rules that will be applied on the element.
+ // This can't be empty as isPropertyUsed need a rule.
+ // - {Integer|undefined} ruleIndex: If there are multiples rules in `rules`, the index
+ // of the one that should be tested in isPropertyUsed.
+ // - {Boolean} isActive: should the property be active (isPropertyUsed `used` result).
+ const testFiles = [
+ "align-content.js",
+ "flex-grid-item-properties.js",
+ "float.js",
+ "gap.js",
+ "grid-with-absolute-properties.js",
+ "margin-padding.js",
+ "max-min-width-height.js",
+ "place-items-content.js",
+ "positioned.js",
+ "vertical-align.js",
+ "text-overflow.js",
+ "outline-radius.js",
+ ].map(file => `${FOLDER}/${file}`);
+
+ // Import all the test cases
+ const tests =
+ // eslint-disable-next-line no-unsanitized/method
+ (await Promise.all(testFiles.map(f => import(f).then(data => data.default)))).flat();
+
+ for (const {
+ info: summary,
+ property,
+ tagName,
+ createTestElement,
+ rules,
+ ruleIndex,
+ isActive
+ } of tests) {
+ // Create an element which will contain the test elements.
+ const main = document.createElement("main");
+ document.firstElementChild.appendChild(main);
+
+ // Apply the CSS rules to the document.
+ const style = document.createElement("style");
+ main.append(style);
+ for (const dataRule of rules) {
+ style.sheet.insertRule(dataRule);
+ }
+ const rule = style.sheet.cssRules[ruleIndex || 0];
+
+ // Create the test elements
+ let el;
+ if (createTestElement) {
+ el = createTestElement(main);
+ } else {
+ el = document.createElement(tagName);
+ main.append(el);
+ }
+
+ const { used } = isPropertyUsed(el, getComputedStyle(el), rule, property);
+ ok(used === isActive, summary);
+
+ main.remove();
+ }
+ SimpleTest.finish();
+})();
+ </script>
+ </head>
+ <body></body>
+</html>
diff --git a/devtools/server/tests/chrome/test_inspector-mutations-attr.html b/devtools/server/tests/chrome/test_inspector-mutations-attr.html
new file mode 100644
index 0000000000..133d13a116
--- /dev/null
+++ b/devtools/server/tests/chrome/test_inspector-mutations-attr.html
@@ -0,0 +1,168 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug </title>
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
+ <script type="application/javascript" src="inspector-helpers.js"></script>
+ <script type="application/javascript">
+"use strict";
+
+window.onload = function() {
+ SimpleTest.waitForExplicitFinish();
+ runNextTest();
+};
+
+let gInspectee = null;
+let gWalker = null;
+let attrNode;
+let attrFront;
+
+addTest(async function setup() {
+ const url = document.getElementById("inspectorContent").href;
+ const { target, doc } = await attachURL(url);
+ const inspector = await target.getFront("inspector");
+ gInspectee = doc;
+ gWalker = inspector.walker;
+ runNextTest();
+});
+
+addTest(setupAttrTest);
+addTest(testAddAttribute);
+addTest(testChangeAttribute);
+addTest(testRemoveAttribute);
+addTest(testQueuedMutations);
+addTest(setupFrameAttrTest);
+addTest(testAddAttribute);
+addTest(testChangeAttribute);
+addTest(testRemoveAttribute);
+addTest(testQueuedMutations);
+
+function setupAttrTest() {
+ attrNode = gInspectee.querySelector("#a");
+ promiseDone(gWalker.querySelector(gWalker.rootNode, "#a").then(node => {
+ attrFront = node;
+ }).then(runNextTest));
+}
+
+function setupFrameAttrTest() {
+ const frame = gInspectee.querySelector("#childFrame");
+ attrNode = frame.contentDocument.querySelector("#a");
+
+ promiseDone(gWalker.querySelector(gWalker.rootNode, "#childFrame").then(childFrame => {
+ return gWalker.children(childFrame);
+ }).then(children => {
+ const nodes = children.nodes;
+ is(nodes.length, 1, "There should be only one child of the iframe");
+ is(nodes[0].nodeType, Node.DOCUMENT_NODE, "iframe child should be a document node");
+ return gWalker.querySelector(nodes[0], "#a");
+ }).then(node => {
+ attrFront = node;
+ }).then(runNextTest));
+}
+
+function testAddAttribute() {
+ attrNode.setAttribute("data-newattr", "newvalue");
+ attrNode.setAttribute("data-newattr2", "newvalue");
+ gWalker.once("mutations", () => {
+ is(attrFront.attributes.length, 3, "Should have id and two new attributes.");
+ is(attrFront.getAttribute("data-newattr"), "newvalue",
+ "Node front should have the first new attribute");
+ is(attrFront.getAttribute("data-newattr2"), "newvalue",
+ "Node front should have the second new attribute.");
+ runNextTest();
+ });
+}
+
+function testChangeAttribute() {
+ attrNode.setAttribute("data-newattr", "changedvalue1");
+ attrNode.setAttribute("data-newattr", "changedvalue2");
+ attrNode.setAttribute("data-newattr", "changedvalue3");
+ gWalker.once("mutations", mutations => {
+ is(mutations.length, 1,
+ "Only one mutation is sent for multiple queued attribute changes");
+ is(attrFront.attributes.length, 3, "Should have id and two new attributes.");
+ is(attrFront.getAttribute("data-newattr"), "changedvalue3",
+ "Node front should have the changed first value");
+ is(attrFront.getAttribute("data-newattr2"), "newvalue",
+ "Second value should remain unchanged.");
+ runNextTest();
+ });
+}
+
+function testRemoveAttribute() {
+ attrNode.removeAttribute("data-newattr2");
+ gWalker.once("mutations", () => {
+ is(attrFront.attributes.length, 2, "Should have id and one remaining attribute.");
+ is(attrFront.getAttribute("data-newattr"), "changedvalue3",
+ "Node front should still have the first value");
+ ok(!attrFront.hasAttribute("data-newattr2"), "Second value should be removed.");
+ runNextTest();
+ });
+}
+
+function testQueuedMutations() {
+ // All modifications to each attribute should be queued in one mutation event.
+
+ attrNode.removeAttribute("data-newattr");
+ attrNode.setAttribute("data-newattr", "1");
+ attrNode.removeAttribute("data-newattr");
+ attrNode.setAttribute("data-newattr", "2");
+ attrNode.removeAttribute("data-newattr");
+
+ for (let i = 0; i <= 1000; i++) {
+ attrNode.setAttribute("data-newattr2", i);
+ }
+
+ attrNode.removeAttribute("data-newattr3");
+ attrNode.setAttribute("data-newattr3", "1");
+ attrNode.removeAttribute("data-newattr3");
+ attrNode.setAttribute("data-newattr3", "2");
+ attrNode.removeAttribute("data-newattr3");
+ attrNode.setAttribute("data-newattr3", "3");
+
+ // This shouldn't be added in the attribute set, since it's a new
+ // attribute that's been added and removed.
+ attrNode.setAttribute("data-newattr4", "4");
+ attrNode.removeAttribute("data-newattr4");
+
+ gWalker.once("mutations", mutations => {
+ is(mutations.length, 4,
+ "Only one mutation each is sent for multiple queued attribute changes");
+ is(attrFront.attributes.length, 3,
+ "Should have id, data-newattr2, and data-newattr3.");
+
+ is(attrFront.getAttribute("data-newattr2"), "1000",
+ "Node front should still have the correct value");
+ is(attrFront.getAttribute("data-newattr3"), "3",
+ "Node front should still have the correct value");
+ ok(!attrFront.hasAttribute("data-newattr"), "Attribute value should be removed.");
+ ok(!attrFront.hasAttribute("data-newattr4"), "Attribute value should be removed.");
+
+ runNextTest();
+ });
+}
+
+addTest(function cleanup() {
+ gInspectee = null;
+ gWalker = null;
+ runNextTest();
+});
+ </script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=">Mozilla Bug </a>
+<a id="inspectorContent" target="_blank" href="inspector-traversal-data.html">Test Document</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+</body>
+</html>
diff --git a/devtools/server/tests/chrome/test_inspector-mutations-events.html b/devtools/server/tests/chrome/test_inspector-mutations-events.html
new file mode 100644
index 0000000000..b48952c4d9
--- /dev/null
+++ b/devtools/server/tests/chrome/test_inspector-mutations-events.html
@@ -0,0 +1,187 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1157469
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1157469</title>
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
+ <script type="application/javascript" src="inspector-helpers.js"></script>
+ <script type="application/javascript">
+"use strict";
+
+window.onload = function() {
+ SimpleTest.waitForExplicitFinish();
+
+ const prevPrefValue = Services.prefs.getBoolPref("devtools.chrome.enabled");
+ Services.prefs.setBoolPref("devtools.chrome.enabled", true);
+
+ let inspectee = null;
+ let inspector = null;
+ let walker = null;
+ const eventListener1 = function() {};
+ const eventListener2 = function() {};
+ let eventNode1;
+ let eventNode2;
+ let eventFront1;
+ let eventFront2;
+
+ addAsyncTest(async function setup() {
+ info("Setting up inspector and walker actors.");
+ const url = document.getElementById("inspectorContent").href;
+ const { target, doc } = await attachURL(url);
+ inspectee = doc;
+ inspector = await target.getFront("inspector");
+ walker = inspector.walker;
+
+ runNextTest();
+ });
+
+ addAsyncTest(async function setupEventTest() {
+ eventNode1 = inspectee.querySelector("#a");
+ eventNode2 = inspectee.querySelector("#b");
+
+ eventFront1 = await walker.querySelector(walker.rootNode, "#a");
+ eventFront2 = await walker.querySelector(walker.rootNode, "#b");
+
+ runNextTest();
+ });
+
+ addAsyncTest(async function testChangeEventListenerOnSingleNode() {
+ checkNodesHaveNoEventListener();
+
+ info("add event listener on a single node");
+ eventNode1.addEventListener("click", eventListener1);
+
+ let mutations = await waitForMutations();
+ is(mutations.length, 1, "one mutation expected");
+ is(mutations[0].target, eventFront1, "mutation targets eventFront1");
+ is(mutations[0].type, "events", "mutation type is events");
+ is(mutations[0].hasEventListeners, true,
+ "mutation target should have event listeners");
+ is(eventFront1.hasEventListeners, true, "eventFront1 should have event listeners");
+
+ info("remove event listener on a single node");
+ eventNode1.removeEventListener("click", eventListener1);
+
+ mutations = await waitForMutations();
+ is(mutations.length, 1, "one mutation expected");
+ is(mutations[0].target, eventFront1, "mutation targets eventFront1");
+ is(mutations[0].type, "events", "mutation type is events");
+ is(mutations[0].hasEventListeners, false,
+ "mutation target should have no event listeners");
+ is(eventFront1.hasEventListeners, false,
+ "eventFront1 should have no event listeners");
+
+ info("perform several event listener changes on a single node");
+ eventNode1.addEventListener("click", eventListener1);
+ eventNode1.addEventListener("click", eventListener2);
+ eventNode1.removeEventListener("click", eventListener1);
+ eventNode1.removeEventListener("click", eventListener2);
+
+ mutations = await waitForMutations();
+ is(mutations.length, 1, "one mutation expected");
+ is(mutations[0].target, eventFront1, "mutation targets eventFront1");
+ is(mutations[0].type, "events", "mutation type is events");
+ is(mutations[0].hasEventListeners, false,
+ "no event listener expected on mutation target");
+ is(eventFront1.hasEventListeners, false, "no event listener expected on node");
+
+ runNextTest();
+ });
+
+ addAsyncTest(async function testChangeEventsOnSeveralNodes() {
+ checkNodesHaveNoEventListener();
+
+ info("add event listeners on both nodes");
+ eventNode1.addEventListener("click", eventListener1);
+ eventNode2.addEventListener("click", eventListener2);
+
+ let mutations = await waitForMutations();
+ is(mutations.length, 2, "two mutations expected, one for each modified node");
+ // first mutation
+ is(mutations[0].target, eventFront1, "first mutation targets eventFront1");
+ is(mutations[0].type, "events", "mutation type is events");
+ is(mutations[0].hasEventListeners, true,
+ "mutation target should have event listeners");
+ is(eventFront1.hasEventListeners, true, "eventFront1 should have event listeners");
+ // second mutation
+ is(mutations[1].target, eventFront2, "second mutation targets eventFront2");
+ is(mutations[1].type, "events", "mutation type is events");
+ is(mutations[1].hasEventListeners, true,
+ "mutation target should have event listeners");
+ is(eventFront2.hasEventListeners, true, "eventFront1 should have event listeners");
+
+ info("remove event listeners on both nodes");
+ eventNode1.removeEventListener("click", eventListener1);
+ eventNode2.removeEventListener("click", eventListener2);
+
+ mutations = await waitForMutations();
+ is(mutations.length, 2, "one mutation registered for event listener change");
+ // first mutation
+ is(mutations[0].target, eventFront1, "first mutation targets eventFront1");
+ is(mutations[0].type, "events", "mutation type is events");
+ is(mutations[0].hasEventListeners, false,
+ "mutation target should have no event listeners");
+ is(eventFront1.hasEventListeners, false,
+ "eventFront2 should have no event listeners");
+ // second mutation
+ is(mutations[1].target, eventFront2, "second mutation targets eventFront2");
+ is(mutations[1].type, "events", "mutation type is events");
+ is(mutations[1].hasEventListeners, false,
+ "mutation target should have no event listeners");
+ is(eventFront2.hasEventListeners, false,
+ "eventFront2 should have no event listeners");
+
+ runNextTest();
+ });
+
+ addAsyncTest(async function testRemoveMissingEvent() {
+ checkNodesHaveNoEventListener();
+
+ info("try to remove an event listener not previously added");
+ eventNode1.removeEventListener("click", eventListener1);
+
+ info("set any attribute on the node to trigger a mutation");
+ eventNode1.setAttribute("data-attr", "somevalue");
+
+ const mutations = await waitForMutations();
+ is(mutations.length, 1, "expect only one mutation");
+ isnot(mutations.type, "events", "mutation type should not be events");
+
+ Services.prefs.setBoolPref("devtools.chrome.enabled", prevPrefValue);
+ runNextTest();
+ });
+
+ function checkNodesHaveNoEventListener() {
+ is(eventFront1.hasEventListeners, false,
+ "eventFront1 hasEventListeners should be false");
+ is(eventFront2.hasEventListeners, false,
+ "eventFront2 hasEventListeners should be false");
+ }
+
+ function waitForMutations() {
+ return new Promise(resolve => {
+ walker.once("mutations", mutations => {
+ resolve(mutations);
+ });
+ });
+ }
+
+ runNextTest();
+};
+ </script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1157469">Mozilla Bug 1157469</a>
+<a id="inspectorContent" target="_blank" href="inspector-traversal-data.html">Test Document</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+</body>
+</html>
diff --git a/devtools/server/tests/chrome/test_inspector-mutations-value.html b/devtools/server/tests/chrome/test_inspector-mutations-value.html
new file mode 100644
index 0000000000..1f0b1dfc23
--- /dev/null
+++ b/devtools/server/tests/chrome/test_inspector-mutations-value.html
@@ -0,0 +1,162 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug </title>
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
+ <script type="application/javascript" src="inspector-helpers.js"></script>
+ <script type="application/javascript">
+"use strict";
+
+const WalkerActor = require("devtools/server/actors/inspector/walker");
+
+window.onload = function() {
+ SimpleTest.waitForExplicitFinish();
+ runNextTest();
+};
+
+const testSummaryLength = 10;
+WalkerActor.setValueSummaryLength(testSummaryLength);
+SimpleTest.registerCleanupFunction(function() {
+ WalkerActor.setValueSummaryLength(WalkerActor.DEFAULT_VALUE_SUMMARY_LENGTH);
+});
+
+let gInspectee = null;
+let gWalker = null;
+let valueNode;
+var valueFront;
+var longStringFront;
+var longString = "stringstringstringstringstringstringstringstringstringstringstring";
+var shortString = "str";
+var shortString2 = "str2";
+
+addTest(async function setup() {
+ const url = document.getElementById("inspectorContent").href;
+ const { target, doc } = await attachURL(url);
+ const inspector = await target.getFront("inspector");
+ gInspectee = doc;
+ gWalker = inspector.walker;
+ runNextTest();
+});
+
+addTest(setupValueTest);
+addTest(testKeepLongValue);
+addTest(testSetShortValue);
+addTest(testKeepShortValue);
+addTest(testSetLongValue);
+addTest(setupFrameValueTest);
+addTest(testKeepLongValue);
+addTest(testSetShortValue);
+addTest(testKeepShortValue);
+addTest(testSetLongValue);
+
+function setupValueTest() {
+ valueNode = gInspectee.querySelector("#longstring").firstChild;
+ promiseDone(gWalker.querySelector(gWalker.rootNode, "#longstring").then(node => {
+ longStringFront = node;
+ return gWalker.children(node);
+ }).then(children => {
+ valueFront = children.nodes[0];
+ }).then(runNextTest));
+}
+
+function setupFrameValueTest() {
+ const frame = gInspectee.querySelector("#childFrame");
+ valueNode = frame.contentDocument.querySelector("#longstring").firstChild;
+
+ promiseDone(gWalker.querySelector(gWalker.rootNode, "#childFrame").then(childFrame => {
+ return gWalker.children(childFrame);
+ }).then(children => {
+ const nodes = children.nodes;
+ is(nodes.length, 1, "There should be only one child of the iframe");
+ is(nodes[0].nodeType, Node.DOCUMENT_NODE, "iframe child should be a document node");
+ return gWalker.querySelector(nodes[0], "#longstring");
+ }).then(node => {
+ longStringFront = node;
+ return gWalker.children(node);
+ }).then(children => {
+ valueFront = children.nodes[0];
+ }).then(runNextTest));
+}
+
+function checkNodeFrontValue(front, expectedValue) {
+ return front.getNodeValue().then(longstring => {
+ return longstring.string();
+ }).then(str => {
+ is(str, expectedValue, "Node value is as expected");
+ });
+}
+
+function testKeepLongValue() {
+ // After first setup we should have a long string in the node
+ ok(!longStringFront.inlineTextChild, "Text node is too long to be inlined.");
+
+ valueNode.nodeValue = longString;
+ gWalker.once("mutations", (changes) => {
+ ok(!longStringFront.inlineTextChild, "Text node is too long to be inlined.");
+ ok(!changes.some(change => change.type === "inlineTextChild"),
+ "No inline text child mutation was fired.");
+ checkNodeFrontValue(valueFront, longString).then(runNextTest);
+ });
+}
+
+function testSetShortValue() {
+ ok(!longStringFront.inlineTextChild, "Text node is too long to be inlined.");
+
+ valueNode.nodeValue = shortString;
+ gWalker.once("mutations", (changes) => {
+ ok(!!longStringFront.inlineTextChild, "Text node is short enough to be inlined.");
+ ok(changes.some(change => change.type === "inlineTextChild"),
+ "An inlineTextChild mutation was fired.");
+ checkNodeFrontValue(valueFront, shortString).then(runNextTest);
+ });
+}
+
+function testKeepShortValue() {
+ ok(!!longStringFront.inlineTextChild, "Text node is short enough to be inlined.");
+
+ valueNode.nodeValue = shortString2;
+ gWalker.once("mutations", (changes) => {
+ ok(!!longStringFront.inlineTextChild, "Text node is short enough to be inlined.");
+ ok(!changes.some(change => change.type === "inlineTextChild"),
+ "No inline text child mutation was fired.");
+ checkNodeFrontValue(valueFront, shortString2).then(runNextTest);
+ });
+}
+
+function testSetLongValue() {
+ ok(!!longStringFront.inlineTextChild, "Text node is short enough to be inlined.");
+
+ valueNode.nodeValue = longString;
+ gWalker.once("mutations", (changes) => {
+ ok(!longStringFront.inlineTextChild, "Text node is too long to be inlined.");
+ ok(changes.some(change => change.type === "inlineTextChild"),
+ "An inlineTextChild mutation was fired.");
+ checkNodeFrontValue(valueFront, longString).then(runNextTest);
+ });
+}
+
+addTest(function cleanup() {
+ gInspectee = null;
+ gWalker = null;
+ runNextTest();
+});
+
+ </script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=">Mozilla Bug </a>
+<a id="inspectorContent" target="_blank" href="inspector-traversal-data.html">Test Document</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+</body>
+</html>
diff --git a/devtools/server/tests/chrome/test_inspector-pick-color.html b/devtools/server/tests/chrome/test_inspector-pick-color.html
new file mode 100644
index 0000000000..df6d9d8228
--- /dev/null
+++ b/devtools/server/tests/chrome/test_inspector-pick-color.html
@@ -0,0 +1,91 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+Test that the inspector actor has the pickColorFromPage and cancelPickColorFromPage
+methods and that when a color is picked the color-picked event is emitted and that when
+the eyedropper is dimissed, the color-pick-canceled event is emitted.
+https://bugzilla.mozilla.org/show_bug.cgi?id=1262439
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1262439</title>
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
+ <script type="application/javascript" src="inspector-helpers.js"></script>
+ <script type="application/javascript">
+"use strict";
+
+window.onload = function() {
+ SimpleTest.waitForExplicitFinish();
+
+ let win = null;
+ let inspector = null;
+
+ addAsyncTest(async function() {
+ info("Setting up inspector actor");
+
+ const url = document.getElementById("inspectorContent").href;
+ const { target, doc } = await attachURL(url);
+ inspector = await target.getFront("inspector");
+ win = doc.defaultView;
+ runNextTest();
+ });
+
+ addAsyncTest(async function() {
+ info("Start picking a color from the page");
+ await inspector.pickColorFromPage();
+
+ info("Click in the page and make sure a color-picked event is received");
+ const onColorPicked = waitForEvent("color-picked");
+ win.document.body.click();
+ const color = await onColorPicked;
+
+ is(color, "#000000", "The color-picked event was received with the right color");
+
+ runNextTest();
+ });
+
+ addAsyncTest(async function() {
+ info("Start picking a color from the page");
+ await inspector.pickColorFromPage();
+
+ info("Use the escape key to dismiss the eyedropper");
+ const onPickCanceled = waitForEvent("color-pick-canceled");
+
+ const keyboardEvent = win.document.createEvent("KeyboardEvent");
+ keyboardEvent.initKeyEvent("keydown", true, true, win, false, false,
+ false, false, 27, 0);
+ win.document.dispatchEvent(keyboardEvent);
+
+ await onPickCanceled;
+ ok(true, "The color-pick-canceled event was received");
+
+ runNextTest();
+ });
+
+ addAsyncTest(async function() {
+ info("Start picking a color from the page");
+ await inspector.pickColorFromPage();
+
+ info("And cancel the color picking");
+ await inspector.cancelPickColorFromPage();
+
+ runNextTest();
+ });
+
+ function waitForEvent(name) {
+ return new Promise(resolve => inspector.once(name, resolve));
+ }
+
+ runNextTest();
+};
+ </script>
+</head>
+<body>
+<a id="inspectorContent" target="_blank" href="inspector-eyedropper.html">Test Document</a>
+<p id="display"></p>
+<div id="content" style="display: none"></div>
+<pre id="test">
+</pre>
+</body>
+</html>
diff --git a/devtools/server/tests/chrome/test_inspector-pseudoclass-lock.html b/devtools/server/tests/chrome/test_inspector-pseudoclass-lock.html
new file mode 100644
index 0000000000..1be13cd4e5
--- /dev/null
+++ b/devtools/server/tests/chrome/test_inspector-pseudoclass-lock.html
@@ -0,0 +1,187 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug </title>
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
+ <script type="application/javascript" src="inspector-helpers.js"></script>
+ <script type="application/javascript">
+"use strict";
+
+const { PSEUDO_CLASSES } = require("devtools/shared/css/constants");
+
+window.onload = function() {
+ SimpleTest.waitForExplicitFinish();
+ runNextTest();
+};
+
+const InspectorUtils = require("InspectorUtils");
+
+let gInspectee = null;
+let gWalker = null;
+
+async function setup(callback) {
+ const url = document.getElementById("inspectorContent").href;
+ const { target, doc } = await attachURL(url);
+ gInspectee = doc;
+ const inspector = await target.getFront("inspector");
+ ok(inspector.walker, "getWalker() should return an actor.");
+ gWalker = inspector.walker;
+ runNextTest();
+}
+
+function teardown() {
+ gWalker = null;
+ gInspectee = null;
+}
+
+function checkChange(change, expectation) {
+ is(change.type, "pseudoClassLock", "Expect a pseudoclass lock change.");
+ const target = change.target;
+ if (expectation.id) {
+ is(target.id, expectation.id, "Expect a change on node id " + expectation.id);
+ }
+ if (expectation.nodeName) {
+ is(target.nodeName, expectation.nodeName,
+ "Expect a change on node name " + expectation.nodeName);
+ }
+
+ is(target.pseudoClassLocks.length, expectation.pseudos.length,
+ "Expect " + expectation.pseudos.length + " pseudoclass locks.");
+ for (let i = 0; i < expectation.pseudos.length; i++) {
+ const pseudo = expectation.pseudos[i];
+ const enabled = expectation.enabled === undefined ? true : expectation.enabled[i];
+ ok(target.hasPseudoClassLock(pseudo), "Expect lock: " + pseudo);
+ const rawNode = target.rawNode();
+ ok(InspectorUtils.hasPseudoClassLock(rawNode, pseudo),
+ "Expect lock in dom: " + pseudo);
+
+ is(rawNode.matches(pseudo), enabled,
+ `Target should match pseudoclass, '${pseudo}', if enabled (with .matches())`);
+ }
+
+ for (const pseudo of PSEUDO_CLASSES) {
+ if (!expectation.pseudos.some(expected => pseudo === expected)) {
+ ok(!target.hasPseudoClassLock(pseudo), "Don't expect lock: " + pseudo);
+ ok(!InspectorUtils.hasPseudoClassLock(target.rawNode(), pseudo),
+ "Don't expect lock in dom: " + pseudo);
+ }
+ }
+}
+
+function checkMutations(mutations, expectations) {
+ is(mutations.length, expectations.length, "Should get the right number of mutations.");
+ for (let i = 0; i < mutations.length; i++) {
+ checkChange(mutations[i], expectations[i]);
+ }
+}
+
+addTest(function testPseudoClassLock() {
+ let contentNode;
+ let nodeFront;
+ setup(() => {
+ contentNode = gInspectee.querySelector("#b");
+ return promiseDone(gWalker.querySelector(gWalker.rootNode, "#b").then(front => {
+ nodeFront = front;
+ // Lock the pseudoclass alone, no parents.
+ gWalker.addPseudoClassLock(nodeFront, ":active");
+ // Expect a single pseudoClassLock mutation.
+ return promiseOnce(gWalker, "mutations");
+ }).then(mutations => {
+ is(mutations.length, 1, "Should get one mutation");
+ is(mutations[0].target, nodeFront, "Should be the node we tried to apply to");
+ checkChange(mutations[0], {
+ id: "b",
+ nodeName: "DIV",
+ pseudos: [":active"],
+ });
+ }).then(() => {
+ // Now add :hover, this time with parents.
+ gWalker.addPseudoClassLock(nodeFront, ":hover", {parents: true});
+ return promiseOnce(gWalker, "mutations");
+ }).then(mutations => {
+ const expectedMutations = [{
+ id: "b",
+ nodeName: "DIV",
+ pseudos: [":hover", ":active"],
+ },
+ {
+ id: "longlist",
+ nodeName: "DIV",
+ pseudos: [":hover"],
+ },
+ {
+ nodeName: "BODY",
+ pseudos: [":hover"],
+ },
+ {
+ nodeName: "HTML",
+ pseudos: [":hover"],
+ }];
+ checkMutations(mutations, expectedMutations);
+ }).then(() => {
+ // Now remove the :hover on all parents
+ gWalker.removePseudoClassLock(nodeFront, ":hover", {parents: true});
+ return promiseOnce(gWalker, "mutations");
+ }).then(mutations => {
+ const expectedMutations = [{
+ id: "b",
+ nodeName: "DIV",
+ // Should still have :active on the original node.
+ pseudos: [":active"],
+ },
+ {
+ id: "longlist",
+ nodeName: "DIV",
+ pseudos: [],
+ },
+ {
+ nodeName: "BODY",
+ pseudos: [],
+ },
+ {
+ nodeName: "HTML",
+ pseudos: [],
+ }];
+ checkMutations(mutations, expectedMutations);
+ }).then(() => {
+ gWalker.addPseudoClassLock(nodeFront, ":hover", {enabled: false});
+ return promiseOnce(gWalker, "mutations");
+ }).then(mutations => {
+ is(mutations.length, 1, "Should get one mutation");
+ is(mutations[0].target, nodeFront, "Should be the node we tried to apply to");
+ checkChange(mutations[0], {
+ id: "b",
+ nodeName: "DIV",
+ pseudos: [":hover", ":active"],
+ enabled: [false, true],
+ });
+ }).then(() => {
+ // Now shut down the walker and make sure that clears up the remaining lock.
+ return gWalker.release();
+ }).then(() => {
+ ok(!InspectorUtils.hasPseudoClassLock(contentNode, ":active"),
+ "Pseudoclass should have been removed during destruction.");
+ teardown();
+ }).then(runNextTest));
+ });
+});
+
+ </script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=">Mozilla Bug </a>
+<a id="inspectorContent" target="_blank" href="inspector-traversal-data.html">Test Document</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+</body>
+</html>
diff --git a/devtools/server/tests/chrome/test_inspector-reload.html b/devtools/server/tests/chrome/test_inspector-reload.html
new file mode 100644
index 0000000000..b7f490bd94
--- /dev/null
+++ b/devtools/server/tests/chrome/test_inspector-reload.html
@@ -0,0 +1,86 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug </title>
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
+ <script type="application/javascript" src="inspector-helpers.js"></script>
+ <script type="application/javascript">
+"use strict";
+
+window.onload = function() {
+ SimpleTest.waitForExplicitFinish();
+ runNextTest();
+};
+
+let gInspectee = null;
+let gWalker = null;
+let gResourceWatcher = null;
+
+addTest(async function setup() {
+ const url = document.getElementById("inspectorContent").href;
+ const { target, doc } = await attachURL(url);
+ const inspector = await target.getFront("inspector");
+ gInspectee = doc;
+ const walker = inspector.walker;
+ gWalker = await inspector.getWalker();
+ gResourceWatcher = createResourceWatcher(target);
+
+ ok(walker === gWalker, "getWalker() twice should return the same walker.");
+ runNextTest();
+});
+
+addTest(async function testReload() {
+ const oldRootID = gWalker.rootNode.actorID;
+
+ info("Start watching for root nodes and wait for the initial root node");
+ let rootNodeResolve;
+ let rootNodePromise = new Promise(r => (rootNodeResolve = r));
+ const onAvailable = rootNodeFront => rootNodeResolve(rootNodeFront);
+ await gResourceWatcher.watchResources([gResourceWatcher.TYPES.ROOT_NODE], {
+ onAvailable,
+ });
+ await rootNodePromise;
+
+ info("Retrieve the node front for the selector `#a`");
+ const nodeFront = await gWalker.querySelector(gWalker.rootNode, "#a");
+ ok(nodeFront.actorID, "Node front has a valid actor ID");
+
+ info("Reload the page and wait for the newRoot mutation");
+ rootNodePromise = new Promise(r => (rootNodeResolve = r));
+
+ gInspectee.defaultView.location.reload();
+ await rootNodePromise;
+
+ info("Retrieve the (new) node front for the selector `#a`");
+ const newNodeFront = await gWalker.querySelector(gWalker.rootNode, "#a");
+ ok(newNodeFront.actorID, "Got a new actor ID");
+ ok(gWalker.rootNode.actorID != oldRootID, "Root node should have changed.");
+
+ runNextTest();
+});
+
+addTest(function cleanup() {
+ gWalker = null;
+ gInspectee = null;
+ gResourceWatcher = null;
+ runNextTest();
+});
+ </script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=">Mozilla Bug </a>
+<a id="inspectorContent" target="_blank" href="inspector-traversal-data.html">Test Document</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+</body>
+</html>
diff --git a/devtools/server/tests/chrome/test_inspector-resize.html b/devtools/server/tests/chrome/test_inspector-resize.html
new file mode 100644
index 0000000000..e0cf9abade
--- /dev/null
+++ b/devtools/server/tests/chrome/test_inspector-resize.html
@@ -0,0 +1,69 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+Test that the inspector actor emits "resize" events when the page is resized.
+https://bugzilla.mozilla.org/show_bug.cgi?id=1222409
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1222409</title>
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
+ <script type="application/javascript" src="inspector-helpers.js"></script>
+ <script type="application/javascript">
+"use strict";
+
+window.onload = function() {
+ SimpleTest.waitForExplicitFinish();
+
+ let win = null;
+ let inspector = null;
+
+ addAsyncTest(async function setup() {
+ info("Setting up inspector and walker actors.");
+
+ const url = document.getElementById("inspectorContent").href;
+
+ const { target, doc } = await attachURL(url);
+ inspector = await target.getFront("inspector");
+ win = doc.defaultView;
+ runNextTest();
+ });
+
+ addAsyncTest(async function() {
+ const walker = inspector.walker;
+
+ // We can't receive events from the walker if we haven't first executed a
+ // method on the actor to initialize it.
+ await walker.querySelector(walker.rootNode, "img");
+
+ const {outerWidth, outerHeight} = win;
+ // eslint-disable-next-line new-cap
+ const onResize = new Promise(resolve => {
+ walker.once("resize", () => {
+ resolve();
+ });
+ });
+ win.resizeTo(800, 600);
+ await onResize;
+
+ ok(true, "The resize event was emitted");
+ win.resizeTo(outerWidth, outerHeight);
+
+ runNextTest();
+ });
+
+ runNextTest();
+};
+ </script>
+</head>
+<body>
+<a id="inspectorContent" target="_blank" href="inspector-search-data.html">Test Document</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+</body>
+</html>
diff --git a/devtools/server/tests/chrome/test_inspector-resolve-url.html b/devtools/server/tests/chrome/test_inspector-resolve-url.html
new file mode 100644
index 0000000000..ddf68f56ed
--- /dev/null
+++ b/devtools/server/tests/chrome/test_inspector-resolve-url.html
@@ -0,0 +1,87 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=921102
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 921102</title>
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
+ <script type="application/javascript" src="inspector-helpers.js"></script>
+ <script type="application/javascript">
+"use strict";
+
+window.onload = function() {
+ SimpleTest.waitForExplicitFinish();
+ runNextTest();
+};
+
+let gInspector;
+let gDoc;
+
+addTest(async function() {
+ const url = document.getElementById("inspectorContent").href;
+ const { target, doc } = await attachURL(url);
+ gInspector = await target.getFront("inspector");
+ gDoc = doc;
+ runNextTest();
+});
+
+addTest(function() {
+ info("Resolve a relative URL without providing a context node");
+ gInspector.resolveRelativeURL("test.png?id=4#wow").then(url => {
+ is(url, "chrome://mochitests/content/chrome/devtools/server/tests/" +
+ "chrome/test.png?id=4#wow");
+ runNextTest();
+ });
+});
+
+addTest(function() {
+ info("Resolve an absolute URL without providing a context node");
+ gInspector.resolveRelativeURL("chrome://mochitests/content/chrome/" +
+ "devtools/server/").then(url => {
+ is(url, "chrome://mochitests/content/chrome/devtools/server/");
+ runNextTest();
+ });
+});
+
+addTest(function() {
+ info("Resolve a relative URL providing a context node");
+ const node = gDoc.querySelector(".big-horizontal");
+ gInspector.resolveRelativeURL("test.png?id=4#wow", node).then(url => {
+ is(url, "chrome://mochitests/content/chrome/devtools/server/tests/" +
+ "chrome/test.png?id=4#wow");
+ runNextTest();
+ });
+});
+
+addTest(function() {
+ info("Resolve an absolute URL providing a context node");
+ const node = gDoc.querySelector(".big-horizontal");
+ gInspector.resolveRelativeURL("chrome://mochitests/content/chrome/" +
+ "devtools/server/", node).then(url => {
+ is(url, "chrome://mochitests/content/chrome/devtools/server/");
+ runNextTest();
+ });
+});
+
+addTest(function() {
+ gInspector = null;
+ gDoc = null;
+ runNextTest();
+});
+ </script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=921102">Mozilla Bug 921102</a>
+<a id="inspectorContent" target="_blank" href="inspector_getImageData.html">Test Document</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+</body>
+</html>
diff --git a/devtools/server/tests/chrome/test_inspector-scroll-into-view.html b/devtools/server/tests/chrome/test_inspector-scroll-into-view.html
new file mode 100644
index 0000000000..0b3eb863ac
--- /dev/null
+++ b/devtools/server/tests/chrome/test_inspector-scroll-into-view.html
@@ -0,0 +1,76 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=901250
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 901250</title>
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
+ <script type="application/javascript" src="inspector-helpers.js"></script>
+ <script type="application/javascript">
+"use strict";
+
+window.onload = function() {
+ SimpleTest.waitForExplicitFinish();
+ runNextTest();
+};
+
+let gInspectee = null;
+let gWalker = null;
+
+addTest(async function setup() {
+ const url = document.getElementById("inspectorContent").href;
+ const { target, doc } = await attachURL(url);
+ gInspectee = doc;
+ const inspector = await target.getFront("inspector");
+ gWalker = inspector.walker;
+ runNextTest();
+});
+
+addTest(async function testScrollIntoView() {
+ const id = "#scroll-into-view";
+ let rect = gInspectee.querySelector(id).getBoundingClientRect();
+ const nodeFront = await gWalker.querySelector(gWalker.rootNode, id);
+ let inViewport = rect.x >= 0 &&
+ rect.y >= 0 &&
+ rect.y <= gInspectee.defaultView.innerHeight &&
+ rect.x <= gInspectee.defaultView.innerWidth;
+
+ ok(!inViewport, "Element is not in viewport.");
+
+ await nodeFront.scrollIntoView();
+
+ SimpleTest.executeSoon(() => {
+ rect = gInspectee.querySelector(id).getBoundingClientRect();
+ inViewport = rect.x >= 0 &&
+ rect.y >= 0 &&
+ rect.y <= gInspectee.defaultView.innerHeight &&
+ rect.x <= gInspectee.defaultView.innerWidth;
+
+ ok(inViewport, "Element is in viewport.");
+
+ runNextTest();
+ });
+});
+
+addTest(function cleanup() {
+ gWalker = null;
+ gInspectee = null;
+ runNextTest();
+});
+ </script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=901250">Mozilla Bug 901250</a>
+<a id="inspectorContent" target="_blank" href="inspector-traversal-data.html">Test Document</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+</body>
+</html>
diff --git a/devtools/server/tests/chrome/test_inspector-search-front.html b/devtools/server/tests/chrome/test_inspector-search-front.html
new file mode 100644
index 0000000000..a8c24135b2
--- /dev/null
+++ b/devtools/server/tests/chrome/test_inspector-search-front.html
@@ -0,0 +1,172 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=835896
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 835896</title>
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
+ <script type="application/javascript" src="inspector-helpers.js"></script>
+ <script type="application/javascript">
+"use strict";
+
+window.onload = function() {
+ SimpleTest.waitForExplicitFinish();
+
+ let walkerFront = null;
+ let inspector = null;
+
+ // WalkerFront specific tests. These aren't to excercise search
+ // edge cases so much as to test the state the Front maintains between
+ // searches.
+ // See also test_inspector-search.html
+
+ addAsyncTest(async function setup() {
+ info("Setting up inspector and walker actors.");
+
+ const url = document.getElementById("inspectorContent").href;
+
+ const { target } = await attachURL(url);
+ inspector = await target.getFront("inspector");
+ walkerFront = inspector.walker;
+
+ runNextTest();
+ });
+
+ addAsyncTest(async function testWalkerFrontDefaults() {
+ info("Testing search API using WalkerFront.");
+ const nodes = await walkerFront.querySelectorAll(walkerFront.rootNode, "h2");
+ const fronts = await nodes.items();
+
+ const frontResult = await walkerFront.search("");
+ ok(!frontResult, "Null result on front when searching for ''");
+
+ let results = await walkerFront.search("h2");
+ isDeeply(results, {
+ node: fronts[0],
+ type: "search",
+ resultsIndex: 0,
+ resultsLength: 3,
+ }, "Default options work");
+
+ results = await walkerFront.search("h2", { });
+ isDeeply(results, {
+ node: fronts[1],
+ type: "search",
+ resultsIndex: 1,
+ resultsLength: 3,
+ }, "Search works with empty options");
+
+ // Clear search data to remove result state on the front
+ await walkerFront.search("");
+ runNextTest();
+ });
+
+ addAsyncTest(async function testMultipleSearches() {
+ info("Testing search API using WalkerFront (reverse=false)");
+ const nodes = await walkerFront.querySelectorAll(walkerFront.rootNode, "h2");
+ const fronts = await nodes.items();
+
+ let results = await walkerFront.search("h2");
+ isDeeply(results, {
+ node: fronts[0],
+ type: "search",
+ resultsIndex: 0,
+ resultsLength: 3,
+ }, "Search works with multiple results (reverse=false)");
+
+ results = await walkerFront.search("h2");
+ isDeeply(results, {
+ node: fronts[1],
+ type: "search",
+ resultsIndex: 1,
+ resultsLength: 3,
+ }, "Search works with multiple results (reverse=false)");
+
+ results = await walkerFront.search("h2");
+ isDeeply(results, {
+ node: fronts[2],
+ type: "search",
+ resultsIndex: 2,
+ resultsLength: 3,
+ }, "Search works with multiple results (reverse=false)");
+
+ results = await walkerFront.search("h2");
+ isDeeply(results, {
+ node: fronts[0],
+ type: "search",
+ resultsIndex: 0,
+ resultsLength: 3,
+ }, "Search works with multiple results (reverse=false)");
+
+ // Clear search data to remove result state on the front
+ await walkerFront.search("");
+ runNextTest();
+ });
+
+ addAsyncTest(async function testMultipleSearchesReverse() {
+ info("Testing search API using WalkerFront (reverse=true)");
+ const nodes = await walkerFront.querySelectorAll(walkerFront.rootNode, "h2");
+ const fronts = await nodes.items();
+
+ let results = await walkerFront.search("h2", {reverse: true});
+ isDeeply(results, {
+ node: fronts[2],
+ type: "search",
+ resultsIndex: 2,
+ resultsLength: 3,
+ }, "Search works with multiple results (reverse=true)");
+
+ results = await walkerFront.search("h2", {reverse: true});
+ isDeeply(results, {
+ node: fronts[1],
+ type: "search",
+ resultsIndex: 1,
+ resultsLength: 3,
+ }, "Search works with multiple results (reverse=true)");
+
+ results = await walkerFront.search("h2", {reverse: true});
+ isDeeply(results, {
+ node: fronts[0],
+ type: "search",
+ resultsIndex: 0,
+ resultsLength: 3,
+ }, "Search works with multiple results (reverse=true)");
+
+ results = await walkerFront.search("h2", {reverse: true});
+ isDeeply(results, {
+ node: fronts[2],
+ type: "search",
+ resultsIndex: 2,
+ resultsLength: 3,
+ }, "Search works with multiple results (reverse=true)");
+
+ results = await walkerFront.search("h2", {reverse: false});
+ isDeeply(results, {
+ node: fronts[0],
+ type: "search",
+ resultsIndex: 0,
+ resultsLength: 3,
+ }, "Search works with multiple results (reverse=false)");
+
+ // Clear search data to remove result state on the front
+ await walkerFront.search("");
+ runNextTest();
+ });
+
+ runNextTest();
+};
+ </script>
+</head>
+<body>
+<a id="inspectorContent" target="_blank" href="inspector-search-data.html">Test Document</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+</body>
+</html>
diff --git a/devtools/server/tests/chrome/test_inspector-template.html b/devtools/server/tests/chrome/test_inspector-template.html
new file mode 100644
index 0000000000..6fbc7742c6
--- /dev/null
+++ b/devtools/server/tests/chrome/test_inspector-template.html
@@ -0,0 +1,66 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1078374
+Display template tag content in inspector.
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug </title>
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
+ <script type="application/javascript" src="inspector-helpers.js"></script>
+ <script type="application/javascript">
+"use strict";
+
+window.onload = function() {
+ SimpleTest.waitForExplicitFinish();
+
+ let gWalker = null;
+
+ addAsyncTest(async function setup() {
+ const url = document.getElementById("inspectorContent").href;
+
+ const { target } = await attachURL(url);
+ const inspector = await target.getFront("inspector");
+ gWalker = inspector.walker;
+
+ runNextTest();
+ });
+
+ addAsyncTest(async function testWalker() {
+ const nodeFront = await gWalker.querySelector(gWalker.rootNode, "template");
+
+ let children = await gWalker.children(nodeFront);
+ is(children.nodes.length, 1, "Found one child under the template element");
+
+ const docFragment = children.nodes[0];
+ is(docFragment.nodeName, "#document-fragment",
+ "First child under <template> is a document-fragment");
+
+ children = await gWalker.children(docFragment);
+ is(children.nodes.length, 1, "Found one child under the template element");
+
+ const p = children.nodes[0];
+ is(p.nodeName, "P",
+ "First child under the document-fragment is a p element");
+
+ runNextTest();
+ });
+
+ runNextTest();
+};
+ </script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=">Mozilla Bug </a>
+<a id="inspectorContent" target="_blank" href="inspector-template.html">Test Document</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+</body>
+</html>
diff --git a/devtools/server/tests/chrome/test_inspector_getImageData-wait-for-load.html b/devtools/server/tests/chrome/test_inspector_getImageData-wait-for-load.html
new file mode 100644
index 0000000000..cd3bffae65
--- /dev/null
+++ b/devtools/server/tests/chrome/test_inspector_getImageData-wait-for-load.html
@@ -0,0 +1,133 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+Tests for InspectorActor.getImageData() in following cases:
+ * Image takes too long to load (the method rejects after a timeout).
+ * Image is loading when the method is called and the load finishes before
+ timeout.
+ * Image fails to load.
+
+https://bugzilla.mozilla.org/show_bug.cgi?id=1192536
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1192536</title>
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
+ <script type="application/javascript" src="inspector-helpers.js"></script>
+ <script type="application/javascript">
+"use strict";
+
+const PATH = "http://mochi.test:8888/chrome/devtools/server/tests/chrome/";
+const BASE_IMAGE = PATH + "inspector-delay-image-response.sjs";
+const DELAYED_IMAGE = BASE_IMAGE + "?delay=300";
+const TIMEOUT_IMAGE = BASE_IMAGE + "?delay=50000";
+const NONEXISTENT_IMAGE = PATH + "this-does-not-exist.png";
+
+window.onload = function() {
+ SimpleTest.waitForExplicitFinish();
+ runNextTest();
+};
+
+function pushPref(preferenceName, value) {
+ return new Promise(resolve => {
+ const options = {"set": [[preferenceName, value]]};
+ SpecialPowers.pushPrefEnv(options, resolve);
+ });
+}
+
+let gImg = null;
+let gNodeFront = null;
+let gWalker = null;
+
+addTest(async function setup() {
+ const url = document.getElementById("inspectorContent").href;
+ const { target, doc } = await attachURL(url);
+ const inspector = await target.getFront("inspector");
+ gWalker = inspector.walker;
+ gNodeFront = await gWalker.querySelector(gWalker.rootNode, "img.custom");
+ gImg = doc.querySelector("img.custom");
+ ok(gNodeFront, "Got the image NodeFront.");
+ ok(gImg, "Got the image Node.");
+ runNextTest();
+});
+
+addTest(async function testTimeout() {
+ info("Testing that the method aborts if the image takes too long to load.");
+
+ // imageToImageData() only times out when flags.testing is not set.
+ await pushPref("devtools.testing", false);
+
+ gImg.src = TIMEOUT_IMAGE;
+
+ info("Calling getImageData().");
+ ensureRejects(gNodeFront.getImageData(), "Timeout image").then(runNextTest);
+});
+
+addTest(async function testNonExistentImage() {
+ info("Testing that non-existent image causes a rejection.");
+
+ // This test shouldn't hit the timeout.
+ await pushPref("devtools.testing", true);
+
+ gImg.src = NONEXISTENT_IMAGE;
+
+ info("Calling getImageData().");
+ ensureRejects(gNodeFront.getImageData(), "Non-existent image").then(runNextTest);
+});
+
+addTest(async function testDelayedImage() {
+ info("Testing that the method waits for an image to load.");
+
+ // This test shouldn't hit the timeout.
+ await pushPref("devtools.testing", true);
+
+ gImg.src = DELAYED_IMAGE;
+
+ info("Calling getImageData().");
+ checkImageData(gNodeFront.getImageData()).then(runNextTest);
+});
+
+addTest(function cleanup() {
+ gImg = null;
+ gNodeFront = null;
+ gWalker = null;
+ runNextTest();
+});
+
+/**
+ * Asserts that the given promise rejects.
+ */
+function ensureRejects(promise, desc) {
+ return promise.then(() => {
+ ok(false, desc + ": promise resolved unexpectedly.");
+ }, () => {
+ ok(true, desc + ": promise rejected as expected.");
+ });
+}
+
+/**
+ * Waits for the call to getImageData() the resolve and checks that the image
+ * size is reported correctly.
+ */
+function checkImageData(promise, { width, height } = { width: 1, height: 1 }) {
+ return promise.then(({ size }) => {
+ is(size.naturalWidth, width, "The width is correct.");
+ is(size.naturalHeight, height, "The height is correct.");
+ });
+}
+
+ </script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1192536">Mozilla Bug 1192536</a>
+<a id="inspectorContent" target="_blank" href="inspector_getImageData.html">Test Document</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+</body>
+</html>
diff --git a/devtools/server/tests/chrome/test_inspector_getImageData.html b/devtools/server/tests/chrome/test_inspector_getImageData.html
new file mode 100644
index 0000000000..53597b144a
--- /dev/null
+++ b/devtools/server/tests/chrome/test_inspector_getImageData.html
@@ -0,0 +1,166 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=932937
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 932937</title>
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
+ <script type="application/javascript" src="inspector-helpers.js"></script>
+ <script type="application/javascript">
+"use strict";
+
+window.onload = function() {
+ SimpleTest.waitForExplicitFinish();
+ runNextTest();
+};
+
+let gWalker = null;
+
+addTest(async function setup() {
+ const url = document.getElementById("inspectorContent").href;
+ const { target } = await attachURL(url);
+ const inspector = await target.getFront("inspector");
+ gWalker = inspector.walker;
+ runNextTest();
+});
+
+addTest(function testLargeImage() {
+ // Select the image node from the test page
+ gWalker.querySelector(gWalker.rootNode, ".big-horizontal").then(img => {
+ ok(img, "Image node found in the test page");
+ ok(img.getImageData, "Image node has the getImageData function");
+
+ img.getImageData(100).then(imageData => {
+ ok(imageData.data, "Image data actor was sent back");
+ ok(imageData.size, "Image size info was sent back too");
+ is(imageData.size.naturalWidth, 5333, "Natural width of the image correct");
+ is(imageData.size.naturalHeight, 3000, "Natural width of the image correct");
+ ok(imageData.size.resized, "Image was resized");
+
+ // eslint-disable-next-line max-nested-callbacks
+ imageData.data.string().then(str => {
+ ok(str, "We have an image data string!");
+ testResizing(imageData, str);
+ });
+ });
+ });
+});
+
+addTest(function testLargeCanvas() {
+ // Select the canvas node from the test page
+ gWalker.querySelector(gWalker.rootNode, ".big-vertical").then(canvas => {
+ ok(canvas, "Image node found in the test page");
+ ok(canvas.getImageData, "Image node has the getImageData function");
+
+ canvas.getImageData(350).then(imageData => {
+ ok(imageData.data, "Image data actor was sent back");
+ ok(imageData.size, "Image size info was sent back too");
+ is(imageData.size.naturalWidth, 1000, "Natural width of the image correct");
+ is(imageData.size.naturalHeight, 2000, "Natural width of the image correct");
+ ok(imageData.size.resized, "Image was resized");
+
+ // eslint-disable-next-line max-nested-callbacks
+ imageData.data.string().then(str => {
+ ok(str, "We have an image data string!");
+ testResizing(imageData, str);
+ });
+ });
+ });
+});
+
+addTest(function testSmallImage() {
+ // Select the small image node from the test page
+ gWalker.querySelector(gWalker.rootNode, ".small").then(img => {
+ ok(img, "Image node found in the test page");
+ ok(img.getImageData, "Image node has the getImageData function");
+
+ img.getImageData().then(imageData => {
+ ok(imageData.data, "Image data actor was sent back");
+ ok(imageData.size, "Image size info was sent back too");
+ is(imageData.size.naturalWidth, 245, "Natural width of the image correct");
+ is(imageData.size.naturalHeight, 240, "Natural width of the image correct");
+ ok(!imageData.size.resized, "Image was NOT resized");
+
+ // eslint-disable-next-line max-nested-callbacks
+ imageData.data.string().then(str => {
+ ok(str, "We have an image data string!");
+ testResizing(imageData, str);
+ });
+ });
+ });
+});
+
+addTest(function testDataImage() {
+ // Select the data image node from the test page
+ gWalker.querySelector(gWalker.rootNode, ".data").then(img => {
+ ok(img, "Image node found in the test page");
+ ok(img.getImageData, "Image node has the getImageData function");
+
+ img.getImageData(14).then(imageData => {
+ ok(imageData.data, "Image data actor was sent back");
+ ok(imageData.size, "Image size info was sent back too");
+ is(imageData.size.naturalWidth, 28, "Natural width of the image correct");
+ is(imageData.size.naturalHeight, 28, "Natural width of the image correct");
+ ok(imageData.size.resized, "Image was resized");
+
+ // eslint-disable-next-line max-nested-callbacks
+ imageData.data.string().then(str => {
+ ok(str, "We have an image data string!");
+ testResizing(imageData, str);
+ });
+ });
+ });
+});
+
+addTest(function testNonImgOrCanvasElements() {
+ gWalker.querySelector(gWalker.rootNode, "body").then(body => {
+ ensureRejects(body.getImageData(), "Invalid element").then(runNextTest);
+ });
+});
+
+addTest(function cleanup() {
+ gWalker = null;
+ runNextTest();
+});
+
+/**
+ * Checks if the server told the truth about resizing the image
+ */
+function testResizing(imageData, str) {
+ const img = document.createElement("img");
+ img.addEventListener("load", () => {
+ const resized = !(img.naturalWidth == imageData.size.naturalWidth &&
+ img.naturalHeight == imageData.size.naturalHeight);
+ is(imageData.size.resized, resized, "Server told the truth about resizing");
+ runNextTest();
+ });
+ img.src = str;
+}
+
+/**
+ * Asserts that the given promise rejects.
+ */
+function ensureRejects(promise, desc) {
+ return promise.then(() => {
+ ok(false, desc + ": promise resolved unexpectedly.");
+ }, () => {
+ ok(true, desc + ": promise rejected as expected.");
+ });
+}
+ </script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=932937">Mozilla Bug 932937</a>
+<a id="inspectorContent" target="_blank" href="inspector_getImageData.html">Test Document</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+</body>
+</html>
diff --git a/devtools/server/tests/chrome/test_inspector_getImageDataFromURL.html b/devtools/server/tests/chrome/test_inspector_getImageDataFromURL.html
new file mode 100644
index 0000000000..f8496fce5e
--- /dev/null
+++ b/devtools/server/tests/chrome/test_inspector_getImageDataFromURL.html
@@ -0,0 +1,116 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+Tests for InspectorActor.getImageDataFromURL() in following cases:
+ * Normal case, image loads after a small delay.
+ * Image takes too long to load (the method rejects after a timeout).
+ * Image fails to load.
+
+https://bugzilla.mozilla.org/show_bug.cgi?id=1192536
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1192536</title>
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
+ <script type="application/javascript" src="inspector-helpers.js"></script>
+ <script type="application/javascript">
+"use strict";
+
+const PATH = "http://mochi.test:8888/chrome/devtools/server/tests/chrome/";
+const BASE_IMAGE = PATH + "inspector-delay-image-response.sjs";
+const DELAYED_IMAGE = BASE_IMAGE + "?delay=300";
+const TIMEOUT_IMAGE = BASE_IMAGE + "?delay=50000";
+const NONEXISTENT_IMAGE = PATH + "this-does-not-exist.png";
+
+window.onload = function() {
+ SimpleTest.waitForExplicitFinish();
+ runNextTest();
+};
+
+function pushPref(preferenceName, value) {
+ return new Promise(resolve => {
+ const options = {"set": [[preferenceName, value]]};
+ SpecialPowers.pushPrefEnv(options, resolve);
+ });
+}
+
+let gInspector = null;
+
+addTest(async function setup() {
+ const url = document.getElementById("inspectorContent").href;
+ const { target } = await attachURL(url);
+ gInspector = await target.getFront("inspector");
+ runNextTest();
+});
+
+addTest(async function testTimeout() {
+ info("Testing that the method aborts if the image takes too long to load.");
+
+ // imageToImageData() only times out when flags.testing is not set.
+ await pushPref("devtools.testing", false);
+
+ ensureRejects(gInspector.getImageDataFromURL(TIMEOUT_IMAGE),
+ "Image that loads for too long").then(runNextTest);
+});
+
+addTest(async function testNonExistentImage() {
+ info("Testing that non-existent image causes a rejection.");
+
+ // This test shouldn't hit the timeout.
+ await pushPref("devtools.testing", true);
+
+ ensureRejects(gInspector.getImageDataFromURL(NONEXISTENT_IMAGE),
+ "Non-existent image").then(runNextTest);
+});
+
+addTest(async function testNormalImage() {
+ info("Testing that the method waits for an image to load.");
+
+ // This test shouldn't hit the timeout.
+ await pushPref("devtools.testing", true);
+
+ checkImageData(gInspector.getImageDataFromURL(DELAYED_IMAGE)).then(runNextTest);
+});
+
+addTest(function cleanup() {
+ gInspector = null;
+ runNextTest();
+});
+
+/**
+ * Asserts that the given promise rejects.
+ */
+function ensureRejects(promise, desc) {
+ return promise.then(() => {
+ ok(false, desc + ": promise resolved unexpectedly.");
+ }, () => {
+ ok(true, desc + ": promise rejected as expected.");
+ });
+}
+
+/**
+ * Waits for the call to getImageData() the resolve and checks that the image
+ * size is reported correctly.
+ */
+function checkImageData(promise, { width, height } = { width: 1, height: 1 }) {
+ return promise.then(({ size }) => {
+ is(size.naturalWidth, width, "The width is correct.");
+ is(size.naturalHeight, height, "The height is correct.");
+ });
+}
+
+ </script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1192536">Mozilla Bug 1192536</a>
+<a id="inspectorContent" target="_blank" href="inspector_getImageData.html">Test Document</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+</body>
+</html>
diff --git a/devtools/server/tests/chrome/test_inspector_getNodeFromActor.html b/devtools/server/tests/chrome/test_inspector_getNodeFromActor.html
new file mode 100644
index 0000000000..c3c5d32af9
--- /dev/null
+++ b/devtools/server/tests/chrome/test_inspector_getNodeFromActor.html
@@ -0,0 +1,84 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1155653
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1155653</title>
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
+ <script type="application/javascript" src="inspector-helpers.js"></script>
+ <script type="application/javascript">
+"use strict";
+
+window.onload = function() {
+ SimpleTest.waitForExplicitFinish();
+ runNextTest();
+};
+
+let gWalker;
+
+addTest(async function() {
+ const url = document.getElementById("inspectorContent").href;
+ const { target } = await attachURL(url);
+ const inspector = await target.getFront("inspector");
+ gWalker = inspector.walker;
+ runNextTest();
+});
+
+addTest(function() {
+ info("Try to get a NodeFront from an invalid actorID");
+ gWalker.getNodeFromActor("invalid", ["node"]).then(node => {
+ ok(!node, "The node returned is null");
+ runNextTest();
+ });
+});
+
+addTest(function() {
+ info("Try to get a NodeFront from a valid actorID but invalid path");
+ gWalker.getNodeFromActor(gWalker.actorID, ["invalid", "path"]).then(node => {
+ ok(!node, "The node returned is null");
+ runNextTest();
+ });
+});
+
+addTest(function() {
+ info("Try to get a NodeFront from a valid actorID and valid path");
+ gWalker.getNodeFromActor(gWalker.actorID, ["rootDoc"]).then(rootDocNode => {
+ ok(rootDocNode, "A node was returned");
+ is(rootDocNode, gWalker.rootNode, "The right node was returned");
+ runNextTest();
+ });
+});
+
+addTest(function() {
+ info("Try to get a NodeFront from a valid actorID and valid complex path");
+ gWalker.getNodeFromActor(gWalker.actorID,
+ ["targetActor", "window", "document", "body"]).then(bodyNode => {
+ ok(bodyNode, "A node was returned");
+ gWalker.querySelector(gWalker.rootNode, "body").then(node => {
+ is(bodyNode, node, "The body node was returned");
+ runNextTest();
+ });
+ });
+});
+
+addTest(function() {
+ gWalker = null;
+ runNextTest();
+});
+ </script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1155653">Mozilla Bug 1155653</a>
+<a id="inspectorContent" target="_blank" href="inspector_getImageData.html">Test Document</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+</body>
+</html>
diff --git a/devtools/server/tests/chrome/test_inspector_getOffsetParent.html b/devtools/server/tests/chrome/test_inspector_getOffsetParent.html
new file mode 100644
index 0000000000..09da7d55d1
--- /dev/null
+++ b/devtools/server/tests/chrome/test_inspector_getOffsetParent.html
@@ -0,0 +1,129 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1345119
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1345119</title>
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
+ <script type="application/javascript" src="inspector-helpers.js"></script>
+ <script type="application/javascript">
+"use strict";
+
+window.onload = function() {
+ SimpleTest.waitForExplicitFinish();
+ runNextTest();
+};
+
+var gWalker;
+var gHTMLNode;
+var gBodyNode;
+
+addTest(async function setup() {
+ const url = document.getElementById("inspectorContent").href;
+ const { target } = await attachURL(url);
+ const inspector = await target.getFront("inspector");
+ gWalker = inspector.walker;
+ gBodyNode = await gWalker.querySelector(gWalker.rootNode, "body");
+ gHTMLNode = await gWalker.querySelector(gWalker.rootNode, "html");
+ runNextTest();
+});
+
+addTest(function() {
+ info("Try to get the offset parent for a dead node (null)");
+ gWalker.getOffsetParent(null).then(offsetParent => {
+ ok(!offsetParent, "No offset parent found");
+ runNextTest();
+ });
+});
+
+addTest(function() {
+ info("Try to get the offset parent for a node that is absolutely positioned inside a " +
+ "relative node");
+ gWalker.querySelector(gWalker.rootNode, "#absolute_child").then(node => {
+ return gWalker.getOffsetParent(node);
+ }).then(offsetParent => {
+ ok(offsetParent, "The node has an offset parent");
+ gWalker.querySelector(gWalker.rootNode, "#relative_parent").then(parent => {
+ ok(offsetParent === parent, "The offset parent is the correct node");
+ runNextTest();
+ });
+ });
+});
+
+addTest(function() {
+ info("Try to get the offset parent for a node that is absolutely positioned outside a" +
+ " relative node");
+ gWalker.querySelector(gWalker.rootNode, "#no_parent").then(node => {
+ return gWalker.getOffsetParent(node);
+ }).then(offsetParent => {
+ ok(offsetParent === gBodyNode || offsetParent === gHTMLNode,
+ "The node's offset parent is the body or html node");
+ runNextTest();
+ });
+});
+
+addTest(function() {
+ info("Try to get the offset parent for a relatively positioned node");
+ gWalker.querySelector(gWalker.rootNode, "#relative_parent").then(node => {
+ return gWalker.getOffsetParent(node);
+ }).then(offsetParent => {
+ ok(offsetParent === gBodyNode || offsetParent === gHTMLNode,
+ "The node's offset parent is the body or html node");
+ runNextTest();
+ });
+});
+
+addTest(function() {
+ info("Try to get the offset parent for a statically positioned node");
+ gWalker.querySelector(gWalker.rootNode, "#static").then(node => {
+ return gWalker.getOffsetParent(node);
+ }).then(offsetParent => {
+ ok(offsetParent === gBodyNode || offsetParent === gHTMLNode,
+ "The node's offset parent is the body or html node");
+ runNextTest();
+ });
+});
+
+addTest(function() {
+ info("Try to get the offset parent for a fixed positioned node");
+ gWalker.querySelector(gWalker.rootNode, "#fixed").then(node => {
+ return gWalker.getOffsetParent(node);
+ }).then(offsetParent => {
+ ok(offsetParent === gBodyNode || offsetParent === gHTMLNode,
+ "The node's offset parent is the body or html node");
+ runNextTest();
+ });
+});
+
+addTest(function() {
+ info("Try to get the offset parent for the body");
+ gWalker.querySelector(gWalker.rootNode, "body").then(node => {
+ return gWalker.getOffsetParent(node);
+ }).then(offsetParent => {
+ ok(!offsetParent, "The body has no offset parent");
+ runNextTest();
+ });
+});
+
+addTest(function() {
+ gWalker = null;
+ gBodyNode = null;
+ runNextTest();
+});
+ </script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1345119">Mozilla Bug 1345119</a>
+<a id="inspectorContent" target="_blank" href="inspector_getOffsetParent.html">Test Document</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+</body>
+</html>
diff --git a/devtools/server/tests/chrome/test_makeGlobalObjectReference.html b/devtools/server/tests/chrome/test_makeGlobalObjectReference.html
new file mode 100644
index 0000000000..c9b26fc86e
--- /dev/null
+++ b/devtools/server/tests/chrome/test_makeGlobalObjectReference.html
@@ -0,0 +1,96 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=914405
+
+Debugger.prototype.makeGlobalObjectReference should dereference WindowProxy
+(outer window) objects.
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Mozilla Bug 914405</title>
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
+</head>
+<body>
+<pre id="test">
+<script>
+"use strict";
+
+const {addSandboxedDebuggerToGlobal} = ChromeUtils.import("resource://gre/modules/jsdebugger.jsm");
+addSandboxedDebuggerToGlobal(this);
+
+window.onload = function() {
+ SimpleTest.waitForExplicitFinish();
+
+ // Load one of our iframes over http to force it in a different compartment
+ // from the current window and the other iframe.
+ const iframe = document.createElement("iframe");
+ const baseURL = "http://mochi.test:8888/chrome/devtools/server/tests/chrome/";
+ iframe.src = baseURL + "iframe1_makeGlobalObjectReference.html";
+ iframe.onload = iframeOnLoad;
+ document.body.appendChild(iframe);
+
+ function iframeOnLoad() {
+ const dbg = new Debugger();
+
+ // 'o' for 'outer window'
+ const g1o = iframe.contentWindow;
+ ok(!dbg.hasDebuggee(g1o), "iframe is not initially a debuggee");
+
+ // Like addDebuggee, makeGlobalObjectReference innerizes.
+ // 'i' stands for 'inner window'.
+ // 'DO' stands for 'Debugger.Object'.
+ const g1iDO = dbg.makeGlobalObjectReference(g1o);
+ ok(!dbg.hasDebuggee(g1o),
+ "makeGlobalObjectReference does not add g1 as debuggee, designated via outer");
+ ok(!dbg.hasDebuggee(g1iDO),
+ "makeGlobalObjectReference does not add g1 as debuggee, designated via D.O ");
+
+ // Wrapping an object automatically outerizes it, so dereferencing an
+ // inner object D.O gets you an outer object.
+ // ('===' does distinguish inner and outer objects.)
+ // (That's a capital '=', if you must know.)
+ ok(g1iDO.unsafeDereference() === g1o, "g1iDO has the right referent");
+
+ // However, Debugger.Objects do distinguish inner and outer windows.
+ const g1oDO = g1iDO.makeDebuggeeValue(g1o);
+ ok(g1iDO !== g1oDO, "makeDebuggeeValue doesn't innerize");
+ ok(g1iDO.unsafeDereference() === g1oDO.unsafeDereference(),
+ "unsafeDereference() outerizes," +
+ " so inner and outer window D.Os both dereference to outer");
+
+ ok(dbg.addDebuggee(g1o) === g1iDO, "addDebuggee returns the inner window's D.O");
+ ok(dbg.hasDebuggee(g1o), "addDebuggee adds the correct global");
+ ok(dbg.hasDebuggee(g1iDO),
+ "hasDebuggee can take a D.O referring to the inner window");
+ ok(dbg.hasDebuggee(g1oDO),
+ "hasDebuggee can take a D.O referring to the outer window");
+
+ const iframe2 = document.createElement("iframe");
+ iframe2.src = "iframe2_makeGlobalObjectReference.html";
+ iframe2.onload = iframe2OnLoad;
+ document.body.appendChild(iframe2);
+
+ function iframe2OnLoad() {
+ // makeGlobalObjectReference dereferences CCWs.
+ const g2o = iframe2.contentWindow;
+ g2o.g1o = g1o;
+
+ const g2iDO = dbg.addDebuggee(g2o);
+ const g2g1oDO = g2iDO.getOwnPropertyDescriptor("g1o").value;
+ ok(g2g1oDO !== g1oDO, "g2's cross-compartment wrapper for g1o gets its own D.O");
+ ok(g2g1oDO.unwrap() === g1oDO,
+ "unwrapping g2's cross-compartment wrapper for g1o gets the right D.O");
+ ok(dbg.makeGlobalObjectReference(g2g1oDO) === g1iDO,
+ "makeGlobalObjectReference unwraps cross-compartment wrappers, and innerizes");
+
+ SimpleTest.finish();
+ }
+ }
+};
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/devtools/server/tests/chrome/test_memory.html b/devtools/server/tests/chrome/test_memory.html
new file mode 100644
index 0000000000..79ba29c913
--- /dev/null
+++ b/devtools/server/tests/chrome/test_memory.html
@@ -0,0 +1,39 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+Bug 923275 - Add a memory monitor widget to the developer toolbar
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Memory monitoring actor test</title>
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
+</head>
+<body>
+<pre id="test">
+<script src="memory-helpers.js" type="application/javascript"></script>
+<script>
+"use strict";
+
+window.onload = function() {
+ SimpleTest.waitForExplicitFinish();
+
+ (async function() {
+ const { memory, target } = await startServerAndGetSelectedTabMemory();
+ const measurement = await memory.measure();
+ ok(measurement.total > 0, "total memory is valid");
+ ok(measurement.domSize > 0, "domSize is valid");
+ ok(measurement.styleSize > 0, "styleSize is valid");
+ ok(measurement.jsObjectsSize > 0, "jsObjectsSize is valid");
+ ok(measurement.jsStringsSize > 0, "jsStringsSize is valid");
+ ok(measurement.jsOtherSize > 0, "jsOtherSize is valid");
+ ok(measurement.otherSize > 0, "otherSize is valid");
+ ok(measurement.jsMilliseconds, "jsMilliseconds is valid");
+ ok(measurement.nonJSMilliseconds, "nonJSMilliseconds is valid");
+ await destroyServerAndFinish(target);
+ })();
+};
+</script>
+</pre>
+</body>
+</html>
diff --git a/devtools/server/tests/chrome/test_memory_allocations_02.html b/devtools/server/tests/chrome/test_memory_allocations_02.html
new file mode 100644
index 0000000000..aedddb49ab
--- /dev/null
+++ b/devtools/server/tests/chrome/test_memory_allocations_02.html
@@ -0,0 +1,80 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+Bug 1132764 - Test controlling the maximum allocations log length over the RDP.
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Memory monitoring actor test</title>
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
+</head>
+<body>
+<pre id="test">
+<script src="memory-helpers.js" type="application/javascript"></script>
+<script>
+"use strict";
+
+window.onload = function() {
+ SimpleTest.waitForExplicitFinish();
+
+ (async function() {
+ const { memory, target } = await startServerAndGetSelectedTabMemory();
+ await memory.attach();
+
+ const allocs = [];
+ let eventsFired = 0;
+ let intervalId = null;
+ function onAlloc() {
+ eventsFired++;
+ }
+ function startAllocating() {
+ intervalId = setInterval(() => {
+ for (let i = 100000; --i;) {
+ allocs.push(new Object());
+ }
+ }, 1);
+ }
+ function stopAllocating() {
+ clearInterval(intervalId);
+ }
+
+ memory.on("allocations", onAlloc);
+
+ await memory.startRecordingAllocations({
+ drainAllocationsTimeout: 10,
+ });
+
+ await waitUntil(() => eventsFired > 5);
+ ok(eventsFired > 5,
+ "Some allocation events fired without allocating much via auto drain");
+ await memory.stopRecordingAllocations();
+
+ // Set a really high auto drain timer so we can test if
+ // it fires on GC
+ eventsFired = 0;
+ const startTime = performance.now();
+ const drainTimer = 1000000;
+ await memory.startRecordingAllocations({
+ drainAllocationsTimeout: drainTimer,
+ });
+
+ startAllocating();
+ await waitUntil(() => {
+ Cu.forceGC();
+ return eventsFired > 1;
+ });
+ stopAllocating();
+ ok(performance.now() - drainTimer < startTime,
+ "Allocation events fired on GC before timer");
+ await memory.stopRecordingAllocations();
+
+ memory.off("allocations", onAlloc);
+ await memory.detach();
+ destroyServerAndFinish(target);
+ })();
+};
+</script>
+</pre>
+</body>
+</html>
diff --git a/devtools/server/tests/chrome/test_memory_allocations_03.html b/devtools/server/tests/chrome/test_memory_allocations_03.html
new file mode 100644
index 0000000000..ca6a1ec1b4
--- /dev/null
+++ b/devtools/server/tests/chrome/test_memory_allocations_03.html
@@ -0,0 +1,80 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+Bug 1067491 - Test that frames keep the same index while we are recording.
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Memory monitoring actor test</title>
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
+</head>
+<body>
+<pre id="test">
+<script src="memory-helpers.js" type="application/javascript"></script>
+<script>
+"use strict";
+
+window.onload = function() {
+ SimpleTest.waitForExplicitFinish();
+
+ (async function() {
+ const { memory, target } = await startServerAndGetSelectedTabMemory();
+ await memory.attach();
+
+ await memory.startRecordingAllocations();
+
+ // Allocate twice with the exact same stack (hence setTimeout rather than
+ // allocating directly in the generator), but with getAllocations() calls in
+ // between.
+
+ const allocs = [];
+ function allocator() {
+ allocs.push({});
+ }
+
+ setTimeout(allocator, 1);
+ await waitForTime(2);
+ const first = await memory.getAllocations();
+
+ setTimeout(allocator, 1);
+ await waitForTime(2);
+ const second = await memory.getAllocations();
+
+ await memory.stopRecordingAllocations();
+
+ // Assert that each frame in the first response has the same index in the
+ // second response. This isn't commutative, so we don't check that all
+ // of the second response's frames are the same in the first response,
+ // because there might be new allocations that happen after the first query
+ // but before the second.
+
+ function assertSameFrame(a, b) {
+ info(" First frame = " + JSON.stringify(a, null, 4));
+ info(" Second frame = " + JSON.stringify(b, null, 4));
+
+ is(!!a, !!b);
+ if (!a || !b) {
+ return;
+ }
+
+ is(a.source, b.source);
+ is(a.line, b.line);
+ is(a.column, b.column);
+ is(a.functionDisplayName, b.functionDisplayName);
+ is(a.parent, b.parent);
+ }
+
+ for (let i = 0; i < first.frames.length; i++) {
+ info("Checking frames at index " + i + ":");
+ assertSameFrame(first.frames[i], second.frames[i]);
+ }
+
+ await memory.detach();
+ destroyServerAndFinish(target);
+ })();
+};
+</script>
+</pre>
+</body>
+</html>
diff --git a/devtools/server/tests/chrome/test_memory_allocations_04.html b/devtools/server/tests/chrome/test_memory_allocations_04.html
new file mode 100644
index 0000000000..8bb64c591c
--- /dev/null
+++ b/devtools/server/tests/chrome/test_memory_allocations_04.html
@@ -0,0 +1,62 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+Bug 1068171 - Test controlling the memory actor's allocation sampling probability.
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Memory monitoring actor test</title>
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
+</head>
+<body>
+<pre id="test">
+<script src="memory-helpers.js" type="application/javascript"></script>
+<script>
+"use strict";
+
+window.onload = function() {
+ SimpleTest.waitForExplicitFinish();
+
+ (async function() {
+ const { memory, target } = await startServerAndGetSelectedTabMemory();
+ await memory.attach();
+
+ const allocs = [];
+ function allocator() {
+ for (let i = 0; i < 100; i++) {
+ allocs.push({});
+ }
+ }
+
+ const testProbability = async function(p, expected) {
+ info("probability = " + p);
+ await memory.startRecordingAllocations({
+ probability: p,
+ });
+ allocator();
+ const response = await memory.getAllocations();
+ await memory.stopRecordingAllocations();
+ return response.allocations.length;
+ };
+
+ is((await testProbability(0.0)), 0,
+ "With probability = 0.0, we shouldn't get any allocations.");
+
+ ok((await testProbability(1.0)) >= 100,
+ "With probability = 1.0, we should get all 100 allocations (plus "
+ + "whatever allocations the actor and SpiderMonkey make).");
+
+ // We don't test any other probabilities because the test would be
+ // non-deterministic. We don't have a way to control the PRNG like we do in
+ // jit-tests
+ // (js/src/jit-test/tests/debug/Memory-allocationsSamplingProbability-*.js).
+
+ await memory.detach();
+ destroyServerAndFinish(target);
+ })();
+};
+</script>
+</pre>
+</body>
+</html>
diff --git a/devtools/server/tests/chrome/test_memory_allocations_05.html b/devtools/server/tests/chrome/test_memory_allocations_05.html
new file mode 100644
index 0000000000..6220e71449
--- /dev/null
+++ b/devtools/server/tests/chrome/test_memory_allocations_05.html
@@ -0,0 +1,93 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+Bug 1068144 - Test getting the timestamps for allocations.
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Memory monitoring actor test</title>
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
+</head>
+<body>
+<pre id="test">
+<script src="memory-helpers.js" type="application/javascript"></script>
+<script>
+"use strict";
+
+window.onload = function() {
+ SimpleTest.waitForExplicitFinish();
+
+ (async function() {
+ const { memory, target } = await startServerAndGetSelectedTabMemory();
+ await memory.attach();
+
+ const allocs = [];
+ function allocator() {
+ allocs.push(new Object());
+ }
+
+ // Using setTimeout results in wildly varying delays that make it hard to
+ // test our timestamps and results in intermittent failures. Instead, we
+ // actually spin an empty loop for a whole millisecond.
+ function actuallyWaitOneWholeMillisecond() {
+ const start = window.performance.now();
+ // eslint-disable-next-line curly
+ while (window.performance.now() - start < 1.000);
+ }
+
+ await memory.startRecordingAllocations();
+
+ allocator();
+ actuallyWaitOneWholeMillisecond();
+ allocator();
+ actuallyWaitOneWholeMillisecond();
+ allocator();
+
+ const response = await memory.getAllocations();
+ await memory.stopRecordingAllocations();
+
+ ok(response.allocationsTimestamps, "The response should have timestamps.");
+ is(response.allocationsTimestamps.length, response.allocations.length,
+ "There should be a timestamp for every allocation.");
+
+ const allocatorIndices = response.allocations
+ .map(function(a, idx) {
+ const frame = response.frames[a];
+ if (frame && frame.functionDisplayName === "allocator") {
+ return idx;
+ }
+ return null;
+ })
+ .filter(function(idx) {
+ return idx !== null;
+ });
+
+ is(allocatorIndices.length, 3,
+ "Should have our 3 allocations from the `allocator` timeouts.");
+
+ let lastTimestamp;
+ for (let i = 0; i < 3; i++) {
+ const timestamp = response.allocationsTimestamps[allocatorIndices[i]];
+ info("timestamp", timestamp);
+ ok(timestamp, "We should have a timestamp for the `allocator` allocation.");
+
+ if (lastTimestamp) {
+ const delta = timestamp - lastTimestamp;
+ info("delta since last timestamp", delta);
+ // ms
+ ok(delta >= 1,
+ "The timestamp should be about 1 ms after the last timestamp.");
+ }
+
+ lastTimestamp = timestamp;
+ }
+
+ await memory.detach();
+ destroyServerAndFinish(target);
+ })();
+};
+</script>
+</pre>
+</body>
+</html>
diff --git a/devtools/server/tests/chrome/test_memory_allocations_06.html b/devtools/server/tests/chrome/test_memory_allocations_06.html
new file mode 100644
index 0000000000..e3da7fac1a
--- /dev/null
+++ b/devtools/server/tests/chrome/test_memory_allocations_06.html
@@ -0,0 +1,51 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+Bug 1132764 - Test controlling the maximum allocations log length over the RDP.
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Memory monitoring actor test</title>
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
+</head>
+<body>
+<pre id="test">
+<script src="memory-helpers.js" type="application/javascript"></script>
+<script>
+"use strict";
+
+window.onload = function() {
+ SimpleTest.waitForExplicitFinish();
+
+ (async function() {
+ const { memory, target } = await startServerAndGetSelectedTabMemory();
+ await memory.attach();
+
+ const allocs = [];
+ function allocator() {
+ allocs.push(new Object());
+ }
+
+ await memory.startRecordingAllocations({
+ maxLogLength: 1,
+ });
+
+ allocator();
+ allocator();
+ allocator();
+
+ const response = await memory.getAllocations();
+ await memory.stopRecordingAllocations();
+
+ is(response.allocations.length, 1,
+ "There should only be one entry in the allocations log.");
+
+ await memory.detach();
+ destroyServerAndFinish(target);
+ })();
+};
+</script>
+</pre>
+</body>
+</html>
diff --git a/devtools/server/tests/chrome/test_memory_allocations_07.html b/devtools/server/tests/chrome/test_memory_allocations_07.html
new file mode 100644
index 0000000000..a9ae810cba
--- /dev/null
+++ b/devtools/server/tests/chrome/test_memory_allocations_07.html
@@ -0,0 +1,58 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+Bug 1192335 - Test getting the byte sizes for allocations.
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Memory monitoring actor test</title>
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
+</head>
+<body>
+<pre id="test">
+<script src="memory-helpers.js" type="application/javascript"></script>
+<script>
+"use strict";
+
+window.onload = function() {
+ SimpleTest.waitForExplicitFinish();
+
+ (async function() {
+ const { memory, target } = await startServerAndGetSelectedTabMemory();
+ await memory.attach();
+
+ const allocs = [];
+ function allocator() {
+ allocs.push(new Object());
+ }
+
+ await memory.startRecordingAllocations();
+
+ allocator();
+ allocator();
+ allocator();
+
+ const response = await memory.getAllocations();
+ await memory.stopRecordingAllocations();
+
+ ok(response.allocationSizes, "The response should have bytesizes.");
+ is(response.allocationSizes.length, response.allocations.length,
+ "There should be a bytesize for every allocation.");
+ ok(response.allocationSizes.length >= 3,
+ "There are atleast 3 allocations.");
+ ok(response.allocationSizes.every(isPositiveNumber),
+ "every bytesize is a positive number");
+
+ await memory.detach();
+ destroyServerAndFinish(target);
+ })();
+};
+
+function isPositiveNumber(n) {
+ return typeof n === "number" && n > 0;
+}
+</script>
+</pre>
+</body>
+</html>
diff --git a/devtools/server/tests/chrome/test_memory_attach_01.html b/devtools/server/tests/chrome/test_memory_attach_01.html
new file mode 100644
index 0000000000..89f1818292
--- /dev/null
+++ b/devtools/server/tests/chrome/test_memory_attach_01.html
@@ -0,0 +1,33 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+Bug 960671 - Test attaching and detaching from a memory actor.
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Memory monitoring actor test</title>
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
+</head>
+<body>
+<pre id="test">
+<script src="memory-helpers.js" type="application/javascript"></script>
+<script>
+"use strict";
+
+window.onload = function() {
+ SimpleTest.waitForExplicitFinish();
+
+ (async function() {
+ const { memory, target } = await startServerAndGetSelectedTabMemory();
+ await memory.attach();
+ ok(true, "Shouldn't have gotten an error attaching.");
+ await memory.detach();
+ ok(true, "Shouldn't have gotten an error detaching.");
+ destroyServerAndFinish(target);
+ })();
+};
+</script>
+</pre>
+</body>
+</html>
diff --git a/devtools/server/tests/chrome/test_memory_attach_02.html b/devtools/server/tests/chrome/test_memory_attach_02.html
new file mode 100644
index 0000000000..18a797fe5e
--- /dev/null
+++ b/devtools/server/tests/chrome/test_memory_attach_02.html
@@ -0,0 +1,49 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+Bug 960671 - Test attaching and detaching while in the wrong state.
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Memory monitoring actor test</title>
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
+</head>
+<body>
+<pre id="test">
+<script src="memory-helpers.js" type="application/javascript"></script>
+<script>
+"use strict";
+
+window.onload = function() {
+ SimpleTest.waitForExplicitFinish();
+
+ (async function() {
+ const { memory, target } = await startServerAndGetSelectedTabMemory();
+
+ let e = null;
+ try {
+ await memory.detach();
+ } catch (ee) {
+ e = ee;
+ }
+ ok(e, "Should have hit the wrongState error");
+
+ await memory.attach();
+
+ e = null;
+ try {
+ await memory.attach();
+ } catch (ee) {
+ e = ee;
+ }
+ ok(e, "Should have hit the wrongState error");
+
+ await memory.detach();
+ destroyServerAndFinish(target);
+ })();
+};
+</script>
+</pre>
+</body>
+</html>
diff --git a/devtools/server/tests/chrome/test_memory_census.html b/devtools/server/tests/chrome/test_memory_census.html
new file mode 100644
index 0000000000..3c351740d3
--- /dev/null
+++ b/devtools/server/tests/chrome/test_memory_census.html
@@ -0,0 +1,35 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+Bug 1067491 - Test taking a census over the RDP.
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Memory monitoring actor test</title>
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
+</head>
+<body>
+<pre id="test">
+<script src="memory-helpers.js" type="application/javascript"></script>
+<script>
+"use strict";
+
+window.onload = function() {
+ SimpleTest.waitForExplicitFinish();
+
+ (async function() {
+ const { memory, target } = await startServerAndGetSelectedTabMemory();
+ await memory.attach();
+
+ const census = await memory.takeCensus();
+ is(typeof census, "object");
+
+ await memory.detach();
+ destroyServerAndFinish(target);
+ })();
+};
+</script>
+</pre>
+</body>
+</html>
diff --git a/devtools/server/tests/chrome/test_memory_gc_01.html b/devtools/server/tests/chrome/test_memory_gc_01.html
new file mode 100644
index 0000000000..8b2f049602
--- /dev/null
+++ b/devtools/server/tests/chrome/test_memory_gc_01.html
@@ -0,0 +1,50 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+Bug 1067491 - Test forcing a gc.
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Memory monitoring actor test</title>
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
+</head>
+<body>
+<pre id="test">
+<script src="memory-helpers.js" type="application/javascript"></script>
+<script>
+"use strict";
+
+window.onload = function() {
+ SimpleTest.waitForExplicitFinish();
+
+ (async function() {
+ const { memory, target } = await startServerAndGetSelectedTabMemory();
+
+ let beforeGC, afterGC;
+
+ do {
+ let objects = [];
+ for (let i = 0; i < 1000; i++) {
+ const o = {};
+ o[Math.random()] = 1;
+ objects.push(o);
+ }
+ objects = null;
+
+ beforeGC = (await memory.measure()).total;
+
+ await memory.forceGarbageCollection();
+
+ afterGC = (await memory.measure()).total;
+ } while (beforeGC < afterGC);
+
+ ok(true, "The amount of memory after GC should eventually decrease");
+
+ destroyServerAndFinish(target);
+ })();
+};
+</script>
+</pre>
+</body>
+</html>
diff --git a/devtools/server/tests/chrome/test_memory_gc_events.html b/devtools/server/tests/chrome/test_memory_gc_events.html
new file mode 100644
index 0000000000..5db1607c91
--- /dev/null
+++ b/devtools/server/tests/chrome/test_memory_gc_events.html
@@ -0,0 +1,44 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+Bug 1137527 - Test receiving GC events from the memory actor.
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Memory monitoring actor test</title>
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
+</head>
+<body>
+<pre id="test">
+<script src="memory-helpers.js" type="application/javascript"></script>
+<script>
+"use strict";
+
+window.onload = function() {
+ SimpleTest.waitForExplicitFinish();
+
+ const EventEmitter = require("devtools/shared/event-emitter");
+
+ (async function() {
+ const { memory, target } = await startServerAndGetSelectedTabMemory();
+ await memory.attach();
+
+ const gotGcEvent = new Promise(resolve => {
+ EventEmitter.on(memory, "garbage-collection", gcData => {
+ ok(gcData, "Got GC data");
+ resolve();
+ });
+ });
+
+ memory.forceGarbageCollection();
+ await gotGcEvent;
+
+ await memory.detach();
+ destroyServerAndFinish(target);
+ })();
+};
+</script>
+</pre>
+</body>
+</html>
diff --git a/devtools/server/tests/chrome/test_overflowing-body.html b/devtools/server/tests/chrome/test_overflowing-body.html
new file mode 100644
index 0000000000..1fe52e0011
--- /dev/null
+++ b/devtools/server/tests/chrome/test_overflowing-body.html
@@ -0,0 +1,42 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+Test InspectorUtils.GetOverflowingChildrenOfElement applied to the body element
+-->
+<head>
+<meta charset="utf-8">
+<title>Test InspectorUtils.GetOverflowingChildrenOfElement on the body element</title>
+<script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+<style>
+body {
+ overflow: auto;
+ margin: 0;
+}
+.tallBox {
+ overflow: auto;
+ background: lavender;
+ width: 200px;
+ height: 110vh;
+}
+</style>
+<script>
+'use strict';
+
+SimpleTest.waitForExplicitFinish();
+const InspectorUtils = SpecialPowers.InspectorUtils;
+
+function runTests() {
+ const body = document.documentElement;
+ const overflowing_children = InspectorUtils.getOverflowingChildrenOfElement(body);
+
+ is(overflowing_children.length, 1, `body has the expected number of children.`);
+
+ SimpleTest.finish();
+};
+window.onload = runTests;
+</script>
+</head>
+<body>
+<div class="tallBox"><div>
+</body>
+</html>
diff --git a/devtools/server/tests/chrome/test_overflowing-children.html b/devtools/server/tests/chrome/test_overflowing-children.html
new file mode 100644
index 0000000000..8ba81bec3d
--- /dev/null
+++ b/devtools/server/tests/chrome/test_overflowing-children.html
@@ -0,0 +1,131 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+Test InspectorUtils.GetOverflowingChildrenOfElement in various cases
+-->
+<head>
+<meta charset="utf-8">
+<title>Test InspectorUtils.GetOverflowingChildrenOfElement</title>
+<script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+<style>
+/* "e" is our custom tag name for "element" */
+e {
+ background: lightgray;
+ display: inline-block;
+ margin: 10px;
+ padding: 0;
+ border: 0;
+ width: 100px;
+ height: 100px;
+ overflow: auto;
+}
+
+/* "c" is our custom tag name for "child" */
+c {
+ display: block;
+ background: green;
+}
+
+.fixedSize {
+ width: 10px;
+ height: 10px;
+}
+
+.target {
+ background: red;
+}
+</style>
+
+<script>
+'use strict';
+
+SimpleTest.waitForExplicitFinish();
+const InspectorUtils = SpecialPowers.InspectorUtils;
+
+const CASES = [
+ {id: "no_children", expected: 0},
+ {id: "one_child_no_overflow", expected: 0},
+ {id: "margin_left_overflow", expected: 1},
+ {id: "transform_overflow", expected: 1},
+ {id: "nested_overflow", expected: 1},
+ {id: "intermediate_overflow", expected: 1},
+ {id: "multiple_overflow_at_different_depths", expected: 2},
+];
+
+function runTests() {
+ // Assign each child element to an inner id so each of them can be identified for testing.
+ Array.from(document.getElementsByTagName('c')).forEach((e, i) => {e.id = `inner${i}`});
+
+ for (const {id, expected} of CASES) {
+ info(`Checking element id ${id}.`);
+
+ const element = document.getElementById(id);
+ if (!element) {
+ ok(false, `Expected to find element with id ${id}.`);
+ continue;
+ }
+ const overflowing_children = InspectorUtils.getOverflowingChildrenOfElement(element);
+
+ is(overflowing_children.length, expected, `${id} has the expected number of children.`);
+
+ // Check that each child has the "target" class. Otherwise, we're getting the
+ // wrong children. We don't check each child with a test function, because we
+ // don't want to needlessly inflate the number of test functions in the log.
+ // But if we find a child that *doesn't* have the class "target", we report
+ // that as a test failure.
+ for (const child of overflowing_children) {
+ // child is a Node, but not necessarily an Element. We want to get the containing
+ // Element so that we can use its classList, tagName, and id properties.
+ let e = child;
+ if (child.nodeType !== Node.ELEMENT_NODE) {
+ e = child.parentElement;
+ }
+ if (!e.classList.contains("target")) {
+ ok(false, `${id} is reporting this unexpected child as a target: ${e.tagName} id=${e.id}`);
+ }
+ }
+ }
+
+ SimpleTest.finish();
+};
+window.onload = runTests;
+</script>
+</head>
+<body onload="runTests()">
+
+<e id="no_children"></e>
+
+<e id="one_child_no_overflow">
+ <c></c>
+</e>
+
+<e id="margin_left_overflow">
+ <c class="target" style="margin-left:100px">abcd</c>
+</e>
+
+<e id="transform_overflow">
+ <c class="target" style="transform: translate(50px)">abcd</c>
+</e>
+
+<e id="nested_overflow">
+ <c>
+ <c class="target" style="margin-left:100px">abcd</c>
+ </c>
+</e>
+
+<e id="intermediate_overflow">
+ <c class="fixedSize target" style="margin-left:100px">
+ <c></c>
+ </c>
+</e>
+
+<e id="multiple_overflow_at_different_depths">
+ <c class="fixedSize target" style="margin-left:100px">
+ <c></c>
+ </c>
+ <c style="margin-left:100px">
+ <c class="target">abcd</c>
+ </c>
+</e>
+</body>
+</html>
diff --git a/devtools/server/tests/chrome/test_preference.html b/devtools/server/tests/chrome/test_preference.html
new file mode 100644
index 0000000000..f8856eee24
--- /dev/null
+++ b/devtools/server/tests/chrome/test_preference.html
@@ -0,0 +1,129 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+Bug 943251 - Test preferences actor
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test Preference Actor</title>
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
+</head>
+<body>
+<pre id="test">
+<script>
+"use strict";
+
+function runTests() {
+ const {require} = ChromeUtils.import("resource://devtools/shared/Loader.jsm");
+ const {DevToolsClient} = require("devtools/client/devtools-client");
+ const {DevToolsServer} = require("devtools/server/devtools-server");
+ const Services = require("Services");
+
+ SimpleTest.waitForExplicitFinish();
+
+ DevToolsServer.init();
+ DevToolsServer.registerAllActors();
+
+ const client = new DevToolsClient(DevToolsServer.connectPipe());
+ client.connect().then(function onConnect() {
+ return client.mainRoot.getFront("preference");
+ }).then(function(p) {
+ const prefs = {};
+
+ const localPref = {
+ boolPref: true,
+ intPref: 0x1234,
+ charPref: "Hello World",
+ };
+
+ function checkValues() {
+ is(prefs.boolPref, localPref.boolPref, "read/write bool pref");
+ is(prefs.intPref, localPref.intPref, "read/write int pref");
+ is(prefs.charPref, localPref.charPref, "read/write string pref");
+
+ ["test.all.bool", "test.all.int", "test.all.string"].forEach(function(key) {
+ let expectedValue;
+ switch (Services.prefs.getPrefType(key)) {
+ case Ci.nsIPrefBranch.PREF_STRING:
+ expectedValue = Services.prefs.getCharPref(key);
+ break;
+ case Ci.nsIPrefBranch.PREF_INT:
+ expectedValue = Services.prefs.getIntPref(key);
+ break;
+ case Ci.nsIPrefBranch.PREF_BOOL:
+ expectedValue = Services.prefs.getBoolPref(key);
+ break;
+ default:
+ ok(false, "unexpected pref type (" + key + ")");
+ break;
+ }
+
+ is(prefs.allPrefs[key].value, expectedValue,
+ "valid preference value (" + key + ")");
+ is(prefs.allPrefs[key].hasUserValue, Services.prefs.prefHasUserValue(key),
+ "valid hasUserValue (" + key + ")");
+ });
+
+ ["test.bool", "test.int", "test.string"].forEach(function(key) {
+ ok(!prefs.allPrefs.hasOwnProperty(key), "expect no pref (" + key + ")");
+ is(Services.prefs.getPrefType(key), Ci.nsIPrefBranch.PREF_INVALID,
+ "pref (" + key + ") is clear");
+ });
+
+ client.close().then(() => {
+ DevToolsServer.destroy();
+ SimpleTest.finish();
+ });
+ }
+
+ function checkUndefined() {
+ let next = p.getCharPref("test.undefined");
+ next = next.then(
+ () => ok(false, "getCharPref should've thrown for an undefined preference"),
+ (ex) => {
+ const messageRe = new RegExp(
+ "Protocol error \\(Error\\): preference is not of the right type: " +
+ `test.undefined from: ${p.actorID} ` +
+ "\\(resource://devtools/server/actors/preference.js:\\d+:\\d+\\)"
+ );
+ ok(messageRe.test(ex.message), "Error message matches the expected format");
+ }
+ );
+ return next;
+ }
+
+ function updatePrefsProperty(key) {
+ return function(value) {
+ prefs[key] = value;
+ };
+ }
+
+ p.getAllPrefs().then(updatePrefsProperty("allPrefs"))
+ .then(() => p.setBoolPref("test.bool", localPref.boolPref))
+ .then(() => p.setIntPref("test.int", localPref.intPref))
+ .then(() => p.setCharPref("test.string", localPref.charPref))
+ .then(() => p.getBoolPref("test.bool")).then(updatePrefsProperty("boolPref"))
+ .then(() => p.getIntPref("test.int")).then(updatePrefsProperty("intPref"))
+ .then(() => p.getCharPref("test.string")).then(updatePrefsProperty("charPref"))
+ .then(() => p.clearUserPref("test.bool"))
+ .then(() => p.clearUserPref("test.int"))
+ .then(() => p.clearUserPref("test.string"))
+ .then(() => checkUndefined())
+ .then(checkValues);
+ });
+}
+
+window.onload = function() {
+ SpecialPowers.pushPrefEnv({
+ "set": [
+ ["test.all.bool", true],
+ ["test.all.int", 0x4321],
+ ["test.all.string", "allizom"],
+ ],
+ }, runTests);
+};
+</script>
+</pre>
+</body>
+</html>
diff --git a/devtools/server/tests/chrome/test_styles-applied.html b/devtools/server/tests/chrome/test_styles-applied.html
new file mode 100644
index 0000000000..9e27d9ab69
--- /dev/null
+++ b/devtools/server/tests/chrome/test_styles-applied.html
@@ -0,0 +1,143 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug </title>
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
+ <script type="application/javascript" src="inspector-helpers.js"></script>
+ <script type="application/javascript">
+"use strict";
+
+window.onload = function() {
+ SimpleTest.waitForExplicitFinish();
+ runNextTest();
+};
+
+let gWalker = null;
+let gStyles = null;
+
+addTest(async function setup() {
+ const url = document.getElementById("inspectorContent").href;
+ const { target } = await attachURL(url);
+ const inspector = await target.getFront("inspector");
+ gWalker = inspector.walker;
+ gStyles = await inspector.getPageStyle();
+ runNextTest();
+});
+
+addTest(function inheritedUserStyles() {
+ promiseDone(gWalker.querySelector(gWalker.rootNode, "#test-node").then(node => {
+ return gStyles.getApplied(node, { inherited: true, filter: "user" });
+ }).then(applied => {
+ ok(!applied[0].inherited, "Entry 0 should be uninherited");
+ is(applied[0].rule.type, 100, "Entry 0 should be an element style");
+ ok(!!applied[0].rule.href, "Element styles should have a URL");
+ is(applied[0].rule.cssText, "", "Entry 0 should be an empty style");
+
+ is(applied[1].inherited.id, "uninheritable-rule-inheritable-style",
+ "Entry 1 should be inherited from the parent");
+ is(applied[1].rule.type, 100, "Entry 1 should be an element style");
+ is(applied[1].rule.cssText, "color: red;",
+ "Entry 1 should have the expected cssText");
+
+ is(applied[2].inherited.id, "inheritable-rule-inheritable-style",
+ "Entry 2 should be inherited from the parent's parent");
+ is(applied[2].rule.type, 100, "Entry 2 should be an element style");
+ is(applied[2].rule.cssText, "color: blue;",
+ "Entry 2 should have the expected cssText");
+
+ is(applied[3].inherited.id, "inheritable-rule-inheritable-style",
+ "Entry 3 should be inherited from the parent's parent");
+ is(applied[3].rule.type, 1, "Entry 3 should be a rule style");
+ is(applied[3].rule.cssText, "font-size: 15px;",
+ "Entry 3 should have the expected cssText");
+ ok(!applied[3].matchedSelectors,
+ "Shouldn't get matchedSelectors with this request.");
+
+ is(applied[4].inherited.id, "inheritable-rule-uninheritable-style",
+ "Entry 4 should be inherited from the parent's parent");
+ is(applied[4].rule.type, 1, "Entry 4 should be an rule style");
+ is(applied[4].rule.cssText, "font-size: 15px;",
+ "Entry 4 should have the expected cssText");
+ ok(!applied[4].matchedSelectors, "Shouldn't get matchedSelectors with this request.");
+
+ is(applied.length, 5, "Should have 5 rules.");
+ }).then(runNextTest));
+});
+
+addTest(function inheritedSystemStyles() {
+ promiseDone(gWalker.querySelector(gWalker.rootNode, "#test-node").then(node => {
+ return gStyles.getApplied(node, { inherited: true, filter: "ua" });
+ }).then(applied => {
+ // If our system stylesheets are prone to churn, this might be a fragile
+ // test. If you're here because of that I apologize, file a bug
+ // and we can find a different way to test.
+
+ ok(!applied[1].inherited, "Entry 1 should not be inherited");
+ ok(applied[1].rule.parentStyleSheet.system, "Entry 1 should be a system style");
+ is(applied[1].rule.type, 1, "Entry 1 should be a rule style");
+
+ is(applied.length, 8, "Should have the expected number of rules.");
+ }).then(runNextTest));
+});
+
+addTest(function noInheritedStyles() {
+ promiseDone(gWalker.querySelector(gWalker.rootNode, "#test-node").then(node => {
+ return gStyles.getApplied(node, { inherited: false, filter: "user" });
+ }).then(applied => {
+ ok(!applied[0].inherited, "Entry 0 should be uninherited");
+ is(applied[0].rule.type, 100, "Entry 0 should be an element style");
+ is(applied[0].rule.cssText, "", "Entry 0 should be an empty style");
+ is(applied.length, 1, "Should have 1 rule.");
+ }).then(runNextTest));
+});
+
+addTest(function matchedSelectors() {
+ promiseDone(gWalker.querySelector(gWalker.rootNode, "#test-node").then(node => {
+ return gStyles.getApplied(node, {
+ inherited: true, filter: "user", matchedSelectors: true,
+ });
+ }).then(applied => {
+ is(applied[3].matchedSelectors[0], ".inheritable-rule",
+ "Entry 3 should have a matched selector");
+ is(applied[4].matchedSelectors[0], ".inheritable-rule",
+ "Entry 4 should have a matched selector");
+ }).then(runNextTest));
+});
+
+addTest(function testMediaQuery() {
+ promiseDone(gWalker.querySelector(gWalker.rootNode, "#mediaqueried").then(node => {
+ return gStyles.getApplied(node, {
+ inherited: false, filter: "user", matchedSelectors: true,
+ });
+ }).then(applied => {
+ is(applied[1].rule.type, 1, "Entry 1 is a rule style");
+ is(applied[1].rule.parentRule.type, 4, "Entry 1's parent rule is a media rule");
+ is(applied[1].rule.media[0], "screen", "Entry 1's rule has the expected medium");
+ }).then(runNextTest));
+});
+
+addTest(function cleanup() {
+ gStyles = null;
+ gWalker = null;
+ runNextTest();
+});
+
+ </script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=">Mozilla Bug </a>
+<a id="inspectorContent" target="_blank" href="inspector-styles-data.html">Test Document</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+</body>
+</html>
diff --git a/devtools/server/tests/chrome/test_styles-computed.html b/devtools/server/tests/chrome/test_styles-computed.html
new file mode 100644
index 0000000000..af96869ed8
--- /dev/null
+++ b/devtools/server/tests/chrome/test_styles-computed.html
@@ -0,0 +1,130 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug </title>
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
+ <script type="application/javascript" src="inspector-helpers.js"></script>
+ <script type="application/javascript">
+"use strict";
+
+window.onload = function() {
+ SimpleTest.waitForExplicitFinish();
+ runNextTest();
+};
+
+let gWalker = null;
+let gStyles = null;
+
+addTest(async function setup() {
+ const url = document.getElementById("inspectorContent").href;
+ const { target } = await attachURL(url);
+ const inspector = await target.getFront("inspector");
+ gWalker = inspector.walker;
+ gStyles = await inspector.getPageStyle();
+ runNextTest();
+});
+
+addTest(function testComputed() {
+ promiseDone(
+ gWalker.querySelector(gWalker.rootNode, "#computed-test-node").then(node => {
+ return gStyles.getComputed(node, {});
+ }).then(computed => {
+ // Test a smattering of properties that include some system-defined
+ // props, some props that were defined in this node's stylesheet,
+ // and some default props.
+ is(computed["white-space"].value, "normal", "Default value should appear");
+ is(computed.display.value, "block", "System stylesheet item should appear");
+ is(computed.cursor.value, "crosshair", "Included stylesheet rule should appear");
+ is(computed.color.value, "rgb(255, 0, 0)",
+ "Inherited style attribute should appear");
+ is(computed["font-size"].value, "15px", "Inherited inline rule should appear");
+
+ // We didn't request markMatched, so these shouldn't be set
+ ok(!computed.cursor.matched, "Didn't ask for matched, shouldn't get it");
+ ok(!computed.color.matched, "Didn't ask for matched, shouldn't get it");
+ ok(!computed["font-size"].matched, "Didn't ask for matched, shouldn't get it");
+ }).then(runNextTest)
+ );
+});
+
+addTest(function testComputedUserMatched() {
+ promiseDone(
+ gWalker.querySelector(gWalker.rootNode, "#computed-test-node").then(node => {
+ return gStyles.getComputed(node, { filter: "user", markMatched: true });
+ }).then(computed => {
+ ok(!computed["white-space"].matched, "Default style shouldn't match");
+ ok(!computed.display.matched, "Only user styles should match");
+ ok(computed.cursor.matched, "Asked for matched, should get it");
+ ok(computed.color.matched, "Asked for matched, should get it");
+ ok(computed["font-size"].matched, "Asked for matched, should get it");
+ }).then(runNextTest)
+ );
+});
+
+addTest(function testComputedSystemMatched() {
+ promiseDone(
+ gWalker.querySelector(gWalker.rootNode, "#computed-test-node").then(node => {
+ return gStyles.getComputed(node, { filter: "ua", markMatched: true });
+ }).then(computed => {
+ ok(!computed["white-space"].matched, "Default style shouldn't match");
+ ok(computed.display.matched, "System stylesheets should match");
+ ok(computed.cursor.matched, "Asked for matched, should get it");
+ ok(computed.color.matched, "Asked for matched, should get it");
+ ok(computed["font-size"].matched, "Asked for matched, should get it");
+ }).then(runNextTest)
+ );
+});
+
+addTest(function testComputedUserOnlyMatched() {
+ promiseDone(
+ gWalker.querySelector(gWalker.rootNode, "#computed-test-node").then(node => {
+ return gStyles.getComputed(node, { filter: "user", onlyMatched: true });
+ }).then(computed => {
+ ok(!("white-space" in computed), "Default style shouldn't exist");
+ ok(!("display" in computed), "System stylesheets shouldn't exist");
+ ok(("cursor" in computed), "User items should exist.");
+ ok(("color" in computed), "User items should exist.");
+ ok(("font-size" in computed), "User items should exist.");
+ }).then(runNextTest)
+ );
+});
+
+addTest(function testComputedSystemOnlyMatched() {
+ promiseDone(
+ gWalker.querySelector(gWalker.rootNode, "#computed-test-node").then(node => {
+ return gStyles.getComputed(node, { filter: "ua", onlyMatched: true });
+ }).then(computed => {
+ ok(!("white-space" in computed), "Default style shouldn't exist");
+ ok(("display" in computed), "System stylesheets should exist");
+ ok(("cursor" in computed), "User items should exist.");
+ ok(("color" in computed), "User items should exist.");
+ ok(("font-size" in computed), "User items should exist.");
+ }).then(runNextTest)
+ );
+});
+
+addTest(function cleanup() {
+ gStyles = null;
+ gWalker = null;
+ runNextTest();
+});
+
+ </script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=">Mozilla Bug </a>
+<a id="inspectorContent" target="_blank" href="inspector-styles-data.html">Test Document</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+</body>
+</html>
diff --git a/devtools/server/tests/chrome/test_styles-layout.html b/devtools/server/tests/chrome/test_styles-layout.html
new file mode 100644
index 0000000000..f0441edd13
--- /dev/null
+++ b/devtools/server/tests/chrome/test_styles-layout.html
@@ -0,0 +1,110 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<meta charset="utf-8">
+<title>Test for Bug 1175040 - PageStyleActor.getLayout</title>
+<script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+<link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
+<script type="application/javascript" src="inspector-helpers.js"></script>
+<script type="application/javascript">
+"use strict";
+
+window.onload = function() {
+ SimpleTest.waitForExplicitFinish();
+ runNextTest();
+};
+
+let gWalker = null;
+let gStyles = null;
+
+addTest(async function() {
+ const url = document.getElementById("inspectorContent").href;
+ const { target } = await attachURL(url);
+ const inspector = await target.getFront("inspector");
+ gWalker = inspector.walker;
+ gStyles = await inspector.getPageStyle();
+ runNextTest();
+});
+
+addTest(function() {
+ ok(gStyles.getLayout, "The PageStyleActor has a getLayout method");
+ runNextTest();
+});
+
+addAsyncTest(async function() {
+ const node = await gWalker.querySelector(gWalker.rootNode, "#layout-element");
+ const layout = await gStyles.getLayout(node, {});
+
+ const properties = ["width", "height",
+ "margin-top", "margin-right", "margin-bottom",
+ "margin-left", "padding-top", "padding-right",
+ "padding-bottom", "padding-left", "border-top-width",
+ "border-right-width", "border-bottom-width",
+ "border-left-width", "z-index", "box-sizing", "display",
+ "position"];
+ for (const prop of properties) {
+ ok((prop in layout), "The layout object returned has " + prop);
+ }
+
+ runNextTest();
+});
+
+addAsyncTest(async function() {
+ const node = await gWalker.querySelector(gWalker.rootNode, "#layout-element");
+ const layout = await gStyles.getLayout(node, {});
+
+ const expected = {
+ "box-sizing": "border-box",
+ "position": "absolute",
+ "z-index": "2",
+ "display": "block",
+ "width": 50,
+ "height": 50,
+ "margin-top": "10px",
+ "margin-right": "20px",
+ "margin-bottom": "30px",
+ "margin-left": "0px",
+ };
+
+ for (const name in expected) {
+ is(layout[name], expected[name], "The " + name + " property is correct");
+ }
+
+ runNextTest();
+});
+
+addAsyncTest(async function() {
+ const node = await gWalker.querySelector(gWalker.rootNode,
+ "#layout-auto-margin-element");
+
+ let layout = await gStyles.getLayout(node, {});
+ ok(!("autoMargins" in layout),
+ "By default, getLayout doesn't return auto margins");
+
+ layout = await gStyles.getLayout(node, {autoMargins: true});
+ ok(("autoMargins" in layout),
+ "getLayout does return auto margins when asked to");
+ is(layout.autoMargins.left, "auto", "The left margin is auto");
+ is(layout.autoMargins.right, "auto", "The right margin is auto");
+ ok(!layout.autoMargins.bottom, "The bottom margin is not auto");
+ ok(!layout.autoMargins.top, "The top margin is not auto");
+
+ runNextTest();
+});
+
+addTest(function() {
+ gStyles = gWalker = null;
+ runNextTest();
+});
+
+</script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1175040">Mozilla Bug 1175040</a>
+<a id="inspectorContent" target="_blank" href="inspector-styles-data.html">Test Document</a>
+<p id="display"></p>
+<div id="content" style="display: none"></div>
+<pre id="test">
+</pre>
+</body>
+</html>
diff --git a/devtools/server/tests/chrome/test_styles-matched.html b/devtools/server/tests/chrome/test_styles-matched.html
new file mode 100644
index 0000000000..e99f538663
--- /dev/null
+++ b/devtools/server/tests/chrome/test_styles-matched.html
@@ -0,0 +1,103 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug </title>
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
+ <script type="application/javascript" src="inspector-helpers.js"></script>
+ <script type="application/javascript">
+"use strict";
+
+const CssLogic = require("devtools/shared/inspector/css-logic");
+
+window.onload = function() {
+ SimpleTest.waitForExplicitFinish();
+ runNextTest();
+};
+
+let gWalker = null;
+let gStyles = null;
+let gInspectee = null;
+
+addTest(async function setup() {
+ const url = document.getElementById("inspectorContent").href;
+ const { target, doc } = await attachURL(url);
+ gInspectee = doc;
+ const inspector = await target.getFront("inspector");
+ gWalker = inspector.walker;
+ gStyles = await inspector.getPageStyle();
+ runNextTest();
+});
+
+addTest(function testMatchedStyles() {
+ promiseDone(gWalker.querySelector(gWalker.rootNode, "#matched-test-node").then(node => {
+ return gStyles.getMatchedSelectors(node, "font-size", {});
+ }).then(matched => {
+ is(matched[0].sourceText, "this.style", "First match comes from the element style");
+ is(matched[0].selector, "@element.style", "Element style has a special selector");
+ is(matched[0].value, "10px", "First match has the expected value");
+ is(matched[0].status, CssLogic.STATUS.BEST, "First match is the best match");
+ is(matched[0].rule.type, 100, "First match is an element style");
+ is(matched[0].rule.href, gInspectee.defaultView.location.href,
+ "Node style comes from this document");
+
+ is(matched[1].sourceText, ".column-rule",
+ "Second match comes from a rule");
+ is(matched[1].selector, ".column-rule",
+ "Second match comes from highest line number");
+ is(matched[1].value, "25px", "Second match comes from highest column");
+ is(matched[1].status, CssLogic.STATUS.PARENT_MATCH,
+ "Second match is from the parent");
+ is(matched[1].rule.parentStyleSheet.href, null,
+ "Inline stylesheet shouldn't have an href");
+ is(matched[1].rule.parentStyleSheet.nodeHref, gInspectee.defaultView.location.href,
+ "Inline stylesheet's nodeHref should match the current document");
+ ok(!matched[1].rule.parentStyleSheet.system,
+ "Inline stylesheet shouldn't be a system stylesheet.");
+
+ // matched[2] is only there to test matched[1]; do not need to test
+
+ is(matched[3].value, "15px", "Third match has the expected value");
+ }).then(runNextTest));
+});
+
+addTest(function testSystemStyles() {
+ let testNode = null;
+
+ promiseDone(gWalker.querySelector(gWalker.rootNode, "#matched-test-node").then(node => {
+ testNode = node;
+ return gStyles.getMatchedSelectors(testNode, "display", { filter: "user" });
+ }).then(matched => {
+ is(matched.length, 0, "No user selectors apply to this rule.");
+ return gStyles.getMatchedSelectors(testNode, "display", { filter: "ua" });
+ }).then(matched => {
+ is(matched[0].selector, "div", "Should match system div selector");
+ is(matched[0].value, "block");
+ }).then(runNextTest));
+});
+
+addTest(function cleanup() {
+ gStyles = null;
+ gWalker = null;
+ gInspectee = null;
+ runNextTest();
+});
+
+ </script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=">Mozilla Bug </a>
+<a id="inspectorContent" target="_blank" href="inspector-styles-data.html">Test Document</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+</body>
+</html>
diff --git a/devtools/server/tests/chrome/test_styles-modify.html b/devtools/server/tests/chrome/test_styles-modify.html
new file mode 100644
index 0000000000..e615ec4425
--- /dev/null
+++ b/devtools/server/tests/chrome/test_styles-modify.html
@@ -0,0 +1,110 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug </title>
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
+ <script type="application/javascript" src="inspector-helpers.js"></script>
+ <script type="application/javascript">
+"use strict";
+
+const {isCssPropertyKnown} = require("devtools/server/actors/css-properties");
+
+window.onload = function() {
+ SimpleTest.waitForExplicitFinish();
+ runNextTest();
+};
+
+var gWalker = null;
+var gStyles = null;
+var gInspectee = null;
+
+addAsyncTest(async function setup() {
+ const url = document.getElementById("inspectorContent").href;
+
+ const { target, doc } = await attachURL(url);
+ const inspector = await target.getFront("inspector");
+ gInspectee = doc;
+
+ gWalker = inspector.walker;
+ gStyles = await inspector.getPageStyle();
+
+ runNextTest();
+});
+
+addAsyncTest(async function modifyProperties() {
+ const localNode = gInspectee.querySelector("#inheritable-rule-inheritable-style");
+
+ const node = await gWalker.querySelector(gWalker.rootNode,
+ "#inheritable-rule-inheritable-style");
+
+ const applied = await gStyles.getApplied(node,
+ { inherited: false, filter: "user" });
+
+ const elementStyle = applied[0].rule;
+ is(elementStyle.cssText, localNode.style.cssText, "Got expected css text");
+
+ // Change an existing property...
+ await setProperty(elementStyle, 0, "color", "black");
+ // Create a new property
+ await setProperty(elementStyle, 1, "background-color", "green");
+
+ // Create a new property and then change it immediately.
+ await setProperty(elementStyle, 2, "border", "1px solid black");
+ await setProperty(elementStyle, 2, "border", "2px solid black");
+
+ is(elementStyle.cssText,
+ "color: black; background-color: green; border: 2px solid black;",
+ "Should have expected cssText");
+ is(elementStyle.cssText, localNode.style.cssText,
+ "Local node and style front match.");
+
+ // Remove all the properties
+ await removeProperty(elementStyle, 0, "color");
+ await removeProperty(elementStyle, 0, "background-color");
+ await removeProperty(elementStyle, 0, "border");
+
+ is(elementStyle.cssText, "", "Should have expected cssText");
+ is(elementStyle.cssText, localNode.style.cssText,
+ "Local node and style front match.");
+
+ runNextTest();
+});
+
+async function setProperty(rule, index, name, value) {
+ const changes = rule.startModifyingProperties(isCssPropertyKnown);
+ changes.setProperty(index, name, value);
+ await changes.apply();
+}
+
+async function removeProperty(rule, index, name) {
+ const changes = rule.startModifyingProperties(isCssPropertyKnown);
+ changes.removeProperty(index, name);
+ await changes.apply();
+}
+
+addTest(function cleanup() {
+ gStyles = null;
+ gWalker = null;
+ gInspectee = null;
+ runNextTest();
+});
+
+ </script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=">Mozilla Bug </a>
+<a id="inspectorContent" target="_blank" href="inspector-styles-data.html">Test Document</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+</body>
+</html>
diff --git a/devtools/server/tests/chrome/test_styles-svg.html b/devtools/server/tests/chrome/test_styles-svg.html
new file mode 100644
index 0000000000..b03bc868b7
--- /dev/null
+++ b/devtools/server/tests/chrome/test_styles-svg.html
@@ -0,0 +1,61 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=921191
+Bug 921191 - allow inspection/editing of SVG elements' CSS properties
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug </title>
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
+ <script type="application/javascript" src="inspector-helpers.js"></script>
+ <script type="application/javascript">
+"use strict";
+
+window.onload = function() {
+ SimpleTest.waitForExplicitFinish();
+ runNextTest();
+};
+
+let gWalker = null;
+let gStyles = null;
+
+addTest(async function setup() {
+ const url = document.getElementById("inspectorContent").href;
+ const { target } = await attachURL(url);
+ const inspector = await target.getFront("inspector");
+ gWalker = inspector.walker;
+ gStyles = await inspector.getPageStyle();
+ runNextTest();
+});
+
+addTest(function inheritedUserStyles() {
+ promiseDone(gWalker.querySelector(gWalker.rootNode, "#svgcontent rect").then(node => {
+ return gStyles.getApplied(node, { inherited: true, filter: "user" });
+ }).then(applied => {
+ is(applied.length, 2, "Should have 2 rules");
+ is(applied[1].rule.cssText, "fill: rgb(1, 2, 3);", "cssText is right");
+ }).then(runNextTest));
+});
+
+addTest(function cleanup() {
+ gStyles = null;
+ gWalker = null;
+ runNextTest();
+});
+
+ </script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=921191">Mozilla Bug 921191</a>
+<a id="inspectorContent" target="_blank" href="inspector-styles-data.html">Test Document</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+</body>
+</html>
diff --git a/devtools/server/tests/chrome/test_suspendTimeouts.html b/devtools/server/tests/chrome/test_suspendTimeouts.html
new file mode 100644
index 0000000000..65a168986f
--- /dev/null
+++ b/devtools/server/tests/chrome/test_suspendTimeouts.html
@@ -0,0 +1,20 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1426467
+
+When we use windowUtils.resumeTimeouts to resume timeouts in a window, that call
+should not immediately dispatch `onmessage` handlers for messages from workers.
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Mozilla Bug 1426467</title>
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
+</head>
+<body>
+<pre id="test">
+<script src='test_suspendTimeouts.js'></script>
+</pre>
+</body>
+</html>
diff --git a/devtools/server/tests/chrome/test_suspendTimeouts.js b/devtools/server/tests/chrome/test_suspendTimeouts.js
new file mode 100644
index 0000000000..caea6936af
--- /dev/null
+++ b/devtools/server/tests/chrome/test_suspendTimeouts.js
@@ -0,0 +1,139 @@
+"use strict";
+
+// The debugger uses nsIDOMWindowUtils::suspendTimeouts and ...::resumeTimeouts
+// to ensure that content event handlers do not run while a JavaScript
+// invocation is stepping or paused at a breakpoint. If a worker thread sends
+// messages to the content while the content is paused, those messages must not
+// run until the JavaScript invocation interrupted by the debugger has completed.
+//
+// Bug 1426467 is that calling nsIDOMWindowUtils::resumeTimeouts actually
+// delivers deferred messages itself, calling the content's 'onmessage' handler.
+// But the debugger calls suspend/resume around each individual interruption of
+// the debuggee -- each step, say -- meaning that hitting the "step into" button
+// causes you to step from the debuggee directly into an onmessage handler,
+// since the onmessage handler is the next function call the debugger sees.
+//
+// In other words, delivering deferred messages from resumeTimeouts, as it is
+// used by the debugger, breaks the run-to-completion rule. They must not be
+// delivered until after the JavaScript invocation at hand is complete. That's
+// what this test checks.
+//
+// For this test to detect the bug, the following steps must take place in
+// order:
+//
+// 1) The content page must call suspendTimeouts.
+// 2) A runnable conveying a message from the worker thread must attempt to
+// deliver the message, see that the content page has suspended such things,
+// and hold the message for later delivery.
+// 3) The content page must call resumeTimeouts.
+//
+// In a correct implementation, the message from the worker thread is delivered
+// only after the main thread returns to the event loop after calling
+// resumeTimeouts in step 3). In the buggy implementation, the onmessage handler
+// is called directly from the call to resumeTimeouts, so that the onmessage
+// handlers run in the midst of whatever JavaScript invocation resumed timeouts
+// (say, stepping in the debugger), in violation of the run-to-completion rule.
+//
+// In this specific bug, the handlers are called from resumeTimeouts, but
+// really, running them any time before that invocation returns to the main
+// event loop would be a bug.
+//
+// Posting the message and calling resumeTimeouts take place in different
+// threads, but if 2) and 3) don't occur in that order, the worker's message
+// will never be delayed and the test will pass spuriously. But the worker
+// can't communicate with the content page directly, to let it know that it
+// should proceed with step 3): the purpose of suspendTimeouts is to pause
+// all such communication.
+//
+// So instead, the content page creates a MessageChannel, and passes one
+// MessagePort to the worker and the other to this mochitest (which has its
+// own window, separate from the one calling suspendTimeouts). The worker
+// notifies the mochitest when it has posted the message, and then the
+// mochitest calls into the content to carry out step 3).
+
+// To help you follow all the callbacks and event handlers, this code pulls out
+// event handler functions so that control flows from top to bottom.
+
+window.onload = function() {
+ // This mochitest is not complete until we call SimpleTest.finish. Don't just
+ // exit as soon as we return to the main event loop.
+ SimpleTest.waitForExplicitFinish();
+
+ const iframe = document.createElement("iframe");
+ iframe.src =
+ "http://mochi.test:8888/chrome/devtools/server/tests/chrome/suspendTimeouts_content.html";
+ iframe.onload = iframe_onload_handler;
+ document.body.appendChild(iframe);
+
+ function iframe_onload_handler() {
+ const content = iframe.contentWindow.wrappedJSObject;
+
+ const windowUtils = iframe.contentWindow.windowUtils;
+
+ // Hand over the suspend and resume functions to the content page, along
+ // with some testing utilities.
+ content.suspendTimeouts = function() {
+ SimpleTest.info("test_suspendTimeouts", "calling suspendTimeouts");
+ windowUtils.suspendTimeouts();
+ };
+ content.resumeTimeouts = function() {
+ windowUtils.resumeTimeouts();
+ SimpleTest.info("test_suspendTimeouts", "resumeTimeouts called");
+ };
+ content.info = function(message) {
+ SimpleTest.info("suspendTimeouts_content.js", message);
+ };
+ content.ok = SimpleTest.ok;
+ content.finish = finish;
+
+ SimpleTest.info(
+ "Disappointed with National Tautology Day? Well, it is what it is."
+ );
+
+ // Once the worker has sent a message to its parent (which should get delayed),
+ // it sends us a message directly on this channel.
+ const workerPort = content.create_channel();
+ workerPort.onmessage = handle_worker_echo;
+
+ // Have content send the worker a message that it should echo back to both
+ // content and us. The echo to content should get delayed; the echo to us
+ // should cause our handle_worker_echo to be called.
+ content.start_worker();
+
+ function handle_worker_echo({ data }) {
+ info(`mochitest received message from worker: ${data}`);
+
+ // As it turns out, it's not correct to assume that, if the worker posts a
+ // message to its parent via the global `postMessage` function, and then
+ // posts a message to the mochitest via the MessagePort, those two
+ // messages will be delivered in the order they were sent.
+ //
+ // - Messages sent via the worker's global's postMessage go through two
+ // ThrottledEventQueues (one in the worker, and one on the parent), and
+ // eventually find their way into the thread's primary event queue,
+ // which is a PrioritizedEventQueue.
+ //
+ // - Messages sent via a MessageChannel whose ports are owned by different
+ // threads are passed as IPDL messages.
+ //
+ // There's basically no reliable way to ensure that delivery to content
+ // has been attempted and the runnable deferred; there are too many
+ // variables affecting the order in which things are processed. Delaying
+ // for a second is the best I could think of.
+ //
+ // Fortunately, this tactic failing can only cause spurious test passes
+ // (the runnable never gets deferred, so things work by accident), not
+ // spurious failures. Without some kind of trustworthy notification that
+ // the runnable has been deferred, perhaps via some special white-box
+ // testing API, we can't do better.
+ setTimeout(() => {
+ content.resume_timeouts();
+ }, 1000);
+ }
+
+ function finish(message) {
+ SimpleTest.info("suspendTimeouts_content.js", "called finish");
+ SimpleTest.finish();
+ }
+ }
+};
diff --git a/devtools/server/tests/chrome/test_unsafeDereference.html b/devtools/server/tests/chrome/test_unsafeDereference.html
new file mode 100644
index 0000000000..72069572bd
--- /dev/null
+++ b/devtools/server/tests/chrome/test_unsafeDereference.html
@@ -0,0 +1,53 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=837723
+
+When we use Debugger.Object.prototype.unsafeDereference to get a non-D.O
+reference to a content object in chrome, that reference should be via an
+xray wrapper.
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Mozilla Bug 837723</title>
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
+</head>
+<body>
+<pre id="test">
+<script>
+"use strict";
+
+const {addDebuggerToGlobal} = ChromeUtils.import("resource://gre/modules/jsdebugger.jsm");
+addDebuggerToGlobal(this);
+
+window.onload = function() {
+ SimpleTest.waitForExplicitFinish();
+
+ const iframe = document.createElement("iframe");
+ iframe.src = "http://mochi.test:8888/chrome/devtools/server/tests/chrome/nonchrome_unsafeDereference.html";
+
+ iframe.onload = function() {
+ const dbg = new Debugger();
+ const contentDO = dbg.addDebuggee(iframe.contentWindow);
+ const xhrDesc = contentDO.getOwnPropertyDescriptor("xhr");
+
+ isnot(xhrDesc, undefined, "xhr should be visible as property of content global");
+ isnot(xhrDesc.value, undefined, "xhr should have a value");
+
+ const xhr = xhrDesc.value.unsafeDereference();
+
+ is(typeof xhr, "object", "we should be able to deference xhr's value's D.O");
+ is(xhr.timeout, 1742, "chrome should see the xhr's 'timeout' property");
+ is(xhr.expando, undefined, "chrome should not see the xhr's 'expando' property");
+
+ SimpleTest.finish();
+ };
+
+ document.body.appendChild(iframe);
+};
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/devtools/server/tests/chrome/test_webconsole-node-grip.html b/devtools/server/tests/chrome/test_webconsole-node-grip.html
new file mode 100644
index 0000000000..7b0b2d1624
--- /dev/null
+++ b/devtools/server/tests/chrome/test_webconsole-node-grip.html
@@ -0,0 +1,68 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>DOMNode Object actor test</title>
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
+ <script type="application/javascript" src="webconsole-helpers.js"></script>
+ <script>
+"use strict";
+
+const TEST_URL = "data:text/html,<html><body>Hello</body></html>";
+
+window.onload = async function() {
+ SimpleTest.waitForExplicitFinish();
+
+ try {
+ const {
+ webConsoleFront,
+ } = await attachURL(TEST_URL);
+ await testNotInTreeElementNode(webConsoleFront);
+ await testInTreeElementNode(webConsoleFront);
+ await testNotInTreeTextNode(webConsoleFront);
+ await testInTreeTextNode(webConsoleFront);
+ } catch (e) {
+ ok(false, `Error thrown: ${e.message}`);
+ }
+ SimpleTest.finish();
+};
+
+async function testNotInTreeElementNode(webConsoleFront) {
+ info("Testing isConnected property on a ElementNode not in the DOM tree");
+ const {result} = await webConsoleFront.evaluateJSAsync("document.createElement(\"div\")");
+ is(result.getGrip().preview.isConnected, false,
+ "isConnected is false since we only created the element");
+}
+
+async function testInTreeElementNode(webConsoleFront) {
+ info("Testing isConnected property on a ElementNode in the DOM tree");
+ const {result} = await webConsoleFront.evaluateJSAsync("document.body");
+ is(result.getGrip().preview.isConnected, true,
+ "isConnected is true as expected, since the element was retrieved from the DOM tree");
+}
+
+async function testNotInTreeTextNode(webConsoleFront) {
+ info("Testing isConnected property on a TextNode not in the DOM tree");
+ const {result} = await webConsoleFront.evaluateJSAsync("document.createTextNode(\"Hello\")");
+ is(result.getGrip().preview.isConnected, false,
+ "isConnected is false since we only created the element");
+}
+
+async function testInTreeTextNode(webConsoleFront) {
+ info("Testing isConnected property on a TextNode in the DOM tree");
+ const {result} = await webConsoleFront.evaluateJSAsync("document.body.firstChild");
+ is(result.getGrip().preview.isConnected, true,
+ "isConnected is true as expected, since the element was retrieved from the DOM tree");
+}
+
+ </script>
+</head>
+<body>
+ <p id="display"></p>
+ <div id="content" style="display: none">
+ </div>
+ <pre id="test">
+ </pre>
+</body>
+</html>
diff --git a/devtools/server/tests/chrome/test_webextension-addon-debugging-connect.html b/devtools/server/tests/chrome/test_webextension-addon-debugging-connect.html
new file mode 100644
index 0000000000..d983d1bbb8
--- /dev/null
+++ b/devtools/server/tests/chrome/test_webextension-addon-debugging-connect.html
@@ -0,0 +1,80 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+Bug 1302702 - Test connect to a webextension addon
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Mozilla Bug</title>
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="chrome://mochikit/content/tests/SimpleTest/ExtensionTestUtils.js"></script>
+ <script src="webextension-helpers.js"></script>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
+</head>
+<body>
+<pre id="test">
+<script type="application/javascript">
+"use strict";
+
+add_task(async function test_webextension_addon_debugging_connect() {
+ // Install and start a test webextension.
+ const extension = ExtensionTestUtils.loadExtension({
+ useAddonManager: "temporary",
+ background: function() {
+ /* eslint-disable no-undef */
+ browser.test.log("background script executed");
+ browser.test.sendMessage("background page ready");
+ /* eslint-enable no-undef */
+ },
+ });
+ await extension.startup();
+ await extension.awaitMessage("background page ready");
+
+ // Connect a DevToolsClient.
+ const transport = DevToolsServer.connectPipe();
+ const client = new DevToolsClient(transport);
+ await client.connect();
+
+ // List addons and assertions on the expected addon actor.
+ const addonDescriptor = await client.mainRoot.getAddon({ id: extension.id });
+ ok(addonDescriptor, "The expected webextension addon actor has been found");
+
+ const addonTarget = await addonDescriptor.getTarget();
+ ok(addonTarget, "The expected webextension target addon actor has been found");
+
+ // Connect to the target addon actor and wait for the updated list of frames.
+ is(addonDescriptor.targetForm.isOOP, true,
+ "Got the expected oop mode in the webextension actor form");
+ const frames = await waitForFramesUpdated(addonTarget);
+ const backgroundPageFrame = frames.filter((frame) => {
+ return frame.url && frame.url.endsWith("/_generated_background_page.html");
+ }).pop();
+ is(backgroundPageFrame.addonID, extension.id, "Got an extension frame");
+
+ // When running in oop mode we can explicitly attach the thread without locking
+ // the main process.
+ const threadFront = await addonTarget.attachThread();
+
+ ok(threadFront, "Got a threadFront for the target addon");
+ is(threadFront.paused, false, "The addon threadActor isn't paused");
+
+ const waitTransportClosed = new Promise(resolve => {
+ client._transport.once("close", resolve);
+ });
+
+ await addonTarget.destroy();
+ await client.close();
+
+ // Check that if we close the debugging client without uninstalling the addon,
+ // the webextension debugging actor should release the debug browser.
+ await waitTransportClosed;
+ is(ExtensionParent.DebugUtils.debugBrowserPromises.size, 0,
+ "The debug browser has been released when the RDP connection has been closed");
+
+ await extension.unload();
+});
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/devtools/server/tests/chrome/test_webextension-addon-debugging-reload.html b/devtools/server/tests/chrome/test_webextension-addon-debugging-reload.html
new file mode 100644
index 0000000000..420cb0df5e
--- /dev/null
+++ b/devtools/server/tests/chrome/test_webextension-addon-debugging-reload.html
@@ -0,0 +1,122 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+Bug 1302702 - Test connect to a webextension addon
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Mozilla Bug</title>
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="webextension-helpers.js"></script>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
+</head>
+<body>
+<pre id="test">
+<script type="application/javascript">
+"use strict";
+
+// NOTE: This test installs the webextension addon using the addon manager, so that
+// it can be reloaded using the same actor RDP method used by the developer tools.
+add_task(async function test_webextension_addon_debugging_reload() {
+ const addonID = "test-webext-debugging-reload@test.mozilla.com";
+ const addonFile = generateWebExtensionXPI({
+ manifest: {
+ applications: {
+ gecko: {id: addonID},
+ },
+ background: { scripts: ["background.js"] },
+ },
+ files: {
+ "background.js": function() {
+ console.log("background page loaded");
+ },
+ },
+ });
+
+ const {addon} = await promiseInstallFile(addonFile);
+ await promiseWebExtensionStartup();
+
+ const addonTarget = await attachAddon(addonID);
+ ok(addonTarget, "Got an addon target");
+
+ const matchBackgroundPageFrame = (data) => {
+ if (data.frames) {
+ const frameFound = data.frames.filter((frame) => {
+ return frame.url && frame.url.endsWith("_generated_background_page.html");
+ }).pop();
+
+ return !!frameFound;
+ }
+
+ return false;
+ };
+
+ const matchFrameSelected = (data) => {
+ return "selected" in data;
+ };
+
+ // Test the target addon actor reload behavior.
+
+ let waitFramesUpdated = waitForFramesUpdated(addonTarget, matchBackgroundPageFrame);
+ let collectFrameSelected = collectFrameUpdates(addonTarget, matchFrameSelected);
+
+ is(ExtensionParent.DebugUtils.debugBrowserPromises.size, 1,
+ "The expected number of debug browser has been created by the addon actor");
+
+ info("Reloading the target addon");
+ reloadAddon(addonTarget, addonID);
+ await promiseWebExtensionStartup();
+
+ is(ExtensionParent.DebugUtils.debugBrowserPromises.size, 1,
+ "The number of debug browser has not been changed after an addon reload");
+
+ let frames = await waitFramesUpdated;
+ const selectedFrame = collectFrameSelected().pop();
+
+ is(selectedFrame.selected, frames[0].id, "The new background page has been selected");
+ is(addonTarget.url, frames[0].url,
+ "The addon target has the url of the selected frame");
+
+ // Test the target addon actor once reloaded twice.
+
+ waitFramesUpdated = waitForFramesUpdated(addonTarget, matchBackgroundPageFrame);
+ collectFrameSelected = collectFrameUpdates(addonTarget, matchFrameSelected);
+
+ info("Reloading the target addon again");
+ reloadAddon(addonTarget, addonID);
+ await promiseWebExtensionStartup();
+
+ frames = await waitFramesUpdated;
+ const newSelectedFrame = collectFrameSelected().pop();
+
+ ok(newSelectedFrame !== selectedFrame,
+ "The new selected frame is different from the previous on");
+ is(newSelectedFrame.selected, frames[0].id,
+ "The new background page has been selected on the second reload");
+ is(addonTarget.url, frames[0].url,
+ "The addon target has the url of the selected frame");
+
+ // Expect the target to be automatically closed when the addon
+ // is finally uninstalled.
+
+ const {client} = addonTarget;
+ const waitDebuggingClientClosed = new Promise(resolve => {
+ addonTarget.once("close", resolve);
+ });
+
+ const waitShutdown = promiseWebExtensionShutdown();
+ addon.uninstall();
+ await waitShutdown;
+
+ info("Waiting the addon target to be closed on addon uninstall");
+ await waitDebuggingClientClosed;
+
+ // Debugging client has to be closed explicitly when
+ // the target has been created as remote.
+ await client.close();
+});
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/devtools/server/tests/chrome/webconsole-helpers.js b/devtools/server/tests/chrome/webconsole-helpers.js
new file mode 100644
index 0000000000..4236cba16b
--- /dev/null
+++ b/devtools/server/tests/chrome/webconsole-helpers.js
@@ -0,0 +1,58 @@
+/* exported attachURL, evaluateJS */
+"use strict";
+
+const { require } = ChromeUtils.import("resource://devtools/shared/Loader.jsm");
+const { DevToolsServer } = require("devtools/server/devtools-server");
+const { TargetFactory } = require("devtools/client/framework/target");
+
+const Services = require("Services");
+
+// Always log packets when running tests.
+Services.prefs.setBoolPref("devtools.debugger.log", true);
+SimpleTest.registerCleanupFunction(function() {
+ Services.prefs.clearUserPref("devtools.debugger.log");
+});
+
+if (!DevToolsServer.initialized) {
+ DevToolsServer.init();
+ DevToolsServer.registerAllActors();
+ SimpleTest.registerCleanupFunction(function() {
+ DevToolsServer.destroy();
+ });
+}
+
+/**
+ * Open a tab, load the url, find the tab with the devtools server,
+ * and attach the console to it.
+ *
+ * @param {string} url : url to navigate to
+ * @return {Promise} Promise resolving when the console is attached.
+ * The Promise resolves with an object containing :
+ * - tab: the attached tab
+ * - targetFront: the target front
+ * - webConsoleFront: the console front
+ * - cleanup: a generator function which can be called to close
+ * the opened tab and disconnect its devtools client.
+ */
+async function attachURL(url) {
+ const tab = await addTab(url);
+ const target = await TargetFactory.forTab(tab);
+ await target.attach();
+ const webConsoleFront = await target.getFront("console");
+ return {
+ webConsoleFront,
+ };
+}
+
+/**
+ * Naive implementaion of addTab working from a mochitest-chrome test.
+ */
+async function addTab(url) {
+ const { gBrowser } = Services.wm.getMostRecentWindow("navigator:browser");
+ const {
+ BrowserTestUtils,
+ } = require("resource://testing-common/BrowserTestUtils.jsm");
+ const tab = (gBrowser.selectedTab = BrowserTestUtils.addTab(gBrowser, url));
+ await BrowserTestUtils.browserLoaded(tab.linkedBrowser);
+ return tab;
+}
diff --git a/devtools/server/tests/chrome/webextension-helpers.js b/devtools/server/tests/chrome/webextension-helpers.js
new file mode 100644
index 0000000000..98f8efd900
--- /dev/null
+++ b/devtools/server/tests/chrome/webextension-helpers.js
@@ -0,0 +1,138 @@
+/* exported attachAddon, waitForFramesUpdated, reloadAddon,
+ collectFrameUpdates, generateWebExtensionXPI, promiseInstallFile,
+ promiseWebExtensionStartup, promiseWebExtensionShutdown
+ */
+
+"use strict";
+
+const { require, loader } = ChromeUtils.import(
+ "resource://devtools/shared/Loader.jsm"
+);
+const { DevToolsClient } = require("devtools/client/devtools-client");
+const { DevToolsServer } = require("devtools/server/devtools-server");
+
+const {
+ AddonTestUtils,
+} = require("resource://testing-common/AddonTestUtils.jsm");
+const {
+ ExtensionTestCommon,
+} = require("resource://testing-common/ExtensionTestCommon.jsm");
+
+loader.lazyImporter(
+ this,
+ "ExtensionParent",
+ "resource://gre/modules/ExtensionParent.jsm"
+);
+
+// Initialize a minimal DevToolsServer and connect to the webextension addon actor.
+if (!DevToolsServer.initialized) {
+ DevToolsServer.init();
+ DevToolsServer.registerAllActors();
+ SimpleTest.registerCleanupFunction(function() {
+ DevToolsServer.destroy();
+ });
+}
+
+SimpleTest.registerCleanupFunction(function() {
+ const { hiddenXULWindow } = ExtensionParent.DebugUtils;
+ const debugBrowserMapSize =
+ ExtensionParent.DebugUtils.debugBrowserPromises.size;
+
+ if (debugBrowserMapSize > 0) {
+ is(
+ debugBrowserMapSize,
+ 0,
+ "ExtensionParent DebugUtils debug browsers have not been released"
+ );
+ }
+
+ if (hiddenXULWindow) {
+ ok(
+ false,
+ "ExtensionParent DebugUtils hiddenXULWindow has not been destroyed"
+ );
+ }
+});
+
+// Test helpers related to the webextensions debugging RDP actors.
+
+function waitForFramesUpdated(target, matchFn) {
+ return new Promise(resolve => {
+ const listener = data => {
+ if (typeof matchFn === "function" && !matchFn(data)) {
+ return;
+ } else if (!data.frames) {
+ return;
+ }
+
+ target.off("frameUpdate", listener);
+ resolve(data.frames);
+ };
+ target.on("frameUpdate", listener);
+ });
+}
+
+function collectFrameUpdates({ client }, matchFn) {
+ const collected = [];
+
+ const listener = data => {
+ if (matchFn(data)) {
+ collected.push(data);
+ }
+ };
+
+ client.on("frameUpdate", listener);
+ let unsubscribe = () => {
+ unsubscribe = null;
+ client.off("frameUpdate", listener);
+ return collected;
+ };
+
+ SimpleTest.registerCleanupFunction(function() {
+ if (unsubscribe) {
+ unsubscribe();
+ }
+ });
+
+ return unsubscribe;
+}
+
+async function attachAddon(addonId) {
+ const transport = DevToolsServer.connectPipe();
+ const client = new DevToolsClient(transport);
+
+ await client.connect();
+
+ const addonDescriptor = await client.mainRoot.getAddon({ id: addonId });
+ const addonTarget = await addonDescriptor.getTarget();
+
+ if (!addonTarget) {
+ client.close();
+ throw new Error(`No WebExtension Actor found for ${addonId}`);
+ }
+
+ return addonTarget;
+}
+
+async function reloadAddon({ client }, addonId) {
+ const addonTargetFront = await client.mainRoot.getAddon({ id: addonId });
+
+ if (!addonTargetFront) {
+ client.close();
+ throw new Error(`No WebExtension Actor found for ${addonId}`);
+ }
+
+ await addonTargetFront.reload();
+}
+
+// Test helpers related to the AddonManager.
+
+function generateWebExtensionXPI(extDetails) {
+ return ExtensionTestCommon.generateXPI(extDetails);
+}
+
+let {
+ promiseInstallFile,
+ promiseWebExtensionStartup,
+ promiseWebExtensionShutdown,
+} = AddonTestUtils;