toolkit/devtools/server/main.js

Thu, 22 Jan 2015 13:21:57 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Thu, 22 Jan 2015 13:21:57 +0100
branch
TOR_BUG_9701
changeset 15
b8a032363ba2
permissions
-rw-r--r--

Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6

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

mercurial