|
1 /* -*- Mode: javascript; 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 * Toolkit glue for the remote debugging protocol, loaded into the |
|
10 * debugging global. |
|
11 */ |
|
12 let { Ci, Cc, CC, Cu, Cr } = require("chrome"); |
|
13 let Debugger = require("Debugger"); |
|
14 let Services = require("Services"); |
|
15 let { ActorPool } = require("devtools/server/actors/common"); |
|
16 let { DebuggerTransport, LocalDebuggerTransport, ChildDebuggerTransport } = require("devtools/server/transport"); |
|
17 let DevToolsUtils = require("devtools/toolkit/DevToolsUtils"); |
|
18 let { dumpn, dbg_assert } = DevToolsUtils; |
|
19 let Services = require("Services"); |
|
20 let EventEmitter = require("devtools/toolkit/event-emitter"); |
|
21 |
|
22 // Until all Debugger server code is converted to SDK modules, |
|
23 // imports Components.* alias from chrome module. |
|
24 var { Ci, Cc, CC, Cu, Cr } = require("chrome"); |
|
25 // On B2G, `this` != Global scope, so `Ci` won't be binded on `this` |
|
26 // (i.e. this.Ci is undefined) Then later, when using loadSubScript, |
|
27 // Ci,... won't be defined for sub scripts. |
|
28 this.Ci = Ci; |
|
29 this.Cc = Cc; |
|
30 this.CC = CC; |
|
31 this.Cu = Cu; |
|
32 this.Cr = Cr; |
|
33 this.Debugger = Debugger; |
|
34 this.Services = Services; |
|
35 this.ActorPool = ActorPool; |
|
36 this.DevToolsUtils = DevToolsUtils; |
|
37 this.dumpn = dumpn; |
|
38 this.dbg_assert = dbg_assert; |
|
39 |
|
40 // Overload `Components` to prevent SDK loader exception on Components |
|
41 // object usage |
|
42 Object.defineProperty(this, "Components", { |
|
43 get: function () require("chrome").components |
|
44 }); |
|
45 |
|
46 const DBG_STRINGS_URI = "chrome://global/locale/devtools/debugger.properties"; |
|
47 |
|
48 const nsFile = CC("@mozilla.org/file/local;1", "nsIFile", "initWithPath"); |
|
49 Cu.import("resource://gre/modules/reflect.jsm"); |
|
50 Cu.import("resource://gre/modules/XPCOMUtils.jsm"); |
|
51 Cu.import("resource://gre/modules/NetUtil.jsm"); |
|
52 dumpn.wantLogging = Services.prefs.getBoolPref("devtools.debugger.log"); |
|
53 |
|
54 Cu.import("resource://gre/modules/devtools/deprecated-sync-thenables.js"); |
|
55 |
|
56 function loadSubScript(aURL) |
|
57 { |
|
58 try { |
|
59 let loader = Cc["@mozilla.org/moz/jssubscript-loader;1"] |
|
60 .getService(Ci.mozIJSSubScriptLoader); |
|
61 loader.loadSubScript(aURL, this); |
|
62 } catch(e) { |
|
63 let errorStr = "Error loading: " + aURL + ":\n" + |
|
64 (e.fileName ? "at " + e.fileName + " : " + e.lineNumber + "\n" : "") + |
|
65 e + " - " + e.stack + "\n"; |
|
66 dump(errorStr); |
|
67 Cu.reportError(errorStr); |
|
68 throw e; |
|
69 } |
|
70 } |
|
71 |
|
72 let events = require("sdk/event/core"); |
|
73 let {defer, resolve, reject, all} = require("devtools/toolkit/deprecated-sync-thenables"); |
|
74 this.defer = defer; |
|
75 this.resolve = resolve; |
|
76 this.reject = reject; |
|
77 this.all = all; |
|
78 |
|
79 Cu.import("resource://gre/modules/devtools/SourceMap.jsm"); |
|
80 |
|
81 XPCOMUtils.defineLazyModuleGetter(this, "console", |
|
82 "resource://gre/modules/devtools/Console.jsm"); |
|
83 |
|
84 XPCOMUtils.defineLazyGetter(this, "NetworkMonitorManager", () => { |
|
85 return require("devtools/toolkit/webconsole/network-monitor").NetworkMonitorManager; |
|
86 }); |
|
87 |
|
88 // XPCOM constructors |
|
89 const ServerSocket = CC("@mozilla.org/network/server-socket;1", |
|
90 "nsIServerSocket", |
|
91 "initSpecialConnection"); |
|
92 const UnixDomainServerSocket = CC("@mozilla.org/network/server-socket;1", |
|
93 "nsIServerSocket", |
|
94 "initWithFilename"); |
|
95 |
|
96 var gRegisteredModules = Object.create(null); |
|
97 |
|
98 /** |
|
99 * The ModuleAPI object is passed to modules loaded using the |
|
100 * DebuggerServer.registerModule() API. Modules can use this |
|
101 * object to register actor factories. |
|
102 * Factories registered through the module API will be removed |
|
103 * when the module is unregistered or when the server is |
|
104 * destroyed. |
|
105 */ |
|
106 function ModuleAPI() { |
|
107 let activeTabActors = new Set(); |
|
108 let activeGlobalActors = new Set(); |
|
109 |
|
110 return { |
|
111 // See DebuggerServer.addGlobalActor for a description. |
|
112 addGlobalActor: function(factory, name) { |
|
113 DebuggerServer.addGlobalActor(factory, name); |
|
114 activeGlobalActors.add(factory); |
|
115 }, |
|
116 // See DebuggerServer.removeGlobalActor for a description. |
|
117 removeGlobalActor: function(factory) { |
|
118 DebuggerServer.removeGlobalActor(factory); |
|
119 activeGlobalActors.delete(factory); |
|
120 }, |
|
121 |
|
122 // See DebuggerServer.addTabActor for a description. |
|
123 addTabActor: function(factory, name) { |
|
124 DebuggerServer.addTabActor(factory, name); |
|
125 activeTabActors.add(factory); |
|
126 }, |
|
127 // See DebuggerServer.removeTabActor for a description. |
|
128 removeTabActor: function(factory) { |
|
129 DebuggerServer.removeTabActor(factory); |
|
130 activeTabActors.delete(factory); |
|
131 }, |
|
132 |
|
133 // Destroy the module API object, unregistering any |
|
134 // factories registered by the module. |
|
135 destroy: function() { |
|
136 for (let factory of activeTabActors) { |
|
137 DebuggerServer.removeTabActor(factory); |
|
138 } |
|
139 activeTabActors = null; |
|
140 for (let factory of activeGlobalActors) { |
|
141 DebuggerServer.removeGlobalActor(factory); |
|
142 } |
|
143 activeGlobalActors = null; |
|
144 } |
|
145 } |
|
146 }; |
|
147 |
|
148 /*** |
|
149 * Public API |
|
150 */ |
|
151 var DebuggerServer = { |
|
152 _listener: null, |
|
153 _initialized: false, |
|
154 _transportInitialized: false, |
|
155 xpcInspector: null, |
|
156 // Number of currently open TCP connections. |
|
157 _socketConnections: 0, |
|
158 // Map of global actor names to actor constructors provided by extensions. |
|
159 globalActorFactories: {}, |
|
160 // Map of tab actor names to actor constructors provided by extensions. |
|
161 tabActorFactories: {}, |
|
162 |
|
163 LONG_STRING_LENGTH: 10000, |
|
164 LONG_STRING_INITIAL_LENGTH: 1000, |
|
165 LONG_STRING_READ_LENGTH: 65 * 1024, |
|
166 |
|
167 /** |
|
168 * A handler function that prompts the user to accept or decline the incoming |
|
169 * connection. |
|
170 */ |
|
171 _allowConnection: null, |
|
172 |
|
173 /** |
|
174 * The windowtype of the chrome window to use for actors that use the global |
|
175 * window (i.e the global style editor). Set this to your main window type, |
|
176 * for example "navigator:browser". |
|
177 */ |
|
178 chromeWindowType: null, |
|
179 |
|
180 /** |
|
181 * Prompt the user to accept or decline the incoming connection. This is the |
|
182 * default implementation that products embedding the debugger server may |
|
183 * choose to override. |
|
184 * |
|
185 * @return true if the connection should be permitted, false otherwise |
|
186 */ |
|
187 _defaultAllowConnection: function DS__defaultAllowConnection() { |
|
188 let title = L10N.getStr("remoteIncomingPromptTitle"); |
|
189 let msg = L10N.getStr("remoteIncomingPromptMessage"); |
|
190 let disableButton = L10N.getStr("remoteIncomingPromptDisable"); |
|
191 let prompt = Services.prompt; |
|
192 let flags = prompt.BUTTON_POS_0 * prompt.BUTTON_TITLE_OK + |
|
193 prompt.BUTTON_POS_1 * prompt.BUTTON_TITLE_CANCEL + |
|
194 prompt.BUTTON_POS_2 * prompt.BUTTON_TITLE_IS_STRING + |
|
195 prompt.BUTTON_POS_1_DEFAULT; |
|
196 let result = prompt.confirmEx(null, title, msg, flags, null, null, |
|
197 disableButton, null, { value: false }); |
|
198 if (result == 0) { |
|
199 return true; |
|
200 } |
|
201 if (result == 2) { |
|
202 DebuggerServer.closeListener(true); |
|
203 Services.prefs.setBoolPref("devtools.debugger.remote-enabled", false); |
|
204 } |
|
205 return false; |
|
206 }, |
|
207 |
|
208 /** |
|
209 * Initialize the debugger server. |
|
210 * |
|
211 * @param function aAllowConnectionCallback |
|
212 * The embedder-provider callback, that decides whether an incoming |
|
213 * remote protocol conection should be allowed or refused. |
|
214 */ |
|
215 init: function DS_init(aAllowConnectionCallback) { |
|
216 if (this.initialized) { |
|
217 return; |
|
218 } |
|
219 |
|
220 this.xpcInspector = Cc["@mozilla.org/jsinspector;1"].getService(Ci.nsIJSInspector); |
|
221 this.initTransport(aAllowConnectionCallback); |
|
222 this.addActors("resource://gre/modules/devtools/server/actors/root.js"); |
|
223 |
|
224 this._initialized = true; |
|
225 }, |
|
226 |
|
227 protocol: require("devtools/server/protocol"), |
|
228 |
|
229 /** |
|
230 * Initialize the debugger server's transport variables. This can be |
|
231 * in place of init() for cases where the jsdebugger isn't needed. |
|
232 * |
|
233 * @param function aAllowConnectionCallback |
|
234 * The embedder-provider callback, that decides whether an incoming |
|
235 * remote protocol conection should be allowed or refused. |
|
236 */ |
|
237 initTransport: function DS_initTransport(aAllowConnectionCallback) { |
|
238 if (this._transportInitialized) { |
|
239 return; |
|
240 } |
|
241 |
|
242 this._connections = {}; |
|
243 this._nextConnID = 0; |
|
244 this._transportInitialized = true; |
|
245 this._allowConnection = aAllowConnectionCallback ? |
|
246 aAllowConnectionCallback : |
|
247 this._defaultAllowConnection; |
|
248 }, |
|
249 |
|
250 get initialized() this._initialized, |
|
251 |
|
252 /** |
|
253 * Performs cleanup tasks before shutting down the debugger server. Such tasks |
|
254 * include clearing any actor constructors added at runtime. This method |
|
255 * should be called whenever a debugger server is no longer useful, to avoid |
|
256 * memory leaks. After this method returns, the debugger server must be |
|
257 * initialized again before use. |
|
258 */ |
|
259 destroy: function DS_destroy() { |
|
260 if (!this._initialized) { |
|
261 return; |
|
262 } |
|
263 |
|
264 for (let connID of Object.getOwnPropertyNames(this._connections)) { |
|
265 this._connections[connID].close(); |
|
266 } |
|
267 |
|
268 for (let id of Object.getOwnPropertyNames(gRegisteredModules)) { |
|
269 let mod = gRegisteredModules[id]; |
|
270 mod.module.unregister(mod.api); |
|
271 } |
|
272 gRegisteredModules = {}; |
|
273 |
|
274 this.closeListener(); |
|
275 this.globalActorFactories = {}; |
|
276 this.tabActorFactories = {}; |
|
277 this._allowConnection = null; |
|
278 this._transportInitialized = false; |
|
279 this._initialized = false; |
|
280 |
|
281 dumpn("Debugger server is shut down."); |
|
282 }, |
|
283 |
|
284 /** |
|
285 * Load a subscript into the debugging global. |
|
286 * |
|
287 * @param aURL string A url that will be loaded as a subscript into the |
|
288 * debugging global. The user must load at least one script |
|
289 * that implements a createRootActor() function to create the |
|
290 * server's root actor. |
|
291 */ |
|
292 addActors: function DS_addActors(aURL) { |
|
293 loadSubScript.call(this, aURL); |
|
294 }, |
|
295 |
|
296 /** |
|
297 * Register a CommonJS module with the debugger server. |
|
298 * @param id string |
|
299 * The ID of a CommonJS module. This module must export |
|
300 * 'register' and 'unregister' functions. |
|
301 */ |
|
302 registerModule: function(id) { |
|
303 if (id in gRegisteredModules) { |
|
304 throw new Error("Tried to register a module twice: " + id + "\n"); |
|
305 } |
|
306 |
|
307 let moduleAPI = ModuleAPI(); |
|
308 let mod = require(id); |
|
309 mod.register(moduleAPI); |
|
310 gRegisteredModules[id] = { module: mod, api: moduleAPI }; |
|
311 }, |
|
312 |
|
313 /** |
|
314 * Returns true if a module id has been registered. |
|
315 */ |
|
316 isModuleRegistered: function(id) { |
|
317 return (id in gRegisteredModules); |
|
318 }, |
|
319 |
|
320 /** |
|
321 * Unregister a previously-loaded CommonJS module from the debugger server. |
|
322 */ |
|
323 unregisterModule: function(id) { |
|
324 let mod = gRegisteredModules[id]; |
|
325 if (!mod) { |
|
326 throw new Error("Tried to unregister a module that was not previously registered."); |
|
327 } |
|
328 mod.module.unregister(mod.api); |
|
329 mod.api.destroy(); |
|
330 delete gRegisteredModules[id]; |
|
331 }, |
|
332 |
|
333 /** |
|
334 * Install Firefox-specific actors. |
|
335 * |
|
336 * /!\ Be careful when adding a new actor, especially global actors. |
|
337 * Any new global actor will be exposed and returned by the root actor. |
|
338 * |
|
339 * That's the reason why tab actors aren't loaded on demand via |
|
340 * restrictPrivileges=true, to prevent exposing them on b2g parent process's |
|
341 * root actor. |
|
342 */ |
|
343 addBrowserActors: function(aWindowType = "navigator:browser", restrictPrivileges = false) { |
|
344 this.chromeWindowType = aWindowType; |
|
345 this.addActors("resource://gre/modules/devtools/server/actors/webbrowser.js"); |
|
346 |
|
347 if (!restrictPrivileges) { |
|
348 this.addTabActors(); |
|
349 this.addGlobalActor(this.ChromeDebuggerActor, "chromeDebugger"); |
|
350 this.registerModule("devtools/server/actors/preference"); |
|
351 } |
|
352 |
|
353 this.addActors("resource://gre/modules/devtools/server/actors/webapps.js"); |
|
354 this.registerModule("devtools/server/actors/device"); |
|
355 }, |
|
356 |
|
357 /** |
|
358 * Install tab actors in documents loaded in content childs |
|
359 */ |
|
360 addChildActors: function () { |
|
361 // In case of apps being loaded in parent process, DebuggerServer is already |
|
362 // initialized and browser actors are already loaded, |
|
363 // but childtab.js hasn't been loaded yet. |
|
364 if (!DebuggerServer.tabActorFactories.hasOwnProperty("consoleActor")) { |
|
365 this.addTabActors(); |
|
366 } |
|
367 // But webbrowser.js and childtab.js aren't loaded from shell.js. |
|
368 if (!("BrowserTabActor" in this)) { |
|
369 this.addActors("resource://gre/modules/devtools/server/actors/webbrowser.js"); |
|
370 } |
|
371 if (!("ContentActor" in this)) { |
|
372 this.addActors("resource://gre/modules/devtools/server/actors/childtab.js"); |
|
373 } |
|
374 }, |
|
375 |
|
376 /** |
|
377 * Install tab actors. |
|
378 */ |
|
379 addTabActors: function() { |
|
380 this.addActors("resource://gre/modules/devtools/server/actors/script.js"); |
|
381 this.registerModule("devtools/server/actors/webconsole"); |
|
382 this.registerModule("devtools/server/actors/inspector"); |
|
383 this.registerModule("devtools/server/actors/call-watcher"); |
|
384 this.registerModule("devtools/server/actors/canvas"); |
|
385 this.registerModule("devtools/server/actors/webgl"); |
|
386 this.registerModule("devtools/server/actors/webaudio"); |
|
387 this.registerModule("devtools/server/actors/stylesheets"); |
|
388 this.registerModule("devtools/server/actors/styleeditor"); |
|
389 this.registerModule("devtools/server/actors/storage"); |
|
390 this.registerModule("devtools/server/actors/gcli"); |
|
391 this.registerModule("devtools/server/actors/tracer"); |
|
392 this.registerModule("devtools/server/actors/memory"); |
|
393 this.registerModule("devtools/server/actors/eventlooplag"); |
|
394 if ("nsIProfiler" in Ci) { |
|
395 this.addActors("resource://gre/modules/devtools/server/actors/profiler.js"); |
|
396 } |
|
397 }, |
|
398 |
|
399 /** |
|
400 * Passes a set of options to the BrowserAddonActors for the given ID. |
|
401 * |
|
402 * @param aId string |
|
403 * The ID of the add-on to pass the options to |
|
404 * @param aOptions object |
|
405 * The options. |
|
406 * @return a promise that will be resolved when complete. |
|
407 */ |
|
408 setAddonOptions: function DS_setAddonOptions(aId, aOptions) { |
|
409 if (!this._initialized) { |
|
410 return; |
|
411 } |
|
412 |
|
413 let promises = []; |
|
414 |
|
415 // Pass to all connections |
|
416 for (let connID of Object.getOwnPropertyNames(this._connections)) { |
|
417 promises.push(this._connections[connID].setAddonOptions(aId, aOptions)); |
|
418 } |
|
419 |
|
420 return all(promises); |
|
421 }, |
|
422 |
|
423 /** |
|
424 * Listens on the given port or socket file for remote debugger connections. |
|
425 * |
|
426 * @param aPortOrPath int, string |
|
427 * If given an integer, the port to listen on. |
|
428 * Otherwise, the path to the unix socket domain file to listen on. |
|
429 */ |
|
430 openListener: function DS_openListener(aPortOrPath) { |
|
431 if (!Services.prefs.getBoolPref("devtools.debugger.remote-enabled")) { |
|
432 return false; |
|
433 } |
|
434 this._checkInit(); |
|
435 |
|
436 // Return early if the server is already listening. |
|
437 if (this._listener) { |
|
438 return true; |
|
439 } |
|
440 |
|
441 let flags = Ci.nsIServerSocket.KeepWhenOffline; |
|
442 // A preference setting can force binding on the loopback interface. |
|
443 if (Services.prefs.getBoolPref("devtools.debugger.force-local")) { |
|
444 flags |= Ci.nsIServerSocket.LoopbackOnly; |
|
445 } |
|
446 |
|
447 try { |
|
448 let backlog = 4; |
|
449 let socket; |
|
450 let port = Number(aPortOrPath); |
|
451 if (port) { |
|
452 socket = new ServerSocket(port, flags, backlog); |
|
453 } else { |
|
454 let file = nsFile(aPortOrPath); |
|
455 if (file.exists()) |
|
456 file.remove(false); |
|
457 socket = new UnixDomainServerSocket(file, parseInt("666", 8), backlog); |
|
458 } |
|
459 socket.asyncListen(this); |
|
460 this._listener = socket; |
|
461 } catch (e) { |
|
462 dumpn("Could not start debugging listener on '" + aPortOrPath + "': " + e); |
|
463 throw Cr.NS_ERROR_NOT_AVAILABLE; |
|
464 } |
|
465 this._socketConnections++; |
|
466 |
|
467 return true; |
|
468 }, |
|
469 |
|
470 /** |
|
471 * Close a previously-opened TCP listener. |
|
472 * |
|
473 * @param aForce boolean [optional] |
|
474 * If set to true, then the socket will be closed, regardless of the |
|
475 * number of open connections. |
|
476 */ |
|
477 closeListener: function DS_closeListener(aForce) { |
|
478 if (!this._listener || this._socketConnections == 0) { |
|
479 return false; |
|
480 } |
|
481 |
|
482 // Only close the listener when the last connection is closed, or if the |
|
483 // aForce flag is passed. |
|
484 if (--this._socketConnections == 0 || aForce) { |
|
485 this._listener.close(); |
|
486 this._listener = null; |
|
487 this._socketConnections = 0; |
|
488 } |
|
489 |
|
490 return true; |
|
491 }, |
|
492 |
|
493 /** |
|
494 * Creates a new connection to the local debugger speaking over a fake |
|
495 * transport. This connection results in straightforward calls to the onPacket |
|
496 * handlers of each side. |
|
497 * |
|
498 * @param aPrefix string [optional] |
|
499 * If given, all actors in this connection will have names starting |
|
500 * with |aPrefix + ':'|. |
|
501 * @returns a client-side DebuggerTransport for communicating with |
|
502 * the newly-created connection. |
|
503 */ |
|
504 connectPipe: function DS_connectPipe(aPrefix) { |
|
505 this._checkInit(); |
|
506 |
|
507 let serverTransport = new LocalDebuggerTransport; |
|
508 let clientTransport = new LocalDebuggerTransport(serverTransport); |
|
509 serverTransport.other = clientTransport; |
|
510 let connection = this._onConnection(serverTransport, aPrefix); |
|
511 |
|
512 // I'm putting this here because I trust you. |
|
513 // |
|
514 // There are times, when using a local connection, when you're going |
|
515 // to be tempted to just get direct access to the server. Resist that |
|
516 // temptation! If you succumb to that temptation, you will make the |
|
517 // fine developers that work on Fennec and Firefox OS sad. They're |
|
518 // professionals, they'll try to act like they understand, but deep |
|
519 // down you'll know that you hurt them. |
|
520 // |
|
521 // This reference allows you to give in to that temptation. There are |
|
522 // times this makes sense: tests, for example, and while porting a |
|
523 // previously local-only codebase to the remote protocol. |
|
524 // |
|
525 // But every time you use this, you will feel the shame of having |
|
526 // used a property that starts with a '_'. |
|
527 clientTransport._serverConnection = connection; |
|
528 |
|
529 return clientTransport; |
|
530 }, |
|
531 |
|
532 /** |
|
533 * In a content child process, create a new connection that exchanges |
|
534 * nsIMessageSender messages with our parent process. |
|
535 * |
|
536 * @param aPrefix |
|
537 * The prefix we should use in our nsIMessageSender message names and |
|
538 * actor names. This connection will use messages named |
|
539 * "debug:<prefix>:packet", and all its actors will have names |
|
540 * beginning with "<prefix>:". |
|
541 */ |
|
542 connectToParent: function(aPrefix, aMessageManager) { |
|
543 this._checkInit(); |
|
544 |
|
545 let transport = new ChildDebuggerTransport(aMessageManager, aPrefix); |
|
546 return this._onConnection(transport, aPrefix, true); |
|
547 }, |
|
548 |
|
549 /** |
|
550 * Connect to a child process. |
|
551 * |
|
552 * @param object aConnection |
|
553 * The debugger server connection to use. |
|
554 * @param nsIDOMElement aFrame |
|
555 * The browser element that holds the child process. |
|
556 * @param function [aOnDisconnect] |
|
557 * Optional function to invoke when the child is disconnected. |
|
558 * @return object |
|
559 * A promise object that is resolved once the connection is |
|
560 * established. |
|
561 */ |
|
562 connectToChild: function(aConnection, aFrame, aOnDisconnect) { |
|
563 let deferred = defer(); |
|
564 |
|
565 let mm = aFrame.QueryInterface(Ci.nsIFrameLoaderOwner).frameLoader |
|
566 .messageManager; |
|
567 mm.loadFrameScript("resource://gre/modules/devtools/server/child.js", false); |
|
568 |
|
569 let actor, childTransport; |
|
570 let prefix = aConnection.allocID("child"); |
|
571 let netMonitor = null; |
|
572 |
|
573 let onActorCreated = DevToolsUtils.makeInfallible(function (msg) { |
|
574 mm.removeMessageListener("debug:actor", onActorCreated); |
|
575 |
|
576 // Pipe Debugger message from/to parent/child via the message manager |
|
577 childTransport = new ChildDebuggerTransport(mm, prefix); |
|
578 childTransport.hooks = { |
|
579 onPacket: aConnection.send.bind(aConnection), |
|
580 onClosed: function () {} |
|
581 }; |
|
582 childTransport.ready(); |
|
583 |
|
584 aConnection.setForwarding(prefix, childTransport); |
|
585 |
|
586 dumpn("establishing forwarding for app with prefix " + prefix); |
|
587 |
|
588 actor = msg.json.actor; |
|
589 |
|
590 netMonitor = new NetworkMonitorManager(aFrame, actor.actor); |
|
591 |
|
592 deferred.resolve(actor); |
|
593 }).bind(this); |
|
594 mm.addMessageListener("debug:actor", onActorCreated); |
|
595 |
|
596 let onMessageManagerDisconnect = DevToolsUtils.makeInfallible(function (subject, topic, data) { |
|
597 if (subject == mm) { |
|
598 Services.obs.removeObserver(onMessageManagerDisconnect, topic); |
|
599 if (childTransport) { |
|
600 // If we have a child transport, the actor has already |
|
601 // been created. We need to stop using this message manager. |
|
602 childTransport.close(); |
|
603 childTransport = null; |
|
604 aConnection.cancelForwarding(prefix); |
|
605 } else { |
|
606 // Otherwise, the app has been closed before the actor |
|
607 // had a chance to be created, so we are not able to create |
|
608 // the actor. |
|
609 deferred.resolve(null); |
|
610 } |
|
611 if (actor) { |
|
612 // The ContentActor within the child process doesn't necessary |
|
613 // have to time to uninitialize itself when the app is closed/killed. |
|
614 // So ensure telling the client that the related actor is detached. |
|
615 aConnection.send({ from: actor.actor, type: "tabDetached" }); |
|
616 actor = null; |
|
617 } |
|
618 |
|
619 if (netMonitor) { |
|
620 netMonitor.destroy(); |
|
621 netMonitor = null; |
|
622 } |
|
623 |
|
624 if (aOnDisconnect) { |
|
625 aOnDisconnect(mm); |
|
626 } |
|
627 } |
|
628 }).bind(this); |
|
629 Services.obs.addObserver(onMessageManagerDisconnect, |
|
630 "message-manager-disconnect", false); |
|
631 |
|
632 events.once(aConnection, "closed", () => { |
|
633 if (childTransport) { |
|
634 // When the client disconnects, we have to unplug the dedicated |
|
635 // ChildDebuggerTransport... |
|
636 childTransport.close(); |
|
637 childTransport = null; |
|
638 aConnection.cancelForwarding(prefix); |
|
639 |
|
640 // ... and notify the child process to clean the tab actors. |
|
641 mm.sendAsyncMessage("debug:disconnect"); |
|
642 } |
|
643 Services.obs.removeObserver(onMessageManagerDisconnect, "message-manager-disconnect"); |
|
644 }); |
|
645 |
|
646 mm.sendAsyncMessage("debug:connect", { prefix: prefix }); |
|
647 |
|
648 return deferred.promise; |
|
649 }, |
|
650 |
|
651 // nsIServerSocketListener implementation |
|
652 |
|
653 onSocketAccepted: |
|
654 DevToolsUtils.makeInfallible(function DS_onSocketAccepted(aSocket, aTransport) { |
|
655 if (Services.prefs.getBoolPref("devtools.debugger.prompt-connection") && !this._allowConnection()) { |
|
656 return; |
|
657 } |
|
658 dumpn("New debugging connection on " + aTransport.host + ":" + aTransport.port); |
|
659 |
|
660 let input = aTransport.openInputStream(0, 0, 0); |
|
661 let output = aTransport.openOutputStream(0, 0, 0); |
|
662 let transport = new DebuggerTransport(input, output); |
|
663 DebuggerServer._onConnection(transport); |
|
664 }, "DebuggerServer.onSocketAccepted"), |
|
665 |
|
666 onStopListening: function DS_onStopListening(aSocket, status) { |
|
667 dumpn("onStopListening, status: " + status); |
|
668 }, |
|
669 |
|
670 /** |
|
671 * Raises an exception if the server has not been properly initialized. |
|
672 */ |
|
673 _checkInit: function DS_checkInit() { |
|
674 if (!this._transportInitialized) { |
|
675 throw "DebuggerServer has not been initialized."; |
|
676 } |
|
677 |
|
678 if (!this.createRootActor) { |
|
679 throw "Use DebuggerServer.addActors() to add a root actor implementation."; |
|
680 } |
|
681 }, |
|
682 |
|
683 /** |
|
684 * Create a new debugger connection for the given transport. Called after |
|
685 * connectPipe(), from connectToParent, or from an incoming socket |
|
686 * connection handler. |
|
687 * |
|
688 * If present, |aForwardingPrefix| is a forwarding prefix that a parent |
|
689 * server is using to recognizes messages intended for this server. Ensure |
|
690 * that all our actors have names beginning with |aForwardingPrefix + ':'|. |
|
691 * In particular, the root actor's name will be |aForwardingPrefix + ':root'|. |
|
692 */ |
|
693 _onConnection: function DS_onConnection(aTransport, aForwardingPrefix, aNoRootActor = false) { |
|
694 let connID; |
|
695 if (aForwardingPrefix) { |
|
696 connID = aForwardingPrefix + ":"; |
|
697 } else { |
|
698 connID = "conn" + this._nextConnID++ + '.'; |
|
699 } |
|
700 let conn = new DebuggerServerConnection(connID, aTransport); |
|
701 this._connections[connID] = conn; |
|
702 |
|
703 // Create a root actor for the connection and send the hello packet. |
|
704 if (!aNoRootActor) { |
|
705 conn.rootActor = this.createRootActor(conn); |
|
706 if (aForwardingPrefix) |
|
707 conn.rootActor.actorID = aForwardingPrefix + ":root"; |
|
708 else |
|
709 conn.rootActor.actorID = "root"; |
|
710 conn.addActor(conn.rootActor); |
|
711 aTransport.send(conn.rootActor.sayHello()); |
|
712 } |
|
713 aTransport.ready(); |
|
714 |
|
715 this.emit("connectionchange", "opened", conn); |
|
716 return conn; |
|
717 }, |
|
718 |
|
719 /** |
|
720 * Remove the connection from the debugging server. |
|
721 */ |
|
722 _connectionClosed: function DS_connectionClosed(aConnection) { |
|
723 delete this._connections[aConnection.prefix]; |
|
724 this.emit("connectionchange", "closed", aConnection); |
|
725 }, |
|
726 |
|
727 // DebuggerServer extension API. |
|
728 |
|
729 /** |
|
730 * Registers handlers for new tab-scoped request types defined dynamically. |
|
731 * This is used for example by add-ons to augment the functionality of the tab |
|
732 * actor. Note that the name or actorPrefix of the request type is not allowed |
|
733 * to clash with existing protocol packet properties, like 'title', 'url' or |
|
734 * 'actor', since that would break the protocol. |
|
735 * |
|
736 * @param aFunction function |
|
737 * The constructor function for this request type. This expects to be |
|
738 * called as a constructor (i.e. with 'new'), and passed two |
|
739 * arguments: the DebuggerServerConnection, and the BrowserTabActor |
|
740 * with which it will be associated. |
|
741 * |
|
742 * @param aName string [optional] |
|
743 * The name of the new request type. If this is not present, the |
|
744 * actorPrefix property of the constructor prototype is used. |
|
745 */ |
|
746 addTabActor: function DS_addTabActor(aFunction, aName) { |
|
747 let name = aName ? aName : aFunction.prototype.actorPrefix; |
|
748 if (["title", "url", "actor"].indexOf(name) != -1) { |
|
749 throw Error(name + " is not allowed"); |
|
750 } |
|
751 if (DebuggerServer.tabActorFactories.hasOwnProperty(name)) { |
|
752 throw Error(name + " already exists"); |
|
753 } |
|
754 DebuggerServer.tabActorFactories[name] = aFunction; |
|
755 }, |
|
756 |
|
757 /** |
|
758 * Unregisters the handler for the specified tab-scoped request type. |
|
759 * This may be used for example by add-ons when shutting down or upgrading. |
|
760 * |
|
761 * @param aFunction function |
|
762 * The constructor function for this request type. |
|
763 */ |
|
764 removeTabActor: function DS_removeTabActor(aFunction) { |
|
765 for (let name in DebuggerServer.tabActorFactories) { |
|
766 let handler = DebuggerServer.tabActorFactories[name]; |
|
767 if (handler.name == aFunction.name) { |
|
768 delete DebuggerServer.tabActorFactories[name]; |
|
769 } |
|
770 } |
|
771 }, |
|
772 |
|
773 /** |
|
774 * Registers handlers for new browser-scoped request types defined |
|
775 * dynamically. This is used for example by add-ons to augment the |
|
776 * functionality of the root actor. Note that the name or actorPrefix of the |
|
777 * request type is not allowed to clash with existing protocol packet |
|
778 * properties, like 'from', 'tabs' or 'selected', since that would break the |
|
779 * protocol. |
|
780 * |
|
781 * @param aFunction function |
|
782 * The constructor function for this request type. This expects to be |
|
783 * called as a constructor (i.e. with 'new'), and passed two |
|
784 * arguments: the DebuggerServerConnection, and the BrowserRootActor |
|
785 * with which it will be associated. |
|
786 * |
|
787 * @param aName string [optional] |
|
788 * The name of the new request type. If this is not present, the |
|
789 * actorPrefix property of the constructor prototype is used. |
|
790 */ |
|
791 addGlobalActor: function DS_addGlobalActor(aFunction, aName) { |
|
792 let name = aName ? aName : aFunction.prototype.actorPrefix; |
|
793 if (["from", "tabs", "selected"].indexOf(name) != -1) { |
|
794 throw Error(name + " is not allowed"); |
|
795 } |
|
796 if (DebuggerServer.globalActorFactories.hasOwnProperty(name)) { |
|
797 throw Error(name + " already exists"); |
|
798 } |
|
799 DebuggerServer.globalActorFactories[name] = aFunction; |
|
800 }, |
|
801 |
|
802 /** |
|
803 * Unregisters the handler for the specified browser-scoped request type. |
|
804 * This may be used for example by add-ons when shutting down or upgrading. |
|
805 * |
|
806 * @param aFunction function |
|
807 * The constructor function for this request type. |
|
808 */ |
|
809 removeGlobalActor: function DS_removeGlobalActor(aFunction) { |
|
810 for (let name in DebuggerServer.globalActorFactories) { |
|
811 let handler = DebuggerServer.globalActorFactories[name]; |
|
812 if (handler.name == aFunction.name) { |
|
813 delete DebuggerServer.globalActorFactories[name]; |
|
814 } |
|
815 } |
|
816 } |
|
817 }; |
|
818 |
|
819 EventEmitter.decorate(DebuggerServer); |
|
820 |
|
821 if (this.exports) { |
|
822 exports.DebuggerServer = DebuggerServer; |
|
823 } |
|
824 // Needed on B2G (See header note) |
|
825 this.DebuggerServer = DebuggerServer; |
|
826 |
|
827 // Export ActorPool for requirers of main.js |
|
828 if (this.exports) { |
|
829 exports.ActorPool = ActorPool; |
|
830 } |
|
831 |
|
832 /** |
|
833 * Creates a DebuggerServerConnection. |
|
834 * |
|
835 * Represents a connection to this debugging global from a client. |
|
836 * Manages a set of actors and actor pools, allocates actor ids, and |
|
837 * handles incoming requests. |
|
838 * |
|
839 * @param aPrefix string |
|
840 * All actor IDs created by this connection should be prefixed |
|
841 * with aPrefix. |
|
842 * @param aTransport transport |
|
843 * Packet transport for the debugging protocol. |
|
844 */ |
|
845 function DebuggerServerConnection(aPrefix, aTransport) |
|
846 { |
|
847 this._prefix = aPrefix; |
|
848 this._transport = aTransport; |
|
849 this._transport.hooks = this; |
|
850 this._nextID = 1; |
|
851 |
|
852 this._actorPool = new ActorPool(this); |
|
853 this._extraPools = []; |
|
854 |
|
855 // Responses to a given actor must be returned the the client |
|
856 // in the same order as the requests that they're replying to, but |
|
857 // Implementations might finish serving requests in a different |
|
858 // order. To keep things in order we generate a promise for each |
|
859 // request, chained to the promise for the request before it. |
|
860 // This map stores the latest request promise in the chain, keyed |
|
861 // by an actor ID string. |
|
862 this._actorResponses = new Map; |
|
863 |
|
864 /* |
|
865 * We can forward packets to other servers, if the actors on that server |
|
866 * all use a distinct prefix on their names. This is a map from prefixes |
|
867 * to transports: it maps a prefix P to a transport T if T conveys |
|
868 * packets to the server whose actors' names all begin with P + ":". |
|
869 */ |
|
870 this._forwardingPrefixes = new Map; |
|
871 } |
|
872 |
|
873 DebuggerServerConnection.prototype = { |
|
874 _prefix: null, |
|
875 get prefix() { return this._prefix }, |
|
876 |
|
877 _transport: null, |
|
878 get transport() { return this._transport }, |
|
879 |
|
880 close: function() { |
|
881 this._transport.close(); |
|
882 }, |
|
883 |
|
884 send: function DSC_send(aPacket) { |
|
885 this.transport.send(aPacket); |
|
886 }, |
|
887 |
|
888 allocID: function DSC_allocID(aPrefix) { |
|
889 return this.prefix + (aPrefix || '') + this._nextID++; |
|
890 }, |
|
891 |
|
892 /** |
|
893 * Add a map of actor IDs to the connection. |
|
894 */ |
|
895 addActorPool: function DSC_addActorPool(aActorPool) { |
|
896 this._extraPools.push(aActorPool); |
|
897 }, |
|
898 |
|
899 /** |
|
900 * Remove a previously-added pool of actors to the connection. |
|
901 * |
|
902 * @param ActorPool aActorPool |
|
903 * The ActorPool instance you want to remove. |
|
904 * @param boolean aNoCleanup [optional] |
|
905 * True if you don't want to disconnect each actor from the pool, false |
|
906 * otherwise. |
|
907 */ |
|
908 removeActorPool: function DSC_removeActorPool(aActorPool, aNoCleanup) { |
|
909 let index = this._extraPools.lastIndexOf(aActorPool); |
|
910 if (index > -1) { |
|
911 let pool = this._extraPools.splice(index, 1); |
|
912 if (!aNoCleanup) { |
|
913 pool.map(function(p) { p.cleanup(); }); |
|
914 } |
|
915 } |
|
916 }, |
|
917 |
|
918 /** |
|
919 * Add an actor to the default actor pool for this connection. |
|
920 */ |
|
921 addActor: function DSC_addActor(aActor) { |
|
922 this._actorPool.addActor(aActor); |
|
923 }, |
|
924 |
|
925 /** |
|
926 * Remove an actor to the default actor pool for this connection. |
|
927 */ |
|
928 removeActor: function DSC_removeActor(aActor) { |
|
929 this._actorPool.removeActor(aActor); |
|
930 }, |
|
931 |
|
932 /** |
|
933 * Match the api expected by the protocol library. |
|
934 */ |
|
935 unmanage: function(aActor) { |
|
936 return this.removeActor(aActor); |
|
937 }, |
|
938 |
|
939 /** |
|
940 * Look up an actor implementation for an actorID. Will search |
|
941 * all the actor pools registered with the connection. |
|
942 * |
|
943 * @param aActorID string |
|
944 * Actor ID to look up. |
|
945 */ |
|
946 getActor: function DSC_getActor(aActorID) { |
|
947 let pool = this.poolFor(aActorID); |
|
948 if (pool) { |
|
949 return pool.get(aActorID); |
|
950 } |
|
951 |
|
952 if (aActorID === "root") { |
|
953 return this.rootActor; |
|
954 } |
|
955 |
|
956 return null; |
|
957 }, |
|
958 |
|
959 poolFor: function DSC_actorPool(aActorID) { |
|
960 if (this._actorPool && this._actorPool.has(aActorID)) { |
|
961 return this._actorPool; |
|
962 } |
|
963 |
|
964 for (let pool of this._extraPools) { |
|
965 if (pool.has(aActorID)) { |
|
966 return pool; |
|
967 } |
|
968 } |
|
969 return null; |
|
970 }, |
|
971 |
|
972 _unknownError: function DSC__unknownError(aPrefix, aError) { |
|
973 let errorString = aPrefix + ": " + DevToolsUtils.safeErrorString(aError); |
|
974 Cu.reportError(errorString); |
|
975 dumpn(errorString); |
|
976 return { |
|
977 error: "unknownError", |
|
978 message: errorString |
|
979 }; |
|
980 }, |
|
981 |
|
982 /** |
|
983 * Passes a set of options to the BrowserAddonActors for the given ID. |
|
984 * |
|
985 * @param aId string |
|
986 * The ID of the add-on to pass the options to |
|
987 * @param aOptions object |
|
988 * The options. |
|
989 * @return a promise that will be resolved when complete. |
|
990 */ |
|
991 setAddonOptions: function DSC_setAddonOptions(aId, aOptions) { |
|
992 let addonList = this.rootActor._parameters.addonList; |
|
993 if (!addonList) { |
|
994 return resolve(); |
|
995 } |
|
996 return addonList.getList().then((addonActors) => { |
|
997 for (let actor of addonActors) { |
|
998 if (actor.id != aId) { |
|
999 continue; |
|
1000 } |
|
1001 actor.setOptions(aOptions); |
|
1002 return; |
|
1003 } |
|
1004 }); |
|
1005 }, |
|
1006 |
|
1007 /* Forwarding packets to other transports based on actor name prefixes. */ |
|
1008 |
|
1009 /* |
|
1010 * Arrange to forward packets to another server. This is how we |
|
1011 * forward debugging connections to child processes. |
|
1012 * |
|
1013 * If we receive a packet for an actor whose name begins with |aPrefix| |
|
1014 * followed by ':', then we will forward that packet to |aTransport|. |
|
1015 * |
|
1016 * This overrides any prior forwarding for |aPrefix|. |
|
1017 * |
|
1018 * @param aPrefix string |
|
1019 * The actor name prefix, not including the ':'. |
|
1020 * @param aTransport object |
|
1021 * A packet transport to which we should forward packets to actors |
|
1022 * whose names begin with |(aPrefix + ':').| |
|
1023 */ |
|
1024 setForwarding: function(aPrefix, aTransport) { |
|
1025 this._forwardingPrefixes.set(aPrefix, aTransport); |
|
1026 }, |
|
1027 |
|
1028 /* |
|
1029 * Stop forwarding messages to actors whose names begin with |
|
1030 * |aPrefix+':'|. Such messages will now elicit 'noSuchActor' errors. |
|
1031 */ |
|
1032 cancelForwarding: function(aPrefix) { |
|
1033 this._forwardingPrefixes.delete(aPrefix); |
|
1034 }, |
|
1035 |
|
1036 // Transport hooks. |
|
1037 |
|
1038 /** |
|
1039 * Called by DebuggerTransport to dispatch incoming packets as appropriate. |
|
1040 * |
|
1041 * @param aPacket object |
|
1042 * The incoming packet. |
|
1043 */ |
|
1044 onPacket: function DSC_onPacket(aPacket) { |
|
1045 // If the actor's name begins with a prefix we've been asked to |
|
1046 // forward, do so. |
|
1047 // |
|
1048 // Note that the presence of a prefix alone doesn't indicate that |
|
1049 // forwarding is needed: in DebuggerServerConnection instances in child |
|
1050 // processes, every actor has a prefixed name. |
|
1051 if (this._forwardingPrefixes.size > 0) { |
|
1052 let colon = aPacket.to.indexOf(':'); |
|
1053 if (colon >= 0) { |
|
1054 let forwardTo = this._forwardingPrefixes.get(aPacket.to.substring(0, colon)); |
|
1055 if (forwardTo) { |
|
1056 forwardTo.send(aPacket); |
|
1057 return; |
|
1058 } |
|
1059 } |
|
1060 } |
|
1061 |
|
1062 let actor = this.getActor(aPacket.to); |
|
1063 if (!actor) { |
|
1064 this.transport.send({ from: aPacket.to ? aPacket.to : "root", |
|
1065 error: "noSuchActor", |
|
1066 message: "No such actor for ID: " + aPacket.to }); |
|
1067 return; |
|
1068 } |
|
1069 |
|
1070 // Dyamically-loaded actors have to be created lazily. |
|
1071 if (typeof actor == "function") { |
|
1072 let instance; |
|
1073 try { |
|
1074 instance = new actor(); |
|
1075 } catch (e) { |
|
1076 this.transport.send(this._unknownError( |
|
1077 "Error occurred while creating actor '" + actor.name, |
|
1078 e)); |
|
1079 } |
|
1080 instance.parentID = actor.parentID; |
|
1081 // We want the newly-constructed actor to completely replace the factory |
|
1082 // actor. Reusing the existing actor ID will make sure ActorPool.addActor |
|
1083 // does the right thing. |
|
1084 instance.actorID = actor.actorID; |
|
1085 actor.registeredPool.addActor(instance); |
|
1086 actor = instance; |
|
1087 } |
|
1088 |
|
1089 var ret = null; |
|
1090 |
|
1091 // handle "requestTypes" RDP request. |
|
1092 if (aPacket.type == "requestTypes") { |
|
1093 ret = { from: actor.actorID, requestTypes: Object.keys(actor.requestTypes) }; |
|
1094 } else if (actor.requestTypes && actor.requestTypes[aPacket.type]) { |
|
1095 // Dispatch the request to the actor. |
|
1096 try { |
|
1097 this.currentPacket = aPacket; |
|
1098 ret = actor.requestTypes[aPacket.type].bind(actor)(aPacket, this); |
|
1099 } catch(e) { |
|
1100 this.transport.send(this._unknownError( |
|
1101 "error occurred while processing '" + aPacket.type, |
|
1102 e)); |
|
1103 } finally { |
|
1104 this.currentPacket = undefined; |
|
1105 } |
|
1106 } else { |
|
1107 ret = { error: "unrecognizedPacketType", |
|
1108 message: ('Actor "' + actor.actorID + |
|
1109 '" does not recognize the packet type "' + |
|
1110 aPacket.type + '"') }; |
|
1111 } |
|
1112 |
|
1113 if (!ret) { |
|
1114 // This should become an error once we've converted every user |
|
1115 // of this to promises in bug 794078. |
|
1116 return; |
|
1117 } |
|
1118 |
|
1119 let pendingResponse = this._actorResponses.get(actor.actorID) || resolve(null); |
|
1120 let response = pendingResponse.then(() => { |
|
1121 return ret; |
|
1122 }).then(aResponse => { |
|
1123 if (!aResponse.from) { |
|
1124 aResponse.from = aPacket.to; |
|
1125 } |
|
1126 this.transport.send(aResponse); |
|
1127 }).then(null, (e) => { |
|
1128 let errorPacket = this._unknownError( |
|
1129 "error occurred while processing '" + aPacket.type, |
|
1130 e); |
|
1131 errorPacket.from = aPacket.to; |
|
1132 this.transport.send(errorPacket); |
|
1133 }); |
|
1134 |
|
1135 this._actorResponses.set(actor.actorID, response); |
|
1136 }, |
|
1137 |
|
1138 /** |
|
1139 * Called by DebuggerTransport when the underlying stream is closed. |
|
1140 * |
|
1141 * @param aStatus nsresult |
|
1142 * The status code that corresponds to the reason for closing |
|
1143 * the stream. |
|
1144 */ |
|
1145 onClosed: function DSC_onClosed(aStatus) { |
|
1146 dumpn("Cleaning up connection."); |
|
1147 if (!this._actorPool) { |
|
1148 // Ignore this call if the connection is already closed. |
|
1149 return; |
|
1150 } |
|
1151 events.emit(this, "closed", aStatus); |
|
1152 |
|
1153 this._actorPool.cleanup(); |
|
1154 this._actorPool = null; |
|
1155 this._extraPools.map(function(p) { p.cleanup(); }); |
|
1156 this._extraPools = null; |
|
1157 |
|
1158 DebuggerServer._connectionClosed(this); |
|
1159 }, |
|
1160 |
|
1161 /* |
|
1162 * Debugging helper for inspecting the state of the actor pools. |
|
1163 */ |
|
1164 _dumpPools: function DSC_dumpPools() { |
|
1165 dumpn("/-------------------- dumping pools:"); |
|
1166 if (this._actorPool) { |
|
1167 dumpn("--------------------- actorPool actors: " + |
|
1168 uneval(Object.keys(this._actorPool._actors))); |
|
1169 } |
|
1170 for each (let pool in this._extraPools) |
|
1171 dumpn("--------------------- extraPool actors: " + |
|
1172 uneval(Object.keys(pool._actors))); |
|
1173 }, |
|
1174 |
|
1175 /* |
|
1176 * Debugging helper for inspecting the state of an actor pool. |
|
1177 */ |
|
1178 _dumpPool: function DSC_dumpPools(aPool) { |
|
1179 dumpn("/-------------------- dumping pool:"); |
|
1180 dumpn("--------------------- actorPool actors: " + |
|
1181 uneval(Object.keys(aPool._actors))); |
|
1182 } |
|
1183 }; |
|
1184 |
|
1185 /** |
|
1186 * Localization convenience methods. |
|
1187 */ |
|
1188 let L10N = { |
|
1189 |
|
1190 /** |
|
1191 * L10N shortcut function. |
|
1192 * |
|
1193 * @param string aName |
|
1194 * @return string |
|
1195 */ |
|
1196 getStr: function L10N_getStr(aName) { |
|
1197 return this.stringBundle.GetStringFromName(aName); |
|
1198 } |
|
1199 }; |
|
1200 |
|
1201 XPCOMUtils.defineLazyGetter(L10N, "stringBundle", function() { |
|
1202 return Services.strings.createBundle(DBG_STRINGS_URI); |
|
1203 }); |