michael@0: var Cu = Components.utils; michael@0: michael@0: Cu.import("resource://gre/modules/devtools/Loader.jsm"); michael@0: Cu.import("resource://gre/modules/devtools/dbg-client.jsm"); michael@0: Cu.import("resource://gre/modules/devtools/dbg-server.jsm"); michael@0: michael@0: const Services = devtools.require("Services"); michael@0: const {_documentWalker} = devtools.require("devtools/server/actors/inspector"); michael@0: michael@0: // Always log packets when running tests. michael@0: Services.prefs.setBoolPref("devtools.debugger.log", true); michael@0: SimpleTest.registerCleanupFunction(function() { michael@0: Services.prefs.clearUserPref("devtools.debugger.log"); michael@0: }); michael@0: michael@0: michael@0: if (!DebuggerServer.initialized) { michael@0: DebuggerServer.init(() => true); michael@0: DebuggerServer.addBrowserActors(); michael@0: SimpleTest.registerCleanupFunction(function() { michael@0: DebuggerServer.destroy(); michael@0: }); michael@0: } michael@0: michael@0: var gAttachCleanups = []; michael@0: michael@0: SimpleTest.registerCleanupFunction(function() { michael@0: for (let cleanup of gAttachCleanups) { michael@0: cleanup(); michael@0: } michael@0: }); michael@0: michael@0: /** michael@0: * Open a tab, load the url, wait for it to signal its readiness, michael@0: * find the tab with the debugger server, and call the callback. michael@0: * michael@0: * Returns a function which can be called to close the opened ta michael@0: * and disconnect its debugger client. michael@0: */ michael@0: function attachURL(url, callback) { michael@0: var win = window.open(url, "_blank"); michael@0: var client = null; michael@0: michael@0: let cleanup = () => { michael@0: if (client) { michael@0: client.close(); michael@0: client = null; michael@0: } michael@0: if (win) { michael@0: win.close(); michael@0: win = null; michael@0: } michael@0: }; michael@0: gAttachCleanups.push(cleanup); michael@0: michael@0: window.addEventListener("message", function loadListener(event) { michael@0: if (event.data === "ready") { michael@0: client = new DebuggerClient(DebuggerServer.connectPipe()); michael@0: client.connect((applicationType, traits) => { michael@0: client.listTabs(response => { michael@0: for (let tab of response.tabs) { michael@0: if (tab.url === url) { michael@0: window.removeEventListener("message", loadListener, false); michael@0: client.attachTab(tab.actor, function(aResponse, aTabClient) { michael@0: try { michael@0: callback(null, client, tab, win.document); michael@0: } catch(ex) { michael@0: Cu.reportError(ex); michael@0: dump(ex); michael@0: } michael@0: }); michael@0: break; michael@0: } michael@0: } michael@0: }); michael@0: }); michael@0: } michael@0: }, false); michael@0: michael@0: return cleanup; michael@0: } michael@0: michael@0: function promiseOnce(target, event) { michael@0: let deferred = promise.defer(); michael@0: target.on(event, (...args) => { michael@0: if (args.length === 1) { michael@0: deferred.resolve(args[0]); michael@0: } else { michael@0: deferred.resolve(args); michael@0: } michael@0: }); michael@0: return deferred.promise; michael@0: } michael@0: michael@0: function sortOwnershipChildren(children) { michael@0: return children.sort((a, b) => a.name.localeCompare(b.name)); michael@0: } michael@0: michael@0: function serverOwnershipSubtree(walker, node) { michael@0: let actor = walker._refMap.get(node); michael@0: if (!actor) { michael@0: return undefined; michael@0: } michael@0: michael@0: let children = []; michael@0: let docwalker = _documentWalker(node, window); michael@0: let child = docwalker.firstChild(); michael@0: while (child) { michael@0: let item = serverOwnershipSubtree(walker, child); michael@0: if (item) { michael@0: children.push(item); michael@0: } michael@0: child = docwalker.nextSibling(); michael@0: } michael@0: return { michael@0: name: actor.actorID, michael@0: children: sortOwnershipChildren(children) michael@0: } michael@0: } michael@0: michael@0: function serverOwnershipTree(walker) { michael@0: let serverConnection = walker.conn._transport._serverConnection; michael@0: let serverWalker = serverConnection.getActor(walker.actorID); michael@0: michael@0: return { michael@0: root: serverOwnershipSubtree(serverWalker, serverWalker.rootDoc ), michael@0: orphaned: [serverOwnershipSubtree(serverWalker, o.rawNode) for (o of serverWalker._orphaned)], michael@0: retained: [serverOwnershipSubtree(serverWalker, o.rawNode) for (o of serverWalker._retainedOrphans)] michael@0: }; michael@0: } michael@0: michael@0: function clientOwnershipSubtree(node) { michael@0: return { michael@0: name: node.actorID, michael@0: children: sortOwnershipChildren([clientOwnershipSubtree(child) for (child of node.treeChildren())]) michael@0: } michael@0: } michael@0: michael@0: function clientOwnershipTree(walker) { michael@0: return { michael@0: root: clientOwnershipSubtree(walker.rootNode), michael@0: orphaned: [clientOwnershipSubtree(o) for (o of walker._orphaned)], michael@0: retained: [clientOwnershipSubtree(o) for (o of walker._retainedOrphans)] michael@0: } michael@0: } michael@0: michael@0: function ownershipTreeSize(tree) { michael@0: let size = 1; michael@0: for (let child of tree.children) { michael@0: size += ownershipTreeSize(child); michael@0: } michael@0: return size; michael@0: } michael@0: michael@0: function assertOwnershipTrees(walker) { michael@0: let serverTree = serverOwnershipTree(walker); michael@0: let clientTree = clientOwnershipTree(walker); michael@0: is(JSON.stringify(clientTree, null, ' '), JSON.stringify(serverTree, null, ' '), "Server and client ownership trees should match."); michael@0: michael@0: return ownershipTreeSize(clientTree.root); michael@0: } michael@0: michael@0: // Verify that an actorID is inaccessible both from the client library and the server. michael@0: function checkMissing(client, actorID) { michael@0: let deferred = promise.defer(); michael@0: let front = client.getActor(actorID); michael@0: ok(!front, "Front shouldn't be accessible from the client for actorID: " + actorID); michael@0: michael@0: let deferred = promise.defer(); michael@0: client.request({ michael@0: to: actorID, michael@0: type: "request", michael@0: }, response => { michael@0: is(response.error, "noSuchActor", "node list actor should no longer be contactable."); michael@0: deferred.resolve(undefined); michael@0: }); michael@0: return deferred.promise; michael@0: } michael@0: michael@0: // Verify that an actorID is accessible both from the client library and the server. michael@0: function checkAvailable(client, actorID) { michael@0: let deferred = promise.defer(); michael@0: let front = client.getActor(actorID); michael@0: ok(front, "Front should be accessible from the client for actorID: " + actorID); michael@0: michael@0: let deferred = promise.defer(); michael@0: client.request({ michael@0: to: actorID, michael@0: type: "garbageAvailableTest", michael@0: }, response => { michael@0: is(response.error, "unrecognizedPacketType", "node list actor should be contactable."); michael@0: deferred.resolve(undefined); michael@0: }); michael@0: return deferred.promise; michael@0: } michael@0: michael@0: function promiseDone(promise) { michael@0: promise.then(null, err => { michael@0: ok(false, "Promise failed: " + err); michael@0: if (err.stack) { michael@0: dump(err.stack); michael@0: } michael@0: SimpleTest.finish(); michael@0: }); michael@0: } michael@0: michael@0: // Mutation list testing michael@0: michael@0: function isSrcChange(change) { michael@0: return (change.type === "attributes" && change.attributeName === "src"); michael@0: } michael@0: michael@0: function assertAndStrip(mutations, message, test) { michael@0: let size = mutations.length; michael@0: mutations = mutations.filter(test); michael@0: ok((mutations.size != size), message); michael@0: return mutations; michael@0: } michael@0: michael@0: function isSrcChange(change) { michael@0: return change.type === "attributes" && change.attributeName === "src"; michael@0: } michael@0: michael@0: function isUnload(change) { michael@0: return change.type === "documentUnload"; michael@0: } michael@0: michael@0: function isFrameLoad(change) { michael@0: return change.type === "frameLoad"; michael@0: } michael@0: michael@0: function isUnretained(change) { michael@0: return change.type === "unretained"; michael@0: } michael@0: michael@0: function isChildList(change) { michael@0: return change.type === "childList"; michael@0: } michael@0: michael@0: function isNewRoot(change) { michael@0: return change.type === "newRoot"; michael@0: } michael@0: michael@0: // Make sure an iframe's src attribute changed and then michael@0: // strip that mutation out of the list. michael@0: function assertSrcChange(mutations) { michael@0: return assertAndStrip(mutations, "Should have had an iframe source change.", isSrcChange); michael@0: } michael@0: michael@0: // Make sure there's an unload in the mutation list and strip michael@0: // that mutation out of the list michael@0: function assertUnload(mutations) { michael@0: return assertAndStrip(mutations, "Should have had a document unload change.", isUnload); michael@0: } michael@0: michael@0: // Make sure there's a frame load in the mutation list and strip michael@0: // that mutation out of the list michael@0: function assertFrameLoad(mutations) { michael@0: return assertAndStrip(mutations, "Should have had a frame load change.", isFrameLoad); michael@0: } michael@0: michael@0: // Make sure there's a childList change in the mutation list and strip michael@0: // that mutation out of the list michael@0: function assertChildList(mutations) { michael@0: return assertAndStrip(mutations, "Should have had a frame load change.", isChildList); michael@0: } michael@0: michael@0: // Load mutations aren't predictable, so keep accumulating mutations until michael@0: // the one we're looking for shows up. michael@0: function waitForMutation(walker, test, mutations=[]) { michael@0: let deferred = promise.defer(); michael@0: for (let change of mutations) { michael@0: if (test(change)) { michael@0: deferred.resolve(mutations); michael@0: } michael@0: } michael@0: michael@0: walker.once("mutations", newMutations => { michael@0: waitForMutation(walker, test, mutations.concat(newMutations)).then(finalMutations => { michael@0: deferred.resolve(finalMutations); michael@0: }) michael@0: }); michael@0: michael@0: return deferred.promise; michael@0: } michael@0: michael@0: michael@0: var _tests = []; michael@0: function addTest(test) { michael@0: _tests.push(test); michael@0: } michael@0: michael@0: function runNextTest() { michael@0: if (_tests.length == 0) { michael@0: SimpleTest.finish() michael@0: return; michael@0: } michael@0: var fn = _tests.shift(); michael@0: try { michael@0: fn(); michael@0: } catch (ex) { michael@0: info("Test function " + (fn.name ? "'" + fn.name + "' " : "") + michael@0: "threw an exception: " + ex); michael@0: } michael@0: }