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); } }