Bug 740803 - Put the debugger in its own compartment again; r=dcamp, a=mfinkle
authorPanos Astithas <past@mozilla.com>
Sun, 22 Apr 2012 12:59:09 +0300
changeset 95522 b9914d4ebc923ab54a2921a5347d07d6ecc4903d
parent 95370 021f95903dedfddb63554dfca228f66a0d3f4b07
child 95523 906f3bb4c5aa1d9f200ffaef7cc8eb55a623fa42
push id886
push userlsblakk@mozilla.com
push dateMon, 04 Jun 2012 19:57:52 +0000
treeherdermozilla-beta@bbd8d5efd6d1 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersdcamp, mfinkle
bugs740803
milestone14.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 740803 - Put the debugger in its own compartment again; r=dcamp, a=mfinkle
toolkit/devtools/debugger/server/dbg-script-actors.js
toolkit/devtools/debugger/server/dbg-server.js
toolkit/devtools/debugger/server/dbg-server.jsm
toolkit/devtools/jar.mn
--- a/toolkit/devtools/debugger/server/dbg-script-actors.js
+++ b/toolkit/devtools/debugger/server/dbg-script-actors.js
@@ -82,17 +82,17 @@ ThreadActor.prototype = {
       this.conn.addActorPool(this._breakpointPool);
     }
     return this._breakpointPool;
   },
 
   _scripts: {},
 
   /**
-   * Add a debuggee global to the JSInspector.
+   * Add a debuggee global to the Debugger object.
    */
   addDebuggee: function TA_addDebuggee(aGlobal) {
     // Use the inspector xpcom component to turn on debugging
     // for aGlobal's compartment.  Ideally this won't be necessary
     // medium- to long-term, and will be managed by the engine
     // instead.
 
     if (!this._dbg) {
copy from toolkit/devtools/debugger/server/dbg-server.jsm
copy to toolkit/devtools/debugger/server/dbg-server.js
--- a/toolkit/devtools/debugger/server/dbg-server.jsm
+++ b/toolkit/devtools/debugger/server/dbg-server.js
@@ -43,34 +43,19 @@
  * debugging global.
  */
 
 const Ci = Components.interfaces;
 const Cc = Components.classes;
 const CC = Components.Constructor;
 const Cu = Components.utils;
 
-var EXPORTED_SYMBOLS = ["DebuggerServer"];
-
 Cu.import("resource://gre/modules/Services.jsm");
 let wantLogging = Services.prefs.getBoolPref("devtools.debugger.log");
 
-function loadSubScript(aURL)
-{
-  try {
-    let loader = Components.classes["@mozilla.org/moz/jssubscript-loader;1"]
-      .getService(Components.interfaces.mozIJSSubScriptLoader);
-    loader.loadSubScript(aURL, this);
-  } catch(e) {
-    dumpn("Error loading: " + aURL + ": " + e + " - " + e.stack + "\n");
-
-    throw e;
-  }
-}
-
 function dumpn(str) {
   if (wantLogging) {
     dump("DBG-SERVER: " + str + "\n");
   }
 }
 
 function dbg_assert(cond, e) {
   if (!cond) {
@@ -96,17 +81,23 @@ var DebuggerServer = {
   /**
    * Initialize the debugger server.
    */
   init: function DH_init() {
     if (this.initialized) {
       return;
     }
 
-    Cu.import("resource://gre/modules/jsdebugger.jsm");
+    // Hack: Merely loading jsdebugger.jsm will not work, because it will load
+    // in the chrome compartment, and then we'd get a cross-compartment wrapper
+    // of that. The Debugger object must be created in the sandbox compartment,
+    // that is, this file's compartment.
+    const init = Cc["@mozilla.org/jsdebugger;1"].createInstance(Ci.IJSDebugger);
+    init.addClass();  // adds global variable Debugger to this global.
+
     this.xpcInspector = Cc["@mozilla.org/jsinspector;1"].getService(Ci.nsIJSInspector);
     this.initTransport();
     this.addActors("chrome://global/content/devtools/dbg-script-actors.js");
   },
 
   /**
    * Initialize the debugger server's transport variables.  This can be
    * in place of init() for cases where the jsdebugger isn't needed.
--- a/toolkit/devtools/debugger/server/dbg-server.jsm
+++ b/toolkit/devtools/debugger/server/dbg-server.jsm
@@ -34,461 +34,43 @@
  * and other provisions required by the GPL or the LGPL. If you do not delete
  * the provisions above, a recipient may use your version of this file under
  * the terms of any one of the MPL, the GPL or the LGPL.
  *
  * ***** END LICENSE BLOCK ***** */
 
 "use strict";
 /**
- * Toolkit glue for the remote debugging protocol, loaded into the
- * debugging global.
+ * Loads the remote debugging protocol code into a sandbox, in order to
+ * shield it from the debuggee. This way, when debugging chrome globals,
+ * debugger and debuggee will be in separate compartments.
  */
 
 const Ci = Components.interfaces;
 const Cc = Components.classes;
-const CC = Components.Constructor;
 const Cu = Components.utils;
 
 var EXPORTED_SYMBOLS = ["DebuggerServer"];
 
-Cu.import("resource://gre/modules/Services.jsm");
-let wantLogging = Services.prefs.getBoolPref("devtools.debugger.log");
-
 function loadSubScript(aURL)
 {
   try {
-    let loader = Components.classes["@mozilla.org/moz/jssubscript-loader;1"]
-      .getService(Components.interfaces.mozIJSSubScriptLoader);
+    let loader = Cc["@mozilla.org/moz/jssubscript-loader;1"]
+      .getService(Ci.mozIJSSubScriptLoader);
     loader.loadSubScript(aURL, this);
   } catch(e) {
-    dumpn("Error loading: " + aURL + ": " + e + " - " + e.stack + "\n");
+    dump("Error loading: " + aURL + ": " + e + " - " + e.stack + "\n");
 
     throw e;
   }
 }
 
-function dumpn(str) {
-  if (wantLogging) {
-    dump("DBG-SERVER: " + str + "\n");
-  }
-}
-
-function dbg_assert(cond, e) {
-  if (!cond) {
-    return e;
-  }
-}
-
-loadSubScript.call(this, "chrome://global/content/devtools/dbg-transport.js");
-
-// XPCOM constructors
-const ServerSocket = CC("@mozilla.org/network/server-socket;1",
-                        "nsIServerSocket",
-                        "init");
-
-/***
- * Public API
- */
-var DebuggerServer = {
-  _listener: null,
-  _transportInitialized: false,
-  xpcInspector: null,
-
-  /**
-   * Initialize the debugger server.
-   */
-  init: function DH_init() {
-    if (this.initialized) {
-      return;
-    }
-
-    Cu.import("resource://gre/modules/jsdebugger.jsm");
-    this.xpcInspector = Cc["@mozilla.org/jsinspector;1"].getService(Ci.nsIJSInspector);
-    this.initTransport();
-    this.addActors("chrome://global/content/devtools/dbg-script-actors.js");
-  },
-
-  /**
-   * Initialize the debugger server's transport variables.  This can be
-   * in place of init() for cases where the jsdebugger isn't needed.
-   */
-  initTransport: function DH_initTransport() {
-    if (this._transportInitialized) {
-      return;
-    }
-
-    this._connections = {};
-    this._nextConnID = 0;
-    this._transportInitialized = true;
-  },
-
-  get initialized() { return !!this.xpcInspector; },
-
-  /**
-   * Load a subscript into the debugging global.
-   *
-   * @param aURL string A url that will be loaded as a subscript into the
-   *        debugging global.  The user must load at least one script
-   *        that implements a createRootActor() function to create the
-   *        server's root actor.
-   */
-  addActors: function DH_addActors(aURL) {
-    loadSubScript.call(this, aURL);
-  },
-
-  /**
-   * Install Firefox-specific actors.
-   */
-  addBrowserActors: function DH_addBrowserActors() {
-    this.addActors("chrome://global/content/devtools/dbg-browser-actors.js");
-  },
-
-  /**
-   * Listens on the given port for remote debugger connections.
-   *
-   * @param aPort int
-   *        The port to listen on.
-   * @param aLocalOnly bool
-   *        If true, server will listen on the loopback device.
-   */
-  openListener: function DH_openListener(aPort, aLocalOnly) {
-    this._checkInit();
-
-    if (this._listener) {
-      throw "Debugging listener already open.";
-    }
-
-    try {
-      let socket = new ServerSocket(aPort, aLocalOnly, 4);
-      socket.asyncListen(this);
-      this._listener = socket;
-    } catch (e) {
-      dumpn("Could not start debugging listener on port " + aPort + ": " + e);
-      throw Cr.NS_ERROR_NOT_AVAILABLE;
-    }
-
-    return true;
-  },
+Cu.import("resource:///modules/devtools/dbg-client.jsm");
 
-  /**
-   * Close a previously-opened TCP listener.
-   */
-  closeListener: function DH_closeListener() {
-    this._checkInit();
-
-    if (!this._listener) {
-      return false;
-    }
-
-    this._listener.close();
-    this._listener = null;
-
-    return true;
-  },
-
-  /**
-   * Creates a new connection to the local debugger speaking over an
-   * nsIPipe.
-   *
-   * @returns a client-side DebuggerTransport for communicating with
-   *          the newly-created connection.
-   */
-  connectPipe: function DH_connectPipe() {
-    this._checkInit();
-
-    let toServer = Cc["@mozilla.org/pipe;1"].createInstance(Ci.nsIPipe);
-    toServer.init(true, true, 0, 0, null);
-    let toClient = Cc["@mozilla.org/pipe;1"].createInstance(Ci.nsIPipe);
-    toClient.init(true, true, 0, 0, null);
-
-    let serverTransport = new DebuggerTransport(toServer.inputStream,
-                                                toClient.outputStream);
-    this._onConnection(serverTransport);
-
-    return new DebuggerTransport(toClient.inputStream, toServer.outputStream);
-  },
-
-
-  // nsIServerSocketListener implementation
-
-  onSocketAccepted: function DH_onSocketAccepted(aSocket, aTransport) {
-    dumpn("New debugging connection on " + aTransport.host + ":" + aTransport.port);
-
-    try {
-      let input = aTransport.openInputStream(0, 0, 0);
-      let output = aTransport.openOutputStream(0, 0, 0);
-      let transport = new DebuggerTransport(input, output);
-      DebuggerServer._onConnection(transport);
-    } catch (e) {
-      dumpn("Couldn't initialize connection: " + e + " - " + e.stack);
-    }
-  },
-
-  onStopListening: function DH_onStopListening() { },
-
-  /**
-   * Raises an exception if the server has not been properly initialized.
-   */
-  _checkInit: function DH_checkInit() {
-    if (!this._transportInitialized) {
-      throw "DebuggerServer has not been initialized.";
-    }
-
-    if (!this.createRootActor) {
-      throw "Use DebuggerServer.addActors() to add a root actor implementation.";
-    }
-  },
-
-  /**
-   * Create a new debugger connection for the given transport.  Called
-   * after connectPipe() or after an incoming socket connection.
-   */
-  _onConnection: function DH_onConnection(aTransport) {
-    let connID = "conn" + this._nextConnID++ + '.';
-    let conn = new DebuggerServerConnection(connID, aTransport);
-    this._connections[connID] = conn;
-
-    // Create a root actor for the connection and send the hello packet.
-    conn.rootActor = this.createRootActor(conn);
-    conn.addActor(conn.rootActor);
-    aTransport.send(conn.rootActor.sayHello());
-    aTransport.ready();
-  },
-
-  /**
-   * Remove the connection from the debugging server.
-   */
-  _connectionClosed: function DH_connectionClosed(aConnection) {
-    delete this._connections[aConnection.prefix];
-  }
-};
-
-/**
- * Construct an ActorPool.
- *
- * ActorPools are actorID -> actor mapping and storage.  These are
- * used to accumulate and quickly dispose of groups of actors that
- * share a lifetime.
- */
-function ActorPool(aConnection)
-{
-  this.conn = aConnection;
-  this._cleanups = {};
-  this._actors = {};
-}
+// Load the debugging server in a sandbox with its own compartment.
+var systemPrincipal = Cc["@mozilla.org/systemprincipal;1"]
+                      .createInstance(Ci.nsIPrincipal);
 
-ActorPool.prototype = {
-  /**
-   * Add an actor to the actor pool.  If the actor doesn't have an ID,
-   * allocate one from the connection.
-   *
-   * @param aActor object
-   *        The actor implementation.  If the object has a
-   *        'disconnected' property, it will be called when the actor
-   *        pool is cleaned up.
-   */
-  addActor: function AP_addActor(aActor) {
-    aActor.conn = this.conn;
-    if (!aActor.actorID) {
-      aActor.actorID = this.conn.allocID(aActor.actorPrefix || undefined);
-    }
-
-    if (aActor.registeredPool) {
-      aActor.registeredPool.removeActor(aActor);
-    }
-    aActor.registeredPool = this;
-
-    this._actors[aActor.actorID] = aActor;
-    if (aActor.disconnect) {
-      this._cleanups[aActor.actorID] = aActor;
-    }
-  },
-
-  get: function AP_get(aActorID) {
-    return this._actors[aActorID];
-  },
-
-  has: function AP_has(aActorID) {
-    return aActorID in this._actors;
-  },
-
-  /**
-   * Remove an actor from the actor pool.
-   */
-  removeActor: function AP_remove(aActorID) {
-    delete this._actors[aActorID];
-    delete this._cleanups[aActorID];
-  },
-
-  /**
-   * Run all cleanups previously registered with addCleanup.
-   */
-  cleanup: function AP_cleanup() {
-    for each (let actor in this._cleanups) {
-      actor.disconnect();
-    }
-    this._cleanups = {};
-  }
-}
-
-/**
- * Creates a DebuggerServerConnection.
- *
- * Represents a connection to this debugging global from a client.
- * Manages a set of actors and actor pools, allocates actor ids, and
- * handles incoming requests.
- *
- * @param aPrefix string
- *        All actor IDs created by this connection should be prefixed
- *        with aPrefix.
- * @param aTransport transport
- *        Packet transport for the debugging protocol.
- */
-function DebuggerServerConnection(aPrefix, aTransport)
-{
-  this._prefix = aPrefix;
-  this._transport = aTransport;
-  this._transport.hooks = this;
-  this._nextID = 1;
-
-  this._actorPool = new ActorPool(this);
-  this._extraPools = [];
-}
-
-DebuggerServerConnection.prototype = {
-  _prefix: null,
-  get prefix() { return this._prefix },
-
-  _transport: null,
-  get transport() { return this._transport },
-
-  send: function DSC_send(aPacket) {
-    this.transport.send(aPacket);
-  },
-
-  allocID: function DSC_allocID(aPrefix) {
-    return this.prefix + (aPrefix || '') + this._nextID++;
-  },
-
-  /**
-   * Add a map of actor IDs to the connection.
-   */
-  addActorPool: function DSC_addActorPool(aActorPool) {
-    this._extraPools.push(aActorPool);
-  },
-
-  /**
-   * Remove a previously-added pool of actors to the connection.
-   */
-  removeActorPool: function DSC_removeActorPool(aActorPool) {
-    let index = this._extraPools.splice(this._extraPools.lastIndexOf(aActorPool), 1);
-  },
+var gGlobal = Cu.Sandbox(systemPrincipal);
+gGlobal.importFunction(loadSubScript);
+gGlobal.loadSubScript("chrome://global/content/devtools/dbg-server.js");
 
-  /**
-   * Add an actor to the default actor pool for this connection.
-   */
-  addActor: function DSC_addActor(aActor) {
-    this._actorPool.addActor(aActor);
-  },
-
-  /**
-   * Remove an actor to the default actor pool for this connection.
-   */
-  removeActor: function DSC_removeActor(aActor) {
-    this._actorPool.removeActor(aActor);
-  },
-
-  /**
-   * Add a cleanup to the default actor pool for this connection.
-   */
-  addCleanup: function DSC_addCleanup(aCleanup) {
-    this._actorPool.addCleanup(aCleanup);
-  },
-
-  /**
-   * Look up an actor implementation for an actorID.  Will search
-   * all the actor pools registered with the connection.
-   *
-   * @param aActorID string
-   *        Actor ID to look up.
-   */
-  getActor: function DSC_getActor(aActorID) {
-    if (this._actorPool.has(aActorID)) {
-      return this._actorPool.get(aActorID);
-    }
-
-    for each (let pool in this._extraPools) {
-      if (pool.has(aActorID)) {
-        return pool.get(aActorID);
-      }
-    }
-
-    if (aActorID === "root") {
-      return this.rootActor;
-    }
-
-    return null;
-  },
-
-  // Transport hooks.
-
-  /**
-   * Called by DebuggerTransport to dispatch incoming packets as appropriate.
-   *
-   * @param aPacket object
-   *        The incoming packet.
-   */
-  onPacket: function DSC_onPacket(aPacket) {
-    let actor = this.getActor(aPacket.to);
-    if (!actor) {
-      this.transport.send({ from: aPacket.to ? aPacket.to : "root",
-                            error: "noSuchActor" });
-      return;
-    }
-
-    var ret = null;
-
-    // Dispatch the request to the actor.
-    if (actor.requestTypes && actor.requestTypes[aPacket.type]) {
-      try {
-        ret = actor.requestTypes[aPacket.type].bind(actor)(aPacket);
-      } catch(e) {
-        Cu.reportError(e);
-        ret = { error: "unknownError",
-                message: "An unknown error has occurred while processing request." };
-      }
-    } else {
-      ret = { error: "unrecognizedPacketType",
-              message: 'Actor "' + actor.actorID + '" does not recognize the packet type "' + aPacket.type + '"' };
-    }
-
-    if (!ret) {
-      // XXX: The actor wasn't ready to reply yet, don't process new
-      // requests until it does.
-      return;
-    }
-
-    if (!ret.from) {
-      ret.from = aPacket.to;
-    }
-
-    this.transport.send(ret);
-  },
-
-  /**
-   * Called by DebuggerTransport when the underlying stream is closed.
-   *
-   * @param aStatus nsresult
-   *        The status code that corresponds to the reason for closing
-   *        the stream.
-   */
-  onClosed: function DSC_onClosed(aStatus) {
-    dumpn("Cleaning up connection.");
-
-    this._actorPool.cleanup();
-    this._actorPool = null;
-    this._extraPools.map(function(p) { p.cleanup(); });
-    this._extraPools = null;
-
-    DebuggerServer._connectionClosed(this);
-  }
-};
+var DebuggerServer = gGlobal.DebuggerServer;
--- a/toolkit/devtools/jar.mn
+++ b/toolkit/devtools/jar.mn
@@ -1,4 +1,5 @@
 toolkit.jar:
   content/global/devtools/dbg-transport.js        (debugger/dbg-transport.js)
+  content/global/devtools/dbg-server.js        		(debugger/server/dbg-server.js)
   content/global/devtools/dbg-script-actors.js    (debugger/server/dbg-script-actors.js)
   content/global/devtools/dbg-browser-actors.js   (debugger/server/dbg-browser-actors.js)