summaryrefslogtreecommitdiffstats
path: root/devtools/server/tests/chrome
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
commit26a029d407be480d791972afb5975cf62c9360a6 (patch)
treef435a8308119effd964b339f76abb83a57c29483 /devtools/server/tests/chrome
parentInitial commit. (diff)
downloadfirefox-26a029d407be480d791972afb5975cf62c9360a6.tar.xz
firefox-26a029d407be480d791972afb5975cf62c9360a6.zip
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-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.toml150
-rw-r--r--devtools/server/tests/chrome/doc_Debugger.Source.prototype.introductionType.xhtml7
-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/align-content.mjs92
-rw-r--r--devtools/server/tests/chrome/inactive-property-helper/border-image.mjs162
-rw-r--r--devtools/server/tests/chrome/inactive-property-helper/cue-pseudo-element.mjs371
-rw-r--r--devtools/server/tests/chrome/inactive-property-helper/first-letter-pseudo-element.mjs32
-rw-r--r--devtools/server/tests/chrome/inactive-property-helper/first-line-pseudo-element.mjs50
-rw-r--r--devtools/server/tests/chrome/inactive-property-helper/flex-grid-item-properties.mjs229
-rw-r--r--devtools/server/tests/chrome/inactive-property-helper/float.mjs76
-rw-r--r--devtools/server/tests/chrome/inactive-property-helper/gap.mjs133
-rw-r--r--devtools/server/tests/chrome/inactive-property-helper/grid-container-properties.mjs43
-rw-r--r--devtools/server/tests/chrome/inactive-property-helper/grid-with-absolute-properties.mjs71
-rw-r--r--devtools/server/tests/chrome/inactive-property-helper/highlight-pseudo-elements.mjs155
-rw-r--r--devtools/server/tests/chrome/inactive-property-helper/margin-padding.mjs260
-rw-r--r--devtools/server/tests/chrome/inactive-property-helper/max-min-width-height.mjs366
-rw-r--r--devtools/server/tests/chrome/inactive-property-helper/multicol-container-properties.mjs39
-rw-r--r--devtools/server/tests/chrome/inactive-property-helper/place-items-content.mjs159
-rw-r--r--devtools/server/tests/chrome/inactive-property-helper/placeholder-pseudo-element.mjs122
-rw-r--r--devtools/server/tests/chrome/inactive-property-helper/positioned.mjs82
-rw-r--r--devtools/server/tests/chrome/inactive-property-helper/scroll-padding.mjs159
-rw-r--r--devtools/server/tests/chrome/inactive-property-helper/table-cell.mjs21
-rw-r--r--devtools/server/tests/chrome/inactive-property-helper/table.mjs28
-rw-r--r--devtools/server/tests/chrome/inactive-property-helper/text-overflow.mjs92
-rw-r--r--devtools/server/tests/chrome/inactive-property-helper/text-wrap.mjs86
-rw-r--r--devtools/server/tests/chrome/inactive-property-helper/vertical-align.mjs56
-rw-r--r--devtools/server/tests/chrome/inactive-property-helper/width-height-ruby.mjs147
-rw-r--r--devtools/server/tests/chrome/inspector-delay-image-response.sjs46
-rw-r--r--devtools/server/tests/chrome/inspector-eyedropper.html20
-rw-r--r--devtools/server/tests/chrome/inspector-helpers.js133
-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.js72
-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.js75
-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.elementAttribute.html159
-rw-r--r--devtools/server/tests/chrome/test_Debugger.Source.prototype.introductionScript.html96
-rw-r--r--devtools/server/tests/chrome/test_Debugger.Source.prototype.introductionType.html159
-rw-r--r--devtools/server/tests/chrome/test_animation-type-longhand.html42
-rw-r--r--devtools/server/tests/chrome/test_css-logic-specificity.html84
-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.html79
-rw-r--r--devtools/server/tests/chrome/test_executeInGlobal-outerized_this.html73
-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-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.html124
-rw-r--r--devtools/server/tests/chrome/test_inspector-mutations-attr.html169
-rw-r--r--devtools/server/tests/chrome/test_inspector-mutations-events.html187
-rw-r--r--devtools/server/tests/chrome/test_inspector-mutations-value.html163
-rw-r--r--devtools/server/tests/chrome/test_inspector-pick-color.html94
-rw-r--r--devtools/server/tests/chrome/test_inspector-pseudoclass-lock.html185
-rw-r--r--devtools/server/tests/chrome/test_inspector-reload.html90
-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.html60
-rw-r--r--devtools/server/tests/chrome/test_inspector-search-front.html163
-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.html142
-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.html44
-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.html128
-rw-r--r--devtools/server/tests/chrome/test_styles-applied.html155
-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.html110
-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.html66
-rw-r--r--devtools/server/tests/chrome/webconsole-helpers.js54
111 files changed, 9434 insertions, 0 deletions
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.toml b/devtools/server/tests/chrome/chrome.toml
new file mode 100644
index 0000000000..b67b1ee971
--- /dev/null
+++ b/devtools/server/tests/chrome/chrome.toml
@@ -0,0 +1,150 @@
+[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",
+ "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",
+ "inactive-property-helper/*.mjs",
+]
+
+["test_Debugger.Script.prototype.global.html"]
+
+["test_Debugger.Source.prototype.elementAttribute.html"]
+
+["test_Debugger.Source.prototype.introductionScript.html"]
+
+["test_Debugger.Source.prototype.introductionType.html"]
+
+["test_animation-type-longhand.html"]
+
+["test_css-logic-specificity.html"]
+
+["test_css-logic.html"]
+
+["test_css-properties.html"]
+
+["test_device.html"]
+
+["test_executeInGlobal-outerized_this.html"]
+
+["test_highlighter_paused_debugger.html"]
+
+["test_inspector-changeattrs.html"]
+
+["test_inspector-changevalue.html"]
+
+["test_inspector-display-type.html"]
+
+["test_inspector-duplicate-node.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-scroll-into-view.html"]
+
+["test_inspector-search-front.html"]
+
+["test_inspector-template.html"]
+
+["test_inspector_getImageData-wait-for-load.html"]
+
+["test_inspector_getImageData.html"]
+
+["test_inspector_getImageDataFromURL.html"]
+
+["test_inspector_getNodeFromActor.html"]
+
+["test_inspector_getOffsetParent.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-body.html"]
+
+["test_overflowing-children.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_suspendTimeouts.html"]
+
+["test_unsafeDereference.html"]
+
+["test_webconsole-node-grip.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/hello-actor.js b/devtools/server/tests/chrome/hello-actor.js
new file mode 100644
index 0000000000..eabb4a6773
--- /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("resource://devtools/shared/protocol.js");
+
+const helloSpec = protocol.generateActorSpec({
+ typeName: "helloActor",
+
+ methods: {
+ count: {
+ request: {},
+ response: { count: protocol.RetVal("number") },
+ },
+ },
+});
+
+class HelloActor extends protocol.Actor {
+ constructor(conn) {
+ super(conn, helloSpec);
+ this.counter = 0;
+ }
+
+ count() {
+ 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/align-content.mjs b/devtools/server/tests/chrome/inactive-property-helper/align-content.mjs
new file mode 100644
index 0000000000..a871081fad
--- /dev/null
+++ b/devtools/server/tests/chrome/inactive-property-helper/align-content.mjs
@@ -0,0 +1,92 @@
+/* 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/border-image.mjs b/devtools/server/tests/chrome/inactive-property-helper/border-image.mjs
new file mode 100644
index 0000000000..85c57418a4
--- /dev/null
+++ b/devtools/server/tests/chrome/inactive-property-helper/border-image.mjs
@@ -0,0 +1,162 @@
+/* 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 `border-image` test cases.
+export default [
+ {
+ info: "border-image is active on another element then a table element or internal table element where border-collapse is not set to collapse",
+ property: "border-image",
+ tagName: "div",
+ rules: ["div { border-image: linear-gradient(red, yellow) 10; }"],
+ isActive: true,
+ },
+ {
+ info: "border-image is active on another element then a table element or internal table element where border-collapse is set to collapse",
+ property: "border-image",
+ tagName: "div",
+ rules: [
+ "div { border-image: linear-gradient(red, yellow) 10; border-collapse: collapse;}",
+ ],
+ isActive: true,
+ },
+ {
+ info: "border-image is active on a td element with no table parent and the browser is not crashing",
+ property: "border-image",
+ tagName: "td",
+ rules: [
+ "td { border-image: linear-gradient(red, yellow) 10; border-collapse: collapse;}",
+ ],
+ isActive: true,
+ },
+ createTableElementsToTestBorderImage({
+ useDivTagWithDisplayTableStyle: false,
+ borderCollapse: true,
+ borderCollapsePropertyIsInherited: false,
+ isActive: true,
+ }),
+ createTableElementsToTestBorderImage({
+ useDivTagWithDisplayTableStyle: false,
+ borderCollapse: false,
+ borderCollapsePropertyIsInherited: false,
+ isActive: true,
+ }),
+ createTableElementsToTestBorderImage({
+ useDivTagWithDisplayTableStyle: false,
+ borderCollapse: true,
+ borderCollapsePropertyIsInherited: true,
+ isActive: false,
+ }),
+ createTableElementsToTestBorderImage({
+ useDivTagWithDisplayTableStyle: false,
+ borderCollapse: false,
+ borderCollapsePropertyIsInherited: true,
+ isActive: true,
+ }),
+ createTableElementsToTestBorderImage({
+ useDivTagWithDisplayTableStyle: true,
+ borderCollapse: true,
+ borderCollapsePropertyIsInherited: false,
+ isActive: true,
+ }),
+ createTableElementsToTestBorderImage({
+ useDivTagWithDisplayTableStyle: true,
+ borderCollapse: false,
+ borderCollapsePropertyIsInherited: false,
+ isActive: true,
+ }),
+ createTableElementsToTestBorderImage({
+ useDivTagWithDisplayTableStyle: true,
+ borderCollapse: true,
+ borderCollapsePropertyIsInherited: true,
+ isActive: false,
+ }),
+ createTableElementsToTestBorderImage({
+ useDivTagWithDisplayTableStyle: true,
+ borderCollapse: false,
+ borderCollapsePropertyIsInherited: true,
+ isActive: true,
+ }),
+];
+
+/**
+ * @param {Object} testParameters
+ * @param {bool} testParameters.useDivTagWithDisplayTableStyle use generic divs using display property instead of actual table/tr/td tags
+ * @param {bool} testParameters.borderCollapse is `border-collapse` property set to `collapse` ( instead of `separate`)
+ * @param {bool} testParameters.borderCollapsePropertyIsInherited should the border collapse property be inherited from the table parent (instead of directly set on the internal table element)
+ * @param {bool} testParameters.isActive is the border-image property actve on the element
+ * @returns
+ */
+function createTableElementsToTestBorderImage({
+ useDivTagWithDisplayTableStyle,
+ borderCollapse,
+ borderCollapsePropertyIsInherited,
+ isActive,
+}) {
+ return {
+ info: `border-image is ${
+ isActive ? "active" : "inactive"
+ } on an internal table element where border-collapse is${
+ borderCollapse ? "" : " not"
+ } set to collapse${
+ borderCollapsePropertyIsInherited
+ ? " by being inherited from its table parent"
+ : ""
+ } when the table and its internal elements are ${
+ useDivTagWithDisplayTableStyle ? "not " : ""
+ }using semantic tags (table, tr, td, ...)`,
+ property: "border-image",
+ createTestElement: rootNode => {
+ const table = useDivTagWithDisplayTableStyle
+ ? document.createElement("div")
+ : document.createElement("table");
+ if (useDivTagWithDisplayTableStyle) {
+ table.style.display = "table";
+ }
+ if (borderCollapsePropertyIsInherited) {
+ table.style.borderCollapse = `${
+ borderCollapse ? "collapse" : "separate"
+ }`;
+ }
+ rootNode.appendChild(table);
+
+ const tbody = useDivTagWithDisplayTableStyle
+ ? document.createElement("div")
+ : document.createElement("tbody");
+ if (useDivTagWithDisplayTableStyle) {
+ tbody.style.display = "table-row-group";
+ }
+ table.appendChild(tbody);
+
+ const tr = useDivTagWithDisplayTableStyle
+ ? document.createElement("div")
+ : document.createElement("tr");
+ if (useDivTagWithDisplayTableStyle) {
+ tr.style.display = "table-row";
+ }
+ tbody.appendChild(tr);
+
+ const td = useDivTagWithDisplayTableStyle
+ ? document.createElement("div")
+ : document.createElement("td");
+ if (useDivTagWithDisplayTableStyle) {
+ td.style.display = "table-cell";
+ td.classList.add("td");
+ }
+ tr.appendChild(td);
+
+ return td;
+ },
+ rules: [
+ `td, .td {
+ border-image: linear-gradient(red, yellow) 10;
+ ${
+ !borderCollapsePropertyIsInherited
+ ? `border-collapse: ${borderCollapse ? "collapse" : "separate"};`
+ : ""
+ }
+ }`,
+ ],
+ isActive,
+ };
+}
diff --git a/devtools/server/tests/chrome/inactive-property-helper/cue-pseudo-element.mjs b/devtools/server/tests/chrome/inactive-property-helper/cue-pseudo-element.mjs
new file mode 100644
index 0000000000..7a55425632
--- /dev/null
+++ b/devtools/server/tests/chrome/inactive-property-helper/cue-pseudo-element.mjs
@@ -0,0 +1,371 @@
+/* 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 `cue-pseudo-element` test cases.
+
+// "background",
+// "background-attachment",
+// "background-blend-mode",
+// "background-clip",
+// "background-color",
+// "background-image",
+// "background-origin",
+// "background-position",
+// "background-position-x",
+// "background-position-y",
+// "background-repeat",
+// "background-size",
+// "color",
+// "font",
+// "font-family",
+// "font-size",
+// "font-stretch",
+// "font-style",
+// "font-variant",
+// "font-variant-alternates",
+// "font-variant-caps",
+// "font-variant-east-asian",
+// "font-variant-ligatures",
+// "font-variant-numeric",
+// "font-variant-position",
+// "font-weight",
+// "line-height",
+// "opacity",
+// "outline",
+// "outline-color",
+// "outline-offset",
+// "outline-style",
+// "outline-width",
+// "ruby-position",
+// "text-combine-upright",
+// "text-decoration",
+// "text-decoration-color",
+// "text-decoration-line",
+// "text-decoration-style",
+// "text-decoration-thickness",
+// "text-shadow",
+// "visibility",
+// "white-space",
+
+export default [
+ {
+ info: "background is active on ::cue",
+ property: "background",
+ tagName: "video",
+ rules: ["video::cue { background: linear-gradient(white, black); }"],
+ isActive: true,
+ },
+ {
+ info: "background-attachment is active on ::cue",
+ property: "background-attachment",
+ tagName: "video",
+ rules: ["video::cue { background-attachment: fixed; }"],
+ isActive: true,
+ },
+ {
+ info: "background-blend-mode is active on ::cue",
+ property: "background-blend-mode",
+ tagName: "video",
+ rules: ["video::cue { background-blend-mode: difference; }"],
+ isActive: true,
+ },
+ {
+ info: "background-clip is active on ::cue",
+ property: "background-clip",
+ tagName: "video",
+ rules: ["video::cue { background-clip: padding-box; }"],
+ isActive: true,
+ },
+ {
+ info: "background-color is active on ::cue",
+ property: "background-color",
+ tagName: "video",
+ rules: ["video::cue { background-color: red; }"],
+ isActive: true,
+ },
+ {
+ info: "background-image is active on ::cue",
+ property: "background-image",
+ tagName: "video",
+ rules: [
+ "video::cue { background-image: url('https://example.com/image.png'); }",
+ ],
+ isActive: true,
+ },
+ {
+ info: "background-origin is active on ::cue",
+ property: "background-origin",
+ tagName: "video",
+ rules: ["video::cue { background-origin: padding-box; }"],
+ isActive: true,
+ },
+ {
+ info: "background-position is active on ::cue",
+ property: "background-position",
+ tagName: "video",
+ rules: ["video::cue { background-position: 0 0; }"],
+ isActive: true,
+ },
+ {
+ info: "background-position-x is active on ::cue",
+ property: "background-position-x",
+ tagName: "video",
+ rules: ["video::cue { background-position-x: 0; }"],
+ isActive: true,
+ },
+ {
+ info: "background-position-y is active on ::cue",
+ property: "background-position-y",
+ tagName: "video",
+ rules: ["video::cue { background-position-y: 0; }"],
+ isActive: true,
+ },
+ {
+ info: "background-repeat is active on ::cue",
+ property: "background-repeat",
+ tagName: "video",
+ rules: ["video::cue { background-repeat: repeat-y; }"],
+ isActive: true,
+ },
+ {
+ info: "background-size is active on ::cue",
+ property: "background-size",
+ tagName: "video",
+ rules: ["video::cue { background-size: 100% 100%; }"],
+ isActive: true,
+ },
+ {
+ info: "color is active on ::cue",
+ property: "color",
+ tagName: "video",
+ rules: ["video::cue { color: red; }"],
+ isActive: true,
+ },
+ {
+ info: "font is active on ::cue",
+ property: "font",
+ tagName: "video",
+ rules: ["video::cue { font: 1em sans-serif; }"],
+ isActive: true,
+ },
+ {
+ info: "font-family is active on ::cue",
+ property: "font-family",
+ tagName: "video",
+ rules: ["video::cue { font-family: sans-serif; }"],
+ isActive: true,
+ },
+ {
+ info: "font-size is active on ::cue",
+ property: "font-size",
+ tagName: "video",
+ rules: ["video::cue { font-size: 1em; }"],
+ isActive: true,
+ },
+ {
+ info: "font-stretch is active on ::cue",
+ property: "font-stretch",
+ tagName: "video",
+ rules: ["video::cue { font-stretch: ultra-condensed; }"],
+ isActive: true,
+ },
+ {
+ info: "font-style is active on ::cue",
+ property: "font-style",
+ tagName: "video",
+ rules: ["video::cue { font-style: italic; }"],
+ isActive: true,
+ },
+ {
+ info: "font-variant is active on ::cue",
+ property: "font-variant",
+ tagName: "video",
+ rules: ["video::cue { font-variant: small-caps; }"],
+ isActive: true,
+ },
+ {
+ info: "font-variant-alternates is active on ::cue",
+ property: "font-variant-alternates",
+ tagName: "video",
+ rules: ["video::cue { font-variant-alternates: slashed-zero; }"],
+ isActive: true,
+ },
+ {
+ info: "font-variant-caps is active on ::cue",
+ property: "font-variant-caps",
+ tagName: "video",
+ rules: ["video::cue { font-variant-caps: all-small-caps; }"],
+ isActive: true,
+ },
+ {
+ info: "font-variant-east-asian is active on ::cue",
+ property: "font-variant-east-asian",
+ tagName: "video",
+ rules: ["video::cue { font-variant-east-asian: ruby; }"],
+ isActive: true,
+ },
+ {
+ info: "font-variant-ligatures is active on ::cue",
+ property: "font-variant-ligatures",
+ tagName: "video",
+ rules: ["video::cue { font-variant-ligatures: common-ligatures; }"],
+ isActive: true,
+ },
+ {
+ info: "font-variant-numeric is active on ::cue",
+ property: "font-variant-numeric",
+ tagName: "video",
+ rules: ["video::cue { font-variant-numeric: ordinal; }"],
+ isActive: true,
+ },
+ {
+ info: "font-variant-position is active on ::cue",
+ property: "font-variant-position",
+ tagName: "video",
+ rules: ["video::cue { font-variant-position: sub; }"],
+ isActive: true,
+ },
+ {
+ info: "font-weight is active on ::cue",
+ property: "font-weight",
+ tagName: "video",
+ rules: ["video::cue { font-weight: bold; }"],
+ isActive: true,
+ },
+ {
+ info: "line-height is active on ::cue",
+ property: "line-height",
+ tagName: "video",
+ rules: ["video::cue { line-height: 1.2; }"],
+ isActive: true,
+ },
+ {
+ info: "opacity is active on ::cue",
+ property: "opacity",
+ tagName: "video",
+ rules: ["video::cue { opacity: 0.8; }"],
+ isActive: true,
+ },
+ {
+ info: "outline is active on ::cue",
+ property: "outline",
+ tagName: "video",
+ rules: ["video::cue { outline: 1px solid red; }"],
+ isActive: true,
+ },
+ {
+ info: "outline-color is active on ::cue",
+ property: "outline-color",
+ tagName: "video",
+ rules: ["video::cue { outline-color: red; }"],
+ isActive: true,
+ },
+ {
+ info: "outline-offset is active on ::cue",
+ property: "outline-offset",
+ tagName: "video",
+ rules: ["video::cue { outline-offset: 1px; }"],
+ isActive: true,
+ },
+ {
+ info: "outline-style is active on ::cue",
+ property: "outline-style",
+ tagName: "video",
+ rules: ["video::cue { outline-style: solid; }"],
+ isActive: true,
+ },
+ {
+ info: "outline-width is active on ::cue",
+ property: "outline-width",
+ tagName: "video",
+ rules: ["video::cue { outline-width: 1px; }"],
+ isActive: true,
+ },
+ {
+ info: "ruby-position is active on ::cue",
+ property: "ruby-position",
+ tagName: "video",
+ rules: ["video::cue { ruby-position: over; }"],
+ isActive: true,
+ },
+ {
+ info: "text-combine-upright is active on ::cue",
+ property: "text-combine-upright",
+ tagName: "video",
+ rules: ["video::cue { text-combine-upright: all; }"],
+ isActive: true,
+ },
+ {
+ info: "text-decoration is active on ::cue",
+ property: "text-decoration",
+ tagName: "video",
+ rules: ["video::cue { text-decoration: 1px underline red; }"],
+ isActive: true,
+ },
+ {
+ info: "text-decoration-color is active on ::cue",
+ property: "text-decoration-color",
+ tagName: "video",
+ rules: ["video::cue { text-decoration-color: red; }"],
+ isActive: true,
+ },
+ {
+ info: "text-decoration-line is active on ::cue",
+ property: "text-decoration-line",
+ tagName: "video",
+ rules: ["video::cue { text-decoration-line: underline; }"],
+ isActive: true,
+ },
+ {
+ info: "text-decoration-style is active on ::cue",
+ property: "text-decoration-style",
+ tagName: "video",
+ rules: ["video::cue { text-decoration-style: wavy; }"],
+ isActive: true,
+ },
+ {
+ info: "text-decoration-thickness is active on ::cue",
+ property: "text-decoration-thickness",
+ tagName: "video",
+ rules: ["video::cue { text-decoration-thickness: 1px; }"],
+ isActive: true,
+ },
+ {
+ info: "text-shadow is active on ::cue",
+ property: "text-shadow",
+ tagName: "video",
+ rules: ["video::cue { text-shadow: 1px 1px 1px red; }"],
+ isActive: true,
+ },
+ {
+ info: "visibility is active on ::cue",
+ property: "visibility",
+ tagName: "video",
+ rules: ["video::cue { visibility: hidden; }"],
+ isActive: true,
+ },
+ {
+ info: "white-space is active on ::cue",
+ property: "white-space",
+ tagName: "video",
+ rules: ["video::cue { white-space: nowrap; }"],
+ isActive: true,
+ },
+ {
+ info: "border is inactive on ::cue",
+ property: "border",
+ tagName: "video",
+ rules: ["video::cue { border: 1px solid red; }"],
+ isActive: false,
+ expectedMsgId: "inactive-css-cue-pseudo-element-not-supported",
+ },
+ {
+ info: "display is inactive on ::cue",
+ property: "display",
+ tagName: "video",
+ rules: ["video::cue { display: grid; }"],
+ isActive: false,
+ expectedMsgId: "inactive-css-cue-pseudo-element-not-supported",
+ },
+];
diff --git a/devtools/server/tests/chrome/inactive-property-helper/first-letter-pseudo-element.mjs b/devtools/server/tests/chrome/inactive-property-helper/first-letter-pseudo-element.mjs
new file mode 100644
index 0000000000..ebce0d292a
--- /dev/null
+++ b/devtools/server/tests/chrome/inactive-property-helper/first-letter-pseudo-element.mjs
@@ -0,0 +1,32 @@
+/* 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 `first-letter-pseudo-element` test cases.
+
+// "content",
+
+export default [
+ {
+ info: "content is inactive on ::first-letter",
+ property: "content",
+ tagName: "div",
+ rules: ["div::first-letter { content: 'invalid'; }"],
+ isActive: false,
+ expectedMsgId: "inactive-css-first-letter-pseudo-element-not-supported",
+ },
+ {
+ info: "color is active on ::first-letter",
+ property: "color",
+ tagName: "div",
+ rules: ["div::first-letter { color: green; }"],
+ isActive: true,
+ },
+ {
+ info: "display is active on ::first-letter",
+ property: "display",
+ tagName: "div",
+ rules: ["div::first-letter { display: grid; }"],
+ isActive: true,
+ },
+];
diff --git a/devtools/server/tests/chrome/inactive-property-helper/first-line-pseudo-element.mjs b/devtools/server/tests/chrome/inactive-property-helper/first-line-pseudo-element.mjs
new file mode 100644
index 0000000000..68948a16bc
--- /dev/null
+++ b/devtools/server/tests/chrome/inactive-property-helper/first-line-pseudo-element.mjs
@@ -0,0 +1,50 @@
+/* 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 `first-line-pseudo-element` test cases.
+
+// "direction",
+// "text-orientation",
+// "writing-mode",
+
+export default [
+ {
+ info: "direction is inactive on ::first-line",
+ property: "direction",
+ tagName: "div",
+ rules: ["div::first-line { direction: rtl; }"],
+ isActive: false,
+ expectedMsgId: "inactive-css-first-line-pseudo-element-not-supported",
+ },
+ {
+ info: "text-orientation is inactive on ::first-line",
+ property: "text-orientation",
+ tagName: "div",
+ rules: ["div::first-line { text-orientation: sideways; }"],
+ isActive: false,
+ expectedMsgId: "inactive-css-first-line-pseudo-element-not-supported",
+ },
+ {
+ info: "writing-mode is inactive on ::first-line",
+ property: "writing-mode",
+ tagName: "div",
+ rules: ["div::first-line { writing-mode: vertical-rl; }"],
+ isActive: false,
+ expectedMsgId: "inactive-css-first-line-pseudo-element-not-supported",
+ },
+ {
+ info: "color is active on ::first-line",
+ property: "color",
+ tagName: "div",
+ rules: ["div::first-line { color: green; }"],
+ isActive: true,
+ },
+ {
+ info: "display is active on ::first-line",
+ property: "display",
+ tagName: "div",
+ rules: ["div::first-line { display: grid; }"],
+ isActive: true,
+ },
+];
diff --git a/devtools/server/tests/chrome/inactive-property-helper/flex-grid-item-properties.mjs b/devtools/server/tests/chrome/inactive-property-helper/flex-grid-item-properties.mjs
new file mode 100644
index 0000000000..79c587798a
--- /dev/null
+++ b/devtools/server/tests/chrome/inactive-property-helper/flex-grid-item-properties.mjs
@@ -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.mjs b/devtools/server/tests/chrome/inactive-property-helper/float.mjs
new file mode 100644
index 0000000000..4c502e3cca
--- /dev/null
+++ b/devtools/server/tests/chrome/inactive-property-helper/float.mjs
@@ -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.mjs b/devtools/server/tests/chrome/inactive-property-helper/gap.mjs
new file mode 100644
index 0000000000..83befcba0d
--- /dev/null
+++ b/devtools/server/tests/chrome/inactive-property-helper/gap.mjs
@@ -0,0 +1,133 @@
+/* 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,
+ },
+ {
+ info: "grid-gap is inactive on non-grid and non-flex container",
+ property: "grid-gap",
+ tagName: "div",
+ rules: ["div { grid-gap: 10px; display: block; }"],
+ isActive: false,
+ },
+ {
+ info: "grid-gap is active on flex container",
+ property: "grid-gap",
+ tagName: "div",
+ rules: ["div { grid-gap: 10px; display: flex; }"],
+ isActive: true,
+ },
+ {
+ info: "grid-gap is active on grid container",
+ property: "grid-gap",
+ tagName: "div",
+ rules: ["div { grid-gap: 10px; display: grid; }"],
+ isActive: true,
+ },
+ {
+ info: "grid-gap is inactive on non-multi-col container",
+ property: "grid-gap",
+ tagName: "div",
+ rules: ["div { grid-gap: 10px; column-count: auto; }"],
+ isActive: false,
+ },
+ {
+ info: "grid-gap is active on multi-col container",
+ property: "grid-gap",
+ tagName: "div",
+ rules: ["div { grid-gap: 10px; column-count: 2; }"],
+ isActive: true,
+ },
+];
diff --git a/devtools/server/tests/chrome/inactive-property-helper/grid-container-properties.mjs b/devtools/server/tests/chrome/inactive-property-helper/grid-container-properties.mjs
new file mode 100644
index 0000000000..1fca234733
--- /dev/null
+++ b/devtools/server/tests/chrome/inactive-property-helper/grid-container-properties.mjs
@@ -0,0 +1,43 @@
+/* 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-auto-columns`, `grid-auto-flow`, `grid-auto-rows`, `grid-template`,
+// `grid-template-areas`, `grid-template-columns`, `grid-template-rows`,
+// and `justify-items`.
+let tests = [];
+
+for (const { propertyName, propertyValue } of [
+ { propertyName: "grid-auto-columns", propertyValue: "100px" },
+ { propertyName: "grid-auto-flow", propertyValue: "columns" },
+ { propertyName: "grid-auto-rows", propertyValue: "100px" },
+ { propertyName: "grid-template", propertyValue: "auto / auto" },
+ { propertyName: "grid-template-areas", propertyValue: "a b c" },
+ { propertyName: "grid-template-columns", propertyValue: "100px 1fr" },
+ { propertyName: "grid-template-rows", propertyValue: "100px 1fr" },
+ { propertyName: "justify-items", propertyValue: "center" },
+]) {
+ tests = tests.concat(createTestsForProp(propertyName, propertyValue));
+}
+
+function createTestsForProp(propertyName, propertyValue) {
+ return [
+ {
+ info: `${propertyName} is active on a grid container`,
+ property: propertyName,
+ tagName: "div",
+ rules: [`div { display:grid; ${propertyName}: ${propertyValue}; }`],
+ isActive: true,
+ },
+ {
+ info: `${propertyName} is inactive on a non-grid container`,
+ property: propertyName,
+ tagName: "div",
+ rules: [`div { ${propertyName}: ${propertyValue}; }`],
+ isActive: false,
+ },
+ ];
+}
+
+export default tests;
diff --git a/devtools/server/tests/chrome/inactive-property-helper/grid-with-absolute-properties.mjs b/devtools/server/tests/chrome/inactive-property-helper/grid-with-absolute-properties.mjs
new file mode 100644
index 0000000000..fd963e0d3b
--- /dev/null
+++ b/devtools/server/tests/chrome/inactive-property-helper/grid-with-absolute-properties.mjs
@@ -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/highlight-pseudo-elements.mjs b/devtools/server/tests/chrome/inactive-property-helper/highlight-pseudo-elements.mjs
new file mode 100644
index 0000000000..bcb5b8763c
--- /dev/null
+++ b/devtools/server/tests/chrome/inactive-property-helper/highlight-pseudo-elements.mjs
@@ -0,0 +1,155 @@
+/* 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 `highlight-pseudo-elements` test cases.
+
+// "background",
+// "background-color",
+// "color",
+// "fill-color",
+// "stroke-color",
+// "stroke-width",
+// "text-decoration",
+// "text-shadow",
+// "text-underline-offset",
+// "text-underline-position",
+
+export default [
+ {
+ info: "width is inactive on ::selection",
+ property: "width",
+ tagName: "span",
+ rules: ["span::selection { width: 10px; }"],
+ isActive: false,
+ // `width` is also inactive on inline element, so make sure we get the warning
+ // because we're using it in a highlight pseudo.
+ expectedMsgId: "inactive-css-highlight-pseudo-elements-not-supported",
+ },
+ {
+ info: "display is inactive on ::highlight",
+ property: "display",
+ tagName: "span",
+ rules: ["span::highlight(result) { display: grid; }"],
+ isActive: false,
+ expectedMsgId: "inactive-css-highlight-pseudo-elements-not-supported",
+ },
+ {
+ // accept background shorthand, even if it might hold inactive values
+ info: "background is active on ::selection",
+ property: "background",
+ tagName: "span",
+ rules: ["span::selection { background: red; }"],
+ isActive: true,
+ },
+ {
+ info: "border-color is inactive on ::selection",
+ property: "border-color",
+ tagName: "span",
+ rules: ["span::selection { border-color: red; }"],
+ isActive: false,
+ // `width` is also inactive on inline element, so make sure we get the warning
+ // because we're using it in a highlight pseudo.
+ expectedMsgId: "inactive-css-highlight-pseudo-elements-not-supported",
+ },
+ {
+ info: "background-color is active on ::selection",
+ property: "background-color",
+ tagName: "span",
+ rules: ["span::selection { background-color: red; }"],
+ isActive: true,
+ },
+ {
+ info: "color is active on ::selection",
+ property: "color",
+ tagName: "span",
+ rules: ["span::selection { color: red; }"],
+ isActive: true,
+ },
+ {
+ info: "text-decoration is active on ::selection",
+ property: "text-decoration",
+ tagName: "span",
+ rules: [
+ "span::selection { text-decoration: double overline #FF3028 4px; }",
+ ],
+ isActive: true,
+ },
+ {
+ info: "text-decoration-color is active on ::selection",
+ property: "text-decoration-color",
+ tagName: "span",
+ rules: ["span::selection { text-decoration-color: #FF3028; }"],
+ isActive: true,
+ },
+ {
+ info: "text-decoration-line is active on ::selection",
+ property: "text-decoration-line",
+ tagName: "span",
+ rules: ["span::selection { text-decoration-line: overline; }"],
+ isActive: true,
+ },
+ {
+ info: "text-decoration-style is active on ::selection",
+ property: "text-decoration-style",
+ tagName: "span",
+ rules: ["span::selection { text-decoration-style: double; }"],
+ isActive: true,
+ },
+ {
+ info: "text-decoration-thickness is active on ::selection",
+ property: "text-decoration-thickness",
+ tagName: "span",
+ rules: ["span::selection { text-decoration-thickness: 4px; }"],
+ isActive: true,
+ },
+ {
+ info: "text-shadow is active on ::selection",
+ property: "text-shadow",
+ tagName: "span",
+ rules: ["span::selection { text-shadow: text-shadow: #FC0 1px 0 10px; }"],
+ isActive: true,
+ },
+ {
+ info: "text-underline-offset is active on ::selection",
+ property: "text-underline-offset",
+ tagName: "span",
+ rules: ["span::selection { text-underline-offset: 10px; }"],
+ isActive: true,
+ },
+ {
+ info: "text-underline-position is active on ::selection",
+ property: "text-underline-position",
+ tagName: "span",
+ rules: ["span::selection { text-underline-position: under; }"],
+ isActive: true,
+ },
+ {
+ info: "-webkit-text-fill-color is active on ::selection",
+ property: "-webkit-text-fill-color",
+ tagName: "span",
+ rules: ["span::selection { -webkit-text-fill-color: red; }"],
+ isActive: true,
+ },
+ {
+ info: "-webkit-text-stroke-color is active on ::selection",
+ property: "-webkit-text-stroke-color",
+ tagName: "span",
+ rules: ["span::selection { -webkit-text-stroke-color: red; }"],
+ isActive: true,
+ },
+ {
+ info: "-webkit-text-stroke-width is active on ::selection",
+ property: "-webkit-text-stroke-width",
+ tagName: "span",
+ rules: ["span::selection { -webkit-text-stroke-width: 4px; }"],
+ isActive: true,
+ },
+ {
+ info: "-webkit-text-stroke is active on ::selection",
+ property: "-webkit-text-stroke",
+ tagName: "span",
+ rules: ["span::selection { -webkit-text-stroke: 4px navy; }"],
+ isActive: true,
+ },
+];
diff --git a/devtools/server/tests/chrome/inactive-property-helper/margin-padding.mjs b/devtools/server/tests/chrome/inactive-property-helper/margin-padding.mjs
new file mode 100644
index 0000000000..7c1d348512
--- /dev/null
+++ b/devtools/server/tests/chrome/inactive-property-helper/margin-padding.mjs
@@ -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.mjs b/devtools/server/tests/chrome/inactive-property-helper/max-min-width-height.mjs
new file mode 100644
index 0000000000..4bb5623f6e
--- /dev/null
+++ b/devtools/server/tests/chrome/inactive-property-helper/max-min-width-height.mjs
@@ -0,0 +1,366 @@
+/* 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/multicol-container-properties.mjs b/devtools/server/tests/chrome/inactive-property-helper/multicol-container-properties.mjs
new file mode 100644
index 0000000000..6bc4e9dd13
--- /dev/null
+++ b/devtools/server/tests/chrome/inactive-property-helper/multicol-container-properties.mjs
@@ -0,0 +1,39 @@
+/* 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:
+// `column-fill`, `column-rule`, `column-rule-color`, `column-rule-style`,
+// and `column-rule-width`.
+let tests = [];
+
+for (const { propertyName, propertyValue } of [
+ { propertyName: "column-fill", propertyValue: "auto" },
+ { propertyName: "column-rule", propertyValue: "1px solid black" },
+ { propertyName: "column-rule-color", propertyValue: "black" },
+ { propertyName: "column-rule-style", propertyValue: "solid" },
+ { propertyName: "column-rule-width", propertyValue: "1px" },
+]) {
+ tests = tests.concat(createTestsForProp(propertyName, propertyValue));
+}
+
+function createTestsForProp(propertyName, propertyValue) {
+ return [
+ {
+ info: `${propertyName} is active on a multi-column container`,
+ property: propertyName,
+ tagName: "div",
+ rules: [`div { columns:2; ${propertyName}: ${propertyValue}; }`],
+ isActive: true,
+ },
+ {
+ info: `${propertyName} is inactive on a non-multi-column container`,
+ property: propertyName,
+ tagName: "div",
+ rules: [`div { ${propertyName}: ${propertyValue}; }`],
+ isActive: false,
+ },
+ ];
+}
+
+export default tests;
diff --git a/devtools/server/tests/chrome/inactive-property-helper/place-items-content.mjs b/devtools/server/tests/chrome/inactive-property-helper/place-items-content.mjs
new file mode 100644
index 0000000000..f554a785a7
--- /dev/null
+++ b/devtools/server/tests/chrome/inactive-property-helper/place-items-content.mjs
@@ -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/placeholder-pseudo-element.mjs b/devtools/server/tests/chrome/inactive-property-helper/placeholder-pseudo-element.mjs
new file mode 100644
index 0000000000..6c9a81472b
--- /dev/null
+++ b/devtools/server/tests/chrome/inactive-property-helper/placeholder-pseudo-element.mjs
@@ -0,0 +1,122 @@
+/* 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 `placeholder-pseudo-element` test cases.
+
+//"baseline-source",
+//"direction",
+//"dominant-baseline",
+//"line-height",
+//"text-orientation",
+//"vertical-align",
+//"writing-mode",
+//"alignment-baseline",
+//"baseline-shift",
+//"initial-letter",
+//"text-box-trim",
+
+export default [
+ {
+ info: "baseline-source is inactive on ::placeholder",
+ property: "baseline-source",
+ tagName: "input",
+ rules: ["input::placeholder { baseline-source: first; }"],
+ isActive: false,
+ expectedMsgId: "inactive-css-placeholder-pseudo-element-not-supported",
+ },
+ {
+ info: "direction is inactive on ::placeholder",
+ property: "direction",
+ tagName: "input",
+ rules: ["input::placeholder { direction: rtl; }"],
+ isActive: false,
+ expectedMsgId: "inactive-css-placeholder-pseudo-element-not-supported",
+ },
+ {
+ info: "dominant-baseline is inactive on ::placeholder",
+ property: "dominant-baseline",
+ tagName: "input",
+ rules: ["input::placeholder { dominant-baseline: central; }"],
+ isActive: false,
+ expectedMsgId: "inactive-css-placeholder-pseudo-element-not-supported",
+ },
+ {
+ info: "line-height is inactive on ::placeholder",
+ property: "line-height",
+ tagName: "input",
+ rules: ["input::placeholder { line-height: 2em; }"],
+ isActive: false,
+ expectedMsgId: "inactive-css-placeholder-pseudo-element-not-supported",
+ },
+ {
+ info: "text-orientation is inactive on ::placeholder",
+ property: "text-orientation",
+ tagName: "input",
+ rules: ["input::placeholder { text-orientation: sideways; }"],
+ isActive: false,
+ expectedMsgId: "inactive-css-placeholder-pseudo-element-not-supported",
+ },
+ {
+ info: "vertical-align is inactive on ::placeholder",
+ property: "vertical-align",
+ tagName: "input",
+ rules: ["input::placeholder { vertical-align: super; }"],
+ isActive: false,
+ expectedMsgId: "inactive-css-placeholder-pseudo-element-not-supported",
+ },
+ {
+ info: "writing-mode is inactive on ::placeholder",
+ property: "writing-mode",
+ tagName: "input",
+ rules: ["input::placeholder { writing-mode: vertical-rl; }"],
+ isActive: false,
+ expectedMsgId: "inactive-css-placeholder-pseudo-element-not-supported",
+ },
+ {
+ info: "alignment-baseline is inactive on ::placeholder",
+ property: "alignment-baseline",
+ tagName: "input",
+ rules: ["input::placeholder { alignment-baseline: central; }"],
+ isActive: false,
+ expectedMsgId: "inactive-css-placeholder-pseudo-element-not-supported",
+ },
+ {
+ info: "baseline-shift is inactive on ::placeholder",
+ property: "baseline-shift",
+ tagName: "input",
+ rules: ["input::placeholder { baseline-shift: super; }"],
+ isActive: false,
+ expectedMsgId: "inactive-css-placeholder-pseudo-element-not-supported",
+ },
+ {
+ info: "initial-letter is inactive on ::placeholder",
+ property: "initial-letter",
+ tagName: "input",
+ rules: ["input::placeholder { initial-letter: 2em; }"],
+ isActive: false,
+ expectedMsgId: "inactive-css-placeholder-pseudo-element-not-supported",
+ },
+ {
+ info: "text-box-trim is inactive on ::placeholder",
+ property: "text-box-trim",
+ tagName: "input",
+ rules: ["input::placeholder { text-box-trim: both; }"],
+ isActive: false,
+ expectedMsgId: "inactive-css-placeholder-pseudo-element-not-supported",
+ },
+ {
+ info: "color is active on ::placeholder",
+ property: "color",
+ tagName: "input",
+ rules: ["input::placeholder { color: green; }"],
+ isActive: true,
+ },
+ {
+ info: "display is active on ::placeholder",
+ property: "display",
+ tagName: "input",
+ rules: ["input::placeholder { display: grid; }"],
+ isActive: true,
+ },
+];
diff --git a/devtools/server/tests/chrome/inactive-property-helper/positioned.mjs b/devtools/server/tests/chrome/inactive-property-helper/positioned.mjs
new file mode 100644
index 0000000000..0386c278c5
--- /dev/null
+++ b/devtools/server/tests/chrome/inactive-property-helper/positioned.mjs
@@ -0,0 +1,82 @@
+/* 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/scroll-padding.mjs b/devtools/server/tests/chrome/inactive-property-helper/scroll-padding.mjs
new file mode 100644
index 0000000000..acb2899be2
--- /dev/null
+++ b/devtools/server/tests/chrome/inactive-property-helper/scroll-padding.mjs
@@ -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 `scroll-padding-*` test cases.
+
+export default [
+ {
+ info: "scroll-padding is active on element with auto-overflow",
+ property: "scroll-padding",
+ tagName: "div",
+ rules: ["div { overflow: auto; scroll-padding: 10px; }"],
+ isActive: true,
+ },
+ {
+ info: "scroll-padding is active on element with scrollable overflow",
+ property: "scroll-padding",
+ tagName: "div",
+ rules: ["div { overflow: scroll; scroll-padding: 10px; }"],
+ isActive: true,
+ },
+ {
+ info: "scroll-padding is active on element with hidden overflow",
+ property: "scroll-padding",
+ tagName: "div",
+ rules: ["div { overflow: hidden; scroll-padding: 10px; }"],
+ isActive: true,
+ },
+ {
+ info: "scroll-padding is inactive on element with visible overflow",
+ property: "scroll-padding",
+ tagName: "div",
+ rules: ["div { scroll-padding: 10px; }"],
+ isActive: false,
+ },
+ {
+ info: "scroll-padding is inactive on element with clipped overflow",
+ property: "scroll-padding",
+ tagName: "div",
+ rules: ["div { overflow: clip; scroll-padding: 10px; }"],
+ isActive: false,
+ },
+ {
+ info: "scroll-padding is inactive on element with horizontally clipped overflow",
+ property: "scroll-padding",
+ tagName: "div",
+ rules: ["div { overflow-x: clip; scroll-padding: 10px; }"],
+ isActive: false,
+ },
+ {
+ info: "scroll-padding is inactive on element with vertically clipped overflow",
+ property: "scroll-padding",
+ tagName: "div",
+ rules: ["div { overflow-y: clip; scroll-padding: 10px; }"],
+ isActive: false,
+ },
+ {
+ info: "scroll-padding-top is inactive on element with visible overflow",
+ property: "scroll-padding-top",
+ tagName: "div",
+ rules: ["div { scroll-padding-top: 10px; }"],
+ isActive: false,
+ },
+ {
+ info: "scroll-padding-top is inactive on element with horizontally clipped overflow",
+ property: "scroll-padding-top",
+ tagName: "div",
+ rules: ["div { overflow-x: clip; scroll-padding-top: 10px; }"],
+ isActive: false,
+ },
+ {
+ info: "scroll-padding-top is inactive on element with vertically clipped overflow",
+ property: "scroll-padding-top",
+ tagName: "div",
+ rules: ["div { overflow-y: clip; scroll-padding-top: 10px; }"],
+ isActive: false,
+ },
+ {
+ info: "scroll-padding-top is active on element with horizontally clipped but vertical auto-overflow (as 'clip' is computed to 'hidden')",
+ property: "scroll-padding-top",
+ tagName: "div",
+ rules: [
+ "div { overflow-x: clip; overflow-y: auto; scroll-padding-top: 10px; }",
+ ],
+ isActive: true,
+ },
+ {
+ info: "scroll-padding-top is active on element with vertically clipped but horizontal auto-overflow (as 'clip' is computed to 'hidden')",
+ property: "scroll-padding-top",
+ tagName: "div",
+ rules: [
+ "div { overflow-x: auto; overflow-y: clip; scroll-padding-top: 10px; }",
+ ],
+ isActive: true,
+ },
+ {
+ info: "scroll-padding-right is inactive on element with visible overflow",
+ property: "scroll-padding-right",
+ tagName: "div",
+ rules: ["div { scroll-padding-right: 10px; }"],
+ isActive: false,
+ },
+ {
+ info: "scroll-padding-bottom is inactive on element with visible overflow",
+ property: "scroll-padding-bottom",
+ tagName: "div",
+ rules: ["div { scroll-padding-bottom: 10px; }"],
+ isActive: false,
+ },
+ {
+ info: "scroll-padding-left is inactive on element with visible overflow",
+ property: "scroll-padding-left",
+ tagName: "div",
+ rules: ["div { scroll-padding-left: 10px; }"],
+ isActive: false,
+ },
+ {
+ info: "scroll-padding-block is inactive on element with visible overflow",
+ property: "scroll-padding-block",
+ tagName: "div",
+ rules: ["div { scroll-padding-block: 10px; }"],
+ isActive: false,
+ },
+ {
+ info: "scroll-padding-block-end is inactive on element with visible overflow",
+ property: "scroll-padding-block-end",
+ tagName: "div",
+ rules: ["div { scroll-padding-block-end: 10px; }"],
+ isActive: false,
+ },
+ {
+ info: "scroll-padding-block-start is inactive on element with visible overflow",
+ property: "scroll-padding-block-start",
+ tagName: "div",
+ rules: ["div { scroll-padding-block-start: 10px; }"],
+ isActive: false,
+ },
+ {
+ info: "scroll-padding-inline is inactive on element with visible overflow",
+ property: "scroll-padding-inline",
+ tagName: "div",
+ rules: ["div { scroll-padding-inline: 10px; }"],
+ isActive: false,
+ },
+ {
+ info: "scroll-padding-inline-end is inactive on element with visible overflow",
+ property: "scroll-padding-inline-end",
+ tagName: "div",
+ rules: ["div { scroll-padding-inline-end: 10px; }"],
+ isActive: false,
+ },
+ {
+ info: "scroll-padding-inline-start is inactive on element with visible overflow",
+ property: "scroll-padding-inline-start",
+ tagName: "div",
+ rules: ["div { scroll-padding-inline-start: 10px; }"],
+ isActive: false,
+ },
+];
diff --git a/devtools/server/tests/chrome/inactive-property-helper/table-cell.mjs b/devtools/server/tests/chrome/inactive-property-helper/table-cell.mjs
new file mode 100644
index 0000000000..bda1f27015
--- /dev/null
+++ b/devtools/server/tests/chrome/inactive-property-helper/table-cell.mjs
@@ -0,0 +1,21 @@
+/* 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 `empty-cells` test cases.
+export default [
+ {
+ info: "empty-cells is inactive on block element",
+ property: "empty-cells",
+ tagName: "div",
+ rules: ["div { empty-cells: hide; }"],
+ isActive: false,
+ },
+ {
+ info: "empty-cells is active on table cell element",
+ property: "empty-cells",
+ tagName: "div",
+ rules: ["div { display: table-cell; empty-cells: hide; }"],
+ isActive: true,
+ },
+];
diff --git a/devtools/server/tests/chrome/inactive-property-helper/table.mjs b/devtools/server/tests/chrome/inactive-property-helper/table.mjs
new file mode 100644
index 0000000000..596698522c
--- /dev/null
+++ b/devtools/server/tests/chrome/inactive-property-helper/table.mjs
@@ -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 `table-layout` test cases.
+export default [
+ {
+ info: "table-layout is inactive on block element",
+ property: "table-layout",
+ tagName: "div",
+ rules: ["div { table-layout: fixed; }"],
+ isActive: false,
+ },
+ {
+ info: "table-layout is active on table element",
+ property: "table-layout",
+ tagName: "div",
+ rules: ["div { display: table; table-layout: fixed; }"],
+ isActive: true,
+ },
+ {
+ info: "table-layout is active on inline table element",
+ property: "table-layout",
+ tagName: "div",
+ rules: ["div { display: inline-table; table-layout: fixed; }"],
+ isActive: true,
+ },
+];
diff --git a/devtools/server/tests/chrome/inactive-property-helper/text-overflow.mjs b/devtools/server/tests/chrome/inactive-property-helper/text-overflow.mjs
new file mode 100644
index 0000000000..ada2211b3a
--- /dev/null
+++ b/devtools/server/tests/chrome/inactive-property-helper/text-overflow.mjs
@@ -0,0 +1,92 @@
+/* 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 active when overflow is set to auto",
+ property: "text-overflow",
+ tagName: "div",
+ rules: ["div { text-overflow: ellipsis; overflow: auto; }"],
+ isActive: true,
+ },
+ {
+ info: "text-overflow is active when overflow is set to scroll",
+ property: "text-overflow",
+ tagName: "div",
+ rules: ["div { text-overflow: ellipsis; overflow: scroll; }"],
+ 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: "text-overflow is active when overflow-x is set to hidden on horizontal writing mode",
+ property: "text-overflow",
+ tagName: "div",
+ rules: [
+ "div { writing-mode: lr; text-overflow: ellipsis; overflow-x: hidden; }",
+ ],
+ isActive: true,
+ },
+ {
+ info: "text-overflow is inactive when overflow-x is set to visible on horizontal writing mode",
+ property: "text-overflow",
+ tagName: "div",
+ rules: [
+ "div { writing-mode: lr; text-overflow: ellipsis; overflow-x: visible; }",
+ ],
+ isActive: false,
+ },
+ {
+ info: "text-overflow is active when overflow-y is set to hidden on vertical writing mode",
+ property: "text-overflow",
+ tagName: "div",
+ rules: [
+ "div { writing-mode: vertical-lr; text-overflow: ellipsis; overflow-y: hidden; }",
+ ],
+ isActive: true,
+ },
+ {
+ info: "text-overflow is inactive when overflow-y is set to visible on vertical writing mode",
+ property: "text-overflow",
+ tagName: "div",
+ rules: [
+ "div { writing-mode: vertical-lr; text-overflow: ellipsis; overflow-y: 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/text-wrap.mjs b/devtools/server/tests/chrome/inactive-property-helper/text-wrap.mjs
new file mode 100644
index 0000000000..58751aa764
--- /dev/null
+++ b/devtools/server/tests/chrome/inactive-property-helper/text-wrap.mjs
@@ -0,0 +1,86 @@
+/* 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-wrap: balance` test cases.
+const LOREM_IPSUM = `
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
+ Donec a diam lectus. Sed sit amet ipsum mauris.
+ Maecenas congue ligula ac quam viverra nec consectetur ante hendrerit.
+`;
+
+export default [
+ {
+ info: "text-wrap: balance; is inactive when line number exceeds threshold",
+ property: "text-wrap",
+ createTestElement: rootNode => {
+ const element = document.createElement("div");
+ element.textContent = LOREM_IPSUM;
+ rootNode.append(element);
+ return element;
+ },
+ tagName: "div",
+ rules: ["div { text-wrap: balance; width: 100px; }"],
+ isActive: false,
+ },
+ {
+ info: "text-wrap: balance; is active when line number is below threshold",
+ property: "text-wrap",
+ createTestElement: rootNode => {
+ const element = document.createElement("div");
+ element.textContent = LOREM_IPSUM;
+ rootNode.append(element);
+ return element;
+ },
+ tagName: "div",
+ rules: ["div { text-wrap: balance; width: 300px; }"],
+ isActive: true,
+ },
+ {
+ info: "text-wrap: balance is inactive when element is fragmented",
+ property: "text-wrap",
+ createTestElement: rootNode => {
+ const element = document.createElement("div");
+ element.textContent = `
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
+ Donec a diam lectus. Sed sit amet ipsum mauris.
+ Maecenas congue ligula ac quam viverra nec consectetur ante hendrerit.
+ `;
+ rootNode.append(element);
+ return element;
+ },
+ tagName: "div",
+ rules: ["div { text-wrap: balance; column-count: 2; }"],
+ isActive: false,
+ },
+ {
+ info: "text-wrap: balance; does not throw if element is not a block",
+ property: "text-wrap",
+ createTestElement: rootNode => {
+ const element = document.createElement("div");
+ element.textContent = LOREM_IPSUM;
+ rootNode.append(element);
+ return element;
+ },
+ tagName: "div",
+ rules: ["div { text-wrap: balance; display: inline; }"],
+ isActive: true,
+ },
+ {
+ info: "text-wrap: initial; is active",
+ property: "text-wrap",
+ createTestElement: rootNode => {
+ const element = document.createElement("div");
+ element.textContent = `
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
+ Donec a diam lectus. Sed sit amet ipsum mauris.
+ Maecenas congue ligula ac quam viverra nec consectetur ante hendrerit.
+ `;
+ rootNode.append(element);
+ return element;
+ },
+ tagName: "div",
+ rules: ["div { text-wrap: initial; width: 100px; }"],
+ isActive: true,
+ },
+];
diff --git a/devtools/server/tests/chrome/inactive-property-helper/vertical-align.mjs b/devtools/server/tests/chrome/inactive-property-helper/vertical-align.mjs
new file mode 100644
index 0000000000..e9873d4865
--- /dev/null
+++ b/devtools/server/tests/chrome/inactive-property-helper/vertical-align.mjs
@@ -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/inactive-property-helper/width-height-ruby.mjs b/devtools/server/tests/chrome/inactive-property-helper/width-height-ruby.mjs
new file mode 100644
index 0000000000..0dda222e0b
--- /dev/null
+++ b/devtools/server/tests/chrome/inactive-property-helper/width-height-ruby.mjs
@@ -0,0 +1,147 @@
+/* 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 & height on ruby element` test cases.
+export default [
+ {
+ info: "width is inactive on ruby element",
+ property: "width",
+ tagName: "ruby",
+ rules: ["ruby { width: 10px; }"],
+ isActive: false,
+ },
+ {
+ info: "min-width is inactive on ruby element",
+ property: "min-width",
+ tagName: "ruby",
+ rules: ["ruby { min-width: 10px; }"],
+ isActive: false,
+ },
+ {
+ info: "max-width is inactive on ruby element",
+ property: "max-width",
+ tagName: "ruby",
+ rules: ["ruby { max-width: 10px; }"],
+ isActive: false,
+ },
+ {
+ info: "height is inactive on ruby element",
+ property: "height",
+ tagName: "ruby",
+ rules: ["ruby { height: 10px; }"],
+ isActive: false,
+ },
+ {
+ info: "min-height is inactive on ruby element",
+ property: "min-height",
+ tagName: "ruby",
+ rules: ["ruby { min-height: 10px; }"],
+ isActive: false,
+ },
+ {
+ info: "max-height is inactive on ruby element",
+ property: "max-height",
+ tagName: "ruby",
+ rules: ["ruby { max-height: 10px; }"],
+ isActive: false,
+ },
+ {
+ info: "width is active on div element",
+ property: "width",
+ tagName: "div",
+ rules: ["div { width: 10px; }"],
+ isActive: true,
+ },
+ {
+ info: "min-width is active on div element",
+ property: "min-width",
+ tagName: "div",
+ rules: ["div { min-width: 10px; }"],
+ isActive: true,
+ },
+ {
+ info: "max-width is active on div element",
+ property: "max-width",
+ tagName: "div",
+ rules: ["div { max-width: 10px; }"],
+ isActive: true,
+ },
+ {
+ info: "height is active on div element",
+ property: "height",
+ tagName: "div",
+ rules: ["div { height: 10px; }"],
+ isActive: true,
+ },
+ {
+ info: "min-height is active on div element",
+ property: "min-height",
+ tagName: "div",
+ rules: ["div { min-height: 10px; }"],
+ isActive: true,
+ },
+ {
+ info: "max-height is active on div element",
+ property: "max-height",
+ tagName: "div",
+ rules: ["div { max-height: 10px; }"],
+ isActive: true,
+ },
+ {
+ info: "width is inactive on div element with display ruby",
+ property: "width",
+ tagName: "div",
+ rules: ["div { width: 10px; display: ruby;}"],
+ isActive: false,
+ },
+ {
+ info: "height is inactive on div element with display ruby",
+ property: "height",
+ tagName: "div",
+ rules: ["div { height: 10px; display: ruby;}"],
+ isActive: false,
+ },
+ {
+ info: "width is active on ruby element with display block",
+ property: "width",
+ tagName: "ruby",
+ rules: ["ruby { width: 10px; display: block;}"],
+ isActive: true,
+ },
+ {
+ info: "height is active on ruby element with display block",
+ property: "height",
+ tagName: "ruby",
+ rules: ["ruby { height: 10px; display: block;}"],
+ isActive: true,
+ },
+ {
+ info: "width is inactive on ruby-text element",
+ property: "width",
+ tagName: "rt",
+ rules: ["rt { width: 10px;}"],
+ isActive: false,
+ },
+ {
+ info: "height is inactive on ruby-text element",
+ property: "height",
+ tagName: "rt",
+ rules: ["rt { height: 10px;}"],
+ isActive: false,
+ },
+ {
+ info: "width is inactive on div elements with display ruby-text",
+ property: "width",
+ tagName: "div",
+ rules: ["div { width: 10px; display: ruby-text;}"],
+ isActive: false,
+ },
+ {
+ info: "height is inactive on div elements with display ruby-text",
+ property: "height",
+ tagName: "div",
+ rules: ["div { height: 10px; display: ruby-text;}"],
+ isActive: false,
+ },
+];
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..633d7e3aa6
--- /dev/null
+++ b/devtools/server/tests/chrome/inspector-delay-image-response.sjs
@@ -0,0 +1,46 @@
+/**
+ * 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) {
+ const query = {};
+ request.queryString.split("&").forEach(function (val) {
+ const [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 = Ci.nsITimer;
+
+ timer = Cc["@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..0b7edd8035
--- /dev/null
+++ b/devtools/server/tests/chrome/inspector-helpers.js
@@ -0,0 +1,133 @@
+/* exported attachURL, promiseDone,
+ promiseOnce,
+ addTest, addAsyncTest,
+ runNextTest, _documentWalker */
+"use strict";
+
+const { require } = ChromeUtils.importESModule(
+ "resource://devtools/shared/loader/Loader.sys.mjs"
+);
+const {
+ CommandsFactory,
+} = require("resource://devtools/shared/commands/commands-factory.js");
+const {
+ DevToolsServer,
+} = require("resource://devtools/server/devtools-server.js");
+const { BrowserTestUtils } = ChromeUtils.importESModule(
+ "resource://testing-common/BrowserTestUtils.sys.mjs"
+);
+const {
+ DocumentWalker: _documentWalker,
+} = require("resource://devtools/server/actors/inspector/document-walker.js");
+
+// 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();
+ }
+});
+
+/**
+ * Open a tab, load the url, wait for it to signal its readiness,
+ * connect to this tab via DevTools protocol and return.
+ *
+ * Returns an object with a few helpful attributes:
+ * - commands {Object}: The commands object defined by modules from devtools/shared/commands
+ * - target {TargetFront}: The current top-level target front.
+ * - doc {HtmlDocument}: the tab's document that got opened
+ */
+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 commands = await CommandsFactory.forTab(gBrowser.selectedTab);
+ await commands.targetCommand.startListening();
+
+ const cleanup = async function () {
+ await commands.destroy();
+ if (win) {
+ win.close();
+ }
+ };
+
+ gAttachCleanups.push(cleanup);
+ return {
+ commands,
+ target: commands.targetCommand.targetFront,
+ 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) {
+ SimpleTest.finish();
+ return;
+ }
+ const fn = _tests.shift();
+ try {
+ fn();
+ } catch (ex) {
+ info(
+ "Test function " +
+ (fn.name ? "'" + fn.name + "' " : "") +
+ "threw an exception: " +
+ ex
+ );
+ }
+}
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..e4db689134
--- /dev/null
+++ b/devtools/server/tests/chrome/memory-helpers.js
@@ -0,0 +1,72 @@
+/* exported Task, startServerAndGetSelectedTabMemory, destroyServerAndFinish,
+ waitForTime, waitUntil */
+"use strict";
+
+const { require } = ChromeUtils.importESModule(
+ "resource://devtools/shared/loader/Loader.sys.mjs"
+);
+const {
+ CommandsFactory,
+} = require("resource://devtools/shared/commands/commands-factory.js");
+
+// 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 commands = await CommandsFactory.forTab(
+ browserWindow.gBrowser.selectedTab
+ );
+ await commands.targetCommand.startListening();
+ const isEveryFrameTargetEnabled = Services.prefs.getBoolPref(
+ "devtools.every-frame-target.enabled",
+ false
+ );
+ if (!isEveryFrameTargetEnabled) {
+ return commands.targetCommand.targetFront;
+ }
+
+ // If EFT is enabled, we need to retrieve the target of the test document
+ const targets = await commands.targetCommand.getAllTargets([
+ commands.targetCommand.TYPES.FRAME,
+ ]);
+
+ return targets.find(t => t.url !== "chrome://mochikit/content/harness.xhtml");
+}
+
+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..cb41653cff
--- /dev/null
+++ b/devtools/server/tests/chrome/suspendTimeouts_content.js
@@ -0,0 +1,75 @@
+"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.
+ // This function is set and defined by test_suspendTimeouts.js
+ // eslint-disable-next-line no-undef
+ 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..d403d6b4a3
--- /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.importESModule("resource://gre/modules/jsdebugger.sys.mjs");
+addDebuggerToGlobal(globalThis);
+
+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.elementAttribute.html b/devtools/server/tests/chrome/test_Debugger.Source.prototype.elementAttribute.html
new file mode 100644
index 0000000000..cb9c2bbcdc
--- /dev/null
+++ b/devtools/server/tests/chrome/test_Debugger.Source.prototype.elementAttribute.html
@@ -0,0 +1,159 @@
+<!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.importESModule("resource://gre/modules/jsdebugger.sys.mjs");
+addSandboxedDebuggerToGlobal(globalThis);
+
+window.onload = function() {
+ SimpleTest.waitForExplicitFinish();
+
+ let log = "";
+ let doc, dieter, ulrich, isolde, albrecht;
+ let dbg, iframeDO;
+
+ // 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);
+ 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.elementAttributeName === undefined,
+ "top frame source doesn't belong to an attribute");
+
+ // The second stack frame should belong to 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.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.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";
+
+ 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.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.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.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..09c23b5253
--- /dev/null
+++ b/devtools/server/tests/chrome/test_Debugger.Source.prototype.introductionScript.html
@@ -0,0 +1,96 @@
+<!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.importESModule("resource://gre/modules/jsdebugger.sys.mjs");
+addDebuggerToGlobal(globalThis);
+
+window.onload = function() {
+ SimpleTest.waitForExplicitFinish();
+
+ let dbg, iframeDO, doc;
+
+ // 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;');";
+ 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");
+
+ // 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..1057d4b94f
--- /dev/null
+++ b/devtools/server/tests/chrome/test_Debugger.Source.prototype.introductionType.html
@@ -0,0 +1,159 @@
+<!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.importESModule("resource://gre/modules/jsdebugger.sys.mjs");
+addSandboxedDebuggerToGlobal(globalThis);
+
+let dbg;
+let iframeDO, doc;
+let Tootles;
+
+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");
+ 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
+ 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
+ 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;
+ iframeDO.makeDebuggeeValue(doc.getElementById("Auddie"));
+
+ is(fnDO.class, "Function",
+ "Script element 'Auddie' defined function '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");
+ rover.text = "debugger;";
+ doc.body.appendChild(rover);
+ iframeDO.makeDebuggeeValue(rover);
+
+ function RoverDebugger(frame) {
+ // sanity checks
+ 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.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..97f5b1e469
--- /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.importESModule("resource://devtools/shared/loader/Loader.sys.mjs");
+ 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: " + [...unseen_longhands].join(", "));
+
+ SimpleTest.finish();
+ };
+</script>
+</body>
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..b5d5c76c0c
--- /dev/null
+++ b/devtools/server/tests/chrome/test_css-logic-specificity.html
@@ -0,0 +1,84 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+Test that css-logic calculates CSS specificity properly
+-->
+<meta charset="utf-8">
+<title>Test css-logic specificity</title>
+<script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+<body style="background:blue;">
+<script>
+ "use strict";
+
+ window.onload = function() {
+ const {require} = ChromeUtils.importESModule("resource://devtools/shared/loader/Loader.sys.mjs");
+ const {CssLogic, CssSelector} = require("devtools/server/actors/inspector/css-logic");
+
+ 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>' + text + " {color:red;}</style>";
+ 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);
+
+ // There could be more stylesheets due to e.g, accessiblecaret, so find the
+ // right one.
+ info(`Sheets: ${cssLogic.sheets.map(s => s.href).join(", ")}`);
+
+ const cssSheet = cssLogic.sheets.find(s => s.href == location.href);
+ const cssRule = cssSheet.domSheet.cssRules[0];
+ const selectors = CssLogic.getSelectors(cssRule);
+
+ is(selectors.length, TEST_DATA.length, "Should be the right rule");
+
+ info("Iterating over the test selectors: " + selectors.join(", "));
+ 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 = selector.cssRule.selectorSpecificityAt(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..6378f5a9e7
--- /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..3cbc3a4aa7
--- /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", "revert-layer"]),
+ "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..117e50b5ca
--- /dev/null
+++ b/devtools/server/tests/chrome/test_device.html
@@ -0,0 +1,79 @@
+<!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.importESModule("resource://devtools/shared/loader/Loader.sys.mjs");
+ const {DevToolsClient} = require("devtools/client/devtools-client");
+ const {DevToolsServer} = require("devtools/server/devtools-server");
+
+ 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 && 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..6a846596b2
--- /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.importESModule("resource://gre/modules/jsdebugger.sys.mjs");
+addDebuggerToGlobal(globalThis);
+
+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_highlighter_paused_debugger.html b/devtools/server/tests/chrome/test_highlighter_paused_debugger.html
new file mode 100644
index 0000000000..82dc939dd6
--- /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.importESModule("resource://devtools/shared/loader/Loader.sys.mjs");
+ 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.root.getElementById(id(elementID)).getAttribute("hidden");
+ return typeof attr === "string" && attr == "true";
+ }
+
+ function getReason() {
+ return anonymousContent.root.getElementById(id("reason")).textContent;
+ }
+
+ function isOverlayShown() {
+ const attr = anonymousContent.root.getElementById(id("root")).getAttribute("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-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..86c783c035
--- /dev/null
+++ b/devtools/server/tests/chrome/test_inspector-inactive-property-helper.html
@@ -0,0 +1,124 @@
+<!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.importESModule("resource://devtools/shared/loader/Loader.sys.mjs");
+ const { isPropertyUsed } = require("devtools/server/actors/utils/inactive-property-helper");
+
+ const INACTIVE_CSS_PREF = "devtools.inspector.inactive.css.enabled";
+ const CUSTOM_HIGHLIGHT_API = "dom.customHighlightAPI.enabled";
+ const TEXT_WRAP_BALANCE = "layout.css.text-wrap-balance.enabled";
+
+ Services.prefs.setBoolPref(INACTIVE_CSS_PREF, true);
+ Services.prefs.setBoolPref(CUSTOM_HIGHLIGHT_API, true);
+ Services.prefs.setBoolPref(TEXT_WRAP_BALANCE, true);
+
+ SimpleTest.registerCleanupFunction(() => {
+ Services.prefs.clearUserPref(INACTIVE_CSS_PREF);
+ Services.prefs.clearUserPref(CUSTOM_HIGHLIGHT_API);
+ Services.prefs.clearUserPref(TEXT_WRAP_BALANCE);
+ });
+
+ 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.mjs",
+ "border-image.mjs",
+ "cue-pseudo-element.mjs",
+ "first-letter-pseudo-element.mjs",
+ "first-line-pseudo-element.mjs",
+ "flex-grid-item-properties.mjs",
+ "float.mjs",
+ "gap.mjs",
+ "grid-container-properties.mjs",
+ "grid-with-absolute-properties.mjs",
+ "multicol-container-properties.mjs",
+ "highlight-pseudo-elements.mjs",
+ "margin-padding.mjs",
+ "max-min-width-height.mjs",
+ "place-items-content.mjs",
+ "placeholder-pseudo-element.mjs",
+ "positioned.mjs",
+ "scroll-padding.mjs",
+ "vertical-align.mjs",
+ "table.mjs",
+ "table-cell.mjs",
+ "text-overflow.mjs",
+ "text-wrap.mjs",
+ "width-height-ruby.mjs",
+ ].map(file => `${FOLDER}/${file}`);
+
+ // Import all the test cases
+ const tests =
+ (await Promise.all(testFiles.map(f => import(f).then(data => data.default)))).flat();
+
+ for (const {
+ info: summary,
+ property,
+ tagName,
+ createTestElement,
+ rules,
+ ruleIndex,
+ isActive,
+ expectedMsgId,
+ } 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, msgId } = isPropertyUsed(el, getComputedStyle(el), rule, property);
+ ok(used === isActive, summary);
+ if (expectedMsgId) {
+ is(msgId, expectedMsgId, `${summary} - returned expected msgId`);
+ }
+
+ 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..9430db65bd
--- /dev/null
+++ b/devtools/server/tests/chrome/test_inspector-mutations-attr.html
@@ -0,0 +1,169 @@
+<!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 childFrame.walkerFront.children(childFrame);
+ }).then(children => {
+ const nodes = children.nodes;
+ is(nodes.length, 1, "There should be only one child of the iframe");
+ const [iframeNode] = nodes;
+ is(iframeNode.nodeType, Node.DOCUMENT_NODE, "iframe child should be a document node");
+ return iframeNode.walkerFront.querySelector(iframeNode, "#a");
+ }).then(node => {
+ attrFront = node;
+ }).then(runNextTest));
+}
+
+function testAddAttribute() {
+ attrNode.setAttribute("data-newattr", "newvalue");
+ attrNode.setAttribute("data-newattr2", "newvalue");
+ attrFront.walkerFront.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");
+ attrFront.walkerFront.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");
+ attrFront.walkerFront.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");
+
+ attrFront.walkerFront.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..14e93b9d1c
--- /dev/null
+++ b/devtools/server/tests/chrome/test_inspector-mutations-value.html
@@ -0,0 +1,163 @@
+<!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");
+ const [node] =nodes;
+ is(node.nodeType, Node.DOCUMENT_NODE, "iframe child should be a document node");
+ return node.walkerFront.querySelector(node, "#longstring");
+ }).then(node => {
+ longStringFront = node;
+ return longStringFront.walkerFront.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;
+ valueFront.walkerFront.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;
+ valueFront.walkerFront.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;
+ valueFront.walkerFront.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;
+ valueFront.walkerFront.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..74aa3c50ce
--- /dev/null
+++ b/devtools/server/tests/chrome/test_inspector-pick-color.html
@@ -0,0 +1,94 @@
+<!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 = new win.KeyboardEvent("keydown", {
+ bubbles: true,
+ cancelable: true,
+ view: win,
+ keyCode: 27
+ });
+ 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..949066255d
--- /dev/null
+++ b/devtools/server/tests/chrome/test_inspector-pseudoclass-lock.html
@@ -0,0 +1,185 @@
+<!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();
+};
+
+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..09bd31cf75
--- /dev/null
+++ b/devtools/server/tests/chrome/test_inspector-reload.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;
+let gResourceCommand = null;
+let gCommands = null;
+
+addTest(async function setup() {
+ const url = document.getElementById("inspectorContent").href;
+ const { commands, doc } = await attachURL(url);
+ const target = commands.targetCommand.targetFront;
+ const inspector = await target.getFront("inspector");
+ gInspectee = doc;
+ const walker = inspector.walker;
+ gWalker = await inspector.getWalker();
+ gResourceCommand = commands.resourceCommand;
+ gCommands = commands;
+
+ 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 gResourceCommand.watchResources([gResourceCommand.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;
+ gWalker = (await gCommands.targetCommand.targetFront.getFront("inspector")).walker;
+
+ 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;
+ gResourceCommand = 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..a107f9ba4a
--- /dev/null
+++ b/devtools/server/tests/chrome/test_inspector-scroll-into-view.html
@@ -0,0 +1,60 @@
+<!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 = async function() {
+ SimpleTest.waitForExplicitFinish();
+
+ const url = document.getElementById("inspectorContent").href;
+ const { target, doc } = await attachURL(url);
+ const inspector = await target.getFront("inspector");
+ const walker = inspector.walker;
+
+ const id = "#scroll-into-view";
+ let rect = doc.querySelector(id).getBoundingClientRect();
+ const nodeFront = await walker.querySelector(walker.rootNode, id);
+ let inViewport = rect.x >= 0 &&
+ rect.y >= 0 &&
+ rect.y <= doc.defaultView.innerHeight &&
+ rect.x <= doc.defaultView.innerWidth;
+
+ ok(!inViewport, "Element is not in viewport initially");
+
+ await nodeFront.scrollIntoView();
+
+ await new Promise(res => SimpleTest.executeSoon(res));
+
+ rect = doc.querySelector(id).getBoundingClientRect();
+ inViewport = rect.x >= 0 &&
+ rect.y >= 0 &&
+ rect.y <= doc.defaultView.innerHeight &&
+ rect.x <= doc.defaultView.innerWidth;
+ ok(inViewport, "Element is in viewport after calling nodeFront.scrollIntoView");
+
+ await target.destroy();
+ SimpleTest.finish();
+};
+ </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..a78700e8e6
--- /dev/null
+++ b/devtools/server/tests/chrome/test_inspector-search-front.html
@@ -0,0 +1,163 @@
+<!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 inspectorCommand = null;
+
+ // WalkerFront and Inspector Command specific tests. These aren't to exercise search
+ // edge cases so much as to test the state the Front maintains between
+ // searches.
+
+ addAsyncTest(async function setup() {
+ info("Setting up inspector and walker actors.");
+
+ const url = document.getElementById("inspectorContent").href;
+
+ const { commands } = await attachURL(url);
+ const target = commands.targetCommand.targetFront;
+ const inspector = await target.getFront("inspector");
+
+ walkerFront = inspector.walker;
+ inspectorCommand = commands.inspectorCommand;
+
+ runNextTest();
+ });
+
+ addAsyncTest(async function testWalkerFrontDefaults() {
+ info("Testing search API using WalkerFront and Inspector Command.");
+ const nodes = await walkerFront.querySelectorAll(walkerFront.rootNode, "h2");
+ const fronts = await nodes.items();
+
+ const commandResult = await inspectorCommand.findNextNode("");
+ ok(!commandResult, "Null result on front when searching for ''");
+
+ let results = await inspectorCommand.findNextNode("h2");
+ isDeeply(results, {
+ node: fronts[0],
+ resultsIndex: 0,
+ resultsLength: 3,
+ }, "Default options work");
+
+ results = await inspectorCommand.findNextNode("h2", { });
+ isDeeply(results, {
+ node: fronts[1],
+ resultsIndex: 1,
+ resultsLength: 3,
+ }, "Search works with empty options");
+
+ // Clear search data to remove result state on the front
+ await inspectorCommand.findNextNode("");
+ runNextTest();
+ });
+
+ addAsyncTest(async function testMultipleSearches() {
+ info("Testing search API using WalkerFront and Inspector Command (reverse=false)");
+ const nodes = await walkerFront.querySelectorAll(walkerFront.rootNode, "h2");
+ const fronts = await nodes.items();
+
+ let results = await inspectorCommand.findNextNode("h2");
+ isDeeply(results, {
+ node: fronts[0],
+ resultsIndex: 0,
+ resultsLength: 3,
+ }, "Search works with multiple results (reverse=false)");
+
+ results = await inspectorCommand.findNextNode("h2");
+ isDeeply(results, {
+ node: fronts[1],
+ resultsIndex: 1,
+ resultsLength: 3,
+ }, "Search works with multiple results (reverse=false)");
+
+ results = await inspectorCommand.findNextNode("h2");
+ isDeeply(results, {
+ node: fronts[2],
+ resultsIndex: 2,
+ resultsLength: 3,
+ }, "Search works with multiple results (reverse=false)");
+
+ results = await inspectorCommand.findNextNode("h2");
+ isDeeply(results, {
+ node: fronts[0],
+ resultsIndex: 0,
+ resultsLength: 3,
+ }, "Search works with multiple results (reverse=false)");
+
+ // Clear search data to remove result state on the front
+ await inspectorCommand.findNextNode("");
+ runNextTest();
+ });
+
+ addAsyncTest(async function testMultipleSearchesReverse() {
+ info("Testing search API using WalkerFront and Inspector Command (reverse=true)");
+ const nodes = await walkerFront.querySelectorAll(walkerFront.rootNode, "h2");
+ const fronts = await nodes.items();
+
+ let results = await inspectorCommand.findNextNode("h2", {reverse: true});
+ isDeeply(results, {
+ node: fronts[2],
+ resultsIndex: 2,
+ resultsLength: 3,
+ }, "Search works with multiple results (reverse=true)");
+
+ results = await inspectorCommand.findNextNode("h2", {reverse: true});
+ isDeeply(results, {
+ node: fronts[1],
+ resultsIndex: 1,
+ resultsLength: 3,
+ }, "Search works with multiple results (reverse=true)");
+
+ results = await inspectorCommand.findNextNode("h2", {reverse: true});
+ isDeeply(results, {
+ node: fronts[0],
+ resultsIndex: 0,
+ resultsLength: 3,
+ }, "Search works with multiple results (reverse=true)");
+
+ results = await inspectorCommand.findNextNode("h2", {reverse: true});
+ isDeeply(results, {
+ node: fronts[2],
+ resultsIndex: 2,
+ resultsLength: 3,
+ }, "Search works with multiple results (reverse=true)");
+
+ results = await inspectorCommand.findNextNode("h2", {reverse: false});
+ isDeeply(results, {
+ node: fronts[0],
+ resultsIndex: 0,
+ resultsLength: 3,
+ }, "Search works with multiple results (reverse=false)");
+
+ // Clear search data to remove result state on the command
+ await inspectorCommand.findNextNode("");
+ 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..129116b913
--- /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 = "https://example.com/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..d95b0e5fd3
--- /dev/null
+++ b/devtools/server/tests/chrome/test_inspector_getImageData.html
@@ -0,0 +1,142 @@
+<!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(async function testLargeImage() {
+ // Select the image node from the test page
+ const img = await gWalker.querySelector(gWalker.rootNode, ".big-horizontal");
+ ok(img, "Image node found in the test page");
+ ok(img.getImageData, "Image node has the getImageData function");
+ const imageData = await img.getImageData(100);
+ 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");
+ const str = await imageData.data.string();
+ ok(str, "We have an image data string!");
+ testResizing(imageData, str);
+});
+
+addTest(async function testLargeCanvas() {
+ // Select the canvas node from the test page
+ const canvas = await gWalker.querySelector(gWalker.rootNode, ".big-vertical");
+ ok(canvas, "Image node found in the test page");
+ ok(canvas.getImageData, "Image node has the getImageData function");
+ const imageData = await canvas.getImageData(350);
+ 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");
+ const str = await imageData.data.string();
+ ok(str, "We have an image data string!");
+ testResizing(imageData, str);
+});
+
+addTest(async function testSmallImage() {
+ // Select the small image node from the test page
+ const img = await gWalker.querySelector(gWalker.rootNode, ".small");
+ ok(img, "Image node found in the test page");
+ ok(img.getImageData, "Image node has the getImageData function");
+ const imageData = await img.getImageData();
+ 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");
+ const str = await imageData.data.string();
+ ok(str, "We have an image data string!");
+ testResizing(imageData, str);
+});
+
+addTest(async function testDataImage() {
+ // Select the data image node from the test page
+ const img = await gWalker.querySelector(gWalker.rootNode, ".data");
+ ok(img, "Image node found in the test page");
+ ok(img.getImageData, "Image node has the getImageData function");
+ const imageData = await img.getImageData(14);
+ 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");
+ const str = await imageData.data.string();
+ ok(str, "We have an image data string!");
+ testResizing(imageData, str);
+});
+
+addTest(async function testNonImgOrCanvasElements() {
+ const body = await gWalker.querySelector(gWalker.rootNode, "body");
+ await ensureRejects(body.getImageData(), "Invalid element");
+ 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..451c49dcc3
--- /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 = "https://example.com/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..d800798427
--- /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.importESModule("resource://gre/modules/jsdebugger.sys.mjs");
+addSandboxedDebuggerToGlobal(globalThis);
+
+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..632903bc04
--- /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({});
+ }
+ }, 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..590b3358e4
--- /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({});
+ }
+
+ // 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..d6223dd062
--- /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({});
+ }
+
+ 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..ce5ba4d2ad
--- /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({});
+ }
+
+ 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..89e23f5ed6
--- /dev/null
+++ b/devtools/server/tests/chrome/test_memory_attach_02.html
@@ -0,0 +1,44 @@
+<!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();
+
+ await memory.attach();
+ ok(true, "We can call attach many times, the duplicates will be ignored");
+
+ 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..b4d23a24aa
--- /dev/null
+++ b/devtools/server/tests/chrome/test_preference.html
@@ -0,0 +1,128 @@
+<!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.importESModule("resource://devtools/shared/loader/Loader.sys.mjs");
+ const {DevToolsClient} = require("devtools/client/devtools-client");
+ const {DevToolsServer} = require("devtools/server/devtools-server");
+
+ 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..0910e9d7bc
--- /dev/null
+++ b/devtools/server/tests/chrome/test_styles-applied.html
@@ -0,0 +1,155 @@
+<!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 { commands, target } = await attachURL(url);
+
+ // We need an active resource command before initializing the inspector front.
+ const resourceCommand = commands.resourceCommand;
+ // We listen to any random resource, we only need to trigger the resource command
+ // onTargetAvailable callback so the `resourceCommand` attribute is set on the target front.
+ await resourceCommand.watchResources([resourceCommand.TYPES.DOCUMENT_EVENT], { onAvailable: () => {} });
+
+ const inspector = await target.getFront("inspector");
+ gWalker = inspector.walker;
+ gStyles = await inspector.getPageStyle();
+
+ runNextTest();
+});
+
+addTest(async function inheritedUserStyles() {
+ const node = await gWalker.querySelector(gWalker.rootNode, "#test-node")
+ const applied = await gStyles.getApplied(node, { inherited: true, filter: "user" });
+
+ 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].matchedDesugaredSelectors,
+ "Shouldn't get matchedDesugaredSelectors 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].matchedDesugaredSelectors, "Shouldn't get matchedDesugaredSelectors with this request.");
+
+ is(applied.length, 5, "Should have 5 rules.");
+
+ runNextTest();
+});
+
+addTest(async function inheritedSystemStyles() {
+ const node = await gWalker.querySelector(gWalker.rootNode, "#test-node");
+ const applied = await gStyles.getApplied(node, { inherited: true, filter: "ua" });
+ // 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, 9, "Should have the expected number of rules.");
+
+ runNextTest();
+});
+
+addTest(async function noInheritedStyles() {
+ const node = await gWalker.querySelector(gWalker.rootNode, "#test-node")
+ const applied = await gStyles.getApplied(node, { inherited: false, filter: "user" });
+ 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.");
+
+ runNextTest();
+});
+
+addTest(async function matchedSelectors() {
+ const node = await gWalker.querySelector(gWalker.rootNode, "#test-node");
+ const applied = await gStyles.getApplied(node, {
+ inherited: true, filter: "user", matchedSelectors: true,
+ });
+ is(applied[3].matchedDesugaredSelectors[0], ".inheritable-rule",
+ "Entry 3 should have a matched selector");
+ is(applied[4].matchedDesugaredSelectors[0], ".inheritable-rule",
+ "Entry 4 should have a matched selector");
+
+ runNextTest();
+});
+
+addTest(async function testMediaQuery() {
+ const node = await gWalker.querySelector(gWalker.rootNode, "#mediaqueried")
+ const applied = await gStyles.getApplied(node, {
+ inherited: false,
+ filter: "user",
+ matchedSelectors: true,
+ });
+
+ const ruleWithMedia = applied[1].rule;
+ is(ruleWithMedia.type, 1, "Entry 1 is a rule style");
+ is(ruleWithMedia.ancestorData[0].type, "media", "Entry 1's rule ancestor data holds the media rule data...");
+ is(ruleWithMedia.ancestorData[0].value, "screen", "...with the expected value");
+
+ 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..9aa962108a
--- /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-collapse"].value, "collapse", "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-collapse"].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-collapse"].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-collapse" 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-collapse" 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..42e1ec7885
--- /dev/null
+++ b/devtools/server/tests/chrome/test_styles-matched.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 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 { commands, target, doc } = await attachURL(url);
+ gInspectee = doc;
+
+ // We need an active resource command before initializing the inspector front.
+ const resourceCommand = commands.resourceCommand;
+ // We listen to any random resource, we only need to trigger the resource command
+ // onTargetAvailable callback so the `resourceCommand` attribute is set on the target front.
+ await resourceCommand.watchResources([resourceCommand.TYPES.DOCUMENT_EVENT], { onAvailable: () => {} });
+
+ 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..614ac60cdb
--- /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..eca1e7d43e
--- /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.importESModule("resource://gre/modules/jsdebugger.sys.mjs");
+addDebuggerToGlobal(globalThis);
+
+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..0c54f65964
--- /dev/null
+++ b/devtools/server/tests/chrome/test_webconsole-node-grip.html
@@ -0,0 +1,66 @@
+<!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 commands = await addTabAndCreateCommands(TEST_URL);
+ await testNotInTreeElementNode(commands);
+ await testInTreeElementNode(commands);
+ await testNotInTreeTextNode(commands);
+ await testInTreeTextNode(commands);
+ } catch (e) {
+ ok(false, `Error thrown: ${e.message}`);
+ }
+ SimpleTest.finish();
+};
+
+async function testNotInTreeElementNode(commands) {
+ info("Testing isConnected property on a ElementNode not in the DOM tree");
+ const {result} = await commands.scriptCommand.execute("document.createElement(\"div\")");
+ is(result.getGrip().preview.isConnected, false,
+ "isConnected is false since we only created the element");
+}
+
+async function testInTreeElementNode(commands) {
+ info("Testing isConnected property on a ElementNode in the DOM tree");
+ const {result} = await commands.scriptCommand.execute("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(commands) {
+ info("Testing isConnected property on a TextNode not in the DOM tree");
+ const {result} = await commands.scriptCommand.execute("document.createTextNode(\"Hello\")");
+ is(result.getGrip().preview.isConnected, false,
+ "isConnected is false since we only created the element");
+}
+
+async function testInTreeTextNode(commands) {
+ info("Testing isConnected property on a TextNode in the DOM tree");
+ const {result} = await commands.scriptCommand.execute("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/webconsole-helpers.js b/devtools/server/tests/chrome/webconsole-helpers.js
new file mode 100644
index 0000000000..8be8554e35
--- /dev/null
+++ b/devtools/server/tests/chrome/webconsole-helpers.js
@@ -0,0 +1,54 @@
+/* exported addTabAndCreateCommands */
+"use strict";
+
+const { require } = ChromeUtils.importESModule(
+ "resource://devtools/shared/loader/Loader.sys.mjs"
+);
+const {
+ DevToolsServer,
+} = require("resource://devtools/server/devtools-server.js");
+const {
+ CommandsFactory,
+} = require("resource://devtools/shared/commands/commands-factory.js");
+
+// 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 commands are initialized
+ * The Promise resolves with the commands.
+ */
+async function addTabAndCreateCommands(url) {
+ const tab = await addTab(url);
+ const commands = await CommandsFactory.forTab(tab);
+ await commands.targetCommand.startListening();
+ return commands;
+}
+
+/**
+ * Naive implementaion of addTab working from a mochitest-chrome test.
+ */
+async function addTab(url) {
+ const { gBrowser } = Services.wm.getMostRecentWindow("navigator:browser");
+ const { BrowserTestUtils } = ChromeUtils.importESModule(
+ "resource://testing-common/BrowserTestUtils.sys.mjs"
+ );
+ const tab = (gBrowser.selectedTab = BrowserTestUtils.addTab(gBrowser, url));
+ await BrowserTestUtils.browserLoaded(tab.linkedBrowser);
+ return tab;
+}