Backed out changeset f55ff90f2b6f (bug 1044736)
authorCarsten "Tomcat" Book <cbook@mozilla.com>
Wed, 12 Nov 2014 08:39:40 +0100
changeset 215304 b3d8ed47bfd78eac7ef4416d9f70b959a97be67c
parent 215303 2af834a11a53d186f5eedb6e4922959e58b268ec
child 215305 ee077ddddcdfec61d01aad77b95fe25d77a883c2
push id51731
push userryanvm@gmail.com
push dateWed, 12 Nov 2014 20:52:34 +0000
treeherdermozilla-inbound@90731dbaab2d [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
bugs1044736
milestone36.0a1
backs outf55ff90f2b6ff1df6a4f24d1fb03f1b44c488878
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
Backed out changeset f55ff90f2b6f (bug 1044736)
dom/browser-element/BrowserElementParent.js
dom/browser-element/BrowserElementParent.jsm
dom/browser-element/BrowserElementParent.manifest
--- a/dom/browser-element/BrowserElementParent.js
+++ b/dom/browser-element/BrowserElementParent.js
@@ -3,15 +3,127 @@
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
 const {utils: Cu, interfaces: Ci} = Components;
 
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
-Cu.import("resource://gre/modules/BrowserElementParent.jsm");
+
+const NS_PREFBRANCH_PREFCHANGE_TOPIC_ID = "nsPref:changed";
+const BROWSER_FRAMES_ENABLED_PREF = "dom.mozBrowserFramesEnabled";
+
+XPCOMUtils.defineLazyModuleGetter(this, "BrowserElementParentBuilder",
+                                  "resource://gre/modules/BrowserElementParent.jsm",
+                                  "BrowserElementParentBuilder");
+
+function debug(msg) {
+  //dump("BrowserElementParent.js - " + msg + "\n");
+}
 
 /**
  * BrowserElementParent implements one half of <iframe mozbrowser>.  (The other
  * half is, unsurprisingly, BrowserElementChild.)
+ *
+ * BrowserElementParentFactory detects when we create a windows or docshell
+ * contained inside a <iframe mozbrowser> and creates a BrowserElementParent
+ * object for that window.
+ *
+ * It creates a BrowserElementParent that injects script to listen for
+ * certain event.
  */
-this.NSGetFactory = XPCOMUtils.generateNSGetFactory([BrowserElementParent]);
+
+function BrowserElementParentFactory() {
+  this._initialized = false;
+}
+
+BrowserElementParentFactory.prototype = {
+  classID: Components.ID("{ddeafdac-cb39-47c4-9cb8-c9027ee36d26}"),
+  QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver,
+                                         Ci.nsISupportsWeakReference]),
+
+  /**
+   * Called on app startup, and also when the browser frames enabled pref is
+   * changed.
+   */
+  _init: function() {
+    if (this._initialized) {
+      return;
+    }
+
+    // If the pref is disabled, do nothing except wait for the pref to change.
+    // (This is important for tests, if nothing else.)
+    if (!this._browserFramesPrefEnabled()) {
+      Services.prefs.addObserver(BROWSER_FRAMES_ENABLED_PREF, this, /* ownsWeak = */ true);
+      return;
+    }
+
+    debug("_init");
+    this._initialized = true;
+
+    // Maps frame elements to BrowserElementParent objects.  We never look up
+    // anything in this map; the purpose is to keep the BrowserElementParent
+    // alive for as long as its frame element lives.
+    this._bepMap = new WeakMap();
+
+    Services.obs.addObserver(this, 'remote-browser-pending', /* ownsWeak = */ true);
+    Services.obs.addObserver(this, 'inprocess-browser-shown', /* ownsWeak = */ true);
+  },
+
+  _browserFramesPrefEnabled: function() {
+    try {
+      return Services.prefs.getBoolPref(BROWSER_FRAMES_ENABLED_PREF);
+    }
+    catch(e) {
+      return false;
+    }
+  },
+
+  _observeInProcessBrowserFrameShown: function(frameLoader) {
+    // Ignore notifications that aren't from a BrowserOrApp
+    if (!frameLoader.QueryInterface(Ci.nsIFrameLoader).ownerIsBrowserOrAppFrame) {
+      return;
+    }
+    debug("In-process browser frame shown " + frameLoader);
+    this._createBrowserElementParent(frameLoader,
+                                     /* hasRemoteFrame = */ false,
+                                     /* pending frame */ false);
+  },
+
+  _observeRemoteBrowserFramePending: function(frameLoader) {
+    // Ignore notifications that aren't from a BrowserOrApp
+    if (!frameLoader.QueryInterface(Ci.nsIFrameLoader).ownerIsBrowserOrAppFrame) {
+      return;
+    }
+    debug("Remote browser frame shown " + frameLoader);
+    this._createBrowserElementParent(frameLoader,
+                                     /* hasRemoteFrame = */ true,
+                                     /* pending frame */ true);
+  },
+
+  _createBrowserElementParent: function(frameLoader, hasRemoteFrame, isPendingFrame) {
+    let frameElement = frameLoader.QueryInterface(Ci.nsIFrameLoader).ownerElement;
+    this._bepMap.set(frameElement, BrowserElementParentBuilder.create(
+      frameLoader, hasRemoteFrame, isPendingFrame));
+  },
+
+  observe: function(subject, topic, data) {
+    switch(topic) {
+    case 'app-startup':
+      this._init();
+      break;
+    case NS_PREFBRANCH_PREFCHANGE_TOPIC_ID:
+      if (data == BROWSER_FRAMES_ENABLED_PREF) {
+        this._init();
+      }
+      break;
+    case 'remote-browser-pending':
+      this._observeRemoteBrowserFramePending(subject);
+      break;
+    case 'inprocess-browser-shown':
+      this._observeInProcessBrowserFrameShown(subject);
+      break;
+    }
+  },
+};
+
+this.NSGetFactory = XPCOMUtils.generateNSGetFactory([BrowserElementParentFactory]);
--- a/dom/browser-element/BrowserElementParent.jsm
+++ b/dom/browser-element/BrowserElementParent.jsm
@@ -9,29 +9,31 @@ let Ci = Components.interfaces;
 let Cc = Components.classes;
 let Cr = Components.results;
 
 /* BrowserElementParent injects script to listen for certain events in the
  * child.  We then listen to messages from the child script and take
  * appropriate action here in the parent.
  */
 
