|
1 var Cu = Components.utils; |
|
2 |
|
3 Cu.import("resource://gre/modules/devtools/Loader.jsm"); |
|
4 Cu.import("resource://gre/modules/devtools/dbg-client.jsm"); |
|
5 Cu.import("resource://gre/modules/devtools/dbg-server.jsm"); |
|
6 |
|
7 const Services = devtools.require("Services"); |
|
8 const {_documentWalker} = devtools.require("devtools/server/actors/inspector"); |
|
9 |
|
10 // Always log packets when running tests. |
|
11 Services.prefs.setBoolPref("devtools.debugger.log", true); |
|
12 SimpleTest.registerCleanupFunction(function() { |
|
13 Services.prefs.clearUserPref("devtools.debugger.log"); |
|
14 }); |
|
15 |
|
16 |
|
17 if (!DebuggerServer.initialized) { |
|
18 DebuggerServer.init(() => true); |
|
19 DebuggerServer.addBrowserActors(); |
|
20 SimpleTest.registerCleanupFunction(function() { |
|
21 DebuggerServer.destroy(); |
|
22 }); |
|
23 } |
|
24 |
|
25 var gAttachCleanups = []; |
|
26 |
|
27 SimpleTest.registerCleanupFunction(function() { |
|
28 for (let cleanup of gAttachCleanups) { |
|
29 cleanup(); |
|
30 } |
|
31 }); |
|
32 |
|
33 /** |
|
34 * Open a tab, load the url, wait for it to signal its readiness, |
|
35 * find the tab with the debugger server, and call the callback. |
|
36 * |
|
37 * Returns a function which can be called to close the opened ta |
|
38 * and disconnect its debugger client. |
|
39 */ |
|
40 function attachURL(url, callback) { |
|
41 var win = window.open(url, "_blank"); |
|
42 var client = null; |
|
43 |
|
44 let cleanup = () => { |
|
45 if (client) { |
|
46 client.close(); |
|
47 client = null; |
|
48 } |
|
49 if (win) { |
|
50 win.close(); |
|
51 win = null; |
|
52 } |
|
53 }; |
|
54 gAttachCleanups.push(cleanup); |
|
55 |
|
56 window.addEventListener("message", function loadListener(event) { |
|
57 if (event.data === "ready") { |
|
58 client = new DebuggerClient(DebuggerServer.connectPipe()); |
|
59 client.connect((applicationType, traits) => { |
|
60 client.listTabs(response => { |
|
61 for (let tab of response.tabs) { |
|
62 if (tab.url === url) { |
|
63 window.removeEventListener("message", loadListener, false); |
|
64 client.attachTab(tab.actor, function(aResponse, aTabClient) { |
|
65 try { |
|
66 callback(null, client, tab, win.document); |
|
67 } catch(ex) { |
|
68 Cu.reportError(ex); |
|
69 dump(ex); |
|
70 } |
|
71 }); |
|
72 break; |
|
73 } |
|
74 } |
|
75 }); |
|
76 }); |
|
77 } |
|
78 }, false); |
|
79 |
|
80 return cleanup; |
|
81 } |
|
82 |
|
83 function promiseOnce(target, event) { |
|
84 let deferred = promise.defer(); |
|
85 target.on(event, (...args) => { |
|
86 if (args.length === 1) { |
|
87 deferred.resolve(args[0]); |
|
88 } else { |
|
89 deferred.resolve(args); |
|
90 } |
|
91 }); |
|
92 return deferred.promise; |
|
93 } |
|
94 |
|
95 function sortOwnershipChildren(children) { |
|
96 return children.sort((a, b) => a.name.localeCompare(b.name)); |
|
97 } |
|
98 |
|
99 function serverOwnershipSubtree(walker, node) { |
|
100 let actor = walker._refMap.get(node); |
|
101 if (!actor) { |
|
102 return undefined; |
|
103 } |
|
104 |
|
105 let children = []; |
|
106 let docwalker = _documentWalker(node, window); |
|
107 let child = docwalker.firstChild(); |
|
108 while (child) { |
|
109 let item = serverOwnershipSubtree(walker, child); |
|
110 if (item) { |
|
111 children.push(item); |
|
112 } |
|
113 child = docwalker.nextSibling(); |
|
114 } |
|
115 return { |
|
116 name: actor.actorID, |
|
117 children: sortOwnershipChildren(children) |
|
118 } |
|
119 } |
|
120 |
|
121 function serverOwnershipTree(walker) { |
|
122 let serverConnection = walker.conn._transport._serverConnection; |
|
123 let serverWalker = serverConnection.getActor(walker.actorID); |
|
124 |
|
125 return { |
|
126 root: serverOwnershipSubtree(serverWalker, serverWalker.rootDoc ), |
|
127 orphaned: [serverOwnershipSubtree(serverWalker, o.rawNode) for (o of serverWalker._orphaned)], |
|
128 retained: [serverOwnershipSubtree(serverWalker, o.rawNode) for (o of serverWalker._retainedOrphans)] |
|
129 }; |
|
130 } |
|
131 |
|
132 function clientOwnershipSubtree(node) { |
|
133 return { |
|
134 name: node.actorID, |
|
135 children: sortOwnershipChildren([clientOwnershipSubtree(child) for (child of node.treeChildren())]) |
|
136 } |
|
137 } |
|
138 |
|
139 function clientOwnershipTree(walker) { |
|
140 return { |
|
141 root: clientOwnershipSubtree(walker.rootNode), |
|
142 orphaned: [clientOwnershipSubtree(o) for (o of walker._orphaned)], |
|
143 retained: [clientOwnershipSubtree(o) for (o of walker._retainedOrphans)] |
|
144 } |
|
145 } |
|
146 |
|
147 function ownershipTreeSize(tree) { |
|
148 let size = 1; |
|
149 for (let child of tree.children) { |
|
150 size += ownershipTreeSize(child); |
|
151 } |
|
152 return size; |
|
153 } |
|
154 |
|
155 function assertOwnershipTrees(walker) { |
|
156 let serverTree = serverOwnershipTree(walker); |
|
157 let clientTree = clientOwnershipTree(walker); |
|
158 is(JSON.stringify(clientTree, null, ' '), JSON.stringify(serverTree, null, ' '), "Server and client ownership trees should match."); |
|
159 |
|
160 return ownershipTreeSize(clientTree.root); |
|
161 } |
|
162 |
|
163 // Verify that an actorID is inaccessible both from the client library and the server. |
|
164 function checkMissing(client, actorID) { |
|
165 let deferred = promise.defer(); |
|
166 let front = client.getActor(actorID); |
|
167 ok(!front, "Front shouldn't be accessible from the client for actorID: " + actorID); |
|
168 |
|
169 let deferred = promise.defer(); |
|
170 client.request({ |
|
171 to: actorID, |
|
172 type: "request", |
|
173 }, response => { |
|
174 is(response.error, "noSuchActor", "node list actor should no longer be contactable."); |
|
175 deferred.resolve(undefined); |
|
176 }); |
|
177 return deferred.promise; |
|
178 } |
|
179 |
|
180 // Verify that an actorID is accessible both from the client library and the server. |
|
181 function checkAvailable(client, actorID) { |
|
182 let deferred = promise.defer(); |
|
183 let front = client.getActor(actorID); |
|
184 ok(front, "Front should be accessible from the client for actorID: " + actorID); |
|
185 |
|
186 let deferred = promise.defer(); |
|
187 client.request({ |
|
188 to: actorID, |
|
189 type: "garbageAvailableTest", |
|
190 }, response => { |
|
191 is(response.error, "unrecognizedPacketType", "node list actor should be contactable."); |
|
192 deferred.resolve(undefined); |
|
193 }); |
|
194 return deferred.promise; |
|
195 } |
|
196 |
|
197 function promiseDone(promise) { |
|
198 promise.then(null, err => { |
|
199 ok(false, "Promise failed: " + err); |
|
200 if (err.stack) { |
|
201 dump(err.stack); |
|
202 } |
|
203 SimpleTest.finish(); |
|
204 }); |
|
205 } |
|
206 |
|
207 // Mutation list testing |
|
208 |
|
209 function isSrcChange(change) { |
|
210 return (change.type === "attributes" && change.attributeName === "src"); |
|
211 } |
|
212 |
|
213 function assertAndStrip(mutations, message, test) { |
|
214 let size = mutations.length; |
|
215 mutations = mutations.filter(test); |
|
216 ok((mutations.size != size), message); |
|
217 return mutations; |
|
218 } |
|
219 |
|
220 function isSrcChange(change) { |
|
221 return change.type === "attributes" && change.attributeName === "src"; |
|
222 } |
|
223 |
|
224 function isUnload(change) { |
|
225 return change.type === "documentUnload"; |
|
226 } |
|
227 |
|
228 function isFrameLoad(change) { |
|
229 return change.type === "frameLoad"; |
|
230 } |
|
231 |
|
232 function isUnretained(change) { |
|
233 return change.type === "unretained"; |
|
234 } |
|
235 |
|
236 function isChildList(change) { |
|
237 return change.type === "childList"; |
|
238 } |
|
239 |
|
240 function isNewRoot(change) { |
|
241 return change.type === "newRoot"; |
|
242 } |
|
243 |
|
244 // Make sure an iframe's src attribute changed and then |
|
245 // strip that mutation out of the list. |
|
246 function assertSrcChange(mutations) { |
|
247 return assertAndStrip(mutations, "Should have had an iframe source change.", isSrcChange); |
|
248 } |
|
249 |
|
250 // Make sure there's an unload in the mutation list and strip |
|
251 // that mutation out of the list |
|
252 function assertUnload(mutations) { |
|
253 return assertAndStrip(mutations, "Should have had a document unload change.", isUnload); |
|
254 } |
|
255 |
|
256 // Make sure there's a frame load in the mutation list and strip |
|
257 // that mutation out of the list |
|
258 function assertFrameLoad(mutations) { |
|
259 return assertAndStrip(mutations, "Should have had a frame load change.", isFrameLoad); |
|
260 } |
|
261 |
|
262 // Make sure there's a childList change in the mutation list and strip |
|
263 // that mutation out of the list |
|
264 function assertChildList(mutations) { |
|
265 return assertAndStrip(mutations, "Should have had a frame load change.", isChildList); |
|
266 } |
|
267 |
|
268 // Load mutations aren't predictable, so keep accumulating mutations until |
|
269 // the one we're looking for shows up. |
|
270 function waitForMutation(walker, test, mutations=[]) { |
|
271 let deferred = promise.defer(); |
|
272 for (let change of mutations) { |
|
273 if (test(change)) { |
|
274 deferred.resolve(mutations); |
|
275 } |
|
276 } |
|
277 |
|
278 walker.once("mutations", newMutations => { |
|
279 waitForMutation(walker, test, mutations.concat(newMutations)).then(finalMutations => { |
|
280 deferred.resolve(finalMutations); |
|
281 }) |
|
282 }); |
|
283 |
|
284 return deferred.promise; |
|
285 } |
|
286 |
|
287 |
|
288 var _tests = []; |
|
289 function addTest(test) { |
|
290 _tests.push(test); |
|
291 } |
|
292 |
|
293 function runNextTest() { |
|
294 if (_tests.length == 0) { |
|
295 SimpleTest.finish() |
|
296 return; |
|
297 } |
|
298 var fn = _tests.shift(); |
|
299 try { |
|
300 fn(); |
|
301 } catch (ex) { |
|
302 info("Test function " + (fn.name ? "'" + fn.name + "' " : "") + |
|
303 "threw an exception: " + ex); |
|
304 } |
|
305 } |