Bug 817580 - Expose tab actors for apps in child processes. r=jimb
authorAlexandre Poirot <poirot.alex@gmail.com>
Wed, 24 Jul 2013 11:28:32 -0400
changeset 139803 738b7898b6c79352aa9a918c1fae0c5dd5c3531e
parent 139802 fedda0ce50c8153b3ed000c42debdcfc93339ecf
child 139804 36323782eaa34b5638fc03fedab8aa0317259abc
push idunknown
push userunknown
push dateunknown
reviewersjimb
bugs817580
milestone25.0a1
Bug 817580 - Expose tab actors for apps in child processes. r=jimb
b2g/chrome/content/settings.js
b2g/chrome/content/shell.js
toolkit/devtools/server/actors/childtab.js
toolkit/devtools/server/actors/webapps.js
toolkit/devtools/server/actors/webconsole.js
toolkit/devtools/server/child.js
toolkit/devtools/server/dbg-server.jsm
toolkit/devtools/server/main.js
toolkit/devtools/webconsole/WebConsoleUtils.jsm
--- a/b2g/chrome/content/settings.js
+++ b/b2g/chrome/content/settings.js
@@ -193,17 +193,21 @@ Components.utils.import('resource://gre/
   lock.set('deviceinfo.product_model', product_model, null, null);
 })();
 
 // =================== Debugger ====================
 SettingsListener.observe('devtools.debugger.remote-enabled', false, function(value) {
   Services.prefs.setBoolPref('devtools.debugger.remote-enabled', value);
   // This preference is consulted during startup
   Services.prefs.savePrefFile(null);
-  value ? RemoteDebugger.start() : RemoteDebugger.stop();
+  try {
+    value ? RemoteDebugger.start() : RemoteDebugger.stop();
+  } catch(e) {
+    dump("Error while initializing devtools: " + e + "\n" + e.stack + "\n");
+  }
 
 #ifdef MOZ_WIDGET_GONK
   let enableAdb = value;
 
   try {
     if (Services.prefs.getBoolPref('marionette.defaultPrefs.enabled')) {
       // Marionette is enabled. Force adb on, since marionette requires remote
       // debugging to be disabled (we don't want adb to track the remote debugger
--- a/b2g/chrome/content/shell.js
+++ b/b2g/chrome/content/shell.js
@@ -972,22 +972,20 @@ let RemoteDebugger = {
   },
 
   // Start the debugger server.
   start: function debugger_start() {
     if (!DebuggerServer.initialized) {
       // Ask for remote connections.
       DebuggerServer.init(this.prompt.bind(this));
       DebuggerServer.addActors("resource://gre/modules/devtools/server/actors/webbrowser.js");
-#ifndef MOZ_WIDGET_GONK
       DebuggerServer.addActors("resource://gre/modules/devtools/server/actors/script.js");
       DebuggerServer.addGlobalActor(DebuggerServer.ChromeDebuggerActor, "chromeDebugger");
       DebuggerServer.addActors("resource://gre/modules/devtools/server/actors/webconsole.js");
       DebuggerServer.addActors("resource://gre/modules/devtools/server/actors/gcli.js");
-#endif
       if ("nsIProfiler" in Ci) {
         DebuggerServer.addActors("resource://gre/modules/devtools/server/actors/profiler.js");
       }
       DebuggerServer.addActors("resource://gre/modules/devtools/server/actors/styleeditor.js");
       DebuggerServer.addActors('chrome://browser/content/dbg-browser-actors.js');
       DebuggerServer.addActors("resource://gre/modules/devtools/server/actors/webapps.js");
     }
 
new file mode 100644
--- /dev/null
+++ b/toolkit/devtools/server/actors/childtab.js
@@ -0,0 +1,78 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+/**
+ * Tab actor for documents living in a child process.
+ *
+ * Depends on BrowserTabActor, defined in webbrowser.js actor.
+ */
+
+/**
+ * Creates a tab actor for handling requests to the single tab, like
+ * attaching and detaching. ContentTabActor respects the actor factories
+ * registered with DebuggerServer.addTabActor.
+ *
+ * @param connection DebuggerServerConnection
+ *        The conection to the client.
+ * @param browser browser
+ *        The browser instance that contains this tab.
+ */
+function ContentTabActor(connection, browser)
+{
+  BrowserTabActor.call(this, connection, browser);
+}
+
+ContentTabActor.prototype = Object.create(BrowserTabActor.prototype);
+
+ContentTabActor.prototype.constructor = ContentTabActor;
+
+Object.defineProperty(ContentTabActor.prototype, "title", {
+  get: function() {
+    return this.browser.title;
+  },
+  enumerable: true,
+  configurable: false
+});
+
+Object.defineProperty(ContentTabActor.prototype, "url", {
+  get: function() {
+    return this.browser.document.documentURI;
+  },
+  enumerable: true,
+  configurable: false
+});
+
+Object.defineProperty(ContentTabActor.prototype, "contentWindow", {
+  get: function() {
+    return this.browser;
+  },
+  enumerable: true,
+  configurable: false
+});
+
+// Override grip just to rename this._tabActorPool to this._tabActorPool2
+// in order to prevent it to be cleaned on detach.
+// We have to keep tab actors alive as we keep the ContentTabActor
+// alive after detach and reuse it for multiple debug sessions.
+ContentTabActor.prototype.grip = function () {
+  let response = {
+    'actor': this.actorID,
+    'title': this.title,
+    'url': this.url
+  };
+
+  // Walk over tab actors added by extensions and add them to a new ActorPool.
+  let actorPool = new ActorPool(this.conn);
+  this._createExtraActors(DebuggerServer.tabActorFactories, actorPool);
+  if (!actorPool.isEmpty()) {
+    this._tabActorPool2 = actorPool;
+    this.conn.addActorPool(this._tabActorPool2);
+  }
+
+  this._appendExtraActors(response);
+  return response;
+};
+
--- a/toolkit/devtools/server/actors/webapps.js
+++ b/toolkit/devtools/server/actors/webapps.js
@@ -3,16 +3,18 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
 let Cu = Components.utils;
 let Cc = Components.classes;
 let Ci = Components.interfaces;
 
+let promise;
+
 function debug(aMsg) {
   /*
   Cc["@mozilla.org/consoleservice;1"]
     .getService(Ci.nsIConsoleService)
     .logStringMessage("--*-- WebappsActor : " + aMsg);
   */
 }
 
@@ -24,17 +26,21 @@ function WebappsActor(aConnection) {
   debug("init");
   // Load actor dependencies lazily as this actor require extra environnement
   // preparation to work (like have a profile setup in xpcshell tests)
 
   Cu.import("resource://gre/modules/Webapps.jsm");
   Cu.import("resource://gre/modules/AppsUtils.jsm");
   Cu.import("resource://gre/modules/FileUtils.jsm");
   Cu.import('resource://gre/modules/Services.jsm');
-  let promise = Cu.import("resource://gre/modules/commonjs/sdk/core/promise.js").Promise;
+  promise = Cu.import("resource://gre/modules/commonjs/sdk/core/promise.js").Promise;
+
+  // Keep reference of already created app actors.
+  // key: app frame message manager, value: ContentTabActor's grip() value
+  this._appActorsMap = new Map();
 }
 
 WebappsActor.prototype = {
   actorPrefix: "webapps",
 
   _registerApp: function wa_actorRegisterApp(aApp, aId, aDir) {
     debug("registerApp");
     let reg = DOMApplicationRegistry;
@@ -370,24 +376,211 @@ WebappsActor.prototype = {
     if (!app) {
       return { error: "missingParameter",
                message: "No application for " + manifestURL };
     }
 
     reg.close(app);
 
     return {};
+  },
+
+  _appFrames: function () {
+    // Register the system app
+    let chromeWindow = Services.wm.getMostRecentWindow('navigator:browser');
+    let systemAppFrame = chromeWindow.shell.contentBrowser;
+    yield systemAppFrame;
+
+    // Register apps hosted in the system app. i.e. the homescreen, all regular
+    // apps and the keyboard.
+    // Bookmark apps and other system app internal frames like captive portal
+    // are also hosted in system app, but they are not using mozapp attribute.
+    let frames = systemAppFrame.contentDocument.querySelectorAll("iframe[mozapp]");
+    for (let i = 0; i < frames.length; i++) {
+      yield frames[i];
+    }
+  },
+
+  listRunningApps: function (aRequest) {
+    debug("listRunningApps\n");
+
+    let apps = [];
+
+    for each (let frame in this._appFrames()) {
+      let manifestURL = frame.getAttribute("mozapp");
+      apps.push(manifestURL);
+    }
+
+    return { apps: apps };
+  },
+
+  _connectToApp: function (aFrame) {
+    let defer = Promise.defer();
+
+    let mm = aFrame.QueryInterface(Ci.nsIFrameLoaderOwner).frameLoader.messageManager;
+    mm.loadFrameScript("resource://gre/modules/devtools/server/child.js", false);
+
+    let childTransport, prefix;
+
+    let onActorCreated = makeInfallible(function (msg) {
+      mm.removeMessageListener("debug:actor", onActorCreated);
+
+      dump("***** Got debug:actor\n");
+      let { actor, appId } = msg.json;
+      prefix = msg.json.prefix;
+
+      // Pipe Debugger message from/to parent/child via the message manager
+      childTransport = new ChildDebuggerTransport(mm, prefix);
+      childTransport.hooks = {
+        onPacket: this.conn.send.bind(this.conn),
+        onClosed: function () {}
+      };
+      childTransport.ready();
+
+      this.conn.setForwarding(prefix, childTransport);
+
+      debug("establishing forwarding for app with prefix " + prefix);
+
+      this._appActorsMap.set(mm, actor);
+
+      defer.resolve(actor);
+    }).bind(this);
+    mm.addMessageListener("debug:actor", onActorCreated);
+
+    let onMessageManagerDisconnect = makeInfallible(function (subject, topic, data) {
+      if (subject == mm) {
+        Services.obs.removeObserver(onMessageManagerDisconnect, topic);
+        if (childTransport) {
+          // If we have a child transport, the actor has already
+          // been created. We need to stop using this message manager.
+          childTransport.close();
+          this.conn.cancelForwarding(prefix);
+        } else {
+          // Otherwise, the app has been closed before the actor
+          // had a chance to be created, so we are not able to create
+          // the actor.
+          defer.resolve(null);
+        }
+        this._appActorsMap.delete(mm);
+      }
+    }).bind(this);
+    Services.obs.addObserver(onMessageManagerDisconnect,
+                             "message-manager-disconnect", false);
+
+    let prefixStart = this.conn.prefix + "child";
+    mm.sendAsyncMessage("debug:connect", { prefix: prefixStart });
+
+    return defer.promise;
+  },
+
+  getAppActor: function ({ manifestURL }) {
+    debug("getAppActor\n");
+
+    let appFrame = null;
+    for each (let frame in this._appFrames()) {
+      if (frame.getAttribute("mozapp") == manifestURL) {
+        appFrame = frame;
+        break;
+      }
+    }
+
+    if (!appFrame) {
+      return { error: "appNotFound",
+               message: "Unable to find any opened app whose manifest " +
+                        "is '" + manifestURL + "'" };
+    }
+
+    // Only create a new actor, if we haven't already
+    // instanciated one for this connection.
+    let mm = appFrame.QueryInterface(Ci.nsIFrameLoaderOwner)
+                     .frameLoader
+                     .messageManager;
+    let actor = this._appActorsMap.get(mm);
+    if (!actor) {
+      return this._connectToApp(appFrame)
+                 .then(function (actor) ({ actor: actor }));
+    }
+
+    return { actor: actor };
+  },
+
+  watchApps: function () {
+    this._framesByOrigin = {};
+    let chromeWindow = Services.wm.getMostRecentWindow('navigator:browser');
+    let systemAppFrame = chromeWindow.getContentWindow();
+    systemAppFrame.addEventListener("appwillopen", this);
+    systemAppFrame.addEventListener("appterminated", this);
+
+    return {};
+  },
+
+  unwatchApps: function () {
+    this._framesByOrigin = null;
+    let chromeWindow = Services.wm.getMostRecentWindow('navigator:browser');
+    let systemAppFrame = chromeWindow.getContentWindow();
+    systemAppFrame.removeEventListener("appwillopen", this);
+    systemAppFrame.removeEventListener("appterminated", this);
+
+    return {};
+  },
+
+  handleEvent: function (event) {
+    let frame;
+    let origin = event.detail.origin;
+    switch(event.type) {
+      case "appwillopen":
+        frame = event.target;
+        // Ignore the event if we already received an appwillopen for this app
+        // (appwillopen is also fired when the app has been moved to background
+        // and get back to foreground)
+        let mm = frame.QueryInterface(Ci.nsIFrameLoaderOwner)
+                         .frameLoader
+                         .messageManager;
+        if (this._appActorsMap.has(mm)) {
+          return;
+        }
+
+        // Workaround to be able to get the related frame on `appterminated`.
+        // `appterminated` event being dispatched by gaia only comes app origin
+        // whereas we need to get the its manifest URL, that we can fetch
+        // on the app frame.
+        this._framesByOrigin[origin] = frame;
+
+        this.conn.send({ from: this.actorID,
+                         type: "appOpen",
+                         manifestURL: frame.getAttribute("mozapp")
+                       });
+        break;
+
+      case "appterminated":
+        // Get the related app frame out of this event
+        // TODO: eventually fire the event on the frame or at least use
+        // manifestURL as key (and propagate manifestURL via event detail)
+        frame = this._framesByOrigin[origin];
+        delete this._framesByOrigin[origin];
+        if (frame) {
+          let manifestURL = frame.getAttribute("mozapp");
+          this.conn.send({ from: this.actorID,
+                           type: "appClose",
+                           manifestURL: manifestURL
+                         });
+        }
+        break;
+    }
   }
 };
 
 /**
  * The request types this actor can handle.
  */
 WebappsActor.prototype.requestTypes = {
   "install": WebappsActor.prototype.install,
   "getAll": WebappsActor.prototype.getAll,
   "launch": WebappsActor.prototype.launch,
   "close": WebappsActor.prototype.close,
-  "uninstall": WebappsActor.prototype.uninstall
+  "uninstall": WebappsActor.prototype.uninstall,
+  "listRunningApps": WebappsActor.prototype.listRunningApps,
+  "getAppActor": WebappsActor.prototype.getAppActor,
+  "watchApps": WebappsActor.prototype.watchApps,
+  "unwatchApps": WebappsActor.prototype.unwatchApps
 };
 
 DebuggerServer.addGlobalActor(WebappsActor, "webappsActor");
-
--- a/toolkit/devtools/server/actors/webconsole.js
+++ b/toolkit/devtools/server/actors/webconsole.js
@@ -49,28 +49,26 @@ XPCOMUtils.defineLazyModuleGetter(this, 
  *        The connection to the client, DebuggerServerConnection.
  * @param object [aParentActor]
  *        Optional, the parent actor.
  */
 function WebConsoleActor(aConnection, aParentActor)
 {
   this.conn = aConnection;
 
-  if (aParentActor instanceof BrowserTabActor &&
-      aParentActor.browser instanceof Ci.nsIDOMWindow) {
-    // B2G tab actor |this.browser| points to a DOM chrome window, not
+  if (aParentActor.browser instanceof Ci.nsIDOMWindow) {
+    // B2G tab actor |this.browser| points to a DOM window, not
     // a xul:browser element.
     //
-    // TODO: bug 802246 - b2g has only one tab actor, the shell.xul, which is
+    // TODO: bug 802246 - b2g has tab actor which is
     // not properly supported by the console actor - see bug for details.
     //
-    // Below we work around the problem: selecting the shell.xul tab actor
+    // Below we work around the problem: selecting a b2g tab actor
     // behaves as if the user picked the global console actor.
-    //this._window = aParentActor.browser;
-    this._window = Services.wm.getMostRecentWindow("navigator:browser");
+    this._window = aParentActor.browser;
     this._isGlobalActor = true;
   }
   else if (aParentActor instanceof BrowserTabActor &&
            aParentActor.browser instanceof Ci.nsIDOMElement) {
     // Firefox for desktop tab actor |this.browser| points to the xul:browser
     // element.
     this._window = aParentActor.browser.contentWindow;
   }
new file mode 100644
--- /dev/null
+++ b/toolkit/devtools/server/child.js
@@ -0,0 +1,34 @@
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/devtools/dbg-server.jsm");
+Cu.import("resource://gre/modules/devtools/DevToolsUtils.jsm");
+
+if (!DebuggerServer.initialized) {
+  DebuggerServer.init();
+}
+
+// In case of apps being loaded in parent process, DebuggerServer is already
+// initialized, but child specific actors are not registered.
+// Otherwise, for apps in child process, we need to load actors the first
+// time we load child.js
+DebuggerServer.addChildActors();
+
+let onConnect = DevToolsUtils.makeInfallible(function (msg) {
+  removeMessageListener("debug:connect", onConnect);
+
+  let mm = msg.target;
+
+  let prefix = msg.data.prefix + docShell.appId;
+
+  let conn = DebuggerServer.connectToParent(prefix, mm);
+
+  let actor = new DebuggerServer.ContentTabActor(conn, content);
+  let actorPool = new ActorPool(conn);
+  actorPool.addActor(actor);
+  conn.addActorPool(actorPool);
+
+  sendAsyncMessage("debug:actor", {actor: actor.grip(),
+                                   appId: docShell.appId,
+                                   prefix: prefix});
+});
+
+addMessageListener("debug:connect", onConnect);
--- a/toolkit/devtools/server/dbg-server.jsm
+++ b/toolkit/devtools/server/dbg-server.jsm
@@ -10,17 +10,17 @@
  * 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 Cu = Components.utils;
 
-this.EXPORTED_SYMBOLS = ["DebuggerServer"];
+this.EXPORTED_SYMBOLS = ["DebuggerServer", "ActorPool"];
 
 var loadSubScript =
   "function loadSubScript(aURL)\n" +
   "{\n" +
   "const Ci = Components.interfaces;\n" +
   "const Cc = Components.classes;\n" +
   "  try {\n" +
   "    let loader = Cc[\"@mozilla.org/moz/jssubscript-loader;1\"]\n" +
@@ -36,8 +36,9 @@ var loadSubScript =
 var systemPrincipal = Cc["@mozilla.org/systemprincipal;1"]
                       .createInstance(Ci.nsIPrincipal);
 
 var gGlobal = Cu.Sandbox(systemPrincipal);
 Cu.evalInSandbox(loadSubScript, gGlobal, "1.8");
 gGlobal.loadSubScript("resource://gre/modules/devtools/server/main.js");
 
 this.DebuggerServer = gGlobal.DebuggerServer;
+this.ActorPool = gGlobal.ActorPool;
--- a/toolkit/devtools/server/main.js
+++ b/toolkit/devtools/server/main.js
@@ -291,16 +291,36 @@ var DebuggerServer = {
       this.addActors("resource://gre/modules/devtools/server/actors/profiler.js");
 
     this.addActors("resource://gre/modules/devtools/server/actors/styleeditor.js");
     this.addActors("resource://gre/modules/devtools/server/actors/webapps.js");
     this.registerModule("devtools/server/actors/inspector");
   },
 
   /**
+   * Install tab actors in documents loaded in content childs
+   */
+  addChildActors: function () {
+    // In case of apps being loaded in parent process, DebuggerServer is already
+    // initialized and browser actors are already loaded,
+    // but childtab.js hasn't been loaded yet.
+    if (!("BrowserTabActor" in this)) {
+      this.addActors("resource://gre/modules/devtools/server/actors/webbrowser.js");
+      this.addActors("resource://gre/modules/devtools/server/actors/script.js");
+      this.addActors("resource://gre/modules/devtools/server/actors/webconsole.js");
+      this.addActors("resource://gre/modules/devtools/server/actors/gcli.js");
+      this.addActors("resource://gre/modules/devtools/server/actors/styleeditor.js");
+      this.registerModule("devtools/server/actors/inspector");
+    }
+    if (!("ContentTabActor" in DebuggerServer)) {
+      this.addActors("resource://gre/modules/devtools/server/actors/childtab.js");
+    }
+  },
+
+  /**
    * Listens on the given port for remote debugger connections.
    *
    * @param aPort int
    *        The port to listen on.
    */
   openListener: function DS_openListener(aPort) {
     if (!Services.prefs.getBoolPref("devtools.debugger.remote-enabled")) {
       return false;
@@ -388,16 +408,32 @@ var DebuggerServer = {
     //
     // But every time you use this, you will feel the shame of having
     // used a property that starts with a '_'.
     clientTransport._serverConnection = connection;
 
     return clientTransport;
   },
 
+  /**
+   * In a content child process, create a new connection that exchanges
+   * nsIMessageSender messages with our parent process.
+   *
+   * @param aPrefix
+   *    The prefix we should use in our nsIMessageSender message names and
+   *    actor names. This connection will use messages named
+   *    "debug:<prefix>:packet", and all its actors will have names
+   *    beginning with "<prefix>:".
+   */
+  connectToParent: function(aPrefix, aMessageManager) {
+    this._checkInit();
+
+    let transport = new ChildDebuggerTransport(aMessageManager, aPrefix);
+    return this._onConnection(transport, aPrefix, true);
+  },
 
   // nsIServerSocketListener implementation
 
   onSocketAccepted:
   makeInfallible(function DS_onSocketAccepted(aSocket, aTransport) {
     if (!this._allowConnection()) {
       return;
     }
@@ -431,34 +467,36 @@ var DebuggerServer = {
    * connectPipe(), from connectToParent, or from an incoming socket
    * connection handler.
    *
    * If present, |aForwardingPrefix| is a forwarding prefix that a parent
    * server is using to recognizes messages intended for this server. Ensure
    * that all our actors have names beginning with |aForwardingPrefix + ':'|.
    * In particular, the root actor's name will be |aForwardingPrefix + ':root'|.
    */
-  _onConnection: function DS_onConnection(aTransport, aForwardingPrefix) {
+  _onConnection: function DS_onConnection(aTransport, aForwardingPrefix, aNoRootActor = false) {
     let connID;
     if (aForwardingPrefix) {
       connID = aForwardingPrefix + ":";
     } else {
       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);
-    if (aForwardingPrefix)
-      conn.rootActor.actorID = aForwardingPrefix + ":root";
-    else
-      conn.rootActor.actorID = "root";
-    conn.addActor(conn.rootActor);
-    aTransport.send(conn.rootActor.sayHello());
+    if (!aNoRootActor) {
+      conn.rootActor = this.createRootActor(conn);
+      if (aForwardingPrefix)
+        conn.rootActor.actorID = aForwardingPrefix + ":root";
+      else
+        conn.rootActor.actorID = "root";
+      conn.addActor(conn.rootActor);
+      aTransport.send(conn.rootActor.sayHello());
+    }
     aTransport.ready();
 
     return conn;
   },
 
   /**
    * Remove the connection from the debugging server.
    */
--- a/toolkit/devtools/webconsole/WebConsoleUtils.jsm
+++ b/toolkit/devtools/webconsole/WebConsoleUtils.jsm
@@ -1818,18 +1818,20 @@ NetworkMonitor.prototype = {
    */
   init: function NM_init()
   {
     this.responsePipeSegmentSize = Services.prefs
                                    .getIntPref("network.buffer.cache.size");
 
     gActivityDistributor.addObserver(this);
 
-    Services.obs.addObserver(this._httpResponseExaminer,
-                             "http-on-examine-response", false);
+    if (Services.appinfo.processType != Ci.nsIXULRuntime.PROCESS_TYPE_CONTENT) {
+      Services.obs.addObserver(this._httpResponseExaminer,
+                               "http-on-examine-response", false);
+    }
   },
 
   /**
    * Observe notifications for the http-on-examine-response topic, coming from
    * the nsIObserverService.
    *
    * @private
    * @param nsIHttpChannel aSubject
@@ -2325,18 +2327,20 @@ NetworkMonitor.prototype = {
   },
 
   /**
    * Suspend Web Console activity. This is called when all Web Consoles are
    * closed.
    */
   destroy: function NM_destroy()
   {
-    Services.obs.removeObserver(this._httpResponseExaminer,
-                                "http-on-examine-response");
+    if (Services.appinfo.processType != Ci.nsIXULRuntime.PROCESS_TYPE_CONTENT) {
+      Services.obs.removeObserver(this._httpResponseExaminer,
+                                  "http-on-examine-response");
+    }
 
     gActivityDistributor.removeObserver(this);
 
     this.openRequests = {};
     this.openResponses = {};
     this.owner = null;
     this.window = null;
   },