diff options
Diffstat (limited to 'devtools/server/tests/mochitest/inspector-helpers.js')
-rw-r--r-- | devtools/server/tests/mochitest/inspector-helpers.js | 310 |
1 files changed, 310 insertions, 0 deletions
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); + } +} |