1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/toolkit/devtools/server/main.js Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,1203 @@ 1.4 +/* -*- Mode: javascript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 1.5 +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ 1.6 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.7 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.8 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.9 + 1.10 +"use strict"; 1.11 +/** 1.12 + * Toolkit glue for the remote debugging protocol, loaded into the 1.13 + * debugging global. 1.14 + */ 1.15 +let { Ci, Cc, CC, Cu, Cr } = require("chrome"); 1.16 +let Debugger = require("Debugger"); 1.17 +let Services = require("Services"); 1.18 +let { ActorPool } = require("devtools/server/actors/common"); 1.19 +let { DebuggerTransport, LocalDebuggerTransport, ChildDebuggerTransport } = require("devtools/server/transport"); 1.20 +let DevToolsUtils = require("devtools/toolkit/DevToolsUtils"); 1.21 +let { dumpn, dbg_assert } = DevToolsUtils; 1.22 +let Services = require("Services"); 1.23 +let EventEmitter = require("devtools/toolkit/event-emitter"); 1.24 + 1.25 +// Until all Debugger server code is converted to SDK modules, 1.26 +// imports Components.* alias from chrome module. 1.27 +var { Ci, Cc, CC, Cu, Cr } = require("chrome"); 1.28 +// On B2G, `this` != Global scope, so `Ci` won't be binded on `this` 1.29 +// (i.e. this.Ci is undefined) Then later, when using loadSubScript, 1.30 +// Ci,... won't be defined for sub scripts. 1.31 +this.Ci = Ci; 1.32 +this.Cc = Cc; 1.33 +this.CC = CC; 1.34 +this.Cu = Cu; 1.35 +this.Cr = Cr; 1.36 +this.Debugger = Debugger; 1.37 +this.Services = Services; 1.38 +this.ActorPool = ActorPool; 1.39 +this.DevToolsUtils = DevToolsUtils; 1.40 +this.dumpn = dumpn; 1.41 +this.dbg_assert = dbg_assert; 1.42 + 1.43 +// Overload `Components` to prevent SDK loader exception on Components 1.44 +// object usage 1.45 +Object.defineProperty(this, "Components", { 1.46 + get: function () require("chrome").components 1.47 +}); 1.48 + 1.49 +const DBG_STRINGS_URI = "chrome://global/locale/devtools/debugger.properties"; 1.50 + 1.51 +const nsFile = CC("@mozilla.org/file/local;1", "nsIFile", "initWithPath"); 1.52 +Cu.import("resource://gre/modules/reflect.jsm"); 1.53 +Cu.import("resource://gre/modules/XPCOMUtils.jsm"); 1.54 +Cu.import("resource://gre/modules/NetUtil.jsm"); 1.55 +dumpn.wantLogging = Services.prefs.getBoolPref("devtools.debugger.log"); 1.56 + 1.57 +Cu.import("resource://gre/modules/devtools/deprecated-sync-thenables.js"); 1.58 + 1.59 +function loadSubScript(aURL) 1.60 +{ 1.61 + try { 1.62 + let loader = Cc["@mozilla.org/moz/jssubscript-loader;1"] 1.63 + .getService(Ci.mozIJSSubScriptLoader); 1.64 + loader.loadSubScript(aURL, this); 1.65 + } catch(e) { 1.66 + let errorStr = "Error loading: " + aURL + ":\n" + 1.67 + (e.fileName ? "at " + e.fileName + " : " + e.lineNumber + "\n" : "") + 1.68 + e + " - " + e.stack + "\n"; 1.69 + dump(errorStr); 1.70 + Cu.reportError(errorStr); 1.71 + throw e; 1.72 + } 1.73 +} 1.74 + 1.75 +let events = require("sdk/event/core"); 1.76 +let {defer, resolve, reject, all} = require("devtools/toolkit/deprecated-sync-thenables"); 1.77 +this.defer = defer; 1.78 +this.resolve = resolve; 1.79 +this.reject = reject; 1.80 +this.all = all; 1.81 + 1.82 +Cu.import("resource://gre/modules/devtools/SourceMap.jsm"); 1.83 + 1.84 +XPCOMUtils.defineLazyModuleGetter(this, "console", 1.85 + "resource://gre/modules/devtools/Console.jsm"); 1.86 + 1.87 +XPCOMUtils.defineLazyGetter(this, "NetworkMonitorManager", () => { 1.88 + return require("devtools/toolkit/webconsole/network-monitor").NetworkMonitorManager; 1.89 +}); 1.90 + 1.91 +// XPCOM constructors 1.92 +const ServerSocket = CC("@mozilla.org/network/server-socket;1", 1.93 + "nsIServerSocket", 1.94 + "initSpecialConnection"); 1.95 +const UnixDomainServerSocket = CC("@mozilla.org/network/server-socket;1", 1.96 + "nsIServerSocket", 1.97 + "initWithFilename"); 1.98 + 1.99 +var gRegisteredModules = Object.create(null); 1.100 + 1.101 +/** 1.102 + * The ModuleAPI object is passed to modules loaded using the 1.103 + * DebuggerServer.registerModule() API. Modules can use this 1.104 + * object to register actor factories. 1.105 + * Factories registered through the module API will be removed 1.106 + * when the module is unregistered or when the server is 1.107 + * destroyed. 1.108 + */ 1.109 +function ModuleAPI() { 1.110 + let activeTabActors = new Set(); 1.111 + let activeGlobalActors = new Set(); 1.112 + 1.113 + return { 1.114 + // See DebuggerServer.addGlobalActor for a description. 1.115 + addGlobalActor: function(factory, name) { 1.116 + DebuggerServer.addGlobalActor(factory, name); 1.117 + activeGlobalActors.add(factory); 1.118 + }, 1.119 + // See DebuggerServer.removeGlobalActor for a description. 1.120 + removeGlobalActor: function(factory) { 1.121 + DebuggerServer.removeGlobalActor(factory); 1.122 + activeGlobalActors.delete(factory); 1.123 + }, 1.124 + 1.125 + // See DebuggerServer.addTabActor for a description. 1.126 + addTabActor: function(factory, name) { 1.127 + DebuggerServer.addTabActor(factory, name); 1.128 + activeTabActors.add(factory); 1.129 + }, 1.130 + // See DebuggerServer.removeTabActor for a description. 1.131 + removeTabActor: function(factory) { 1.132 + DebuggerServer.removeTabActor(factory); 1.133 + activeTabActors.delete(factory); 1.134 + }, 1.135 + 1.136 + // Destroy the module API object, unregistering any 1.137 + // factories registered by the module. 1.138 + destroy: function() { 1.139 + for (let factory of activeTabActors) { 1.140 + DebuggerServer.removeTabActor(factory); 1.141 + } 1.142 + activeTabActors = null; 1.143 + for (let factory of activeGlobalActors) { 1.144 + DebuggerServer.removeGlobalActor(factory); 1.145 + } 1.146 + activeGlobalActors = null; 1.147 + } 1.148 + } 1.149 +}; 1.150 + 1.151 +/*** 1.152 + * Public API 1.153 + */ 1.154 +var DebuggerServer = { 1.155 + _listener: null, 1.156 + _initialized: false, 1.157 + _transportInitialized: false, 1.158 + xpcInspector: null, 1.159 + // Number of currently open TCP connections. 1.160 + _socketConnections: 0, 1.161 + // Map of global actor names to actor constructors provided by extensions. 1.162 + globalActorFactories: {}, 1.163 + // Map of tab actor names to actor constructors provided by extensions. 1.164 + tabActorFactories: {}, 1.165 + 1.166 + LONG_STRING_LENGTH: 10000, 1.167 + LONG_STRING_INITIAL_LENGTH: 1000, 1.168 + LONG_STRING_READ_LENGTH: 65 * 1024, 1.169 + 1.170 + /** 1.171 + * A handler function that prompts the user to accept or decline the incoming 1.172 + * connection. 1.173 + */ 1.174 + _allowConnection: null, 1.175 + 1.176 + /** 1.177 + * The windowtype of the chrome window to use for actors that use the global 1.178 + * window (i.e the global style editor). Set this to your main window type, 1.179 + * for example "navigator:browser". 1.180 + */ 1.181 + chromeWindowType: null, 1.182 + 1.183 + /** 1.184 + * Prompt the user to accept or decline the incoming connection. This is the 1.185 + * default implementation that products embedding the debugger server may 1.186 + * choose to override. 1.187 + * 1.188 + * @return true if the connection should be permitted, false otherwise 1.189 + */ 1.190 + _defaultAllowConnection: function DS__defaultAllowConnection() { 1.191 + let title = L10N.getStr("remoteIncomingPromptTitle"); 1.192 + let msg = L10N.getStr("remoteIncomingPromptMessage"); 1.193 + let disableButton = L10N.getStr("remoteIncomingPromptDisable"); 1.194 + let prompt = Services.prompt; 1.195 + let flags = prompt.BUTTON_POS_0 * prompt.BUTTON_TITLE_OK + 1.196 + prompt.BUTTON_POS_1 * prompt.BUTTON_TITLE_CANCEL + 1.197 + prompt.BUTTON_POS_2 * prompt.BUTTON_TITLE_IS_STRING + 1.198 + prompt.BUTTON_POS_1_DEFAULT; 1.199 + let result = prompt.confirmEx(null, title, msg, flags, null, null, 1.200 + disableButton, null, { value: false }); 1.201 + if (result == 0) { 1.202 + return true; 1.203 + } 1.204 + if (result == 2) { 1.205 + DebuggerServer.closeListener(true); 1.206 + Services.prefs.setBoolPref("devtools.debugger.remote-enabled", false); 1.207 + } 1.208 + return false; 1.209 + }, 1.210 + 1.211 + /** 1.212 + * Initialize the debugger server. 1.213 + * 1.214 + * @param function aAllowConnectionCallback 1.215 + * The embedder-provider callback, that decides whether an incoming 1.216 + * remote protocol conection should be allowed or refused. 1.217 + */ 1.218 + init: function DS_init(aAllowConnectionCallback) { 1.219 + if (this.initialized) { 1.220 + return; 1.221 + } 1.222 + 1.223 + this.xpcInspector = Cc["@mozilla.org/jsinspector;1"].getService(Ci.nsIJSInspector); 1.224 + this.initTransport(aAllowConnectionCallback); 1.225 + this.addActors("resource://gre/modules/devtools/server/actors/root.js"); 1.226 + 1.227 + this._initialized = true; 1.228 + }, 1.229 + 1.230 + protocol: require("devtools/server/protocol"), 1.231 + 1.232 + /** 1.233 + * Initialize the debugger server's transport variables. This can be 1.234 + * in place of init() for cases where the jsdebugger isn't needed. 1.235 + * 1.236 + * @param function aAllowConnectionCallback 1.237 + * The embedder-provider callback, that decides whether an incoming 1.238 + * remote protocol conection should be allowed or refused. 1.239 + */ 1.240 + initTransport: function DS_initTransport(aAllowConnectionCallback) { 1.241 + if (this._transportInitialized) { 1.242 + return; 1.243 + } 1.244 + 1.245 + this._connections = {}; 1.246 + this._nextConnID = 0; 1.247 + this._transportInitialized = true; 1.248 + this._allowConnection = aAllowConnectionCallback ? 1.249 + aAllowConnectionCallback : 1.250 + this._defaultAllowConnection; 1.251 + }, 1.252 + 1.253 + get initialized() this._initialized, 1.254 + 1.255 + /** 1.256 + * Performs cleanup tasks before shutting down the debugger server. Such tasks 1.257 + * include clearing any actor constructors added at runtime. This method 1.258 + * should be called whenever a debugger server is no longer useful, to avoid 1.259 + * memory leaks. After this method returns, the debugger server must be 1.260 + * initialized again before use. 1.261 + */ 1.262 + destroy: function DS_destroy() { 1.263 + if (!this._initialized) { 1.264 + return; 1.265 + } 1.266 + 1.267 + for (let connID of Object.getOwnPropertyNames(this._connections)) { 1.268 + this._connections[connID].close(); 1.269 + } 1.270 + 1.271 + for (let id of Object.getOwnPropertyNames(gRegisteredModules)) { 1.272 + let mod = gRegisteredModules[id]; 1.273 + mod.module.unregister(mod.api); 1.274 + } 1.275 + gRegisteredModules = {}; 1.276 + 1.277 + this.closeListener(); 1.278 + this.globalActorFactories = {}; 1.279 + this.tabActorFactories = {}; 1.280 + this._allowConnection = null; 1.281 + this._transportInitialized = false; 1.282 + this._initialized = false; 1.283 + 1.284 + dumpn("Debugger server is shut down."); 1.285 + }, 1.286 + 1.287 + /** 1.288 + * Load a subscript into the debugging global. 1.289 + * 1.290 + * @param aURL string A url that will be loaded as a subscript into the 1.291 + * debugging global. The user must load at least one script 1.292 + * that implements a createRootActor() function to create the 1.293 + * server's root actor. 1.294 + */ 1.295 + addActors: function DS_addActors(aURL) { 1.296 + loadSubScript.call(this, aURL); 1.297 + }, 1.298 + 1.299 + /** 1.300 + * Register a CommonJS module with the debugger server. 1.301 + * @param id string 1.302 + * The ID of a CommonJS module. This module must export 1.303 + * 'register' and 'unregister' functions. 1.304 + */ 1.305 + registerModule: function(id) { 1.306 + if (id in gRegisteredModules) { 1.307 + throw new Error("Tried to register a module twice: " + id + "\n"); 1.308 + } 1.309 + 1.310 + let moduleAPI = ModuleAPI(); 1.311 + let mod = require(id); 1.312 + mod.register(moduleAPI); 1.313 + gRegisteredModules[id] = { module: mod, api: moduleAPI }; 1.314 + }, 1.315 + 1.316 + /** 1.317 + * Returns true if a module id has been registered. 1.318 + */ 1.319 + isModuleRegistered: function(id) { 1.320 + return (id in gRegisteredModules); 1.321 + }, 1.322 + 1.323 + /** 1.324 + * Unregister a previously-loaded CommonJS module from the debugger server. 1.325 + */ 1.326 + unregisterModule: function(id) { 1.327 + let mod = gRegisteredModules[id]; 1.328 + if (!mod) { 1.329 + throw new Error("Tried to unregister a module that was not previously registered."); 1.330 + } 1.331 + mod.module.unregister(mod.api); 1.332 + mod.api.destroy(); 1.333 + delete gRegisteredModules[id]; 1.334 + }, 1.335 + 1.336 + /** 1.337 + * Install Firefox-specific actors. 1.338 + * 1.339 + * /!\ Be careful when adding a new actor, especially global actors. 1.340 + * Any new global actor will be exposed and returned by the root actor. 1.341 + * 1.342 + * That's the reason why tab actors aren't loaded on demand via 1.343 + * restrictPrivileges=true, to prevent exposing them on b2g parent process's 1.344 + * root actor. 1.345 + */ 1.346 + addBrowserActors: function(aWindowType = "navigator:browser", restrictPrivileges = false) { 1.347 + this.chromeWindowType = aWindowType; 1.348 + this.addActors("resource://gre/modules/devtools/server/actors/webbrowser.js"); 1.349 + 1.350 + if (!restrictPrivileges) { 1.351 + this.addTabActors(); 1.352 + this.addGlobalActor(this.ChromeDebuggerActor, "chromeDebugger"); 1.353 + this.registerModule("devtools/server/actors/preference"); 1.354 + } 1.355 + 1.356 + this.addActors("resource://gre/modules/devtools/server/actors/webapps.js"); 1.357 + this.registerModule("devtools/server/actors/device"); 1.358 + }, 1.359 + 1.360 + /** 1.361 + * Install tab actors in documents loaded in content childs 1.362 + */ 1.363 + addChildActors: function () { 1.364 + // In case of apps being loaded in parent process, DebuggerServer is already 1.365 + // initialized and browser actors are already loaded, 1.366 + // but childtab.js hasn't been loaded yet. 1.367 + if (!DebuggerServer.tabActorFactories.hasOwnProperty("consoleActor")) { 1.368 + this.addTabActors(); 1.369 + } 1.370 + // But webbrowser.js and childtab.js aren't loaded from shell.js. 1.371 + if (!("BrowserTabActor" in this)) { 1.372 + this.addActors("resource://gre/modules/devtools/server/actors/webbrowser.js"); 1.373 + } 1.374 + if (!("ContentActor" in this)) { 1.375 + this.addActors("resource://gre/modules/devtools/server/actors/childtab.js"); 1.376 + } 1.377 + }, 1.378 + 1.379 + /** 1.380 + * Install tab actors. 1.381 + */ 1.382 + addTabActors: function() { 1.383 + this.addActors("resource://gre/modules/devtools/server/actors/script.js"); 1.384 + this.registerModule("devtools/server/actors/webconsole"); 1.385 + this.registerModule("devtools/server/actors/inspector"); 1.386 + this.registerModule("devtools/server/actors/call-watcher"); 1.387 + this.registerModule("devtools/server/actors/canvas"); 1.388 + this.registerModule("devtools/server/actors/webgl"); 1.389 + this.registerModule("devtools/server/actors/webaudio"); 1.390 + this.registerModule("devtools/server/actors/stylesheets"); 1.391 + this.registerModule("devtools/server/actors/styleeditor"); 1.392 + this.registerModule("devtools/server/actors/storage"); 1.393 + this.registerModule("devtools/server/actors/gcli"); 1.394 + this.registerModule("devtools/server/actors/tracer"); 1.395 + this.registerModule("devtools/server/actors/memory"); 1.396 + this.registerModule("devtools/server/actors/eventlooplag"); 1.397 + if ("nsIProfiler" in Ci) { 1.398 + this.addActors("resource://gre/modules/devtools/server/actors/profiler.js"); 1.399 + } 1.400 + }, 1.401 + 1.402 + /** 1.403 + * Passes a set of options to the BrowserAddonActors for the given ID. 1.404 + * 1.405 + * @param aId string 1.406 + * The ID of the add-on to pass the options to 1.407 + * @param aOptions object 1.408 + * The options. 1.409 + * @return a promise that will be resolved when complete. 1.410 + */ 1.411 + setAddonOptions: function DS_setAddonOptions(aId, aOptions) { 1.412 + if (!this._initialized) { 1.413 + return; 1.414 + } 1.415 + 1.416 + let promises = []; 1.417 + 1.418 + // Pass to all connections 1.419 + for (let connID of Object.getOwnPropertyNames(this._connections)) { 1.420 + promises.push(this._connections[connID].setAddonOptions(aId, aOptions)); 1.421 + } 1.422 + 1.423 + return all(promises); 1.424 + }, 1.425 + 1.426 + /** 1.427 + * Listens on the given port or socket file for remote debugger connections. 1.428 + * 1.429 + * @param aPortOrPath int, string 1.430 + * If given an integer, the port to listen on. 1.431 + * Otherwise, the path to the unix socket domain file to listen on. 1.432 + */ 1.433 + openListener: function DS_openListener(aPortOrPath) { 1.434 + if (!Services.prefs.getBoolPref("devtools.debugger.remote-enabled")) { 1.435 + return false; 1.436 + } 1.437 + this._checkInit(); 1.438 + 1.439 + // Return early if the server is already listening. 1.440 + if (this._listener) { 1.441 + return true; 1.442 + } 1.443 + 1.444 + let flags = Ci.nsIServerSocket.KeepWhenOffline; 1.445 + // A preference setting can force binding on the loopback interface. 1.446 + if (Services.prefs.getBoolPref("devtools.debugger.force-local")) { 1.447 + flags |= Ci.nsIServerSocket.LoopbackOnly; 1.448 + } 1.449 + 1.450 + try { 1.451 + let backlog = 4; 1.452 + let socket; 1.453 + let port = Number(aPortOrPath); 1.454 + if (port) { 1.455 + socket = new ServerSocket(port, flags, backlog); 1.456 + } else { 1.457 + let file = nsFile(aPortOrPath); 1.458 + if (file.exists()) 1.459 + file.remove(false); 1.460 + socket = new UnixDomainServerSocket(file, parseInt("666", 8), backlog); 1.461 + } 1.462 + socket.asyncListen(this); 1.463 + this._listener = socket; 1.464 + } catch (e) { 1.465 + dumpn("Could not start debugging listener on '" + aPortOrPath + "': " + e); 1.466 + throw Cr.NS_ERROR_NOT_AVAILABLE; 1.467 + } 1.468 + this._socketConnections++; 1.469 + 1.470 + return true; 1.471 + }, 1.472 + 1.473 + /** 1.474 + * Close a previously-opened TCP listener. 1.475 + * 1.476 + * @param aForce boolean [optional] 1.477 + * If set to true, then the socket will be closed, regardless of the 1.478 + * number of open connections. 1.479 + */ 1.480 + closeListener: function DS_closeListener(aForce) { 1.481 + if (!this._listener || this._socketConnections == 0) { 1.482 + return false; 1.483 + } 1.484 + 1.485 + // Only close the listener when the last connection is closed, or if the 1.486 + // aForce flag is passed. 1.487 + if (--this._socketConnections == 0 || aForce) { 1.488 + this._listener.close(); 1.489 + this._listener = null; 1.490 + this._socketConnections = 0; 1.491 + } 1.492 + 1.493 + return true; 1.494 + }, 1.495 + 1.496 + /** 1.497 + * Creates a new connection to the local debugger speaking over a fake 1.498 + * transport. This connection results in straightforward calls to the onPacket 1.499 + * handlers of each side. 1.500 + * 1.501 + * @param aPrefix string [optional] 1.502 + * If given, all actors in this connection will have names starting 1.503 + * with |aPrefix + ':'|. 1.504 + * @returns a client-side DebuggerTransport for communicating with 1.505 + * the newly-created connection. 1.506 + */ 1.507 + connectPipe: function DS_connectPipe(aPrefix) { 1.508 + this._checkInit(); 1.509 + 1.510 + let serverTransport = new LocalDebuggerTransport; 1.511 + let clientTransport = new LocalDebuggerTransport(serverTransport); 1.512 + serverTransport.other = clientTransport; 1.513 + let connection = this._onConnection(serverTransport, aPrefix); 1.514 + 1.515 + // I'm putting this here because I trust you. 1.516 + // 1.517 + // There are times, when using a local connection, when you're going 1.518 + // to be tempted to just get direct access to the server. Resist that 1.519 + // temptation! If you succumb to that temptation, you will make the 1.520 + // fine developers that work on Fennec and Firefox OS sad. They're 1.521 + // professionals, they'll try to act like they understand, but deep 1.522 + // down you'll know that you hurt them. 1.523 + // 1.524 + // This reference allows you to give in to that temptation. There are 1.525 + // times this makes sense: tests, for example, and while porting a 1.526 + // previously local-only codebase to the remote protocol. 1.527 + // 1.528 + // But every time you use this, you will feel the shame of having 1.529 + // used a property that starts with a '_'. 1.530 + clientTransport._serverConnection = connection; 1.531 + 1.532 + return clientTransport; 1.533 + }, 1.534 + 1.535 + /** 1.536 + * In a content child process, create a new connection that exchanges 1.537 + * nsIMessageSender messages with our parent process. 1.538 + * 1.539 + * @param aPrefix 1.540 + * The prefix we should use in our nsIMessageSender message names and 1.541 + * actor names. This connection will use messages named 1.542 + * "debug:<prefix>:packet", and all its actors will have names 1.543 + * beginning with "<prefix>:". 1.544 + */ 1.545 + connectToParent: function(aPrefix, aMessageManager) { 1.546 + this._checkInit(); 1.547 + 1.548 + let transport = new ChildDebuggerTransport(aMessageManager, aPrefix); 1.549 + return this._onConnection(transport, aPrefix, true); 1.550 + }, 1.551 + 1.552 + /** 1.553 + * Connect to a child process. 1.554 + * 1.555 + * @param object aConnection 1.556 + * The debugger server connection to use. 1.557 + * @param nsIDOMElement aFrame 1.558 + * The browser element that holds the child process. 1.559 + * @param function [aOnDisconnect] 1.560 + * Optional function to invoke when the child is disconnected. 1.561 + * @return object 1.562 + * A promise object that is resolved once the connection is 1.563 + * established. 1.564 + */ 1.565 + connectToChild: function(aConnection, aFrame, aOnDisconnect) { 1.566 + let deferred = defer(); 1.567 + 1.568 + let mm = aFrame.QueryInterface(Ci.nsIFrameLoaderOwner).frameLoader 1.569 + .messageManager; 1.570 + mm.loadFrameScript("resource://gre/modules/devtools/server/child.js", false); 1.571 + 1.572 + let actor, childTransport; 1.573 + let prefix = aConnection.allocID("child"); 1.574 + let netMonitor = null; 1.575 + 1.576 + let onActorCreated = DevToolsUtils.makeInfallible(function (msg) { 1.577 + mm.removeMessageListener("debug:actor", onActorCreated); 1.578 + 1.579 + // Pipe Debugger message from/to parent/child via the message manager 1.580 + childTransport = new ChildDebuggerTransport(mm, prefix); 1.581 + childTransport.hooks = { 1.582 + onPacket: aConnection.send.bind(aConnection), 1.583 + onClosed: function () {} 1.584 + }; 1.585 + childTransport.ready(); 1.586 + 1.587 + aConnection.setForwarding(prefix, childTransport); 1.588 + 1.589 + dumpn("establishing forwarding for app with prefix " + prefix); 1.590 + 1.591 + actor = msg.json.actor; 1.592 + 1.593 + netMonitor = new NetworkMonitorManager(aFrame, actor.actor); 1.594 + 1.595 + deferred.resolve(actor); 1.596 + }).bind(this); 1.597 + mm.addMessageListener("debug:actor", onActorCreated); 1.598 + 1.599 + let onMessageManagerDisconnect = DevToolsUtils.makeInfallible(function (subject, topic, data) { 1.600 + if (subject == mm) { 1.601 + Services.obs.removeObserver(onMessageManagerDisconnect, topic); 1.602 + if (childTransport) { 1.603 + // If we have a child transport, the actor has already 1.604 + // been created. We need to stop using this message manager. 1.605 + childTransport.close(); 1.606 + childTransport = null; 1.607 + aConnection.cancelForwarding(prefix); 1.608 + } else { 1.609 + // Otherwise, the app has been closed before the actor 1.610 + // had a chance to be created, so we are not able to create 1.611 + // the actor. 1.612 + deferred.resolve(null); 1.613 + } 1.614 + if (actor) { 1.615 + // The ContentActor within the child process doesn't necessary 1.616 + // have to time to uninitialize itself when the app is closed/killed. 1.617 + // So ensure telling the client that the related actor is detached. 1.618 + aConnection.send({ from: actor.actor, type: "tabDetached" }); 1.619 + actor = null; 1.620 + } 1.621 + 1.622 + if (netMonitor) { 1.623 + netMonitor.destroy(); 1.624 + netMonitor = null; 1.625 + } 1.626 + 1.627 + if (aOnDisconnect) { 1.628 + aOnDisconnect(mm); 1.629 + } 1.630 + } 1.631 + }).bind(this); 1.632 + Services.obs.addObserver(onMessageManagerDisconnect, 1.633 + "message-manager-disconnect", false); 1.634 + 1.635 + events.once(aConnection, "closed", () => { 1.636 + if (childTransport) { 1.637 + // When the client disconnects, we have to unplug the dedicated 1.638 + // ChildDebuggerTransport... 1.639 + childTransport.close(); 1.640 + childTransport = null; 1.641 + aConnection.cancelForwarding(prefix); 1.642 + 1.643 + // ... and notify the child process to clean the tab actors. 1.644 + mm.sendAsyncMessage("debug:disconnect"); 1.645 + } 1.646 + Services.obs.removeObserver(onMessageManagerDisconnect, "message-manager-disconnect"); 1.647 + }); 1.648 + 1.649 + mm.sendAsyncMessage("debug:connect", { prefix: prefix }); 1.650 + 1.651 + return deferred.promise; 1.652 + }, 1.653 + 1.654 + // nsIServerSocketListener implementation 1.655 + 1.656 + onSocketAccepted: 1.657 + DevToolsUtils.makeInfallible(function DS_onSocketAccepted(aSocket, aTransport) { 1.658 + if (Services.prefs.getBoolPref("devtools.debugger.prompt-connection") && !this._allowConnection()) { 1.659 + return; 1.660 + } 1.661 + dumpn("New debugging connection on " + aTransport.host + ":" + aTransport.port); 1.662 + 1.663 + let input = aTransport.openInputStream(0, 0, 0); 1.664 + let output = aTransport.openOutputStream(0, 0, 0); 1.665 + let transport = new DebuggerTransport(input, output); 1.666 + DebuggerServer._onConnection(transport); 1.667 + }, "DebuggerServer.onSocketAccepted"), 1.668 + 1.669 + onStopListening: function DS_onStopListening(aSocket, status) { 1.670 + dumpn("onStopListening, status: " + status); 1.671 + }, 1.672 + 1.673 + /** 1.674 + * Raises an exception if the server has not been properly initialized. 1.675 + */ 1.676 + _checkInit: function DS_checkInit() { 1.677 + if (!this._transportInitialized) { 1.678 + throw "DebuggerServer has not been initialized."; 1.679 + } 1.680 + 1.681 + if (!this.createRootActor) { 1.682 + throw "Use DebuggerServer.addActors() to add a root actor implementation."; 1.683 + } 1.684 + }, 1.685 + 1.686 + /** 1.687 + * Create a new debugger connection for the given transport. Called after 1.688 + * connectPipe(), from connectToParent, or from an incoming socket 1.689 + * connection handler. 1.690 + * 1.691 + * If present, |aForwardingPrefix| is a forwarding prefix that a parent 1.692 + * server is using to recognizes messages intended for this server. Ensure 1.693 + * that all our actors have names beginning with |aForwardingPrefix + ':'|. 1.694 + * In particular, the root actor's name will be |aForwardingPrefix + ':root'|. 1.695 + */ 1.696 + _onConnection: function DS_onConnection(aTransport, aForwardingPrefix, aNoRootActor = false) { 1.697 + let connID; 1.698 + if (aForwardingPrefix) { 1.699 + connID = aForwardingPrefix + ":"; 1.700 + } else { 1.701 + connID = "conn" + this._nextConnID++ + '.'; 1.702 + } 1.703 + let conn = new DebuggerServerConnection(connID, aTransport); 1.704 + this._connections[connID] = conn; 1.705 + 1.706 + // Create a root actor for the connection and send the hello packet. 1.707 + if (!aNoRootActor) { 1.708 + conn.rootActor = this.createRootActor(conn); 1.709 + if (aForwardingPrefix) 1.710 + conn.rootActor.actorID = aForwardingPrefix + ":root"; 1.711 + else 1.712 + conn.rootActor.actorID = "root"; 1.713 + conn.addActor(conn.rootActor); 1.714 + aTransport.send(conn.rootActor.sayHello()); 1.715 + } 1.716 + aTransport.ready(); 1.717 + 1.718 + this.emit("connectionchange", "opened", conn); 1.719 + return conn; 1.720 + }, 1.721 + 1.722 + /** 1.723 + * Remove the connection from the debugging server. 1.724 + */ 1.725 + _connectionClosed: function DS_connectionClosed(aConnection) { 1.726 + delete this._connections[aConnection.prefix]; 1.727 + this.emit("connectionchange", "closed", aConnection); 1.728 + }, 1.729 + 1.730 + // DebuggerServer extension API. 1.731 + 1.732 + /** 1.733 + * Registers handlers for new tab-scoped request types defined dynamically. 1.734 + * This is used for example by add-ons to augment the functionality of the tab 1.735 + * actor. Note that the name or actorPrefix of the request type is not allowed 1.736 + * to clash with existing protocol packet properties, like 'title', 'url' or 1.737 + * 'actor', since that would break the protocol. 1.738 + * 1.739 + * @param aFunction function 1.740 + * The constructor function for this request type. This expects to be 1.741 + * called as a constructor (i.e. with 'new'), and passed two 1.742 + * arguments: the DebuggerServerConnection, and the BrowserTabActor 1.743 + * with which it will be associated. 1.744 + * 1.745 + * @param aName string [optional] 1.746 + * The name of the new request type. If this is not present, the 1.747 + * actorPrefix property of the constructor prototype is used. 1.748 + */ 1.749 + addTabActor: function DS_addTabActor(aFunction, aName) { 1.750 + let name = aName ? aName : aFunction.prototype.actorPrefix; 1.751 + if (["title", "url", "actor"].indexOf(name) != -1) { 1.752 + throw Error(name + " is not allowed"); 1.753 + } 1.754 + if (DebuggerServer.tabActorFactories.hasOwnProperty(name)) { 1.755 + throw Error(name + " already exists"); 1.756 + } 1.757 + DebuggerServer.tabActorFactories[name] = aFunction; 1.758 + }, 1.759 + 1.760 + /** 1.761 + * Unregisters the handler for the specified tab-scoped request type. 1.762 + * This may be used for example by add-ons when shutting down or upgrading. 1.763 + * 1.764 + * @param aFunction function 1.765 + * The constructor function for this request type. 1.766 + */ 1.767 + removeTabActor: function DS_removeTabActor(aFunction) { 1.768 + for (let name in DebuggerServer.tabActorFactories) { 1.769 + let handler = DebuggerServer.tabActorFactories[name]; 1.770 + if (handler.name == aFunction.name) { 1.771 + delete DebuggerServer.tabActorFactories[name]; 1.772 + } 1.773 + } 1.774 + }, 1.775 + 1.776 + /** 1.777 + * Registers handlers for new browser-scoped request types defined 1.778 + * dynamically. This is used for example by add-ons to augment the 1.779 + * functionality of the root actor. Note that the name or actorPrefix of the 1.780 + * request type is not allowed to clash with existing protocol packet 1.781 + * properties, like 'from', 'tabs' or 'selected', since that would break the 1.782 + * protocol. 1.783 + * 1.784 + * @param aFunction function 1.785 + * The constructor function for this request type. This expects to be 1.786 + * called as a constructor (i.e. with 'new'), and passed two 1.787 + * arguments: the DebuggerServerConnection, and the BrowserRootActor 1.788 + * with which it will be associated. 1.789 + * 1.790 + * @param aName string [optional] 1.791 + * The name of the new request type. If this is not present, the 1.792 + * actorPrefix property of the constructor prototype is used. 1.793 + */ 1.794 + addGlobalActor: function DS_addGlobalActor(aFunction, aName) { 1.795 + let name = aName ? aName : aFunction.prototype.actorPrefix; 1.796 + if (["from", "tabs", "selected"].indexOf(name) != -1) { 1.797 + throw Error(name + " is not allowed"); 1.798 + } 1.799 + if (DebuggerServer.globalActorFactories.hasOwnProperty(name)) { 1.800 + throw Error(name + " already exists"); 1.801 + } 1.802 + DebuggerServer.globalActorFactories[name] = aFunction; 1.803 + }, 1.804 + 1.805 + /** 1.806 + * Unregisters the handler for the specified browser-scoped request type. 1.807 + * This may be used for example by add-ons when shutting down or upgrading. 1.808 + * 1.809 + * @param aFunction function 1.810 + * The constructor function for this request type. 1.811 + */ 1.812 + removeGlobalActor: function DS_removeGlobalActor(aFunction) { 1.813 + for (let name in DebuggerServer.globalActorFactories) { 1.814 + let handler = DebuggerServer.globalActorFactories[name]; 1.815 + if (handler.name == aFunction.name) { 1.816 + delete DebuggerServer.globalActorFactories[name]; 1.817 + } 1.818 + } 1.819 + } 1.820 +}; 1.821 + 1.822 +EventEmitter.decorate(DebuggerServer); 1.823 + 1.824 +if (this.exports) { 1.825 + exports.DebuggerServer = DebuggerServer; 1.826 +} 1.827 +// Needed on B2G (See header note) 1.828 +this.DebuggerServer = DebuggerServer; 1.829 + 1.830 +// Export ActorPool for requirers of main.js 1.831 +if (this.exports) { 1.832 + exports.ActorPool = ActorPool; 1.833 +} 1.834 + 1.835 +/** 1.836 + * Creates a DebuggerServerConnection. 1.837 + * 1.838 + * Represents a connection to this debugging global from a client. 1.839 + * Manages a set of actors and actor pools, allocates actor ids, and 1.840 + * handles incoming requests. 1.841 + * 1.842 + * @param aPrefix string 1.843 + * All actor IDs created by this connection should be prefixed 1.844 + * with aPrefix. 1.845 + * @param aTransport transport 1.846 + * Packet transport for the debugging protocol. 1.847 + */ 1.848 +function DebuggerServerConnection(aPrefix, aTransport) 1.849 +{ 1.850 + this._prefix = aPrefix; 1.851 + this._transport = aTransport; 1.852 + this._transport.hooks = this; 1.853 + this._nextID = 1; 1.854 + 1.855 + this._actorPool = new ActorPool(this); 1.856 + this._extraPools = []; 1.857 + 1.858 + // Responses to a given actor must be returned the the client 1.859 + // in the same order as the requests that they're replying to, but 1.860 + // Implementations might finish serving requests in a different 1.861 + // order. To keep things in order we generate a promise for each 1.862 + // request, chained to the promise for the request before it. 1.863 + // This map stores the latest request promise in the chain, keyed 1.864 + // by an actor ID string. 1.865 + this._actorResponses = new Map; 1.866 + 1.867 + /* 1.868 + * We can forward packets to other servers, if the actors on that server 1.869 + * all use a distinct prefix on their names. This is a map from prefixes 1.870 + * to transports: it maps a prefix P to a transport T if T conveys 1.871 + * packets to the server whose actors' names all begin with P + ":". 1.872 + */ 1.873 + this._forwardingPrefixes = new Map; 1.874 +} 1.875 + 1.876 +DebuggerServerConnection.prototype = { 1.877 + _prefix: null, 1.878 + get prefix() { return this._prefix }, 1.879 + 1.880 + _transport: null, 1.881 + get transport() { return this._transport }, 1.882 + 1.883 + close: function() { 1.884 + this._transport.close(); 1.885 + }, 1.886 + 1.887 + send: function DSC_send(aPacket) { 1.888 + this.transport.send(aPacket); 1.889 + }, 1.890 + 1.891 + allocID: function DSC_allocID(aPrefix) { 1.892 + return this.prefix + (aPrefix || '') + this._nextID++; 1.893 + }, 1.894 + 1.895 + /** 1.896 + * Add a map of actor IDs to the connection. 1.897 + */ 1.898 + addActorPool: function DSC_addActorPool(aActorPool) { 1.899 + this._extraPools.push(aActorPool); 1.900 + }, 1.901 + 1.902 + /** 1.903 + * Remove a previously-added pool of actors to the connection. 1.904 + * 1.905 + * @param ActorPool aActorPool 1.906 + * The ActorPool instance you want to remove. 1.907 + * @param boolean aNoCleanup [optional] 1.908 + * True if you don't want to disconnect each actor from the pool, false 1.909 + * otherwise. 1.910 + */ 1.911 + removeActorPool: function DSC_removeActorPool(aActorPool, aNoCleanup) { 1.912 + let index = this._extraPools.lastIndexOf(aActorPool); 1.913 + if (index > -1) { 1.914 + let pool = this._extraPools.splice(index, 1); 1.915 + if (!aNoCleanup) { 1.916 + pool.map(function(p) { p.cleanup(); }); 1.917 + } 1.918 + } 1.919 + }, 1.920 + 1.921 + /** 1.922 + * Add an actor to the default actor pool for this connection. 1.923 + */ 1.924 + addActor: function DSC_addActor(aActor) { 1.925 + this._actorPool.addActor(aActor); 1.926 + }, 1.927 + 1.928 + /** 1.929 + * Remove an actor to the default actor pool for this connection. 1.930 + */ 1.931 + removeActor: function DSC_removeActor(aActor) { 1.932 + this._actorPool.removeActor(aActor); 1.933 + }, 1.934 + 1.935 + /** 1.936 + * Match the api expected by the protocol library. 1.937 + */ 1.938 + unmanage: function(aActor) { 1.939 + return this.removeActor(aActor); 1.940 + }, 1.941 + 1.942 + /** 1.943 + * Look up an actor implementation for an actorID. Will search 1.944 + * all the actor pools registered with the connection. 1.945 + * 1.946 + * @param aActorID string 1.947 + * Actor ID to look up. 1.948 + */ 1.949 + getActor: function DSC_getActor(aActorID) { 1.950 + let pool = this.poolFor(aActorID); 1.951 + if (pool) { 1.952 + return pool.get(aActorID); 1.953 + } 1.954 + 1.955 + if (aActorID === "root") { 1.956 + return this.rootActor; 1.957 + } 1.958 + 1.959 + return null; 1.960 + }, 1.961 + 1.962 + poolFor: function DSC_actorPool(aActorID) { 1.963 + if (this._actorPool && this._actorPool.has(aActorID)) { 1.964 + return this._actorPool; 1.965 + } 1.966 + 1.967 + for (let pool of this._extraPools) { 1.968 + if (pool.has(aActorID)) { 1.969 + return pool; 1.970 + } 1.971 + } 1.972 + return null; 1.973 + }, 1.974 + 1.975 + _unknownError: function DSC__unknownError(aPrefix, aError) { 1.976 + let errorString = aPrefix + ": " + DevToolsUtils.safeErrorString(aError); 1.977 + Cu.reportError(errorString); 1.978 + dumpn(errorString); 1.979 + return { 1.980 + error: "unknownError", 1.981 + message: errorString 1.982 + }; 1.983 + }, 1.984 + 1.985 + /** 1.986 + * Passes a set of options to the BrowserAddonActors for the given ID. 1.987 + * 1.988 + * @param aId string 1.989 + * The ID of the add-on to pass the options to 1.990 + * @param aOptions object 1.991 + * The options. 1.992 + * @return a promise that will be resolved when complete. 1.993 + */ 1.994 + setAddonOptions: function DSC_setAddonOptions(aId, aOptions) { 1.995 + let addonList = this.rootActor._parameters.addonList; 1.996 + if (!addonList) { 1.997 + return resolve(); 1.998 + } 1.999 + return addonList.getList().then((addonActors) => { 1.1000 + for (let actor of addonActors) { 1.1001 + if (actor.id != aId) { 1.1002 + continue; 1.1003 + } 1.1004 + actor.setOptions(aOptions); 1.1005 + return; 1.1006 + } 1.1007 + }); 1.1008 + }, 1.1009 + 1.1010 + /* Forwarding packets to other transports based on actor name prefixes. */ 1.1011 + 1.1012 + /* 1.1013 + * Arrange to forward packets to another server. This is how we 1.1014 + * forward debugging connections to child processes. 1.1015 + * 1.1016 + * If we receive a packet for an actor whose name begins with |aPrefix| 1.1017 + * followed by ':', then we will forward that packet to |aTransport|. 1.1018 + * 1.1019 + * This overrides any prior forwarding for |aPrefix|. 1.1020 + * 1.1021 + * @param aPrefix string 1.1022 + * The actor name prefix, not including the ':'. 1.1023 + * @param aTransport object 1.1024 + * A packet transport to which we should forward packets to actors 1.1025 + * whose names begin with |(aPrefix + ':').| 1.1026 + */ 1.1027 + setForwarding: function(aPrefix, aTransport) { 1.1028 + this._forwardingPrefixes.set(aPrefix, aTransport); 1.1029 + }, 1.1030 + 1.1031 + /* 1.1032 + * Stop forwarding messages to actors whose names begin with 1.1033 + * |aPrefix+':'|. Such messages will now elicit 'noSuchActor' errors. 1.1034 + */ 1.1035 + cancelForwarding: function(aPrefix) { 1.1036 + this._forwardingPrefixes.delete(aPrefix); 1.1037 + }, 1.1038 + 1.1039 + // Transport hooks. 1.1040 + 1.1041 + /** 1.1042 + * Called by DebuggerTransport to dispatch incoming packets as appropriate. 1.1043 + * 1.1044 + * @param aPacket object 1.1045 + * The incoming packet. 1.1046 + */ 1.1047 + onPacket: function DSC_onPacket(aPacket) { 1.1048 + // If the actor's name begins with a prefix we've been asked to 1.1049 + // forward, do so. 1.1050 + // 1.1051 + // Note that the presence of a prefix alone doesn't indicate that 1.1052 + // forwarding is needed: in DebuggerServerConnection instances in child 1.1053 + // processes, every actor has a prefixed name. 1.1054 + if (this._forwardingPrefixes.size > 0) { 1.1055 + let colon = aPacket.to.indexOf(':'); 1.1056 + if (colon >= 0) { 1.1057 + let forwardTo = this._forwardingPrefixes.get(aPacket.to.substring(0, colon)); 1.1058 + if (forwardTo) { 1.1059 + forwardTo.send(aPacket); 1.1060 + return; 1.1061 + } 1.1062 + } 1.1063 + } 1.1064 + 1.1065 + let actor = this.getActor(aPacket.to); 1.1066 + if (!actor) { 1.1067 + this.transport.send({ from: aPacket.to ? aPacket.to : "root", 1.1068 + error: "noSuchActor", 1.1069 + message: "No such actor for ID: " + aPacket.to }); 1.1070 + return; 1.1071 + } 1.1072 + 1.1073 + // Dyamically-loaded actors have to be created lazily. 1.1074 + if (typeof actor == "function") { 1.1075 + let instance; 1.1076 + try { 1.1077 + instance = new actor(); 1.1078 + } catch (e) { 1.1079 + this.transport.send(this._unknownError( 1.1080 + "Error occurred while creating actor '" + actor.name, 1.1081 + e)); 1.1082 + } 1.1083 + instance.parentID = actor.parentID; 1.1084 + // We want the newly-constructed actor to completely replace the factory 1.1085 + // actor. Reusing the existing actor ID will make sure ActorPool.addActor 1.1086 + // does the right thing. 1.1087 + instance.actorID = actor.actorID; 1.1088 + actor.registeredPool.addActor(instance); 1.1089 + actor = instance; 1.1090 + } 1.1091 + 1.1092 + var ret = null; 1.1093 + 1.1094 + // handle "requestTypes" RDP request. 1.1095 + if (aPacket.type == "requestTypes") { 1.1096 + ret = { from: actor.actorID, requestTypes: Object.keys(actor.requestTypes) }; 1.1097 + } else if (actor.requestTypes && actor.requestTypes[aPacket.type]) { 1.1098 + // Dispatch the request to the actor. 1.1099 + try { 1.1100 + this.currentPacket = aPacket; 1.1101 + ret = actor.requestTypes[aPacket.type].bind(actor)(aPacket, this); 1.1102 + } catch(e) { 1.1103 + this.transport.send(this._unknownError( 1.1104 + "error occurred while processing '" + aPacket.type, 1.1105 + e)); 1.1106 + } finally { 1.1107 + this.currentPacket = undefined; 1.1108 + } 1.1109 + } else { 1.1110 + ret = { error: "unrecognizedPacketType", 1.1111 + message: ('Actor "' + actor.actorID + 1.1112 + '" does not recognize the packet type "' + 1.1113 + aPacket.type + '"') }; 1.1114 + } 1.1115 + 1.1116 + if (!ret) { 1.1117 + // This should become an error once we've converted every user 1.1118 + // of this to promises in bug 794078. 1.1119 + return; 1.1120 + } 1.1121 + 1.1122 + let pendingResponse = this._actorResponses.get(actor.actorID) || resolve(null); 1.1123 + let response = pendingResponse.then(() => { 1.1124 + return ret; 1.1125 + }).then(aResponse => { 1.1126 + if (!aResponse.from) { 1.1127 + aResponse.from = aPacket.to; 1.1128 + } 1.1129 + this.transport.send(aResponse); 1.1130 + }).then(null, (e) => { 1.1131 + let errorPacket = this._unknownError( 1.1132 + "error occurred while processing '" + aPacket.type, 1.1133 + e); 1.1134 + errorPacket.from = aPacket.to; 1.1135 + this.transport.send(errorPacket); 1.1136 + }); 1.1137 + 1.1138 + this._actorResponses.set(actor.actorID, response); 1.1139 + }, 1.1140 + 1.1141 + /** 1.1142 + * Called by DebuggerTransport when the underlying stream is closed. 1.1143 + * 1.1144 + * @param aStatus nsresult 1.1145 + * The status code that corresponds to the reason for closing 1.1146 + * the stream. 1.1147 + */ 1.1148 + onClosed: function DSC_onClosed(aStatus) { 1.1149 + dumpn("Cleaning up connection."); 1.1150 + if (!this._actorPool) { 1.1151 + // Ignore this call if the connection is already closed. 1.1152 + return; 1.1153 + } 1.1154 + events.emit(this, "closed", aStatus); 1.1155 + 1.1156 + this._actorPool.cleanup(); 1.1157 + this._actorPool = null; 1.1158 + this._extraPools.map(function(p) { p.cleanup(); }); 1.1159 + this._extraPools = null; 1.1160 + 1.1161 + DebuggerServer._connectionClosed(this); 1.1162 + }, 1.1163 + 1.1164 + /* 1.1165 + * Debugging helper for inspecting the state of the actor pools. 1.1166 + */ 1.1167 + _dumpPools: function DSC_dumpPools() { 1.1168 + dumpn("/-------------------- dumping pools:"); 1.1169 + if (this._actorPool) { 1.1170 + dumpn("--------------------- actorPool actors: " + 1.1171 + uneval(Object.keys(this._actorPool._actors))); 1.1172 + } 1.1173 + for each (let pool in this._extraPools) 1.1174 + dumpn("--------------------- extraPool actors: " + 1.1175 + uneval(Object.keys(pool._actors))); 1.1176 + }, 1.1177 + 1.1178 + /* 1.1179 + * Debugging helper for inspecting the state of an actor pool. 1.1180 + */ 1.1181 + _dumpPool: function DSC_dumpPools(aPool) { 1.1182 + dumpn("/-------------------- dumping pool:"); 1.1183 + dumpn("--------------------- actorPool actors: " + 1.1184 + uneval(Object.keys(aPool._actors))); 1.1185 + } 1.1186 +}; 1.1187 + 1.1188 +/** 1.1189 + * Localization convenience methods. 1.1190 + */ 1.1191 +let L10N = { 1.1192 + 1.1193 + /** 1.1194 + * L10N shortcut function. 1.1195 + * 1.1196 + * @param string aName 1.1197 + * @return string 1.1198 + */ 1.1199 + getStr: function L10N_getStr(aName) { 1.1200 + return this.stringBundle.GetStringFromName(aName); 1.1201 + } 1.1202 +}; 1.1203 + 1.1204 +XPCOMUtils.defineLazyGetter(L10N, "stringBundle", function() { 1.1205 + return Services.strings.createBundle(DBG_STRINGS_URI); 1.1206 +});