1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/toolkit/devtools/server/tests/mochitest/inspector-helpers.js Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,305 @@ 1.4 +var Cu = Components.utils; 1.5 + 1.6 +Cu.import("resource://gre/modules/devtools/Loader.jsm"); 1.7 +Cu.import("resource://gre/modules/devtools/dbg-client.jsm"); 1.8 +Cu.import("resource://gre/modules/devtools/dbg-server.jsm"); 1.9 + 1.10 +const Services = devtools.require("Services"); 1.11 +const {_documentWalker} = devtools.require("devtools/server/actors/inspector"); 1.12 + 1.13 +// Always log packets when running tests. 1.14 +Services.prefs.setBoolPref("devtools.debugger.log", true); 1.15 +SimpleTest.registerCleanupFunction(function() { 1.16 + Services.prefs.clearUserPref("devtools.debugger.log"); 1.17 +}); 1.18 + 1.19 + 1.20 +if (!DebuggerServer.initialized) { 1.21 + DebuggerServer.init(() => true); 1.22 + DebuggerServer.addBrowserActors(); 1.23 + SimpleTest.registerCleanupFunction(function() { 1.24 + DebuggerServer.destroy(); 1.25 + }); 1.26 +} 1.27 + 1.28 +var gAttachCleanups = []; 1.29 + 1.30 +SimpleTest.registerCleanupFunction(function() { 1.31 + for (let cleanup of gAttachCleanups) { 1.32 + cleanup(); 1.33 + } 1.34 +}); 1.35 + 1.36 +/** 1.37 + * Open a tab, load the url, wait for it to signal its readiness, 1.38 + * find the tab with the debugger server, and call the callback. 1.39 + * 1.40 + * Returns a function which can be called to close the opened ta 1.41 + * and disconnect its debugger client. 1.42 + */ 1.43 +function attachURL(url, callback) { 1.44 + var win = window.open(url, "_blank"); 1.45 + var client = null; 1.46 + 1.47 + let cleanup = () => { 1.48 + if (client) { 1.49 + client.close(); 1.50 + client = null; 1.51 + } 1.52 + if (win) { 1.53 + win.close(); 1.54 + win = null; 1.55 + } 1.56 + }; 1.57 + gAttachCleanups.push(cleanup); 1.58 + 1.59 + window.addEventListener("message", function loadListener(event) { 1.60 + if (event.data === "ready") { 1.61 + client = new DebuggerClient(DebuggerServer.connectPipe()); 1.62 + client.connect((applicationType, traits) => { 1.63 + client.listTabs(response => { 1.64 + for (let tab of response.tabs) { 1.65 + if (tab.url === url) { 1.66 + window.removeEventListener("message", loadListener, false); 1.67 + client.attachTab(tab.actor, function(aResponse, aTabClient) { 1.68 + try { 1.69 + callback(null, client, tab, win.document); 1.70 + } catch(ex) { 1.71 + Cu.reportError(ex); 1.72 + dump(ex); 1.73 + } 1.74 + }); 1.75 + break; 1.76 + } 1.77 + } 1.78 + }); 1.79 + }); 1.80 + } 1.81 + }, false); 1.82 + 1.83 + return cleanup; 1.84 +} 1.85 + 1.86 +function promiseOnce(target, event) { 1.87 + let deferred = promise.defer(); 1.88 + target.on(event, (...args) => { 1.89 + if (args.length === 1) { 1.90 + deferred.resolve(args[0]); 1.91 + } else { 1.92 + deferred.resolve(args); 1.93 + } 1.94 + }); 1.95 + return deferred.promise; 1.96 +} 1.97 + 1.98 +function sortOwnershipChildren(children) { 1.99 + return children.sort((a, b) => a.name.localeCompare(b.name)); 1.100 +} 1.101 + 1.102 +function serverOwnershipSubtree(walker, node) { 1.103 + let actor = walker._refMap.get(node); 1.104 + if (!actor) { 1.105 + return undefined; 1.106 + } 1.107 + 1.108 + let children = []; 1.109 + let docwalker = _documentWalker(node, window); 1.110 + let child = docwalker.firstChild(); 1.111 + while (child) { 1.112 + let item = serverOwnershipSubtree(walker, child); 1.113 + if (item) { 1.114 + children.push(item); 1.115 + } 1.116 + child = docwalker.nextSibling(); 1.117 + } 1.118 + return { 1.119 + name: actor.actorID, 1.120 + children: sortOwnershipChildren(children) 1.121 + } 1.122 +} 1.123 + 1.124 +function serverOwnershipTree(walker) { 1.125 + let serverConnection = walker.conn._transport._serverConnection; 1.126 + let serverWalker = serverConnection.getActor(walker.actorID); 1.127 + 1.128 + return { 1.129 + root: serverOwnershipSubtree(serverWalker, serverWalker.rootDoc ), 1.130 + orphaned: [serverOwnershipSubtree(serverWalker, o.rawNode) for (o of serverWalker._orphaned)], 1.131 + retained: [serverOwnershipSubtree(serverWalker, o.rawNode) for (o of serverWalker._retainedOrphans)] 1.132 + }; 1.133 +} 1.134 + 1.135 +function clientOwnershipSubtree(node) { 1.136 + return { 1.137 + name: node.actorID, 1.138 + children: sortOwnershipChildren([clientOwnershipSubtree(child) for (child of node.treeChildren())]) 1.139 + } 1.140 +} 1.141 + 1.142 +function clientOwnershipTree(walker) { 1.143 + return { 1.144 + root: clientOwnershipSubtree(walker.rootNode), 1.145 + orphaned: [clientOwnershipSubtree(o) for (o of walker._orphaned)], 1.146 + retained: [clientOwnershipSubtree(o) for (o of walker._retainedOrphans)] 1.147 + } 1.148 +} 1.149 + 1.150 +function ownershipTreeSize(tree) { 1.151 + let size = 1; 1.152 + for (let child of tree.children) { 1.153 + size += ownershipTreeSize(child); 1.154 + } 1.155 + return size; 1.156 +} 1.157 + 1.158 +function assertOwnershipTrees(walker) { 1.159 + let serverTree = serverOwnershipTree(walker); 1.160 + let clientTree = clientOwnershipTree(walker); 1.161 + is(JSON.stringify(clientTree, null, ' '), JSON.stringify(serverTree, null, ' '), "Server and client ownership trees should match."); 1.162 + 1.163 + return ownershipTreeSize(clientTree.root); 1.164 +} 1.165 + 1.166 +// Verify that an actorID is inaccessible both from the client library and the server. 1.167 +function checkMissing(client, actorID) { 1.168 + let deferred = promise.defer(); 1.169 + let front = client.getActor(actorID); 1.170 + ok(!front, "Front shouldn't be accessible from the client for actorID: " + actorID); 1.171 + 1.172 + let deferred = promise.defer(); 1.173 + client.request({ 1.174 + to: actorID, 1.175 + type: "request", 1.176 + }, response => { 1.177 + is(response.error, "noSuchActor", "node list actor should no longer be contactable."); 1.178 + deferred.resolve(undefined); 1.179 + }); 1.180 + return deferred.promise; 1.181 +} 1.182 + 1.183 +// Verify that an actorID is accessible both from the client library and the server. 1.184 +function checkAvailable(client, actorID) { 1.185 + let deferred = promise.defer(); 1.186 + let front = client.getActor(actorID); 1.187 + ok(front, "Front should be accessible from the client for actorID: " + actorID); 1.188 + 1.189 + let deferred = promise.defer(); 1.190 + client.request({ 1.191 + to: actorID, 1.192 + type: "garbageAvailableTest", 1.193 + }, response => { 1.194 + is(response.error, "unrecognizedPacketType", "node list actor should be contactable."); 1.195 + deferred.resolve(undefined); 1.196 + }); 1.197 + return deferred.promise; 1.198 +} 1.199 + 1.200 +function promiseDone(promise) { 1.201 + promise.then(null, err => { 1.202 + ok(false, "Promise failed: " + err); 1.203 + if (err.stack) { 1.204 + dump(err.stack); 1.205 + } 1.206 + SimpleTest.finish(); 1.207 + }); 1.208 +} 1.209 + 1.210 +// Mutation list testing 1.211 + 1.212 +function isSrcChange(change) { 1.213 + return (change.type === "attributes" && change.attributeName === "src"); 1.214 +} 1.215 + 1.216 +function assertAndStrip(mutations, message, test) { 1.217 + let size = mutations.length; 1.218 + mutations = mutations.filter(test); 1.219 + ok((mutations.size != size), message); 1.220 + return mutations; 1.221 +} 1.222 + 1.223 +function isSrcChange(change) { 1.224 + return change.type === "attributes" && change.attributeName === "src"; 1.225 +} 1.226 + 1.227 +function isUnload(change) { 1.228 + return change.type === "documentUnload"; 1.229 +} 1.230 + 1.231 +function isFrameLoad(change) { 1.232 + return change.type === "frameLoad"; 1.233 +} 1.234 + 1.235 +function isUnretained(change) { 1.236 + return change.type === "unretained"; 1.237 +} 1.238 + 1.239 +function isChildList(change) { 1.240 + return change.type === "childList"; 1.241 +} 1.242 + 1.243 +function isNewRoot(change) { 1.244 + return change.type === "newRoot"; 1.245 +} 1.246 + 1.247 +// Make sure an iframe's src attribute changed and then 1.248 +// strip that mutation out of the list. 1.249 +function assertSrcChange(mutations) { 1.250 + return assertAndStrip(mutations, "Should have had an iframe source change.", isSrcChange); 1.251 +} 1.252 + 1.253 +// Make sure there's an unload in the mutation list and strip 1.254 +// that mutation out of the list 1.255 +function assertUnload(mutations) { 1.256 + return assertAndStrip(mutations, "Should have had a document unload change.", isUnload); 1.257 +} 1.258 + 1.259 +// Make sure there's a frame load in the mutation list and strip 1.260 +// that mutation out of the list 1.261 +function assertFrameLoad(mutations) { 1.262 + return assertAndStrip(mutations, "Should have had a frame load change.", isFrameLoad); 1.263 +} 1.264 + 1.265 +// Make sure there's a childList change in the mutation list and strip 1.266 +// that mutation out of the list 1.267 +function assertChildList(mutations) { 1.268 + return assertAndStrip(mutations, "Should have had a frame load change.", isChildList); 1.269 +} 1.270 + 1.271 +// Load mutations aren't predictable, so keep accumulating mutations until 1.272 +// the one we're looking for shows up. 1.273 +function waitForMutation(walker, test, mutations=[]) { 1.274 + let deferred = promise.defer(); 1.275 + for (let change of mutations) { 1.276 + if (test(change)) { 1.277 + deferred.resolve(mutations); 1.278 + } 1.279 + } 1.280 + 1.281 + walker.once("mutations", newMutations => { 1.282 + waitForMutation(walker, test, mutations.concat(newMutations)).then(finalMutations => { 1.283 + deferred.resolve(finalMutations); 1.284 + }) 1.285 + }); 1.286 + 1.287 + return deferred.promise; 1.288 +} 1.289 + 1.290 + 1.291 +var _tests = []; 1.292 +function addTest(test) { 1.293 + _tests.push(test); 1.294 +} 1.295 + 1.296 +function runNextTest() { 1.297 + if (_tests.length == 0) { 1.298 + SimpleTest.finish() 1.299 + return; 1.300 + } 1.301 + var fn = _tests.shift(); 1.302 + try { 1.303 + fn(); 1.304 + } catch (ex) { 1.305 + info("Test function " + (fn.name ? "'" + fn.name + "' " : "") + 1.306 + "threw an exception: " + ex); 1.307 + } 1.308 +}