|
1 /* -*- tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ |
|
2 /* vim: set ft=javascript ts=2 et sw=2 tw=80: */ |
|
3 /* This Source Code Form is subject to the terms of the Mozilla Public |
|
4 * License, v. 2.0. If a copy of the MPL was not distributed with this |
|
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
|
6 |
|
7 "use strict"; |
|
8 |
|
9 let devtools_ = Cu.import("resource://gre/modules/devtools/Loader.jsm", {}).devtools; |
|
10 let { createExtraActors, appendExtraActors } = devtools_.require("devtools/server/actors/common"); |
|
11 |
|
12 /* Root actor for the remote debugging protocol. */ |
|
13 |
|
14 /** |
|
15 * Create a remote debugging protocol root actor. |
|
16 * |
|
17 * @param aConnection |
|
18 * The DebuggerServerConnection whose root actor we are constructing. |
|
19 * |
|
20 * @param aParameters |
|
21 * The properties of |aParameters| provide backing objects for the root |
|
22 * actor's requests; if a given property is omitted from |aParameters|, the |
|
23 * root actor won't implement the corresponding requests or notifications. |
|
24 * Supported properties: |
|
25 * |
|
26 * - tabList: a live list (see below) of tab actors. If present, the |
|
27 * new root actor supports the 'listTabs' request, providing the live |
|
28 * list's elements as its tab actors, and sending 'tabListChanged' |
|
29 * notifications when the live list's contents change. One actor in |
|
30 * this list must have a true '.selected' property. |
|
31 * |
|
32 * - addonList: a live list (see below) of addon actors. If present, the |
|
33 * new root actor supports the 'listAddons' request, providing the live |
|
34 * list's elements as its addon actors, and sending 'addonListchanged' |
|
35 * notifications when the live list's contents change. |
|
36 * |
|
37 * - globalActorFactories: an object |A| describing further actors to |
|
38 * attach to the 'listTabs' reply. This is the type accumulated by |
|
39 * DebuggerServer.addGlobalActor. For each own property |P| of |A|, |
|
40 * the root actor adds a property named |P| to the 'listTabs' |
|
41 * reply whose value is the name of an actor constructed by |
|
42 * |A[P]|. |
|
43 * |
|
44 * - onShutdown: a function to call when the root actor is disconnected. |
|
45 * |
|
46 * Instance properties: |
|
47 * |
|
48 * - applicationType: the string the root actor will include as the |
|
49 * "applicationType" property in the greeting packet. By default, this |
|
50 * is "browser". |
|
51 * |
|
52 * Live lists: |
|
53 * |
|
54 * A "live list", as used for the |tabList|, is an object that presents a |
|
55 * list of actors, and also notifies its clients of changes to the list. A |
|
56 * live list's interface is two properties: |
|
57 * |
|
58 * - getList: a method that returns a promise to the contents of the list. |
|
59 * |
|
60 * - onListChanged: a handler called, with no arguments, when the set of |
|
61 * values the iterator would produce has changed since the last |
|
62 * time 'iterator' was called. This may only be set to null or a |
|
63 * callable value (one for which the typeof operator returns |
|
64 * 'function'). (Note that the live list will not call the |
|
65 * onListChanged handler until the list has been iterated over |
|
66 * once; if nobody's seen the list in the first place, nobody |
|
67 * should care if its contents have changed!) |
|
68 * |
|
69 * When the list changes, the list implementation should ensure that any |
|
70 * actors yielded in previous iterations whose referents (tabs) still exist |
|
71 * get yielded again in subsequent iterations. If the underlying referent |
|
72 * is the same, the same actor should be presented for it. |
|
73 * |
|
74 * The root actor registers an 'onListChanged' handler on the appropriate |
|
75 * list when it may need to send the client 'tabListChanged' notifications, |
|
76 * and is careful to remove the handler whenever it does not need to send |
|
77 * such notifications (including when it is disconnected). This means that |
|
78 * live list implementations can use the state of the handler property (set |
|
79 * or null) to install and remove observers and event listeners. |
|
80 * |
|
81 * Note that, as the only way for the root actor to see the members of the |
|
82 * live list is to begin an iteration over the list, the live list need not |
|
83 * actually produce any actors until they are reached in the course of |
|
84 * iteration: alliterative lazy live lists. |
|
85 */ |
|
86 function RootActor(aConnection, aParameters) { |
|
87 this.conn = aConnection; |
|
88 this._parameters = aParameters; |
|
89 this._onTabListChanged = this.onTabListChanged.bind(this); |
|
90 this._onAddonListChanged = this.onAddonListChanged.bind(this); |
|
91 this._extraActors = {}; |
|
92 } |
|
93 |
|
94 RootActor.prototype = { |
|
95 constructor: RootActor, |
|
96 applicationType: "browser", |
|
97 |
|
98 /** |
|
99 * Return a 'hello' packet as specified by the Remote Debugging Protocol. |
|
100 */ |
|
101 sayHello: function() { |
|
102 return { |
|
103 from: this.actorID, |
|
104 applicationType: this.applicationType, |
|
105 /* This is not in the spec, but it's used by tests. */ |
|
106 testConnectionPrefix: this.conn.prefix, |
|
107 traits: { |
|
108 sources: true, |
|
109 editOuterHTML: true, |
|
110 // Wether the server-side highlighter actor exists and can be used to |
|
111 // remotely highlight nodes (see server/actors/highlighter.js) |
|
112 highlightable: true, |
|
113 // Wether the inspector actor implements the getImageDataFromURL |
|
114 // method that returns data-uris for image URLs. This is used for image |
|
115 // tooltips for instance |
|
116 urlToImageDataResolver: true, |
|
117 networkMonitor: true, |
|
118 // Wether the storage inspector actor to inspect cookies, etc. |
|
119 storageInspector: true, |
|
120 // Wether storage inspector is read only |
|
121 storageInspectorReadOnly: true, |
|
122 // Wether conditional breakpoints are supported |
|
123 conditionalBreakpoints: true |
|
124 } |
|
125 }; |
|
126 }, |
|
127 |
|
128 /** |
|
129 * This is true for the root actor only, used by some child actors |
|
130 */ |
|
131 get isRootActor() true, |
|
132 |
|
133 /** |
|
134 * The (chrome) window, for use by child actors |
|
135 */ |
|
136 get window() Services.wm.getMostRecentWindow(DebuggerServer.chromeWindowType), |
|
137 |
|
138 /** |
|
139 * URL of the chrome window. |
|
140 */ |
|
141 get url() { return this.window ? this.window.document.location.href : null; }, |
|
142 |
|
143 /** |
|
144 * Getter for the best nsIWebProgress for to watching this window. |
|
145 */ |
|
146 get webProgress() { |
|
147 return this.window |
|
148 .QueryInterface(Ci.nsIInterfaceRequestor) |
|
149 .getInterface(Ci.nsIDocShell) |
|
150 .QueryInterface(Ci.nsIInterfaceRequestor) |
|
151 .getInterface(Ci.nsIWebProgress); |
|
152 }, |
|
153 |
|
154 /** |
|
155 * Disconnects the actor from the browser window. |
|
156 */ |
|
157 disconnect: function() { |
|
158 /* Tell the live lists we aren't watching any more. */ |
|
159 if (this._parameters.tabList) { |
|
160 this._parameters.tabList.onListChanged = null; |
|
161 } |
|
162 if (this._parameters.addonList) { |
|
163 this._parameters.addonList.onListChanged = null; |
|
164 } |
|
165 if (typeof this._parameters.onShutdown === 'function') { |
|
166 this._parameters.onShutdown(); |
|
167 } |
|
168 this._extraActors = null; |
|
169 }, |
|
170 |
|
171 /* The 'listTabs' request and the 'tabListChanged' notification. */ |
|
172 |
|
173 /** |
|
174 * Handles the listTabs request. The actors will survive until at least |
|
175 * the next listTabs request. |
|
176 */ |
|
177 onListTabs: function() { |
|
178 let tabList = this._parameters.tabList; |
|
179 if (!tabList) { |
|
180 return { from: this.actorID, error: "noTabs", |
|
181 message: "This root actor has no browser tabs." }; |
|
182 } |
|
183 |
|
184 /* |
|
185 * Walk the tab list, accumulating the array of tab actors for the |
|
186 * reply, and moving all the actors to a new ActorPool. We'll |
|
187 * replace the old tab actor pool with the one we build here, thus |
|
188 * retiring any actors that didn't get listed again, and preparing any |
|
189 * new actors to receive packets. |
|
190 */ |
|
191 let newActorPool = new ActorPool(this.conn); |
|
192 let tabActorList = []; |
|
193 let selected; |
|
194 return tabList.getList().then((tabActors) => { |
|
195 for (let tabActor of tabActors) { |
|
196 if (tabActor.selected) { |
|
197 selected = tabActorList.length; |
|
198 } |
|
199 tabActor.parentID = this.actorID; |
|
200 newActorPool.addActor(tabActor); |
|
201 tabActorList.push(tabActor); |
|
202 } |
|
203 |
|
204 /* DebuggerServer.addGlobalActor support: create actors. */ |
|
205 if (!this._globalActorPool) { |
|
206 this._globalActorPool = new ActorPool(this.conn); |
|
207 this._createExtraActors(this._parameters.globalActorFactories, this._globalActorPool); |
|
208 this.conn.addActorPool(this._globalActorPool); |
|
209 } |
|
210 |
|
211 /* |
|
212 * Drop the old actorID -> actor map. Actors that still mattered were |
|
213 * added to the new map; others will go away. |
|
214 */ |
|
215 if (this._tabActorPool) { |
|
216 this.conn.removeActorPool(this._tabActorPool); |
|
217 } |
|
218 this._tabActorPool = newActorPool; |
|
219 this.conn.addActorPool(this._tabActorPool); |
|
220 |
|
221 let reply = { |
|
222 "from": this.actorID, |
|
223 "selected": selected || 0, |
|
224 "tabs": [actor.form() for (actor of tabActorList)], |
|
225 }; |
|
226 |
|
227 /* If a root window is accessible, include its URL. */ |
|
228 if (this.url) { |
|
229 reply.url = this.url; |
|
230 } |
|
231 |
|
232 /* DebuggerServer.addGlobalActor support: name actors in 'listTabs' reply. */ |
|
233 this._appendExtraActors(reply); |
|
234 |
|
235 /* |
|
236 * Now that we're actually going to report the contents of tabList to |
|
237 * the client, we're responsible for letting the client know if it |
|
238 * changes. |
|
239 */ |
|
240 tabList.onListChanged = this._onTabListChanged; |
|
241 |
|
242 return reply; |
|
243 }); |
|
244 }, |
|
245 |
|
246 onTabListChanged: function () { |
|
247 this.conn.send({ from: this.actorID, type:"tabListChanged" }); |
|
248 /* It's a one-shot notification; no need to watch any more. */ |
|
249 this._parameters.tabList.onListChanged = null; |
|
250 }, |
|
251 |
|
252 onListAddons: function () { |
|
253 let addonList = this._parameters.addonList; |
|
254 if (!addonList) { |
|
255 return { from: this.actorID, error: "noAddons", |
|
256 message: "This root actor has no browser addons." }; |
|
257 } |
|
258 |
|
259 return addonList.getList().then((addonActors) => { |
|
260 let addonActorPool = new ActorPool(this.conn); |
|
261 for (let addonActor of addonActors) { |
|
262 addonActorPool.addActor(addonActor); |
|
263 } |
|
264 |
|
265 if (this._addonActorPool) { |
|
266 this.conn.removeActorPool(this._addonActorPool); |
|
267 } |
|
268 this._addonActorPool = addonActorPool; |
|
269 this.conn.addActorPool(this._addonActorPool); |
|
270 |
|
271 addonList.onListChanged = this._onAddonListChanged; |
|
272 |
|
273 return { |
|
274 "from": this.actorID, |
|
275 "addons": [addonActor.form() for (addonActor of addonActors)] |
|
276 }; |
|
277 }); |
|
278 }, |
|
279 |
|
280 onAddonListChanged: function () { |
|
281 this.conn.send({ from: this.actorID, type: "addonListChanged" }); |
|
282 this._parameters.addonList.onListChanged = null; |
|
283 }, |
|
284 |
|
285 /* This is not in the spec, but it's used by tests. */ |
|
286 onEcho: function (aRequest) { |
|
287 /* |
|
288 * Request packets are frozen. Copy aRequest, so that |
|
289 * DebuggerServerConnection.onPacket can attach a 'from' property. |
|
290 */ |
|
291 return JSON.parse(JSON.stringify(aRequest)); |
|
292 }, |
|
293 |
|
294 onProtocolDescription: function (aRequest) { |
|
295 return protocol.dumpProtocolSpec() |
|
296 }, |
|
297 |
|
298 /* Support for DebuggerServer.addGlobalActor. */ |
|
299 _createExtraActors: createExtraActors, |
|
300 _appendExtraActors: appendExtraActors, |
|
301 |
|
302 /* ThreadActor hooks. */ |
|
303 |
|
304 /** |
|
305 * Prepare to enter a nested event loop by disabling debuggee events. |
|
306 */ |
|
307 preNest: function() { |
|
308 // Disable events in all open windows. |
|
309 let e = Services.wm.getEnumerator(null); |
|
310 while (e.hasMoreElements()) { |
|
311 let win = e.getNext(); |
|
312 let windowUtils = win.QueryInterface(Ci.nsIInterfaceRequestor) |
|
313 .getInterface(Ci.nsIDOMWindowUtils); |
|
314 windowUtils.suppressEventHandling(true); |
|
315 windowUtils.suspendTimeouts(); |
|
316 } |
|
317 }, |
|
318 |
|
319 /** |
|
320 * Prepare to exit a nested event loop by enabling debuggee events. |
|
321 */ |
|
322 postNest: function(aNestData) { |
|
323 // Enable events in all open windows. |
|
324 let e = Services.wm.getEnumerator(null); |
|
325 while (e.hasMoreElements()) { |
|
326 let win = e.getNext(); |
|
327 let windowUtils = win.QueryInterface(Ci.nsIInterfaceRequestor) |
|
328 .getInterface(Ci.nsIDOMWindowUtils); |
|
329 windowUtils.resumeTimeouts(); |
|
330 windowUtils.suppressEventHandling(false); |
|
331 } |
|
332 } |
|
333 }; |
|
334 |
|
335 RootActor.prototype.requestTypes = { |
|
336 "listTabs": RootActor.prototype.onListTabs, |
|
337 "listAddons": RootActor.prototype.onListAddons, |
|
338 "echo": RootActor.prototype.onEcho, |
|
339 "protocolDescription": RootActor.prototype.onProtocolDescription |
|
340 }; |