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