summaryrefslogtreecommitdiffstats
path: root/devtools/server/tests/mochitest
diff options
context:
space:
mode:
authorMatt A. Tobin <mattatobin@localhost.localdomain>2018-02-02 04:16:08 -0500
committerMatt A. Tobin <mattatobin@localhost.localdomain>2018-02-02 04:16:08 -0500
commit5f8de423f190bbb79a62f804151bc24824fa32d8 (patch)
tree10027f336435511475e392454359edea8e25895d /devtools/server/tests/mochitest
parent49ee0794b5d912db1f95dce6eb52d781dc210db5 (diff)
downloadUXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar
UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.gz
UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.lz
UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.xz
UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.zip
Add m-esr52 at 52.6.0
Diffstat (limited to 'devtools/server/tests/mochitest')
-rw-r--r--devtools/server/tests/mochitest/.eslintrc.js6
-rw-r--r--devtools/server/tests/mochitest/Debugger.Source.prototype.element-2.js1
-rw-r--r--devtools/server/tests/mochitest/Debugger.Source.prototype.element.html17
-rw-r--r--devtools/server/tests/mochitest/Debugger.Source.prototype.element.js1
-rw-r--r--devtools/server/tests/mochitest/animation-data.html120
-rw-r--r--devtools/server/tests/mochitest/chrome.ini103
-rw-r--r--devtools/server/tests/mochitest/director-helpers.js44
-rw-r--r--devtools/server/tests/mochitest/hello-actor.js26
-rw-r--r--devtools/server/tests/mochitest/inspector-delay-image-response.sjs42
-rw-r--r--devtools/server/tests/mochitest/inspector-eyedropper.html18
-rw-r--r--devtools/server/tests/mochitest/inspector-helpers.js310
-rw-r--r--devtools/server/tests/mochitest/inspector-search-data.html52
-rw-r--r--devtools/server/tests/mochitest/inspector-styles-data.css3
-rw-r--r--devtools/server/tests/mochitest/inspector-styles-data.html81
-rw-r--r--devtools/server/tests/mochitest/inspector-traversal-data.html90
-rw-r--r--devtools/server/tests/mochitest/inspector_css-properties.html10
-rw-r--r--devtools/server/tests/mochitest/inspector_getImageData.html21
-rw-r--r--devtools/server/tests/mochitest/large-image.jpgbin0 -> 793541 bytes
-rw-r--r--devtools/server/tests/mochitest/memory-helpers.js52
-rw-r--r--devtools/server/tests/mochitest/nonchrome_unsafeDereference.html8
-rw-r--r--devtools/server/tests/mochitest/setup-in-child.js20
-rw-r--r--devtools/server/tests/mochitest/setup-in-parent.js10
-rw-r--r--devtools/server/tests/mochitest/small-image.gifbin0 -> 510655 bytes
-rw-r--r--devtools/server/tests/mochitest/test_Debugger.Script.prototype.global.html48
-rw-r--r--devtools/server/tests/mochitest/test_Debugger.Source.prototype.element.html182
-rw-r--r--devtools/server/tests/mochitest/test_Debugger.Source.prototype.introductionScript.html97
-rw-r--r--devtools/server/tests/mochitest/test_Debugger.Source.prototype.introductionType.html181
-rw-r--r--devtools/server/tests/mochitest/test_animation_actor-lifetime.html91
-rw-r--r--devtools/server/tests/mochitest/test_connectToChild.html134
-rw-r--r--devtools/server/tests/mochitest/test_connection-manager.html119
-rw-r--r--devtools/server/tests/mochitest/test_css-logic-media-queries.html62
-rw-r--r--devtools/server/tests/mochitest/test_css-logic-specificity.html84
-rw-r--r--devtools/server/tests/mochitest/test_css-logic.html167
-rw-r--r--devtools/server/tests/mochitest/test_css-properties_01.html121
-rw-r--r--devtools/server/tests/mochitest/test_css-properties_02.html86
-rw-r--r--devtools/server/tests/mochitest/test_device.html99
-rw-r--r--devtools/server/tests/mochitest/test_director.html114
-rw-r--r--devtools/server/tests/mochitest/test_director_connectToChild.html98
-rw-r--r--devtools/server/tests/mochitest/test_executeInGlobal-outerized_this.html69
-rw-r--r--devtools/server/tests/mochitest/test_framerate_01.html141
-rw-r--r--devtools/server/tests/mochitest/test_framerate_02.html113
-rw-r--r--devtools/server/tests/mochitest/test_framerate_03.html82
-rw-r--r--devtools/server/tests/mochitest/test_framerate_04.html72
-rw-r--r--devtools/server/tests/mochitest/test_framerate_05.html77
-rw-r--r--devtools/server/tests/mochitest/test_framerate_06.html82
-rw-r--r--devtools/server/tests/mochitest/test_getProcess.html120
-rw-r--r--devtools/server/tests/mochitest/test_inspector-anonymous.html201
-rw-r--r--devtools/server/tests/mochitest/test_inspector-changeattrs.html99
-rw-r--r--devtools/server/tests/mochitest/test_inspector-changevalue.html82
-rw-r--r--devtools/server/tests/mochitest/test_inspector-dead-nodes.html386
-rw-r--r--devtools/server/tests/mochitest/test_inspector-duplicate-node.html75
-rw-r--r--devtools/server/tests/mochitest/test_inspector-hide.html76
-rw-r--r--devtools/server/tests/mochitest/test_inspector-insert.html119
-rw-r--r--devtools/server/tests/mochitest/test_inspector-mutations-attr.html167
-rw-r--r--devtools/server/tests/mochitest/test_inspector-mutations-childlist.html310
-rw-r--r--devtools/server/tests/mochitest/test_inspector-mutations-events.html183
-rw-r--r--devtools/server/tests/mochitest/test_inspector-mutations-frameload.html214
-rw-r--r--devtools/server/tests/mochitest/test_inspector-mutations-value.html168
-rw-r--r--devtools/server/tests/mochitest/test_inspector-pick-color.html101
-rw-r--r--devtools/server/tests/mochitest/test_inspector-pseudoclass-lock.html174
-rw-r--r--devtools/server/tests/mochitest/test_inspector-release.html102
-rw-r--r--devtools/server/tests/mochitest/test_inspector-reload.html80
-rw-r--r--devtools/server/tests/mochitest/test_inspector-remove.html117
-rw-r--r--devtools/server/tests/mochitest/test_inspector-resize.html77
-rw-r--r--devtools/server/tests/mochitest/test_inspector-resolve-url.html88
-rw-r--r--devtools/server/tests/mochitest/test_inspector-retain.html180
-rw-r--r--devtools/server/tests/mochitest/test_inspector-scroll-into-view.html87
-rw-r--r--devtools/server/tests/mochitest/test_inspector-search-front.html217
-rw-r--r--devtools/server/tests/mochitest/test_inspector-search.html296
-rw-r--r--devtools/server/tests/mochitest/test_inspector-traversal.html354
-rw-r--r--devtools/server/tests/mochitest/test_inspector_getImageData-wait-for-load.html136
-rw-r--r--devtools/server/tests/mochitest/test_inspector_getImageData.html166
-rw-r--r--devtools/server/tests/mochitest/test_inspector_getImageDataFromURL.html114
-rw-r--r--devtools/server/tests/mochitest/test_inspector_getNodeFromActor.html88
-rw-r--r--devtools/server/tests/mochitest/test_makeGlobalObjectReference.html86
-rw-r--r--devtools/server/tests/mochitest/test_memory.html37
-rw-r--r--devtools/server/tests/mochitest/test_memory_allocations_01.html98
-rw-r--r--devtools/server/tests/mochitest/test_memory_allocations_02.html76
-rw-r--r--devtools/server/tests/mochitest/test_memory_allocations_03.html78
-rw-r--r--devtools/server/tests/mochitest/test_memory_allocations_04.html60
-rw-r--r--devtools/server/tests/mochitest/test_memory_allocations_05.html87
-rw-r--r--devtools/server/tests/mochitest/test_memory_allocations_06.html49
-rw-r--r--devtools/server/tests/mochitest/test_memory_allocations_07.html55
-rw-r--r--devtools/server/tests/mochitest/test_memory_attach_01.html31
-rw-r--r--devtools/server/tests/mochitest/test_memory_attach_02.html49
-rw-r--r--devtools/server/tests/mochitest/test_memory_census.html33
-rw-r--r--devtools/server/tests/mochitest/test_memory_gc_01.html46
-rw-r--r--devtools/server/tests/mochitest/test_memory_gc_events.html42
-rw-r--r--devtools/server/tests/mochitest/test_preference.html115
-rw-r--r--devtools/server/tests/mochitest/test_settings.html130
-rw-r--r--devtools/server/tests/mochitest/test_setupInParentChild.html110
-rw-r--r--devtools/server/tests/mochitest/test_styles-applied.html145
-rw-r--r--devtools/server/tests/mochitest/test_styles-computed.html139
-rw-r--r--devtools/server/tests/mochitest/test_styles-layout.html116
-rw-r--r--devtools/server/tests/mochitest/test_styles-matched.html98
-rw-r--r--devtools/server/tests/mochitest/test_styles-modify.html116
-rw-r--r--devtools/server/tests/mochitest/test_styles-svg.html70
-rw-r--r--devtools/server/tests/mochitest/test_unsafeDereference.html52
-rw-r--r--devtools/server/tests/mochitest/test_websocket-server.html82
99 files changed, 9931 insertions, 0 deletions
diff --git a/devtools/server/tests/mochitest/.eslintrc.js b/devtools/server/tests/mochitest/.eslintrc.js
new file mode 100644
index 000000000..c5b919ce3
--- /dev/null
+++ b/devtools/server/tests/mochitest/.eslintrc.js
@@ -0,0 +1,6 @@
+"use strict";
+
+module.exports = {
+ // Extend from the common devtools mochitest eslintrc config.
+ "extends": "../../../.eslintrc.xpcshell.js"
+};
diff --git a/devtools/server/tests/mochitest/Debugger.Source.prototype.element-2.js b/devtools/server/tests/mochitest/Debugger.Source.prototype.element-2.js
new file mode 100644
index 000000000..eab746921
--- /dev/null
+++ b/devtools/server/tests/mochitest/Debugger.Source.prototype.element-2.js
@@ -0,0 +1 @@
+debugger;
diff --git a/devtools/server/tests/mochitest/Debugger.Source.prototype.element.html b/devtools/server/tests/mochitest/Debugger.Source.prototype.element.html
new file mode 100644
index 000000000..fcf4c6c85
--- /dev/null
+++ b/devtools/server/tests/mochitest/Debugger.Source.prototype.element.html
@@ -0,0 +1,17 @@
+<head>
+ <!-- Static (not dynamically inserted) inline script. -->
+ <script id='franz'>function franz() { 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/mochitest/Debugger.Source.prototype.element.js b/devtools/server/tests/mochitest/Debugger.Source.prototype.element.js
new file mode 100644
index 000000000..44115b373
--- /dev/null
+++ b/devtools/server/tests/mochitest/Debugger.Source.prototype.element.js
@@ -0,0 +1 @@
+function heinrichFun() { franz(); }
diff --git a/devtools/server/tests/mochitest/animation-data.html b/devtools/server/tests/mochitest/animation-data.html
new file mode 100644
index 000000000..01be59548
--- /dev/null
+++ b/devtools/server/tests/mochitest/animation-data.html
@@ -0,0 +1,120 @@
+<html>
+<head>
+ <meta charset="UTF-8">
+ <title>Animation Test Data</title>
+ <style>
+ .ball {
+ width: 80px;
+ height: 80px;
+ border-radius: 50%;
+ background: #f06;
+
+ position: absolute;
+ }
+
+ .still {
+ top: 0;
+ left: 10px;
+ }
+
+ .animated {
+ top: 100px;
+ left: 10px;
+
+ animation: simple-animation 2s infinite alternate;
+ }
+
+ .multi {
+ top: 200px;
+ left: 10px;
+
+ animation: simple-animation 2s infinite alternate,
+ other-animation 5s infinite alternate;
+ }
+
+ .delayed {
+ top: 300px;
+ left: 10px;
+ background: rebeccapurple;
+
+ animation: simple-animation 3s 60s 10;
+ }
+
+ .multi-finite {
+ top: 400px;
+ left: 10px;
+ background: yellow;
+
+ animation: simple-animation 3s,
+ other-animation 4s;
+ }
+
+ .short {
+ top: 500px;
+ left: 10px;
+ background: red;
+
+ animation: simple-animation 2s;
+ }
+
+ .long {
+ top: 600px;
+ left: 10px;
+ background: blue;
+
+ animation: simple-animation 120s;
+ }
+
+ .negative-delay {
+ top: 700px;
+ left: 10px;
+ background: gray;
+
+ animation: simple-animation 15s -10s;
+ animation-fill-mode: forwards;
+ }
+
+ .no-compositor {
+ top: 0;
+ right: 10px;
+ background: gold;
+
+ animation: no-compositor 10s cubic-bezier(.57,-0.02,1,.31) forwards;
+ }
+
+ @keyframes simple-animation {
+ 100% {
+ transform: translateX(300px);
+ }
+ }
+
+ @keyframes other-animation {
+ 100% {
+ background: blue;
+ }
+ }
+
+ @keyframes no-compositor {
+ 100% {
+ margin-right: 600px;
+ }
+ }
+ </style>
+ <script type="text/javascript">
+ window.onload = function() {
+ window.opener.postMessage('ready', '*');
+ };
+ </script>
+</head>
+</body>
+ <div class="ball still"></div>
+ <div class="ball animated"></div>
+ <div class="ball multi"></div>
+ <div class="ball delayed"></div>
+ <div class="ball multi-finite"></div>
+ <div class="ball short"></div>
+ <div class="ball long"></div>
+ <div class="ball negative-delay"></div>
+ <div class="ball no-compositor"></div>
+</body>
+</html>
diff --git a/devtools/server/tests/mochitest/chrome.ini b/devtools/server/tests/mochitest/chrome.ini
new file mode 100644
index 000000000..ae69d163e
--- /dev/null
+++ b/devtools/server/tests/mochitest/chrome.ini
@@ -0,0 +1,103 @@
+[DEFAULT]
+tags = devtools
+skip-if = os == 'android'
+support-files =
+ animation-data.html
+ Debugger.Source.prototype.element.js
+ Debugger.Source.prototype.element-2.js
+ Debugger.Source.prototype.element.html
+ director-helpers.js
+ hello-actor.js
+ inspector_css-properties.html
+ inspector_getImageData.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-traversal-data.html
+ large-image.jpg
+ memory-helpers.js
+ nonchrome_unsafeDereference.html
+ small-image.gif
+ setup-in-child.js
+ setup-in-parent.js
+
+[test_animation_actor-lifetime.html]
+[test_connection-manager.html]
+[test_connectToChild.html]
+[test_css-logic.html]
+[test_css-logic-media-queries.html]
+[test_css-logic-specificity.html]
+[test_css-properties_01.html]
+[test_css-properties_02.html]
+[test_Debugger.Source.prototype.introductionScript.html]
+[test_Debugger.Source.prototype.introductionType.html]
+[test_Debugger.Source.prototype.element.html]
+[test_Debugger.Script.prototype.global.html]
+[test_device.html]
+[test_director.html]
+[test_director_connectToChild.html]
+[test_executeInGlobal-outerized_this.html]
+[test_framerate_01.html]
+[test_framerate_02.html]
+[test_framerate_03.html]
+[test_framerate_04.html]
+[test_framerate_05.html]
+[test_framerate_06.html]
+[test_getProcess.html]
+[test_inspector-anonymous.html]
+[test_inspector-changeattrs.html]
+[test_inspector-changevalue.html]
+[test_inspector-dead-nodes.html]
+[test_inspector-duplicate-node.html]
+[test_inspector_getImageData.html]
+[test_inspector_getImageDataFromURL.html]
+[test_inspector_getImageData-wait-for-load.html]
+[test_inspector_getNodeFromActor.html]
+[test_inspector-hide.html]
+[test_inspector-insert.html]
+[test_inspector-mutations-attr.html]
+[test_inspector-mutations-events.html]
+[test_inspector-mutations-childlist.html]
+[test_inspector-mutations-frameload.html]
+[test_inspector-mutations-value.html]
+[test_inspector-pick-color.html]
+[test_inspector-pseudoclass-lock.html]
+[test_inspector-release.html]
+[test_inspector-reload.html]
+[test_inspector-remove.html]
+[test_inspector-resize.html]
+[test_inspector-resolve-url.html]
+[test_inspector-retain.html]
+[test_inspector-search.html]
+[test_inspector-search-front.html]
+[test_inspector-scroll-into-view.html]
+[test_inspector-traversal.html]
+[test_makeGlobalObjectReference.html]
+[test_memory.html]
+[test_memory_allocations_01.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_preference.html]
+[test_settings.html]
+[test_setupInParentChild.html]
+[test_styles-applied.html]
+[test_styles-computed.html]
+[test_styles-layout.html]
+[test_styles-matched.html]
+[test_styles-modify.html]
+[test_styles-svg.html]
+[test_unsafeDereference.html]
+[test_websocket-server.html]
+skip-if = false
diff --git a/devtools/server/tests/mochitest/director-helpers.js b/devtools/server/tests/mochitest/director-helpers.js
new file mode 100644
index 000000000..fe1f7d394
--- /dev/null
+++ b/devtools/server/tests/mochitest/director-helpers.js
@@ -0,0 +1,44 @@
+var Cu = Components.utils;
+const {require} = Cu.import("resource://devtools/shared/Loader.jsm", {});
+const {DebuggerClient} = require("devtools/shared/client/main");
+const {DebuggerServer} = require("devtools/server/main");
+const Services = require("Services");
+
+// Always log packets when running tests.
+Services.prefs.setBoolPref("devtools.debugger.log", true);
+Services.prefs.setBoolPref("dom.mozBrowserFramesEnabled", true);
+
+SimpleTest.registerCleanupFunction(function () {
+ Services.prefs.clearUserPref("devtools.debugger.log");
+ Services.prefs.clearUserPref("dom.mozBrowserFramesEnabled");
+});
+
+const { DirectorRegistry } = require("devtools/server/actors/director-registry");
+const { DirectorRegistryFront } = require("devtools/shared/fronts/director-registry");
+
+const { DirectorManagerFront } = require("devtools/shared/fronts/director-manager");
+
+const { Task } = require("devtools/shared/task");
+
+/** *********************************
+ * director helpers functions
+ **********************************/
+
+function* newConnectedDebuggerClient(opts) {
+ var transport = DebuggerServer.connectPipe();
+ var client = new DebuggerClient(transport);
+
+ yield client.connect();
+
+ var root = yield client.listTabs();
+
+ return {
+ client: client,
+ root: root,
+ transport: transport
+ };
+}
+
+function purgeInstalledDirectorScripts() {
+ DirectorRegistry.clear();
+}
diff --git a/devtools/server/tests/mochitest/hello-actor.js b/devtools/server/tests/mochitest/hello-actor.js
new file mode 100644
index 000000000..603192938
--- /dev/null
+++ b/devtools/server/tests/mochitest/hello-actor.js
@@ -0,0 +1,26 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+const protocol = require("devtools/shared/protocol");
+
+const helloSpec = protocol.generateActorSpec({
+ typeName: "helloActor",
+
+ methods: {
+ count: {
+ request: {},
+ response: {count: protocol.RetVal("number")}
+ }
+ }
+});
+
+var HelloActor = protocol.ActorClassWithSpec(helloSpec, {
+ initialize: function () {
+ protocol.Actor.prototype.initialize.apply(this, arguments);
+ this.counter = 0;
+ },
+
+ count: function () {
+ return ++this.counter;
+ }
+});
diff --git a/devtools/server/tests/mochitest/inspector-delay-image-response.sjs b/devtools/server/tests/mochitest/inspector-delay-image-response.sjs
new file mode 100644
index 000000000..215d1e4d8
--- /dev/null
+++ b/devtools/server/tests/mochitest/inspector-delay-image-response.sjs
@@ -0,0 +1,42 @@
+/**
+ * Adapted from https://dxr.mozilla.org/mozilla-central/rev/
+ * 4e883591bb5dff021c108d3e30198a99547eed1e/layout/reftests/backgrounds/
+ * delay-image-response.sjs
+ */
+"use strict";
+
+// A 1x1 PNG image.
+// Source: https://commons.wikimedia.org/wiki/File:1x1.png (Public Domain)
+const IMAGE = atob("iVBORw0KGgoAAAANSUhEUgAAAAEAAAABAQMAAAAl21bKAAAAA1BMVEUAA" +
+ "ACnej3aAAAAAXRSTlMAQObYZgAAAApJREFUCNdjYAAAAAIAAeIhvDMAAAAASUVORK5CYII=");
+
+// To avoid GC.
+let timer = null;
+
+function handleRequest(request, response) {
+ let query = {};
+ request.queryString.split("&").forEach(function(val) {
+ let [name, value] = val.split("=");
+ query[name] = unescape(value);
+ });
+
+ response.setStatusLine(request.httpVersion, 200, "OK");
+ response.setHeader("Content-Type", "image/png", false);
+
+ // If there is no delay, we write the image and leave.
+ if (!("delay" in query)) {
+ response.write(IMAGE);
+ return;
+ }
+
+ // If there is a delay, we create a timer which, when it fires, will write
+ // image and leave.
+ response.processAsync();
+ const nsITimer = Components.interfaces.nsITimer;
+
+ timer = Components.classes["@mozilla.org/timer;1"].createInstance(nsITimer);
+ timer.initWithCallback(function() {
+ response.write(IMAGE);
+ response.finish();
+ }, query.delay, nsITimer.TYPE_ONE_SHOT);
+}
diff --git a/devtools/server/tests/mochitest/inspector-eyedropper.html b/devtools/server/tests/mochitest/inspector-eyedropper.html
new file mode 100644
index 000000000..ded9f392d
--- /dev/null
+++ b/devtools/server/tests/mochitest/inspector-eyedropper.html
@@ -0,0 +1,18 @@
+<html>
+<head>
+ <meta charset="UTF-8">
+ <title>Inspector Eyedropper tests</title>
+ <style>
+ html {
+ background: black;
+ }
+ </style>
+ <script type="text/javascript">
+ window.onload = function() {
+ window.opener.postMessage('ready', '*');
+ };
+ </script>
+</head>
+</body>
+</body>
+</html> \ No newline at end of file
diff --git a/devtools/server/tests/mochitest/inspector-helpers.js b/devtools/server/tests/mochitest/inspector-helpers.js
new file mode 100644
index 000000000..47c643868
--- /dev/null
+++ b/devtools/server/tests/mochitest/inspector-helpers.js
@@ -0,0 +1,310 @@
+var Cu = Components.utils;
+
+const {require} = Cu.import("resource://devtools/shared/Loader.jsm", {});
+const {DebuggerClient} = require("devtools/shared/client/main");
+const {DebuggerServer} = require("devtools/server/main");
+const { Task } = require("devtools/shared/task");
+
+const Services = require("Services");
+const promise = require("promise");
+const {_documentWalker} = require("devtools/server/actors/inspector");
+
+// Always log packets when running tests.
+Services.prefs.setBoolPref("devtools.debugger.log", true);
+SimpleTest.registerCleanupFunction(function () {
+ Services.prefs.clearUserPref("devtools.debugger.log");
+});
+
+
+if (!DebuggerServer.initialized) {
+ DebuggerServer.init();
+ DebuggerServer.addBrowserActors();
+ SimpleTest.registerCleanupFunction(function () {
+ DebuggerServer.destroy();
+ });
+}
+
+var gAttachCleanups = [];
+
+SimpleTest.registerCleanupFunction(function () {
+ for (let cleanup of gAttachCleanups) {
+ cleanup();
+ }
+});
+
+/**
+ * Open a tab, load the url, wait for it to signal its readiness,
+ * find the tab with the debugger server, and call the callback.
+ *
+ * Returns a function which can be called to close the opened ta
+ * and disconnect its debugger client.
+ */
+function attachURL(url, callback) {
+ var win = window.open(url, "_blank");
+ var client = null;
+
+ let cleanup = () => {
+ if (client) {
+ client.close();
+ client = null;
+ }
+ if (win) {
+ win.close();
+ win = null;
+ }
+ };
+ gAttachCleanups.push(cleanup);
+
+ window.addEventListener("message", function loadListener(event) {
+ if (event.data === "ready") {
+ client = new DebuggerClient(DebuggerServer.connectPipe());
+ client.connect().then(([applicationType, traits]) => {
+ client.listTabs(response => {
+ for (let tab of response.tabs) {
+ if (tab.url === url) {
+ window.removeEventListener("message", loadListener, false);
+ client.attachTab(tab.actor, function (aResponse, aTabClient) {
+ try {
+ callback(null, client, tab, win.document);
+ } catch (ex) {
+ Cu.reportError(ex);
+ dump(ex);
+ }
+ });
+ break;
+ }
+ }
+ });
+ });
+ }
+ }, false);
+
+ return cleanup;
+}
+
+function promiseOnce(target, event) {
+ let deferred = promise.defer();
+ target.on(event, (...args) => {
+ if (args.length === 1) {
+ deferred.resolve(args[0]);
+ } else {
+ deferred.resolve(args);
+ }
+ });
+ return deferred.promise;
+}
+
+function sortOwnershipChildren(children) {
+ return children.sort((a, b) => a.name.localeCompare(b.name));
+}
+
+function serverOwnershipSubtree(walker, node) {
+ let actor = walker._refMap.get(node);
+ if (!actor) {
+ return undefined;
+ }
+
+ let children = [];
+ let docwalker = new _documentWalker(node, window);
+ let child = docwalker.firstChild();
+ while (child) {
+ let item = serverOwnershipSubtree(walker, child);
+ if (item) {
+ children.push(item);
+ }
+ child = docwalker.nextSibling();
+ }
+ return {
+ name: actor.actorID,
+ children: sortOwnershipChildren(children)
+ };
+}
+
+function serverOwnershipTree(walker) {
+ let serverWalker = DebuggerServer._searchAllConnectionsForActor(walker.actorID);
+
+ return {
+ root: serverOwnershipSubtree(serverWalker, serverWalker.rootDoc),
+ orphaned: [...serverWalker._orphaned].map(o => serverOwnershipSubtree(serverWalker, o.rawNode)),
+ retained: [...serverWalker._retainedOrphans].map(o => serverOwnershipSubtree(serverWalker, o.rawNode))
+ };
+}
+
+function clientOwnershipSubtree(node) {
+ return {
+ name: node.actorID,
+ children: sortOwnershipChildren(node.treeChildren().map(child => clientOwnershipSubtree(child)))
+ };
+}
+
+function clientOwnershipTree(walker) {
+ return {
+ root: clientOwnershipSubtree(walker.rootNode),
+ orphaned: [...walker._orphaned].map(o => clientOwnershipSubtree(o)),
+ retained: [...walker._retainedOrphans].map(o => clientOwnershipSubtree(o))
+ };
+}
+
+function ownershipTreeSize(tree) {
+ let size = 1;
+ for (let child of tree.children) {
+ size += ownershipTreeSize(child);
+ }
+ return size;
+}
+
+function assertOwnershipTrees(walker) {
+ let serverTree = serverOwnershipTree(walker);
+ let clientTree = clientOwnershipTree(walker);
+ is(JSON.stringify(clientTree, null, " "), JSON.stringify(serverTree, null, " "), "Server and client ownership trees should match.");
+
+ return ownershipTreeSize(clientTree.root);
+}
+
+// Verify that an actorID is inaccessible both from the client library and the server.
+function checkMissing(client, actorID) {
+ let deferred = promise.defer();
+ let front = client.getActor(actorID);
+ ok(!front, "Front shouldn't be accessible from the client for actorID: " + actorID);
+
+ deferred = promise.defer();
+ client.request({
+ to: actorID,
+ type: "request",
+ }, response => {
+ is(response.error, "noSuchActor", "node list actor should no longer be contactable.");
+ deferred.resolve(undefined);
+ });
+ return deferred.promise;
+}
+
+// Verify that an actorID is accessible both from the client library and the server.
+function checkAvailable(client, actorID) {
+ let deferred = promise.defer();
+ let front = client.getActor(actorID);
+ ok(front, "Front should be accessible from the client for actorID: " + actorID);
+
+ deferred = promise.defer();
+ client.request({
+ to: actorID,
+ type: "garbageAvailableTest",
+ }, response => {
+ is(response.error, "unrecognizedPacketType", "node list actor should be contactable.");
+ deferred.resolve(undefined);
+ });
+ return deferred.promise;
+}
+
+function promiseDone(promise) {
+ promise.then(null, err => {
+ ok(false, "Promise failed: " + err);
+ if (err.stack) {
+ dump(err.stack);
+ }
+ SimpleTest.finish();
+ });
+}
+
+// Mutation list testing
+
+function isSrcChange(change) {
+ return (change.type === "attributes" && change.attributeName === "src");
+}
+
+function assertAndStrip(mutations, message, test) {
+ let size = mutations.length;
+ mutations = mutations.filter(test);
+ ok((mutations.size != size), message);
+ return mutations;
+}
+
+function isSrcChange(change) {
+ return change.type === "attributes" && change.attributeName === "src";
+}
+
+function isUnload(change) {
+ return change.type === "documentUnload";
+}
+
+function isFrameLoad(change) {
+ return change.type === "frameLoad";
+}
+
+function isUnretained(change) {
+ return change.type === "unretained";
+}
+
+function isChildList(change) {
+ return change.type === "childList";
+}
+
+function isNewRoot(change) {
+ return change.type === "newRoot";
+}
+
+// Make sure an iframe's src attribute changed and then
+// strip that mutation out of the list.
+function assertSrcChange(mutations) {
+ return assertAndStrip(mutations, "Should have had an iframe source change.", isSrcChange);
+}
+
+// Make sure there's an unload in the mutation list and strip
+// that mutation out of the list
+function assertUnload(mutations) {
+ return assertAndStrip(mutations, "Should have had a document unload change.", isUnload);
+}
+
+// Make sure there's a frame load in the mutation list and strip
+// that mutation out of the list
+function assertFrameLoad(mutations) {
+ return assertAndStrip(mutations, "Should have had a frame load change.", isFrameLoad);
+}
+
+// Make sure there's a childList change in the mutation list and strip
+// that mutation out of the list
+function assertChildList(mutations) {
+ return assertAndStrip(mutations, "Should have had a frame load change.", isChildList);
+}
+
+// Load mutations aren't predictable, so keep accumulating mutations until
+// the one we're looking for shows up.
+function waitForMutation(walker, test, mutations = []) {
+ let deferred = promise.defer();
+ for (let change of mutations) {
+ if (test(change)) {
+ deferred.resolve(mutations);
+ }
+ }
+
+ walker.once("mutations", newMutations => {
+ waitForMutation(walker, test, mutations.concat(newMutations)).then(finalMutations => {
+ deferred.resolve(finalMutations);
+ });
+ });
+
+ return deferred.promise;
+}
+
+
+var _tests = [];
+function addTest(test) {
+ _tests.push(test);
+}
+
+function addAsyncTest(generator) {
+ _tests.push(() => Task.spawn(generator).then(null, ok.bind(null, false)));
+}
+
+function runNextTest() {
+ if (_tests.length == 0) {
+ SimpleTest.finish();
+ return;
+ }
+ var fn = _tests.shift();
+ try {
+ fn();
+ } catch (ex) {
+ info("Test function " + (fn.name ? "'" + fn.name + "' " : "") +
+ "threw an exception: " + ex);
+ }
+}
diff --git a/devtools/server/tests/mochitest/inspector-search-data.html b/devtools/server/tests/mochitest/inspector-search-data.html
new file mode 100644
index 000000000..d0d5f9c68
--- /dev/null
+++ b/devtools/server/tests/mochitest/inspector-search-data.html
@@ -0,0 +1,52 @@
+<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">
+ 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> \ No newline at end of file
diff --git a/devtools/server/tests/mochitest/inspector-styles-data.css b/devtools/server/tests/mochitest/inspector-styles-data.css
new file mode 100644
index 000000000..5c3652f52
--- /dev/null
+++ b/devtools/server/tests/mochitest/inspector-styles-data.css
@@ -0,0 +1,3 @@
+.external-rule {
+ cursor: crosshair;
+}
diff --git a/devtools/server/tests/mochitest/inspector-styles-data.html b/devtools/server/tests/mochitest/inspector-styles-data.html
new file mode 100644
index 000000000..a2a126f0e
--- /dev/null
+++ b/devtools/server/tests/mochitest/inspector-styles-data.html
@@ -0,0 +1,81 @@
+<html>
+<script>
+ window.onload = () => {
+ window.opener.postMessage('ready', '*')
+ }
+</script>
+<style>
+ .inheritable-rule {
+ font-size: 15px;
+ }
+ .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 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/mochitest/inspector-traversal-data.html b/devtools/server/tests/mochitest/inspector-traversal-data.html
new file mode 100644
index 000000000..45b8c2ede
--- /dev/null
+++ b/devtools/server/tests/mochitest/inspector-traversal-data.html
@@ -0,0 +1,90 @@
+<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">
+ window.onload = function() {
+
+ // Set up a basic shadow DOM
+ var host = document.querySelector('#shadow');
+ if (host.createShadowRoot) {
+ var root = host.createShadowRoot();
+ root.innerHTML = '<h3>Shadow <em>DOM</em></h3><select multiple></select>';
+ }
+
+ // Put a copy of the body in an iframe to test frame traversal.
+ var body = document.querySelector("body");
+ var data = "data:text/html,<html>" + body.outerHTML + "<html>";
+ var 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/mochitest/inspector_css-properties.html b/devtools/server/tests/mochitest/inspector_css-properties.html
new file mode 100644
index 000000000..2c160c928
--- /dev/null
+++ b/devtools/server/tests/mochitest/inspector_css-properties.html
@@ -0,0 +1,10 @@
+<html>
+<head>
+<body>
+ <script type="text/javascript">
+ window.onload = function() {
+ window.opener.postMessage('ready', '*');
+ };
+ </script>
+</body>
+</html>
diff --git a/devtools/server/tests/mochitest/inspector_getImageData.html b/devtools/server/tests/mochitest/inspector_getImageData.html
new file mode 100644
index 000000000..eebecea90
--- /dev/null
+++ b/devtools/server/tests/mochitest/inspector_getImageData.html
@@ -0,0 +1,21 @@
+<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>
+ window.onload = () => {
+ var 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/mochitest/large-image.jpg b/devtools/server/tests/mochitest/large-image.jpg
new file mode 100644
index 000000000..bda383e59
--- /dev/null
+++ b/devtools/server/tests/mochitest/large-image.jpg
Binary files differ
diff --git a/devtools/server/tests/mochitest/memory-helpers.js b/devtools/server/tests/mochitest/memory-helpers.js
new file mode 100644
index 000000000..aea8c4732
--- /dev/null
+++ b/devtools/server/tests/mochitest/memory-helpers.js
@@ -0,0 +1,52 @@
+var Cu = Components.utils;
+var Cc = Components.classes;
+var Ci = Components.interfaces;
+
+var { require } = Cu.import("resource://devtools/shared/Loader.jsm", {});
+const { Task } = require("devtools/shared/task");
+var Services = require("Services");
+var { DebuggerClient } = require("devtools/shared/client/main");
+var { DebuggerServer } = require("devtools/server/main");
+
+var { MemoryFront } = require("devtools/shared/fronts/memory");
+
+// Always log packets when running tests.
+Services.prefs.setBoolPref("devtools.debugger.log", true);
+SimpleTest.registerCleanupFunction(function () {
+ Services.prefs.clearUserPref("devtools.debugger.log");
+});
+
+function startServerAndGetSelectedTabMemory() {
+ DebuggerServer.init();
+ DebuggerServer.addBrowserActors();
+ var client = new DebuggerClient(DebuggerServer.connectPipe());
+
+ return client.connect()
+ .then(() => client.listTabs())
+ .then(response => {
+ var form = response.tabs[response.selected];
+ var memory = MemoryFront(client, form, response);
+
+ return { memory, client };
+ });
+}
+
+function destroyServerAndFinish(client) {
+ client.close().then(() => {
+ DebuggerServer.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/mochitest/nonchrome_unsafeDereference.html b/devtools/server/tests/mochitest/nonchrome_unsafeDereference.html
new file mode 100644
index 000000000..6c19d3104
--- /dev/null
+++ b/devtools/server/tests/mochitest/nonchrome_unsafeDereference.html
@@ -0,0 +1,8 @@
+<!DOCTYPE HTML>
+<html>
+<script>
+var xhr = new XMLHttpRequest;
+xhr.timeout = 1742;
+xhr.expando = 'Expando!';
+</script>
+</html>
diff --git a/devtools/server/tests/mochitest/setup-in-child.js b/devtools/server/tests/mochitest/setup-in-child.js
new file mode 100644
index 000000000..a575faa20
--- /dev/null
+++ b/devtools/server/tests/mochitest/setup-in-child.js
@@ -0,0 +1,20 @@
+const {Cc, Ci} = require("chrome");
+const cpmm = Cc["@mozilla.org/childprocessmessagemanager;1"].
+ getService(Ci.nsIMessageListenerManager);
+const { DebuggerServer } = require("devtools/server/main");
+
+exports.setupChild = function (a, b, c) {
+ cpmm.sendAsyncMessage("test:setupChild", [a, b, c]);
+};
+
+exports.callParent = function () {
+ // Hack! Fetch DebuggerServerConnection objects directly within DebuggerServer guts.
+ for (let id in DebuggerServer._connections) {
+ let conn = DebuggerServer._connections[id];
+ conn.setupInParent({
+ module: "chrome://mochitests/content/chrome/devtools/server/tests/mochitest/setup-in-parent.js",
+ setupParent: "setupParent",
+ args: [{one: true}, 2, "three"]
+ });
+ }
+};
diff --git a/devtools/server/tests/mochitest/setup-in-parent.js b/devtools/server/tests/mochitest/setup-in-parent.js
new file mode 100644
index 000000000..9845ee647
--- /dev/null
+++ b/devtools/server/tests/mochitest/setup-in-parent.js
@@ -0,0 +1,10 @@
+var {Ci} = require("chrome");
+var Services = require("Services");
+
+exports.setupParent = function ({mm, prefix}) {
+ let args = [
+ !!mm.QueryInterface(Ci.nsIMessageSender),
+ prefix
+ ];
+ Services.obs.notifyObservers(null, "test:setupParent", JSON.stringify(args));
+};
diff --git a/devtools/server/tests/mochitest/small-image.gif b/devtools/server/tests/mochitest/small-image.gif
new file mode 100644
index 000000000..e702427a5
--- /dev/null
+++ b/devtools/server/tests/mochitest/small-image.gif
Binary files differ
diff --git a/devtools/server/tests/mochitest/test_Debugger.Script.prototype.global.html b/devtools/server/tests/mochitest/test_Debugger.Script.prototype.global.html
new file mode 100644
index 000000000..77357e608
--- /dev/null
+++ b/devtools/server/tests/mochitest/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 type="application/javascript" 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>
+
+Components.utils.import("resource://gre/modules/jsdebugger.jsm");
+addDebuggerToGlobal(this);
+
+window.onload = function () {
+ SimpleTest.waitForExplicitFinish();
+
+ var iframe = document.createElement("iframe");
+ iframe.src = "data:text/html,<script>function glorp() { }<\/script>";
+ iframe.onload = firstOnLoadHandler;
+ document.body.appendChild(iframe);
+
+ function firstOnLoadHandler() {
+ var dbg = new Debugger;
+ var 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.
+ var 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/mochitest/test_Debugger.Source.prototype.element.html b/devtools/server/tests/mochitest/test_Debugger.Source.prototype.element.html
new file mode 100644
index 000000000..3cbc22353
--- /dev/null
+++ b/devtools/server/tests/mochitest/test_Debugger.Source.prototype.element.html
@@ -0,0 +1,182 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=941876
+
+Debugger.Source.prototype.element and .elementAttributeName should report the DOM
+element to which code is attached (if any), and how.
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Debugger.Source.prototype.element should return owning element</title>
+ <script type="application/javascript" 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>
+
+Components.utils.import("resource://gre/modules/jsdebugger.jsm");
+addDebuggerToGlobal(this);
+
+window.onload = function () {
+ SimpleTest.waitForExplicitFinish();
+
+ var log = '';
+ var doc, dieter, ulrich, isolde, albrecht;
+ var dbg, iframeDO, DOFor;
+
+ // Create an iframe to debug.
+ // We can't use a data: URL here, because we want to test script elements
+ // that refer to the JavaScript via 'src' attributes, and data: documents
+ // can't refer to those. So we use a separate HTML document.
+ var iframe = document.createElement("iframe");
+ iframe.src = "Debugger.Source.prototype.element.html";
+ iframe.onload = onLoadHandler;
+ document.body.appendChild(iframe);
+
+ function onLoadHandler() {
+ log += 'l';
+
+ // Now that the iframe's window has been created, we can add
+ // it as a debuggee.
+ dbg = new Debugger;
+ dbg.onDebuggerStatement = franzDebuggerHandler;
+ iframeDO = dbg.addDebuggee(iframe.contentWindow);
+ DOFor = iframeDO.makeDebuggeeValue.bind(iframeDO);
+
+ // Send a click event to heidi.
+ doc = iframe.contentWindow.document;
+ doc.getElementById('heidi').dispatchEvent(new Event('click'));
+ }
+
+ function franzDebuggerHandler(frame) {
+ log += 'f';
+
+ // The top stack frame should be franz, belonging to the script element.
+ ok(frame.callee.displayName === 'franz', 'top frame is franz');
+ ok(frame.script.source.element === DOFor(doc.getElementById('franz')),
+ 'top frame source belongs to element franz');
+ ok(frame.script.source.elementAttributeName === undefined,
+ "top frame source doesn't belong to an attribute");
+
+ // The second stack frame should belong to heinrich.
+ ok(frame.older.script.source.element === DOFor(doc.getElementById('heinrich')),
+ "second frame source belongs to element heinrich");
+ ok(frame.older.script.source.elementAttributeName === undefined,
+ "second frame source doesn't belong to an attribute");
+
+ // The next stack frame should belong to heidi's onclick handler.
+ ok(frame.older.older.script.source.element === DOFor(doc.getElementById('heidi')),
+ 'third frame source belongs to element heidi');
+ ok(frame.older.older.script.source.elementAttributeName === 'onclick',
+ "third frame source belongs to 'onclick' attribute");
+
+ // Try a dynamically inserted inline script element.
+ ulrich = doc.createElement('script');
+ ulrich.text = 'debugger;'
+ dbg.onDebuggerStatement = ulrichDebuggerHandler;
+ doc.body.appendChild(ulrich);
+ }
+
+ function ulrichDebuggerHandler(frame) {
+ log += 'u';
+
+ // The top frame should be ulrich's text.
+ ok(frame.script.source.element === DOFor(ulrich),
+ "top frame belongs to ulrich");
+ ok(frame.script.source.elementAttributeName === undefined,
+ "top frame is not on an attribute of ulrich");
+
+ // Try a dynamically inserted out-of-line script element.
+ isolde = doc.createElement('script');
+ isolde.setAttribute('src', 'Debugger.Source.prototype.element-2.js');
+ isolde.setAttribute('id', 'idolde, my dear');
+ dbg.onDebuggerStatement = isoldeDebuggerHandler;
+ doc.body.appendChild(isolde);
+ }
+
+ function isoldeDebuggerHandler(frame) {
+ log += 'i';
+
+ // The top frame should belong to isolde.
+ ok(frame.script.source.element === DOFor(isolde),
+ "top frame belongs to isolde");
+ info("frame.script.source.element is: " + uneval(frame.script.source.element));
+ if (typeof frame.script.source.element.unsafeDereference() == 'object') {
+ info(" toString: " + frame.script.source.element.unsafeDereference());
+ info(" id: " + frame.script.source.element.unsafeDereference().id);
+ }
+
+ ok(frame.script.source.elementAttributeName === undefined,
+ "top frame source is not an attribute of isolde");
+ info("frame.script.source.elementAttributeName is: " +
+ uneval(frame.script.source.elementAttributeName));
+
+ // Try a dynamically created div element with a handler.
+ dieter = doc.createElement('div');
+ dieter.setAttribute('id', 'dieter');
+ dieter.setAttribute('ondrag', 'debugger;');
+ dbg.onDebuggerStatement = dieterDebuggerHandler;
+ dieter.dispatchEvent(new Event('drag'));
+ }
+
+ function dieterDebuggerHandler(frame) {
+ log += 'd';
+
+ // The top frame should belong to dieter's ondrag handler.
+ ok(frame.script.source.element === DOFor(dieter),
+ "second event's handler belongs to dieter");
+ ok(frame.script.source.elementAttributeName === 'ondrag',
+ "second event's handler is on dieter's 'ondrag' element");
+
+ // Try sending an 'onresize' event to the window.
+ //
+ // Note that we only want Debugger to see the events we send, not any
+ // genuine resize events accidentally generated by the test harness (see bug
+ // 1162067). So we mark our events as cancelable; that seems to be the only
+ // bit chrome can fiddle on an Event that content code will see and that
+ // won't affect propagation. Then, the content event only runs its
+ // 'debugger' statement when the event is cancelable. It's a kludge.
+ dbg.onDebuggerStatement = resizeDebuggerHandler;
+ iframe.contentWindow.dispatchEvent(new Event('resize', { cancelable: true }));
+ }
+
+ function resizeDebuggerHandler(frame) {
+ log += 'e';
+
+ // The top frame should belong to the body's 'onresize' handler, even
+ // though we sent the message to the window and it was handled.
+ ok(frame.script.source.element === DOFor(doc.body),
+ "onresize event handler belongs to body element");
+ ok(frame.script.source.elementAttributeName === 'onresize',
+ "onresize event handler is on body element's 'onresize' attribute");
+
+ // In SVG, the event and the attribute that holds that event's handler
+ // have different names. Debugger.Source.prototype.elementAttributeName
+ // should report (as one might infer) the attribute name, not the event
+ // name.
+ albrecht = doc.createElementNS('http://www.w3.org/2000/svg', 'svg');
+ albrecht.setAttribute('onload', 'debugger;');
+ dbg.onDebuggerStatement = SVGLoadHandler;
+ albrecht.dispatchEvent(new Event("SVGLoad"));
+ }
+
+ function SVGLoadHandler(frame) {
+ log += 's';
+
+ // The top frame's source should be on albrecht's 'onload' attribute.
+ ok(frame.script.source.element === DOFor(albrecht),
+ "SVGLoad event handler belongs to albrecht");
+ ok(frame.script.source.elementAttributeName === 'onload',
+ "SVGLoad event handler is on albrecht's 'onload' attribute");
+
+ ok(log === 'lfuides', "all tests actually ran");
+ SimpleTest.finish();
+ }
+}
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/devtools/server/tests/mochitest/test_Debugger.Source.prototype.introductionScript.html b/devtools/server/tests/mochitest/test_Debugger.Source.prototype.introductionScript.html
new file mode 100644
index 000000000..cbc2ae615
--- /dev/null
+++ b/devtools/server/tests/mochitest/test_Debugger.Source.prototype.introductionScript.html
@@ -0,0 +1,97 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=969786
+
+Debugger.Source.prototype.introductionScript and .introductionOffset should
+behave when 'eval' is called with no scripted frames active at all.
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Debugger.Source.prototype.introductionScript with no caller</title>
+ <script type="application/javascript" 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>
+
+Components.utils.import("resource://gre/modules/jsdebugger.jsm");
+addDebuggerToGlobal(this);
+
+window.onload = function () {
+ SimpleTest.waitForExplicitFinish();
+
+ var dbg, iframeDO, doc, script2DO;
+
+ // Create an iframe to debug.
+ var 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;
+ var 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.
+ var 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.
+ var script2 = doc.createElement('script');
+ script2.text = "eval('debugger;');";
+ script2DO = iframeDO.makeDebuggeeValue(script2);
+ dbg.onDebuggerStatement = evalHandler;
+ doc.body.appendChild(script2);
+ }
+
+ function evalHandler(frame) {
+ // The top stack frame's source should be introduced by the script that
+ // called eval.
+ var source = frame.script.source;
+ var frame2 = frame.older;
+
+ ok(source.introductionType === 'eval',
+ "top frame's source was introduced by 'eval'");
+ ok(source.introductionScript === frame2.script,
+ "eval frame's introduction script is the older frame's script");
+ ok(source.introductionOffset === frame2.offset,
+ "eval frame's introduction offset is current offset in older frame");
+ ok(source.introductionScript.source.element === script2DO,
+ "eval frame's introducer belongs to script2 element");
+
+ // The frame that called eval, in turn, should have no introduction
+ // information. (In the future, we certainly could point at the call
+ // that inserted the script element into the document; if that happens,
+ // we can update this test.)
+ ok(frame2.script.source.introductionType === 'scriptElement',
+ "older frame has no introduction type");
+ ok(frame2.script.source.introductionScript === undefined,
+ "older frame has no introduction script");
+ ok(frame2.script.source.introductionOffset === undefined,
+ "older frame has no introduction offset");
+
+ SimpleTest.finish();
+ }
+}
+</script>
+</pre>
+</body>
+</html>
diff --git a/devtools/server/tests/mochitest/test_Debugger.Source.prototype.introductionType.html b/devtools/server/tests/mochitest/test_Debugger.Source.prototype.introductionType.html
new file mode 100644
index 000000000..c0066659c
--- /dev/null
+++ b/devtools/server/tests/mochitest/test_Debugger.Source.prototype.introductionType.html
@@ -0,0 +1,181 @@
+<!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 type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript;version=1.8" 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>
+
+Components.utils.import("resource://gre/modules/jsdebugger.jsm");
+addDebuggerToGlobal(this);
+
+var dbg;
+var iframeDO, doc;
+var Tootles, TootlesDO;
+
+window.onload = function () {
+ SimpleTest.waitForExplicitFinish();
+ runNextTest();
+};
+
+addTest(function setup() {
+ // Create an iframe to debug.
+ var iframe = document.createElement("iframe");
+ iframe.src = "data:text/html," +
+ "<div id='Tootles' onclick='debugger;'>I'm a DIV!</div>" +
+ "<script id='Auddie'>function auddie() { debugger; }<\/script>";
+ iframe.onload = onLoadHandler;
+ document.body.appendChild(iframe);
+
+ function onLoadHandler() {
+ // Now that the iframe's window has been created, we can add
+ // it as a debuggee.
+ dbg = new Debugger;
+ iframeDO = dbg.addDebuggee(iframe.contentWindow);
+ doc = iframe.contentWindow.document;
+ Tootles = doc.getElementById('Tootles');
+ TootlesDO = iframeDO.makeDebuggeeValue(Tootles);
+
+ runNextTest();
+ }
+});
+
+
+// Check the introduction type of in-markup event handler code.
+// Send a click event to Tootles, whose handler has a 'debugger' statement,
+// and check that script's introduction type.
+addTest(function ClickOnTootles() {
+ dbg.onDebuggerStatement = TootlesClickDebugger;
+ Tootles.dispatchEvent(new Event('click'));
+
+ function TootlesClickDebugger(frame) {
+ // some sanity checks
+ ok(frame.script.source.element === TootlesDO,
+ "top frame source belongs to element 'Tootles'");
+ is(frame.script.source.elementAttributeName, 'onclick',
+ "top frame source belongs to 'onclick' attribute");
+
+ // And, the actual point of this test:
+ is(frame.script.source.introductionType, 'eventHandler',
+ "top frame source's introductionType is 'eventHandler'");
+
+ runNextTest();
+ }
+});
+
+
+// Check the introduction type of dynamically added event handler code.
+// Add a drag event handler to Tootles as a string, and then send
+// Tootles a drag event.
+addTest(function DragTootles() {
+ dbg.onDebuggerStatement = TootlesDragDebugger;
+ Tootles.setAttribute('ondrag', 'debugger;');
+ Tootles.dispatchEvent(new Event('drag'));
+
+ function TootlesDragDebugger(frame) {
+ // sanity checks
+ ok(frame.script.source.element === TootlesDO,
+ "top frame source belongs to element 'Tootles'");
+ is(frame.script.source.elementAttributeName, 'ondrag',
+ "top frame source belongs to 'ondrag' attribute");
+
+ // And, the actual point of this test:
+ is(frame.script.source.introductionType, 'eventHandler',
+ "top frame source's introductionType is 'eventHandler'");
+
+ runNextTest();
+ }
+});
+
+
+// Check the introduction type of an in-markup script element.
+addTest(function checkAuddie() {
+ var fnDO = iframeDO.getOwnPropertyDescriptor('auddie').value;
+ var AuddieDO = iframeDO.makeDebuggeeValue(doc.getElementById('Auddie'));
+
+ is(fnDO.class, 'Function',
+ "Script element 'Auddie' defined function 'auddie'.");
+ ok(fnDO.script.source.element === AuddieDO,
+ "Function auddie's script belongs to script element 'Auddie'");
+ is(fnDO.script.source.elementAttributeName, undefined,
+ "Function auddie's script doesn't belong to any attribute of 'Auddie'");
+ is(fnDO.script.source.introductionType, 'scriptElement',
+ "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;
+ var rover = doc.createElement('script');
+ var roverDO = iframeDO.makeDebuggeeValue(rover);
+ rover.text = 'debugger;';
+ doc.body.appendChild(rover);
+
+ function RoverDebugger(frame) {
+ // sanity checks
+ ok(frame.script.source.element === roverDO,
+ "Rover script belongs to Rover");
+ ok(frame.script.source.elementAttributeName === undefined,
+ "Rover script doesn't belong to an attribute of Rover");
+
+ // Check the introduction type.
+ ok(frame.script.source.introductionType === 'scriptElement',
+ "Rover script's introduction type is 'scriptElement'");
+
+ runNextTest();
+ }
+});
+
+
+// Create a XUL document with a script element, and check its introduction type.
+addTest(function XULDocumentScript() {
+ var xulFrame = document.createElement('iframe');
+ xulFrame.src = "data:application/vnd.mozilla.xul+xml;charset=utf-8," +
+ "<?xml version=\"1.0\"?>" +
+ "<window xmlns='http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul'>" +
+ "<script id='xulie'>function xulScriptFunc() { debugger; }<\/script>" +
+ "</window>";
+ xulFrame.onload = xulLoaded;
+ info("Appending iframe containing XUL document");
+ document.body.appendChild(xulFrame);
+
+ function xulLoaded() {
+ info("Loaded XUL document");
+ var xulFrameDO = dbg.addDebuggee(xulFrame.contentWindow);
+ var xulDoc = xulFrame.contentWindow.document;
+ var xulieDO = xulFrameDO.makeDebuggeeValue(xulDoc.getElementById('xulie'));
+ var 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 document's script elements' code gets shared amongst all
+ // instantiations of the document, so there's no specific DOM element
+ // we can attribute the code to.
+ is(xulFnDO.script.source.element, undefined,
+ "XUL script code should not be attributed to any individual element");
+
+ is(xulFnDO.script.source.introductionType, 'scriptElement',
+ "xulScriptFunc's introduction type is 'scriptElement'");
+ runNextTest();
+ }
+});
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/devtools/server/tests/mochitest/test_animation_actor-lifetime.html b/devtools/server/tests/mochitest/test_animation_actor-lifetime.html
new file mode 100644
index 000000000..a5265d918
--- /dev/null
+++ b/devtools/server/tests/mochitest/test_animation_actor-lifetime.html
@@ -0,0 +1,91 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1247243
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1247243</title>
+
+ <script type="application/javascript" 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;version=1.8" src="inspector-helpers.js"></script>
+ <script type="application/javascript;version=1.8">
+window.onload = function() {
+ const Ci = Components.interfaces;
+ const {AnimationsFront} = require("devtools/shared/fronts/animation");
+ const {InspectorFront} = require("devtools/shared/fronts/inspector");
+
+ SimpleTest.waitForExplicitFinish();
+
+ let gWalker = null;
+ let gClient = null;
+ let animationsFront = null;
+
+ addTest(function setup() {
+ info ("Setting up inspector and animation actors.");
+
+ let url = document.getElementById("animationContent").href;
+ attachURL(url, function(err, client, tab, doc) {
+ let inspector = InspectorFront(client, tab);
+
+ animationsFront = new AnimationsFront(client, tab);
+
+ promiseDone(inspector.getWalker().then(walker => {
+ ok(walker, "getWalker() should return an actor.");
+ gClient = client;
+ gWalker = walker;
+ }).then(runNextTest));
+
+ });
+ });
+
+ addAsyncTest(function* testActorLifetime() {
+
+ info ("Testing animated node actor");
+ let animatedNodeActor = yield gWalker.querySelector(gWalker.rootNode,
+ ".animated");
+ yield animationsFront.getAnimationPlayersForNode(animatedNodeActor);
+
+ let animationsActor = DebuggerServer._searchAllConnectionsForActor(animationsFront.actorID);
+
+ is(animationsActor.actors.length, 1,
+ "AnimationActor have 1 AnimationPlayerActors");
+
+ info ("Testing AnimationPlayerActors release");
+ let stillNodeActor = yield gWalker.querySelector(gWalker.rootNode,
+ ".still");
+ yield animationsFront.getAnimationPlayersForNode(stillNodeActor);
+ is(animationsActor.actors.length, 0,
+ "AnimationActor does not have any AnimationPlayerActors anymore");
+
+ info ("Testing multi animated node actor");
+ let multiNodeActor = yield gWalker.querySelector(gWalker.rootNode,
+ ".multi");
+ yield animationsFront.getAnimationPlayersForNode(multiNodeActor);
+ is(animationsActor.actors.length, 2,
+ "AnimationActor has now 2 AnimationPlayerActors");
+
+ info ("Testing single animated node actor");
+ yield animationsFront.getAnimationPlayersForNode(animatedNodeActor);
+ is(animationsActor.actors.length, 1,
+ "AnimationActor has only one AnimationPlayerActors");
+
+ info ("Testing AnimationPlayerActors release again");
+ yield animationsFront.getAnimationPlayersForNode(stillNodeActor);
+ is(animationsActor.actors.length, 0,
+ "AnimationActor does not have any AnimationPlayerActors anymore");
+
+ runNextTest();
+ });
+
+
+ runNextTest();
+};
+ </script>
+</head>
+<body>
+ <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1247243">Mozilla Bug 1247243</a>
+ <a id="animationContent" target="_blank" href="animation-data.html">Test Document</a>
+</body>
+</html>
diff --git a/devtools/server/tests/mochitest/test_connectToChild.html b/devtools/server/tests/mochitest/test_connectToChild.html
new file mode 100644
index 000000000..3bda7b566
--- /dev/null
+++ b/devtools/server/tests/mochitest/test_connectToChild.html
@@ -0,0 +1,134 @@
+<SDOCTYPv HTM.>
+<html>
+<!--
+Bug 966991 - Test DebuggerServer.connectToChild
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Mozilla Bug</title>
+ <script type="application/javascript" 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 type="application/javascript;version=1.8">
+
+let Cu = Components.utils;
+let Cc = Components.classes;
+let Ci = Components.interfaces;
+
+let { require } = Cu.import("resource://devtools/shared/Loader.jsm", {});
+let { DebuggerClient } = require("devtools/shared/client/main");
+let { DebuggerServer } = require("devtools/server/main");
+
+window.onload = function() {
+ SimpleTest.waitForExplicitFinish();
+
+ SpecialPowers.pushPrefEnv({
+ "set": [
+ // Always log packets when running tests.
+ ["devtools.debugger.log", true],
+ ["dom.mozBrowserFramesEnabled", true]
+ ]
+ }, runTests);
+}
+
+function runTests() {
+ // Create a minimal iframe with a message manager
+ let iframe = document.createElement("iframe");
+ iframe.mozbrowser = true;
+ document.body.appendChild(iframe);
+
+ let mm = iframe.QueryInterface(Ci.nsIFrameLoaderOwner).frameLoader.messageManager;
+
+ // Register a test actor in the child process so that we can know if and when
+ // this fake actor is disconnected.
+ mm.loadFrameScript("data:text/javascript,new " + function FrameScriptScope() {
+ const { require } = Cu.import("resource://devtools/shared/Loader.jsm", {});
+ const { DebuggerServer } = require("devtools/server/main");
+
+ if (!DebuggerServer.initialized) {
+ DebuggerServer.init();
+ }
+
+ function TestActor() {dump("instanciate test actor\n");}
+ TestActor.prototype = {
+ actorPrefix: "test",
+
+ disconnect: function () {
+ sendAsyncMessage("test-actor-disconnected", null);
+ },
+ hello: function () {
+ return {msg:"world"};
+ }
+ };
+ TestActor.prototype.requestTypes = {
+ "hello": TestActor.prototype.hello
+ };
+ DebuggerServer.addTabActor(TestActor, "testActor");
+ }, false);
+
+ // Instantiate a minimal server
+ if (!DebuggerServer.initialized) {
+ DebuggerServer.init();
+ }
+ if (!DebuggerServer.createRootActor) {
+ DebuggerServer.addBrowserActors();
+ }
+
+ function firstClient() {
+ // Fake a first connection to an iframe
+ let transport = DebuggerServer.connectPipe();
+ let conn = transport._serverConnection;
+ let client = new DebuggerClient(transport);
+ DebuggerServer.connectToChild(conn, iframe).then(actor => {
+ ok(actor.testActor, "Got the test actor");
+
+ // Ensure sending at least one request to our actor,
+ // otherwise it won't be instanciated, nor be disconnected...
+ client.request({
+ to: actor.testActor,
+ type: "hello",
+ }, function (response) {
+
+ // Then close the client. That should end up cleaning our test actor
+ client.close();
+
+ // Ensure that our test actor got cleaned up;
+ // its disconnect method should be called
+ mm.addMessageListener("test-actor-disconnected", function listener() {
+ mm.removeMessageListener("test-actor-disconnected", listener);
+ ok(true, "Actor is cleaned up");
+
+ secondClient(actor.testActor);
+ });
+ });
+ });
+ }
+
+ function secondClient(firstActor) {
+ // Then fake a second one, that should spawn a new set of tab actors
+ let transport = DebuggerServer.connectPipe();
+ let conn = transport._serverConnection;
+ let client = new DebuggerClient(transport);
+ DebuggerServer.connectToChild(conn, iframe).then(actor => {
+ ok(actor.testActor, "Got a test actor for the second connection");
+ isnot(actor.testActor, firstActor, "We get different actor instances between two connections");
+
+ client.close(cleanup);
+ });
+ }
+
+ function cleanup() {
+ DebuggerServer.destroy();
+ iframe.remove();
+ SimpleTest.finish()
+ }
+
+ firstClient();
+}
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/devtools/server/tests/mochitest/test_connection-manager.html b/devtools/server/tests/mochitest/test_connection-manager.html
new file mode 100644
index 000000000..bc802f933
--- /dev/null
+++ b/devtools/server/tests/mochitest/test_connection-manager.html
@@ -0,0 +1,119 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+Bug 898485 - [app manager] Implement an abstract connection manager
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Mozilla Bug</title>
+ <script type="application/javascript" 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>
+
+window.onload = function() {
+ SimpleTest.waitForExplicitFinish();
+
+ var Cu = Components.utils;
+
+ var {require} = Cu.import("resource://devtools/shared/Loader.jsm", {});
+ var {DebuggerServer} = require("devtools/server/main");
+ var Services = require("Services");
+
+ if (!DebuggerServer.initialized) {
+ DebuggerServer.init();
+ DebuggerServer.addBrowserActors();
+ }
+
+ var {ConnectionManager, Connection} = require("devtools/shared/client/connection-manager");
+
+ var orgCount = ConnectionManager.connections.length;
+
+ ConnectionManager.once("new", (event, c) => {
+ is(ConnectionManager.connections[orgCount], c, "new event fired, with correct connection");
+ });
+
+ var c1 = ConnectionManager.createConnection();
+ var c2 = ConnectionManager.createConnection();
+
+ is(ConnectionManager.connections[orgCount], c1, "Connection 1 registered");
+ is(ConnectionManager.connections[orgCount + 1], c2, "Connection 2 registered");
+
+ c1.once(Connection.Events.DESTROYED, function() {
+ is(ConnectionManager.connections.length, orgCount + 1, "Connection 1 destroyed");
+
+ var c = c2;
+
+ var eventsRef = "connecting connected disconnecting disconnected host-changed disconnected timeout destroyed";
+ var events = [];
+
+ var s = Connection.Status;
+
+ is(c.status, s.DISCONNECTED, "disconnected");
+
+ c.once(Connection.Events.CONNECTING, function(e) { events.push(e); is(c.status, s.CONNECTING, "connecting"); });
+ c.once(Connection.Events.CONNECTED, function(e) { events.push(e); is(c.status, s.CONNECTED, "connected"); c.disconnect()});
+ c.once(Connection.Events.DISCONNECTING, function(e) { events.push(e); is(c.status, s.DISCONNECTING, "disconnecting"); });
+ c.once(Connection.Events.DISCONNECTED, function(e) { events.push(e); is(c.status, s.DISCONNECTED, "disconnected"); testError()});
+ c.once(Connection.Events.DESTROYED, function(e) { events.push(e); is(c.status, s.DESTROYED, "destroyed"); finish()});
+
+ c.connect();
+
+ function testStore() {
+ c.store.on("set", function(e,path) {
+ if (path.join(".") == "device.width") {
+ is(c.store.object.device.width, window.screen.width, "Store is fed with valid data");
+ c.disconnect();
+ }
+ });
+ }
+
+ function testError() {
+ c.once(Connection.Events.DISCONNECTED, function(e) {
+ events.push(e);
+ testKeepConnecting();
+ });
+ c.once(Connection.Events.HOST_CHANGED, function(e) {
+ events.push(e);
+ c.connect();
+ });
+ c.port = 1;
+ c.host = "localhost";
+ }
+
+ function testKeepConnecting() {
+ // ensure that keepConnecting keep trying connecting
+ // until the connection attempts timeout
+ var originalTimeout = Services.prefs.getIntPref("devtools.debugger.remote-timeout");
+ Services.prefs.setIntPref("devtools.debugger.remote-timeout", 1000);
+ c.once("timeout", function (e) {
+ events.push(e);
+ Services.prefs.setIntPref("devtools.debugger.remote-timeout", originalTimeout);
+ ConnectionManager.destroyConnection(c);
+ });
+ c.keepConnecting = true;
+ var port = ConnectionManager.getFreeTCPPort();
+ ok(parseInt(port), "Free TCP port looks like a port number");
+ c.port = port;
+ c.host = "locahost";
+ c.connect();
+ }
+
+ function finish() {
+ is(events.join(" "), eventsRef, "Events received in the right order");
+ DebuggerServer.destroy();
+ SimpleTest.finish();
+ }
+
+ });
+
+ ConnectionManager.destroyConnection(c1);
+
+
+}
+</script>
+</pre>
+</body>
+</html>
diff --git a/devtools/server/tests/mochitest/test_css-logic-media-queries.html b/devtools/server/tests/mochitest/test_css-logic-media-queries.html
new file mode 100644
index 000000000..bc465df55
--- /dev/null
+++ b/devtools/server/tests/mochitest/test_css-logic-media-queries.html
@@ -0,0 +1,62 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+Test that css-logic handles media-queries correctly
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test css-logic media-queries</title>
+ <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
+ <style>
+ div {
+ width: 1000px;
+ height: 100px;
+ background-color: #f00;
+ }
+
+ @media screen and (min-width: 1px) {
+ div {
+ width: 200px;
+ }
+ }
+ </style>
+</head>
+<body>
+ <div></div>
+ <script type="application/javascript;version=1.8">
+
+ window.onload = function() {
+ var { classes: Cc, utils: Cu, interfaces: Ci } = Components;
+ const DOMUtils = Cc["@mozilla.org/inspector/dom-utils;1"]
+ .getService(Ci.inIDOMUtils);
+
+ var {require} = Cu.import("resource://devtools/shared/Loader.jsm", {});
+ var Services = require("Services");
+ const {CssLogic} = require("devtools/server/css-logic");
+
+ SimpleTest.waitForExplicitFinish();
+
+ let div = document.querySelector("div");
+ let cssLogic = new CssLogic(DOMUtils.isInheritedProperty);
+ cssLogic.highlight(div);
+ cssLogic.processMatchedSelectors();
+
+ let _strings = Services.strings
+ .createBundle("chrome://devtools-shared/locale/styleinspector.properties");
+
+ let inline = _strings.GetStringFromName("rule.sourceInline");
+
+ let source1 = inline + ":12";
+ let source2 = inline + ":19 @media screen and (min-width: 1px)";
+ is(cssLogic._matchedRules[0][0].source, source1,
+ "rule.source gives correct output for rule 1");
+ is(cssLogic._matchedRules[1][0].source, source2,
+ "rule.source gives correct output for rule 2");
+
+ SimpleTest.finish();
+ }
+
+ </script>
+</body>
+</html>
diff --git a/devtools/server/tests/mochitest/test_css-logic-specificity.html b/devtools/server/tests/mochitest/test_css-logic-specificity.html
new file mode 100644
index 000000000..45169c1fd
--- /dev/null
+++ b/devtools/server/tests/mochitest/test_css-logic-specificity.html
@@ -0,0 +1,84 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+Test that css-logic calculates CSS specificity properly
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test css-logic specificity</title>
+ <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+</head>
+<body style="background:blue;">
+ <script type="application/javascript;version=1.8">
+
+ window.onload = function() {
+ var {utils: Cu, classes: Cc, interfaces: Ci} = Components;
+
+ const {require} = Cu.import("resource://devtools/shared/Loader.jsm", {});
+ const {CssLogic, CssSelector} = require("devtools/server/css-logic");
+ const DOMUtils = Cc["@mozilla.org/inspector/dom-utils;1"]
+ .getService(Ci.inIDOMUtils);
+
+ 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: 257},
+ {text: "UL OL LI.red", expected: 259},
+ {text: "LI.red.level", expected: 513},
+ {text: ".red .level", expected: 512},
+ {text: "#x34y", expected: 65536},
+ {text: "#s12:not(FOO)", expected: 65537},
+ {text: "body#home div#warning p.message", expected: 131331},
+ {text: "* body#home div#warning p.message", expected: 131331},
+ {text: "#footer :not(nav) li", expected: 65538},
+ {text: "bar:nth-child(n)", expected: 257},
+ {text: "li::-moz-list-number", expected: 1},
+ {text: "a:hover", expected: 257}
+ ];
+
+ function createDocument() {
+ let text = TEST_DATA.map(i=>i.text).join(",");
+ text = '<style type="text/css">' + text + " {color:red;}</style>";
+ document.body.innerHTML = text;
+ }
+
+ function getExpectedSpecificity(selectorText) {
+ return TEST_DATA.filter(i => i.text === selectorText)[0].expected;
+ }
+
+ SimpleTest.waitForExplicitFinish();
+
+ createDocument();
+ let cssLogic = new CssLogic(DOMUtils.isInheritedProperty);
+
+ cssLogic.highlight(document.body);
+ let cssSheet = cssLogic.sheets[0];
+ let cssRule = cssSheet.domSheet.cssRules[0];
+ let selectors = CssLogic.getSelectors(cssRule);
+
+ info("Iterating over the test selectors")
+ for (let i = 0; i < selectors.length; i++) {
+ let selectorText = selectors[i];
+ info("Testing selector " + selectorText);
+
+ let selector = new CssSelector(cssRule, selectorText, i);
+ let expected = getExpectedSpecificity(selectorText);
+ let specificity = DOMUtils.getSpecificity(selector.cssRule,
+ selector.selectorIndex)
+ is(specificity, expected,
+ 'Selector "' + selectorText + '" has a specificity of ' + expected);
+ }
+
+ info("Testing specificity of element.style");
+ let colorProp = cssLogic.getPropertyInfo("background");
+ is(colorProp.matchedSelectors[0].specificity, 0x01000000,
+ "Element styles have specificity of 0x01000000 (16777216).");
+
+ SimpleTest.finish();
+ }
+
+ </script>
+</body>
+</html>
diff --git a/devtools/server/tests/mochitest/test_css-logic.html b/devtools/server/tests/mochitest/test_css-logic.html
new file mode 100644
index 000000000..6c21e72c8
--- /dev/null
+++ b/devtools/server/tests/mochitest/test_css-logic.html
@@ -0,0 +1,167 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug </title>
+
+ <script type="application/javascript" 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;version=1.8" src="inspector-helpers.js"></script>
+ <script type="application/javascript;version=1.8">
+const {CssLogic} = require("devtools/server/css-logic");
+
+window.onload = function() {
+ SimpleTest.waitForExplicitFinish();
+ runNextTest();
+}
+
+addTest(function findAllCssSelectors() {
+ var nodes = document.querySelectorAll('*');
+ for (var i = 0; i < nodes.length; i++) {
+ var selector = CssLogic.findCssSelector(nodes[i]);
+ var matches = document.querySelectorAll(selector);
+
+ is(matches.length, 1, 'There is a single match: ' + selector);
+ is(matches[0], nodes[i], 'The selector matches the correct node: ' + selector);
+ }
+
+ runNextTest();
+});
+
+addTest(function findCssSelectorNotContainedInDocument() {
+
+ var unattached = document.createElement("div");
+ unattached.id = "unattached";
+ try {
+ CssLogic.findCssSelector(unattached);
+ ok (false, "Unattached node did not throw")
+ } catch(e) {
+ ok(e, "Unattached node throws an exception");
+ }
+
+ var unattachedChild = document.createElement("div");
+ unattached.appendChild(unattachedChild);
+ try {
+ CssLogic.findCssSelector(unattachedChild);
+ ok (false, "Unattached child node did not throw")
+ } catch(e) {
+ ok(e, "Unattached child node throws an exception");
+ }
+
+ var unattachedBody = document.createElement("body");
+ try {
+ CssLogic.findCssSelector(unattachedBody);
+ ok (false, "Unattached body node did not throw")
+ } catch(e) {
+ ok(e, "Unattached body node throws an exception");
+ }
+
+ runNextTest();
+});
+
+addTest(function findCssSelector() {
+
+ let data = [
+ "#one",
+ "#" + CSS.escape("2"),
+ ".three",
+ "." + CSS.escape("4"),
+ "#find-css-selector > div:nth-child(5)",
+ "#find-css-selector > p:nth-child(6)",
+ ".seven",
+ ".eight",
+ ".nine",
+ ".ten",
+ "div.sameclass:nth-child(11)",
+ "div.sameclass:nth-child(12)",
+ "div.sameclass:nth-child(13)",
+ "#" + CSS.escape("!, \", #, $, %, &, ', (, ), *, +, ,, -, ., /, :, ;, <, =, >, ?, @, [, \\, ], ^, `, {, |, }, ~"),
+ ];
+
+ let container = document.querySelector("#find-css-selector");
+ is (container.children.length, data.length, "Container has correct number of children.");
+
+ for (let i = 0; i < data.length; i++) {
+ let node = container.children[i];
+ is (CssLogic.findCssSelector(node), data[i], "matched id for index " + (i-1));
+ }
+
+ runNextTest();
+});
+
+addTest(function getComputedStyle() {
+ let 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)");
+
+ let firstChild = new _documentWalker(node, window).firstChild();
+ is (CssLogic.getComputedStyle(firstChild).getPropertyValue("content"),
+ "\"before\"", "Computed style on a ::before node works (content)");
+ let 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() {
+ let node = document.querySelector("#computed-style");
+ var {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");
+
+ let firstChild = new _documentWalker(node, window).firstChild();
+ var {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");
+
+ let lastChild = new _documentWalker(node, window).lastChild();
+ var {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>
+ <div id="find-css-selector">
+ <div id="one"></div> <!-- Basic ID -->
+ <div id="2"></div> <!-- Escaped ID -->
+ <div class="three"></div> <!-- Basic Class -->
+ <div class="4"></div> <!-- Escaped Class -->
+ <div attr="5"></div> <!-- Only an attribute -->
+ <p></p> <!-- Nothing unique -->
+ <div class="seven seven"></div> <!-- Two classes with same name -->
+ <div class="eight eight2"></div> <!-- Two classes with different names -->
+
+ <!-- Two elements with the same id - should not use ID -->
+ <div class="nine" id="nine-and-ten"></div>
+ <div class="ten" id="nine-and-ten"></div>
+
+ <!-- Three elements with the same id - should use class and nth-child instead -->
+ <div class="sameclass" id="11-12-13"></div>
+ <div class="sameclass" id="11-12-13"></div>
+ <div class="sameclass" id="11-12-13"></div>
+
+ <!-- Special characters -->
+ <div id="!, &quot;, #, $, %, &amp;, ', (, ), *, +, ,, -, ., /, :, ;, <, =, >, ?, @, [, \, ], ^, `, {, |, }, ~"></div>
+ </div>
+ <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/mochitest/test_css-properties_01.html b/devtools/server/tests/mochitest/test_css-properties_01.html
new file mode 100644
index 000000000..45386b830
--- /dev/null
+++ b/devtools/server/tests/mochitest/test_css-properties_01.html
@@ -0,0 +1,121 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+Bug 1265798 - Replace inIDOMUtils.cssPropertyIsShorthand
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test CSS Properties Actor</title>
+ <script type="application/javascript" 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;version=1.8" src="inspector-helpers.js"></script>
+ <script type="application/javascript;version=1.8">
+window.onload = function() {
+ const { initCssProperties, getCssProperties } =
+ require("devtools/shared/fronts/css-properties");
+
+ function promiseAttachUrl (url) {
+ return new Promise((resolve, reject) => {
+ attachURL(url, function(err, client, tab, doc) {
+ if (err) {
+ return reject(err);
+ }
+ resolve({client, tab, doc});
+ });
+ })
+ }
+
+ function toSortedString(array) {
+ return JSON.stringify(array.sort());
+ }
+
+ const runCssPropertiesTests = Task.async(function* (url, useActor) {
+ info(`Opening two tabs ${useActor ? "with" : "without"} CssPropertiesActor support.`);
+
+ let attachmentA = yield promiseAttachUrl(url);
+ let attachmentB = yield promiseAttachUrl(url);
+
+ const toolboxMockA = {
+ target: {
+ hasActor: () => useActor,
+ client: attachmentA.client,
+ form: attachmentA.tab
+ },
+ // Fake the window for css-properties.js's getClientBrowserVersion to work
+ win: window
+ };
+ const toolboxMockB = {
+ target: {
+ hasActor: () => useActor,
+ client: attachmentB.client,
+ form: attachmentB.tab
+ },
+ win: window
+ };
+
+ yield initCssProperties(toolboxMockA);
+ yield initCssProperties(toolboxMockB);
+
+ const cssProperties = getCssProperties(toolboxMockA);
+ const cssPropertiesA = getCssProperties(toolboxMockA);
+ const cssPropertiesB = getCssProperties(toolboxMockB);
+
+ is(cssProperties, cssPropertiesA,
+ "Multiple calls with the same toolbox returns the same object.");
+ isnot(cssProperties, cssPropertiesB,
+ "Multiple calls with the different toolboxes return different "+
+ " objects.");
+
+ 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(["-moz-calc","auto","calc","inherit","initial","unset"]),
+ "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.");
+
+ ok(cssProperties.isValidOnClient("margin", "0px", window.document),
+ "Margin and 0px are valid CSS values");
+ ok(!cssProperties.isValidOnClient("margin", "foo", window.document),
+ "Margin and foo are not valid CSS values");
+ });
+
+ addAsyncTest(function* setup() {
+ let url = document.getElementById("cssProperties").href;
+ yield runCssPropertiesTests(url, true);
+ yield runCssPropertiesTests(url, false);
+
+ 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/mochitest/test_css-properties_02.html b/devtools/server/tests/mochitest/test_css-properties_02.html
new file mode 100644
index 000000000..1a5d99d72
--- /dev/null
+++ b/devtools/server/tests/mochitest/test_css-properties_02.html
@@ -0,0 +1,86 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+Bug 1265798 - Replace inIDOMUtils.cssPropertyIsShorthand
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test CSS Properties Actor</title>
+ <script type="application/javascript" 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;version=1.8" src="inspector-helpers.js"></script>
+ <script type="application/javascript;version=1.8">
+window.onload = function() {
+ const { initCssProperties, getCssProperties } =
+ require("devtools/shared/fronts/css-properties");
+
+ const { CSS_PROPERTIES_DB } = require("devtools/shared/css/properties-db");
+
+ function promiseAttachUrl (url) {
+ return new Promise((resolve, reject) => {
+ attachURL(url, function(err, client, tab, doc) {
+ if (err) {
+ return reject(err);
+ }
+ resolve({client, tab, doc});
+ });
+ })
+ }
+
+ addAsyncTest(function* setup() {
+ let url = document.getElementById("cssProperties").href;
+
+ let attachmentA = yield promiseAttachUrl(url);
+ let attachmentB = yield promiseAttachUrl(url);
+ let attachmentC = yield promiseAttachUrl(url);
+
+ const toolboxMatchingVersions = {
+ target: {
+ hasActor: () => true,
+ client: attachmentA.client,
+ form: attachmentA.tab,
+ },
+ win: window
+ };
+ const toolboxDifferentVersions = {
+ target: {
+ hasActor: () => true,
+ client: attachmentB.client,
+ form: attachmentB.tab
+ },
+ win: { navigator: { userAgent:
+ "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.11; rv:49.0) Gecko/20100101 " +
+ "Firefox/30.0" }}
+ };
+
+ // Modify a property on the static database, to differentiate between a generated
+ // and static CSS properties database.
+ CSS_PROPERTIES_DB.properties.color.isStatic = true;
+
+ yield initCssProperties(toolboxMatchingVersions);
+ yield initCssProperties(toolboxDifferentVersions);
+
+ const cssPropertiesMatching = getCssProperties(toolboxMatchingVersions);
+ const cssPropertiesDifferent = getCssProperties(toolboxDifferentVersions);
+
+ is(cssPropertiesMatching.properties.color.isStatic, true,
+ "The static CSS database is used when the client and platform versions match.");
+ isnot(cssPropertiesDifferent.properties.color.isStatic, undefined,
+ "The generated CSS database is used when the client and platform versions do " +
+ "not match, but the client is a Firefox.");
+
+ delete CSS_PROPERTIES_DB.properties.color.isStatic;
+
+ 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/mochitest/test_device.html b/devtools/server/tests/mochitest/test_device.html
new file mode 100644
index 000000000..d678f185f
--- /dev/null
+++ b/devtools/server/tests/mochitest/test_device.html
@@ -0,0 +1,99 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+Bug 895360 - [app manager] Device meta data actor
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Mozilla Bug</title>
+ <script type="application/javascript" 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>
+
+window.onload = function() {
+ var Cu = Components.utils;
+ var Cc = Components.classes;
+ var Ci = Components.interfaces;
+
+ Cu.import("resource://gre/modules/PermissionsTable.jsm");
+ var {require} = Cu.import("resource://devtools/shared/Loader.jsm", {});
+ var {DebuggerClient} = require("devtools/shared/client/main");
+ var {DebuggerServer} = require("devtools/server/main");
+ var Services = require("Services");
+
+ SimpleTest.waitForExplicitFinish();
+
+ var {getDeviceFront} = require("devtools/shared/fronts/device");
+
+ if (!DebuggerServer.initialized) {
+ DebuggerServer.init();
+ DebuggerServer.addBrowserActors();
+ }
+
+ var client = new DebuggerClient(DebuggerServer.connectPipe());
+ client.connect().then(function onConnect() {
+ client.listTabs(function onListTabs(aResponse) {
+ var d = getDeviceFront(client, aResponse);
+
+ var desc, permissions;
+ var appInfo = Services.appinfo;
+ var utils = window.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowUtils);
+
+
+ var 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: Cc["@mozilla.org/chrome/chrome-registry;1"].getService(Ci.nsIXULChromeRegistry).getSelectedLocale("global"),
+ 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 (var key in localDesc) {
+ is(desc[key], localDesc[key], "valid field (" + key + ")");
+ }
+
+ var currProfD = Services.dirsvc.get("ProfD", Ci.nsIFile);
+ var profileDir = currProfD.path;
+ ok(profileDir.indexOf(desc.profile.length > 0 && desc.profile) != -1, "valid profile name");
+
+ var a = JSON.stringify(PermissionsTable);
+ var b = JSON.stringify(permissions.rawPermissionsTable);
+
+ is(a, b, "Permissions Tables is valid");
+
+ client.close().then(() => {
+ DebuggerServer.destroy();
+ SimpleTest.finish()
+ });
+ }
+
+
+ d.getDescription().then((v) => desc = v)
+ .then(() => d.getRawPermissionsTable())
+ .then((json) => permissions = json)
+ .then(checkValues);
+
+ });
+ });
+
+}
+</script>
+</pre>
+</body>
+</html>
diff --git a/devtools/server/tests/mochitest/test_director.html b/devtools/server/tests/mochitest/test_director.html
new file mode 100644
index 000000000..ad2648bfa
--- /dev/null
+++ b/devtools/server/tests/mochitest/test_director.html
@@ -0,0 +1,114 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug </title>
+
+ <script type="application/javascript" 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 type="application/javascript;version=1.8" src="./director-helpers.js"></script>
+ <script type="application/javascript;version=1.8">
+const WAIT_EVENT_TIMEOUT = 3000;
+
+window.onload = function() {
+ Task.spawn(function* () {
+ SimpleTest.waitForExplicitFinish();
+
+ var tests = [
+ runDirectorRegistryActorTest
+ ].map((testCase) => {
+ return function* () {
+ setup();
+ yield testCase().then(null, (e) => {
+ console.error("Exception during testCase run", e);
+ ok(false, "Exception during testCase run: " + [e, e.fileName, e.lineNumber].join("\n\t"));
+ });
+
+ teardown();
+ };
+ });
+
+ for (var test of tests) {
+ yield test();
+ }
+ }).then(
+ function success() {
+ SimpleTest.finish()
+ },
+ function error(e) {
+ console.error("Exception during testCase run", e);
+ ok(false, "Exception during testCase run: " + [e, e.fileName, e.lineNumber].join("\n\t"));
+
+ SimpleTest.finish();
+ }
+ );
+};
+
+var targetWin = null;
+
+function setup() {
+ if (!DebuggerServer.initialized) {
+ DebuggerServer.init(() => true);
+ DebuggerServer.addBrowserActors();
+
+ SimpleTest.registerCleanupFunction(teardown);
+ }
+}
+
+function teardown() {
+ purgeInstalledDirectorScripts();
+
+ DebuggerServer.destroy();
+ if (targetWin) {
+ targetWin.close();
+ }
+}
+
+/***********************************
+ * test cases
+ **********************************/
+
+
+function runDirectorRegistryActorTest() {
+ let testDirectorScriptOptions = {
+ scriptCode: "(" + (function() {
+ module.exports = function({port}) {
+ port.onmessage = function(evt) {
+ // echo messages
+ evt.source.postMessage(evt.data);
+ };
+ };
+ }).toString() + ")();",
+ scriptOptions: {}
+ }
+
+ return Task.spawn(function* () {
+ let { client, root } = yield newConnectedDebuggerClient();
+
+ var directorRegistryClient = new DirectorRegistryFront(client, root);
+
+ let installed = yield directorRegistryClient.install("testDirectorScript", testDirectorScriptOptions);
+ is(installed, true, "DirectorManager.install returns true");
+
+ let list = yield directorRegistryClient.list();
+ is(JSON.stringify(list), JSON.stringify(["testDirectorScript"]),
+ "DirectorManager.list contains the installed director script");
+
+ let uninstalled = yield directorRegistryClient.uninstall("testDirectorScript");
+ is(uninstalled, true, "DirectorManager.uninstall return true");
+
+ yield client.close();
+ });
+}
+
+
+ </script>
+</pre>
+</body>
+</html>
diff --git a/devtools/server/tests/mochitest/test_director_connectToChild.html b/devtools/server/tests/mochitest/test_director_connectToChild.html
new file mode 100644
index 000000000..cb348efe6
--- /dev/null
+++ b/devtools/server/tests/mochitest/test_director_connectToChild.html
@@ -0,0 +1,98 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug </title>
+
+ <script type="application/javascript" 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 type="application/javascript;version=1.8" src="./director-helpers.js"></script>
+ <script type="application/javascript;version=1.8">
+window.onload = function() {
+ Task.spawn(function* () {
+ SimpleTest.waitForExplicitFinish();
+
+ var tests = [
+ runPropagateDirectorScriptsToChildTest,
+ ].map((testCase) => {
+ return function* () {
+ setup();
+ yield testCase().then(null, (e) => {
+ ok(false, "Exception during testCase run: " + [e, e.fileName, e.lineNumber].join("\n\t"));
+ });
+
+ teardown();
+ };
+ });
+
+ for (var test of tests) {
+ yield test();
+ }
+
+ SimpleTest.finish();
+ });
+};
+
+function setup() {
+ if (!DebuggerServer.initialized) {
+ DebuggerServer.init(() => true);
+ DebuggerServer.addBrowserActors();
+ SimpleTest.registerCleanupFunction(function() {
+ DebuggerServer.destroy();
+ });
+ }
+}
+
+function teardown() {
+ purgeInstalledDirectorScripts();
+ DebuggerServer.destroy();
+}
+
+/***********************************
+ * test cases
+ **********************************/
+
+function runPropagateDirectorScriptsToChildTest() {
+ let iframe = document.createElement("iframe");
+ iframe.mozbrowser = true;
+
+ document.body.appendChild(iframe);
+
+ return Task.spawn(function* () {
+ var { client, root, transport } = yield newConnectedDebuggerClient();
+
+ var directorRegistryClient = new DirectorRegistryFront(client, root);
+
+ // install a director script
+ yield directorRegistryClient.install("testPropagatedDirectorScript", {
+ scriptCode: "console.log('director script test');",
+ scriptOptions: {}
+ });
+
+ var conn = transport._serverConnection;
+ var childActor = yield DebuggerServer.connectToChild(conn, iframe);
+
+ ok(typeof childActor.directorManagerActor !== "undefined",
+ "childActor.directorActor should be defined");
+
+ var childDirectorManagerClient = new DirectorManagerFront(client, childActor);
+
+ var directorScriptList = yield childDirectorManagerClient.list();
+
+ ok(directorScriptList.installed.length === 1 &&
+ directorScriptList.installed[0] === "testPropagatedDirectorScript",
+ "director scripts propagated correctly")
+
+ yield client.close();
+ });
+}
+ </script>
+</pre>
+</body>
+</html>
diff --git a/devtools/server/tests/mochitest/test_executeInGlobal-outerized_this.html b/devtools/server/tests/mochitest/test_executeInGlobal-outerized_this.html
new file mode 100644
index 000000000..8bedde618
--- /dev/null
+++ b/devtools/server/tests/mochitest/test_executeInGlobal-outerized_this.html
@@ -0,0 +1,69 @@
+<!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 type="application/javascript" 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>
+
+Components.utils.import("resource://gre/modules/jsdebugger.jsm");
+addDebuggerToGlobal(this);
+
+window.onload = function () {
+ SimpleTest.waitForExplicitFinish();
+
+ var iframe = document.createElement("iframe");
+ iframe.src = "data:text/html,<script>var me = 'page 1';<\/script>";
+ iframe.onload = firstOnLoadHandler;
+ document.body.appendChild(iframe);
+
+ function firstOnLoadHandler() {
+ var dbg = new Debugger;
+ var page1DO = dbg.addDebuggee(iframe.contentWindow);
+ iframe.src = "data:text/html,<script>var me = 'page 2';<\/script>";
+ iframe.onload = function () {
+ var 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.
+ var 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(function () { 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/mochitest/test_framerate_01.html b/devtools/server/tests/mochitest/test_framerate_01.html
new file mode 100644
index 000000000..0282d50a2
--- /dev/null
+++ b/devtools/server/tests/mochitest/test_framerate_01.html
@@ -0,0 +1,141 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+Bug 1007200 - Create a framerate actor
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Framerate actor test</title>
+ <script type="application/javascript" 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>
+
+window.onload = function() {
+ var Cu = Components.utils;
+ var Cc = Components.classes;
+ var Ci = Components.interfaces;
+
+ var { require } = Cu.import("resource://devtools/shared/Loader.jsm", {});
+ var Services = require("Services");
+ var { DebuggerClient } = require("devtools/shared/client/main");
+ var { DebuggerServer } = require("devtools/server/main");
+
+ // Always log packets when running tests.
+ Services.prefs.setBoolPref("devtools.debugger.log", true);
+ SimpleTest.registerCleanupFunction(function() {
+ Services.prefs.clearUserPref("devtools.debugger.log");
+ });
+
+ SimpleTest.waitForExplicitFinish();
+
+ var {FramerateFront} = require("devtools/shared/fronts/framerate");
+
+ function plotFPS(ticks, interval = 100, clamp = 60) {
+ var timeline = [];
+ var totalTicks = ticks.length;
+
+ // If the refresh driver didn't get a chance to tick before the
+ // recording was stopped, assume framerate was 0.
+ if (totalTicks == 0) {
+ timeline.push({ delta: 0, value: 0 });
+ timeline.push({ delta: interval, value: 0 });
+ return timeline;
+ }
+
+ var frameCount = 0;
+ var prevTime = ticks[0];
+
+ for (var i = 1; i < totalTicks; i++) {
+ var currTime = ticks[i];
+ frameCount++;
+
+ var elapsedTime = currTime - prevTime;
+ if (elapsedTime < interval) {
+ continue;
+ }
+
+ var framerate = Math.min(1000 / (elapsedTime / frameCount), clamp);
+ timeline.push({ delta: prevTime, value: framerate });
+ timeline.push({ delta: currTime, value: framerate });
+
+ frameCount = 0;
+ prevTime = currTime;
+ }
+
+ return timeline;
+ };
+
+ if (!DebuggerServer.initialized) {
+ DebuggerServer.init();
+ DebuggerServer.addBrowserActors();
+ }
+
+ var client = new DebuggerClient(DebuggerServer.connectPipe());
+ client.connect().then(function onConnect() {
+ client.listTabs(function onListTabs(aResponse) {
+ var form = aResponse.tabs[aResponse.selected];
+ var front = FramerateFront(client, form);
+
+ window.setTimeout(() => {
+ front.startRecording().then(() => {
+ window.setTimeout(() => {
+ front.stopRecording().then(rawData => {
+ onRecordingStopped(front, rawData);
+ });
+ }, 1000);
+ });
+ }, 1000);
+ });
+ });
+
+ function onRecordingStopped(front, rawData) {
+ ok(rawData, "There should be a recording available.");
+
+ var timeline = plotFPS(rawData);
+ ok(timeline.length >= 2,
+ "There should be at least one measurement available, with two entries.");
+
+ var prevTimeStart = timeline[0].delta;
+
+ for (var i = 0; i < timeline.length; i += 2) {
+ var currTimeStart = timeline[i].delta;
+ var currTimeEnd = timeline[i + 1].delta;
+ info("Testing delta: " + currTimeStart + " vs. " + currTimeEnd);
+
+ ok(currTimeStart < currTimeEnd,
+ "The start and end time deltas should be consecutive.");
+ is(currTimeStart, prevTimeStart,
+ "There should be two time deltas for each framerate value.");
+
+ prevTimeStart = currTimeEnd;
+ }
+
+ var prevFramerateValue = -1;
+
+ for (var i = 0; i < timeline.length; i += 2) {
+ var currFramerateStart = timeline[i].value;
+ var currFramerateEnd = timeline[i + 1].value;
+ info("Testing framerate: " + currFramerateStart);
+
+ is(currFramerateStart, currFramerateEnd,
+ "The start and end framerate values should be equal.");
+
+ is(typeof currFramerateStart, "number", "All values should be numbers.");
+ ok(currFramerateStart <= 60, "All values were correctly clamped.")
+
+ prevFramerateValue = currFramerateStart;
+ }
+
+ client.close().then(() => {
+ DebuggerServer.destroy();
+ SimpleTest.finish()
+ });
+ }
+}
+</script>
+</pre>
+</body>
+</html>
diff --git a/devtools/server/tests/mochitest/test_framerate_02.html b/devtools/server/tests/mochitest/test_framerate_02.html
new file mode 100644
index 000000000..9d4626b12
--- /dev/null
+++ b/devtools/server/tests/mochitest/test_framerate_02.html
@@ -0,0 +1,113 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+Bug 1007200 - Create a framerate actor
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Framerate actor test</title>
+ <script type="application/javascript" 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>
+
+window.onload = function() {
+ var Cu = Components.utils;
+ var Cc = Components.classes;
+ var Ci = Components.interfaces;
+
+ var {require} = Cu.import("resource://devtools/shared/Loader.jsm", {});
+ var {DebuggerClient} = require("devtools/shared/client/main");
+ var {DebuggerServer} = require("devtools/server/main");
+ var Services = require("Services");
+
+ // Always log packets when running tests.
+ Services.prefs.setBoolPref("devtools.debugger.log", true);
+ SimpleTest.registerCleanupFunction(function() {
+ Services.prefs.clearUserPref("devtools.debugger.log");
+ });
+
+ SimpleTest.waitForExplicitFinish();
+
+ var {FramerateFront} = require("devtools/shared/fronts/framerate");
+
+ function plotFPS(ticks, interval = 100, clamp = 60) {
+ var timeline = [];
+ var totalTicks = ticks.length;
+
+ // If the refresh driver didn't get a chance to tick before the
+ // recording was stopped, assume framerate was 0.
+ if (totalTicks == 0) {
+ timeline.push({ delta: 0, value: 0 });
+ timeline.push({ delta: interval, value: 0 });
+ return timeline;
+ }
+
+ var frameCount = 0;
+ var prevTime = ticks[0];
+
+ for (var i = 1; i < totalTicks; i++) {
+ var currTime = ticks[i];
+ frameCount++;
+
+ var elapsedTime = currTime - prevTime;
+ if (elapsedTime < interval) {
+ continue;
+ }
+
+ var framerate = Math.min(1000 / (elapsedTime / frameCount), clamp);
+ timeline.push({ delta: prevTime, value: framerate });
+ timeline.push({ delta: currTime, value: framerate });
+
+ frameCount = 0;
+ prevTime = currTime;
+ }
+
+ return timeline;
+ };
+
+ if (!DebuggerServer.initialized) {
+ DebuggerServer.init();
+ DebuggerServer.addBrowserActors();
+ }
+
+ var client = new DebuggerClient(DebuggerServer.connectPipe());
+ client.connect().then(function onConnect() {
+ client.listTabs(function onListTabs(aResponse) {
+ var form = aResponse.tabs[aResponse.selected];
+ var front = FramerateFront(client, form);
+
+ front.stopRecording().then(rawData => {
+ ok(rawData, "There should be a recording available.");
+ is(rawData.length, 0, "...but it should be empty.");
+
+ var timeline = plotFPS(rawData);
+ is(timeline.length, 2,
+ "There should be one measurement plotted, with two entries.");
+
+ info("The framerate should be assumed to be 0 if the recording is empty.");
+
+ is(timeline[0].delta, 0,
+ "The first time delta should be 0.");
+ is(timeline[0].value, 0,
+ "The first framerate value should be 0.");
+
+ is(timeline[1].delta, 100,
+ "The last time delta should be 100 (the default interval value).");
+ is(timeline[1].value, 0,
+ "The last framerate value should be 0.");
+
+ client.close().then(() => {
+ DebuggerServer.destroy();
+ SimpleTest.finish()
+ });
+ });
+ });
+ });
+}
+</script>
+</pre>
+</body>
+</html>
diff --git a/devtools/server/tests/mochitest/test_framerate_03.html b/devtools/server/tests/mochitest/test_framerate_03.html
new file mode 100644
index 000000000..da76ebc20
--- /dev/null
+++ b/devtools/server/tests/mochitest/test_framerate_03.html
@@ -0,0 +1,82 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+Bug 1023018 - Tests whether or not the framerate actor can handle time ranges.
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Framerate actor test</title>
+ <script type="application/javascript" 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>
+
+window.onload = function() {
+ var Cu = Components.utils;
+ var Cc = Components.classes;
+ var Ci = Components.interfaces;
+
+ var {require} = Cu.import("resource://devtools/shared/Loader.jsm", {});
+ var {DebuggerClient} = require("devtools/shared/client/main");
+ var {DebuggerServer} = require("devtools/server/main");
+ var Services = require("Services");
+
+ // Always log packets when running tests.
+ Services.prefs.setBoolPref("devtools.debugger.log", true);
+ SimpleTest.registerCleanupFunction(function() {
+ Services.prefs.clearUserPref("devtools.debugger.log");
+ });
+
+ SimpleTest.waitForExplicitFinish();
+
+ var {FramerateFront} = require("devtools/shared/fronts/framerate");
+ var START_TICK = 2000;
+ var STOP_TICK = 3000;
+ var TOTAL_TIME = 5000;
+
+ if (!DebuggerServer.initialized) {
+ DebuggerServer.init();
+ DebuggerServer.addBrowserActors();
+ }
+
+ var client = new DebuggerClient(DebuggerServer.connectPipe());
+ client.connect().then(function onConnect() {
+ client.listTabs(function onListTabs(aResponse) {
+ var form = aResponse.tabs[aResponse.selected];
+ var front = FramerateFront(client, form);
+
+ front.startRecording().then(() => {
+ window.setTimeout(() => {
+ front.stopRecording(START_TICK, STOP_TICK).then(rawData => {
+ onRecordingStopped(front, rawData);
+ });
+ }, TOTAL_TIME);
+ });
+ });
+ });
+
+ function onRecordingStopped(front, rawData) {
+ ok(rawData, "There should be a recording available.");
+
+ ok(!rawData.find(e => e < START_TICK),
+ "There should be no tick before 2000ms.");
+ ok(!rawData.find(e => e > STOP_TICK),
+ "There should be no tick after 3000ms.");
+
+ for (var tick of rawData) {
+ info("Testing tick: " + tick);
+ is(typeof tick, "number", "All values should be numbers.");
+ }
+
+ client.close().then(() => {
+ DebuggerServer.destroy();
+ SimpleTest.finish()
+ });
+ }
+}
+</script>
+</pre>
+</body>
+</html>
diff --git a/devtools/server/tests/mochitest/test_framerate_04.html b/devtools/server/tests/mochitest/test_framerate_04.html
new file mode 100644
index 000000000..af6747291
--- /dev/null
+++ b/devtools/server/tests/mochitest/test_framerate_04.html
@@ -0,0 +1,72 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+Bug 1023018 - Tests if the framerate actor keeps recording after navigations.
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Framerate actor test</title>
+ <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript;version=1.8" 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>
+
+window.onload = function() {
+ SimpleTest.waitForExplicitFinish();
+ var {FramerateFront} = require("devtools/shared/fronts/framerate");
+ var {TargetFactory} = require("devtools/client/framework/target");
+
+ var url = document.getElementById("testContent").href;
+ attachURL(url, onTab);
+
+ function onTab(_, client, form, contentDoc) {
+ var contentWin = contentDoc.defaultView;
+ var chromeWin = Services.wm.getMostRecentWindow("navigator:browser");
+ var selectedTab = chromeWin.gBrowser.selectedTab;
+
+ var target = TargetFactory.forTab(selectedTab);
+ var front = FramerateFront(client, form);
+
+ front.startRecording().then(() => {
+ window.setTimeout(() => {
+ front.getPendingTicks().then(firstBatch => {
+ target.once("will-navigate", () => {
+ window.setTimeout(() => {
+ front.stopRecording().then(secondBatch => {
+ onRecordingStopped(client, firstBatch, secondBatch);
+ });
+ }, 1000);
+ });
+ contentWin.location.reload();
+ });
+ }, 1000);
+ });
+ }
+
+ function onRecordingStopped(client, firstBatch, secondBatch) {
+ ok(firstBatch, "There should be a first batch recording available.");
+ ok(secondBatch, "There should be a second batch recording available.");
+
+ var diff = secondBatch.length - firstBatch.length;
+ info("Difference in ticks: " + diff);
+ ok(diff > 0, "More ticks should be recorded in the second batch.");
+
+ ok(firstBatch.every((e) => secondBatch.indexOf(e) != -1),
+ "All the ticks in the first batch should be in the second batch as well.");
+ ok(secondBatch.every((e, i, array) => i < array.length - 1 ? e < array[i + 1] : true),
+ "All the ticks in the final batch should be ascending in value.");
+
+ client.close().then(() => {
+ DebuggerServer.destroy();
+ SimpleTest.finish()
+ });
+ }
+}
+</script>
+</pre>
+<a id="testContent" target="_blank" href="inspector_getImageData.html">Test Document</a>
+</body>
+</html>
diff --git a/devtools/server/tests/mochitest/test_framerate_05.html b/devtools/server/tests/mochitest/test_framerate_05.html
new file mode 100644
index 000000000..96f56a18f
--- /dev/null
+++ b/devtools/server/tests/mochitest/test_framerate_05.html
@@ -0,0 +1,77 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+Bug 1034648 - Tests whether a framerate recording can be cancelled.
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Framerate actor test</title>
+ <script type="application/javascript" 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>
+
+window.onload = function() {
+ var Cu = Components.utils;
+ var Cc = Components.classes;
+ var Ci = Components.interfaces;
+
+ var {require} = Cu.import("resource://devtools/shared/Loader.jsm", {});
+ var {DebuggerClient} = require("devtools/shared/client/main");
+ var {DebuggerServer} = require("devtools/server/main");
+ var Services = require("Services");
+
+ // Always log packets when running tests.
+ Services.prefs.setBoolPref("devtools.debugger.log", true);
+ SimpleTest.registerCleanupFunction(function() {
+ Services.prefs.clearUserPref("devtools.debugger.log");
+ });
+
+ SimpleTest.waitForExplicitFinish();
+
+ var {FramerateFront} = require("devtools/shared/fronts/framerate");
+
+ DebuggerServer.init();
+ DebuggerServer.addBrowserActors();
+
+ var client = new DebuggerClient(DebuggerServer.connectPipe());
+ client.connect().then(function onConnect() {
+ client.listTabs(function onListTabs(aResponse) {
+ var form = aResponse.tabs[aResponse.selected];
+ var front = FramerateFront(client, form);
+
+ front.startRecording().then(() => {
+ window.setTimeout(() => {
+ front.cancelRecording().then(() => {
+ window.setTimeout(() => {
+ front.getPendingTicks().then(rawTicks => {
+ ok(rawTicks,
+ "The returned pending ticks should be empty (1).");
+ is(rawTicks.length, 0,
+ "The returned pending ticks should be empty (2).");
+
+ front.stopRecording().then(rawData => {
+ ok(rawData,
+ "The returned raw data should be an empty array (1).");
+ is(rawData.length, 0,
+ "The returned raw data should be an empty array (2).");
+
+ client.close().then(() => {
+ DebuggerServer.destroy();
+ SimpleTest.finish()
+ });
+ });
+ });
+ }, 1000);
+ });
+ }, 1000);
+ });
+ });
+ });
+}
+</script>
+</pre>
+</body>
+</html>
diff --git a/devtools/server/tests/mochitest/test_framerate_06.html b/devtools/server/tests/mochitest/test_framerate_06.html
new file mode 100644
index 000000000..ecb0a71e0
--- /dev/null
+++ b/devtools/server/tests/mochitest/test_framerate_06.html
@@ -0,0 +1,82 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+Bug 1171489 - Tests if the framerate actor does not record timestamps from multiple frames.
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Framerate actor test</title>
+ <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript;version=1.8" 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>
+
+window.onload = function() {
+ SimpleTest.waitForExplicitFinish();
+ var {FramerateFront} = require("devtools/shared/fronts/framerate");
+ var {TargetFactory} = require("devtools/client/framework/target");
+
+ var url = document.getElementById("testContent").href;
+ attachURL(url, onTab);
+
+ function onTab(_, client, form, contentDoc) {
+ var contentWin = contentDoc.defaultView;
+ var chromeWin = Services.wm.getMostRecentWindow("navigator:browser");
+ var selectedTab = chromeWin.gBrowser.selectedTab;
+
+ var target = TargetFactory.forTab(selectedTab);
+ var front = FramerateFront(client, form);
+
+ front.startRecording().then(() => {
+ window.setTimeout(() => {
+ // Wait for the iframe to be loaded again
+ window.addEventListener("message", function loaded (event) {
+ if (event.data === "ready") {
+ window.removeEventListener("message", loaded);
+ window.setTimeout(() => {
+ front.stopRecording().then(ticks => {
+ onRecordingStopped(client, ticks);
+ });
+ }, 1000);
+ }
+ });
+ contentWin.location.reload();
+ }, 1000);
+ });
+ }
+
+ function onRecordingStopped(client, ticks) {
+ var diffs = [];
+
+ info(`Got ${ticks.length} ticks.`);
+
+ for (var i = 1; i < ticks.length; i++) {
+ var prev = ticks[i - 1];
+ var curr = ticks[i];
+ diffs.push(curr - prev);
+ info(curr + " - " + (curr - prev));
+ }
+
+ // 1000 / 60 => 16.666... so we shouldn't get more than diffs of 16.66.. but
+ // when we get ticks from other frames they're usually at diffs of < 1. Sometimes
+ // ticks can still be less than 16ms even on one frame (usually following a very slow
+ // frame), so use a low number (2) to be our threshold
+ var THRESHOLD = 2;
+ ok(ticks.length >= 20, "we should have atleast 20 ticks over the course of two seconds.");
+ var belowThreshold = diffs.filter(v => v <= THRESHOLD);
+ ok(belowThreshold.length <= 10, "we should have very few frames less than the threshold");
+
+ client.close().then(() => {
+ DebuggerServer.destroy();
+ SimpleTest.finish()
+ });
+ }
+}
+</script>
+</pre>
+<a id="testContent" target="_blank" href="inspector-traversal-data.html">Test Document</a>
+</body>
+</html>
diff --git a/devtools/server/tests/mochitest/test_getProcess.html b/devtools/server/tests/mochitest/test_getProcess.html
new file mode 100644
index 000000000..3c8ca5727
--- /dev/null
+++ b/devtools/server/tests/mochitest/test_getProcess.html
@@ -0,0 +1,120 @@
+<SDOCTYPv HTM.>
+<html>
+<!--
+Bug 1060093 - Test DebuggerServer.getProcess
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Mozilla Bug</title>
+ <script type="application/javascript" 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 type="application/javascript;version=1.8">
+
+let Cu = Components.utils;
+let Cc = Components.classes;
+let Ci = Components.interfaces;
+
+let {require} = Cu.import("resource://devtools/shared/Loader.jsm", {});
+let {DebuggerClient} = require("devtools/shared/client/main");
+let {DebuggerServer} = require("devtools/server/main");
+
+window.onload = function() {
+ SimpleTest.waitForExplicitFinish();
+
+ SpecialPowers.pushPrefEnv({
+ "set": [
+ // Always log packets when running tests.
+ ["devtools.debugger.log", true],
+ // Enabled mozbrowser frame to support remote=true
+ ["dom.mozBrowserFramesEnabled", true],
+ // Allows creating a branch new process when creation the iframe
+ ["dom.ipc.processCount", 10],
+ ]
+ }, runTests);
+}
+
+function runTests() {
+ // Instantiate a minimal server
+ if (!DebuggerServer.initialized) {
+ DebuggerServer.init();
+ }
+ DebuggerServer.allowChromeProcess = true;
+ if (!DebuggerServer.createRootActor) {
+ DebuggerServer.addBrowserActors();
+ }
+
+ let client, iframe, processCount;
+
+ function connect() {
+ // Fake a first connection to the content process
+ let transport = DebuggerServer.connectPipe();
+ client = new DebuggerClient(transport);
+ client.connect().then(listProcess);
+ }
+
+ function listProcess() {
+ // Call listProcesses in order to start receiving new process notifications
+ client.addListener("processListChanged", function listener() {
+ client.removeListener("processListChanged", listener);
+ ok(true, "Received processListChanged event");
+ getProcess();
+ });
+ client.mainRoot.listProcesses(response => {
+ processCount = response.processes.length;
+ // Create a remote iframe to spawn a new process
+ createRemoteIframe();
+ });
+ }
+
+ function createRemoteIframe() {
+ iframe = document.createElement("iframe");
+ iframe.mozbrowser = true;
+ iframe.setAttribute("remote", "true");
+ iframe.setAttribute("src", "data:text/html,foo");
+ document.body.appendChild(iframe);
+ }
+
+ function getProcess() {
+ client.mainRoot.listProcesses(response => {
+ ok(response.processes.length >= 2, "Got at least the parent process and one child");
+ is(response.processes.length, processCount+1 , "Got one additional process on the second call to listProcesses");
+
+ // Connect to the first content processe available
+ let content = response.processes.filter(p => (!p.parent))[0];
+
+ client.getProcess(content.id).then(response => {
+ let actor = response.form;
+ ok(actor.consoleActor, "Got the console actor");
+ ok(actor.chromeDebugger, "Got the thread actor");
+
+ // Ensure sending at least one request to an actor...
+ client.request({
+ to: actor.consoleActor,
+ type: "evaluateJS",
+ text: "var a = 42; a"
+ }, function (response) {
+ ok(response.result, 42, "console.eval worked");
+ cleanup();
+ });
+ });
+ });
+ }
+
+ function cleanup() {
+ client.close().then(function () {
+ DebuggerServer.destroy();
+ iframe.remove();
+ SimpleTest.finish()
+ });
+ }
+
+ connect();
+}
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/devtools/server/tests/mochitest/test_inspector-anonymous.html b/devtools/server/tests/mochitest/test_inspector-anonymous.html
new file mode 100644
index 000000000..56a911c89
--- /dev/null
+++ b/devtools/server/tests/mochitest/test_inspector-anonymous.html
@@ -0,0 +1,201 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=777674
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 777674</title>
+
+ <script type="application/javascript" 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;version=1.8" src="inspector-helpers.js"></script>
+ <script type="application/javascript;version=1.8">
+window.onload = function() {
+ const {InspectorFront} =
+ require("devtools/shared/fronts/inspector");
+ const {_documentWalker} =
+ require("devtools/server/actors/inspector");
+ const nodeFilterConstants =
+ require("devtools/shared/dom-node-filter-constants");
+ const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
+
+ SpecialPowers.pushPrefEnv({"set": [
+ ["dom.webcomponents.enabled", true]
+ ]});
+ SimpleTest.waitForExplicitFinish();
+
+ let gWalker = null;
+ let gClient = null;
+
+ addTest(function setup() {
+ info ("Setting up inspector and walker actors.");
+
+ let url = document.getElementById("inspectorContent").href;
+ attachURL(url, function(err, client, tab, doc) {
+ gInspectee = doc;
+ let inspector = InspectorFront(client, tab);
+ promiseDone(inspector.getWalker().then(walker => {
+ ok(walker, "getWalker() should return an actor.");
+ gClient = client;
+ gWalker = walker;
+ }).then(runNextTest));
+ });
+ });
+
+ addAsyncTest(function* testXBLAnonymousInHTMLDocument() {
+ info ("Testing XBL anonymous in an HTML document.");
+ let rawToolbarbutton = gInspectee.createElementNS(XUL_NS, "toolbarbutton");
+ gInspectee.documentElement.appendChild(rawToolbarbutton);
+
+ let toolbarbutton = yield gWalker.querySelector(gWalker.rootNode, "toolbarbutton");
+ let children = yield gWalker.children(toolbarbutton);
+
+ is (toolbarbutton.numChildren, 0, "XBL content is not visible in HTML doc");
+ is (children.nodes.length, 0, "XBL content is not returned in HTML doc");
+
+ runNextTest();
+ });
+
+ addAsyncTest(function* testNativeAnonymous() {
+ info ("Testing native anonymous content with walker.");
+
+ let select = yield gWalker.querySelector(gWalker.rootNode, "select");
+ let children = yield gWalker.children(select);
+
+ is (select.numChildren, 2, "No native anon content for form control");
+ is (children.nodes.length, 2, "No native anon content for form control");
+
+ runNextTest();
+ });
+
+ addAsyncTest(function* testNativeAnonymousStartingNode() {
+ info ("Tests attaching an element that a walker can't see.");
+
+ let serverWalker = DebuggerServer._searchAllConnectionsForActor(gWalker.actorID);
+ let docwalker = new _documentWalker(
+ gInspectee.querySelector("select"),
+ gInspectee.defaultView,
+ nodeFilterConstants.SHOW_ALL,
+ () => {
+ return nodeFilterConstants.FILTER_ACCEPT
+ }
+ );
+ let scrollbar = docwalker.lastChild();
+ is (scrollbar.tagName, "scrollbar", "An anonymous child has been fetched");
+
+ let node = yield serverWalker.attachElement(scrollbar);
+
+ ok (node, "A response has arrived");
+ ok (node.node, "A node is in the response");
+ is (node.node.rawNode.tagName, "SELECT",
+ "The node has changed to a parent that the walker recognizes");
+
+ runNextTest();
+ });
+
+ addAsyncTest(function* testPseudoElements() {
+ info ("Testing pseudo elements with walker.");
+
+ // Markup looks like: <div><::before /><span /><::after /></div>
+ let pseudo = yield gWalker.querySelector(gWalker.rootNode, "#pseudo");
+ let children = yield gWalker.children(pseudo);
+
+ is (pseudo.numChildren, 1, "::before/::after are not counted if there is a child");
+ is (children.nodes.length, 3, "Correct number of children");
+
+ let before = children.nodes[0];
+ ok (before.isAnonymous, "Child is anonymous");
+ ok (!before._form.isXBLAnonymous, "Child is not XBL anonymous");
+ ok (!before._form.isShadowAnonymous, "Child is not shadow anonymous");
+ ok (before._form.isNativeAnonymous, "Child is native anonymous");
+
+ let span = children.nodes[1];
+ ok (!span.isAnonymous, "Child is not anonymous");
+
+ let after = children.nodes[2];
+ ok (after.isAnonymous, "Child is anonymous");
+ ok (!after._form.isXBLAnonymous, "Child is not XBL anonymous");
+ ok (!after._form.isShadowAnonymous, "Child is not shadow anonymous");
+ ok (after._form.isNativeAnonymous, "Child is native anonymous");
+
+ runNextTest();
+ });
+
+ addAsyncTest(function* testEmptyWithPseudo() {
+ info ("Testing elements with no childrent, except for pseudos.");
+
+ info ("Checking an element whose only child is a pseudo element");
+ let pseudo = yield gWalker.querySelector(gWalker.rootNode, "#pseudo-empty");
+ let children = yield gWalker.children(pseudo);
+
+ is (pseudo.numChildren, 1, "::before/::after are is counted if there are no other children");
+ is (children.nodes.length, 1, "Correct number of children");
+
+ let before = children.nodes[0];
+ ok (before.isAnonymous, "Child is anonymous");
+ ok (!before._form.isXBLAnonymous, "Child is not XBL anonymous");
+ ok (!before._form.isShadowAnonymous, "Child is not shadow anonymous");
+ ok (before._form.isNativeAnonymous, "Child is native anonymous");
+
+ runNextTest();
+ });
+
+ addAsyncTest(function* testShadowAnonymous() {
+ info ("Testing shadow DOM content.");
+
+ let shadow = yield gWalker.querySelector(gWalker.rootNode, "#shadow");
+ let children = yield gWalker.children(shadow);
+
+ is (shadow.numChildren, 3, "Children of the shadow root are counted");
+ is (children.nodes.length, 3, "Children returned from walker");
+
+ let before = children.nodes[0];
+ ok (before.isAnonymous, "Child is anonymous");
+ ok (!before._form.isXBLAnonymous, "Child is not XBL anonymous");
+ ok (!before._form.isShadowAnonymous, "Child is not shadow anonymous");
+ ok (before._form.isNativeAnonymous, "Child is native anonymous");
+
+ // <h3>Shadow <em>DOM</em></h3>
+ let shadowChild1 = children.nodes[1];
+ ok (shadowChild1.isAnonymous, "Child is anonymous");
+ ok (!shadowChild1._form.isXBLAnonymous, "Child is not XBL anonymous");
+ ok (shadowChild1._form.isShadowAnonymous, "Child is shadow anonymous");
+ ok (!shadowChild1._form.isNativeAnonymous, "Child is not native anonymous");
+
+ let shadowSubChildren = yield gWalker.children(children.nodes[1]);
+ is (shadowChild1.numChildren, 2, "Subchildren of the shadow root are counted");
+ is (shadowSubChildren.nodes.length, 2, "Subchildren are returned from walker");
+
+ // <em>DOM</em>
+ let shadowSubChild = children.nodes[1];
+ ok (shadowSubChild.isAnonymous, "Child is anonymous");
+ ok (!shadowSubChild._form.isXBLAnonymous, "Child is not XBL anonymous");
+ ok (shadowSubChild._form.isShadowAnonymous, "Child is shadow anonymous");
+ ok (!shadowSubChild._form.isNativeAnonymous, "Child is not native anonymous");
+
+ // <select multiple></select>
+ let shadowChild2 = children.nodes[2];
+ ok (shadowChild2.isAnonymous, "Child is anonymous");
+ ok (!shadowChild2._form.isXBLAnonymous, "Child is not XBL anonymous");
+ ok (shadowChild2._form.isShadowAnonymous, "Child is shadow anonymous");
+ ok (!shadowChild2._form.isNativeAnonymous, "Child is not native anonymous");
+
+ 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-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/mochitest/test_inspector-changeattrs.html b/devtools/server/tests/mochitest/test_inspector-changeattrs.html
new file mode 100644
index 000000000..23b7660d2
--- /dev/null
+++ b/devtools/server/tests/mochitest/test_inspector-changeattrs.html
@@ -0,0 +1,99 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug </title>
+
+ <script type="application/javascript" 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;version=1.8" src="inspector-helpers.js"></script>
+ <script type="application/javascript;version=1.8">
+const inspector = require("devtools/shared/fronts/inspector");
+
+window.onload = function() {
+ SimpleTest.waitForExplicitFinish();
+ runNextTest();
+}
+
+var gInspectee = null;
+var gClient = null;
+var gWalker = null;
+var checkActorIDs = [];
+
+function assertOwnership() {
+ assertOwnershipTrees(gWalker);
+}
+
+addTest(function setup() {
+ let url = document.getElementById("inspectorContent").href;
+ attachURL(url, function(err, client, tab, doc) {
+ gInspectee = doc;
+ let {InspectorFront} = require("devtools/shared/fronts/inspector");
+ let inspector = InspectorFront(client, tab);
+ promiseDone(inspector.getWalker().then(walker => {
+ ok(walker, "getWalker() should return an actor.");
+ gClient = client;
+ gWalker = walker;
+ }).then(runNextTest));
+ });
+});
+
+addTest(function testChangeAttrs() {
+ let 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.
+ let 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.
+ let 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(() => {
+ let 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() {
+ delete gWalker;
+ delete gInspectee;
+ delete gClient;
+ 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/mochitest/test_inspector-changevalue.html b/devtools/server/tests/mochitest/test_inspector-changevalue.html
new file mode 100644
index 000000000..a5b613157
--- /dev/null
+++ b/devtools/server/tests/mochitest/test_inspector-changevalue.html
@@ -0,0 +1,82 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug </title>
+
+ <script type="application/javascript" 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;version=1.8" src="inspector-helpers.js"></script>
+ <script type="application/javascript;version=1.8">
+const Ci = Components.interfaces;
+const inspector = require("devtools/shared/fronts/inspector");
+
+window.onload = function() {
+ SimpleTest.waitForExplicitFinish();
+ runNextTest();
+}
+
+var gInspectee = null;
+var gClient = null;
+var gWalker = null;
+
+function assertOwnership() {
+ assertOwnershipTrees(gWalker);
+}
+
+addTest(function setup() {
+ let url = document.getElementById("inspectorContent").href;
+ attachURL(url, function(err, client, tab, doc) {
+ gInspectee = doc;
+ let {InspectorFront} = require("devtools/shared/fronts/inspector");
+ let inspector = InspectorFront(client, tab);
+ promiseDone(inspector.getWalker().then(walker => {
+ ok(walker, "getWalker() should return an actor.");
+ gClient = client;
+ gWalker = walker;
+ }).then(runNextTest));
+ });
+});
+
+addTest(function testChangeValue() {
+ let 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, Ci.nsIDOMNode.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() {
+ delete gWalker;
+ delete gInspectee;
+ delete gClient;
+ 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/mochitest/test_inspector-dead-nodes.html b/devtools/server/tests/mochitest/test_inspector-dead-nodes.html
new file mode 100644
index 000000000..274636cd6
--- /dev/null
+++ b/devtools/server/tests/mochitest/test_inspector-dead-nodes.html
@@ -0,0 +1,386 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1121528
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1121528</title>
+
+ <script type="application/javascript" 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;version=1.8" src="inspector-helpers.js"></script>
+ <script type="application/javascript;version=1.8">
+const inspector = require("devtools/shared/fronts/inspector");
+
+window.onload = function() {
+ SimpleTest.waitForExplicitFinish();
+ runNextTest();
+}
+
+var gWalker, gDoc;
+
+addAsyncTest(function() {
+ let url = document.getElementById("inspectorContent").href;
+
+ let def = promise.defer();
+ attachURL(url, function(err, client, tab, doc) {
+ def.resolve({client, tab, doc});
+ });
+ let {client, tab, doc} = yield def.promise;
+ gDoc = doc;
+
+ let {InspectorFront} = require("devtools/shared/fronts/inspector");
+ let inspector = InspectorFront(client, tab);
+ gWalker = yield inspector.getWalker();
+
+ runNextTest();
+});
+
+addAsyncTest(function() {
+ info("Getting a nodeFront, reloading the page, and calling " +
+ "walker.parents(nodeFront) before the load completes shouldn't fail");
+
+ let nodeFront = yield gWalker.querySelector(gWalker.rootNode, "h1");
+ let newRoot = waitForMutation(gWalker, isNewRoot);
+ gDoc.defaultView.location.reload();
+ yield gWalker.parents(nodeFront);
+ yield newRoot;
+
+ ok(true, "The call to walker.parents() didn't fail");
+ runNextTest();
+});
+
+addAsyncTest(function() {
+ info("Getting a nodeFront, reloading the page, and calling " +
+ "walker.children(nodeFront) before the load completes shouldn't fail");
+
+ let nodeFront = yield gWalker.querySelector(gWalker.rootNode, "body");
+ let newRoot = waitForMutation(gWalker, isNewRoot);
+ gDoc.defaultView.location.reload();
+ yield gWalker.children(nodeFront);
+ yield newRoot;
+
+ ok(true, "The call to walker.children() didn't fail");
+ runNextTest();
+});
+
+addAsyncTest(function() {
+ info("Getting a nodeFront, reloading the page, and calling " +
+ "walker.siblings(nodeFront) before the load completes shouldn't fail");
+
+ let nodeFront = yield gWalker.querySelector(gWalker.rootNode, "h1");
+ let newRoot = waitForMutation(gWalker, isNewRoot);
+ gDoc.defaultView.location.reload();
+ yield gWalker.siblings(nodeFront);
+ yield newRoot;
+
+ ok(true, "The call to walker.siblings() didn't fail");
+ runNextTest();
+});
+
+addAsyncTest(function() {
+ info("Getting a nodeFront, reloading the page, and calling " +
+ "walker.nextSibling(nodeFront) before the load completes shouldn't fail");
+
+ let nodeFront = yield gWalker.querySelector(gWalker.rootNode, "h1");
+ let newRoot = waitForMutation(gWalker, isNewRoot);
+ gDoc.defaultView.location.reload();
+ yield gWalker.nextSibling(nodeFront);
+ yield newRoot;
+
+ ok(true, "The call to walker.nextSibling() didn't fail");
+ runNextTest();
+});
+
+addAsyncTest(function() {
+ info("Getting a nodeFront, reloading the page, and calling " +
+ "walker.previousSibling(nodeFront) before the load completes shouldn't fail");
+
+ let nodeFront = yield gWalker.querySelector(gWalker.rootNode, "h1");
+ let newRoot = waitForMutation(gWalker, isNewRoot);
+ gDoc.defaultView.location.reload();
+ yield gWalker.previousSibling(nodeFront);
+ yield newRoot;
+
+ ok(true, "The call to walker.previousSibling() didn't fail");
+ runNextTest();
+});
+
+addAsyncTest(function() {
+ info("Getting a nodeFront, reloading the page, and calling " +
+ "walker.addPseudoClassLock(nodeFront) before the load completes " +
+ "shouldn't fail");
+
+ let nodeFront = yield gWalker.querySelector(gWalker.rootNode, "h1");
+ let newRoot = waitForMutation(gWalker, isNewRoot);
+ gDoc.defaultView.location.reload();
+ yield gWalker.addPseudoClassLock(nodeFront, ":hover");
+ yield newRoot;
+
+ ok(true, "The call to walker.addPseudoClassLock() didn't fail");
+ runNextTest();
+});
+
+addAsyncTest(function() {
+ info("Getting a nodeFront, reloading the page, and calling " +
+ "walker.removePseudoClassLock(nodeFront) before the load completes " +
+ "shouldn't fail");
+
+ let nodeFront = yield gWalker.querySelector(gWalker.rootNode, "h1");
+ let newRoot = waitForMutation(gWalker, isNewRoot);
+ gDoc.defaultView.location.reload();
+ yield gWalker.removePseudoClassLock(nodeFront, ":hover");
+ yield newRoot;
+
+ ok(true, "The call to walker.removePseudoClassLock() didn't fail");
+ runNextTest();
+});
+
+addAsyncTest(function() {
+ info("Getting a nodeFront, reloading the page, and calling " +
+ "walker.clearPseudoClassLocks(nodeFront) before the load completes " +
+ "shouldn't fail");
+
+ let nodeFront = yield gWalker.querySelector(gWalker.rootNode, "h1");
+ let newRoot = waitForMutation(gWalker, isNewRoot);
+ gDoc.defaultView.location.reload();
+ yield gWalker.clearPseudoClassLocks(nodeFront);
+ yield newRoot;
+
+ ok(true, "The call to walker.clearPseudoClassLocks() didn't fail");
+ runNextTest();
+});
+
+addAsyncTest(function() {
+ info("Getting a nodeFront, reloading the page, and calling " +
+ "walker.innerHTML(nodeFront) before the load completes shouldn't fail");
+
+ let nodeFront = yield gWalker.querySelector(gWalker.rootNode, "h1");
+ let newRoot = waitForMutation(gWalker, isNewRoot);
+ gDoc.defaultView.location.reload();
+ yield gWalker.innerHTML(nodeFront);
+ yield newRoot;
+
+ ok(true, "The call to walker.innerHTML() didn't fail");
+ runNextTest();
+});
+
+addAsyncTest(function() {
+ info("Getting a nodeFront, reloading the page, and calling " +
+ "walker.setInnerHTML(nodeFront) before the load completes shouldn't fail");
+
+ let nodeFront = yield gWalker.querySelector(gWalker.rootNode, "h1");
+ let newRoot = waitForMutation(gWalker, isNewRoot);
+ gDoc.defaultView.location.reload();
+ yield gWalker.setInnerHTML(nodeFront, "<span>innerHTML changed</span>");
+ yield newRoot;
+
+ ok(true, "The call to walker.setInnerHTML() didn't fail");
+ runNextTest();
+});
+
+addAsyncTest(function() {
+ info("Getting a nodeFront, reloading the page, and calling " +
+ "walker.outerHTML(nodeFront) before the load completes shouldn't fail");
+
+ let nodeFront = yield gWalker.querySelector(gWalker.rootNode, "h1");
+ let newRoot = waitForMutation(gWalker, isNewRoot);
+ gDoc.defaultView.location.reload();
+ yield gWalker.outerHTML(nodeFront);
+ yield newRoot;
+
+ ok(true, "The call to walker.outerHTML() didn't fail");
+ runNextTest();
+});
+
+addAsyncTest(function() {
+ info("Getting a nodeFront, reloading the page, and calling " +
+ "walker.setOuterHTML(nodeFront) before the load completes shouldn't fail");
+
+ let nodeFront = yield gWalker.querySelector(gWalker.rootNode, "h1");
+ let newRoot = waitForMutation(gWalker, isNewRoot);
+ gDoc.defaultView.location.reload();
+ yield gWalker.setOuterHTML(nodeFront, "<h1><span>innerHTML changed</span></h1>");
+ yield newRoot;
+
+ ok(true, "The call to walker.setOuterHTML() didn't fail");
+ runNextTest();
+});
+
+addAsyncTest(function() {
+ info("Getting a nodeFront, reloading the page, and calling " +
+ "walker.insertAdjacentHTML(nodeFront) before the load completes shouldn't " +
+ "fail");
+
+ let nodeFront = yield gWalker.querySelector(gWalker.rootNode, "h1");
+ let newRoot = waitForMutation(gWalker, isNewRoot);
+ gDoc.defaultView.location.reload();
+ yield gWalker.insertAdjacentHTML(nodeFront, "afterEnd",
+ "<span>new adjacent HTML</span>");
+ yield newRoot;
+
+ ok(true, "The call to walker.insertAdjacentHTML() didn't fail");
+ runNextTest();
+});
+
+addAsyncTest(function() {
+ info("Getting a nodeFront, reloading the page, and calling " +
+ "walker.removeNode(nodeFront) before the load completes should throw");
+
+ let nodeFront = yield gWalker.querySelector(gWalker.rootNode, "h1");
+ let newRoot = waitForMutation(gWalker, isNewRoot);
+ gDoc.defaultView.location.reload();
+ let hasThrown = false;
+ try {
+ yield gWalker.removeNode(nodeFront);
+ } catch (e) {
+ hasThrown = true;
+ }
+ yield newRoot;
+
+ ok(hasThrown, "The call to walker.removeNode() threw");
+ runNextTest();
+});
+
+addAsyncTest(function() {
+ info("Getting a nodeFront, reloading the page, and calling " +
+ "walker.removeNodes([nodeFront]) before the load completes should throw");
+
+ let nodeFront1 = yield gWalker.querySelector(gWalker.rootNode, "h1");
+ let nodeFront2 = yield gWalker.querySelector(gWalker.rootNode, "#longstring");
+ let nodeFront3 = yield gWalker.querySelector(gWalker.rootNode, "#shortstring");
+ let newRoot = waitForMutation(gWalker, isNewRoot);
+ gDoc.defaultView.location.reload();
+ let hasThrown = false;
+ try {
+ yield gWalker.removeNodes([nodeFront1, nodeFront2, nodeFront3]);
+ } catch (e) {
+ hasThrown = true;
+ }
+ yield newRoot;
+
+ ok(hasThrown, "The call to walker.removeNodes() threw");
+ runNextTest();
+});
+
+addAsyncTest(function() {
+ info("Getting a nodeFront, reloading the page, and calling " +
+ "walker.insertBefore(nodeFront, parent, null) before the load completes " +
+ "shouldn't fail");
+
+ let nodeFront = yield gWalker.querySelector(gWalker.rootNode, "h1");
+ let newParentFront = yield gWalker.querySelector(gWalker.rootNode, "#longlist");
+ let newRoot = waitForMutation(gWalker, isNewRoot);
+ gDoc.defaultView.location.reload();
+ yield gWalker.insertBefore(nodeFront, newParentFront);
+ yield newRoot;
+
+ ok(true, "The call to walker.insertBefore() didn't fail");
+ runNextTest();
+});
+
+addAsyncTest(function() {
+ info("Getting a nodeFront, reloading the page, and calling " +
+ "walker.insertBefore(nodeFront, parent, sibling) before the load completes " +
+ "shouldn't fail");
+
+ let nodeFront = yield gWalker.querySelector(gWalker.rootNode, "h1");
+ let newParentFront = yield gWalker.querySelector(gWalker.rootNode, "#longlist");
+ let siblingFront = yield gWalker.querySelector(gWalker.rootNode, "#b");
+ let newRoot = waitForMutation(gWalker, isNewRoot);
+ gDoc.defaultView.location.reload();
+ yield gWalker.insertBefore(nodeFront, newParentFront, siblingFront);
+ yield newRoot;
+
+ ok(true, "The call to walker.insertBefore() didn't fail");
+ runNextTest();
+});
+
+addAsyncTest(function() {
+ info("Getting a nodeFront, reloading the page, and calling " +
+ "walker.editTagName(nodeFront) before the load completes shouldn't fail");
+
+ let nodeFront = yield gWalker.querySelector(gWalker.rootNode, "h1");
+ let newRoot = waitForMutation(gWalker, isNewRoot);
+ gDoc.defaultView.location.reload();
+ yield gWalker.editTagName(nodeFront, "h2");
+ yield newRoot;
+
+ ok(true, "The call to walker.editTagName() didn't fail");
+ runNextTest();
+});
+
+addAsyncTest(function() {
+ info("Getting a nodeFront, reloading the page, and calling " +
+ "walker.hideNode(nodeFront) before the load completes shouldn't fail");
+
+ let nodeFront = yield gWalker.querySelector(gWalker.rootNode, "h1");
+ let newRoot = waitForMutation(gWalker, isNewRoot);
+ gDoc.defaultView.location.reload();
+ yield gWalker.hideNode(nodeFront);
+ yield newRoot;
+
+ ok(true, "The call to walker.hideNode() didn't fail");
+ runNextTest();
+});
+
+addAsyncTest(function() {
+ info("Getting a nodeFront, reloading the page, and calling " +
+ "walker.unhideNode(nodeFront) before the load completes shouldn't fail");
+
+ let nodeFront = yield gWalker.querySelector(gWalker.rootNode, "h1");
+ let newRoot = waitForMutation(gWalker, isNewRoot);
+ gDoc.defaultView.location.reload();
+ yield gWalker.unhideNode(nodeFront);
+ yield newRoot;
+
+ ok(true, "The call to walker.unhideNode() didn't fail");
+ runNextTest();
+});
+
+addAsyncTest(function() {
+ info("Getting a nodeFront, reloading the page, and calling " +
+ "walker.releaseNode(nodeFront) before the load completes shouldn't fail");
+
+ let nodeFront = yield gWalker.querySelector(gWalker.rootNode, "h1");
+ let newRoot = waitForMutation(gWalker, isNewRoot);
+ gDoc.defaultView.location.reload();
+ yield gWalker.releaseNode(nodeFront);
+ yield newRoot;
+
+ ok(true, "The call to walker.releaseNode() didn't fail");
+ runNextTest();
+});
+
+addAsyncTest(function() {
+ info("Getting a nodeFront, reloading the page, and calling " +
+ "walker.querySelector(nodeFront) before the load completes shouldn't fail");
+
+ let nodeFront = yield gWalker.querySelector(gWalker.rootNode, "body");
+ let newRoot = waitForMutation(gWalker, isNewRoot);
+ gDoc.defaultView.location.reload();
+ yield gWalker.querySelector(nodeFront, "h1");
+ yield newRoot;
+
+ ok(true, "The call to walker.querySelector() didn't fail");
+ runNextTest();
+});
+
+addTest(function cleanup() {
+ gWalker = gDoc = null;
+ runNextTest();
+});
+ </script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=932937">Mozilla Bug 1121528</a>
+<a id="inspectorContent" target="_blank" href="inspector-traversal-data.html">Test Document</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+</body>
+</html>
diff --git a/devtools/server/tests/mochitest/test_inspector-duplicate-node.html b/devtools/server/tests/mochitest/test_inspector-duplicate-node.html
new file mode 100644
index 000000000..35722f226
--- /dev/null
+++ b/devtools/server/tests/mochitest/test_inspector-duplicate-node.html
@@ -0,0 +1,75 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1208864
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1208864</title>
+
+ <script type="application/javascript" 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;version=1.8" src="inspector-helpers.js"></script>
+ <script type="application/javascript;version=1.8">
+const inspector = require("devtools/shared/fronts/inspector");
+
+window.onload = function() {
+ SimpleTest.waitForExplicitFinish();
+ runNextTest();
+}
+
+var gInspectee = null;
+var gClient = null;
+var gWalker = null;
+
+function assertOwnership() {
+ assertOwnershipTrees(gWalker);
+}
+
+addTest(function setup() {
+ let url = document.getElementById("inspectorContent").href;
+ attachURL(url, function(err, client, tab, doc) {
+ gInspectee = doc;
+ let {InspectorFront} = require("devtools/shared/fronts/inspector");
+ let inspector = InspectorFront(client, tab);
+ promiseDone(inspector.getWalker().then(walker => {
+ ok(walker, "getWalker() should return an actor.");
+ gClient = client;
+ gWalker = walker;
+ }).then(runNextTest));
+ });
+});
+
+addTest(Task.async(function* testDuplicateNode() {
+ let className = ".node-to-duplicate";
+ let matches = yield gWalker.querySelectorAll(gWalker.rootNode, className);
+ is(matches.length, 1, "There should initially be one node to duplicate.");
+
+ let nodeFront = yield gWalker.querySelector(gWalker.rootNode, className);
+ yield gWalker.duplicateNode(nodeFront);
+
+ matches = yield gWalker.querySelectorAll(gWalker.rootNode, className);
+ is(matches.length, 2, "The node should now be duplicated.");
+
+ runNextTest();
+}));
+
+addTest(function cleanup() {
+ delete gWalker;
+ delete gInspectee;
+ delete gClient;
+ 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/mochitest/test_inspector-hide.html b/devtools/server/tests/mochitest/test_inspector-hide.html
new file mode 100644
index 000000000..d9b134c22
--- /dev/null
+++ b/devtools/server/tests/mochitest/test_inspector-hide.html
@@ -0,0 +1,76 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug </title>
+
+ <script type="application/javascript" 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;version=1.8" src="inspector-helpers.js"></script>
+ <script type="application/javascript;version=1.8">
+const inspector = require("devtools/shared/fronts/inspector");
+
+window.onload = function() {
+ SimpleTest.waitForExplicitFinish();
+ runNextTest();
+}
+
+var gWalker = null;
+var gClient = null;
+
+addTest(function setup() {
+ let url = document.getElementById("inspectorContent").href;
+ attachURL(url, function(err, client, tab, doc) {
+ gInspectee = doc;
+ let {InspectorFront} = require("devtools/shared/fronts/inspector");
+ let inspector = InspectorFront(client, tab);
+ promiseDone(inspector.getWalker().then(walker => {
+ ok(walker, "getWalker() should return an actor.");
+ gClient = client;
+ gWalker = walker;
+ }).then(runNextTest));
+ });
+});
+
+addTest(function testRearrange() {
+ let listFront = null;
+ let listNode = gInspectee.querySelector("#longlist");
+
+ promiseDone(gWalker.querySelector(gWalker.rootNode, "#longlist").then(front => {
+ listFront = front;
+ }).then(() => {
+ let computed = gInspectee.defaultView.getComputedStyle(listNode);
+ ok(computed.visibility, "visible", "Node should be visible to start with");
+ return gWalker.hideNode(listFront);
+ }).then(response => {
+ let computed = gInspectee.defaultView.getComputedStyle(listNode);
+ ok(computed.visibility, "hidden", "Node should be hidden");
+ return gWalker.unhideNode(listFront);
+ }).then(() => {
+ let computed = gInspectee.defaultView.getComputedStyle(listNode);
+ ok(computed.visibility, "visible", "Node should be visible again.");
+ }).then(runNextTest));
+});
+
+addTest(function cleanup() {
+ delete gWalker;
+ delete gClient;
+ 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/mochitest/test_inspector-insert.html b/devtools/server/tests/mochitest/test_inspector-insert.html
new file mode 100644
index 000000000..82b4fef3e
--- /dev/null
+++ b/devtools/server/tests/mochitest/test_inspector-insert.html
@@ -0,0 +1,119 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug </title>
+
+ <script type="application/javascript" 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;version=1.8" src="inspector-helpers.js"></script>
+ <script type="application/javascript;version=1.8">
+const inspector = require("devtools/server/actors/inspector");
+
+window.onload = function() {
+ SimpleTest.waitForExplicitFinish();
+ runNextTest();
+}
+
+var gWalker = null;
+var gClient = null;
+
+function assertOwnership() {
+ return assertOwnershipTrees(gWalker);
+}
+
+addTest(function setup() {
+ let url = document.getElementById("inspectorContent").href;
+ attachURL(url, function(err, client, tab, doc) {
+ gInspectee = doc;
+ let {InspectorFront} = require("devtools/shared/fronts/inspector");
+ let inspector = InspectorFront(client, tab);
+ promiseDone(inspector.getWalker().then(walker => {
+ ok(walker, "getWalker() should return an actor.");
+ gClient = client;
+ gWalker = walker;
+ }).then(runNextTest));
+ });
+});
+
+addAsyncTest(function* testRearrange() {
+ let longlist = yield gWalker.querySelector(gWalker.rootNode, "#longlist");
+ let children = yield gWalker.children(longlist);
+ let nodeA = children.nodes[0];
+ is(nodeA.id, "a", "Got the expected node.");
+
+ // Move nodeA to the end of the list.
+ yield gWalker.insertBefore(nodeA, longlist, null);
+ ok(!gInspectee.querySelector("#a").nextSibling, "a should now be at the end of the list.");
+ children = yield gWalker.children(longlist);
+ is(nodeA, children.nodes[children.nodes.length - 1], "a should now be the last returned child.");
+
+ // Now move it to the middle of the list.
+ let nextNode = children.nodes[13];
+ yield gWalker.insertBefore(nodeA, longlist, nextNode);
+ let sibling =
+ new inspector._documentWalker(gInspectee.querySelector("#a"), window).nextSibling();
+ is(sibling, nextNode.rawNode(), "Node should match the expected next node.");
+ children = yield gWalker.children(longlist);
+ is(nodeA, children.nodes[13], "a should be where we expect it.");
+ is(nextNode, children.nodes[14], "next node should be where we expect it.");
+
+ runNextTest();
+});
+
+addAsyncTest(function* testInsertInvalidInput() {
+ let longlist = yield gWalker.querySelector(gWalker.rootNode, "#longlist");
+ let children = yield gWalker.children(longlist);
+ let nodeA = children.nodes[0];
+ let nextSibling = children.nodes[1];
+
+ // Now move it to the original location and make sure no mutation happens.
+ let hasMutated = false;
+ let observer = new gInspectee.defaultView.MutationObserver(() => {
+ hasMutated = true;
+ });
+ observer.observe(longlist.rawNode(), {
+ childList: true,
+ });
+
+ yield gWalker.insertBefore(nodeA, longlist, nodeA);
+ ok(!hasMutated, "hasn't mutated");
+ hasMutated = false;
+
+ yield gWalker.insertBefore(nodeA, longlist, nextSibling);
+ ok(!hasMutated, "still hasn't mutated after inserting before nextSibling");
+ hasMutated = false;
+
+ yield gWalker.insertBefore(nodeA, longlist);
+ ok(hasMutated, "has mutated after inserting with null sibling");
+ hasMutated = false;
+
+ yield gWalker.insertBefore(nodeA, longlist);
+ ok(!hasMutated, "hasn't mutated after inserting with null sibling again");
+
+ observer.disconnect();
+ runNextTest();
+});
+
+addTest(function cleanup() {
+ delete gWalker;
+ delete gClient;
+ 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/mochitest/test_inspector-mutations-attr.html b/devtools/server/tests/mochitest/test_inspector-mutations-attr.html
new file mode 100644
index 000000000..15c14608b
--- /dev/null
+++ b/devtools/server/tests/mochitest/test_inspector-mutations-attr.html
@@ -0,0 +1,167 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug </title>
+
+ <script type="application/javascript" 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;version=1.8" src="inspector-helpers.js"></script>
+ <script type="application/javascript;version=1.8">
+const inspector = require("devtools/shared/fronts/inspector");
+
+window.onload = function() {
+ SimpleTest.waitForExplicitFinish();
+ runNextTest();
+}
+
+var gInspectee = null;
+var gWalker = null;
+var gClient = null;
+var attrNode;
+var attrFront;
+
+addTest(function setup() {
+ let url = document.getElementById("inspectorContent").href;
+ attachURL(url, function(err, client, tab, doc) {
+ gInspectee = doc;
+ let {InspectorFront} = require("devtools/shared/fronts/inspector");
+ let inspector = InspectorFront(client, tab);
+ promiseDone(inspector.getWalker().then(walker => {
+ ok(walker, "getWalker() should return an actor.");
+ gClient = client;
+ gWalker = walker;
+ }).then(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() {
+ let frame = gInspectee.querySelector('#childFrame');
+ attrNode = frame.contentDocument.querySelector("#a");
+
+ promiseDone(gWalker.querySelector(gWalker.rootNode, "#childFrame").then(childFrame => {
+ return gWalker.children(childFrame);
+ }).then(children => {
+ let nodes = children.nodes;
+ ok(nodes.length, 1, "There should be only one child of the iframe");
+ is(nodes[0].nodeType, Node.DOCUMENT_NODE, "iframe child should be a document node");
+ return gWalker.querySelector(nodes[0], "#a");
+ }).then(node => {
+ attrFront = node;
+ }).then(runNextTest));
+}
+
+function testAddAttribute() {
+ attrNode.setAttribute("data-newattr", "newvalue");
+ attrNode.setAttribute("data-newattr2", "newvalue");
+ gWalker.once("mutations", () => {
+ is(attrFront.attributes.length, 3, "Should have id and two new attributes.");
+ is(attrFront.getAttribute("data-newattr"), "newvalue", "Node front should have the first new attribute");
+ is(attrFront.getAttribute("data-newattr2"), "newvalue", "Node front should have the second new attribute.");
+ runNextTest();
+ });
+}
+
+function testChangeAttribute() {
+ attrNode.setAttribute("data-newattr", "changedvalue1");
+ attrNode.setAttribute("data-newattr", "changedvalue2");
+ attrNode.setAttribute("data-newattr", "changedvalue3");
+ gWalker.once("mutations", mutations => {
+ is(mutations.length, 1, "Only one mutation is sent for multiple queued attribute changes");
+ is(attrFront.attributes.length, 3, "Should have id and two new attributes.");
+ is(attrFront.getAttribute("data-newattr"), "changedvalue3", "Node front should have the changed first value");
+ is(attrFront.getAttribute("data-newattr2"), "newvalue", "Second value should remain unchanged.");
+ runNextTest();
+ });
+}
+
+function testRemoveAttribute() {
+ attrNode.removeAttribute("data-newattr2");
+ gWalker.once("mutations", () => {
+ is(attrFront.attributes.length, 2, "Should have id and one remaining attribute.");
+ is(attrFront.getAttribute("data-newattr"), "changedvalue3", "Node front should still have the first value");
+ ok(!attrFront.hasAttribute("data-newattr2"), "Second value should be removed.");
+ runNextTest();
+ })
+}
+
+function testQueuedMutations() {
+ // All modifications to each attribute should be queued in one mutation event.
+
+ attrNode.removeAttribute("data-newattr");
+ attrNode.setAttribute("data-newattr", "1");
+ attrNode.removeAttribute("data-newattr");
+ attrNode.setAttribute("data-newattr", "2");
+ attrNode.removeAttribute("data-newattr");
+
+ for (var i = 0; i <= 1000; i++) {
+ attrNode.setAttribute("data-newattr2", i);
+ }
+
+ attrNode.removeAttribute("data-newattr3");
+ attrNode.setAttribute("data-newattr3", "1");
+ attrNode.removeAttribute("data-newattr3");
+ attrNode.setAttribute("data-newattr3", "2");
+ attrNode.removeAttribute("data-newattr3");
+ attrNode.setAttribute("data-newattr3", "3");
+
+ // This shouldn't be added in the attribute set, since it's a new
+ // attribute that's been added and removed.
+ attrNode.setAttribute("data-newattr4", "4");
+ attrNode.removeAttribute("data-newattr4");
+
+ gWalker.once("mutations", mutations => {
+ is(mutations.length, 4, "Only one mutation each is sent for multiple queued attribute changes");
+ is(attrFront.attributes.length, 3, "Should have id, data-newattr2, and data-newattr3.");
+
+ is(attrFront.getAttribute("data-newattr2"), "1000", "Node front should still have the correct value");
+ is(attrFront.getAttribute("data-newattr3"), "3", "Node front should still have the correct value");
+ ok(!attrFront.hasAttribute("data-newattr"), "Attribute value should be removed.");
+ ok(!attrFront.hasAttribute("data-newattr4"), "Attribute value should be removed.");
+
+ runNextTest();
+ })
+}
+
+addTest(function cleanup() {
+ delete gInspectee;
+ delete gWalker;
+ delete gClient;
+ 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/mochitest/test_inspector-mutations-childlist.html b/devtools/server/tests/mochitest/test_inspector-mutations-childlist.html
new file mode 100644
index 000000000..d845b987e
--- /dev/null
+++ b/devtools/server/tests/mochitest/test_inspector-mutations-childlist.html
@@ -0,0 +1,310 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug </title>
+
+ <script type="application/javascript" 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;version=1.8" src="inspector-helpers.js"></script>
+ <script type="application/javascript;version=1.8">
+const inspector = require("devtools/shared/fronts/inspector");
+
+window.onload = function() {
+ SimpleTest.waitForExplicitFinish();
+ runNextTest();
+}
+
+var gInspectee = null;
+var gWalker = null;
+var gClient = null;
+var gCleanupConnection = null;
+
+function setup(callback) {
+ let url = document.getElementById("inspectorContent").href;
+ gCleanupConnection = attachURL(url, function(err, client, tab, doc) {
+ gInspectee = doc;
+ let {InspectorFront} = require("devtools/shared/fronts/inspector");
+ let inspector = InspectorFront(client, tab);
+ promiseDone(inspector.getWalker().then(walker => {
+ gClient = client;
+ gWalker = walker;
+ }).then(callback));
+ });
+}
+
+function teardown() {
+ gWalker = null;
+ gClient = null;
+ gInspectee = null;
+ if (gCleanupConnection) {
+ gCleanupConnection();
+ gCleanupConnection = null;
+ }
+}
+
+function assertOwnership() {
+ let num = assertOwnershipTrees(gWalker);
+}
+
+function setParent(nodeSelector, newParentSelector) {
+ let node = gInspectee.querySelector(nodeSelector);
+ if (newParentSelector) {
+ let newParent = gInspectee.querySelector(newParentSelector);
+ newParent.appendChild(node);
+ } else {
+ node.parentNode.removeChild(node);
+ }
+}
+
+function loadSelector(selector) {
+ return gWalker.querySelectorAll(gWalker.rootNode, selector).then(nodeList => {
+ return nodeList.items();
+ });
+}
+
+function loadSelectors(selectors) {
+ return promise.all(Array.from(selectors, (sel) => loadSelector(sel)));
+}
+
+function doMoves(moves) {
+ for (let move of moves) {
+ setParent(move[0], move[1]);
+ }
+}
+
+/**
+ * Test a set of tree rearrangements and make sure they cause the expected changes.
+ */
+
+var gDummySerial = 0;
+
+function mutationTest(testSpec) {
+ return function() {
+ setup(() => {
+ promiseDone(loadSelectors(testSpec.load || ["html"]).then(() => {
+ gWalker.autoCleanup = !!testSpec.autoCleanup;
+ if (testSpec.preCheck) {
+ testSpec.preCheck();
+ }
+ doMoves(testSpec.moves || []);
+
+ // Some of these moves will trigger no mutation events,
+ // so do a dummy change to the root node to trigger
+ // a mutation event anyway.
+ gInspectee.documentElement.setAttribute("data-dummy", gDummySerial++);
+
+ gWalker.once("mutations", (mutations) => {
+ // Filter out our dummy mutation.
+ mutations = mutations.filter(change => {
+ if (change.type == "attributes" &&
+ change.attributeName == "data-dummy") {
+ return false;
+ }
+ return true;
+ });
+ assertOwnership();
+ if (testSpec.postCheck) {
+ testSpec.postCheck(mutations);
+ }
+ teardown();
+ runNextTest();
+ });
+ }));
+ })
+ }
+}
+
+// Verify that our dummy mutation works.
+addTest(mutationTest({
+ autoCleanup: false,
+ postCheck: function(mutations) {
+ is(mutations.length, 0, "Dummy mutation is filtered out.");
+ }
+}));
+
+// Test a simple move to a different location in the sibling list for the same
+// parent.
+addTest(mutationTest({
+ autoCleanup: false,
+ load: ["#longlist div"],
+ moves: [
+ ["#a", "#longlist"]
+ ],
+ postCheck: function(mutations) {
+ let remove = mutations[0];
+ is(remove.type, "childList", "First mutation should be a childList.")
+ ok(remove.removed.length > 0, "First mutation should be a removal.")
+ let add = mutations[1];
+ is(add.type, "childList", "Second mutation should be a childList removal.")
+ ok(add.added.length > 0, "Second mutation should be an addition.")
+ let a = add.added[0];
+ is(a.id, "a", "Added node should be #a");
+ is(a.parentNode(), remove.target, "Should still be a child of longlist.");
+ is(remove.target, add.target, "First and second mutations should be against the same node.");
+ }
+}));
+
+// Test a move to another location that is within our ownership tree.
+addTest(mutationTest({
+ autoCleanup: false,
+ load: ["#longlist div", "#longlist-sibling"],
+ moves: [
+ ["#a", "#longlist-sibling"]
+ ],
+ postCheck: function(mutations) {
+ let remove = mutations[0];
+ is(remove.type, "childList", "First mutation should be a childList.")
+ ok(remove.removed.length > 0, "First mutation should be a removal.")
+ let add = mutations[1];
+ is(add.type, "childList", "Second mutation should be a childList removal.")
+ ok(add.added.length > 0, "Second mutation should be an addition.")
+ let a = add.added[0];
+ is(a.id, "a", "Added node should be #a");
+ is(a.parentNode(), add.target, "Should still be a child of longlist.");
+ is(add.target.id, "longlist-sibling", "long-sibling should be the target.");
+ }
+}));
+
+// Move an unseen node with a seen parent into our ownership tree - should generate a
+// childList pair with no adds or removes.
+addTest(mutationTest({
+ autoCleanup: false,
+ load: ["#longlist"],
+ moves: [
+ ["#longlist-sibling", "#longlist"]
+ ],
+ postCheck: function(mutations) {
+ is(mutations.length, 2, "Should generate two mutations");
+ is(mutations[0].type, "childList", "Should be childList mutations.");
+ is(mutations[0].added.length, 0, "Should have no adds.");
+ is(mutations[0].removed.length, 0, "Should have no removes.");
+ is(mutations[1].type, "childList", "Should be childList mutations.");
+ is(mutations[1].added.length, 0, "Should have no adds.");
+ is(mutations[1].removed.length, 0, "Should have no removes.");
+ }
+}));
+
+// Move an unseen node with an unseen parent into our ownership tree. Should only
+// generate one childList mutation with no adds or removes.
+addTest(mutationTest({
+ autoCleanup: false,
+ load: ["#longlist div"],
+ moves: [
+ ["#longlist-sibling-firstchild", "#longlist"]
+ ],
+ postCheck: function(mutations) {
+ is(mutations.length, 1, "Should generate two mutations");
+ is(mutations[0].type, "childList", "Should be childList mutations.");
+ is(mutations[0].added.length, 0, "Should have no adds.");
+ is(mutations[0].removed.length, 0, "Should have no removes.");
+ }
+}));
+
+// Move a node between unseen nodes, should generate no mutations.
+addTest(mutationTest({
+ autoCleanup: false,
+ load: ["html"],
+ moves: [
+ ["#longlist-sibling", "#longlist"]
+ ],
+ postCheck: function(mutations) {
+ is(mutations.length, 0, "Should generate no mutations.");
+ }
+}));
+
+// Orphan a node and don't clean it up
+addTest(mutationTest({
+ autoCleanup: false,
+ load: ["#longlist div"],
+ moves: [
+ ["#longlist", null]
+ ],
+ postCheck: function(mutations) {
+ is(mutations.length, 1, "Should generate one mutation.");
+ let change = mutations[0];
+ is(change.type, "childList", "Should be a childList.");
+ is(change.removed.length, 1, "Should have removed a child.");
+ let ownership = clientOwnershipTree(gWalker);
+ is(ownership.orphaned.length, 1, "Should have one orphaned subtree.");
+ is(ownershipTreeSize(ownership.orphaned[0]), 1 + 26 + 26, "Should have orphaned longlist, and 26 children, and 26 singleTextChilds");
+ }
+}));
+
+// Orphan a node, and do clean it up.
+addTest(mutationTest({
+ autoCleanup: true,
+ load: ["#longlist div"],
+ moves: [
+ ["#longlist", null]
+ ],
+ postCheck: function(mutations) {
+ is(mutations.length, 1, "Should generate one mutation.");
+ let change = mutations[0];
+ is(change.type, "childList", "Should be a childList.");
+ is(change.removed.length, 1, "Should have removed a child.");
+ let ownership = clientOwnershipTree(gWalker);
+ is(ownership.orphaned.length, 0, "Should have no orphaned subtrees.");
+ }
+}));
+
+// Orphan a node by moving it into the tree but out of our visible subtree.
+addTest(mutationTest({
+ autoCleanup: false,
+ load: ["#longlist div"],
+ moves: [
+ ["#longlist", "#longlist-sibling"]
+ ],
+ postCheck: function(mutations) {
+ is(mutations.length, 1, "Should generate one mutation.");
+ let change = mutations[0];
+ is(change.type, "childList", "Should be a childList.");
+ is(change.removed.length, 1, "Should have removed a child.");
+ let ownership = clientOwnershipTree(gWalker);
+ is(ownership.orphaned.length, 1, "Should have one orphaned subtree.");
+ is(ownershipTreeSize(ownership.orphaned[0]), 1 + 26 + 26, "Should have orphaned longlist, 26 children, and 26 singleTextChilds.");
+ }
+}));
+
+// Orphan a node by moving it into the tree but out of our visible subtree, and clean it up.
+addTest(mutationTest({
+ autoCleanup: true,
+ load: ["#longlist div"],
+ moves: [
+ ["#longlist", "#longlist-sibling"]
+ ],
+ postCheck: function(mutations) {
+ is(mutations.length, 1, "Should generate one mutation.");
+ let change = mutations[0];
+ is(change.type, "childList", "Should be a childList.");
+ is(change.removed.length, 1, "Should have removed a child.");
+ let ownership = clientOwnershipTree(gWalker);
+ is(ownership.orphaned.length, 0, "Should have no orphaned subtrees.");
+ }
+}));
+
+
+addTest(function cleanup() {
+ delete gInspectee;
+ delete gWalker;
+ delete gClient;
+ 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/mochitest/test_inspector-mutations-events.html b/devtools/server/tests/mochitest/test_inspector-mutations-events.html
new file mode 100644
index 000000000..992bc7f8d
--- /dev/null
+++ b/devtools/server/tests/mochitest/test_inspector-mutations-events.html
@@ -0,0 +1,183 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1157469
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1157469</title>
+ <script type="application/javascript" 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;version=1.8" src="inspector-helpers.js"></script>
+ <script type="application/javascript;version=1.8">
+
+window.onload = function() {
+
+ const Cu = Components.utils;
+ const {require} = Cu.import("resource://devtools/shared/Loader.jsm", {});
+ const {InspectorFront} = require("devtools/shared/fronts/inspector");
+
+ SimpleTest.waitForExplicitFinish();
+
+ let inspectee = null;
+ let inspector = null;
+ let walker = null;
+ let eventListener1 = function () {};
+ let eventListener2 = function () {};
+ let eventNode1;
+ let eventNode2;
+ let eventFront1;
+ let eventFront2;
+
+ addAsyncTest(function* setup() {
+ info ("Setting up inspector and walker actors.");
+ let url = document.getElementById("inspectorContent").href;
+
+ yield new Promise(resolve => {
+ attachURL(url, function(err, client, tab, doc) {
+ inspectee = doc;
+ inspector = InspectorFront(client, tab);
+ resolve();
+ });
+ });
+
+ walker = yield inspector.getWalker();
+ ok(walker, "getWalker() should return an actor.");
+
+ runNextTest();
+ });
+
+ addAsyncTest(function* setupEventTest() {
+ eventNode1 = inspectee.querySelector("#a")
+ eventNode2 = inspectee.querySelector("#b")
+
+ eventFront1 = yield walker.querySelector(walker.rootNode, "#a");
+ eventFront2 = yield walker.querySelector(walker.rootNode, "#b");
+
+ runNextTest();
+ });
+
+ addAsyncTest(function* testChangeEventListenerOnSingleNode() {
+ checkNodesHaveNoEventListener();
+
+ info("add event listener on a single node");
+ eventNode1.addEventListener("click", eventListener1);
+
+ let mutations = yield 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 = yield 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 = yield 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(function* testChangeEventsOnSeveralNodes() {
+ checkNodesHaveNoEventListener();
+
+ info("add event listeners on both nodes");
+ eventNode1.addEventListener("click", eventListener1);
+ eventNode2.addEventListener("click", eventListener2);
+
+ let mutations = yield 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 = yield 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(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");
+
+ let mutations = yield waitForMutations();
+ is(mutations.length, 1, "expect only one mutation");
+ isnot(mutations.type, "events", "mutation type should not be events");
+
+ 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/mochitest/test_inspector-mutations-frameload.html b/devtools/server/tests/mochitest/test_inspector-mutations-frameload.html
new file mode 100644
index 000000000..54966cea7
--- /dev/null
+++ b/devtools/server/tests/mochitest/test_inspector-mutations-frameload.html
@@ -0,0 +1,214 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug </title>
+
+ <script type="application/javascript" 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;version=1.8" src="inspector-helpers.js"></script>
+ <script type="application/javascript;version=1.8">
+const inspector = require("devtools/shared/fronts/inspector");
+
+window.onload = function() {
+ SimpleTest.waitForExplicitFinish();
+ runNextTest();
+}
+
+var gInspectee = null;
+var gWalker = null;
+var gClient = null;
+var gChildFrame = null;
+var gChildDocument = null;
+var gCleanupConnection = null;
+
+function setup(callback) {
+ let url = document.getElementById("inspectorContent").href;
+ gCleanupConnection = attachURL(url, function(err, client, tab, doc) {
+ gInspectee = doc;
+ let {InspectorFront} = require("devtools/shared/fronts/inspector");
+ let inspector = InspectorFront(client, tab);
+ promiseDone(inspector.getWalker().then(walker => {
+ gClient = client;
+ gWalker = walker;
+ }).then(callback));
+ });
+}
+
+function teardown() {
+ gWalker = null;
+ gClient = null;
+ gInspectee = null;
+ gChildFrame = null;
+ if (gCleanupConnection) {
+ gCleanupConnection();
+ gCleanupConnection = null;
+ }
+}
+
+function assertOwnership() {
+ return assertOwnershipTrees(gWalker);
+}
+
+function loadChildSelector(selector) {
+ return gWalker.querySelector(gWalker.rootNode, "#childFrame").then(frame => {
+ ok(frame.numChildren > 0, "Child frame should consider its loaded document as a child.");
+ gChildFrame = frame;
+ return gWalker.children(frame);
+ }).then(children => {
+ return gWalker.querySelectorAll(children.nodes[0], selector);
+ }).then(nodeList => {
+ return nodeList.items();
+ });
+}
+
+function getUnloadedDoc(mutations) {
+ for (let change of mutations) {
+ if (isUnload(change)) {
+ return change.target;
+ }
+ }
+ return null;
+}
+
+addTest(function loadNewChild() {
+ setup(() => {
+ let beforeUnloadSize = 0;
+ // Load a bunch of fronts for actors inside the child frame.
+ promiseDone(loadChildSelector("#longlist div").then(() => {
+ let childFrame = gInspectee.querySelector("#childFrame");
+ childFrame.src = "data:text/html,<html>new child</html>";
+ return waitForMutation(gWalker, isChildList);
+ }).then(mutations => {
+ let unloaded = getUnloadedDoc(mutations);
+ mutations = assertSrcChange(mutations);
+ mutations = assertUnload(mutations);
+ mutations = assertFrameLoad(mutations);
+ mutations = assertChildList(mutations);
+
+ is(mutations.length, 0, "Got the expected mutations.");
+
+ assertOwnership();
+
+ return checkMissing(gClient, unloaded);
+ }).then(() => {
+ teardown();
+ }).then(runNextTest));
+ });
+});
+
+addTest(function loadNewChildTwice() {
+ setup(() => {
+ let beforeUnloadSize = 0;
+ // Load a bunch of fronts for actors inside the child frame.
+ promiseDone(loadChildSelector("#longlist div").then(() => {
+ let childFrame = gInspectee.querySelector("#childFrame");
+ childFrame.src = "data:text/html,<html>new child</html>";
+ return waitForMutation(gWalker, isChildList);
+ }).then(mutations => {
+ // The first load went through as expected (as tested in loadNewChild)
+ // Now change the source again, but this time we *don't* expect
+ // an unload, because we haven't seen the new child document yet.
+ let childFrame = gInspectee.querySelector("#childFrame");
+ childFrame.src = "data:text/html,<html>second new child</html>";
+ return waitForMutation(gWalker, isChildList);
+ }).then(mutations => {
+ mutations = assertSrcChange(mutations);
+ mutations = assertFrameLoad(mutations);
+ mutations = assertChildList(mutations);
+ ok(!getUnloadedDoc(mutations), "Should not have gotten an unload.");
+
+ is(mutations.length, 0, "Got the expected mutations.");
+
+ assertOwnership();
+ }).then(() => {
+ teardown();
+ }).then(runNextTest));
+ });
+});
+
+
+addTest(function loadNewChildTwiceAndCareAboutIt() {
+ setup(() => {
+ let beforeUnloadSize = 0;
+ // Load a bunch of fronts for actors inside the child frame.
+ promiseDone(loadChildSelector("#longlist div").then(() => {
+ let childFrame = gInspectee.querySelector("#childFrame");
+ childFrame.src = "data:text/html,<html>new child</html>";
+ return waitForMutation(gWalker, isChildList);
+ }).then(mutations => {
+ // Read the new child
+ return loadChildSelector("#longlist div");
+ }).then(() => {
+ // Now change the source again, and expect the same results as loadNewChild.
+ let childFrame = gInspectee.querySelector("#childFrame");
+ childFrame.src = "data:text/html,<html>second new child</html>";
+ return waitForMutation(gWalker, isChildList);
+ }).then(mutations => {
+ let unloaded = getUnloadedDoc(mutations);
+
+ mutations = assertSrcChange(mutations);
+ mutations = assertUnload(mutations);
+ mutations = assertFrameLoad(mutations);
+ mutations = assertChildList(mutations);
+
+ is(mutations.length, 0, "Got the expected mutations.");
+
+ assertOwnership();
+
+ return checkMissing(gClient, unloaded);
+ }).then(() => {
+ teardown();
+ }).then(runNextTest));
+ });
+});
+
+addTest(function testBack() {
+ setup(() => {
+ let beforeUnloadSize = 0;
+ // Load a bunch of fronts for actors inside the child frame.
+ promiseDone(loadChildSelector("#longlist div").then(() => {
+ let childFrame = gInspectee.querySelector("#childFrame");
+ childFrame.src = "data:text/html,<html>new child</html>";
+ return waitForMutation(gWalker, isChildList);
+ }).then(mutations => {
+ // Read the new child
+ return loadChildSelector("#longlist div");
+ }).then(() => {
+ // Now use history.back to change the source, and expect the same results as loadNewChild.
+ let childFrame = gInspectee.querySelector("#childFrame");
+ childFrame.contentWindow.history.back();
+ return waitForMutation(gWalker, isChildList);
+ }).then(mutations => {
+ let unloaded = getUnloadedDoc(mutations);
+ mutations = assertSrcChange(mutations);
+ mutations = assertUnload(mutations);
+ mutations = assertFrameLoad(mutations);
+ mutations = assertChildList(mutations);
+ is(mutations.length, 0, "Got the expected mutations.");
+
+ assertOwnership();
+
+ return checkMissing(gClient, unloaded);
+ }).then(() => {
+ 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/mochitest/test_inspector-mutations-value.html b/devtools/server/tests/mochitest/test_inspector-mutations-value.html
new file mode 100644
index 000000000..352526b13
--- /dev/null
+++ b/devtools/server/tests/mochitest/test_inspector-mutations-value.html
@@ -0,0 +1,168 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug </title>
+
+ <script type="application/javascript" 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;version=1.8" src="inspector-helpers.js"></script>
+ <script type="application/javascript;version=1.8">
+const inspector = require("devtools/server/actors/inspector");
+
+window.onload = function() {
+ SimpleTest.waitForExplicitFinish();
+ runNextTest();
+}
+
+const testSummaryLength = 10;
+inspector.setValueSummaryLength(testSummaryLength);
+SimpleTest.registerCleanupFunction(function() {
+ inspector.setValueSummaryLength(inspector.DEFAULT_VALUE_SUMMARY_LENGTH);
+});
+
+var gInspectee = null;
+var gWalker = null;
+var gClient = null;
+var valueNode;
+var valueFront;
+var longStringFront;
+var longString = "stringstringstringstringstringstringstringstringstringstringstring";
+var truncatedLongString = longString.substring(0, testSummaryLength);
+var shortString = "str";
+var shortString2 = "str2";
+
+addTest(function setup() {
+ let url = document.getElementById("inspectorContent").href;
+ attachURL(url, function(err, client, tab, doc) {
+ gInspectee = doc;
+ let {InspectorFront} = require("devtools/shared/fronts/inspector");
+ let inspector = InspectorFront(client, tab);
+ promiseDone(inspector.getWalker().then(walker => {
+ ok(walker, "getWalker() should return an actor.");
+ gClient = client;
+ gWalker = walker;
+ }).then(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() {
+ let frame = gInspectee.querySelector('#childFrame');
+ valueNode = frame.contentDocument.querySelector("#longstring").firstChild;
+
+ promiseDone(gWalker.querySelector(gWalker.rootNode, "#childFrame").then(childFrame => {
+ return gWalker.children(childFrame);
+ }).then(children => {
+ let nodes = children.nodes;
+ is(nodes.length, 1, "There should be only one child of the iframe");
+ is(nodes[0].nodeType, Node.DOCUMENT_NODE, "iframe child should be a document node");
+ return gWalker.querySelector(nodes[0], "#longstring");
+ }).then(node => {
+ longStringFront = node;
+ return gWalker.children(node);
+ }).then(children => {
+ valueFront = children.nodes[0];
+ }).then(runNextTest));
+}
+
+function checkNodeFrontValue(front, expectedValue) {
+ return front.getNodeValue().then(longstring => {
+ return longstring.string();
+ }).then(str => {
+ is(str, expectedValue, "Node value is as expected");
+ })
+}
+
+function testKeepLongValue() {
+ // After first setup we should have a long string in the node
+ ok(!longStringFront.inlineTextChild, "Text node is too long to be inlined.");
+
+ valueNode.nodeValue = longString;
+ gWalker.once("mutations", (changes) => {
+ ok(!longStringFront.inlineTextChild, "Text node is too long to be inlined.");
+ ok(!changes.some(change => change.type === "inlineTextChild"),
+ "No inline text child mutation was fired.");
+ checkNodeFrontValue(valueFront, longString).then(runNextTest);
+ });
+}
+
+function testSetShortValue() {
+ ok(!longStringFront.inlineTextChild, "Text node is too long to be inlined.");
+
+ valueNode.nodeValue = shortString;
+ gWalker.once("mutations", (changes) => {
+ ok(!!longStringFront.inlineTextChild, "Text node is short enough to be inlined.");
+ ok(changes.some(change => change.type === "inlineTextChild"),
+ "An inlineTextChild mutation was fired.");
+ checkNodeFrontValue(valueFront, shortString).then(runNextTest);
+ });
+}
+
+function testKeepShortValue() {
+ ok(!!longStringFront.inlineTextChild, "Text node is short enough to be inlined.");
+
+ valueNode.nodeValue = shortString2;
+ gWalker.once("mutations", (changes) => {
+ ok(!!longStringFront.inlineTextChild, "Text node is short enough to be inlined.");
+ ok(!changes.some(change => change.type === "inlineTextChild"),
+ "No inline text child mutation was fired.");
+ checkNodeFrontValue(valueFront, shortString2).then(runNextTest);
+ });
+}
+
+function testSetLongValue() {
+ ok(!!longStringFront.inlineTextChild, "Text node is short enough to be inlined.");
+
+ valueNode.nodeValue = longString;
+ gWalker.once("mutations", (changes) => {
+ ok(!longStringFront.inlineTextChild, "Text node is too long to be inlined.");
+ ok(changes.some(change => change.type === "inlineTextChild"),
+ "An inlineTextChild mutation was fired.");
+ checkNodeFrontValue(valueFront, longString).then(runNextTest);
+ });
+}
+
+addTest(function cleanup() {
+ delete gInspectee;
+ delete gWalker;
+ delete gClient;
+ 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/mochitest/test_inspector-pick-color.html b/devtools/server/tests/mochitest/test_inspector-pick-color.html
new file mode 100644
index 000000000..48ad08468
--- /dev/null
+++ b/devtools/server/tests/mochitest/test_inspector-pick-color.html
@@ -0,0 +1,101 @@
+<!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 type="application/javascript" 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;version=1.8" src="inspector-helpers.js"></script>
+ <script type="application/javascript;version=1.8">
+window.onload = function() {
+ const Cu = Components.utils;
+ Cu.import("resource://devtools/shared/Loader.jsm");
+ const {Promise: promise} = Cu.import("resource://gre/modules/Promise.jsm", {});
+ const {InspectorFront} = devtools.require("devtools/shared/fronts/inspector");
+ const {console} = Cu.import("resource://gre/modules/Console.jsm", {});
+
+ SimpleTest.waitForExplicitFinish();
+
+ let win = null;
+ let inspector = null;
+
+ addAsyncTest(function*() {
+ info("Setting up inspector actor");
+
+ let url = document.getElementById("inspectorContent").href;
+
+ yield new Promise(resolve => {
+ attachURL(url, function(err, client, tab, doc) {
+ win = doc.defaultView;
+ inspector = InspectorFront(client, tab);
+ resolve();
+ });
+ });
+
+ runNextTest();
+ });
+
+ addAsyncTest(function*() {
+ info("Start picking a color from the page");
+ yield inspector.pickColorFromPage();
+
+ info("Click in the page and make sure a color-picked event is received");
+ let onColorPicked = waitForEvent("color-picked");
+ win.document.body.click();
+ let color = yield onColorPicked;
+
+ is(color, "#000000", "The color-picked event was received with the right color");
+
+ runNextTest();
+ });
+
+ addAsyncTest(function*() {
+ info("Start picking a color from the page");
+ yield inspector.pickColorFromPage();
+
+ info("Use the escape key to dismiss the eyedropper");
+ let onPickCanceled = waitForEvent("color-pick-canceled");
+
+ let keyboardEvent = win.document.createEvent("KeyboardEvent");
+ keyboardEvent.initKeyEvent("keydown", true, true, win, false, false,
+ false, false, 27, 0);
+ win.document.dispatchEvent(keyboardEvent);
+
+ yield onPickCanceled;
+ ok(true, "The color-pick-canceled event was received");
+
+ runNextTest();
+ });
+
+ addAsyncTest(function*() {
+ info("Start picking a color from the page");
+ yield inspector.pickColorFromPage();
+
+ info("And cancel the color picking");
+ yield 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/mochitest/test_inspector-pseudoclass-lock.html b/devtools/server/tests/mochitest/test_inspector-pseudoclass-lock.html
new file mode 100644
index 000000000..64bb03f80
--- /dev/null
+++ b/devtools/server/tests/mochitest/test_inspector-pseudoclass-lock.html
@@ -0,0 +1,174 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug </title>
+
+ <script type="application/javascript" 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;version=1.8" src="inspector-helpers.js"></script>
+ <script type="application/javascript;version=1.8">
+const inspector = require("devtools/shared/fronts/inspector");
+const DOMUtils = Components.classes["@mozilla.org/inspector/dom-utils;1"].
+ getService(Components.interfaces.inIDOMUtils);
+
+const KNOWN_PSEUDOCLASSES = [':hover', ':active', ':focus']
+
+window.onload = function() {
+ SimpleTest.waitForExplicitFinish();
+ runNextTest();
+}
+
+var gInspectee = null;
+var gWalker = null;
+var gClient = null;
+var gCleanupConnection = null;
+
+function setup(callback) {
+ let url = document.getElementById("inspectorContent").href;
+ gCleanupConnection = attachURL(url, function(err, client, tab, doc) {
+ gInspectee = doc;
+ let {InspectorFront} = require("devtools/shared/fronts/inspector");
+ let inspector = InspectorFront(client, tab);
+ promiseDone(inspector.getWalker().then(walker => {
+ gClient = client;
+ gWalker = walker;
+ }).then(callback));
+ });
+}
+
+function teardown() {
+ gWalker = null;
+ gClient = null;
+ gInspectee = null;
+ if (gCleanupConnection) {
+ gCleanupConnection();
+ gCleanupConnection = null;
+ }
+}
+
+function checkChange(change, expectation) {
+ is(change.type, "pseudoClassLock", "Expect a pseudoclass lock change.");
+ let 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 pseudo of expectation.pseudos) {
+ ok(target.hasPseudoClassLock(pseudo), "Expect lock: " + pseudo);
+ ok(DOMUtils.hasPseudoClassLock(target.rawNode(), pseudo), "Expect lock in dom: " + pseudo);
+ }
+
+ for (let pseudo of KNOWN_PSEUDOCLASSES) {
+ if (!expectation.pseudos.some(expected => pseudo === expected)) {
+ ok(!target.hasPseudoClassLock(pseudo), "Don't expect lock: " + pseudo);
+ ok(!DOMUtils.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 mutations");
+ 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 => {
+ let 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 => {
+ let 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(() => {
+ // Now shut down the walker and make sure that clears up the remaining lock.
+ return gWalker.release();
+ }).then(() => {
+ ok(!DOMUtils.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/mochitest/test_inspector-release.html b/devtools/server/tests/mochitest/test_inspector-release.html
new file mode 100644
index 000000000..45412bef0
--- /dev/null
+++ b/devtools/server/tests/mochitest/test_inspector-release.html
@@ -0,0 +1,102 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug </title>
+
+ <script type="application/javascript" 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;version=1.8" src="inspector-helpers.js"></script>
+ <script type="application/javascript;version=1.8">
+const inspector = require("devtools/shared/fronts/inspector");
+
+window.onload = function() {
+ SimpleTest.waitForExplicitFinish();
+ runNextTest();
+}
+
+var gWalker = null;
+var gClient = null;
+
+function assertOwnership() {
+ return assertOwnershipTrees(gWalker);
+}
+
+addTest(function setup() {
+ let url = document.getElementById("inspectorContent").href;
+ attachURL(url, function(err, client, tab, doc) {
+ let {InspectorFront} = require("devtools/shared/fronts/inspector");
+ let inspector = InspectorFront(client, tab);
+ promiseDone(inspector.getWalker().then(walker => {
+ ok(walker, "getWalker() should return an actor.");
+ gClient = client;
+ gWalker = walker;
+ }).then(runNextTest));
+ });
+});
+
+addTest(function testReleaseSubtree() {
+ let originalOwnershipSize = 0;
+ let longlist = null;
+ let firstChild = null;
+ promiseDone(gWalker.querySelectorAll(gWalker.rootNode, "#longlist div").then(list => {
+ // Make sure we have the 26 children of longlist in our ownership tree.
+ is(list.length, 26, "Expect 26 div children.");
+ // Make sure we've read in all those children and incorporated them in our ownership tree.
+ return list.items();
+ }).then((items)=> {
+ originalOwnershipSize = assertOwnership();
+
+ // Here is how the ownership tree is summed up:
+ // #document 1
+ // <html> 1
+ // <body> 1
+ // <div id=longlist> 1
+ // <div id=a>a</div> 26*2 (each child plus it's singleTextChild)
+ // ...
+ // <div id=z>z</div>
+ // -----
+ // 56
+ is(originalOwnershipSize, 56, "Correct number of items in ownership tree");
+ firstChild = items[0].actorID;
+ }).then(() => {
+ // Now get the longlist and release it from the ownership tree.
+ return gWalker.querySelector(gWalker.rootNode, "#longlist");
+ }).then(node => {
+ longlist = node.actorID;
+ return gWalker.releaseNode(node);
+ }).then(() => {
+ // Our ownership size should now be 53 fewer (we forgot about #longlist + 26 children + 26 singleTextChild nodes)
+ let newOwnershipSize = assertOwnership();
+ is(newOwnershipSize, originalOwnershipSize - 53,
+ "Ownership tree should be lower");
+ // Now verify that some nodes have gone away
+ return checkMissing(gClient, longlist);
+ }).then(() => {
+ return checkMissing(gClient, firstChild);
+ }).then(runNextTest));
+});
+
+addTest(function cleanup() {
+ delete gWalker;
+ delete gClient;
+ 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/mochitest/test_inspector-reload.html b/devtools/server/tests/mochitest/test_inspector-reload.html
new file mode 100644
index 000000000..91252aa8f
--- /dev/null
+++ b/devtools/server/tests/mochitest/test_inspector-reload.html
@@ -0,0 +1,80 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug </title>
+
+ <script type="application/javascript" 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;version=1.8" src="inspector-helpers.js"></script>
+ <script type="application/javascript;version=1.8">
+const inspector = require("devtools/shared/fronts/inspector");
+
+window.onload = function() {
+ SimpleTest.waitForExplicitFinish();
+ runNextTest();
+}
+
+var gInspectee = null;
+var gClient = null;
+var gWalker = null;
+
+addTest(function setup() {
+ let url = document.getElementById("inspectorContent").href;
+ attachURL(url, function(err, client, tab, doc) {
+ gInspectee = doc;
+ let {InspectorFront} = require("devtools/shared/fronts/inspector");
+ let inspector = InspectorFront(client, tab);
+ promiseDone(inspector.getWalker().then(walker => {
+ ok(walker, "getWalker() should return an actor.");
+ gClient = client;
+ gWalker = walker;
+ return inspector.getWalker();
+ }).then(walker => {
+ dump(walker.actorID + "\n");
+ ok(walker === gWalker, "getWalker() twice should return the same walker.");
+ }).then(runNextTest));
+ });
+});
+
+addTest(function testReload() {
+ let nodeFront;
+ let oldRootID = gWalker.rootNode.actorID;
+ // Load a node to populate the tree a bit.
+ promiseDone(gWalker.querySelector(gWalker.rootNode, "#a").then(front => {
+ gInspectee.defaultView.location.reload();
+ return waitForMutation(gWalker, isNewRoot);
+ }).then(() => {
+ ok(gWalker.rootNode.actorID != oldRootID, "Root node should have changed.");
+ }).then(() => {
+ // Make sure we can still access the document
+ return gWalker.querySelector(gWalker.rootNode, "#a");
+ }).then(front => {
+ ok(front.actorID, "Got a new actor ID");
+ }).then(runNextTest));
+});
+
+addTest(function cleanup() {
+ delete gWalker;
+ delete gInspectee;
+ delete gClient;
+ 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/mochitest/test_inspector-remove.html b/devtools/server/tests/mochitest/test_inspector-remove.html
new file mode 100644
index 000000000..2331c3e30
--- /dev/null
+++ b/devtools/server/tests/mochitest/test_inspector-remove.html
@@ -0,0 +1,117 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug </title>
+
+ <script type="application/javascript" 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;version=1.8" src="inspector-helpers.js"></script>
+ <script type="application/javascript;version=1.8">
+const inspector = require("devtools/shared/fronts/inspector");
+
+window.onload = function() {
+ SimpleTest.waitForExplicitFinish();
+ runNextTest();
+}
+
+var gWalker = null;
+var gClient = null;
+
+function assertOwnership() {
+ return assertOwnershipTrees(gWalker);
+}
+
+function ignoreNode(node) {
+ // Duplicate the walker logic to skip blank nodes...
+ return node.nodeType === Components.interfaces.nsIDOMNode.TEXT_NODE &&
+ !/[^\s]/.test(node.nodeValue);
+}
+
+addTest(function setup() {
+ let url = document.getElementById("inspectorContent").href;
+ attachURL(url, function(err, client, tab, doc) {
+ gInspectee = doc;
+ let {InspectorFront} = require("devtools/shared/fronts/inspector");
+ let inspector = InspectorFront(client, tab);
+ promiseDone(inspector.getWalker().then(walker => {
+ ok(walker, "getWalker() should return an actor.");
+ gClient = client;
+ gWalker = walker;
+ }).then(runNextTest));
+ });
+});
+
+addTest(function testRemoveSubtree() {
+ let originalOwnershipSize = 0;
+ let longlist = null;
+ let longlistID = null;
+
+ let nextSibling = gInspectee.querySelector("#longlist").nextSibling;
+ while (nextSibling && ignoreNode(nextSibling)) {
+ nextSibling = nextSibling.nextSibling;
+ }
+
+ let previousSibling = gInspectee.querySelector("#longlist").previousSibling;
+ while (previousSibling && ignoreNode(previousSibling)) {
+ previousSibling = previousSibling.previousSibling;
+ }
+
+ promiseDone(gWalker.querySelector(gWalker.rootNode, "#longlist").then(listFront => {
+ longlist = listFront;
+ longlistID = longlist.actorID;
+ }).then(() => {
+ return gWalker.children(longlist);
+ }).then((items)=> {
+ originalOwnershipSize = assertOwnership();
+ // Here is how the ownership tree is summed up:
+ // #document 1
+ // <html> 1
+ // <body> 1
+ // <div id=longlist> 1
+ // <div id=a>a</div> 26*2 (each child plus it's singleTextChild)
+ // ...
+ // <div id=z>z</div>
+ // -----
+ // 56
+ is(originalOwnershipSize, 56, "Correct number of items in ownership tree");
+ return gWalker.removeNode(longlist);
+ }).then(siblings => {
+ is(siblings.previousSibling.rawNode(), previousSibling, "Should have returned the previous sibling.");
+ is(siblings.nextSibling.rawNode(), nextSibling, "Should have returned the next sibling.");
+ return waitForMutation(gWalker, isChildList);
+ }).then(() => {
+ // Our ownership size should now be 51 fewer (we forgot about #longlist + 26
+ // children + 26 singleTextChild nodes, but learned about #longlist's
+ // prev/next sibling)
+ let newOwnershipSize = assertOwnership();
+ is(newOwnershipSize, originalOwnershipSize - 51,
+ "Ownership tree should be lower");
+ // Now verify that some nodes have gone away
+ return checkMissing(gClient, longlistID);
+ }).then(runNextTest));
+});
+
+addTest(function cleanup() {
+ delete gWalker;
+ delete gClient;
+ 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/mochitest/test_inspector-resize.html b/devtools/server/tests/mochitest/test_inspector-resize.html
new file mode 100644
index 000000000..eafa6436c
--- /dev/null
+++ b/devtools/server/tests/mochitest/test_inspector-resize.html
@@ -0,0 +1,77 @@
+<!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 type="application/javascript" 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;version=1.8" src="inspector-helpers.js"></script>
+ <script type="application/javascript;version=1.8">
+window.onload = function() {
+ const Cu = Components.utils;
+ const {require} = Cu.import("resource://devtools/shared/Loader.jsm", {});
+ const promise = require("promise");
+ const {InspectorFront} = require("devtools/shared/fronts/inspector");
+ const {console} = Cu.import("resource://gre/modules/Console.jsm", {});
+
+ SimpleTest.waitForExplicitFinish();
+
+ let win = null;
+ let inspector = null;
+
+ addAsyncTest(function* setup() {
+ info ("Setting up inspector and walker actors.");
+
+ let url = document.getElementById("inspectorContent").href;
+
+ yield new promise(resolve => {
+ attachURL(url, function(err, client, tab, doc) {
+ win = doc.defaultView;
+ inspector = InspectorFront(client, tab);
+ resolve();
+ });
+ });
+
+ runNextTest();
+ });
+
+ addAsyncTest(function*() {
+ let walker = yield inspector.getWalker();
+
+ // We can't receive events from the walker if we haven't first executed a
+ // method on the actor to initialize it.
+ yield walker.querySelector(walker.rootNode, "img");
+
+ let {outerWidth, outerHeight} = win;
+ let onResize = new promise(resolve => {
+ walker.once("resize", () => {
+ resolve();
+ });
+ });
+ win.resizeTo(800, 600);
+ yield 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/mochitest/test_inspector-resolve-url.html b/devtools/server/tests/mochitest/test_inspector-resolve-url.html
new file mode 100644
index 000000000..1494739ed
--- /dev/null
+++ b/devtools/server/tests/mochitest/test_inspector-resolve-url.html
@@ -0,0 +1,88 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=921102
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 921102</title>
+
+ <script type="application/javascript" 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;version=1.8" src="inspector-helpers.js"></script>
+ <script type="application/javascript;version=1.8">
+const inspector = require("devtools/shared/fronts/inspector");
+
+window.onload = function() {
+ SimpleTest.waitForExplicitFinish();
+ runNextTest();
+}
+
+var gInspector;
+var gDoc;
+
+addTest(function() {
+ let url = document.getElementById("inspectorContent").href;
+ attachURL(url, function(err, client, tab, doc) {
+ gDoc = doc;
+ let {InspectorFront} = require("devtools/shared/fronts/inspector");
+ gInspector = InspectorFront(client, tab);
+ 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/" +
+ "mochitest/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");
+ let node = gDoc.querySelector(".big-horizontal");
+ gInspector.resolveRelativeURL("test.png?id=4#wow", node).then(url => {
+ is(url, "chrome://mochitests/content/chrome/devtools/server/tests/" +
+ "mochitest/test.png?id=4#wow");
+ runNextTest();
+ });
+});
+
+addTest(function() {
+ info("Resolve an absolute URL providing a context node");
+ let 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 = 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/mochitest/test_inspector-retain.html b/devtools/server/tests/mochitest/test_inspector-retain.html
new file mode 100644
index 000000000..e8342cf67
--- /dev/null
+++ b/devtools/server/tests/mochitest/test_inspector-retain.html
@@ -0,0 +1,180 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug </title>
+
+ <script type="application/javascript" 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;version=1.8" src="inspector-helpers.js"></script>
+ <script type="application/javascript;version=1.8">
+const inspector = require("devtools/shared/fronts/inspector");
+
+window.onload = function() {
+ SimpleTest.waitForExplicitFinish();
+ runNextTest();
+}
+
+var gWalker = null;
+var gClient = null;
+var gInspectee = null;
+
+function assertOwnership() {
+ return assertOwnershipTrees(gWalker);
+}
+
+addTest(function setup() {
+ let url = document.getElementById("inspectorContent").href;
+ attachURL(url, function(err, client, tab, doc) {
+ gInspectee = doc;
+ let {InspectorFront} = require("devtools/shared/fronts/inspector");
+ let inspector = InspectorFront(client, tab);
+ promiseDone(inspector.getWalker().then(walker => {
+ ok(walker, "getWalker() should return an actor.");
+ gClient = client;
+ gWalker = walker;
+ }).then(runNextTest));
+ });
+});
+
+// Retain a node, and a second-order child (in another document, for kicks)
+// Release the parent of the top item, which should cause one retained orphan.
+
+// Then unretain the top node, which should retain the orphan.
+
+// Then change the source of the iframe, which should kill that orphan.
+
+addTest(function testRetain() {
+ let originalOwnershipSize = 0;
+ let bodyFront = null;
+ let frameFront = null;
+ let childListFront = null;
+ // Get the toplevel body element and retain it.
+ promiseDone(gWalker.querySelector(gWalker.rootNode, "body").then(front => {
+ bodyFront = front;
+ return gWalker.retainNode(bodyFront);
+ }).then(() => {
+ // Get an element in the child frame and retain it.
+ return gWalker.querySelector(gWalker.rootNode, "#childFrame");
+ }).then(frame => {
+ frameFront = frame;
+ return gWalker.children(frame, { maxNodes: 1 }).then(children => {
+ return children.nodes[0];
+ });
+ }).then(childDoc => {
+ return gWalker.querySelector(childDoc, "#longlist");
+ }).then(list => {
+ childListFront = list;
+ originalOwnershipSize = assertOwnership();
+ // and rtain it.
+ return gWalker.retainNode(childListFront);
+ }).then(() => {
+ // OK, try releasing the parent of the first retained.
+ return gWalker.releaseNode(bodyFront.parentNode());
+ }).then(() => {
+ let size = assertOwnership();
+ let clientTree = clientOwnershipTree(gWalker);
+
+ // That request should have freed the parent of the first retained
+ // but moved the rest into the retained orphaned tree.
+ is(ownershipTreeSize(clientTree.root) + ownershipTreeSize(clientTree.retained[0]) + 1,
+ originalOwnershipSize,
+ "Should have only lost one item overall.");
+ is(gWalker._retainedOrphans.size, 1, "Should have retained one orphan");
+ ok(gWalker._retainedOrphans.has(bodyFront), "Should have retained the expected node.");
+ }).then(() => {
+ // Unretain the body, which should promote the childListFront to a retained orphan.
+ return gWalker.unretainNode(bodyFront);
+ }).then(() => {
+ assertOwnership();
+ let clientTree = clientOwnershipTree(gWalker);
+
+ is(gWalker._retainedOrphans.size, 1, "Should still only have one retained orphan.");
+ ok(!gWalker._retainedOrphans.has(bodyFront), "Should have dropped the body node.")
+ ok(gWalker._retainedOrphans.has(childListFront), "Should have retained the child node.")
+ }).then(() => {
+ // Change the source of the iframe, which should kill the retained orphan.
+ gInspectee.querySelector("#childFrame").src = "data:text/html,<html>new child</html>";
+ return waitForMutation(gWalker, isUnretained);
+ }).then(mutations => {
+ assertOwnership();
+ let clientTree = clientOwnershipTree(gWalker);
+ is(gWalker._retainedOrphans.size, 0, "Should have no more retained orphans.");
+
+ }).then(runNextTest));
+});
+
+// Get a hold of a node, remove it from the doc and retain it at the same time.
+// We should always win that race (even though the mutation happens before the
+// retain request), because we haven't issued `getMutations` yet.
+addTest(function testWinRace() {
+ let front = null;
+ promiseDone(gWalker.querySelector(gWalker.rootNode, "#a").then(node => {
+ front = node;
+ let contentNode = gInspectee.querySelector("#a");
+ contentNode.parentNode.removeChild(contentNode);
+ // Now wait for that mutation and retain response to come in.
+ return promise.all([
+ gWalker.retainNode(front),
+ waitForMutation(gWalker, isChildList)
+ ]);
+ }).then(() => {
+ assertOwnership();
+ let clientTree = clientOwnershipTree(gWalker);
+ is(gWalker._retainedOrphans.size, 1, "Should have a retained orphan.");
+ ok(gWalker._retainedOrphans.has(front), "Should have retained our expected node.");
+ return gWalker.unretainNode(front);
+ }).then(() => {
+ // Make sure we're clear for the next test.
+ assertOwnership();
+ let clientTree = clientOwnershipTree(gWalker);
+ is(gWalker._retainedOrphans.size, 0, "Should have no more retained orphans.");
+ }).then(runNextTest));
+});
+
+// Same as above, but issue the request right after the 'new-mutations' event, so that
+// we *lose* the race.
+addTest(function testLoseRace() {
+ let front = null;
+ promiseDone(gWalker.querySelector(gWalker.rootNode, "#z").then(node => {
+ front = node;
+ gInspectee.querySelector("#z").parentNode = null;
+ let contentNode = gInspectee.querySelector("#a");
+ contentNode.parentNode.removeChild(contentNode);
+ return promiseOnce(gWalker, "new-mutations");
+ }).then(() => {
+ // Verify that we have an outstanding request (no good way to tell that it's a
+ // getMutations request, but there's nothing else it would be).
+ is(gWalker._requests.length, 1, "Should have an outstanding request.");
+ return gWalker.retainNode(front)
+ }).then(() => { ok(false, "Request should not have succeeded!"); },
+ (err) => {
+ ok(err, "noSuchActor", "Should have lost the race.");
+ let clientTree = clientOwnershipTree(gWalker);
+ is(gWalker._retainedOrphans.size, 0, "Should have no more retained orphans.");
+ // Don't re-throw the error.
+ }).then(runNextTest));
+});
+
+addTest(function cleanup() {
+ delete gWalker;
+ delete gClient;
+ 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/mochitest/test_inspector-scroll-into-view.html b/devtools/server/tests/mochitest/test_inspector-scroll-into-view.html
new file mode 100644
index 000000000..1e164e83d
--- /dev/null
+++ b/devtools/server/tests/mochitest/test_inspector-scroll-into-view.html
@@ -0,0 +1,87 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=901250
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 901250</title>
+
+ <script type="application/javascript" 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;version=1.8" src="inspector-helpers.js"></script>
+ <script type="application/javascript;version=1.8">
+const inspector = require("devtools/shared/fronts/inspector");
+
+window.onload = function() {
+ SimpleTest.waitForExplicitFinish();
+ runNextTest();
+}
+
+var gInspectee = null;
+var gClient = null;
+var gWalker = null;
+
+function assertOwnership() {
+ assertOwnershipTrees(gWalker);
+}
+
+addTest(function setup() {
+ let url = document.getElementById("inspectorContent").href;
+ attachURL(url, function(err, client, tab, doc) {
+ gInspectee = doc;
+ let {InspectorFront} = require("devtools/shared/fronts/inspector");
+ let inspector = InspectorFront(client, tab);
+ promiseDone(inspector.getWalker().then(walker => {
+ ok(walker, "getWalker() should return an actor.");
+ gClient = client;
+ gWalker = walker;
+ }).then(runNextTest));
+ });
+});
+
+addTest(Task.async(function* testScrollIntoView() {
+ let id = "#scroll-into-view";
+ let rect = gInspectee.querySelector(id).getBoundingClientRect();
+ let nodeFront = yield gWalker.querySelector(gWalker.rootNode, id);
+ let inViewport = rect.x >= 0 &&
+ rect.y >= 0 &&
+ rect.y <= gInspectee.defaultView.innerHeight &&
+ rect.x <= gInspectee.defaultView.innerWidth;
+
+ ok(!inViewport, "Element is not in viewport.");
+
+ yield nodeFront.scrollIntoView();
+
+ SimpleTest.executeSoon(() => {
+ rect = gInspectee.querySelector(id).getBoundingClientRect();
+ inViewport = rect.x >= 0 &&
+ rect.y >= 0 &&
+ rect.y <= gInspectee.defaultView.innerHeight &&
+ rect.x <= gInspectee.defaultView.innerWidth;
+
+ ok(inViewport, "Element is in viewport.");
+
+ runNextTest();
+ });
+}));
+
+addTest(function cleanup() {
+ delete gWalker;
+ delete gInspectee;
+ delete gClient;
+ runNextTest();
+});
+ </script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=901250">Mozilla Bug 901250</a>
+<a id="inspectorContent" target="_blank" href="inspector-traversal-data.html">Test Document</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+</body>
+</html>
diff --git a/devtools/server/tests/mochitest/test_inspector-search-front.html b/devtools/server/tests/mochitest/test_inspector-search-front.html
new file mode 100644
index 000000000..e0f8f77e8
--- /dev/null
+++ b/devtools/server/tests/mochitest/test_inspector-search-front.html
@@ -0,0 +1,217 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=835896
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 835896</title>
+ <script type="application/javascript" 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;version=1.8" src="inspector-helpers.js"></script>
+ <script type="application/javascript;version=1.8">
+window.onload = function() {
+ const Cu = Components.utils;
+ const {require} = Cu.import("resource://devtools/shared/Loader.jsm", {});
+ const promise = require("promise");
+ const {InspectorFront} = require("devtools/shared/fronts/inspector");
+ const {console} = Cu.import("resource://gre/modules/Console.jsm", {});
+
+ SimpleTest.waitForExplicitFinish();
+
+ let walkerFront = null;
+ let inspectee = null;
+ let inspector = null;
+
+ // WalkerFront specific tests. These aren't to excercise search
+ // edge cases so much as to test the state the Front maintains between
+ // searches.
+ // See also test_inspector-search.html
+
+ addAsyncTest(function* setup() {
+ info ("Setting up inspector and walker actors.");
+
+ let url = document.getElementById("inspectorContent").href;
+
+ yield new promise(resolve => {
+ attachURL(url, function(err, client, tab, doc) {
+ inspectee = doc;
+ inspector = InspectorFront(client, tab);
+ resolve();
+ });
+ });
+
+ walkerFront = yield inspector.getWalker();
+ ok(walkerFront, "getWalker() should return an actor.");
+
+ runNextTest();
+ });
+
+ addAsyncTest(function* testWalkerFrontDefaults() {
+ info ("Testing search API using WalkerFront.");
+ let nodes = yield walkerFront.querySelectorAll(walkerFront.rootNode, "h2");
+ let fronts = yield nodes.items();
+
+ let frontResult = yield walkerFront.search("");
+ ok(!frontResult, "Null result on front when searching for ''");
+
+ let results = yield walkerFront.search("h2");
+ isDeeply(results, {
+ node: fronts[0],
+ type: "search",
+ resultsIndex: 0,
+ resultsLength: 3
+ }, "Default options work");
+
+ results = yield walkerFront.search("h2", { });
+ isDeeply(results, {
+ node: fronts[1],
+ type: "search",
+ resultsIndex: 1,
+ resultsLength: 3
+ }, "Search works with empty options");
+
+ // Clear search data to remove result state on the front
+ yield walkerFront.search("");
+ runNextTest();
+ });
+
+ addAsyncTest(function* testMultipleSearches() {
+ info ("Testing search API using WalkerFront (reverse=false)");
+ let nodes = yield walkerFront.querySelectorAll(walkerFront.rootNode, "h2");
+ let fronts = yield nodes.items();
+
+ let results = yield walkerFront.search("h2");
+ isDeeply(results, {
+ node: fronts[0],
+ type: "search",
+ resultsIndex: 0,
+ resultsLength: 3
+ }, "Search works with multiple results (reverse=false)");
+
+ results = yield walkerFront.search("h2");
+ isDeeply(results, {
+ node: fronts[1],
+ type: "search",
+ resultsIndex: 1,
+ resultsLength: 3
+ }, "Search works with multiple results (reverse=false)");
+
+ results = yield walkerFront.search("h2");
+ isDeeply(results, {
+ node: fronts[2],
+ type: "search",
+ resultsIndex: 2,
+ resultsLength: 3
+ }, "Search works with multiple results (reverse=false)");
+
+ results = yield walkerFront.search("h2");
+ isDeeply(results, {
+ node: fronts[0],
+ type: "search",
+ resultsIndex: 0,
+ resultsLength: 3
+ }, "Search works with multiple results (reverse=false)");
+
+ // Clear search data to remove result state on the front
+ yield walkerFront.search("");
+ runNextTest();
+ });
+
+ addAsyncTest(function* testMultipleSearchesReverse() {
+ info ("Testing search API using WalkerFront (reverse=true)");
+ let nodes = yield walkerFront.querySelectorAll(walkerFront.rootNode, "h2");
+ let fronts = yield nodes.items();
+
+ let results = yield walkerFront.search("h2", {reverse: true});
+ isDeeply(results, {
+ node: fronts[2],
+ type: "search",
+ resultsIndex: 2,
+ resultsLength: 3
+ }, "Search works with multiple results (reverse=true)");
+
+ results = yield walkerFront.search("h2", {reverse: true});
+ isDeeply(results, {
+ node: fronts[1],
+ type: "search",
+ resultsIndex: 1,
+ resultsLength: 3
+ }, "Search works with multiple results (reverse=true)");
+
+ results = yield walkerFront.search("h2", {reverse: true});
+ isDeeply(results, {
+ node: fronts[0],
+ type: "search",
+ resultsIndex: 0,
+ resultsLength: 3
+ }, "Search works with multiple results (reverse=true)");
+
+ results = yield walkerFront.search("h2", {reverse: true});
+ isDeeply(results, {
+ node: fronts[2],
+ type: "search",
+ resultsIndex: 2,
+ resultsLength: 3
+ }, "Search works with multiple results (reverse=true)");
+
+ results = yield walkerFront.search("h2", {reverse: false});
+ isDeeply(results, {
+ node: fronts[0],
+ type: "search",
+ resultsIndex: 0,
+ resultsLength: 3
+ }, "Search works with multiple results (reverse=false)");
+
+ // Clear search data to remove result state on the front
+ yield walkerFront.search("");
+ runNextTest();
+ });
+
+
+ addAsyncTest(function* testBackwardsCompat() {
+ info ("Simulating a server that doesn't have the new search functionality.");
+ walkerFront.traits.textSearch = false;
+ let front = yield walkerFront.querySelector(walkerFront.rootNode, "h1");
+
+ let results = yield walkerFront.search("h1");
+ isDeeply(results, {
+ node: front,
+ type: "selector",
+ resultsIndex: 0,
+ resultsLength: 1
+ }, "Only querySelectorAll results being returned");
+
+ // Clear search data to remove result state on the front
+ yield walkerFront.search("");
+
+ // Reset the normal textSearch behavior
+ walkerFront.traits.textSearch = true;
+
+ results = yield walkerFront.search("h1");
+ isDeeply(results, {
+ node: front,
+ type: "search",
+ resultsIndex: 0,
+ resultsLength: 3
+ }, "Other results being included");
+
+ // Clear search data to remove result state on the front
+ yield walkerFront.search("");
+ runNextTest();
+ });
+
+ runNextTest();
+};
+ </script>
+</head>
+<body>
+<a id="inspectorContent" target="_blank" href="inspector-search-data.html">Test Document</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+</body>
+</html>
diff --git a/devtools/server/tests/mochitest/test_inspector-search.html b/devtools/server/tests/mochitest/test_inspector-search.html
new file mode 100644
index 000000000..623d3018d
--- /dev/null
+++ b/devtools/server/tests/mochitest/test_inspector-search.html
@@ -0,0 +1,296 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=835896
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 835896</title>
+ <script type="application/javascript" 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;version=1.8" src="inspector-helpers.js"></script>
+ <script type="application/javascript;version=1.8">
+window.onload = function() {
+ const Cu = Components.utils;
+ const {require} = Cu.import("resource://devtools/shared/Loader.jsm", {});
+ const promise = require("promise");
+ const {InspectorFront} = require("devtools/shared/fronts/inspector");
+ const {WalkerSearch, WalkerIndex} =
+ require("devtools/server/actors/utils/walker-search");
+ const {console} = Cu.import("resource://gre/modules/Console.jsm", {});
+
+ SimpleTest.waitForExplicitFinish();
+
+ let walkerActor = null;
+ let walkerSearch = null;
+ let inspectee = null;
+ let inspector = null;
+
+ // WalkerSearch specific tests. This is to make sure search results are
+ // coming back as expected.
+ // See also test_inspector-search-front.html.
+
+ addAsyncTest(function* setup() {
+ info ("Setting up inspector and walker actors.");
+
+ let url = document.getElementById("inspectorContent").href;
+
+ yield new promise(resolve => {
+ attachURL(url, function(err, client, tab, doc) {
+ inspectee = doc;
+ inspector = InspectorFront(client, tab);
+ resolve();
+ });
+ });
+
+ let walkerFront = yield inspector.getWalker();
+ ok(walkerFront, "getWalker() should return an actor.");
+
+ walkerActor = DebuggerServer._searchAllConnectionsForActor(walkerFront.actorID);
+ ok(walkerActor,
+ "Got a reference to the walker actor (" + walkerFront.actorID + ")");
+
+ walkerSearch = walkerActor.walkerSearch;
+
+ runNextTest();
+ });
+
+ addAsyncTest(function* testIndexExists() {
+ info ("Testing basic index APIs exist.");
+
+ let index = new WalkerIndex(walkerActor);
+ ok(index.data.size > 0, "public index is filled after getting");
+
+ index.clearIndex();
+ ok(!index._data, "private index is empty after clearing");
+ ok(index.data.size > 0, "public index is filled after getting");
+
+ index.destroy();
+ runNextTest();
+ });
+
+ addAsyncTest(function* testSearchExists() {
+ info ("Testing basic search APIs exist.");
+
+ ok(walkerSearch, "walker search exists on the WalkerActor");
+ ok(walkerSearch.search, "walker search has `search` method");
+ ok(walkerSearch.index, "walker search has `index` property");
+ is(walkerSearch.walker, walkerActor, "referencing the correct WalkerActor");
+
+ let search = new WalkerSearch(walkerActor);
+ ok(search, "a new search instance can be created");
+ ok(search.search, "new search instance has `search` method");
+ ok(search.index, "new search instance has `index` property");
+ isnot(search, walkerSearch, "new search instance differs from the WalkerActor's");
+
+ search.destroy();
+ runNextTest();
+ });
+
+ addAsyncTest(function* testEmptySearch() {
+ info ("Testing search with an empty query.");
+ results = walkerSearch.search("");
+ is(results.length, 0, "No results when searching for ''");
+
+ results = walkerSearch.search(null);
+ is(results.length, 0, "No results when searching for null");
+
+ results = walkerSearch.search(undefined);
+ is(results.length, 0, "No results when searching for undefined");
+
+ results = walkerSearch.search(10);
+ is(results.length, 0, "No results when searching for 10");
+
+ runNextTest();
+ });
+
+ addAsyncTest(function* testBasicSearchData() {
+ let testData = [
+ {
+ desc: "Search for tag with one result.",
+ search: "body",
+ expected: [
+ {node: inspectee.body, type: "tag"}
+ ]
+ },
+ {
+ desc: "Search for tag with multiple results",
+ search: "h2",
+ expected: [
+ {node: inspectee.querySelectorAll("h2")[0], type: "tag"},
+ {node: inspectee.querySelectorAll("h2")[1], type: "tag"},
+ {node: inspectee.querySelectorAll("h2")[2], type: "tag"},
+ ]
+ },
+ {
+ desc: "Search for selector with multiple results",
+ search: "body > h2",
+ expected: [
+ {node: inspectee.querySelectorAll("h2")[0], type: "selector"},
+ {node: inspectee.querySelectorAll("h2")[1], type: "selector"},
+ {node: inspectee.querySelectorAll("h2")[2], type: "selector"},
+ ]
+ },
+ {
+ desc: "Search for selector with multiple results",
+ search: ":root h2",
+ expected: [
+ {node: inspectee.querySelectorAll("h2")[0], type: "selector"},
+ {node: inspectee.querySelectorAll("h2")[1], type: "selector"},
+ {node: inspectee.querySelectorAll("h2")[2], type: "selector"},
+ ]
+ },
+ {
+ desc: "Search for selector with multiple results",
+ search: "* h2",
+ expected: [
+ {node: inspectee.querySelectorAll("h2")[0], type: "selector"},
+ {node: inspectee.querySelectorAll("h2")[1], type: "selector"},
+ {node: inspectee.querySelectorAll("h2")[2], type: "selector"},
+ ]
+ },
+ {
+ desc: "Search with multiple matches in a single tag expecting a single result",
+ search: "💩",
+ expected: [
+ {node: inspectee.getElementById("💩"), type: "attributeValue"}
+ ]
+ },
+ {
+ desc: "Search that has tag and text results",
+ search: "h1",
+ expected: [
+ {node: inspectee.querySelector("h1"), type: "tag"},
+ {node: inspectee.querySelector("h1 + p").childNodes[0], type: "text"},
+ {node: inspectee.querySelector("h1 + p > strong").childNodes[0], type: "text"},
+ ]
+ },
+ ]
+
+ for (let {desc, search, expected} of testData) {
+ info("Running test: " + desc);
+ let results = walkerSearch.search(search);
+ isDeeply(results, expected,
+ "Search returns correct results with '" + search + "'");
+ }
+
+ runNextTest();
+ });
+
+ addAsyncTest(function* testPseudoElements() {
+ info ("Testing ::before and ::after element matching");
+
+ let beforeElt = new _documentWalker(inspectee.querySelector("#pseudo"),
+ inspectee.defaultView).firstChild();
+ let afterElt = new _documentWalker(inspectee.querySelector("#pseudo"),
+ inspectee.defaultView).lastChild();
+ let styleText = inspectee.querySelector("style").childNodes[0];
+
+ // ::before
+ let results = walkerSearch.search("::before");
+ isDeeply(results, [ {node: beforeElt, type: "tag"} ],
+ "Tag search works for pseudo element");
+
+ results = walkerSearch.search("_moz_generated_content_before");
+ is(results.length, 0, "No results for anon tag name");
+
+ results = walkerSearch.search("before element");
+ isDeeply(results, [
+ {node: styleText, type: "text"},
+ {node: beforeElt, type: "text"}
+ ], "Text search works for pseudo element");
+
+ // ::after
+ results = walkerSearch.search("::after");
+ isDeeply(results, [ {node: afterElt, type: "tag"} ],
+ "Tag search works for pseudo element");
+
+ results = walkerSearch.search("_moz_generated_content_after");
+ is(results.length, 0, "No results for anon tag name");
+
+ results = walkerSearch.search("after element");
+ isDeeply(results, [
+ {node: styleText, type: "text"},
+ {node: afterElt, type: "text"}
+ ], "Text search works for pseudo element");
+
+ runNextTest();
+ });
+
+ addAsyncTest(function* testSearchMutationChangeResults() {
+ info ("Testing search before and after a mutation.");
+ let expected = [
+ {node: inspectee.querySelectorAll("h3")[0], type: "tag"},
+ {node: inspectee.querySelectorAll("h3")[1], type: "tag"},
+ {node: inspectee.querySelectorAll("h3")[2], type: "tag"},
+ ];
+
+ let results = walkerSearch.search("h3");
+ isDeeply(results, expected, "Search works with tag results");
+
+ yield mutateDocumentAndWaitForMutation(() => {
+ expected[0].node.remove();
+ });
+
+ results = walkerSearch.search("h3");
+ isDeeply(results, [
+ expected[1],
+ expected[2]
+ ], "Results are updated after removal");
+
+ yield new promise(resolve => {
+ info("Waiting for a mutation to happen");
+ let observer = new inspectee.defaultView.MutationObserver(() => {
+ resolve();
+ });
+ observer.observe(inspectee, {attributes: true, subtree: true});
+ inspectee.body.setAttribute("h3", "true");
+ });
+
+ results = walkerSearch.search("h3");
+ isDeeply(results, [
+ {node: inspectee.body, type: "attributeName"},
+ expected[1],
+ expected[2]
+ ], "Results are updated after addition");
+
+ yield new promise(resolve => {
+ info("Waiting for a mutation to happen");
+ let observer = new inspectee.defaultView.MutationObserver(() => {
+ resolve();
+ });
+ observer.observe(inspectee, {attributes: true, childList: true, subtree: true});
+ inspectee.body.removeAttribute("h3");
+ expected[1].node.remove();
+ expected[2].node.remove();
+ });
+
+ results = walkerSearch.search("h3");
+ is(results.length, 0, "Results are updated after removal");
+
+ runNextTest();
+ });
+
+ runNextTest();
+
+ function mutateDocumentAndWaitForMutation(mutationFn) {
+ return new promise(resolve => {
+ info("Listening to markup mutation on the inspectee");
+ let observer = new inspectee.defaultView.MutationObserver(resolve);
+ observer.observe(inspectee, {childList: true, subtree: true});
+ mutationFn();
+ });
+ }
+};
+ </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/mochitest/test_inspector-traversal.html b/devtools/server/tests/mochitest/test_inspector-traversal.html
new file mode 100644
index 000000000..ffac8e915
--- /dev/null
+++ b/devtools/server/tests/mochitest/test_inspector-traversal.html
@@ -0,0 +1,354 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug </title>
+
+ <script type="application/javascript" 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;version=1.8" src="inspector-helpers.js"></script>
+ <script type="application/javascript;version=1.8">
+const inspector = require("devtools/server/actors/inspector");
+
+window.onload = function() {
+ SimpleTest.waitForExplicitFinish();
+ runNextTest();
+}
+
+var gInspectee = null;
+var gClient = null;
+var gWalker = null;
+var checkActorIDs = [];
+
+function assertOwnership() {
+ assertOwnershipTrees(gWalker);
+}
+addTest(function setup() {
+ let url = document.getElementById("inspectorContent").href;
+ attachURL(url, function(err, client, tab, doc) {
+ gInspectee = doc;
+ let {InspectorFront} = require("devtools/shared/fronts/inspector");
+ let inspector = InspectorFront(client, tab);
+ promiseDone(inspector.getWalker().then(walker => {
+ ok(walker, "getWalker() should return an actor.");
+ gClient = client;
+ gWalker = walker;
+ }).then(runNextTest));
+ });
+});
+
+addTest(function testWalkerRoot() {
+ // Make sure that refetching the root document of the walker returns the same
+ // actor as the getWalker returned.
+ promiseDone(gWalker.document().then(root => {
+ ok(root === gWalker.rootNode, "Re-fetching the document node should match the root document node.");
+ checkActorIDs.push(root.actorID);
+ assertOwnership();
+ }).then(runNextTest));
+});
+
+addTest(function testInnerHTML() {
+ promiseDone(gWalker.documentElement().then(docElement => {
+ return gWalker.innerHTML(docElement);
+ }).then(longstring => {
+ return longstring.string();
+ }).then(innerHTML => {
+ ok(innerHTML === gInspectee.documentElement.innerHTML, "innerHTML should match");
+ }).then(runNextTest));
+});
+
+addTest(function testOuterHTML() {
+ promiseDone(gWalker.documentElement().then(docElement => {
+ return gWalker.outerHTML(docElement);
+ }).then(longstring => {
+ return longstring.string();
+ }).then(outerHTML => {
+ ok(outerHTML === gInspectee.documentElement.outerHTML, "outerHTML should match");
+ }).then(runNextTest));
+});
+
+addTest(function testSetOuterHTMLNode() {
+ let newHTML = "<p id=\"edit-html-done\">after edit</p>";
+ promiseDone(gWalker.querySelector(gWalker.rootNode, "#edit-html").then(node => {
+ return gWalker.setOuterHTML(node, newHTML);
+ }).then(() => {
+ return gWalker.querySelector(gWalker.rootNode, "#edit-html-done");
+ }).then(node => {
+ return gWalker.outerHTML(node);
+ }).then(longstring => {
+ return longstring.string();
+ }).then(outerHTML => {
+ is(outerHTML, newHTML, "outerHTML has been updated");
+ }).then(() => {
+ return gWalker.querySelector(gWalker.rootNode, "#edit-html");
+ }).then(node => {
+ ok(!node, "The node with the old ID cannot be selected anymore");
+ }).then(runNextTest));
+});
+
+addTest(function testQuerySelector() {
+ promiseDone(gWalker.querySelector(gWalker.rootNode, "#longlist").then(node => {
+ is(node.getAttribute("data-test"), "exists", "should have found the right node");
+ assertOwnership();
+ }).then(() => {
+ return gWalker.querySelector(gWalker.rootNode, "unknownqueryselector").then(node => {
+ ok(!node, "Should not find a node here.");
+ assertOwnership();
+ });
+ }).then(runNextTest));
+});
+
+addTest(function testQuerySelectors() {
+ let nodeList = null;
+ let firstNode = null;
+ let nodeListID = null;
+ promiseDone(gWalker.querySelectorAll(gWalker.rootNode, "#longlist div").then(list => {
+ nodeList = list;
+ is(nodeList.length, 26, "Expect 26 div children.");
+ assertOwnership();
+ return nodeList.item(0);
+ }).then(node => {
+ firstNode = node;
+ checkActorIDs.push(node.actorID);
+ is(node.id, "a", "First child should be a");
+ assertOwnership();
+ return nodeList.items();
+ }).then(nodes => {
+ is(nodes.length, 26, "Expect 26 nodes");
+ is(nodes[0], firstNode, "First node should be reused.");
+ ok(nodes[0]._parent, "Parent node should be set.");
+ ok(nodes[0]._next || nodes[0]._prev, "Siblings should be set.");
+ ok(nodes[25]._next || nodes[25]._prev, "Siblings of " + nodes[25] + " should be set.");
+ assertOwnership();
+ return nodeList.items(-1);
+ }).then(nodes => {
+ is(nodes.length, 1, "Expect 1 node")
+ is(nodes[0].id, "z", "Expect it to be the last node.");
+ checkActorIDs.push(nodes[0].actorID);
+ // Save the node list ID so we can ensure it was destroyed.
+ nodeListID = nodeList.actorID;
+ assertOwnership();
+ return nodeList.release();
+ }).then(() => {
+ ok(!nodeList.actorID, "Actor should have been destroyed.");
+ assertOwnership();
+ return checkMissing(gClient, nodeListID);
+ }).then(runNextTest));
+});
+
+// Helper to check the response of requests that return hasFirst/hasLast/nodes
+// node lists (like `children` and `siblings`)
+function nodeArrayChecker(first, last, ids) {
+ return function(response) {
+ is(response.hasFirst, first, "Should " + (first ? "" : "not ") + " have the first node.");
+ is(response.hasLast, last, "Should " + (last ? "" : "not ") + " have the last node.");
+ is(response.nodes.length, ids.length, "Should have " + ids.length + " children listed.");
+ let responseIds = '';
+ for (node of response.nodes) {
+ responseIds += node.id;
+ }
+ is(responseIds, ids, "Correct nodes were returned.");
+ assertOwnership();
+ }
+}
+
+addTest(function testNoChildren() {
+ promiseDone(gWalker.querySelector(gWalker.rootNode, "#empty").then(empty => {
+ assertOwnership();
+ return gWalker.children(empty).then(nodeArrayChecker(true, true, ""));
+ }).then(runNextTest));
+});
+
+addTest(function testLongListTraversal() {
+ var longList;
+ var allChildren;
+ promiseDone(gWalker.querySelector(gWalker.rootNode, "#longlist").then(node => {
+ longList = node;
+ // First call with no options, expect all children.
+ assertOwnership();
+ return gWalker.children(longList).then(response => {
+ nodeArrayChecker(true, true, "abcdefghijklmnopqrstuvwxyz")(response);
+ allChildren = response.nodes;
+ assertOwnership();
+ });
+ }).then(() => {
+ // maxNodes should limit us to the first 5 nodes.
+ assertOwnership();
+ return gWalker.children(longList, { maxNodes: 5 }).then(nodeArrayChecker(true, false, 'abcde'));
+ }).then(() => {
+ assertOwnership();
+ // maxNodes with the second item centered should still give us the first 5 nodes.
+ return gWalker.children(longList, { maxNodes: 5, center: allChildren[1] }).then(
+ nodeArrayChecker(true, false, 'abcde')
+ );
+ }).then(() => {
+ // maxNodes with a center in the middle of the list should put that item in the middle
+ let center = allChildren[13];
+ is(center.id, 'n', "Make sure I know how to count letters.");
+ return gWalker.children(longList, { maxNodes: 5, center: center }).then(
+ nodeArrayChecker(false, false, 'lmnop')
+ );
+ }).then(() => {
+ // maxNodes with the second-to-last item centered should give us the last 5 nodes.
+ return gWalker.children(longList, { maxNodes: 5, center: allChildren[24] }).then(
+ nodeArrayChecker(false, true, 'vwxyz')
+ );
+ }).then(() => {
+ // maxNodes with a start in the middle should start at that node and fetch 5
+ let start = allChildren[13];
+ is(start.id, 'n', "Make sure I know how to count letters.")
+ return gWalker.children(longList, { maxNodes: 5, start: start }).then(
+ nodeArrayChecker(false, false, 'nopqr')
+ );
+ }).then(() => {
+ // maxNodes near the end should only return what's left
+ return gWalker.children(longList, { maxNodes: 5, start: allChildren[24] }).then(
+ nodeArrayChecker(false, true, 'yz')
+ );
+ }).then(runNextTest));
+});
+
+addTest(function testObjectNodeChildren() {
+ promiseDone(
+ gWalker.querySelector(gWalker.rootNode, "object")
+ .then(object => gWalker.children(object))
+ .then(nodeArrayChecker(true, true, "1"))
+ .then(runNextTest));
+});
+
+addTest(function testSiblings() {
+ promiseDone(gWalker.querySelector(gWalker.rootNode, "#a").then(a => {
+ return gWalker.siblings(a, { maxNodes: 5, center: a }).then(nodeArrayChecker(true, false, "abcde"));
+ }).then(() => {
+ return gWalker.siblings(gWalker.rootNode).then(response => {
+ ok(response.hasFirst && response.hasLast, "Has first and last.");
+ is(response.nodes.length, 1, "Has only the document element.");
+ ok(response.nodes[0] === gWalker.rootNode, "Document element is its own sibling.");
+ });
+ }).then(runNextTest));
+});
+
+addTest(function testNextSibling() {
+ promiseDone(gWalker.querySelector(gWalker.rootNode, "#y").then(y => {
+ is(y.id, "y", "Got the right node.");
+ return gWalker.nextSibling(y);
+ }).then(z => {
+ is(z.id, "z", "nextSibling got the next node.");
+ return gWalker.nextSibling(z);
+ }).then(nothing => {
+ is(nothing, null, "nextSibling on the last node returned null.");
+ }).then(runNextTest));
+});
+
+addTest(function testPreviousSibling() {
+ promiseDone(gWalker.querySelector(gWalker.rootNode, "#b").then(b => {
+ is(b.id, "b", "Got the right node.");
+ return gWalker.previousSibling(b);
+ }).then(a => {
+ is(a.id, "a", "nextSibling got the next node.");
+ return gWalker.previousSibling(a);
+ }).then(nothing => {
+ is(nothing, null, "previousSibling on the first node returned null.");
+ }).then(runNextTest));
+});
+
+
+addTest(function testFrameTraversal() {
+ promiseDone(gWalker.querySelector(gWalker.rootNode, "#childFrame").then(childFrame => {
+ return gWalker.children(childFrame);
+ }).then(children => {
+ let nodes = children.nodes;
+ is(nodes.length, 1, "There should be only one child of the iframe");
+ is(nodes[0].nodeType, Node.DOCUMENT_NODE, "iframe child should be a document node");
+ return gWalker.querySelector(nodes[0], "#z");
+ }).then(childDocumentZ => {
+ return gWalker.parents(childDocumentZ);
+ }).then(parents => {
+ // Expected set of parent tag names for this item:
+ let expectedParents = ['DIV', 'BODY', 'HTML', '#document', 'IFRAME', 'BODY', 'HTML', '#document'];
+ for (let parent of parents) {
+ let expected = expectedParents.shift();
+ is(parent.nodeName, expected, "Got expected parent");
+ }
+ }).then(runNextTest));
+});
+
+addTest(function testLongValue() {
+ const testSummaryLength = 10;
+ inspector.setValueSummaryLength(testSummaryLength);
+ SimpleTest.registerCleanupFunction(function() {
+ inspector.setValueSummaryLength(inspector.DEFAULT_VALUE_SUMMARY_LENGTH);
+ });
+
+ let longstringText = gInspectee.getElementById("longstring").firstChild.nodeValue;
+
+ promiseDone(gWalker.querySelector(gWalker.rootNode, "#longstring").then(node => {
+ ok(!node.inlineTextChild, "Text is too long to be inlined");
+ // Now we need to get the text node child...
+ return gWalker.children(node, { maxNodes: 1 });
+ }).then(children => {
+ let textNode = children.nodes[0];
+ is(textNode.nodeType, Node.TEXT_NODE, "Value should be a text node");
+ return textNode;
+ }).then(textNode => {
+ return textNode.getNodeValue();
+ }).then(value => {
+ return value.string();
+ }).then(valueStr => {
+ is(valueStr, longstringText, "Full node value should match the string from the document.");
+ }).then(runNextTest));
+});
+
+addTest(function testShortValue() {
+ let shortstringText = gInspectee.getElementById("shortstring").firstChild.nodeValue;
+
+ promiseDone(gWalker.querySelector(gWalker.rootNode, "#shortstring").then(node => {
+ ok(!!node.inlineTextChild, "Text is short enough to be inlined");
+ // Now we need to get the text node child...
+ return gWalker.children(node, { maxNodes: 1 });
+ }).then(children => {
+ let textNode = children.nodes[0];
+ is(textNode.nodeType, Node.TEXT_NODE, "Value should be a text node");
+ return textNode;
+ }).then(textNode => {
+ return textNode.getNodeValue();
+ }).then(value => {
+ return value.string();
+ }).then(valueStr => {
+ is(valueStr, shortstringText, "Full node value should match the string from the document.");
+ }).then(runNextTest));
+});
+
+addTest(function testReleaseWalker() {
+ checkActorIDs.push(gWalker.actorID);
+
+ promiseDone(gWalker.release().then(() => {
+ let promises = Array.from(checkActorIDs, (id) => checkMissing(gClient, id));
+ return promise.all(promises)
+ }).then(runNextTest));
+});
+
+addTest(function cleanup() {
+ delete gWalker;
+ delete gInspectee;
+ delete gClient;
+ 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/mochitest/test_inspector_getImageData-wait-for-load.html b/devtools/server/tests/mochitest/test_inspector_getImageData-wait-for-load.html
new file mode 100644
index 000000000..63eb0bd3c
--- /dev/null
+++ b/devtools/server/tests/mochitest/test_inspector_getImageData-wait-for-load.html
@@ -0,0 +1,136 @@
+<!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 type="application/javascript" 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;version=1.8" src="inspector-helpers.js"></script>
+ <script type="application/javascript;version=1.8">
+
+const flags = require("devtools/shared/flags");
+const wasTesting = flags.testing;
+SimpleTest.registerCleanupFunction(() => flags.testing = wasTesting);
+
+const PATH = "http://mochi.test:8888/chrome/devtools/server/tests/mochitest/";
+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();
+}
+
+var gImg = null;
+var gNodeFront = null;
+var gWalker = null;
+
+addTest(function setup() {
+ let url = document.getElementById("inspectorContent").href;
+ attachURL(url, function(err, client, tab, doc) {
+ let {InspectorFront} = require("devtools/shared/fronts/inspector");
+ let inspector = InspectorFront(client, tab);
+
+ promiseDone(inspector.getWalker().then(walker => {
+ gWalker = walker;
+ return walker.querySelector(gWalker.rootNode, "img.custom").then(img => {
+ gNodeFront = img;
+ gImg = doc.querySelector("img.custom");
+
+ ok(gNodeFront, "Got the image NodeFront.");
+ ok(gImg, "Got the image Node.");
+ });
+ }).then(runNextTest));
+ });
+});
+
+addTest(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.
+ flags.testing = false;
+
+ gImg.src = TIMEOUT_IMAGE;
+
+ info("Calling getImageData().");
+ ensureRejects(gNodeFront.getImageData(), "Timeout image").then(runNextTest);
+});
+
+addTest(function testNonExistentImage() {
+ info("Testing that non-existent image causes a rejection.");
+
+ // This test shouldn't hit the timeout.
+ flags.testing = true;
+
+ gImg.src = NONEXISTENT_IMAGE;
+
+ info("Calling getImageData().");
+ ensureRejects(gNodeFront.getImageData(), "Non-existent image").then(runNextTest);
+});
+
+addTest(function testDelayedImage() {
+ info("Testing that the method waits for an image to load.");
+
+ // This test shouldn't hit the timeout.
+ flags.testing = true;
+
+ gImg.src = DELAYED_IMAGE;
+
+ info("Calling getImageData().");
+ checkImageData(gNodeFront.getImageData()).then(runNextTest);
+});
+
+addTest(function cleanup() {
+ delete gImg;
+ delete gNodeFront
+ delete gWalker;
+ 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/mochitest/test_inspector_getImageData.html b/devtools/server/tests/mochitest/test_inspector_getImageData.html
new file mode 100644
index 000000000..be3c24194
--- /dev/null
+++ b/devtools/server/tests/mochitest/test_inspector_getImageData.html
@@ -0,0 +1,166 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=932937
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 932937</title>
+
+ <script type="application/javascript" 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;version=1.8" src="inspector-helpers.js"></script>
+ <script type="application/javascript;version=1.8">
+const inspector = require("devtools/shared/fronts/inspector");
+
+window.onload = function() {
+ SimpleTest.waitForExplicitFinish();
+ runNextTest();
+}
+
+var gWalker = null;
+
+addTest(function setup() {
+ let url = document.getElementById("inspectorContent").href;
+ attachURL(url, function(err, client, tab, doc) {
+ let {InspectorFront} = require("devtools/shared/fronts/inspector");
+ let inspector = InspectorFront(client, tab);
+
+ promiseDone(inspector.getWalker().then(walker => {
+ gWalker = walker;
+ }).then(runNextTest));
+ });
+});
+
+addTest(function testLargeImage() {
+ // Select the image node from the test page
+ gWalker.querySelector(gWalker.rootNode, ".big-horizontal").then(img => {
+ ok(img, "Image node found in the test page");
+ ok(img.getImageData, "Image node has the getImageData function");
+
+ img.getImageData(100).then(imageData => {
+ ok(imageData.data, "Image data actor was sent back");
+ ok(imageData.size, "Image size info was sent back too");
+ is(imageData.size.naturalWidth, 5333, "Natural width of the image correct");
+ is(imageData.size.naturalHeight, 3000, "Natural width of the image correct");
+ ok(imageData.size.resized, "Image was resized");
+
+ imageData.data.string().then(str => {
+ ok(str, "We have an image data string!");
+ testResizing(imageData, str);
+ });
+ });
+ });
+});
+
+addTest(function testLargeCanvas() {
+ // Select the canvas node from the test page
+ gWalker.querySelector(gWalker.rootNode, ".big-vertical").then(canvas => {
+ ok(canvas, "Image node found in the test page");
+ ok(canvas.getImageData, "Image node has the getImageData function");
+
+ canvas.getImageData(350).then(imageData => {
+ ok(imageData.data, "Image data actor was sent back");
+ ok(imageData.size, "Image size info was sent back too");
+ is(imageData.size.naturalWidth, 1000, "Natural width of the image correct");
+ is(imageData.size.naturalHeight, 2000, "Natural width of the image correct");
+ ok(imageData.size.resized, "Image was resized");
+
+ imageData.data.string().then(str => {
+ ok(str, "We have an image data string!");
+ testResizing(imageData, str);
+ });
+ });
+ });
+});
+
+addTest(function testSmallImage() {
+ // Select the small image node from the test page
+ gWalker.querySelector(gWalker.rootNode, ".small").then(img => {
+ ok(img, "Image node found in the test page");
+ ok(img.getImageData, "Image node has the getImageData function");
+
+ img.getImageData().then(imageData => {
+ ok(imageData.data, "Image data actor was sent back");
+ ok(imageData.size, "Image size info was sent back too");
+ is(imageData.size.naturalWidth, 245, "Natural width of the image correct");
+ is(imageData.size.naturalHeight, 240, "Natural width of the image correct");
+ ok(!imageData.size.resized, "Image was NOT resized");
+
+ imageData.data.string().then(str => {
+ ok(str, "We have an image data string!");
+ testResizing(imageData, str);
+ });
+ });
+ });
+});
+
+addTest(function testDataImage() {
+ // Select the data image node from the test page
+ gWalker.querySelector(gWalker.rootNode, ".data").then(img => {
+ ok(img, "Image node found in the test page");
+ ok(img.getImageData, "Image node has the getImageData function");
+
+ img.getImageData(14).then(imageData => {
+ ok(imageData.data, "Image data actor was sent back");
+ ok(imageData.size, "Image size info was sent back too");
+ is(imageData.size.naturalWidth, 28, "Natural width of the image correct");
+ is(imageData.size.naturalHeight, 28, "Natural width of the image correct");
+ ok(imageData.size.resized, "Image was resized");
+
+ imageData.data.string().then(str => {
+ ok(str, "We have an image data string!");
+ testResizing(imageData, str);
+ });
+ });
+ });
+});
+
+addTest(function testNonImgOrCanvasElements() {
+ gWalker.querySelector(gWalker.rootNode, "body").then(body => {
+ ensureRejects(body.getImageData(), "Invalid element").then(runNextTest);
+ });
+});
+
+addTest(function cleanup() {
+ delete gWalker;
+ runNextTest();
+});
+
+/**
+ * Checks if the server told the truth about resizing the image
+ */
+function testResizing(imageData, str) {
+ let img = document.createElement("img");
+ img.addEventListener("load", () => {
+ let resized = !(img.naturalWidth == imageData.size.naturalWidth &&
+ img.naturalHeight == imageData.size.naturalHeight);
+ is(imageData.size.resized, resized, "Server told the truth about resizing");
+ runNextTest();
+ }, false);
+ 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/mochitest/test_inspector_getImageDataFromURL.html b/devtools/server/tests/mochitest/test_inspector_getImageDataFromURL.html
new file mode 100644
index 000000000..473a62275
--- /dev/null
+++ b/devtools/server/tests/mochitest/test_inspector_getImageDataFromURL.html
@@ -0,0 +1,114 @@
+<!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 type="application/javascript" 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;version=1.8" src="inspector-helpers.js"></script>
+ <script type="application/javascript;version=1.8">
+
+const flags = require("devtools/shared/flags");
+const wasTesting = flags.testing;
+SimpleTest.registerCleanupFunction(() => flags.testing = wasTesting);
+
+const PATH = "http://mochi.test:8888/chrome/devtools/server/tests/mochitest/";
+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();
+}
+
+var gInspector = null;
+
+addTest(function setup() {
+ let url = document.getElementById("inspectorContent").href;
+ attachURL(url, function(err, client, tab, doc) {
+ let {InspectorFront} = require("devtools/shared/fronts/inspector");
+ gInspector = InspectorFront(client, tab);
+ runNextTest();
+ });
+});
+
+addTest(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.
+ flags.testing = false;
+
+ ensureRejects(gInspector.getImageDataFromURL(TIMEOUT_IMAGE),
+ "Image that loads for too long").then(runNextTest);
+});
+
+addTest(function testNonExistentImage() {
+ info("Testing that non-existent image causes a rejection.");
+
+ // This test shouldn't hit the timeout.
+ flags.testing = true;
+
+ ensureRejects(gInspector.getImageDataFromURL(NONEXISTENT_IMAGE),
+ "Non-existent image").then(runNextTest);
+});
+
+addTest(function testNormalImage() {
+ info("Testing that the method waits for an image to load.");
+
+ // This test shouldn't hit the timeout.
+ flags.testing = true;
+
+ checkImageData(gInspector.getImageDataFromURL(DELAYED_IMAGE)).then(runNextTest);
+});
+
+addTest(function cleanup() {
+ delete gInspector;
+ 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/mochitest/test_inspector_getNodeFromActor.html b/devtools/server/tests/mochitest/test_inspector_getNodeFromActor.html
new file mode 100644
index 000000000..6c06d8a7b
--- /dev/null
+++ b/devtools/server/tests/mochitest/test_inspector_getNodeFromActor.html
@@ -0,0 +1,88 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1155653
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1155653</title>
+
+ <script type="application/javascript" 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;version=1.8" src="inspector-helpers.js"></script>
+ <script type="application/javascript;version=1.8">
+const inspector = require("devtools/shared/fronts/inspector");
+
+window.onload = function() {
+ SimpleTest.waitForExplicitFinish();
+ runNextTest();
+}
+
+var gWalker;
+
+addTest(function() {
+ let url = document.getElementById("inspectorContent").href;
+ attachURL(url, function(err, client, tab, doc) {
+ let {InspectorFront} = require("devtools/shared/fronts/inspector");
+ let inspector = InspectorFront(client, tab);
+
+ promiseDone(inspector.getWalker().then(walker => {
+ gWalker = walker;
+ }).then(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,
+ ["tabActor", "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/mochitest/test_makeGlobalObjectReference.html b/devtools/server/tests/mochitest/test_makeGlobalObjectReference.html
new file mode 100644
index 000000000..8bd7e0476
--- /dev/null
+++ b/devtools/server/tests/mochitest/test_makeGlobalObjectReference.html
@@ -0,0 +1,86 @@
+<!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 type="application/javascript" 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>
+
+Components.utils.import("resource://gre/modules/jsdebugger.jsm");
+addDebuggerToGlobal(this);
+
+window.onload = function () {
+ SimpleTest.waitForExplicitFinish();
+
+ var iframe = document.createElement("iframe");
+ iframe.src = "data:text/html,<html>The word 'smorgasbord', spoken by an adorably plump child, symbolizing prosperity</html>";
+ iframe.onload = iframeOnLoad;
+ document.body.appendChild(iframe);
+
+ function iframeOnLoad() {
+ var dbg = new Debugger;
+
+ var g1o = iframe.contentWindow; // 'o' for 'outer window'
+ ok(!dbg.hasDebuggee(g1o), "iframe is not initially a debuggee");
+
+ // Like addDebuggee, makeGlobalObjectReference innerizes.
+ // 'i' stands for 'inner window'.
+ // 'DO' stands for 'Debugger.Object'.
+ var 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.
+ var 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");
+
+ var iframe2 = document.createElement("iframe");
+ iframe2.src = "data:text/html,<html>Her retrospection, in hindsight, was prescient.</html>";
+ iframe2.onload = iframe2OnLoad;
+ document.body.appendChild(iframe2);
+
+ function iframe2OnLoad() {
+ // makeGlobalObjectReference dereferences CCWs.
+ var g2o = iframe2.contentWindow;
+ g2o.g1o = g1o;
+
+ var g2iDO = dbg.addDebuggee(g2o);
+ var 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/mochitest/test_memory.html b/devtools/server/tests/mochitest/test_memory.html
new file mode 100644
index 000000000..9f191da76
--- /dev/null
+++ b/devtools/server/tests/mochitest/test_memory.html
@@ -0,0 +1,37 @@
+<!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 type="application/javascript" 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;version=1.8"></script>
+<script>
+window.onload = function() {
+ SimpleTest.waitForExplicitFinish();
+
+ Task.spawn(function* () {
+ var { memory, client } = yield startServerAndGetSelectedTabMemory();
+ var measurement = yield 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");
+ destroyServerAndFinish(client);
+ });
+};
+</script>
+</pre>
+</body>
+</html>
diff --git a/devtools/server/tests/mochitest/test_memory_allocations_01.html b/devtools/server/tests/mochitest/test_memory_allocations_01.html
new file mode 100644
index 000000000..2ed9b74bc
--- /dev/null
+++ b/devtools/server/tests/mochitest/test_memory_allocations_01.html
@@ -0,0 +1,98 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+Bug 1067491 - Test recording allocations.
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Memory monitoring actor test</title>
+ <script type="application/javascript" 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;version=1.8"></script>
+<script>
+window.onload = function() {
+ SimpleTest.waitForExplicitFinish();
+
+ Task.spawn(function* () {
+ var { memory, client } = yield startServerAndGetSelectedTabMemory();
+ yield memory.attach();
+
+ yield memory.startRecordingAllocations();
+ ok(true, "Can start recording allocations");
+
+ // Allocate some objects.
+
+ var alloc1, alloc2, alloc3;
+ (function outer() {
+ (function middle() {
+ (function inner() {
+ alloc1 = {}; alloc1.line = Error().lineNumber;
+ alloc2 = []; alloc2.line = Error().lineNumber;
+ alloc3 = new function() {}; alloc3.line = Error().lineNumber;
+ }());
+ }());
+ }());
+
+ var response = yield memory.getAllocations();
+
+ yield memory.stopRecordingAllocations();
+ ok(true, "Can stop recording allocations");
+
+ // Filter out allocations by library and test code, and get only the
+ // allocations that occurred in our test case above.
+
+ function isTestAllocation(alloc) {
+ var frame = response.frames[alloc];
+ return frame
+ && frame.functionDisplayName === "inner"
+ && (frame.line === alloc1.line
+ || frame.line === alloc2.line
+ || frame.line === alloc3.line);
+ }
+
+ var testAllocations = response.allocations.filter(isTestAllocation);
+ ok(testAllocations.length >= 3,
+ "Should find our 3 test allocations (plus some allocations for the error "
+ + "objects used to get line numbers)");
+
+ // For each of the test case's allocations, ensure that the parent frame
+ // indices are correct. Also test that we did get an allocation at each
+ // line we expected (rather than a bunch on the first line and none on the
+ // others, etc).
+
+ var expectedLines = new Set([alloc1.line, alloc2.line, alloc3.line]);
+
+ for (var alloc of testAllocations) {
+ var innerFrame = response.frames[alloc];
+ ok(innerFrame, "Should get the inner frame");
+ is(innerFrame.functionDisplayName, "inner");
+ expectedLines.delete(innerFrame.line);
+
+ var middleFrame = response.frames[innerFrame.parent];
+ ok(middleFrame, "Should get the middle frame");
+ is(middleFrame.functionDisplayName, "middle");
+
+ var outerFrame = response.frames[middleFrame.parent];
+ ok(outerFrame, "Should get the outer frame");
+ is(outerFrame.functionDisplayName, "outer");
+
+ // Not going to test the rest of the frames because they are Task.jsm
+ // and promise frames and it gets gross. Plus, I wouldn't want this test
+ // to start failing if they changed their implementations in a way that
+ // added or removed stack frames here.
+ }
+
+ is(expectedLines.size, 0,
+ "Should have found all the expected lines");
+
+ yield memory.detach();
+ destroyServerAndFinish(client);
+ });
+};
+</script>
+</pre>
+</body>
+</html>
diff --git a/devtools/server/tests/mochitest/test_memory_allocations_02.html b/devtools/server/tests/mochitest/test_memory_allocations_02.html
new file mode 100644
index 000000000..0133a27b0
--- /dev/null
+++ b/devtools/server/tests/mochitest/test_memory_allocations_02.html
@@ -0,0 +1,76 @@
+<!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 type="application/javascript" 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;version=1.8"></script>
+<script>
+window.onload = function() {
+ SimpleTest.waitForExplicitFinish();
+
+ Task.spawn(function* () {
+ var { memory, client } = yield startServerAndGetSelectedTabMemory();
+ yield memory.attach();
+
+ var allocs = [];
+ var eventsFired = 0;
+ var intervalId = null;
+ function onAlloc () {
+ eventsFired++;
+ }
+ function startAllocating () {
+ intervalId = setInterval(() => {
+ for (var i = 100000; --i;) {
+ allocs.push(new Object());
+ }
+ }, 1);
+ }
+ function stopAllocating () {
+ clearInterval(intervalId);
+ }
+
+ memory.on("allocations", onAlloc);
+
+ yield memory.startRecordingAllocations({
+ drainAllocationsTimeout: 10
+ });
+
+ yield waitUntil(() => eventsFired > 5);
+ ok(eventsFired > 5, "Some allocation events fired without allocating much via auto drain");
+ yield memory.stopRecordingAllocations();
+
+ // Set a really high auto drain timer so we can test if
+ // it fires on GC
+ eventsFired = 0;
+ var startTime = performance.now();
+ var drainTimer = 1000000;
+ yield memory.startRecordingAllocations({
+ drainAllocationsTimeout: drainTimer
+ });
+
+ startAllocating();
+ yield waitUntil(() => {
+ Cu.forceGC();
+ return eventsFired > 1;
+ });
+ stopAllocating();
+ ok(performance.now() - drainTimer < startTime, "Allocation events fired on GC before timer");
+ yield memory.stopRecordingAllocations();
+
+ memory.off("allocations", onAlloc);
+ yield memory.detach();
+ destroyServerAndFinish(client);
+ });
+};
+</script>
+</pre>
+</body>
+</html>
diff --git a/devtools/server/tests/mochitest/test_memory_allocations_03.html b/devtools/server/tests/mochitest/test_memory_allocations_03.html
new file mode 100644
index 000000000..b7d18d7ed
--- /dev/null
+++ b/devtools/server/tests/mochitest/test_memory_allocations_03.html
@@ -0,0 +1,78 @@
+<!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 type="application/javascript" 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;version=1.8"></script>
+<script>
+window.onload = function() {
+ SimpleTest.waitForExplicitFinish();
+
+ Task.spawn(function* () {
+ var { memory, client } = yield startServerAndGetSelectedTabMemory();
+ yield memory.attach();
+
+ yield memory.startRecordingAllocations();
+
+ // Allocate twice with the exact same stack (hence setTimeout rather than
+ // allocating directly in the generator), but with getAllocations() calls in
+ // between.
+
+ var allocs = [];
+ function allocator() {
+ allocs.push({});
+ }
+
+ setTimeout(allocator, 1);
+ yield waitForTime(2);
+ var first = yield memory.getAllocations();
+
+ setTimeout(allocator, 1);
+ yield waitForTime(2);
+ var second = yield memory.getAllocations();
+
+ yield 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("Checking frames at index " + i + ":");
+ 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 (var i = 0; i < first.frames.length; i++) {
+ assertSameFrame(first.frames[i], second.frames[i]);
+ }
+
+ yield memory.detach();
+ destroyServerAndFinish(client);
+ });
+};
+</script>
+</pre>
+</body>
+</html>
diff --git a/devtools/server/tests/mochitest/test_memory_allocations_04.html b/devtools/server/tests/mochitest/test_memory_allocations_04.html
new file mode 100644
index 000000000..5568736d3
--- /dev/null
+++ b/devtools/server/tests/mochitest/test_memory_allocations_04.html
@@ -0,0 +1,60 @@
+<!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 type="application/javascript" 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;version=1.8"></script>
+<script>
+window.onload = function() {
+ SimpleTest.waitForExplicitFinish();
+
+ Task.spawn(function* () {
+ var { memory, client } = yield startServerAndGetSelectedTabMemory();
+ yield memory.attach();
+
+ var allocs = [];
+ function allocator() {
+ for (var i = 0; i < 100; i++) {
+ allocs.push({});
+ }
+ }
+
+ var testProbability = Task.async(function* (p, expected) {
+ info("probability = " + p);
+ yield memory.startRecordingAllocations({
+ probability: p
+ });
+ allocator();
+ var response = yield memory.getAllocations();
+ yield memory.stopRecordingAllocations();
+ return response.allocations.length;
+ });
+
+ is((yield testProbability(0.0)), 0,
+ "With probability = 0.0, we shouldn't get any allocations.");
+
+ ok((yield 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).
+
+ yield memory.detach();
+ destroyServerAndFinish(client);
+ });
+};
+</script>
+</pre>
+</body>
+</html>
diff --git a/devtools/server/tests/mochitest/test_memory_allocations_05.html b/devtools/server/tests/mochitest/test_memory_allocations_05.html
new file mode 100644
index 000000000..0eeb7bd16
--- /dev/null
+++ b/devtools/server/tests/mochitest/test_memory_allocations_05.html
@@ -0,0 +1,87 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+Bug 1068144 - Test getting the timestamps for allocations.
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Memory monitoring actor test</title>
+ <script type="application/javascript" 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;version=1.8"></script>
+<script>
+window.onload = function() {
+ SimpleTest.waitForExplicitFinish();
+
+ Task.spawn(function* () {
+ var { memory, client } = yield startServerAndGetSelectedTabMemory();
+ yield memory.attach();
+
+ var allocs = [];
+ function allocator() {
+ allocs.push(new Object);
+ }
+
+ // Using setTimeout results in wildly varying delays that make it hard to
+ // test our timestamps and results in intermittent failures. Instead, we
+ // actually spin an empty loop for a whole millisecond.
+ function actuallyWaitOneWholeMillisecond() {
+ var start = window.performance.now();
+ while (window.performance.now() - start < 1.000) ;
+ }
+
+ yield memory.startRecordingAllocations();
+
+ allocator();
+ actuallyWaitOneWholeMillisecond();
+ allocator();
+ actuallyWaitOneWholeMillisecond();
+ allocator();
+
+ var response = yield memory.getAllocations();
+ yield 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.");
+
+ var allocatorIndices = response.allocations
+ .map(function (a, idx) {
+ var frame = response.frames[a];
+ if (frame && frame.functionDisplayName === "allocator") {
+ return idx;
+ }
+ })
+ .filter(function (idx) {
+ return idx !== undefined;
+ });
+
+ is(allocatorIndices.length, 3, "Should have our 3 allocations from the `allocator` timeouts.");
+
+ var lastTimestamp;
+ for (var i = 0; i < 3; i++) {
+ var timestamp = response.allocationsTimestamps[allocatorIndices[i]];
+ info("timestamp", timestamp);
+ ok(timestamp, "We should have a timestamp for the `allocator` allocation.");
+
+ if (lastTimestamp) {
+ var delta = timestamp - lastTimestamp;
+ info("delta since last timestamp", delta);
+ ok(delta >= 1 /* ms */,
+ "The timestamp should be about 1 ms after the last timestamp.");
+ }
+
+ lastTimestamp = timestamp;
+ }
+
+ yield memory.detach();
+ destroyServerAndFinish(client);
+ });
+};
+</script>
+</pre>
+</body>
+</html>
diff --git a/devtools/server/tests/mochitest/test_memory_allocations_06.html b/devtools/server/tests/mochitest/test_memory_allocations_06.html
new file mode 100644
index 000000000..56a9f8041
--- /dev/null
+++ b/devtools/server/tests/mochitest/test_memory_allocations_06.html
@@ -0,0 +1,49 @@
+<!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 type="application/javascript" 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;version=1.8"></script>
+<script>
+window.onload = function() {
+ SimpleTest.waitForExplicitFinish();
+
+ Task.spawn(function* () {
+ var { memory, client } = yield startServerAndGetSelectedTabMemory();
+ yield memory.attach();
+
+ var allocs = [];
+ function allocator() {
+ allocs.push(new Object);
+ }
+
+ yield memory.startRecordingAllocations({
+ maxLogLength: 1
+ });
+
+ allocator();
+ allocator();
+ allocator();
+
+ var response = yield memory.getAllocations();
+ yield memory.stopRecordingAllocations();
+
+ is(response.allocations.length, 1,
+ "There should only be one entry in the allocations log.");
+
+ yield memory.detach();
+ destroyServerAndFinish(client);
+ });
+};
+</script>
+</pre>
+</body>
+</html>
diff --git a/devtools/server/tests/mochitest/test_memory_allocations_07.html b/devtools/server/tests/mochitest/test_memory_allocations_07.html
new file mode 100644
index 000000000..c26c2d8ec
--- /dev/null
+++ b/devtools/server/tests/mochitest/test_memory_allocations_07.html
@@ -0,0 +1,55 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+Bug 1192335 - Test getting the byte sizes for allocations.
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Memory monitoring actor test</title>
+ <script type="application/javascript" 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;version=1.8"></script>
+<script>
+window.onload = function() {
+ SimpleTest.waitForExplicitFinish();
+
+ Task.spawn(function* () {
+ var { memory, client } = yield startServerAndGetSelectedTabMemory();
+ yield memory.attach();
+
+ var allocs = [];
+ function allocator() {
+ allocs.push(new Object);
+ }
+
+ yield memory.startRecordingAllocations();
+
+ allocator();
+ allocator();
+ allocator();
+
+ var response = yield memory.getAllocations();
+ yield 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");
+
+ yield memory.detach();
+ destroyServerAndFinish(client);
+ });
+};
+
+function isPositiveNumber (n) {
+ return typeof n === "number" && n > 0;
+}
+</script>
+</pre>
+</body>
+</html>
diff --git a/devtools/server/tests/mochitest/test_memory_attach_01.html b/devtools/server/tests/mochitest/test_memory_attach_01.html
new file mode 100644
index 000000000..5b0b3f75e
--- /dev/null
+++ b/devtools/server/tests/mochitest/test_memory_attach_01.html
@@ -0,0 +1,31 @@
+<!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 type="application/javascript" 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;version=1.8"></script>
+<script>
+window.onload = function() {
+ SimpleTest.waitForExplicitFinish();
+
+ Task.spawn(function* () {
+ var { memory, client } = yield startServerAndGetSelectedTabMemory();
+ yield memory.attach();
+ ok(true, "Shouldn't have gotten an error attaching.");
+ yield memory.detach();
+ ok(true, "Shouldn't have gotten an error detaching.");
+ destroyServerAndFinish(client);
+ });
+};
+</script>
+</pre>
+</body>
+</html>
diff --git a/devtools/server/tests/mochitest/test_memory_attach_02.html b/devtools/server/tests/mochitest/test_memory_attach_02.html
new file mode 100644
index 000000000..76269a6df
--- /dev/null
+++ b/devtools/server/tests/mochitest/test_memory_attach_02.html
@@ -0,0 +1,49 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+Bug 960671 - Test attaching and detaching while in the wrong state.
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Memory monitoring actor test</title>
+ <script type="application/javascript" 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;version=1.8"></script>
+<script>
+window.onload = function() {
+ SimpleTest.waitForExplicitFinish();
+
+ Task.spawn(function* () {
+ var { memory, client } = yield startServerAndGetSelectedTabMemory();
+
+ var e = null;
+ try {
+ yield memory.detach();
+ }
+ catch (ee) {
+ e = ee;
+ }
+ ok(e, "Should have hit the wrongState error");
+
+ yield memory.attach();
+
+ e = null;
+ try {
+ yield memory.attach();
+ }
+ catch (ee) {
+ e = ee;
+ }
+ ok(e, "Should have hit the wrongState error");
+
+ yield memory.detach();
+ destroyServerAndFinish(client);
+ });
+};
+</script>
+</pre>
+</body>
+</html>
diff --git a/devtools/server/tests/mochitest/test_memory_census.html b/devtools/server/tests/mochitest/test_memory_census.html
new file mode 100644
index 000000000..f24050337
--- /dev/null
+++ b/devtools/server/tests/mochitest/test_memory_census.html
@@ -0,0 +1,33 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+Bug 1067491 - Test taking a census over the RDP.
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Memory monitoring actor test</title>
+ <script type="application/javascript" 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;version=1.8"></script>
+<script>
+window.onload = function() {
+ SimpleTest.waitForExplicitFinish();
+
+ Task.spawn(function* () {
+ var { memory, client } = yield startServerAndGetSelectedTabMemory();
+ yield memory.attach();
+
+ var census = yield memory.takeCensus();
+ is(typeof census, "object");
+
+ yield memory.detach();
+ destroyServerAndFinish(client);
+ });
+};
+</script>
+</pre>
+</body>
+</html>
diff --git a/devtools/server/tests/mochitest/test_memory_gc_01.html b/devtools/server/tests/mochitest/test_memory_gc_01.html
new file mode 100644
index 000000000..97cb754f0
--- /dev/null
+++ b/devtools/server/tests/mochitest/test_memory_gc_01.html
@@ -0,0 +1,46 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+Bug 1067491 - Test forcing a gc.
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Memory monitoring actor test</title>
+ <script type="application/javascript" 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;version=1.8"></script>
+<script>
+window.onload = function() {
+ SimpleTest.waitForExplicitFinish();
+
+ Task.spawn(function* () {
+ var { memory, client } = yield startServerAndGetSelectedTabMemory();
+
+ do {
+ var objects = [];
+ for (var i = 0; i < 1000; i++) {
+ var o = {};
+ o[Math.random()] = 1;
+ objects.push(o);
+ }
+
+ objects = null;
+
+ var { total: beforeGC } = yield memory.measure();
+
+ yield memory.forceGarbageCollection();
+ var { total: afterGC } = yield memory.measure();
+ } while(beforeGC < afterGC);
+
+ ok(true, "The amount of memory after GC should eventually decrease");
+
+ destroyServerAndFinish(client);
+ });
+};
+</script>
+</pre>
+</body>
+</html>
diff --git a/devtools/server/tests/mochitest/test_memory_gc_events.html b/devtools/server/tests/mochitest/test_memory_gc_events.html
new file mode 100644
index 000000000..2297481d4
--- /dev/null
+++ b/devtools/server/tests/mochitest/test_memory_gc_events.html
@@ -0,0 +1,42 @@
+<!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 type="application/javascript" 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;version=1.8"></script>
+<script>
+window.onload = function() {
+ SimpleTest.waitForExplicitFinish();
+
+ var event = require("sdk/event/core");
+
+ Task.spawn(function* () {
+ var { memory, client } = yield startServerAndGetSelectedTabMemory();
+ yield memory.attach();
+
+ var gotGcEvent = new Promise(resolve => {
+ event.on(memory, "garbage-collection", gcData => {
+ ok(gcData, "Got GC data");
+ resolve();
+ });
+ });
+
+ memory.forceGarbageCollection();
+ yield gotGcEvent;
+
+ yield memory.detach();
+ destroyServerAndFinish(client);
+ });
+};
+</script>
+</pre>
+</body>
+</html>
diff --git a/devtools/server/tests/mochitest/test_preference.html b/devtools/server/tests/mochitest/test_preference.html
new file mode 100644
index 000000000..54903f455
--- /dev/null
+++ b/devtools/server/tests/mochitest/test_preference.html
@@ -0,0 +1,115 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+Bug 943251 - Allow accessing about:config from WebIDE
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test Preference Actor</title>
+ <script type="application/javascript" 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>
+
+function runTests() {
+ var Cu = Components.utils;
+ var Cc = Components.classes;
+ var Ci = Components.interfaces;
+
+ var {require} = Cu.import("resource://devtools/shared/Loader.jsm", {});
+ var {DebuggerClient} = require("devtools/shared/client/main");
+ var {DebuggerServer} = require("devtools/server/main");
+ var Services = require("Services");
+
+ SimpleTest.waitForExplicitFinish();
+
+ var {getPreferenceFront} = require("devtools/shared/fronts/preference");
+
+ DebuggerServer.init();
+ DebuggerServer.addBrowserActors();
+
+ var client = new DebuggerClient(DebuggerServer.connectPipe());
+ client.connect().then(function onConnect() {
+ client.listTabs(function onListTabs(aResponse) {
+ var p = getPreferenceFront(client, aResponse);
+
+ var prefs = {};
+
+ var 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) {
+ var 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(() => {
+ DebuggerServer.destroy();
+ SimpleTest.finish()
+ });
+ }
+
+
+ p.getAllPrefs().then((json) => prefs["allPrefs"] = json)
+ .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((value) => prefs["boolPref"] = value)
+ .then(() => p.getIntPref("test.int")).then((value) => prefs["intPref"] = value)
+ .then(() => p.getCharPref("test.string")).then((value) => prefs["charPref"] = value)
+ .then(() => p.clearUserPref("test.bool"))
+ .then(() => p.clearUserPref("test.int"))
+ .then(() => p.clearUserPref("test.string"))
+ .then(checkValues);
+
+ });
+ });
+
+}
+
+window.onload = function () {
+ SpecialPowers.pushPrefEnv({
+ "set": [
+ ["devtools.debugger.forbid-certified-apps", false],
+ ["test.all.bool", true],
+ ["test.all.int", 0x4321],
+ ["test.all.string", "allizom"],
+ ]
+ }, runTests);
+}
+</script>
+</pre>
+</body>
+</html>
diff --git a/devtools/server/tests/mochitest/test_settings.html b/devtools/server/tests/mochitest/test_settings.html
new file mode 100644
index 000000000..5665b46b3
--- /dev/null
+++ b/devtools/server/tests/mochitest/test_settings.html
@@ -0,0 +1,130 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+Bug 1022797 - Settings support from WebIDE
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test Settings Actor</title>
+ <script type="text/javascript" src="chrome://mochikit/content/MochiKit/MochiKit.js"></script>
+ <script type="application/javascript" 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>
+
+function runTests() {
+ var Cu = Components.utils;
+ var Cc = Components.classes;
+ var Ci = Components.interfaces;
+
+ var {require} = Cu.import("resource://devtools/shared/Loader.jsm", {});
+ var {DebuggerClient} = require("devtools/shared/client/main");
+ var {DebuggerServer} = require("devtools/server/main");
+
+ if (SpecialPowers.isMainProcess()) {
+ Cu.import("resource://gre/modules/SettingsRequestManager.jsm");
+ }
+
+ SimpleTest.waitForExplicitFinish();
+
+ var {getSettingsFront} = require("devtools/shared/fronts/settings");
+ var {_setDefaultSettings} = require("devtools/server/actors/settings");
+
+ DebuggerServer.init(function () { return true; });
+ DebuggerServer.addBrowserActors();
+
+ var client = new DebuggerClient(DebuggerServer.connectPipe());
+ client.connect().then(function onConnect() {
+ client.listTabs(function onListTabs(aResponse) {
+ var s = getSettingsFront(client, aResponse);
+
+ var settings = {};
+ var resetSettings = {};
+ var fakeSettings = {
+ "wifi.enabled": true,
+ "audio.volume.alarm": 15,
+ "app.reportCrashes": "ask",
+ "app.someObject": { active: true }
+ };
+ var localSetting = {
+ "wifi.enabled": false,
+ "audio.volume.alarm": 0,
+ "app.reportCrashes": "none",
+ "app.someObject": {}
+ };
+
+ function checkValues() {
+ is(settings.allSettings["wifi.enabled"].hasUserValue, false, "original unchanged bool setting");
+ is(settings.allSettings["audio.volume.alarm"].hasUserValue, false, "original unchanged int setting");
+ is(settings.allSettings["app.reportCrashes"].hasUserValue, false, "original unchanged string setting");
+ is(settings.allSettings["app.someObject"].hasUserValue, false, "original unchanged object setting");
+
+ is(settings.allSettings["wifi.enabled"].value, fakeSettings["wifi.enabled"], "original read/write bool setting");
+ is(settings.allSettings["audio.volume.alarm"].value, fakeSettings["audio.volume.alarm"], "original read/write int setting");
+ is(settings.allSettings["app.reportCrashes"].value, fakeSettings["app.reportCrashes"], "original read/write string setting");
+ is(JSON.stringify(settings.allSettings["app.someObject"].value), JSON.stringify(fakeSettings["app.someObject"]), "original read/write object setting");
+
+ is(settings.allUpdatedSettings["wifi.enabled"].hasUserValue, true, "updated user-changed bool setting");
+ is(settings.allUpdatedSettings["audio.volume.alarm"].hasUserValue, true, "updated user-changed int setting");
+ is(settings.allUpdatedSettings["app.reportCrashes"].hasUserValue, true, "updated user-changed string setting");
+ is(settings.allUpdatedSettings["app.someObject"].hasUserValue, true, "updated user-changed object setting");
+
+ is(settings["wifi.enabled"], localSetting["wifi.enabled"], "updated bool setting");
+ is(settings["audio.volume.alarm"], localSetting["audio.volume.alarm"], "updated int setting");
+ is(settings["app.reportCrashes"], localSetting["app.reportCrashes"], "updated string setting");
+ is(JSON.stringify(settings["app.someObject"]), JSON.stringify(localSetting["app.someObject"]), "updated object as string setting");
+
+ is(resetSettings["wifi.enabled"], fakeSettings["wifi.enabled"], "reset to original bool setting");
+ is(resetSettings["audio.volume.alarm"], fakeSettings["audio.volume.alarm"], "reset to original int setting");
+ is(resetSettings["app.reportCrashes"], fakeSettings["app.reportCrashes"], "reset to original string setting");
+ is(JSON.stringify(resetSettings["app.someObject"]), JSON.stringify(fakeSettings["app.someObject"]), "reset to original object setting");
+
+ client.close().then(() => {
+ DebuggerServer.destroy();
+ SimpleTest.finish();
+ });
+ }
+
+ // settings.json doesn't exist outside of b2g so we will fake it.
+ _setDefaultSettings(fakeSettings);
+ s.setSetting("wifi.enabled", fakeSettings["wifi.enabled"])
+ .then(() => s.setSetting("audio.volume.alarm", fakeSettings["audio.volume.alarm"]))
+ .then(() => s.setSetting("app.reportCrashes", fakeSettings["app.reportCrashes"]))
+ .then(() => s.setSetting("app.someObject", fakeSettings["app.someObject"]))
+ .then(() => s.getAllSettings().then(json => settings.allSettings = json))
+ .then(() => s.setSetting("wifi.enabled", localSetting["wifi.enabled"]))
+ .then(() => s.setSetting("audio.volume.alarm", localSetting["audio.volume.alarm"]))
+ .then(() => s.setSetting("app.reportCrashes", localSetting["app.reportCrashes"]))
+ .then(() => s.setSetting("app.someObject", localSetting["app.someObject"]))
+ .then(() => s.getAllSettings().then(json => settings.allUpdatedSettings = json))
+ .then(() => s.getSetting("wifi.enabled")).then(value => settings["wifi.enabled"] = value)
+ .then(() => s.getSetting("audio.volume.alarm")).then(value => settings["audio.volume.alarm"] = value)
+ .then(() => s.getSetting("app.reportCrashes")).then(value => settings["app.reportCrashes"] = value)
+ .then(() => s.getSetting("app.someObject")).then(value => settings["app.someObject"] = value)
+ .then(() => s.clearUserSetting("wifi.enabled")).then(() => {
+ s.getSetting("wifi.enabled").then(value => resetSettings["wifi.enabled"] = value);
+ })
+ .then(() => s.clearUserSetting("audio.volume.alarm")).then(() => {
+ s.getSetting("audio.volume.alarm").then(value => resetSettings["audio.volume.alarm"] = value);
+ })
+ .then(() => s.clearUserSetting("app.reportCrashes")).then(() => {
+ s.getSetting("app.reportCrashes").then(value => resetSettings["app.reportCrashes"] = value);
+ })
+ .then(() => s.clearUserSetting("app.someObject")).then(() => {
+ s.getSetting("app.someObject").then(value => {
+ resetSettings["app.someObject"] = value
+ }).then(checkValues);
+ });
+ });
+ });
+}
+
+window.onload = function () {
+ runTests();
+}
+</script>
+</pre>
+</body>
+</html>
diff --git a/devtools/server/tests/mochitest/test_setupInParentChild.html b/devtools/server/tests/mochitest/test_setupInParentChild.html
new file mode 100644
index 000000000..fc94ca96a
--- /dev/null
+++ b/devtools/server/tests/mochitest/test_setupInParentChild.html
@@ -0,0 +1,110 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+Bug 1181100 - Test DebuggerServerConnection.setupInParent and DebuggerServer.setupInChild
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Mozilla Bug</title>
+ <script type="application/javascript" 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 type="application/javascript;version=1.8">
+
+let Cu = Components.utils;
+let Cc = Components.classes;
+let Ci = Components.interfaces;
+
+let {require} = Cu.import("resource://devtools/shared/Loader.jsm", {});
+let {DebuggerClient} = require("devtools/shared/client/main");
+let {DebuggerServer} = require("devtools/server/main");
+let Services = require("Services");
+
+window.onload = function() {
+ SimpleTest.waitForExplicitFinish();
+
+ SpecialPowers.pushPrefEnv({
+ "set": [
+ // Always log packets when running tests.
+ ["devtools.debugger.log", true],
+ ["dom.mozBrowserFramesEnabled", true]
+ ]
+ }, runTests);
+}
+
+function runTests() {
+ // Create a minimal iframe with a message manager
+ let iframe = document.createElement("iframe");
+ iframe.mozbrowser = true;
+ document.body.appendChild(iframe);
+
+ let mm = iframe.frameLoader.messageManager;
+
+ // Instantiate a minimal server
+ if (!DebuggerServer.initialized) {
+ DebuggerServer.init();
+ }
+ if (!DebuggerServer.createRootActor) {
+ DebuggerServer.addBrowserActors();
+ }
+
+ // Fake a connection to an iframe
+ let transport = DebuggerServer.connectPipe();
+ let conn = transport._serverConnection;
+ let client = new DebuggerClient(transport);
+
+ // Wait for a response from setupInChild
+ const ppmm = Cc["@mozilla.org/parentprocessmessagemanager;1"]
+ .getService(Ci.nsIMessageListenerManager);
+ let onChild = msg => {
+ ppmm.removeMessageListener("test:setupChild", onChild);
+ let args = msg.json;
+
+ is(args[0], 1, "Got first numeric argument");
+ is(args[1], "two", "Got second string argument");
+ is(args[2].three, true, "Got last JSON argument");
+
+ // Ask the child to call setupInParent
+ DebuggerServer.setupInChild({
+ module: "chrome://mochitests/content/chrome/devtools/server/tests/mochitest/setup-in-child.js",
+ setupChild: "callParent"
+ });
+ };
+ ppmm.addMessageListener("test:setupChild", onChild);
+
+ // Wait also for a reponse from setupInParent called from setup-in-child.js
+ let onParent = (_, topic, args) => {
+ Services.obs.removeObserver(onParent, "test:setupParent", false);
+ args = JSON.parse(args);
+
+ is(args[0], true, "Got `mm` argument, a message manager");
+ ok(args[1].match(/server\d+.conn\d+.child\d+/), "Got `prefix` argument");
+
+ cleanup();
+ };
+ Services.obs.addObserver(onParent, "test:setupParent", false);
+
+ // Instanciate e10s machinery and call setupInChild
+ DebuggerServer.connectToChild(conn, iframe).then(actor => {
+ DebuggerServer.setupInChild({
+ module: "chrome://mochitests/content/chrome/devtools/server/tests/mochitest/setup-in-child.js",
+ setupChild: "setupChild",
+ args: [1, "two", {three: true}]
+ });
+ });
+
+ function cleanup() {
+ client.close().then(function () {
+ DebuggerServer.destroy();
+ iframe.remove();
+ SimpleTest.finish()
+ });
+ }
+
+}
+</script>
+</pre>
+</body>
+</html>
diff --git a/devtools/server/tests/mochitest/test_styles-applied.html b/devtools/server/tests/mochitest/test_styles-applied.html
new file mode 100644
index 000000000..d9fb6ec7f
--- /dev/null
+++ b/devtools/server/tests/mochitest/test_styles-applied.html
@@ -0,0 +1,145 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug </title>
+
+ <script type="application/javascript" 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;version=1.8" src="inspector-helpers.js"></script>
+ <script type="application/javascript;version=1.8">
+const inspector = require("devtools/server/actors/inspector");
+
+window.onload = function() {
+ SimpleTest.waitForExplicitFinish();
+ runNextTest();
+}
+
+var gWalker = null;
+var gStyles = null;
+var gClient = null;
+
+addTest(function setup() {
+ let url = document.getElementById("inspectorContent").href;
+ attachURL(url, function(err, client, tab, doc) {
+ let {InspectorFront} = require("devtools/shared/fronts/inspector");
+ let inspector = InspectorFront(client, tab);
+ promiseDone(inspector.getWalker().then(walker => {
+ ok(walker, "getWalker() should return an actor.");
+ gClient = client;
+ gWalker = walker;
+ return inspector.getPageStyle();
+ }).then(styles => {
+ gStyles = styles;
+ }).then(runNextTest));
+ });
+});
+
+addTest(function inheritedUserStyles() {
+ promiseDone(gWalker.querySelector(gWalker.rootNode, "#test-node").then(node => {
+ return gStyles.getApplied(node, { inherited: true, filter: "user" });
+ }).then(applied => {
+ ok(!applied[0].inherited, "Entry 0 should be uninherited");
+ is(applied[0].rule.type, 100, "Entry 0 should be an element style");
+ ok(!!applied[0].rule.href, "Element styles should have a URL");
+ is(applied[0].rule.cssText, "", "Entry 0 should be an empty style");
+
+ is(applied[1].inherited.id, "uninheritable-rule-inheritable-style",
+ "Entry 1 should be inherited from the parent");
+ is(applied[1].rule.type, 100, "Entry 1 should be an element style");
+ is(applied[1].rule.cssText, "color: red;", "Entry 1 should have the expected cssText");
+
+ is(applied[2].inherited.id, "inheritable-rule-inheritable-style",
+ "Entry 2 should be inherited from the parent's parent");
+ is(applied[2].rule.type, 100, "Entry 2 should be an element style");
+ is(applied[2].rule.cssText, "color: blue;", "Entry 2 should have the expected cssText");
+
+ is(applied[3].inherited.id, "inheritable-rule-inheritable-style",
+ "Entry 3 should be inherited from the parent's parent");
+ is(applied[3].rule.type, 1, "Entry 3 should be a rule style");
+ is(applied[3].rule.cssText, "font-size: 15px;", "Entry 3 should have the expected cssText");
+ ok(!applied[3].matchedSelectors, "Shouldn't get matchedSelectors with this request.");
+
+ is(applied[4].inherited.id, "inheritable-rule-uninheritable-style",
+ "Entry 4 should be inherited from the parent's parent");
+ is(applied[4].rule.type, 1, "Entry 4 should be an rule style");
+ is(applied[4].rule.cssText, "font-size: 15px;", "Entry 4 should have the expected cssText");
+ ok(!applied[4].matchedSelectors, "Shouldn't get matchedSelectors with this request.");
+
+ is(applied.length, 5, "Should have 5 rules.");
+ }).then(runNextTest));
+});
+
+addTest(function inheritedSystemStyles() {
+ promiseDone(gWalker.querySelector(gWalker.rootNode, "#test-node").then(node => {
+ return gStyles.getApplied(node, { inherited: true, filter: "ua" });
+ }).then(applied => {
+ // If our system stylesheets are prone to churn, this might be a fragile
+ // test. If you're here because of that I apologize, file a bug
+ // and we can find a different way to test.
+
+ ok(!applied[1].inherited, "Entry 1 should not be inherited");
+ ok(!applied[1].rule.parentStyleSheet.system, "Entry 1 should be a system style");
+ is(applied[1].rule.type, 1, "Entry 1 should be a rule style");
+
+ is(applied.length, 12, "Should have 12 rules.");
+ }).then(runNextTest));
+});
+
+addTest(function noInheritedStyles() {
+ promiseDone(gWalker.querySelector(gWalker.rootNode, "#test-node").then(node => {
+ return gStyles.getApplied(node, { inherited: false, filter: "user" });
+ }).then(applied => {
+ ok(!applied[0].inherited, "Entry 0 should be uninherited");
+ is(applied[0].rule.type, 100, "Entry 0 should be an element style");
+ is(applied[0].rule.cssText, "", "Entry 0 should be an empty style");
+ is(applied.length, 1, "Should have 1 rule.");
+ }).then(runNextTest));
+});
+
+addTest(function matchedSelectors() {
+ promiseDone(gWalker.querySelector(gWalker.rootNode, "#test-node").then(node => {
+ return gStyles.getApplied(node, {
+ inherited: true, filter: "user", matchedSelectors: true
+ });
+ }).then(applied => {
+ is(applied[3].matchedSelectors[0], ".inheritable-rule", "Entry 3 should have a matched selector");
+ is(applied[4].matchedSelectors[0], ".inheritable-rule", "Entry 4 should have a matched selector");
+ }).then(runNextTest));
+});
+
+addTest(function testMediaQuery() {
+ promiseDone(gWalker.querySelector(gWalker.rootNode, "#mediaqueried").then(node => {
+ return gStyles.getApplied(node, {
+ inherited: false, filter: "user", matchedSelectors: true
+ });
+ }).then(applied => {
+ is(applied[1].rule.type, 1, "Entry 1 is a rule style");
+ is(applied[1].rule.parentRule.type, 4, "Entry 1's parent rule is a media rule");
+ is(applied[1].rule.media[0], "screen", "Entry 1's rule has the expected medium");
+ }).then(runNextTest));
+});
+
+addTest(function cleanup() {
+ delete gStyles;
+ delete gWalker;
+ delete gClient;
+ 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/mochitest/test_styles-computed.html b/devtools/server/tests/mochitest/test_styles-computed.html
new file mode 100644
index 000000000..c70adc8eb
--- /dev/null
+++ b/devtools/server/tests/mochitest/test_styles-computed.html
@@ -0,0 +1,139 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug </title>
+
+ <script type="application/javascript" 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;version=1.8" src="inspector-helpers.js"></script>
+ <script type="application/javascript;version=1.8">
+const inspector = require("devtools/server/actors/inspector");
+
+window.onload = function() {
+ SimpleTest.waitForExplicitFinish();
+ runNextTest();
+}
+
+var gWalker = null;
+var gStyles = null;
+var gClient = null;
+
+addTest(function setup() {
+ let url = document.getElementById("inspectorContent").href;
+ attachURL(url, function(err, client, tab, doc) {
+ gInspectee = doc;
+ let {InspectorFront} = require("devtools/shared/fronts/inspector");
+ let inspector = InspectorFront(client, tab);
+ promiseDone(inspector.getWalker().then(walker => {
+ ok(walker, "getWalker() should return an actor.");
+ gClient = client;
+ gWalker = walker;
+ return inspector.getPageStyle();
+ }).then(styles => {
+ gStyles = styles;
+ }).then(runNextTest));
+ });
+});
+
+addTest(function testComputed() {
+ let localNode = gInspectee.querySelector("#computed-test-node");
+ let elementStyle = null;
+ promiseDone(gWalker.querySelector(gWalker.rootNode, "#computed-test-node").then(node => {
+ return gStyles.getComputed(node, {});
+ }).then(computed => {
+ // Test a smattering of properties that include some system-defined
+ // props, some props that were defined in this node's stylesheet,
+ // and some default props.
+ is(computed["white-space"].value, "normal", "Default value should appear");
+ is(computed["display"].value, "block", "System stylesheet item should appear");
+ is(computed["cursor"].value, "crosshair", "Included stylesheet rule should appear");
+ is(computed["color"].value, "rgb(255, 0, 0)", "Inherited style attribute should appear");
+ is(computed["font-size"].value, "15px", "Inherited inline rule should appear");
+
+ // We didn't request markMatched, so these shouldn't be set
+ ok(!computed["cursor"].matched, "Didn't ask for matched, shouldn't get it");
+ ok(!computed["color"].matched, "Didn't ask for matched, shouldn't get it");
+ ok(!computed["font-size"].matched, "Didn't ask for matched, shouldn't get it");
+ }).then(runNextTest));
+});
+
+addTest(function testComputedUserMatched() {
+ let localNode = gInspectee.querySelector("#computed-test-node");
+ let elementStyle = null;
+ promiseDone(gWalker.querySelector(gWalker.rootNode, "#computed-test-node").then(node => {
+ return gStyles.getComputed(node, { filter: "user", markMatched: true });
+ }).then(computed => {
+ ok(!computed["white-space"].matched, "Default style shouldn't match");
+ ok(!computed["display"].matched, "Only user styles should match");
+ ok(computed["cursor"].matched, "Asked for matched, should get it");
+ ok(computed["color"].matched, "Asked for matched, should get it");
+ ok(computed["font-size"].matched, "Asked for matched, should get it");
+ }).then(runNextTest));
+});
+
+addTest(function testComputedSystemMatched() {
+ let localNode = gInspectee.querySelector("#computed-test-node");
+ let elementStyle = null;
+ promiseDone(gWalker.querySelector(gWalker.rootNode, "#computed-test-node").then(node => {
+ return gStyles.getComputed(node, { filter: "ua", markMatched: true });
+ }).then(computed => {
+ ok(!computed["white-space"].matched, "Default style shouldn't match");
+ ok(computed["display"].matched, "System stylesheets should match");
+ ok(computed["cursor"].matched, "Asked for matched, should get it");
+ ok(computed["color"].matched, "Asked for matched, should get it");
+ ok(computed["font-size"].matched, "Asked for matched, should get it");
+ }).then(runNextTest));
+});
+
+addTest(function testComputedUserOnlyMatched() {
+ let localNode = gInspectee.querySelector("#computed-test-node");
+ let elementStyle = null;
+ promiseDone(gWalker.querySelector(gWalker.rootNode, "#computed-test-node").then(node => {
+ return gStyles.getComputed(node, { filter: "user", onlyMatched: true });
+ }).then(computed => {
+ ok(!("white-space" in computed), "Default style shouldn't exist");
+ ok(!("display" in computed), "System stylesheets shouldn't exist");
+ ok(("cursor" in computed), "User items should exist.");
+ ok(("color" in computed), "User items should exist.");
+ ok(("font-size" in computed), "User items should exist.");
+ }).then(runNextTest));
+});
+
+addTest(function testComputedSystemOnlyMatched() {
+ let localNode = gInspectee.querySelector("#computed-test-node");
+ let elementStyle = null;
+ promiseDone(gWalker.querySelector(gWalker.rootNode, "#computed-test-node").then(node => {
+ return gStyles.getComputed(node, { filter: "ua", onlyMatched: true });
+ }).then(computed => {
+ ok(!("white-space" in computed), "Default style shouldn't exist");
+ ok(("display" in computed), "System stylesheets should exist");
+ ok(("cursor" in computed), "User items should exist.");
+ ok(("color" in computed), "User items should exist.");
+ ok(("font-size" in computed), "User items should exist.");
+ }).then(runNextTest));
+});
+
+addTest(function cleanup() {
+ delete gStyles;
+ delete gWalker;
+ delete gClient;
+ 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/mochitest/test_styles-layout.html b/devtools/server/tests/mochitest/test_styles-layout.html
new file mode 100644
index 000000000..b2134b0c9
--- /dev/null
+++ b/devtools/server/tests/mochitest/test_styles-layout.html
@@ -0,0 +1,116 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<meta charset="utf-8">
+<title>Test for Bug 1175040 - PageStyleActor.getLayout</title>
+<script type="application/javascript" 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;version=1.8" src="inspector-helpers.js"></script>
+<script type="application/javascript;version=1.8">
+"use strict";
+
+window.onload = function() {
+ SimpleTest.waitForExplicitFinish();
+ runNextTest();
+};
+
+let gWalker = null;
+let gStyles = null;
+
+addTest(function() {
+ let url = document.getElementById("inspectorContent").href;
+ attachURL(url, function(err, client, tab, doc) {
+ let {InspectorFront} = require("devtools/shared/fronts/inspector");
+ let inspector = InspectorFront(client, tab);
+ promiseDone(inspector.getWalker().then(walker => {
+ ok(walker, "getWalker() should return an actor.");
+ gWalker = walker;
+ return inspector.getPageStyle();
+ }).then(styles => {
+ gStyles = styles;
+ }).then(runNextTest));
+ });
+});
+
+addTest(function() {
+ ok(gStyles.getLayout, "The PageStyleActor has a getLayout method");
+ runNextTest();
+});
+
+addAsyncTest(function*() {
+ let node = yield gWalker.querySelector(gWalker.rootNode, "#layout-element");
+ let layout = yield gStyles.getLayout(node, {});
+
+ let 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 (let prop of properties) {
+ ok((prop in layout), "The layout object returned has " + prop);
+ }
+
+ runNextTest();
+});
+
+addAsyncTest(function*() {
+ let node = yield gWalker.querySelector(gWalker.rootNode, "#layout-element");
+ let layout = yield gStyles.getLayout(node, {});
+
+ let 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 (let name in expected) {
+ is(layout[name], expected[name], "The " + name + " property is correct");
+ }
+
+ runNextTest();
+});
+
+addAsyncTest(function*() {
+ let node = yield gWalker.querySelector(gWalker.rootNode,
+ "#layout-auto-margin-element");
+
+ let layout = yield gStyles.getLayout(node, {});
+ ok(!("autoMargins" in layout),
+ "By default, getLayout doesn't return auto margins");
+
+ layout = yield 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/mochitest/test_styles-matched.html b/devtools/server/tests/mochitest/test_styles-matched.html
new file mode 100644
index 000000000..4d24fbe70
--- /dev/null
+++ b/devtools/server/tests/mochitest/test_styles-matched.html
@@ -0,0 +1,98 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug </title>
+
+ <script type="application/javascript" 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;version=1.8" src="inspector-helpers.js"></script>
+ <script type="application/javascript;version=1.8">
+const inspector = require("devtools/server/actors/inspector");
+const CssLogic = require("devtools/shared/inspector/css-logic");
+
+window.onload = function() {
+ SimpleTest.waitForExplicitFinish();
+ runNextTest();
+}
+
+var gWalker = null;
+var gStyles = null;
+var gClient = null;
+
+addTest(function setup() {
+ let url = document.getElementById("inspectorContent").href;
+ attachURL(url, function(err, client, tab, doc) {
+ gInspectee = doc;
+ let {InspectorFront} = require("devtools/shared/fronts/inspector");
+ let inspector = InspectorFront(client, tab);
+ promiseDone(inspector.getWalker().then(walker => {
+ ok(walker, "getWalker() should return an actor.");
+ gClient = client;
+ gWalker = walker;
+ return inspector.getPageStyle();
+ }).then(styles => {
+ gStyles = styles;
+ }).then(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, ".inheritable-rule", "Second match comes from a rule");
+ is(matched[1].selector, ".inheritable-rule", "Second style has a selector");
+ is(matched[1].value, "15px", "Second match has the expected value");
+ 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.");
+ }).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() {
+ delete gStyles;
+ delete gWalker;
+ delete gClient;
+ 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/mochitest/test_styles-modify.html b/devtools/server/tests/mochitest/test_styles-modify.html
new file mode 100644
index 000000000..5a8e20bc3
--- /dev/null
+++ b/devtools/server/tests/mochitest/test_styles-modify.html
@@ -0,0 +1,116 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug </title>
+
+ <script type="application/javascript" 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;version=1.8" src="inspector-helpers.js"></script>
+ <script type="application/javascript;version=1.8">
+const inspector = require("devtools/server/actors/inspector");
+const {isCssPropertyKnown} = require("devtools/server/actors/css-properties");
+
+window.onload = function() {
+ SimpleTest.waitForExplicitFinish();
+ runNextTest();
+}
+
+var gWalker = null;
+var gStyles = null;
+var gClient = null;
+
+addAsyncTest(function* setup() {
+ let url = document.getElementById("inspectorContent").href;
+ let inspector;
+
+ yield new Promise(resolve => {
+ attachURL(url, function(err, client, tab, doc) {
+ gInspectee = doc;
+ gClient = client;
+ let {InspectorFront} = require("devtools/shared/fronts/inspector");
+ inspector = InspectorFront(client, tab);
+ resolve();
+ });
+ });
+
+ gWalker = yield inspector.getWalker();
+ gStyles = yield inspector.getPageStyle();
+
+ runNextTest();
+});
+
+addAsyncTest(function* modifyProperties() {
+ let localNode = gInspectee.querySelector("#inheritable-rule-inheritable-style");
+
+ let node = yield gWalker.querySelector(gWalker.rootNode,
+ "#inheritable-rule-inheritable-style");
+
+ let applied = yield gStyles.getApplied(node,
+ { inherited: false, filter: "user" });
+
+ let elementStyle = applied[0].rule;
+ is(elementStyle.cssText, localNode.style.cssText, "Got expected css text");
+
+ // Change an existing property...
+ yield setProperty(elementStyle, 0, "color", "black");
+ // Create a new property
+ yield setProperty(elementStyle, 1, "background-color", "green");
+
+ // Create a new property and then change it immediately.
+ yield setProperty(elementStyle, 2, "border", "1px solid black");
+ yield 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
+ yield removeProperty(elementStyle, 0, "color");
+ yield removeProperty(elementStyle, 0, "background-color");
+ yield removeProperty(elementStyle, 0, "border");
+
+ is(elementStyle.cssText, "", "Should have expected cssText");
+ is(elementStyle.cssText, localNode.style.cssText,
+ "Local node and style front match.");
+
+ runNextTest();
+});
+
+function* setProperty(rule, index, name, value) {
+ let changes = rule.startModifyingProperties(isCssPropertyKnown);
+ changes.setProperty(index, name, value);
+ yield changes.apply();
+}
+
+function* removeProperty(rule, index, name) {
+ let changes = rule.startModifyingProperties(isCssPropertyKnown);
+ changes.removeProperty(index, name);
+ yield changes.apply();
+}
+
+addTest(function cleanup() {
+ delete gStyles;
+ delete gWalker;
+ delete gClient;
+ 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/mochitest/test_styles-svg.html b/devtools/server/tests/mochitest/test_styles-svg.html
new file mode 100644
index 000000000..51a84420c
--- /dev/null
+++ b/devtools/server/tests/mochitest/test_styles-svg.html
@@ -0,0 +1,70 @@
+<!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 type="application/javascript" 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;version=1.8" src="inspector-helpers.js"></script>
+ <script type="application/javascript;version=1.8">
+const inspector = require("devtools/server/actors/inspector");
+
+window.onload = function() {
+ SimpleTest.waitForExplicitFinish();
+ runNextTest();
+}
+
+var gWalker = null;
+var gStyles = null;
+var gClient = null;
+
+addTest(function setup() {
+ let url = document.getElementById("inspectorContent").href;
+ attachURL(url, function(err, client, tab, doc) {
+ let {InspectorFront} = require("devtools/shared/fronts/inspector");
+ let inspector = InspectorFront(client, tab);
+ promiseDone(inspector.getWalker().then(walker => {
+ ok(walker, "getWalker() should return an actor.");
+ gClient = client;
+ gWalker = walker;
+ return inspector.getPageStyle();
+ }).then(styles => {
+ gStyles = styles;
+ }).then(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() {
+ delete gStyles;
+ delete gWalker;
+ delete gClient;
+ 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/mochitest/test_unsafeDereference.html b/devtools/server/tests/mochitest/test_unsafeDereference.html
new file mode 100644
index 000000000..df44fac51
--- /dev/null
+++ b/devtools/server/tests/mochitest/test_unsafeDereference.html
@@ -0,0 +1,52 @@
+<!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 type="application/javascript" 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>
+
+Components.utils.import("resource://gre/modules/jsdebugger.jsm");
+addDebuggerToGlobal(this);
+
+window.onload = function () {
+ SimpleTest.waitForExplicitFinish();
+
+ var iframe = document.createElement("iframe");
+ iframe.src = "http://mochi.test:8888/chrome/devtools/server/tests/mochitest/nonchrome_unsafeDereference.html";
+
+ iframe.onload = function () {
+ var dbg = new Debugger;
+ var contentDO = dbg.addDebuggee(iframe.contentWindow);
+ var 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");
+
+ var 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/mochitest/test_websocket-server.html b/devtools/server/tests/mochitest/test_websocket-server.html
new file mode 100644
index 000000000..583d96dd9
--- /dev/null
+++ b/devtools/server/tests/mochitest/test_websocket-server.html
@@ -0,0 +1,82 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Mozilla Bug</title>
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="chrome://mochikit/content/tests/SimpleTest/SpawnTask.js"></script>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
+</head>
+<body>
+<script>
+window.onload = function() {
+ const { Constructor: CC, utils: Cu } = Components;
+ const { require } = Cu.import("resource://devtools/shared/Loader.jsm", {});
+ const { Task } = require("devtools/shared/task");
+ const WebSocketServer = require("devtools/server/websocket-server");
+
+ const ServerSocket = CC("@mozilla.org/network/server-socket;1",
+ "nsIServerSocket", "init");
+
+ add_task(function* () {
+ // Create a TCP server on auto-assigned port
+ let server = new ServerSocket(-1, true, -1);
+ ok(server, `Launched WebSocket server on port ${server.port}`);
+ server.asyncListen({
+ onSocketAccepted: Task.async(function* (socket, transport) {
+ info("Accepted incoming connection");
+ let input = transport.openInputStream(0, 0, 0);
+ let output = transport.openOutputStream(0, 0, 0);
+
+ // Perform the WebSocket handshake
+ let webSocket = yield WebSocketServer.accept(transport, input, output);
+
+ // Echo the received message back to the sender
+ webSocket.onmessage = ({ data }) => {
+ info("Server received message, echoing back");
+ webSocket.send(data);
+ };
+ }),
+
+ onStopListening(socket, status) {
+ info(`Server stopped listening with status: ${status}`);
+ }
+ });
+
+ SimpleTest.registerCleanupFunction(() => {
+ server.close();
+ });
+
+ // Create client connection
+ let client = yield new Promise((resolve, reject) => {
+ let socket = new WebSocket(`ws://localhost:${server.port}`);
+ socket.onopen = () => resolve(socket);
+ socket.onerror = reject;
+ });
+ ok(client, `Created WebSocket connection to port ${server.port}`);
+
+ // Create a promise that resolves when the WebSocket closes
+ let closed = new Promise(resolve => {
+ client.onclose = resolve;
+ });
+
+ // Send a message
+ let message = "hello there";
+ client.send(message);
+ info("Sent a message to server");
+ // Check that it was echoed
+ let echoedMessage = yield new Promise((resolve, reject) => {
+ client.onmessage = ({ data }) => resolve(data);
+ client.onerror = reject;
+ });
+
+ is(echoedMessage, message, "Echoed message matches");
+
+ // Close the connection
+ client.close();
+ yield closed;
+ });
+}
+</script>
+</body>
+</html>