diff options
author | Matt A. Tobin <mattatobin@localhost.localdomain> | 2018-02-02 04:16:08 -0500 |
---|---|---|
committer | Matt A. Tobin <mattatobin@localhost.localdomain> | 2018-02-02 04:16:08 -0500 |
commit | 5f8de423f190bbb79a62f804151bc24824fa32d8 (patch) | |
tree | 10027f336435511475e392454359edea8e25895d /devtools/server/tests | |
parent | 49ee0794b5d912db1f95dce6eb52d781dc210db5 (diff) | |
download | UXP-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')
397 files changed, 32362 insertions, 0 deletions
diff --git a/devtools/server/tests/browser/.eslintrc.js b/devtools/server/tests/browser/.eslintrc.js new file mode 100644 index 000000000..c5b919ce3 --- /dev/null +++ b/devtools/server/tests/browser/.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/browser/animation.html b/devtools/server/tests/browser/animation.html new file mode 100644 index 000000000..d10a9873d --- /dev/null +++ b/devtools/server/tests/browser/animation.html @@ -0,0 +1,170 @@ +<!DOCTYPE html> +<style> + .not-animated { + display: inline-block; + + width: 50px; + height: 50px; + border-radius: 50%; + background: #eee; + } + + .simple-animation { + display: inline-block; + + width: 50px; + height: 50px; + border-radius: 50%; + background: red; + + animation: move 200s infinite; + } + + .multiple-animations { + display: inline-block; + + width: 50px; + height: 50px; + border-radius: 50%; + background: #eee; + + animation: move 200s infinite , glow 100s 5; + animation-timing-function: ease-out; + animation-direction: reverse; + animation-fill-mode: both; + } + + .transition { + display: inline-block; + + width: 50px; + height: 50px; + border-radius: 50%; + background: #f06; + + transition: width 500s ease-out; + } + .transition.get-round { + width: 200px; + } + + .long-animation { + display: inline-block; + + width: 50px; + height: 50px; + border-radius: 50%; + background: gold; + + animation: move 100s; + } + + .short-animation { + display: inline-block; + + width: 50px; + height: 50px; + border-radius: 50%; + background: purple; + + animation: move 1s; + } + + .delayed-animation { + display: inline-block; + + width: 50px; + height: 50px; + border-radius: 50%; + background: rebeccapurple; + + animation: move 200s 5s infinite; + } + + .delayed-transition { + display: inline-block; + + width: 50px; + height: 50px; + border-radius: 50%; + background: black; + + transition: width 500s 3s; + } + .delayed-transition.get-round { + width: 200px; + } + + .delayed-multiple-animations { + display: inline-block; + + width: 50px; + height: 50px; + border-radius: 50%; + background: green; + + animation: move .5s 1s 10, glow 1s .75s 30; + } + + .multiple-animations-2 { + display: inline-block; + + width: 50px; + height: 50px; + border-radius: 50%; + background: blue; + + animation: move .5s, glow 100s 2s infinite, grow 300s 1s 100; + } + + .all-transitions { + position: absolute; + top: 0; + right: 0; + width: 50px; + height: 50px; + background: blue; + transition: all .2s; + } + .all-transitions.expand { + width: 200px; + height: 100px; + } + + @keyframes move { + 100% { + transform: translateY(100px); + } + } + + @keyframes glow { + 100% { + background: yellow; + } + } + + @keyframes grow { + 100% { + width: 100px; + } + } +</style> +<div class="not-animated"></div> +<div class="simple-animation"></div> +<div class="multiple-animations"></div> +<div class="transition"></div> +<div class="long-animation"></div> +<div class="short-animation"></div> +<div class="delayed-animation"></div> +<div class="delayed-transition"></div> +<div class="delayed-multiple-animations"></div> +<div class="multiple-animations-2"></div> +<div class="all-transitions"></div> +<script type="text/javascript"> + // Get the transitions started when the page loads + var players; + addEventListener("load", function() { + document.querySelector(".transition").classList.add("get-round"); + document.querySelector(".delayed-transition").classList.add("get-round"); + }); +</script> diff --git a/devtools/server/tests/browser/browser.ini b/devtools/server/tests/browser/browser.ini new file mode 100644 index 000000000..c05933230 --- /dev/null +++ b/devtools/server/tests/browser/browser.ini @@ -0,0 +1,97 @@ +[DEFAULT] +tags = devtools +subsuite = devtools +support-files = + head.js + animation.html + doc_allocations.html + doc_force_cc.html + doc_force_gc.html + doc_innerHTML.html + doc_perf.html + navigate-first.html + navigate-second.html + storage-dynamic-windows.html + storage-listings.html + storage-unsecured-iframe.html + storage-updates.html + storage-secured-iframe.html + stylesheets-nested-iframes.html + timeline-iframe-child.html + timeline-iframe-parent.html + director-script-target.html + storage-helpers.js + !/devtools/server/tests/mochitest/hello-actor.js + +[browser_animation_emitMutations.js] +[browser_animation_getFrames.js] +[browser_animation_getProperties.js] +[browser_animation_getMultipleStates.js] +[browser_animation_getPlayers.js] +[browser_animation_getStateAfterFinished.js] +[browser_animation_getSubTreeAnimations.js] +[browser_animation_keepFinished.js] +[browser_animation_playerState.js] +[browser_animation_playPauseIframe.js] +[browser_animation_playPauseSeveral.js] +[browser_animation_reconstructState.js] +[browser_animation_refreshTransitions.js] +[browser_animation_setCurrentTime.js] +[browser_animation_setPlaybackRate.js] +[browser_animation_simple.js] +[browser_animation_updatedState.js] +skip-if = e10s # Bug 1183605 - devtools/server/tests/browser/ tests are still disabled in E10S +[browser_canvasframe_helper_01.js] +skip-if = e10s # Bug 1183605 - devtools/server/tests/browser/ tests are still disabled in E10S +[browser_canvasframe_helper_02.js] +[browser_canvasframe_helper_03.js] +skip-if = e10s # Bug 1183605 - devtools/server/tests/browser/ tests are still disabled in E10S +[browser_canvasframe_helper_04.js] +skip-if = e10s # Bug 1183605 - devtools/server/tests/browser/ tests are still disabled in E10S +[browser_canvasframe_helper_05.js] +skip-if = e10s # Bug 1183605 - devtools/server/tests/browser/ tests are still disabled in E10S +[browser_canvasframe_helper_06.js] +skip-if = e10s # Bug 1183605 - devtools/server/tests/browser/ tests are still disabled in E10S +[browser_markers-cycle-collection.js] +[browser_markers-docloading-01.js] +[browser_markers-docloading-02.js] +[browser_markers-docloading-03.js] +[browser_markers-gc.js] +[browser_markers-minor-gc.js] +[browser_markers-parse-html.js] +[browser_markers-styles.js] +[browser_markers-timestamp.js] +[browser_navigateEvents.js] +skip-if = e10s # Bug 1183605 - devtools/server/tests/browser/ tests are still disabled in E10S +[browser_perf-allocation-data.js] +skip-if = e10s # Bug 1183605 - devtools/server/tests/browser/ tests are still disabled in E10S +[browser_perf-profiler-01.js] +[browser_perf-profiler-02.js] +skip-if = true # Needs to be updated for async actor destruction +[browser_perf-profiler-03.js] +skip-if = true # Needs to be updated for async actor destruction +[browser_perf-realtime-markers.js] +[browser_perf-recording-actor-01.js] +skip-if = e10s # Bug 1183605 - devtools/server/tests/browser/ tests are still disabled in E10S +[browser_perf-recording-actor-02.js] +skip-if = e10s # Bug 1183605 - devtools/server/tests/browser/ tests are still disabled in E10S +[browser_perf-samples-01.js] +[browser_perf-samples-02.js] +#[browser_perf-front-profiler-01.js] bug 1077464 +#[browser_perf-front-profiler-05.js] bug 1077464 +#[browser_perf-front-profiler-06.js] +[browser_storage_dynamic_windows.js] +[browser_storage_listings.js] +[browser_storage_updates.js] +[browser_stylesheets_getTextEmpty.js] +[browser_stylesheets_nested-iframes.js] +[browser_timeline.js] +[browser_timeline_actors.js] +[browser_timeline_iframes.js] +[browser_directorscript_actors_exports.js] +skip-if = e10s # Bug 1183605 - devtools/server/tests/browser/ tests are still disabled in E10S +[browser_directorscript_actors_error_events.js] +skip-if = e10s # Bug 1183605 - devtools/server/tests/browser/ tests are still disabled in E10S +[browser_directorscript_actors.js] +skip-if = e10s # Bug 1183605 - devtools/server/tests/browser/ tests are still disabled in E10S +[browser_register_actor.js] diff --git a/devtools/server/tests/browser/browser_animation_emitMutations.js b/devtools/server/tests/browser/browser_animation_emitMutations.js new file mode 100644 index 000000000..4783a8209 --- /dev/null +++ b/devtools/server/tests/browser/browser_animation_emitMutations.js @@ -0,0 +1,62 @@ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Test that the AnimationsActor emits events about changed animations on a +// node after getAnimationPlayersForNode was called on that node. + +add_task(function* () { + let {client, walker, animations} = + yield initAnimationsFrontForUrl(MAIN_DOMAIN + "animation.html"); + + info("Retrieve a non-animated node"); + let node = yield walker.querySelector(walker.rootNode, ".not-animated"); + + info("Retrieve the animation player for the node"); + let players = yield animations.getAnimationPlayersForNode(node); + is(players.length, 0, "The node has no animation players"); + + info("Listen for new animations"); + let onMutations = once(animations, "mutations"); + + info("Add a couple of animation on the node"); + yield node.modifyAttributes([ + {attributeName: "class", newValue: "multiple-animations"} + ]); + let changes = yield onMutations; + + ok(true, "The mutations event was emitted"); + is(changes.length, 2, "There are 2 changes in the mutation event"); + ok(changes.every(({type}) => type === "added"), "Both changes are additions"); + + let names = changes.map(c => c.player.initialState.name).sort(); + is(names[0], "glow", "The animation 'glow' was added"); + is(names[1], "move", "The animation 'move' was added"); + + info("Store the 2 new players for comparing later"); + let p1 = changes[0].player; + let p2 = changes[1].player; + + info("Listen for removed animations"); + onMutations = once(animations, "mutations"); + + info("Remove the animation css class on the node"); + yield node.modifyAttributes([ + {attributeName: "class", newValue: "not-animated"} + ]); + + changes = yield onMutations; + + ok(true, "The mutations event was emitted"); + is(changes.length, 2, "There are 2 changes in the mutation event"); + ok(changes.every(({type}) => type === "removed"), "Both are removals"); + ok(changes[0].player === p1 || changes[0].player === p2, + "The first removed player was one of the previously added players"); + ok(changes[1].player === p1 || changes[1].player === p2, + "The second removed player was one of the previously added players"); + + yield client.close(); + gBrowser.removeCurrentTab(); +}); diff --git a/devtools/server/tests/browser/browser_animation_getFrames.js b/devtools/server/tests/browser/browser_animation_getFrames.js new file mode 100644 index 000000000..25ccfae3b --- /dev/null +++ b/devtools/server/tests/browser/browser_animation_getFrames.js @@ -0,0 +1,32 @@ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Check that the AnimationPlayerActor exposes a getFrames method that returns
+// the list of keyframes in the animation.
+
+const URL = MAIN_DOMAIN + "animation.html";
+
+add_task(function* () {
+ let {client, walker, animations} =
+ yield initAnimationsFrontForUrl(MAIN_DOMAIN + "animation.html");
+
+ info("Get the test node and its animation front");
+ let node = yield walker.querySelector(walker.rootNode, ".simple-animation");
+ let [player] = yield animations.getAnimationPlayersForNode(node);
+
+ ok(player.getFrames, "The front has the getFrames method");
+
+ let frames = yield player.getFrames();
+ is(frames.length, 2, "The correct number of keyframes was retrieved");
+ ok(frames[0].transform, "Frame 0 has the transform property");
+ ok(frames[1].transform, "Frame 1 has the transform property");
+ // Note that we don't really test the content of the frame object here on
+ // purpose. This object comes straight out of the web animations API
+ // unmodified.
+
+ yield client.close();
+ gBrowser.removeCurrentTab();
+});
diff --git a/devtools/server/tests/browser/browser_animation_getMultipleStates.js b/devtools/server/tests/browser/browser_animation_getMultipleStates.js new file mode 100644 index 000000000..4436695b0 --- /dev/null +++ b/devtools/server/tests/browser/browser_animation_getMultipleStates.js @@ -0,0 +1,55 @@ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +add_task(function* setup() { + yield SpecialPowers.pushPrefEnv({ + set: [["dom.ipc.processCount", 1]] + }); +}); + +// Check that the duration, iterationCount and delay are retrieved correctly for +// multiple animations. + +add_task(function* () { + let {client, walker, animations} = + yield initAnimationsFrontForUrl(MAIN_DOMAIN + "animation.html"); + + yield playerHasAnInitialState(walker, animations); + + yield client.close(); + gBrowser.removeCurrentTab(); +}); + +function* playerHasAnInitialState(walker, animations) { + let state = yield getAnimationStateForNode(walker, animations, + ".delayed-multiple-animations", 0); + + ok(state.duration, 50000, + "The duration of the first animation is correct"); + ok(state.iterationCount, 10, + "The iterationCount of the first animation is correct"); + ok(state.delay, 1000, + "The delay of the first animation is correct"); + + state = yield getAnimationStateForNode(walker, animations, + ".delayed-multiple-animations", 1); + + ok(state.duration, 100000, + "The duration of the secon animation is correct"); + ok(state.iterationCount, 30, + "The iterationCount of the secon animation is correct"); + ok(state.delay, 750, + "The delay of the secon animation is correct"); +} + +function* getAnimationStateForNode(walker, animations, selector, playerIndex) { + let node = yield walker.querySelector(walker.rootNode, selector); + let players = yield animations.getAnimationPlayersForNode(node); + let player = players[playerIndex]; + yield player.ready(); + let state = yield player.getCurrentState(); + return state; +} diff --git a/devtools/server/tests/browser/browser_animation_getPlayers.js b/devtools/server/tests/browser/browser_animation_getPlayers.js new file mode 100644 index 000000000..a99a4dc4e --- /dev/null +++ b/devtools/server/tests/browser/browser_animation_getPlayers.js @@ -0,0 +1,63 @@ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Check the output of getAnimationPlayersForNode + +add_task(function* () { + let {client, walker, animations} = + yield initAnimationsFrontForUrl(MAIN_DOMAIN + "animation.html"); + + yield theRightNumberOfPlayersIsReturned(walker, animations); + yield playersCanBePausedAndResumed(walker, animations); + + yield client.close(); + gBrowser.removeCurrentTab(); +}); + +function* theRightNumberOfPlayersIsReturned(walker, animations) { + let node = yield walker.querySelector(walker.rootNode, ".not-animated"); + let players = yield animations.getAnimationPlayersForNode(node); + is(players.length, 0, + "0 players were returned for the unanimated node"); + + node = yield walker.querySelector(walker.rootNode, ".simple-animation"); + players = yield animations.getAnimationPlayersForNode(node); + is(players.length, 1, + "One animation player was returned"); + + node = yield walker.querySelector(walker.rootNode, ".multiple-animations"); + players = yield animations.getAnimationPlayersForNode(node); + is(players.length, 2, + "Two animation players were returned"); + + node = yield walker.querySelector(walker.rootNode, ".transition"); + players = yield animations.getAnimationPlayersForNode(node); + is(players.length, 1, + "One animation player was returned for the transitioned node"); +} + +function* playersCanBePausedAndResumed(walker, animations) { + let node = yield walker.querySelector(walker.rootNode, ".simple-animation"); + let [player] = yield animations.getAnimationPlayersForNode(node); + yield player.ready(); + + ok(player.initialState, + "The player has an initialState"); + ok(player.getCurrentState, + "The player has the getCurrentState method"); + is(player.initialState.playState, "running", + "The animation is currently running"); + + yield player.pause(); + let state = yield player.getCurrentState(); + is(state.playState, "paused", + "The animation is now paused"); + + yield player.play(); + state = yield player.getCurrentState(); + is(state.playState, "running", + "The animation is now running again"); +} diff --git a/devtools/server/tests/browser/browser_animation_getProperties.js b/devtools/server/tests/browser/browser_animation_getProperties.js new file mode 100644 index 000000000..e95db544c --- /dev/null +++ b/devtools/server/tests/browser/browser_animation_getProperties.js @@ -0,0 +1,36 @@ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Check that the AnimationPlayerActor exposes a getProperties method that
+// returns the list of animated properties in the animation.
+
+const URL = MAIN_DOMAIN + "animation.html";
+
+add_task(function* () {
+ let {client, walker, animations} = yield initAnimationsFrontForUrl(URL);
+
+ info("Get the test node and its animation front");
+ let node = yield walker.querySelector(walker.rootNode, ".simple-animation");
+ let [player] = yield animations.getAnimationPlayersForNode(node);
+
+ ok(player.getProperties, "The front has the getProperties method");
+
+ let properties = yield player.getProperties();
+ is(properties.length, 1, "The correct number of properties was retrieved");
+
+ let propertyObject = properties[0];
+ is(propertyObject.name, "transform", "Property 0 is transform");
+
+ is(propertyObject.values.length, 2,
+ "The correct number of property values was retrieved");
+
+ // Note that we don't really test the content of the frame object here on
+ // purpose. This object comes straight out of the web animations API
+ // unmodified.
+
+ yield client.close();
+ gBrowser.removeCurrentTab();
+});
diff --git a/devtools/server/tests/browser/browser_animation_getStateAfterFinished.js b/devtools/server/tests/browser/browser_animation_getStateAfterFinished.js new file mode 100644 index 000000000..dd33237c1 --- /dev/null +++ b/devtools/server/tests/browser/browser_animation_getStateAfterFinished.js @@ -0,0 +1,55 @@ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Check that the right duration/iterationCount/delay are retrieved even when +// the node has multiple animations and one of them already ended before getting +// the player objects. +// See devtools/server/actors/animation.js |getPlayerIndex| for more +// information. + +add_task(function* () { + let {client, walker, animations} = + yield initAnimationsFrontForUrl(MAIN_DOMAIN + "animation.html"); + + info("Retrieve a non animated node"); + let node = yield walker.querySelector(walker.rootNode, ".not-animated"); + + info("Apply the multiple-animations-2 class to start the animations"); + yield node.modifyAttributes([ + {attributeName: "class", newValue: "multiple-animations-2"} + ]); + + info("Get the list of players, by the time this executes, the first, " + + "short, animation should have ended."); + let players = yield animations.getAnimationPlayersForNode(node); + if (players.length === 3) { + info("The short animation hasn't ended yet, wait for a bit."); + // The animation lasts for 500ms, so 1000ms should do it. + yield new Promise(resolve => setTimeout(resolve, 1000)); + + info("And get the list again"); + players = yield animations.getAnimationPlayersForNode(node); + } + + is(players.length, 2, "2 animations remain on the node"); + + is(players[0].state.duration, 100000, + "The duration of the first animation is correct"); + is(players[0].state.delay, 2000, + "The delay of the first animation is correct"); + is(players[0].state.iterationCount, null, + "The iterationCount of the first animation is correct"); + + is(players[1].state.duration, 300000, + "The duration of the second animation is correct"); + is(players[1].state.delay, 1000, + "The delay of the second animation is correct"); + is(players[1].state.iterationCount, 100, + "The iterationCount of the second animation is correct"); + + yield client.close(); + gBrowser.removeCurrentTab(); +}); diff --git a/devtools/server/tests/browser/browser_animation_getSubTreeAnimations.js b/devtools/server/tests/browser/browser_animation_getSubTreeAnimations.js new file mode 100644 index 000000000..50782d6de --- /dev/null +++ b/devtools/server/tests/browser/browser_animation_getSubTreeAnimations.js @@ -0,0 +1,38 @@ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Check that the AnimationsActor can retrieve all animations inside a node's +// subtree (but not going into iframes). + +const URL = MAIN_DOMAIN + "animation.html"; + +add_task(function* () { + info("Creating a test document with 2 iframes containing animated nodes"); + + let {client, walker, animations} = yield initAnimationsFrontForUrl( + "data:text/html;charset=utf-8," + + "<iframe id='iframe' src='" + URL + "'></iframe>"); + + info("Try retrieving all animations from the root doc's <body> node"); + let rootBody = yield walker.querySelector(walker.rootNode, "body"); + let players = yield animations.getAnimationPlayersForNode(rootBody); + is(players.length, 0, "The node has no animation players"); + + info("Retrieve all animations from the iframe's <body> node"); + let iframe = yield walker.querySelector(walker.rootNode, "#iframe"); + let {nodes} = yield walker.children(iframe); + let frameBody = yield walker.querySelector(nodes[0], "body"); + players = yield animations.getAnimationPlayersForNode(frameBody); + + // Testing for a hard-coded number of animations here would intermittently + // fail depending on how fast or slow the test is (indeed, the test page + // contains short transitions, and delayed animations). So just make sure we + // at least have the infinitely running animations. + ok(players.length >= 4, "All subtree animations were retrieved"); + + yield client.close(); + gBrowser.removeCurrentTab(); +}); diff --git a/devtools/server/tests/browser/browser_animation_keepFinished.js b/devtools/server/tests/browser/browser_animation_keepFinished.js new file mode 100644 index 000000000..a3240a5e0 --- /dev/null +++ b/devtools/server/tests/browser/browser_animation_keepFinished.js @@ -0,0 +1,54 @@ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Test that the AnimationsActor doesn't report finished animations as removed. +// Indeed, animations that only have the "finished" playState can be modified +// still, so we want the AnimationsActor to preserve the corresponding +// AnimationPlayerActor. + +add_task(function* () { + let {client, walker, animations} = + yield initAnimationsFrontForUrl(MAIN_DOMAIN + "animation.html"); + + info("Retrieve a non-animated node"); + let node = yield walker.querySelector(walker.rootNode, ".not-animated"); + + info("Retrieve the animation player for the node"); + let players = yield animations.getAnimationPlayersForNode(node); + is(players.length, 0, "The node has no animation players"); + + info("Listen for new animations"); + let reportedMutations = []; + function onMutations(mutations) { + reportedMutations = [...reportedMutations, ...mutations]; + } + animations.on("mutations", onMutations); + + info("Add a short animation on the node"); + yield node.modifyAttributes([ + {attributeName: "class", newValue: "short-animation"} + ]); + + info("Wait for longer than the animation's duration"); + yield wait(2000); + + players = yield animations.getAnimationPlayersForNode(node); + is(players.length, 0, "The added animation is surely finished"); + + is(reportedMutations.length, 1, "Only one mutation was reported"); + is(reportedMutations[0].type, "added", "The mutation was an addition"); + + animations.off("mutations", onMutations); + + yield client.close(); + gBrowser.removeCurrentTab(); +}); + +function wait(ms) { + return new Promise(resolve => { + setTimeout(resolve, ms); + }); +} diff --git a/devtools/server/tests/browser/browser_animation_playPauseIframe.js b/devtools/server/tests/browser/browser_animation_playPauseIframe.js new file mode 100644 index 000000000..52320b84e --- /dev/null +++ b/devtools/server/tests/browser/browser_animation_playPauseIframe.js @@ -0,0 +1,51 @@ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Check that the AnimationsActor can pause/play all animations even those +// within iframes. + +const URL = MAIN_DOMAIN + "animation.html"; + +add_task(function* () { + info("Creating a test document with 2 iframes containing animated nodes"); + + let {client, walker, animations} = yield initAnimationsFrontForUrl( + "data:text/html;charset=utf-8," + + "<iframe id='i1' src='" + URL + "'></iframe>" + + "<iframe id='i2' src='" + URL + "'></iframe>"); + + info("Getting the 2 iframe container nodes and animated nodes in them"); + let nodeInFrame1 = yield getNodeInFrame(walker, "#i1", ".simple-animation"); + let nodeInFrame2 = yield getNodeInFrame(walker, "#i2", ".simple-animation"); + + info("Pause all animations in the test document"); + yield animations.pauseAll(); + yield checkState(animations, nodeInFrame1, "paused"); + yield checkState(animations, nodeInFrame2, "paused"); + + info("Play all animations in the test document"); + yield animations.playAll(); + yield checkState(animations, nodeInFrame1, "running"); + yield checkState(animations, nodeInFrame2, "running"); + + yield client.close(); + gBrowser.removeCurrentTab(); +}); + +function* checkState(animations, nodeFront, playState) { + info("Getting the AnimationPlayerFront for the test node"); + let [player] = yield animations.getAnimationPlayersForNode(nodeFront); + yield player.ready; + let state = yield player.getCurrentState(); + is(state.playState, playState, + "The playState of the test node is " + playState); +} + +function* getNodeInFrame(walker, frameSelector, nodeSelector) { + let iframe = yield walker.querySelector(walker.rootNode, frameSelector); + let {nodes} = yield walker.children(iframe); + return walker.querySelector(nodes[0], nodeSelector); +} diff --git a/devtools/server/tests/browser/browser_animation_playPauseSeveral.js b/devtools/server/tests/browser/browser_animation_playPauseSeveral.js new file mode 100644 index 000000000..9c52b5f57 --- /dev/null +++ b/devtools/server/tests/browser/browser_animation_playPauseSeveral.js @@ -0,0 +1,92 @@ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Check that the AnimationsActor can pause/play all animations at once, and +// check that it can also pause/play a given list of animations at once. + +// List of selectors that match "all" animated nodes in the test page. +// This list misses a bunch of animated nodes on purpose. Only the ones that +// have infinite animations are listed. This is done to avoid intermittents +// caused when finite animations are already done playing by the time the test +// runs. +const ALL_ANIMATED_NODES = [".simple-animation", ".multiple-animations", + ".delayed-animation"]; + +add_task(function* () { + let {client, walker, animations} = + yield initAnimationsFrontForUrl(MAIN_DOMAIN + "animation.html"); + + info("Pause all animations in the test document"); + yield animations.pauseAll(); + yield checkStates(walker, animations, ALL_ANIMATED_NODES, "paused"); + + info("Play all animations in the test document"); + yield animations.playAll(); + yield checkStates(walker, animations, ALL_ANIMATED_NODES, "running"); + + info("Pause all animations in the test document using toggleAll"); + yield animations.toggleAll(); + yield checkStates(walker, animations, ALL_ANIMATED_NODES, "paused"); + + info("Play all animations in the test document using toggleAll"); + yield animations.toggleAll(); + yield checkStates(walker, animations, ALL_ANIMATED_NODES, "running"); + + info("Play all animations from multiple animated node using toggleSeveral"); + let players = yield getPlayersFor(walker, animations, + [".multiple-animations"]); + is(players.length, 2, "Node has 2 animation players"); + yield animations.toggleSeveral(players, false); + let state1 = yield players[0].getCurrentState(); + is(state1.playState, "running", + "The playState of the first player is running"); + let state2 = yield players[1].getCurrentState(); + is(state2.playState, "running", + "The playState of the second player is running"); + + info("Pause one animation from a multiple animated node using toggleSeveral"); + yield animations.toggleSeveral([players[0]], true); + state1 = yield players[0].getCurrentState(); + is(state1.playState, "paused", "The playState of the first player is paused"); + state2 = yield players[1].getCurrentState(); + is(state2.playState, "running", + "The playState of the second player is running"); + + info("Play the same animation"); + yield animations.toggleSeveral([players[0]], false); + state1 = yield players[0].getCurrentState(); + is(state1.playState, "running", + "The playState of the first player is running"); + state2 = yield players[1].getCurrentState(); + is(state2.playState, "running", + "The playState of the second player is running"); + + yield client.close(); + gBrowser.removeCurrentTab(); +}); + +function* checkStates(walker, animations, selectors, playState) { + info("Checking the playState of all the nodes that have infinite running " + + "animations"); + + for (let selector of selectors) { + info("Getting the AnimationPlayerFront for node " + selector); + let [player] = yield getPlayersFor(walker, animations, selector); + yield player.ready(); + yield checkPlayState(player, selector, playState); + } +} + +function* getPlayersFor(walker, animations, selector) { + let node = yield walker.querySelector(walker.rootNode, selector); + return animations.getAnimationPlayersForNode(node); +} + +function* checkPlayState(player, selector, expectedState) { + let state = yield player.getCurrentState(); + is(state.playState, expectedState, + "The playState of node " + selector + " is " + expectedState); +} diff --git a/devtools/server/tests/browser/browser_animation_playerState.js b/devtools/server/tests/browser/browser_animation_playerState.js new file mode 100644 index 000000000..ac5842e39 --- /dev/null +++ b/devtools/server/tests/browser/browser_animation_playerState.js @@ -0,0 +1,123 @@ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Check the animation player's initial state + +add_task(function* () { + let {client, walker, animations} = + yield initAnimationsFrontForUrl(MAIN_DOMAIN + "animation.html"); + + yield playerHasAnInitialState(walker, animations); + yield playerStateIsCorrect(walker, animations); + + yield client.close(); + gBrowser.removeCurrentTab(); +}); + +function* playerHasAnInitialState(walker, animations) { + let node = yield walker.querySelector(walker.rootNode, ".simple-animation"); + let [player] = yield animations.getAnimationPlayersForNode(node); + + ok(player.initialState, "The player front has an initial state"); + ok("startTime" in player.initialState, "Player's state has startTime"); + ok("currentTime" in player.initialState, "Player's state has currentTime"); + ok("playState" in player.initialState, "Player's state has playState"); + ok("playbackRate" in player.initialState, "Player's state has playbackRate"); + ok("name" in player.initialState, "Player's state has name"); + ok("duration" in player.initialState, "Player's state has duration"); + ok("delay" in player.initialState, "Player's state has delay"); + ok("iterationCount" in player.initialState, + "Player's state has iterationCount"); + ok("fill" in player.initialState, "Player's state has fill"); + ok("easing" in player.initialState, "Player's state has easing"); + ok("direction" in player.initialState, "Player's state has direction"); + ok("isRunningOnCompositor" in player.initialState, + "Player's state has isRunningOnCompositor"); + ok("type" in player.initialState, "Player's state has type"); + ok("documentCurrentTime" in player.initialState, + "Player's state has documentCurrentTime"); +} + +function* playerStateIsCorrect(walker, animations) { + info("Checking the state of the simple animation"); + + let player = yield getAnimationPlayerForNode(walker, animations, + ".simple-animation", 0); + let state = yield player.getCurrentState(); + is(state.name, "move", "Name is correct"); + is(state.duration, 200000, "Duration is correct"); + // null = infinite count + is(state.iterationCount, null, "Iteration count is correct"); + is(state.fill, "none", "Fill is correct"); + is(state.easing, "linear", "Easing is correct"); + is(state.direction, "normal", "Direction is correct"); + is(state.playState, "running", "PlayState is correct"); + is(state.playbackRate, 1, "PlaybackRate is correct"); + is(state.type, "cssanimation", "Type is correct"); + + info("Checking the state of the transition"); + + player = + yield getAnimationPlayerForNode(walker, animations, ".transition", 0); + state = yield player.getCurrentState(); + is(state.name, "width", "Transition name matches transition property"); + is(state.duration, 500000, "Transition duration is correct"); + // transitions run only once + is(state.iterationCount, 1, "Transition iteration count is correct"); + is(state.fill, "backwards", "Transition fill is correct"); + is(state.easing, "linear", "Transition easing is correct"); + is(state.direction, "normal", "Transition direction is correct"); + is(state.playState, "running", "Transition playState is correct"); + is(state.playbackRate, 1, "Transition playbackRate is correct"); + is(state.type, "csstransition", "Transition type is correct"); + // chech easing in keyframe + let keyframes = yield player.getFrames(); + is(keyframes.length, 2, "Transition length of keyframe is correct"); + is(keyframes[0].easing, + "ease-out", "Transition kerframes's easing is correct"); + + info("Checking the state of one of multiple animations on a node"); + + // Checking the 2nd player + player = yield getAnimationPlayerForNode(walker, animations, + ".multiple-animations", 1); + state = yield player.getCurrentState(); + is(state.name, "glow", "The 2nd animation's name is correct"); + is(state.duration, 100000, "The 2nd animation's duration is correct"); + is(state.iterationCount, 5, "The 2nd animation's iteration count is correct"); + is(state.fill, "both", "The 2nd animation's fill is correct"); + is(state.easing, "linear", "The 2nd animation's easing is correct"); + is(state.direction, "reverse", "The 2nd animation's direction is correct"); + is(state.playState, "running", "The 2nd animation's playState is correct"); + is(state.playbackRate, 1, "The 2nd animation's playbackRate is correct"); + // chech easing in keyframe + keyframes = yield player.getFrames(); + is(keyframes.length, 2, "The 2nd animation's length of keyframe is correct"); + is(keyframes[0].easing, + "ease-out", "The 2nd animation's easing of kerframes is correct"); + + info("Checking the state of an animation with delay"); + + player = yield getAnimationPlayerForNode(walker, animations, + ".delayed-animation", 0); + state = yield player.getCurrentState(); + is(state.delay, 5000, "The animation delay is correct"); + + info("Checking the state of an transition with delay"); + + player = yield getAnimationPlayerForNode(walker, animations, + ".delayed-transition", 0); + state = yield player.getCurrentState(); + is(state.delay, 3000, "The transition delay is correct"); +} + +function* getAnimationPlayerForNode(walker, animations, nodeSelector, index) { + let node = yield walker.querySelector(walker.rootNode, nodeSelector); + let players = yield animations.getAnimationPlayersForNode(node); + let player = players[index]; + yield player.ready(); + return player; +} diff --git a/devtools/server/tests/browser/browser_animation_reconstructState.js b/devtools/server/tests/browser/browser_animation_reconstructState.js new file mode 100644 index 000000000..cd3007b86 --- /dev/null +++ b/devtools/server/tests/browser/browser_animation_reconstructState.js @@ -0,0 +1,38 @@ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Check that, even though the AnimationPlayerActor only sends the bits of its +// state that change, the front reconstructs the whole state everytime. + +add_task(function* () { + let {client, walker, animations} = + yield initAnimationsFrontForUrl(MAIN_DOMAIN + "animation.html"); + + yield playerHasCompleteStateAtAllTimes(walker, animations); + + yield client.close(); + gBrowser.removeCurrentTab(); +}); + +function* playerHasCompleteStateAtAllTimes(walker, animations) { + let node = yield walker.querySelector(walker.rootNode, ".simple-animation"); + let [player] = yield animations.getAnimationPlayersForNode(node); + yield player.ready(); + + // Get the list of state key names from the initialstate. + let keys = Object.keys(player.initialState); + + // Get the state over and over again and check that the object returned + // contains all keys. + // Normally, only the currentTime will have changed in between 2 calls. + for (let i = 0; i < 10; i++) { + yield player.refreshState(); + keys.forEach(key => { + ok(typeof player.state[key] !== "undefined", + "The state retrieved has key " + key); + }); + } +} diff --git a/devtools/server/tests/browser/browser_animation_refreshTransitions.js b/devtools/server/tests/browser/browser_animation_refreshTransitions.js new file mode 100644 index 000000000..4cec0df69 --- /dev/null +++ b/devtools/server/tests/browser/browser_animation_refreshTransitions.js @@ -0,0 +1,77 @@ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// When a transition finishes, no "removed" event is sent because it may still +// be used, but when it restarts again (transitions back), then a new +// AnimationPlayerFront should be sent, and the old one should be removed. + +add_task(function* () { + let {client, walker, animations} = + yield initAnimationsFrontForUrl(MAIN_DOMAIN + "animation.html"); + + info("Retrieve the test node"); + let node = yield walker.querySelector(walker.rootNode, ".all-transitions"); + + info("Retrieve the animation players for the node"); + let players = yield animations.getAnimationPlayersForNode(node); + is(players.length, 0, "The node has no animation players yet"); + + info("Play a transition by adding the expand class, wait for mutations"); + let onMutations = expectMutationEvents(animations, 2); + let cpow = content.document.querySelector(".all-transitions"); + cpow.classList.add("expand"); + let reportedMutations = yield onMutations; + + is(reportedMutations.length, 2, "2 mutation events were received"); + is(reportedMutations[0].type, "added", "The first event was 'added'"); + is(reportedMutations[1].type, "added", "The second event was 'added'"); + + info("Wait for the transitions to be finished"); + yield waitForEnd(reportedMutations[0].player); + yield waitForEnd(reportedMutations[1].player); + + info("Play the transition back by removing the class, wait for mutations"); + onMutations = expectMutationEvents(animations, 4); + cpow.classList.remove("expand"); + reportedMutations = yield onMutations; + + is(reportedMutations.length, 4, "4 new mutation events were received"); + is(reportedMutations.filter(m => m.type === "removed").length, 2, + "2 'removed' events were sent (for the old transitions)"); + is(reportedMutations.filter(m => m.type === "added").length, 2, + "2 'added' events were sent (for the new transitions)"); + + yield client.close(); + gBrowser.removeCurrentTab(); +}); + +function expectMutationEvents(animationsFront, nbOfEvents) { + return new Promise(resolve => { + let reportedMutations = []; + function onMutations(mutations) { + reportedMutations = [...reportedMutations, ...mutations]; + info("Received " + reportedMutations.length + " mutation events, " + + "expecting " + nbOfEvents); + if (reportedMutations.length === nbOfEvents) { + animationsFront.off("mutations", onMutations); + resolve(reportedMutations); + } + } + + info("Start listening for mutation events from the AnimationsFront"); + animationsFront.on("mutations", onMutations); + }); +} + +function* waitForEnd(animationFront) { + let playState; + while (playState !== "finished") { + let state = yield animationFront.getCurrentState(); + playState = state.playState; + info("Wait for transition " + animationFront.state.name + + " to finish, playState=" + playState); + } +} diff --git a/devtools/server/tests/browser/browser_animation_setCurrentTime.js b/devtools/server/tests/browser/browser_animation_setCurrentTime.js new file mode 100644 index 000000000..16dbaa544 --- /dev/null +++ b/devtools/server/tests/browser/browser_animation_setCurrentTime.js @@ -0,0 +1,74 @@ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Check that a player's currentTime can be changed and that the AnimationsActor +// allows changing many players' currentTimes at once. + +add_task(function* () { + let {client, walker, animations} = + yield initAnimationsFrontForUrl(MAIN_DOMAIN + "animation.html"); + + yield testSetCurrentTime(walker, animations); + yield testSetCurrentTimes(walker, animations); + + yield client.close(); + gBrowser.removeCurrentTab(); +}); + +function* testSetCurrentTime(walker, animations) { + info("Retrieve an animated node"); + let node = yield walker.querySelector(walker.rootNode, ".simple-animation"); + + info("Retrieve the animation player for the node"); + let [player] = yield animations.getAnimationPlayersForNode(node); + + ok(player.setCurrentTime, "Player has the setCurrentTime method"); + + info("Check that the setCurrentTime method can be called"); + // Note that we don't check that it sets the animation to the right time here, + // this is too prone to intermittent failures, we'll do this later after + // pausing the animation. Here we merely test that the method doesn't fail. + yield player.setCurrentTime(player.initialState.currentTime + 1000); + + info("Pause the animation so we can really test if setCurrentTime works"); + yield player.pause(); + let pausedState = yield player.getCurrentState(); + + info("Set the current time to currentTime + 5s"); + yield player.setCurrentTime(pausedState.currentTime + 5000); + + let updatedState1 = yield player.getCurrentState(); + is(Math.round(updatedState1.currentTime - pausedState.currentTime), 5000, + "The currentTime was updated to +5s"); + + info("Set the current time to currentTime - 2s"); + yield player.setCurrentTime(updatedState1.currentTime - 2000); + let updatedState2 = yield player.getCurrentState(); + is(Math.round(updatedState2.currentTime - updatedState1.currentTime), -2000, + "The currentTime was updated to -2s"); +} + +function* testSetCurrentTimes(walker, animations) { + ok(animations.setCurrentTimes, "The AnimationsActor has the right method"); + + info("Retrieve multiple animated node and its animation players"); + + let nodeMulti = yield walker.querySelector(walker.rootNode, + ".multiple-animations"); + let players = (yield animations.getAnimationPlayersForNode(nodeMulti)); + + ok(players.length > 1, "Node has more than 1 animation player"); + + info("Try to set multiple current times at once"); + yield animations.setCurrentTimes(players, 500, true); + + info("Get the states of players and verify their correctness"); + for (let i = 0; i < players.length; i++) { + let state = yield players[i].getCurrentState(); + is(state.playState, "paused", `Player ${i + 1} is paused`); + is(state.currentTime, 500, `Player ${i + 1} has the right currentTime`); + } +} diff --git a/devtools/server/tests/browser/browser_animation_setPlaybackRate.js b/devtools/server/tests/browser/browser_animation_setPlaybackRate.js new file mode 100644 index 000000000..b6d41b51e --- /dev/null +++ b/devtools/server/tests/browser/browser_animation_setPlaybackRate.js @@ -0,0 +1,51 @@ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Check that a player's playbackRate can be changed, and that multiple players +// can have their rates changed at the same time. + +add_task(function* () { + let {client, walker, animations} = + yield initAnimationsFrontForUrl(MAIN_DOMAIN + "animation.html"); + + info("Retrieve an animated node"); + let node = yield walker.querySelector(walker.rootNode, ".simple-animation"); + + info("Retrieve the animation player for the node"); + let [player] = yield animations.getAnimationPlayersForNode(node); + + ok(player.setPlaybackRate, "Player has the setPlaybackRate method"); + + info("Change the rate to 10"); + yield player.setPlaybackRate(10); + + info("Query the state again"); + let state = yield player.getCurrentState(); + is(state.playbackRate, 10, "The playbackRate was updated"); + + info("Change the rate back to 1"); + yield player.setPlaybackRate(1); + + info("Query the state again"); + state = yield player.getCurrentState(); + is(state.playbackRate, 1, "The playbackRate was changed back"); + + info("Retrieve several animation players and set their rates"); + node = yield walker.querySelector(walker.rootNode, "body"); + let players = yield animations.getAnimationPlayersForNode(node); + + info("Change all animations in <body> to .5 rate"); + yield animations.setPlaybackRates(players, .5); + + info("Query their states and check they are correct"); + for (let player of players) { + let state = yield player.getCurrentState(); + is(state.playbackRate, .5, "The playbackRate was updated"); + } + + yield client.close(); + gBrowser.removeCurrentTab(); +}); diff --git a/devtools/server/tests/browser/browser_animation_simple.js b/devtools/server/tests/browser/browser_animation_simple.js new file mode 100644 index 000000000..52daf0084 --- /dev/null +++ b/devtools/server/tests/browser/browser_animation_simple.js @@ -0,0 +1,35 @@ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Simple checks for the AnimationsActor + +add_task(function* () { + let {client, walker, animations} = yield initAnimationsFrontForUrl( + "data:text/html;charset=utf-8,<title>test</title><div></div>"); + + ok(animations, "The AnimationsFront was created"); + ok(animations.getAnimationPlayersForNode, + "The getAnimationPlayersForNode method exists"); + ok(animations.toggleAll, "The toggleAll method exists"); + ok(animations.playAll, "The playAll method exists"); + ok(animations.pauseAll, "The pauseAll method exists"); + + let didThrow = false; + try { + yield animations.getAnimationPlayersForNode(null); + } catch (e) { + didThrow = true; + } + ok(didThrow, "An exception was thrown for a missing NodeActor"); + + let invalidNode = yield walker.querySelector(walker.rootNode, "title"); + let players = yield animations.getAnimationPlayersForNode(invalidNode); + ok(Array.isArray(players), "An array of players was returned"); + is(players.length, 0, "0 players have been returned for the invalid node"); + + yield client.close(); + gBrowser.removeCurrentTab(); +}); diff --git a/devtools/server/tests/browser/browser_animation_updatedState.js b/devtools/server/tests/browser/browser_animation_updatedState.js new file mode 100644 index 000000000..17d68e9e5 --- /dev/null +++ b/devtools/server/tests/browser/browser_animation_updatedState.js @@ -0,0 +1,55 @@ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Check the animation player's updated state + +add_task(function* () { + let {client, walker, animations} = + yield initAnimationsFrontForUrl(MAIN_DOMAIN + "animation.html"); + + yield playStateIsUpdatedDynamically(walker, animations); + + yield client.close(); + gBrowser.removeCurrentTab(); +}); + +function* playStateIsUpdatedDynamically(walker, animations) { + info("Getting the test node (which runs a very long animation)"); + // The animation lasts for 100s, to avoid intermittents. + let node = yield walker.querySelector(walker.rootNode, ".long-animation"); + + info("Getting the animation player front for this node"); + let [player] = yield animations.getAnimationPlayersForNode(node); + yield player.ready(); + + let state = yield player.getCurrentState(); + is(state.playState, "running", + "The playState is running while the animation is running"); + + info("Change the animation's currentTime to be near the end and wait for " + + "it to finish"); + let onFinished = waitForAnimationPlayState(player, "finished"); + // Set the currentTime to 98s, knowing that the animation lasts for 100s. + yield player.setCurrentTime(98 * 1000); + state = yield onFinished; + is(state.playState, "finished", + "The animation has ended and the state has been updated"); + ok(state.currentTime > player.initialState.currentTime, + "The currentTime has been updated"); +} + +function* waitForAnimationPlayState(player, playState) { + let state = {}; + while (state.playState !== playState) { + state = yield player.getCurrentState(); + yield wait(500); + } + return state; +} + +function wait(ms) { + return new Promise(r => setTimeout(r, ms)); +} diff --git a/devtools/server/tests/browser/browser_canvasframe_helper_01.js b/devtools/server/tests/browser/browser_canvasframe_helper_01.js new file mode 100644 index 000000000..7fd943197 --- /dev/null +++ b/devtools/server/tests/browser/browser_canvasframe_helper_01.js @@ -0,0 +1,90 @@ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Simple CanvasFrameAnonymousContentHelper tests. + +// This makes sure the 'domnode' protocol actor type is known when importing +// highlighter. +require("devtools/server/actors/inspector"); +const {HighlighterEnvironment} = require("devtools/server/actors/highlighters"); + +const { + CanvasFrameAnonymousContentHelper +} = require("devtools/server/actors/highlighters/utils/markup"); + +const TEST_URL = "data:text/html;charset=utf-8,CanvasFrameAnonymousContentHelper test"; + +add_task(function* () { + let browser = yield addTab(TEST_URL); + let doc = browser.contentDocument; + + let nodeBuilder = () => { + let root = doc.createElement("div"); + let child = doc.createElement("div"); + child.style = "width:200px;height:200px;background:red;"; + child.id = "child-element"; + child.className = "child-element"; + child.textContent = "test element"; + root.appendChild(child); + return root; + }; + + info("Building the helper"); + let env = new HighlighterEnvironment(); + env.initFromWindow(doc.defaultView); + let helper = new CanvasFrameAnonymousContentHelper(env, nodeBuilder); + + ok(helper.content instanceof AnonymousContent, + "The helper owns the AnonymousContent object"); + ok(helper.getTextContentForElement, + "The helper has the getTextContentForElement method"); + ok(helper.setTextContentForElement, + "The helper has the setTextContentForElement method"); + ok(helper.setAttributeForElement, + "The helper has the setAttributeForElement method"); + ok(helper.getAttributeForElement, + "The helper has the getAttributeForElement method"); + ok(helper.removeAttributeForElement, + "The helper has the removeAttributeForElement method"); + ok(helper.addEventListenerForElement, + "The helper has the addEventListenerForElement method"); + ok(helper.removeEventListenerForElement, + "The helper has the removeEventListenerForElement method"); + ok(helper.getElement, + "The helper has the getElement method"); + ok(helper.scaleRootElement, + "The helper has the scaleRootElement method"); + + is(helper.getTextContentForElement("child-element"), "test element", + "The text content was retrieve correctly"); + is(helper.getAttributeForElement("child-element", "id"), "child-element", + "The ID attribute was retrieve correctly"); + is(helper.getAttributeForElement("child-element", "class"), "child-element", + "The class attribute was retrieve correctly"); + + let el = helper.getElement("child-element"); + ok(el, "The DOMNode-like element was created"); + + is(el.getTextContent(), "test element", + "The text content was retrieve correctly"); + is(el.getAttribute("id"), "child-element", + "The ID attribute was retrieve correctly"); + is(el.getAttribute("class"), "child-element", + "The class attribute was retrieve correctly"); + + info("Destroying the helper"); + helper.destroy(); + env.destroy(); + + ok(!helper.getTextContentForElement("child-element"), + "No text content was retrieved after the helper was destroyed"); + ok(!helper.getAttributeForElement("child-element", "id"), + "No ID attribute was retrieved after the helper was destroyed"); + ok(!helper.getAttributeForElement("child-element", "class"), + "No class attribute was retrieved after the helper was destroyed"); + + gBrowser.removeCurrentTab(); +}); diff --git a/devtools/server/tests/browser/browser_canvasframe_helper_02.js b/devtools/server/tests/browser/browser_canvasframe_helper_02.js new file mode 100644 index 000000000..90400c50a --- /dev/null +++ b/devtools/server/tests/browser/browser_canvasframe_helper_02.js @@ -0,0 +1,48 @@ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Test that the CanvasFrameAnonymousContentHelper does not insert content in +// XUL windows. + +// This makes sure the 'domnode' protocol actor type is known when importing +// highlighter. +require("devtools/server/actors/inspector"); + +const {HighlighterEnvironment} = require("devtools/server/actors/highlighters"); + +const { + CanvasFrameAnonymousContentHelper +} = require("devtools/server/actors/highlighters/utils/markup"); + +add_task(function* () { + let browser = yield addTab("about:preferences"); + let doc = browser.contentDocument; + + let nodeBuilder = () => { + let root = doc.createElement("div"); + let child = doc.createElement("div"); + child.style = "width:200px;height:200px;background:red;"; + child.id = "child-element"; + child.className = "child-element"; + child.textContent = "test element"; + root.appendChild(child); + return root; + }; + + info("Building the helper"); + let env = new HighlighterEnvironment(); + env.initFromWindow(doc.defaultView); + let helper = new CanvasFrameAnonymousContentHelper(env, nodeBuilder); + + ok(!helper.content, "The AnonymousContent was not inserted in the window"); + ok(!helper.getTextContentForElement("child-element"), + "No text content is returned"); + + env.destroy(); + helper.destroy(); + + gBrowser.removeCurrentTab(); +}); diff --git a/devtools/server/tests/browser/browser_canvasframe_helper_03.js b/devtools/server/tests/browser/browser_canvasframe_helper_03.js new file mode 100644 index 000000000..85e27c7de --- /dev/null +++ b/devtools/server/tests/browser/browser_canvasframe_helper_03.js @@ -0,0 +1,102 @@ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Test the CanvasFrameAnonymousContentHelper event handling mechanism. + +// This makes sure the 'domnode' protocol actor type is known when importing +// highlighter. +require("devtools/server/actors/inspector"); + +const {HighlighterEnvironment} = require("devtools/server/actors/highlighters"); + +const { + CanvasFrameAnonymousContentHelper +} = require("devtools/server/actors/highlighters/utils/markup"); + +const TEST_URL = "data:text/html;charset=utf-8,CanvasFrameAnonymousContentHelper test"; + +add_task(function* () { + let browser = yield addTab(TEST_URL); + let doc = browser.contentDocument; + + let nodeBuilder = () => { + let root = doc.createElement("div"); + let child = doc.createElement("div"); + child.style = "pointer-events:auto;width:200px;height:200px;background:red;"; + child.id = "child-element"; + child.className = "child-element"; + root.appendChild(child); + return root; + }; + + info("Building the helper"); + let env = new HighlighterEnvironment(); + env.initFromWindow(doc.defaultView); + let helper = new CanvasFrameAnonymousContentHelper(env, nodeBuilder); + + let el = helper.getElement("child-element"); + + info("Adding an event listener on the inserted element"); + let mouseDownHandled = 0; + function onMouseDown(e, id) { + is(id, "child-element", "The mousedown event was triggered on the element"); + ok(!e.originalTarget, "The originalTarget property isn't available"); + mouseDownHandled++; + } + el.addEventListener("mousedown", onMouseDown); + + info("Synthesizing an event on the inserted element"); + let onDocMouseDown = once(doc, "mousedown"); + synthesizeMouseDown(100, 100, doc.defaultView); + yield onDocMouseDown; + + is(mouseDownHandled, 1, "The mousedown event was handled once on the element"); + + info("Synthesizing an event somewhere else"); + onDocMouseDown = once(doc, "mousedown"); + synthesizeMouseDown(400, 400, doc.defaultView); + yield onDocMouseDown; + + is(mouseDownHandled, 1, "The mousedown event was not handled on the element"); + + info("Removing the event listener"); + el.removeEventListener("mousedown", onMouseDown); + + info("Synthesizing another event after the listener has been removed"); + // Using a document event listener to know when the event has been synthesized. + onDocMouseDown = once(doc, "mousedown"); + synthesizeMouseDown(100, 100, doc.defaultView); + yield onDocMouseDown; + + is(mouseDownHandled, 1, + "The mousedown event hasn't been handled after the listener was removed"); + + info("Adding again the event listener"); + el.addEventListener("mousedown", onMouseDown); + + info("Destroying the helper"); + env.destroy(); + helper.destroy(); + + info("Synthesizing another event after the helper has been destroyed"); + // Using a document event listener to know when the event has been synthesized. + onDocMouseDown = once(doc, "mousedown"); + synthesizeMouseDown(100, 100, doc.defaultView); + yield onDocMouseDown; + + is(mouseDownHandled, 1, + "The mousedown event hasn't been handled after the helper was destroyed"); + + gBrowser.removeCurrentTab(); +}); + +function synthesizeMouseDown(x, y, win) { + // We need to make sure the inserted anonymous content can be targeted by the + // event right after having been inserted, and so we need to force a sync + // reflow. + let forceReflow = win.document.documentElement.offsetWidth; + EventUtils.synthesizeMouseAtPoint(x, y, {type: "mousedown"}, win); +} diff --git a/devtools/server/tests/browser/browser_canvasframe_helper_04.js b/devtools/server/tests/browser/browser_canvasframe_helper_04.js new file mode 100644 index 000000000..d038f84a0 --- /dev/null +++ b/devtools/server/tests/browser/browser_canvasframe_helper_04.js @@ -0,0 +1,98 @@ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Test the CanvasFrameAnonymousContentHelper re-inserts the content when the +// page reloads. + +// This makes sure the 'domnode' protocol actor type is known when importing +// highlighter. +require("devtools/server/actors/inspector"); +const events = require("sdk/event/core"); + +const {HighlighterEnvironment} = require("devtools/server/actors/highlighters"); + +const { + CanvasFrameAnonymousContentHelper +} = require("devtools/server/actors/highlighters/utils/markup"); + +const TEST_URL_1 = "data:text/html;charset=utf-8,CanvasFrameAnonymousContentHelper test 1"; +const TEST_URL_2 = "data:text/html;charset=utf-8,CanvasFrameAnonymousContentHelper test 2"; + +add_task(function* () { + let browser = yield addTab(TEST_URL_2); + let doc = browser.contentDocument; + + let nodeBuilder = () => { + let root = doc.createElement("div"); + let child = doc.createElement("div"); + child.style = "pointer-events:auto;width:200px;height:200px;background:red;"; + child.id = "child-element"; + child.className = "child-element"; + child.textContent = "test content"; + root.appendChild(child); + return root; + }; + + info("Building the helper"); + let env = new HighlighterEnvironment(); + env.initFromWindow(doc.defaultView); + let helper = new CanvasFrameAnonymousContentHelper(env, nodeBuilder); + + info("Get an element from the helper"); + let el = helper.getElement("child-element"); + + info("Try to access the element"); + is(el.getAttribute("class"), "child-element", + "The attribute is correct before navigation"); + is(el.getTextContent(), "test content", + "The text content is correct before navigation"); + + info("Add an event listener on the element"); + let mouseDownHandled = 0; + function onMouseDown(e, id) { + is(id, "child-element", "The mousedown event was triggered on the element"); + mouseDownHandled++; + } + el.addEventListener("mousedown", onMouseDown); + + info("Synthesizing an event on the element"); + let onDocMouseDown = once(doc, "mousedown"); + synthesizeMouseDown(100, 100, doc.defaultView); + yield onDocMouseDown; + is(mouseDownHandled, 1, "The mousedown event was handled once before navigation"); + + info("Navigating to a new page"); + let loaded = BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser); + content.location = TEST_URL_2; + yield loaded; + doc = gBrowser.selectedBrowser.contentWindow.document; + + info("Try to access the element again"); + is(el.getAttribute("class"), "child-element", + "The attribute is correct after navigation"); + is(el.getTextContent(), "test content", + "The text content is correct after navigation"); + + info("Synthesizing an event on the element again"); + onDocMouseDown = once(doc, "mousedown"); + synthesizeMouseDown(100, 100, doc.defaultView); + yield onDocMouseDown; + is(mouseDownHandled, 1, "The mousedown event was not handled after navigation"); + + info("Destroying the helper"); + env.destroy(); + helper.destroy(); + + gBrowser.removeCurrentTab(); +}); + +function synthesizeMouseDown(x, y, win) { + // We need to make sure the inserted anonymous content can be targeted by the + // event right after having been inserted, and so we need to force a sync + // reflow. + let forceReflow = win.document.documentElement.offsetWidth; + EventUtils.synthesizeMouseAtPoint(x, y, {type: "mousedown"}, win); +} diff --git a/devtools/server/tests/browser/browser_canvasframe_helper_05.js b/devtools/server/tests/browser/browser_canvasframe_helper_05.js new file mode 100644 index 000000000..94fb66914 --- /dev/null +++ b/devtools/server/tests/browser/browser_canvasframe_helper_05.js @@ -0,0 +1,112 @@ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Test some edge cases of the CanvasFrameAnonymousContentHelper event handling +// mechanism. + +// This makes sure the 'domnode' protocol actor type is known when importing +// highlighter. +require("devtools/server/actors/inspector"); + +const {HighlighterEnvironment} = require("devtools/server/actors/highlighters"); + +const { + CanvasFrameAnonymousContentHelper +} = require("devtools/server/actors/highlighters/utils/markup"); + +const TEST_URL = "data:text/html;charset=utf-8,CanvasFrameAnonymousContentHelper test"; + +add_task(function* () { + let browser = yield addTab(TEST_URL); + let doc = browser.contentDocument; + + let nodeBuilder = () => { + let root = doc.createElement("div"); + + let parent = doc.createElement("div"); + parent.style = "pointer-events:auto;width:300px;height:300px;background:yellow;"; + parent.id = "parent-element"; + root.appendChild(parent); + + let child = doc.createElement("div"); + child.style = "pointer-events:auto;width:200px;height:200px;background:red;"; + child.id = "child-element"; + parent.appendChild(child); + + return root; + }; + + info("Building the helper"); + let env = new HighlighterEnvironment(); + env.initFromWindow(doc.defaultView); + let helper = new CanvasFrameAnonymousContentHelper(env, nodeBuilder); + + info("Getting the parent and child elements"); + let parentEl = helper.getElement("parent-element"); + let childEl = helper.getElement("child-element"); + + info("Adding an event listener on both elements"); + let mouseDownHandled = []; + function onMouseDown(e, id) { + mouseDownHandled.push(id); + } + parentEl.addEventListener("mousedown", onMouseDown); + childEl.addEventListener("mousedown", onMouseDown); + + info("Synthesizing an event on the child element"); + let onDocMouseDown = once(doc, "mousedown"); + synthesizeMouseDown(100, 100, doc.defaultView); + yield onDocMouseDown; + + is(mouseDownHandled.length, 2, "The mousedown event was handled twice"); + is(mouseDownHandled[0], "child-element", + "The mousedown event was handled on the child element"); + is(mouseDownHandled[1], "parent-element", + "The mousedown event was handled on the parent element"); + + info("Synthesizing an event on the parent, outside of the child element"); + mouseDownHandled = []; + onDocMouseDown = once(doc, "mousedown"); + synthesizeMouseDown(250, 250, doc.defaultView); + yield onDocMouseDown; + + is(mouseDownHandled.length, 1, "The mousedown event was handled only once"); + is(mouseDownHandled[0], "parent-element", + "The mousedown event was handled on the parent element"); + + info("Removing the event listener"); + parentEl.removeEventListener("mousedown", onMouseDown); + childEl.removeEventListener("mousedown", onMouseDown); + + info("Adding an event listener on the parent element only"); + mouseDownHandled = []; + parentEl.addEventListener("mousedown", onMouseDown); + + info("Synthesizing an event on the child element"); + onDocMouseDown = once(doc, "mousedown"); + synthesizeMouseDown(100, 100, doc.defaultView); + yield onDocMouseDown; + + is(mouseDownHandled.length, 1, "The mousedown event was handled once"); + is(mouseDownHandled[0], "parent-element", + "The mousedown event did bubble to the parent element"); + + info("Removing the parent listener"); + parentEl.removeEventListener("mousedown", onMouseDown); + + env.destroy(); + helper.destroy(); + + gBrowser.removeCurrentTab(); +}); + +function synthesizeMouseDown(x, y, win) { + // We need to make sure the inserted anonymous content can be targeted by the + // event right after having been inserted, and so we need to force a sync + // reflow. + let forceReflow = win.document.documentElement.offsetWidth; + EventUtils.synthesizeMouseAtPoint(x, y, {type: "mousedown"}, win); +} diff --git a/devtools/server/tests/browser/browser_canvasframe_helper_06.js b/devtools/server/tests/browser/browser_canvasframe_helper_06.js new file mode 100644 index 000000000..2b137fe26 --- /dev/null +++ b/devtools/server/tests/browser/browser_canvasframe_helper_06.js @@ -0,0 +1,100 @@ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Test support for event propagation stop in the +// CanvasFrameAnonymousContentHelper event handling mechanism. + +// This makes sure the 'domnode' protocol actor type is known when importing +// highlighter. +require("devtools/server/actors/inspector"); + +const {HighlighterEnvironment} = require("devtools/server/actors/highlighters"); + +const { + CanvasFrameAnonymousContentHelper +} = require("devtools/server/actors/highlighters/utils/markup"); + +const TEST_URL = "data:text/html;charset=utf-8,CanvasFrameAnonymousContentHelper test"; + +add_task(function* () { + let browser = yield addTab(TEST_URL); + let doc = browser.contentDocument; + + let nodeBuilder = () => { + let root = doc.createElement("div"); + + let parent = doc.createElement("div"); + parent.style = "pointer-events:auto;width:300px;height:300px;background:yellow;"; + parent.id = "parent-element"; + root.appendChild(parent); + + let child = doc.createElement("div"); + child.style = "pointer-events:auto;width:200px;height:200px;background:red;"; + child.id = "child-element"; + parent.appendChild(child); + + return root; + }; + + info("Building the helper"); + let env = new HighlighterEnvironment(); + env.initFromWindow(doc.defaultView); + let helper = new CanvasFrameAnonymousContentHelper(env, nodeBuilder); + + info("Getting the parent and child elements"); + let parentEl = helper.getElement("parent-element"); + let childEl = helper.getElement("child-element"); + + info("Adding an event listener on both elements"); + let mouseDownHandled = []; + + function onParentMouseDown(e, id) { + mouseDownHandled.push(id); + } + parentEl.addEventListener("mousedown", onParentMouseDown); + + function onChildMouseDown(e, id) { + mouseDownHandled.push(id); + e.stopPropagation(); + } + childEl.addEventListener("mousedown", onChildMouseDown); + + info("Synthesizing an event on the child element"); + let onDocMouseDown = once(doc, "mousedown"); + synthesizeMouseDown(100, 100, doc.defaultView); + yield onDocMouseDown; + + is(mouseDownHandled.length, 1, "The mousedown event was handled only once"); + is(mouseDownHandled[0], "child-element", + "The mousedown event was handled on the child element"); + + info("Synthesizing an event on the parent, outside of the child element"); + mouseDownHandled = []; + onDocMouseDown = once(doc, "mousedown"); + synthesizeMouseDown(250, 250, doc.defaultView); + yield onDocMouseDown; + + is(mouseDownHandled.length, 1, "The mousedown event was handled only once"); + is(mouseDownHandled[0], "parent-element", + "The mousedown event was handled on the parent element"); + + info("Removing the event listener"); + parentEl.removeEventListener("mousedown", onParentMouseDown); + childEl.removeEventListener("mousedown", onChildMouseDown); + + env.destroy(); + helper.destroy(); + + gBrowser.removeCurrentTab(); +}); + +function synthesizeMouseDown(x, y, win) { + // We need to make sure the inserted anonymous content can be targeted by the + // event right after having been inserted, and so we need to force a sync + // reflow. + let forceReflow = win.document.documentElement.offsetWidth; + EventUtils.synthesizeMouseAtPoint(x, y, {type: "mousedown"}, win); +} diff --git a/devtools/server/tests/browser/browser_directorscript_actors.js b/devtools/server/tests/browser/browser_directorscript_actors.js new file mode 100644 index 000000000..bdfc8f8f1 --- /dev/null +++ b/devtools/server/tests/browser/browser_directorscript_actors.js @@ -0,0 +1,159 @@ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +const {DirectorManagerFront} = require("devtools/shared/fronts/director-manager"); +const {DirectorRegistry} = require("devtools/server/actors/director-registry"); + +add_task(function* () { + let browser = yield addTab(MAIN_DOMAIN + "director-script-target.html"); + let doc = browser.contentDocument; + + initDebuggerServer(); + let client = new DebuggerClient(DebuggerServer.connectPipe()); + let form = yield connectDebuggerClient(client); + + DirectorRegistry.clear(); + let directorManager = DirectorManagerFront(client, form); + + yield testDirectorScriptAttachEventAttributes(directorManager); + yield testDirectorScriptMessagePort(directorManager); + yield testDirectorScriptWindowEval(directorManager); + yield testDirectorScriptUnloadOnDetach(directorManager); + + yield client.close(); + gBrowser.removeCurrentTab(); + DirectorRegistry.clear(); +}); + +function* testDirectorScriptAttachEventAttributes(directorManager) { + let attachEvent = yield installAndEnableDirectorScript(directorManager, { + scriptId: "testDirectorScript_attachEventAttributes", + scriptCode: "(" + (function () { + exports.attach = function () {}; + }).toString() + ")();", + scriptOptions: { + attachMethod: "attach" + } + }); + + let { directorScriptId, url } = attachEvent; + + is(directorScriptId, "testDirectorScript_attachEventAttributes", + "attach event should contains directorScriptId"); + is(url, MAIN_DOMAIN + "director-script-target.html"); +} + +function* testDirectorScriptMessagePort(directorManager) { + let { port } = yield installAndEnableDirectorScript(directorManager, { + scriptId: "testDirectorScript_MessagePort", + scriptCode: "(" + (function () { + exports.attach = function ({port}) { + port.onmessage = function (evt) { + // echo messages + evt.source.postMessage(evt.data); + }; + }; + }).toString() + ")();", + scriptOptions: { + attachMethod: "attach" + } + }); + + ok(port && port.postMessage, "testDirector_MessagePort port received"); + + // exchange messages over the MessagePort + let waitForMessagePortEvent = once(port, "message"); + // needs to explicit start the port + port.start(); + + var msg = { k1: "v1", k2: [1, 2, 3] }; + port.postMessage(msg); + + var reply = yield waitForMessagePortEvent; + + is(JSON.stringify(reply.data), JSON.stringify(msg), "echo reply received on the MessagePortClient"); +} + +function* testDirectorScriptWindowEval(directorManager) { + let { port } = yield installAndEnableDirectorScript(directorManager, { + scriptId: "testDirectorScript_WindowEval", + scriptCode: "(" + (function () { + exports.attach = function ({window, port}) { + var onpageloaded = function () { + var globalVarValue = window.eval("globalAccessibleVar;"); + port.postMessage(globalVarValue); + }; + + if (window.document && window.document.readyState === "complete") { + onpageloaded(); + } else { + window.addEventListener("load", onpageloaded, false); + } + }; + }).toString() + ")();", + scriptOptions: { + attachMethod: "attach" + } + }); + + ok(port, "testDirectorScript_WindowEval port received"); + + // exchange messages over the MessagePort + let waitForMessagePortEvent = once(port, "message"); + // needs to explicit start the port + port.start(); + + var portEvent = yield waitForMessagePortEvent; + + ok(portEvent.data !== "unsecure-eval", "window.eval should be wrapped and safe"); + is(portEvent.data, "global-value", "globalAccessibleVar should be accessible through window.eval"); +} + +function* testDirectorScriptUnloadOnDetach(directorManager) { + let { port } = yield installAndEnableDirectorScript(directorManager, { + scriptId: "testDirectorScript_unloadOnDetach", + scriptCode: "(" + (function () { + exports.attach = function ({port, onUnload}) { + onUnload(function () { + port.postMessage("ONUNLOAD"); + }); + }; + }).toString() + ")();", + scriptOptions: { + attachMethod: "attach" + } + }); + + ok(port, "testDirectorScript_unloadOnDetach port received"); + port.start(); + + let waitForDetach = once(directorManager, "director-script-detach"); + let waitForMessage = once(port, "message"); + + directorManager.disableByScriptIds(["testDirectorScript_unloadOnDetach"], {reload: false}); + + let { directorScriptId } = yield waitForDetach; + is(directorScriptId, "testDirectorScript_unloadOnDetach", + "detach event should contains directorScriptId"); + + let portEvent = yield waitForMessage; + is(portEvent.data, "ONUNLOAD", "director-script's exports.onUnload called on detach"); +} + +function* installAndEnableDirectorScript(directorManager, directorScriptDef) { + let { scriptId } = directorScriptDef; + + DirectorRegistry.install(scriptId, directorScriptDef); + + let waitForAttach = once(directorManager, "director-script-attach"); + let waitForError = once(directorManager, "director-script-error"); + + directorManager.enableByScriptIds([scriptId], {reload: false}); + + let attachOrErrorEvent = yield Promise.race([waitForAttach, waitForError]); + + return attachOrErrorEvent; +} diff --git a/devtools/server/tests/browser/browser_directorscript_actors_error_events.js b/devtools/server/tests/browser/browser_directorscript_actors_error_events.js new file mode 100644 index 000000000..0afe16388 --- /dev/null +++ b/devtools/server/tests/browser/browser_directorscript_actors_error_events.js @@ -0,0 +1,132 @@ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +const {DirectorManagerFront} = require("devtools/shared/fronts/director-manager"); +const {DirectorRegistry} = require("devtools/server/actors/director-registry"); + +add_task(function* () { + let browser = yield addTab(MAIN_DOMAIN + "director-script-target.html"); + let doc = browser.contentDocument; + + initDebuggerServer(); + let client = new DebuggerClient(DebuggerServer.connectPipe()); + let form = yield connectDebuggerClient(client); + + DirectorRegistry.clear(); + let directorManager = DirectorManagerFront(client, form); + + yield testErrorOnRequire(directorManager); + yield testErrorOnEvaluate(directorManager); + yield testErrorOnAttach(directorManager); + yield testErrorOnDetach(directorManager); + + yield client.close(); + gBrowser.removeCurrentTab(); + DirectorRegistry.clear(); +}); + +function* testErrorOnRequire(directorManager) { + // director script require method should raise a "not implemented" exception + let errorOnRequire = yield installAndEnableDirectorScript(directorManager, { + scriptId: "testDirectorScript_errorOnRequire", + scriptCode: "(" + (function () { + // this director script should generate an error event + // because require raise a "not implemented" exception + require("fake_module"); + }).toString() + ")();", + scriptOptions: {} + }); + + assertIsDirectorScriptError(errorOnRequire); + + let { message } = errorOnRequire; + is(message, "Error: NOT IMPLEMENTED", "error.message contains the expected error message"); +} + +function* testErrorOnEvaluate(directorManager) { + // director scripts should send an error events if the director script raise an exception on + // evaluation + let errorOnEvaluate = yield installAndEnableDirectorScript(directorManager, { + scriptId: "testDirectorScript_errorOnEvaluate", + scriptCode: "(" + (function () { + // this will raise an exception evaluating + // the director script + raise.an_error.during.content_script.load(); + }).toString() + ")();", + scriptOptions: {} + }); + assertIsDirectorScriptError(errorOnEvaluate); +} + +function* testErrorOnAttach(directorManager) { + // director scripts should send an error events if the director script raise an exception on + // evaluation + let errorOnAttach = yield installAndEnableDirectorScript(directorManager, { + scriptId: "testDirectorScript_errorOnAttach", + scriptCode: "(" + (function () { + // this will raise an exception on evaluating + // the director script + module.exports = function () { + raise.an_error.during.content_script.load(); + }; + }).toString() + ")();", + scriptOptions: {} + }); + assertIsDirectorScriptError(errorOnAttach); +} + +function* testErrorOnDetach(directorManager) { + // director scripts should send an error events if the director script raise an exception on + // evaluation + let attach = yield installAndEnableDirectorScript(directorManager, { + scriptId: "testDirectorScript_errorOnDetach", + scriptCode: "(" + (function () { + module.exports = function ({onUnload}) { + // this will raise an exception on unload the director script + onUnload(function () { + raise_an_error_onunload(); + }); + }; + }).toString() + ")();", + scriptOptions: {} + }); + + let waitForDetach = once(directorManager, "director-script-detach"); + let waitForError = once(directorManager, "director-script-error"); + + directorManager.disableByScriptIds(["testDirectorScript_errorOnDetach"], {reload: false}); + + let detach = yield waitForDetach; + let error = yield waitForError; + ok(detach, "testDirectorScript_errorOnDetach detach event received"); + ok(error, "testDirectorScript_errorOnDetach detach error received"); + assertIsDirectorScriptError(error); +} + +function* installAndEnableDirectorScript(directorManager, directorScriptDef) { + let { scriptId } = directorScriptDef; + + DirectorRegistry.install(scriptId, directorScriptDef); + + let waitForAttach = once(directorManager, "director-script-attach"); + let waitForError = once(directorManager, "director-script-error"); + + directorManager.enableByScriptIds([scriptId], {reload: false}); + + let attachOrErrorEvent = yield Promise.race([waitForAttach, waitForError]); + + return attachOrErrorEvent; +} + +function assertIsDirectorScriptError(error) { + ok(!!error.message, "errors should contain a message"); + ok(!!error.stack, "errors should contain a stack trace"); + ok(!!error.fileName, "errors should contain a fileName"); + ok(typeof error.columnNumber == "number", "errors should contain a columnNumber"); + ok(typeof error.lineNumber == "number", "errors should contain a lineNumber"); + + ok(!!error.directorScriptId, "errors should contain a directorScriptId"); +} diff --git a/devtools/server/tests/browser/browser_directorscript_actors_exports.js b/devtools/server/tests/browser/browser_directorscript_actors_exports.js new file mode 100644 index 000000000..f9ef56f51 --- /dev/null +++ b/devtools/server/tests/browser/browser_directorscript_actors_exports.js @@ -0,0 +1,87 @@ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +const {DirectorManagerFront} = require("devtools/shared/fronts/director-manager"); +const {DirectorRegistry} = require("devtools/server/actors/director-registry"); + +DirectorRegistry.clear(); + +add_task(function* () { + let browser = yield addTab(MAIN_DOMAIN + "director-script-target.html"); + let doc = browser.contentDocument; + + initDebuggerServer(); + let client = new DebuggerClient(DebuggerServer.connectPipe()); + let form = yield connectDebuggerClient(client); + + DirectorRegistry.clear(); + let directorManager = DirectorManagerFront(client, form); + + // director scripts attach method defaults to module.exports + let attachModuleExports = yield testDirectorScriptExports(directorManager, { + scriptId: "testDirectorScript_moduleExports", + scriptCode: "(" + (function () { + module.exports = function () {}; + }).toString() + ")();", + scriptOptions: {} + }); + ok(attachModuleExports.port, "testDirectorScript_moduleExports attach event received"); + + // director scripts attach method can be configured using the attachMethod scriptOptions + let attachExportsAttach = yield testDirectorScriptExports(directorManager, { + scriptId: "testDirectorScript_exportsAttach", + scriptCode: "(" + (function () { + exports.attach = function () {}; + }).toString() + ")();", + scriptOptions: { + attachMethod: "attach" + } + }); + ok(attachExportsAttach.port, "testDirectorScript_exportsAttach attach event received"); + + // director scripts without an attach method generates an error event + let errorUndefinedAttachMethod = yield testDirectorScriptExports(directorManager, { + scriptId: "testDirectorScript_undefinedAttachMethod", + scriptCode: "(" + (function () { + // this director script should raise an error + // because it doesn't export any attach method + }).toString() + ")();", + scriptOptions: { + attachMethod: "attach" + } + }); + let { message } = errorUndefinedAttachMethod; + ok(!!message, "testDirectorScript_undefinedAttachMethod error event received"); + assertIsDirectorScriptError(errorUndefinedAttachMethod); + + yield client.close(); + gBrowser.removeCurrentTab(); + DirectorRegistry.clear(); +}); + +function assertIsDirectorScriptError(error) { + ok(!!error.message, "errors should contain a message"); + ok(!!error.stack, "errors should contain a stack trace"); + ok(!!error.fileName, "errors should contain a fileName"); + ok(typeof error.columnNumber == "number", "errors should contain a columnNumber"); + ok(typeof error.lineNumber == "number", "errors should contain a lineNumber"); + + ok(!!error.directorScriptId, "errors should contain a directorScriptId"); +} + +function* testDirectorScriptExports(directorManager, directorScriptDef) { + let { scriptId } = directorScriptDef; + + DirectorRegistry.install(scriptId, directorScriptDef); + + let waitForAttach = once(directorManager, "director-script-attach"); + let waitForError = once(directorManager, "director-script-error"); + directorManager.enableByScriptIds([scriptId], {reload: false}); + + let attachOrErrorEvent = yield Promise.race([waitForAttach, waitForError]); + + return attachOrErrorEvent; +} diff --git a/devtools/server/tests/browser/browser_markers-cycle-collection.js b/devtools/server/tests/browser/browser_markers-cycle-collection.js new file mode 100644 index 000000000..dc33375f2 --- /dev/null +++ b/devtools/server/tests/browser/browser_markers-cycle-collection.js @@ -0,0 +1,33 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Test that we get "nsCycleCollector::Collect" and + * "nsCycleCollector::ForgetSkippable" markers when we force cycle collection. + */ + +const { PerformanceFront } = require("devtools/shared/fronts/performance"); + +add_task(function* () { + // This test runs very slowly on linux32 debug EC2 instances. + requestLongerTimeout(2); + + let browser = yield addTab(MAIN_DOMAIN + "doc_force_cc.html"); + let doc = browser.contentDocument; + + initDebuggerServer(); + let client = new DebuggerClient(DebuggerServer.connectPipe()); + let form = yield connectDebuggerClient(client); + let front = PerformanceFront(client, form); + yield front.connect(); + let rec = yield front.startRecording({ withMarkers: true }); + + let markers = yield waitForMarkerType(front, ["nsCycleCollector::Collect", "nsCycleCollector::ForgetSkippable"]); + yield front.stopRecording(rec); + + ok(markers.some(m => m.name === "nsCycleCollector::Collect"), "got some nsCycleCollector::Collect markers"); + ok(markers.some(m => m.name === "nsCycleCollector::ForgetSkippable"), "got some nsCycleCollector::Collect markers"); + + yield client.close(); + gBrowser.removeCurrentTab(); +}); diff --git a/devtools/server/tests/browser/browser_markers-docloading-01.js b/devtools/server/tests/browser/browser_markers-docloading-01.js new file mode 100644 index 000000000..3c82d56c4 --- /dev/null +++ b/devtools/server/tests/browser/browser_markers-docloading-01.js @@ -0,0 +1,37 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Test that we get DOMContentLoaded and Load markers + */ + +const { TimelineFront } = require("devtools/shared/fronts/timeline"); +const MARKER_NAMES = ["document::DOMContentLoaded", "document::Load"]; + +add_task(function* () { + let browser = yield addTab(MAIN_DOMAIN + "doc_innerHTML.html"); + let doc = browser.contentDocument; + + initDebuggerServer(); + let client = new DebuggerClient(DebuggerServer.connectPipe()); + let form = yield connectDebuggerClient(client); + let front = TimelineFront(client, form); + let rec = yield front.start({ withMarkers: true }); + + front.once("doc-loading", e => { + ok(false, "Should not be emitting doc-loading events."); + }); + + executeSoon(() => doc.location.reload()); + + yield waitForMarkerType(front, MARKER_NAMES, () => true, e => e, "markers"); + yield front.stop(rec); + + ok(true, "Found the required marker names."); + + // Wait some more time to make sure the 'doc-loading' events never get fired. + yield DevToolsUtils.waitForTime(1000); + + yield client.close(); + gBrowser.removeCurrentTab(); +}); diff --git a/devtools/server/tests/browser/browser_markers-docloading-02.js b/devtools/server/tests/browser/browser_markers-docloading-02.js new file mode 100644 index 000000000..0142ea0cd --- /dev/null +++ b/devtools/server/tests/browser/browser_markers-docloading-02.js @@ -0,0 +1,35 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Test that we get DOMContentLoaded and Load markers + */ + +const { TimelineFront } = require("devtools/shared/fronts/timeline"); +const MARKER_NAMES = ["document::DOMContentLoaded", "document::Load"]; + +add_task(function* () { + let browser = yield addTab(MAIN_DOMAIN + "doc_innerHTML.html"); + let doc = browser.contentDocument; + + initDebuggerServer(); + let client = new DebuggerClient(DebuggerServer.connectPipe()); + let form = yield connectDebuggerClient(client); + let front = TimelineFront(client, form); + let rec = yield front.start({ withMarkers: true, withDocLoadingEvents: true }); + + yield new Promise(resolve => { + front.once("doc-loading", resolve); + doc.location.reload(); + }); + + ok(true, "At least one doc-loading event got fired."); + + yield waitForMarkerType(front, MARKER_NAMES, () => true, e => e, "markers"); + yield front.stop(rec); + + ok(true, "Found the required marker names."); + + yield client.close(); + gBrowser.removeCurrentTab(); +}); diff --git a/devtools/server/tests/browser/browser_markers-docloading-03.js b/devtools/server/tests/browser/browser_markers-docloading-03.js new file mode 100644 index 000000000..1960da4da --- /dev/null +++ b/devtools/server/tests/browser/browser_markers-docloading-03.js @@ -0,0 +1,39 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Test that we get DOMContentLoaded and Load markers + */ + +const { TimelineFront } = require("devtools/shared/fronts/timeline"); +const MARKER_NAMES = ["document::DOMContentLoaded", "document::Load"]; + +add_task(function* () { + let browser = yield addTab(MAIN_DOMAIN + "doc_innerHTML.html"); + let doc = browser.contentDocument; + + initDebuggerServer(); + let client = new DebuggerClient(DebuggerServer.connectPipe()); + let form = yield connectDebuggerClient(client); + let front = TimelineFront(client, form); + let rec = yield front.start({ withDocLoadingEvents: true }); + + waitForMarkerType(front, MARKER_NAMES, () => true, e => e, "markers").then(e => { + ok(false, "Should not be emitting doc-loading markers."); + }); + + yield new Promise(resolve => { + front.once("doc-loading", resolve); + doc.location.reload(); + }); + + ok(true, "At least one doc-loading event got fired."); + + yield front.stop(rec); + + // Wait some more time to make sure the 'doc-loading' markers never get fired. + yield DevToolsUtils.waitForTime(1000); + + yield client.close(); + gBrowser.removeCurrentTab(); +}); diff --git a/devtools/server/tests/browser/browser_markers-gc.js b/devtools/server/tests/browser/browser_markers-gc.js new file mode 100644 index 000000000..559b19161 --- /dev/null +++ b/devtools/server/tests/browser/browser_markers-gc.js @@ -0,0 +1,50 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Test that we get "GarbageCollection" markers. + */ + +const { PerformanceFront } = require("devtools/shared/fronts/performance"); +const MARKER_NAME = "GarbageCollection"; + +add_task(function* () { + let browser = yield addTab(MAIN_DOMAIN + "doc_force_gc.html"); + let doc = browser.contentDocument; + + initDebuggerServer(); + let client = new DebuggerClient(DebuggerServer.connectPipe()); + let form = yield connectDebuggerClient(client); + let front = PerformanceFront(client, form); + yield front.connect(); + let rec = yield front.startRecording({ withMarkers: true }); + + let markers = yield waitForMarkerType(front, MARKER_NAME); + yield front.stopRecording(rec); + + ok(markers.some(m => m.name === MARKER_NAME), `got some ${MARKER_NAME} markers`); + ok(markers.every(({causeName}) => typeof causeName === "string"), + "All markers have a causeName."); + ok(markers.every(({cycle}) => typeof cycle === "number"), + "All markers have a `cycle` ID."); + + markers = rec.getMarkers(); + + // Bug 1197646 + let ordered = true; + markers.reduce((previousStart, current, i) => { + if (i === 0) { + return current.start; + } + if (current.start < previousStart) { + ok(false, `markers must be in order. ${current.name} marker has later start time (${current.start}) thanprevious: ${previousStart}`); + ordered = false; + } + return current.start; + }); + + is(ordered, true, "All GC and non-GC markers are in order by start time."); + + yield client.close(); + gBrowser.removeCurrentTab(); +}); diff --git a/devtools/server/tests/browser/browser_markers-minor-gc.js b/devtools/server/tests/browser/browser_markers-minor-gc.js new file mode 100644 index 000000000..332764348 --- /dev/null +++ b/devtools/server/tests/browser/browser_markers-minor-gc.js @@ -0,0 +1,32 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Test that we get "MinorGC" markers when we continue to steadily allocate + * objects. + */ + +const { PerformanceFront } = require("devtools/shared/fronts/performance"); + +add_task(function* () { + // This test runs very slowly on linux32 debug EC2 instances. + requestLongerTimeout(2); + + let doc = yield addTab(MAIN_DOMAIN + "doc_allocations.html"); + + initDebuggerServer(); + let client = new DebuggerClient(DebuggerServer.connectPipe()); + let form = yield connectDebuggerClient(client); + let front = PerformanceFront(client, form); + yield front.connect(); + let rec = yield front.startRecording({ withMarkers: true }); + + let markers = yield waitForMarkerType(front, ["MinorGC"]); + yield front.stopRecording(rec); + + ok(markers.some(m => m.name === "MinorGC" && m.causeName), + "got some MinorGC markers"); + + yield client.close(); + gBrowser.removeCurrentTab(); +}); diff --git a/devtools/server/tests/browser/browser_markers-parse-html.js b/devtools/server/tests/browser/browser_markers-parse-html.js new file mode 100644 index 000000000..bd4f479c0 --- /dev/null +++ b/devtools/server/tests/browser/browser_markers-parse-html.js @@ -0,0 +1,29 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Test that we get "Parse HTML" markers. + */ + +const { PerformanceFront } = require("devtools/shared/fronts/performance"); +const MARKER_NAME = "Parse HTML"; + +add_task(function* () { + let browser = yield addTab(MAIN_DOMAIN + "doc_innerHTML.html"); + let doc = browser.contentDocument; + + initDebuggerServer(); + let client = new DebuggerClient(DebuggerServer.connectPipe()); + let form = yield connectDebuggerClient(client); + let front = PerformanceFront(client, form); + yield front.connect(); + let rec = yield front.startRecording({ withMarkers: true }); + + let markers = yield waitForMarkerType(front, MARKER_NAME); + yield front.stopRecording(rec); + + ok(markers.some(m => m.name === MARKER_NAME), `got some ${MARKER_NAME} markers`); + + yield client.close(); + gBrowser.removeCurrentTab(); +}); diff --git a/devtools/server/tests/browser/browser_markers-styles.js b/devtools/server/tests/browser/browser_markers-styles.js new file mode 100644 index 000000000..a3dffe8b5 --- /dev/null +++ b/devtools/server/tests/browser/browser_markers-styles.js @@ -0,0 +1,34 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Test that we get "Styles" markers with correct meta. + */ + +const { PerformanceFront } = require("devtools/shared/fronts/performance"); +const MARKER_NAME = "Styles"; + +add_task(function* () { + let browser = yield addTab(MAIN_DOMAIN + "doc_perf.html"); + let doc = browser.contentDocument; + + initDebuggerServer(); + let client = new DebuggerClient(DebuggerServer.connectPipe()); + let form = yield connectDebuggerClient(client); + let front = PerformanceFront(client, form); + yield front.connect(); + let rec = yield front.startRecording({ withMarkers: true }); + + let markers = yield waitForMarkerType(front, MARKER_NAME, function (markers) { + return markers.some(({restyleHint}) => restyleHint != void 0); + }); + + yield front.stopRecording(rec); + + ok(markers.some(m => m.name === MARKER_NAME), `got some ${MARKER_NAME} markers`); + ok(markers.some(({restyleHint}) => restyleHint != void 0), + "Some markers have a restyleHint."); + + yield client.close(); + gBrowser.removeCurrentTab(); +}); diff --git a/devtools/server/tests/browser/browser_markers-timestamp.js b/devtools/server/tests/browser/browser_markers-timestamp.js new file mode 100644 index 000000000..428499502 --- /dev/null +++ b/devtools/server/tests/browser/browser_markers-timestamp.js @@ -0,0 +1,43 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Test that we get a "TimeStamp" marker. + */ + +const { PerformanceFront } = require("devtools/shared/fronts/performance"); +const { pmmConsoleMethod, pmmLoadFrameScripts, pmmClearFrameScripts } = require("devtools/client/performance/test/helpers/profiler-mm-utils"); +const MARKER_NAME = "TimeStamp"; + +add_task(function* () { + let browser = yield addTab(MAIN_DOMAIN + "doc_perf.html"); + let doc = browser.contentDocument; + + initDebuggerServer(); + let client = new DebuggerClient(DebuggerServer.connectPipe()); + let form = yield connectDebuggerClient(client); + let front = PerformanceFront(client, form); + yield front.connect(); + let rec = yield front.startRecording({ withMarkers: true }); + + pmmLoadFrameScripts(gBrowser); + pmmConsoleMethod("timeStamp"); + pmmConsoleMethod("timeStamp", "myLabel"); + + let markers = yield waitForMarkerType(front, MARKER_NAME, markers => markers.length >= 2); + + yield front.stopRecording(rec); + + ok(markers.every(({stack}) => typeof stack === "number"), "All markers have stack references."); + ok(markers.every(({name}) => name === "TimeStamp"), "All markers found are TimeStamp markers"); + ok(markers.length === 2, "found 2 TimeStamp markers"); + ok(markers.every(({start, end}) => typeof start === "number" && start === end), + "All markers have equal start and end times"); + is(markers[0].causeName, void 0, "Unlabeled timestamps have an empty causeName"); + is(markers[1].causeName, "myLabel", "Labeled timestamps have correct causeName"); + + pmmClearFrameScripts(); + + yield client.close(); + gBrowser.removeCurrentTab(); +}); diff --git a/devtools/server/tests/browser/browser_navigateEvents.js b/devtools/server/tests/browser/browser_navigateEvents.js new file mode 100644 index 000000000..f8652f197 --- /dev/null +++ b/devtools/server/tests/browser/browser_navigateEvents.js @@ -0,0 +1,160 @@ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +const URL1 = MAIN_DOMAIN + "navigate-first.html"; +const URL2 = MAIN_DOMAIN + "navigate-second.html"; + +var events = require("sdk/event/core"); +var client; + +SpecialPowers.pushPrefEnv({"set": [["dom.require_user_interaction_for_beforeunload", false]]}); + +// State machine to check events order +var i = 0; +function assertEvent(event, data) { + let x = 0; + switch (i++) { + case x++: + is(event, "request", "Get first page load"); + is(data, URL1); + break; + case x++: + is(event, "load-new-document", "Ask to load the second page"); + break; + case x++: + is(event, "unload-dialog", "We get the dialog on first page unload"); + break; + case x++: + is(event, "will-navigate", "The very first event is will-navigate on server side"); + is(data.newURI, URL2, "newURI property is correct"); + break; + case x++: + is(event, "request", "RDP is async with messageManager, the request happens after will-navigate"); + is(data, URL2); + break; + case x++: + is(event, "tabNavigated", "After the request, the client receive tabNavigated"); + is(data.state, "start", "state is start"); + is(data.url, URL2, "url property is correct"); + is(data.nativeConsoleAPI, true, "nativeConsoleAPI is correct"); + break; + case x++: + is(event, "DOMContentLoaded"); + is(content.document.readyState, "interactive"); + break; + case x++: + is(event, "load"); + is(content.document.readyState, "complete"); + break; + case x++: + is(event, "navigate", "Then once the second doc is loaded, we get the navigate event"); + is(content.document.readyState, "complete", "navigate is emitted only once the document is fully loaded"); + break; + case x++: + is(event, "tabNavigated", "Finally, the receive the client event"); + is(data.state, "stop", "state is stop"); + is(data.url, URL2, "url property is correct"); + is(data.nativeConsoleAPI, true, "nativeConsoleAPI is correct"); + + // End of test! + cleanup(); + break; + } +} + +function waitForOnBeforeUnloadDialog(browser, callback) { + browser.addEventListener("DOMWillOpenModalDialog", function onModalDialog() { + browser.removeEventListener("DOMWillOpenModalDialog", onModalDialog, true); + + executeSoon(() => { + let stack = browser.parentNode; + let dialogs = stack.getElementsByTagName("tabmodalprompt"); + let {button0, button1} = dialogs[0].ui; + callback(button0, button1); + }); + }, true); +} + +var httpObserver = function (subject, topic, state) { + let channel = subject.QueryInterface(Ci.nsIHttpChannel); + let url = channel.URI.spec; + // Only listen for our document request, as many other requests can happen + if (url == URL1 || url == URL2) { + assertEvent("request", url); + } +}; +Services.obs.addObserver(httpObserver, "http-on-modify-request", false); + +function onDOMContentLoaded() { + assertEvent("DOMContentLoaded"); +} +function onLoad() { + assertEvent("load"); +} + +function getServerTabActor(callback) { + // Ensure having a minimal server + initDebuggerServer(); + + // Connect to this tab + let transport = DebuggerServer.connectPipe(); + client = new DebuggerClient(transport); + connectDebuggerClient(client).then(form => { + let actorID = form.actor; + client.attachTab(actorID, function (aResponse, aTabClient) { + // !Hack! Retrieve a server side object, the BrowserTabActor instance + let tabActor = DebuggerServer._searchAllConnectionsForActor(actorID); + callback(tabActor); + }); + }); + + client.addListener("tabNavigated", function (aEvent, aPacket) { + assertEvent("tabNavigated", aPacket); + }); +} + +function test() { + // Open a test tab + addTab(URL1).then(function (browser) { + let doc = browser.contentDocument; + getServerTabActor(function (tabActor) { + // In order to listen to internal will-navigate/navigate events + events.on(tabActor, "will-navigate", function (data) { + assertEvent("will-navigate", data); + }); + events.on(tabActor, "navigate", function (data) { + assertEvent("navigate", data); + }); + + // Start listening for page load events + browser.addEventListener("DOMContentLoaded", onDOMContentLoaded, true); + browser.addEventListener("load", onLoad, true); + + // Listen for alert() call being made in navigate-first during unload + waitForOnBeforeUnloadDialog(browser, function (btnLeave, btnStay) { + assertEvent("unload-dialog"); + // accept to quit this page to another + btnLeave.click(); + }); + + // Load another document in this doc to dispatch these events + assertEvent("load-new-document"); + content.location = URL2; + }); + + }); +} + +function cleanup() { + let browser = gBrowser.selectedBrowser; + browser.removeEventListener("DOMContentLoaded", onDOMContentLoaded); + browser.removeEventListener("load", onLoad); + client.close().then(function () { + Services.obs.addObserver(httpObserver, "http-on-modify-request", false); + DebuggerServer.destroy(); + finish(); + }); +} diff --git a/devtools/server/tests/browser/browser_perf-allocation-data.js b/devtools/server/tests/browser/browser_perf-allocation-data.js new file mode 100644 index 000000000..3d4a94dee --- /dev/null +++ b/devtools/server/tests/browser/browser_perf-allocation-data.js @@ -0,0 +1,38 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Test that we have allocation data coming from the front. + */ + +const { PerformanceFront } = require("devtools/shared/fronts/performance"); + +add_task(function* () { + let browser = yield addTab(MAIN_DOMAIN + "doc_allocations.html"); + let doc = browser.contentDocument; + + initDebuggerServer(); + let client = new DebuggerClient(DebuggerServer.connectPipe()); + let form = yield connectDebuggerClient(client); + let front = PerformanceFront(client, form); + yield front.connect(); + + let rec = yield front.startRecording({ withMarkers: true, withAllocations: true, withTicks: true }); + + yield waitUntil(() => rec.getAllocations().frames.length); + yield waitUntil(() => rec.getAllocations().timestamps.length); + yield waitUntil(() => rec.getAllocations().sizes.length); + yield waitUntil(() => rec.getAllocations().sites.length); + + yield front.stopRecording(rec); + + let { frames, timestamps, sizes, sites } = rec.getAllocations(); + + is(timestamps.length, sizes.length, "we have the same amount of timestamps and sizes"); + ok(timestamps.every(time => time > 0 && typeof time === "number"), "all timestamps have numeric values"); + ok(sizes.every(n => n > 0 && typeof n === "number"), "all sizes are positive numbers"); + + yield front.destroy(); + yield client.close(); + gBrowser.removeCurrentTab(); +}); diff --git a/devtools/server/tests/browser/browser_perf-profiler-01.js b/devtools/server/tests/browser/browser_perf-profiler-01.js new file mode 100644 index 000000000..36d200f01 --- /dev/null +++ b/devtools/server/tests/browser/browser_perf-profiler-01.js @@ -0,0 +1,45 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Tests if the profiler connection front does not activate the built-in + * profiler module if not necessary, and doesn't deactivate it when + * a recording is stopped. + */ + +const { PerformanceFront } = require("devtools/shared/fronts/performance"); +const { pmmIsProfilerActive, pmmStopProfiler, pmmLoadFrameScripts } = require("devtools/client/performance/test/helpers/profiler-mm-utils"); + +add_task(function* () { + let browser = yield addTab(MAIN_DOMAIN + "doc_perf.html"); + let doc = browser.contentDocument; + + initDebuggerServer(); + let client = new DebuggerClient(DebuggerServer.connectPipe()); + let form = yield connectDebuggerClient(client); + let front = PerformanceFront(client, form); + yield front.connect(); + + pmmLoadFrameScripts(gBrowser); + + ok(!(yield pmmIsProfilerActive()), + "The built-in profiler module should not have been automatically started."); + + let rec = yield front.startRecording(); + yield front.stopRecording(rec); + ok((yield pmmIsProfilerActive()), + "The built-in profiler module should still be active (1)."); + + rec = yield front.startRecording(); + yield front.stopRecording(rec); + ok((yield pmmIsProfilerActive()), + "The built-in profiler module should still be active (2)."); + + yield front.destroy(); + yield client.close(); + + ok(!(yield pmmIsProfilerActive()), + "The built-in profiler module should no longer be active."); + + gBrowser.removeCurrentTab(); +}); diff --git a/devtools/server/tests/browser/browser_perf-profiler-02.js b/devtools/server/tests/browser/browser_perf-profiler-02.js new file mode 100644 index 000000000..29842cef7 --- /dev/null +++ b/devtools/server/tests/browser/browser_perf-profiler-02.js @@ -0,0 +1,46 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Tests if the built-in profiler module doesn't deactivate when the toolbox + * is destroyed if there are other consumers using it. + */ + +const { PerformanceFront } = require("devtools/shared/fronts/performance"); +const { pmmIsProfilerActive, pmmStopProfiler, pmmLoadFrameScripts } = require("devtools/client/performance/test/helpers/profiler-mm-utils"); + +add_task(function* () { + yield addTab(MAIN_DOMAIN + "doc_perf.html"); + initDebuggerServer(); + let client = new DebuggerClient(DebuggerServer.connectPipe()); + let form = yield connectDebuggerClient(client); + let firstFront = PerformanceFront(client, form); + yield firstFront.connect(); + + pmmLoadFrameScripts(gBrowser); + + yield firstFront.startRecording(); + + yield addTab(MAIN_DOMAIN + "doc_perf.html"); + let client2 = new DebuggerClient(DebuggerServer.connectPipe()); + let form2 = yield connectDebuggerClient(client2); + let secondFront = PerformanceFront(client2, form2); + yield secondFront.connect(); + pmmLoadFrameScripts(gBrowser); + + yield secondFront.startRecording(); + + // Manually teardown the tabs so we can check profiler status + yield secondFront.destroy(); + yield client2.close(); + ok((yield pmmIsProfilerActive()), + "The built-in profiler module should still be active."); + + yield firstFront.destroy(); + yield client.close(); + ok(!(yield pmmIsProfilerActive()), + "The built-in profiler module should no longer be active."); + + gBrowser.removeCurrentTab(); + gBrowser.removeCurrentTab(); +}); diff --git a/devtools/server/tests/browser/browser_perf-profiler-03.js b/devtools/server/tests/browser/browser_perf-profiler-03.js new file mode 100644 index 000000000..28d87fe45 --- /dev/null +++ b/devtools/server/tests/browser/browser_perf-profiler-03.js @@ -0,0 +1,54 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Tests if the built-in profiler module is not reactivated if no other + * consumer was using it over the remote debugger protocol, and ensures + * that the actor will work properly even in such cases (e.g. the Gecko Profiler + * addon was installed and automatically activated the profiler module). + */ + +const { PerformanceFront } = require("devtools/shared/fronts/performance"); +const { pmmIsProfilerActive, pmmStartProfiler, pmmStopProfiler, pmmLoadFrameScripts, pmmClearFrameScripts } = require("devtools/client/performance/test/helpers/profiler-mm-utils"); + +add_task(function* () { + // Ensure the profiler is already running when the test starts. + pmmLoadFrameScripts(gBrowser); + let entries = 1000000; + let interval = 1; + let features = ["js"]; + yield pmmStartProfiler({ entries, interval, features }); + + ok((yield pmmIsProfilerActive()), + "The built-in profiler module should still be active."); + + yield addTab(MAIN_DOMAIN + "doc_perf.html"); + initDebuggerServer(); + let client = new DebuggerClient(DebuggerServer.connectPipe()); + let form = yield connectDebuggerClient(client); + let firstFront = PerformanceFront(client, form); + yield firstFront.connect(); + + let recording = yield firstFront.startRecording(); + + yield addTab(MAIN_DOMAIN + "doc_perf.html"); + let client2 = new DebuggerClient(DebuggerServer.connectPipe()); + let form2 = yield connectDebuggerClient(client2); + let secondFront = PerformanceFront(client2, form2); + yield secondFront.connect(); + + yield secondFront.destroy(); + yield client2.close(); + ok((yield pmmIsProfilerActive()), + "The built-in profiler module should still be active."); + + yield firstFront.destroy(); + yield client.close(); + ok(!(yield pmmIsProfilerActive()), + "The built-in profiler module should have been automatically stopped."); + + pmmClearFrameScripts(); + + gBrowser.removeCurrentTab(); + gBrowser.removeCurrentTab(); +}); diff --git a/devtools/server/tests/browser/browser_perf-realtime-markers.js b/devtools/server/tests/browser/browser_perf-realtime-markers.js new file mode 100644 index 000000000..b9eab211c --- /dev/null +++ b/devtools/server/tests/browser/browser_perf-realtime-markers.js @@ -0,0 +1,93 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Test functionality of real time markers. + */ + +const { PerformanceFront } = require("devtools/shared/fronts/performance"); + +add_task(function* () { + let browser = yield addTab(MAIN_DOMAIN + "doc_perf.html"); + let doc = browser.contentDocument; + + initDebuggerServer(); + let client = new DebuggerClient(DebuggerServer.connectPipe()); + let form = yield connectDebuggerClient(client); + let front = PerformanceFront(client, form); + yield front.connect(); + + let lastMemoryDelta = 0; + let lastTickDelta = 0; + + let counters = { + markers: [], + memory: [], + ticks: [] + }; + + let deferreds = { + markers: defer(), + memory: defer(), + ticks: defer() + }; + + front.on("timeline-data", handler); + + let rec = yield front.startRecording({ withMarkers: true, withMemory: true, withTicks: true }); + yield Promise.all(Object.keys(deferreds).map(type => deferreds[type].promise)); + yield front.stopRecording(rec); + front.off("timeline-data", handler); + + is(counters.markers.length, 1, "one marker event fired."); + is(counters.memory.length, 3, "three memory events fired."); + is(counters.ticks.length, 3, "three ticks events fired."); + + yield front.destroy(); + yield client.close(); + gBrowser.removeCurrentTab(); + + function handler(name, data) { + if (name === "markers") { + if (counters.markers.length >= 1) { return; } + ok(data.markers[0].start, "received atleast one marker with `start`"); + ok(data.markers[0].end, "received atleast one marker with `end`"); + ok(data.markers[0].name, "received atleast one marker with `name`"); + + counters.markers.push(data.markers); + } + else if (name === "memory") { + if (counters.memory.length >= 3) { return; } + let { delta, measurement } = data; + is(typeof delta, "number", "received `delta` in memory event"); + ok(delta > lastMemoryDelta, "received `delta` in memory event"); + ok(measurement.total, "received `total` in memory event"); + + counters.memory.push({ delta, measurement }); + lastMemoryDelta = delta; + } + else if (name === "ticks") { + if (counters.ticks.length >= 3) { return; } + let { delta, timestamps } = data; + ok(delta > lastTickDelta, "received `delta` in ticks event"); + + // Timestamps aren't guaranteed to always contain tick events, since + // they're dependent on the refresh driver, which may be blocked. + + counters.ticks.push({ delta, timestamps }); + lastTickDelta = delta; + } + else if (name === "frames") { + // Nothing to do here. + } + else { + ok(false, `Received unknown event: ${name}`); + } + + if (name === "markers" && counters[name].length === 1 || + name === "memory" && counters[name].length === 3 || + name === "ticks" && counters[name].length === 3) { + deferreds[name].resolve(); + } + } +}); diff --git a/devtools/server/tests/browser/browser_perf-recording-actor-01.js b/devtools/server/tests/browser/browser_perf-recording-actor-01.js new file mode 100644 index 000000000..683493121 --- /dev/null +++ b/devtools/server/tests/browser/browser_perf-recording-actor-01.js @@ -0,0 +1,80 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Tests the state of a recording rec from start to finish for recording, + * completed, and rec data. + */ + +const { PerformanceFront } = require("devtools/shared/fronts/performance"); + +add_task(function* () { + let browser = yield addTab(MAIN_DOMAIN + "doc_perf.html"); + let doc = browser.contentDocument; + + initDebuggerServer(); + let client = new DebuggerClient(DebuggerServer.connectPipe()); + let form = yield connectDebuggerClient(client); + let front = PerformanceFront(client, form); + yield front.connect(); + + let rec = yield front.startRecording({ withMarkers: true, withTicks: true, withMemory: true }); + ok(rec.isRecording(), "RecordingModel is recording when created"); + yield busyWait(100); + yield waitUntil(() => rec.getMemory().length); + ok(true, "RecordingModel populates memory while recording"); + yield waitUntil(() => rec.getTicks().length); + ok(true, "RecordingModel populates ticks while recording"); + yield waitUntil(() => rec.getMarkers().length); + ok(true, "RecordingModel populates markers while recording"); + + ok(!rec.isCompleted(), "RecordingModel is not completed when still recording"); + + let stopping = once(front, "recording-stopping"); + let stopped = once(front, "recording-stopped"); + front.stopRecording(rec); + + yield stopping; + ok(!rec.isRecording(), "on 'recording-stopping', model is no longer recording"); + // This handler should be called BEFORE "recording-stopped" is called, as + // there is some delay, but in the event where "recording-stopped" finishes + // before we check here, ensure that we're atleast in the right state + if (rec.getProfile()) { + ok(rec.isCompleted(), "recording is completed once it has profile data"); + } else { + ok(!rec.isCompleted(), "recording is not yet completed on 'recording-stopping'"); + ok(rec.isFinalizing(), "recording is considered finalizing between 'recording-stopping' and 'recording-stopped'"); + } + + yield stopped; + ok(!rec.isRecording(), "on 'recording-stopped', model is still no longer recording"); + ok(rec.isCompleted(), "on 'recording-stopped', model is considered 'complete'"); + + checkSystemInfo(rec, "Host"); + checkSystemInfo(rec, "Client"); + + // Export and import a rec, and ensure it has the correct state. + let file = FileUtils.getFile("TmpD", ["tmpprofile.json"]); + file.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, parseInt("666", 8)); + yield rec.exportRecording(file); + + let importedModel = yield front.importRecording(file); + + ok(importedModel.isCompleted(), "All imported recordings should be completed"); + ok(!importedModel.isRecording(), "All imported recordings should not be recording"); + ok(importedModel.isImported(), "All imported recordings should be considerd imported"); + + checkSystemInfo(importedModel, "Host"); + checkSystemInfo(importedModel, "Client"); + + yield front.destroy(); + yield client.close(); + gBrowser.removeCurrentTab(); +}); + +function checkSystemInfo(recording, type) { + let data = recording[`get${type}SystemInfo`](); + for (let field of ["appid", "apptype", "vendor", "name", "version"]) { + ok(data[field], `get${type}SystemInfo() has ${field} property`); + } +} diff --git a/devtools/server/tests/browser/browser_perf-recording-actor-02.js b/devtools/server/tests/browser/browser_perf-recording-actor-02.js new file mode 100644 index 000000000..8337ad2ef --- /dev/null +++ b/devtools/server/tests/browser/browser_perf-recording-actor-02.js @@ -0,0 +1,54 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Test that buffer status is correctly updated in recording models. + */ + +var BUFFER_SIZE = 20000; +var config = { bufferSize: BUFFER_SIZE }; + +const { PerformanceFront } = require("devtools/shared/fronts/performance"); + +add_task(function* () { + let browser = yield addTab(MAIN_DOMAIN + "doc_perf.html"); + let doc = browser.contentDocument; + + initDebuggerServer(); + let client = new DebuggerClient(DebuggerServer.connectPipe()); + let form = yield connectDebuggerClient(client); + let front = PerformanceFront(client, form); + yield front.connect(); + + yield front.setProfilerStatusInterval(10); + let model = yield front.startRecording(config); + let stats = yield once(front, "profiler-status"); + is(stats.totalSize, BUFFER_SIZE, `profiler-status event has totalSize: ${stats.totalSize}`); + ok(stats.position < BUFFER_SIZE, `profiler-status event has position: ${stats.position}`); + ok(stats.generation >= 0, `profiler-status event has generation: ${stats.generation}`); + ok(stats.isActive, "profiler-status event is isActive"); + is(typeof stats.currentTime, "number", "profiler-status event has currentTime"); + + // Halt once more for a buffer status to ensure we're beyond 0 + yield once(front, "profiler-status"); + + let lastBufferStatus = 0; + let checkCount = 0; + while (lastBufferStatus < 1) { + let currentBufferStatus = front.getBufferUsageForRecording(model); + ok(currentBufferStatus > lastBufferStatus, `buffer is more filled than before: ${currentBufferStatus} > ${lastBufferStatus}`); + lastBufferStatus = currentBufferStatus; + checkCount++; + yield once(front, "profiler-status"); + } + + ok(checkCount >= 1, "atleast 1 event were fired until the buffer was filled"); + is(lastBufferStatus, 1, "buffer usage cannot surpass 100%"); + yield front.stopRecording(model); + + is(front.getBufferUsageForRecording(model), null, "buffer usage should be null when no longer recording."); + + yield front.destroy(); + yield client.close(); + gBrowser.removeCurrentTab(); +}); diff --git a/devtools/server/tests/browser/browser_perf-samples-01.js b/devtools/server/tests/browser/browser_perf-samples-01.js new file mode 100644 index 000000000..f8f4bf393 --- /dev/null +++ b/devtools/server/tests/browser/browser_perf-samples-01.js @@ -0,0 +1,63 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Tests if the retrieved profiler data samples are correctly filtered and + * normalized before passed to consumers. + */ + +const WAIT_TIME = 1000; // ms + +const { PerformanceFront } = require("devtools/shared/fronts/performance"); + +add_task(function* () { + let browser = yield addTab(MAIN_DOMAIN + "doc_perf.html"); + let doc = browser.contentDocument; + + initDebuggerServer(); + let client = new DebuggerClient(DebuggerServer.connectPipe()); + let form = yield connectDebuggerClient(client); + let front = PerformanceFront(client, form); + yield front.connect(); + + // Perform the first recording... + + let firstRecording = yield front.startRecording(); + let firstRecordingStartTime = firstRecording._startTime; + info("Started profiling at: " + firstRecordingStartTime); + + busyWait(WAIT_TIME); // allow the profiler module to sample some cpu activity + + yield front.stopRecording(firstRecording); + + ok(firstRecording.getDuration() >= WAIT_TIME, + "The first recording duration is correct."); + + // Perform the second recording... + + let secondRecording = yield front.startRecording(); + let secondRecordingStartTime = secondRecording._startTime; + info("Started profiling at: " + secondRecordingStartTime); + + busyWait(WAIT_TIME); // allow the profiler module to sample more cpu activity + + yield front.stopRecording(secondRecording); + let secondRecordingProfile = secondRecording.getProfile(); + let secondRecordingSamples = secondRecordingProfile.threads[0].samples.data; + + ok(secondRecording.getDuration() >= WAIT_TIME, + "The second recording duration is correct."); + + const TIME_SLOT = secondRecordingProfile.threads[0].samples.schema.time; + ok(secondRecordingSamples[0][TIME_SLOT] < secondRecordingStartTime, + "The second recorded sample times were normalized."); + ok(secondRecordingSamples[0][TIME_SLOT] > 0, + "The second recorded sample times were normalized correctly."); + ok(!secondRecordingSamples.find(e => e[TIME_SLOT] + secondRecordingStartTime <= firstRecording.getDuration()), + "There should be no samples from the first recording in the second one, " + + "even though the total number of frames did not overflow."); + + yield front.destroy(); + yield client.close(); + gBrowser.removeCurrentTab(); +}); diff --git a/devtools/server/tests/browser/browser_perf-samples-02.js b/devtools/server/tests/browser/browser_perf-samples-02.js new file mode 100644 index 000000000..c4d51230d --- /dev/null +++ b/devtools/server/tests/browser/browser_perf-samples-02.js @@ -0,0 +1,77 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Tests if the retrieved profiler data samples always have a (root) node. + * If this ever changes, the |ThreadNode.prototype.insert| function in + * devtools/client/performance/modules/logic/tree-model.js will have to be changed. + */ + +const WAIT_TIME = 1000; // ms + +const { PerformanceFront } = require("devtools/shared/fronts/performance"); + +add_task(function* () { + let browser = yield addTab(MAIN_DOMAIN + "doc_perf.html"); + let doc = browser.contentDocument; + + initDebuggerServer(); + let client = new DebuggerClient(DebuggerServer.connectPipe()); + let form = yield connectDebuggerClient(client); + let front = PerformanceFront(client, form); + yield front.connect(); + + let rec = yield front.startRecording(); + busyWait(WAIT_TIME); // allow the profiler module to sample some cpu activity + + yield front.stopRecording(rec); + let profile = rec.getProfile(); + let sampleCount = 0; + + for (let thread of profile.threads) { + info("Checking thread: " + thread.name); + + for (let sample of thread.samples.data) { + sampleCount++; + + let stack = getInflatedStackLocations(thread, sample); + if (stack[0] != "(root)") { + ok(false, "The sample " + stack.toSource() + " doesn't have a root node."); + } + } + } + + ok(sampleCount > 0, + "At least some samples have been iterated over, checking for root nodes."); + + yield front.destroy(); + yield client.close(); + gBrowser.removeCurrentTab(); +}); + +/** + * Inflate a particular sample's stack and return an array of strings. + */ +function getInflatedStackLocations(thread, sample) { + let stackTable = thread.stackTable; + let frameTable = thread.frameTable; + let stringTable = thread.stringTable; + let SAMPLE_STACK_SLOT = thread.samples.schema.stack; + let STACK_PREFIX_SLOT = stackTable.schema.prefix; + let STACK_FRAME_SLOT = stackTable.schema.frame; + let FRAME_LOCATION_SLOT = frameTable.schema.location; + + // Build the stack from the raw data and accumulate the locations in + // an array. + let stackIndex = sample[SAMPLE_STACK_SLOT]; + let locations = []; + while (stackIndex !== null) { + let stackEntry = stackTable.data[stackIndex]; + let frame = frameTable.data[stackEntry[STACK_FRAME_SLOT]]; + locations.push(stringTable[frame[FRAME_LOCATION_SLOT]]); + stackIndex = stackEntry[STACK_PREFIX_SLOT]; + } + + // The profiler tree is inverted, so reverse the array. + return locations.reverse(); +} diff --git a/devtools/server/tests/browser/browser_register_actor.js b/devtools/server/tests/browser/browser_register_actor.js new file mode 100644 index 000000000..73ee0cedc --- /dev/null +++ b/devtools/server/tests/browser/browser_register_actor.js @@ -0,0 +1,76 @@ +var gClient; + +function test() { + waitForExplicitFinish(); + var {ActorRegistryFront} = require("devtools/shared/fronts/actor-registry"); + var actorURL = "chrome://mochitests/content/chrome/devtools/server/tests/mochitest/hello-actor.js"; + + if (!DebuggerServer.initialized) { + DebuggerServer.init(); + DebuggerServer.addBrowserActors(); + } + + gClient = new DebuggerClient(DebuggerServer.connectPipe()); + gClient.connect() + .then(() => gClient.listTabs()) + .then(aResponse => { + + var options = { + prefix: "helloActor", + constructor: "HelloActor", + type: { tab: true } + }; + + var registry = ActorRegistryFront(gClient, aResponse); + registry.registerActor(actorURL, options).then(actorFront => { + gClient.listTabs(response => { + var tab = response.tabs[response.selected]; + ok(!!tab.helloActor, "Hello actor must exist"); + + // Make sure actor's state is maintained across listTabs requests. + checkActorState(tab.helloActor, () => { + + // Clean up + actorFront.unregister().then(() => { + gClient.close().then(() => { + DebuggerServer.destroy(); + gClient = null; + finish(); + }); + }); + }); + }); + }); + }); +} + +function checkActorState(helloActor, callback) { + getCount(helloActor, response => { + ok(!response.error, "No error"); + is(response.count, 1, "The counter must be valid"); + + getCount(helloActor, response => { + ok(!response.error, "No error"); + is(response.count, 2, "The counter must be valid"); + + gClient.listTabs(response => { + var tab = response.tabs[response.selected]; + is(tab.helloActor, helloActor, "Hello actor must be valid"); + + getCount(helloActor, response => { + ok(!response.error, "No error"); + is(response.count, 3, "The counter must be valid"); + + callback(); + }); + }); + }); + }); +} + +function getCount(actor, callback) { + gClient.request({ + to: actor, + type: "count" + }, callback); +} diff --git a/devtools/server/tests/browser/browser_storage_dynamic_windows.js b/devtools/server/tests/browser/browser_storage_dynamic_windows.js new file mode 100644 index 000000000..440c91222 --- /dev/null +++ b/devtools/server/tests/browser/browser_storage_dynamic_windows.js @@ -0,0 +1,294 @@ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +const {StorageFront} = require("devtools/shared/fronts/storage"); +Services.scriptloader.loadSubScript("chrome://mochitests/content/browser/devtools/server/tests/browser/storage-helpers.js", this); + +const beforeReload = { + cookies: { + "test1.example.org": ["c1", "cs2", "c3", "uc1"], + "sectest1.example.org": ["uc1", "cs2"] + }, + localStorage: { + "http://test1.example.org": ["ls1", "ls2"], + "http://sectest1.example.org": ["iframe-u-ls1"] + }, + sessionStorage: { + "http://test1.example.org": ["ss1"], + "http://sectest1.example.org": ["iframe-u-ss1", "iframe-u-ss2"] + }, + indexedDB: { + "http://test1.example.org": [ + JSON.stringify(["idb1", "obj1"]), + JSON.stringify(["idb1", "obj2"]), + JSON.stringify(["idb2", "obj3"]), + ], + "http://sectest1.example.org": [] + } +}; + +function* testStores(data, front) { + testWindowsBeforeReload(data); + + // FIXME: Bug 1183581 - browser_storage_dynamic_windows.js IsSafeToRunScript + // errors when testing reload in E10S mode + // yield testReload(front); + yield testAddIframe(front); + yield testRemoveIframe(front); +} + +function testWindowsBeforeReload(data) { + for (let storageType in beforeReload) { + ok(data[storageType], storageType + " storage actor is present"); + is(Object.keys(data[storageType].hosts).length, + Object.keys(beforeReload[storageType]).length, + "Number of hosts for " + storageType + "match"); + for (let host in beforeReload[storageType]) { + ok(data[storageType].hosts[host], "Host " + host + " is present"); + } + } +} + +function markOutMatched(toBeEmptied, data, deleted) { + if (!Object.keys(toBeEmptied).length) { + info("Object empty"); + return; + } + ok(Object.keys(data).length, + "At least one storage type should be present"); + for (let storageType in toBeEmptied) { + if (!data[storageType]) { + continue; + } + info("Testing for " + storageType); + for (let host in data[storageType]) { + ok(toBeEmptied[storageType][host], "Host " + host + " found"); + if (!deleted) { + for (let item of data[storageType][host]) { + let index = toBeEmptied[storageType][host].indexOf(item); + ok(index > -1, "Item found - " + item); + if (index > -1) { + toBeEmptied[storageType][host].splice(index, 1); + } + } + if (!toBeEmptied[storageType][host].length) { + delete toBeEmptied[storageType][host]; + } + } else { + delete toBeEmptied[storageType][host]; + } + } + if (!Object.keys(toBeEmptied[storageType]).length) { + delete toBeEmptied[storageType]; + } + } +} + +// function testReload(front) { +// info("Testing if reload works properly"); + +// let shouldBeEmptyFirst = Cu.cloneInto(beforeReload, {}); +// let shouldBeEmptyLast = Cu.cloneInto(beforeReload, {}); +// return new Promise(resolve => { + +// let onStoresUpdate = data => { +// info("in stores update of testReload"); +// // This might be second time stores update is happening, in which case, +// // data.deleted will be null. +// // OR.. This might be the first time on a super slow machine where both +// // data.deleted and data.added is missing in the first update. +// if (data.deleted) { +// markOutMatched(shouldBeEmptyFirst, data.deleted, true); +// } + +// if (!Object.keys(shouldBeEmptyFirst).length) { +// info("shouldBeEmptyFirst is empty now"); +// } + +// // stores-update call might not have data.added for the first time on +// // slow machines, in which case, data.added will be null +// if (data.added) { +// markOutMatched(shouldBeEmptyLast, data.added); +// } + +// if (!Object.keys(shouldBeEmptyLast).length) { +// info("Everything to be received is received."); +// endTestReloaded(); +// } +// }; + +// let endTestReloaded = () => { +// front.off("stores-update", onStoresUpdate); +// resolve(); +// }; + +// front.on("stores-update", onStoresUpdate); + +// content.location.reload(); +// }); +// } + +function testAddIframe(front) { + info("Testing if new iframe addition works properly"); + return new Promise(resolve => { + let shouldBeEmpty = { + localStorage: { + "https://sectest1.example.org": ["iframe-s-ls1"] + }, + sessionStorage: { + "https://sectest1.example.org": ["iframe-s-ss1"] + }, + cookies: { + "sectest1.example.org": ["sc1"] + }, + indexedDB: { + // empty because indexed db creation happens after the page load, so at + // the time of window-ready, there was no indexed db present. + "https://sectest1.example.org": [] + }, + Cache: { + "https://sectest1.example.org":[] + } + }; + + let onStoresUpdate = data => { + info("checking if the hosts list is correct for this iframe addition"); + + markOutMatched(shouldBeEmpty, data.added); + + ok(!data.changed || !data.changed.cookies || + !data.changed.cookies["https://sectest1.example.org"], + "Nothing got changed for cookies"); + ok(!data.changed || !data.changed.localStorage || + !data.changed.localStorage["https://sectest1.example.org"], + "Nothing got changed for local storage"); + ok(!data.changed || !data.changed.sessionStorage || + !data.changed.sessionStorage["https://sectest1.example.org"], + "Nothing got changed for session storage"); + ok(!data.changed || !data.changed.indexedDB || + !data.changed.indexedDB["https://sectest1.example.org"], + "Nothing got changed for indexed db"); + + ok(!data.deleted || !data.deleted.cookies || + !data.deleted.cookies["https://sectest1.example.org"], + "Nothing got deleted for cookies"); + ok(!data.deleted || !data.deleted.localStorage || + !data.deleted.localStorage["https://sectest1.example.org"], + "Nothing got deleted for local storage"); + ok(!data.deleted || !data.deleted.sessionStorage || + !data.deleted.sessionStorage["https://sectest1.example.org"], + "Nothing got deleted for session storage"); + ok(!data.deleted || !data.deleted.indexedDB || + !data.deleted.indexedDB["https://sectest1.example.org"], + "Nothing got deleted for indexed db"); + + if (!Object.keys(shouldBeEmpty).length) { + info("Everything to be received is received."); + endTestReloaded(); + } + }; + + let endTestReloaded = () => { + front.off("stores-update", onStoresUpdate); + resolve(); + }; + + front.on("stores-update", onStoresUpdate); + + let iframe = content.document.createElement("iframe"); + iframe.src = ALT_DOMAIN_SECURED + "storage-secured-iframe.html"; + content.document.querySelector("body").appendChild(iframe); + }); +} + +function testRemoveIframe(front) { + info("Testing if iframe removal works properly"); + return new Promise(resolve => { + let shouldBeEmpty = { + localStorage: { + "http://sectest1.example.org": [] + }, + sessionStorage: { + "http://sectest1.example.org": [] + }, + Cache: { + "http://sectest1.example.org": [] + }, + indexedDB: { + "http://sectest1.example.org": [] + } + }; + + let onStoresUpdate = data => { + info("checking if the hosts list is correct for this iframe deletion"); + + markOutMatched(shouldBeEmpty, data.deleted, true); + + ok(!data.deleted.cookies || !data.deleted.cookies["sectest1.example.org"], + "Nothing got deleted for Cookies as " + + "the same hostname is still present"); + + ok(!data.changed || !data.changed.cookies || + !data.changed.cookies["http://sectest1.example.org"], + "Nothing got changed for cookies"); + ok(!data.changed || !data.changed.localStorage || + !data.changed.localStorage["http://sectest1.example.org"], + "Nothing got changed for local storage"); + ok(!data.changed || !data.changed.sessionStorage || + !data.changed.sessionStorage["http://sectest1.example.org"], + "Nothing got changed for session storage"); + + ok(!data.added || !data.added.cookies || + !data.added.cookies["http://sectest1.example.org"], + "Nothing got added for cookies"); + ok(!data.added || !data.added.localStorage || + !data.added.localStorage["http://sectest1.example.org"], + "Nothing got added for local storage"); + ok(!data.added || !data.added.sessionStorage || + !data.added.sessionStorage["http://sectest1.example.org"], + "Nothing got added for session storage"); + + if (!Object.keys(shouldBeEmpty).length) { + info("Everything to be received is received."); + endTestReloaded(); + } + }; + + let endTestReloaded = () => { + front.off("stores-update", onStoresUpdate); + resolve(); + }; + + front.on("stores-update", onStoresUpdate); + + for (let iframe of content.document.querySelectorAll("iframe")) { + if (iframe.src.startsWith("http:")) { + iframe.remove(); + break; + } + } + }); +} + +add_task(function* () { + yield openTabAndSetupStorage(MAIN_DOMAIN + "storage-dynamic-windows.html"); + + initDebuggerServer(); + let client = new DebuggerClient(DebuggerServer.connectPipe()); + let form = yield connectDebuggerClient(client); + let front = StorageFront(client, form); + let data = yield front.listStores(); + yield testStores(data, front); + + yield clearStorage(); + + // Forcing GC/CC to get rid of docshells and windows created by this test. + forceCollections(); + yield client.close(); + forceCollections(); + DebuggerServer.destroy(); + forceCollections(); +}); diff --git a/devtools/server/tests/browser/browser_storage_listings.js b/devtools/server/tests/browser/browser_storage_listings.js new file mode 100644 index 000000000..4ff3c3fc1 --- /dev/null +++ b/devtools/server/tests/browser/browser_storage_listings.js @@ -0,0 +1,610 @@ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +const {StorageFront} = require("devtools/shared/fronts/storage"); +Services.scriptloader.loadSubScript("chrome://mochitests/content/browser/devtools/server/tests/browser/storage-helpers.js", this); + +const storeMap = { + cookies: { + "test1.example.org": [ + { + name: "c1", + value: "foobar", + expires: 2000000000000, + path: "/browser", + host: "test1.example.org", + isDomain: false, + isSecure: false, + }, + { + name: "cs2", + value: "sessionCookie", + path: "/", + host: ".example.org", + expires: 0, + isDomain: true, + isSecure: false, + }, + { + name: "c3", + value: "foobar-2", + expires: 2000000001000, + path: "/", + host: "test1.example.org", + isDomain: false, + isSecure: false, + }, + { + name: "uc1", + value: "foobar", + host: ".example.org", + path: "/", + expires: 0, + isDomain: true, + isSecure: true, + } + ], + "sectest1.example.org": [ + { + name: "uc1", + value: "foobar", + host: ".example.org", + path: "/", + expires: 0, + isDomain: true, + isSecure: true, + }, + { + name: "cs2", + value: "sessionCookie", + path: "/", + host: ".example.org", + expires: 0, + isDomain: true, + isSecure: false, + }, + { + name: "sc1", + value: "foobar", + path: "/browser/devtools/server/tests/browser/", + host: "sectest1.example.org", + expires: 0, + isDomain: false, + isSecure: false, + } + ] + }, + localStorage: { + "http://test1.example.org": [ + { + name: "ls1", + value: "foobar" + }, + { + name: "ls2", + value: "foobar-2" + } + ], + "http://sectest1.example.org": [ + { + name: "iframe-u-ls1", + value: "foobar" + } + ], + "https://sectest1.example.org": [ + { + name: "iframe-s-ls1", + value: "foobar" + } + ] + }, + sessionStorage: { + "http://test1.example.org": [ + { + name: "ss1", + value: "foobar-3" + } + ], + "http://sectest1.example.org": [ + { + name: "iframe-u-ss1", + value: "foobar1" + }, + { + name: "iframe-u-ss2", + value: "foobar2" + } + ], + "https://sectest1.example.org": [ + { + name: "iframe-s-ss1", + value: "foobar-2" + } + ] + } +}; + +const IDBValues = { + listStoresResponse: { + "http://test1.example.org": [ + ["idb1", "obj1"], ["idb1", "obj2"], ["idb2", "obj3"] + ], + "http://sectest1.example.org": [ + ], + "https://sectest1.example.org": [ + ["idb-s1", "obj-s1"], ["idb-s2", "obj-s2"] + ] + }, + dbDetails : { + "http://test1.example.org": [ + { + db: "idb1", + origin: "http://test1.example.org", + version: 1, + objectStores: 2 + }, + { + db: "idb2", + origin: "http://test1.example.org", + version: 1, + objectStores: 1 + }, + ], + "http://sectest1.example.org": [ + ], + "https://sectest1.example.org": [ + { + db: "idb-s1", + origin: "https://sectest1.example.org", + version: 1, + objectStores: 1 + }, + { + db: "idb-s2", + origin: "https://sectest1.example.org", + version: 1, + objectStores: 1 + }, + ] + }, + objectStoreDetails: { + "http://test1.example.org": { + idb1: [ + { + objectStore: "obj1", + keyPath: "id", + autoIncrement: false, + indexes: [ + { + name: "name", + keyPath: "name", + "unique": false, + multiEntry: false, + }, + { + name: "email", + keyPath: "email", + "unique": true, + multiEntry: false, + }, + ] + }, + { + objectStore: "obj2", + keyPath: "id2", + autoIncrement: false, + indexes: [] + } + ], + idb2: [ + { + objectStore: "obj3", + keyPath: "id3", + autoIncrement: false, + indexes: [ + { + name: "name2", + keyPath: "name2", + "unique": true, + multiEntry: false, + } + ] + }, + ] + }, + "http://sectest1.example.org" : {}, + "https://sectest1.example.org": { + "idb-s1": [ + { + objectStore: "obj-s1", + keyPath: "id", + autoIncrement: false, + indexes: [] + }, + ], + "idb-s2": [ + { + objectStore: "obj-s2", + keyPath: "id3", + autoIncrement: true, + indexes: [ + { + name: "name2", + keyPath: "name2", + "unique": true, + multiEntry: false, + } + ] + }, + ] + } + + }, + entries: { + "http://test1.example.org": { + "idb1#obj1": [ + { + name: 1, + value: { + id: 1, + name: "foo", + email: "foo@bar.com", + } + }, + { + name: 2, + value: { + id: 2, + name: "foo2", + email: "foo2@bar.com", + } + }, + { + name: 3, + value: { + id: 3, + name: "foo2", + email: "foo3@bar.com", + } + } + ], + "idb1#obj2": [ + { + name: 1, + value: { + id2: 1, + name: "foo", + email: "foo@bar.com", + extra: "baz" + } + } + ], + "idb2#obj3": [] + }, + "http://sectest1.example.org" : {}, + "https://sectest1.example.org": { + "idb-s1#obj-s1": [ + { + name: 6, + value: { + id: 6, + name: "foo", + email: "foo@bar.com", + } + }, + { + name: 7, + value: { + id: 7, + name: "foo2", + email: "foo2@bar.com", + } + } + ], + "idb-s2#obj-s2": [ + { + name: 13, + value: { + id2: 13, + name2: "foo", + email: "foo@bar.com", + } + } + ] + } + } +}; + +function finishTests(client) { + + let closeConnection = () => { + + }; +} + +function* testStores(data) { + ok(data.cookies, "Cookies storage actor is present"); + ok(data.localStorage, "Local Storage storage actor is present"); + ok(data.sessionStorage, "Session Storage storage actor is present"); + ok(data.indexedDB, "Indexed DB storage actor is present"); + yield testCookies(data.cookies); + yield testLocalStorage(data.localStorage); + yield testSessionStorage(data.sessionStorage); + yield testIndexedDB(data.indexedDB); +} + +function testCookies(cookiesActor) { + is(Object.keys(cookiesActor.hosts).length, 2, "Correct number of host entries for cookies"); + return testCookiesObjects(0, cookiesActor.hosts, cookiesActor); +} + +var testCookiesObjects = Task.async(function* (index, hosts, cookiesActor) { + let host = Object.keys(hosts)[index]; + let matchItems = data => { + let cookiesLength = 0; + for (let secureCookie of storeMap.cookies[host]) { + if (secureCookie.isSecure) { + ++cookiesLength; + } + } + // Any secure cookies did not get stored in the database. + is(data.total, storeMap.cookies[host].length - cookiesLength, + "Number of cookies in host " + host + " matches"); + for (let item of data.data) { + let found = false; + for (let toMatch of storeMap.cookies[host]) { + if (item.name == toMatch.name) { + found = true; + ok(true, "Found cookie " + item.name + " in response"); + is(item.value.str, toMatch.value, "The value matches."); + is(item.expires, toMatch.expires, "The expiry time matches."); + is(item.path, toMatch.path, "The path matches."); + is(item.host, toMatch.host, "The host matches."); + is(item.isSecure, toMatch.isSecure, "The isSecure value matches."); + is(item.isDomain, toMatch.isDomain, "The isDomain value matches."); + break; + } + } + ok(found, "cookie " + item.name + " should exist in response"); + } + }; + + ok(!!storeMap.cookies[host], "Host is present in the list : " + host); + matchItems(yield cookiesActor.getStoreObjects(host)); + if (index == Object.keys(hosts).length - 1) { + return; + } + yield testCookiesObjects(++index, hosts, cookiesActor); +}); + +function testLocalStorage(localStorageActor) { + is(Object.keys(localStorageActor.hosts).length, 3, + "Correct number of host entries for local storage"); + return testLocalStorageObjects(0, localStorageActor.hosts, localStorageActor); +} + +var testLocalStorageObjects = Task.async(function* (index, hosts, localStorageActor) { + let host = Object.keys(hosts)[index]; + let matchItems = data => { + is(data.total, storeMap.localStorage[host].length, + "Number of local storage items in host " + host + " matches"); + for (let item of data.data) { + let found = false; + for (let toMatch of storeMap.localStorage[host]) { + if (item.name == toMatch.name) { + found = true; + ok(true, "Found local storage item " + item.name + " in response"); + is(item.value.str, toMatch.value, "The value matches."); + break; + } + } + ok(found, "local storage item " + item.name + " should exist in response"); + } + }; + + ok(!!storeMap.localStorage[host], "Host is present in the list : " + host); + matchItems(yield localStorageActor.getStoreObjects(host)); + if (index == Object.keys(hosts).length - 1) { + return; + } + yield testLocalStorageObjects(++index, hosts, localStorageActor); +}); + +function testSessionStorage(sessionStorageActor) { + is(Object.keys(sessionStorageActor.hosts).length, 3, + "Correct number of host entries for session storage"); + return testSessionStorageObjects(0, sessionStorageActor.hosts, + sessionStorageActor); +} + +var testSessionStorageObjects = Task.async(function* (index, hosts, sessionStorageActor) { + let host = Object.keys(hosts)[index]; + let matchItems = data => { + is(data.total, storeMap.sessionStorage[host].length, + "Number of session storage items in host " + host + " matches"); + for (let item of data.data) { + let found = false; + for (let toMatch of storeMap.sessionStorage[host]) { + if (item.name == toMatch.name) { + found = true; + ok(true, "Found session storage item " + item.name + " in response"); + is(item.value.str, toMatch.value, "The value matches."); + break; + } + } + ok(found, "session storage item " + item.name + " should exist in response"); + } + }; + + ok(!!storeMap.sessionStorage[host], "Host is present in the list : " + host); + matchItems(yield sessionStorageActor.getStoreObjects(host)); + if (index == Object.keys(hosts).length - 1) { + return; + } + yield testSessionStorageObjects(++index, hosts, sessionStorageActor); +}); + +var testIndexedDB = Task.async(function* (indexedDBActor) { + is(Object.keys(indexedDBActor.hosts).length, 3, + "Correct number of host entries for indexed db"); + + for (let host in indexedDBActor.hosts) { + for (let item of indexedDBActor.hosts[host]) { + let parsedItem = JSON.parse(item); + let found = false; + for (let toMatch of IDBValues.listStoresResponse[host]) { + if (toMatch[0] == parsedItem[0] && toMatch[1] == parsedItem[1]) { + found = true; + break; + } + } + ok(found, item + " should exist in list stores response"); + } + } + + yield testIndexedDBs(0, indexedDBActor.hosts, indexedDBActor); + yield testObjectStores(0, indexedDBActor.hosts, indexedDBActor); + yield testIDBEntries(0, indexedDBActor.hosts, indexedDBActor); +}); + +var testIndexedDBs = Task.async(function* (index, hosts, indexedDBActor) { + let host = Object.keys(hosts)[index]; + let matchItems = data => { + is(data.total, IDBValues.dbDetails[host].length, + "Number of indexed db in host " + host + " matches"); + for (let item of data.data) { + let found = false; + for (let toMatch of IDBValues.dbDetails[host]) { + if (item.db == toMatch.db) { + found = true; + ok(true, "Found indexed db " + item.db + " in response"); + is(item.origin, toMatch.origin, "The origin matches."); + is(item.version, toMatch.version, "The version matches."); + is(item.objectStores, toMatch.objectStores, + "The numebr of object stores matches."); + break; + } + } + ok(found, "indexed db " + item.name + " should exist in response"); + } + }; + + ok(!!IDBValues.dbDetails[host], "Host is present in the list : " + host); + matchItems(yield indexedDBActor.getStoreObjects(host)); + if (index == Object.keys(hosts).length - 1) { + return; + } + yield testIndexedDBs(++index, hosts, indexedDBActor); +}); + +var testObjectStores = Task.async(function* (index, hosts, indexedDBActor) { + let host = Object.keys(hosts)[index]; + let matchItems = (data, db) => { + is(data.total, IDBValues.objectStoreDetails[host][db].length, + "Number of object stores in host " + host + " matches"); + for (let item of data.data) { + let found = false; + for (let toMatch of IDBValues.objectStoreDetails[host][db]) { + if (item.objectStore == toMatch.objectStore) { + found = true; + ok(true, "Found object store " + item.objectStore + " in response"); + is(item.keyPath, toMatch.keyPath, "The keyPath matches."); + is(item.autoIncrement, toMatch.autoIncrement, "The autoIncrement matches."); + item.indexes = JSON.parse(item.indexes); + is(item.indexes.length, toMatch.indexes.length, "Number of indexes match"); + for (let index of item.indexes) { + let indexFound = false; + for (let toMatchIndex of toMatch.indexes) { + if (toMatchIndex.name == index.name) { + indexFound = true; + ok(true, "Found index " + index.name); + is(index.keyPath, toMatchIndex.keyPath, + "The keyPath of index matches."); + is(index.unique, toMatchIndex.unique, "The unique matches"); + is(index.multiEntry, toMatchIndex.multiEntry, + "The multiEntry matches"); + break; + } + } + ok(indexFound, "Index " + index + " should exist in response"); + } + break; + } + } + ok(found, "indexed db " + item.name + " should exist in response"); + } + }; + + ok(!!IDBValues.objectStoreDetails[host], "Host is present in the list : " + host); + for (let name of hosts[host]) { + let objName = JSON.parse(name).slice(0, 1); + matchItems(( + yield indexedDBActor.getStoreObjects(host, [JSON.stringify(objName)]) + ), objName[0]); + } + if (index == Object.keys(hosts).length - 1) { + return; + } + yield testObjectStores(++index, hosts, indexedDBActor); +}); + +var testIDBEntries = Task.async(function* (index, hosts, indexedDBActor) { + let host = Object.keys(hosts)[index]; + let matchItems = (data, obj) => { + is(data.total, IDBValues.entries[host][obj].length, + "Number of items in object store " + obj + " matches"); + for (let item of data.data) { + let found = false; + for (let toMatch of IDBValues.entries[host][obj]) { + if (item.name == toMatch.name) { + found = true; + ok(true, "Found indexed db item " + item.name + " in response"); + let value = JSON.parse(item.value.str); + is(Object.keys(value).length, Object.keys(toMatch.value).length, + "Number of entries in the value matches"); + for (let key in value) { + is(value[key], toMatch.value[key], + "value of " + key + " value key matches"); + } + break; + } + } + ok(found, "indexed db item " + item.name + " should exist in response"); + } + }; + + ok(!!IDBValues.entries[host], "Host is present in the list : " + host); + for (let name of hosts[host]) { + let parsed = JSON.parse(name); + matchItems(( + yield indexedDBActor.getStoreObjects(host, [name]) + ), parsed[0] + "#" + parsed[1]); + } + if (index == Object.keys(hosts).length - 1) { + return; + } + yield testObjectStores(++index, hosts, indexedDBActor); +}); + +add_task(function* () { + yield openTabAndSetupStorage(MAIN_DOMAIN + "storage-listings.html"); + + initDebuggerServer(); + let client = new DebuggerClient(DebuggerServer.connectPipe()); + let form = yield connectDebuggerClient(client); + let front = StorageFront(client, form); + let data = yield front.listStores(); + yield testStores(data); + + yield clearStorage(); + + // Forcing GC/CC to get rid of docshells and windows created by this test. + forceCollections(); + yield client.close(); + forceCollections(); + DebuggerServer.destroy(); + forceCollections(); +}); diff --git a/devtools/server/tests/browser/browser_storage_updates.js b/devtools/server/tests/browser/browser_storage_updates.js new file mode 100644 index 000000000..28b2e509f --- /dev/null +++ b/devtools/server/tests/browser/browser_storage_updates.js @@ -0,0 +1,304 @@ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +const {StorageFront} = require("devtools/shared/fronts/storage"); +const beforeReload = { + cookies: ["test1.example.org", "sectest1.example.org"], + localStorage: ["http://test1.example.org", "http://sectest1.example.org"], + sessionStorage: ["http://test1.example.org", "http://sectest1.example.org"], +}; + +const TESTS = [ + // index 0 + { + action: function (win) { + info('win.addCookie("c1", "foobar1")'); + win.addCookie("c1", "foobar1"); + + info('win.addCookie("c2", "foobar2")'); + win.addCookie("c2", "foobar2"); + + info('win.localStorage.setItem("l1", "foobar1")'); + win.localStorage.setItem("l1", "foobar1"); + }, + expected: { + added: { + cookies: { + "test1.example.org": ["c1", "c2"] + }, + localStorage: { + "http://test1.example.org": ["l1"] + } + } + } + }, + + // index 1 + { + action: function (win) { + info('win.addCookie("c1", "new_foobar1")'); + win.addCookie("c1", "new_foobar1"); + + info('win.localStorage.setItem("l2", "foobar2")'); + win.localStorage.setItem("l2", "foobar2"); + }, + expected: { + changed: { + cookies: { + "test1.example.org": ["c1"] + } + }, + added: { + localStorage: { + "http://test1.example.org": ["l2"] + } + } + } + }, + + // index 2 + { + action: function (win) { + info('win.removeCookie("c2")'); + win.removeCookie("c2"); + + info('win.localStorage.removeItem("l1")'); + win.localStorage.removeItem("l1"); + + info('win.localStorage.setItem("l3", "foobar3")'); + win.localStorage.setItem("l3", "foobar3"); + }, + expected: { + deleted: { + cookies: { + "test1.example.org": ["c2"] + }, + localStorage: { + "http://test1.example.org": ["l1"] + } + }, + added: { + localStorage: { + "http://test1.example.org": ["l3"] + } + } + } + }, + + // index 3 + { + action: function (win) { + info('win.removeCookie("c1")'); + win.removeCookie("c1"); + + info('win.addCookie("c3", "foobar3")'); + win.addCookie("c3", "foobar3"); + + info('win.localStorage.removeItem("l2")'); + win.localStorage.removeItem("l2"); + + info('win.sessionStorage.setItem("s1", "foobar1")'); + win.sessionStorage.setItem("s1", "foobar1"); + + info('win.sessionStorage.setItem("s2", "foobar2")'); + win.sessionStorage.setItem("s2", "foobar2"); + + info('win.localStorage.setItem("l3", "new_foobar3")'); + win.localStorage.setItem("l3", "new_foobar3"); + }, + expected: { + added: { + cookies: { + "test1.example.org": ["c3"] + }, + sessionStorage: { + "http://test1.example.org": ["s1", "s2"] + } + }, + changed: { + localStorage: { + "http://test1.example.org": ["l3"] + } + }, + deleted: { + cookies: { + "test1.example.org": ["c1"] + }, + localStorage: { + "http://test1.example.org": ["l2"] + } + } + } + }, + + // index 4 + { + action: function (win) { + info('win.sessionStorage.removeItem("s1")'); + win.sessionStorage.removeItem("s1"); + }, + expected: { + deleted: { + sessionStorage: { + "http://test1.example.org": ["s1"] + } + } + } + }, + + // index 5 + { + action: function (win) { + info("win.clearCookies()"); + win.clearCookies(); + }, + expected: { + deleted: { + cookies: { + "test1.example.org": ["c3"] + } + } + } + } +]; + +function markOutMatched(toBeEmptied, data) { + if (!Object.keys(toBeEmptied).length) { + info("Object empty"); + return; + } + ok(Object.keys(data).length, "At least one storage type should be present"); + + for (let storageType in toBeEmptied) { + if (!data[storageType]) { + continue; + } + info("Testing for " + storageType); + for (let host in data[storageType]) { + ok(toBeEmptied[storageType][host], "Host " + host + " found"); + + for (let item of data[storageType][host]) { + let index = toBeEmptied[storageType][host].indexOf(item); + ok(index > -1, "Item found - " + item); + if (index > -1) { + toBeEmptied[storageType][host].splice(index, 1); + } + } + if (!toBeEmptied[storageType][host].length) { + delete toBeEmptied[storageType][host]; + } + } + if (!Object.keys(toBeEmptied[storageType]).length) { + delete toBeEmptied[storageType]; + } + } +} + +function onStoresUpdate(expected, {added, changed, deleted}, index) { + info("inside stores update for index " + index); + + // Here, added, changed and deleted might be null even if they are required as + // per expected. This is fine as they might come in the next stores-update + // call or have already come in the previous one. + if (added) { + info("matching added object for index " + index); + markOutMatched(expected.added, added); + } + if (changed) { + info("matching changed object for index " + index); + markOutMatched(expected.changed, changed); + } + if (deleted) { + info("matching deleted object for index " + index); + markOutMatched(expected.deleted, deleted); + } + + if ((!expected.added || !Object.keys(expected.added).length) && + (!expected.changed || !Object.keys(expected.changed).length) && + (!expected.deleted || !Object.keys(expected.deleted).length)) { + info("Everything expected has been received for index " + index); + } else { + info("Still some updates pending for index " + index); + } +} + +function runTest({action, expected}, front, win, index) { + return new Promise(resolve => { + front.once("stores-update", function (addedChangedDeleted) { + onStoresUpdate(expected, addedChangedDeleted, index); + resolve(); + }); + + info("Running test at index " + index); + action(win); + }); +} + +function* testClearLocalAndSessionStores(front, win) { + return new Promise(resolve => { + // We need to wait until we have received stores-cleared for both local and + // session storage. + let localStorage = false; + let sessionStorage = false; + + front.on("stores-cleared", function onStoresCleared(data) { + storesCleared(data); + + if (data.localStorage) { + localStorage = true; + } + if (data.sessionStorage) { + sessionStorage = true; + } + if (localStorage && sessionStorage) { + front.off("stores-cleared", onStoresCleared); + resolve(); + } + }); + + win.clearLocalAndSessionStores(); + }); +} + +function storesCleared(data) { + if (data.sessionStorage || data.localStorage) { + let hosts = data.sessionStorage || data.localStorage; + info("Stores cleared required for session storage"); + is(hosts.length, 1, "number of hosts is 1"); + is(hosts[0], "http://test1.example.org", + "host matches for " + Object.keys(data)[0]); + } else { + ok(false, "Stores cleared should only be for local and session storage"); + } +} + +function* finishTests(client) { + yield client.close(); + DebuggerServer.destroy(); + finish(); +} + +add_task(function* () { + let browser = yield addTab(MAIN_DOMAIN + "storage-updates.html"); + let doc = browser.contentDocument; + + initDebuggerServer(); + + let client = new DebuggerClient(DebuggerServer.connectPipe()); + let form = yield connectDebuggerClient(client); + let front = StorageFront(client, form); + let win = doc.defaultView.wrappedJSObject; + + yield front.listStores(); + + for (let i = 0; i < TESTS.length; i++) { + let test = TESTS[i]; + yield runTest(test, front, win, i); + } + + yield testClearLocalAndSessionStores(front, win); + yield finishTests(client); +}); diff --git a/devtools/server/tests/browser/browser_stylesheets_getTextEmpty.js b/devtools/server/tests/browser/browser_stylesheets_getTextEmpty.js new file mode 100644 index 000000000..f7bb7057e --- /dev/null +++ b/devtools/server/tests/browser/browser_stylesheets_getTextEmpty.js @@ -0,0 +1,40 @@ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Test that StyleSheetActor.getText handles empty text correctly. + +const {StyleSheetsFront} = require("devtools/shared/fronts/stylesheets"); + +const CONTENT = "<style>body { background-color: #f0c; }</style>"; +const TEST_URI = "data:text/html;charset=utf-8," + encodeURIComponent(CONTENT); + +add_task(function* () { + yield addTab(TEST_URI); + + info("Initialising the debugger server and client."); + initDebuggerServer(); + let client = new DebuggerClient(DebuggerServer.connectPipe()); + let form = yield connectDebuggerClient(client); + + info("Attaching to the active tab."); + yield client.attachTab(form.actor); + + let front = StyleSheetsFront(client, form); + ok(front, "The StyleSheetsFront was created."); + + let sheets = yield front.getStyleSheets(); + ok(sheets, "getStyleSheets() succeeded"); + is(sheets.length, 1, + "getStyleSheets() returned the correct number of sheets"); + + let sheet = sheets[0]; + yield sheet.update("", false); + let longStr = yield sheet.getText(); + let source = yield longStr.string(); + is(source, "", "text is empty"); + + yield client.close(); +}); diff --git a/devtools/server/tests/browser/browser_stylesheets_nested-iframes.js b/devtools/server/tests/browser/browser_stylesheets_nested-iframes.js new file mode 100644 index 000000000..c382b6ce8 --- /dev/null +++ b/devtools/server/tests/browser/browser_stylesheets_nested-iframes.js @@ -0,0 +1,38 @@ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Test that StyleSheetsActor.getStyleSheets() works if an iframe does not have +// a content document. + +const {StyleSheetsFront} = require("devtools/shared/fronts/stylesheets"); + +add_task(function* () { + let browser = yield addTab(MAIN_DOMAIN + "stylesheets-nested-iframes.html"); + let doc = browser.contentDocument; + + info("Initialising the debugger server and client."); + initDebuggerServer(); + let client = new DebuggerClient(DebuggerServer.connectPipe()); + let form = yield connectDebuggerClient(client); + + info("Attaching to the active tab."); + yield client.attachTab(form.actor); + + let front = StyleSheetsFront(client, form); + ok(front, "The StyleSheetsFront was created."); + + let sheets = yield front.getStyleSheets(); + ok(sheets, "getStyleSheets() succeeded even with documentless iframes."); + + // Bug 285395 limits the number of nested iframes to 10. There's one sheet per + // frame so we should get 10 sheets. However, the limit might change in the + // future so it's better not to rely on the limit. Asserting > 2 ensures that + // the test page is actually loading nested iframes and this test is doing + // something sensible (if we got this far, the test has served its purpose). + ok(sheets.length > 2, sheets.length + " sheets found (expected 3 or more)."); + + yield client.close(); +}); diff --git a/devtools/server/tests/browser/browser_timeline.js b/devtools/server/tests/browser/browser_timeline.js new file mode 100644 index 000000000..1e5793447 --- /dev/null +++ b/devtools/server/tests/browser/browser_timeline.js @@ -0,0 +1,63 @@ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Test that the timeline front's start/stop/isRecording methods work in a +// simple use case, and that markers events are sent when operations occur. +// Note that this test isn't concerned with which markers are actually recorded, +// just that markers are recorded at all. +// Trying to check marker types here may lead to intermittents, see bug 1066474. + +const {TimelineFront} = require("devtools/shared/fronts/timeline"); + +add_task(function* () { + let browser = yield addTab("data:text/html;charset=utf-8,mop"); + let doc = browser.contentDocument; + + initDebuggerServer(); + let client = new DebuggerClient(DebuggerServer.connectPipe()); + let form = yield connectDebuggerClient(client); + let front = TimelineFront(client, form); + + ok(front, "The TimelineFront was created"); + + let isActive = yield front.isRecording(); + ok(!isActive, "The TimelineFront is not initially recording"); + + info("Flush any pending reflows"); + let forceSyncReflow = doc.body.innerHeight; + + info("Start recording"); + yield front.start({ withMarkers: true }); + + isActive = yield front.isRecording(); + ok(isActive, "The TimelineFront is now recording"); + + info("Change some style on the page to cause style/reflow/paint"); + let onMarkers = once(front, "markers"); + doc.body.style.padding = "10px"; + let markers = yield onMarkers; + + ok(true, "The markers event was fired"); + ok(markers.length > 0, "Markers were returned"); + + info("Flush pending reflows again"); + forceSyncReflow = doc.body.innerHeight; + + info("Change some style on the page to cause style/paint"); + onMarkers = once(front, "markers"); + doc.body.style.backgroundColor = "red"; + markers = yield onMarkers; + + ok(markers.length > 0, "markers were returned"); + + yield front.stop(); + + isActive = yield front.isRecording(); + ok(!isActive, "Not recording after stop()"); + + yield client.close(); + gBrowser.removeCurrentTab(); +}); diff --git a/devtools/server/tests/browser/browser_timeline_actors.js b/devtools/server/tests/browser/browser_timeline_actors.js new file mode 100644 index 000000000..a902775fa --- /dev/null +++ b/devtools/server/tests/browser/browser_timeline_actors.js @@ -0,0 +1,69 @@ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Test that the timeline can also record data from the memory and framerate +// actors, emitted as events in tadem with the markers. + +const {TimelineFront} = require("devtools/shared/fronts/timeline"); + +add_task(function* () { + let browser = yield addTab("data:text/html;charset=utf-8,mop"); + let doc = browser.contentDocument; + + initDebuggerServer(); + let client = new DebuggerClient(DebuggerServer.connectPipe()); + let form = yield connectDebuggerClient(client); + let front = TimelineFront(client, form); + + info("Start timeline marker recording"); + yield front.start({ withMemory: true, withTicks: true }); + + let updatedMemory = 0; + let updatedTicks = 0; + + front.on("memory", (delta, measurement) => { + ok(delta > 0, "The delta should be a timestamp."); + ok(measurement, "The measurement should not be null."); + ok(measurement.total > 0, "There should be a 'total' value in the measurement."); + info("Received 'memory' event at " + delta + " with " + measurement.toSource()); + updatedMemory++; + }); + + front.on("ticks", (delta, ticks) => { + ok(delta > 0, "The delta should be a timestamp."); + ok(ticks, "The ticks should not be null."); + info("Received 'ticks' event with " + ticks.toSource()); + updatedTicks++; + }); + + ok((yield waitUntil(() => updatedMemory > 1)), + "Some memory measurements were emitted."); + ok((yield waitUntil(() => updatedTicks > 1)), + "Some refresh driver ticks were emitted."); + + info("Stop timeline marker recording"); + yield front.stop(); + yield client.close(); + gBrowser.removeCurrentTab(); +}); + +/** + * Waits until a predicate returns true. + * + * @param function predicate + * Invoked once in a while until it returns true. + * @param number interval [optional] + * How often the predicate is invoked, in milliseconds. + */ +function waitUntil(predicate, interval = 10) { + if (predicate()) { + return Promise.resolve(true); + } + return new Promise(resolve => + setTimeout(function () { + waitUntil(predicate).then(() => resolve(true)); + }, interval)); +} diff --git a/devtools/server/tests/browser/browser_timeline_iframes.js b/devtools/server/tests/browser/browser_timeline_iframes.js new file mode 100644 index 000000000..60728873f --- /dev/null +++ b/devtools/server/tests/browser/browser_timeline_iframes.js @@ -0,0 +1,41 @@ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Test the timeline front receives markers events for operations that occur in +// iframes. + +const {TimelineFront} = require("devtools/shared/fronts/timeline"); + +add_task(function* () { + let browser = yield addTab(MAIN_DOMAIN + "timeline-iframe-parent.html"); + let doc = browser.contentDocument; + + initDebuggerServer(); + let client = new DebuggerClient(DebuggerServer.connectPipe()); + let form = yield connectDebuggerClient(client); + let front = TimelineFront(client, form); + + info("Start timeline marker recording"); + yield front.start({ withMarkers: true }); + + // Check that we get markers for a few iterations of the timer that runs in + // the child frame. + for (let i = 0; i < 3; i++) { + yield wait(300); // That's the time the child frame waits before changing styles. + let markers = yield once(front, "markers"); + ok(markers.length, "Markers were received for operations in the child frame"); + } + + info("Stop timeline marker recording"); + yield front.stop(); + yield client.close(); + gBrowser.removeCurrentTab(); +}); + +function wait(ms) { + return new Promise(resolve => + setTimeout(resolve, ms)); +} diff --git a/devtools/server/tests/browser/director-script-target.html b/devtools/server/tests/browser/director-script-target.html new file mode 100644 index 000000000..0b0b56d64 --- /dev/null +++ b/devtools/server/tests/browser/director-script-target.html @@ -0,0 +1,15 @@ +<html> + <head> + <script> + // change the eval function to ensure the window object in the debug-script is correctly wrapped + window.eval = function () { + return "unsecure-eval-called"; + }; + + var globalAccessibleVar = "global-value"; + </script> + </head> + <body> + <h1>debug script target</h1> + </body> +</html> diff --git a/devtools/server/tests/browser/doc_allocations.html b/devtools/server/tests/browser/doc_allocations.html new file mode 100644 index 000000000..314c40cac --- /dev/null +++ b/devtools/server/tests/browser/doc_allocations.html @@ -0,0 +1,21 @@ +<!DOCTYPE HTML> +<html> +<head> + <meta charset="utf-8"> +</head> +<body> +<script> +window.allocs = []; +window.onload = function() { + function allocator() { + for (var i = 0; i < 1000; i++) { + window.allocs.push(new Object); + } + } + + window.setInterval(allocator, 1); +}; +</script> +</pre> +</body> +</html> diff --git a/devtools/server/tests/browser/doc_force_cc.html b/devtools/server/tests/browser/doc_force_cc.html new file mode 100644 index 000000000..d5868bd6b --- /dev/null +++ b/devtools/server/tests/browser/doc_force_cc.html @@ -0,0 +1,29 @@ +<!-- Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ --> +<!doctype html> + +<html> + <head> + <meta charset="utf-8"/> + <title>Performance tool + cycle collection test page</title> + </head> + + <body> + <script type="text/javascript"> + window.test = function () { + document.body.expando1 = { cycle: document.body }; + SpecialPowers.Cu.forceCC(); + + document.body.expando2 = { cycle: document.body }; + SpecialPowers.Cu.forceCC(); + + document.body.expando3 = { cycle: document.body }; + SpecialPowers.Cu.forceCC(); + + setTimeout(window.test, 100); + }; + test(); + </script> + </body> + +</html> diff --git a/devtools/server/tests/browser/doc_force_gc.html b/devtools/server/tests/browser/doc_force_gc.html new file mode 100644 index 000000000..f8b617533 --- /dev/null +++ b/devtools/server/tests/browser/doc_force_gc.html @@ -0,0 +1,27 @@ +<!-- Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ --> +<!doctype html> + +<html> + <head> + <meta charset="utf-8"/> + <title>Performance tool + garbage collection test page</title> + </head> + + <body> + <script type="text/javascript"> + var x = 1; + window.test = function () { + SpecialPowers.Cu.forceGC(); + document.body.style.borderTop = x + "px solid red"; + x = 1^x; + document.body.innerHeight; // flush pending reflows + + // Prevent this script from being garbage collected. + setTimeout(window.test, 100); + }; + test(); + </script> + </body> + +</html> diff --git a/devtools/server/tests/browser/doc_innerHTML.html b/devtools/server/tests/browser/doc_innerHTML.html new file mode 100644 index 000000000..f5ce72de2 --- /dev/null +++ b/devtools/server/tests/browser/doc_innerHTML.html @@ -0,0 +1,21 @@ +<!-- Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ --> +<!doctype html> + +<html> + <head> + <meta charset="utf-8"/> + <title>Performance tool + innerHTML test page</title> + </head> + + <body> + <script type="text/javascript"> + "use strict"; + window.test = function () { + document.body.innerHTML = "<h1>LOL</h1>"; + }; + setInterval(window.test, 100); + </script> + </body> + +</html> diff --git a/devtools/server/tests/browser/doc_perf.html b/devtools/server/tests/browser/doc_perf.html new file mode 100644 index 000000000..1da36328b --- /dev/null +++ b/devtools/server/tests/browser/doc_perf.html @@ -0,0 +1,25 @@ +<!-- Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ --> +<!doctype html> + +<html> + <head> + <meta charset="utf-8"/> + <title>Performance test page</title> + </head> + + <body> + <script type="text/javascript"> + var x = 1; + function test() { + document.body.style.borderTop = x + "px solid red"; + x = 1^x; + document.body.innerHeight; // flush pending reflows + } + + // Prevent this script from being garbage collected. + window.setInterval(test, 1); + </script> + </body> + +</html> diff --git a/devtools/server/tests/browser/head.js b/devtools/server/tests/browser/head.js new file mode 100644 index 000000000..1e7f09d95 --- /dev/null +++ b/devtools/server/tests/browser/head.js @@ -0,0 +1,203 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +var Cc = Components.classes; +var Ci = Components.interfaces; +var Cu = Components.utils; + +const {console} = Cu.import("resource://gre/modules/Console.jsm", {}); +const {require} = Cu.import("resource://devtools/shared/Loader.jsm", {}); +const {DebuggerClient} = require("devtools/shared/client/main"); +const {DebuggerServer} = require("devtools/server/main"); +const {defer} = require("promise"); +const DevToolsUtils = require("devtools/shared/DevToolsUtils"); +const Services = require("Services"); + +const PATH = "browser/devtools/server/tests/browser/"; +const MAIN_DOMAIN = "http://test1.example.org/" + PATH; +const ALT_DOMAIN = "http://sectest1.example.org/" + PATH; +const ALT_DOMAIN_SECURED = "https://sectest1.example.org:443/" + PATH; + +// All tests are asynchronous. +waitForExplicitFinish(); + +/** + * Add a new test tab in the browser and load the given url. + * @param {String} url The url to be loaded in the new tab + * @return a promise that resolves to the new browser that the document + * is loaded in. Note that we cannot return the document + * directly, since this would be a CPOW in the e10s case, + * and Promises cannot be resolved with CPOWs (see bug 1233497). + */ +var addTab = Task.async(function* (url) { + info(`Adding a new tab with URL: ${url}`); + let tab = gBrowser.selectedTab = gBrowser.addTab(url); + yield BrowserTestUtils.browserLoaded(tab.linkedBrowser); + + info(`Tab added and URL ${url} loaded`); + + return tab.linkedBrowser; +}); + +function* initAnimationsFrontForUrl(url) { + const {AnimationsFront} = require("devtools/shared/fronts/animation"); + const {InspectorFront} = require("devtools/shared/fronts/inspector"); + + yield addTab(url); + + initDebuggerServer(); + let client = new DebuggerClient(DebuggerServer.connectPipe()); + let form = yield connectDebuggerClient(client); + let inspector = InspectorFront(client, form); + let walker = yield inspector.getWalker(); + let animations = AnimationsFront(client, form); + + return {inspector, walker, animations, client}; +} + +function initDebuggerServer() { + try { + // Sometimes debugger server does not get destroyed correctly by previous + // tests. + DebuggerServer.destroy(); + } catch (e) { + info(`DebuggerServer destroy error: ${e}\n${e.stack}`); + } + DebuggerServer.init(); + DebuggerServer.addBrowserActors(); +} + +/** + * Connect a debugger client. + * @param {DebuggerClient} + * @return {Promise} Resolves to the selected tabActor form when the client is + * connected. + */ +function connectDebuggerClient(client) { + return client.connect() + .then(() => client.listTabs()) + .then(tabs => { + return tabs.tabs[tabs.selected]; + }); +} + +/** + * Wait for eventName on target. + * @param {Object} target An observable object that either supports on/off or + * addEventListener/removeEventListener + * @param {String} eventName + * @param {Boolean} useCapture Optional, for addEventListener/removeEventListener + * @return A promise that resolves when the event has been handled + */ +function once(target, eventName, useCapture = false) { + info("Waiting for event: '" + eventName + "' on " + target + "."); + + return new Promise(resolve => { + + for (let [add, remove] of [ + ["addEventListener", "removeEventListener"], + ["addListener", "removeListener"], + ["on", "off"] + ]) { + if ((add in target) && (remove in target)) { + target[add](eventName, function onEvent(...aArgs) { + info("Got event: '" + eventName + "' on " + target + "."); + target[remove](eventName, onEvent, useCapture); + resolve(...aArgs); + }, useCapture); + break; + } + } + }); +} + +/** + * Forces GC, CC and Shrinking GC to get rid of disconnected docshells and + * windows. + */ +function forceCollections() { + Cu.forceGC(); + Cu.forceCC(); + Cu.forceShrinkingGC(); +} + +/** + * Get a mock tabActor from a given window. + * This is sometimes useful to test actors or classes that use the tabActor in + * isolation. + * @param {DOMWindow} win + * @return {Object} + */ +function getMockTabActor(win) { + return { + window: win, + isRootActor: true + }; +} + +registerCleanupFunction(function tearDown() { + while (gBrowser.tabs.length > 1) { + gBrowser.removeCurrentTab(); + } +}); + +function idleWait(time) { + return DevToolsUtils.waitForTime(time); +} + +function busyWait(time) { + let start = Date.now(); + let stack; + while (Date.now() - start < time) { stack = Components.stack; } +} + +/** + * Waits until a predicate returns true. + * + * @param function predicate + * Invoked once in a while until it returns true. + * @param number interval [optional] + * How often the predicate is invoked, in milliseconds. + */ +function waitUntil(predicate, interval = 10) { + if (predicate()) { + return Promise.resolve(true); + } + return new Promise(resolve => { + setTimeout(function () { + waitUntil(predicate).then(() => resolve(true)); + }, interval); + }); +} + +function waitForMarkerType(front, types, predicate, + unpackFun = (name, data) => data.markers, + eventName = "timeline-data") +{ + types = [].concat(types); + predicate = predicate || function () { return true; }; + let filteredMarkers = []; + let { promise, resolve } = defer(); + + info("Waiting for markers of type: " + types); + + function handler(name, data) { + if (typeof name === "string" && name !== "markers") { + return; + } + + let markers = unpackFun(name, data); + info("Got markers: " + JSON.stringify(markers, null, 2)); + + filteredMarkers = filteredMarkers.concat(markers.filter(m => types.indexOf(m.name) !== -1)); + + if (types.every(t => filteredMarkers.some(m => m.name === t)) && predicate(filteredMarkers)) { + front.off(eventName, handler); + resolve(filteredMarkers); + } + } + front.on(eventName, handler); + + return promise; +} diff --git a/devtools/server/tests/browser/navigate-first.html b/devtools/server/tests/browser/navigate-first.html new file mode 100644 index 000000000..829372427 --- /dev/null +++ b/devtools/server/tests/browser/navigate-first.html @@ -0,0 +1,15 @@ +<!DOCTYPE HTML> +<html> +<head> + <meta charset="utf-8"> +</head> +<body> +First +<script> + +window.onbeforeunload=function(e){ + e.returnValue="?"; +}; +</script> +</body> +</html> diff --git a/devtools/server/tests/browser/navigate-second.html b/devtools/server/tests/browser/navigate-second.html new file mode 100644 index 000000000..4b30fe465 --- /dev/null +++ b/devtools/server/tests/browser/navigate-second.html @@ -0,0 +1,9 @@ +<!DOCTYPE HTML> +<html> +<head> + <meta charset="utf-8"> +</head> +<body> +Second +</body> +</html> diff --git a/devtools/server/tests/browser/storage-dynamic-windows.html b/devtools/server/tests/browser/storage-dynamic-windows.html new file mode 100644 index 000000000..67aa35d67 --- /dev/null +++ b/devtools/server/tests/browser/storage-dynamic-windows.html @@ -0,0 +1,117 @@ +<!DOCTYPE HTML> +<html> +<!-- +Bug 965872 - Storage inspector actor with cookies, local storage and session storage. +--> +<head> + <meta charset="utf-8"> + <title>Storage inspector test for listing hosts and storages</title> +</head> +<body> +<iframe src="http://sectest1.example.org/browser/devtools/server/tests/browser/storage-unsecured-iframe.html"></iframe> +<script type="application/javascript;version=1.7"> +"use strict"; +const partialHostname = location.hostname.match(/^[^.]+(\..*)$/)[1]; +const cookieExpiresTime1 = 2000000000000; +const cookieExpiresTime2 = 2000000001000; +// Setting up some cookies to eat. +document.cookie = "c1=foobar; expires=" + + new Date(cookieExpiresTime1).toGMTString() + "; path=/browser"; +document.cookie = "cs2=sessionCookie; path=/; domain=" + partialHostname; +document.cookie = "c3=foobar-2; expires=" + + new Date(cookieExpiresTime2).toGMTString() + "; path=/"; +// ... and some local storage items .. +localStorage.setItem("ls1", "foobar"); +localStorage.setItem("ls2", "foobar-2"); +// ... and finally some session storage items too +sessionStorage.setItem("ss1", "foobar-3"); + +let idbGenerator = function*() { + let request = indexedDB.open("idb1", 1); + request.onerror = function() { + throw new Error("error opening db connection"); + }; + let db = yield new Promise(done => { + request.onupgradeneeded = event => { + let db = event.target.result; + let store1 = db.createObjectStore("obj1", { keyPath: "id" }); + store1.createIndex("name", "name", { unique: false }); + store1.createIndex("email", "email", { unique: true }); + let store2 = db.createObjectStore("obj2", { keyPath: "id2" }); + store1.transaction.oncomplete = () => { + done(db); + }; + }; + }); + + // Prevents AbortError + yield new Promise(done => { + request.onsuccess = done; + }); + + let transaction = db.transaction(["obj1", "obj2"], "readwrite"); + let store1 = transaction.objectStore("obj1"); + let store2 = transaction.objectStore("obj2"); + store1.add({id: 1, name: "foo", email: "foo@bar.com"}); + store1.add({id: 2, name: "foo2", email: "foo2@bar.com"}); + store1.add({id: 3, name: "foo2", email: "foo3@bar.com"}); + store2.add({ + id2: 1, + name: "foo", + email: "foo@bar.com", + extra: "baz" + }); + // Prevents AbortError during close() + yield new Promise(success => { + transaction.oncomplete = success; + }); + + db.close(); + + request = indexedDB.open("idb2", 1); + let db2 = yield new Promise(done => { + request.onupgradeneeded = event => { + let db2 = event.target.result; + let store3 = db2.createObjectStore("obj3", { keyPath: "id3" }); + store3.createIndex("name2", "name2", { unique: true }); + store3.transaction.oncomplete = () => { + done(db2); + } + }; + }); + // Prevents AbortError during close() + yield new Promise(done => { + request.onsuccess = done; + }); + db2.close(); + + console.log("added cookies and stuff from main page"); +}; + +function deleteDB(dbName) { + return new Promise(resolve => { + dump("removing database " + dbName + " from " + document.location + "\n"); + indexedDB.deleteDatabase(dbName).onsuccess = resolve; + }); +} + +window.setup = function*() { + yield idbGenerator(); +}; + +window.clear = function*() { + document.cookie = "c1=; expires=Thu, 01 Jan 1970 00:00:00 GMT"; + document.cookie = "c3=; expires=Thu, 01 Jan 1970 00:00:00 GMT"; + document.cookie = "cs2=; expires=Thu, 01 Jan 1970 00:00:00 GMT"; + + localStorage.clear(); + + yield deleteDB("idb1"); + yield deleteDB("idb2"); + + dump("removed cookies, localStorage and indexedDB data from " + + document.location + "\n"); +}; +</script> +</body> +</html> diff --git a/devtools/server/tests/browser/storage-helpers.js b/devtools/server/tests/browser/storage-helpers.js new file mode 100644 index 000000000..1c4f37705 --- /dev/null +++ b/devtools/server/tests/browser/storage-helpers.js @@ -0,0 +1,85 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +/** + * This generator function opens the given url in a new tab, then sets up the + * page by waiting for all cookies, indexedDB items etc. to be created. + * + * @param url {String} The url to be opened in the new tab + * + * @return {Promise} A promise that resolves after storage inspector is ready + */ +function* openTabAndSetupStorage(url) { + let content = yield addTab(url); + + // Setup the async storages in main window and for all its iframes + yield ContentTask.spawn(gBrowser.selectedBrowser, null, function* () { + /** + * Get all windows including frames recursively. + * + * @param {Window} [baseWindow] + * The base window at which to start looking for child windows + * (optional). + * @return {Set} + * A set of windows. + */ + function getAllWindows(baseWindow) { + let windows = new Set(); + + let _getAllWindows = function (win) { + windows.add(win.wrappedJSObject); + + for (let i = 0; i < win.length; i++) { + _getAllWindows(win[i]); + } + }; + _getAllWindows(baseWindow); + + return windows; + } + + let windows = getAllWindows(content); + for (let win of windows) { + if (win.setup) { + yield win.setup(); + } + } + }); +} + +function* clearStorage() { + yield ContentTask.spawn(gBrowser.selectedBrowser, null, function* () { + /** + * Get all windows including frames recursively. + * + * @param {Window} [baseWindow] + * The base window at which to start looking for child windows + * (optional). + * @return {Set} + * A set of windows. + */ + function getAllWindows(baseWindow) { + let windows = new Set(); + + let _getAllWindows = function (win) { + windows.add(win.wrappedJSObject); + + for (let i = 0; i < win.length; i++) { + _getAllWindows(win[i]); + } + }; + _getAllWindows(baseWindow); + + return windows; + } + + let windows = getAllWindows(content); + for (let win of windows) { + if (win.clear) { + yield win.clear(); + } + } + }); +} diff --git a/devtools/server/tests/browser/storage-listings.html b/devtools/server/tests/browser/storage-listings.html new file mode 100644 index 000000000..c3b7ef3c8 --- /dev/null +++ b/devtools/server/tests/browser/storage-listings.html @@ -0,0 +1,123 @@ +<!DOCTYPE HTML> +<html> +<!-- +Bug 965872 - Storage inspector actor with cookies, local storage and session storage. +--> +<head> + <meta charset="utf-8"> + <title>Storage inspector test for listing hosts and storages</title> +</head> +<body> +<iframe src="http://sectest1.example.org/browser/devtools/server/tests/browser/storage-unsecured-iframe.html"></iframe> +<iframe src="https://sectest1.example.org:443/browser/devtools/server/tests/browser/storage-secured-iframe.html"></iframe> +<script type="application/javascript;version=1.7"> +"use strict"; +const partialHostname = location.hostname.match(/^[^.]+(\..*)$/)[1]; +const cookieExpiresTime1 = 2000000000000; +const cookieExpiresTime2 = 2000000001000; +// Setting up some cookies to eat. +document.cookie = "c1=foobar; expires=" + + new Date(cookieExpiresTime1).toGMTString() + "; path=/browser"; +document.cookie = "cs2=sessionCookie; path=/; domain=" + partialHostname; +document.cookie = "c3=foobar-2; secure=true; expires=" + + new Date(cookieExpiresTime2).toGMTString() + "; path=/"; +// ... and some local storage items .. +localStorage.setItem("ls1", "foobar"); +localStorage.setItem("ls2", "foobar-2"); +// ... and finally some session storage items too +sessionStorage.setItem("ss1", "foobar-3"); +console.log("added cookies and stuff from main page"); + +let idbGenerator = function*() { + let request = indexedDB.open("idb1", 1); + request.onerror = function() { + throw new Error("error opening db connection"); + }; + let db = yield new Promise(done => { + request.onupgradeneeded = event => { + let db = event.target.result; + let store1 = db.createObjectStore("obj1", { keyPath: "id" }); + store1.createIndex("name", "name", { unique: false }); + store1.createIndex("email", "email", { unique: true }); + let store2 = db.createObjectStore("obj2", { keyPath: "id2" }); + store1.transaction.oncomplete = () => { + done(db); + }; + }; + }); + + // Prevents AbortError + yield new Promise(done => { + request.onsuccess = done; + }); + + let transaction = db.transaction(["obj1", "obj2"], "readwrite"); + let store1 = transaction.objectStore("obj1"); + let store2 = transaction.objectStore("obj2"); + store1.add({id: 1, name: "foo", email: "foo@bar.com"}); + store1.add({id: 2, name: "foo2", email: "foo2@bar.com"}); + store1.add({id: 3, name: "foo2", email: "foo3@bar.com"}); + store2.add({ + id2: 1, + name: "foo", + email: "foo@bar.com", + extra: "baz" + }); + // Prevents AbortError during close() + yield new Promise(success => { + transaction.oncomplete = success; + }); + + db.close(); + + request = indexedDB.open("idb2", 1); + let db2 = yield new Promise(done => { + request.onupgradeneeded = event => { + let db2 = event.target.result; + let store3 = db2.createObjectStore("obj3", { keyPath: "id3" }); + store3.createIndex("name2", "name2", { unique: true }); + store3.transaction.oncomplete = () => { + done(db2); + } + }; + }); + // Prevents AbortError during close() + yield new Promise(done => { + request.onsuccess = done; + }); + db2.close(); + + dump("added cookies and stuff from main page\n"); +}; + +function deleteDB(dbName) { + return new Promise(resolve => { + dump("removing database " + dbName + " from " + document.location + "\n"); + indexedDB.deleteDatabase(dbName).onsuccess = resolve; + }); +} + +window.setup = function*() { + yield idbGenerator(); +}; + +window.clear = function*() { + document.cookie = "c1=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/browser"; + document.cookie = + "c3=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/; secure=true"; + document.cookie = + "cs2=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/; domain=" + + partialHostname; + + localStorage.clear(); + sessionStorage.clear(); + + yield deleteDB("idb1"); + yield deleteDB("idb2"); + + dump("removed cookies, localStorage, sessionStorage and indexedDB data " + + "from " + document.location + "\n"); +}; +</script> +</body> +</html> diff --git a/devtools/server/tests/browser/storage-secured-iframe.html b/devtools/server/tests/browser/storage-secured-iframe.html new file mode 100644 index 000000000..860b20aab --- /dev/null +++ b/devtools/server/tests/browser/storage-secured-iframe.html @@ -0,0 +1,94 @@ +<!DOCTYPE HTML> +<html> +<!-- +Iframe for testing multiple host detetion in storage actor +--> +<head> + <meta charset="utf-8"> +</head> +<body> +<script type="application/javascript;version=1.7"> + +document.cookie = "sc1=foobar;"; +localStorage.setItem("iframe-s-ls1", "foobar"); +sessionStorage.setItem("iframe-s-ss1", "foobar-2"); + +let idbGenerator = function*() { + let request = indexedDB.open("idb-s1", 1); + request.onerror = function() { + throw new Error("error opening db connection"); + }; + let db = yield new Promise(done => { + request.onupgradeneeded = event => { + let db = event.target.result; + let store1 = db.createObjectStore("obj-s1", { keyPath: "id" }); + store1.transaction.oncomplete = () => { + done(db); + }; + }; + }); + yield new Promise(done => { + request.onsuccess = done; + }); + + let transaction = db.transaction(["obj-s1"], "readwrite"); + let store1 = transaction.objectStore("obj-s1"); + store1.add({id: 6, name: "foo", email: "foo@bar.com"}); + store1.add({id: 7, name: "foo2", email: "foo2@bar.com"}); + yield new Promise(success => { + transaction.oncomplete = success; + }); + + db.close(); + + request = indexedDB.open("idb-s2", 1); + let db2 = yield new Promise(done => { + request.onupgradeneeded = event => { + let db2 = event.target.result; + let store3 = + db2.createObjectStore("obj-s2", { keyPath: "id3", autoIncrement: true }); + store3.createIndex("name2", "name2", { unique: true }); + store3.transaction.oncomplete = () => { + done(db2); + }; + }; + }); + yield new Promise(done => { + request.onsuccess = done; + }); + + transaction = db2.transaction(["obj-s2"], "readwrite"); + let store3 = transaction.objectStore("obj-s2"); + store3.add({id3: 16, name2: "foo", email: "foo@bar.com"}); + yield new Promise(success => { + transaction.oncomplete = success; + }); + + db2.close(); + dump("added cookies and stuff from secured iframe\n"); +} + +function deleteDB(dbName) { + return new Promise(resolve => { + dump("removing database " + dbName + " from " + document.location + "\n"); + indexedDB.deleteDatabase(dbName).onsuccess = resolve; + }); +} + +window.setup = function*() { + yield idbGenerator(); +}; + +window.clear = function*() { + document.cookie = "sc1=; expires=Thu, 01 Jan 1970 00:00:00 GMT"; + + localStorage.clear(); + + yield deleteDB("idb-s1"); + yield deleteDB("idb-s2"); + + console.log("removed cookies and stuff from secured iframe"); +} +</script> +</body> +</html> diff --git a/devtools/server/tests/browser/storage-unsecured-iframe.html b/devtools/server/tests/browser/storage-unsecured-iframe.html new file mode 100644 index 000000000..d339fb7f2 --- /dev/null +++ b/devtools/server/tests/browser/storage-unsecured-iframe.html @@ -0,0 +1,26 @@ +<!DOCTYPE HTML> +<html> +<!-- +Iframe for testing multiple host detetion in storage actor +--> +<head> + <meta charset="utf-8"> +</head> +<body> +<script> + +document.cookie = "uc1=foobar; domain=.example.org; path=/; secure=true"; +localStorage.setItem("iframe-u-ls1", "foobar"); +sessionStorage.setItem("iframe-u-ss1", "foobar1"); +sessionStorage.setItem("iframe-u-ss2", "foobar2"); +console.log("added cookies and stuff from unsecured iframe"); + +window.clear = function*() { + document.cookie = "uc1=; expires=Thu, 01 Jan 1970 00:00:00 GMT"; + localStorage.clear(); + sessionStorage.clear(); + console.log("removed cookies and stuff from unsecured iframe"); +} +</script> +</body> +</html> diff --git a/devtools/server/tests/browser/storage-updates.html b/devtools/server/tests/browser/storage-updates.html new file mode 100644 index 000000000..6ffaf4316 --- /dev/null +++ b/devtools/server/tests/browser/storage-updates.html @@ -0,0 +1,47 @@ +<!DOCTYPE HTML> +<html> +<!-- +Bug 965872 - Storage inspector actor with cookies, local storage and session storage. +--> +<head> + <meta charset="utf-8"> + <title>Storage inspector blank html for tests</title> +</head> +<body> +<script type="application/javascript;version=1.7"> +"use strict"; +window.addCookie = function(name, value, path, domain, expires, secure) { + let cookieString = name + "=" + value + ";"; + if (path) { + cookieString += "path=" + path + ";"; + } + if (domain) { + cookieString += "domain=" + domain + ";"; + } + if (expires) { + cookieString += "expires=" + expires + ";"; + } + if (secure) { + cookieString += "secure=true;"; + } + document.cookie = cookieString; +}; + +window.removeCookie = function(name) { + document.cookie = name + "=;expires=Thu, 01 Jan 1970 00:00:00 GMT"; +}; + +window.clearLocalAndSessionStores = function() { + localStorage.clear(); + sessionStorage.clear(); +}; + +window.clearCookies = function() { + let cookies = document.cookie; + for (let cookie of cookies.split(";")) { + removeCookie(cookie.split("=")[0]); + } +}; +</script> +</body> +</html> diff --git a/devtools/server/tests/browser/stylesheets-nested-iframes.html b/devtools/server/tests/browser/stylesheets-nested-iframes.html new file mode 100644 index 000000000..7ee775323 --- /dev/null +++ b/devtools/server/tests/browser/stylesheets-nested-iframes.html @@ -0,0 +1,25 @@ +<!DOCTYPE HTML> +<html> +<head> + <meta charset="utf-8"> + <title>StyleSheetsActor iframe test</title> + <style> + p { + padding: 1em; + } + </style> +</head> +<body> + <p>A test page with nested iframes</p> + <iframe></iframe> + <script type="application/javascript;version=1.8"> + let iframe = document.querySelector("iframe"); + let i = parseInt(location.href.split("?")[1]) || 1; + + // The frame can't have the same src URL as any of its ancestors. + // This will not infinitely recurse because a frame won't get a content + // document once it's nested deeply enough. + iframe.src = location.href.split("?")[0] + "?" + (++i); + </script> +</body> +</html> diff --git a/devtools/server/tests/browser/timeline-iframe-child.html b/devtools/server/tests/browser/timeline-iframe-child.html new file mode 100644 index 000000000..5385c6485 --- /dev/null +++ b/devtools/server/tests/browser/timeline-iframe-child.html @@ -0,0 +1,19 @@ +<!DOCTYPE HTML> +<html> +<head> + <meta charset="utf-8"> + <title>Timeline iframe test - child frame</title> +</head> +<body> + <h1>Child frame</h1> + <script> + var h1 = document.querySelector("h1"); + setInterval(function() { + h1.style.backgroundColor = "rgb(" + ((Math.random()*255)|0) + "," + + ((Math.random()*255)|0) + "," + + ((Math.random()*255)|0) +")"; + h1.style.width = ((Math.random()*500)|0) + "px"; + }, 300); + </script> +</body> +</html> diff --git a/devtools/server/tests/browser/timeline-iframe-parent.html b/devtools/server/tests/browser/timeline-iframe-parent.html new file mode 100644 index 000000000..b94ba4259 --- /dev/null +++ b/devtools/server/tests/browser/timeline-iframe-parent.html @@ -0,0 +1,11 @@ +<!DOCTYPE HTML> +<html> +<head> + <meta charset="utf-8"> + <title>Timeline iframe test - parent frame</title> +</head> +<body> + <h1>Parent frame</h1> + <iframe src="timeline-iframe-child.html"></iframe> +</body> +</html> 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 Binary files differnew file mode 100644 index 000000000..bda383e59 --- /dev/null +++ b/devtools/server/tests/mochitest/large-image.jpg 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 Binary files differnew file mode 100644 index 000000000..e702427a5 --- /dev/null +++ b/devtools/server/tests/mochitest/small-image.gif 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="!, ", #, $, %, &, ', (, ), *, +, ,, -, ., /, :, ;, <, =, >, ?, @, [, \, ], ^, `, {, |, }, ~"></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> diff --git a/devtools/server/tests/unit/.eslintrc.js b/devtools/server/tests/unit/.eslintrc.js new file mode 100644 index 000000000..012428019 --- /dev/null +++ b/devtools/server/tests/unit/.eslintrc.js @@ -0,0 +1,6 @@ +"use strict"; + +module.exports = { + // Extend from the common devtools xpcshell eslintrc config. + "extends": "../../../.eslintrc.xpcshell.js" +}; diff --git a/devtools/server/tests/unit/addons/web-extension-upgrade/manifest.json b/devtools/server/tests/unit/addons/web-extension-upgrade/manifest.json new file mode 100644 index 000000000..f70b11efd --- /dev/null +++ b/devtools/server/tests/unit/addons/web-extension-upgrade/manifest.json @@ -0,0 +1,10 @@ +{ + "manifest_version": 2, + "name": "Test Addons Actor Upgrade", + "version": "1.0", + "applications": { + "gecko": { + "id": "test-addons-actor@mozilla.org" + } + } +} diff --git a/devtools/server/tests/unit/addons/web-extension/manifest.json b/devtools/server/tests/unit/addons/web-extension/manifest.json new file mode 100644 index 000000000..d120cf3da --- /dev/null +++ b/devtools/server/tests/unit/addons/web-extension/manifest.json @@ -0,0 +1,10 @@ +{ + "manifest_version": 2, + "name": "Test Addons Actor", + "version": "1.0", + "applications": { + "gecko": { + "id": "test-addons-actor@mozilla.org" + } + } +} diff --git a/devtools/server/tests/unit/addons/web-extension2/manifest.json b/devtools/server/tests/unit/addons/web-extension2/manifest.json new file mode 100644 index 000000000..57daae29d --- /dev/null +++ b/devtools/server/tests/unit/addons/web-extension2/manifest.json @@ -0,0 +1,10 @@ +{ + "manifest_version": 2, + "name": "Test Addons Actor 2", + "version": "1.0", + "applications": { + "gecko": { + "id": "test-addons-actor2@mozilla.org" + } + } +} diff --git a/devtools/server/tests/unit/babel_and_browserify_script_with_source_map.js b/devtools/server/tests/unit/babel_and_browserify_script_with_source_map.js new file mode 100644 index 000000000..317eb68ca --- /dev/null +++ b/devtools/server/tests/unit/babel_and_browserify_script_with_source_map.js @@ -0,0 +1,79 @@ +(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){ +"use strict"; + +var add = require("./lib/add"); +var subtract = require("./lib/subtract"); +var upperCase = require("upper-case"); + +(function () { + return add(1, 2); +}); + +},{"./lib/add":2,"./lib/subtract":3,"upper-case":4}],2:[function(require,module,exports){ +"use strict"; + +module.exports = function (a, b) { + return a + b; +}; + +},{}],3:[function(require,module,exports){ +"use strict"; + +module.exports = function (a, b) { + return a - b; +}; + +},{}],4:[function(require,module,exports){ +/** + * Special language-specific overrides. + * + * Source: ftp://ftp.unicode.org/Public/UCD/latest/ucd/SpecialCasing.txt + * + * @type {Object} + */ +var LANGUAGES = { + tr: { + regexp: /[\u0069]/g, + map: { + '\u0069': '\u0130' + } + }, + az: { + regexp: /[\u0069]/g, + map: { + '\u0069': '\u0130' + } + }, + lt: { + regexp: /[\u0069\u006A\u012F]\u0307|\u0069\u0307[\u0300\u0301\u0303]/g, + map: { + '\u0069\u0307': '\u0049', + '\u006A\u0307': '\u004A', + '\u012F\u0307': '\u012E', + '\u0069\u0307\u0300': '\u00CC', + '\u0069\u0307\u0301': '\u00CD', + '\u0069\u0307\u0303': '\u0128' + } + } +} + +/** + * Upper case a string. + * + * @param {String} str + * @return {String} + */ +module.exports = function (str, locale) { + var lang = LANGUAGES[locale] + + str = str == null ? '' : String(str) + + if (lang) { + str = str.replace(lang.regexp, function (m) { return lang.map[m] }) + } + + return str.toUpperCase() +} + +},{}]},{},[1]) +//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIi4uLy4uLy4uLy4uL3Vzci9sb2NhbC9saWIvbm9kZV9tb2R1bGVzL2Jyb3dzZXJpZnkvbm9kZV9tb2R1bGVzL2Jyb3dzZXItcGFjay9fcHJlbHVkZS5qcyIsIi9Vc2Vycy9qc2FudGVsbC9EZXYvc291cmNlLW1hcC10ZXN0L2luZGV4LmpzIiwiL1VzZXJzL2pzYW50ZWxsL0Rldi9zb3VyY2UtbWFwLXRlc3QvbGliL2FkZC5qcyIsIi9Vc2Vycy9qc2FudGVsbC9EZXYvc291cmNlLW1hcC10ZXN0L2xpYi9zdWJ0cmFjdC5qcyIsIm5vZGVfbW9kdWxlcy91cHBlci1jYXNlL3VwcGVyLWNhc2UuanMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUE7OztBQ0FBLElBQUksR0FBRyxHQUFHLE9BQU8sQ0FBQyxXQUFXLENBQUMsQ0FBQztBQUMvQixJQUFJLFFBQVEsR0FBRyxPQUFPLENBQUMsZ0JBQWdCLENBQUMsQ0FBQztBQUN6QyxJQUFJLFNBQVMsR0FBRyxPQUFPLENBQUMsWUFBWSxDQUFDLENBQUM7O0FBRXRDLENBQUM7U0FBTSxHQUFHLENBQUMsQ0FBQyxFQUFDLENBQUMsQ0FBQztFQUFBLENBQUU7Ozs7O0FDSmpCLE1BQU0sQ0FBQyxPQUFPLEdBQUcsVUFBVSxDQUFDLEVBQUUsQ0FBQyxFQUFFO0FBQy9CLFNBQU8sQ0FBQyxHQUFHLENBQUMsQ0FBQztDQUNkLENBQUM7Ozs7O0FDRkYsTUFBTSxDQUFDLE9BQU8sR0FBRyxVQUFVLENBQUMsRUFBRSxDQUFDLEVBQUU7QUFDL0IsU0FBTyxDQUFDLEdBQUcsQ0FBQyxDQUFDO0NBQ2QsQ0FBQzs7O0FDRkY7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBIiwiZmlsZSI6ImdlbmVyYXRlZC5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzQ29udGVudCI6WyIoZnVuY3Rpb24gZSh0LG4scil7ZnVuY3Rpb24gcyhvLHUpe2lmKCFuW29dKXtpZighdFtvXSl7dmFyIGE9dHlwZW9mIHJlcXVpcmU9PVwiZnVuY3Rpb25cIiYmcmVxdWlyZTtpZighdSYmYSlyZXR1cm4gYShvLCEwKTtpZihpKXJldHVybiBpKG8sITApO3ZhciBmPW5ldyBFcnJvcihcIkNhbm5vdCBmaW5kIG1vZHVsZSAnXCIrbytcIidcIik7dGhyb3cgZi5jb2RlPVwiTU9EVUxFX05PVF9GT1VORFwiLGZ9dmFyIGw9bltvXT17ZXhwb3J0czp7fX07dFtvXVswXS5jYWxsKGwuZXhwb3J0cyxmdW5jdGlvbihlKXt2YXIgbj10W29dWzFdW2VdO3JldHVybiBzKG4/bjplKX0sbCxsLmV4cG9ydHMsZSx0LG4scil9cmV0dXJuIG5bb10uZXhwb3J0c312YXIgaT10eXBlb2YgcmVxdWlyZT09XCJmdW5jdGlvblwiJiZyZXF1aXJlO2Zvcih2YXIgbz0wO288ci5sZW5ndGg7bysrKXMocltvXSk7cmV0dXJuIHN9KSIsInZhciBhZGQgPSByZXF1aXJlKFwiLi9saWIvYWRkXCIpO1xudmFyIHN1YnRyYWN0ID0gcmVxdWlyZShcIi4vbGliL3N1YnRyYWN0XCIpO1xudmFyIHVwcGVyQ2FzZSA9IHJlcXVpcmUoXCJ1cHBlci1jYXNlXCIpO1xuXG4oKCkgPT4gYWRkKDEsMikpO1xuIiwibW9kdWxlLmV4cG9ydHMgPSBmdW5jdGlvbiAoYSwgYikge1xuICByZXR1cm4gYSArIGI7XG59O1xuIiwibW9kdWxlLmV4cG9ydHMgPSBmdW5jdGlvbiAoYSwgYikge1xuICByZXR1cm4gYSAtIGI7XG59O1xuIiwiLyoqXG4gKiBTcGVjaWFsIGxhbmd1YWdlLXNwZWNpZmljIG92ZXJyaWRlcy5cbiAqXG4gKiBTb3VyY2U6IGZ0cDovL2Z0cC51bmljb2RlLm9yZy9QdWJsaWMvVUNEL2xhdGVzdC91Y2QvU3BlY2lhbENhc2luZy50eHRcbiAqXG4gKiBAdHlwZSB7T2JqZWN0fVxuICovXG52YXIgTEFOR1VBR0VTID0ge1xuICB0cjoge1xuICAgIHJlZ2V4cDogL1tcXHUwMDY5XS9nLFxuICAgIG1hcDoge1xuICAgICAgJ1xcdTAwNjknOiAnXFx1MDEzMCdcbiAgICB9XG4gIH0sXG4gIGF6OiB7XG4gICAgcmVnZXhwOiAvW1xcdTAwNjldL2csXG4gICAgbWFwOiB7XG4gICAgICAnXFx1MDA2OSc6ICdcXHUwMTMwJ1xuICAgIH1cbiAgfSxcbiAgbHQ6IHtcbiAgICByZWdleHA6IC9bXFx1MDA2OVxcdTAwNkFcXHUwMTJGXVxcdTAzMDd8XFx1MDA2OVxcdTAzMDdbXFx1MDMwMFxcdTAzMDFcXHUwMzAzXS9nLFxuICAgIG1hcDoge1xuICAgICAgJ1xcdTAwNjlcXHUwMzA3JzogJ1xcdTAwNDknLFxuICAgICAgJ1xcdTAwNkFcXHUwMzA3JzogJ1xcdTAwNEEnLFxuICAgICAgJ1xcdTAxMkZcXHUwMzA3JzogJ1xcdTAxMkUnLFxuICAgICAgJ1xcdTAwNjlcXHUwMzA3XFx1MDMwMCc6ICdcXHUwMENDJyxcbiAgICAgICdcXHUwMDY5XFx1MDMwN1xcdTAzMDEnOiAnXFx1MDBDRCcsXG4gICAgICAnXFx1MDA2OVxcdTAzMDdcXHUwMzAzJzogJ1xcdTAxMjgnXG4gICAgfVxuICB9XG59XG5cbi8qKlxuICogVXBwZXIgY2FzZSBhIHN0cmluZy5cbiAqXG4gKiBAcGFyYW0gIHtTdHJpbmd9IHN0clxuICogQHJldHVybiB7U3RyaW5nfVxuICovXG5tb2R1bGUuZXhwb3J0cyA9IGZ1bmN0aW9uIChzdHIsIGxvY2FsZSkge1xuICB2YXIgbGFuZyA9IExBTkdVQUdFU1tsb2NhbGVdXG5cbiAgc3RyID0gc3RyID09IG51bGwgPyAnJyA6IFN0cmluZyhzdHIpXG5cbiAgaWYgKGxhbmcpIHtcbiAgICBzdHIgPSBzdHIucmVwbGFjZShsYW5nLnJlZ2V4cCwgZnVuY3Rpb24gKG0pIHsgcmV0dXJuIGxhbmcubWFwW21dIH0pXG4gIH1cblxuICByZXR1cm4gc3RyLnRvVXBwZXJDYXNlKClcbn1cbiJdfQ== diff --git a/devtools/server/tests/unit/head_dbg.js b/devtools/server/tests/unit/head_dbg.js new file mode 100644 index 000000000..57d0eb8ff --- /dev/null +++ b/devtools/server/tests/unit/head_dbg.js @@ -0,0 +1,862 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; +var Cc = Components.classes; +var Ci = Components.interfaces; +var Cu = Components.utils; +var Cr = Components.results; +var CC = Components.Constructor; + +// Populate AppInfo before anything (like the shared loader) accesses +// System.appinfo, which is a lazy getter. +const _appInfo = {}; +Cu.import("resource://testing-common/AppInfo.jsm", _appInfo); +_appInfo.updateAppInfo({ + ID: "devtools@tests.mozilla.org", + name: "devtools-tests", + version: "1", + platformVersion: "42", + crashReporter: true, +}); + +const { require, loader } = Cu.import("resource://devtools/shared/Loader.jsm", {}); +const { worker } = Cu.import("resource://devtools/shared/worker/loader.js", {}); +const promise = require("promise"); +const { Task } = require("devtools/shared/task"); +const { console } = require("resource://gre/modules/Console.jsm"); +const { NetUtil } = require("resource://gre/modules/NetUtil.jsm"); + +const Services = require("Services"); +// Always log packets when running tests. runxpcshelltests.py will throw +// the output away anyway, unless you give it the --verbose flag. +Services.prefs.setBoolPref("devtools.debugger.log", true); +// Enable remote debugging for the relevant tests. +Services.prefs.setBoolPref("devtools.debugger.remote-enabled", true); + +const DevToolsUtils = require("devtools/shared/DevToolsUtils"); +const { DebuggerServer } = require("devtools/server/main"); +const { DebuggerServer: WorkerDebuggerServer } = worker.require("devtools/server/main"); +const { DebuggerClient, ObjectClient } = require("devtools/shared/client/main"); +const { MemoryFront } = require("devtools/shared/fronts/memory"); + +const { addDebuggerToGlobal } = Cu.import("resource://gre/modules/jsdebugger.jsm", {}); + +const systemPrincipal = Cc["@mozilla.org/systemprincipal;1"].createInstance(Ci.nsIPrincipal); + +var loadSubScript = Cc[ + "@mozilla.org/moz/jssubscript-loader;1" +].getService(Ci.mozIJSSubScriptLoader).loadSubScript; + +/** + * Initializes any test that needs to work with add-ons. + */ +function startupAddonsManager() { + // Create a directory for extensions. + const profileDir = do_get_profile().clone(); + profileDir.append("extensions"); + + const internalManager = Cc["@mozilla.org/addons/integration;1"] + .getService(Ci.nsIObserver) + .QueryInterface(Ci.nsITimerCallback); + + internalManager.observe(null, "addons-startup", null); +} + +/** + * Create a `run_test` function that runs the given generator in a task after + * having attached to a memory actor. When done, the memory actor is detached + * from, the client is finished, and the test is finished. + * + * @param {GeneratorFunction} testGeneratorFunction + * The generator function is passed (DebuggerClient, MemoryFront) + * arguments. + * + * @returns `run_test` function + */ +function makeMemoryActorTest(testGeneratorFunction) { + const TEST_GLOBAL_NAME = "test_MemoryActor"; + + return function run_test() { + do_test_pending(); + startTestDebuggerServer(TEST_GLOBAL_NAME).then(client => { + DebuggerServer.registerModule("devtools/server/actors/heap-snapshot-file", { + prefix: "heapSnapshotFile", + constructor: "HeapSnapshotFileActor", + type: { global: true } + }); + + getTestTab(client, TEST_GLOBAL_NAME, function (tabForm, rootForm) { + if (!tabForm || !rootForm) { + ok(false, "Could not attach to test tab: " + TEST_GLOBAL_NAME); + return; + } + + Task.spawn(function* () { + try { + const memoryFront = new MemoryFront(client, tabForm, rootForm); + yield memoryFront.attach(); + yield* testGeneratorFunction(client, memoryFront); + yield memoryFront.detach(); + } catch (err) { + DevToolsUtils.reportException("makeMemoryActorTest", err); + ok(false, "Got an error: " + err); + } + + finishClient(client); + }); + }); + }); + }; +} + +/** + * Save as makeMemoryActorTest but attaches the MemoryFront to the MemoryActor + * scoped to the full runtime rather than to a tab. + */ +function makeFullRuntimeMemoryActorTest(testGeneratorFunction) { + return function run_test() { + do_test_pending(); + startTestDebuggerServer("test_MemoryActor").then(client => { + DebuggerServer.registerModule("devtools/server/actors/heap-snapshot-file", { + prefix: "heapSnapshotFile", + constructor: "HeapSnapshotFileActor", + type: { global: true } + }); + + getChromeActors(client).then(function (form) { + if (!form) { + ok(false, "Could not attach to chrome actors"); + return; + } + + Task.spawn(function* () { + try { + const rootForm = yield listTabs(client); + const memoryFront = new MemoryFront(client, form, rootForm); + yield memoryFront.attach(); + yield* testGeneratorFunction(client, memoryFront); + yield memoryFront.detach(); + } catch (err) { + DevToolsUtils.reportException("makeMemoryActorTest", err); + ok(false, "Got an error: " + err); + } + + finishClient(client); + }); + }); + }); + }; +} + +function createTestGlobal(name) { + let sandbox = Cu.Sandbox( + Cc["@mozilla.org/systemprincipal;1"].createInstance(Ci.nsIPrincipal) + ); + sandbox.__name = name; + return sandbox; +} + +function connect(client) { + dump("Connecting client.\n"); + return client.connect(); +} + +function close(client) { + dump("Closing client.\n"); + return client.close(); +} + +function listTabs(client) { + dump("Listing tabs.\n"); + return client.listTabs(); +} + +function findTab(tabs, title) { + dump("Finding tab with title '" + title + "'.\n"); + for (let tab of tabs) { + if (tab.title === title) { + return tab; + } + } + return null; +} + +function attachTab(client, tab) { + dump("Attaching to tab with title '" + tab.title + "'.\n"); + return client.attachTab(tab.actor); +} + +function waitForNewSource(threadClient, url) { + dump("Waiting for new source with url '" + url + "'.\n"); + return waitForEvent(threadClient, "newSource", function (packet) { + return packet.source.url === url; + }); +} + +function attachThread(tabClient, options = {}) { + dump("Attaching to thread.\n"); + return tabClient.attachThread(options); +} + +function resume(threadClient) { + dump("Resuming thread.\n"); + return threadClient.resume(); +} + +function getSources(threadClient) { + dump("Getting sources.\n"); + return threadClient.getSources(); +} + +function findSource(sources, url) { + dump("Finding source with url '" + url + "'.\n"); + for (let source of sources) { + if (source.url === url) { + return source; + } + } + return null; +} + +function waitForPause(threadClient) { + dump("Waiting for pause.\n"); + return waitForEvent(threadClient, "paused"); +} + +function setBreakpoint(sourceClient, location) { + dump("Setting breakpoint.\n"); + return sourceClient.setBreakpoint(location); +} + +function dumpn(msg) { + dump("DBG-TEST: " + msg + "\n"); +} + +function testExceptionHook(ex) { + try { + do_report_unexpected_exception(ex); + } catch (ex) { + return {throw: ex}; + } + return undefined; +} + +// Convert an nsIScriptError 'aFlags' value into an appropriate string. +function scriptErrorFlagsToKind(aFlags) { + var kind; + if (aFlags & Ci.nsIScriptError.warningFlag) + kind = "warning"; + if (aFlags & Ci.nsIScriptError.exceptionFlag) + kind = "exception"; + else + kind = "error"; + + if (aFlags & Ci.nsIScriptError.strictFlag) + kind = "strict " + kind; + + return kind; +} + +// Register a console listener, so console messages don't just disappear +// into the ether. +var errorCount = 0; +var listener = { + observe: function (aMessage) { + try { + errorCount++; + try { + // If we've been given an nsIScriptError, then we can print out + // something nicely formatted, for tools like Emacs to pick up. + var scriptError = aMessage.QueryInterface(Ci.nsIScriptError); + dumpn(aMessage.sourceName + ":" + aMessage.lineNumber + ": " + + scriptErrorFlagsToKind(aMessage.flags) + ": " + + aMessage.errorMessage); + var string = aMessage.errorMessage; + } catch (x) { + // Be a little paranoid with message, as the whole goal here is to lose + // no information. + try { + var string = "" + aMessage.message; + } catch (x) { + var string = "<error converting error message to string>"; + } + } + + // Make sure we exit all nested event loops so that the test can finish. + while (DebuggerServer + && DebuggerServer.xpcInspector + && DebuggerServer.xpcInspector.eventLoopNestLevel > 0) { + DebuggerServer.xpcInspector.exitNestedEventLoop(); + } + + // In the world before bug 997440, exceptions were getting lost because of + // the arbitrary JSContext being used in nsXPCWrappedJSClass::CallMethod. + // In the new world, the wanderers have returned. However, because of the, + // currently very-broken, exception reporting machinery in + // XPCWrappedJSClass these get reported as errors to the console, even if + // there's actually JS on the stack above that will catch them. If we + // throw an error here because of them our tests start failing. So, we'll + // just dump the message to the logs instead, to make sure the information + // isn't lost. + dumpn("head_dbg.js observed a console message: " + string); + } catch (_) { + // Swallow everything to avoid console reentrancy errors. We did our best + // to log above, but apparently that didn't cut it. + } + } +}; + +var consoleService = Cc["@mozilla.org/consoleservice;1"].getService(Ci.nsIConsoleService); +consoleService.registerListener(listener); + +function check_except(func) +{ + try { + func(); + } catch (e) { + do_check_true(true); + return; + } + dumpn("Should have thrown an exception: " + func.toString()); + do_check_true(false); +} + +function testGlobal(aName) { + let systemPrincipal = Cc["@mozilla.org/systemprincipal;1"] + .createInstance(Ci.nsIPrincipal); + + let sandbox = Cu.Sandbox(systemPrincipal); + sandbox.__name = aName; + return sandbox; +} + +function addTestGlobal(aName, aServer = DebuggerServer) +{ + let global = testGlobal(aName); + aServer.addTestGlobal(global); + return global; +} + +// List the DebuggerClient |aClient|'s tabs, look for one whose title is +// |aTitle|, and apply |aCallback| to the packet's entry for that tab. +function getTestTab(aClient, aTitle, aCallback) { + aClient.listTabs(function (aResponse) { + for (let tab of aResponse.tabs) { + if (tab.title === aTitle) { + aCallback(tab, aResponse); + return; + } + } + aCallback(null); + }); +} + +// Attach to |aClient|'s tab whose title is |aTitle|; pass |aCallback| the +// response packet and a TabClient instance referring to that tab. +function attachTestTab(aClient, aTitle, aCallback) { + getTestTab(aClient, aTitle, function (aTab) { + aClient.attachTab(aTab.actor, aCallback); + }); +} + +// Attach to |aClient|'s tab whose title is |aTitle|, and then attach to +// that tab's thread. Pass |aCallback| the thread attach response packet, a +// TabClient referring to the tab, and a ThreadClient referring to the +// thread. +function attachTestThread(aClient, aTitle, aCallback) { + attachTestTab(aClient, aTitle, function (aTabResponse, aTabClient) { + function onAttach(aResponse, aThreadClient) { + aCallback(aResponse, aTabClient, aThreadClient, aTabResponse); + } + aTabClient.attachThread({ + useSourceMaps: true, + autoBlackBox: true + }, onAttach); + }); +} + +// Attach to |aClient|'s tab whose title is |aTitle|, attach to the tab's +// thread, and then resume it. Pass |aCallback| the thread's response to +// the 'resume' packet, a TabClient for the tab, and a ThreadClient for the +// thread. +function attachTestTabAndResume(aClient, aTitle, aCallback = () => {}) { + return new Promise((resolve, reject) => { + attachTestThread(aClient, aTitle, function (aResponse, aTabClient, aThreadClient) { + aThreadClient.resume(function (aResponse) { + aCallback(aResponse, aTabClient, aThreadClient); + resolve([aResponse, aTabClient, aThreadClient]); + }); + }); + }); +} + +/** + * Initialize the testing debugger server. + */ +function initTestDebuggerServer(aServer = DebuggerServer) +{ + aServer.registerModule("xpcshell-test/testactors"); + // Allow incoming connections. + aServer.init(function () { return true; }); +} + +/** + * Initialize the testing debugger server with a tab whose title is |title|. + */ +function startTestDebuggerServer(title, server = DebuggerServer) { + initTestDebuggerServer(server); + addTestGlobal(title); + DebuggerServer.addTabActors(); + + let transport = DebuggerServer.connectPipe(); + let client = new DebuggerClient(transport); + + return connect(client).then(() => client); +} + +function finishClient(aClient) +{ + aClient.close(function () { + DebuggerServer.destroy(); + do_test_finished(); + }); +} + +// Create a server, connect to it and fetch tab actors for the parent process; +// pass |aCallback| the debugger client and tab actor form with all actor IDs. +function get_chrome_actors(callback) +{ + if (!DebuggerServer.initialized) { + DebuggerServer.init(); + DebuggerServer.addBrowserActors(); + } + DebuggerServer.allowChromeProcess = true; + + let client = new DebuggerClient(DebuggerServer.connectPipe()); + client.connect() + .then(() => client.getProcess()) + .then(response => { + callback(client, response.form); + }); +} + +function getChromeActors(client, server = DebuggerServer) { + server.allowChromeProcess = true; + return client.getProcess().then(response => response.form); +} + +/** + * Takes a relative file path and returns the absolute file url for it. + */ +function getFileUrl(aName, aAllowMissing = false) { + let file = do_get_file(aName, aAllowMissing); + return Services.io.newFileURI(file).spec; +} + +/** + * Returns the full path of the file with the specified name in a + * platform-independent and URL-like form. + */ +function getFilePath(aName, aAllowMissing = false, aUsePlatformPathSeparator = false) +{ + let file = do_get_file(aName, aAllowMissing); + let path = Services.io.newFileURI(file).spec; + let filePrePath = "file://"; + if ("nsILocalFileWin" in Ci && + file instanceof Ci.nsILocalFileWin) { + filePrePath += "/"; + } + + path = path.slice(filePrePath.length); + + if (aUsePlatformPathSeparator && path.match(/^\w:/)) { + path = path.replace(/\//g, "\\"); + } + + return path; +} + +/** + * Returns the full text contents of the given file. + */ +function readFile(aFileName) { + let f = do_get_file(aFileName); + let s = Cc["@mozilla.org/network/file-input-stream;1"] + .createInstance(Ci.nsIFileInputStream); + s.init(f, -1, -1, false); + try { + return NetUtil.readInputStreamToString(s, s.available()); + } finally { + s.close(); + } +} + +function writeFile(aFileName, aContent) { + let file = do_get_file(aFileName, true); + let stream = Cc["@mozilla.org/network/file-output-stream;1"] + .createInstance(Ci.nsIFileOutputStream); + stream.init(file, -1, -1, 0); + try { + do { + let numWritten = stream.write(aContent, aContent.length); + aContent = aContent.slice(numWritten); + } while (aContent.length > 0); + } finally { + stream.close(); + } +} + +function connectPipeTracing() { + return new TracingTransport(DebuggerServer.connectPipe()); +} + +function TracingTransport(childTransport) { + this.hooks = null; + this.child = childTransport; + this.child.hooks = this; + + this.expectations = []; + this.packets = []; + this.checkIndex = 0; +} + +TracingTransport.prototype = { + // Remove actor names + normalize: function (packet) { + return JSON.parse(JSON.stringify(packet, (key, value) => { + if (key === "to" || key === "from" || key === "actor") { + return "<actorid>"; + } + return value; + })); + }, + send: function (packet) { + this.packets.push({ + type: "sent", + packet: this.normalize(packet) + }); + return this.child.send(packet); + }, + close: function () { + return this.child.close(); + }, + ready: function () { + return this.child.ready(); + }, + onPacket: function (packet) { + this.packets.push({ + type: "received", + packet: this.normalize(packet) + }); + this.hooks.onPacket(packet); + }, + onClosed: function () { + this.hooks.onClosed(); + }, + + expectSend: function (expected) { + let packet = this.packets[this.checkIndex++]; + do_check_eq(packet.type, "sent"); + deepEqual(packet.packet, this.normalize(expected)); + }, + + expectReceive: function (expected) { + let packet = this.packets[this.checkIndex++]; + do_check_eq(packet.type, "received"); + deepEqual(packet.packet, this.normalize(expected)); + }, + + // Write your tests, call dumpLog at the end, inspect the output, + // then sprinkle the calls through the right places in your test. + dumpLog: function () { + for (let entry of this.packets) { + if (entry.type === "sent") { + dumpn("trace.expectSend(" + entry.packet + ");"); + } else { + dumpn("trace.expectReceive(" + entry.packet + ");"); + } + } + } +}; + +function StubTransport() { } +StubTransport.prototype.ready = function () {}; +StubTransport.prototype.send = function () {}; +StubTransport.prototype.close = function () {}; + +function executeSoon(aFunc) { + Services.tm.mainThread.dispatch({ + run: DevToolsUtils.makeInfallible(aFunc) + }, Ci.nsIThread.DISPATCH_NORMAL); +} + +// The do_check_* family of functions expect their last argument to be an +// optional stack object. Unfortunately, most tests actually pass a in a string +// containing an error message instead, which causes error reporting to break if +// strict warnings as errors is turned on. To avoid this, we wrap these +// functions here below to ensure the correct number of arguments is passed. +// +// TODO: Remove this once bug 906232 is resolved +// +var do_check_true_old = do_check_true; +var do_check_true = function (condition) { + do_check_true_old(condition); +}; + +var do_check_false_old = do_check_false; +var do_check_false = function (condition) { + do_check_false_old(condition); +}; + +var do_check_eq_old = do_check_eq; +var do_check_eq = function (left, right) { + do_check_eq_old(left, right); +}; + +var do_check_neq_old = do_check_neq; +var do_check_neq = function (left, right) { + do_check_neq_old(left, right); +}; + +var do_check_matches_old = do_check_matches; +var do_check_matches = function (pattern, value) { + do_check_matches_old(pattern, value); +}; + +// Create async version of the object where calling each method +// is equivalent of calling it with asyncall. Mainly useful for +// destructuring objects with methods that take callbacks. +const Async = target => new Proxy(target, Async); +Async.get = (target, name) => + typeof (target[name]) === "function" ? asyncall.bind(null, target[name], target) : + target[name]; + +// Calls async function that takes callback and errorback and returns +// returns promise representing result. +const asyncall = (fn, self, ...args) => + new Promise((...etc) => fn.call(self, ...args, ...etc)); + +const Test = task => () => { + add_task(task); + run_next_test(); +}; + +const assert = do_check_true; + +/** + * Create a promise that is resolved on the next occurence of the given event. + * + * @param DebuggerClient client + * @param String event + * @param Function predicate + * @returns Promise + */ +function waitForEvent(client, type, predicate) { + return new Promise(function (resolve) { + function listener(type, packet) { + if (!predicate(packet)) { + return; + } + client.removeListener(listener); + resolve(packet); + } + + if (predicate) { + client.addListener(type, listener); + } else { + client.addOneTimeListener(type, function (type, packet) { + resolve(packet); + }); + } + }); +} + +/** + * Execute the action on the next tick and return a promise that is resolved on + * the next pause. + * + * When using promises and Task.jsm, we often want to do an action that causes a + * pause and continue the task once the pause has ocurred. Unfortunately, if we + * do the action that causes the pause within the task's current tick we will + * pause before we have a chance to yield the promise that waits for the pause + * and we enter a dead lock. The solution is to create the promise that waits + * for the pause, schedule the action to run on the next tick of the event loop, + * and finally yield the promise. + * + * @param Function action + * @param DebuggerClient client + * @returns Promise + */ +function executeOnNextTickAndWaitForPause(action, client) { + const paused = waitForPause(client); + executeSoon(action); + return paused; +} + +/** + * Interrupt JS execution for the specified thread. + * + * @param ThreadClient threadClient + * @returns Promise + */ +function interrupt(threadClient) { + dumpn("Interrupting."); + return threadClient.interrupt(); +} + +/** + * Resume JS execution for the specified thread and then wait for the next pause + * event. + * + * @param DebuggerClient client + * @param ThreadClient threadClient + * @returns Promise + */ +function resumeAndWaitForPause(client, threadClient) { + const paused = waitForPause(client); + return resume(threadClient).then(() => paused); +} + +/** + * Resume JS execution for a single step and wait for the pause after the step + * has been taken. + * + * @param DebuggerClient client + * @param ThreadClient threadClient + * @returns Promise + */ +function stepIn(client, threadClient) { + dumpn("Stepping in."); + const paused = waitForPause(client); + return threadClient.stepIn() + .then(() => paused); +} + +/** + * Resume JS execution for a step over and wait for the pause after the step + * has been taken. + * + * @param DebuggerClient client + * @param ThreadClient threadClient + * @returns Promise + */ +function stepOver(client, threadClient) { + dumpn("Stepping over."); + return threadClient.stepOver() + .then(() => waitForPause(client)); +} + +/** + * Get the list of `count` frames currently on stack, starting at the index + * `first` for the specified thread. + * + * @param ThreadClient threadClient + * @param Number first + * @param Number count + * @returns Promise + */ +function getFrames(threadClient, first, count) { + dumpn("Getting frames."); + return threadClient.getFrames(first, count); +} + +/** + * Black box the specified source. + * + * @param SourceClient sourceClient + * @returns Promise + */ +function blackBox(sourceClient) { + dumpn("Black boxing source: " + sourceClient.actor); + return sourceClient.blackBox(); +} + +/** + * Stop black boxing the specified source. + * + * @param SourceClient sourceClient + * @returns Promise + */ +function unBlackBox(sourceClient) { + dumpn("Un-black boxing source: " + sourceClient.actor); + return sourceClient.unblackBox(); +} + +/** + * Perform a "source" RDP request with the given SourceClient to get the source + * content and content type. + * + * @param SourceClient sourceClient + * @returns Promise + */ +function getSourceContent(sourceClient) { + dumpn("Getting source content for " + sourceClient.actor); + return sourceClient.source(); +} + +/** + * Get a source at the specified url. + * + * @param ThreadClient threadClient + * @param string url + * @returns Promise<SourceClient> + */ +function getSource(threadClient, url) { + let deferred = promise.defer(); + threadClient.getSources((res) => { + let source = res.sources.filter(function (s) { + return s.url === url; + }); + if (source.length) { + deferred.resolve(threadClient.source(source[0])); + } + else { + deferred.reject(new Error("source not found")); + } + }); + return deferred.promise; +} + +/** + * Do a fake reload which clears the thread debugger + * + * @param TabClient tabClient + * @returns Promise<response> + */ +function reload(tabClient) { + let deferred = promise.defer(); + tabClient._reload({}, deferred.resolve); + return deferred.promise; +} + +/** + * Returns an array of stack location strings given a thread and a sample. + * + * @param object thread + * @param object sample + * @returns object + */ +function getInflatedStackLocations(thread, sample) { + let stackTable = thread.stackTable; + let frameTable = thread.frameTable; + let stringTable = thread.stringTable; + let SAMPLE_STACK_SLOT = thread.samples.schema.stack; + let STACK_PREFIX_SLOT = stackTable.schema.prefix; + let STACK_FRAME_SLOT = stackTable.schema.frame; + let FRAME_LOCATION_SLOT = frameTable.schema.location; + + // Build the stack from the raw data and accumulate the locations in + // an array. + let stackIndex = sample[SAMPLE_STACK_SLOT]; + let locations = []; + while (stackIndex !== null) { + let stackEntry = stackTable.data[stackIndex]; + let frame = frameTable.data[stackEntry[STACK_FRAME_SLOT]]; + locations.push(stringTable[frame[FRAME_LOCATION_SLOT]]); + stackIndex = stackEntry[STACK_PREFIX_SLOT]; + } + + // The profiler tree is inverted, so reverse the array. + return locations.reverse(); +} diff --git a/devtools/server/tests/unit/hello-actor.js b/devtools/server/tests/unit/hello-actor.js new file mode 100644 index 000000000..6d7427f63 --- /dev/null +++ b/devtools/server/tests/unit/hello-actor.js @@ -0,0 +1,18 @@ +/* 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: { + hello: {} + } +}); + +var HelloActor = protocol.ActorClassWithSpec(helloSpec, { + hello: function () { + return; + } +}); diff --git a/devtools/server/tests/unit/post_init_global_actors.js b/devtools/server/tests/unit/post_init_global_actors.js new file mode 100644 index 000000000..0035e8914 --- /dev/null +++ b/devtools/server/tests/unit/post_init_global_actors.js @@ -0,0 +1,17 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +function PostInitGlobalActor(aConnection) {} + +PostInitGlobalActor.prototype = { + actorPrefix: "postInitGlobal", + onPing: function onPing(aRequest) { + return { message: "pong" }; + }, +}; + +PostInitGlobalActor.prototype.requestTypes = { + "ping": PostInitGlobalActor.prototype.onPing, +}; + +DebuggerServer.addGlobalActor(PostInitGlobalActor, "postInitGlobalActor"); diff --git a/devtools/server/tests/unit/post_init_tab_actors.js b/devtools/server/tests/unit/post_init_tab_actors.js new file mode 100644 index 000000000..9b9ddf111 --- /dev/null +++ b/devtools/server/tests/unit/post_init_tab_actors.js @@ -0,0 +1,17 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +function PostInitTabActor(aConnection) {} + +PostInitTabActor.prototype = { + actorPostfix: "postInitTab", + onPing: function onPing(aRequest) { + return { message: "pong" }; + }, +}; + +PostInitTabActor.prototype.requestTypes = { + "ping": PostInitTabActor.prototype.onPing, +}; + +DebuggerServer.addGlobalActor(PostInitTabActor, "postInitTabActor"); diff --git a/devtools/server/tests/unit/pre_init_global_actors.js b/devtools/server/tests/unit/pre_init_global_actors.js new file mode 100644 index 000000000..bd4284a70 --- /dev/null +++ b/devtools/server/tests/unit/pre_init_global_actors.js @@ -0,0 +1,17 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +function PreInitGlobalActor(aConnection) {} + +PreInitGlobalActor.prototype = { + actorPrefix: "preInitGlobal", + onPing: function onPing(aRequest) { + return { message: "pong" }; + }, +}; + +PreInitGlobalActor.prototype.requestTypes = { + "ping": PreInitGlobalActor.prototype.onPing, +}; + +DebuggerServer.addGlobalActor(PreInitGlobalActor, "preInitGlobalActor"); diff --git a/devtools/server/tests/unit/pre_init_tab_actors.js b/devtools/server/tests/unit/pre_init_tab_actors.js new file mode 100644 index 000000000..628f0fb2f --- /dev/null +++ b/devtools/server/tests/unit/pre_init_tab_actors.js @@ -0,0 +1,17 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +function PreInitTabActor(aConnection) {} + +PreInitTabActor.prototype = { + actorPrefix: "preInitTab", + onPing: function onPing(aRequest) { + return { message: "pong" }; + }, +}; + +PreInitTabActor.prototype.requestTypes = { + "ping": PreInitTabActor.prototype.onPing, +}; + +DebuggerServer.addGlobalActor(PreInitTabActor, "preInitTabActor"); diff --git a/devtools/server/tests/unit/registertestactors-01.js b/devtools/server/tests/unit/registertestactors-01.js new file mode 100644 index 000000000..92f511225 --- /dev/null +++ b/devtools/server/tests/unit/registertestactors-01.js @@ -0,0 +1,15 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +function Actor() {} + +exports.register = function (handle) { + handle.addTabActor(Actor, "registeredActor1"); + handle.addGlobalActor(Actor, "registeredActor1"); +}; + +exports.unregister = function (handle) { + handle.removeTabActor(Actor); + handle.removeGlobalActor(Actor); +}; + diff --git a/devtools/server/tests/unit/registertestactors-02.js b/devtools/server/tests/unit/registertestactors-02.js new file mode 100644 index 000000000..54f78e508 --- /dev/null +++ b/devtools/server/tests/unit/registertestactors-02.js @@ -0,0 +1,15 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +function Actor() {} + +exports.register = function (handle) { + handle.addGlobalActor(Actor, "registeredActor2"); + handle.addTabActor(Actor, "registeredActor2"); +}; + +exports.unregister = function (handle) { + handle.removeTabActor(Actor); + handle.removeGlobalActor(Actor); +}; + diff --git a/devtools/server/tests/unit/registertestactors-03.js b/devtools/server/tests/unit/registertestactors-03.js new file mode 100644 index 000000000..8d42fdbd8 --- /dev/null +++ b/devtools/server/tests/unit/registertestactors-03.js @@ -0,0 +1,40 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +var {method, RetVal, Actor, ActorClassWithSpec, Front, FrontClassWithSpec, + generateActorSpec} = require("devtools/shared/protocol"); +var Services = require("Services"); + +const lazySpec = generateActorSpec({ + typeName: "lazy", + + methods: { + hello: { + response: { str: RetVal("string") } + } + } +}); + +exports.LazyActor = ActorClassWithSpec(lazySpec, { + initialize: function (conn, id) { + Actor.prototype.initialize.call(this, conn); + + Services.obs.notifyObservers(null, "actor", "instantiated"); + }, + + hello: function (str) { + return "world"; + } +}); + +Services.obs.notifyObservers(null, "actor", "loaded"); + +exports.LazyFront = FrontClassWithSpec(lazySpec, { + initialize: function (client, form) { + Front.prototype.initialize.call(this, client); + this.actorID = form.lazyActor; + + client.addActorPool(this); + this.manage(this); + } +}); diff --git a/devtools/server/tests/unit/setBreakpoint-on-column-in-gcd-script.js b/devtools/server/tests/unit/setBreakpoint-on-column-in-gcd-script.js new file mode 100644 index 000000000..575915c4f --- /dev/null +++ b/devtools/server/tests/unit/setBreakpoint-on-column-in-gcd-script.js @@ -0,0 +1,7 @@ +"use strict"; + +function f() {} + +(function () { + var a = 1; var b = 2; var c = 3; +})(); diff --git a/devtools/server/tests/unit/setBreakpoint-on-column-with-no-offsets-at-end-of-line.js b/devtools/server/tests/unit/setBreakpoint-on-column-with-no-offsets-at-end-of-line.js new file mode 100644 index 000000000..4c1b52eb4 --- /dev/null +++ b/devtools/server/tests/unit/setBreakpoint-on-column-with-no-offsets-at-end-of-line.js @@ -0,0 +1,6 @@ +"use strict"; + +function f() { + var a = 1; var b = 2; + var c = 3; +} diff --git a/devtools/server/tests/unit/setBreakpoint-on-column-with-no-offsets-in-gcd-script.js b/devtools/server/tests/unit/setBreakpoint-on-column-with-no-offsets-in-gcd-script.js new file mode 100644 index 000000000..adce39193 --- /dev/null +++ b/devtools/server/tests/unit/setBreakpoint-on-column-with-no-offsets-in-gcd-script.js @@ -0,0 +1,7 @@ +"use strict"; + +function f() {} + +(function () { + var a = 1; var c = 3; +})(); diff --git a/devtools/server/tests/unit/setBreakpoint-on-column-with-no-offsets.js b/devtools/server/tests/unit/setBreakpoint-on-column-with-no-offsets.js new file mode 100644 index 000000000..5faefc3c8 --- /dev/null +++ b/devtools/server/tests/unit/setBreakpoint-on-column-with-no-offsets.js @@ -0,0 +1,5 @@ +"use strict"; + +function f() { + var a = 1; var c = 3; +} diff --git a/devtools/server/tests/unit/setBreakpoint-on-column.js b/devtools/server/tests/unit/setBreakpoint-on-column.js new file mode 100644 index 000000000..d92231e65 --- /dev/null +++ b/devtools/server/tests/unit/setBreakpoint-on-column.js @@ -0,0 +1,5 @@ +"use strict"; + +function f() { + var a = 1; var b = 2; var c = 3; +} diff --git a/devtools/server/tests/unit/setBreakpoint-on-line-in-gcd-script.js b/devtools/server/tests/unit/setBreakpoint-on-line-in-gcd-script.js new file mode 100644 index 000000000..fb96be8ab --- /dev/null +++ b/devtools/server/tests/unit/setBreakpoint-on-line-in-gcd-script.js @@ -0,0 +1,9 @@ +"use strict"; + +function f() {} + +(function () { + var a = 1; + var b = 2; + var c = 3; +})(); diff --git a/devtools/server/tests/unit/setBreakpoint-on-line-with-multiple-offsets.js b/devtools/server/tests/unit/setBreakpoint-on-line-with-multiple-offsets.js new file mode 100644 index 000000000..b30ebb504 --- /dev/null +++ b/devtools/server/tests/unit/setBreakpoint-on-line-with-multiple-offsets.js @@ -0,0 +1,7 @@ +"use strict"; + +function f() { + for (var i = 0; i < 1; ++i) { + ; + } +} diff --git a/devtools/server/tests/unit/setBreakpoint-on-line-with-multiple-statements.js b/devtools/server/tests/unit/setBreakpoint-on-line-with-multiple-statements.js new file mode 100644 index 000000000..d92231e65 --- /dev/null +++ b/devtools/server/tests/unit/setBreakpoint-on-line-with-multiple-statements.js @@ -0,0 +1,5 @@ +"use strict"; + +function f() { + var a = 1; var b = 2; var c = 3; +} diff --git a/devtools/server/tests/unit/setBreakpoint-on-line-with-no-offsets-in-gcd-script.js b/devtools/server/tests/unit/setBreakpoint-on-line-with-no-offsets-in-gcd-script.js new file mode 100644 index 000000000..b03d40079 --- /dev/null +++ b/devtools/server/tests/unit/setBreakpoint-on-line-with-no-offsets-in-gcd-script.js @@ -0,0 +1,9 @@ +"use strict"; + +function f() {} + +(function () { + var a = 1; + + var c = 3; +})(); diff --git a/devtools/server/tests/unit/setBreakpoint-on-line-with-no-offsets.js b/devtools/server/tests/unit/setBreakpoint-on-line-with-no-offsets.js new file mode 100644 index 000000000..1268cf8db --- /dev/null +++ b/devtools/server/tests/unit/setBreakpoint-on-line-with-no-offsets.js @@ -0,0 +1,7 @@ +"use strict"; + +function f() { + var a = 1; + + var c = 3; +} diff --git a/devtools/server/tests/unit/setBreakpoint-on-line.js b/devtools/server/tests/unit/setBreakpoint-on-line.js new file mode 100644 index 000000000..1b15e2a5e --- /dev/null +++ b/devtools/server/tests/unit/setBreakpoint-on-line.js @@ -0,0 +1,7 @@ +"use strict"; + +function f() { + var a = 1; + var b = 2; + var c = 3; +} diff --git a/devtools/server/tests/unit/source-map-data/sourcemapped.coffee b/devtools/server/tests/unit/source-map-data/sourcemapped.coffee new file mode 100644 index 000000000..73a400a21 --- /dev/null +++ b/devtools/server/tests/unit/source-map-data/sourcemapped.coffee @@ -0,0 +1,6 @@ +foo = (n) -> + return "foo" + i for i in [0...n] + +[first, second, third] = foo(3) + +debugger
\ No newline at end of file diff --git a/devtools/server/tests/unit/source-map-data/sourcemapped.map b/devtools/server/tests/unit/source-map-data/sourcemapped.map new file mode 100644 index 000000000..dcee3c33c --- /dev/null +++ b/devtools/server/tests/unit/source-map-data/sourcemapped.map @@ -0,0 +1,10 @@ +{ + "version": 3, + "file": "sourcemapped.js", + "sourceRoot": "", + "sources": [ + "sourcemapped.coffee" + ], + "names": [], + "mappings": ";AAAA;CAAA,KAAA,yBAAA;CAAA;CAAA,CAAA,CAAA,MAAO;CACL,IAAA,GAAA;AAAA,CAAA,EAAA,MAA0B,qDAA1B;CAAA,EAAe,EAAR,QAAA;CAAP,IADI;CAAN,EAAM;;CAAN,CAGA,CAAyB,IAAA;;CAEzB,UALA;CAAA" +}
\ No newline at end of file diff --git a/devtools/server/tests/unit/sourcemapped.js b/devtools/server/tests/unit/sourcemapped.js new file mode 100644 index 000000000..94d130903 --- /dev/null +++ b/devtools/server/tests/unit/sourcemapped.js @@ -0,0 +1,16 @@ +// Generated by CoffeeScript 1.6.1 +(function () { + var first, foo, second, third, _ref; + + foo = function (n) { + var i, _i; + for (i = _i = 0; 0 <= n ? _i < n : _i > n; i = 0 <= n ? ++_i : --_i) { + return "foo" + i; + } + }; + + _ref = foo(3), first = _ref[0], second = _ref[1], third = _ref[2]; + + debugger; + +}).call(this); diff --git a/devtools/server/tests/unit/test_MemoryActor_saveHeapSnapshot_01.js b/devtools/server/tests/unit/test_MemoryActor_saveHeapSnapshot_01.js new file mode 100644 index 000000000..2fd38d49c --- /dev/null +++ b/devtools/server/tests/unit/test_MemoryActor_saveHeapSnapshot_01.js @@ -0,0 +1,18 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Test that we can tell the memory actor to take a heap snapshot over the RDP +// and then create a HeapSnapshot instance from the resulting file. + +const { OS } = require("resource://gre/modules/osfile.jsm"); + +const run_test = makeMemoryActorTest(function* (client, memoryFront) { + const snapshotFilePath = yield memoryFront.saveHeapSnapshot(); + ok(!!(yield OS.File.stat(snapshotFilePath)), + "Should have the heap snapshot file"); + const snapshot = ThreadSafeChromeUtils.readHeapSnapshot(snapshotFilePath); + ok(snapshot instanceof HeapSnapshot, + "And we should be able to read a HeapSnapshot instance from the file"); +}); diff --git a/devtools/server/tests/unit/test_MemoryActor_saveHeapSnapshot_02.js b/devtools/server/tests/unit/test_MemoryActor_saveHeapSnapshot_02.js new file mode 100644 index 000000000..564ec0d06 --- /dev/null +++ b/devtools/server/tests/unit/test_MemoryActor_saveHeapSnapshot_02.js @@ -0,0 +1,20 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Test that we can properly stream heap snapshot files over the RDP as bulk +// data. + +const { OS } = require("resource://gre/modules/osfile.jsm"); + +const run_test = makeMemoryActorTest(function* (client, memoryFront) { + const snapshotFilePath = yield memoryFront.saveHeapSnapshot({ + forceCopy: true + }); + ok(!!(yield OS.File.stat(snapshotFilePath)), + "Should have the heap snapshot file"); + const snapshot = ThreadSafeChromeUtils.readHeapSnapshot(snapshotFilePath); + ok(snapshot instanceof HeapSnapshot, + "And we should be able to read a HeapSnapshot instance from the file"); +}); diff --git a/devtools/server/tests/unit/test_MemoryActor_saveHeapSnapshot_03.js b/devtools/server/tests/unit/test_MemoryActor_saveHeapSnapshot_03.js new file mode 100644 index 000000000..e9e81594d --- /dev/null +++ b/devtools/server/tests/unit/test_MemoryActor_saveHeapSnapshot_03.js @@ -0,0 +1,18 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Test that we can save full runtime heap snapshots when attached to the +// ChromeActor or a ChildProcessActor. + +const { OS } = require("resource://gre/modules/osfile.jsm"); + +const run_test = makeFullRuntimeMemoryActorTest(function* (client, memoryFront) { + const snapshotFilePath = yield memoryFront.saveHeapSnapshot(); + ok(!!(yield OS.File.stat(snapshotFilePath)), + "Should have the heap snapshot file"); + const snapshot = ThreadSafeChromeUtils.readHeapSnapshot(snapshotFilePath); + ok(snapshot instanceof HeapSnapshot, + "And we should be able to read a HeapSnapshot instance from the file"); +}); diff --git a/devtools/server/tests/unit/test_actor-registry-actor.js b/devtools/server/tests/unit/test_actor-registry-actor.js new file mode 100644 index 000000000..8b0abfbbb --- /dev/null +++ b/devtools/server/tests/unit/test_actor-registry-actor.js @@ -0,0 +1,80 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Check that you can register new actors via the ActorRegistrationActor. + */ + +var gClient; +var gRegistryFront; +var gActorFront; +var gOldPref; + +const { ActorRegistryFront } = require("devtools/shared/fronts/actor-registry"); + +function run_test() +{ + gOldPref = Services.prefs.getBoolPref("devtools.debugger.forbid-certified-apps"); + Services.prefs.setBoolPref("devtools.debugger.forbid-certified-apps", false); + initTestDebuggerServer(); + DebuggerServer.addBrowserActors(); + gClient = new DebuggerClient(DebuggerServer.connectPipe()); + gClient.connect().then(getRegistry); + do_test_pending(); +} + +function getRegistry() { + gClient.listTabs((response) => { + gRegistryFront = ActorRegistryFront(gClient, response); + registerNewActor(); + }); +} + +function registerNewActor() { + let options = { + prefix: "helloActor", + constructor: "HelloActor", + type: { global: true } + }; + + gRegistryFront + .registerActor("resource://test/hello-actor.js", options) + .then(actorFront => gActorFront = actorFront) + .then(talkToNewActor) + .then(null, e => { + DevToolsUtils.reportException("registerNewActor", e); + do_check_true(false); + }); +} + +function talkToNewActor() { + gClient.listTabs(({ helloActor }) => { + do_check_true(!!helloActor); + gClient.request({ + to: helloActor, + type: "hello" + }, response => { + do_check_true(!response.error); + unregisterNewActor(); + }); + }); +} + +function unregisterNewActor() { + gActorFront + .unregister() + .then(testActorIsUnregistered) + .then(null, e => { + DevToolsUtils.reportException("unregisterNewActor", e); + do_check_true(false); + }); +} + +function testActorIsUnregistered() { + gClient.listTabs(({ helloActor }) => { + do_check_true(!helloActor); + + Services.prefs.setBoolPref("devtools.debugger.forbid-certified-apps", gOldPref); + finishClient(gClient); + }); +} diff --git a/devtools/server/tests/unit/test_add_actors.js b/devtools/server/tests/unit/test_add_actors.js new file mode 100644 index 000000000..9b90da724 --- /dev/null +++ b/devtools/server/tests/unit/test_add_actors.js @@ -0,0 +1,107 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +var gClient; +var gActors; + +/** + * The purpose of these tests is to verify that it's possible to add actors + * both before and after the DebuggerServer has been initialized, so addons + * that add actors don't have to poll the object for its initialization state + * in order to add actors after initialization but rather can add actors anytime + * regardless of the object's state. + */ +function run_test() +{ + DebuggerServer.addActors("resource://test/pre_init_global_actors.js"); + DebuggerServer.addActors("resource://test/pre_init_tab_actors.js"); + + DebuggerServer.init(); + DebuggerServer.addBrowserActors(); + + DebuggerServer.addActors("resource://test/post_init_global_actors.js"); + DebuggerServer.addActors("resource://test/post_init_tab_actors.js"); + + add_test(init); + add_test(test_pre_init_global_actor); + add_test(test_pre_init_tab_actor); + add_test(test_post_init_global_actor); + add_test(test_post_init_tab_actor); + add_test(test_stable_global_actor_instances); + add_test(close_client); + run_next_test(); +} + +function init() +{ + gClient = new DebuggerClient(DebuggerServer.connectPipe()); + gClient.connect() + .then(() => gClient.listTabs()) + .then(aResponse => { + gActors = aResponse; + run_next_test(); + }); +} + +function test_pre_init_global_actor() +{ + gClient.request({ to: gActors.preInitGlobalActor, type: "ping" }, + function onResponse(aResponse) { + do_check_eq(aResponse.message, "pong"); + run_next_test(); + } + ); +} + +function test_pre_init_tab_actor() +{ + gClient.request({ to: gActors.preInitTabActor, type: "ping" }, + function onResponse(aResponse) { + do_check_eq(aResponse.message, "pong"); + run_next_test(); + } + ); +} + +function test_post_init_global_actor() +{ + gClient.request({ to: gActors.postInitGlobalActor, type: "ping" }, + function onResponse(aResponse) { + do_check_eq(aResponse.message, "pong"); + run_next_test(); + } + ); +} + +function test_post_init_tab_actor() +{ + gClient.request({ to: gActors.postInitTabActor, type: "ping" }, + function onResponse(aResponse) { + do_check_eq(aResponse.message, "pong"); + run_next_test(); + } + ); +} + +// Get the object object, from the server side, for a given actor ID +function getActorInstance(connID, actorID) { + return DebuggerServer._connections[connID].getActor(actorID); +} + +function test_stable_global_actor_instances() +{ + // Consider that there is only one connection, + // and the first one is ours + let connID = Object.keys(DebuggerServer._connections)[0]; + let postInitGlobalActor = getActorInstance(connID, gActors.postInitGlobalActor); + let preInitGlobalActor = getActorInstance(connID, gActors.preInitGlobalActor); + gClient.listTabs(function onListTabs(aResponse) { + do_check_eq(postInitGlobalActor, getActorInstance(connID, aResponse.postInitGlobalActor)); + do_check_eq(preInitGlobalActor, getActorInstance(connID, aResponse.preInitGlobalActor)); + run_next_test(); + }); +} + +function close_client() { + gClient.close().then(() => run_next_test()); +} diff --git a/devtools/server/tests/unit/test_addon_reload.js b/devtools/server/tests/unit/test_addon_reload.js new file mode 100644 index 000000000..0187fa3b3 --- /dev/null +++ b/devtools/server/tests/unit/test_addon_reload.js @@ -0,0 +1,98 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +const protocol = require("devtools/shared/protocol"); +const {AddonManager} = require("resource://gre/modules/AddonManager.jsm"); + +startupAddonsManager(); + +function promiseAddonEvent(event) { + return new Promise(resolve => { + let listener = { + [event]: function (...args) { + AddonManager.removeAddonListener(listener); + resolve(args); + } + }; + + AddonManager.addAddonListener(listener); + }); +} + +function* findAddonInRootList(client, addonId) { + const result = yield client.listAddons(); + const addonActor = result.addons.filter(addon => addon.id === addonId)[0]; + ok(addonActor, `Found add-on actor for ${addonId}`); + return addonActor; +} + +function* reloadAddon(client, addonActor) { + // The add-on will be re-installed after a successful reload. + const onInstalled = promiseAddonEvent("onInstalled"); + yield client.request({to: addonActor.actor, type: "reload"}); + yield onInstalled; +} + +function getSupportFile(path) { + const allowMissing = false; + return do_get_file(path, allowMissing); +} + +add_task(function* testReloadExitedAddon() { + const client = yield new Promise(resolve => { + get_chrome_actors(client => resolve(client)); + }); + + // Install our main add-on to trigger reloads on. + const addonFile = getSupportFile("addons/web-extension"); + const installedAddon = yield AddonManager.installTemporaryAddon( + addonFile); + + // Install a decoy add-on. + const addonFile2 = getSupportFile("addons/web-extension2"); + const installedAddon2 = yield AddonManager.installTemporaryAddon( + addonFile2); + + let addonActor = yield findAddonInRootList(client, installedAddon.id); + + yield reloadAddon(client, addonActor); + + // Uninstall the decoy add-on, which should cause its actor to exit. + const onUninstalled = promiseAddonEvent("onUninstalled"); + installedAddon2.uninstall(); + const [uninstalledAddon] = yield onUninstalled; + + // Try to re-list all add-ons after a reload. + // This was throwing an exception because of the exited actor. + const newAddonActor = yield findAddonInRootList(client, installedAddon.id); + equal(newAddonActor.id, addonActor.id); + + // The actor id should be the same after the reload + equal(newAddonActor.actor, addonActor.actor); + + const onAddonListChanged = new Promise((resolve) => { + client.addListener("addonListChanged", function listener() { + client.removeListener("addonListChanged", listener); + resolve(); + }); + }); + + // Install an upgrade version of the first add-on. + const addonUpgradeFile = getSupportFile("addons/web-extension-upgrade"); + const upgradedAddon = yield AddonManager.installTemporaryAddon( + addonUpgradeFile); + + // Waiting for addonListChanged unsolicited event + yield onAddonListChanged; + + // re-list all add-ons after an upgrade. + const upgradedAddonActor = yield findAddonInRootList(client, upgradedAddon.id); + equal(upgradedAddonActor.id, addonActor.id); + // The actor id should be the same after the upgrade. + equal(upgradedAddonActor.actor, addonActor.actor); + + // The addon metadata has been updated. + equal(upgradedAddonActor.name, "Test Addons Actor Upgrade"); + + yield close(client); +}); diff --git a/devtools/server/tests/unit/test_addons_actor.js b/devtools/server/tests/unit/test_addons_actor.js new file mode 100644 index 000000000..1815d43c6 --- /dev/null +++ b/devtools/server/tests/unit/test_addons_actor.js @@ -0,0 +1,51 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +const protocol = require("devtools/shared/protocol"); +const {AddonsActor} = require("devtools/server/actors/addons"); +const {AddonsFront} = require("devtools/shared/fronts/addons"); + +startupAddonsManager(); + +function* connect() { + const client = yield new Promise(resolve => { + get_chrome_actors(client => resolve(client)); + }); + const root = yield listTabs(client); + const addonsActor = root.addonsActor; + ok(addonsActor, "Got AddonsActor instance"); + + const addons = AddonsFront(client, {addonsActor}); + return [client, addons]; +} + +add_task(function* testSuccessfulInstall() { + const [client, addons] = yield connect(); + + const allowMissing = false; + const usePlatformSeparator = true; + const addonPath = getFilePath("addons/web-extension", + allowMissing, usePlatformSeparator); + const installedAddon = yield addons.installTemporaryAddon(addonPath); + equal(installedAddon.id, "test-addons-actor@mozilla.org"); + // The returned object is currently not a proper actor. + equal(installedAddon.actor, false); + + const addonList = yield client.listAddons(); + ok(addonList && addonList.addons && addonList.addons.map(a => a.name), + "Received list of add-ons"); + const addon = addonList.addons.filter(a => a.id === installedAddon.id)[0]; + ok(addon, "Test add-on appeared in root install list"); + + yield close(client); +}); + +add_task(function* testNonExistantPath() { + const [client, addons] = yield connect(); + + yield Assert.rejects( + addons.installTemporaryAddon("some-non-existant-path"), + /Could not install add-on.*Component returned failure/); + + yield close(client); +}); diff --git a/devtools/server/tests/unit/test_animation_name.js b/devtools/server/tests/unit/test_animation_name.js new file mode 100644 index 000000000..4cd708fc4 --- /dev/null +++ b/devtools/server/tests/unit/test_animation_name.js @@ -0,0 +1,87 @@ +/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+"use strict";
+
+// Test that AnimationPlayerActor.getName returns the right name depending on
+// the type of an animation and the various properties available on it.
+
+const { AnimationPlayerActor } = require("devtools/server/actors/animation");
+
+function run_test() {
+ // Mock a window with just the properties the AnimationPlayerActor uses.
+ let window = {
+ MutationObserver: function () {
+ this.observe = () => {};
+ },
+ Animation: function () {
+ this.effect = {target: getMockNode()};
+ },
+ CSSAnimation: function () {
+ this.effect = {target: getMockNode()};
+ },
+ CSSTransition: function () {
+ this.effect = {target: getMockNode()};
+ }
+ };
+
+ window.CSSAnimation.prototype = Object.create(window.Animation.prototype);
+ window.CSSTransition.prototype = Object.create(window.Animation.prototype);
+
+ // Helper to get a mock DOM node.
+ function getMockNode() {
+ return {
+ ownerDocument: {
+ defaultView: window
+ }
+ };
+ }
+
+ // Objects in this array should contain the following properties:
+ // - desc {String} For logging
+ // - animation {Object} An animation object instantiated from one of the mock
+ // window animation constructors.
+ // - props {Objet} Properties of this object will be added to the animation
+ // object.
+ // - expectedName {String} The expected name returned by
+ // AnimationPlayerActor.getName.
+ const TEST_DATA = [{
+ desc: "Animation with an id",
+ animation: new window.Animation(),
+ props: { id: "animation-id" },
+ expectedName: "animation-id"
+ }, {
+ desc: "Animation without an id",
+ animation: new window.Animation(),
+ props: {},
+ expectedName: ""
+ }, {
+ desc: "CSSTransition with an id",
+ animation: new window.CSSTransition(),
+ props: { id: "transition-with-id", transitionProperty: "width" },
+ expectedName: "transition-with-id"
+ }, {
+ desc: "CSSAnimation with an id",
+ animation: new window.CSSAnimation(),
+ props: { id: "animation-with-id", animationName: "move" },
+ expectedName: "animation-with-id"
+ }, {
+ desc: "CSSTransition without an id",
+ animation: new window.CSSTransition(),
+ props: { transitionProperty: "width" },
+ expectedName: "width"
+ }, {
+ desc: "CSSAnimation without an id",
+ animation: new window.CSSAnimation(),
+ props: { animationName: "move" },
+ expectedName: "move"
+ }];
+
+ for (let { desc, animation, props, expectedName } of TEST_DATA) {
+ do_print(desc);
+ for (let key in props) {
+ animation[key] = props[key];
+ }
+ let actor = AnimationPlayerActor({}, animation);
+ do_check_eq(actor.getName(), expectedName);
+ }
+}
diff --git a/devtools/server/tests/unit/test_animation_type.js b/devtools/server/tests/unit/test_animation_type.js new file mode 100644 index 000000000..0f37755a4 --- /dev/null +++ b/devtools/server/tests/unit/test_animation_type.js @@ -0,0 +1,68 @@ +/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+"use strict";
+
+// Test the output of AnimationPlayerActor.getType().
+
+const { ANIMATION_TYPES, AnimationPlayerActor } =
+ require("devtools/server/actors/animation");
+
+function run_test() {
+ // Mock a window with just the properties the AnimationPlayerActor uses.
+ let window = {
+ MutationObserver: function () {
+ this.observe = () => {};
+ },
+ Animation: function () {
+ this.effect = {target: getMockNode()};
+ },
+ CSSAnimation: function () {
+ this.effect = {target: getMockNode()};
+ },
+ CSSTransition: function () {
+ this.effect = {target: getMockNode()};
+ }
+ };
+
+ window.CSSAnimation.prototype = Object.create(window.Animation.prototype);
+ window.CSSTransition.prototype = Object.create(window.Animation.prototype);
+
+ // Helper to get a mock DOM node.
+ function getMockNode() {
+ return {
+ ownerDocument: {
+ defaultView: window
+ }
+ };
+ }
+
+ // Objects in this array should contain the following properties:
+ // - desc {String} For logging
+ // - animation {Object} An animation object instantiated from one of the mock
+ // window animation constructors.
+ // - expectedType {String} The expected type returned by
+ // AnimationPlayerActor.getType.
+ const TEST_DATA = [{
+ desc: "Test CSSAnimation type",
+ animation: new window.CSSAnimation(),
+ expectedType: ANIMATION_TYPES.CSS_ANIMATION
+ }, {
+ desc: "Test CSSTransition type",
+ animation: new window.CSSTransition(),
+ expectedType: ANIMATION_TYPES.CSS_TRANSITION
+ }, {
+ desc: "Test ScriptAnimation type",
+ animation: new window.Animation(),
+ expectedType: ANIMATION_TYPES.SCRIPT_ANIMATION
+ }, {
+ desc: "Test unknown type",
+ animation: {effect: {target: getMockNode()}},
+ expectedType: ANIMATION_TYPES.UNKNOWN
+ }];
+
+ for (let { desc, animation, expectedType } of TEST_DATA) {
+ do_print(desc);
+ let actor = AnimationPlayerActor({}, animation);
+ do_check_eq(actor.getType(), expectedType);
+ }
+}
diff --git a/devtools/server/tests/unit/test_attach.js b/devtools/server/tests/unit/test_attach.js new file mode 100644 index 000000000..a69db2c2b --- /dev/null +++ b/devtools/server/tests/unit/test_attach.js @@ -0,0 +1,37 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +var gClient; +var gDebuggee; + +function run_test() +{ + initTestDebuggerServer(); + gDebuggee = testGlobal("test-1"); + DebuggerServer.addTestGlobal(gDebuggee); + + let transport = DebuggerServer.connectPipe(); + gClient = new DebuggerClient(transport); + gClient.connect().then(function ([aType, aTraits]) { + attachTestTab(gClient, "test-1", function (aReply, aTabClient) { + test_attach(aTabClient); + }); + }); + do_test_pending(); +} + +function test_attach(aTabClient) +{ + aTabClient.attachThread({}, function (aResponse, aThreadClient) { + do_check_eq(aThreadClient.state, "paused"); + aThreadClient.resume(cleanup); + }); +} + +function cleanup() +{ + gClient.addListener("closed", function (aEvent) { + do_test_finished(); + }); + gClient.close(); +} diff --git a/devtools/server/tests/unit/test_blackboxing-01.js b/devtools/server/tests/unit/test_blackboxing-01.js new file mode 100644 index 000000000..d5356c390 --- /dev/null +++ b/devtools/server/tests/unit/test_blackboxing-01.js @@ -0,0 +1,145 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Test basic black boxing. + */ + +var gDebuggee; +var gClient; +var gThreadClient; + +function run_test() +{ + initTestDebuggerServer(); + gDebuggee = addTestGlobal("test-black-box"); + gClient = new DebuggerClient(DebuggerServer.connectPipe()); + gClient.connect().then(function () { + attachTestTabAndResume(gClient, "test-black-box", function (aResponse, aTabClient, aThreadClient) { + gThreadClient = aThreadClient; + testBlackBox(); + }); + }); + do_test_pending(); +} + +const BLACK_BOXED_URL = "http://example.com/blackboxme.js"; +const SOURCE_URL = "http://example.com/source.js"; + +const testBlackBox = Task.async(function* () { + let packet = yield executeOnNextTickAndWaitForPause(evalCode, gClient); + let source = gThreadClient.source(packet.frame.where.source); + + yield setBreakpoint(source, { + line: 2 + }); + yield resume(gThreadClient); + + const { sources } = yield getSources(gThreadClient); + let sourceClient = gThreadClient.source( + sources.filter(s => s.url == BLACK_BOXED_URL)[0]); + do_check_true(!sourceClient.isBlackBoxed, + "By default the source is not black boxed."); + + // Test that we can step into `doStuff` when we are not black boxed. + yield runTest( + function onSteppedLocation(aLocation) { + do_check_eq(aLocation.source.url, BLACK_BOXED_URL); + do_check_eq(aLocation.line, 2); + }, + function onDebuggerStatementFrames(aFrames) { + do_check_true(!aFrames.some(f => f.where.source.isBlackBoxed)); + } + ); + + let blackBoxResponse = yield blackBox(sourceClient); + do_check_true(sourceClient.isBlackBoxed); + + // Test that we step through `doStuff` when we are black boxed and its frame + // doesn't show up. + yield runTest( + function onSteppedLocation(aLocation) { + do_check_eq(aLocation.source.url, SOURCE_URL); + do_check_eq(aLocation.line, 4); + }, + function onDebuggerStatementFrames(aFrames) { + for (let f of aFrames) { + if (f.where.source.url == BLACK_BOXED_URL) { + do_check_true(f.where.source.isBlackBoxed); + } else { + do_check_true(!f.where.source.isBlackBoxed); + } + } + } + ); + + let unBlackBoxResponse = yield unBlackBox(sourceClient); + do_check_true(!sourceClient.isBlackBoxed); + + // Test that we can step into `doStuff` again. + yield runTest( + function onSteppedLocation(aLocation) { + do_check_eq(aLocation.source.url, BLACK_BOXED_URL); + do_check_eq(aLocation.line, 2); + }, + function onDebuggerStatementFrames(aFrames) { + do_check_true(!aFrames.some(f => f.where.source.isBlackBoxed)); + } + ); + + finishClient(gClient); +}); + +function evalCode() { + Components.utils.evalInSandbox( + "" + function doStuff(k) { // line 1 + let arg = 15; // line 2 - Step in here + k(arg); // line 3 + }, // line 4 + gDebuggee, + "1.8", + BLACK_BOXED_URL, + 1 + ); + + Components.utils.evalInSandbox( + "" + function runTest() { // line 1 + doStuff( // line 2 - Break here + function (n) { // line 3 - Step through `doStuff` to here + debugger; // line 4 + } // line 5 + ); // line 6 + } + "\n" // line 7 + + "debugger;", // line 8 + gDebuggee, + "1.8", + SOURCE_URL, + 1 + ); +} + +const runTest = Task.async(function* (onSteppedLocation, onDebuggerStatementFrames) { + let packet = yield executeOnNextTickAndWaitForPause(gDebuggee.runTest, + gClient); + do_check_eq(packet.why.type, "breakpoint"); + + yield stepIn(gClient, gThreadClient); + yield stepIn(gClient, gThreadClient); + yield stepIn(gClient, gThreadClient); + + const location = yield getCurrentLocation(); + onSteppedLocation(location); + + packet = yield resumeAndWaitForPause(gClient, gThreadClient); + do_check_eq(packet.why.type, "debuggerStatement"); + + let { frames } = yield getFrames(gThreadClient, 0, 100); + onDebuggerStatementFrames(frames); + + return resume(gThreadClient); +}); + +const getCurrentLocation = Task.async(function* () { + const response = yield getFrames(gThreadClient, 0, 1); + return response.frames[0].where; +}); diff --git a/devtools/server/tests/unit/test_blackboxing-02.js b/devtools/server/tests/unit/test_blackboxing-02.js new file mode 100644 index 000000000..5bfb3641a --- /dev/null +++ b/devtools/server/tests/unit/test_blackboxing-02.js @@ -0,0 +1,113 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Test that we don't hit breakpoints in black boxed sources, and that when we + * unblack box the source again, the breakpoint hasn't disappeared and we will + * hit it again. + */ + +var gDebuggee; +var gClient; +var gThreadClient; + +function run_test() +{ + initTestDebuggerServer(); + gDebuggee = addTestGlobal("test-black-box"); + gClient = new DebuggerClient(DebuggerServer.connectPipe()); + gClient.connect().then(function () { + attachTestTabAndResume(gClient, "test-black-box", function (aResponse, aTabClient, aThreadClient) { + gThreadClient = aThreadClient; + test_black_box(); + }); + }); + do_test_pending(); +} + +const BLACK_BOXED_URL = "http://example.com/blackboxme.js"; +const SOURCE_URL = "http://example.com/source.js"; + +function test_black_box() +{ + gClient.addOneTimeListener("paused", function (aEvent, aPacket) { + gThreadClient.eval(aPacket.frame.actor, "doStuff", function (aResponse) { + gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) { + let obj = gThreadClient.pauseGrip(aPacket.why.frameFinished.return); + obj.getDefinitionSite(runWithSource); + }); + }); + + function runWithSource(aPacket) { + let source = gThreadClient.source(aPacket.source); + source.setBreakpoint({ + line: 2 + }, function (aResponse) { + do_check_true(!aResponse.error, "Should be able to set breakpoint."); + gThreadClient.resume(test_black_box_breakpoint); + }); + } + }); + + Components.utils.evalInSandbox( + "" + function doStuff(k) { // line 1 + let arg = 15; // line 2 - Break here + k(arg); // line 3 + }, // line 4 + gDebuggee, + "1.8", + BLACK_BOXED_URL, + 1 + ); + + Components.utils.evalInSandbox( + "" + function runTest() { // line 1 + doStuff( // line 2 + function (n) { // line 3 + debugger; // line 5 + } // line 6 + ); // line 7 + } // line 8 + + "\n debugger;", // line 9 + gDebuggee, + "1.8", + SOURCE_URL, + 1 + ); +} + +function test_black_box_breakpoint() { + gThreadClient.getSources(function ({error, sources}) { + do_check_true(!error, "Should not get an error: " + error); + let sourceClient = gThreadClient.source(sources.filter(s => s.url == BLACK_BOXED_URL)[0]); + sourceClient.blackBox(function ({error}) { + do_check_true(!error, "Should not get an error: " + error); + + gClient.addOneTimeListener("paused", function (aEvent, aPacket) { + do_check_eq(aPacket.why.type, "debuggerStatement", + "We should pass over the breakpoint since the source is black boxed."); + gThreadClient.resume(test_unblack_box_breakpoint.bind(null, sourceClient)); + }); + gDebuggee.runTest(); + }); + }); +} + +function test_unblack_box_breakpoint(aSourceClient) { + aSourceClient.unblackBox(function ({error}) { + do_check_true(!error, "Should not get an error: " + error); + gClient.addOneTimeListener("paused", function (aEvent, aPacket) { + do_check_eq(aPacket.why.type, "breakpoint", + "We should hit the breakpoint again"); + + // We will hit the debugger statement on resume, so do this nastiness to skip over it. + gClient.addOneTimeListener( + "paused", + gThreadClient.resume.bind( + gThreadClient, + finishClient.bind(null, gClient))); + gThreadClient.resume(); + }); + gDebuggee.runTest(); + }); +} diff --git a/devtools/server/tests/unit/test_blackboxing-03.js b/devtools/server/tests/unit/test_blackboxing-03.js new file mode 100644 index 000000000..48f178777 --- /dev/null +++ b/devtools/server/tests/unit/test_blackboxing-03.js @@ -0,0 +1,102 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Test that we don't stop at debugger statements inside black boxed sources. + */ + +var gDebuggee; +var gClient; +var gThreadClient; +var gBpClient; + +function run_test() +{ + initTestDebuggerServer(); + gDebuggee = addTestGlobal("test-black-box"); + gClient = new DebuggerClient(DebuggerServer.connectPipe()); + gClient.connect().then(function () { + attachTestTabAndResume(gClient, "test-black-box", function (aResponse, aTabClient, aThreadClient) { + gThreadClient = aThreadClient; + test_black_box(); + }); + }); + do_test_pending(); +} + +const BLACK_BOXED_URL = "http://example.com/blackboxme.js"; +const SOURCE_URL = "http://example.com/source.js"; + +function test_black_box() +{ + gClient.addOneTimeListener("paused", function (aEvent, aPacket) { + let source = gThreadClient.source(aPacket.frame.where.source); + source.setBreakpoint({ + line: 4 + }, function ({error}, bpClient) { + gBpClient = bpClient; + do_check_true(!error, "Should not get an error: " + error); + gThreadClient.resume(test_black_box_dbg_statement); + }); + }); + + Components.utils.evalInSandbox( + "" + function doStuff(k) { // line 1 + debugger; // line 2 - Break here + k(100); // line 3 + }, // line 4 + gDebuggee, + "1.8", + BLACK_BOXED_URL, + 1 + ); + + Components.utils.evalInSandbox( + "" + function runTest() { // line 1 + doStuff( // line 2 + function (n) { // line 3 + Math.abs(n); // line 4 - Break here + } // line 5 + ); // line 6 + } // line 7 + + "\n debugger;", // line 8 + gDebuggee, + "1.8", + SOURCE_URL, + 1 + ); +} + +function test_black_box_dbg_statement() { + gThreadClient.getSources(function ({error, sources}) { + do_check_true(!error, "Should not get an error: " + error); + let sourceClient = gThreadClient.source(sources.filter(s => s.url == BLACK_BOXED_URL)[0]); + + sourceClient.blackBox(function ({error}) { + do_check_true(!error, "Should not get an error: " + error); + + gClient.addOneTimeListener("paused", function (aEvent, aPacket) { + do_check_eq(aPacket.why.type, "breakpoint", + "We should pass over the debugger statement."); + gBpClient.remove(function ({error}) { + do_check_true(!error, "Should not get an error: " + error); + gThreadClient.resume(test_unblack_box_dbg_statement.bind(null, sourceClient)); + }); + }); + gDebuggee.runTest(); + }); + }); +} + +function test_unblack_box_dbg_statement(aSourceClient) { + aSourceClient.unblackBox(function ({error}) { + do_check_true(!error, "Should not get an error: " + error); + + gClient.addOneTimeListener("paused", function (aEvent, aPacket) { + do_check_eq(aPacket.why.type, "debuggerStatement", + "We should stop at the debugger statement again"); + finishClient(gClient); + }); + gDebuggee.runTest(); + }); +} diff --git a/devtools/server/tests/unit/test_blackboxing-04.js b/devtools/server/tests/unit/test_blackboxing-04.js new file mode 100644 index 000000000..fbfaf2881 --- /dev/null +++ b/devtools/server/tests/unit/test_blackboxing-04.js @@ -0,0 +1,88 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Test behavior of blackboxing sources we are currently paused in. + */ + +var gDebuggee; +var gClient; +var gThreadClient; + +function run_test() +{ + initTestDebuggerServer(); + gDebuggee = addTestGlobal("test-black-box"); + gClient = new DebuggerClient(DebuggerServer.connectPipe()); + gClient.connect().then(function () { + attachTestTabAndResume(gClient, "test-black-box", function (aResponse, aTabClient, aThreadClient) { + gThreadClient = aThreadClient; + test_black_box(); + }); + }); + do_test_pending(); +} + +const BLACK_BOXED_URL = "http://example.com/blackboxme.js"; +const SOURCE_URL = "http://example.com/source.js"; + +function test_black_box() +{ + gClient.addOneTimeListener("paused", function (aEvent, aPacket) { + gThreadClient.eval(aPacket.frame.actor, "doStuff", function (aResponse) { + gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) { + let obj = gThreadClient.pauseGrip(aPacket.why.frameFinished.return); + obj.getDefinitionSite(runWithSource); + }); + }); + + function runWithSource(aPacket) { + let source = gThreadClient.source(aPacket.source); + source.setBreakpoint({ + line: 2 + }, function (aResponse) { + do_check_true(!aResponse.error, "Should be able to set breakpoint."); + test_black_box_paused(); + }); + } + }); + + Components.utils.evalInSandbox( + "" + function doStuff(k) { // line 1 + debugger; // line 2 + k(100); // line 3 + }, // line 4 + gDebuggee, + "1.8", + BLACK_BOXED_URL, + 1 + ); + + Components.utils.evalInSandbox( + "" + function runTest() { // line 1 + doStuff( // line 2 + function (n) { // line 3 + return n; // line 4 + } // line 5 + ); // line 6 + } // line 7 + + "\n runTest();", // line 8 + gDebuggee, + "1.8", + SOURCE_URL, + 1 + ); +} + +function test_black_box_paused() { + gThreadClient.getSources(function ({error, sources}) { + do_check_true(!error, "Should not get an error: " + error); + let sourceClient = gThreadClient.source(sources.filter(s => s.url == BLACK_BOXED_URL)[0]); + + sourceClient.blackBox(function ({error, pausedInSource}) { + do_check_true(!error, "Should not get an error: " + error); + do_check_true(pausedInSource, "We should be notified that we are currently paused in this source"); + finishClient(gClient); + }); + }); +} diff --git a/devtools/server/tests/unit/test_blackboxing-05.js b/devtools/server/tests/unit/test_blackboxing-05.js new file mode 100644 index 000000000..fa8142e87 --- /dev/null +++ b/devtools/server/tests/unit/test_blackboxing-05.js @@ -0,0 +1,84 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Test exceptions inside black boxed sources. + */ + +var gDebuggee; +var gClient; +var gThreadClient; + +function run_test() +{ + initTestDebuggerServer(); + gDebuggee = addTestGlobal("test-black-box"); + gClient = new DebuggerClient(DebuggerServer.connectPipe()); + gClient.connect().then(function () { + attachTestTabAndResume(gClient, "test-black-box", function (aResponse, aTabClient, aThreadClient) { + gThreadClient = aThreadClient; + // XXX: We have to do an executeSoon so that the error isn't caught and + // reported by DebuggerClient.requester (because we are using the local + // transport and share a stack) which causes the test to fail. + Services.tm.mainThread.dispatch({ + run: test_black_box + }, Ci.nsIThread.DISPATCH_NORMAL); + }); + }); + do_test_pending(); +} + +const BLACK_BOXED_URL = "http://example.com/blackboxme.js"; +const SOURCE_URL = "http://example.com/source.js"; + +function test_black_box() +{ + gClient.addOneTimeListener("paused", test_black_box_exception); + + Components.utils.evalInSandbox( + "" + function doStuff(k) { // line 1 + throw new Error("wu tang clan ain't nuthin' ta fuck wit"); // line 2 + k(100); // line 3 + }, // line 4 + gDebuggee, + "1.8", + BLACK_BOXED_URL, + 1 + ); + + Components.utils.evalInSandbox( + "" + function runTest() { // line 1 + doStuff( // line 2 + function (n) { // line 3 + debugger; // line 4 + } // line 5 + ); // line 6 + } // line 7 + + "\ndebugger;\n" // line 8 + + "try { runTest() } catch (ex) { }", // line 9 + gDebuggee, + "1.8", + SOURCE_URL, + 1 + ); +} + +function test_black_box_exception() { + gThreadClient.getSources(function ({error, sources}) { + do_check_true(!error, "Should not get an error: " + error); + let sourceClient = gThreadClient.source(sources.filter(s => s.url == BLACK_BOXED_URL)[0]); + + sourceClient.blackBox(function ({error}) { + do_check_true(!error, "Should not get an error: " + error); + gThreadClient.pauseOnExceptions(true); + + gClient.addOneTimeListener("paused", function (aEvent, aPacket) { + do_check_eq(aPacket.frame.where.source.url, SOURCE_URL, + "We shouldn't pause while in the black boxed source."); + finishClient(gClient); + }); + + gThreadClient.resume(); + }); + }); +} diff --git a/devtools/server/tests/unit/test_blackboxing-06.js b/devtools/server/tests/unit/test_blackboxing-06.js new file mode 100644 index 000000000..9384f2cc2 --- /dev/null +++ b/devtools/server/tests/unit/test_blackboxing-06.js @@ -0,0 +1,102 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Test that we can black box source mapped sources. + */ + +var gDebuggee; +var gClient; +var gThreadClient; + +const {SourceNode} = require("source-map"); + +function run_test() +{ + initTestDebuggerServer(); + gDebuggee = addTestGlobal("test-black-box"); + gClient = new DebuggerClient(DebuggerServer.connectPipe()); + gClient.connect().then(function () { + attachTestTabAndResume(gClient, "test-black-box", function (aResponse, aTabClient, aThreadClient) { + gThreadClient = aThreadClient; + + promise.resolve(setup_code()) + .then(black_box_code) + .then(run_code) + .then(test_correct_location) + .then(null, function (error) { + do_check_true(false, "Should not get an error, got " + error); + }) + .then(function () { + finishClient(gClient); + }); + }); + }); + do_test_pending(); +} + +function setup_code() { + let { code, map } = (new SourceNode(null, null, null, [ + new SourceNode(1, 0, "a.js", "" + function a() { + return b(); + }), + "\n", + new SourceNode(1, 0, "b.js", "" + function b() { + debugger; // Don't want to stop here. + return c(); + }), + "\n", + new SourceNode(1, 0, "c.js", "" + function c() { + debugger; // Want to stop here. + }), + "\n" + ])).toStringWithSourceMap({ + file: "abc.js", + sourceRoot: "http://example.com/" + }); + + code += "//# sourceMappingURL=data:text/json," + map.toString(); + + Components.utils.evalInSandbox(code, + gDebuggee, + "1.8", + "http://example.com/abc.js"); +} + +function black_box_code() { + const d = promise.defer(); + + gThreadClient.getSources(function ({ sources, error }) { + do_check_true(!error, "Shouldn't get an error getting sources"); + const source = sources.filter((s) => { + return s.url.indexOf("b.js") !== -1; + })[0]; + do_check_true(!!source, "We should have our source in the sources list"); + + gThreadClient.source(source).blackBox(function ({ error }) { + do_check_true(!error, "Should not get an error black boxing"); + d.resolve(true); + }); + }); + + return d.promise; +} + +function run_code() { + const d = promise.defer(); + + gClient.addOneTimeListener("paused", function (aEvent, aPacket) { + d.resolve(aPacket); + gThreadClient.resume(); + }); + gDebuggee.a(); + + return d.promise; +} + +function test_correct_location(aPacket) { + do_check_eq(aPacket.why.type, "debuggerStatement", + "Should hit a debugger statement."); + do_check_eq(aPacket.frame.where.source.url, "http://example.com/c.js", + "Should have skipped over the debugger statement in the black boxed source"); +} diff --git a/devtools/server/tests/unit/test_blackboxing-07.js b/devtools/server/tests/unit/test_blackboxing-07.js new file mode 100644 index 000000000..da3147021 --- /dev/null +++ b/devtools/server/tests/unit/test_blackboxing-07.js @@ -0,0 +1,62 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Test that sources whose URL ends with ".min.js" automatically get black + * boxed. + */ + +var gDebuggee; +var gClient; +var gThreadClient; + +function run_test() +{ + initTestDebuggerServer(); + gDebuggee = addTestGlobal("test-black-box"); + gClient = new DebuggerClient(DebuggerServer.connectPipe()); + gClient.connect().then(function () { + attachTestTabAndResume(gClient, "test-black-box", function (aResponse, aTabClient, aThreadClient) { + gThreadClient = aThreadClient; + testBlackBox(); + }); + }); + do_test_pending(); +} + +const BLACK_BOXED_URL = "http://example.com/black-boxed.min.js"; +const SOURCE_URL = "http://example.com/source.js"; + +const testBlackBox = Task.async(function* () { + yield executeOnNextTickAndWaitForPause(evalCode, gClient); + + const { sources } = yield getSources(gThreadClient); + equal(sources.length, 2); + + const blackBoxedSource = sources.filter(s => s.url === BLACK_BOXED_URL)[0]; + equal(blackBoxedSource.isBlackBoxed, true); + + const regularSource = sources.filter(s => s.url === SOURCE_URL)[0]; + equal(regularSource.isBlackBoxed, false); + + finishClient(gClient); +}); + +function evalCode() { + Components.utils.evalInSandbox( + "" + function blackBoxed() {}, + gDebuggee, + "1.8", + BLACK_BOXED_URL, + 1 + ); + + Components.utils.evalInSandbox( + "" + function source() {} + + "\ndebugger;", + gDebuggee, + "1.8", + SOURCE_URL, + 1 + ); +} diff --git a/devtools/server/tests/unit/test_breakpoint-01.js b/devtools/server/tests/unit/test_breakpoint-01.js new file mode 100644 index 000000000..9a20257a3 --- /dev/null +++ b/devtools/server/tests/unit/test_breakpoint-01.js @@ -0,0 +1,75 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Check basic breakpoint functionality. + */ +var gDebuggee; +var gClient; +var gThreadClient; +var gCallback; + +function run_test() +{ + run_test_with_server(DebuggerServer, function () { + run_test_with_server(WorkerDebuggerServer, do_test_finished); + }); + do_test_pending(); +} + +function run_test_with_server(aServer, aCallback) +{ + gCallback = aCallback; + initTestDebuggerServer(aServer); + gDebuggee = addTestGlobal("test-stack", aServer); + gClient = new DebuggerClient(aServer.connectPipe()); + gClient.connect().then(function () { + attachTestTabAndResume(gClient, "test-stack", function (aResponse, aTabClient, aThreadClient) { + gThreadClient = aThreadClient; + test_simple_breakpoint(); + }); + }); +} + +function test_simple_breakpoint() +{ + gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) { + let source = gThreadClient.source(aPacket.frame.where.source); + let location = { + line: gDebuggee.line0 + 3 + }; + + source.setBreakpoint(location, function (aResponse, bpClient) { + gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) { + // Check the return value. + do_check_eq(aPacket.type, "paused"); + do_check_eq(aPacket.frame.where.source.actor, source.actor); + do_check_eq(aPacket.frame.where.line, location.line); + do_check_eq(aPacket.why.type, "breakpoint"); + do_check_eq(aPacket.why.actors[0], bpClient.actor); + // Check that the breakpoint worked. + do_check_eq(gDebuggee.a, 1); + do_check_eq(gDebuggee.b, undefined); + + // Remove the breakpoint. + bpClient.remove(function (aResponse) { + gThreadClient.resume(function () { + gClient.close().then(gCallback); + }); + }); + + }); + + // Continue until the breakpoint is hit. + gThreadClient.resume(); + }); + }); + + Cu.evalInSandbox( + "var line0 = Error().lineNumber;\n" + + "debugger;\n" + // line0 + 1 + "var a = 1;\n" + // line0 + 2 + "var b = 2;\n", // line0 + 3 + gDebuggee + ); +} diff --git a/devtools/server/tests/unit/test_breakpoint-02.js b/devtools/server/tests/unit/test_breakpoint-02.js new file mode 100644 index 000000000..d2b220d83 --- /dev/null +++ b/devtools/server/tests/unit/test_breakpoint-02.js @@ -0,0 +1,67 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Check that setting breakpoints when the debuggee is running works. + */ + +var gDebuggee; +var gClient; +var gThreadClient; +var gCallback; + +function run_test() +{ + run_test_with_server(DebuggerServer, function () { + run_test_with_server(WorkerDebuggerServer, do_test_finished); + }); + do_test_pending(); +} + +function run_test_with_server(aServer, aCallback) +{ + gCallback = aCallback; + initTestDebuggerServer(aServer); + gDebuggee = addTestGlobal("test-stack", aServer); + gClient = new DebuggerClient(aServer.connectPipe()); + gClient.connect().then(function () { + attachTestTabAndResume(gClient, "test-stack", function (aResponse, aTabClient, aThreadClient) { + gThreadClient = aThreadClient; + test_breakpoint_running(); + }); + }); +} + +function test_breakpoint_running() +{ + gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) { + let location = { line: gDebuggee.line0 + 3 }; + + gThreadClient.resume(); + + // Setting the breakpoint later should interrupt the debuggee. + gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) { + do_check_eq(aPacket.type, "paused"); + do_check_eq(aPacket.why.type, "interrupted"); + }); + + let source = gThreadClient.source(aPacket.frame.where.source); + source.setBreakpoint(location, function (aResponse) { + // Eval scripts don't stick around long enough for the breakpoint to be set, + // so just make sure we got the expected response from the actor. + do_check_neq(aResponse.error, "noScript"); + + do_execute_soon(function () { + gClient.close().then(gCallback); + }); + }); + }); + + Cu.evalInSandbox( + "var line0 = Error().lineNumber;\n" + + "debugger;\n" + + "var a = 1;\n" + // line0 + 2 + "var b = 2;\n", // line0 + 3 + gDebuggee + ); +} diff --git a/devtools/server/tests/unit/test_breakpoint-03.js b/devtools/server/tests/unit/test_breakpoint-03.js new file mode 100644 index 000000000..b1792866b --- /dev/null +++ b/devtools/server/tests/unit/test_breakpoint-03.js @@ -0,0 +1,96 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Check that setting a breakpoint on a line without code will skip + * forward when we know the script isn't GCed (the debugger is connected, + * so it's kept alive). + */ + +var gDebuggee; +var gClient; +var gThreadClient; +var gCallback; + +function run_test() +{ + run_test_with_server(DebuggerServer, function () { + run_test_with_server(WorkerDebuggerServer, do_test_finished); + }); + do_test_pending(); +} + +function run_test_with_server(aServer, aCallback) +{ + gCallback = aCallback; + initTestDebuggerServer(aServer); + gDebuggee = addTestGlobal("test-stack", aServer); + gClient = new DebuggerClient(aServer.connectPipe()); + gClient.connect().then(function () { + attachTestTabAndResume(gClient, + "test-stack", + function (aResponse, aTabClient, aThreadClient) { + gThreadClient = aThreadClient; + test_skip_breakpoint(); + }); + }); +} + +var test_no_skip_breakpoint = Task.async(function*(source, location) { + let [aResponse, bpClient] = yield source.setBreakpoint( + Object.assign({}, location, { noSliding: true }) + ); + + do_check_true(!aResponse.actualLocation); + do_check_eq(bpClient.location.line, gDebuggee.line0 + 3); + yield bpClient.remove(); +}); + +var test_skip_breakpoint = function() { + gThreadClient.addOneTimeListener("paused", Task.async(function *(aEvent, aPacket) { + let location = { line: gDebuggee.line0 + 3 }; + let source = gThreadClient.source(aPacket.frame.where.source); + + // First, make sure that we can disable sliding with the + // `noSliding` option. + yield test_no_skip_breakpoint(source, location); + + // Now make sure that the breakpoint properly slides forward one line. + const [aResponse, bpClient] = yield source.setBreakpoint(location); + do_check_true(!!aResponse.actualLocation); + do_check_eq(aResponse.actualLocation.source.actor, source.actor); + do_check_eq(aResponse.actualLocation.line, location.line + 1); + + gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) { + // Check the return value. + do_check_eq(aPacket.type, "paused"); + do_check_eq(aPacket.frame.where.source.actor, source.actor); + do_check_eq(aPacket.frame.where.line, location.line + 1); + do_check_eq(aPacket.why.type, "breakpoint"); + do_check_eq(aPacket.why.actors[0], bpClient.actor); + // Check that the breakpoint worked. + do_check_eq(gDebuggee.a, 1); + do_check_eq(gDebuggee.b, undefined); + + // Remove the breakpoint. + bpClient.remove(function (aResponse) { + gThreadClient.resume(function () { + gClient.close().then(gCallback); + }); + }); + }); + + gThreadClient.resume(); + })); + + // Use `evalInSandbox` to make the debugger treat it as normal + // globally-scoped code, where breakpoint sliding rules apply. + Cu.evalInSandbox( + "var line0 = Error().lineNumber;\n" + + "debugger;\n" + // line0 + 1 + "var a = 1;\n" + // line0 + 2 + "// A comment.\n" + // line0 + 3 + "var b = 2;", // line0 + 4 + gDebuggee + ); +} diff --git a/devtools/server/tests/unit/test_breakpoint-04.js b/devtools/server/tests/unit/test_breakpoint-04.js new file mode 100644 index 000000000..9004c092b --- /dev/null +++ b/devtools/server/tests/unit/test_breakpoint-04.js @@ -0,0 +1,80 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Check that setting a breakpoint in a line in a child script works. + */ + +var gDebuggee; +var gClient; +var gThreadClient; +var gCallback; + +function run_test() +{ + run_test_with_server(DebuggerServer, function () { + run_test_with_server(WorkerDebuggerServer, do_test_finished); + }); + do_test_pending(); +} + +function run_test_with_server(aServer, aCallback) +{ + gCallback = aCallback; + initTestDebuggerServer(aServer); + gDebuggee = addTestGlobal("test-stack", aServer); + gClient = new DebuggerClient(aServer.connectPipe()); + gClient.connect().then(function () { + attachTestTabAndResume(gClient, "test-stack", function (aResponse, aTabClient, aThreadClient) { + gThreadClient = aThreadClient; + test_child_breakpoint(); + }); + }); +} + +function test_child_breakpoint() +{ + gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) { + let source = gThreadClient.source(aPacket.frame.where.source); + let location = { line: gDebuggee.line0 + 3 }; + + source.setBreakpoint(location, function (aResponse, bpClient) { + // actualLocation is not returned when breakpoints don't skip forward. + do_check_eq(aResponse.actualLocation, undefined); + + gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) { + // Check the return value. + do_check_eq(aPacket.type, "paused"); + do_check_eq(aPacket.frame.where.source.actor, source.actor); + do_check_eq(aPacket.frame.where.line, location.line); + do_check_eq(aPacket.why.type, "breakpoint"); + do_check_eq(aPacket.why.actors[0], bpClient.actor); + // Check that the breakpoint worked. + do_check_eq(gDebuggee.a, 1); + do_check_eq(gDebuggee.b, undefined); + + // Remove the breakpoint. + bpClient.remove(function (aResponse) { + gThreadClient.resume(function () { + gClient.close().then(gCallback); + }); + }); + }); + + // Continue until the breakpoint is hit. + gThreadClient.resume(); + }); + + }); + + Cu.evalInSandbox( + "var line0 = Error().lineNumber;\n" + + "function foo() {\n" + // line0 + 1 + " this.a = 1;\n" + // line0 + 2 + " this.b = 2;\n" + // line0 + 3 + "}\n" + // line0 + 4 + "debugger;\n" + // line0 + 5 + "foo();\n", // line0 + 6 + gDebuggee + ); +} diff --git a/devtools/server/tests/unit/test_breakpoint-05.js b/devtools/server/tests/unit/test_breakpoint-05.js new file mode 100644 index 000000000..9e04d0271 --- /dev/null +++ b/devtools/server/tests/unit/test_breakpoint-05.js @@ -0,0 +1,82 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Check that setting a breakpoint in a line without code in a child script + * will skip forward. + */ + +var gDebuggee; +var gClient; +var gThreadClient; +var gCallback; + +function run_test() +{ + run_test_with_server(DebuggerServer, function () { + run_test_with_server(WorkerDebuggerServer, do_test_finished); + }); + do_test_pending(); +} + +function run_test_with_server(aServer, aCallback) +{ + gCallback = aCallback; + initTestDebuggerServer(aServer); + gDebuggee = addTestGlobal("test-stack", aServer); + gClient = new DebuggerClient(aServer.connectPipe()); + gClient.connect().then(function () { + attachTestTabAndResume(gClient, "test-stack", function (aResponse, aTabClient, aThreadClient) { + gThreadClient = aThreadClient; + test_child_skip_breakpoint(); + }); + }); +} + +function test_child_skip_breakpoint() +{ + gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) { + let source = gThreadClient.source(aPacket.frame.where.source); + let location = { line: gDebuggee.line0 + 3 }; + + source.setBreakpoint(location, function (aResponse, bpClient) { + // Check that the breakpoint has properly skipped forward one line. + do_check_eq(aResponse.actualLocation.source.actor, source.actor); + do_check_eq(aResponse.actualLocation.line, location.line + 1); + + gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) { + // Check the return value. + do_check_eq(aPacket.type, "paused"); + do_check_eq(aPacket.frame.where.source.actor, source.actor); + do_check_eq(aPacket.frame.where.line, location.line + 1); + do_check_eq(aPacket.why.type, "breakpoint"); + do_check_eq(aPacket.why.actors[0], bpClient.actor); + // Check that the breakpoint worked. + do_check_eq(gDebuggee.a, 1); + do_check_eq(gDebuggee.b, undefined); + + // Remove the breakpoint. + bpClient.remove(function (aResponse) { + gThreadClient.resume(function () { + gClient.close().then(gCallback); + }); + }); + }); + + // Continue until the breakpoint is hit. + gThreadClient.resume(); + }); + }); + + Cu.evalInSandbox( + "var line0 = Error().lineNumber;\n" + + "function foo() {\n" + // line0 + 1 + " this.a = 1;\n" + // line0 + 2 + " // A comment.\n" + // line0 + 3 + " this.b = 2;\n" + // line0 + 4 + "}\n" + // line0 + 5 + "debugger;\n" + // line0 + 6 + "foo();\n", // line0 + 7 + gDebuggee + ); +} diff --git a/devtools/server/tests/unit/test_breakpoint-06.js b/devtools/server/tests/unit/test_breakpoint-06.js new file mode 100644 index 000000000..aa92b1a5f --- /dev/null +++ b/devtools/server/tests/unit/test_breakpoint-06.js @@ -0,0 +1,89 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Check that setting a breakpoint in a line without code in a deeply-nested + * child script will skip forward. + */ + +var gDebuggee; +var gClient; +var gThreadClient; +var gCallback; + +function run_test() +{ + run_test_with_server(DebuggerServer, function () { + run_test_with_server(WorkerDebuggerServer, do_test_finished); + }); + do_test_pending(); +} + +function run_test_with_server(aServer, aCallback) +{ + gCallback = aCallback; + initTestDebuggerServer(aServer); + gDebuggee = addTestGlobal("test-stack", aServer); + gClient = new DebuggerClient(aServer.connectPipe()); + gClient.connect().then(function () { + attachTestTabAndResume(gClient, "test-stack", function (aResponse, aTabClient, aThreadClient) { + gThreadClient = aThreadClient; + test_nested_breakpoint(); + }); + }); +} + +function test_nested_breakpoint() +{ + gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) { + let source = gThreadClient.source(aPacket.frame.where.source); + let location = { line: gDebuggee.line0 + 5 }; + + source.setBreakpoint(location, function (aResponse, bpClient) { + // Check that the breakpoint has properly skipped forward one line. + do_check_eq(aResponse.actualLocation.source.actor, source.actor); + do_check_eq(aResponse.actualLocation.line, location.line + 1); + + gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) { + // Check the return value. + do_check_eq(aPacket.type, "paused"); + do_check_eq(aPacket.frame.where.source.actor, source.actor); + do_check_eq(aPacket.frame.where.line, location.line + 1); + do_check_eq(aPacket.why.type, "breakpoint"); + do_check_eq(aPacket.why.actors[0], bpClient.actor); + // Check that the breakpoint worked. + do_check_eq(gDebuggee.a, 1); + do_check_eq(gDebuggee.b, undefined); + + // Remove the breakpoint. + bpClient.remove(function (aResponse) { + gThreadClient.resume(function () { + gClient.close().then(gCallback); + }); + }); + }); + + // Continue until the breakpoint is hit. + gThreadClient.resume(); + }); + + }); + + Cu.evalInSandbox( + "var line0 = Error().lineNumber;\n" + + "function foo() {\n" + // line0 + 1 + " function bar() {\n" + // line0 + 2 + " function baz() {\n" + // line0 + 3 + " this.a = 1;\n" + // line0 + 4 + " // A comment.\n" + // line0 + 5 + " this.b = 2;\n" + // line0 + 6 + " }\n" + // line0 + 7 + " baz();\n" + // line0 + 8 + " }\n" + // line0 + 9 + " bar();\n" + // line0 + 10 + "}\n" + // line0 + 11 + "debugger;\n" + // line0 + 12 + "foo();\n", // line0 + 13 + gDebuggee + ); +} diff --git a/devtools/server/tests/unit/test_breakpoint-07.js b/devtools/server/tests/unit/test_breakpoint-07.js new file mode 100644 index 000000000..008f1424d --- /dev/null +++ b/devtools/server/tests/unit/test_breakpoint-07.js @@ -0,0 +1,85 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Check that setting a breakpoint in a line without code in the second child + * script will skip forward. + */ + +var gDebuggee; +var gClient; +var gThreadClient; +var gCallback; + +function run_test() +{ + run_test_with_server(DebuggerServer, function () { + run_test_with_server(WorkerDebuggerServer, do_test_finished); + }); + do_test_pending(); +} + +function run_test_with_server(aServer, aCallback) +{ + gCallback = aCallback; + initTestDebuggerServer(aServer); + gDebuggee = addTestGlobal("test-stack", aServer); + gClient = new DebuggerClient(aServer.connectPipe()); + gClient.connect().then(function () { + attachTestTabAndResume(gClient, "test-stack", function (aResponse, aTabClient, aThreadClient) { + gThreadClient = aThreadClient; + test_second_child_skip_breakpoint(); + }); + }); +} + +function test_second_child_skip_breakpoint() +{ + gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) { + let source = gThreadClient.source(aPacket.frame.where.source); + let location = { line: gDebuggee.line0 + 6 }; + + source.setBreakpoint(location, function (aResponse, bpClient) { + // Check that the breakpoint has properly skipped forward one line. + do_check_eq(aResponse.actualLocation.source.actor, source.actor); + do_check_eq(aResponse.actualLocation.line, location.line + 1); + + gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) { + // Check the return value. + do_check_eq(aPacket.type, "paused"); + do_check_eq(aPacket.frame.where.source.actor, source.actor); + do_check_eq(aPacket.frame.where.line, location.line + 1); + do_check_eq(aPacket.why.type, "breakpoint"); + do_check_eq(aPacket.why.actors[0], bpClient.actor); + // Check that the breakpoint worked. + do_check_eq(gDebuggee.a, 1); + do_check_eq(gDebuggee.b, undefined); + + // Remove the breakpoint. + bpClient.remove(function (aResponse) { + gThreadClient.resume(function () { + gClient.close().then(gCallback); + }); + }); + }); + + // Continue until the breakpoint is hit. + gThreadClient.resume(); + }); + }); + + Cu.evalInSandbox( + "var line0 = Error().lineNumber;\n" + + "function foo() {\n" + // line0 + 1 + " bar();\n" + // line0 + 2 + "}\n" + // line0 + 3 + "function bar() {\n" + // line0 + 4 + " this.a = 1;\n" + // line0 + 5 + " // A comment.\n" + // line0 + 6 + " this.b = 2;\n" + // line0 + 7 + "}\n" + // line0 + 8 + "debugger;\n" + // line0 + 9 + "foo();\n", // line0 + 10 + gDebuggee + ); +} diff --git a/devtools/server/tests/unit/test_breakpoint-08.js b/devtools/server/tests/unit/test_breakpoint-08.js new file mode 100644 index 000000000..6215a61ac --- /dev/null +++ b/devtools/server/tests/unit/test_breakpoint-08.js @@ -0,0 +1,96 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Check that setting a breakpoint in a line without code in a child script + * will skip forward, in a file with two scripts. + */ + +var gDebuggee; +var gClient; +var gThreadClient; +var gCallback; + +function run_test() +{ + run_test_with_server(DebuggerServer, function () { + run_test_with_server(WorkerDebuggerServer, do_test_finished); + }); + do_test_pending(); +} + +function run_test_with_server(aServer, aCallback) +{ + gCallback = aCallback; + initTestDebuggerServer(aServer); + gDebuggee = addTestGlobal("test-stack", aServer); + gClient = new DebuggerClient(aServer.connectPipe()); + gClient.connect().then(function () { + attachTestTabAndResume(gClient, "test-stack", function (aResponse, aTabClient, aThreadClient) { + gThreadClient = aThreadClient; + test_child_skip_breakpoint(); + }); + }); +} + +function test_child_skip_breakpoint() +{ + gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) { + gThreadClient.eval(aPacket.frame.actor, "foo", function (aResponse) { + gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) { + let obj = gThreadClient.pauseGrip(aPacket.why.frameFinished.return); + obj.getDefinitionSite(runWithBreakpoint); + }); + }); + + function runWithBreakpoint(aPacket) { + let source = gThreadClient.source(aPacket.source); + let location = { line: gDebuggee.line0 + 3 }; + + source.setBreakpoint(location, function (aResponse, bpClient) { + // Check that the breakpoint has properly skipped forward one line. + do_check_eq(aResponse.actualLocation.source.actor, source.actor); + do_check_eq(aResponse.actualLocation.line, location.line + 1); + + gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) { + // Check the return value. + do_check_eq(aPacket.type, "paused"); + do_check_eq(aPacket.frame.where.source.actor, source.actor); + do_check_eq(aPacket.frame.where.line, location.line + 1); + do_check_eq(aPacket.why.type, "breakpoint"); + do_check_eq(aPacket.why.actors[0], bpClient.actor); + // Check that the breakpoint worked. + do_check_eq(gDebuggee.a, 1); + do_check_eq(gDebuggee.b, undefined); + + // Remove the breakpoint. + bpClient.remove(function (aResponse) { + gThreadClient.resume(function () { + gClient.close().then(gCallback); + }); + }); + }); + + // Continue until the breakpoint is hit. + gThreadClient.resume(); + }); + } + }); + + Cu.evalInSandbox("var line0 = Error().lineNumber;\n" + + "function foo() {\n" + // line0 + 1 + " this.a = 1;\n" + // line0 + 2 + " // A comment.\n" + // line0 + 3 + " this.b = 2;\n" + // line0 + 4 + "}\n", // line0 + 5 + gDebuggee, + "1.7", + "script1.js"); + + Cu.evalInSandbox("var line1 = Error().lineNumber;\n" + + "debugger;\n" + // line1 + 1 + "foo();\n", // line1 + 2 + gDebuggee, + "1.7", + "script2.js"); +} diff --git a/devtools/server/tests/unit/test_breakpoint-09.js b/devtools/server/tests/unit/test_breakpoint-09.js new file mode 100644 index 000000000..8bea375b9 --- /dev/null +++ b/devtools/server/tests/unit/test_breakpoint-09.js @@ -0,0 +1,88 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Check that removing a breakpoint works. + */ + +var gDebuggee; +var gClient; +var gThreadClient; +var gCallback; + +function run_test() +{ + run_test_with_server(DebuggerServer, function () { + run_test_with_server(WorkerDebuggerServer, do_test_finished); + }); + do_test_pending(); +} + +function run_test_with_server(aServer, aCallback) +{ + gCallback = aCallback; + initTestDebuggerServer(aServer); + gDebuggee = addTestGlobal("test-stack", aServer); + gClient = new DebuggerClient(aServer.connectPipe()); + gClient.connect().then(function () { + attachTestTabAndResume(gClient, "test-stack", function (aResponse, aTabClient, aThreadClient) { + gThreadClient = aThreadClient; + test_remove_breakpoint(); + }); + }); +} + +function test_remove_breakpoint() +{ + let done = false; + gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) { + let source = gThreadClient.source(aPacket.frame.where.source); + let location = { line: gDebuggee.line0 + 2 }; + + source.setBreakpoint(location, function (aResponse, bpClient) { + gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) { + // Check the return value. + do_check_eq(aPacket.type, "paused"); + do_check_eq(aPacket.frame.where.source.actor, source.actor); + do_check_eq(aPacket.frame.where.line, location.line); + do_check_eq(aPacket.why.type, "breakpoint"); + do_check_eq(aPacket.why.actors[0], bpClient.actor); + // Check that the breakpoint worked. + do_check_eq(gDebuggee.a, undefined); + + // Remove the breakpoint. + bpClient.remove(function (aResponse) { + done = true; + gThreadClient.addOneTimeListener("paused", + function (aEvent, aPacket) { + // The breakpoint should not be hit again. + gThreadClient.resume(function () { + do_check_true(false); + }); + }); + gThreadClient.resume(); + }); + + }); + // Continue until the breakpoint is hit. + gThreadClient.resume(); + + }); + + }); + + Cu.evalInSandbox("var line0 = Error().lineNumber;\n" + + "function foo(stop) {\n" + // line0 + 1 + " this.a = 1;\n" + // line0 + 2 + " if (stop) return;\n" + // line0 + 3 + " delete this.a;\n" + // line0 + 4 + " foo(true);\n" + // line0 + 5 + "}\n" + // line0 + 6 + "debugger;\n" + // line1 + 7 + "foo();\n", // line1 + 8 + gDebuggee); + if (!done) { + do_check_true(false); + } + gClient.close().then(gCallback); +} diff --git a/devtools/server/tests/unit/test_breakpoint-10.js b/devtools/server/tests/unit/test_breakpoint-10.js new file mode 100644 index 000000000..c69576767 --- /dev/null +++ b/devtools/server/tests/unit/test_breakpoint-10.js @@ -0,0 +1,89 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Check that setting a breakpoint in a line with multiple entry points + * triggers no matter which entry point we reach. + */ + +var gDebuggee; +var gClient; +var gThreadClient; +var gCallback; + +function run_test() +{ + run_test_with_server(DebuggerServer, function () { + run_test_with_server(WorkerDebuggerServer, do_test_finished); + }); + do_test_pending(); +} + +function run_test_with_server(aServer, aCallback) +{ + gCallback = aCallback; + initTestDebuggerServer(aServer); + gDebuggee = addTestGlobal("test-stack", aServer); + gClient = new DebuggerClient(aServer.connectPipe()); + gClient.connect().then(function () { + attachTestTabAndResume(gClient, "test-stack", function (aResponse, aTabClient, aThreadClient) { + gThreadClient = aThreadClient; + test_child_breakpoint(); + }); + }); +} + +function test_child_breakpoint() +{ + gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) { + let source = gThreadClient.source(aPacket.frame.where.source); + let location = { line: gDebuggee.line0 + 3 }; + + source.setBreakpoint(location, function (aResponse, bpClient) { + // actualLocation is not returned when breakpoints don't skip forward. + do_check_eq(aResponse.actualLocation, undefined); + + gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) { + // Check the return value. + do_check_eq(aPacket.type, "paused"); + do_check_eq(aPacket.why.type, "breakpoint"); + do_check_eq(aPacket.why.actors[0], bpClient.actor); + // Check that the breakpoint worked. + do_check_eq(gDebuggee.i, 0); + + gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) { + // Check the return value. + do_check_eq(aPacket.type, "paused"); + do_check_eq(aPacket.why.type, "breakpoint"); + do_check_eq(aPacket.why.actors[0], bpClient.actor); + // Check that the breakpoint worked. + do_check_eq(gDebuggee.i, 1); + + // Remove the breakpoint. + bpClient.remove(function (aResponse) { + gThreadClient.resume(function () { + gClient.close().then(gCallback); + }); + }); + }); + + // Continue until the breakpoint is hit again. + gThreadClient.resume(); + + }); + // Continue until the breakpoint is hit. + gThreadClient.resume(); + + }); + + }); + + + Cu.evalInSandbox("var line0 = Error().lineNumber;\n" + + "debugger;\n" + // line0 + 1 + "var a, i = 0;\n" + // line0 + 2 + "for (i = 1; i <= 2; i++) {\n" + // line0 + 3 + " a = i;\n" + // line0 + 4 + "}\n", // line0 + 5 + gDebuggee); +} diff --git a/devtools/server/tests/unit/test_breakpoint-11.js b/devtools/server/tests/unit/test_breakpoint-11.js new file mode 100644 index 000000000..480b95984 --- /dev/null +++ b/devtools/server/tests/unit/test_breakpoint-11.js @@ -0,0 +1,88 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Make sure that setting a breakpoint in a line with bytecodes in multiple + * scripts, sets the breakpoint in all of them (bug 793214). + */ + +var gDebuggee; +var gClient; +var gThreadClient; +var gCallback; + +function run_test() +{ + run_test_with_server(DebuggerServer, function () { + run_test_with_server(WorkerDebuggerServer, do_test_finished); + }); + do_test_pending(); +} + +function run_test_with_server(aServer, aCallback) +{ + gCallback = aCallback; + initTestDebuggerServer(aServer); + gDebuggee = addTestGlobal("test-stack", aServer); + gClient = new DebuggerClient(aServer.connectPipe()); + gClient.connect().then(function () { + attachTestTabAndResume(gClient, "test-stack", function (aResponse, aTabClient, aThreadClient) { + gThreadClient = aThreadClient; + test_child_breakpoint(); + }); + }); +} + +function test_child_breakpoint() +{ + gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) { + let source = gThreadClient.source(aPacket.frame.where.source); + let location = { line: gDebuggee.line0 + 2 }; + + source.setBreakpoint(location, function (aResponse, bpClient) { + // actualLocation is not returned when breakpoints don't skip forward. + do_check_eq(aResponse.actualLocation, undefined); + + gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) { + // Check the return value. + do_check_eq(aPacket.type, "paused"); + do_check_eq(aPacket.why.type, "breakpoint"); + do_check_eq(aPacket.why.actors[0], bpClient.actor); + // Check that the breakpoint worked. + do_check_eq(gDebuggee.a, undefined); + + gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) { + // Check the return value. + do_check_eq(aPacket.type, "paused"); + do_check_eq(aPacket.why.type, "breakpoint"); + do_check_eq(aPacket.why.actors[0], bpClient.actor); + // Check that the breakpoint worked. + do_check_eq(gDebuggee.a.b, 1); + do_check_eq(gDebuggee.res, undefined); + + // Remove the breakpoint. + bpClient.remove(function (aResponse) { + gThreadClient.resume(function () { + gClient.close().then(gCallback); + }); + }); + }); + + // Continue until the breakpoint is hit again. + gThreadClient.resume(); + + }); + // Continue until the breakpoint is hit. + gThreadClient.resume(); + + }); + + }); + + + Cu.evalInSandbox("var line0 = Error().lineNumber;\n" + + "debugger;\n" + // line0 + 1 + "var a = { b: 1, f: function() { return 2; } };\n" + // line0+2 + "var res = a.f();\n", // line0 + 3 + gDebuggee); +} diff --git a/devtools/server/tests/unit/test_breakpoint-12.js b/devtools/server/tests/unit/test_breakpoint-12.js new file mode 100644 index 000000000..fb147da9f --- /dev/null +++ b/devtools/server/tests/unit/test_breakpoint-12.js @@ -0,0 +1,113 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Make sure that setting a breakpoint twice in a line without bytecodes works + * as expected. + */ + +const NUM_BREAKPOINTS = 10; +var gDebuggee; +var gClient; +var gThreadClient; +var gBpActor; +var gCount; +var gCallback; + +function run_test() +{ + run_test_with_server(DebuggerServer, function () { + run_test_with_server(WorkerDebuggerServer, do_test_finished); + }); + do_test_pending(); +} + +function run_test_with_server(aServer, aCallback) +{ + gCallback = aCallback; + gCount = 1; + initTestDebuggerServer(aServer); + gDebuggee = addTestGlobal("test-stack", aServer); + gClient = new DebuggerClient(aServer.connectPipe()); + gClient.connect().then(function () { + attachTestTabAndResume(gClient, "test-stack", function (aResponse, aTabClient, aThreadClient) { + gThreadClient = aThreadClient; + test_child_skip_breakpoint(); + }); + }); +} + +function test_child_skip_breakpoint() +{ + gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) { + let source = gThreadClient.source(aPacket.frame.where.source); + let location = { line: gDebuggee.line0 + 3}; + + source.setBreakpoint(location, function (aResponse, bpClient) { + // Check that the breakpoint has properly skipped forward one line. + do_check_eq(aResponse.actualLocation.source.actor, source.actor); + do_check_eq(aResponse.actualLocation.line, location.line + 1); + gBpActor = aResponse.actor; + + // Set more breakpoints at the same location. + set_breakpoints(source, location); + }); + + }); + + Cu.evalInSandbox("var line0 = Error().lineNumber;\n" + + "function foo() {\n" + // line0 + 1 + " this.a = 1;\n" + // line0 + 2 + " // A comment.\n" + // line0 + 3 + " this.b = 2;\n" + // line0 + 4 + "}\n" + // line0 + 5 + "debugger;\n" + // line0 + 6 + "foo();\n", // line0 + 7 + gDebuggee); +} + +// Set many breakpoints at the same location. +function set_breakpoints(source, location) { + do_check_neq(gCount, NUM_BREAKPOINTS); + source.setBreakpoint(location, function (aResponse, bpClient) { + // Check that the breakpoint has properly skipped forward one line. + do_check_eq(aResponse.actualLocation.source.actor, source.actor); + do_check_eq(aResponse.actualLocation.line, location.line + 1); + // Check that the same breakpoint actor was returned. + do_check_eq(aResponse.actor, gBpActor); + + if (++gCount < NUM_BREAKPOINTS) { + set_breakpoints(source, location); + return; + } + + // After setting all the breakpoints, check that only one has effectively + // remained. + gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) { + // Check the return value. + do_check_eq(aPacket.type, "paused"); + do_check_eq(aPacket.frame.where.source.actor, source.actor); + do_check_eq(aPacket.frame.where.line, location.line + 1); + do_check_eq(aPacket.why.type, "breakpoint"); + do_check_eq(aPacket.why.actors[0], bpClient.actor); + // Check that the breakpoint worked. + do_check_eq(gDebuggee.a, 1); + do_check_eq(gDebuggee.b, undefined); + + gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) { + // We don't expect any more pauses after the breakpoint was hit once. + do_check_true(false); + }); + gThreadClient.resume(function () { + // Give any remaining breakpoints a chance to trigger. + do_timeout(1000, function () { + gClient.close().then(gCallback); + }); + }); + + }); + // Continue until the breakpoint is hit. + gThreadClient.resume(); + }); + +} diff --git a/devtools/server/tests/unit/test_breakpoint-13.js b/devtools/server/tests/unit/test_breakpoint-13.js new file mode 100644 index 000000000..cdc4c9091 --- /dev/null +++ b/devtools/server/tests/unit/test_breakpoint-13.js @@ -0,0 +1,115 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Check that execution doesn't pause twice while stepping, when encountering + * either a breakpoint or a debugger statement. + */ + +var gDebuggee; +var gClient; +var gThreadClient; +var gCallback; + +function run_test() +{ + run_test_with_server(DebuggerServer, function () { + run_test_with_server(WorkerDebuggerServer, do_test_finished); + }); + do_test_pending(); +} + +function run_test_with_server(aServer, aCallback) +{ + gCallback = aCallback; + initTestDebuggerServer(aServer); + gDebuggee = addTestGlobal("test-stack", aServer); + gClient = new DebuggerClient(aServer.connectPipe()); + gClient.connect().then(function () { + attachTestTabAndResume(gClient, "test-stack", function (aResponse, aTabClient, aThreadClient) { + gThreadClient = aThreadClient; + test_simple_breakpoint(); + }); + }); +} + +function test_simple_breakpoint() +{ + gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) { + let source = gThreadClient.source(aPacket.frame.where.source); + let location = { line: gDebuggee.line0 + 2 }; + + source.setBreakpoint(location, Task.async(function* (aResponse, bpClient) { + const testCallbacks = [ + function (aPacket) { + // Check that the stepping worked. + do_check_eq(aPacket.frame.where.line, gDebuggee.line0 + 5); + do_check_eq(aPacket.why.type, "resumeLimit"); + }, + function (aPacket) { + // Entered the foo function call frame. + do_check_eq(aPacket.frame.where.line, location.line); + do_check_neq(aPacket.why.type, "breakpoint"); + do_check_eq(aPacket.why.type, "resumeLimit"); + }, + function (aPacket) { + // At the end of the foo function call frame. + do_check_eq(aPacket.frame.where.line, gDebuggee.line0 + 3); + do_check_neq(aPacket.why.type, "breakpoint"); + do_check_eq(aPacket.why.type, "resumeLimit"); + }, + function (aPacket) { + // Check that the breakpoint wasn't the reason for this pause, but + // that the frame is about to be popped while stepping. + do_check_eq(aPacket.frame.where.line, gDebuggee.line0 + 3); + do_check_neq(aPacket.why.type, "breakpoint"); + do_check_eq(aPacket.why.type, "resumeLimit"); + do_check_eq(aPacket.why.frameFinished.return.type, "undefined"); + }, + function (aPacket) { + // The foo function call frame was just popped from the stack. + do_check_eq(gDebuggee.a, 1); + do_check_eq(gDebuggee.b, undefined); + do_check_eq(aPacket.frame.where.line, gDebuggee.line0 + 5); + do_check_eq(aPacket.why.type, "resumeLimit"); + do_check_eq(aPacket.poppedFrames.length, 1); + }, + function (aPacket) { + // Check that the debugger statement wasn't the reason for this pause. + do_check_eq(aPacket.frame.where.line, gDebuggee.line0 + 6); + do_check_neq(aPacket.why.type, "debuggerStatement"); + do_check_eq(aPacket.why.type, "resumeLimit"); + }, + function (aPacket) { + // Check that the debugger statement wasn't the reason for this pause. + do_check_eq(aPacket.frame.where.line, gDebuggee.line0 + 7); + do_check_neq(aPacket.why.type, "debuggerStatement"); + do_check_eq(aPacket.why.type, "resumeLimit"); + }, + ]; + + for (let callback of testCallbacks) { + let waiter = waitForPause(gThreadClient); + gThreadClient.stepIn(); + let packet = yield waiter; + callback(packet); + } + + // Remove the breakpoint and finish. + let waiter = waitForPause(gThreadClient); + gThreadClient.stepIn(); + yield waiter; + bpClient.remove(() => gThreadClient.resume(() => gClient.close().then(gCallback))); + })); + }); + + Cu.evalInSandbox("var line0 = Error().lineNumber;\n" + + "function foo() {\n" + // line0 + 1 + " this.a = 1;\n" + // line0 + 2 <-- Breakpoint is set here. + "}\n" + // line0 + 3 + "debugger;\n" + // line0 + 4 + "foo();\n" + // line0 + 5 + "debugger;\n" + // line0 + 6 + "var b = 2;\n", // line0 + 7 + gDebuggee); +} diff --git a/devtools/server/tests/unit/test_breakpoint-14.js b/devtools/server/tests/unit/test_breakpoint-14.js new file mode 100644 index 000000000..aa86975b6 --- /dev/null +++ b/devtools/server/tests/unit/test_breakpoint-14.js @@ -0,0 +1,113 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Check that a breakpoint or a debugger statement cause execution to pause even + * in a stepped-over function. + */ + +var gDebuggee; +var gClient; +var gThreadClient; +var gCallback; + +function run_test() +{ + run_test_with_server(DebuggerServer, function () { + run_test_with_server(WorkerDebuggerServer, do_test_finished); + }); + do_test_pending(); +} + +function run_test_with_server(aServer, aCallback) +{ + gCallback = aCallback; + initTestDebuggerServer(aServer); + gDebuggee = addTestGlobal("test-stack", aServer); + gClient = new DebuggerClient(aServer.connectPipe()); + gClient.connect().then(function () { + attachTestTabAndResume(gClient, "test-stack", function (aResponse, aTabClient, aThreadClient) { + gThreadClient = aThreadClient; + test_simple_breakpoint(); + }); + }); +} + +function test_simple_breakpoint() +{ + gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) { + let source = gThreadClient.source(aPacket.frame.where.source); + let location = { line: gDebuggee.line0 + 2 }; + + source.setBreakpoint(location, Task.async(function* (aResponse, bpClient) { + const testCallbacks = [ + function (aPacket) { + // Check that the stepping worked. + do_check_eq(aPacket.frame.where.line, gDebuggee.line0 + 5); + do_check_eq(aPacket.why.type, "resumeLimit"); + }, + function (aPacket) { + // Reached the breakpoint. + do_check_eq(aPacket.frame.where.line, location.line); + do_check_eq(aPacket.why.type, "breakpoint"); + do_check_neq(aPacket.why.type, "resumeLimit"); + }, + function (aPacket) { + // Stepped to the closing brace of the function. + do_check_eq(aPacket.frame.where.line, gDebuggee.line0 + 3); + do_check_eq(aPacket.why.type, "resumeLimit"); + }, + function (aPacket) { + // The frame is about to be popped while stepping. + do_check_eq(aPacket.frame.where.line, gDebuggee.line0 + 3); + do_check_neq(aPacket.why.type, "breakpoint"); + do_check_eq(aPacket.why.type, "resumeLimit"); + do_check_eq(aPacket.why.frameFinished.return.type, "undefined"); + }, + function (aPacket) { + // The foo function call frame was just popped from the stack. + do_check_eq(gDebuggee.a, 1); + do_check_eq(gDebuggee.b, undefined); + do_check_eq(aPacket.frame.where.line, gDebuggee.line0 + 5); + do_check_eq(aPacket.why.type, "resumeLimit"); + do_check_eq(aPacket.poppedFrames.length, 1); + }, + function (aPacket) { + // Check that the debugger statement wasn't the reason for this pause. + do_check_eq(aPacket.frame.where.line, gDebuggee.line0 + 6); + do_check_neq(aPacket.why.type, "debuggerStatement"); + do_check_eq(aPacket.why.type, "resumeLimit"); + }, + function (aPacket) { + // Check that the debugger statement wasn't the reason for this pause. + do_check_eq(aPacket.frame.where.line, gDebuggee.line0 + 7); + do_check_neq(aPacket.why.type, "debuggerStatement"); + do_check_eq(aPacket.why.type, "resumeLimit"); + }, + ]; + + for (let callback of testCallbacks) { + let waiter = waitForPause(gThreadClient); + gThreadClient.stepOver(); + let packet = yield waiter; + callback(packet); + } + + // Remove the breakpoint and finish. + let waiter = waitForPause(gThreadClient); + gThreadClient.stepOver(); + yield waiter; + bpClient.remove(() => gThreadClient.resume(() => gClient.close().then(gCallback))); + })); + }); + + Cu.evalInSandbox("var line0 = Error().lineNumber;\n" + + "function foo() {\n" + // line0 + 1 + " this.a = 1;\n" + // line0 + 2 <-- Breakpoint is set here. + "}\n" + // line0 + 3 + "debugger;\n" + // line0 + 4 + "foo();\n" + // line0 + 5 + "debugger;\n" + // line0 + 6 + "var b = 2;\n", // line0 + 7 + gDebuggee); +} diff --git a/devtools/server/tests/unit/test_breakpoint-15.js b/devtools/server/tests/unit/test_breakpoint-15.js new file mode 100644 index 000000000..6a3ab7c6f --- /dev/null +++ b/devtools/server/tests/unit/test_breakpoint-15.js @@ -0,0 +1,69 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Check that adding a breakpoint in the same place returns the same actor. + */ + +var gDebuggee; +var gClient; +var gThreadClient; + +function run_test() +{ + initTestDebuggerServer(); + gDebuggee = addTestGlobal("test-stack"); + gClient = new DebuggerClient(DebuggerServer.connectPipe()); + gClient.connect().then(function () { + attachTestTabAndResume(gClient, "test-stack", function (aResponse, aTabClient, aThreadClient) { + gThreadClient = aThreadClient; + testSameBreakpoint(); + }); + }); + do_test_pending(); +} + +const SOURCE_URL = "http://example.com/source.js"; + +const testSameBreakpoint = Task.async(function* () { + let packet = yield executeOnNextTickAndWaitForPause(evalCode, gClient); + let source = gThreadClient.source(packet.frame.where.source); + + // Whole line + let wholeLineLocation = { + line: 2 + }; + + let [firstResponse, firstBpClient] = yield setBreakpoint(source, wholeLineLocation); + let [secondResponse, secondBpClient] = yield setBreakpoint(source, wholeLineLocation); + + do_check_eq(firstBpClient.actor, secondBpClient.actor, "Should get the same actor w/ whole line breakpoints"); + + // Specific column + + let columnLocation = { + line: 2, + column: 6 + }; + + [firstResponse, firstBpClient] = yield setBreakpoint(source, columnLocation); + [secondResponse, secondBpClient] = yield setBreakpoint(source, columnLocation); + + do_check_eq(secondBpClient.actor, secondBpClient.actor, "Should get the same actor column breakpoints"); + + finishClient(gClient); +}); + +function evalCode() { + Components.utils.evalInSandbox( + "" + function doStuff(k) { // line 1 + let arg = 15; // line 2 - Step in here + k(arg); // line 3 + } + "\n" // line 4 + + "debugger;", // line 5 + gDebuggee, + "1.8", + SOURCE_URL, + 1 + ); +} diff --git a/devtools/server/tests/unit/test_breakpoint-16.js b/devtools/server/tests/unit/test_breakpoint-16.js new file mode 100644 index 000000000..43a9086ec --- /dev/null +++ b/devtools/server/tests/unit/test_breakpoint-16.js @@ -0,0 +1,83 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Check that we can set breakpoints in columns, not just lines. + */ + +var gDebuggee; +var gClient; +var gThreadClient; +var gCallback; + +function run_test() +{ + run_test_with_server(DebuggerServer, function () { + run_test_with_server(WorkerDebuggerServer, do_test_finished); + }); + do_test_pending(); +} + +function run_test_with_server(aServer, aCallback) +{ + gCallback = aCallback; + initTestDebuggerServer(aServer); + gDebuggee = addTestGlobal("test-breakpoints", aServer); + gClient = new DebuggerClient(aServer.connectPipe()); + gClient.connect().then(function () { + attachTestTabAndResume(gClient, + "test-breakpoints", + function (aResponse, aTabClient, aThreadClient) { + gThreadClient = aThreadClient; + test_column_breakpoint(); + }); + }); +} + +function test_column_breakpoint() +{ + // Debugger statement + gClient.addOneTimeListener("paused", function (aEvent, aPacket) { + let source = gThreadClient.source(aPacket.frame.where.source); + let location = { + line: gDebuggee.line0 + 1, + column: 55 + }; + let timesBreakpointHit = 0; + + source.setBreakpoint(location, function (aResponse, bpClient) { + gThreadClient.addListener("paused", function onPaused(aEvent, aPacket) { + do_check_eq(aPacket.type, "paused"); + do_check_eq(aPacket.why.type, "breakpoint"); + do_check_eq(aPacket.why.actors[0], bpClient.actor); + do_check_eq(aPacket.frame.where.source.actor, source.actor); + do_check_eq(aPacket.frame.where.line, location.line); + do_check_eq(aPacket.frame.where.column, location.column); + + do_check_eq(gDebuggee.acc, timesBreakpointHit); + do_check_eq(aPacket.frame.environment.bindings.variables.i.value, + timesBreakpointHit); + + if (++timesBreakpointHit === 3) { + gThreadClient.removeListener("paused", onPaused); + bpClient.remove(function (aResponse) { + gThreadClient.resume(() => gClient.close().then(gCallback)); + }); + } else { + gThreadClient.resume(); + } + }); + + // Continue until the breakpoint is hit. + gThreadClient.resume(); + }); + + }); + + + Components.utils.evalInSandbox( + "var line0 = Error().lineNumber;\n" + + "(function () { debugger; this.acc = 0; for (var i = 0; i < 3; i++) this.acc++; }());", + gDebuggee + ); +} diff --git a/devtools/server/tests/unit/test_breakpoint-17.js b/devtools/server/tests/unit/test_breakpoint-17.js new file mode 100644 index 000000000..944627e61 --- /dev/null +++ b/devtools/server/tests/unit/test_breakpoint-17.js @@ -0,0 +1,120 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Test that when we add 2 breakpoints to the same line at different columns and + * then remove one of them, we don't remove them both. + */ + +var gDebuggee; +var gClient; +var gThreadClient; +var gCallback; + +function run_test() +{ + run_test_with_server(DebuggerServer, do_test_finished); + do_test_pending(); +} + +function run_test_with_server(aServer, aCallback) +{ + gCallback = aCallback; + initTestDebuggerServer(aServer); + gDebuggee = addTestGlobal("test-breakpoints", aServer); + gClient = new DebuggerClient(aServer.connectPipe()); + gClient.connect().then(function () { + attachTestTabAndResume(gClient, "test-breakpoints", function (aResponse, aTabClient, aThreadClient) { + gThreadClient = aThreadClient; + test_breakpoints_columns(); + }); + }); +} + +const code = +"(" + function (global) { + global.foo = function () { + Math.abs(-1); Math.log(0.5); + debugger; + }; + debugger; +} + "(this))"; + +const firstLocation = { + line: 3, + column: 4 +}; + +const secondLocation = { + line: 3, + column: 18 +}; + +function test_breakpoints_columns() { + gClient.addOneTimeListener("paused", set_breakpoints); + + Components.utils.evalInSandbox(code, gDebuggee, "1.8", "http://example.com/", 1); +} + +function set_breakpoints(aEvent, aPacket) { + let first, second; + let source = gThreadClient.source(aPacket.frame.where.source); + + source.setBreakpoint(firstLocation, function ({ error, actualLocation }, + aBreakpointClient) { + do_check_true(!error, "Should not get an error setting the breakpoint"); + do_check_true(!actualLocation, "Should not get an actualLocation"); + first = aBreakpointClient; + + source.setBreakpoint(secondLocation, function ({ error, actualLocation }, + aBreakpointClient) { + do_check_true(!error, "Should not get an error setting the breakpoint"); + do_check_true(!actualLocation, "Should not get an actualLocation"); + second = aBreakpointClient; + + test_different_actors(first, second); + }); + }); +} + +function test_different_actors(aFirst, aSecond) { + do_check_neq(aFirst.actor, aSecond.actor, + "Each breakpoint should have a different actor"); + test_remove_one(aFirst, aSecond); +} + +function test_remove_one(aFirst, aSecond) { + aFirst.remove(function ({error}) { + do_check_true(!error, "Should not get an error removing a breakpoint"); + + let hitSecond; + gClient.addListener("paused", function _onPaused(aEvent, {why, frame}) { + if (why.type == "breakpoint") { + hitSecond = true; + do_check_eq(why.actors.length, 1, + "Should only be paused because of one breakpoint actor"); + do_check_eq(why.actors[0], aSecond.actor, + "Should be paused because of the correct breakpoint actor"); + do_check_eq(frame.where.line, secondLocation.line, + "Should be at the right line"); + do_check_eq(frame.where.column, secondLocation.column, + "Should be at the right column"); + gThreadClient.resume(); + return; + } + + if (why.type == "debuggerStatement") { + gClient.removeListener("paused", _onPaused); + do_check_true(hitSecond, + "We should still hit `second`, but not `first`."); + + gClient.close().then(gCallback); + return; + } + + do_check_true(false, "Should never get here"); + }); + + gThreadClient.resume(() => gDebuggee.foo()); + }); +} diff --git a/devtools/server/tests/unit/test_breakpoint-18.js b/devtools/server/tests/unit/test_breakpoint-18.js new file mode 100644 index 000000000..d153d3eff --- /dev/null +++ b/devtools/server/tests/unit/test_breakpoint-18.js @@ -0,0 +1,82 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Check that we only break on offsets that are entry points for the line we are + * breaking on. Bug 907278. + */ + +var gDebuggee; +var gClient; +var gThreadClient; +var gCallback; + +function run_test() +{ + run_test_with_server(DebuggerServer, function () { + run_test_with_server(WorkerDebuggerServer, do_test_finished); + }); + do_test_pending(); +} + +function run_test_with_server(aServer, aCallback) +{ + gCallback = aCallback; + initTestDebuggerServer(aServer); + gDebuggee = addTestGlobal("test-breakpoints", aServer); + gDebuggee.console = { log: x => void x }; + gClient = new DebuggerClient(aServer.connectPipe()); + gClient.connect().then(function () { + attachTestTabAndResume(gClient, + "test-breakpoints", + function (aResponse, aTabClient, aThreadClient) { + gThreadClient = aThreadClient; + setUpCode(); + }); + }); +} + +function setUpCode() { + gClient.addOneTimeListener("paused", setBreakpoint); + Cu.evalInSandbox( + "debugger;\n" + + function test() { + console.log("foo bar"); + debugger; + }, + gDebuggee, + "1.8", + "http://example.com/", + 1 + ); +} + +function setBreakpoint(aEvent, aPacket) { + let source = gThreadClient.source(aPacket.frame.where.source); + gClient.addOneTimeListener("resumed", runCode); + + source.setBreakpoint({ line: 2 }, ({ error }) => { + do_check_true(!error); + gThreadClient.resume(); + }); +} + +function runCode() { + gClient.addOneTimeListener("paused", testBPHit); + gDebuggee.test(); +} + +function testBPHit(event, { why }) { + do_check_eq(why.type, "breakpoint"); + gClient.addOneTimeListener("paused", testDbgStatement); + gThreadClient.resume(); +} + +function testDbgStatement(event, { why }) { + // Should continue to the debugger statement. + do_check_eq(why.type, "debuggerStatement"); + // Not break on another offset from the same line (that isn't an entry point + // to the line) + do_check_neq(why.type, "breakpoint"); + gClient.close().then(gCallback); +} diff --git a/devtools/server/tests/unit/test_breakpoint-19.js b/devtools/server/tests/unit/test_breakpoint-19.js new file mode 100644 index 000000000..da04a5268 --- /dev/null +++ b/devtools/server/tests/unit/test_breakpoint-19.js @@ -0,0 +1,70 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Make sure that setting a breakpoint in a not-yet-existing script doesn't throw + * an error (see bug 897567). Also make sure that this breakpoint works. + */ + +var gDebuggee; +var gClient; +var gThreadClient; +var gCallback; + +function run_test() +{ + run_test_with_server(DebuggerServer, function () { + run_test_with_server(WorkerDebuggerServer, do_test_finished); + }); + do_test_pending(); +} + +function run_test_with_server(aServer, aCallback) +{ + gCallback = aCallback; + initTestDebuggerServer(aServer); + gDebuggee = addTestGlobal("test-breakpoints", aServer); + gDebuggee.console = { log: x => void x }; + gClient = new DebuggerClient(aServer.connectPipe()); + gClient.connect().then(function () { + attachTestTabAndResume(gClient, + "test-breakpoints", + function (aResponse, aTabClient, aThreadClient) { + gThreadClient = aThreadClient; + testBreakpoint(); + }); + }); +} + +const URL = "test.js"; + +function setUpCode() { + Cu.evalInSandbox( + "" + function test() { // 1 + var a = 1; // 2 + debugger; // 3 + } + // 4 + "\ndebugger;", // 5 + gDebuggee, + "1.8", + URL + ); +} + +const testBreakpoint = Task.async(function* () { + let source = yield getSource(gThreadClient, URL); + let [response, bpClient] = yield setBreakpoint(source, {line: 2}); + ok(!response.error); + + let actor = response.actor; + ok(actor); + + yield executeOnNextTickAndWaitForPause(setUpCode, gClient); + yield resume(gThreadClient); + + let packet = yield executeOnNextTickAndWaitForPause(gDebuggee.test, gClient); + equal(packet.why.type, "breakpoint"); + notEqual(packet.why.actors.indexOf(actor), -1); + + finishClient(gClient); +}); diff --git a/devtools/server/tests/unit/test_breakpoint-20.js b/devtools/server/tests/unit/test_breakpoint-20.js new file mode 100644 index 000000000..b70282dae --- /dev/null +++ b/devtools/server/tests/unit/test_breakpoint-20.js @@ -0,0 +1,108 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Verify that when two of the "same" source are loaded concurrently (like e10s + * frame scripts), breakpoints get hit in scripts defined by all sources. + */ + +var gDebuggee; +var gClient; +var gTraceClient; +var gThreadClient; + +function run_test() +{ + initTestDebuggerServer(); + gDebuggee = addTestGlobal("test-breakpoints"); + gClient = new DebuggerClient(DebuggerServer.connectPipe()); + gClient.connect().then(function () { + attachTestThread(gClient, "test-breakpoints", testBreakpoint); + }); + do_test_pending(); +} + +const testBreakpoint = Task.async(function* (threadResponse, tabClient, threadClient, tabResponse) { + evalSetupCode(); + + // Load the test source once. + + evalTestCode(); + equal(gDebuggee.functions.length, 1, + "The test code should have added a function."); + + // Set a breakpoint in the test source. + + const source = yield getSource(threadClient, "test.js"); + const [response, bpClient] = yield setBreakpoint(source, { + line: 3 + }); + ok(!response.error, "Shouldn't get an error setting the BP."); + ok(!response.actualLocation, + "Shouldn't get an actualLocation, the location we provided was good."); + const bpActor = response.actor; + + yield resume(threadClient); + + // Load the test source again. + + evalTestCode(); + equal(gDebuggee.functions.length, 2, + "The test code should have added another function."); + + // Should hit our breakpoint in a script defined by the first instance of the + // test source. + + const bpPause1 = yield executeOnNextTickAndWaitForPause(gDebuggee.functions[0], + gClient); + equal(bpPause1.why.type, "breakpoint", + "Should pause because of hitting our breakpoint (not debugger statement)."); + equal(bpPause1.why.actors[0], bpActor, + "And the breakpoint actor should be correct."); + const dbgStmtPause1 = yield executeOnNextTickAndWaitForPause(() => resume(threadClient), + gClient); + equal(dbgStmtPause1.why.type, "debuggerStatement", + "And we should hit the debugger statement after the pause."); + yield resume(threadClient); + + // Should also hit our breakpoint in a script defined by the second instance + // of the test source. + + const bpPause2 = yield executeOnNextTickAndWaitForPause(gDebuggee.functions[1], + gClient); + equal(bpPause2.why.type, "breakpoint", + "Should pause because of hitting our breakpoint (not debugger statement)."); + equal(bpPause2.why.actors[0], bpActor, + "And the breakpoint actor should be correct."); + const dbgStmtPause2 = yield executeOnNextTickAndWaitForPause(() => resume(threadClient), + gClient); + equal(dbgStmtPause2.why.type, "debuggerStatement", + "And we should hit the debugger statement after the pause."); + + finishClient(gClient); +}); + +function evalSetupCode() { + Cu.evalInSandbox( + "this.functions = [];", + gDebuggee, + "1.8", + "setup.js", + 1 + ); +} + +function evalTestCode() { + Cu.evalInSandbox( + ` // 1 + this.functions.push(function () { // 2 + var setBreakpointHere = 1; // 3 + debugger; // 4 + }); // 5 + `, + gDebuggee, + "1.8", + "test.js", + 1 + ); +} diff --git a/devtools/server/tests/unit/test_breakpoint-21.js b/devtools/server/tests/unit/test_breakpoint-21.js new file mode 100644 index 000000000..e5f2e9e4a --- /dev/null +++ b/devtools/server/tests/unit/test_breakpoint-21.js @@ -0,0 +1,85 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Bug 1122064 - make sure that scripts introduced via onNewScripts + * properly populate the `ScriptStore` with all there nested child + * scripts, so you can set breakpoints on deeply nested scripts + */ + +var gDebuggee; +var gClient; +var gThreadClient; +var gCallback; + +function run_test() +{ + run_test_with_server(DebuggerServer, function () { + run_test_with_server(WorkerDebuggerServer, do_test_finished); + }); + do_test_pending(); +} + +function run_test_with_server(aServer, aCallback) +{ + gCallback = aCallback; + initTestDebuggerServer(aServer); + gDebuggee = addTestGlobal("test-breakpoints", aServer); + gClient = new DebuggerClient(aServer.connectPipe()); + gClient.connect().then(function () { + attachTestTabAndResume(gClient, + "test-breakpoints", + function (aResponse, aTabClient, aThreadClient) { + gThreadClient = aThreadClient; + test(); + }); + }); +} + +const test = Task.async(function* () { + // Populate the `ScriptStore` so that we only test that the script + // is added through `onNewScript` + yield getSources(gThreadClient); + + let packet = yield executeOnNextTickAndWaitForPause(evalCode, gClient); + let source = gThreadClient.source(packet.frame.where.source); + let location = { + line: gDebuggee.line0 + 8 + }; + + let [res, bpClient] = yield setBreakpoint(source, location); + ok(!res.error); + + yield resume(gThreadClient); + packet = yield waitForPause(gClient); + do_check_eq(packet.type, "paused"); + do_check_eq(packet.why.type, "breakpoint"); + do_check_eq(packet.why.actors[0], bpClient.actor); + do_check_eq(packet.frame.where.source.actor, source.actor); + do_check_eq(packet.frame.where.line, location.line); + + yield resume(gThreadClient); + finishClient(gClient); +}); + +function evalCode() { + // Start a new script + Components.utils.evalInSandbox( + "var line0 = Error().lineNumber;\n(" + function () { + debugger; + var a = (function () { + return (function () { + return (function () { + return (function () { + return (function () { + var x = 10; // This line gets a breakpoint + return 1; + })(); + })(); + })(); + })(); + })(); + } + ")()", + gDebuggee + ); +} diff --git a/devtools/server/tests/unit/test_breakpoint-actor-map.js b/devtools/server/tests/unit/test_breakpoint-actor-map.js new file mode 100644 index 000000000..d1d149648 --- /dev/null +++ b/devtools/server/tests/unit/test_breakpoint-actor-map.js @@ -0,0 +1,180 @@ +/* -*- js-indent-level: 2; indent-tabs-mode: nil -*- */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Test the functionality of the BreakpointActorMap object. + +const { BreakpointActorMap } = require("devtools/server/actors/script"); + +function run_test() { + test_get_actor(); + test_set_actor(); + test_delete_actor(); + test_find_actors(); + test_duplicate_actors(); +} + +function test_get_actor() { + let bpStore = new BreakpointActorMap(); + let location = { + originalSourceActor: { actor: "actor1" }, + originalLine: 3 + }; + let columnLocation = { + originalSourceActor: { actor: "actor2" }, + originalLine: 5, + originalColumn: 15 + }; + + // Shouldn't have breakpoint + do_check_eq(null, bpStore.getActor(location), + "Breakpoint not added and shouldn't exist."); + + bpStore.setActor(location, {}); + do_check_true(!!bpStore.getActor(location), + "Breakpoint added but not found in Breakpoint Store."); + + bpStore.deleteActor(location); + do_check_eq(null, bpStore.getActor(location), + "Breakpoint removed but still exists."); + + // Same checks for breakpoint with a column + do_check_eq(null, bpStore.getActor(columnLocation), + "Breakpoint with column not added and shouldn't exist."); + + bpStore.setActor(columnLocation, {}); + do_check_true(!!bpStore.getActor(columnLocation), + "Breakpoint with column added but not found in Breakpoint Store."); + + bpStore.deleteActor(columnLocation); + do_check_eq(null, bpStore.getActor(columnLocation), + "Breakpoint with column removed but still exists in Breakpoint Store."); +} + +function test_set_actor() { + // Breakpoint with column + let bpStore = new BreakpointActorMap(); + let location = { + originalSourceActor: { actor: "actor1" }, + originalLine: 10, + originalColumn: 9 + }; + bpStore.setActor(location, {}); + do_check_true(!!bpStore.getActor(location), + "We should have the column breakpoint we just added"); + + // Breakpoint without column (whole line breakpoint) + location = { + originalSourceActor: { actor: "actor2" }, + originalLine: 103 + }; + bpStore.setActor(location, {}); + do_check_true(!!bpStore.getActor(location), + "We should have the whole line breakpoint we just added"); +} + +function test_delete_actor() { + // Breakpoint with column + let bpStore = new BreakpointActorMap(); + let location = { + originalSourceActor: { actor: "actor1" }, + originalLine: 10, + originalColumn: 9 + }; + bpStore.setActor(location, {}); + bpStore.deleteActor(location); + do_check_eq(bpStore.getActor(location), null, + "We should not have the column breakpoint anymore"); + + // Breakpoint without column (whole line breakpoint) + location = { + originalSourceActor: { actor: "actor2" }, + originalLine: 103 + }; + bpStore.setActor(location, {}); + bpStore.deleteActor(location); + do_check_eq(bpStore.getActor(location), null, + "We should not have the whole line breakpoint anymore"); +} + +function test_find_actors() { + let bps = [ + { originalSourceActor: { actor: "actor1" }, originalLine: 10 }, + { originalSourceActor: { actor: "actor1" }, originalLine: 10, originalColumn: 3 }, + { originalSourceActor: { actor: "actor1" }, originalLine: 10, originalColumn: 10 }, + { originalSourceActor: { actor: "actor1" }, originalLine: 23, originalColumn: 89 }, + { originalSourceActor: { actor: "actor2" }, originalLine: 10, originalColumn: 1 }, + { originalSourceActor: { actor: "actor2" }, originalLine: 20, originalColumn: 5 }, + { originalSourceActor: { actor: "actor2" }, originalLine: 30, originalColumn: 34 }, + { originalSourceActor: { actor: "actor2" }, originalLine: 40, originalColumn: 56 } + ]; + + let bpStore = new BreakpointActorMap(); + + for (let bp of bps) { + bpStore.setActor(bp, bp); + } + + // All breakpoints + + let bpSet = new Set(bps); + for (let bp of bpStore.findActors()) { + bpSet.delete(bp); + } + do_check_eq(bpSet.size, 0, + "Should be able to iterate over all breakpoints"); + + // Breakpoints by URL + + bpSet = new Set(bps.filter(bp => { return bp.originalSourceActor.actorID === "actor1"; })); + for (let bp of bpStore.findActors({ originalSourceActor: { actorID: "actor1" } })) { + bpSet.delete(bp); + } + do_check_eq(bpSet.size, 0, + "Should be able to filter the iteration by url"); + + // Breakpoints by URL and line + + bpSet = new Set(bps.filter(bp => { return bp.originalSourceActor.actorID === "actor1" && bp.originalLine === 10; })); + let first = true; + for (let bp of bpStore.findActors({ originalSourceActor: { actorID: "actor1" }, originalLine: 10 })) { + if (first) { + do_check_eq(bp.originalColumn, undefined, + "Should always get the whole line breakpoint first"); + first = false; + } else { + do_check_neq(bp.originalColumn, undefined, + "Should not get the whole line breakpoint any time other than first."); + } + bpSet.delete(bp); + } + do_check_eq(bpSet.size, 0, + "Should be able to filter the iteration by url and line"); +} + +function test_duplicate_actors() { + let bpStore = new BreakpointActorMap(); + + // Breakpoint with column + let location = { + originalSourceActor: { actorID: "foo-actor" }, + originalLine: 10, + originalColumn: 9 + }; + bpStore.setActor(location, {}); + bpStore.setActor(location, {}); + do_check_eq(bpStore.size, 1, "We should have only 1 column breakpoint"); + bpStore.deleteActor(location); + + // Breakpoint without column (whole line breakpoint) + location = { + originalSourceActor: { actorID: "foo-actor" }, + originalLine: 15 + }; + bpStore.setActor(location, {}); + bpStore.setActor(location, {}); + do_check_eq(bpStore.size, 1, "We should have only 1 whole line breakpoint"); + bpStore.deleteActor(location); +} diff --git a/devtools/server/tests/unit/test_client_close.js b/devtools/server/tests/unit/test_client_close.js new file mode 100644 index 000000000..84747e85b --- /dev/null +++ b/devtools/server/tests/unit/test_client_close.js @@ -0,0 +1,39 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +var gClient; +var gDebuggee; + +function run_test() +{ + initTestDebuggerServer(); + gDebuggee = testGlobal("test-1"); + DebuggerServer.addTestGlobal(gDebuggee); + + let transport = DebuggerServer.connectPipe(); + gClient = new DebuggerClient(transport); + gClient.connect().then(function (aType, aTraits) { + attachTestTab(gClient, "test-1", function (aReply, aTabClient) { + test_close(transport); + }); + }); + do_test_pending(); +} + +function test_close(aTransport) +{ + // Check that, if we fake a transport shutdown + // (like if a device is unplugged) + // the client is automatically closed, + // and we can still call client.close. + let onClosed = function () { + gClient.removeListener("closed", onClosed); + ok(true, "Client emitted 'closed' event"); + gClient.close().then(function () { + ok(true, "client.close() successfully called its callback"); + do_test_finished(); + }); + }; + gClient.addListener("closed", onClosed); + aTransport.close(); +} diff --git a/devtools/server/tests/unit/test_client_request.js b/devtools/server/tests/unit/test_client_request.js new file mode 100644 index 000000000..c0c2c3a92 --- /dev/null +++ b/devtools/server/tests/unit/test_client_request.js @@ -0,0 +1,214 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +// Test the DebuggerClient.request API. + +var gClient, gActorId; + +function TestActor(conn) { + this.conn = conn; +} +TestActor.prototype = { + actorPrefix: "test", + + hello: function () { + return {hello: "world"}; + }, + + error: function () { + return {error: "code", message: "human message"}; + } +}; +TestActor.prototype.requestTypes = { + "hello": TestActor.prototype.hello, + "error": TestActor.prototype.error +}; + +function run_test() +{ + DebuggerServer.addGlobalActor(TestActor); + + DebuggerServer.init(); + DebuggerServer.addBrowserActors(); + + add_test(init); + add_test(test_client_request_callback); + add_test(test_client_request_promise); + add_test(test_client_request_promise_error); + add_test(test_client_request_event_emitter); + add_test(test_close_client_while_sending_requests); + add_test(test_client_request_after_close); + add_test(test_client_request_after_close_callback); + run_next_test(); +} + +function init() +{ + gClient = new DebuggerClient(DebuggerServer.connectPipe()); + gClient.connect() + .then(() => gClient.listTabs()) + .then(aResponse => { + gActorId = aResponse.test; + run_next_test(); + }); +} + +function checkStack(expectedName) { + if (!Services.prefs.getBoolPref("javascript.options.asyncstack")) { + do_print("Async stacks are disabled."); + return; + } + + let stack = Components.stack; + while (stack) { + do_print(stack.name); + if (stack.name == expectedName) { + // Reached back to outer function before request + ok(true, "Complete stack"); + return; + } + stack = stack.asyncCaller || stack.caller; + } + ok(false, "Incomplete stack"); +} + +function test_client_request_callback() +{ + // Test that DebuggerClient.request accepts a `onResponse` callback as 2nd argument + gClient.request({ + to: gActorId, + type: "hello" + }, response => { + do_check_eq(response.from, gActorId); + do_check_eq(response.hello, "world"); + checkStack("test_client_request_callback"); + run_next_test(); + }); +} + +function test_client_request_promise() +{ + // Test that DebuggerClient.request returns a promise that resolves on response + let request = gClient.request({ + to: gActorId, + type: "hello" + }); + + request.then(response => { + do_check_eq(response.from, gActorId); + do_check_eq(response.hello, "world"); + checkStack("test_client_request_promise"); + run_next_test(); + }); +} + +function test_client_request_promise_error() +{ + // Test that DebuggerClient.request returns a promise that reject when server + // returns an explicit error message + let request = gClient.request({ + to: gActorId, + type: "error" + }); + + request.then(() => { + do_throw("Promise shouldn't be resolved on error"); + }, response => { + do_check_eq(response.from, gActorId); + do_check_eq(response.error, "code"); + do_check_eq(response.message, "human message"); + checkStack("test_client_request_promise_error"); + run_next_test(); + }); +} + +function test_client_request_event_emitter() +{ + // Test that DebuggerClient.request returns also an EventEmitter object + let request = gClient.request({ + to: gActorId, + type: "hello" + }); + request.on("json-reply", reply => { + do_check_eq(reply.from, gActorId); + do_check_eq(reply.hello, "world"); + checkStack("test_client_request_event_emitter"); + run_next_test(); + }); +} + +function test_close_client_while_sending_requests() { + // First send a first request that will be "active" + // while the connection is closed. + // i.e. will be sent but no response received yet. + let activeRequest = gClient.request({ + to: gActorId, + type: "hello" + }); + + // Pile up a second one that will be "pending". + // i.e. won't event be sent. + let pendingRequest = gClient.request({ + to: gActorId, + type: "hello" + }); + + let expectReply = promise.defer(); + gClient.expectReply("root", function (response) { + do_check_eq(response.error, "connectionClosed"); + do_check_eq(response.message, "server side packet can't be received as the connection just closed."); + expectReply.resolve(); + }); + + gClient.close().then(() => { + activeRequest.then(() => { + ok(false, "First request unexpectedly succeed while closing the connection"); + }, response => { + do_check_eq(response.error, "connectionClosed"); + do_check_eq(response.message, "'hello' active request packet to '" + gActorId + "' can't be sent as the connection just closed."); + }) + .then(() => pendingRequest) + .then(() => { + ok(false, "Second request unexpectedly succeed while closing the connection"); + }, response => { + do_check_eq(response.error, "connectionClosed"); + do_check_eq(response.message, "'hello' pending request packet to '" + gActorId + "' can't be sent as the connection just closed."); + }) + .then(() => expectReply.promise) + .then(run_next_test); + }); +} + +function test_client_request_after_close() +{ + // Test that DebuggerClient.request fails after we called client.close() + // (with promise API) + let request = gClient.request({ + to: gActorId, + type: "hello" + }); + + request.then(response => { + ok(false, "Request succeed even after client.close"); + }, response => { + ok(true, "Request failed after client.close"); + do_check_eq(response.error, "connectionClosed"); + ok(response.message.match(/'hello' request packet to '.*' can't be sent as the connection is closed./)); + run_next_test(); + }); +} + +function test_client_request_after_close_callback() +{ + // Test that DebuggerClient.request fails after we called client.close() + // (with callback API) + let request = gClient.request({ + to: gActorId, + type: "hello" + }, response => { + ok(true, "Request failed after client.close"); + do_check_eq(response.error, "connectionClosed"); + ok(response.message.match(/'hello' request packet to '.*' can't be sent as the connection is closed./)); + run_next_test(); + }); +} diff --git a/devtools/server/tests/unit/test_conditional_breakpoint-01.js b/devtools/server/tests/unit/test_conditional_breakpoint-01.js new file mode 100644 index 000000000..4661bb0c4 --- /dev/null +++ b/devtools/server/tests/unit/test_conditional_breakpoint-01.js @@ -0,0 +1,61 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Check conditional breakpoint when condition evaluates to true. + */ + +var gDebuggee; +var gClient; +var gThreadClient; + +function run_test() +{ + initTestDebuggerServer(); + gDebuggee = addTestGlobal("test-conditional-breakpoint"); + gClient = new DebuggerClient(DebuggerServer.connectPipe()); + gClient.connect().then(function () { + attachTestTabAndResume(gClient, "test-conditional-breakpoint", function (aResponse, aTabClient, aThreadClient) { + gThreadClient = aThreadClient; + test_simple_breakpoint(); + }); + }); + do_test_pending(); +} + +function test_simple_breakpoint() +{ + gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) { + let source = gThreadClient.source(aPacket.frame.where.source); + source.setBreakpoint({ + line: 3, + condition: "a === 1" + }, function (aResponse, bpClient) { + gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) { + // Check the return value. + do_check_eq(aPacket.why.type, "breakpoint"); + do_check_eq(aPacket.frame.where.line, 3); + + // Remove the breakpoint. + bpClient.remove(function (aResponse) { + gThreadClient.resume(function () { + finishClient(gClient); + }); + }); + + }); + // Continue until the breakpoint is hit. + gThreadClient.resume(); + + }); + + }); + + Components.utils.evalInSandbox("debugger;\n" + // 1 + "var a = 1;\n" + // 2 + "var b = 2;\n", // 3 + gDebuggee, + "1.8", + "test.js", + 1); +} diff --git a/devtools/server/tests/unit/test_conditional_breakpoint-02.js b/devtools/server/tests/unit/test_conditional_breakpoint-02.js new file mode 100644 index 000000000..873f76159 --- /dev/null +++ b/devtools/server/tests/unit/test_conditional_breakpoint-02.js @@ -0,0 +1,60 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Check conditional breakpoint when condition evaluates to false. + */ + +var gDebuggee; +var gClient; +var gThreadClient; + +function run_test() +{ + initTestDebuggerServer(); + gDebuggee = addTestGlobal("test-conditional-breakpoint"); + gClient = new DebuggerClient(DebuggerServer.connectPipe()); + gClient.connect().then(function () { + attachTestTabAndResume(gClient, "test-conditional-breakpoint", function (aResponse, aTabClient, aThreadClient) { + gThreadClient = aThreadClient; + test_simple_breakpoint(); + }); + }); + do_test_pending(); +} + +function test_simple_breakpoint() +{ + gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) { + let source = gThreadClient.source(aPacket.frame.where.source); + source.setBreakpoint({ + line: 3, + condition: "a === 2" + }, function (aResponse, bpClient) { + gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) { + // Check the return value. + do_check_eq(aPacket.why.type, "debuggerStatement"); + do_check_eq(aPacket.frame.where.line, 4); + + // Remove the breakpoint. + bpClient.remove(function (aResponse) { + gThreadClient.resume(function () { + finishClient(gClient); + }); + }); + + }); + // Continue until the breakpoint is hit. + gThreadClient.resume(); + }); + }); + + Components.utils.evalInSandbox("debugger;\n" + // 1 + "var a = 1;\n" + // 2 + "var b = 2;\n" + // 3 + "debugger;", // 4 + gDebuggee, + "1.8", + "test.js", + 1); +} diff --git a/devtools/server/tests/unit/test_conditional_breakpoint-03.js b/devtools/server/tests/unit/test_conditional_breakpoint-03.js new file mode 100644 index 000000000..d9cf13e00 --- /dev/null +++ b/devtools/server/tests/unit/test_conditional_breakpoint-03.js @@ -0,0 +1,61 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Check conditional breakpoint when condition throws and make sure it pauses + */ + +var gDebuggee; +var gClient; +var gThreadClient; + +function run_test() +{ + initTestDebuggerServer(); + gDebuggee = addTestGlobal("test-conditional-breakpoint"); + gClient = new DebuggerClient(DebuggerServer.connectPipe()); + gClient.connect().then(function () { + attachTestTabAndResume(gClient, "test-conditional-breakpoint", function (aResponse, aTabClient, aThreadClient) { + gThreadClient = aThreadClient; + test_simple_breakpoint(); + }); + }); + do_test_pending(); +} + +function test_simple_breakpoint() +{ + gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) { + let source = gThreadClient.source(aPacket.frame.where.source); + source.setBreakpoint({ + line: 3, + condition: "throw new Error()" + }, function (aResponse, bpClient) { + gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) { + // Check the return value. + do_check_eq(aPacket.why.type, "breakpointConditionThrown"); + do_check_eq(aPacket.frame.where.line, 3); + + // Remove the breakpoint. + bpClient.remove(function (aResponse) { + gThreadClient.resume(function () { + finishClient(gClient); + }); + }); + + }); + // Continue until the breakpoint is hit. + gThreadClient.resume(); + + }); + + }); + + Components.utils.evalInSandbox("debugger;\n" + // 1 + "var a = 1;\n" + // 2 + "var b = 2;\n", // 3 + gDebuggee, + "1.8", + "test.js", + 1); +} diff --git a/devtools/server/tests/unit/test_dbgactor.js b/devtools/server/tests/unit/test_dbgactor.js new file mode 100644 index 000000000..b22b8446b --- /dev/null +++ b/devtools/server/tests/unit/test_dbgactor.js @@ -0,0 +1,116 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +var gClient; +var gDebuggee; + +const xpcInspector = Cc["@mozilla.org/jsinspector;1"].getService(Ci.nsIJSInspector); + +function run_test() +{ + initTestDebuggerServer(); + gDebuggee = testGlobal("test-1"); + DebuggerServer.addTestGlobal(gDebuggee); + + let transport = DebuggerServer.connectPipe(); + gClient = new DebuggerClient(transport); + gClient.addListener("connected", function (aEvent, aType, aTraits) { + gClient.listTabs((aResponse) => { + do_check_true("tabs" in aResponse); + for (let tab of aResponse.tabs) { + if (tab.title == "test-1") { + test_attach_tab(tab.actor); + return false; + } + } + do_check_true(false); // We should have found our tab in the list. + return undefined; + }); + }); + + gClient.connect(); + + do_test_pending(); +} + +// Attach to |aTabActor|, and check the response. +function test_attach_tab(aTabActor) +{ + gClient.request({ to: aTabActor, type: "attach" }, function (aResponse) { + do_check_false("error" in aResponse); + do_check_eq(aResponse.from, aTabActor); + do_check_eq(aResponse.type, "tabAttached"); + do_check_true(typeof aResponse.threadActor === "string"); + + test_attach_thread(aResponse.threadActor); + }); +} + +// Attach to |aThreadActor|, check the response, and resume it. +function test_attach_thread(aThreadActor) +{ + gClient.request({ to: aThreadActor, type: "attach" }, function (aResponse) { + do_check_false("error" in aResponse); + do_check_eq(aResponse.from, aThreadActor); + do_check_eq(aResponse.type, "paused"); + do_check_true("why" in aResponse); + do_check_eq(aResponse.why.type, "attached"); + + test_resume_thread(aThreadActor); + }); +} + +// Resume |aThreadActor|, and see that it stops at the 'debugger' +// statement. +function test_resume_thread(aThreadActor) +{ + // Allow the client to resume execution. + gClient.request({ to: aThreadActor, type: "resume" }, function (aResponse) { + do_check_false("error" in aResponse); + do_check_eq(aResponse.from, aThreadActor); + do_check_eq(aResponse.type, "resumed"); + + do_check_eq(xpcInspector.eventLoopNestLevel, 0); + + // Now that we know we're resumed, we can make the debuggee do something. + Cu.evalInSandbox("var a = true; var b = false; debugger; var b = true;", gDebuggee); + // Now make sure that we've run the code after the debugger statement... + do_check_true(gDebuggee.b); + }); + + gClient.addListener("paused", function (aName, aPacket) { + do_check_eq(aName, "paused"); + do_check_false("error" in aPacket); + do_check_eq(aPacket.from, aThreadActor); + do_check_eq(aPacket.type, "paused"); + do_check_true("actor" in aPacket); + do_check_true("why" in aPacket); + do_check_eq(aPacket.why.type, "debuggerStatement"); + + // Reach around the protocol to check that the debuggee is in the state + // we expect. + do_check_true(gDebuggee.a); + do_check_false(gDebuggee.b); + + do_check_eq(xpcInspector.eventLoopNestLevel, 1); + + // Let the debuggee continue execution. + gClient.request({ to: aThreadActor, type: "resume" }, cleanup); + }); +} + +function cleanup() +{ + gClient.addListener("closed", function (aEvent, aResult) { + do_test_finished(); + }); + + try { + let xpcInspector = Cc["@mozilla.org/jsinspector;1"].getService(Ci.nsIJSInspector); + do_check_eq(xpcInspector.eventLoopNestLevel, 0); + } catch (e) { + dump(e); + } + + gClient.close(); +} diff --git a/devtools/server/tests/unit/test_dbgclient_debuggerstatement.js b/devtools/server/tests/unit/test_dbgclient_debuggerstatement.js new file mode 100644 index 000000000..40468cb1d --- /dev/null +++ b/devtools/server/tests/unit/test_dbgclient_debuggerstatement.js @@ -0,0 +1,73 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +var gClient; +var gTabClient; +var gDebuggee; + +function run_test() +{ + initTestDebuggerServer(); + gDebuggee = testGlobal("test-1"); + DebuggerServer.addTestGlobal(gDebuggee); + + let transport = DebuggerServer.connectPipe(); + gClient = new DebuggerClient(transport); + gClient.connect().then(function ([aType, aTraits]) { + attachTestTab(gClient, "test-1", function (aReply, aTabClient) { + gTabClient = aTabClient; + test_threadAttach(aReply.threadActor); + }); + }); + do_test_pending(); +} + +function test_threadAttach(aThreadActorID) +{ + do_print("Trying to attach to thread " + aThreadActorID); + gTabClient.attachThread({}, function (aResponse, aThreadClient) { + do_check_eq(aThreadClient.state, "paused"); + do_check_eq(aThreadClient.actor, aThreadActorID); + aThreadClient.resume(function () { + do_check_eq(aThreadClient.state, "attached"); + test_debugger_statement(aThreadClient); + }); + }); +} + +function test_debugger_statement(aThreadClient) +{ + aThreadClient.addListener("paused", function (aEvent, aPacket) { + do_check_eq(aThreadClient.state, "paused"); + // Reach around the protocol to check that the debuggee is in the state + // we expect. + do_check_true(gDebuggee.a); + do_check_false(gDebuggee.b); + + let xpcInspector = Cc["@mozilla.org/jsinspector;1"].getService(Ci.nsIJSInspector); + do_check_eq(xpcInspector.eventLoopNestLevel, 1); + + aThreadClient.resume(cleanup); + }); + + Cu.evalInSandbox("var a = true; var b = false; debugger; var b = true;", gDebuggee); + + // Now make sure that we've run the code after the debugger statement... + do_check_true(gDebuggee.b); +} + +function cleanup() +{ + gClient.addListener("closed", function (aEvent) { + do_test_finished(); + }); + + try { + let xpcInspector = Cc["@mozilla.org/jsinspector;1"].getService(Ci.nsIJSInspector); + do_check_eq(xpcInspector.eventLoopNestLevel, 0); + } catch (e) { + dump(e); + } + + gClient.close(); +} diff --git a/devtools/server/tests/unit/test_dbgglobal.js b/devtools/server/tests/unit/test_dbgglobal.js new file mode 100644 index 000000000..ff4291932 --- /dev/null +++ b/devtools/server/tests/unit/test_dbgglobal.js @@ -0,0 +1,62 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +function run_test() +{ + // Should get an exception if we try to interact with DebuggerServer + // before we initialize it... + check_except(function () { + DebuggerServer.createListener(); + }); + check_except(DebuggerServer.closeAllListeners); + check_except(DebuggerServer.connectPipe); + + // Allow incoming connections. + DebuggerServer.init(); + + // These should still fail because we haven't added a createRootActor + // implementation yet. + check_except(function () { + DebuggerServer.createListener(); + }); + check_except(DebuggerServer.closeAllListeners); + check_except(DebuggerServer.connectPipe); + + DebuggerServer.registerModule("xpcshell-test/testactors"); + + // Now they should work. + DebuggerServer.createListener(); + DebuggerServer.closeAllListeners(); + + // Make sure we got the test's root actor all set up. + let client1 = DebuggerServer.connectPipe(); + client1.hooks = { + onPacket: function (aPacket1) { + do_check_eq(aPacket1.from, "root"); + do_check_eq(aPacket1.applicationType, "xpcshell-tests"); + + // Spin up a second connection, make sure it has its own root + // actor. + let client2 = DebuggerServer.connectPipe(); + client2.hooks = { + onPacket: function (aPacket2) { + do_check_eq(aPacket2.from, "root"); + do_check_neq(aPacket1.testConnectionPrefix, + aPacket2.testConnectionPrefix); + client2.close(); + }, + onClosed: function (aResult) { + client1.close(); + }, + }; + client2.ready(); + }, + + onClosed: function (aResult) { + do_test_finished(); + }, + }; + + client1.ready(); + do_test_pending(); +} diff --git a/devtools/server/tests/unit/test_eval-01.js b/devtools/server/tests/unit/test_eval-01.js new file mode 100644 index 000000000..b11903f87 --- /dev/null +++ b/devtools/server/tests/unit/test_eval-01.js @@ -0,0 +1,58 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Check basic eval resume/re-pause + */ + +var gDebuggee; +var gClient; +var gThreadClient; + +function run_test() +{ + initTestDebuggerServer(); + gDebuggee = addTestGlobal("test-stack"); + gClient = new DebuggerClient(DebuggerServer.connectPipe()); + gClient.connect().then(function () { + attachTestTabAndResume(gClient, "test-stack", function (aResponse, aTabClient, aThreadClient) { + gThreadClient = aThreadClient; + test_simple_eval(); + }); + }); + do_test_pending(); +} + +function test_simple_eval() +{ + gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) { + let arg1Actor = aPacket.frame.arguments[0].actor; + gThreadClient.eval(null, "({ obj: true })", function (aResponse) { + do_check_eq(aResponse.type, "resumed"); + // Expect a pause notification immediately. + gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) { + // Check the return value... + do_check_eq(aPacket.type, "paused"); + do_check_eq(aPacket.why.type, "clientEvaluated"); + do_check_eq(aPacket.why.frameFinished.return.type, "object"); + do_check_eq(aPacket.why.frameFinished.return.class, "Object"); + + // Make sure the previous pause lifetime was correctly dropped. + gClient.request({ to: arg1Actor, type: "bogusRequest" }, function (aResponse) { + do_check_eq(aResponse.error, "noSuchActor"); + gThreadClient.resume(function () { + finishClient(gClient); + }); + }); + + }); + + }); + + }); + + gDebuggee.eval("(" + function () { + function stopMe(arg1) { debugger; } + stopMe({obj: true}); + } + ")()"); +} diff --git a/devtools/server/tests/unit/test_eval-02.js b/devtools/server/tests/unit/test_eval-02.js new file mode 100644 index 000000000..386ea5c98 --- /dev/null +++ b/devtools/server/tests/unit/test_eval-02.js @@ -0,0 +1,48 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Check eval resume/re-pause with a throw. + */ + +var gDebuggee; +var gClient; +var gThreadClient; + +function run_test() +{ + initTestDebuggerServer(); + gDebuggee = addTestGlobal("test-stack"); + gClient = new DebuggerClient(DebuggerServer.connectPipe()); + gClient.connect().then(function () { + attachTestTabAndResume(gClient, "test-stack", function (aResponse, aTabClient, aThreadClient) { + gThreadClient = aThreadClient; + test_throw_eval(); + }); + }); + do_test_pending(); +} + +function test_throw_eval() +{ + gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) { + gThreadClient.eval(null, "throw 'failure'", function (aResponse) { + do_check_eq(aResponse.type, "resumed"); + // Expect a pause notification immediately. + gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) { + // Check the return value... + do_check_eq(aPacket.type, "paused"); + do_check_eq(aPacket.why.type, "clientEvaluated"); + do_check_eq(aPacket.why.frameFinished.throw, "failure"); + gThreadClient.resume(function () { + finishClient(gClient); + }); + }); + }); + }); + + gDebuggee.eval("(" + function () { + function stopMe(arg1) { debugger; } + stopMe({obj: true}); + } + ")()"); +} diff --git a/devtools/server/tests/unit/test_eval-03.js b/devtools/server/tests/unit/test_eval-03.js new file mode 100644 index 000000000..2234259aa --- /dev/null +++ b/devtools/server/tests/unit/test_eval-03.js @@ -0,0 +1,50 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Check syntax errors in an eval. + */ + +var gDebuggee; +var gClient; +var gThreadClient; + +function run_test() +{ + initTestDebuggerServer(); + gDebuggee = addTestGlobal("test-stack"); + gClient = new DebuggerClient(DebuggerServer.connectPipe()); + gClient.connect().then().then(function () { + attachTestTabAndResume(gClient, "test-stack", function (aResponse, aTabClient, aThreadClient) { + gThreadClient = aThreadClient; + test_syntax_error_eval(); + }); + }); + do_test_pending(); +} + +function test_syntax_error_eval() +{ + gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) { + gThreadClient.eval(null, "%$@!@#", function (aResponse) { + do_check_eq(aResponse.type, "resumed"); + // Expect a pause notification immediately. + gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) { + // Check the return value... + do_check_eq(aPacket.type, "paused"); + do_check_eq(aPacket.why.type, "clientEvaluated"); + do_check_eq(aPacket.why.frameFinished.throw.type, "object"); + do_check_eq(aPacket.why.frameFinished.throw.class, "Error"); + + gThreadClient.resume(function () { + finishClient(gClient); + }); + }); + }); + }); + + gDebuggee.eval("(" + function () { + function stopMe(arg1) { debugger; } + stopMe({obj: true}); + } + ")()"); +} diff --git a/devtools/server/tests/unit/test_eval-04.js b/devtools/server/tests/unit/test_eval-04.js new file mode 100644 index 000000000..77cb58d97 --- /dev/null +++ b/devtools/server/tests/unit/test_eval-04.js @@ -0,0 +1,69 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Check evals against different frames. + */ + +var gDebuggee; +var gClient; +var gThreadClient; + +function run_test() +{ + initTestDebuggerServer(); + gDebuggee = addTestGlobal("test-stack"); + gClient = new DebuggerClient(DebuggerServer.connectPipe()); + gClient.connect().then(function () { + attachTestTabAndResume(gClient, "test-stack", function (aResponse, aTabClient, aThreadClient) { + gThreadClient = aThreadClient; + test_syntax_error_eval(); + }); + }); + do_test_pending(); +} + +function test_syntax_error_eval() +{ + gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) { + + gThreadClient.getFrames(0, 2, function (aResponse) { + let frame0 = aResponse.frames[0]; + let frame1 = aResponse.frames[1]; + + // Eval against the top frame... + gThreadClient.eval(frame0.actor, "arg", function (aResponse) { + do_check_eq(aResponse.type, "resumed"); + gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) { + // 'arg' should have been evaluated in frame0 + do_check_eq(aPacket.type, "paused"); + do_check_eq(aPacket.why.type, "clientEvaluated"); + do_check_eq(aPacket.why.frameFinished.return, "arg0"); + + // Now eval against the second frame. + gThreadClient.eval(frame1.actor, "arg", function (aResponse) { + gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) { + // 'arg' should have been evaluated in frame1 + do_check_eq(aPacket.type, "paused"); + do_check_eq(aPacket.why.frameFinished.return, "arg1"); + + gThreadClient.resume(function () { + finishClient(gClient); + }); + }); + }); + }); + }); + }); + }); + + gDebuggee.eval("(" + function () { + function frame0(arg) { + debugger; + } + function frame1(arg) { + frame0("arg0"); + } + frame1("arg1"); + } + ")()"); +} diff --git a/devtools/server/tests/unit/test_eval-05.js b/devtools/server/tests/unit/test_eval-05.js new file mode 100644 index 000000000..b199e4afb --- /dev/null +++ b/devtools/server/tests/unit/test_eval-05.js @@ -0,0 +1,54 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Check pauses within evals. + */ + +var gDebuggee; +var gClient; +var gThreadClient; + +function run_test() +{ + initTestDebuggerServer(); + gDebuggee = addTestGlobal("test-stack"); + gClient = new DebuggerClient(DebuggerServer.connectPipe()); + gClient.connect().then(function () { + attachTestTabAndResume(gClient, "test-stack", function (aResponse, aTabClient, aThreadClient) { + gThreadClient = aThreadClient; + test_syntax_error_eval(); + }); + }); + do_test_pending(); +} + +function test_syntax_error_eval() +{ + gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) { + gThreadClient.eval(null, "debugger", function (aResponse) { + // Expect a resume then a debuggerStatement pause. + do_check_eq(aResponse.type, "resumed"); + gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) { + do_check_eq(aPacket.why.type, "debuggerStatement"); + // Resume from the debugger statement should immediately re-pause + // with a clientEvaluated reason. + gThreadClient.resume(function (aPacket) { + do_check_eq(aPacket.type, "resumed"); + gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) { + do_check_eq(aPacket.why.type, "clientEvaluated"); + gThreadClient.resume(function () { + finishClient(gClient); + }); + }); + }); + }); + }); + }); + gDebuggee.eval("(" + function () { + function stopMe(arg) { + debugger; + } + stopMe(); + } + ")()"); +} diff --git a/devtools/server/tests/unit/test_eventlooplag_actor.js b/devtools/server/tests/unit/test_eventlooplag_actor.js new file mode 100644 index 000000000..d2acdd8f8 --- /dev/null +++ b/devtools/server/tests/unit/test_eventlooplag_actor.js @@ -0,0 +1,59 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Test the eventLoopLag actor. + */ + +"use strict"; + +function run_test() +{ + let {EventLoopLagFront} = require("devtools/shared/fronts/eventlooplag"); + + DebuggerServer.init(); + DebuggerServer.addBrowserActors(); + + // As seen in EventTracer.cpp + let threshold = 20; + let interval = 10; + + + let front; + let client = new DebuggerClient(DebuggerServer.connectPipe()); + + // Start tracking event loop lags. + client.connect().then(function () { + client.listTabs(function (resp) { + front = new EventLoopLagFront(client, resp); + front.start().then(success => { + do_check_true(success); + front.once("event-loop-lag", gotLagEvent); + do_execute_soon(lag); + }); + }); + }); + + // Force a lag + function lag() { + let start = new Date(); + let duration = threshold + interval + 1; + while (true) { + if (((new Date()) - start) > duration) { + break; + } + } + } + + // Got a lag event. The test will time out if the actor + // fails to detect the lag. + function gotLagEvent(time) { + do_print("lag: " + time); + do_check_true(time >= threshold); + front.stop().then(() => { + finishClient(client); + }); + } + + do_test_pending(); +} diff --git a/devtools/server/tests/unit/test_forwardingprefix.js b/devtools/server/tests/unit/test_forwardingprefix.js new file mode 100644 index 000000000..885a99db8 --- /dev/null +++ b/devtools/server/tests/unit/test_forwardingprefix.js @@ -0,0 +1,196 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/* Exercise prefix-based forwarding of packets to other transports. */ + +const { RootActor } = require("devtools/server/actors/root"); + +var gMainConnection, gMainTransport; +var gSubconnection1, gSubconnection2; +var gClient; + +function run_test() +{ + DebuggerServer.init(); + + add_test(createMainConnection); + add_test(TestNoForwardingYet); + add_test(createSubconnection1); + add_test(TestForwardPrefix1OnlyRoot); + add_test(createSubconnection2); + add_test(TestForwardPrefix12OnlyRoot); + add_test(TestForwardPrefix12WithActor1); + add_test(TestForwardPrefix12WithActor12); + run_next_test(); +} + +/* + * Create a pipe connection, and return an object |{ conn, transport }|, + * where |conn| is the new DebuggerServerConnection instance, and + * |transport| is the client side of the transport on which it communicates + * (that is, packets sent on |transport| go to the new connection, and + * |transport|'s hooks receive replies). + * + * |aPrefix| is optional; if present, it's the prefix (minus the '/') for + * actors in the new connection. + */ +function newConnection(aPrefix) +{ + var conn; + DebuggerServer.createRootActor = function (aConn) { + conn = aConn; + return new RootActor(aConn, {}); + }; + + var transport = DebuggerServer.connectPipe(aPrefix); + + return { conn: conn, transport: transport }; +} + +/* Create the main connection for these tests. */ +function createMainConnection() +{ + ({ conn: gMainConnection, transport: gMainTransport } = newConnection()); + gClient = new DebuggerClient(gMainTransport); + gClient.connect().then(([aType, aTraits]) => run_next_test()); +} + +/* + * Exchange 'echo' messages with five actors: + * - root + * - prefix1/root + * - prefix1/actor + * - prefix2/root + * - prefix2/actor + * + * Expect proper echos from those named in |aReachables|, and 'noSuchActor' + * errors from the others. When we've gotten all our replies (errors or + * otherwise), call |aCompleted|. + * + * To avoid deep stacks, we call aCompleted from the next tick. + */ +function tryActors(aReachables, aCompleted) { + let count = 0; + + let outerActor; + for (outerActor of [ "root", + "prefix1/root", "prefix1/actor", + "prefix2/root", "prefix2/actor" ]) { + /* + * Let each callback capture its own iteration's value; outerActor is + * local to the whole loop, not to a single iteration. + */ + let actor = outerActor; + + count++; + + gClient.request({ to: actor, type: "echo", value: "tango"}, // phone home + (aResponse) => { + if (aReachables.has(actor)) + do_check_matches({ from: actor, to: actor, type: "echo", value: "tango" }, aResponse); + else + do_check_matches({ from: actor, error: "noSuchActor", message: "No such actor for ID: " + actor }, aResponse); + + if (--count == 0) + do_execute_soon(aCompleted, "tryActors callback " + aCompleted.name); + }); + } +} + +/* + * With no forwarding established, sending messages to root should work, + * but sending messages to prefixed actor names, or anyone else, should get + * an error. + */ +function TestNoForwardingYet() +{ + tryActors(new Set(["root"]), run_next_test); +} + +/* + * Create a new pipe connection which forwards its reply packets to + * gMainConnection's client, and to which gMainConnection forwards packets + * directed to actors whose names begin with |aPrefix + '/'|, and. + * + * Return an object { conn, transport }, as for newConnection. + */ +function newSubconnection(aPrefix) +{ + let { conn, transport } = newConnection(aPrefix); + transport.hooks = { + onPacket: (aPacket) => gMainConnection.send(aPacket), + onClosed: () => {} + }; + gMainConnection.setForwarding(aPrefix, transport); + + return { conn: conn, transport: transport }; +} + +/* Create a second root actor, to which we can forward things. */ +function createSubconnection1() +{ + let { conn, transport } = newSubconnection("prefix1"); + gSubconnection1 = conn; + transport.ready(); + gClient.expectReply("prefix1/root", (aReply) => run_next_test()); +} + +// Establish forwarding, but don't put any actors in that server. +function TestForwardPrefix1OnlyRoot() +{ + tryActors(new Set(["root", "prefix1/root"]), run_next_test); +} + +/* Create a third root actor, to which we can forward things. */ +function createSubconnection2() +{ + let { conn, transport } = newSubconnection("prefix2"); + gSubconnection2 = conn; + transport.ready(); + gClient.expectReply("prefix2/root", (aReply) => run_next_test()); +} + +function TestForwardPrefix12OnlyRoot() +{ + tryActors(new Set(["root", "prefix1/root", "prefix2/root"]), run_next_test); +} + +// A dumb actor that implements 'echo'. +// +// It's okay that both subconnections' actors behave identically, because +// the reply-sending code attaches the replying actor's name to the packet, +// so simply matching the 'from' field in the reply ensures that we heard +// from the right actor. +function EchoActor(aConnection) +{ + this.conn = aConnection; +} +EchoActor.prototype.actorPrefix = "EchoActor"; +EchoActor.prototype.onEcho = function (aRequest) { + /* + * Request packets are frozen. Copy aRequest, so that + * DebuggerServerConnection.onPacket can attach a 'from' property. + */ + return JSON.parse(JSON.stringify(aRequest)); +}; +EchoActor.prototype.requestTypes = { + "echo": EchoActor.prototype.onEcho +}; + +function TestForwardPrefix12WithActor1() +{ + let actor = new EchoActor(gSubconnection1); + actor.actorID = "prefix1/actor"; + gSubconnection1.addActor(actor); + + tryActors(new Set(["root", "prefix1/root", "prefix1/actor", "prefix2/root"]), run_next_test); +} + +function TestForwardPrefix12WithActor12() +{ + let actor = new EchoActor(gSubconnection2); + actor.actorID = "prefix2/actor"; + gSubconnection2.addActor(actor); + + tryActors(new Set(["root", "prefix1/root", "prefix1/actor", "prefix2/root", "prefix2/actor"]), run_next_test); +} diff --git a/devtools/server/tests/unit/test_frameactor-01.js b/devtools/server/tests/unit/test_frameactor-01.js new file mode 100644 index 000000000..ad37a8ab5 --- /dev/null +++ b/devtools/server/tests/unit/test_frameactor-01.js @@ -0,0 +1,43 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Verify that we get a frame actor along with a debugger statement. + */ + +var gDebuggee; +var gClient; +var gThreadClient; + +function run_test() +{ + initTestDebuggerServer(); + gDebuggee = addTestGlobal("test-stack"); + gClient = new DebuggerClient(DebuggerServer.connectPipe()); + gClient.connect().then(function () { + attachTestTabAndResume(gClient, "test-stack", function (aResponse, aTabClient, aThreadClient) { + gThreadClient = aThreadClient; + test_pause_frame(); + }); + }); + do_test_pending(); +} + +function test_pause_frame() +{ + gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) { + do_check_true(!!aPacket.frame); + do_check_true(!!aPacket.frame.actor); + do_check_eq(aPacket.frame.callee.name, "stopMe"); + gThreadClient.resume(function () { + finishClient(gClient); + }); + }); + + gDebuggee.eval("(" + function () { + function stopMe() { + debugger; + } + stopMe(); + } + ")()"); +} diff --git a/devtools/server/tests/unit/test_frameactor-02.js b/devtools/server/tests/unit/test_frameactor-02.js new file mode 100644 index 000000000..f2890adac --- /dev/null +++ b/devtools/server/tests/unit/test_frameactor-02.js @@ -0,0 +1,45 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Verify that two pauses in a row will keep the same frame actor. + */ + +var gDebuggee; +var gClient; +var gThreadClient; + +function run_test() +{ + initTestDebuggerServer(); + gDebuggee = addTestGlobal("test-stack"); + gClient = new DebuggerClient(DebuggerServer.connectPipe()); + gClient.connect().then(function () { + attachTestTabAndResume(gClient, "test-stack", function (aResponse, aTabClient, aThreadClient) { + gThreadClient = aThreadClient; + test_pause_frame(); + }); + }); + do_test_pending(); +} + +function test_pause_frame() +{ + gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket1) { + gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket2) { + do_check_eq(aPacket1.frame.actor, aPacket2.frame.actor); + gThreadClient.resume(function () { + finishClient(gClient); + }); + }); + gThreadClient.resume(); + }); + + gDebuggee.eval("(" + function () { + function stopMe() { + debugger; + debugger; + } + stopMe(); + } + ")()"); +} diff --git a/devtools/server/tests/unit/test_frameactor-03.js b/devtools/server/tests/unit/test_frameactor-03.js new file mode 100644 index 000000000..0d7739d5a --- /dev/null +++ b/devtools/server/tests/unit/test_frameactor-03.js @@ -0,0 +1,47 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Verify that a frame actor is properly expired when the frame goes away. + */ + +var gDebuggee; +var gClient; +var gThreadClient; + +function run_test() +{ + initTestDebuggerServer(); + gDebuggee = addTestGlobal("test-stack"); + gClient = new DebuggerClient(DebuggerServer.connectPipe()); + gClient.connect().then(function () { + attachTestTabAndResume(gClient, "test-stack", function (aResponse, aTabClient, aThreadClient) { + gThreadClient = aThreadClient; + test_pause_frame(); + }); + }); + do_test_pending(); +} + +function test_pause_frame() +{ + gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket1) { + gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket2) { + let poppedFrames = aPacket2.poppedFrames; + do_check_eq(typeof (poppedFrames), typeof ([])); + do_check_true(poppedFrames.indexOf(aPacket1.frame.actor) >= 0); + gThreadClient.resume(function () { + finishClient(gClient); + }); + }); + gThreadClient.resume(); + }); + + gDebuggee.eval("(" + function () { + function stopMe() { + debugger; + } + stopMe(); + debugger; + } + ")()"); +} diff --git a/devtools/server/tests/unit/test_frameactor-04.js b/devtools/server/tests/unit/test_frameactor-04.js new file mode 100644 index 000000000..b4faa96e0 --- /dev/null +++ b/devtools/server/tests/unit/test_frameactor-04.js @@ -0,0 +1,91 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Verify the "frames" request on the thread. + */ + +var gDebuggee; +var gClient; +var gThreadClient; + +function run_test() +{ + initTestDebuggerServer(); + gDebuggee = addTestGlobal("test-stack"); + gClient = new DebuggerClient(DebuggerServer.connectPipe()); + gClient.connect().then(function () { + attachTestTabAndResume(gClient, "test-stack", function (aResponse, aTabClient, aThreadClient) { + gThreadClient = aThreadClient; + test_pause_frame(); + }); + }); + do_test_pending(); +} + +var gFrames = [ + // Function calls... + { type: "call", callee: { name: "depth3" } }, + { type: "call", callee: { name: "depth2" } }, + { type: "call", callee: { name: "depth1" } }, + + // Anonymous function call in our eval... + { type: "call", callee: { name: undefined } }, + + // The eval itself. + { type: "eval", callee: { name: undefined } }, +]; + +var gSliceTests = [ + { start: 0, count: undefined, resetActors: true }, + { start: 0, count: 1 }, + { start: 2, count: 2 }, + { start: 1, count: 15 }, + { start: 15, count: undefined }, +]; + +function test_frame_slice() { + if (gSliceTests.length == 0) { + gThreadClient.resume(function () { finishClient(gClient); }); + return; + } + + let test = gSliceTests.shift(); + gThreadClient.getFrames(test.start, test.count, function (aResponse) { + var testFrames = gFrames.slice(test.start, test.count ? test.start + test.count : undefined); + do_check_eq(testFrames.length, aResponse.frames.length); + for (var i = 0; i < testFrames.length; i++) { + let expected = testFrames[i]; + let actual = aResponse.frames[i]; + + if (test.resetActors) { + expected.actor = actual.actor; + } + + for (let key of ["type", "callee-name"]) { + do_check_eq(expected[key] || undefined, actual[key]); + } + } + test_frame_slice(); + }); +} + +function test_pause_frame() +{ + gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket1) { + test_frame_slice(); + }); + + gDebuggee.eval("(" + function () { + function depth3() { + debugger; + } + function depth2() { + depth3(); + } + function depth1() { + depth2(); + } + depth1(); + } + ")()"); +} diff --git a/devtools/server/tests/unit/test_frameactor-05.js b/devtools/server/tests/unit/test_frameactor-05.js new file mode 100644 index 000000000..feece598e --- /dev/null +++ b/devtools/server/tests/unit/test_frameactor-05.js @@ -0,0 +1,89 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Verify that frame actors retrieved with the frames request + * are included in the pause packet's popped-frames property. + */ + +var gDebuggee; +var gClient; +var gThreadClient; + +function run_test() +{ + initTestDebuggerServer(); + gDebuggee = addTestGlobal("test-stack"); + gClient = new DebuggerClient(DebuggerServer.connectPipe()); + gClient.connect().then(function () { + attachTestTabAndResume(gClient, "test-stack", function (aResponse, aTabClient, aThreadClient) { + gThreadClient = aThreadClient; + test_pause_frame(); + }); + }); + do_test_pending(); +} + +function test_frame_slice() { + if (gSliceTests.length == 0) { + gThreadClient.resume(function () { finishClient(gClient); }); + return; + } + + let test = gSliceTests.shift(); + gThreadClient.getFrames(test.start, test.count, function (aResponse) { + var testFrames = gFrames.slice(test.start, test.count ? test.start + test.count : undefined); + do_check_eq(testFrames.length, aResponse.frames.length); + for (var i = 0; i < testFrames.length; i++) { + let expected = testFrames[i]; + let actual = aResponse.frames[i]; + + if (test.resetActors) { + expected.actor = actual.actor; + } + + for (var key in expected) { + do_check_eq(expected[key], actual[key]); + } + } + test_frame_slice(); + }); +} + +function test_pause_frame() +{ + gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket1) { + gThreadClient.getFrames(0, null, function (aFrameResponse) { + do_check_eq(aFrameResponse.frames.length, 5); + // Now wait for the next pause, after which the three + // youngest actors should be popped.. + let expectPopped = aFrameResponse.frames.slice(0, 3).map(frame => frame.actor); + expectPopped.sort(); + + gThreadClient.addOneTimeListener("paused", function (aEvent, aPausePacket) { + let popped = aPausePacket.poppedFrames.sort(); + do_check_eq(popped.length, 3); + for (let i = 0; i < 3; i++) { + do_check_eq(expectPopped[i], popped[i]); + } + + gThreadClient.resume(function () { finishClient(gClient); }); + }); + gThreadClient.resume(); + }); + }); + + gDebuggee.eval("(" + function () { + function depth3() { + debugger; + } + function depth2() { + depth3(); + } + function depth1() { + depth2(); + } + depth1(); + debugger; + } + ")()"); +} diff --git a/devtools/server/tests/unit/test_framearguments-01.js b/devtools/server/tests/unit/test_framearguments-01.js new file mode 100644 index 000000000..e075d2c25 --- /dev/null +++ b/devtools/server/tests/unit/test_framearguments-01.js @@ -0,0 +1,51 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Check a frame actor's arguments property. + */ + +var gDebuggee; +var gClient; +var gThreadClient; + +function run_test() +{ + initTestDebuggerServer(); + gDebuggee = addTestGlobal("test-stack"); + gClient = new DebuggerClient(DebuggerServer.connectPipe()); + gClient.connect().then(function () { + attachTestTabAndResume(gClient, "test-stack", function (aResponse, aTabClient, aThreadClient) { + gThreadClient = aThreadClient; + test_pause_frame(); + }); + }); + do_test_pending(); +} + +function test_pause_frame() +{ + gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) { + let args = aPacket.frame["arguments"]; + do_check_eq(args.length, 6); + do_check_eq(args[0], 42); + do_check_eq(args[1], true); + do_check_eq(args[2], "nasu"); + do_check_eq(args[3].type, "null"); + do_check_eq(args[4].type, "undefined"); + do_check_eq(args[5].type, "object"); + do_check_eq(args[5].class, "Object"); + do_check_true(!!args[5].actor); + + gThreadClient.resume(function () { + finishClient(gClient); + }); + }); + + gDebuggee.eval("(" + function () { + function stopMe(aNumber, aBool, aString, aNull, aUndefined, aObject) { + debugger; + } + stopMe(42, true, "nasu", null, undefined, { foo: "bar" }); + } + ")()"); +} diff --git a/devtools/server/tests/unit/test_framebindings-01.js b/devtools/server/tests/unit/test_framebindings-01.js new file mode 100644 index 000000000..ae62f8ff1 --- /dev/null +++ b/devtools/server/tests/unit/test_framebindings-01.js @@ -0,0 +1,77 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Check a frame actor's bindings property. + */ + +var gDebuggee; +var gClient; +var gThreadClient; + +function run_test() +{ + initTestDebuggerServer(); + gDebuggee = addTestGlobal("test-stack"); + gClient = new DebuggerClient(DebuggerServer.connectPipe()); + gClient.connect().then(function () { + attachTestTabAndResume(gClient, "test-stack", function (aResponse, aTabClient, aThreadClient) { + gThreadClient = aThreadClient; + test_pause_frame(); + }); + }); + do_test_pending(); +} + +function test_pause_frame() +{ + gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) { + let bindings = aPacket.frame.environment.bindings; + let args = bindings.arguments; + let vars = bindings.variables; + + do_check_eq(args.length, 6); + do_check_eq(args[0].aNumber.value, 42); + do_check_eq(args[1].aBool.value, true); + do_check_eq(args[2].aString.value, "nasu"); + do_check_eq(args[3].aNull.value.type, "null"); + do_check_eq(args[4].aUndefined.value.type, "undefined"); + do_check_eq(args[5].aObject.value.type, "object"); + do_check_eq(args[5].aObject.value.class, "Object"); + do_check_true(!!args[5].aObject.value.actor); + + do_check_eq(vars.a.value, 1); + do_check_eq(vars.b.value, true); + do_check_eq(vars.c.value.type, "object"); + do_check_eq(vars.c.value.class, "Object"); + do_check_true(!!vars.c.value.actor); + + let objClient = gThreadClient.pauseGrip(vars.c.value); + objClient.getPrototypeAndProperties(function (aResponse) { + do_check_eq(aResponse.ownProperties.a.configurable, true); + do_check_eq(aResponse.ownProperties.a.enumerable, true); + do_check_eq(aResponse.ownProperties.a.writable, true); + do_check_eq(aResponse.ownProperties.a.value, "a"); + + do_check_eq(aResponse.ownProperties.b.configurable, true); + do_check_eq(aResponse.ownProperties.b.enumerable, true); + do_check_eq(aResponse.ownProperties.b.writable, true); + do_check_eq(aResponse.ownProperties.b.value.type, "undefined"); + do_check_false("class" in aResponse.ownProperties.b.value); + + gThreadClient.resume(function () { + finishClient(gClient); + }); + }); + }); + + gDebuggee.eval("(" + function () { + function stopMe(aNumber, aBool, aString, aNull, aUndefined, aObject) { + var a = 1; + var b = true; + var c = { a: "a", b: undefined }; + debugger; + } + stopMe(42, true, "nasu", null, undefined, { foo: "bar" }); + } + ")()"); +} diff --git a/devtools/server/tests/unit/test_framebindings-02.js b/devtools/server/tests/unit/test_framebindings-02.js new file mode 100644 index 000000000..552670349 --- /dev/null +++ b/devtools/server/tests/unit/test_framebindings-02.js @@ -0,0 +1,63 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Check a frame actor's parent bindings. + */ + +var gDebuggee; +var gClient; +var gThreadClient; + +function run_test() +{ + initTestDebuggerServer(); + gDebuggee = addTestGlobal("test-stack"); + gClient = new DebuggerClient(DebuggerServer.connectPipe()); + gClient.connect().then(function () { + attachTestTabAndResume(gClient, "test-stack", function (aResponse, aTabClient, aThreadClient) { + gThreadClient = aThreadClient; + test_pause_frame(); + }); + }); + do_test_pending(); +} + +function test_pause_frame() +{ + gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) { + let parentEnv = aPacket.frame.environment.parent; + let bindings = parentEnv.bindings; + let args = bindings.arguments; + let vars = bindings.variables; + do_check_neq(parentEnv, undefined); + do_check_eq(args.length, 0); + do_check_eq(vars.stopMe.value.type, "object"); + do_check_eq(vars.stopMe.value.class, "Function"); + do_check_true(!!vars.stopMe.value.actor); + + // Skip the global lexical scope. + parentEnv = parentEnv.parent.parent; + do_check_neq(parentEnv, undefined); + let objClient = gThreadClient.pauseGrip(parentEnv.object); + objClient.getPrototypeAndProperties(function (aResponse) { + do_check_eq(aResponse.ownProperties.Object.value.type, "object"); + do_check_eq(aResponse.ownProperties.Object.value.class, "Function"); + do_check_true(!!aResponse.ownProperties.Object.value.actor); + + gThreadClient.resume(function () { + finishClient(gClient); + }); + }); + }); + + gDebuggee.eval("(" + function () { + function stopMe(aNumber, aBool, aString, aNull, aUndefined, aObject) { + var a = 1; + var b = true; + var c = { a: "a" }; + debugger; + } + stopMe(42, true, "nasu", null, undefined, { foo: "bar" }); + } + ")()"); +} diff --git a/devtools/server/tests/unit/test_framebindings-03.js b/devtools/server/tests/unit/test_framebindings-03.js new file mode 100644 index 000000000..1ec51570d --- /dev/null +++ b/devtools/server/tests/unit/test_framebindings-03.js @@ -0,0 +1,69 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Check a |with| frame actor's bindings. + */ + +var gDebuggee; +var gClient; +var gThreadClient; + +function run_test() +{ + initTestDebuggerServer(); + gDebuggee = addTestGlobal("test-stack"); + gClient = new DebuggerClient(DebuggerServer.connectPipe()); + gClient.connect().then(function () { + attachTestTabAndResume(gClient, "test-stack", function (aResponse, aTabClient, aThreadClient) { + gThreadClient = aThreadClient; + test_pause_frame(); + }); + }); + do_test_pending(); +} + +function test_pause_frame() +{ + gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) { + let env = aPacket.frame.environment; + do_check_neq(env, undefined); + + let parentEnv = env.parent; + do_check_neq(parentEnv, undefined); + + let bindings = parentEnv.bindings; + let args = bindings.arguments; + let vars = bindings.variables; + do_check_eq(args.length, 1); + do_check_eq(args[0].aNumber.value, 10); + do_check_eq(vars.r.value, 10); + do_check_eq(vars.a.value, Math.PI * 100); + do_check_eq(vars.arguments.value.class, "Arguments"); + do_check_true(!!vars.arguments.value.actor); + + let objClient = gThreadClient.pauseGrip(env.object); + objClient.getPrototypeAndProperties(function (aResponse) { + do_check_eq(aResponse.ownProperties.PI.value, Math.PI); + do_check_eq(aResponse.ownProperties.cos.value.type, "object"); + do_check_eq(aResponse.ownProperties.cos.value.class, "Function"); + do_check_true(!!aResponse.ownProperties.cos.value.actor); + + gThreadClient.resume(function () { + finishClient(gClient); + }); + }); + }); + + gDebuggee.eval("(" + function () { + function stopMe(aNumber) { + var a; + var r = aNumber; + with (Math) { + a = PI * r * r; + debugger; + } + } + stopMe(10); + } + ")()"); +} diff --git a/devtools/server/tests/unit/test_framebindings-04.js b/devtools/server/tests/unit/test_framebindings-04.js new file mode 100644 index 000000000..963a12055 --- /dev/null +++ b/devtools/server/tests/unit/test_framebindings-04.js @@ -0,0 +1,84 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Check the environment bindongs of a |with| within a |with|. + */ + +var gDebuggee; +var gClient; +var gThreadClient; + +function run_test() +{ + initTestDebuggerServer(); + gDebuggee = addTestGlobal("test-stack"); + gClient = new DebuggerClient(DebuggerServer.connectPipe()); + gClient.connect().then(function () { + attachTestTabAndResume(gClient, "test-stack", function (aResponse, aTabClient, aThreadClient) { + gThreadClient = aThreadClient; + test_pause_frame(); + }); + }); + do_test_pending(); +} + +function test_pause_frame() +{ + gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) { + let env = aPacket.frame.environment; + do_check_neq(env, undefined); + + let objClient = gThreadClient.pauseGrip(env.object); + objClient.getPrototypeAndProperties(function (aResponse) { + do_check_eq(aResponse.ownProperties.one.value, 1); + do_check_eq(aResponse.ownProperties.two.value, 2); + do_check_eq(aResponse.ownProperties.foo, undefined); + + let parentEnv = env.parent; + do_check_neq(parentEnv, undefined); + + let parentClient = gThreadClient.pauseGrip(parentEnv.object); + parentClient.getPrototypeAndProperties(function (aResponse) { + do_check_eq(aResponse.ownProperties.PI.value, Math.PI); + do_check_eq(aResponse.ownProperties.cos.value.type, "object"); + do_check_eq(aResponse.ownProperties.cos.value.class, "Function"); + do_check_true(!!aResponse.ownProperties.cos.value.actor); + + parentEnv = parentEnv.parent; + do_check_neq(parentEnv, undefined); + + let bindings = parentEnv.bindings; + let args = bindings.arguments; + let vars = bindings.variables; + do_check_eq(args.length, 1); + do_check_eq(args[0].aNumber.value, 10); + do_check_eq(vars.r.value, 10); + do_check_eq(vars.a.value, Math.PI * 100); + do_check_eq(vars.arguments.value.class, "Arguments"); + do_check_true(!!vars.arguments.value.actor); + do_check_eq(vars.foo.value, 2 * Math.PI); + + gThreadClient.resume(function () { + finishClient(gClient); + }); + }); + }); + + }); + + gDebuggee.eval("(" + function () { + function stopMe(aNumber) { + var a, obj = { one: 1, two: 2 }; + var r = aNumber; + with (Math) { + a = PI * r * r; + with (obj) { + var foo = two * PI; + debugger; + } + } + } + stopMe(10); + } + ")()"); +} diff --git a/devtools/server/tests/unit/test_framebindings-05.js b/devtools/server/tests/unit/test_framebindings-05.js new file mode 100644 index 000000000..9827c617a --- /dev/null +++ b/devtools/server/tests/unit/test_framebindings-05.js @@ -0,0 +1,63 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Check the environment bindings of a |with| in global scope. + */ + +var gDebuggee; +var gClient; +var gThreadClient; + +function run_test() +{ + initTestDebuggerServer(); + gDebuggee = addTestGlobal("test-stack"); + gClient = new DebuggerClient(DebuggerServer.connectPipe()); + gClient.connect().then(function () { + attachTestTabAndResume(gClient, "test-stack", function (aResponse, aTabClient, aThreadClient) { + gThreadClient = aThreadClient; + test_pause_frame(); + }); + }); + do_test_pending(); +} + +function test_pause_frame() +{ + gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) { + let env = aPacket.frame.environment; + do_check_neq(env, undefined); + + let objClient = gThreadClient.pauseGrip(env.object); + objClient.getPrototypeAndProperties(function (aResponse) { + do_check_eq(aResponse.ownProperties.PI.value, Math.PI); + do_check_eq(aResponse.ownProperties.cos.value.type, "object"); + do_check_eq(aResponse.ownProperties.cos.value.class, "Function"); + do_check_true(!!aResponse.ownProperties.cos.value.actor); + + // Skip the global lexical scope. + let parentEnv = env.parent.parent; + do_check_neq(parentEnv, undefined); + + let parentClient = gThreadClient.pauseGrip(parentEnv.object); + parentClient.getPrototypeAndProperties(function (aResponse) { + do_check_eq(aResponse.ownProperties.a.value, Math.PI * 100); + do_check_eq(aResponse.ownProperties.r.value, 10); + do_check_eq(aResponse.ownProperties.Object.value.type, "object"); + do_check_eq(aResponse.ownProperties.Object.value.class, "Function"); + do_check_true(!!aResponse.ownProperties.Object.value.actor); + + gThreadClient.resume(function () { + finishClient(gClient); + }); + }); + }); + }); + + gDebuggee.eval("var a, r = 10;\n" + + "with (Math) {\n" + + " a = PI * r * r;\n" + + " debugger;\n" + + "}"); +} diff --git a/devtools/server/tests/unit/test_framebindings-06.js b/devtools/server/tests/unit/test_framebindings-06.js new file mode 100644 index 000000000..9d8478a29 --- /dev/null +++ b/devtools/server/tests/unit/test_framebindings-06.js @@ -0,0 +1,60 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +var gDebuggee; +var gClient; +var gThreadClient; + +function run_test() +{ + initTestDebuggerServer(); + gDebuggee = addTestGlobal("test-grips"); + + gClient = new DebuggerClient(DebuggerServer.connectPipe()); + gClient.connect().then(function () { + attachTestTabAndResume(gClient, "test-grips", function (aResponse, aTabClient, aThreadClient) { + gThreadClient = aThreadClient; + test_banana_environment(); + }); + }); + do_test_pending(); +} + +function test_banana_environment() +{ + + gThreadClient.addOneTimeListener("paused", + function (aEvent, aPacket) { + equal(aPacket.type, "paused"); + let env = aPacket.frame.environment; + equal(env.type, "function"); + equal(env.function.name, "banana3"); + let parent = env.parent; + equal(parent.type, "block"); + ok("banana3" in parent.bindings.variables); + parent = parent.parent; + equal(parent.type, "function"); + equal(parent.function.name, "banana2"); + parent = parent.parent; + equal(parent.type, "block"); + ok("banana2" in parent.bindings.variables); + parent = parent.parent; + equal(parent.type, "function"); + equal(parent.function.name, "banana"); + + gThreadClient.resume(function () { + finishClient(gClient); + }); + }); + + gDebuggee.eval("\ + function banana(x) { \n\ + return function banana2(y) { \n\ + return function banana3(z) { \n\ + debugger; \n\ + }; \n\ + }; \n\ + } \n\ + banana('x')('y')('z'); \n\ + "); +} diff --git a/devtools/server/tests/unit/test_framebindings-07.js b/devtools/server/tests/unit/test_framebindings-07.js new file mode 100644 index 000000000..bdfc36d97 --- /dev/null +++ b/devtools/server/tests/unit/test_framebindings-07.js @@ -0,0 +1,64 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +var gDebuggee; +var gClient; +var gThreadClient; + +// Test that the EnvironmentClient's getBindings() method works as expected. +function run_test() +{ + initTestDebuggerServer(); + gDebuggee = addTestGlobal("test-bindings"); + + gClient = new DebuggerClient(DebuggerServer.connectPipe()); + gClient.connect().then(function () { + attachTestTabAndResume(gClient, "test-bindings", function (aResponse, aTabClient, aThreadClient) { + gThreadClient = aThreadClient; + test_banana_environment(); + }); + }); + do_test_pending(); +} + +function test_banana_environment() +{ + + gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) { + let environment = aPacket.frame.environment; + do_check_eq(environment.type, "function"); + + let parent = environment.parent; + do_check_eq(parent.type, "block"); + + let grandpa = parent.parent; + do_check_eq(grandpa.type, "function"); + + let envClient = gThreadClient.environment(environment); + envClient.getBindings(aResponse => { + do_check_eq(aResponse.bindings.arguments[0].z.value, "z"); + + let parentClient = gThreadClient.environment(parent); + parentClient.getBindings(aResponse => { + do_check_eq(aResponse.bindings.variables.banana3.value.class, "Function"); + + let grandpaClient = gThreadClient.environment(grandpa); + grandpaClient.getBindings(aResponse => { + do_check_eq(aResponse.bindings.arguments[0].y.value, "y"); + gThreadClient.resume(() => finishClient(gClient)); + }); + }); + }); + }); + + gDebuggee.eval("\ + function banana(x) { \n\ + return function banana2(y) { \n\ + return function banana3(z) { \n\ + debugger; \n\ + }; \n\ + }; \n\ + } \n\ + banana('x')('y')('z'); \n\ + "); +} diff --git a/devtools/server/tests/unit/test_frameclient-01.js b/devtools/server/tests/unit/test_frameclient-01.js new file mode 100644 index 000000000..a441c9ade --- /dev/null +++ b/devtools/server/tests/unit/test_frameclient-01.js @@ -0,0 +1,53 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +var gDebuggee; +var gClient; +var gThreadClient; + +function run_test() +{ + initTestDebuggerServer(); + gDebuggee = addTestGlobal("test-stack"); + gClient = new DebuggerClient(DebuggerServer.connectPipe()); + gClient.connect().then(function () { + attachTestTabAndResume(gClient, "test-stack", function (aResponse, aTabClient, aThreadClient) { + gThreadClient = aThreadClient; + test_pause_frame(); + }); + }); + do_test_pending(); +} + +function test_pause_frame() +{ + gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) { + gThreadClient.addOneTimeListener("framesadded", function () { + do_check_eq(gThreadClient.cachedFrames.length, 3); + do_check_true(gThreadClient.moreFrames); + do_check_false(gThreadClient.fillFrames(3)); + + do_check_true(gThreadClient.fillFrames(30)); + gThreadClient.addOneTimeListener("framesadded", function () { + do_check_false(gThreadClient.moreFrames); + do_check_eq(gThreadClient.cachedFrames.length, 7); + gThreadClient.resume(function () { + finishClient(gClient); + }); + }); + }); + do_check_true(gThreadClient.fillFrames(3)); + }); + + gDebuggee.eval("(" + function () { + var recurseLeft = 5; + function recurse() { + if (--recurseLeft == 0) { + debugger; + return; + } + recurse(); + } + recurse(); + } + ")()"); +} diff --git a/devtools/server/tests/unit/test_frameclient-02.js b/devtools/server/tests/unit/test_frameclient-02.js new file mode 100644 index 000000000..a257e5960 --- /dev/null +++ b/devtools/server/tests/unit/test_frameclient-02.js @@ -0,0 +1,46 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +var gDebuggee; +var gClient; +var gThreadClient; + +function run_test() +{ + initTestDebuggerServer(); + gDebuggee = addTestGlobal("test-stack"); + gClient = new DebuggerClient(DebuggerServer.connectPipe()); + gClient.connect().then(function () { + attachTestTabAndResume(gClient, "test-stack", function (aResponse, aTabClient, aThreadClient) { + gThreadClient = aThreadClient; + test_pause_frame(); + }); + }); + do_test_pending(); +} + +function test_pause_frame() +{ + gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) { + // Ask for exactly the number of frames we expect. + gThreadClient.addOneTimeListener("framesadded", function () { + do_check_false(gThreadClient.moreFrames); + gThreadClient.resume(function () { + finishClient(gClient); + }); + }); + do_check_true(gThreadClient.fillFrames(3)); + }); + + gDebuggee.eval("(" + function () { + var recurseLeft = 1; + function recurse() { + if (--recurseLeft == 0) { + debugger; + return; + } + recurse(); + } + recurse(); + } + ")()"); +} diff --git a/devtools/server/tests/unit/test_functiongrips-01.js b/devtools/server/tests/unit/test_functiongrips-01.js new file mode 100644 index 000000000..c41a7cad5 --- /dev/null +++ b/devtools/server/tests/unit/test_functiongrips-01.js @@ -0,0 +1,95 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +var gDebuggee; +var gClient; +var gThreadClient; + +function run_test() +{ + initTestDebuggerServer(); + gDebuggee = addTestGlobal("test-grips"); + gDebuggee.eval(function stopMe(arg1) { + debugger; + }.toString()); + + gClient = new DebuggerClient(DebuggerServer.connectPipe()); + gClient.connect().then(function () { + attachTestTabAndResume(gClient, "test-grips", function (aResponse, aTabClient, aThreadClient) { + gThreadClient = aThreadClient; + test_named_function(); + }); + }); + do_test_pending(); +} + +function test_named_function() +{ + gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) { + let args = aPacket.frame.arguments; + + do_check_eq(args[0].class, "Function"); + do_check_eq(args[0].name, "stopMe"); + do_check_eq(args[0].displayName, "stopMe"); + + let objClient = gThreadClient.pauseGrip(args[0]); + objClient.getParameterNames(function (aResponse) { + do_check_eq(aResponse.parameterNames.length, 1); + do_check_eq(aResponse.parameterNames[0], "arg1"); + + gThreadClient.resume(test_inferred_name_function); + }); + + }); + + gDebuggee.eval("stopMe(stopMe)"); +} + +function test_inferred_name_function() { + gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) { + let args = aPacket.frame.arguments; + + do_check_eq(args[0].class, "Function"); + // No name for an anonymous function, but it should have an inferred name. + do_check_eq(args[0].name, undefined); + do_check_eq(args[0].displayName, "o.m"); + + let objClient = gThreadClient.pauseGrip(args[0]); + objClient.getParameterNames(function (aResponse) { + do_check_eq(aResponse.parameterNames.length, 3); + do_check_eq(aResponse.parameterNames[0], "foo"); + do_check_eq(aResponse.parameterNames[1], "bar"); + do_check_eq(aResponse.parameterNames[2], "baz"); + + gThreadClient.resume(test_anonymous_function); + }); + }); + + gDebuggee.eval("var o = { m: function(foo, bar, baz) { } }; stopMe(o.m)"); +} + +function test_anonymous_function() { + gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) { + let args = aPacket.frame.arguments; + + do_check_eq(args[0].class, "Function"); + // No name for an anonymous function, and no inferred name, either. + do_check_eq(args[0].name, undefined); + do_check_eq(args[0].displayName, undefined); + + let objClient = gThreadClient.pauseGrip(args[0]); + objClient.getParameterNames(function (aResponse) { + do_check_eq(aResponse.parameterNames.length, 3); + do_check_eq(aResponse.parameterNames[0], "foo"); + do_check_eq(aResponse.parameterNames[1], "bar"); + do_check_eq(aResponse.parameterNames[2], "baz"); + + gThreadClient.resume(function () { + finishClient(gClient); + }); + }); + }); + + gDebuggee.eval("stopMe(function(foo, bar, baz) { })"); +} + diff --git a/devtools/server/tests/unit/test_get-executable-lines-source-map.js b/devtools/server/tests/unit/test_get-executable-lines-source-map.js new file mode 100644 index 000000000..bca8eebee --- /dev/null +++ b/devtools/server/tests/unit/test_get-executable-lines-source-map.js @@ -0,0 +1,56 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/** + * Test if getExecutableLines return correct information + */ + +var gDebuggee; +var gClient; +var gThreadClient; + +const SOURCE_MAPPED_FILE = getFileUrl("sourcemapped.js"); + +function run_test() { + initTestDebuggerServer(); + gDebuggee = addTestGlobal("test-get-executable-lines"); + gClient = new DebuggerClient(DebuggerServer.connectPipe()); + gClient.connect().then(function _onConnect() { + attachTestTabAndResume( + gClient, + "test-get-executable-lines", + function (aResponse, aTabClient, aThreadClient) { + gThreadClient = aThreadClient; + test_executable_lines(); + } + ); + }); + + do_test_pending(); +} + +function test_executable_lines() { + gThreadClient.addOneTimeListener("newSource", function _onNewSource(evt, packet) { + do_check_eq(evt, "newSource"); + + gThreadClient.getSources(function ({error, sources}) { + do_check_true(!error); + let source = gThreadClient.source(sources[0]); + source.getExecutableLines(function (lines) { + do_check_true(arrays_equal([1, 2, 4, 6], lines)); + finishClient(gClient); + }); + }); + }); + + let code = readFile("sourcemapped.js") + "\n//# sourceMappingURL=" + + getFileUrl("source-map-data/sourcemapped.map"); + + Components.utils.evalInSandbox(code, gDebuggee, "1.8", + SOURCE_MAPPED_FILE, 1); +} + +function arrays_equal(a, b) { + return !(a < b || b < a); +} diff --git a/devtools/server/tests/unit/test_get-executable-lines.js b/devtools/server/tests/unit/test_get-executable-lines.js new file mode 100644 index 000000000..233fb6ada --- /dev/null +++ b/devtools/server/tests/unit/test_get-executable-lines.js @@ -0,0 +1,55 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/** + * Test if getExecutableLines return correct information + */ + +var gDebuggee; +var gClient; +var gThreadClient; + +const SOURCE_MAPPED_FILE = getFileUrl("sourcemapped.js"); + +function run_test() { + initTestDebuggerServer(); + gDebuggee = addTestGlobal("test-get-executable-lines"); + gClient = new DebuggerClient(DebuggerServer.connectPipe()); + gClient.connect().then(function _onConnect() { + attachTestTabAndResume( + gClient, + "test-get-executable-lines", + function (aResponse, aTabClient, aThreadClient) { + gThreadClient = aThreadClient; + test_executable_lines(); + } + ); + }); + + do_test_pending(); +} + +function test_executable_lines() { + gThreadClient.addOneTimeListener("newSource", function _onNewSource(evt, packet) { + do_check_eq(evt, "newSource"); + + gThreadClient.getSources(function ({error, sources}) { + do_check_true(!error); + let source = gThreadClient.source(sources[0]); + source.getExecutableLines(function (lines) { + do_check_true(arrays_equal([2, 5, 7, 8, 10, 12, 14, 16], lines)); + finishClient(gClient); + }); + }); + }); + + let code = readFile("sourcemapped.js"); + + Components.utils.evalInSandbox(code, gDebuggee, "1.8", + SOURCE_MAPPED_FILE, 1); +} + +function arrays_equal(a, b) { + return !(a < b || b < a); +} diff --git a/devtools/server/tests/unit/test_getRuleText.js b/devtools/server/tests/unit/test_getRuleText.js new file mode 100644 index 000000000..fe735928d --- /dev/null +++ b/devtools/server/tests/unit/test_getRuleText.js @@ -0,0 +1,137 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +const {getRuleText} = require("devtools/server/actors/styles"); + +const TEST_DATA = [ + { + desc: "Empty input", + input: "", + line: 1, + column: 1, + throws: true + }, + { + desc: "Simplest test case", + input: "#id{color:red;background:yellow;}", + line: 1, + column: 1, + expected: {offset: 4, text: "color:red;background:yellow;"} + }, + { + desc: "Multiple rules test case", + input: "#id{color:red;background:yellow;}.class-one .class-two { position:absolute; line-height: 45px}", + line: 1, + column: 34, + expected: {offset: 56, text: " position:absolute; line-height: 45px"} + }, + { + desc: "Unclosed rule", + input: "#id{color:red;background:yellow;", + line: 1, + column: 1, + expected: {offset: 4, text: "color:red;background:yellow;"} + }, + { + desc: "Null input", + input: null, + line: 1, + column: 1, + throws: true + }, + { + desc: "Missing loc", + input: "#id{color:red;background:yellow;}", + throws: true + }, + { + desc: "Multi-lines CSS", + input: [ + "/* this is a multi line css */", + "body {", + " color: green;", + " background-repeat: no-repeat", + "}", + " /*something else here */", + "* {", + " color: purple;", + "}" + ].join("\n"), + line: 7, + column: 1, + expected: {offset: 116, text: "\n color: purple;\n"} + }, + { + desc: "Multi-lines CSS and multi-line rule", + input: [ + "/* ", + "* some comments", + "*/", + "", + "body {", + " margin: 0;", + " padding: 15px 15px 2px 15px;", + " color: red;", + "}", + "", + "#header .btn, #header .txt {", + " font-size: 100%;", + "}", + "", + "#header #information {", + " color: #dddddd;", + " font-size: small;", + "}", + ].join("\n"), + line: 5, + column: 1, + expected: { + offset: 30, + text: "\n margin: 0;\n padding: 15px 15px 2px 15px;\n color: red;\n"} + }, + { + desc: "Content string containing a } character", + input: " #id{border:1px solid red;content: '}';color:red;}", + line: 1, + column: 4, + expected: {offset: 7, text: "border:1px solid red;content: '}';color:red;"} + }, + { + desc: "Rule contains no tokens", + input: "div{}", + line: 1, + column: 1, + expected: {offset: 4, text: ""} + }, +]; + +function run_test() { + for (let test of TEST_DATA) { + do_print("Starting test: " + test.desc); + do_print("Input string " + test.input); + let output; + try { + output = getRuleText(test.input, test.line, test.column); + if (test.throws) { + do_print("Test should have thrown"); + do_check_true(false); + } + } catch (e) { + do_print("getRuleText threw an exception with the given input string"); + if (test.throws) { + do_print("Exception expected"); + do_check_true(true); + } else { + do_print("Exception unexpected\n" + e); + do_check_true(false); + } + } + if (output) { + deepEqual(output, test.expected); + } + } +} diff --git a/devtools/server/tests/unit/test_getTextAtLineColumn.js b/devtools/server/tests/unit/test_getTextAtLineColumn.js new file mode 100644 index 000000000..16ec47608 --- /dev/null +++ b/devtools/server/tests/unit/test_getTextAtLineColumn.js @@ -0,0 +1,35 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +const {getTextAtLineColumn} = require("devtools/server/actors/styles"); + +const TEST_DATA = [ + { + desc: "simplest", + input: "#id{color:red;background:yellow;}", + line: 1, + column: 5, + expected: {offset: 4, text: "color:red;background:yellow;}"} + }, + { + desc: "multiple lines", + input: "one\n two\n three", + line: 3, + column: 3, + expected: {offset: 11, text: "three"} + }, +]; + +function run_test() { + for (let test of TEST_DATA) { + do_print("Starting test: " + test.desc); + do_print("Input string " + test.input); + + let output = getTextAtLineColumn(test.input, test.line, test.column); + deepEqual(output, test.expected); + } +} diff --git a/devtools/server/tests/unit/test_getyoungestframe.js b/devtools/server/tests/unit/test_getyoungestframe.js new file mode 100644 index 000000000..035ab5b0c --- /dev/null +++ b/devtools/server/tests/unit/test_getyoungestframe.js @@ -0,0 +1,30 @@ +function run_test() +{ + Components.utils.import("resource://gre/modules/jsdebugger.jsm"); + addDebuggerToGlobal(this); + var xpcInspector = Cc["@mozilla.org/jsinspector;1"].getService(Ci.nsIJSInspector); + var g = testGlobal("test1"); + + var dbg = new Debugger(); + dbg.uncaughtExceptionHook = testExceptionHook; + + dbg.addDebuggee(g); + dbg.onDebuggerStatement = function (aFrame) { + do_check_true(aFrame === dbg.getNewestFrame()); + // Execute from the nested event loop, dbg.getNewestFrame() won't + // be working anymore. + + do_execute_soon(function () { + try { + do_check_true(aFrame === dbg.getNewestFrame()); + } finally { + xpcInspector.exitNestedEventLoop("test"); + } + }); + xpcInspector.enterNestedEventLoop("test"); + }; + + g.eval("function debuggerStatement() { debugger; }; debuggerStatement();"); + + dbg.enabled = false; +} diff --git a/devtools/server/tests/unit/test_ignore_caught_exceptions.js b/devtools/server/tests/unit/test_ignore_caught_exceptions.js new file mode 100644 index 000000000..a4b221823 --- /dev/null +++ b/devtools/server/tests/unit/test_ignore_caught_exceptions.js @@ -0,0 +1,50 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Test that setting ignoreCaughtExceptions will cause the debugger to ignore + * caught exceptions, but not uncaught ones. + */ + +var gDebuggee; +var gClient; +var gThreadClient; + +function run_test() +{ + initTestDebuggerServer(); + gDebuggee = addTestGlobal("test-stack"); + gClient = new DebuggerClient(DebuggerServer.connectPipe()); + gClient.connect().then(function () { + attachTestTabAndResume(gClient, "test-stack", function (aResponse, aTabClient, aThreadClient) { + gThreadClient = aThreadClient; + test_pause_frame(); + }); + }); + do_test_pending(); +} + +function test_pause_frame() +{ + gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) { + gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) { + do_check_eq(aPacket.why.type, "exception"); + do_check_eq(aPacket.why.exception, "bar"); + gThreadClient.resume(function () { + finishClient(gClient); + }); + }); + gThreadClient.pauseOnExceptions(true, true); + gThreadClient.resume(); + }); + + try { + gDebuggee.eval("(" + function () { + debugger; + try { + throw "foo"; + } catch (e) {} + throw "bar"; + } + ")()"); + } catch (e) {} +} diff --git a/devtools/server/tests/unit/test_ignore_no_interface_exceptions.js b/devtools/server/tests/unit/test_ignore_no_interface_exceptions.js new file mode 100644 index 000000000..5aaa31de3 --- /dev/null +++ b/devtools/server/tests/unit/test_ignore_no_interface_exceptions.js @@ -0,0 +1,54 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Test that the debugger automatically ignores NS_ERROR_NO_INTERFACE + * exceptions, but not normal ones. + */ + + +var gDebuggee; +var gClient; +var gThreadClient; + +function run_test() +{ + initTestDebuggerServer(); + gDebuggee = addTestGlobal("test-no-interface"); + gClient = new DebuggerClient(DebuggerServer.connectPipe()); + gClient.connect().then(function () { + attachTestTabAndResume(gClient, "test-no-interface", function (aResponse, aTabClient, aThreadClient) { + gThreadClient = aThreadClient; + test_pause_frame(); + }); + }); + do_test_pending(); +} + +function test_pause_frame() +{ + gThreadClient.pauseOnExceptions(true, false, function () { + gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) { + do_check_eq(aPacket.why.type, "exception"); + do_check_eq(aPacket.why.exception, 42); + gThreadClient.resume(function () { + finishClient(gClient); + }); + }); + + gDebuggee.eval("(" + function () { + function QueryInterface() { + throw Components.results.NS_ERROR_NO_INTERFACE; + } + function stopMe() { + throw 42; + } + try { + QueryInterface(); + } catch (e) {} + try { + stopMe(); + } catch (e) {} + } + ")()"); + }); +} diff --git a/devtools/server/tests/unit/test_interrupt.js b/devtools/server/tests/unit/test_interrupt.js new file mode 100644 index 000000000..34835cc0a --- /dev/null +++ b/devtools/server/tests/unit/test_interrupt.js @@ -0,0 +1,50 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +var gClient; +var gDebuggee; + +function run_test() +{ + initTestDebuggerServer(); + gDebuggee = testGlobal("test-1"); + DebuggerServer.addTestGlobal(gDebuggee); + + let transport = DebuggerServer.connectPipe(); + gClient = new DebuggerClient(transport); + gClient.connect().then(function (aType, aTraits) { + attachTestTab(gClient, "test-1", test_attach); + }); + do_test_pending(); +} + +function test_attach(aResponse, aTabClient) +{ + aTabClient.attachThread({}, function (aResponse, aThreadClient) { + do_check_eq(aThreadClient.paused, true); + aThreadClient.resume(function () { + test_interrupt(aThreadClient); + }); + }); +} + +function test_interrupt(aThreadClient) +{ + do_check_eq(aThreadClient.paused, false); + aThreadClient.interrupt(function (aResponse) { + do_check_eq(aThreadClient.paused, true); + aThreadClient.resume(function () { + do_check_eq(aThreadClient.paused, false); + cleanup(); + }); + }); +} + +function cleanup() +{ + gClient.addListener("closed", function (aEvent) { + do_test_finished(); + }); + gClient.close(); +} + diff --git a/devtools/server/tests/unit/test_layout-reflows-observer.js b/devtools/server/tests/unit/test_layout-reflows-observer.js new file mode 100644 index 000000000..ff6c07b26 --- /dev/null +++ b/devtools/server/tests/unit/test_layout-reflows-observer.js @@ -0,0 +1,286 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +// Test the LayoutChangesObserver + +var { + getLayoutChangesObserver, + releaseLayoutChangesObserver, + LayoutChangesObserver +} = require("devtools/server/actors/reflow"); + +// Override set/clearTimeout on LayoutChangesObserver to avoid depending on +// time in this unit test. This means that LayoutChangesObserver.eventLoopTimer +// will be the timeout callback instead of the timeout itself, so test cases +// will need to execute it to fake a timeout +LayoutChangesObserver.prototype._setTimeout = cb => cb; +LayoutChangesObserver.prototype._clearTimeout = function () {}; + +// Mock the tabActor since we only really want to test the LayoutChangesObserver +// and don't want to depend on a window object, nor want to test protocol.js +function MockTabActor() { + this.window = new MockWindow(); + this.windows = [this.window]; + this.attached = true; +} + +function MockWindow() {} +MockWindow.prototype = { + QueryInterface: function () { + let self = this; + return { + getInterface: function () { + return { + QueryInterface: function () { + if (!self.docShell) { + self.docShell = new MockDocShell(); + } + return self.docShell; + } + }; + } + }; + }, + setTimeout: function (cb) { + // Simply return the cb itself so that we can execute it in the test instead + // of depending on a real timeout + return cb; + }, + clearTimeout: function () {} +}; + +function MockDocShell() { + this.observer = null; +} +MockDocShell.prototype = { + addWeakReflowObserver: function (observer) { + this.observer = observer; + }, + removeWeakReflowObserver: function () {}, + get chromeEventHandler() { + return { + addEventListener: (type, cb) => { + if (type === "resize") { + this.resizeCb = cb; + } + }, + removeEventListener: (type, cb) => { + if (type === "resize" && cb === this.resizeCb) { + this.resizeCb = null; + } + } + }; + }, + mockResize: function () { + if (this.resizeCb) { + this.resizeCb(); + } + } +}; + +function run_test() { + instancesOfObserversAreSharedBetweenWindows(); + eventsAreBatched(); + noEventsAreSentWhenThereAreNoReflowsAndLoopTimeouts(); + observerIsAlreadyStarted(); + destroyStopsObserving(); + stoppingAndStartingSeveralTimesWorksCorrectly(); + reflowsArentStackedWhenStopped(); + stackedReflowsAreResetOnStop(); +} + +function instancesOfObserversAreSharedBetweenWindows() { + do_print("Checking that when requesting twice an instances of the observer " + + "for the same TabActor, the instance is shared"); + + do_print("Checking 2 instances of the observer for the tabActor 1"); + let tabActor1 = new MockTabActor(); + let obs11 = getLayoutChangesObserver(tabActor1); + let obs12 = getLayoutChangesObserver(tabActor1); + do_check_eq(obs11, obs12); + + do_print("Checking 2 instances of the observer for the tabActor 2"); + let tabActor2 = new MockTabActor(); + let obs21 = getLayoutChangesObserver(tabActor2); + let obs22 = getLayoutChangesObserver(tabActor2); + do_check_eq(obs21, obs22); + + do_print("Checking that observers instances for 2 different tabActors are " + + "different"); + do_check_neq(obs11, obs21); + + releaseLayoutChangesObserver(tabActor1); + releaseLayoutChangesObserver(tabActor1); + releaseLayoutChangesObserver(tabActor2); + releaseLayoutChangesObserver(tabActor2); +} + +function eventsAreBatched() { + do_print("Checking that reflow events are batched and only sent when the " + + "timeout expires"); + + // Note that in this test, we mock the TabActor and its window property, so we + // also mock the setTimeout/clearTimeout mechanism and just call the callback + // manually + let tabActor = new MockTabActor(); + let observer = getLayoutChangesObserver(tabActor); + + let reflowsEvents = []; + let onReflows = (event, reflows) => reflowsEvents.push(reflows); + observer.on("reflows", onReflows); + + let resizeEvents = []; + let onResize = () => resizeEvents.push("resize"); + observer.on("resize", onResize); + + do_print("Fake one reflow event"); + tabActor.window.docShell.observer.reflow(); + do_print("Checking that no batched reflow event has been emitted"); + do_check_eq(reflowsEvents.length, 0); + + do_print("Fake another reflow event"); + tabActor.window.docShell.observer.reflow(); + do_print("Checking that still no batched reflow event has been emitted"); + do_check_eq(reflowsEvents.length, 0); + + do_print("Fake a few of resize events too"); + tabActor.window.docShell.mockResize(); + tabActor.window.docShell.mockResize(); + tabActor.window.docShell.mockResize(); + do_print("Checking that still no batched resize event has been emitted"); + do_check_eq(resizeEvents.length, 0); + + do_print("Faking timeout expiration and checking that events are sent"); + observer.eventLoopTimer(); + do_check_eq(reflowsEvents.length, 1); + do_check_eq(reflowsEvents[0].length, 2); + do_check_eq(resizeEvents.length, 1); + + observer.off("reflows", onReflows); + observer.off("resize", onResize); + releaseLayoutChangesObserver(tabActor); +} + +function noEventsAreSentWhenThereAreNoReflowsAndLoopTimeouts() { + do_print("Checking that if no reflows were detected and the event batching " + + "loop expires, then no reflows event is sent"); + + let tabActor = new MockTabActor(); + let observer = getLayoutChangesObserver(tabActor); + + let reflowsEvents = []; + let onReflows = (event, reflows) => reflowsEvents.push(reflows); + observer.on("reflows", onReflows); + + do_print("Faking timeout expiration and checking for reflows"); + observer.eventLoopTimer(); + do_check_eq(reflowsEvents.length, 0); + + observer.off("reflows", onReflows); + releaseLayoutChangesObserver(tabActor); +} + +function observerIsAlreadyStarted() { + do_print("Checking that the observer is already started when getting it"); + + let tabActor = new MockTabActor(); + let observer = getLayoutChangesObserver(tabActor); + do_check_true(observer.isObserving); + + observer.stop(); + do_check_false(observer.isObserving); + + observer.start(); + do_check_true(observer.isObserving); + + releaseLayoutChangesObserver(tabActor); +} + +function destroyStopsObserving() { + do_print("Checking that the destroying the observer stops it"); + + let tabActor = new MockTabActor(); + let observer = getLayoutChangesObserver(tabActor); + do_check_true(observer.isObserving); + + observer.destroy(); + do_check_false(observer.isObserving); + + releaseLayoutChangesObserver(tabActor); +} + +function stoppingAndStartingSeveralTimesWorksCorrectly() { + do_print("Checking that the stopping and starting several times the observer" + + " works correctly"); + + let tabActor = new MockTabActor(); + let observer = getLayoutChangesObserver(tabActor); + + do_check_true(observer.isObserving); + observer.start(); + observer.start(); + observer.start(); + do_check_true(observer.isObserving); + + observer.stop(); + do_check_false(observer.isObserving); + + observer.stop(); + observer.stop(); + do_check_false(observer.isObserving); + + releaseLayoutChangesObserver(tabActor); +} + +function reflowsArentStackedWhenStopped() { + do_print("Checking that when stopped, reflows aren't stacked in the observer"); + + let tabActor = new MockTabActor(); + let observer = getLayoutChangesObserver(tabActor); + + do_print("Stoping the observer"); + observer.stop(); + + do_print("Faking reflows"); + tabActor.window.docShell.observer.reflow(); + tabActor.window.docShell.observer.reflow(); + tabActor.window.docShell.observer.reflow(); + + do_print("Checking that reflows aren't recorded"); + do_check_eq(observer.reflows.length, 0); + + do_print("Starting the observer and faking more reflows"); + observer.start(); + tabActor.window.docShell.observer.reflow(); + tabActor.window.docShell.observer.reflow(); + tabActor.window.docShell.observer.reflow(); + + do_print("Checking that reflows are recorded"); + do_check_eq(observer.reflows.length, 3); + + releaseLayoutChangesObserver(tabActor); +} + +function stackedReflowsAreResetOnStop() { + do_print("Checking that stacked reflows are reset on stop"); + + let tabActor = new MockTabActor(); + let observer = getLayoutChangesObserver(tabActor); + + tabActor.window.docShell.observer.reflow(); + do_check_eq(observer.reflows.length, 1); + + observer.stop(); + do_check_eq(observer.reflows.length, 0); + + tabActor.window.docShell.observer.reflow(); + do_check_eq(observer.reflows.length, 0); + + observer.start(); + do_check_eq(observer.reflows.length, 0); + + tabActor.window.docShell.observer.reflow(); + do_check_eq(observer.reflows.length, 1); + + releaseLayoutChangesObserver(tabActor); +} diff --git a/devtools/server/tests/unit/test_listsources-01.js b/devtools/server/tests/unit/test_listsources-01.js new file mode 100644 index 000000000..231e6a1e4 --- /dev/null +++ b/devtools/server/tests/unit/test_listsources-01.js @@ -0,0 +1,59 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Check basic getSources functionality. + */ + +var gDebuggee; +var gClient; +var gThreadClient; + +var gNumTimesSourcesSent = 0; + +function run_test() +{ + initTestDebuggerServer(); + gDebuggee = addTestGlobal("test-stack"); + gClient = new DebuggerClient(DebuggerServer.connectPipe()); + gClient.request = (function (request) { + return function (aRequest, aOnResponse) { + if (aRequest.type === "sources") { + ++gNumTimesSourcesSent; + } + return request.call(this, aRequest, aOnResponse); + }; + }(gClient.request)); + gClient.connect().then(function () { + attachTestTabAndResume(gClient, "test-stack", function (aResponse, aTabClient, aThreadClient) { + gThreadClient = aThreadClient; + test_simple_listsources(); + }); + }); + do_test_pending(); +} + +function test_simple_listsources() +{ + gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) { + gThreadClient.getSources(function (aResponse) { + do_check_true(aResponse.sources.some(function (s) { + return s.url && s.url.match(/test_listsources-01.js/); + })); + + do_check_true(gNumTimesSourcesSent <= 1, + "Should only send one sources request at most, even though we" + + " might have had to send one to determine feature support."); + + gThreadClient.resume(function () { + finishClient(gClient); + }); + }); + }); + + Components.utils.evalInSandbox("var line0 = Error().lineNumber;\n" + + "debugger;\n" + // line0 + 1 + "var a = 1;\n" + // line0 + 2 + "var b = 2;\n", // line0 + 3 + gDebuggee); +} diff --git a/devtools/server/tests/unit/test_listsources-02.js b/devtools/server/tests/unit/test_listsources-02.js new file mode 100644 index 000000000..190a5e31b --- /dev/null +++ b/devtools/server/tests/unit/test_listsources-02.js @@ -0,0 +1,49 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Check getting sources before there are any. + */ + +var gDebuggee; +var gClient; +var gThreadClient; + +var gNumTimesSourcesSent = 0; + +function run_test() +{ + initTestDebuggerServer(); + gDebuggee = addTestGlobal("test-stack"); + gClient = new DebuggerClient(DebuggerServer.connectPipe()); + gClient.request = (function (request) { + return function (aRequest, aOnResponse) { + if (aRequest.type === "sources") { + ++gNumTimesSourcesSent; + } + return request.call(this, aRequest, aOnResponse); + }; + }(gClient.request)); + gClient.connect().then(function () { + attachTestTabAndResume(gClient, "test-stack", function (aResponse, aTabClient, aThreadClient) { + gThreadClient = aThreadClient; + test_listing_zero_sources(); + }); + }); + do_test_pending(); +} + +function test_listing_zero_sources() +{ + gThreadClient.getSources(function (aPacket) { + do_check_true(!aPacket.error); + do_check_true(!!aPacket.sources); + do_check_eq(aPacket.sources.length, 0); + + do_check_true(gNumTimesSourcesSent <= 1, + "Should only send one sources request at most, even though we" + + " might have had to send one to determine feature support."); + + finishClient(gClient); + }); +} diff --git a/devtools/server/tests/unit/test_listsources-03.js b/devtools/server/tests/unit/test_listsources-03.js new file mode 100644 index 000000000..72ebb5e1c --- /dev/null +++ b/devtools/server/tests/unit/test_listsources-03.js @@ -0,0 +1,52 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Check getSources functionality when there are lots of sources. + */ + +var gDebuggee; +var gClient; +var gThreadClient; + +function run_test() +{ + initTestDebuggerServer(); + gDebuggee = addTestGlobal("test-sources"); + gClient = new DebuggerClient(DebuggerServer.connectPipe()); + gClient.connect().then(function () { + attachTestTabAndResume(gClient, "test-sources", function (aResponse, aTabClient, aThreadClient) { + gThreadClient = aThreadClient; + test_simple_listsources(); + }); + }); + do_test_pending(); +} + +function test_simple_listsources() +{ + gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) { + gThreadClient.getSources(function (aResponse) { + do_check_true( + !aResponse.error, + "There shouldn't be an error fetching large amounts of sources."); + + do_check_true(aResponse.sources.some(function (s) { + return s.url.match(/foo-999.js$/); + })); + + gThreadClient.resume(function () { + finishClient(gClient); + }); + }); + }); + + for (let i = 0; i < 1000; i++) { + Cu.evalInSandbox("function foo###() {return ###;}".replace(/###/g, i), + gDebuggee, + "1.8", + "http://example.com/foo-" + i + ".js", + 1); + } + gDebuggee.eval("debugger;"); +} diff --git a/devtools/server/tests/unit/test_listsources-04.js b/devtools/server/tests/unit/test_listsources-04.js new file mode 100644 index 000000000..6da99a6ce --- /dev/null +++ b/devtools/server/tests/unit/test_listsources-04.js @@ -0,0 +1,58 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Check getSources functionality with sourcemaps. + */ + +const {SourceNode} = require("source-map"); + +function run_test() { + run_test_with_server(DebuggerServer, function () { + // Bug 1304144 - This test does not run in a worker because the + // `rpc` method which talks to the main thread does not work. + // run_test_with_server(WorkerDebuggerServer, do_test_finished); + do_test_finished(); + }); + do_test_pending(); +} + +function run_test_with_server(server, cb) { + Task.spawn(function*() { + initTestDebuggerServer(server); + const debuggee = addTestGlobal("test-sources", server); + const client = new DebuggerClient(server.connectPipe()); + yield client.connect(); + const [,,threadClient] = yield attachTestTabAndResume(client, "test-sources"); + + yield threadClient.reconfigure({ useSourceMaps: true }); + addSources(debuggee); + + threadClient.getSources(Task.async(function* (res) { + do_check_true(res.sources.length === 3, "3 sources exist"); + + yield threadClient.reconfigure({ useSourceMaps: false }); + + threadClient.getSources(function(res) { + do_check_true(res.sources.length === 1, "1 source exist"); + client.close().then(cb); + }); + })); + }); +} + +function addSources(debuggee) { + let { code, map } = (new SourceNode(null, null, null, [ + new SourceNode(1, 0, "a.js", "function a() { return 'a'; }\n"), + new SourceNode(1, 0, "b.js", "function b() { return 'b'; }\n"), + new SourceNode(1, 0, "c.js", "function c() { return 'c'; }\n"), + ])).toStringWithSourceMap({ + file: "abc.js", + sourceRoot: "http://example.com/www/js/" + }); + + code += "//# sourceMappingURL=data:text/json;base64," + btoa(map.toString()); + + Components.utils.evalInSandbox(code, debuggee, "1.8", + "http://example.com/www/js/abc.js", 1); +} diff --git a/devtools/server/tests/unit/test_longstringactor.js b/devtools/server/tests/unit/test_longstringactor.js new file mode 100644 index 000000000..18b928910 --- /dev/null +++ b/devtools/server/tests/unit/test_longstringactor.js @@ -0,0 +1,104 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2; js-indent-level: 2 -*- */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +const { LongStringActor } = require("devtools/server/actors/object"); + +function run_test() { + test_LSA_disconnect(); + test_LSA_grip(); + test_LSA_onSubstring(); +} + +const TEST_STRING = "This is a very long string!"; + +function makeMockLongStringActor() +{ + let string = TEST_STRING; + let actor = new LongStringActor(string); + actor.actorID = "longString1"; + actor.registeredPool = { + longStringActors: { + [string]: actor + } + }; + return actor; +} + +function test_LSA_disconnect() +{ + let actor = makeMockLongStringActor(); + do_check_eq(actor.registeredPool.longStringActors[TEST_STRING], actor); + + actor.disconnect(); + do_check_eq(actor.registeredPool.longStringActors[TEST_STRING], void 0); +} + +function test_LSA_substring() +{ + let actor = makeMockLongStringActor(); + do_check_eq(actor._substring(0, 4), TEST_STRING.substring(0, 4)); + do_check_eq(actor._substring(6, 9), TEST_STRING.substring(6, 9)); + do_check_eq(actor._substring(0, TEST_STRING.length), TEST_STRING); +} + +function test_LSA_grip() +{ + let actor = makeMockLongStringActor(); + + let grip = actor.grip(); + do_check_eq(grip.type, "longString"); + do_check_eq(grip.initial, TEST_STRING.substring(0, DebuggerServer.LONG_STRING_INITIAL_LENGTH)); + do_check_eq(grip.length, TEST_STRING.length); + do_check_eq(grip.actor, actor.actorID); +} + +function test_LSA_onSubstring() +{ + let actor = makeMockLongStringActor(); + let response; + + // From the start + response = actor.onSubstring({ + start: 0, + end: 4 + }); + do_check_eq(response.from, actor.actorID); + do_check_eq(response.substring, TEST_STRING.substring(0, 4)); + + // In the middle + response = actor.onSubstring({ + start: 5, + end: 8 + }); + do_check_eq(response.from, actor.actorID); + do_check_eq(response.substring, TEST_STRING.substring(5, 8)); + + // Whole string + response = actor.onSubstring({ + start: 0, + end: TEST_STRING.length + }); + do_check_eq(response.from, actor.actorID); + do_check_eq(response.substring, TEST_STRING); + + // Negative index + response = actor.onSubstring({ + start: -5, + end: TEST_STRING.length + }); + do_check_eq(response.from, actor.actorID); + do_check_eq(response.substring, + TEST_STRING.substring(-5, TEST_STRING.length)); + + // Past the end + response = actor.onSubstring({ + start: TEST_STRING.length - 5, + end: 100 + }); + do_check_eq(response.from, actor.actorID); + do_check_eq(response.substring, + TEST_STRING.substring(TEST_STRING.length - 5, 100)); +} diff --git a/devtools/server/tests/unit/test_longstringgrips-01.js b/devtools/server/tests/unit/test_longstringgrips-01.js new file mode 100644 index 000000000..b8e6789c7 --- /dev/null +++ b/devtools/server/tests/unit/test_longstringgrips-01.js @@ -0,0 +1,71 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +var gDebuggee; +var gClient; +var gThreadClient; + +function run_test() +{ + initTestDebuggerServer(); + gDebuggee = addTestGlobal("test-grips"); + gDebuggee.eval(function stopMe(arg1) { + debugger; + }.toString()); + + gClient = new DebuggerClient(DebuggerServer.connectPipe()); + gClient.connect().then(function () { + attachTestTabAndResume(gClient, "test-grips", function (aResponse, aTabClient, aThreadClient) { + gThreadClient = aThreadClient; + test_longstring_grip(); + }); + }); + do_test_pending(); +} + +function test_longstring_grip() +{ + let longString = "All I want is to be a monkey of moderate intelligence who" + + " wears a suit... that's why I'm transferring to business school! Maybe I" + + " love you so much, I love you no matter who you are pretending to be." + + " Enough about your promiscuous mother, Hermes! We have bigger problems." + + " For example, if you killed your grandfather, you'd cease to exist! What" + + " kind of a father would I be if I said no? Yep, I remember. They came in" + + " last at the Olympics, then retired to promote alcoholic beverages! And" + + " remember, don't do anything that affects anything, unless it turns out" + + " you were supposed to, in which case, for the love of God, don't not do" + + " it!"; + + DebuggerServer.LONG_STRING_LENGTH = 200; + + gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) { + let args = aPacket.frame.arguments; + do_check_eq(args.length, 1); + let grip = args[0]; + + try { + do_check_eq(grip.type, "longString"); + do_check_eq(grip.length, longString.length); + do_check_eq(grip.initial, longString.substr(0, DebuggerServer.LONG_STRING_INITIAL_LENGTH)); + + let longStringClient = gThreadClient.pauseLongString(grip); + longStringClient.substring(22, 28, function (aResponse) { + try { + do_check_eq(aResponse.substring, "monkey"); + } finally { + gThreadClient.resume(function () { + finishClient(gClient); + }); + } + }); + } catch (error) { + gThreadClient.resume(function () { + finishClient(gClient); + do_throw(error); + }); + } + }); + + gDebuggee.eval('stopMe("' + longString + '")'); +} + diff --git a/devtools/server/tests/unit/test_longstringgrips-02.js b/devtools/server/tests/unit/test_longstringgrips-02.js new file mode 100644 index 000000000..01f9c1b8f --- /dev/null +++ b/devtools/server/tests/unit/test_longstringgrips-02.js @@ -0,0 +1,60 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +var gDebuggee; +var gClient; +var gThreadClient; + +function run_test() +{ + initTestDebuggerServer(); + gDebuggee = addTestGlobal("test-grips"); + gDebuggee.eval(function stopMe(arg1) { + debugger; + }.toString()); + + gClient = new DebuggerClient(DebuggerServer.connectPipe()); + gClient.connect().then(function () { + attachTestTabAndResume( + gClient, "test-grips", function (aResponse, aTabClient, aThreadClient) { + gThreadClient = aThreadClient; + test_longstring_grip(); + }); + }); + do_test_pending(); +} + +function test_longstring_grip() +{ + DebuggerServer.LONG_STRING_LENGTH = 200; + + gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) { + try { + let fakeLongStringGrip = { + type: "longString", + length: 1000000, + actor: "123fakeActor123", + initial: "" + }; + let longStringClient = gThreadClient.pauseLongString(fakeLongStringGrip); + longStringClient.substring(22, 28, function (aResponse) { + try { + do_check_true(!!aResponse.error, + "We should not get a response, but an error."); + } finally { + gThreadClient.resume(function () { + finishClient(gClient); + }); + } + }); + } catch (error) { + gThreadClient.resume(function () { + finishClient(gClient); + do_throw(error); + }); + } + }); + + gDebuggee.eval("stopMe()"); +} + diff --git a/devtools/server/tests/unit/test_monitor_actor.js b/devtools/server/tests/unit/test_monitor_actor.js new file mode 100644 index 000000000..17c272d80 --- /dev/null +++ b/devtools/server/tests/unit/test_monitor_actor.js @@ -0,0 +1,76 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Test the monitor actor. + */ + +"use strict"; + +function run_test() +{ + let EventEmitter = require("devtools/shared/event-emitter"); + + function MonitorClient(client, form) { + this.client = client; + this.actor = form.monitorActor; + this.events = ["update"]; + + EventEmitter.decorate(this); + client.registerClient(this); + } + MonitorClient.prototype.destroy = function () { + this.client.unregisterClient(this); + }; + MonitorClient.prototype.start = function (callback) { + this.client.request({ + to: this.actor, + type: "start" + }, callback); + }; + MonitorClient.prototype.stop = function (callback) { + this.client.request({ + to: this.actor, + type: "stop" + }, callback); + }; + + let monitor, client; + + // Start the monitor actor. + get_chrome_actors((c, form) => { + client = c; + monitor = new MonitorClient(client, form); + monitor.on("update", gotUpdate); + monitor.start(update); + }); + + let time = Date.now(); + + function update() { + let event = { + graph: "Test", + curve: "test", + value: 42, + time: time, + }; + Services.obs.notifyObservers(null, "devtools-monitor-update", JSON.stringify(event)); + } + + function gotUpdate(type, packet) { + packet.data.forEach(function (event) { + // Ignore updates that were not sent by this test. + if (event.graph === "Test") { + do_check_eq(event.curve, "test"); + do_check_eq(event.value, 42); + do_check_eq(event.time, time); + monitor.stop(function (aResponse) { + monitor.destroy(); + finishClient(client); + }); + } + }); + } + + do_test_pending(); +} diff --git a/devtools/server/tests/unit/test_nativewrappers.js b/devtools/server/tests/unit/test_nativewrappers.js new file mode 100644 index 000000000..fbadfcdec --- /dev/null +++ b/devtools/server/tests/unit/test_nativewrappers.js @@ -0,0 +1,30 @@ +function run_test() +{ + Components.utils.import("resource://gre/modules/jsdebugger.jsm"); + addDebuggerToGlobal(this); + var g = testGlobal("test1"); + + var dbg = new Debugger(); + dbg.addDebuggee(g); + dbg.onDebuggerStatement = function (aFrame) { + let args = aFrame.arguments; + try { + args[0]; + do_check_true(true); + } catch (ex) { + do_check_true(false); + } + }; + + g.eval("function stopMe(arg) {debugger;}"); + + g2 = testGlobal("test2"); + g2.g = g; + g2.eval("(" + function createBadEvent() { + let parser = Components.classes["@mozilla.org/xmlextras/domparser;1"].createInstance(Components.interfaces.nsIDOMParser); + let doc = parser.parseFromString("<foo></foo>", "text/xml"); + g.stopMe(doc.createEvent("MouseEvent")); + } + ")()"); + + dbg.enabled = false; +} diff --git a/devtools/server/tests/unit/test_nesting-01.js b/devtools/server/tests/unit/test_nesting-01.js new file mode 100644 index 000000000..e515f051e --- /dev/null +++ b/devtools/server/tests/unit/test_nesting-01.js @@ -0,0 +1,48 @@ +/* -*- js-indent-level: 2; indent-tabs-mode: nil -*- */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +// Test that we can nest event loops when needed in +// ThreadActor.prototype.unsafeSynchronize. + +var gClient; +var gThreadActor; + +function run_test() { + initTestDebuggerServer(); + let gDebuggee = addTestGlobal("test-nesting"); + gClient = new DebuggerClient(DebuggerServer.connectPipe()); + gClient.connect().then(function () { + attachTestTabAndResume(gClient, "test-nesting", function (aResponse, aTabClient, aThreadClient) { + // Reach over the protocol connection and get a reference to the thread actor. + gThreadActor = aThreadClient._transport._serverConnection.getActor(aThreadClient._actor); + + test_nesting(); + }); + }); + do_test_pending(); +} + +function test_nesting() { + const thread = gThreadActor; + const { resolve, reject, promise: p } = promise.defer(); + + let currentStep = 0; + + executeSoon(function () { + // Should be on the first step + do_check_eq(++currentStep, 1); + // We should have one nested event loop from unsfeSynchronize + do_check_eq(thread._nestedEventLoops.size, 1); + resolve(true); + }); + + do_check_eq(thread.unsafeSynchronize(p), true); + + // Should be on the second step + do_check_eq(++currentStep, 2); + // There shouldn't be any nested event loops anymore + do_check_eq(thread._nestedEventLoops.size, 0); + + finishClient(gClient); +} diff --git a/devtools/server/tests/unit/test_nesting-02.js b/devtools/server/tests/unit/test_nesting-02.js new file mode 100644 index 000000000..928331be5 --- /dev/null +++ b/devtools/server/tests/unit/test_nesting-02.js @@ -0,0 +1,81 @@ +/* -*- js-indent-level: 2; indent-tabs-mode: nil -*- */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +// Test that we can nest event loops and then automatically exit nested event +// loops when requested. + +var gClient; +var gThreadActor; + +function run_test() { + initTestDebuggerServer(); + let gDebuggee = addTestGlobal("test-nesting"); + gClient = new DebuggerClient(DebuggerServer.connectPipe()); + gClient.connect().then(function () { + attachTestTabAndResume(gClient, "test-nesting", function (aResponse, aTabClient, aThreadClient) { + // Reach over the protocol connection and get a reference to the thread + // actor. + gThreadActor = aThreadClient._transport._serverConnection.getActor(aThreadClient._actor); + + test_nesting(); + }); + }); + do_test_pending(); +} + +function test_nesting() { + const thread = gThreadActor; + const { resolve, reject, promise: p } = promise.defer(); + + // The following things should happen (in order): + // 1. In the new event loop (created by unsafeSynchronize) + // 2. Resolve the promise (shouldn't exit any event loops) + // 3. Exit the event loop (should also then exit unsafeSynchronize's event loop) + // 4. Be after the unsafeSynchronize call + let currentStep = 0; + + executeSoon(function () { + let eventLoop; + + executeSoon(function () { + // Should be at step 2 + do_check_eq(++currentStep, 2); + // Before resolving, should have the unsafeSynchronize event loop and the + // one just created. + do_check_eq(thread._nestedEventLoops.size, 2); + + executeSoon(function () { + // Should be at step 3 + do_check_eq(++currentStep, 3); + // Before exiting the manually created event loop, should have the + // unsafeSynchronize event loop and the manual event loop. + do_check_eq(thread._nestedEventLoops.size, 2); + // Should have the event loop + do_check_true(!!eventLoop); + eventLoop.resolve(); + }); + + resolve(true); + // Shouldn't exit any event loops because a new one started since the call + // to unsafeSynchronize + do_check_eq(thread._nestedEventLoops.size, 2); + }); + + // Should be at step 1 + do_check_eq(++currentStep, 1); + // Should have only the unsafeSynchronize event loop + do_check_eq(thread._nestedEventLoops.size, 1); + eventLoop = thread._nestedEventLoops.push(); + eventLoop.enter(); + }); + + do_check_eq(thread.unsafeSynchronize(p), true); + + // Should be on the fourth step + do_check_eq(++currentStep, 4); + // There shouldn't be any nested event loops anymore + do_check_eq(thread._nestedEventLoops.size, 0); + + finishClient(gClient); +} diff --git a/devtools/server/tests/unit/test_nesting-03.js b/devtools/server/tests/unit/test_nesting-03.js new file mode 100644 index 000000000..6a0e5a66b --- /dev/null +++ b/devtools/server/tests/unit/test_nesting-03.js @@ -0,0 +1,51 @@ +/* -*- js-indent-level: 2; indent-tabs-mode: nil -*- */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +// Test that we can detect nested event loops in tabs with the same URL. + +var gClient1, gClient2, gThreadClient1, gThreadClient2; + +function run_test() { + initTestDebuggerServer(); + addTestGlobal("test-nesting1"); + addTestGlobal("test-nesting1"); + // Conect the first client to the first debuggee. + gClient1 = new DebuggerClient(DebuggerServer.connectPipe()); + gClient1.connect(function () { + attachTestThread(gClient1, "test-nesting1", function (aResponse, aTabClient, aThreadClient) { + gThreadClient1 = aThreadClient; + start_second_connection(); + }); + }); + do_test_pending(); +} + +function start_second_connection() { + gClient2 = new DebuggerClient(DebuggerServer.connectPipe()); + gClient2.connect(function () { + attachTestThread(gClient2, "test-nesting1", function (aResponse, aTabClient, aThreadClient) { + gThreadClient2 = aThreadClient; + test_nesting(); + }); + }); +} + +function test_nesting() { + const { resolve, reject, promise: p } = promise.defer(); + + gThreadClient1.resume(aResponse => { + do_check_eq(aResponse.error, "wrongOrder"); + gThreadClient2.resume(aResponse => { + do_check_true(!aResponse.error); + do_check_eq(aResponse.from, gThreadClient2.actor); + + gThreadClient1.resume(aResponse => { + do_check_true(!aResponse.error); + do_check_eq(aResponse.from, gThreadClient1.actor); + + gClient1.close(() => finishClient(gClient2)); + }); + }); + }); +} diff --git a/devtools/server/tests/unit/test_new_source-01.js b/devtools/server/tests/unit/test_new_source-01.js new file mode 100644 index 000000000..aa2498371 --- /dev/null +++ b/devtools/server/tests/unit/test_new_source-01.js @@ -0,0 +1,40 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Check basic newSource packet sent from server. + */ + +var gDebuggee; +var gClient; +var gThreadClient; + +function run_test() +{ + initTestDebuggerServer(); + gDebuggee = addTestGlobal("test-stack"); + gClient = new DebuggerClient(DebuggerServer.connectPipe()); + gClient.connect().then(function () { + attachTestTabAndResume(gClient, "test-stack", function (aResponse, aTabClient, aThreadClient) { + gThreadClient = aThreadClient; + test_simple_new_source(); + }); + }); + do_test_pending(); +} + +function test_simple_new_source() +{ + gThreadClient.addOneTimeListener("newSource", function (aEvent, aPacket) { + do_check_eq(aEvent, "newSource"); + do_check_eq(aPacket.type, "newSource"); + do_check_true(!!aPacket.source); + do_check_true(!!aPacket.source.url.match(/test_new_source-01.js$/)); + + finishClient(gClient); + }); + + Components.utils.evalInSandbox(function inc(n) { + return n + 1; + }.toString(), gDebuggee); +} diff --git a/devtools/server/tests/unit/test_nodelistactor.js b/devtools/server/tests/unit/test_nodelistactor.js new file mode 100644 index 000000000..4d9ec1a7a --- /dev/null +++ b/devtools/server/tests/unit/test_nodelistactor.js @@ -0,0 +1,26 @@ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ +"use strict"; + +// Test that a NodeListActor initialized with null nodelist doesn't cause +// exceptions when calling NodeListActor.form. + +const { NodeListActor } = require("devtools/server/actors/inspector"); + +function run_test() { + check_actor_for_list(null); + check_actor_for_list([]); + check_actor_for_list(["fakenode"]); +} + +function check_actor_for_list(nodelist) { + do_print("Checking NodeListActor with nodelist '" + nodelist + "' works."); + let actor = new NodeListActor({}, nodelist); + let form = actor.form(); + + // No exception occured as a exceptions abort the test. + ok(true, "No exceptions occured."); + equal(form.length, nodelist ? nodelist.length : 0, + "NodeListActor reported correct length."); +} diff --git a/devtools/server/tests/unit/test_nsjsinspector.js b/devtools/server/tests/unit/test_nsjsinspector.js new file mode 100644 index 000000000..14a99a308 --- /dev/null +++ b/devtools/server/tests/unit/test_nsjsinspector.js @@ -0,0 +1,59 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +// Test the basic functionality of the nsIJSInspector component. +var gCount = 0; +const MAX = 10; +var inspector = Cc["@mozilla.org/jsinspector;1"].getService(Ci.nsIJSInspector); +var tm = Cc["@mozilla.org/thread-manager;1"].getService(Ci.nsIThreadManager); + +// Emulate 10 simultaneously-debugged windows from 3 separate client connections. +var requestor = (count) => ({ + url:"http://foo/bar/" + count, + connection: "conn" + (count % 3) +}); + +function run_test() +{ + test_nesting(); +} + +function test_nesting() +{ + do_check_eq(inspector.eventLoopNestLevel, 0); + + tm.currentThread.dispatch({ run: enterEventLoop}, 0); + + do_check_eq(inspector.enterNestedEventLoop(requestor(gCount)), 0); + do_check_eq(inspector.eventLoopNestLevel, 0); + do_check_eq(inspector.lastNestRequestor, null); +} + +function enterEventLoop() { + if (gCount++ < MAX) { + tm.currentThread.dispatch({ run: enterEventLoop}, 0); + + let r = Object.create(requestor(gCount)); + + do_check_eq(inspector.eventLoopNestLevel, gCount); + do_check_eq(inspector.lastNestRequestor.url, requestor(gCount - 1).url); + do_check_eq(inspector.lastNestRequestor.connection, requestor(gCount - 1).connection); + do_check_eq(inspector.enterNestedEventLoop(requestor(gCount)), gCount); + } else { + do_check_eq(gCount, MAX + 1); + tm.currentThread.dispatch({ run: exitEventLoop}, 0); + } +} + +function exitEventLoop() { + if (inspector.lastNestRequestor != null) { + do_check_eq(inspector.lastNestRequestor.url, requestor(gCount - 1).url); + do_check_eq(inspector.lastNestRequestor.connection, requestor(gCount - 1).connection); + if (gCount-- > 1) { + tm.currentThread.dispatch({ run: exitEventLoop}, 0); + } + + do_check_eq(inspector.exitNestedEventLoop(), gCount); + do_check_eq(inspector.eventLoopNestLevel, gCount); + } +} diff --git a/devtools/server/tests/unit/test_objectgrips-01.js b/devtools/server/tests/unit/test_objectgrips-01.js new file mode 100644 index 000000000..e1857e5b8 --- /dev/null +++ b/devtools/server/tests/unit/test_objectgrips-01.js @@ -0,0 +1,58 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +var gDebuggee; +var gClient; +var gThreadClient; +var gCallback; + +function run_test() +{ + run_test_with_server(DebuggerServer, function () { + run_test_with_server(WorkerDebuggerServer, do_test_finished); + }); + do_test_pending(); +} + +function run_test_with_server(aServer, aCallback) +{ + gCallback = aCallback; + initTestDebuggerServer(aServer); + gDebuggee = addTestGlobal("test-grips", aServer); + gDebuggee.eval(function stopMe(arg1) { + debugger; + }.toString()); + + gClient = new DebuggerClient(aServer.connectPipe()); + gClient.connect().then(function () { + attachTestTabAndResume(gClient, "test-grips", function (aResponse, aTabClient, aThreadClient) { + gThreadClient = aThreadClient; + test_object_grip(); + }); + }); +} + +function test_object_grip() +{ + gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) { + let args = aPacket.frame.arguments; + + do_check_eq(args[0].class, "Object"); + + let objClient = gThreadClient.pauseGrip(args[0]); + objClient.getOwnPropertyNames(function (aResponse) { + do_check_eq(aResponse.ownPropertyNames.length, 3); + do_check_eq(aResponse.ownPropertyNames[0], "a"); + do_check_eq(aResponse.ownPropertyNames[1], "b"); + do_check_eq(aResponse.ownPropertyNames[2], "c"); + + gThreadClient.resume(function () { + gClient.close().then(gCallback); + }); + }); + + }); + + gDebuggee.eval("stopMe({ a: 1, b: true, c: 'foo' })"); +} + diff --git a/devtools/server/tests/unit/test_objectgrips-02.js b/devtools/server/tests/unit/test_objectgrips-02.js new file mode 100644 index 000000000..649d52c64 --- /dev/null +++ b/devtools/server/tests/unit/test_objectgrips-02.js @@ -0,0 +1,65 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +var gDebuggee; +var gClient; +var gThreadClient; +var gCallback; + +function run_test() +{ + run_test_with_server(DebuggerServer, function () { + run_test_with_server(WorkerDebuggerServer, do_test_finished); + }); + do_test_pending(); +} + +function run_test_with_server(aServer, aCallback) +{ + gCallback = aCallback; + initTestDebuggerServer(aServer); + gDebuggee = addTestGlobal("test-grips", aServer); + gDebuggee.eval(function stopMe(arg1) { + debugger; + }.toString()); + + gClient = new DebuggerClient(aServer.connectPipe()); + gClient.connect().then(function () { + attachTestTabAndResume(gClient, "test-grips", function (aResponse, aTabClient, aThreadClient) { + gThreadClient = aThreadClient; + test_object_grip(); + }); + }); +} + +function test_object_grip() +{ + gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) { + let args = aPacket.frame.arguments; + + do_check_eq(args[0].class, "Object"); + + let objClient = gThreadClient.pauseGrip(args[0]); + objClient.getPrototype(function (aResponse) { + do_check_true(aResponse.prototype != undefined); + + let protoClient = gThreadClient.pauseGrip(aResponse.prototype); + protoClient.getOwnPropertyNames(function (aResponse) { + do_check_eq(aResponse.ownPropertyNames.length, 2); + do_check_eq(aResponse.ownPropertyNames[0], "b"); + do_check_eq(aResponse.ownPropertyNames[1], "c"); + + gThreadClient.resume(function () { + gClient.close().then(gCallback); + }); + }); + }); + + }); + + gDebuggee.eval(function Constr() { + this.a = 1; + }.toString()); + gDebuggee.eval("Constr.prototype = { b: true, c: 'foo' }; var o = new Constr(); stopMe(o)"); +} + diff --git a/devtools/server/tests/unit/test_objectgrips-03.js b/devtools/server/tests/unit/test_objectgrips-03.js new file mode 100644 index 000000000..8b19db713 --- /dev/null +++ b/devtools/server/tests/unit/test_objectgrips-03.js @@ -0,0 +1,73 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +var gDebuggee; +var gClient; +var gThreadClient; +var gCallback; + +function run_test() +{ + run_test_with_server(DebuggerServer, function () { + run_test_with_server(WorkerDebuggerServer, do_test_finished); + }); + do_test_pending(); +} + +function run_test_with_server(aServer, aCallback) +{ + gCallback = aCallback; + initTestDebuggerServer(aServer); + gDebuggee = addTestGlobal("test-grips", aServer); + gDebuggee.eval(function stopMe(arg1) { + debugger; + }.toString()); + + gClient = new DebuggerClient(aServer.connectPipe()); + gClient.connect().then(function () { + attachTestTabAndResume(gClient, "test-grips", function (aResponse, aTabClient, aThreadClient) { + gThreadClient = aThreadClient; + test_object_grip(); + }); + }); +} + +function test_object_grip() +{ + gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) { + let args = aPacket.frame.arguments; + + do_check_eq(args[0].class, "Object"); + + let objClient = gThreadClient.pauseGrip(args[0]); + objClient.getProperty("x", function (aResponse) { + do_check_eq(aResponse.descriptor.configurable, true); + do_check_eq(aResponse.descriptor.enumerable, true); + do_check_eq(aResponse.descriptor.writable, true); + do_check_eq(aResponse.descriptor.value, 10); + + objClient.getProperty("y", function (aResponse) { + do_check_eq(aResponse.descriptor.configurable, true); + do_check_eq(aResponse.descriptor.enumerable, true); + do_check_eq(aResponse.descriptor.writable, true); + do_check_eq(aResponse.descriptor.value, "kaiju"); + + objClient.getProperty("a", function (aResponse) { + do_check_eq(aResponse.descriptor.configurable, true); + do_check_eq(aResponse.descriptor.enumerable, true); + do_check_eq(aResponse.descriptor.get.type, "object"); + do_check_eq(aResponse.descriptor.get.class, "Function"); + do_check_eq(aResponse.descriptor.set.type, "undefined"); + + gThreadClient.resume(function () { + gClient.close().then(gCallback); + }); + }); + }); + }); + + }); + + gDebuggee.eval("stopMe({ x: 10, y: 'kaiju', get a() { return 42; } })"); +} + diff --git a/devtools/server/tests/unit/test_objectgrips-04.js b/devtools/server/tests/unit/test_objectgrips-04.js new file mode 100644 index 000000000..1662358e0 --- /dev/null +++ b/devtools/server/tests/unit/test_objectgrips-04.js @@ -0,0 +1,76 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +var gDebuggee; +var gClient; +var gThreadClient; +var gCallback; + +function run_test() +{ + run_test_with_server(DebuggerServer, function () { + run_test_with_server(WorkerDebuggerServer, do_test_finished); + }); + do_test_pending(); +} + +function run_test_with_server(aServer, aCallback) +{ + gCallback = aCallback; + initTestDebuggerServer(aServer); + gDebuggee = addTestGlobal("test-grips", aServer); + gDebuggee.eval(function stopMe(arg1) { + debugger; + }.toString()); + + gClient = new DebuggerClient(aServer.connectPipe()); + gClient.connect().then(function () { + attachTestTabAndResume(gClient, "test-grips", function (aResponse, aTabClient, aThreadClient) { + gThreadClient = aThreadClient; + test_object_grip(); + }); + }); +} + +function test_object_grip() +{ + gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) { + let args = aPacket.frame.arguments; + + do_check_eq(args[0].class, "Object"); + + let objClient = gThreadClient.pauseGrip(args[0]); + objClient.getPrototypeAndProperties(function (aResponse) { + do_check_eq(aResponse.ownProperties.x.configurable, true); + do_check_eq(aResponse.ownProperties.x.enumerable, true); + do_check_eq(aResponse.ownProperties.x.writable, true); + do_check_eq(aResponse.ownProperties.x.value, 10); + + do_check_eq(aResponse.ownProperties.y.configurable, true); + do_check_eq(aResponse.ownProperties.y.enumerable, true); + do_check_eq(aResponse.ownProperties.y.writable, true); + do_check_eq(aResponse.ownProperties.y.value, "kaiju"); + + do_check_eq(aResponse.ownProperties.a.configurable, true); + do_check_eq(aResponse.ownProperties.a.enumerable, true); + do_check_eq(aResponse.ownProperties.a.get.type, "object"); + do_check_eq(aResponse.ownProperties.a.get.class, "Function"); + do_check_eq(aResponse.ownProperties.a.set.type, "undefined"); + + do_check_true(aResponse.prototype != undefined); + + let protoClient = gThreadClient.pauseGrip(aResponse.prototype); + protoClient.getOwnPropertyNames(function (aResponse) { + do_check_true(aResponse.ownPropertyNames.toString != undefined); + + gThreadClient.resume(function () { + gClient.close().then(gCallback); + }); + }); + }); + + }); + + gDebuggee.eval("stopMe({ x: 10, y: 'kaiju', get a() { return 42; } })"); +} + diff --git a/devtools/server/tests/unit/test_objectgrips-05.js b/devtools/server/tests/unit/test_objectgrips-05.js new file mode 100644 index 000000000..5bbb37d88 --- /dev/null +++ b/devtools/server/tests/unit/test_objectgrips-05.js @@ -0,0 +1,66 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * This test checks that frozen objects report themselves as frozen in their + * grip. + */ + +var gDebuggee; +var gClient; +var gThreadClient; +var gCallback; + +function run_test() +{ + run_test_with_server(DebuggerServer, function () { + run_test_with_server(WorkerDebuggerServer, do_test_finished); + }); + do_test_pending(); +} + +function run_test_with_server(aServer, aCallback) +{ + gCallback = aCallback; + initTestDebuggerServer(aServer); + gDebuggee = addTestGlobal("test-grips", aServer); + gDebuggee.eval(function stopMe(arg1, arg2) { + debugger; + }.toString()); + + gClient = new DebuggerClient(aServer.connectPipe()); + gClient.connect().then(function () { + attachTestTabAndResume(gClient, "test-grips", function (aResponse, aTabClient, aThreadClient) { + gThreadClient = aThreadClient; + test_object_grip(); + }); + }); +} + +function test_object_grip() +{ + gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) { + let obj1 = aPacket.frame.arguments[0]; + do_check_true(obj1.frozen); + + let obj1Client = gThreadClient.pauseGrip(obj1); + do_check_true(obj1Client.isFrozen); + + let obj2 = aPacket.frame.arguments[1]; + do_check_false(obj2.frozen); + + let obj2Client = gThreadClient.pauseGrip(obj2); + do_check_false(obj2Client.isFrozen); + + gThreadClient.resume(_ => { + gClient.close().then(gCallback); + }); + }); + + gDebuggee.eval("(" + function () { + let obj1 = {}; + Object.freeze(obj1); + stopMe(obj1, {}); + } + "())"); +} + diff --git a/devtools/server/tests/unit/test_objectgrips-06.js b/devtools/server/tests/unit/test_objectgrips-06.js new file mode 100644 index 000000000..bb9888ab8 --- /dev/null +++ b/devtools/server/tests/unit/test_objectgrips-06.js @@ -0,0 +1,66 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * This test checks that sealed objects report themselves as sealed in their + * grip. + */ + +var gDebuggee; +var gClient; +var gThreadClient; +var gCallback; + +function run_test() +{ + run_test_with_server(DebuggerServer, function () { + run_test_with_server(WorkerDebuggerServer, do_test_finished); + }); + do_test_pending(); +} + +function run_test_with_server(aServer, aCallback) +{ + gCallback = aCallback; + initTestDebuggerServer(aServer); + gDebuggee = addTestGlobal("test-grips", aServer); + gDebuggee.eval(function stopMe(arg1, arg2) { + debugger; + }.toString()); + + gClient = new DebuggerClient(aServer.connectPipe()); + gClient.connect().then(function () { + attachTestTabAndResume(gClient, "test-grips", function (aResponse, aTabClient, aThreadClient) { + gThreadClient = aThreadClient; + test_object_grip(); + }); + }); +} + +function test_object_grip() +{ + gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) { + let obj1 = aPacket.frame.arguments[0]; + do_check_true(obj1.sealed); + + let obj1Client = gThreadClient.pauseGrip(obj1); + do_check_true(obj1Client.isSealed); + + let obj2 = aPacket.frame.arguments[1]; + do_check_false(obj2.sealed); + + let obj2Client = gThreadClient.pauseGrip(obj2); + do_check_false(obj2Client.isSealed); + + gThreadClient.resume(_ => { + gClient.close().then(gCallback); + }); + }); + + gDebuggee.eval("(" + function () { + let obj1 = {}; + Object.seal(obj1); + stopMe(obj1, {}); + } + "())"); +} + diff --git a/devtools/server/tests/unit/test_objectgrips-07.js b/devtools/server/tests/unit/test_objectgrips-07.js new file mode 100644 index 000000000..6d9ac11fb --- /dev/null +++ b/devtools/server/tests/unit/test_objectgrips-07.js @@ -0,0 +1,74 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * This test checks that objects which are not extensible report themselves as + * such. + */ + +var gDebuggee; +var gClient; +var gThreadClient; +var gCallback; + +function run_test() +{ + run_test_with_server(DebuggerServer, function () { + run_test_with_server(WorkerDebuggerServer, do_test_finished); + }); + do_test_pending(); +} + +function run_test_with_server(aServer, aCallback) +{ + gCallback = aCallback; + initTestDebuggerServer(aServer); + gDebuggee = addTestGlobal("test-grips", aServer); + gDebuggee.eval(function stopMe(arg1, arg2, arg3, arg4) { + debugger; + }.toString()); + + gClient = new DebuggerClient(aServer.connectPipe()); + gClient.connect().then(function () { + attachTestTabAndResume(gClient, "test-grips", function (aResponse, aTabClient, aThreadClient) { + gThreadClient = aThreadClient; + test_object_grip(); + }); + }); +} + +function test_object_grip() +{ + gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) { + let [f, s, ne, e] = aPacket.frame.arguments; + let [fClient, sClient, neClient, eClient] = aPacket.frame.arguments.map( + a => gThreadClient.pauseGrip(a)); + + do_check_false(f.extensible); + do_check_false(fClient.isExtensible); + + do_check_false(s.extensible); + do_check_false(sClient.isExtensible); + + do_check_false(ne.extensible); + do_check_false(neClient.isExtensible); + + do_check_true(e.extensible); + do_check_true(eClient.isExtensible); + + gThreadClient.resume(_ => { + gClient.close().then(gCallback); + }); + }); + + gDebuggee.eval("(" + function () { + let f = {}; + Object.freeze(f); + let s = {}; + Object.seal(s); + let ne = {}; + Object.preventExtensions(ne); + stopMe(f, s, ne, {}); + } + "())"); +} + diff --git a/devtools/server/tests/unit/test_objectgrips-08.js b/devtools/server/tests/unit/test_objectgrips-08.js new file mode 100644 index 000000000..ecaa7146d --- /dev/null +++ b/devtools/server/tests/unit/test_objectgrips-08.js @@ -0,0 +1,72 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +var gDebuggee; +var gClient; +var gThreadClient; +var gCallback; + +function run_test() +{ + run_test_with_server(DebuggerServer, function () { + run_test_with_server(WorkerDebuggerServer, do_test_finished); + }); + do_test_pending(); +} + +function run_test_with_server(aServer, aCallback) +{ + gCallback = aCallback; + initTestDebuggerServer(aServer); + gDebuggee = addTestGlobal("test-grips", aServer); + gDebuggee.eval(function stopMe(arg1) { + debugger; + }.toString()); + + gClient = new DebuggerClient(aServer.connectPipe()); + gClient.connect().then(function () { + attachTestTabAndResume(gClient, "test-grips", function (aResponse, aTabClient, aThreadClient) { + gThreadClient = aThreadClient; + test_object_grip(); + }); + }); +} + +function test_object_grip() +{ + gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) { + let args = aPacket.frame.arguments; + + do_check_eq(args[0].class, "Object"); + + let objClient = gThreadClient.pauseGrip(args[0]); + objClient.getPrototypeAndProperties(function (aResponse) { + do_check_eq(aResponse.ownProperties.a.configurable, true); + do_check_eq(aResponse.ownProperties.a.enumerable, true); + do_check_eq(aResponse.ownProperties.a.writable, true); + do_check_eq(aResponse.ownProperties.a.value.type, "Infinity"); + + do_check_eq(aResponse.ownProperties.b.configurable, true); + do_check_eq(aResponse.ownProperties.b.enumerable, true); + do_check_eq(aResponse.ownProperties.b.writable, true); + do_check_eq(aResponse.ownProperties.b.value.type, "-Infinity"); + + do_check_eq(aResponse.ownProperties.c.configurable, true); + do_check_eq(aResponse.ownProperties.c.enumerable, true); + do_check_eq(aResponse.ownProperties.c.writable, true); + do_check_eq(aResponse.ownProperties.c.value.type, "NaN"); + + do_check_eq(aResponse.ownProperties.d.configurable, true); + do_check_eq(aResponse.ownProperties.d.enumerable, true); + do_check_eq(aResponse.ownProperties.d.writable, true); + do_check_eq(aResponse.ownProperties.d.value.type, "-0"); + + gThreadClient.resume(function () { + gClient.close().then(gCallback); + }); + }); + }); + + gDebuggee.eval("stopMe({ a: Infinity, b: -Infinity, c: NaN, d: -0 })"); +} + diff --git a/devtools/server/tests/unit/test_objectgrips-09.js b/devtools/server/tests/unit/test_objectgrips-09.js new file mode 100644 index 000000000..498154b1e --- /dev/null +++ b/devtools/server/tests/unit/test_objectgrips-09.js @@ -0,0 +1,74 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ +/** + * This tests exercises getProtypesAndProperties message accepted + * by a thread actor. + */ + +var gDebuggee; +var gClient; +var gThreadClient; +var gCallback; + +function run_test() +{ + run_test_with_server(DebuggerServer, function () { + run_test_with_server(WorkerDebuggerServer, do_test_finished); + }); + do_test_pending(); +} + +function run_test_with_server(aServer, aCallback) +{ + gCallback = aCallback; + initTestDebuggerServer(aServer); + gDebuggee = addTestGlobal("test-grips", aServer); + gDebuggee.eval(function stopMe(arg1, arg2) { + debugger; + }.toString()); + + gClient = new DebuggerClient(aServer.connectPipe()); + gClient.connect().then(function () { + attachTestTabAndResume(gClient, "test-grips", function (aResponse, aTabClient, aThreadClient) { + gThreadClient = aThreadClient; + test_object_grip(); + }); + }); +} + +function test_object_grip() +{ + gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) { + let args = aPacket.frame.arguments; + + gThreadClient.getPrototypesAndProperties([args[0].actor, args[1].actor], function (aResponse) { + let obj1 = aResponse.actors[args[0].actor]; + let obj2 = aResponse.actors[args[1].actor]; + do_check_eq(obj1.ownProperties.x.configurable, true); + do_check_eq(obj1.ownProperties.x.enumerable, true); + do_check_eq(obj1.ownProperties.x.writable, true); + do_check_eq(obj1.ownProperties.x.value, 10); + + do_check_eq(obj1.ownProperties.y.configurable, true); + do_check_eq(obj1.ownProperties.y.enumerable, true); + do_check_eq(obj1.ownProperties.y.writable, true); + do_check_eq(obj1.ownProperties.y.value, "kaiju"); + + do_check_eq(obj2.ownProperties.z.configurable, true); + do_check_eq(obj2.ownProperties.z.enumerable, true); + do_check_eq(obj2.ownProperties.z.writable, true); + do_check_eq(obj2.ownProperties.z.value, 123); + + do_check_true(obj1.prototype != undefined); + do_check_true(obj2.prototype != undefined); + + gThreadClient.resume(function () { + gClient.close().then(gCallback); + }); + }); + + }); + + gDebuggee.eval("stopMe({ x: 10, y: 'kaiju'}, { z: 123 })"); +} + diff --git a/devtools/server/tests/unit/test_objectgrips-10.js b/devtools/server/tests/unit/test_objectgrips-10.js new file mode 100644 index 000000000..a5d1b18c6 --- /dev/null +++ b/devtools/server/tests/unit/test_objectgrips-10.js @@ -0,0 +1,72 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +var gDebuggee; +var gClient; +var gThreadClient; + +// Test that closures can be inspected. + +function run_test() +{ + initTestDebuggerServer(); + gDebuggee = addTestGlobal("test-closures"); + + gClient = new DebuggerClient(DebuggerServer.connectPipe()); + gClient.connect().then(function () { + attachTestTabAndResume(gClient, "test-closures", function (aResponse, aTabClient, aThreadClient) { + gThreadClient = aThreadClient; + test_object_grip(); + }); + }); + do_test_pending(); +} + +function test_object_grip() +{ + gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) { + let person = aPacket.frame.environment.bindings.variables.person; + + do_check_eq(person.value.class, "Object"); + + let personClient = gThreadClient.pauseGrip(person.value); + personClient.getPrototypeAndProperties(aResponse => { + do_check_eq(aResponse.ownProperties.getName.value.class, "Function"); + + do_check_eq(aResponse.ownProperties.getAge.value.class, "Function"); + + do_check_eq(aResponse.ownProperties.getFoo.value.class, "Function"); + + let getNameClient = gThreadClient.pauseGrip(aResponse.ownProperties.getName.value); + let getAgeClient = gThreadClient.pauseGrip(aResponse.ownProperties.getAge.value); + let getFooClient = gThreadClient.pauseGrip(aResponse.ownProperties.getFoo.value); + getNameClient.getScope(aResponse => { + do_check_eq(aResponse.scope.bindings.arguments[0].name.value, "Bob"); + + getAgeClient.getScope(aResponse => { + do_check_eq(aResponse.scope.bindings.arguments[1].age.value, 58); + + getFooClient.getScope(aResponse => { + do_check_eq(aResponse.scope.bindings.variables.foo.value, 10); + + gThreadClient.resume(() => finishClient(gClient)); + }); + }); + }); + }); + + }); + + gDebuggee.eval("(" + function () { + var PersonFactory = function (name, age) { + var foo = 10; + return { + getName: function () { return name; }, + getAge: function () { return age; }, + getFoo: function () { foo = Date.now(); return foo; } + }; + }; + var person = new PersonFactory("Bob", 58); + debugger; + } + ")()"); +} diff --git a/devtools/server/tests/unit/test_objectgrips-11.js b/devtools/server/tests/unit/test_objectgrips-11.js new file mode 100644 index 000000000..1ad5c353a --- /dev/null +++ b/devtools/server/tests/unit/test_objectgrips-11.js @@ -0,0 +1,52 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +// Test that we get the magic properties on Error objects. + +var gDebuggee; +var gClient; +var gThreadClient; + +function run_test() +{ + initTestDebuggerServer(); + gDebuggee = addTestGlobal("test-grips"); + gDebuggee.eval(function stopMe(arg1) { + debugger; + }.toString()); + + gClient = new DebuggerClient(DebuggerServer.connectPipe()); + gClient.connect().then(function () { + attachTestTabAndResume(gClient, "test-grips", function (aResponse, aTabClient, aThreadClient) { + gThreadClient = aThreadClient; + test_object_grip(); + }); + }); + do_test_pending(); +} + +function test_object_grip() +{ + gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) { + let args = aPacket.frame.arguments; + + let objClient = gThreadClient.pauseGrip(args[0]); + objClient.getOwnPropertyNames(function (aResponse) { + var opn = aResponse.ownPropertyNames; + do_check_eq(opn.length, 4); + opn.sort(); + do_check_eq(opn[0], "columnNumber"); + do_check_eq(opn[1], "fileName"); + do_check_eq(opn[2], "lineNumber"); + do_check_eq(opn[3], "message"); + + gThreadClient.resume(function () { + finishClient(gClient); + }); + }); + + }); + + gDebuggee.eval("stopMe(new TypeError('error message text'))"); +} + diff --git a/devtools/server/tests/unit/test_objectgrips-12.js b/devtools/server/tests/unit/test_objectgrips-12.js new file mode 100644 index 000000000..32d4d47e0 --- /dev/null +++ b/devtools/server/tests/unit/test_objectgrips-12.js @@ -0,0 +1,162 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +// Test getDisplayString. + +Cu.import("resource://testing-common/PromiseTestUtils.jsm", this); + +var gDebuggee; +var gClient; +var gThreadClient; + +function run_test() +{ + initTestDebuggerServer(); + gDebuggee = addTestGlobal("test-grips"); + gDebuggee.eval(function stopMe(arg1) { + debugger; + }.toString()); + + gClient = new DebuggerClient(DebuggerServer.connectPipe()); + gClient.connect().then(function () { + attachTestTabAndResume(gClient, "test-grips", function (aResponse, aTabClient, aThreadClient) { + gThreadClient = aThreadClient; + test_display_string(); + }); + }); + do_test_pending(); +} + +function test_display_string() +{ + const testCases = [ + { + input: "new Boolean(true)", + output: "true" + }, + { + input: "new Number(5)", + output: "5" + }, + { + input: "new String('foo')", + output: "foo" + }, + { + input: "new Map()", + output: "[object Map]" + }, + { + input: "[,,,,,,,]", + output: ",,,,,," + }, + { + input: "[1, 2, 3]", + output: "1,2,3" + }, + { + input: "[undefined, null, true, 'foo', 5]", + output: ",,true,foo,5" + }, + { + input: "[{},{}]", + output: "[object Object],[object Object]" + }, + { + input: "(" + function () { + const arr = [1]; + arr.push(arr); + return arr; + } + ")()", + output: "1," + }, + { + input: "{}", + output: "[object Object]" + }, + { + input: "Object.create(null)", + output: "[object Object]" + }, + { + input: "new Error('foo')", + output: "Error: foo" + }, + { + input: "new SyntaxError()", + output: "SyntaxError" + }, + { + input: "new ReferenceError('')", + output: "ReferenceError" + }, + { + input: "(" + function () { + const err = new Error("bar"); + err.name = "foo"; + return err; + } + ")()", + output: "foo: bar" + }, + { + input: "() => {}", + output: "() => {}" + }, + { + input: "function (foo, bar) {}", + output: "function (foo, bar) {}" + }, + { + input: "function foo(bar) {}", + output: "function foo(bar) {}" + }, + { + input: "Array", + output: Array + "" + }, + { + input: "/foo[bar]/g", + output: "/foo[bar]/g" + }, + { + input: "new Proxy({}, {})", + output: "[object Object]" + }, + { + input: "Promise.resolve(5)", + output: "Promise (fulfilled: 5)" + }, + { + // This rejection is left uncaught, see expectUncaughtRejection below. + input: "Promise.reject(new Error())", + output: "Promise (rejected: Error)" + }, + { + input: "new Promise(function () {})", + output: "Promise (pending)" + } + ]; + + PromiseTestUtils.expectUncaughtRejection(/Error/); + + gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) { + const args = aPacket.frame.arguments; + + (function loop() { + const objClient = gThreadClient.pauseGrip(args.pop()); + objClient.getDisplayString(function ({ displayString }) { + do_check_eq(displayString, testCases.pop().output); + if (args.length) { + loop(); + } else { + gThreadClient.resume(function () { + finishClient(gClient); + }); + } + }); + })(); + }); + + const inputs = testCases.map(({ input }) => input).join(","); + gDebuggee.eval("stopMe(" + inputs + ")"); +} diff --git a/devtools/server/tests/unit/test_objectgrips-13.js b/devtools/server/tests/unit/test_objectgrips-13.js new file mode 100644 index 000000000..166e8a0d5 --- /dev/null +++ b/devtools/server/tests/unit/test_objectgrips-13.js @@ -0,0 +1,66 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +// Test that ObjectClient.prototype.getDefinitionSite and the "definitionSite" +// request work properly. + +var gDebuggee; +var gClient; +var gThreadClient; + +function run_test() +{ + initTestDebuggerServer(); + gDebuggee = addTestGlobal("test-grips"); + Components.utils.evalInSandbox(function stopMe() { + debugger; + }.toString(), gDebuggee); + + gClient = new DebuggerClient(DebuggerServer.connectPipe()); + gClient.connect().then(function () { + attachTestTabAndResume(gClient, "test-grips", function (aResponse, aTabClient, aThreadClient) { + gThreadClient = aThreadClient; + add_pause_listener(); + }); + }); + do_test_pending(); +} + +function add_pause_listener() +{ + gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) { + const [funcGrip, objGrip] = aPacket.frame.arguments; + const func = gThreadClient.pauseGrip(funcGrip); + const obj = gThreadClient.pauseGrip(objGrip); + test_definition_site(func, obj); + }); + + eval_code(); +} + +function eval_code() { + Components.utils.evalInSandbox([ + "this.line0 = Error().lineNumber;", + "function f() {}", + "stopMe(f, {});" + ].join("\n"), gDebuggee); +} + +function test_definition_site(func, obj) { + func.getDefinitionSite(({ error, source, line, column }) => { + do_check_true(!error); + do_check_eq(source.url, getFilePath("test_objectgrips-13.js")); + do_check_eq(line, gDebuggee.line0 + 1); + do_check_eq(column, 0); + + test_bad_definition_site(obj); + }); +} + +function test_bad_definition_site(obj) { + try { + obj._client.request("definitionSite", () => do_check_true(false)); + } catch (e) { + gThreadClient.resume(() => finishClient(gClient)); + } +} diff --git a/devtools/server/tests/unit/test_pause_exceptions-01.js b/devtools/server/tests/unit/test_pause_exceptions-01.js new file mode 100644 index 000000000..56ee6816d --- /dev/null +++ b/devtools/server/tests/unit/test_pause_exceptions-01.js @@ -0,0 +1,50 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Test that setting pauseOnExceptions to true will cause the debuggee to pause + * when an exceptions is thrown. + */ + +var gDebuggee; +var gClient; +var gThreadClient; + +function run_test() +{ + initTestDebuggerServer(); + gDebuggee = addTestGlobal("test-stack"); + gClient = new DebuggerClient(DebuggerServer.connectPipe()); + gClient.connect().then(function () { + attachTestTabAndResume(gClient, "test-stack", function (aResponse, aTabClient, aThreadClient) { + gThreadClient = aThreadClient; + test_pause_frame(); + }); + }); + do_test_pending(); +} + +function test_pause_frame() +{ + gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) { + gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) { + do_check_eq(aPacket.why.type, "exception"); + do_check_eq(aPacket.why.exception, 42); + gThreadClient.resume(function () { + finishClient(gClient); + }); + }); + gThreadClient.pauseOnExceptions(true); + gThreadClient.resume(); + }); + + gDebuggee.eval("(" + function () { + function stopMe() { + debugger; + throw 42; + } + try { + stopMe(); + } catch (e) {} + } + ")()"); +} diff --git a/devtools/server/tests/unit/test_pause_exceptions-02.js b/devtools/server/tests/unit/test_pause_exceptions-02.js new file mode 100644 index 000000000..fa9b419f0 --- /dev/null +++ b/devtools/server/tests/unit/test_pause_exceptions-02.js @@ -0,0 +1,47 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Test that setting pauseOnExceptions to true when the debugger isn't in a + * paused state will cause the debuggee to pause when an exceptions is thrown. + */ + +var gDebuggee; +var gClient; +var gThreadClient; + +function run_test() +{ + initTestDebuggerServer(); + gDebuggee = addTestGlobal("test-stack"); + gClient = new DebuggerClient(DebuggerServer.connectPipe()); + gClient.connect().then(function () { + attachTestTabAndResume(gClient, "test-stack", function (aResponse, aTabClient, aThreadClient) { + gThreadClient = aThreadClient; + test_pause_frame(); + }); + }); + do_test_pending(); +} + +function test_pause_frame() +{ + gThreadClient.pauseOnExceptions(true, false, function () { + gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) { + do_check_eq(aPacket.why.type, "exception"); + do_check_eq(aPacket.why.exception, 42); + gThreadClient.resume(function () { + finishClient(gClient); + }); + }); + + gDebuggee.eval("(" + function () { + function stopMe() { + throw 42; + } + try { + stopMe(); + } catch (e) {} + } + ")()"); + }); +} diff --git a/devtools/server/tests/unit/test_pauselifetime-01.js b/devtools/server/tests/unit/test_pauselifetime-01.js new file mode 100644 index 000000000..71c2ddae7 --- /dev/null +++ b/devtools/server/tests/unit/test_pauselifetime-01.js @@ -0,0 +1,54 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Check that pause-lifetime grips go away correctly after a resume. + */ + +var gDebuggee; +var gClient; +var gThreadClient; + +function run_test() +{ + initTestDebuggerServer(); + gDebuggee = addTestGlobal("test-stack"); + gClient = new DebuggerClient(DebuggerServer.connectPipe()); + gClient.connect().then(function () { + attachTestTabAndResume(gClient, "test-stack", function (aResponse, aTabClient, aThreadClient) { + gThreadClient = aThreadClient; + test_pause_frame(); + }); + }); + do_test_pending(); +} + +function test_pause_frame() +{ + gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) { + let pauseActor = aPacket.actor; + + // Make a bogus request to the pause-liftime actor. Should get + // unrecognized-packet-type (and not no-such-actor). + gClient.request({ to: pauseActor, type: "bogusRequest" }, function (aResponse) { + do_check_eq(aResponse.error, "unrecognizedPacketType"); + + gThreadClient.resume(function () { + // Now that we've resumed, should get no-such-actor for the + // same request. + gClient.request({ to: pauseActor, type: "bogusRequest" }, function (aResponse) { + do_check_eq(aResponse.error, "noSuchActor"); + finishClient(gClient); + }); + }); + + }); + }); + + gDebuggee.eval("(" + function () { + function stopMe() { + debugger; + } + stopMe(); + } + ")()"); +} diff --git a/devtools/server/tests/unit/test_pauselifetime-02.js b/devtools/server/tests/unit/test_pauselifetime-02.js new file mode 100644 index 000000000..6c90725bb --- /dev/null +++ b/devtools/server/tests/unit/test_pauselifetime-02.js @@ -0,0 +1,56 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Check that pause-lifetime grips go away correctly after a resume. + */ + +var gDebuggee; +var gClient; +var gThreadClient; + +function run_test() +{ + initTestDebuggerServer(); + gDebuggee = addTestGlobal("test-stack"); + gClient = new DebuggerClient(DebuggerServer.connectPipe()); + gClient.connect().then(function () { + attachTestTabAndResume(gClient, "test-stack", function (aResponse, aTabClient, aThreadClient) { + gThreadClient = aThreadClient; + test_pause_frame(); + }); + }); + do_test_pending(); +} + +function test_pause_frame() +{ + gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) { + let args = aPacket.frame.arguments; + let objActor = args[0].actor; + do_check_eq(args[0].class, "Object"); + do_check_true(!!objActor); + + // Make a bogus request to the grip actor. Should get + // unrecognized-packet-type (and not no-such-actor). + gClient.request({ to: objActor, type: "bogusRequest" }, function (aResponse) { + do_check_eq(aResponse.error, "unrecognizedPacketType"); + + gThreadClient.resume(function () { + // Now that we've resumed, should get no-such-actor for the + // same request. + gClient.request({ to: objActor, type: "bogusRequest" }, function (aResponse) { + do_check_eq(aResponse.error, "noSuchActor"); + finishClient(gClient); + }); + }); + }); + }); + + gDebuggee.eval("(" + function () { + function stopMe(aObject) { + debugger; + } + stopMe({ foo: "bar" }); + } + ")()"); +} diff --git a/devtools/server/tests/unit/test_pauselifetime-03.js b/devtools/server/tests/unit/test_pauselifetime-03.js new file mode 100644 index 000000000..9fca887b7 --- /dev/null +++ b/devtools/server/tests/unit/test_pauselifetime-03.js @@ -0,0 +1,61 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Check that pause-lifetime grip clients are marked invalid after a resume. + */ + +var gDebuggee; +var gClient; +var gThreadClient; + +function run_test() +{ + initTestDebuggerServer(); + gDebuggee = addTestGlobal("test-stack"); + gClient = new DebuggerClient(DebuggerServer.connectPipe()); + gClient.connect().then(function () { + attachTestTabAndResume(gClient, "test-stack", function (aResponse, aTabClient, aThreadClient) { + gThreadClient = aThreadClient; + test_pause_frame(); + }); + }); + do_test_pending(); +} + +function test_pause_frame() +{ + gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) { + let args = aPacket.frame.arguments; + let objActor = args[0].actor; + do_check_eq(args[0].class, "Object"); + do_check_true(!!objActor); + + let objClient = gThreadClient.pauseGrip(args[0]); + do_check_true(objClient.valid); + + // Make a bogus request to the grip actor. Should get + // unrecognized-packet-type (and not no-such-actor). + gClient.request({ to: objActor, type: "bogusRequest" }, function (aResponse) { + do_check_eq(aResponse.error, "unrecognizedPacketType"); + do_check_true(objClient.valid); + + gThreadClient.resume(function () { + // Now that we've resumed, should get no-such-actor for the + // same request. + gClient.request({ to: objActor, type: "bogusRequest" }, function (aResponse) { + do_check_false(objClient.valid); + do_check_eq(aResponse.error, "noSuchActor"); + finishClient(gClient); + }); + }); + }); + }); + + gDebuggee.eval("(" + function () { + function stopMe(aObject) { + debugger; + } + stopMe({ foo: "bar" }); + } + ")()"); +} diff --git a/devtools/server/tests/unit/test_pauselifetime-04.js b/devtools/server/tests/unit/test_pauselifetime-04.js new file mode 100644 index 000000000..c863da921 --- /dev/null +++ b/devtools/server/tests/unit/test_pauselifetime-04.js @@ -0,0 +1,48 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Test that requesting a pause actor for the same value multiple + * times returns the same actor. + */ + +var gDebuggee; +var gClient; +var gThreadClient; + +function run_test() +{ + initTestDebuggerServer(); + gDebuggee = addTestGlobal("test-stack"); + gClient = new DebuggerClient(DebuggerServer.connectPipe()); + gClient.connect().then(function () { + attachTestTabAndResume(gClient, "test-stack", function (aResponse, aTabClient, aThreadClient) { + gThreadClient = aThreadClient; + test_pause_frame(); + }); + }); + do_test_pending(); +} + +function test_pause_frame() +{ + gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) { + let args = aPacket.frame.arguments; + let objActor1 = args[0].actor; + + gThreadClient.getFrames(0, 1, function (aResponse) { + let frame = aResponse.frames[0]; + do_check_eq(objActor1, frame.arguments[0].actor); + gThreadClient.resume(function () { + finishClient(gClient); + }); + }); + }); + + gDebuggee.eval("(" + function () { + function stopMe(aObject) { + debugger; + } + stopMe({ foo: "bar" }); + } + ")()"); +} diff --git a/devtools/server/tests/unit/test_profiler_activation-01.js b/devtools/server/tests/unit/test_profiler_activation-01.js new file mode 100644 index 000000000..31efbb5e3 --- /dev/null +++ b/devtools/server/tests/unit/test_profiler_activation-01.js @@ -0,0 +1,89 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +/** + * Tests whether the profiler module and actor have the correct state on + * initialization, activation, and when a clients' connection closes. + */ + +const Profiler = Cc["@mozilla.org/tools/profiler;1"].getService(Ci.nsIProfiler); +const MAX_PROFILER_ENTRIES = 10000000; + +function run_test() +{ + // Ensure the profiler is not running when the test starts (it could + // happen if the MOZ_PROFILER_STARTUP environment variable is set). + Profiler.StopProfiler(); + + get_chrome_actors((client1, form1) => { + let actor1 = form1.profilerActor; + get_chrome_actors((client2, form2) => { + let actor2 = form2.profilerActor; + test_activate(client1, actor1, client2, actor2, () => { + do_test_finished(); + }); + }); + }); + + do_test_pending(); +} + +function test_activate(client1, actor1, client2, actor2, callback) { + // Profiler should be inactive at this point. + client1.request({ to: actor1, type: "isActive" }, response => { + do_check_false(Profiler.IsActive()); + do_check_false(response.isActive); + do_check_eq(response.currentTime, undefined); + do_check_true(typeof response.position === "number"); + do_check_true(typeof response.totalSize === "number"); + do_check_true(typeof response.generation === "number"); + + // Start the profiler on the first connection.... + client1.request({ to: actor1, type: "startProfiler", entries: MAX_PROFILER_ENTRIES }, response => { + do_check_true(Profiler.IsActive()); + do_check_true(response.started); + do_check_true(typeof response.position === "number"); + do_check_true(typeof response.totalSize === "number"); + do_check_true(typeof response.generation === "number"); + do_check_true(response.position >= 0 && response.position < response.totalSize); + do_check_true(response.totalSize === MAX_PROFILER_ENTRIES); + + // On the next connection just make sure the actor has been instantiated. + client2.request({ to: actor2, type: "isActive" }, response => { + do_check_true(Profiler.IsActive()); + do_check_true(response.isActive); + do_check_true(response.currentTime > 0); + do_check_true(typeof response.position === "number"); + do_check_true(typeof response.totalSize === "number"); + do_check_true(typeof response.generation === "number"); + do_check_true(response.position >= 0 && response.position < response.totalSize); + do_check_true(response.totalSize === MAX_PROFILER_ENTRIES); + + let origConnectionClosed = DebuggerServer._connectionClosed; + + DebuggerServer._connectionClosed = function (conn) { + origConnectionClosed.call(this, conn); + + // The first client is the only actor that started the profiler, + // however the second client can request the accumulated profile data + // at any moment, so the profiler module shouldn't have deactivated. + do_check_true(Profiler.IsActive()); + + DebuggerServer._connectionClosed = function (conn) { + origConnectionClosed.call(this, conn); + + // Now there are no open clients at all, it should *definitely* + // be deactivated by now. + do_check_false(Profiler.IsActive()); + + callback(); + }; + client2.close(); + }; + client1.close(); + }); + }); + }); +} diff --git a/devtools/server/tests/unit/test_profiler_activation-02.js b/devtools/server/tests/unit/test_profiler_activation-02.js new file mode 100644 index 000000000..cf06b1e06 --- /dev/null +++ b/devtools/server/tests/unit/test_profiler_activation-02.js @@ -0,0 +1,46 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +/** + * Tests whether the profiler actor correctly handles the case where the + * built-in module was already started. + */ + +const Profiler = Cc["@mozilla.org/tools/profiler;1"].getService(Ci.nsIProfiler); +const WAIT_TIME = 1000; // ms + +function run_test() +{ + // Ensure the profiler is already running when the test starts. + Profiler.StartProfiler(1000000, 1, ["js"], 1); + + DevToolsUtils.waitForTime(WAIT_TIME).then(() => { + + get_chrome_actors((client, form) => { + let actor = form.profilerActor; + test_start_time(client, actor, () => { + client.close().then(do_test_finished); + }); + }); + }); + + do_test_pending(); +} + +function test_start_time(client, actor, callback) { + // Profiler should already be active at this point. + client.request({ to: actor, type: "isActive" }, firstResponse => { + do_check_true(Profiler.IsActive()); + do_check_true(firstResponse.isActive); + do_check_true(firstResponse.currentTime > 0); + + client.request({ to: actor, type: "getProfile" }, secondResponse => { + do_check_true("profile" in secondResponse); + do_check_true(secondResponse.currentTime > firstResponse.currentTime); + + callback(); + }); + }); +} diff --git a/devtools/server/tests/unit/test_profiler_bufferstatus.js b/devtools/server/tests/unit/test_profiler_bufferstatus.js new file mode 100644 index 000000000..9c86bf817 --- /dev/null +++ b/devtools/server/tests/unit/test_profiler_bufferstatus.js @@ -0,0 +1,127 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +/** + * Tests if the profiler actor returns its buffer status via getBufferInfo. + */ + +const Profiler = Cc["@mozilla.org/tools/profiler;1"].getService(Ci.nsIProfiler); +const INITIAL_WAIT_TIME = 100; // ms +const MAX_WAIT_TIME = 20000; // ms +const MAX_PROFILER_ENTRIES = 10000000; + +function run_test() +{ + // Ensure the profiler is not running when the test starts (it could + // happen if the MOZ_PROFILER_STARTUP environment variable is set). + Profiler.StopProfiler(); + + get_chrome_actors((client, form) => { + let actor = form.profilerActor; + check_empty_buffer(client, actor, () => { + activate_profiler(client, actor, startTime => { + wait_for_samples(client, actor, () => { + check_buffer(client, actor, () => { + deactivate_profiler(client, actor, () => { + client.close().then(do_test_finished); + }); + }); + }); + }); + }); + }); + + do_test_pending(); +} + +function check_buffer(client, actor, callback) +{ + client.request({ to: actor, type: "isActive" }, response => { + do_check_true(typeof response.position === "number"); + do_check_true(typeof response.totalSize === "number"); + do_check_true(typeof response.generation === "number"); + do_check_true(response.position > 0 && response.position < response.totalSize); + do_check_true(response.totalSize === MAX_PROFILER_ENTRIES); + // There's no way we'll fill the buffer in this test. + do_check_true(response.generation === 0); + + callback(); + }); +} + +function check_empty_buffer(client, actor, callback) +{ + client.request({ to: actor, type: "isActive" }, response => { + do_check_false(Profiler.IsActive()); + do_check_false(response.isActive); + do_check_true(response.position === void 0); + do_check_true(response.totalSize === void 0); + do_check_true(response.generation === void 0); + do_check_false(response.isActive); + do_check_eq(response.currentTime, undefined); + calback(); + }); +} + +function activate_profiler(client, actor, callback) +{ + client.request({ to: actor, type: "startProfiler", entries: MAX_PROFILER_ENTRIES }, response => { + do_check_true(response.started); + client.request({ to: actor, type: "isActive" }, response => { + do_check_true(response.isActive); + callback(response.currentTime); + }); + }); +} + +function deactivate_profiler(client, actor, callback) +{ + client.request({ to: actor, type: "stopProfiler" }, response => { + do_check_false(response.started); + client.request({ to: actor, type: "isActive" }, response => { + do_check_false(response.isActive); + callback(); + }); + }); +} + +function wait_for_samples(client, actor, callback) +{ + function attempt(delay) + { + // No idea why, but Components.stack.sourceLine returns null. + let funcLine = Components.stack.lineNumber - 3; + + // Spin for the requested time, then take a sample. + let start = Date.now(); + let stack; + do_print("Attempt: delay = " + delay); + while (Date.now() - start < delay) { stack = Components.stack; } + do_print("Attempt: finished waiting."); + + client.request({ to: actor, type: "getProfile" }, response => { + // At this point, we may or may not have samples, depending on + // whether the spin loop above has given the profiler enough time + // to get started. + if (response.profile.threads[0].samples.length == 0) { + if (delay < MAX_WAIT_TIME) { + // Double the spin-wait time and try again. + do_print("Attempt: no samples, going around again."); + return attempt(delay * 2); + } else { + // We've waited long enough, so just fail. + do_print("Attempt: waited a long time, but no samples were collected."); + do_print("Giving up."); + do_check_true(false); + return; + } + } + callback(); + }); + } + + // Start off with a 100 millisecond delay. + attempt(INITIAL_WAIT_TIME); +} diff --git a/devtools/server/tests/unit/test_profiler_close.js b/devtools/server/tests/unit/test_profiler_close.js new file mode 100644 index 000000000..a8b3040fd --- /dev/null +++ b/devtools/server/tests/unit/test_profiler_close.js @@ -0,0 +1,69 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +/** + * Tests whether the profiler module is kept active when there are multiple + * client consumers and one requests deactivation. + */ + +const Profiler = Cc["@mozilla.org/tools/profiler;1"].getService(Ci.nsIProfiler); + +function run_test() +{ + get_chrome_actors((client1, form1) => { + let actor1 = form1.profilerActor; + get_chrome_actors((client2, form2) => { + let actor2 = form2.profilerActor; + test_close(client1, actor1, client2, actor2, () => { + client1.close(() => { + client2.close(() => { + do_test_finished(); + }); + }); + }); + }); + }); + + do_test_pending(); +} + +function activate_profiler(client, actor, callback) +{ + client.request({ to: actor, type: "startProfiler" }, response => { + do_check_true(response.started); + do_check_true(Profiler.IsActive()); + + client.request({ to: actor, type: "isActive" }, response => { + do_check_true(response.isActive); + callback(); + }); + }); +} + +function deactivate_profiler(client, actor, callback) +{ + client.request({ to: actor, type: "stopProfiler" }, response => { + do_check_false(response.started); + do_check_true(Profiler.IsActive()); + + client.request({ to: actor, type: "isActive" }, response => { + do_check_true(response.isActive); + callback(); + }); + }); +} + +function test_close(client1, actor1, client2, actor2, callback) +{ + activate_profiler(client1, actor1, () => { + activate_profiler(client2, actor2, () => { + deactivate_profiler(client1, actor1, () => { + deactivate_profiler(client2, actor2, () => { + callback(); + }); + }); + }); + }); +} diff --git a/devtools/server/tests/unit/test_profiler_data.js b/devtools/server/tests/unit/test_profiler_data.js new file mode 100644 index 000000000..2a79eed1f --- /dev/null +++ b/devtools/server/tests/unit/test_profiler_data.js @@ -0,0 +1,110 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +/** + * Tests if the profiler actor can correctly retrieve a profile after + * it is activated. + */ + +const Profiler = Cc["@mozilla.org/tools/profiler;1"].getService(Ci.nsIProfiler); +const INITIAL_WAIT_TIME = 100; // ms +const MAX_WAIT_TIME = 20000; // ms + +function run_test() +{ + get_chrome_actors((client, form) => { + let actor = form.profilerActor; + activate_profiler(client, actor, startTime => { + test_data(client, actor, startTime, () => { + deactivate_profiler(client, actor, () => { + client.close().then(do_test_finished); + }); + }); + }); + }); + + do_test_pending(); +} + +function activate_profiler(client, actor, callback) +{ + client.request({ to: actor, type: "startProfiler" }, response => { + do_check_true(response.started); + client.request({ to: actor, type: "isActive" }, response => { + do_check_true(response.isActive); + callback(response.currentTime); + }); + }); +} + +function deactivate_profiler(client, actor, callback) +{ + client.request({ to: actor, type: "stopProfiler" }, response => { + do_check_false(response.started); + client.request({ to: actor, type: "isActive" }, response => { + do_check_false(response.isActive); + callback(); + }); + }); +} + +function test_data(client, actor, startTime, callback) +{ + function attempt(delay) + { + // No idea why, but Components.stack.sourceLine returns null. + let funcLine = Components.stack.lineNumber - 3; + + // Spin for the requested time, then take a sample. + let start = Date.now(); + let stack; + do_print("Attempt: delay = " + delay); + while (Date.now() - start < delay) { stack = Components.stack; } + do_print("Attempt: finished waiting."); + + client.request({ to: actor, type: "getProfile", startTime }, response => { + // Any valid getProfile response should have the following top + // level structure. + do_check_eq(typeof response.profile, "object"); + do_check_eq(typeof response.profile.meta, "object"); + do_check_eq(typeof response.profile.meta.platform, "string"); + do_check_eq(typeof response.profile.threads, "object"); + do_check_eq(typeof response.profile.threads[0], "object"); + do_check_eq(typeof response.profile.threads[0].samples, "object"); + + // At this point, we may or may not have samples, depending on + // whether the spin loop above has given the profiler enough time + // to get started. + if (response.profile.threads[0].samples.length == 0) { + if (delay < MAX_WAIT_TIME) { + // Double the spin-wait time and try again. + do_print("Attempt: no samples, going around again."); + return attempt(delay * 2); + } else { + // We've waited long enough, so just fail. + do_print("Attempt: waited a long time, but no samples were collected."); + do_print("Giving up."); + do_check_true(false); + return; + } + } + + // Now check the samples. At least one sample is expected to + // have been in the busy wait above. + let loc = stack.name + " (" + stack.filename + ":" + funcLine + ")"; + let thread0 = response.profile.threads[0]; + do_check_true(thread0.samples.data.some(sample => { + let frames = getInflatedStackLocations(thread0, sample); + return frames.length != 0 && + frames.some(location => (location == loc)); + })); + + callback(); + }); + } + + // Start off with a 100 millisecond delay. + attempt(INITIAL_WAIT_TIME); +} diff --git a/devtools/server/tests/unit/test_profiler_events-01.js b/devtools/server/tests/unit/test_profiler_events-01.js new file mode 100644 index 000000000..b8ca592b9 --- /dev/null +++ b/devtools/server/tests/unit/test_profiler_events-01.js @@ -0,0 +1,62 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +/** + * Tests the event notification service for the profiler actor. + */ + +const Profiler = Cc["@mozilla.org/tools/profiler;1"].getService(Ci.nsIProfiler); +const { ProfilerFront } = require("devtools/shared/fronts/profiler"); + +function run_test() { + run_next_test(); +} + +add_task(function* () { + let [client, form] = yield getChromeActors(); + let front = new ProfilerFront(client, form); + + let events = [0, 0, 0, 0]; + front.on("console-api-profiler", () => events[0]++); + front.on("profiler-started", () => events[1]++); + front.on("profiler-stopped", () => events[2]++); + client.addListener("eventNotification", (type, response) => { + do_check_true(type === "eventNotification"); + events[3]++; + }); + + yield front.startProfiler(); + yield front.stopProfiler(); + + // All should be empty without binding events + do_check_true(events[0] === 0); + do_check_true(events[1] === 0); + do_check_true(events[2] === 0); + do_check_true(events[3] === 0); + + let ret = yield front.registerEventNotifications({ events: ["console-api-profiler", "profiler-started", "profiler-stopped"] }); + do_check_true(ret.registered.length === 3); + + yield front.startProfiler(); + do_check_true(events[0] === 0); + do_check_true(events[1] === 1); + do_check_true(events[2] === 0); + do_check_true(events[3] === 1, "compatibility events supported for eventNotifications"); + + yield front.stopProfiler(); + do_check_true(events[0] === 0); + do_check_true(events[1] === 1); + do_check_true(events[2] === 1); + do_check_true(events[3] === 2, "compatibility events supported for eventNotifications"); + + ret = yield front.unregisterEventNotifications({ events: ["console-api-profiler", "profiler-started", "profiler-stopped"] }); + do_check_true(ret.registered.length === 3); +}); + +function getChromeActors() { + let deferred = promise.defer(); + get_chrome_actors((client, form) => deferred.resolve([client, form])); + return deferred.promise; +} diff --git a/devtools/server/tests/unit/test_profiler_events-02.js b/devtools/server/tests/unit/test_profiler_events-02.js new file mode 100644 index 000000000..fed702043 --- /dev/null +++ b/devtools/server/tests/unit/test_profiler_events-02.js @@ -0,0 +1,70 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +/** + * Tests the event notification service for the profiler actor. + */ + +const Profiler = Cc["@mozilla.org/tools/profiler;1"].getService(Ci.nsIProfiler); +const MAX_PROFILER_ENTRIES = 10000000; +const { ProfilerFront } = require("devtools/shared/fronts/profiler"); +const { waitForTime } = DevToolsUtils; + +function run_test() { + run_next_test(); +} + +add_task(function* () { + let [client, form] = yield getChromeActors(); + let front = new ProfilerFront(client, form); + + // Ensure the profiler is not running when the test starts (it could + // happen if the MOZ_PROFILER_STARTUP environment variable is set). + Profiler.StopProfiler(); + let eventsCalled = 0; + let handledThreeTimes = promise.defer(); + + front.on("profiler-status", (response) => { + dump("'profiler-status' fired\n"); + do_check_true(typeof response.position === "number"); + do_check_true(typeof response.totalSize === "number"); + do_check_true(typeof response.generation === "number"); + do_check_true(response.position > 0 && response.position < response.totalSize); + do_check_true(response.totalSize === MAX_PROFILER_ENTRIES); + // There's no way we'll fill the buffer in this test. + do_check_true(response.generation === 0); + + eventsCalled++; + if (eventsCalled > 2) { + handledThreeTimes.resolve(); + } + }); + + yield front.setProfilerStatusInterval(1); + dump("Set the profiler-status event interval to 1\n"); + yield front.startProfiler(); + yield waitForTime(500); + yield front.stopProfiler(); + + do_check_true(eventsCalled === 0, "No 'profiler-status' events should be fired before registering."); + + let ret = yield front.registerEventNotifications({ events: ["profiler-status"] }); + do_check_true(ret.registered.length === 1); + + yield front.startProfiler(); + yield handledThreeTimes.promise; + yield front.stopProfiler(); + do_check_true(eventsCalled >= 3, "profiler-status fired atleast three times while recording"); + + let totalEvents = eventsCalled; + yield waitForTime(50); + do_check_true(totalEvents === eventsCalled, "No more profiler-status events after recording."); +}); + +function getChromeActors() { + let deferred = promise.defer(); + get_chrome_actors((client, form) => deferred.resolve([client, form])); + return deferred.promise; +} diff --git a/devtools/server/tests/unit/test_profiler_getbufferinfo.js b/devtools/server/tests/unit/test_profiler_getbufferinfo.js new file mode 100644 index 000000000..1ec536738 --- /dev/null +++ b/devtools/server/tests/unit/test_profiler_getbufferinfo.js @@ -0,0 +1,123 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +/** + * Tests if the profiler actor returns its buffer status via getBufferInfo. + */ + +const Profiler = Cc["@mozilla.org/tools/profiler;1"].getService(Ci.nsIProfiler); +const INITIAL_WAIT_TIME = 100; // ms +const MAX_WAIT_TIME = 20000; // ms +const MAX_PROFILER_ENTRIES = 10000000; + +function run_test() +{ + // Ensure the profiler is not running when the test starts (it could + // happen if the MOZ_PROFILER_STARTUP environment variable is set). + Profiler.StopProfiler(); + + get_chrome_actors((client, form) => { + let actor = form.profilerActor; + check_empty_buffer(client, actor, () => { + activate_profiler(client, actor, startTime => { + wait_for_samples(client, actor, () => { + check_buffer(client, actor, () => { + deactivate_profiler(client, actor, () => { + client.close().then(do_test_finished); + }); + }); + }); + }); + }); + }); + + do_test_pending(); +} + +function check_empty_buffer(client, actor, callback) +{ + client.request({ to: actor, type: "getBufferInfo" }, response => { + do_check_true(response.position === 0); + do_check_true(response.totalSize === 0); + do_check_true(response.generation === 0); + callback(); + }); +} + +function check_buffer(client, actor, callback) +{ + client.request({ to: actor, type: "getBufferInfo" }, response => { + do_check_true(typeof response.position === "number"); + do_check_true(typeof response.totalSize === "number"); + do_check_true(typeof response.generation === "number"); + do_check_true(response.position > 0 && response.position < response.totalSize); + do_check_true(response.totalSize === MAX_PROFILER_ENTRIES); + // There's no way we'll fill the buffer in this test. + do_check_true(response.generation === 0); + + callback(); + }); +} + +function activate_profiler(client, actor, callback) +{ + client.request({ to: actor, type: "startProfiler", entries: MAX_PROFILER_ENTRIES }, response => { + do_check_true(response.started); + client.request({ to: actor, type: "isActive" }, response => { + do_check_true(response.isActive); + callback(response.currentTime); + }); + }); +} + +function deactivate_profiler(client, actor, callback) +{ + client.request({ to: actor, type: "stopProfiler" }, response => { + do_check_false(response.started); + client.request({ to: actor, type: "isActive" }, response => { + do_check_false(response.isActive); + callback(); + }); + }); +} + +function wait_for_samples(client, actor, callback) +{ + function attempt(delay) + { + // No idea why, but Components.stack.sourceLine returns null. + let funcLine = Components.stack.lineNumber - 3; + + // Spin for the requested time, then take a sample. + let start = Date.now(); + let stack; + do_print("Attempt: delay = " + delay); + while (Date.now() - start < delay) { stack = Components.stack; } + do_print("Attempt: finished waiting."); + + client.request({ to: actor, type: "getProfile" }, response => { + // At this point, we may or may not have samples, depending on + // whether the spin loop above has given the profiler enough time + // to get started. + if (response.profile.threads[0].samples.length == 0) { + if (delay < MAX_WAIT_TIME) { + // Double the spin-wait time and try again. + do_print("Attempt: no samples, going around again."); + return attempt(delay * 2); + } else { + // We've waited long enough, so just fail. + do_print("Attempt: waited a long time, but no samples were collected."); + do_print("Giving up."); + do_check_true(false); + return; + } + } + callback(); + }); + } + + // Start off with a 100 millisecond delay. + attempt(INITIAL_WAIT_TIME); +} diff --git a/devtools/server/tests/unit/test_profiler_getfeatures.js b/devtools/server/tests/unit/test_profiler_getfeatures.js new file mode 100644 index 000000000..5b37e7d55 --- /dev/null +++ b/devtools/server/tests/unit/test_profiler_getfeatures.js @@ -0,0 +1,35 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +/** + * Tests whether the profiler responds to "getFeatures" adequately. + */ + +const Profiler = Cc["@mozilla.org/tools/profiler;1"].getService(Ci.nsIProfiler); + +function run_test() +{ + get_chrome_actors((client, form) => { + let actor = form.profilerActor; + test_getfeatures(client, actor, () => { + client.close().then(() => { + do_test_finished(); + }); + }); + }); + + do_test_pending(); +} + +function test_getfeatures(client, actor, callback) +{ + client.request({ to: actor, type: "getFeatures" }, response => { + do_check_eq(typeof response.features, "object"); + do_check_true(response.features.length >= 1); + do_check_eq(typeof response.features[0], "string"); + do_check_true(response.features.indexOf("js") != -1); + callback(); + }); +} diff --git a/devtools/server/tests/unit/test_profiler_getsharedlibraryinformation.js b/devtools/server/tests/unit/test_profiler_getsharedlibraryinformation.js new file mode 100644 index 000000000..a36577320 --- /dev/null +++ b/devtools/server/tests/unit/test_profiler_getsharedlibraryinformation.js @@ -0,0 +1,45 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +/** + * Tests whether the profiler responds to "getSharedLibraryInformation" adequately. + */ + +const Profiler = Cc["@mozilla.org/tools/profiler;1"].getService(Ci.nsIProfiler); + +function run_test() +{ + get_chrome_actors((client, form) => { + let actor = form.profilerActor; + test_getsharedlibraryinformation(client, actor, () => { + client.close().then(() => { + do_test_finished(); + }); + }); + }); + + do_test_pending(); +} + +function test_getsharedlibraryinformation(client, actor, callback) +{ + client.request({ to: actor, type: "getSharedLibraryInformation" }, response => { + do_check_eq(typeof response.sharedLibraryInformation, "string"); + let libs = []; + try { + libs = JSON.parse(response.sharedLibraryInformation); + } catch (e) { + do_check_true(false); + } + do_check_eq(typeof libs, "object"); + do_check_true(libs.length >= 1); + do_check_eq(typeof libs[0], "object"); + do_check_eq(typeof libs[0].name, "string"); + do_check_eq(typeof libs[0].start, "number"); + do_check_eq(typeof libs[0].end, "number"); + do_check_true(libs[0].start <= libs[0].end); + callback(); + }); +} diff --git a/devtools/server/tests/unit/test_promise_state-01.js b/devtools/server/tests/unit/test_promise_state-01.js new file mode 100644 index 000000000..a525560ab --- /dev/null +++ b/devtools/server/tests/unit/test_promise_state-01.js @@ -0,0 +1,40 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Test that the preview in a Promise's grip is correct when the Promise is + * pending. + */ + +function run_test() +{ + initTestDebuggerServer(); + const debuggee = addTestGlobal("test-promise-state"); + const client = new DebuggerClient(DebuggerServer.connectPipe()); + client.connect().then(function () { + attachTestTabAndResume(client, "test-promise-state", function (response, tabClient, threadClient) { + Task.spawn(function* () { + const packet = yield executeOnNextTickAndWaitForPause(() => evalCode(debuggee), client); + + const grip = packet.frame.environment.bindings.variables.p; + ok(grip.value.preview); + equal(grip.value.class, "Promise"); + equal(grip.value.promiseState.state, "pending"); + + finishClient(client); + }); + }); + }); + do_test_pending(); +} + +function evalCode(debuggee) { + Components.utils.evalInSandbox( + "doTest();\n" + + function doTest() { + var p = new Promise(function () {}); + debugger; + }, + debuggee + ); +} diff --git a/devtools/server/tests/unit/test_promise_state-02.js b/devtools/server/tests/unit/test_promise_state-02.js new file mode 100644 index 000000000..cf44f1946 --- /dev/null +++ b/devtools/server/tests/unit/test_promise_state-02.js @@ -0,0 +1,45 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Test that the preview in a Promise's grip is correct when the Promise is + * fulfilled. + */ + +function run_test() +{ + initTestDebuggerServer(); + const debuggee = addTestGlobal("test-promise-state"); + const client = new DebuggerClient(DebuggerServer.connectPipe()); + client.connect().then(function () { + attachTestTabAndResume(client, "test-promise-state", function (response, tabClient, threadClient) { + Task.spawn(function* () { + const packet = yield executeOnNextTickAndWaitForPause(() => evalCode(debuggee), client); + + const grip = packet.frame.environment.bindings.variables.p; + ok(grip.value.preview); + equal(grip.value.class, "Promise"); + equal(grip.value.promiseState.state, "fulfilled"); + equal(grip.value.promiseState.value.actorID, packet.frame.arguments[0].actorID, + "The promise's fulfilled state value should be the same value passed to the then function"); + + finishClient(client); + }); + }); + }); + do_test_pending(); +} + +function evalCode(debuggee) { + Components.utils.evalInSandbox( + "doTest();\n" + + function doTest() { + var resolved = Promise.resolve({}); + resolved.then(() => { + var p = resolved; + debugger; + }); + }, + debuggee + ); +} diff --git a/devtools/server/tests/unit/test_promise_state-03.js b/devtools/server/tests/unit/test_promise_state-03.js new file mode 100644 index 000000000..cf64e3e27 --- /dev/null +++ b/devtools/server/tests/unit/test_promise_state-03.js @@ -0,0 +1,45 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Test that the preview in a Promise's grip is correct when the Promise is + * rejected. + */ + +function run_test() +{ + initTestDebuggerServer(); + const debuggee = addTestGlobal("test-promise-state"); + const client = new DebuggerClient(DebuggerServer.connectPipe()); + client.connect().then(function () { + attachTestTabAndResume(client, "test-promise-state", function (response, tabClient, threadClient) { + Task.spawn(function* () { + const packet = yield executeOnNextTickAndWaitForPause(() => evalCode(debuggee), client); + + const grip = packet.frame.environment.bindings.variables.p; + ok(grip.value.preview); + equal(grip.value.class, "Promise"); + equal(grip.value.promiseState.state, "rejected"); + equal(grip.value.promiseState.reason.actorID, packet.frame.arguments[0].actorID, + "The promise's rejected state reason should be the same value passed to the then function"); + + finishClient(client); + }); + }); + }); + do_test_pending(); +} + +function evalCode(debuggee) { + Components.utils.evalInSandbox( + "doTest();\n" + + function doTest() { + var resolved = Promise.reject(new Error("uh oh")); + resolved.then(null, () => { + var p = resolved; + debugger; + }); + }, + debuggee + ); +} diff --git a/devtools/server/tests/unit/test_promises_actor_attach.js b/devtools/server/tests/unit/test_promises_actor_attach.js new file mode 100644 index 000000000..17c2a1f41 --- /dev/null +++ b/devtools/server/tests/unit/test_promises_actor_attach.js @@ -0,0 +1,52 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Test that we can attach and detach to the PromisesActor under the correct + * states. + */ + +const { PromisesFront } = require("devtools/shared/fronts/promises"); + +add_task(function* () { + let client = yield startTestDebuggerServer("promises-actor-test"); + let chromeActors = yield getChromeActors(client); + + // We have to attach the chrome TabActor before playing with the PromiseActor + yield attachTab(client, chromeActors); + yield testAttach(client, chromeActors); + + let response = yield listTabs(client); + let targetTab = findTab(response.tabs, "promises-actor-test"); + ok(targetTab, "Found our target tab."); + + let [ tabResponse ] = yield attachTab(client, targetTab); + + yield testAttach(client, tabResponse); + + yield close(client); +}); + +function* testAttach(client, parent) { + let promises = PromisesFront(client, parent); + + try { + yield promises.detach(); + ok(false, "Should not be able to detach when in a detached state."); + } catch (e) { + ok(true, "Expected detach to fail when already in a detached state."); + } + + yield promises.attach(); + ok(true, "Expected attach to succeed."); + + try { + yield promises.attach(); + ok(false, "Should not be able to attach when in an attached state."); + } catch (e) { + ok(true, "Expected attach to fail when already in an attached state."); + } + + yield promises.detach(); + ok(true, "Expected detach to succeed."); +} diff --git a/devtools/server/tests/unit/test_promises_actor_exist.js b/devtools/server/tests/unit/test_promises_actor_exist.js new file mode 100644 index 000000000..13eef3e99 --- /dev/null +++ b/devtools/server/tests/unit/test_promises_actor_exist.js @@ -0,0 +1,29 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Test that the PromisesActor exists in the TabActors and ChromeActors. + */ + +add_task(function* () { + let client = yield startTestDebuggerServer("promises-actor-test"); + + let response = yield listTabs(client); + let targetTab = findTab(response.tabs, "promises-actor-test"); + ok(targetTab, "Found our target tab."); + + // Attach to the TabActor and check the response + client.request({ to: targetTab.actor, type: "attach" }, response => { + ok(!("error" in response), "Expect no error in response."); + ok(response.from, targetTab.actor, + "Expect the target TabActor in response form field."); + ok(response.type, "tabAttached", + "Expect tabAttached in the response type."); + is(typeof response.promisesActor === "string", + "Should have a tab context PromisesActor."); + }); + + let chromeActors = yield getChromeActors(client); + ok(typeof chromeActors.promisesActor === "string", + "Should have a chrome context PromisesActor."); +}); diff --git a/devtools/server/tests/unit/test_promises_actor_list_promises.js b/devtools/server/tests/unit/test_promises_actor_list_promises.js new file mode 100644 index 000000000..f5b273121 --- /dev/null +++ b/devtools/server/tests/unit/test_promises_actor_list_promises.js @@ -0,0 +1,63 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Test that we can get the list of all live Promise objects from the + * PromisesActor. + */ + +"use strict"; + +const { PromisesFront } = require("devtools/shared/fronts/promises"); +const SECRET = "MyLittleSecret"; + +add_task(function* () { + let client = yield startTestDebuggerServer("promises-actor-test"); + let chromeActors = yield getChromeActors(client); + + // We have to attach the chrome TabActor before playing with the PromiseActor + yield attachTab(client, chromeActors); + yield testListPromises(client, chromeActors, v => + new Promise(resolve => resolve(v))); + + let response = yield listTabs(client); + let targetTab = findTab(response.tabs, "promises-actor-test"); + ok(targetTab, "Found our target tab."); + + yield testListPromises(client, targetTab, v => { + const debuggee = DebuggerServer.getTestGlobal("promises-actor-test"); + return debuggee.Promise.resolve(v); + }); + + yield close(client); +}); + +function* testListPromises(client, form, makePromise) { + let resolution = SECRET + Math.random(); + let promise = makePromise(resolution); + let front = PromisesFront(client, form); + + yield front.attach(); + + let promises = yield front.listPromises(); + + let found = false; + for (let p of promises) { + equal(p.type, "object", "Expect type to be Object"); + equal(p.class, "Promise", "Expect class to be Promise"); + equal(typeof p.promiseState.creationTimestamp, "number", + "Expect creation timestamp to be a number"); + equal(typeof p.promiseState.timeToSettle, "number", + "Expect time to settle to be a number"); + + if (p.promiseState.state === "fulfilled" && + p.promiseState.value === resolution) { + found = true; + } + } + + ok(found, "Found our promise"); + yield front.detach(); + // Appease eslint + void promise; +} diff --git a/devtools/server/tests/unit/test_promises_actor_onnewpromise.js b/devtools/server/tests/unit/test_promises_actor_onnewpromise.js new file mode 100644 index 000000000..04b3e6510 --- /dev/null +++ b/devtools/server/tests/unit/test_promises_actor_onnewpromise.js @@ -0,0 +1,72 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Test that we can get the list of all new Promise objects from the + * PromisesActor onNewPromise event handler. + */ + +"use strict"; + +const { PromisesFront } = require("devtools/shared/fronts/promises"); + +var events = require("sdk/event/core"); + +add_task(function* () { + let client = yield startTestDebuggerServer("promises-actor-test"); + let chromeActors = yield getChromeActors(client); + + ok(Promise.toString().includes("native code"), "Expect native DOM Promise"); + + // We have to attach the chrome TabActor before playing with the PromiseActor + yield attachTab(client, chromeActors); + yield testNewPromisesEvent(client, chromeActors, + v => new Promise(resolve => resolve(v))); + + let response = yield listTabs(client); + let targetTab = findTab(response.tabs, "promises-actor-test"); + ok(targetTab, "Found our target tab."); + + yield testNewPromisesEvent(client, targetTab, v => { + const debuggee = DebuggerServer.getTestGlobal("promises-actor-test"); + return debuggee.Promise.resolve(v); + }); + + yield close(client); +}); + +function* testNewPromisesEvent(client, form, makePromise) { + let front = PromisesFront(client, form); + let resolution = "MyLittleSecret" + Math.random(); + let found = false; + + yield front.attach(); + yield front.listPromises(); + + let onNewPromise = new Promise(resolve => { + events.on(front, "new-promises", promises => { + for (let p of promises) { + equal(p.type, "object", "Expect type to be Object"); + equal(p.class, "Promise", "Expect class to be Promise"); + equal(typeof p.promiseState.creationTimestamp, "number", + "Expect creation timestamp to be a number"); + + if (p.promiseState.state === "fulfilled" && + p.promiseState.value === resolution) { + found = true; + resolve(); + } else { + dump("Found non-target promise\n"); + } + } + }); + }); + + let promise = makePromise(resolution); + + yield onNewPromise; + ok(found, "Found our new promise"); + yield front.detach(); + // Appease eslint + void promise; +} diff --git a/devtools/server/tests/unit/test_promises_actor_onpromisesettled.js b/devtools/server/tests/unit/test_promises_actor_onpromisesettled.js new file mode 100644 index 000000000..ab4774733 --- /dev/null +++ b/devtools/server/tests/unit/test_promises_actor_onpromisesettled.js @@ -0,0 +1,92 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Test that we can get the list of Promise objects that have settled from the + * PromisesActor onPromiseSettled event handler. + */ + +"use strict"; + +Cu.import("resource://testing-common/PromiseTestUtils.jsm", this); + +const { PromisesFront } = require("devtools/shared/fronts/promises"); + +var events = require("sdk/event/core"); + +add_task(function* () { + let client = yield startTestDebuggerServer("promises-actor-test"); + let chromeActors = yield getChromeActors(client); + + ok(Promise.toString().includes("native code"), "Expect native DOM Promise"); + + // We have to attach the chrome TabActor before playing with the PromiseActor + yield attachTab(client, chromeActors); + yield testPromisesSettled(client, chromeActors, + v => new Promise(resolve => resolve(v)), + v => new Promise((resolve, reject) => reject(v))); + + let response = yield listTabs(client); + let targetTab = findTab(response.tabs, "promises-actor-test"); + ok(targetTab, "Found our target tab."); + + yield testPromisesSettled(client, targetTab, v => { + const debuggee = DebuggerServer.getTestGlobal("promises-actor-test"); + return debuggee.Promise.resolve(v); + }, v => { + const debuggee = DebuggerServer.getTestGlobal("promises-actor-test"); + return debuggee.Promise.reject(v); + }); + + yield close(client); +}); + +function* testPromisesSettled(client, form, makeResolvePromise, + makeRejectPromise) { + let front = PromisesFront(client, form); + let resolution = "MyLittleSecret" + Math.random(); + + yield front.attach(); + yield front.listPromises(); + + let onPromiseSettled = oncePromiseSettled(front, resolution, true, false); + let resolvedPromise = makeResolvePromise(resolution); + let foundResolvedPromise = yield onPromiseSettled; + ok(foundResolvedPromise, "Found our resolved promise"); + + PromiseTestUtils.expectUncaughtRejection(r => r.message == resolution); + onPromiseSettled = oncePromiseSettled(front, resolution, false, true); + let rejectedPromise = makeRejectPromise(resolution); + let foundRejectedPromise = yield onPromiseSettled; + ok(foundRejectedPromise, "Found our rejected promise"); + + yield front.detach(); + // Appease eslint + void resolvedPromise; + void rejectedPromise; +} + +function oncePromiseSettled(front, resolution, resolveValue, rejectValue) { + return new Promise(resolve => { + events.on(front, "promises-settled", promises => { + for (let p of promises) { + equal(p.type, "object", "Expect type to be Object"); + equal(p.class, "Promise", "Expect class to be Promise"); + equal(typeof p.promiseState.creationTimestamp, "number", + "Expect creation timestamp to be a number"); + equal(typeof p.promiseState.timeToSettle, "number", + "Expect time to settle to be a number"); + + if (p.promiseState.state === "fulfilled" && + p.promiseState.value === resolution) { + resolve(resolveValue); + } else if (p.promiseState.state === "rejected" && + p.promiseState.reason === resolution) { + resolve(rejectValue); + } else { + dump("Found non-target promise\n"); + } + } + }); + }); +} diff --git a/devtools/server/tests/unit/test_promises_client_getdependentpromises.js b/devtools/server/tests/unit/test_promises_client_getdependentpromises.js new file mode 100644 index 000000000..8900cf81c --- /dev/null +++ b/devtools/server/tests/unit/test_promises_client_getdependentpromises.js @@ -0,0 +1,112 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Test that we can get the list of dependent promises from the ObjectClient. + */ + +"use strict"; + +const { PromisesFront } = require("devtools/shared/fronts/promises"); + +var events = require("sdk/event/core"); + +add_task(function* () { + let client = yield startTestDebuggerServer("test-promises-dependentpromises"); + let chromeActors = yield getChromeActors(client); + yield attachTab(client, chromeActors); + + ok(Promise.toString().includes("native code"), "Expect native DOM Promise."); + + yield testGetDependentPromises(client, chromeActors, () => { + let p = new Promise(() => {}); + p.name = "p"; + let q = p.then(); + q.name = "q"; + let r = p.then(null, () => {}); + r.name = "r"; + + return p; + }); + + let response = yield listTabs(client); + let targetTab = findTab(response.tabs, "test-promises-dependentpromises"); + ok(targetTab, "Found our target tab."); + yield attachTab(client, targetTab); + + yield testGetDependentPromises(client, targetTab, () => { + const debuggee = + DebuggerServer.getTestGlobal("test-promises-dependentpromises"); + + let p = new debuggee.Promise(() => {}); + p.name = "p"; + let q = p.then(); + q.name = "q"; + let r = p.then(null, () => {}); + r.name = "r"; + + return p; + }); + + yield close(client); +}); + +function* testGetDependentPromises(client, form, makePromises) { + let front = PromisesFront(client, form); + + yield front.attach(); + yield front.listPromises(); + + // Get the grip for promise p + let onNewPromise = new Promise(resolve => { + events.on(front, "new-promises", promises => { + for (let p of promises) { + if (p.preview.ownProperties.name && + p.preview.ownProperties.name.value === "p") { + resolve(p); + } + } + }); + }); + + let promise = makePromises(); + + let grip = yield onNewPromise; + ok(grip, "Found our promise p."); + + let objectClient = new ObjectClient(client, grip); + ok(objectClient, "Got Object Client."); + + // Get the dependent promises for promise p and assert that the list of + // dependent promises is correct + yield new Promise(resolve => { + objectClient.getDependentPromises(response => { + let dependentNames = response.promises.map(p => + p.preview.ownProperties.name.value); + let expectedDependentNames = ["q", "r"]; + + equal(dependentNames.length, expectedDependentNames.length, + "Got expected number of dependent promises."); + + for (let i = 0; i < dependentNames.length; i++) { + equal(dependentNames[i], expectedDependentNames[i], + "Got expected dependent name."); + } + + for (let p of response.promises) { + equal(p.type, "object", "Expect type to be Object."); + equal(p.class, "Promise", "Expect class to be Promise."); + equal(typeof p.promiseState.creationTimestamp, "number", + "Expect creation timestamp to be a number."); + ok(!p.promiseState.timeToSettle, + "Expect time to settle to be undefined."); + } + + resolve(); + }); + }); + + yield front.detach(); + // Appease eslint + void promise; +} diff --git a/devtools/server/tests/unit/test_promises_object_creationtimestamp.js b/devtools/server/tests/unit/test_promises_object_creationtimestamp.js new file mode 100644 index 000000000..1360be56a --- /dev/null +++ b/devtools/server/tests/unit/test_promises_object_creationtimestamp.js @@ -0,0 +1,71 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Test that we get the approximate time range for promise creation timestamp. + */ + +"use strict"; + +const { PromisesFront } = require("devtools/shared/fronts/promises"); + +var events = require("sdk/event/core"); + +add_task(function* () { + let client = yield startTestDebuggerServer("promises-object-test"); + let chromeActors = yield getChromeActors(client); + + ok(Promise.toString().includes("native code"), "Expect native DOM Promise."); + + // We have to attach the chrome TabActor before playing with the PromiseActor + yield attachTab(client, chromeActors); + yield testPromiseCreationTimestamp(client, chromeActors, v => { + return new Promise(resolve => resolve(v)); + }); + + let response = yield listTabs(client); + let targetTab = findTab(response.tabs, "promises-object-test"); + ok(targetTab, "Found our target tab."); + + yield testPromiseCreationTimestamp(client, targetTab, v => { + const debuggee = DebuggerServer.getTestGlobal("promises-object-test"); + return debuggee.Promise.resolve(v); + }); + + yield close(client); +}); + +function* testPromiseCreationTimestamp(client, form, makePromise) { + let front = PromisesFront(client, form); + let resolution = "MyLittleSecret" + Math.random(); + + yield front.attach(); + yield front.listPromises(); + + let onNewPromise = new Promise(resolve => { + events.on(front, "new-promises", promises => { + for (let p of promises) { + if (p.promiseState.state === "fulfilled" && + p.promiseState.value === resolution) { + resolve(p); + } + } + }); + }); + + let start = Date.now(); + let promise = makePromise(resolution); + let end = Date.now(); + + let grip = yield onNewPromise; + ok(grip, "Found our new promise."); + + let creationTimestamp = grip.promiseState.creationTimestamp; + + ok(start - 1 <= creationTimestamp && creationTimestamp <= end + 1, + "Expect promise creation timestamp to be within elapsed time range."); + + yield front.detach(); + // Appease eslint + void promise; +} diff --git a/devtools/server/tests/unit/test_promises_object_timetosettle-01.js b/devtools/server/tests/unit/test_promises_object_timetosettle-01.js new file mode 100644 index 000000000..1b3240e3d --- /dev/null +++ b/devtools/server/tests/unit/test_promises_object_timetosettle-01.js @@ -0,0 +1,80 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Test whether or not we get the time to settle depending on the state of the + * promise. + */ + +"use strict"; + +const { PromisesFront } = require("devtools/shared/fronts/promises"); + +var events = require("sdk/event/core"); + +add_task(function* () { + let client = yield startTestDebuggerServer("test-promises-timetosettle"); + let chromeActors = yield getChromeActors(client); + + ok(Promise.toString().includes("native code"), "Expect native DOM Promise."); + + // We have to attach the chrome TabActor before playing with the PromiseActor + yield attachTab(client, chromeActors); + yield testGetTimeToSettle(client, chromeActors, () => { + let p = new Promise(() => {}); + p.name = "p"; + let q = p.then(); + q.name = "q"; + + return p; + }); + + let response = yield listTabs(client); + let targetTab = findTab(response.tabs, "test-promises-timetosettle"); + ok(targetTab, "Found our target tab."); + + yield testGetTimeToSettle(client, targetTab, () => { + const debuggee = + DebuggerServer.getTestGlobal("test-promises-timetosettle"); + + let p = new debuggee.Promise(() => {}); + p.name = "p"; + let q = p.then(); + q.name = "q"; + + return p; + }); + + yield close(client); +}); + +function* testGetTimeToSettle(client, form, makePromises) { + let front = PromisesFront(client, form); + + yield front.attach(); + yield front.listPromises(); + + let onNewPromise = new Promise(resolve => { + events.on(front, "new-promises", promises => { + for (let p of promises) { + if (p.promiseState.state === "pending") { + ok(!p.promiseState.timeToSettle, + "Expect no time to settle for unsettled promise."); + } else { + ok(p.promiseState.timeToSettle, + "Expect time to settle for settled promise."); + equal(typeof p.promiseState.timeToSettle, "number", + "Expect time to settle to be a number."); + } + } + resolve(); + }); + }); + + let promise = makePromises(); + + yield onNewPromise; + yield front.detach(); + // Appease eslint + void promise; +} diff --git a/devtools/server/tests/unit/test_promises_object_timetosettle-02.js b/devtools/server/tests/unit/test_promises_object_timetosettle-02.js new file mode 100644 index 000000000..10224d0b9 --- /dev/null +++ b/devtools/server/tests/unit/test_promises_object_timetosettle-02.js @@ -0,0 +1,74 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Test that we get the expected settlement time for promise time to settle. + */ + +"use strict"; + +const { PromisesFront } = require("devtools/shared/fronts/promises"); +const { setTimeout } = require("sdk/timers"); + +var events = require("sdk/event/core"); + +add_task(function* () { + let client = yield startTestDebuggerServer("test-promises-timetosettle"); + let chromeActors = yield getChromeActors(client); + yield attachTab(client, chromeActors); + + ok(Promise.toString().includes("native code"), "Expect native DOM Promise."); + + // We have to attach the chrome TabActor before playing with the PromiseActor + yield attachTab(client, chromeActors); + yield testGetTimeToSettle(client, chromeActors, + v => new Promise(resolve => setTimeout(() => resolve(v), 100))); + + let response = yield listTabs(client); + let targetTab = findTab(response.tabs, "test-promises-timetosettle"); + ok(targetTab, "Found our target tab."); + yield attachTab(client, targetTab); + + yield testGetTimeToSettle(client, targetTab, v => { + const debuggee = + DebuggerServer.getTestGlobal("test-promises-timetosettle"); + return new debuggee.Promise(resolve => setTimeout(() => resolve(v), 100)); + }); + + yield close(client); +}); + +function* testGetTimeToSettle(client, form, makePromise) { + let front = PromisesFront(client, form); + let resolution = "MyLittleSecret" + Math.random(); + let found = false; + + yield front.attach(); + yield front.listPromises(); + + let onNewPromise = new Promise(resolve => { + events.on(front, "promises-settled", promises => { + for (let p of promises) { + if (p.promiseState.state === "fulfilled" && + p.promiseState.value === resolution) { + let timeToSettle = Math.floor(p.promiseState.timeToSettle / 100) * 100; + ok(timeToSettle >= 100, + "Expect time to settle for resolved promise to be " + + "at least 100ms, got " + timeToSettle + "ms."); + found = true; + resolve(); + } else { + dump("Found non-target promise.\n"); + } + } + }); + }); + + let promise = makePromise(resolution); + + yield onNewPromise; + ok(found, "Found our new promise."); + yield front.detach(); + // Appease eslint + void promise; +} diff --git a/devtools/server/tests/unit/test_protocolSpec.js b/devtools/server/tests/unit/test_protocolSpec.js new file mode 100644 index 000000000..cc0746387 --- /dev/null +++ b/devtools/server/tests/unit/test_protocolSpec.js @@ -0,0 +1,17 @@ +const run_test = Test(function* () { + initTestDebuggerServer(); + const connection = DebuggerServer.connectPipe(); + const client = Async(new DebuggerClient(connection)); + + yield client.connect(); + + const response = yield client.request({ + to: "root", + type: "protocolDescription" + }); + + assert(response.from == "root"); + assert(typeof (response.types) === "object"); + + yield client.close(); +}); diff --git a/devtools/server/tests/unit/test_protocol_abort.js b/devtools/server/tests/unit/test_protocol_abort.js new file mode 100644 index 000000000..bb25d1b2c --- /dev/null +++ b/devtools/server/tests/unit/test_protocol_abort.js @@ -0,0 +1,83 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Outstanding requests should be rejected when the connection aborts + * unexpectedly. + */ + +var protocol = require("devtools/shared/protocol"); +var {Arg, Option, RetVal} = protocol; +var events = require("sdk/event/core"); + +function simpleHello() { + return { + from: "root", + applicationType: "xpcshell-tests", + traits: [], + }; +} + +const rootSpec = protocol.generateActorSpec({ + typeName: "root", + + methods: { + simpleReturn: { + response: { value: RetVal() } + } + } +}); + +var RootActor = protocol.ActorClassWithSpec(rootSpec, { + typeName: "root", + initialize: function (conn) { + protocol.Actor.prototype.initialize.call(this, conn); + // Root actor owns itself. + this.manage(this); + this.actorID = "root"; + this.sequence = 0; + }, + + sayHello: simpleHello, + + simpleReturn: function () { + return this.sequence++; + } +}); + +var RootFront = protocol.FrontClassWithSpec(rootSpec, { + initialize: function (client) { + this.actorID = "root"; + protocol.Front.prototype.initialize.call(this, client); + // Root owns itself. + this.manage(this); + } +}); + +function run_test() { + DebuggerServer.createRootActor = RootActor; + DebuggerServer.init(); + + let trace = connectPipeTracing(); + let client = new DebuggerClient(trace); + let rootClient; + + client.connect().then(([applicationType, traits]) => { + rootClient = RootFront(client); + + rootClient.simpleReturn().then(() => { + ok(false, "Connection was aborted, request shouldn't resolve"); + do_test_finished(); + }, e => { + let error = e.toString(); + ok(true, "Connection was aborted, request rejected correctly"); + ok(error.includes("Request stack:"), "Error includes request stack"); + ok(error.includes("test_protocol_abort.js"), "Stack includes this test"); + do_test_finished(); + }); + + trace.close(); + }); + + do_test_pending(); +} diff --git a/devtools/server/tests/unit/test_protocol_async.js b/devtools/server/tests/unit/test_protocol_async.js new file mode 100644 index 000000000..75f053863 --- /dev/null +++ b/devtools/server/tests/unit/test_protocol_async.js @@ -0,0 +1,184 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Make sure we get replies in the same order that we sent their + * requests even when earlier requests take several event ticks to + * complete. + */ + +var protocol = require("devtools/shared/protocol"); +var {Arg, Option, RetVal} = protocol; +var events = require("sdk/event/core"); + +function simpleHello() { + return { + from: "root", + applicationType: "xpcshell-tests", + traits: [], + }; +} + +const rootSpec = protocol.generateActorSpec({ + typeName: "root", + + methods: { + simpleReturn: { + response: { value: RetVal() }, + }, + promiseReturn: { + request: { toWait: Arg(0, "number") }, + response: { value: RetVal("number") }, + }, + simpleThrow: { + response: { value: RetVal("number") } + }, + promiseThrow: { + response: { value: RetVal("number") }, + } + } +}); + +var RootActor = protocol.ActorClassWithSpec(rootSpec, { + initialize: function (conn) { + protocol.Actor.prototype.initialize.call(this, conn); + // Root actor owns itself. + this.manage(this); + this.actorID = "root"; + this.sequence = 0; + }, + + sayHello: simpleHello, + + simpleReturn: function () { + return this.sequence++; + }, + + promiseReturn: function (toWait) { + // Guarantee that this resolves after simpleReturn returns. + let deferred = promise.defer(); + let sequence = this.sequence++; + + // Wait until the number of requests specified by toWait have + // happened, to test queuing. + let check = () => { + if ((this.sequence - sequence) < toWait) { + do_execute_soon(check); + return; + } + deferred.resolve(sequence); + }; + do_execute_soon(check); + + return deferred.promise; + }, + + simpleThrow: function () { + throw new Error(this.sequence++); + }, + + promiseThrow: function () { + // Guarantee that this resolves after simpleReturn returns. + let deferred = promise.defer(); + let sequence = this.sequence++; + // This should be enough to force a failure if the code is broken. + do_timeout(150, () => { + deferred.reject(sequence++); + }); + return deferred.promise; + } +}); + +var RootFront = protocol.FrontClassWithSpec(rootSpec, { + initialize: function (client) { + this.actorID = "root"; + protocol.Front.prototype.initialize.call(this, client); + // Root owns itself. + this.manage(this); + } +}); + +function run_test() +{ + DebuggerServer.createRootActor = RootActor; + DebuggerServer.init(); + + let trace = connectPipeTracing(); + let client = new DebuggerClient(trace); + let rootClient; + + client.connect().then(([applicationType, traits]) => { + rootClient = RootFront(client); + + let calls = []; + let sequence = 0; + + // Execute a call that won't finish processing until 2 + // more calls have happened + calls.push(rootClient.promiseReturn(2).then(ret => { + do_check_eq(sequence, 0); // Check right return order + do_check_eq(ret, sequence++); // Check request handling order + })); + + // Put a few requests into the backlog + + calls.push(rootClient.simpleReturn().then(ret => { + do_check_eq(sequence, 1); // Check right return order + do_check_eq(ret, sequence++); // Check request handling order + })); + + calls.push(rootClient.simpleReturn().then(ret => { + do_check_eq(sequence, 2); // Check right return order + do_check_eq(ret, sequence++); // Check request handling order + })); + + calls.push(rootClient.simpleThrow().then(() => { + do_check_true(false, "simpleThrow shouldn't succeed!"); + }, error => { + do_check_eq(sequence++, 3); // Check right return order + })); + + // While packets are sent in the correct order, rejection handlers + // registered in "Promise.jsm" may be invoked later than fulfillment + // handlers, meaning that we can't check the actual order with certainty. + let deferAfterRejection = promise.defer(); + + calls.push(rootClient.promiseThrow().then(() => { + do_check_true(false, "promiseThrow shouldn't succeed!"); + }, error => { + do_check_eq(sequence++, 4); // Check right return order + do_check_true(true, "simple throw should throw"); + deferAfterRejection.resolve(); + })); + + calls.push(rootClient.simpleReturn().then(ret => { + return deferAfterRejection.promise.then(function () { + do_check_eq(sequence, 5); // Check right return order + do_check_eq(ret, sequence++); // Check request handling order + }); + })); + + // Break up the backlog with a long request that waits + // for another simpleReturn before completing + calls.push(rootClient.promiseReturn(1).then(ret => { + return deferAfterRejection.promise.then(function () { + do_check_eq(sequence, 6); // Check right return order + do_check_eq(ret, sequence++); // Check request handling order + }); + })); + + calls.push(rootClient.simpleReturn().then(ret => { + return deferAfterRejection.promise.then(function () { + do_check_eq(sequence, 7); // Check right return order + do_check_eq(ret, sequence++); // Check request handling order + }); + })); + + promise.all(calls).then(() => { + client.close().then(() => { + do_test_finished(); + }); + }); + }); + do_test_pending(); +} diff --git a/devtools/server/tests/unit/test_protocol_children.js b/devtools/server/tests/unit/test_protocol_children.js new file mode 100644 index 000000000..67773ebef --- /dev/null +++ b/devtools/server/tests/unit/test_protocol_children.js @@ -0,0 +1,559 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Test simple requests using the protocol helpers. + */ +var protocol = require("devtools/shared/protocol"); +var {preEvent, types, Arg, Option, RetVal} = protocol; + +var events = require("sdk/event/core"); + +function simpleHello() { + return { + from: "root", + applicationType: "xpcshell-tests", + traits: [], + }; +} + +var testTypes = {}; + +// Predeclaring the actor type so that it can be used in the +// implementation of the child actor. +types.addActorType("childActor"); + +const childSpec = protocol.generateActorSpec({ + typeName: "childActor", + + events: { + "event1" : { + a: Arg(0), + b: Arg(1), + c: Arg(2) + }, + "event2" : { + a: Arg(0), + b: Arg(1), + c: Arg(2) + }, + "named-event": { + type: "namedEvent", + a: Arg(0), + b: Arg(1), + c: Arg(2) + }, + "object-event": { + type: "objectEvent", + detail: Arg(0, "childActor#detail1"), + }, + "array-object-event": { + type: "arrayObjectEvent", + detail: Arg(0, "array:childActor#detail2"), + } + }, + + methods: { + echo: { + request: { str: Arg(0) }, + response: { str: RetVal("string") }, + }, + getDetail1: { + // This also exercises return-value-as-packet. + response: RetVal("childActor#detail1"), + }, + getDetail2: { + // This also exercises return-value-as-packet. + response: RetVal("childActor#detail2"), + }, + getIDDetail: { + response: { + idDetail: RetVal("childActor#actorid") + } + }, + getIntArray: { + request: { inputArray: Arg(0, "array:number") }, + response: RetVal("array:number") + }, + getSibling: { + request: { id: Arg(0) }, + response: { sibling: RetVal("childActor") } + }, + emitEvents: { + response: { value: "correct response" }, + }, + release: { + release: true + } + } +}); + +var ChildActor = protocol.ActorClassWithSpec(childSpec, { + // Actors returned by this actor should be owned by the root actor. + marshallPool: function () { return this.parent(); }, + + toString: function () { return "[ChildActor " + this.childID + "]"; }, + + initialize: function (conn, id) { + protocol.Actor.prototype.initialize.call(this, conn); + this.childID = id; + }, + + destroy: function () { + protocol.Actor.prototype.destroy.call(this); + this.destroyed = true; + }, + + form: function (detail) { + if (detail === "actorid") { + return this.actorID; + } + return { + actor: this.actorID, + childID: this.childID, + detail: detail + }; + }, + + echo: function (str) { + return str; + }, + + getDetail1: function () { + return this; + }, + + getDetail2: function () { + return this; + }, + + getIDDetail: function () { + return this; + }, + + getIntArray: function (inputArray) { + // Test that protocol.js converts an iterator to an array. + let f = function* () { + for (let i of inputArray) { + yield 2 * i; + } + }; + return f(); + }, + + getSibling: function (id) { + return this.parent().getChild(id); + }, + + emitEvents: function () { + events.emit(this, "event1", 1, 2, 3); + events.emit(this, "event2", 4, 5, 6); + events.emit(this, "named-event", 1, 2, 3); + events.emit(this, "object-event", this); + events.emit(this, "array-object-event", [this]); + }, + + release: function () { }, +}); + +var ChildFront = protocol.FrontClassWithSpec(childSpec, { + initialize: function (client, form) { + protocol.Front.prototype.initialize.call(this, client, form); + }, + + destroy: function () { + this.destroyed = true; + protocol.Front.prototype.destroy.call(this); + }, + + marshallPool: function () { return this.parent(); }, + + toString: function () { return "[child front " + this.childID + "]"; }, + + form: function (form, detail) { + if (detail === "actorid") { + return; + } + this.childID = form.childID; + this.detail = form.detail; + }, + + onEvent1: preEvent("event1", function (a, b, c) { + this.event1arg3 = c; + }), + + onEvent2a: preEvent("event2", function (a, b, c) { + return promise.resolve().then(() => this.event2arg3 = c); + }), + + onEvent2b: preEvent("event2", function (a, b, c) { + this.event2arg2 = b; + }), +}); + +types.addDictType("manyChildrenDict", { + child5: "childActor", + more: "array:childActor", +}); + +types.addLifetime("temp", "_temporaryHolder"); + +const rootSpec = protocol.generateActorSpec({ + typeName: "root", + + methods: { + getChild: { + request: { str: Arg(0) }, + response: { actor: RetVal("childActor") }, + }, + getChildren: { + request: { ids: Arg(0, "array:string") }, + response: { children: RetVal("array:childActor") }, + }, + getChildren2: { + request: { ids: Arg(0, "array:childActor") }, + response: { children: RetVal("array:childActor") }, + }, + getManyChildren: { + response: RetVal("manyChildrenDict") + }, + getTemporaryChild: { + request: { id: Arg(0) }, + response: { child: RetVal("temp:childActor") } + }, + clearTemporaryChildren: {} + } +}); + +var rootActor = null; +var RootActor = protocol.ActorClassWithSpec(rootSpec, { + toString: function () { return "[root actor]"; }, + + initialize: function (conn) { + rootActor = this; + this.actorID = "root"; + this._children = {}; + protocol.Actor.prototype.initialize.call(this, conn); + // Root actor owns itself. + this.manage(this); + }, + + sayHello: simpleHello, + + getChild: function (id) { + if (id in this._children) { + return this._children[id]; + } + let child = new ChildActor(this.conn, id); + this._children[id] = child; + return child; + }, + + getChildren: function (ids) { + return ids.map(id => this.getChild(id)); + }, + + getChildren2: function (ids) { + let f = function* () { + for (let c of ids) { + yield c; + } + }; + return f(); + }, + + getManyChildren: function () { + return { + foo: "bar", // note that this isn't in the specialization array. + child5: this.getChild("child5"), + more: [ this.getChild("child6"), this.getChild("child7") ] + }; + }, + + // This should remind you of a pause actor. + getTemporaryChild: function (id) { + if (!this._temporaryHolder) { + this._temporaryHolder = this.manage(new protocol.Actor(this.conn)); + } + return new ChildActor(this.conn, id); + }, + + clearTemporaryChildren: function (id) { + if (!this._temporaryHolder) { + return; + } + this._temporaryHolder.destroy(); + delete this._temporaryHolder; + } +}); + +var RootFront = protocol.FrontClassWithSpec(rootSpec, { + toString: function () { return "[root front]"; }, + initialize: function (client) { + this.actorID = "root"; + protocol.Front.prototype.initialize.call(this, client); + // Root actor owns itself. + this.manage(this); + }, + + getTemporaryChild: protocol.custom(function (id) { + if (!this._temporaryHolder) { + this._temporaryHolder = protocol.Front(this.conn); + this._temporaryHolder.actorID = this.actorID + "_temp"; + this._temporaryHolder = this.manage(this._temporaryHolder); + } + return this._getTemporaryChild(id); + }, { + impl: "_getTemporaryChild" + }), + + clearTemporaryChildren: protocol.custom(function () { + if (!this._temporaryHolder) { + return promise.resolve(undefined); + } + this._temporaryHolder.destroy(); + delete this._temporaryHolder; + return this._clearTemporaryChildren(); + }, { + impl: "_clearTemporaryChildren" + }) +}); + +function run_test() +{ + DebuggerServer.createRootActor = (conn => { + return RootActor(conn); + }); + DebuggerServer.init(); + + let trace = connectPipeTracing(); + let client = new DebuggerClient(trace); + client.connect().then(([applicationType, traits]) => { + trace.expectReceive({"from":"<actorid>", "applicationType":"xpcshell-tests", "traits":[]}); + do_check_eq(applicationType, "xpcshell-tests"); + + let rootFront = RootFront(client); + let childFront = null; + + let expectRootChildren = size => { + do_check_eq(rootActor._poolMap.size, size + 1); + do_check_eq(rootFront._poolMap.size, size + 1); + if (childFront) { + do_check_eq(childFront._poolMap.size, 0); + } + }; + + rootFront.getChild("child1").then(ret => { + trace.expectSend({"type":"getChild", "str":"child1", "to":"<actorid>"}); + trace.expectReceive({"actor":"<actorid>", "from":"<actorid>"}); + + childFront = ret; + do_check_true(childFront instanceof ChildFront); + do_check_eq(childFront.childID, "child1"); + expectRootChildren(1); + }).then(() => { + // Request the child again, make sure the same is returned. + return rootFront.getChild("child1"); + }).then(ret => { + trace.expectSend({"type":"getChild", "str":"child1", "to":"<actorid>"}); + trace.expectReceive({"actor":"<actorid>", "from":"<actorid>"}); + + expectRootChildren(1); + do_check_true(ret === childFront); + }).then(() => { + return childFront.echo("hello"); + }).then(ret => { + trace.expectSend({"type":"echo", "str":"hello", "to":"<actorid>"}); + trace.expectReceive({"str":"hello", "from":"<actorid>"}); + + do_check_eq(ret, "hello"); + }).then(() => { + return childFront.getDetail1(); + }).then(ret => { + trace.expectSend({"type":"getDetail1", "to":"<actorid>"}); + trace.expectReceive({"actor":"<actorid>", "childID":"child1", "detail":"detail1", "from":"<actorid>"}); + do_check_true(ret === childFront); + do_check_eq(childFront.detail, "detail1"); + }).then(() => { + return childFront.getDetail2(); + }).then(ret => { + trace.expectSend({"type":"getDetail2", "to":"<actorid>"}); + trace.expectReceive({"actor":"<actorid>", "childID":"child1", "detail":"detail2", "from":"<actorid>"}); + do_check_true(ret === childFront); + do_check_eq(childFront.detail, "detail2"); + }).then(() => { + return childFront.getIDDetail(); + }).then(ret => { + trace.expectSend({"type":"getIDDetail", "to":"<actorid>"}); + trace.expectReceive({"idDetail": childFront.actorID, "from":"<actorid>"}); + do_check_true(ret === childFront); + }).then(() => { + return childFront.getSibling("siblingID"); + }).then(ret => { + trace.expectSend({"type":"getSibling", "id":"siblingID", "to":"<actorid>"}); + trace.expectReceive({"sibling":{"actor":"<actorid>", "childID":"siblingID"}, "from":"<actorid>"}); + + expectRootChildren(2); + }).then(ret => { + return rootFront.getTemporaryChild("temp1").then(temp1 => { + trace.expectSend({"type":"getTemporaryChild", "id":"temp1", "to":"<actorid>"}); + trace.expectReceive({"child":{"actor":"<actorid>", "childID":"temp1"}, "from":"<actorid>"}); + + // At this point we expect two direct children, plus the temporary holder + // which should hold 1 itself. + do_check_eq(rootActor._temporaryHolder.__poolMap.size, 1); + do_check_eq(rootFront._temporaryHolder.__poolMap.size, 1); + + expectRootChildren(3); + return rootFront.getTemporaryChild("temp2").then(temp2 => { + trace.expectSend({"type":"getTemporaryChild", "id":"temp2", "to":"<actorid>"}); + trace.expectReceive({"child":{"actor":"<actorid>", "childID":"temp2"}, "from":"<actorid>"}); + + // Same amount of direct children, and an extra in the temporary holder. + expectRootChildren(3); + do_check_eq(rootActor._temporaryHolder.__poolMap.size, 2); + do_check_eq(rootFront._temporaryHolder.__poolMap.size, 2); + + // Get the children of the temporary holder... + let checkActors = rootActor._temporaryHolder.__poolMap.values(); + let checkFronts = rootFront._temporaryHolder.__poolMap.values(); + + // Now release the temporary holders and expect them to drop again. + return rootFront.clearTemporaryChildren().then(() => { + trace.expectSend({"type":"clearTemporaryChildren", "to":"<actorid>"}); + trace.expectReceive({"from":"<actorid>"}); + + expectRootChildren(2); + do_check_false(!!rootActor._temporaryHolder); + do_check_false(!!rootFront._temporaryHolder); + for (let checkActor of checkActors) { + do_check_true(checkActor.destroyed); + do_check_true(checkActor.destroyed); + } + }); + }); + }); + }).then(ret => { + return rootFront.getChildren(["child1", "child2"]); + }).then(ret => { + trace.expectSend({"type":"getChildren", "ids":["child1", "child2"], "to":"<actorid>"}); + trace.expectReceive({"children":[{"actor":"<actorid>", "childID":"child1"}, {"actor":"<actorid>", "childID":"child2"}], "from":"<actorid>"}); + + expectRootChildren(3); + do_check_true(ret[0] === childFront); + do_check_true(ret[1] !== childFront); + do_check_true(ret[1] instanceof ChildFront); + + // On both children, listen to events. We're only + // going to trigger events on the first child, so an event + // triggered on the second should cause immediate failures. + + let set = new Set(["event1", "event2", "named-event", "object-event", "array-object-event"]); + + childFront.on("event1", (a, b, c) => { + do_check_eq(a, 1); + do_check_eq(b, 2); + do_check_eq(c, 3); + // Verify that the pre-event handler was called. + do_check_eq(childFront.event1arg3, 3); + set.delete("event1"); + }); + childFront.on("event2", (a, b, c) => { + do_check_eq(a, 4); + do_check_eq(b, 5); + do_check_eq(c, 6); + // Verify that the async pre-event handler was called, + // setting the property before this handler was called. + do_check_eq(childFront.event2arg3, 6); + // And check that the sync preEvent with the same name is also + // executed + do_check_eq(childFront.event2arg2, 5); + set.delete("event2"); + }); + childFront.on("named-event", (a, b, c) => { + do_check_eq(a, 1); + do_check_eq(b, 2); + do_check_eq(c, 3); + set.delete("named-event"); + }); + childFront.on("object-event", (obj) => { + do_check_true(obj === childFront); + do_check_eq(childFront.detail, "detail1"); + set.delete("object-event"); + }); + childFront.on("array-object-event", (array) => { + do_check_true(array[0] === childFront); + do_check_eq(childFront.detail, "detail2"); + set.delete("array-object-event"); + }); + + let fail = function () { + do_throw("Unexpected event"); + }; + ret[1].on("event1", fail); + ret[1].on("event2", fail); + ret[1].on("named-event", fail); + ret[1].on("object-event", fail); + ret[1].on("array-object-event", fail); + + return childFront.emitEvents().then(() => { + trace.expectSend({"type":"emitEvents", "to":"<actorid>"}); + trace.expectReceive({"type":"event1", "a":1, "b":2, "c":3, "from":"<actorid>"}); + trace.expectReceive({"type":"event2", "a":4, "b":5, "c":6, "from":"<actorid>"}); + trace.expectReceive({"type":"namedEvent", "a":1, "b":2, "c":3, "from":"<actorid>"}); + trace.expectReceive({"type":"objectEvent", "detail":{"actor":"<actorid>", "childID":"child1", "detail":"detail1"}, "from":"<actorid>"}); + trace.expectReceive({"type":"arrayObjectEvent", "detail":[{"actor":"<actorid>", "childID":"child1", "detail":"detail2"}], "from":"<actorid>"}); + trace.expectReceive({"value":"correct response", "from":"<actorid>"}); + + + do_check_eq(set.size, 0); + }); + }).then(ret => { + return rootFront.getManyChildren(); + }).then(ret => { + trace.expectSend({"type":"getManyChildren", "to":"<actorid>"}); + trace.expectReceive({"foo":"bar", "child5":{"actor":"<actorid>", "childID":"child5"}, "more":[{"actor":"<actorid>", "childID":"child6"}, {"actor":"<actorid>", "childID":"child7"}], "from":"<actorid>"}); + + // Check all the crazy stuff we did in getManyChildren + do_check_eq(ret.foo, "bar"); + do_check_eq(ret.child5.childID, "child5"); + do_check_eq(ret.more[0].childID, "child6"); + do_check_eq(ret.more[1].childID, "child7"); + }).then(() => { + // Test accepting a generator. + let f = function* () { + for (let i of [1, 2, 3, 4, 5]) { + yield i; + } + }; + return childFront.getIntArray(f()); + }).then((ret) => { + do_check_eq(ret.length, 5); + let expected = [2, 4, 6, 8, 10]; + for (let i = 0; i < 5; ++i) { + do_check_eq(ret[i], expected[i]); + } + }).then(() => { + return rootFront.getChildren(["child1", "child2"]); + }).then(ids => { + let f = function* () { + for (let id of ids) { + yield id; + } + }; + return rootFront.getChildren2(f()); + }).then(ret => { + do_check_eq(ret.length, 2); + do_check_true(ret[0] === childFront); + do_check_true(ret[1] !== childFront); + do_check_true(ret[1] instanceof ChildFront); + }).then(() => { + client.close().then(() => { + do_test_finished(); + }); + }).then(null, err => { + do_report_unexpected_exception(err, "Failure executing test"); + }); + }); + do_test_pending(); +} diff --git a/devtools/server/tests/unit/test_protocol_formtype.js b/devtools/server/tests/unit/test_protocol_formtype.js new file mode 100644 index 000000000..27ac0bee9 --- /dev/null +++ b/devtools/server/tests/unit/test_protocol_formtype.js @@ -0,0 +1,177 @@ +var protocol = require("devtools/shared/protocol"); +var {Arg, Option, RetVal} = protocol; + +protocol.types.addActorType("child"); +protocol.types.addActorType("root"); + +const childSpec = protocol.generateActorSpec({ + typeName: "child", + + methods: { + getChild: { + response: RetVal("child") + } + } +}); + +// The child actor doesn't provide a form description +var ChildActor = protocol.ActorClassWithSpec(childSpec, { + initialize(conn) { + protocol.Actor.prototype.initialize.call(this, conn); + }, + + form(detail) { + return { + actor: this.actorID, + extra: "extra" + }; + }, + + getChild: function () { + return this; + } +}); + +var ChildFront = protocol.FrontClassWithSpec(childSpec, { + initialize(client) { + protocol.Front.prototype.initialize.call(this, client); + }, + + form(v, ctx, detail) { + this.extra = v.extra; + } +}); + +const rootSpec = protocol.generateActorSpec({ + typeName: "root", + + // Basic form type, relies on implicit DictType creation + formType: { + childActor: "child" + }, + + // This detail uses explicit DictType creation + "formType#detail1": protocol.types.addDictType("RootActorFormTypeDetail1", { + detailItem: "child" + }), + + // This detail a string type. + "formType#actorid": "string", + + methods: { + getDefault: { + response: RetVal("root") + }, + getDetail1: { + response: RetVal("root#detail1") + }, + getDetail2: { + response: { + item: RetVal("root#actorid") + } + }, + getUnknownDetail: { + response: RetVal("root#unknownDetail") + } + } +}); + +// The root actor does provide a form description. +var RootActor = protocol.ActorClassWithSpec(rootSpec, { + initialize(conn) { + protocol.Actor.prototype.initialize.call(this, conn); + this.manage(this); + this.child = new ChildActor(); + }, + + sayHello() { + return { + from: "root", + applicationType: "xpcshell-tests", + traits: [] + }; + }, + + form(detail) { + if (detail === "detail1") { + return { + actor: this.actorID, + detailItem: this.child + }; + } else if (detail === "actorid") { + return this.actorID; + } + + return { + actor: this.actorID, + childActor: this.child + }; + }, + + getDefault: function () { + return this; + }, + + getDetail1: function () { + return this; + }, + + getDetail2: function () { + return this; + }, + + getUnknownDetail: function () { + return this; + } +}); + +var RootFront = protocol.FrontClassWithSpec(rootSpec, { + initialize(client) { + this.actorID = "root"; + protocol.Front.prototype.initialize.call(this, client); + + // Root owns itself. + this.manage(this); + }, + + form(v, ctx, detail) { + this.lastForm = v; + } +}); + +const run_test = Test(function* () { + DebuggerServer.createRootActor = (conn => { + return RootActor(conn); + }); + DebuggerServer.init(); + + const connection = DebuggerServer.connectPipe(); + const conn = new DebuggerClient(connection); + const client = Async(conn); + + yield client.connect(); + + let rootFront = RootFront(conn); + + // Trigger some methods that return forms. + let retval = yield rootFront.getDefault(); + do_check_true(retval instanceof RootFront); + do_check_true(rootFront.lastForm.childActor instanceof ChildFront); + + retval = yield rootFront.getDetail1(); + do_check_true(retval instanceof RootFront); + do_check_true(rootFront.lastForm.detailItem instanceof ChildFront); + + retval = yield rootFront.getDetail2(); + do_check_true(retval instanceof RootFront); + do_check_true(typeof (rootFront.lastForm) === "string"); + + // getUnknownDetail should fail, since no typeName is specified. + try { + yield rootFront.getUnknownDetail(); + do_check_true(false); + } catch (ex) { + } + + yield client.close(); +}); diff --git a/devtools/server/tests/unit/test_protocol_longstring.js b/devtools/server/tests/unit/test_protocol_longstring.js new file mode 100644 index 000000000..c37f4251e --- /dev/null +++ b/devtools/server/tests/unit/test_protocol_longstring.js @@ -0,0 +1,218 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Test simple requests using the protocol helpers. + */ +var protocol = require("devtools/shared/protocol"); +var {RetVal, Arg, Option} = protocol; +var events = require("sdk/event/core"); +var {LongStringActor} = require("devtools/server/actors/string"); + +// The test implicitly relies on this. +require("devtools/shared/fronts/string"); + +function simpleHello() { + return { + from: "root", + applicationType: "xpcshell-tests", + traits: [], + }; +} + +DebuggerServer.LONG_STRING_LENGTH = DebuggerServer.LONG_STRING_INITIAL_LENGTH = DebuggerServer.LONG_STRING_READ_LENGTH = 5; + +var SHORT_STR = "abc"; +var LONG_STR = "abcdefghijklmnop"; + +var rootActor = null; + +const rootSpec = protocol.generateActorSpec({ + typeName: "root", + + events: { + "string-event": { + str: Arg(0, "longstring") + } + }, + + methods: { + shortString: { + response: { value: RetVal("longstring") }, + }, + longString: { + response: { value: RetVal("longstring") }, + }, + emitShortString: { + oneway: true, + }, + emitLongString: { + oneway: true, + } + } +}); + +var RootActor = protocol.ActorClassWithSpec(rootSpec, { + initialize: function (conn) { + rootActor = this; + protocol.Actor.prototype.initialize.call(this, conn); + // Root actor owns itself. + this.manage(this); + this.actorID = "root"; + }, + + sayHello: simpleHello, + + shortString: function () { + return new LongStringActor(this.conn, SHORT_STR); + }, + + longString: function () { + return new LongStringActor(this.conn, LONG_STR); + }, + + emitShortString: function () { + events.emit(this, "string-event", new LongStringActor(this.conn, SHORT_STR)); + }, + + emitLongString: function () { + events.emit(this, "string-event", new LongStringActor(this.conn, LONG_STR)); + }, +}); + +var RootFront = protocol.FrontClassWithSpec(rootSpec, { + initialize: function (client) { + this.actorID = "root"; + protocol.Front.prototype.initialize.call(this, client); + // Root owns itself. + this.manage(this); + } +}); + +function run_test() +{ + DebuggerServer.createRootActor = (conn => { + return RootActor(conn); + }); + + DebuggerServer.init(); + let trace = connectPipeTracing(); + let client = new DebuggerClient(trace); + let rootClient; + + let strfront = null; + + let expectRootChildren = function (size) { + do_check_eq(rootActor.__poolMap.size, size + 1); + do_check_eq(rootClient.__poolMap.size, size + 1); + }; + + + client.connect().then(([applicationType, traits]) => { + rootClient = RootFront(client); + + // Root actor has no children yet. + expectRootChildren(0); + + trace.expectReceive({"from":"<actorid>", "applicationType":"xpcshell-tests", "traits":[]}); + do_check_eq(applicationType, "xpcshell-tests"); + rootClient.shortString().then(ret => { + trace.expectSend({"type":"shortString", "to":"<actorid>"}); + trace.expectReceive({"value":"abc", "from":"<actorid>"}); + + // Should only own the one reference (itself) at this point. + expectRootChildren(0); + strfront = ret; + }).then(() => { + return strfront.string(); + }).then(ret => { + do_check_eq(ret, SHORT_STR); + }).then(() => { + return rootClient.longString(); + }).then(ret => { + trace.expectSend({"type":"longString", "to":"<actorid>"}); + trace.expectReceive({"value":{"type":"longString", "actor":"<actorid>", "length":16, "initial":"abcde"}, "from":"<actorid>"}); + + strfront = ret; + // Should own a reference to itself and an extra string now. + expectRootChildren(1); + }).then(() => { + return strfront.string(); + }).then(ret => { + trace.expectSend({"type":"substring", "start":5, "end":10, "to":"<actorid>"}); + trace.expectReceive({"substring":"fghij", "from":"<actorid>"}); + trace.expectSend({"type":"substring", "start":10, "end":15, "to":"<actorid>"}); + trace.expectReceive({"substring":"klmno", "from":"<actorid>"}); + trace.expectSend({"type":"substring", "start":15, "end":20, "to":"<actorid>"}); + trace.expectReceive({"substring":"p", "from":"<actorid>"}); + + do_check_eq(ret, LONG_STR); + }).then(() => { + return strfront.release(); + }).then(() => { + trace.expectSend({"type":"release", "to":"<actorid>"}); + trace.expectReceive({"from":"<actorid>"}); + + // That reference should be removed now. + expectRootChildren(0); + }).then(() => { + let deferred = promise.defer(); + rootClient.once("string-event", (str) => { + trace.expectSend({"type":"emitShortString", "to":"<actorid>"}); + trace.expectReceive({"type":"string-event", "str":"abc", "from":"<actorid>"}); + + do_check_true(!!str); + strfront = str; + // Shouldn't generate any new references + expectRootChildren(0); + // will generate no packets. + strfront.string().then((value) => { deferred.resolve(value); }); + }); + rootClient.emitShortString(); + return deferred.promise; + }).then(value => { + do_check_eq(value, SHORT_STR); + }).then(() => { + // Will generate no packets + return strfront.release(); + }).then(() => { + let deferred = promise.defer(); + rootClient.once("string-event", (str) => { + trace.expectSend({"type":"emitLongString", "to":"<actorid>"}); + trace.expectReceive({"type":"string-event", "str":{"type":"longString", "actor":"<actorid>", "length":16, "initial":"abcde"}, "from":"<actorid>"}); + + do_check_true(!!str); + // Should generate one new reference + expectRootChildren(1); + strfront = str; + strfront.string().then((value) => { + trace.expectSend({"type":"substring", "start":5, "end":10, "to":"<actorid>"}); + trace.expectReceive({"substring":"fghij", "from":"<actorid>"}); + trace.expectSend({"type":"substring", "start":10, "end":15, "to":"<actorid>"}); + trace.expectReceive({"substring":"klmno", "from":"<actorid>"}); + trace.expectSend({"type":"substring", "start":15, "end":20, "to":"<actorid>"}); + trace.expectReceive({"substring":"p", "from":"<actorid>"}); + + deferred.resolve(value); + }); + }); + rootClient.emitLongString(); + return deferred.promise; + }).then(value => { + do_check_eq(value, LONG_STR); + }).then(() => { + return strfront.release(); + }).then(() => { + trace.expectSend({"type":"release", "to":"<actorid>"}); + trace.expectReceive({"from":"<actorid>"}); + expectRootChildren(0); + }).then(() => { + client.close().then(() => { + do_test_finished(); + }); + }).then(null, err => { + do_report_unexpected_exception(err, "Failure executing test"); + }); + }); + do_test_pending(); +} diff --git a/devtools/server/tests/unit/test_protocol_simple.js b/devtools/server/tests/unit/test_protocol_simple.js new file mode 100644 index 000000000..c85003954 --- /dev/null +++ b/devtools/server/tests/unit/test_protocol_simple.js @@ -0,0 +1,319 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Test simple requests using the protocol helpers. + */ + +var protocol = require("devtools/shared/protocol"); +var {Arg, Option, RetVal} = protocol; +var events = require("sdk/event/core"); + +function simpleHello() { + return { + from: "root", + applicationType: "xpcshell-tests", + traits: [], + }; +} + +const rootSpec = protocol.generateActorSpec({ + typeName: "root", + + events: { + "oneway": { a: Arg(0) }, + "falsyOptions": { + zero: Option(0), + farce: Option(0) + } + }, + + methods: { + simpleReturn: { + response: { value: RetVal() }, + }, + promiseReturn: { + response: { value: RetVal("number") }, + }, + simpleArgs: { + request: { + firstArg: Arg(0), + secondArg: Arg(1), + }, + response: RetVal() + }, + nestedArgs: { + request: { + firstArg: Arg(0), + nest: { + secondArg: Arg(1), + nest: { + thirdArg: Arg(2) + } + } + }, + response: RetVal() + }, + optionArgs: { + request: { + option1: Option(0), + option2: Option(0) + }, + response: RetVal() + }, + optionalArgs: { + request: { + a: Arg(0), + b: Arg(1, "nullable:number") + }, + response: { + value: RetVal("number") + }, + }, + arrayArgs: { + request: { + a: Arg(0, "array:number") + }, + response: { + arrayReturn: RetVal("array:number") + }, + }, + nestedArrayArgs: { + request: { a: Arg(0, "array:array:number") }, + response: { value: RetVal("array:array:number") }, + }, + renamedEcho: { + request: { + type: "echo", + a: Arg(0), + }, + response: { + value: RetVal("string") + }, + }, + testOneWay: { + request: { a: Arg(0) }, + oneway: true + }, + emitFalsyOptions: { + oneway: true + } + } +}); + +var RootActor = protocol.ActorClassWithSpec(rootSpec, { + initialize: function (conn) { + protocol.Actor.prototype.initialize.call(this, conn); + // Root actor owns itself. + this.manage(this); + this.actorID = "root"; + }, + + sayHello: simpleHello, + + simpleReturn: function () { + return 1; + }, + + promiseReturn: function () { + return promise.resolve(1); + }, + + simpleArgs: function (a, b) { + return { firstResponse: a + 1, secondResponse: b + 1 }; + }, + + nestedArgs: function (a, b, c) { + return { a: a, b: b, c: c }; + }, + + optionArgs: function (options) { + return { option1: options.option1, option2: options.option2 }; + }, + + optionalArgs: function (a, b = 200) { + return b; + }, + + arrayArgs: function (a) { + return a; + }, + + nestedArrayArgs: function (a) { + return a; + }, + + /** + * Test that the 'type' part of the request packet works + * correctly when the type isn't the same as the method name + */ + renamedEcho: function (a) { + if (this.conn.currentPacket.type != "echo") { + return "goodbye"; + } + return a; + }, + + testOneWay: function (a) { + // Emit to show that we got this message, because there won't be a response. + events.emit(this, "oneway", a); + }, + + emitFalsyOptions: function () { + events.emit(this, "falsyOptions", { zero: 0, farce: false }); + } +}); + +var RootFront = protocol.FrontClassWithSpec(rootSpec, { + initialize: function (client) { + this.actorID = "root"; + protocol.Front.prototype.initialize.call(this, client); + // Root owns itself. + this.manage(this); + } +}); + +function run_test() +{ + DebuggerServer.createRootActor = (conn => { + return RootActor(conn); + }); + DebuggerServer.init(); + + check_except(() => { + let badActor = ActorClassWithSpec({}, { + missing: preEvent("missing-event", function () { + }) + }); + }); + + protocol.types.getType("array:array:array:number"); + protocol.types.getType("array:array:array:number"); + + check_except(() => protocol.types.getType("unknown")); + check_except(() => protocol.types.getType("array:unknown")); + check_except(() => protocol.types.getType("unknown:number")); + let trace = connectPipeTracing(); + let client = new DebuggerClient(trace); + let rootClient; + + client.connect().then(([applicationType, traits]) => { + trace.expectReceive({"from":"<actorid>", "applicationType":"xpcshell-tests", "traits":[]}); + do_check_eq(applicationType, "xpcshell-tests"); + + rootClient = RootFront(client); + + rootClient.simpleReturn().then(ret => { + trace.expectSend({"type":"simpleReturn", "to":"<actorid>"}); + trace.expectReceive({"value":1, "from":"<actorid>"}); + do_check_eq(ret, 1); + }).then(() => { + return rootClient.promiseReturn(); + }).then(ret => { + trace.expectSend({"type":"promiseReturn", "to":"<actorid>"}); + trace.expectReceive({"value":1, "from":"<actorid>"}); + do_check_eq(ret, 1); + }).then(() => { + // Missing argument should throw an exception + check_except(() => { + rootClient.simpleArgs(5); + }); + + return rootClient.simpleArgs(5, 10); + }).then(ret => { + trace.expectSend({"type":"simpleArgs", "firstArg":5, "secondArg":10, "to":"<actorid>"}); + trace.expectReceive({"firstResponse":6, "secondResponse":11, "from":"<actorid>"}); + do_check_eq(ret.firstResponse, 6); + do_check_eq(ret.secondResponse, 11); + }).then(() => { + return rootClient.nestedArgs(1, 2, 3); + }).then(ret => { + trace.expectSend({"type":"nestedArgs", "firstArg":1, "nest":{"secondArg":2, "nest":{"thirdArg":3}}, "to":"<actorid>"}); + trace.expectReceive({"a":1, "b":2, "c":3, "from":"<actorid>"}); + do_check_eq(ret.a, 1); + do_check_eq(ret.b, 2); + do_check_eq(ret.c, 3); + }).then(() => { + return rootClient.optionArgs({ + "option1": 5, + "option2": 10 + }); + }).then(ret => { + trace.expectSend({"type":"optionArgs", "option1":5, "option2":10, "to":"<actorid>"}); + trace.expectReceive({"option1":5, "option2":10, "from":"<actorid>"}); + do_check_eq(ret.option1, 5); + do_check_eq(ret.option2, 10); + }).then(() => { + return rootClient.optionArgs({}); + }).then(ret => { + trace.expectSend({"type":"optionArgs", "to":"<actorid>"}); + trace.expectReceive({"from":"<actorid>"}); + do_check_true(typeof (ret.option1) === "undefined"); + do_check_true(typeof (ret.option2) === "undefined"); + }).then(() => { + // Explicitly call an optional argument... + return rootClient.optionalArgs(5, 10); + }).then(ret => { + trace.expectSend({"type":"optionalArgs", "a":5, "b":10, "to":"<actorid>"}); + trace.expectReceive({"value":10, "from":"<actorid>"}); + do_check_eq(ret, 10); + }).then(() => { + // Now don't pass the optional argument, expect the default. + return rootClient.optionalArgs(5); + }).then(ret => { + trace.expectSend({"type":"optionalArgs", "a":5, "to":"<actorid>"}); + trace.expectReceive({"value":200, "from":"<actorid>"}); + do_check_eq(ret, 200); + }).then(ret => { + return rootClient.arrayArgs([0, 1, 2, 3, 4, 5]); + }).then(ret => { + trace.expectSend({"type":"arrayArgs", "a":[0, 1, 2, 3, 4, 5], "to":"<actorid>"}); + trace.expectReceive({"arrayReturn":[0, 1, 2, 3, 4, 5], "from":"<actorid>"}); + do_check_eq(ret[0], 0); + do_check_eq(ret[5], 5); + }).then(() => { + return rootClient.arrayArgs([[5]]); + }).then(ret => { + trace.expectSend({"type":"arrayArgs", "a":[[5]], "to":"<actorid>"}); + trace.expectReceive({"arrayReturn":[[5]], "from":"<actorid>"}); + do_check_eq(ret[0][0], 5); + }).then(() => { + return rootClient.renamedEcho("hello"); + }).then(str => { + trace.expectSend({"type":"echo", "a":"hello", "to":"<actorid>"}); + trace.expectReceive({"value":"hello", "from":"<actorid>"}); + + do_check_eq(str, "hello"); + + let deferred = promise.defer(); + rootClient.on("oneway", (response) => { + trace.expectSend({"type":"testOneWay", "a":"hello", "to":"<actorid>"}); + trace.expectReceive({"type":"oneway", "a":"hello", "from":"<actorid>"}); + + do_check_eq(response, "hello"); + deferred.resolve(); + }); + do_check_true(typeof (rootClient.testOneWay("hello")) === "undefined"); + return deferred.promise; + }).then(() => { + let deferred = promise.defer(); + rootClient.on("falsyOptions", res => { + trace.expectSend({"type":"emitFalsyOptions", "to":"<actorid>"}); + trace.expectReceive({"type":"falsyOptions", "farce":false, "zero": 0, "from":"<actorid>"}); + + do_check_true(res.zero === 0); + do_check_true(res.farce === false); + deferred.resolve(); + }); + rootClient.emitFalsyOptions(); + return deferred.promise; + }).then(() => { + client.close().then(() => { + do_test_finished(); + }); + }).then(null, err => { + do_report_unexpected_exception(err, "Failure executing test"); + }); + }); + do_test_pending(); +} diff --git a/devtools/server/tests/unit/test_protocol_stack.js b/devtools/server/tests/unit/test_protocol_stack.js new file mode 100644 index 000000000..a81f99a8e --- /dev/null +++ b/devtools/server/tests/unit/test_protocol_stack.js @@ -0,0 +1,98 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +/** + * Client request stacks should span the entire process from before making the + * request to handling the reply from the server. The server frames are not + * included, nor can they be in most cases, since the server can be a remote + * device. + */ + +var protocol = require("devtools/shared/protocol"); +var {Arg, Option, RetVal} = protocol; +var events = require("sdk/event/core"); + +function simpleHello() { + return { + from: "root", + applicationType: "xpcshell-tests", + traits: [], + }; +} + +const rootSpec = protocol.generateActorSpec({ + typeName: "root", + + methods: { + simpleReturn: { + response: { value: RetVal() }, + } + } +}); + +var RootActor = protocol.ActorClassWithSpec(rootSpec, { + initialize: function (conn) { + protocol.Actor.prototype.initialize.call(this, conn); + // Root actor owns itself. + this.manage(this); + this.actorID = "root"; + this.sequence = 0; + }, + + sayHello: simpleHello, + + simpleReturn: function () { + return this.sequence++; + } +}); + +var RootFront = protocol.FrontClassWithSpec(rootSpec, { + initialize: function (client) { + this.actorID = "root"; + protocol.Front.prototype.initialize.call(this, client); + // Root owns itself. + this.manage(this); + } +}); + +function run_test() { + if (!Services.prefs.getBoolPref("javascript.options.asyncstack")) { + do_print("Async stacks are disabled."); + return; + } + + DebuggerServer.createRootActor = RootActor; + DebuggerServer.init(); + + let trace = connectPipeTracing(); + let client = new DebuggerClient(trace); + let rootClient; + + client.connect().then(function onConnect() { + rootClient = RootFront(client); + + rootClient.simpleReturn().then(() => { + let stack = Components.stack; + while (stack) { + do_print(stack.name); + if (stack.name == "onConnect") { + // Reached back to outer function before request + ok(true, "Complete stack"); + return; + } + stack = stack.asyncCaller || stack.caller; + } + ok(false, "Incomplete stack"); + }, () => { + ok(false, "Request failed unexpectedly"); + }).then(() => { + client.close().then(() => { + do_test_finished(); + }); + }); + }); + + do_test_pending(); +} diff --git a/devtools/server/tests/unit/test_protocol_unregister.js b/devtools/server/tests/unit/test_protocol_unregister.js new file mode 100644 index 000000000..5b32dd0a3 --- /dev/null +++ b/devtools/server/tests/unit/test_protocol_unregister.js @@ -0,0 +1,44 @@ +const {types} = require("devtools/shared/protocol"); + + +function run_test() +{ + types.addType("test", { + read: (v) => { return "successful read: " + v; }, + write: (v) => { return "successful write: " + v; } + }); + + // Verify the type registered correctly. + + let type = types.getType("test"); + let arrayType = types.getType("array:test"); + do_check_eq(type.read("foo"), "successful read: foo"); + do_check_eq(arrayType.read(["foo"])[0], "successful read: foo"); + + types.removeType("test"); + + do_check_eq(type.name, "DEFUNCT:test"); + try { + types.getType("test"); + do_check_true(false, "getType should fail"); + } catch (ex) { + do_check_eq(ex.toString(), "Error: Unknown type: test"); + } + + try { + type.read("foo"); + do_check_true(false, "type.read should have thrown an exception."); + } catch (ex) { + do_check_eq(ex.toString(), "Error: Using defunct type: test"); + } + + try { + arrayType.read(["foo"]); + do_check_true(false, "array:test.read should have thrown an exception."); + } catch (ex) { + do_check_eq(ex.toString(), "Error: Using defunct type: test"); + } + +} + + diff --git a/devtools/server/tests/unit/test_reattach-thread.js b/devtools/server/tests/unit/test_reattach-thread.js new file mode 100644 index 000000000..6d089e896 --- /dev/null +++ b/devtools/server/tests/unit/test_reattach-thread.js @@ -0,0 +1,58 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Test that reattaching to a previously detached thread works. + */ + +var gClient, gDebuggee, gThreadClient, gTabClient; + +function run_test() +{ + initTestDebuggerServer(); + gDebuggee = testGlobal("test-reattach"); + DebuggerServer.addTestGlobal(gDebuggee); + + let transport = DebuggerServer.connectPipe(); + gClient = new DebuggerClient(transport); + gClient.connect().then(() => { + attachTestTab(gClient, "test-reattach", (aReply, aTabClient) => { + gTabClient = aTabClient; + test_attach(); + }); + }); + do_test_pending(); +} + +function test_attach() +{ + gTabClient.attachThread({}, (aResponse, aThreadClient) => { + do_check_eq(aThreadClient.state, "paused"); + gThreadClient = aThreadClient; + aThreadClient.resume(test_detach); + }); +} + +function test_detach() +{ + gThreadClient.detach(() => { + do_check_eq(gThreadClient.state, "detached"); + do_check_eq(gTabClient.thread, null); + test_reattach(); + }); +} + +function test_reattach() +{ + gTabClient.attachThread({}, (aResponse, aThreadClient) => { + do_check_neq(gThreadClient, aThreadClient); + do_check_eq(aThreadClient.state, "paused"); + do_check_eq(gTabClient.thread, aThreadClient); + aThreadClient.resume(cleanup); + }); +} + +function cleanup() +{ + gClient.close().then(do_test_finished); +} diff --git a/devtools/server/tests/unit/test_registerClient.js b/devtools/server/tests/unit/test_registerClient.js new file mode 100644 index 000000000..c018e4454 --- /dev/null +++ b/devtools/server/tests/unit/test_registerClient.js @@ -0,0 +1,95 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +// Test the DebuggerClient.registerClient API + +var EventEmitter = require("devtools/shared/event-emitter"); + +var gClient; +var gActors; +var gTestClient; + +function TestActor(conn) { + this.conn = conn; +} +TestActor.prototype = { + actorPrefix: "test", + + start: function () { + this.conn.sendActorEvent(this.actorID, "foo", { + hello: "world" + }); + return {}; + } +}; +TestActor.prototype.requestTypes = { + "start": TestActor.prototype.start +}; + +function TestClient(client, form) { + this.client = client; + this.actor = form.test; + this.events = ["foo"]; + EventEmitter.decorate(this); + client.registerClient(this); + + this.detached = false; +} +TestClient.prototype = { + start: function () { + this.client.request({ + to: this.actor, + type: "start" + }); + }, + + detach: function (onDone) { + this.detached = true; + onDone(); + } +}; + +function run_test() +{ + DebuggerServer.addGlobalActor(TestActor); + + DebuggerServer.init(); + DebuggerServer.addBrowserActors(); + + add_test(init); + add_test(test_client_events); + add_test(close_client); + run_next_test(); +} + +function init() +{ + gClient = new DebuggerClient(DebuggerServer.connectPipe()); + gClient.connect() + .then(() => gClient.listTabs()) + .then(aResponse => { + gActors = aResponse; + gTestClient = new TestClient(gClient, aResponse); + run_next_test(); + }); +} + +function test_client_events() +{ + // Test DebuggerClient.registerClient and DebuggerServerConnection.sendActorEvent + gTestClient.on("foo", function (type, data) { + do_check_eq(type, "foo"); + do_check_eq(data.hello, "world"); + run_next_test(); + }); + gTestClient.start(); +} + +function close_client() { + gClient.close().then(() => { + // Check that client.detach method is call on client destruction + do_check_true(gTestClient.detached); + run_next_test(); + }); +} + diff --git a/devtools/server/tests/unit/test_register_actor.js b/devtools/server/tests/unit/test_register_actor.js new file mode 100644 index 000000000..8f3a243eb --- /dev/null +++ b/devtools/server/tests/unit/test_register_actor.js @@ -0,0 +1,113 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +const Profiler = Cc["@mozilla.org/tools/profiler;1"].getService(Ci.nsIProfiler); + +function check_actors(expect) { + do_check_eq(expect, DebuggerServer.tabActorFactories.hasOwnProperty("registeredActor1")); + do_check_eq(expect, DebuggerServer.tabActorFactories.hasOwnProperty("registeredActor2")); + + do_check_eq(expect, DebuggerServer.globalActorFactories.hasOwnProperty("registeredActor2")); + do_check_eq(expect, DebuggerServer.globalActorFactories.hasOwnProperty("registeredActor1")); +} + +function run_test() +{ + // Allow incoming connections. + DebuggerServer.init(); + DebuggerServer.addBrowserActors(); + + add_test(test_deprecated_api); + add_test(test_lazy_api); + add_test(cleanup); + run_next_test(); +} + +function test_deprecated_api() { + // The xpcshell-test/ path maps to resource://test/ + DebuggerServer.registerModule("xpcshell-test/registertestactors-01"); + DebuggerServer.registerModule("xpcshell-test/registertestactors-02"); + + check_actors(true); + + check_except(() => { + DebuggerServer.registerModule("xpcshell-test/registertestactors-01"); + }); + check_except(() => { + DebuggerServer.registerModule("xpcshell-test/registertestactors-02"); + }); + + DebuggerServer.unregisterModule("xpcshell-test/registertestactors-01"); + DebuggerServer.unregisterModule("xpcshell-test/registertestactors-02"); + check_actors(false); + + DebuggerServer.registerModule("xpcshell-test/registertestactors-01"); + DebuggerServer.registerModule("xpcshell-test/registertestactors-02"); + check_actors(true); + + run_next_test(); +} + +// Bug 988237: Test the new lazy actor loading +function test_lazy_api() { + let isActorLoaded = false; + let isActorInstanciated = false; + function onActorEvent(subject, topic, data) { + if (data == "loaded") { + isActorLoaded = true; + } else if (data == "instantiated") { + isActorInstanciated = true; + } + } + Services.obs.addObserver(onActorEvent, "actor", false); + DebuggerServer.registerModule("xpcshell-test/registertestactors-03", { + prefix: "lazy", + constructor: "LazyActor", + type: { global: true, tab: true } + }); + // The actor is immediatly registered, but not loaded + do_check_true(DebuggerServer.tabActorFactories.hasOwnProperty("lazyActor")); + do_check_true(DebuggerServer.globalActorFactories.hasOwnProperty("lazyActor")); + do_check_false(isActorLoaded); + do_check_false(isActorInstanciated); + + let client = new DebuggerClient(DebuggerServer.connectPipe()); + client.connect().then(function onConnect() { + client.listTabs(onListTabs); + }); + function onListTabs(aResponse) { + // On listTabs, the actor is still not loaded, + // but we can see its name in the list of available actors + do_check_false(isActorLoaded); + do_check_false(isActorInstanciated); + do_check_true("lazyActor" in aResponse); + + let {LazyFront} = require("xpcshell-test/registertestactors-03"); + let front = LazyFront(client, aResponse); + front.hello().then(onRequest); + } + function onRequest(aResponse) { + do_check_eq(aResponse, "world"); + + // Finally, the actor is loaded on the first request being made to it + do_check_true(isActorLoaded); + do_check_true(isActorInstanciated); + + Services.obs.removeObserver(onActorEvent, "actor", false); + client.close().then(() => run_next_test()); + } +} + +function cleanup() { + DebuggerServer.destroy(); + + // Check that all actors are unregistered on server destruction + check_actors(false); + do_check_false(DebuggerServer.tabActorFactories.hasOwnProperty("lazyActor")); + do_check_false(DebuggerServer.globalActorFactories.hasOwnProperty("lazyActor")); + + run_next_test(); +} + diff --git a/devtools/server/tests/unit/test_requestTypes.js b/devtools/server/tests/unit/test_requestTypes.js new file mode 100644 index 000000000..694e276bc --- /dev/null +++ b/devtools/server/tests/unit/test_requestTypes.js @@ -0,0 +1,36 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +const { RootActor } = require("devtools/server/actors/root"); + +function test_requestTypes_request(aClient, anActor) +{ + aClient.request({ to: "root", type: "requestTypes" }, function (aResponse) { + var expectedRequestTypes = Object.keys(RootActor. + prototype. + requestTypes); + + do_check_true(Array.isArray(aResponse.requestTypes)); + do_check_eq(JSON.stringify(aResponse.requestTypes), + JSON.stringify(expectedRequestTypes)); + + aClient.close().then(() => { + do_test_finished(); + }); + }); +} + +function run_test() +{ + DebuggerServer.init(); + DebuggerServer.addBrowserActors(); + + var client = new DebuggerClient(DebuggerServer.connectPipe()); + client.connect().then(function () { + test_requestTypes_request(client); + }); + + do_test_pending(); +} diff --git a/devtools/server/tests/unit/test_safe-getter.js b/devtools/server/tests/unit/test_safe-getter.js new file mode 100644 index 000000000..08949154d --- /dev/null +++ b/devtools/server/tests/unit/test_safe-getter.js @@ -0,0 +1,25 @@ +function run_test() { + Components.utils.import("resource://gre/modules/jsdebugger.jsm"); + addDebuggerToGlobal(this); + var g = testGlobal("test"); + var dbg = new Debugger(); + var gw = dbg.addDebuggee(g); + + g.eval(` + // This is not a CCW. + Object.defineProperty(this, "bar", { + get: function() { return "bar"; }, + configurable: true, + enumerable: true + }); + + Components.utils.import("resource://gre/modules/XPCOMUtils.jsm"); + + // This is a CCW. + XPCOMUtils.defineLazyGetter(this, "foo", function() { return "foo"; }); + `); + + // Neither scripted getter should be considered safe. + assert(!DevToolsUtils.hasSafeGetter(gw.getOwnPropertyDescriptor("bar"))); + assert(!DevToolsUtils.hasSafeGetter(gw.getOwnPropertyDescriptor("foo"))); +} diff --git a/devtools/server/tests/unit/test_setBreakpoint-on-column-in-gcd-script.js b/devtools/server/tests/unit/test_setBreakpoint-on-column-in-gcd-script.js new file mode 100644 index 000000000..a9d82e434 --- /dev/null +++ b/devtools/server/tests/unit/test_setBreakpoint-on-column-in-gcd-script.js @@ -0,0 +1,58 @@ +"use strict"; + +var SOURCE_URL = getFileUrl("setBreakpoint-on-column-in-gcd-script.js"); + +function run_test() { + return Task.spawn(function* () { + do_test_pending(); + + let global = testGlobal("test"); + loadSubScript(SOURCE_URL, global); + Cu.forceGC(); Cu.forceGC(); Cu.forceGC(); + + DebuggerServer.registerModule("xpcshell-test/testactors"); + DebuggerServer.init(() => true); + DebuggerServer.addTestGlobal(global); + let client = new DebuggerClient(DebuggerServer.connectPipe()); + yield connect(client); + + let { tabs } = yield listTabs(client); + let tab = findTab(tabs, "test"); + let [, tabClient] = yield attachTab(client, tab); + let [, threadClient] = yield attachThread(tabClient); + yield resume(threadClient); + + let { sources } = yield getSources(threadClient); + let source = findSource(sources, SOURCE_URL); + let sourceClient = threadClient.source(source); + + let location = { line: 6, column: 17 }; + let [packet, breakpointClient] = yield setBreakpoint(sourceClient, location); + do_check_true(packet.isPending); + do_check_false("actualLocation" in packet); + + packet = yield executeOnNextTickAndWaitForPause(function () { + reload(tabClient).then(function () { + loadSubScript(SOURCE_URL, global); + }); + }, client); + do_check_eq(packet.type, "paused"); + let why = packet.why; + do_check_eq(why.type, "breakpoint"); + do_check_eq(why.actors.length, 1); + do_check_eq(why.actors[0], breakpointClient.actor); + let frame = packet.frame; + let where = frame.where; + do_check_eq(where.source.actor, source.actor); + do_check_eq(where.line, location.line); + do_check_eq(where.column, location.column); + let variables = frame.environment.bindings.variables; + do_check_eq(variables.a.value, 1); + do_check_eq(variables.b.value.type, "undefined"); + do_check_eq(variables.c.value.type, "undefined"); + yield resume(threadClient); + + yield close(client); + do_test_finished(); + }); +} diff --git a/devtools/server/tests/unit/test_setBreakpoint-on-column-with-no-offsets-at-end-of-line.js b/devtools/server/tests/unit/test_setBreakpoint-on-column-with-no-offsets-at-end-of-line.js new file mode 100644 index 000000000..6185cf589 --- /dev/null +++ b/devtools/server/tests/unit/test_setBreakpoint-on-column-with-no-offsets-at-end-of-line.js @@ -0,0 +1,39 @@ +"use strict"; + +var SOURCE_URL = getFileUrl("setBreakpoint-on-column-with-no-offsets-at-end-of-line.js"); + +function run_test() { + return Task.spawn(function* () { + do_test_pending(); + + DebuggerServer.registerModule("xpcshell-test/testactors"); + DebuggerServer.init(() => true); + + let global = createTestGlobal("test"); + DebuggerServer.addTestGlobal(global); + + let client = new DebuggerClient(DebuggerServer.connectPipe()); + yield connect(client); + + let { tabs } = yield listTabs(client); + let tab = findTab(tabs, "test"); + let [, tabClient] = yield attachTab(client, tab); + let [, threadClient] = yield attachThread(tabClient); + yield resume(threadClient); + + let promise = waitForNewSource(threadClient, SOURCE_URL); + loadSubScript(SOURCE_URL, global); + let { source } = yield promise; + let sourceClient = threadClient.source(source); + + let location = { line: 4, column: 23 }; + let [packet, breakpointClient] = yield setBreakpoint(sourceClient, location); + do_check_true(packet.isPending); + do_check_false("actualLocation" in packet); + + Cu.evalInSandbox("f()", global); + yield close(client); + + do_test_finished(); + }); +} diff --git a/devtools/server/tests/unit/test_setBreakpoint-on-column.js b/devtools/server/tests/unit/test_setBreakpoint-on-column.js new file mode 100644 index 000000000..bee9fe004 --- /dev/null +++ b/devtools/server/tests/unit/test_setBreakpoint-on-column.js @@ -0,0 +1,57 @@ +"use strict"; + +var SOURCE_URL = getFileUrl("setBreakpoint-on-column.js"); + +function run_test() { + return Task.spawn(function* () { + do_test_pending(); + + DebuggerServer.registerModule("xpcshell-test/testactors"); + DebuggerServer.init(() => true); + + let global = createTestGlobal("test"); + DebuggerServer.addTestGlobal(global); + + let client = new DebuggerClient(DebuggerServer.connectPipe()); + yield connect(client); + + let { tabs } = yield listTabs(client); + let tab = findTab(tabs, "test"); + let [, tabClient] = yield attachTab(client, tab); + let [, threadClient] = yield attachThread(tabClient); + yield resume(threadClient); + + let promise = waitForNewSource(threadClient, SOURCE_URL); + loadSubScript(SOURCE_URL, global); + let { source } = yield promise; + let sourceClient = threadClient.source(source); + + let location = { line: 4, column: 17 }; + let [packet, breakpointClient] = yield setBreakpoint(sourceClient, location); + do_check_false(packet.isPending); + do_check_false("actualLocation" in packet); + + packet = yield executeOnNextTickAndWaitForPause(function () { + Cu.evalInSandbox("f()", global); + }, client); + do_check_eq(packet.type, "paused"); + let why = packet.why; + do_check_eq(why.type, "breakpoint"); + do_check_eq(why.actors.length, 1); + do_check_eq(why.actors[0], breakpointClient.actor); + let frame = packet.frame; + let where = frame.where; + do_check_eq(where.source.actor, source.actor); + do_check_eq(where.line, location.line); + do_check_eq(where.column, location.column); + let variables = frame.environment.bindings.variables; + do_check_eq(variables.a.value, 1); + do_check_eq(variables.b.value.type, "undefined"); + do_check_eq(variables.c.value.type, "undefined"); + + yield resume(threadClient); + yield close(client); + + do_test_finished(); + }); +} diff --git a/devtools/server/tests/unit/test_setBreakpoint-on-line-in-gcd-script.js b/devtools/server/tests/unit/test_setBreakpoint-on-line-in-gcd-script.js new file mode 100644 index 000000000..8479c797e --- /dev/null +++ b/devtools/server/tests/unit/test_setBreakpoint-on-line-in-gcd-script.js @@ -0,0 +1,57 @@ +"use strict"; + +var SOURCE_URL = getFileUrl("setBreakpoint-on-line-in-gcd-script.js"); + +function run_test() { + return Task.spawn(function* () { + do_test_pending(); + + let global = createTestGlobal("test"); + loadSubScript(SOURCE_URL, global); + Cu.forceGC(); Cu.forceGC(); Cu.forceGC(); + + DebuggerServer.registerModule("xpcshell-test/testactors"); + DebuggerServer.init(() => true); + DebuggerServer.addTestGlobal(global); + let client = new DebuggerClient(DebuggerServer.connectPipe()); + yield connect(client); + + let { tabs } = yield listTabs(client); + let tab = findTab(tabs, "test"); + let [, tabClient] = yield attachTab(client, tab); + let [, threadClient] = yield attachThread(tabClient); + yield resume(threadClient); + + let { sources } = yield getSources(threadClient); + let source = findSource(sources, SOURCE_URL); + let sourceClient = threadClient.source(source); + + let location = { line: 7 }; + let [packet, breakpointClient] = yield setBreakpoint(sourceClient, location); + do_check_true(packet.isPending); + do_check_false("actualLocation" in packet); + + packet = yield executeOnNextTickAndWaitForPause(function () { + reload(tabClient).then(function () { + loadSubScript(SOURCE_URL, global); + }); + }, client); + do_check_eq(packet.type, "paused"); + let why = packet.why; + do_check_eq(why.type, "breakpoint"); + do_check_eq(why.actors.length, 1); + do_check_eq(why.actors[0], breakpointClient.actor); + let frame = packet.frame; + let where = frame.where; + do_check_eq(where.source.actor, source.actor); + do_check_eq(where.line, location.line); + let variables = frame.environment.bindings.variables; + do_check_eq(variables.a.value, 1); + do_check_eq(variables.b.value.type, "undefined"); + do_check_eq(variables.c.value.type, "undefined"); + yield resume(threadClient); + + yield close(client); + do_test_finished(); + }); +} diff --git a/devtools/server/tests/unit/test_setBreakpoint-on-line-with-multiple-offsets.js b/devtools/server/tests/unit/test_setBreakpoint-on-line-with-multiple-offsets.js new file mode 100644 index 000000000..2f5c1b9aa --- /dev/null +++ b/devtools/server/tests/unit/test_setBreakpoint-on-line-with-multiple-offsets.js @@ -0,0 +1,70 @@ +"use strict"; + +var SOURCE_URL = getFileUrl("setBreakpoint-on-line-with-multiple-offsets.js"); + +function run_test() { + return Task.spawn(function* () { + do_test_pending(); + + DebuggerServer.registerModule("xpcshell-test/testactors"); + DebuggerServer.init(() => true); + + let global = createTestGlobal("test"); + DebuggerServer.addTestGlobal(global); + + let client = new DebuggerClient(DebuggerServer.connectPipe()); + yield connect(client); + + let { tabs } = yield listTabs(client); + let tab = findTab(tabs, "test"); + let [, tabClient] = yield attachTab(client, tab); + + let [, threadClient] = yield attachThread(tabClient); + yield resume(threadClient); + + let promise = waitForNewSource(threadClient, SOURCE_URL); + loadSubScript(SOURCE_URL, global); + let { source } = yield promise; + let sourceClient = threadClient.source(source); + + let location = { line: 4 }; + let [packet, breakpointClient] = yield setBreakpoint(sourceClient, location); + do_check_false(packet.isPending); + do_check_false("actualLocation" in packet); + + packet = yield executeOnNextTickAndWaitForPause(function () { + Cu.evalInSandbox("f()", global); + }, client); + do_check_eq(packet.type, "paused"); + let why = packet.why; + do_check_eq(why.type, "breakpoint"); + do_check_eq(why.actors.length, 1); + do_check_eq(why.actors[0], breakpointClient.actor); + let frame = packet.frame; + let where = frame.where; + do_check_eq(where.source.actor, source.actor); + do_check_eq(where.line, location.line); + let variables = frame.environment.bindings.variables; + do_check_eq(variables.i.value.type, "undefined"); + + packet = yield executeOnNextTickAndWaitForPause(function () { + resume(threadClient); + }, client); + do_check_eq(packet.type, "paused"); + why = packet.why; + do_check_eq(why.type, "breakpoint"); + do_check_eq(why.actors.length, 1); + do_check_eq(why.actors[0], breakpointClient.actor); + frame = packet.frame; + where = frame.where; + do_check_eq(where.source.actor, source.actor); + do_check_eq(where.line, location.line); + variables = frame.environment.bindings.variables; + do_check_eq(variables.i.value, 0); + + yield resume(threadClient); + yield close(client); + + do_test_finished(); + }); +} diff --git a/devtools/server/tests/unit/test_setBreakpoint-on-line-with-multiple-statements.js b/devtools/server/tests/unit/test_setBreakpoint-on-line-with-multiple-statements.js new file mode 100644 index 000000000..104152441 --- /dev/null +++ b/devtools/server/tests/unit/test_setBreakpoint-on-line-with-multiple-statements.js @@ -0,0 +1,57 @@ +"use strict"; + +var SOURCE_URL = getFileUrl("setBreakpoint-on-line-with-multiple-statements.js"); + +function run_test() { + return Task.spawn(function* () { + do_test_pending(); + + DebuggerServer.registerModule("xpcshell-test/testactors"); + DebuggerServer.init(() => true); + + let global = createTestGlobal("test"); + DebuggerServer.addTestGlobal(global); + + let client = new DebuggerClient(DebuggerServer.connectPipe()); + yield connect(client); + + let { tabs } = yield listTabs(client); + let tab = findTab(tabs, "test"); + let [, tabClient] = yield attachTab(client, tab); + + let [, threadClient] = yield attachThread(tabClient); + yield resume(threadClient); + + let promise = waitForNewSource(threadClient, SOURCE_URL); + loadSubScript(SOURCE_URL, global); + let { source } = yield promise; + let sourceClient = threadClient.source(source); + + let location = { line: 4 }; + let [packet, breakpointClient] = yield setBreakpoint(sourceClient, location); + do_check_false(packet.isPending); + do_check_false("actualLocation" in packet); + + packet = yield executeOnNextTickAndWaitForPause(function () { + Cu.evalInSandbox("f()", global); + }, client); + do_check_eq(packet.type, "paused"); + let why = packet.why; + do_check_eq(why.type, "breakpoint"); + do_check_eq(why.actors.length, 1); + do_check_eq(why.actors[0], breakpointClient.actor); + let frame = packet.frame; + let where = frame.where; + do_check_eq(where.source.actor, source.actor); + do_check_eq(where.line, location.line); + let variables = frame.environment.bindings.variables; + do_check_eq(variables.a.value.type, "undefined"); + do_check_eq(variables.b.value.type, "undefined"); + do_check_eq(variables.c.value.type, "undefined"); + + yield resume(threadClient); + yield close(client); + + do_test_finished(); + }); +} diff --git a/devtools/server/tests/unit/test_setBreakpoint-on-line-with-no-offsets-in-gcd-script.js b/devtools/server/tests/unit/test_setBreakpoint-on-line-with-no-offsets-in-gcd-script.js new file mode 100644 index 000000000..2e841fe19 --- /dev/null +++ b/devtools/server/tests/unit/test_setBreakpoint-on-line-with-no-offsets-in-gcd-script.js @@ -0,0 +1,58 @@ +"use strict"; + +var SOURCE_URL = getFileUrl("setBreakpoint-on-line-with-no-offsets-in-gcd-script.js"); + +function run_test() { + return Task.spawn(function* () { + do_test_pending(); + + let global = createTestGlobal("test"); + loadSubScript(SOURCE_URL, global); + Cu.forceGC(); Cu.forceGC(); Cu.forceGC(); + + DebuggerServer.registerModule("xpcshell-test/testactors"); + DebuggerServer.init(() => true); + DebuggerServer.addTestGlobal(global); + + let client = new DebuggerClient(DebuggerServer.connectPipe()); + yield connect(client); + + let { tabs } = yield listTabs(client); + let tab = findTab(tabs, "test"); + let [, tabClient] = yield attachTab(client, tab); + let [, threadClient] = yield attachThread(tabClient); + yield resume(threadClient); + + let { sources } = yield getSources(threadClient); + let source = findSource(sources, SOURCE_URL); + let sourceClient = threadClient.source(source); + + let location = { line: 7 }; + let [packet, breakpointClient] = yield setBreakpoint(sourceClient, location); + do_check_true(packet.isPending); + do_check_false("actualLocation" in packet); + + packet = yield executeOnNextTickAndWaitForPause(function () { + reload(tabClient).then(function () { + loadSubScript(SOURCE_URL, global); + }); + }, client); + do_check_eq(packet.type, "paused"); + let why = packet.why; + do_check_eq(why.type, "breakpoint"); + do_check_eq(why.actors.length, 1); + do_check_eq(why.actors[0], breakpointClient.actor); + let frame = packet.frame; + let where = frame.where; + do_check_eq(where.source.actor, source.actor); + do_check_eq(where.line, 8); + let variables = frame.environment.bindings.variables; + do_check_eq(variables.a.value, 1); + do_check_eq(variables.c.value.type, "undefined"); + + yield resume(threadClient); + yield close(client); + + do_test_finished(); + }); +} diff --git a/devtools/server/tests/unit/test_setBreakpoint-on-line-with-no-offsets.js b/devtools/server/tests/unit/test_setBreakpoint-on-line-with-no-offsets.js new file mode 100644 index 000000000..5959b23ef --- /dev/null +++ b/devtools/server/tests/unit/test_setBreakpoint-on-line-with-no-offsets.js @@ -0,0 +1,57 @@ +"use strict"; + +var SOURCE_URL = getFileUrl("setBreakpoint-on-line-with-no-offsets.js"); + +function run_test() { + return Task.spawn(function* () { + do_test_pending(); + + DebuggerServer.registerModule("xpcshell-test/testactors"); + DebuggerServer.init(() => true); + + let global = createTestGlobal("test"); + DebuggerServer.addTestGlobal(global); + + let client = new DebuggerClient(DebuggerServer.connectPipe()); + yield connect(client); + + let { tabs } = yield listTabs(client); + let tab = findTab(tabs, "test"); + let [, tabClient] = yield attachTab(client, tab); + let [, threadClient] = yield attachThread(tabClient); + yield resume(threadClient); + + let promise = waitForNewSource(threadClient, SOURCE_URL); + loadSubScript(SOURCE_URL, global); + let { source } = yield promise; + let sourceClient = threadClient.source(source); + + let location = { line: 5 }; + let [packet, breakpointClient] = yield setBreakpoint(sourceClient, location); + do_check_false(packet.isPending); + do_check_true("actualLocation" in packet); + let actualLocation = packet.actualLocation; + do_check_eq(actualLocation.line, 6); + + packet = yield executeOnNextTickAndWaitForPause(function () { + Cu.evalInSandbox("f()", global); + }, client); + do_check_eq(packet.type, "paused"); + let why = packet.why; + do_check_eq(why.type, "breakpoint"); + do_check_eq(why.actors.length, 1); + do_check_eq(why.actors[0], breakpointClient.actor); + let frame = packet.frame; + let where = frame.where; + do_check_eq(where.source.actor, source.actor); + do_check_eq(where.line, actualLocation.line); + let variables = frame.environment.bindings.variables; + do_check_eq(variables.a.value, 1); + do_check_eq(variables.c.value.type, "undefined"); + + yield resume(threadClient); + yield close(client); + + do_test_finished(); + }); +} diff --git a/devtools/server/tests/unit/test_setBreakpoint-on-line.js b/devtools/server/tests/unit/test_setBreakpoint-on-line.js new file mode 100644 index 000000000..1dab6a633 --- /dev/null +++ b/devtools/server/tests/unit/test_setBreakpoint-on-line.js @@ -0,0 +1,57 @@ +"use strict"; + +var SOURCE_URL = getFileUrl("setBreakpoint-on-line.js"); + +function run_test() { + return Task.spawn(function* () { + do_test_pending(); + + DebuggerServer.registerModule("xpcshell-test/testactors"); + DebuggerServer.init(() => true); + + let global = createTestGlobal("test"); + DebuggerServer.addTestGlobal(global); + + let client = new DebuggerClient(DebuggerServer.connectPipe()); + yield connect(client); + + let { tabs } = yield listTabs(client); + let tab = findTab(tabs, "test"); + let [, tabClient] = yield attachTab(client, tab); + + let [, threadClient] = yield attachThread(tabClient); + yield resume(threadClient); + + let promise = waitForNewSource(threadClient, SOURCE_URL); + loadSubScript(SOURCE_URL, global); + let { source } = yield promise; + let sourceClient = threadClient.source(source); + + let location = { line: 5 }; + let [packet, breakpointClient] = yield setBreakpoint(sourceClient, location); + do_check_false(packet.isPending); + do_check_false("actualLocation" in packet); + + packet = yield executeOnNextTickAndWaitForPause(function () { + Cu.evalInSandbox("f()", global); + }, client); + do_check_eq(packet.type, "paused"); + let why = packet.why; + do_check_eq(why.type, "breakpoint"); + do_check_eq(why.actors.length, 1); + do_check_eq(why.actors[0], breakpointClient.actor); + let frame = packet.frame; + let where = frame.where; + do_check_eq(where.source.actor, source.actor); + do_check_eq(where.line, location.line); + let variables = frame.environment.bindings.variables; + do_check_eq(variables.a.value, 1); + do_check_eq(variables.b.value.type, "undefined"); + do_check_eq(variables.c.value.type, "undefined"); + + yield resume(threadClient); + yield close(client); + + do_test_finished(); + }); +} diff --git a/devtools/server/tests/unit/test_source-01.js b/devtools/server/tests/unit/test_source-01.js new file mode 100644 index 000000000..3401cc660 --- /dev/null +++ b/devtools/server/tests/unit/test_source-01.js @@ -0,0 +1,78 @@ +/* -*- js-indent-level: 2; indent-tabs-mode: nil -*- */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +var gDebuggee; +var gClient; +var gThreadClient; + +// This test ensures that we can create SourceActors and SourceClients properly, +// and that they can communicate over the protocol to fetch the source text for +// a given script. + +function run_test() +{ + initTestDebuggerServer(); + gDebuggee = addTestGlobal("test-grips"); + Cu.evalInSandbox( + "" + function stopMe(arg1) { + debugger; + }, + gDebuggee, + "1.8", + getFileUrl("test_source-01.js") + ); + + gClient = new DebuggerClient(DebuggerServer.connectPipe()); + gClient.connect().then(function () { + attachTestTabAndResume(gClient, "test-grips", function (aResponse, aTabClient, aThreadClient) { + gThreadClient = aThreadClient; + test_source(); + }); + }); + do_test_pending(); +} + +const SOURCE_URL = "http://example.com/foobar.js"; +const SOURCE_CONTENT = "stopMe()"; + +function test_source() +{ + DebuggerServer.LONG_STRING_LENGTH = 200; + + gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) { + gThreadClient.getSources(function (aResponse) { + do_check_true(!!aResponse); + do_check_true(!!aResponse.sources); + + let source = aResponse.sources.filter(function (s) { + return s.url === SOURCE_URL; + })[0]; + + do_check_true(!!source); + + let sourceClient = gThreadClient.source(source); + sourceClient.source(function (aResponse) { + do_check_true(!!aResponse); + do_check_true(!aResponse.error); + do_check_true(!!aResponse.contentType); + do_check_true(aResponse.contentType.includes("javascript")); + + do_check_true(!!aResponse.source); + do_check_eq(SOURCE_CONTENT, + aResponse.source); + + gThreadClient.resume(function () { + finishClient(gClient); + }); + }); + }); + }); + + Cu.evalInSandbox( + SOURCE_CONTENT, + gDebuggee, + "1.8", + SOURCE_URL + ); +} diff --git a/devtools/server/tests/unit/test_sourcemaps-01.js b/devtools/server/tests/unit/test_sourcemaps-01.js new file mode 100644 index 000000000..4d92bf9cc --- /dev/null +++ b/devtools/server/tests/unit/test_sourcemaps-01.js @@ -0,0 +1,64 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Check basic source map integration with the "newSource" packet in the RDP. + */ + +var gDebuggee; +var gClient; +var gThreadClient; + +const {SourceNode} = require("source-map"); + +function run_test() +{ + initTestDebuggerServer(); + gDebuggee = addTestGlobal("test-source-map"); + gClient = new DebuggerClient(DebuggerServer.connectPipe()); + gClient.connect().then(function () { + attachTestTabAndResume(gClient, "test-source-map", function (aResponse, aTabClient, aThreadClient) { + gThreadClient = aThreadClient; + test_simple_source_map(); + }); + }); + do_test_pending(); +} + +function test_simple_source_map() +{ + // Because we are source mapping, we should be notified of a.js, b.js, and + // c.js as sources, and shouldn't receive abc.js or test_sourcemaps-01.js. + let expectedSources = new Set(["http://example.com/www/js/a.js", + "http://example.com/www/js/b.js", + "http://example.com/www/js/c.js"]); + + gThreadClient.addListener("newSource", function _onNewSource(aEvent, aPacket) { + do_check_eq(aEvent, "newSource"); + do_check_eq(aPacket.type, "newSource"); + do_check_true(!!aPacket.source); + + do_check_true(expectedSources.has(aPacket.source.url), + "The source url should be one of our original sources."); + expectedSources.delete(aPacket.source.url); + + if (expectedSources.size === 0) { + gClient.removeListener("newSource", _onNewSource); + finishClient(gClient); + } + }); + + let { code, map } = (new SourceNode(null, null, null, [ + new SourceNode(1, 0, "a.js", "function a() { return 'a'; }\n"), + new SourceNode(1, 0, "b.js", "function b() { return 'b'; }\n"), + new SourceNode(1, 0, "c.js", "function c() { return 'c'; }\n"), + ])).toStringWithSourceMap({ + file: "abc.js", + sourceRoot: "http://example.com/www/js/" + }); + + code += "//# sourceMappingURL=data:text/json;base64," + btoa(map.toString()); + + Components.utils.evalInSandbox(code, gDebuggee, "1.8", + "http://example.com/www/js/abc.js", 1); +} diff --git a/devtools/server/tests/unit/test_sourcemaps-02.js b/devtools/server/tests/unit/test_sourcemaps-02.js new file mode 100644 index 000000000..2a343afaa --- /dev/null +++ b/devtools/server/tests/unit/test_sourcemaps-02.js @@ -0,0 +1,67 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Check basic source map integration with the "sources" packet in the RDP. + */ + +var gDebuggee; +var gClient; +var gThreadClient; + +const {SourceNode} = require("source-map"); + +function run_test() +{ + initTestDebuggerServer(); + gDebuggee = addTestGlobal("test-source-map"); + gClient = new DebuggerClient(DebuggerServer.connectPipe()); + gClient.connect().then(function () { + attachTestTabAndResume(gClient, "test-source-map", function (aResponse, aTabClient, aThreadClient) { + gThreadClient = aThreadClient; + test_simple_source_map(); + }); + }); + do_test_pending(); +} + +function test_simple_source_map() +{ + // Because we are source mapping, we should be notified of a.js, b.js, and + // c.js as sources, and shouldn"t receive abc.js or test_sourcemaps-01.js. + let expectedSources = new Set(["http://example.com/www/js/a.js", + "http://example.com/www/js/b.js", + "http://example.com/www/js/c.js"]); + + gClient.addOneTimeListener("paused", function (aEvent, aPacket) { + gThreadClient.getSources(function (aResponse) { + do_check_true(!aResponse.error, "Should not get an error"); + + for (let s of aResponse.sources) { + do_check_neq(s.url, "http://example.com/www/js/abc.js", + "Shouldn't get the generated source's url."); + expectedSources.delete(s.url); + } + + do_check_eq(expectedSources.size, 0, + "Should have found all the expected sources sources by now."); + + finishClient(gClient); + }); + }); + + let { code, map } = (new SourceNode(null, null, null, [ + new SourceNode(1, 0, "a.js", "function a() { return 'a'; }\n"), + new SourceNode(1, 0, "b.js", "function b() { return 'b'; }\n"), + new SourceNode(1, 0, "c.js", "function c() { return 'c'; }\n"), + new SourceNode(1, 0, "d.js", "debugger;\n") + ])).toStringWithSourceMap({ + file: "abc.js", + sourceRoot: "http://example.com/www/js/" + }); + + code += "//# sourceMappingURL=data:text/json;base64," + btoa(map.toString()); + + Components.utils.evalInSandbox(code, gDebuggee, "1.8", + "http://example.com/www/js/abc.js", 1); +} diff --git a/devtools/server/tests/unit/test_sourcemaps-03.js b/devtools/server/tests/unit/test_sourcemaps-03.js new file mode 100644 index 000000000..2fbd99aad --- /dev/null +++ b/devtools/server/tests/unit/test_sourcemaps-03.js @@ -0,0 +1,137 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Check setting breakpoints in source mapped sources. + */ + +var gDebuggee; +var gClient; +var gThreadClient; + +const {SourceNode} = require("source-map"); + +function run_test() +{ + initTestDebuggerServer(); + gDebuggee = addTestGlobal("test-source-map"); + gClient = new DebuggerClient(DebuggerServer.connectPipe()); + gClient.connect().then(function () { + attachTestTabAndResume(gClient, "test-source-map", function (aResponse, aTabClient, aThreadClient) { + gThreadClient = aThreadClient; + test_simple_source_map(); + }); + }); + do_test_pending(); +} + +function testBreakpointMapping(aName, aCallback) +{ + Task.spawn(function* () { + let response = yield waitForPause(gThreadClient); + do_check_eq(response.why.type, "debuggerStatement"); + + const source = yield getSource(gThreadClient, "http://example.com/www/js/" + aName + ".js"); + response = yield setBreakpoint(source, { + // Setting the breakpoint on an empty line so that it is pushed down one + // line and we can check the source mapped actualLocation later. + line: 3 + }); + + // Should not slide breakpoints for sourcemapped sources + do_check_true(!response.actualLocation); + + yield setBreakpoint(source, { line: 4 }); + + // The eval will cause us to resume, then we get an unsolicited pause + // because of our breakpoint, we resume again to finish the eval, and + // finally receive our last pause which has the result of the client + // evaluation. + response = yield gThreadClient.eval(null, aName + "()"); + do_check_eq(response.type, "resumed"); + + response = yield waitForPause(gThreadClient); + do_check_eq(response.why.type, "breakpoint"); + // Assert that we paused because of the breakpoint at the correct + // location in the code by testing that the value of `ret` is still + // undefined. + do_check_eq(response.frame.environment.bindings.variables.ret.value.type, + "undefined"); + + response = yield resume(gThreadClient); + + response = yield waitForPause(gThreadClient); + do_check_eq(response.why.type, "clientEvaluated"); + do_check_eq(response.why.frameFinished.return, aName); + + response = yield resume(gThreadClient); + + aCallback(); + }); + + gDebuggee.eval("(" + function () { + debugger; + } + "());"); +} + +function test_simple_source_map() +{ + let expectedSources = new Set([ + "http://example.com/www/js/a.js", + "http://example.com/www/js/b.js", + "http://example.com/www/js/c.js" + ]); + + gThreadClient.addListener("newSource", function _onNewSource(aEvent, aPacket) { + expectedSources.delete(aPacket.source.url); + if (expectedSources.size > 0) { + return; + } + gThreadClient.removeListener("newSource", _onNewSource); + + testBreakpointMapping("a", function () { + testBreakpointMapping("b", function () { + testBreakpointMapping("c", function () { + finishClient(gClient); + }); + }); + }); + }); + + let a = new SourceNode(null, null, null, [ + new SourceNode(1, 0, "a.js", "function a() {\n"), + new SourceNode(2, 0, "a.js", " var ret;\n"), + new SourceNode(3, 0, "a.js", " // Empty line\n"), + new SourceNode(4, 0, "a.js", " ret = 'a';\n"), + new SourceNode(5, 0, "a.js", " return ret;\n"), + new SourceNode(6, 0, "a.js", "}\n") + ]); + let b = new SourceNode(null, null, null, [ + new SourceNode(1, 0, "b.js", "function b() {\n"), + new SourceNode(2, 0, "b.js", " var ret;\n"), + new SourceNode(3, 0, "b.js", " // Empty line\n"), + new SourceNode(4, 0, "b.js", " ret = 'b';\n"), + new SourceNode(5, 0, "b.js", " return ret;\n"), + new SourceNode(6, 0, "b.js", "}\n") + ]); + let c = new SourceNode(null, null, null, [ + new SourceNode(1, 0, "c.js", "function c() {\n"), + new SourceNode(2, 0, "c.js", " var ret;\n"), + new SourceNode(3, 0, "c.js", " // Empty line\n"), + new SourceNode(4, 0, "c.js", " ret = 'c';\n"), + new SourceNode(5, 0, "c.js", " return ret;\n"), + new SourceNode(6, 0, "c.js", "}\n") + ]); + + let { code, map } = (new SourceNode(null, null, null, [ + a, b, c + ])).toStringWithSourceMap({ + file: "http://example.com/www/js/abc.js", + sourceRoot: "http://example.com/www/js/" + }); + + code += "//# sourceMappingURL=data:text/json;base64," + btoa(map.toString()); + + Components.utils.evalInSandbox(code, gDebuggee, "1.8", + "http://example.com/www/js/abc.js", 1); +} diff --git a/devtools/server/tests/unit/test_sourcemaps-04.js b/devtools/server/tests/unit/test_sourcemaps-04.js new file mode 100644 index 000000000..5fecb44c8 --- /dev/null +++ b/devtools/server/tests/unit/test_sourcemaps-04.js @@ -0,0 +1,46 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Check that absolute source map urls work. + */ + +var gDebuggee; +var gClient; +var gThreadClient; + +function run_test() +{ + initTestDebuggerServer(); + gDebuggee = addTestGlobal("test-source-map"); + gClient = new DebuggerClient(DebuggerServer.connectPipe()); + gClient.connect().then(function () { + attachTestTabAndResume(gClient, "test-source-map", function (aResponse, aTabClient, aThreadClient) { + gThreadClient = aThreadClient; + test_absolute_source_map(); + }); + }); + do_test_pending(); +} + +function test_absolute_source_map() +{ + gThreadClient.addOneTimeListener("newSource", function _onNewSource(aEvent, aPacket) { + do_check_eq(aEvent, "newSource"); + do_check_eq(aPacket.type, "newSource"); + do_check_true(!!aPacket.source); + + do_check_true(aPacket.source.url.indexOf("sourcemapped.coffee") !== -1, + "The new source should be a coffee file."); + do_check_eq(aPacket.source.url.indexOf("sourcemapped.js"), -1, + "The new source should not be a js file."); + + finishClient(gClient); + }); + + code = readFile("sourcemapped.js") + + "\n//# sourceMappingURL=" + getFileUrl("source-map-data/sourcemapped.map"); + + Components.utils.evalInSandbox(code, gDebuggee, "1.8", + getFileUrl("sourcemapped.js"), 1); +} diff --git a/devtools/server/tests/unit/test_sourcemaps-05.js b/devtools/server/tests/unit/test_sourcemaps-05.js new file mode 100644 index 000000000..edc9178db --- /dev/null +++ b/devtools/server/tests/unit/test_sourcemaps-05.js @@ -0,0 +1,46 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Check that relative source map urls work. + */ + +var gDebuggee; +var gClient; +var gThreadClient; + +function run_test() +{ + initTestDebuggerServer(); + gDebuggee = addTestGlobal("test-source-map"); + gClient = new DebuggerClient(DebuggerServer.connectPipe()); + gClient.connect().then(function () { + attachTestTabAndResume(gClient, "test-source-map", function (aResponse, aTabClient, aThreadClient) { + gThreadClient = aThreadClient; + test_relative_source_map(); + }); + }); + do_test_pending(); +} + +function test_relative_source_map() +{ + gThreadClient.addOneTimeListener("newSource", function _onNewSource(aEvent, aPacket) { + do_check_eq(aEvent, "newSource"); + do_check_eq(aPacket.type, "newSource"); + do_check_true(!!aPacket.source); + + do_check_true(aPacket.source.url.indexOf("sourcemapped.coffee") !== -1, + "The new source should be a coffee file."); + do_check_eq(aPacket.source.url.indexOf("sourcemapped.js"), -1, + "The new source should not be a js file."); + + finishClient(gClient); + }); + + code = readFile("sourcemapped.js") + + "\n//# sourceMappingURL=source-map-data/sourcemapped.map"; + + Components.utils.evalInSandbox(code, gDebuggee, "1.8", + getFileUrl("sourcemapped.js"), 1); +} diff --git a/devtools/server/tests/unit/test_sourcemaps-06.js b/devtools/server/tests/unit/test_sourcemaps-06.js new file mode 100644 index 000000000..41b518753 --- /dev/null +++ b/devtools/server/tests/unit/test_sourcemaps-06.js @@ -0,0 +1,94 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Check that we can load sources whose content is embedded in the + * "sourcesContent" field of a source map. + */ + +var gDebuggee; +var gClient; +var gThreadClient; + +const {SourceNode} = require("source-map"); + +function run_test() +{ + initTestDebuggerServer(); + gDebuggee = addTestGlobal("test-source-map"); + gClient = new DebuggerClient(DebuggerServer.connectPipe()); + gClient.connect().then(function () { + attachTestTabAndResume(gClient, "test-source-map", function (aResponse, aTabClient, aThreadClient) { + gThreadClient = aThreadClient; + test_source_content(); + }); + }); + do_test_pending(); +} + +function test_source_content() +{ + let numNewSources = 0; + + gThreadClient.addListener("newSource", function _onNewSource(aEvent, aPacket) { + if (++numNewSources !== 3) { + return; + } + gThreadClient.removeListener("newSource", _onNewSource); + + gThreadClient.getSources(function (aResponse) { + do_check_true(!aResponse.error, "Should not get an error"); + + testContents(aResponse.sources, 0, (timesCalled) => { + do_check_eq(timesCalled, 3); + finishClient(gClient); + }); + }); + }); + + let node = new SourceNode(null, null, null, [ + new SourceNode(1, 0, "a.js", "function a() { return 'a'; }\n"), + new SourceNode(1, 0, "b.js", "function b() { return 'b'; }\n"), + new SourceNode(1, 0, "c.js", "function c() { return 'c'; }\n"), + ]); + + node.setSourceContent("a.js", "content for a.js"); + node.setSourceContent("b.js", "content for b.js"); + node.setSourceContent("c.js", "content for c.js"); + + let { code, map } = node.toStringWithSourceMap({ + file: "abc.js" + }); + + code += "//# sourceMappingURL=data:text/json;base64," + btoa(map.toString()); + + Components.utils.evalInSandbox(code, gDebuggee, "1.8", + "http://example.com/www/js/abc.js", 1); +} + +function testContents(sources, timesCalled, callback) { + if (sources.length === 0) { + callback(timesCalled); + return; + } + + + let source = sources[0]; + let sourceClient = gThreadClient.source(sources[0]); + + if (sourceClient.url) { + sourceClient.source((aResponse) => { + do_check_true(!aResponse.error, + "Should not get an error loading the source from sourcesContent"); + + let expectedContent = "content for " + source.url; + do_check_eq(aResponse.source, expectedContent, + "Should have the expected source content"); + + testContents(sources.slice(1), timesCalled + 1, callback); + }); + } + else { + testContents(sources.slice(1), timesCalled, callback); + } +} diff --git a/devtools/server/tests/unit/test_sourcemaps-07.js b/devtools/server/tests/unit/test_sourcemaps-07.js new file mode 100644 index 000000000..b8a9462c0 --- /dev/null +++ b/devtools/server/tests/unit/test_sourcemaps-07.js @@ -0,0 +1,67 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Test that we don't permanently cache sources from source maps. + */ + +var gDebuggee; +var gClient; +var gThreadClient; + +const {SourceNode} = require("source-map"); + +function run_test() +{ + initTestDebuggerServer(); + gDebuggee = addTestGlobal("test-source-map"); + gClient = new DebuggerClient(DebuggerServer.connectPipe()); + gClient.connect().then(function () { + attachTestTabAndResume(gClient, "test-source-map", function (aResponse, aTabClient, aThreadClient) { + gThreadClient = aThreadClient; + test_cached_original_sources(); + }); + }); + do_test_pending(); +} + +function test_cached_original_sources() +{ + writeFile("temp.js", "initial content"); + + gThreadClient.addOneTimeListener("newSource", onNewSource); + + let node = new SourceNode(1, 0, + getFileUrl("temp.js"), + "function funcFromTemp() {}\n"); + let { code, map } = node.toStringWithSourceMap({ + file: "abc.js" + }); + code += "//# sourceMappingURL=data:text/json;base64," + btoa(map.toString()); + + + Components.utils.evalInSandbox(code, gDebuggee, "1.8", + "http://example.com/www/js/abc.js", 1); +} + +function onNewSource(aEvent, aPacket) { + let sourceClient = gThreadClient.source(aPacket.source); + sourceClient.source(function (aResponse) { + do_check_true(!aResponse.error, + "Should not be an error grabbing the source"); + do_check_eq(aResponse.source, "initial content", + "The correct source content should be sent"); + + writeFile("temp.js", "new content"); + + sourceClient.source(function (aResponse) { + do_check_true(!aResponse.error, + "Should not be an error grabbing the source"); + do_check_eq(aResponse.source, "new content", + "The correct source content should not be cached, so we should get the new content"); + + do_get_file("temp.js").remove(false); + finishClient(gClient); + }); + }); +} diff --git a/devtools/server/tests/unit/test_sourcemaps-08.js b/devtools/server/tests/unit/test_sourcemaps-08.js new file mode 100644 index 000000000..b23665e43 --- /dev/null +++ b/devtools/server/tests/unit/test_sourcemaps-08.js @@ -0,0 +1,50 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Regression test for bug 882986 regarding sourcesContent and absolute source + * URLs. + */ + +var gDebuggee; +var gClient; +var gThreadClient; + +function run_test() +{ + initTestDebuggerServer(); + gDebuggee = addTestGlobal("test-source-map"); + gClient = new DebuggerClient(DebuggerServer.connectPipe()); + gClient.connect().then(function () { + attachTestTabAndResume(gClient, "test-source-map", function (aResponse, aTabClient, aThreadClient) { + gThreadClient = aThreadClient; + test_source_maps(); + }); + }); + do_test_pending(); +} + +function test_source_maps() +{ + gThreadClient.addOneTimeListener("newSource", function (aEvent, aPacket) { + let sourceClient = gThreadClient.source(aPacket.source); + sourceClient.source(function ({error, source}) { + do_check_true(!error, "should be able to grab the source"); + do_check_eq(source, "foo", + "Should load the source from the sourcesContent field"); + finishClient(gClient); + }); + }); + + let code = "'nothing here';\n"; + code += "//# sourceMappingURL=data:text/json," + JSON.stringify({ + version: 3, + file: "foo.js", + sources: ["/a"], + names: [], + mappings: "AACA", + sourcesContent: ["foo"] + }); + Components.utils.evalInSandbox(code, gDebuggee, "1.8", + "http://example.com/foo.js", 1); +} diff --git a/devtools/server/tests/unit/test_sourcemaps-09.js b/devtools/server/tests/unit/test_sourcemaps-09.js new file mode 100644 index 000000000..c317cf723 --- /dev/null +++ b/devtools/server/tests/unit/test_sourcemaps-09.js @@ -0,0 +1,95 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Check that source maps and breakpoints work with minified javascript. + */ + +var gDebuggee; +var gClient; +var gThreadClient; + +function run_test() +{ + initTestDebuggerServer(); + gDebuggee = addTestGlobal("test-source-map"); + gClient = new DebuggerClient(DebuggerServer.connectPipe()); + gClient.connect().then(function () { + attachTestTabAndResume(gClient, "test-source-map", function (aResponse, aTabClient, aThreadClient) { + gThreadClient = aThreadClient; + test_minified(); + }); + }); + do_test_pending(); +} + +function test_minified() +{ + let newSourceFired = false; + + gThreadClient.addOneTimeListener("newSource", function _onNewSource(aEvent, aPacket) { + do_check_eq(aEvent, "newSource"); + do_check_eq(aPacket.type, "newSource"); + do_check_true(!!aPacket.source); + + do_check_eq(aPacket.source.url, "http://example.com/foo.js", + "The new source should be foo.js"); + do_check_eq(aPacket.source.url.indexOf("foo.min.js"), -1, + "The new source should not be the minified file"); + + newSourceFired = true; + }); + + gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) { + do_check_eq(aEvent, "paused"); + do_check_eq(aPacket.why.type, "debuggerStatement"); + + let location = { + line: 5 + }; + + getSource(gThreadClient, "http://example.com/foo.js").then(source => { + source.setBreakpoint(location, function (aResponse, bpClient) { + do_check_true(!aResponse.error); + testHitBreakpoint(); + }); + }); + }); + + // This is the original foo.js, which was then minified with uglifyjs version + // 2.2.5 and the "--mangle" option. + // + // (function () { + // debugger; + // function foo(n) { + // var bar = n + n; + // var unused = null; + // return bar; + // } + // for (var i = 0; i < 10; i++) { + // foo(i); + // } + // }()); + + let code = '(function(){debugger;function r(r){var n=r+r;var u=null;return n}for(var n=0;n<10;n++){r(n)}})();\n//# sourceMappingURL=data:text/json,{"file":"foo.min.js","version":3,"sources":["foo.js"],"names":["foo","n","bar","unused","i"],"mappings":"CAAC,WACC,QACA,SAASA,GAAIC,GACX,GAAIC,GAAMD,EAAIA,CACd,IAAIE,GAAS,IACb,OAAOD,GAET,IAAK,GAAIE,GAAI,EAAGA,EAAI,GAAIA,IAAK,CAC3BJ,EAAII"}'; + + Components.utils.evalInSandbox(code, gDebuggee, "1.8", + "http://example.com/foo.min.js", 1); +} + +function testHitBreakpoint(timesHit = 0) { + gClient.addOneTimeListener("paused", function (aEvent, aPacket) { + ++timesHit; + + do_check_eq(aEvent, "paused"); + do_check_eq(aPacket.why.type, "breakpoint"); + + if (timesHit === 10) { + gThreadClient.resume(() => finishClient(gClient)); + } else { + testHitBreakpoint(timesHit); + } + }); + + gThreadClient.resume(); +} diff --git a/devtools/server/tests/unit/test_sourcemaps-10.js b/devtools/server/tests/unit/test_sourcemaps-10.js new file mode 100644 index 000000000..e955dc8fb --- /dev/null +++ b/devtools/server/tests/unit/test_sourcemaps-10.js @@ -0,0 +1,73 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Check that we source map frame locations for the frame we are paused at. + */ + +var gDebuggee; +var gClient; +var gThreadClient; + +const {SourceNode} = require("source-map"); + +function run_test() { + initTestDebuggerServer(); + gDebuggee = addTestGlobal("test-source-map"); + gClient = new DebuggerClient(DebuggerServer.connectPipe()); + gClient.connect().then(function () { + attachTestTabAndResume(gClient, "test-source-map", function (aResponse, aTabClient, aThreadClient) { + gThreadClient = aThreadClient; + promise.resolve(define_code()) + .then(run_code) + .then(test_frame_location) + .then(null, error => { + dump(error + "\n"); + dump(error.stack); + do_check_true(false); + }) + .then(() => { + finishClient(gClient); + }); + }); + }); + do_test_pending(); +} + +function define_code() { + let { code, map } = (new SourceNode(null, null, null, [ + new SourceNode(1, 0, "a.js", "function a() {\n"), + new SourceNode(2, 0, "a.js", " b();\n"), + new SourceNode(3, 0, "a.js", "}\n"), + new SourceNode(1, 0, "b.js", "function b() {\n"), + new SourceNode(2, 0, "b.js", " c();\n"), + new SourceNode(3, 0, "b.js", "}\n"), + new SourceNode(1, 0, "c.js", "function c() {\n"), + new SourceNode(2, 0, "c.js", " debugger;\n"), + new SourceNode(3, 0, "c.js", "}\n"), + ])).toStringWithSourceMap({ + file: "abc.js", + sourceRoot: "http://example.com/www/js/" + }); + + code += "//# sourceMappingURL=data:text/json," + map.toString(); + + Components.utils.evalInSandbox(code, gDebuggee, "1.8", + "http://example.com/www/js/abc.js", 1); +} + +function run_code() { + const d = promise.defer(); + gClient.addOneTimeListener("paused", function (aEvent, aPacket) { + d.resolve(aPacket); + gThreadClient.resume(); + }); + gDebuggee.a(); + return d.promise; +} + +function test_frame_location({ frame: { where: { source, line, column } } }) { + do_check_eq(source.url, "http://example.com/www/js/c.js"); + do_check_eq(line, 2); + do_check_eq(column, 0); +} diff --git a/devtools/server/tests/unit/test_sourcemaps-11.js b/devtools/server/tests/unit/test_sourcemaps-11.js new file mode 100644 index 000000000..e598c4c09 --- /dev/null +++ b/devtools/server/tests/unit/test_sourcemaps-11.js @@ -0,0 +1,83 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Check that we source map frame locations returned by "frames" requests. + */ + +var gDebuggee; +var gClient; +var gThreadClient; + +const {SourceNode} = require("source-map"); + +function run_test() { + initTestDebuggerServer(); + gDebuggee = addTestGlobal("test-source-map"); + gClient = new DebuggerClient(DebuggerServer.connectPipe()); + gClient.connect().then(function () { + attachTestTabAndResume(gClient, "test-source-map", function (aResponse, aTabClient, aThreadClient) { + gThreadClient = aThreadClient; + promise.resolve(define_code()) + .then(run_code) + .then(test_frames) + .then(null, error => { + dump(error + "\n"); + dump(error.stack); + do_check_true(false); + }) + .then(() => { + finishClient(gClient); + }); + }); + }); + do_test_pending(); +} + +function define_code() { + let { code, map } = (new SourceNode(null, null, null, [ + new SourceNode(1, 0, "a.js", "function a() {\n"), + new SourceNode(2, 0, "a.js", " b();\n"), + new SourceNode(3, 0, "a.js", "}\n"), + new SourceNode(1, 0, "b.js", "function b() {\n"), + new SourceNode(2, 0, "b.js", " c();\n"), + new SourceNode(3, 0, "b.js", "}\n"), + new SourceNode(1, 0, "c.js", "function c() {\n"), + new SourceNode(2, 0, "c.js", " debugger;\n"), + new SourceNode(3, 0, "c.js", "}\n"), + ])).toStringWithSourceMap({ + file: "abc.js", + sourceRoot: "http://example.com/www/js/" + }); + + code += "//# sourceMappingURL=data:text/json," + map.toString(); + + Components.utils.evalInSandbox(code, gDebuggee, "1.8", + "http://example.com/www/js/abc.js", 1); +} + +function run_code() { + const d = promise.defer(); + gClient.addOneTimeListener("paused", function () { + gThreadClient.getFrames(0, 3, function (aResponse) { + d.resolve(aResponse); + gThreadClient.resume(); + }); + }); + gDebuggee.a(); + return d.promise; +} + +function test_frames({ error, frames }) { + do_check_true(!error); + do_check_eq(frames.length, 3); + check_frame(frames[0], "http://example.com/www/js/c.js"); + check_frame(frames[1], "http://example.com/www/js/b.js"); + check_frame(frames[2], "http://example.com/www/js/a.js"); +} + +function check_frame({ where: { source, line, column } }, aExpectedUrl) { + do_check_eq(source.url, aExpectedUrl); + do_check_eq(line, 2); + do_check_eq(column, 0); +} diff --git a/devtools/server/tests/unit/test_sourcemaps-12.js b/devtools/server/tests/unit/test_sourcemaps-12.js new file mode 100644 index 000000000..cb7f29920 --- /dev/null +++ b/devtools/server/tests/unit/test_sourcemaps-12.js @@ -0,0 +1,75 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Check that we continue stepping when a single original source's line + * corresponds to multiple generated js lines. + */ + +var gDebuggee; +var gClient; +var gThreadClient; + +const {SourceNode} = require("source-map"); + +function run_test() { + initTestDebuggerServer(); + gDebuggee = addTestGlobal("test-source-map"); + gClient = new DebuggerClient(DebuggerServer.connectPipe()); + gClient.connect().then(function () { + attachTestTabAndResume(gClient, "test-source-map", function (aResponse, aTabClient, aThreadClient) { + gThreadClient = aThreadClient; + define_code(); + }); + }); + do_test_pending(); +} + +function define_code() { + let { code, map } = (new SourceNode(null, null, null, [ + new SourceNode(1, 0, "a.js", "function runTest() {\n"), + // A bunch of js lines map to the same original source line. + new SourceNode(2, 0, "a.js", " debugger;\n"), + new SourceNode(2, 0, "a.js", " var sum = 0;\n"), + new SourceNode(2, 0, "a.js", " for (var i = 0; i < 5; i++) {\n"), + new SourceNode(2, 0, "a.js", " sum += i;\n"), + new SourceNode(2, 0, "a.js", " }\n"), + // And now we have a new line in the original source that we should stop at. + new SourceNode(3, 0, "a.js", " sum;\n"), + new SourceNode(3, 0, "a.js", "}\n"), + ])).toStringWithSourceMap({ + file: "abc.js", + sourceRoot: "http://example.com/" + }); + + code += "//# sourceMappingURL=data:text/json," + map.toString(); + + Components.utils.evalInSandbox(code, gDebuggee, "1.8", + "http://example.com/abc.js", 1); + + run_code(); +} + +function run_code() { + gClient.addOneTimeListener("paused", function (aEvent, aPacket) { + do_check_eq(aPacket.why.type, "debuggerStatement"); + step_in(); + }); + gDebuggee.runTest(); +} + +function step_in() { + gClient.addOneTimeListener("paused", function (aEvent, aPacket) { + do_check_eq(aPacket.why.type, "resumeLimit"); + let { frame: { environment, where: { source, line } } } = aPacket; + // Stepping should have moved us to the next source mapped line. + do_check_eq(source.url, "http://example.com/a.js"); + do_check_eq(line, 3); + // Which should have skipped over the for loop in the generated js and sum + // should be calculated. + do_check_eq(environment.bindings.variables.sum.value, 10); + finishClient(gClient); + }); + gThreadClient.stepIn(); +} + diff --git a/devtools/server/tests/unit/test_sourcemaps-13.js b/devtools/server/tests/unit/test_sourcemaps-13.js new file mode 100644 index 000000000..203731fda --- /dev/null +++ b/devtools/server/tests/unit/test_sourcemaps-13.js @@ -0,0 +1,105 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Test that we don't permanently cache source maps across reloads. + */ + +var gDebuggee; +var gClient; +var gThreadClient; +var gTabClient; + +const {SourceNode} = require("source-map"); + +function run_test() +{ + initTestDebuggerServer(); + gDebuggee = addTestGlobal("test-source-map"); + gClient = new DebuggerClient(DebuggerServer.connectPipe()); + gClient.connect().then(function () { + attachTestTabAndResume(gClient, "test-source-map", function (aResponse, aTabClient, aThreadClient) { + gThreadClient = aThreadClient; + gTabClient = aTabClient; + setup_code(); + }); + }); + do_test_pending(); +} + +// The MAP_FILE_NAME is .txt so that the OS will definitely have an extension -> +// content type mapping for the extension. If it doesn't (like .map or .json), +// it logs console errors, which cause the test to fail. See bug 907839. +const MAP_FILE_NAME = "temporary-generated.txt"; + +const TEMP_FILE_1 = "temporary1.js"; +const TEMP_FILE_2 = "temporary2.js"; +const TEMP_GENERATED_SOURCE = "temporary-generated.js"; + +function setup_code() { + let node = new SourceNode(1, 0, + getFileUrl(TEMP_FILE_1, true), + "function temporary1() {}\n"); + let { code, map } = node.toStringWithSourceMap({ + file: getFileUrl(TEMP_GENERATED_SOURCE, true) + }); + + code += "//# sourceMappingURL=" + getFileUrl(MAP_FILE_NAME, true); + writeFile(MAP_FILE_NAME, map.toString()); + + Cu.evalInSandbox(code, + gDebuggee, + "1.8", + getFileUrl(TEMP_GENERATED_SOURCE, true), + 1); + + test_initial_sources(); +} + +function test_initial_sources() { + gThreadClient.getSources(function ({ error, sources }) { + do_check_true(!error); + sources = sources.filter(source => source.url); + do_check_eq(sources.length, 1); + do_check_eq(sources[0].url, getFileUrl(TEMP_FILE_1, true)); + reload(gTabClient).then(setup_new_code); + }); +} + +function setup_new_code() { + let node = new SourceNode(1, 0, + getFileUrl(TEMP_FILE_2, true), + "function temporary2() {}\n"); + let { code, map } = node.toStringWithSourceMap({ + file: getFileUrl(TEMP_GENERATED_SOURCE, true) + }); + + code += "\n//# sourceMappingURL=" + getFileUrl(MAP_FILE_NAME, true); + writeFile(MAP_FILE_NAME, map.toString()); + + gThreadClient.addOneTimeListener("newSource", test_new_sources); + Cu.evalInSandbox(code, + gDebuggee, + "1.8", + getFileUrl(TEMP_GENERATED_SOURCE, true), + 1); +} + +function test_new_sources() { + gThreadClient.getSources(function ({ error, sources }) { + do_check_true(!error); + sources = sources.filter(source => source.url); + + // Should now have TEMP_FILE_2 as a source. + do_check_eq(sources.length, 1); + let s = sources.filter(s => s.url === getFileUrl(TEMP_FILE_2, true))[0]; + do_check_true(!!s); + + finish_test(); + }); +} + +function finish_test() { + do_get_file(MAP_FILE_NAME).remove(false); + finishClient(gClient); +} diff --git a/devtools/server/tests/unit/test_sourcemaps-16.js b/devtools/server/tests/unit/test_sourcemaps-16.js new file mode 100644 index 000000000..4df9ece23 --- /dev/null +++ b/devtools/server/tests/unit/test_sourcemaps-16.js @@ -0,0 +1,46 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Verify that we can load the contents of every source in a source map produced + * by babel and browserify. + */ + +var gDebuggee; +var gClient; + +function run_test() +{ + initTestDebuggerServer(); + gDebuggee = addTestGlobal("test-sourcemaps"); + gClient = new DebuggerClient(DebuggerServer.connectPipe()); + gClient.connect().then(function () { + attachTestThread(gClient, "test-sourcemaps", testSourcemap); + }); + do_test_pending(); +} + +const testSourcemap = Task.async(function* (threadResponse, tabClient, threadClient, tabResponse) { + evalTestCode(); + + const { sources } = yield getSources(threadClient); + + for (let form of sources) { + let sourceResponse = yield getSourceContent(threadClient.source(form)); + ok(sourceResponse, "Should be able to get the source response"); + ok(sourceResponse.source, "Should have the source text as well"); + } + + finishClient(gClient); +}); + +const TEST_FILE = "babel_and_browserify_script_with_source_map.js"; + +function evalTestCode() { + const testFileContents = readFile(TEST_FILE); + Cu.evalInSandbox(testFileContents, + gDebuggee, + "1.8", + getFileUrl(TEST_FILE), + 1); +} diff --git a/devtools/server/tests/unit/test_sourcemaps-17.js b/devtools/server/tests/unit/test_sourcemaps-17.js new file mode 100644 index 000000000..85da95972 --- /dev/null +++ b/devtools/server/tests/unit/test_sourcemaps-17.js @@ -0,0 +1,63 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Check that we properly handle frames that cannot be sourcemapped + * when using sourcemaps. + */ + +var gDebuggee; +var gClient; +var gThreadClient; + +const {SourceNode} = require("source-map"); + +function run_test() { + initTestDebuggerServer(); + gDebuggee = addTestGlobal("test-source-map"); + gClient = new DebuggerClient(DebuggerServer.connectPipe()); + gClient.connect().then(function () { + attachTestTabAndResume(gClient, "test-source-map", function (aResponse, aTabClient, aThreadClient) { + gThreadClient = aThreadClient; + test_source_map(); + }); + }); + do_test_pending(); +} + +function test_source_map() { + // Set up debuggee code. + const a = new SourceNode(1, 1, "a.js", "function a() { b(); }"); + const b = new SourceNode(null, null, null, "function b() { c(); }"); + const c = new SourceNode(1, 1, "c.js", "function c() { d(); }"); + const d = new SourceNode(null, null, null, "function d() { e(); }"); + const e = new SourceNode(1, 1, "e.js", "function e() { debugger; }"); + const { map, code } = (new SourceNode(null, null, null, [a, b, c, d, e])).toStringWithSourceMap({ + file: "root.js", + sourceRoot: "root", + }); + Components.utils.evalInSandbox( + code + "//# sourceMappingURL=data:text/json;base64," + btoa(map.toString()), + gDebuggee, + "1.8", + "http://example.com/www/js/abc.js", + 1 + ); + + gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) { + gThreadClient.getFrames(0, 50, function ({ error, frames }) { + do_check_true(!error); + do_check_eq(frames.length, 4); + // b.js should be skipped + do_check_eq(frames[0].where.source.url, "http://example.com/www/root/e.js"); + do_check_eq(frames[1].where.source.url, "http://example.com/www/root/c.js"); + do_check_eq(frames[2].where.source.url, "http://example.com/www/root/a.js"); + do_check_eq(frames[3].where.source.url, null); + + finishClient(gClient); + }); + }); + + // Trigger it. + gDebuggee.eval("a()"); +} diff --git a/devtools/server/tests/unit/test_stepping-01.js b/devtools/server/tests/unit/test_stepping-01.js new file mode 100644 index 000000000..593a485a1 --- /dev/null +++ b/devtools/server/tests/unit/test_stepping-01.js @@ -0,0 +1,83 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Check basic step-over functionality. + */ + +var gDebuggee; +var gClient; +var gThreadClient; +var gCallback; + +function run_test() +{ + run_test_with_server(DebuggerServer, function () { + run_test_with_server(WorkerDebuggerServer, do_test_finished); + }); + do_test_pending(); +} + +function run_test_with_server(aServer, aCallback) +{ + gCallback = aCallback; + initTestDebuggerServer(aServer); + gDebuggee = addTestGlobal("test-stack", aServer); + gClient = new DebuggerClient(aServer.connectPipe()); + gClient.connect().then(function () { + attachTestTabAndResume(gClient, "test-stack", function (aResponse, aTabClient, aThreadClient) { + gThreadClient = aThreadClient; + test_simple_stepping(); + }); + }); +} + +function test_simple_stepping() +{ + gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) { + gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) { + // Check the return value. + do_check_eq(aPacket.type, "paused"); + do_check_eq(aPacket.frame.where.line, gDebuggee.line0 + 2); + do_check_eq(aPacket.why.type, "resumeLimit"); + // Check that stepping worked. + do_check_eq(gDebuggee.a, undefined); + do_check_eq(gDebuggee.b, undefined); + + gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) { + // Check the return value. + do_check_eq(aPacket.type, "paused"); + do_check_eq(aPacket.frame.where.line, gDebuggee.line0 + 3); + do_check_eq(aPacket.why.type, "resumeLimit"); + // Check that stepping worked. + do_check_eq(gDebuggee.a, 1); + do_check_eq(gDebuggee.b, undefined); + + gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) { + // Check the return value. + do_check_eq(aPacket.type, "paused"); + // When leaving a stack frame the line number doesn't change. + do_check_eq(aPacket.frame.where.line, gDebuggee.line0 + 3); + do_check_eq(aPacket.why.type, "resumeLimit"); + // Check that stepping worked. + do_check_eq(gDebuggee.a, 1); + do_check_eq(gDebuggee.b, 2); + + gThreadClient.resume(function () { + gClient.close().then(gCallback); + }); + }); + gThreadClient.stepOver(); + }); + gThreadClient.stepOver(); + + }); + gThreadClient.stepOver(); + + }); + + gDebuggee.eval("var line0 = Error().lineNumber;\n" + + "debugger;\n" + // line0 + 1 + "var a = 1;\n" + // line0 + 2 + "var b = 2;\n"); // line0 + 3 +} diff --git a/devtools/server/tests/unit/test_stepping-02.js b/devtools/server/tests/unit/test_stepping-02.js new file mode 100644 index 000000000..eaee099b9 --- /dev/null +++ b/devtools/server/tests/unit/test_stepping-02.js @@ -0,0 +1,83 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Check basic step-in functionality. + */ + +var gDebuggee; +var gClient; +var gThreadClient; +var gCallback; + +function run_test() +{ + run_test_with_server(DebuggerServer, function () { + run_test_with_server(WorkerDebuggerServer, do_test_finished); + }); + do_test_pending(); +} + +function run_test_with_server(aServer, aCallback) +{ + gCallback = aCallback; + initTestDebuggerServer(aServer); + gDebuggee = addTestGlobal("test-stack", aServer); + gClient = new DebuggerClient(aServer.connectPipe()); + gClient.connect().then(function () { + attachTestTabAndResume(gClient, "test-stack", function (aResponse, aTabClient, aThreadClient) { + gThreadClient = aThreadClient; + test_simple_stepping(); + }); + }); +} + +function test_simple_stepping() +{ + gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) { + gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) { + // Check the return value. + do_check_eq(aPacket.type, "paused"); + do_check_eq(aPacket.frame.where.line, gDebuggee.line0 + 2); + do_check_eq(aPacket.why.type, "resumeLimit"); + // Check that stepping worked. + do_check_eq(gDebuggee.a, undefined); + do_check_eq(gDebuggee.b, undefined); + + gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) { + // Check the return value. + do_check_eq(aPacket.type, "paused"); + do_check_eq(aPacket.frame.where.line, gDebuggee.line0 + 3); + do_check_eq(aPacket.why.type, "resumeLimit"); + // Check that stepping worked. + do_check_eq(gDebuggee.a, 1); + do_check_eq(gDebuggee.b, undefined); + + gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) { + // Check the return value. + do_check_eq(aPacket.type, "paused"); + // When leaving a stack frame the line number doesn't change. + do_check_eq(aPacket.frame.where.line, gDebuggee.line0 + 3); + do_check_eq(aPacket.why.type, "resumeLimit"); + // Check that stepping worked. + do_check_eq(gDebuggee.a, 1); + do_check_eq(gDebuggee.b, 2); + + gThreadClient.resume(function () { + gClient.close().then(gCallback); + }); + }); + gThreadClient.stepIn(); + }); + gThreadClient.stepIn(); + + }); + gThreadClient.stepIn(); + + }); + + gDebuggee.eval("var line0 = Error().lineNumber;\n" + + "debugger;\n" + // line0 + 1 + "var a = 1;\n" + // line0 + 2 + "var b = 2;\n"); // line0 + 3 +} diff --git a/devtools/server/tests/unit/test_stepping-03.js b/devtools/server/tests/unit/test_stepping-03.js new file mode 100644 index 000000000..6123928d7 --- /dev/null +++ b/devtools/server/tests/unit/test_stepping-03.js @@ -0,0 +1,62 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Check basic step-out functionality. + */ + +var gDebuggee; +var gClient; +var gThreadClient; +var gCallback; + +function run_test() +{ + run_test_with_server(DebuggerServer, function () { + run_test_with_server(WorkerDebuggerServer, do_test_finished); + }); + do_test_pending(); +} + +function run_test_with_server(aServer, aCallback) +{ + gCallback = aCallback; + initTestDebuggerServer(aServer); + gDebuggee = addTestGlobal("test-stack", aServer); + gClient = new DebuggerClient(aServer.connectPipe()); + gClient.connect().then(function () { + attachTestTabAndResume(gClient, "test-stack", function (aResponse, aTabClient, aThreadClient) { + gThreadClient = aThreadClient; + test_simple_stepping(); + }); + }); +} + +function test_simple_stepping() +{ + gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) { + gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) { + // Check the return value. + do_check_eq(aPacket.type, "paused"); + do_check_eq(aPacket.frame.where.line, gDebuggee.line0 + 5); + do_check_eq(aPacket.why.type, "resumeLimit"); + // Check that stepping worked. + do_check_eq(gDebuggee.a, 1); + do_check_eq(gDebuggee.b, 2); + + gThreadClient.resume(function () { + gClient.close().then(gCallback); + }); + }); + gThreadClient.stepOut(); + + }); + + gDebuggee.eval("var line0 = Error().lineNumber;\n" + + "function f() {\n" + // line0 + 1 + " debugger;\n" + // line0 + 2 + " this.a = 1;\n" + // line0 + 3 + " this.b = 2;\n" + // line0 + 4 + "}\n" + // line0 + 5 + "f();\n"); // line0 + 6 +} diff --git a/devtools/server/tests/unit/test_stepping-04.js b/devtools/server/tests/unit/test_stepping-04.js new file mode 100644 index 000000000..efe52200d --- /dev/null +++ b/devtools/server/tests/unit/test_stepping-04.js @@ -0,0 +1,74 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Check that stepping over a function call does not pause inside the function. + */ + +var gDebuggee; +var gClient; +var gThreadClient; +var gCallback; + +function run_test() +{ + run_test_with_server(DebuggerServer, function () { + run_test_with_server(WorkerDebuggerServer, do_test_finished); + }); + do_test_pending(); +} + +function run_test_with_server(aServer, aCallback) +{ + gCallback = aCallback; + initTestDebuggerServer(aServer); + gDebuggee = addTestGlobal("test-stack", aServer); + gClient = new DebuggerClient(aServer.connectPipe()); + gClient.connect().then(function () { + attachTestTabAndResume(gClient, "test-stack", function (aResponse, aTabClient, aThreadClient) { + gThreadClient = aThreadClient; + test_simple_stepping(); + }); + }); +} + +function test_simple_stepping() +{ + gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) { + gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) { + // Check the return value. + do_check_eq(aPacket.type, "paused"); + do_check_eq(aPacket.frame.where.line, gDebuggee.line0 + 5); + do_check_eq(aPacket.why.type, "resumeLimit"); + // Check that stepping worked. + do_check_eq(gDebuggee.a, undefined); + do_check_eq(gDebuggee.b, undefined); + + gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) { + // Check the return value. + do_check_eq(aPacket.type, "paused"); + do_check_eq(aPacket.frame.where.line, gDebuggee.line0 + 6); + do_check_eq(aPacket.why.type, "resumeLimit"); + // Check that stepping worked. + do_check_eq(gDebuggee.a, 1); + do_check_eq(gDebuggee.b, undefined); + + gThreadClient.resume(function () { + gClient.close().then(gCallback); + }); + }); + gThreadClient.stepOver(); + + }); + gThreadClient.stepOver(); + + }); + + gDebuggee.eval("var line0 = Error().lineNumber;\n" + + "function f() {\n" + // line0 + 1 + " this.a = 1;\n" + // line0 + 2 + "}\n" + // line0 + 3 + "debugger;\n" + // line0 + 4 + "f();\n" + // line0 + 5 + "let b = 2;\n"); // line0 + 6 +} diff --git a/devtools/server/tests/unit/test_stepping-05.js b/devtools/server/tests/unit/test_stepping-05.js new file mode 100644 index 000000000..2ab4570af --- /dev/null +++ b/devtools/server/tests/unit/test_stepping-05.js @@ -0,0 +1,101 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Make sure that stepping in the last statement of the last frame doesn't + * cause an unexpected pause, when another JS frame is pushed on the stack + * (bug 785689). + */ + +var gDebuggee; +var gClient; +var gThreadClient; +var gCallback; + +function run_test() +{ + run_test_with_server(DebuggerServer, function () { + run_test_with_server(WorkerDebuggerServer, do_test_finished); + }); + do_test_pending(); +} + +function run_test_with_server(aServer, aCallback) +{ + gCallback = aCallback; + initTestDebuggerServer(aServer); + gDebuggee = addTestGlobal("test-stack", aServer); + gClient = new DebuggerClient(aServer.connectPipe()); + gClient.connect().then(function () { + attachTestTabAndResume(gClient, "test-stack", function (aResponse, aTabClient, aThreadClient) { + gThreadClient = aThreadClient; + test_stepping_last(); + }); + }); +} + +function test_stepping_last() +{ + gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) { + gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) { + // Check the return value. + do_check_eq(aPacket.type, "paused"); + do_check_eq(aPacket.frame.where.line, gDebuggee.line0 + 2); + do_check_eq(aPacket.why.type, "resumeLimit"); + // Check that stepping worked. + do_check_eq(gDebuggee.a, undefined); + do_check_eq(gDebuggee.b, undefined); + + gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) { + // Check the return value. + do_check_eq(aPacket.type, "paused"); + do_check_eq(aPacket.frame.where.line, gDebuggee.line0 + 3); + do_check_eq(aPacket.why.type, "resumeLimit"); + // Check that stepping worked. + do_check_eq(gDebuggee.a, 1); + do_check_eq(gDebuggee.b, undefined); + + gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) { + // Check the return value. + do_check_eq(aPacket.type, "paused"); + // When leaving a stack frame the line number doesn't change. + do_check_eq(aPacket.frame.where.line, gDebuggee.line0 + 3); + do_check_eq(aPacket.why.type, "resumeLimit"); + // Check that stepping worked. + do_check_eq(gDebuggee.a, 1); + do_check_eq(gDebuggee.b, 2); + + gThreadClient.stepIn(function () { + test_next_pause(); + }); + }); + gThreadClient.stepIn(); + }); + gThreadClient.stepIn(); + + }); + gThreadClient.stepIn(); + + }); + + gDebuggee.eval("var line0 = Error().lineNumber;\n" + + "debugger;\n" + // line0 + 1 + "var a = 1;\n" + // line0 + 2 + "var b = 2;\n"); // line0 + 3 +} + +function test_next_pause() +{ + gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) { + // Check the return value. + do_check_eq(aPacket.type, "paused"); + // Before fixing bug 785689, the type was resumeLimit. + do_check_eq(aPacket.why.type, "debuggerStatement"); + + gThreadClient.resume(function () { + gClient.close().then(gCallback); + }); + }); + + gDebuggee.eval("debugger;"); +} diff --git a/devtools/server/tests/unit/test_stepping-06.js b/devtools/server/tests/unit/test_stepping-06.js new file mode 100644 index 000000000..49689f830 --- /dev/null +++ b/devtools/server/tests/unit/test_stepping-06.js @@ -0,0 +1,99 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Check that stepping out of a function returns the right return value. + */ + +var gDebuggee; +var gClient; +var gThreadClient; +var gCallback; + +function run_test() +{ + run_test_with_server(DebuggerServer, function () { + run_test_with_server(WorkerDebuggerServer, do_test_finished); + }); + do_test_pending(); +} + +function run_test_with_server(aServer, aCallback) +{ + gCallback = aCallback; + initTestDebuggerServer(aServer); + gDebuggee = addTestGlobal("test-stack", aServer); + gClient = new DebuggerClient(aServer.connectPipe()); + gClient.connect().then(function () { + attachTestTabAndResume(gClient, "test-stack", function (aResponse, aTabClient, aThreadClient) { + gThreadClient = aThreadClient; + // XXX: We have to do an executeSoon so that the error isn't caught and + // reported by DebuggerClient.requester (because we are using the local + // transport and share a stack) which causes the test to fail. + Services.tm.mainThread.dispatch({ + run: test_simple_stepping + }, Ci.nsIThread.DISPATCH_NORMAL); + }); + }); +} + +function test_simple_stepping() +{ + gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) { + gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) { + // Check that the return value is 10. + do_check_eq(aPacket.type, "paused"); + do_check_eq(aPacket.frame.where.line, gDebuggee.line0 + 5); + do_check_eq(aPacket.why.type, "resumeLimit"); + do_check_eq(aPacket.why.frameFinished.return, 10); + + gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) { + gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) { + // Check that the return value is undefined. + do_check_eq(aPacket.type, "paused"); + do_check_eq(aPacket.frame.where.line, gDebuggee.line0 + 8); + do_check_eq(aPacket.why.type, "resumeLimit"); + do_check_eq(aPacket.why.frameFinished.return.type, "undefined"); + + gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) { + gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) { + // Check that the exception was thrown. + do_check_eq(aPacket.type, "paused"); + do_check_eq(aPacket.frame.where.line, gDebuggee.line0 + 11); + do_check_eq(aPacket.why.type, "resumeLimit"); + do_check_eq(aPacket.why.frameFinished.throw, "ah"); + + gThreadClient.resume(function () { + gClient.close().then(gCallback); + }); + }); + gThreadClient.stepOut(); + }); + gThreadClient.resume(); + }); + gThreadClient.stepOut(); + }); + gThreadClient.resume(); + }); + gThreadClient.stepOut(); + + }); + + gDebuggee.eval("var line0 = Error().lineNumber;\n" + + "function f() {\n" + // line0 + 1 + " debugger;\n" + // line0 + 2 + " var a = 10;\n" + // line0 + 3 + " return a;\n" + // line0 + 4 + "}\n" + // line0 + 5 + "function g() {\n" + // line0 + 6 + " debugger;\n" + // line0 + 7 + "}\n" + // line0 + 8 + "function h() {\n" + // line0 + 9 + " debugger;\n" + // line0 + 10 + " throw 'ah';\n" + // line0 + 11 + " return 2;\n" + // line0 + 12 + "}\n" + // line0 + 13 + "f();\n" + // line0 + 14 + "g();\n" + // line0 + 15 + "try { h() } catch (ex) { };\n"); // line0 + 16 +} diff --git a/devtools/server/tests/unit/test_stepping-07.js b/devtools/server/tests/unit/test_stepping-07.js new file mode 100644 index 000000000..9ad0d79ff --- /dev/null +++ b/devtools/server/tests/unit/test_stepping-07.js @@ -0,0 +1,92 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Check that stepping over an implicit return makes sense. Bug 1155966. + */ + +var gDebuggee; +var gClient; +var gCallback; + +function run_test() { + do_test_pending(); + run_test_with_server(DebuggerServer, function () { + run_test_with_server(WorkerDebuggerServer, do_test_finished); + }); +} + +function run_test_with_server(aServer, aCallback) { + gCallback = aCallback; + initTestDebuggerServer(aServer); + gDebuggee = addTestGlobal("test-stepping", aServer); + gClient = new DebuggerClient(aServer.connectPipe()); + gClient.connect(testSteppingAndReturns); +} + +const testSteppingAndReturns = Task.async(function* () { + const [attachResponse, tabClient, threadClient] = yield attachTestTabAndResume(gClient, "test-stepping"); + ok(!attachResponse.error, "Should not get an error attaching"); + + dumpn("Evaluating test code and waiting for first debugger statement"); + const dbgStmt1 = yield executeOnNextTickAndWaitForPause(evaluateTestCode, gClient); + equal(dbgStmt1.frame.where.line, 3, + "Should be at debugger statement on line 3"); + + dumpn("Testing stepping with implicit return"); + const step1 = yield stepOver(gClient, threadClient); + equal(step1.frame.where.line, 4, "Should step to line 4"); + const step2 = yield stepOver(gClient, threadClient); + equal(step2.frame.where.line, 7, + "Should step to line 7, the implicit return at the last line of the function"); + // This assertion doesn't pass yet. You would need to do *another* + // step at the end of this function to get the frameFinished. + // See bug 923975. + // + // ok(step2.why.frameFinished, "This should be the implicit function return"); + + dumpn("Continuing and waiting for second debugger statement"); + const dbgStmt2 = yield resumeAndWaitForPause(gClient, threadClient); + equal(dbgStmt2.frame.where.line, 12, + "Should be at debugger statement on line 3"); + + dumpn("Testing stepping with explicit return"); + const step3 = yield stepOver(gClient, threadClient); + equal(step3.frame.where.line, 13, "Should step to line 13"); + const step4 = yield stepOver(gClient, threadClient); + equal(step4.frame.where.line, 15, "Should step out of the function from line 15"); + // This step is a bit funny, see bug 1013219 for details. + const step5 = yield stepOver(gClient, threadClient); + equal(step5.frame.where.line, 15, "Should step out of the function from line 15"); + ok(step5.why.frameFinished, "This should be the explicit function return"); + + finishClient(gClient, gCallback); +}); + +function evaluateTestCode() { + Cu.evalInSandbox( + ` // 1 + function implicitReturn() { // 2 + debugger; // 3 + if (this.someUndefinedProperty) { // 4 + yikes(); // 5 + } // 6 + } // 7 + // 8 + var yes = true; // 9 + function explicitReturn() { // 10 + if (yes) { // 11 + debugger; // 12 + return 1; // 13 + } // 14 + } // 15 + // 16 + implicitReturn(); // 17 + explicitReturn(); // 18 + `, // 19 + gDebuggee, + "1.8", + "test_stepping-07-test-code.js", + 1 + ); +} diff --git a/devtools/server/tests/unit/test_symbols-01.js b/devtools/server/tests/unit/test_symbols-01.js new file mode 100644 index 000000000..8ea26086a --- /dev/null +++ b/devtools/server/tests/unit/test_symbols-01.js @@ -0,0 +1,58 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Test that we can represent ES6 Symbols over the RDP. + */ + +const URL = "foo.js"; + +function run_test() { + initTestDebuggerServer(); + const debuggee = addTestGlobal("test-symbols"); + const client = new DebuggerClient(DebuggerServer.connectPipe()); + + client.connect().then(function () { + attachTestTabAndResume(client, "test-symbols", function (response, tabClient, threadClient) { + add_task(testSymbols.bind(null, client, debuggee)); + run_next_test(); + }); + }); + + do_test_pending(); +} + +function* testSymbols(client, debuggee) { + const evalCode = () => { + Components.utils.evalInSandbox( + "(" + function () { + var symbolWithName = Symbol("Chris"); + var symbolWithoutName = Symbol(); + var iteratorSymbol = Symbol.iterator; + debugger; + } + "())", + debuggee, + "1.8", + URL, + 1 + ); + }; + + const packet = yield executeOnNextTickAndWaitForPause(evalCode, client); + const { + symbolWithName, + symbolWithoutName, + iteratorSymbol + } = packet.frame.environment.bindings.variables; + + equal(symbolWithName.value.type, "symbol"); + equal(symbolWithName.value.name, "Chris"); + + equal(symbolWithoutName.value.type, "symbol"); + ok(!("name" in symbolWithoutName.value)); + + equal(iteratorSymbol.value.type, "symbol"); + equal(iteratorSymbol.value.name, "Symbol.iterator"); + + finishClient(client); +} diff --git a/devtools/server/tests/unit/test_symbols-02.js b/devtools/server/tests/unit/test_symbols-02.js new file mode 100644 index 000000000..f80dc3322 --- /dev/null +++ b/devtools/server/tests/unit/test_symbols-02.js @@ -0,0 +1,49 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Test that we don't run debuggee code when getting symbol names. + */ + +const URL = "foo.js"; + +function run_test() { + initTestDebuggerServer(); + const debuggee = addTestGlobal("test-symbols"); + const client = new DebuggerClient(DebuggerServer.connectPipe()); + + client.connect().then(function () { + attachTestTabAndResume(client, "test-symbols", function (response, tabClient, threadClient) { + add_task(testSymbols.bind(null, client, debuggee)); + run_next_test(); + }); + }); + + do_test_pending(); +} + +function* testSymbols(client, debuggee) { + const evalCode = () => { + Components.utils.evalInSandbox( + "(" + function () { + Symbol.prototype.toString = () => { + throw new Error("lololol"); + }; + var sym = Symbol("le troll"); + debugger; + } + "())", + debuggee, + "1.8", + URL, + 1 + ); + }; + + const packet = yield executeOnNextTickAndWaitForPause(evalCode, client); + const { sym } = packet.frame.environment.bindings.variables; + + equal(sym.value.type, "symbol"); + equal(sym.value.name, "le troll"); + + finishClient(client); +} diff --git a/devtools/server/tests/unit/test_threadlifetime-01.js b/devtools/server/tests/unit/test_threadlifetime-01.js new file mode 100644 index 000000000..1325a45fa --- /dev/null +++ b/devtools/server/tests/unit/test_threadlifetime-01.js @@ -0,0 +1,58 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Check that thread-lifetime grips last past a resume. + */ + +var gDebuggee; +var gClient; +var gThreadClient; + +function run_test() +{ + initTestDebuggerServer(); + gDebuggee = addTestGlobal("test-grips"); + gClient = new DebuggerClient(DebuggerServer.connectPipe()); + gClient.connect().then(function () { + attachTestTabAndResume(gClient, "test-grips", function (aResponse, aTabClient, aThreadClient) { + gThreadClient = aThreadClient; + test_thread_lifetime(); + }); + }); + do_test_pending(); +} + +function test_thread_lifetime() +{ + gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) { + let pauseGrip = aPacket.frame.arguments[0]; + + // Create a thread-lifetime actor for this object. + gClient.request({ to: pauseGrip.actor, type: "threadGrip" }, function (aResponse) { + // Successful promotion won't return an error. + do_check_eq(aResponse.error, undefined); + gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) { + // Verify that the promoted actor is returned again. + do_check_eq(pauseGrip.actor, aPacket.frame.arguments[0].actor); + // Now that we've resumed, should get unrecognizePacketType for the + // promoted grip. + gClient.request({ to: pauseGrip.actor, type: "bogusRequest"}, function (aResponse) { + do_check_eq(aResponse.error, "unrecognizedPacketType"); + gThreadClient.resume(function () { + finishClient(gClient); + }); + }); + }); + gThreadClient.resume(); + }); + }); + + gDebuggee.eval("(" + function () { + function stopMe(arg1) { + debugger; + debugger; + } + stopMe({obj: true}); + } + ")()"); +} diff --git a/devtools/server/tests/unit/test_threadlifetime-02.js b/devtools/server/tests/unit/test_threadlifetime-02.js new file mode 100644 index 000000000..a7d21a7f9 --- /dev/null +++ b/devtools/server/tests/unit/test_threadlifetime-02.js @@ -0,0 +1,59 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Check that thread-lifetime grips last past a resume. + */ + +var gDebuggee; +var gClient; +var gThreadClient; + +function run_test() +{ + initTestDebuggerServer(); + gDebuggee = addTestGlobal("test-grips"); + gClient = new DebuggerClient(DebuggerServer.connectPipe()); + gClient.connect().then(function () { + attachTestTabAndResume(gClient, "test-grips", function (aResponse, aTabClient, aThreadClient) { + gThreadClient = aThreadClient; + test_thread_lifetime(); + }); + }); + do_test_pending(); +} + +function test_thread_lifetime() +{ + gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) { + let pauseGrip = aPacket.frame.arguments[0]; + + // Create a thread-lifetime actor for this object. + gClient.request({ to: pauseGrip.actor, type: "threadGrip" }, function (aResponse) { + // Successful promotion won't return an error. + do_check_eq(aResponse.error, undefined); + gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) { + // Verify that the promoted actor is returned again. + do_check_eq(pauseGrip.actor, aPacket.frame.arguments[0].actor); + // Now that we've resumed, release the thread-lifetime grip. + gClient.release(pauseGrip.actor, function (aResponse) { + gClient.request({ to: pauseGrip.actor, type: "bogusRequest" }, function (aResponse) { + do_check_eq(aResponse.error, "noSuchActor"); + gThreadClient.resume(function (aResponse) { + finishClient(gClient); + }); + }); + }); + }); + gThreadClient.resume(); + }); + }); + + gDebuggee.eval("(" + function () { + function stopMe(arg1) { + debugger; + debugger; + } + stopMe({obj: true}); + } + ")()"); +} diff --git a/devtools/server/tests/unit/test_threadlifetime-03.js b/devtools/server/tests/unit/test_threadlifetime-03.js new file mode 100644 index 000000000..22b707ff8 --- /dev/null +++ b/devtools/server/tests/unit/test_threadlifetime-03.js @@ -0,0 +1,81 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Check that thread-lifetime grips last past a resume. + */ + +var gDebuggee; +var gClient; +var gThreadClient; + +function run_test() +{ + initTestDebuggerServer(); + gDebuggee = addTestGlobal("test-grips"); + gClient = new DebuggerClient(DebuggerServer.connectPipe()); + gClient.connect().then(function () { + attachTestTabAndResume(gClient, "test-grips", function (aResponse, aTabClient, aThreadClient) { + gThreadClient = aThreadClient; + test_thread_lifetime(); + }); + }); + do_test_pending(); +} + +function test_thread_lifetime() +{ + // Get three thread-lifetime grips. + gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) { + let grips = []; + + let handler = function (aResponse) { + if (aResponse.error) { + do_check_eq(aResponse.error, ""); + finishClient(gClient); + } + grips.push(aResponse.from); + if (grips.length == 3) { + test_release_many(grips); + } + }; + for (let i = 0; i < 3; i++) { + gClient.request({ to: aPacket.frame.arguments[i].actor, type: "threadGrip" }, + handler); + } + }); + + gDebuggee.eval("(" + function () { + function stopMe(arg1, arg2, arg3) { + debugger; + } + stopMe({obj: 1}, {obj: 2}, {obj: 3}); + } + ")()"); +} + +function test_release_many(grips) +{ + // Release the first two grips, leave the third alive. + + let release = [grips[0], grips[1]]; + + gThreadClient.releaseMany(release, function (aResponse) { + // First two actors should return a noSuchActor error, because + // they're gone now. + gClient.request({ to: grips[0], type: "bogusRequest" }, function (aResponse) { + do_check_eq(aResponse.error, "noSuchActor"); + gClient.request({ to: grips[1], type: "bogusRequest" }, function (aResponse) { + do_check_eq(aResponse.error, "noSuchActor"); + + // Last actor should return unrecognizedPacketType, because it's still + // alive. + gClient.request({ to: grips[2], type: "bogusRequest" }, function (aResponse) { + do_check_eq(aResponse.error, "unrecognizedPacketType"); + gThreadClient.resume(function () { + finishClient(gClient); + }); + }); + }); + }); + }); +} diff --git a/devtools/server/tests/unit/test_threadlifetime-04.js b/devtools/server/tests/unit/test_threadlifetime-04.js new file mode 100644 index 000000000..aff8b525c --- /dev/null +++ b/devtools/server/tests/unit/test_threadlifetime-04.js @@ -0,0 +1,53 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Check that requesting a thread-lifetime actor twice for the same + * value returns the same actor. + */ + +var gDebuggee; +var gClient; +var gThreadClient; + +function run_test() +{ + initTestDebuggerServer(); + gDebuggee = addTestGlobal("test-grips"); + gClient = new DebuggerClient(DebuggerServer.connectPipe()); + gClient.connect().then(function () { + attachTestTabAndResume(gClient, "test-grips", function (aResponse, aTabClient, aThreadClient) { + gThreadClient = aThreadClient; + test_thread_lifetime(); + }); + }); + do_test_pending(); +} + +function test_thread_lifetime() +{ + gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) { + let pauseGrip = aPacket.frame.arguments[0]; + + gClient.request({ to: pauseGrip.actor, type: "threadGrip" }, function (aResponse) { + // Successful promotion won't return an error. + do_check_eq(aResponse.error, undefined); + + let threadGrip1 = aResponse.from; + + gClient.request({ to: pauseGrip.actor, type: "threadGrip" }, function (aResponse) { + do_check_eq(threadGrip1, aResponse.from); + gThreadClient.resume(function () { + finishClient(gClient); + }); + }); + }); + }); + + gDebuggee.eval("(" + function () { + function stopMe(arg1) { + debugger; + } + stopMe({obj: true}); + } + ")()"); +} diff --git a/devtools/server/tests/unit/test_threadlifetime-05.js b/devtools/server/tests/unit/test_threadlifetime-05.js new file mode 100644 index 000000000..6697947c1 --- /dev/null +++ b/devtools/server/tests/unit/test_threadlifetime-05.js @@ -0,0 +1,83 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Make sure that releasing a pause-lifetime actorin a releaseMany returns an + * error, but releases all the thread-lifetime actors. + */ + +var gDebuggee; +var gClient; +var gThreadClient; +var gPauseGrip; + +function run_test() +{ + initTestDebuggerServer(); + gDebuggee = addTestGlobal("test-grips"); + gClient = new DebuggerClient(DebuggerServer.connectPipe()); + gClient.connect().then(function () { + attachTestTabAndResume(gClient, "test-grips", function (aResponse, aTabClient, aThreadClient) { + gThreadClient = aThreadClient; + test_thread_lifetime(); + }); + }); + do_test_pending(); +} + +function arg_grips(aFrameArgs, aOnResponse) { + let grips = []; + let handler = function (aResponse) { + if (aResponse.error) { + grips.push(aResponse.error); + } else { + grips.push(aResponse.from); + } + if (grips.length == aFrameArgs.length) { + aOnResponse(grips); + } + }; + for (let i = 0; i < aFrameArgs.length; i++) { + gClient.request({ to: aFrameArgs[i].actor, type: "threadGrip" }, + handler); + } +} + +function test_thread_lifetime() +{ + // Get two thread-lifetime grips. + gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) { + + let frameArgs = [ aPacket.frame.arguments[0], aPacket.frame.arguments[1] ]; + gPauseGrip = aPacket.frame.arguments[2]; + arg_grips(frameArgs, function (aGrips) { + release_grips(frameArgs, aGrips); + }); + }); + + gDebuggee.eval("(" + function () { + function stopMe(arg1, arg2, arg3) { + debugger; + } + stopMe({obj: 1}, {obj: 2}, {obj: 3}); + } + ")()"); +} + + +function release_grips(aFrameArgs, aThreadGrips) +{ + // Release all actors with releaseMany... + let release = [aThreadGrips[0], aThreadGrips[1], gPauseGrip.actor]; + gThreadClient.releaseMany(release, function (aResponse) { + do_check_eq(aResponse.error, "notReleasable"); + // Now ask for thread grips again, they should not exist. + arg_grips(aFrameArgs, function (aNewGrips) { + for (let i = 0; i < aNewGrips.length; i++) { + do_check_eq(aNewGrips[i], "noSuchActor"); + } + gThreadClient.resume(function () { + finishClient(gClient); + }); + }); + }); +} diff --git a/devtools/server/tests/unit/test_threadlifetime-06.js b/devtools/server/tests/unit/test_threadlifetime-06.js new file mode 100644 index 000000000..dba0156f9 --- /dev/null +++ b/devtools/server/tests/unit/test_threadlifetime-06.js @@ -0,0 +1,71 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Check that promoting multiple pause-lifetime actors to thread-lifetime actors + * works as expected. + */ + +var gDebuggee; +var gClient; +var gThreadClient; + +function run_test() +{ + initTestDebuggerServer(); + gDebuggee = addTestGlobal("test-grips"); + gClient = new DebuggerClient(DebuggerServer.connectPipe()); + gClient.connect().then(function () { + attachTestTabAndResume(gClient, "test-grips", function (aResponse, aTabClient, aThreadClient) { + gThreadClient = aThreadClient; + test_thread_lifetime(); + }); + }); + do_test_pending(); +} + +function test_thread_lifetime() +{ + gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) { + let actors = []; + let last; + for (let aGrip of aPacket.frame.arguments) { + actors.push(aGrip.actor); + last = aGrip.actor; + } + + // Create thread-lifetime actors for these objects. + gThreadClient.threadGrips(actors, function (aResponse) { + // Successful promotion won't return an error. + do_check_eq(aResponse.error, undefined); + + gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) { + // Verify that the promoted actors are returned again. + actors.forEach(function (actor, i) { + do_check_eq(actor, aPacket.frame.arguments[i].actor); + }); + // Now that we've resumed, release the thread-lifetime grips. + gThreadClient.releaseMany(actors, function (aResponse) { + // Successful release won't return an error. + do_check_eq(aResponse.error, undefined); + + gClient.request({ to: last, type: "bogusRequest" }, function (aResponse) { + do_check_eq(aResponse.error, "noSuchActor"); + gThreadClient.resume(function (aResponse) { + finishClient(gClient); + }); + }); + }); + }); + gThreadClient.resume(); + }); + }); + + gDebuggee.eval("(" + function () { + function stopMe(arg1, arg2, arg3) { + debugger; + debugger; + } + stopMe({obj: 1}, {obj: 2}, {obj: 3}); + } + ")()"); +} diff --git a/devtools/server/tests/unit/test_unsafeDereference.js b/devtools/server/tests/unit/test_unsafeDereference.js new file mode 100644 index 000000000..d7afe645f --- /dev/null +++ b/devtools/server/tests/unit/test_unsafeDereference.js @@ -0,0 +1,134 @@ +// Any copyright is dedicated to the Public Domain. +// http://creativecommons.org/publicdomain/zero/1.0/ + +// Test Debugger.Object.prototype.unsafeDereference in the presence of +// interesting cross-compartment wrappers. +// +// This is not really a debugger server test; it's more of a Debugger test. +// But we need xpcshell and Components.utils.Sandbox to get +// cross-compartment wrappers with interesting properties, and this is the +// xpcshell test directory most closely related to the JS Debugger API. + +Components.utils.import("resource://gre/modules/jsdebugger.jsm"); +addDebuggerToGlobal(this); + +// Add a method to Debugger.Object for fetching value properties +// conveniently. +Debugger.Object.prototype.getProperty = function (aName) { + let desc = this.getOwnPropertyDescriptor(aName); + if (!desc) + return undefined; + if (!desc.value) { + throw Error("Debugger.Object.prototype.getProperty: " + + "not a value property: " + aName); + } + return desc.value; +}; + +function run_test() { + // Create a low-privilege sandbox, and a chrome-privilege sandbox. + let contentBox = Components.utils.Sandbox("http://www.example.com"); + let chromeBox = Components.utils.Sandbox(this); + + // Create an objects in this compartment, and one in each sandbox. We'll + // refer to the objects as "mainObj", "contentObj", and "chromeObj", in + // variable and property names. + var mainObj = { name: "mainObj" }; + Components.utils.evalInSandbox('var contentObj = { name: "contentObj" };', + contentBox); + Components.utils.evalInSandbox('var chromeObj = { name: "chromeObj" };', + chromeBox); + + // Give each global a pointer to all the other globals' objects. + contentBox.mainObj = chromeBox.mainObj = mainObj; + var contentObj = chromeBox.contentObj = contentBox.contentObj; + var chromeObj = contentBox.chromeObj = chromeBox.chromeObj; + + // First, a whole bunch of basic sanity checks, to ensure that JavaScript + // evaluated in various scopes really does see the world the way this + // test expects it to. + + // The objects appear as global variables in the sandbox, and as + // the sandbox object's properties in chrome. + do_check_true(Components.utils.evalInSandbox("mainObj", contentBox) + === contentBox.mainObj); + do_check_true(Components.utils.evalInSandbox("contentObj", contentBox) + === contentBox.contentObj); + do_check_true(Components.utils.evalInSandbox("chromeObj", contentBox) + === contentBox.chromeObj); + do_check_true(Components.utils.evalInSandbox("mainObj", chromeBox) + === chromeBox.mainObj); + do_check_true(Components.utils.evalInSandbox("contentObj", chromeBox) + === chromeBox.contentObj); + do_check_true(Components.utils.evalInSandbox("chromeObj", chromeBox) + === chromeBox.chromeObj); + + // We (the main global) can see properties of all objects in all globals. + do_check_true(contentBox.mainObj.name === "mainObj"); + do_check_true(contentBox.contentObj.name === "contentObj"); + do_check_true(contentBox.chromeObj.name === "chromeObj"); + + // chromeBox can see properties of all objects in all globals. + do_check_eq(Components.utils.evalInSandbox("mainObj.name", chromeBox), + "mainObj"); + do_check_eq(Components.utils.evalInSandbox("contentObj.name", chromeBox), + "contentObj"); + do_check_eq(Components.utils.evalInSandbox("chromeObj.name", chromeBox), + "chromeObj"); + + // contentBox can see properties of the content object, but not of either + // chrome object, because by default, content -> chrome wrappers hide all + // object properties. + do_check_eq(Components.utils.evalInSandbox("mainObj.name", contentBox), + undefined); + do_check_eq(Components.utils.evalInSandbox("contentObj.name", contentBox), + "contentObj"); + do_check_eq(Components.utils.evalInSandbox("chromeObj.name", contentBox), + undefined); + + // When viewing an object in compartment A from the vantage point of + // compartment B, Debugger should give the same results as debuggee code + // would. + + // Create a debugger, debugging our two sandboxes. + let dbg = new Debugger; + + // Create Debugger.Object instances referring to the two sandboxes, as + // seen from their own compartments. + let contentBoxDO = dbg.addDebuggee(contentBox); + let chromeBoxDO = dbg.addDebuggee(chromeBox); + + // Use Debugger to view the objects from contentBox. We should get the + // same D.O instance from both getProperty and makeDebuggeeValue, and the + // same property visibility we checked for above. + let mainFromContentDO = contentBoxDO.getProperty("mainObj"); + do_check_eq(mainFromContentDO, contentBoxDO.makeDebuggeeValue(mainObj)); + do_check_eq(mainFromContentDO.getProperty("name"), undefined); + do_check_eq(mainFromContentDO.unsafeDereference(), mainObj); + + let contentFromContentDO = contentBoxDO.getProperty("contentObj"); + do_check_eq(contentFromContentDO, contentBoxDO.makeDebuggeeValue(contentObj)); + do_check_eq(contentFromContentDO.getProperty("name"), "contentObj"); + do_check_eq(contentFromContentDO.unsafeDereference(), contentObj); + + let chromeFromContentDO = contentBoxDO.getProperty("chromeObj"); + do_check_eq(chromeFromContentDO, contentBoxDO.makeDebuggeeValue(chromeObj)); + do_check_eq(chromeFromContentDO.getProperty("name"), undefined); + do_check_eq(chromeFromContentDO.unsafeDereference(), chromeObj); + + // Similarly, viewing from chromeBox. + let mainFromChromeDO = chromeBoxDO.getProperty("mainObj"); + do_check_eq(mainFromChromeDO, chromeBoxDO.makeDebuggeeValue(mainObj)); + do_check_eq(mainFromChromeDO.getProperty("name"), "mainObj"); + do_check_eq(mainFromChromeDO.unsafeDereference(), mainObj); + + let contentFromChromeDO = chromeBoxDO.getProperty("contentObj"); + do_check_eq(contentFromChromeDO, chromeBoxDO.makeDebuggeeValue(contentObj)); + do_check_eq(contentFromChromeDO.getProperty("name"), "contentObj"); + do_check_eq(contentFromChromeDO.unsafeDereference(), contentObj); + + let chromeFromChromeDO = chromeBoxDO.getProperty("chromeObj"); + do_check_eq(chromeFromChromeDO, chromeBoxDO.makeDebuggeeValue(chromeObj)); + do_check_eq(chromeFromChromeDO.getProperty("name"), "chromeObj"); + do_check_eq(chromeFromChromeDO.unsafeDereference(), chromeObj); +} diff --git a/devtools/server/tests/unit/test_xpcshell_debugging.js b/devtools/server/tests/unit/test_xpcshell_debugging.js new file mode 100644 index 000000000..7026a02b3 --- /dev/null +++ b/devtools/server/tests/unit/test_xpcshell_debugging.js @@ -0,0 +1,48 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +// Test the xpcshell-test debug support. Ideally we should have this test +// next to the xpcshell support code, but that's tricky... + +function run_test() { + let testFile = do_get_file("xpcshell_debugging_script.js"); + + // _setupDebuggerServer is from xpcshell-test's head.js + let testResumed = false; + let DebuggerServer = _setupDebuggerServer([testFile.path], () => testResumed = true); + let transport = DebuggerServer.connectPipe(); + let client = new DebuggerClient(transport); + client.connect().then(() => { + // Even though we have no tabs, listTabs gives us the chromeDebugger. + client.getProcess().then(response => { + let actor = response.form.actor; + client.attachTab(actor, (response, tabClient) => { + tabClient.attachThread(null, (response, threadClient) => { + threadClient.addOneTimeListener("paused", (event, packet) => { + equal(packet.why.type, "breakpoint", + "yay - hit the breakpoint at the first line in our script"); + // Resume again - next stop should be our "debugger" statement. + threadClient.addOneTimeListener("paused", (event, packet) => { + equal(packet.why.type, "debuggerStatement", + "yay - hit the 'debugger' statement in our script"); + threadClient.resume(() => { + finishClient(client); + }); + }); + threadClient.resume(); + }); + // tell the thread to do the initial resume. This would cause the + // xpcshell test harness to resume and load the file under test. + threadClient.resume(response => { + // should have been told to resume the test itself. + ok(testResumed); + // Now load our test script. + load(testFile.path); + // and our "paused" listener above should get hit. + }); + }); + }); + }); + }); + do_test_pending(); +} diff --git a/devtools/server/tests/unit/testactors.js b/devtools/server/tests/unit/testactors.js new file mode 100644 index 000000000..39564eeef --- /dev/null +++ b/devtools/server/tests/unit/testactors.js @@ -0,0 +1,176 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +const { ActorPool, appendExtraActors, createExtraActors } = require("devtools/server/actors/common"); +const { RootActor } = require("devtools/server/actors/root"); +const { ThreadActor } = require("devtools/server/actors/script"); +const { DebuggerServer } = require("devtools/server/main"); +const { TabSources } = require("devtools/server/actors/utils/TabSources"); +const promise = require("promise"); +const makeDebugger = require("devtools/server/actors/utils/make-debugger"); + +var gTestGlobals = []; +DebuggerServer.addTestGlobal = function (aGlobal) { + gTestGlobals.push(aGlobal); +}; + +DebuggerServer.getTestGlobal = function (name) { + for (let g of gTestGlobals) { + if (g.__name == name) { + return g; + } + } + + return null; +}; + +// A mock tab list, for use by tests. This simply presents each global in +// gTestGlobals as a tab, and the list is fixed: it never calls its +// onListChanged handler. +// +// As implemented now, we consult gTestGlobals when we're constructed, not +// when we're iterated over, so tests have to add their globals before the +// root actor is created. +function TestTabList(aConnection) { + this.conn = aConnection; + + // An array of actors for each global added with + // DebuggerServer.addTestGlobal. + this._tabActors = []; + + // A pool mapping those actors' names to the actors. + this._tabActorPool = new ActorPool(aConnection); + + for (let global of gTestGlobals) { + let actor = new TestTabActor(aConnection, global); + actor.selected = false; + this._tabActors.push(actor); + this._tabActorPool.addActor(actor); + } + if (this._tabActors.length > 0) { + this._tabActors[0].selected = true; + } + + aConnection.addActorPool(this._tabActorPool); +} + +TestTabList.prototype = { + constructor: TestTabList, + getList: function () { + return Promise.resolve([...this._tabActors]); + } +}; + +function createRootActor(aConnection) +{ + let root = new RootActor(aConnection, { + tabList: new TestTabList(aConnection), + globalActorFactories: DebuggerServer.globalActorFactories, + }); + + root.applicationType = "xpcshell-tests"; + return root; +} + +function TestTabActor(aConnection, aGlobal) +{ + this.conn = aConnection; + this._global = aGlobal; + this._global.wrappedJSObject = aGlobal; + this.threadActor = new ThreadActor(this, this._global); + this.conn.addActor(this.threadActor); + this._attached = false; + this._extraActors = {}; + this.makeDebugger = makeDebugger.bind(null, { + findDebuggees: () => [this._global], + shouldAddNewGlobalAsDebuggee: g => g.hostAnnotations && + g.hostAnnotations.type == "document" && + g.hostAnnotations.element === this._global + + }); +} + +TestTabActor.prototype = { + constructor: TestTabActor, + actorPrefix: "TestTabActor", + + get window() { + return this._global; + }, + + get url() { + return this._global.__name; + }, + + get sources() { + if (!this._sources) { + this._sources = new TabSources(this.threadActor); + } + return this._sources; + }, + + form: function () { + let response = { actor: this.actorID, title: this._global.__name }; + + // Walk over tab actors added by extensions and add them to a new ActorPool. + let actorPool = new ActorPool(this.conn); + this._createExtraActors(DebuggerServer.tabActorFactories, actorPool); + if (!actorPool.isEmpty()) { + this._tabActorPool = actorPool; + this.conn.addActorPool(this._tabActorPool); + } + + this._appendExtraActors(response); + + return response; + }, + + onAttach: function (aRequest) { + this._attached = true; + + let response = { type: "tabAttached", threadActor: this.threadActor.actorID }; + this._appendExtraActors(response); + + return response; + }, + + onDetach: function (aRequest) { + if (!this._attached) { + return { "error":"wrongState" }; + } + return { type: "detached" }; + }, + + onReload: function (aRequest) { + this.sources.reset({ sourceMaps: true }); + this.threadActor.clearDebuggees(); + this.threadActor.dbg.addDebuggees(); + return {}; + }, + + removeActorByName: function (aName) { + const actor = this._extraActors[aName]; + if (this._tabActorPool) { + this._tabActorPool.removeActor(actor); + } + delete this._extraActors[aName]; + }, + + /* Support for DebuggerServer.addTabActor. */ + _createExtraActors: createExtraActors, + _appendExtraActors: appendExtraActors +}; + +TestTabActor.prototype.requestTypes = { + "attach": TestTabActor.prototype.onAttach, + "detach": TestTabActor.prototype.onDetach, + "reload": TestTabActor.prototype.onReload +}; + +exports.register = function (handle) { + handle.setRootActor(createRootActor); +}; + +exports.unregister = function (handle) { + handle.setRootActor(null); +}; diff --git a/devtools/server/tests/unit/tracerlocations.js b/devtools/server/tests/unit/tracerlocations.js new file mode 100644 index 000000000..aa4677c0f --- /dev/null +++ b/devtools/server/tests/unit/tracerlocations.js @@ -0,0 +1,8 @@ +// For JS Tracer tests dealing with source locations. + +function foo(x) { + x += 6; + return "bar"; +} + +foo(42); diff --git a/devtools/server/tests/unit/xpcshell.ini b/devtools/server/tests/unit/xpcshell.ini new file mode 100644 index 000000000..703aa48fb --- /dev/null +++ b/devtools/server/tests/unit/xpcshell.ini @@ -0,0 +1,232 @@ +[DEFAULT] +tags = devtools +head = head_dbg.js +tail = +firefox-appdir = browser +skip-if = toolkit == 'android' + +support-files = + babel_and_browserify_script_with_source_map.js + source-map-data/sourcemapped.coffee + source-map-data/sourcemapped.map + post_init_global_actors.js + post_init_tab_actors.js + pre_init_global_actors.js + pre_init_tab_actors.js + registertestactors-01.js + registertestactors-02.js + registertestactors-03.js + sourcemapped.js + testactors.js + hello-actor.js + setBreakpoint-on-column.js + setBreakpoint-on-column-in-gcd-script.js + setBreakpoint-on-column-with-no-offsets.js + setBreakpoint-on-column-with-no-offsets-at-end-of-line.js + setBreakpoint-on-column-with-no-offsets-in-gcd-script.js + setBreakpoint-on-line.js + setBreakpoint-on-line-in-gcd-script.js + setBreakpoint-on-line-with-multiple-offsets.js + setBreakpoint-on-line-with-multiple-statements.js + setBreakpoint-on-line-with-no-offsets.js + setBreakpoint-on-line-with-no-offsets-in-gcd-script.js + addons/web-extension/manifest.json + addons/web-extension-upgrade/manifest.json + addons/web-extension2/manifest.json + +[test_addon_reload.js] +[test_addons_actor.js] +[test_animation_name.js] +[test_animation_type.js] +[test_actor-registry-actor.js] +[test_nesting-01.js] +[test_nesting-02.js] +[test_nesting-03.js] +[test_forwardingprefix.js] +[test_getyoungestframe.js] +[test_nsjsinspector.js] +[test_dbgactor.js] +[test_dbgglobal.js] +[test_dbgclient_debuggerstatement.js] +[test_attach.js] +[test_MemoryActor_saveHeapSnapshot_01.js] +[test_MemoryActor_saveHeapSnapshot_02.js] +[test_MemoryActor_saveHeapSnapshot_03.js] +[test_reattach-thread.js] +[test_blackboxing-01.js] +[test_blackboxing-02.js] +[test_blackboxing-03.js] +[test_blackboxing-04.js] +[test_blackboxing-05.js] +[test_blackboxing-06.js] +[test_blackboxing-07.js] +[test_frameactor-01.js] +[test_frameactor-02.js] +[test_frameactor-03.js] +[test_frameactor-04.js] +[test_frameactor-05.js] +[test_framearguments-01.js] +[test_getRuleText.js] +[test_getTextAtLineColumn.js] +[test_pauselifetime-01.js] +[test_pauselifetime-02.js] +[test_pauselifetime-03.js] +[test_pauselifetime-04.js] +[test_threadlifetime-01.js] +[test_threadlifetime-02.js] +[test_threadlifetime-03.js] +[test_threadlifetime-04.js] +[test_threadlifetime-05.js] +[test_threadlifetime-06.js] +[test_functiongrips-01.js] +[test_frameclient-01.js] +[test_frameclient-02.js] +[test_nativewrappers.js] +[test_nodelistactor.js] +[test_eval-01.js] +[test_eval-02.js] +[test_eval-03.js] +[test_eval-04.js] +[test_eval-05.js] +[test_promises_actor_attach.js] +[test_promises_actor_exist.js] +[test_promises_actor_list_promises.js] +[test_promises_actor_onnewpromise.js] +[test_promises_actor_onpromisesettled.js] +[test_promises_client_getdependentpromises.js] +[test_promises_object_creationtimestamp.js] +[test_promises_object_timetosettle-01.js] +[test_promises_object_timetosettle-02.js] +[test_protocol_abort.js] +[test_protocol_async.js] +[test_protocol_children.js] +[test_protocol_formtype.js] +[test_protocol_longstring.js] +[test_protocol_simple.js] +[test_protocol_stack.js] +[test_protocol_unregister.js] +[test_breakpoint-01.js] +[test_register_actor.js] +[test_breakpoint-02.js] +[test_breakpoint-03.js] +[test_breakpoint-04.js] +[test_breakpoint-05.js] +[test_breakpoint-06.js] +[test_breakpoint-07.js] +[test_breakpoint-08.js] +[test_breakpoint-09.js] +[test_breakpoint-10.js] +[test_breakpoint-11.js] +[test_breakpoint-12.js] +[test_breakpoint-13.js] +[test_breakpoint-14.js] +[test_breakpoint-15.js] +[test_breakpoint-16.js] +[test_breakpoint-17.js] +[test_breakpoint-18.js] +[test_breakpoint-19.js] +skip-if = true +reason = bug 1104838 +[test_breakpoint-20.js] +[test_breakpoint-21.js] +[test_conditional_breakpoint-01.js] +[test_conditional_breakpoint-02.js] +[test_conditional_breakpoint-03.js] +[test_eventlooplag_actor.js] +skip-if = true +reason = only ran on B2G +[test_listsources-01.js] +[test_listsources-02.js] +[test_listsources-03.js] +[test_listsources-04.js] +[test_new_source-01.js] +[test_sourcemaps-01.js] +[test_sourcemaps-02.js] +[test_sourcemaps-03.js] +[test_sourcemaps-04.js] +[test_sourcemaps-05.js] +[test_sourcemaps-06.js] +[test_sourcemaps-07.js] +[test_sourcemaps-08.js] +[test_sourcemaps-09.js] +[test_sourcemaps-10.js] +[test_sourcemaps-11.js] +[test_sourcemaps-12.js] +[test_sourcemaps-13.js] +[test_sourcemaps-16.js] +[test_sourcemaps-17.js] +[test_objectgrips-01.js] +[test_objectgrips-02.js] +[test_objectgrips-03.js] +[test_objectgrips-04.js] +[test_objectgrips-05.js] +[test_objectgrips-06.js] +[test_objectgrips-07.js] +[test_objectgrips-08.js] +[test_objectgrips-09.js] +[test_objectgrips-10.js] +[test_objectgrips-11.js] +[test_objectgrips-12.js] +[test_objectgrips-13.js] +[test_promise_state-01.js] +[test_promise_state-02.js] +[test_promise_state-03.js] +[test_interrupt.js] +[test_stepping-01.js] +[test_stepping-02.js] +[test_stepping-03.js] +[test_stepping-04.js] +[test_stepping-05.js] +[test_stepping-06.js] +[test_stepping-07.js] +[test_framebindings-01.js] +[test_framebindings-02.js] +[test_framebindings-03.js] +[test_framebindings-04.js] +[test_framebindings-05.js] +[test_framebindings-06.js] +[test_framebindings-07.js] +[test_pause_exceptions-01.js] +[test_pause_exceptions-02.js] +[test_longstringactor.js] +[test_longstringgrips-01.js] +[test_longstringgrips-02.js] +[test_source-01.js] +[test_breakpoint-actor-map.js] +[test_profiler_activation-01.js] +[test_profiler_activation-02.js] +[test_profiler_close.js] +[test_profiler_data.js] +[test_profiler_events-01.js] +[test_profiler_events-02.js] +[test_profiler_getbufferinfo.js] +[test_profiler_getfeatures.js] +[test_profiler_getsharedlibraryinformation.js] +[test_unsafeDereference.js] +[test_add_actors.js] +[test_ignore_caught_exceptions.js] +[test_ignore_no_interface_exceptions.js] +[test_requestTypes.js] +reason = bug 937197 +[test_layout-reflows-observer.js] +[test_protocolSpec.js] +[test_registerClient.js] +[test_client_request.js] +[test_monitor_actor.js] +[test_symbols-01.js] +[test_symbols-02.js] +[test_get-executable-lines.js] +[test_get-executable-lines-source-map.js] +[test_xpcshell_debugging.js] +support-files = xpcshell_debugging_script.js +[test_setBreakpoint-on-column.js] +[test_setBreakpoint-on-column-in-gcd-script.js] +[test_setBreakpoint-on-column-with-no-offsets-at-end-of-line.js] +[test_setBreakpoint-on-line.js] +[test_setBreakpoint-on-line-in-gcd-script.js] +[test_setBreakpoint-on-line-with-multiple-offsets.js] +[test_setBreakpoint-on-line-with-multiple-statements.js] +[test_setBreakpoint-on-line-with-no-offsets.js] +[test_setBreakpoint-on-line-with-no-offsets-in-gcd-script.js] +[test_safe-getter.js] +[test_client_close.js] diff --git a/devtools/server/tests/unit/xpcshell_debugging_script.js b/devtools/server/tests/unit/xpcshell_debugging_script.js new file mode 100644 index 000000000..de2870e96 --- /dev/null +++ b/devtools/server/tests/unit/xpcshell_debugging_script.js @@ -0,0 +1,9 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +// This is a file that test_xpcshell_debugging.js debugs. + +// We should hit this dump as it is the first debuggable line +dump("hello from the debugee!\n"); + +debugger; // and why not check we hit this!? |