toolkit/devtools/server/main.js

changeset 0
6474c204b198
     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 +});

mercurial