-this.EXPORTED_SYMBOLS = ["BrowserElementParent"];
+this.EXPORTED_SYMBOLS = ["BrowserElementParentBuilder"];
 
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource://gre/modules/BrowserElementPromptService.jsm");
 
 XPCOMUtils.defineLazyGetter(this, "DOMApplicationRegistry", function () {
   Cu.import("resource://gre/modules/Webapps.jsm");
   return DOMApplicationRegistry;
 });
 
+const TOUCH_EVENTS_ENABLED_PREF = "dom.w3c_touch_events.enabled";
+
 function debug(msg) {
-  //dump("BrowserElementParent - " + msg + "\n");
+  //dump("BrowserElementParent.jsm - " + msg + "\n");
 }
 
 function getIntPref(prefName, def) {
   try {
     return Services.prefs.getIntPref(prefName);
   }
   catch(err) {
     return def;
@@ -52,93 +54,148 @@ function visibilityChangeHandler(e) {
     return;
   }
 
   for (let i = 0; i < beps.length; i++) {
     beps[i]._ownerVisibilityChange();
   }
 }
 
-function defineNoReturnMethod(fn) {
-  return function method() {
-    if (!this._domRequestReady) {
-      // Remote browser haven't been created, we just queue the API call.
-      let args = Array.slice(arguments);
-      args.unshift(this);
-      this._pendingAPICalls.push(method.bind.apply(fn, args));
-      return;
-    }
-    if (this._isAlive()) {
-      fn.apply(this, arguments);
-    }
-  };
+this.BrowserElementParentBuilder = {
+  create: function create(frameLoader, hasRemoteFrame, isPendingFrame) {
+    return new BrowserElementParent(frameLoader, hasRemoteFrame);
+  }
 }
 
-function defineDOMRequestMethod(msgName) {
-  return function() {
-    return this._sendDOMRequest(msgName);
-  };
-}
-
-function BrowserElementParent() {
-  debug("Creating new BrowserElementParent object");
+function BrowserElementParent(frameLoader, hasRemoteFrame, isPendingFrame) {
+  debug("Creating new BrowserElementParent object for " + frameLoader);
   this._domRequestCounter = 0;
   this._domRequestReady = false;
   this._pendingAPICalls = [];
   this._pendingDOMRequests = {};
   this._pendingSetInputMethodActive = [];
+  this._hasRemoteFrame = hasRemoteFrame;
   this._nextPaintListeners = [];
 
+  this._frameLoader = frameLoader;
+  this._frameElement = frameLoader.QueryInterface(Ci.nsIFrameLoader).ownerElement;
+  let self = this;
+  if (!this._frameElement) {
+    debug("No frame element?");
+    return;
+  }
+
   Services.obs.addObserver(this, 'ask-children-to-exit-fullscreen', /* ownsWeak = */ true);
   Services.obs.addObserver(this, 'oop-frameloader-crashed', /* ownsWeak = */ true);
   Services.obs.addObserver(this, 'copypaste-docommand', /* ownsWeak = */ true);
+
+  let defineMethod = function(name, fn) {
+    XPCNativeWrapper.unwrap(self._frameElement)[name] = Cu.exportFunction(function() {
+      if (self._isAlive()) {
+        return fn.apply(self, arguments);
+      }
+    }, self._frameElement);
+  }
+
+  let defineNoReturnMethod = function(name, fn) {
+    XPCNativeWrapper.unwrap(self._frameElement)[name] = Cu.exportFunction(function method() {
+      if (!self._domRequestReady) {
+        // Remote browser haven't been created, we just queue the API call.
+        let args = Array.slice(arguments);
+        args.unshift(self);
+        self._pendingAPICalls.push(method.bind.apply(fn, args));
+        return;
+      }
+      if (self._isAlive()) {
+        fn.apply(self, arguments);
+      }
+    }, self._frameElement);
+  };
+
+  let defineDOMRequestMethod = function(domName, msgName) {
+    XPCNativeWrapper.unwrap(self._frameElement)[domName] = Cu.exportFunction(function() {
+      return self._sendDOMRequest(msgName);
+    }, self._frameElement);
+  }
+
+  // Define methods on the frame element.
+  defineNoReturnMethod('setVisible', this._setVisible);
+  defineDOMRequestMethod('getVisible', 'get-visible');
+
+  // Not expose security sensitive browser API for widgets
+  if (!this._frameLoader.QueryInterface(Ci.nsIFrameLoader).ownerIsWidget) {
+    defineNoReturnMethod('sendMouseEvent', this._sendMouseEvent);
+
+    // 0 = disabled, 1 = enabled, 2 - auto detect
+    if (getIntPref(TOUCH_EVENTS_ENABLED_PREF, 0) != 0) {
+      defineNoReturnMethod('sendTouchEvent', this._sendTouchEvent);
+    }
+    defineNoReturnMethod('goBack', this._goBack);
+    defineNoReturnMethod('goForward', this._goForward);
+    defineNoReturnMethod('reload', this._reload);
+    defineNoReturnMethod('stop', this._stop);
+    defineMethod('download', this._download);
+    defineDOMRequestMethod('purgeHistory', 'purge-history');
+    defineMethod('getScreenshot', this._getScreenshot);
+    defineNoReturnMethod('zoom', this._zoom);
+
+    defineDOMRequestMethod('getCanGoBack', 'get-can-go-back');
+    defineDOMRequestMethod('getCanGoForward', 'get-can-go-forward');
+    defineDOMRequestMethod('getContentDimensions', 'get-contentdimensions');
+  }
+
+  defineMethod('addNextPaintListener', this._addNextPaintListener);
+  defineMethod('removeNextPaintListener', this._removeNextPaintListener);
+  defineNoReturnMethod('setActive', this._setActive);
+  defineMethod('getActive', 'this._getActive');
+
+  let principal = this._frameElement.ownerDocument.nodePrincipal;
+  let perm = Services.perms
+             .testExactPermissionFromPrincipal(principal, "input-manage");
+  if (perm === Ci.nsIPermissionManager.ALLOW_ACTION) {
+    defineMethod('setInputMethodActive', this._setInputMethodActive);
+  }
+
+  // Listen to visibilitychange on the iframe's owner window, and forward
+  // changes down to the child.  We want to do this while registering as few
+  // visibilitychange listeners on _window as possible, because such a listener
+  // may live longer than this BrowserElementParent object.
+  //
+  // To accomplish this, we register just one listener on the window, and have
+  // it reference a WeakMap whose keys are all the BrowserElementParent objects
+  // on the window.  Then when the listener fires, we iterate over the
+  // WeakMap's keys (which we can do, because we're chrome) to notify the
+  // BrowserElementParents.
+  if (!this._window._browserElementParents) {
+    this._window._browserElementParents = new WeakMap();
+    this._window.addEventListener('visibilitychange',
+                                  visibilityChangeHandler,
+                                  /* useCapture = */ false,
+                                  /* wantsUntrusted = */ false);
+  }
+
+  this._window._browserElementParents.set(this, null);
+
+  // Insert ourself into the prompt service.
+  BrowserElementPromptService.mapFrameToBrowserElementParent(this._frameElement, this);
+  if (!isPendingFrame) {
+    this._setupMessageListener();
+    this._registerAppManifest();
+  } else {
+    // if we are a pending frame, we setup message manager after
+    // observing remote-browser-frame-shown
+    Services.obs.addObserver(this, 'remote-browser-frame-shown', /* ownsWeak = */ true);
+  }
 }
 
 BrowserElementParent.prototype = {
 
-  classDescription: "BrowserElementAPI implementation",
-  classID: Components.ID("{9f171ac4-0939-4ef8-b360-3408aedc3060}"),
-  contractID: "@mozilla.org/dom/browser-element-api;1",
-  QueryInterface: XPCOMUtils.generateQI([Ci.nsIBrowserElementAPI,
-                                         Ci.nsIObserver,
+  QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver,
                                          Ci.nsISupportsWeakReference]),
 
-  setFrameLoader: function(frameLoader) {
-    this._frameLoader = frameLoader;
-    this._frameElement = frameLoader.QueryInterface(Ci.nsIFrameLoader).ownerElement;
-    if (!this._frameElement) {
-      debug("No frame element?");
-      return;
-    }
-    // Listen to visibilitychange on the iframe's owner window, and forward
-    // changes down to the child.  We want to do this while registering as few
-    // visibilitychange listeners on _window as possible, because such a listener
-    // may live longer than this BrowserElementParent object.
-    //
-    // To accomplish this, we register just one listener on the window, and have
-    // it reference a WeakMap whose keys are all the BrowserElementParent objects
-    // on the window.  Then when the listener fires, we iterate over the
-    // WeakMap's keys (which we can do, because we're chrome) to notify the
-    // BrowserElementParents.
-    if (!this._window._browserElementParents) {
-      this._window._browserElementParents = new WeakMap();
-      this._window.addEventListener('visibilitychange',
-                                    visibilityChangeHandler,
-                                    /* useCapture = */ false,
-                                    /* wantsUntrusted = */ false);
-    }
-
-    this._window._browserElementParents.set(this, null);
-
-    // Insert ourself into the prompt service.
-    BrowserElementPromptService.mapFrameToBrowserElementParent(this._frameElement, this);
-    this._setupMessageListener();
-    this._registerAppManifest();
-  },
-
   _runPendingAPICall: function() {
     if (!this._pendingAPICalls) {
       return;
     }
     for (let i = 0; i < this._pendingAPICalls.length; i++) {
       try {
         this._pendingAPICalls[i]();
       } catch (e) {
@@ -530,50 +587,43 @@ BrowserElementParent.prototype = {
     }
     else {
       debug("Got error in gotDOMRequestResult.");
       Services.DOMRequest.fireErrorAsync(req,
         Cu.cloneInto(data.json.errorMsg, this._window));
     }
   },
 
-  setVisible: defineNoReturnMethod(function(visible) {
+  _setVisible: function(visible) {
     this._sendAsyncMsg('set-visible', {visible: visible});
     this._frameLoader.visible = visible;
-  }),
+  },
 
-  getVisible: defineDOMRequestMethod('get-visible'),
-
-  setActive: defineNoReturnMethod(function(active) {
+  _setActive: function(active) {
     this._frameLoader.visible = active;
-  }),
+  },
 
-  getActive: function() {
-    if (!this._isAlive()) {
-      throw Components.Exception("Dead content process",
-                                 Cr.NS_ERROR_DOM_INVALID_STATE_ERR);
-    }
-
+  _getActive: function() {
     return this._frameLoader.visible;
   },
 
-  sendMouseEvent: defineNoReturnMethod(function(type, x, y, button, clickCount, modifiers) {
+  _sendMouseEvent: function(type, x, y, button, clickCount, modifiers) {
     this._sendAsyncMsg("send-mouse-event", {
       "type": type,
       "x": x,
       "y": y,
       "button": button,
       "clickCount": clickCount,
       "modifiers": modifiers
     });
-  }),
+  },
 
-  sendTouchEvent: defineNoReturnMethod(function(type, identifiers, touchesX, touchesY,
-                                                radiisX, radiisY, rotationAngles, forces,
-                                                count, modifiers) {
+  _sendTouchEvent: function(type, identifiers, touchesX, touchesY,
+                            radiisX, radiisY, rotationAngles, forces,
+                            count, modifiers) {
 
     let tabParent = this._frameLoader.tabParent;
     if (tabParent && tabParent.useAsyncPanZoom) {
       tabParent.injectTouchEvent(type,
                                  identifiers,
                                  touchesX,
                                  touchesY,
                                  radiisX,
@@ -591,55 +641,45 @@ BrowserElementParent.prototype = {
         "radiisX": radiisX,
         "radiisY": radiisY,
         "rotationAngles": rotationAngles,
         "forces": forces,
         "count": count,
         "modifiers": modifiers
       });
     }
-  }),
+  },
 
-  getCanGoBack: defineDOMRequestMethod('get-can-go-back'),
-  getCanGoForward: defineDOMRequestMethod('get-can-go-forward'),
-  getContentDimensions: defineDOMRequestMethod('get-contentdimensions'),
-
-  goBack: defineNoReturnMethod(function() {
+  _goBack: function() {
     this._sendAsyncMsg('go-back');
-  }),
+  },
 
-  goForward: defineNoReturnMethod(function() {
+  _goForward: function() {
     this._sendAsyncMsg('go-forward');
-  }),
+  },
 
-  reload: defineNoReturnMethod(function(hardReload) {
+  _reload: function(hardReload) {
     this._sendAsyncMsg('reload', {hardReload: hardReload});
-  }),
+  },
 
-  stop: defineNoReturnMethod(function() {
+  _stop: function() {
     this._sendAsyncMsg('stop');
-  }),
+  },
 
   /*
    * The valid range of zoom scale is defined in preference "zoom.maxPercent" and "zoom.minPercent".
    */
-  zoom: defineNoReturnMethod(function(zoom) {
+  _zoom: function(zoom) {
     zoom *= 100;
     zoom = Math.min(getIntPref("zoom.maxPercent", 300), zoom);
     zoom = Math.max(getIntPref("zoom.minPercent", 50), zoom);
     this._sendAsyncMsg('zoom', {zoom: zoom / 100.0});
-  }),
-
-  purgeHistory: defineDOMRequestMethod('purge-history'),
-
+  },
 
-  download: function(_url, _options) {
-    if (!this._isAlive()) {
-      return null;
-    }
+  _download: function(_url, _options) {
     let ioService =
       Cc['@mozilla.org/network/io-service;1'].getService(Ci.nsIIOService);
     let uri = ioService.newURI(_url, null, null);
     let url = uri.QueryInterface(Ci.nsIURL);
 
     // Ensure we have _options, we always use it to send the filename.
     _options = _options || {};
     if (!_options.filename) {
@@ -749,22 +789,17 @@ BrowserElementParent.prototype = {
     }
 
     // Set-up complete, let's get things started.
     channel.asyncOpen(new DownloadListener(), null);
 
     return req;
   },
 
-  getScreenshot: function(_width, _height, _mimeType) {
-    if (!this._isAlive()) {
-      throw Components.Exception("Dead content process",
-                                 Cr.NS_ERROR_DOM_INVALID_STATE_ERR);
-    }
-
+  _getScreenshot: function(_width, _height, _mimeType) {
     let width = parseInt(_width);
     let height = parseInt(_height);
     let mimeType = (typeof _mimeType === 'string') ?
       _mimeType.trim().toLowerCase() : 'image/jpeg';
     if (isNaN(width) || isNaN(height) || width < 0 || height < 0) {
       throw Components.Exception("Invalid argument",
                                  Cr.NS_ERROR_INVALID_ARG);
     }
@@ -774,46 +809,42 @@ BrowserElementParent.prototype = {
                                  mimeType: mimeType});
   },
 
   _recvNextPaint: function(data) {
     let listeners = this._nextPaintListeners;
     this._nextPaintListeners = [];
     for (let listener of listeners) {
       try {
-        listener.recvNextPaint();
+        listener();
       } catch (e) {
         // If a listener throws we'll continue.
       }
     }
   },
 
-  addNextPaintListener: function(listener) {
-    if (!this._isAlive()) {
-      throw Components.Exception("Dead content process",
-                                 Cr.NS_ERROR_DOM_INVALID_STATE_ERR);
-    }
+  _addNextPaintListener: function(listener) {
+    if (typeof listener != 'function')
+      throw Components.Exception("Invalid argument", Cr.NS_ERROR_INVALID_ARG);
 
     let self = this;
     let run = function() {
       if (self._nextPaintListeners.push(listener) == 1)
         self._sendAsyncMsg('activate-next-paint-listener');
     };
     if (!this._domRequestReady) {
       this._pendingAPICalls.push(run);
     } else {
       run();
     }
   },
 
-  removeNextPaintListener: function(listener) {
-    if (!this._isAlive()) {
-      throw Components.Exception("Dead content process",
-                                 Cr.NS_ERROR_DOM_INVALID_STATE_ERR);
-    }
+  _removeNextPaintListener: function(listener) {
+    if (typeof listener != 'function')
+      throw Components.Exception("Invalid argument", Cr.NS_ERROR_INVALID_ARG);
 
     let self = this;
     let run = function() {
       for (let i = self._nextPaintListeners.length - 1; i >= 0; i--) {
         if (self._nextPaintListeners[i] == listener) {
           self._nextPaintListeners.splice(i, 1);
           break;
         }
@@ -824,22 +855,17 @@ BrowserElementParent.prototype = {
     };
     if (!this._domRequestReady) {
       this._pendingAPICalls.push(run);
     } else {
       run();
     }
   },
 
-  setInputMethodActive: function(isActive) {
-    if (!this._isAlive()) {
-      throw Components.Exception("Dead content process",
-                                 Cr.NS_ERROR_DOM_INVALID_STATE_ERR);
-    }
-
+  _setInputMethodActive: function(isActive) {
     if (typeof isActive !== 'boolean') {
       throw Components.Exception("Invalid argument",
                                  Cr.NS_ERROR_INVALID_ARG);
     }
 
     return this._sendDOMRequest('set-input-method-active',
                                 {isActive: isActive});
   },
@@ -891,20 +917,28 @@ BrowserElementParent.prototype = {
     case 'oop-frameloader-crashed':
       if (this._isAlive() && subject == this._frameLoader) {
         this._fireFatalError();
       }
       break;
     case 'ask-children-to-exit-fullscreen':
       if (this._isAlive() &&
           this._frameElement.ownerDocument == subject &&
-          this._frameLoader.QueryInterface(Ci.nsIFrameLoader).tabParent) {
+          this._hasRemoteFrame) {
         this._sendAsyncMsg('exit-fullscreen');
       }
       break;
+    case 'remote-browser-frame-shown':
+      if (this._frameLoader == subject) {
+        if (!this._mm) {
+          this._setupMessageListener();
+          this._registerAppManifest();
+        }
+        Services.obs.removeObserver(this, 'remote-browser-frame-shown');
+      }
     case 'copypaste-docommand':
       if (this._isAlive() && this._frameElement.isEqualNode(subject.wrappedJSObject)) {
         this._sendAsyncMsg('do-command', { command: data });
       }
       break;
     default:
       debug('Unknown topic: ' + topic);
       break;
--- a/dom/browser-element/BrowserElementParent.manifest
+++ b/dom/browser-element/BrowserElementParent.manifest
@@ -1,2 +1,3 @@
-component {9f171ac4-0939-4ef8-b360-3408aedc3060} BrowserElementParent.js
-contract @mozilla.org/dom/browser-element-api;1 {9f171ac4-0939-4ef8-b360-3408aedc3060}
+component {ddeafdac-cb39-47c4-9cb8-c9027ee36d26} BrowserElementParent.js
+contract @mozilla.org/browser-element-parent-factory;1 {ddeafdac-cb39-47c4-9cb8-c9027ee36d26}
+category app-startup BrowserElementParentFactory service,@mozilla.org/browser-element-parent-factory;1