toolkit/devtools/server/main.js

Sat, 03 Jan 2015 20:18:00 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Sat, 03 Jan 2015 20:18:00 +0100
branch
TOR_BUG_3246
changeset 7
129ffea94266
permissions
-rw-r--r--

Conditionally enable double key logic according to:
private browsing mode or privacy.thirdparty.isolate preference and
implement in GetCookieStringCommon and FindCookie where it counts...
With some reservations of how to convince FindCookie users to test
condition and pass a nullptr when disabling double key logic.

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

mercurial