Backed out changeset bd0fc79d7a8b (bug 835548), changeset b8d1949eeb05 (bug 834999), changeset 3d90fdbf38a4 (bug 835596), and changeset ba7ffb02dc3f (bug 834371) for mochitest-4 failures on a CLOSED TREE.
authorRyan VanderMeulen <ryanvm@gmail.com>
Wed, 30 Jan 2013 18:20:40 -0500
changeset 120416 220ee1b126c31d4aa53efc0017c07f6912c7f428
parent 120415 838146e5e46a1fd9cb3d07d1e135c9527fb04d78
child 120417 f5789352370eb948a13e210a9bc2cf455337ea26
push id24251
push userryanvm@gmail.com
push dateThu, 31 Jan 2013 20:56:22 +0000
treeherdermozilla-central@683b08dc1afd [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
bugs835548, 834999, 835596, 834371
milestone21.0a1
backs outbd0fc79d7a8b311541be293e23f19ebc070ad719
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 bd0fc79d7a8b (bug 835548), changeset b8d1949eeb05 (bug 834999), changeset 3d90fdbf38a4 (bug 835596), and changeset ba7ffb02dc3f (bug 834371) for mochitest-4 failures on a CLOSED TREE.
dom/apps/src/AppsServiceChild.jsm
dom/apps/src/AppsUtils.jsm
dom/apps/src/Webapps.js
dom/apps/src/Webapps.jsm
dom/browser-element/BrowserElementChild.js
dom/browser-element/BrowserElementParent.js
dom/browser-element/BrowserElementParent.jsm
--- a/dom/apps/src/AppsServiceChild.jsm
+++ b/dom/apps/src/AppsServiceChild.jsm
@@ -28,24 +28,16 @@ this.DOMApplicationRegistry = {
 
     ["Webapps:AddApp", "Webapps:RemoveApp"].forEach((function(aMsgName) {
       this.cpmm.addMessageListener(aMsgName, this);
     }).bind(this));
 
     // We need to prime the cache with the list of apps.
     // XXX shoud we do this async and block callers if it's not yet there?
     this.webapps = this.cpmm.sendSyncMessage("Webapps:GetList", { })[0];
-
-    // We need a fast mapping from localId -> app, so we add an index.
-    this.localIdIndex = { };
-    for (let id in this.webapps) {
-      let app = this.webapps[id];
-      this.localIdIndex[app.localId] = app;
-    }
-
     Services.obs.addObserver(this, "xpcom-shutdown", false);
   },
 
   observe: function(aSubject, aTopic, aData) {
     // cpmm.addMessageListener causes the DOMApplicationRegistry object to live
     // forever if we don't clean up properly.
     this.webapps = null;
     ["Webapps:AddApp", "Webapps:RemoveApp"].forEach((function(aMsgName) {
@@ -54,20 +46,18 @@ this.DOMApplicationRegistry = {
   },
 
   receiveMessage: function receiveMessage(aMessage) {
     debug("Received " + aMessage.name + " message.");
     let msg = aMessage.json;
     switch (aMessage.name) {
       case "Webapps:AddApp":
         this.webapps[msg.id] = msg.app;
-        this.localIdIndex[msg.app.localId] = msg.app;
         break;
       case "Webapps:RemoveApp":
-        delete this.localIdIndex[this.webapps[msg.id].localId];
         delete this.webapps[msg.id];
         break;
     }
   },
 
   getAppByManifestURL: function getAppByManifestURL(aManifestURL) {
     debug("getAppByManifestURL " + aManifestURL);
     return AppsUtils.getAppByManifestURL(this.webapps, aManifestURL);
@@ -80,23 +70,17 @@ this.DOMApplicationRegistry = {
 
   getCSPByLocalId: function(aLocalId) {
     debug("getCSPByLocalId:" + aLocalId);
     return AppsUtils.getCSPByLocalId(this.webapps, aLocalId);
   },
 
   getAppByLocalId: function getAppByLocalId(aLocalId) {
     debug("getAppByLocalId " + aLocalId);
-    let app = this.localIdIndex[aLocalId];
-    if (!app) {
-      debug("Ouch, No app!");
-      return null;
-    }
-
-    return AppsUtils.cloneAsMozIApplication(app);
+    return AppsUtils.getAppByLocalId(this.webapps, aLocalId);
   },
 
   getManifestURLByLocalId: function getManifestURLByLocalId(aLocalId) {
     debug("getManifestURLByLocalId " + aLocalId);
     return AppsUtils.getManifestURLByLocalId(this.webapps, aLocalId);
   },
 
   getAppFromObserverMessage: function getAppFromObserverMessage(aMessage) {
--- a/dom/apps/src/AppsUtils.jsm
+++ b/dom/apps/src/AppsUtils.jsm
@@ -76,24 +76,18 @@ this.AppsUtils = {
       // specific permission. It is not checking if browsers inside |aApp| have such
       // permission.
       let principal = secMan.getAppCodebasePrincipal(uri, aApp.localId,
                                                      /*mozbrowser*/false);
       let perm = Services.perms.testExactPermissionFromPrincipal(principal,
                                                                  aPermission);
       return (perm === Ci.nsIPermissionManager.ALLOW_ACTION);
     };
-    res.QueryInterface = function(aIID) {
-      if (aIID.equals(Ci.mozIDOMApplication) ||
-          aIID.equals(Ci.mozIApplication) ||
-          aIID.equals(Ci.nsISupports))
-        return this;
-      throw Cr.NS_ERROR_NO_INTERFACE;
-    }
-
+    res.QueryInterface = XPCOMUtils.generateQI([Ci.mozIDOMApplication,
+                                                Ci.mozIApplication]);
     return res;
   },
 
   getAppByManifestURL: function getAppByManifestURL(aApps, aManifestURL) {
     debug("getAppByManifestURL " + aManifestURL);
     // This could be O(1) if |webapps| was a dictionary indexed on manifestURL
     // which should be the unique app identifier.
     // It's currently O(n).
--- a/dom/apps/src/Webapps.js
+++ b/dom/apps/src/Webapps.js
@@ -328,36 +328,16 @@ DOMError.prototype = {
                                     flags: Ci.nsIClassInfo.DOM_OBJECT,
                                     classDescription: "DOMError object"})
 }
 
 /**
   * mozIDOMApplication object
   */
 
-// A simple cache for the wrapped manifests.
-let manifestCache = {
-  _cache: { },
-
-  // Gets an entry from the cache, and populates the cache if needed.
-  get : function mcache_get(aManifestURL, aManifest, aWindow) {
-    if (!(aManifestURL in this._cache)) {
-      this._cache[aManifestURL] = ObjectWrapper.wrap(aManifest, aWindow);
-    }
-    return this._cache[aManifestURL];
-  },
-
-  // Invalidates an entry in the cache.
-  evict: function mcache_evict(aManifestURL) {
-    if (aManifestURL in this._cache) {
-      delete this._cache[aManifestURL];
-    }
-  }
-}
-
 function createApplicationObject(aWindow, aApp) {
   let app = Cc["@mozilla.org/webapps/application;1"].createInstance(Ci.mozIDOMApplication);
   app.wrappedJSObject.init(aWindow, aApp);
   return app;
 }
 
 function WebappsApplication() {
   this.wrappedJSObject = this;
@@ -402,17 +382,17 @@ WebappsApplication.prototype = {
 
     cpmm.sendAsyncMessage("Webapps:RegisterForMessages",
                           ["Webapps:OfflineCache",
                            "Webapps:PackageEvent",
                            "Webapps:CheckForUpdate:Return:OK"]);
   },
 
   get manifest() {
-    return manifestCache.get(this.manifestURL, this._manifest, this._window);
+    return this.manifest = ObjectWrapper.wrap(this._manifest, this._window);
   },
 
   get updateManifest() {
     return this.updateManifest = this._updateManifest ? ObjectWrapper.wrap(this._updateManifest, this._window)
                                                       : null;
   },
 
   set onprogress(aCallback) {
@@ -587,32 +567,29 @@ WebappsApplication.prototype = {
           case "canceled":
             this._downloadError = msg.error;
             this._fireEvent("downloaderror", this._ondownloaderror);
             break;
           case "progress":
             this._fireEvent("downloadprogress", this._onprogress);
             break;
           case "installed":
-            manifestCache.evict(this.manifestURL);
             this._manifest = msg.manifest;
             this._fireEvent("downloadsuccess", this._ondownloadsuccess);
             this._fireEvent("downloadapplied", this._ondownloadapplied);
             break;
           case "downloaded":
             // We don't update the packaged apps manifests until they
             // are installed or until the update is unstaged.
             if (msg.manifest) {
-              manifestCache.evict(this.manifestURL);
               this._manifest = msg.manifest;
             }
             this._fireEvent("downloadsuccess", this._ondownloadsuccess);
             break;
           case "applied":
-            manifestCache.evict(this.manifestURL);
             this._manifest = msg.manifest;
             this._fireEvent("downloadapplied", this._ondownloadapplied);
             break;
         }
         break;
     }
   },
 
--- a/dom/apps/src/Webapps.jsm
+++ b/dom/apps/src/Webapps.jsm
@@ -105,58 +105,50 @@ this.DOMApplicationRegistry = {
   loadCurrentRegistry: function loadCurrentRegistry(aNext) {
     let file = FileUtils.getFile(DIRECTORY_NAME, ["webapps", "webapps.json"], false);
     if (file && file.exists()) {
       this._loadJSONAsync(file, (function loadRegistry(aData) {
         if (aData) {
           this.webapps = aData;
           let appDir = FileUtils.getDir(DIRECTORY_NAME, ["webapps"], false);
           for (let id in this.webapps) {
-            let app = this.webapps[id];
-
-            app.id = id;
+
+            this.webapps[id].id = id;
 
             // Make sure we have a localId
-            if (app.localId === undefined) {
-              app.localId = this._nextLocalId();
+            if (this.webapps[id].localId === undefined) {
+              this.webapps[id].localId = this._nextLocalId();
             }
 
-            if (app.basePath === undefined) {
-              app.basePath = appDir.path;
+            if (this.webapps[id].basePath === undefined) {
+              this.webapps[id].basePath = appDir.path;
             }
 
             // Default to removable apps.
-            if (app.removable === undefined) {
-              app.removable = true;
+            if (this.webapps[id].removable === undefined) {
+              this.webapps[id].removable = true;
             }
 
             // Default to a non privileged status.
-            if (app.appStatus === undefined) {
-              app.appStatus = Ci.nsIPrincipal.APP_STATUS_INSTALLED;
+            if (this.webapps[id].appStatus === undefined) {
+              this.webapps[id].appStatus = Ci.nsIPrincipal.APP_STATUS_INSTALLED;
             }
 
             // Default to NO_APP_ID and not in browser.
-            if (app.installerAppId === undefined) {
-              app.installerAppId = Ci.nsIScriptSecurityManager.NO_APP_ID;
+            if (this.webapps[id].installerAppId === undefined) {
+              this.webapps[id].installerAppId = Ci.nsIScriptSecurityManager.NO_APP_ID;
             }
-            if (app.installerIsBrowser === undefined) {
-              app.installerIsBrowser = false;
+            if (this.webapps[id].installerIsBrowser === undefined) {
+              this.webapps[id].installerIsBrowser = false;
             }
 
-            // Default installState to "installed", and reset if we shutdown
-            // during an update.
-            if (app.installState === undefined ||
-                app.installState === "updating") {
-              app.installState = "installed";
+            // Default installState to "installed".
+            if (this.webapps[id].installState === undefined) {
+              this.webapps[id].installState = "installed";
             }
-
-            // At startup we can't be downloading, and the $TMP directory
-            // will be empty so we can't just apply a staged update.
-            app.downloading = false;
-            app.readyToApplyDownload = false;
           };
         }
         aNext();
       }).bind(this));
     } else {
       aNext();
     }
   },
--- a/dom/browser-element/BrowserElementChild.js
+++ b/dom/browser-element/BrowserElementChild.js
@@ -20,31 +20,21 @@ let whitelistedEvents = [
   Ci.nsIDOMKeyEvent.DOM_VK_PAGE_DOWN // Volume down.
 ];
 
 function debug(msg) {
   //dump("BrowserElementChild - " + msg + "\n");
 }
 
 function sendAsyncMsg(msg, data) {
-  if (!data) {
-    data = { };
-  }
-
-  data.msg_name = msg;
-  sendAsyncMessage('browser-element-api:call', data);
+  sendAsyncMessage('browser-element-api:' + msg, data);
 }
 
 function sendSyncMsg(msg, data) {
-  if (!data) {
-    data = { };
-  }
-
-  data.msg_name = msg;
-  return sendSyncMessage('browser-element-api:call', data);
+  return sendSyncMessage('browser-element-api:' + msg, data);
 }
 
 /**
  * The BrowserElementChild implements one half of <iframe mozbrowser>.
  * (The other half is, unsurprisingly, BrowserElementParent.)
  *
  * This script is injected into an <iframe mozbrowser> via
  * nsIMessageManager::LoadFrameScript().
@@ -121,44 +111,39 @@ BrowserElementChild.prototype = {
                      /* useCapture = */ true,
                      /* wantsUntrusted = */ false);
 
     // Registers a MozAfterPaint handler for the very first paint.
     this._addMozAfterPaintHandler(function () {
       sendAsyncMsg('firstpaint');
     });
 
-    let self = this;
-
-    let mmCalls = {
-      "purge-history": this._recvPurgeHistory,
-      "get-screenshot": this._recvGetScreenshot,
-      "set-visible": this._recvSetVisible,
-      "get-visible": this._recvVisible,
-      "send-mouse-event": this._recvSendMouseEvent,
-      "send-touch-event": this._recvSendTouchEvent,
-      "get-can-go-back": this._recvCanGoBack,
-      "get-can-go-forward": this._recvCanGoForward,
-      "go-back": this._recvGoBack,
-      "go-forward": this._recvGoForward,
-      "reload": this._recvReload,
-      "stop": this._recvStop,
-      "unblock-modal-prompt": this._recvStopWaiting,
-      "fire-ctx-callback": this._recvFireCtxCallback,
-      "owner-visibility-change": this._recvOwnerVisibilityChange,
-      "exit-fullscreen": this._recvExitFullscreen.bind(this),
-      "activate-next-paint-listener": this._activateNextPaintListener.bind(this),
-      "deactivate-next-paint-listener": this._deactivateNextPaintListener.bind(this)
+    var self = this;
+    function addMsgListener(msg, handler) {
+      addMessageListener('browser-element-api:' + msg, handler.bind(self));
     }
 
-    addMessageListener("browser-element-api:call", function(aMessage) {
-      if (aMessage.data.msg_name in mmCalls) {
-        return mmCalls[aMessage.data.msg_name].apply(self, arguments);
-      }
-    });
+    addMsgListener("purge-history", this._recvPurgeHistory);
+    addMsgListener("get-screenshot", this._recvGetScreenshot);
+    addMsgListener("set-visible", this._recvSetVisible);
+    addMsgListener("get-visible", this._recvVisible);
+    addMsgListener("send-mouse-event", this._recvSendMouseEvent);
+    addMsgListener("send-touch-event", this._recvSendTouchEvent);
+    addMsgListener("get-can-go-back", this._recvCanGoBack);
+    addMsgListener("get-can-go-forward", this._recvCanGoForward);
+    addMsgListener("go-back", this._recvGoBack);
+    addMsgListener("go-forward", this._recvGoForward);
+    addMsgListener("reload", this._recvReload);
+    addMsgListener("stop", this._recvStop);
+    addMsgListener("unblock-modal-prompt", this._recvStopWaiting);
+    addMsgListener("fire-ctx-callback", this._recvFireCtxCallback);
+    addMsgListener("owner-visibility-change", this._recvOwnerVisibilityChange);
+    addMsgListener("exit-fullscreen", this._recvExitFullscreen.bind(this));
+    addMsgListener("activate-next-paint-listener", this._activateNextPaintListener.bind(this));
+    addMsgListener("deactivate-next-paint-listener", this._deactivateNextPaintListener.bind(this));
 
     let els = Cc["@mozilla.org/eventlistenerservice;1"]
                 .getService(Ci.nsIEventListenerService);
 
     // We are using the system group for those events so if something in the
     // content called .stopPropagation() this will still be called.
     els.addSystemEventListener(global, 'keydown',
                                this._keyEventHandler.bind(this),
@@ -196,17 +181,17 @@ BrowserElementChild.prototype = {
   },
 
   observe: function(subject, topic, data) {
     // Ignore notifications not about our document.
     if (subject != content.document)
       return;
     switch (topic) {
       case 'fullscreen-origin-change':
-        sendAsyncMsg('fullscreen-origin-change', { _payload_: data });
+        sendAsyncMsg('fullscreen-origin-change', data);
         break;
       case 'ask-parent-to-exit-fullscreen':
         sendAsyncMsg('exit-fullscreen');
         break;
       case 'ask-parent-to-rollback-fullscreen':
         sendAsyncMsg('rollback-fullscreen');
         break;
     }
@@ -352,17 +337,17 @@ BrowserElementChild.prototype = {
 
   _titleChangedHandler: function(e) {
     debug("Got titlechanged: (" + e.target.title + ")");
     var win = e.target.defaultView;
 
     // Ignore titlechanges which don't come from the top-level
     // <iframe mozbrowser> window.
     if (win == content) {
-      sendAsyncMsg('titlechange', { _payload_: e.target.title });
+      sendAsyncMsg('titlechange', e.target.title);
     }
     else {
       debug("Not top level!");
     }
   },
 
   _iconChangedHandler: function(e) {
     debug("Got iconchanged: (" + e.target.href + ")");
@@ -370,17 +355,17 @@ BrowserElementChild.prototype = {
       return x.toLowerCase() === 'icon';
     });
 
     if (hasIcon) {
       var win = e.target.ownerDocument.defaultView;
       // Ignore iconchanges which don't come from the top-level
       // <iframe mozbrowser> window.
       if (win == content) {
-        sendAsyncMsg('iconchange', { _payload_: e.target.href });
+        sendAsyncMsg('iconchange', e.target.href);
       }
       else {
         debug("Not top level!");
       }
     }
   },
 
   _addMozAfterPaintHandler: function(callback) {
@@ -772,17 +757,17 @@ BrowserElementChild.prototype = {
       if (!this._seenLoadStart) {
         return;
       }
 
       // Remove password and wyciwyg from uri.
       location = Cc["@mozilla.org/docshell/urifixup;1"]
         .getService(Ci.nsIURIFixup).createExposableURI(location);
 
-      sendAsyncMsg('locationchange', { _payload_: location.spec });
+      sendAsyncMsg('locationchange', location.spec);
     },
 
     onStateChange: function(webProgress, request, stateFlags, status) {
       if (webProgress != docShell) {
         return;
       }
 
       if (stateFlags & Ci.nsIWebProgressListener.STATE_START) {
@@ -797,17 +782,17 @@ BrowserElementChild.prototype = {
         // stopped.
         if (status == Cr.NS_OK ||
             status == Cr.NS_BINDING_ABORTED) {
           return;
         }
 
         // TODO See nsDocShell::DisplayLoadError for a list of all the error
         // codes (the status param) we should eventually handle here.
-        sendAsyncMsg('error', { type: 'other' });
+        sendAsyncMsg('error', {type: 'other'});
       }
     },
 
     onSecurityChange: function(webProgress, request, state) {
       if (webProgress != docShell) {
         return;
       }
 
@@ -824,17 +809,17 @@ BrowserElementChild.prototype = {
       else {
         debug("Unexpected securitychange state!");
         stateDesc = '???';
       }
 
       // XXX Until bug 764496 is fixed, this will always return false.
       var isEV = !!(state & Ci.nsIWebProgressListener.STATE_IDENTITY_EV_TOPLEVEL);
 
-      sendAsyncMsg('securitychange', { state: stateDesc, extendedValidation: isEV });
+      sendAsyncMsg('securitychange', {state: stateDesc, extendedValidation: isEV});
     },
 
     onStatusChange: function(webProgress, request, status, message) {},
     onProgressChange: function(webProgress, request, curSelfProgress,
                                maxSelfProgress, curTotalProgress, maxTotalProgress) {},
   },
 
   // Expose the message manager for WebApps and others.
--- a/dom/browser-element/BrowserElementParent.js
+++ b/dom/browser-element/BrowserElementParent.js
@@ -6,35 +6,87 @@
 
 let Cu = Components.utils;
 let Ci = Components.interfaces;
 let Cc = Components.classes;
 let Cr = Components.results;
 
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
-Cu.import("resource://gre/modules/BrowserElementParent.jsm");
+Cu.import("resource://gre/modules/BrowserElementPromptService.jsm");
+
+XPCOMUtils.defineLazyGetter(this, "DOMApplicationRegistry", function () {
+  Cu.import("resource://gre/modules/Webapps.jsm");
+  return DOMApplicationRegistry;
+});
 
 const NS_PREFBRANCH_PREFCHANGE_TOPIC_ID = "nsPref:changed";
 const BROWSER_FRAMES_ENABLED_PREF = "dom.mozBrowserFramesEnabled";
+const TOUCH_EVENTS_ENABLED_PREF = "dom.w3c_touch_events.enabled";
 
 function debug(msg) {
-  //dump("BrowserElementParent.js - " + msg + "\n");
+  //dump("BrowserElementParent - " + msg + "\n");
+}
+
+function getBoolPref(prefName, def) {
+  try {
+    return Services.prefs.getBoolPref(prefName);
+  }
+  catch(err) {
+    return def;
+  }
+}
+
+function getIntPref(prefName, def) {
+  try {
+    return Services.prefs.getIntPref(prefName);
+  }
+  catch(err) {
+    return def;
+  }
+}
+
+function exposeAll(obj) {
+  // Filter for Objects and Arrays.
+  if (typeof obj !== "object" || !obj)
+    return;
+
+  // Recursively expose our children.
+  Object.keys(obj).forEach(function(key) {
+    exposeAll(obj[key]);
+  });
+
+  // If we're not an Array, generate an __exposedProps__ object for ourselves.
+  if (obj instanceof Array)
+    return;
+  var exposed = {};
+  Object.keys(obj).forEach(function(key) {
+    exposed[key] = 'rw';
+  });
+  obj.__exposedProps__ = exposed;
+}
+
+function defineAndExpose(obj, name, value) {
+  obj[name] = value;
+  if (!('__exposedProps__' in obj))
+    obj.__exposedProps__ = {};
+  obj.__exposedProps__[name] = 'r';
 }
 
 /**
  * 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.
+ * 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.
  */
 
 function BrowserElementParentFactory() {
   this._initialized = false;
 }
 
 BrowserElementParentFactory.prototype = {
   classID: Components.ID("{ddeafdac-cb39-47c4-9cb8-c9027ee36d26}"),
@@ -48,35 +100,38 @@ BrowserElementParentFactory.prototype = 
   _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);
+      var prefs = Cc["@mozilla.org/preferences-service;1"].getService(Ci.nsIPrefBranch);
+      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-frame-shown', /* ownsWeak = */ true);
-    Services.obs.addObserver(this, 'in-process-browser-or-app-frame-shown', /* ownsWeak = */ true);
+    var os = Cc["@mozilla.org/observer-service;1"].getService(Ci.nsIObserverService);
+    os.addObserver(this, 'remote-browser-frame-shown', /* ownsWeak = */ true);
+    os.addObserver(this, 'in-process-browser-or-app-frame-shown', /* ownsWeak = */ true);
   },
 
   _browserFramesPrefEnabled: function() {
+    var prefs = Cc["@mozilla.org/preferences-service;1"].getService(Ci.nsIPrefBranch);
     try {
-      return Services.prefs.getBoolPref(BROWSER_FRAMES_ENABLED_PREF);
+      return prefs.getBoolPref(BROWSER_FRAMES_ENABLED_PREF);
     }
     catch(e) {
       return false;
     }
   },
 
   _observeInProcessBrowserFrameShown: function(frameLoader) {
     debug("In-process browser frame shown " + frameLoader);
@@ -85,17 +140,17 @@ BrowserElementParentFactory.prototype = 
 
   _observeRemoteBrowserFrameShown: function(frameLoader) {
     debug("Remote browser frame shown " + frameLoader);
     this._createBrowserElementParent(frameLoader, /* hasRemoteFrame = */ true);
   },
 
   _createBrowserElementParent: function(frameLoader, hasRemoteFrame) {
     let frameElement = frameLoader.QueryInterface(Ci.nsIFrameLoader).ownerElement;
-    this._bepMap.set(frameElement, BrowserElementParentBuilder.create(frameLoader, hasRemoteFrame));
+    this._bepMap.set(frameElement, new BrowserElementParent(frameLoader, hasRemoteFrame));
   },
 
   observe: function(subject, topic, data) {
     switch(topic) {
     case 'app-startup':
       this._init();
       break;
     case NS_PREFBRANCH_PREFCHANGE_TOPIC_ID:
@@ -111,9 +166,532 @@ BrowserElementParentFactory.prototype = 
       break;
     case 'content-document-global-created':
       this._observeContentGlobalCreated(subject);
       break;
     }
   },
 };
 
+function BrowserElementParent(frameLoader, hasRemoteFrame) {
+  debug("Creating new BrowserElementParent object for " + frameLoader);
+  this._domRequestCounter = 0;
+  this._pendingDOMRequests = {};
+  this._hasRemoteFrame = hasRemoteFrame;
+  this._nextPaintListeners = [];
+
+  this._frameLoader = frameLoader;
+  this._frameElement = frameLoader.QueryInterface(Ci.nsIFrameLoader).ownerElement;
+  if (!this._frameElement) {
+    debug("No frame element?");
+    return;
+  }
+
+  this._mm = frameLoader.messageManager;
+
+  // Messages we receive are handed to functions which take a (data) argument,
+  // where |data| is the message manager's data object.
+
+  let self = this;
+  function addMessageListener(msg, handler) {
+    function checkedHandler() {
+      if (self._isAlive()) {
+        return handler.apply(self, arguments);
+      }
+    }
+    self._mm.addMessageListener('browser-element-api:' + msg, checkedHandler);
+  }
+
+  addMessageListener("hello", this._recvHello);
+  addMessageListener("get-name", this._recvGetName);
+  addMessageListener("get-fullscreen-allowed", this._recvGetFullscreenAllowed);
+  addMessageListener("contextmenu", this._fireCtxMenuEvent);
+  addMessageListener("locationchange", this._fireEventFromMsg);
+  addMessageListener("loadstart", this._fireEventFromMsg);
+  addMessageListener("loadend", this._fireEventFromMsg);
+  addMessageListener("titlechange", this._fireEventFromMsg);
+  addMessageListener("iconchange", this._fireEventFromMsg);
+  addMessageListener("close", this._fireEventFromMsg);
+  addMessageListener("securitychange", this._fireEventFromMsg);
+  addMessageListener("error", this._fireEventFromMsg);
+  addMessageListener("scroll", this._fireEventFromMsg);
+  addMessageListener("firstpaint", this._fireEventFromMsg);
+  addMessageListener("documentfirstpaint", this._fireEventFromMsg);
+  addMessageListener("nextpaint", this._recvNextPaint);
+  addMessageListener("keyevent", this._fireKeyEvent);
+  addMessageListener("showmodalprompt", this._handleShowModalPrompt);
+  addMessageListener('got-purge-history', this._gotDOMRequestResult);
+  addMessageListener('got-screenshot', this._gotDOMRequestResult);
+  addMessageListener('got-can-go-back', this._gotDOMRequestResult);
+  addMessageListener('got-can-go-forward', this._gotDOMRequestResult);
+  addMessageListener('fullscreen-origin-change', this._remoteFullscreenOriginChange);
+  addMessageListener('rollback-fullscreen', this._remoteFrameFullscreenReverted);
+  addMessageListener('exit-fullscreen', this._exitFullscreen);
+  addMessageListener('got-visible', this._gotDOMRequestResult);
+
+  let os = Cc["@mozilla.org/observer-service;1"].getService(Ci.nsIObserverService);
+  os.addObserver(this, 'ask-children-to-exit-fullscreen', /* ownsWeak = */ true);
+  os.addObserver(this, 'oop-frameloader-crashed', /* ownsWeak = */ true);
+
+  function defineMethod(name, fn) {
+    XPCNativeWrapper.unwrap(self._frameElement)[name] = function() {
+      if (self._isAlive()) {
+        return fn.apply(self, arguments);
+      }
+    };
+  }
+
+  function defineDOMRequestMethod(domName, msgName) {
+    XPCNativeWrapper.unwrap(self._frameElement)[domName] = function() {
+      if (self._isAlive()) {
+        return self._sendDOMRequest(msgName);
+      }
+    };
+  }
+
+  // Define methods on the frame element.
+  defineMethod('setVisible', this._setVisible);
+  defineDOMRequestMethod('getVisible', 'get-visible');
+  defineMethod('sendMouseEvent', this._sendMouseEvent);
+
+  // 0 = disabled, 1 = enabled, 2 - auto detect
+  if (getIntPref(TOUCH_EVENTS_ENABLED_PREF, 0) != 0) {
+    defineMethod('sendTouchEvent', this._sendTouchEvent);
+  }
+  defineMethod('goBack', this._goBack);
+  defineMethod('goForward', this._goForward);
+  defineMethod('reload', this._reload);
+  defineMethod('stop', this._stop);
+  defineMethod('purgeHistory', this._purgeHistory);
+  defineMethod('getScreenshot', this._getScreenshot);
+  defineMethod('addNextPaintListener', this._addNextPaintListener);
+  defineMethod('removeNextPaintListener', this._removeNextPaintListener);
+  defineDOMRequestMethod('getCanGoBack', 'get-can-go-back');
+  defineDOMRequestMethod('getCanGoForward', 'get-can-go-forward');
+
+  // Listen to visibilitychange on the iframe's owner window, and forward it
+  // down to the child.
+  this._window.addEventListener('visibilitychange',
+                                this._ownerVisibilityChange.bind(this),
+                                /* useCapture = */ false,
+                                /* wantsUntrusted = */ false);
+
+  // Insert ourself into the prompt service.
+  BrowserElementPromptService.mapFrameToBrowserElementParent(this._frameElement, this);
+
+  // If this browser represents an app then let the Webapps module register for
+  // any messages that it needs.
+  let appManifestURL =
+    this._frameElement.QueryInterface(Ci.nsIMozBrowserFrame).appManifestURL;
+  if (appManifestURL) {
+    let appId =
+      DOMApplicationRegistry.getAppLocalIdByManifestURL(appManifestURL);
+    if (appId != Ci.nsIScriptSecurityManager.NO_APP_ID) {
+      DOMApplicationRegistry.registerBrowserElementParentForApp(this, appId);
+    }
+  }
+}
+
+BrowserElementParent.prototype = {
+
+  QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver,
+                                         Ci.nsISupportsWeakReference]),
+
+  /**
+   * You shouldn't touch this._frameElement or this._window if _isAlive is
+   * false.  (You'll likely get an exception if you do.)
+   */
+  _isAlive: function() {
+    return !Cu.isDeadWrapper(this._frameElement) &&
+           !Cu.isDeadWrapper(this._frameElement.ownerDocument) &&
+           !Cu.isDeadWrapper(this._frameElement.ownerDocument.defaultView);
+  },
+
+  get _window() {
+    return this._frameElement.ownerDocument.defaultView;
+  },
+
+  get _windowUtils() {
+    return this._window.QueryInterface(Ci.nsIInterfaceRequestor)
+                       .getInterface(Ci.nsIDOMWindowUtils);
+  },
+
+  promptAuth: function(authDetail, callback) {
+    let evt;
+    let self = this;
+    let callbackCalled = false;
+    let cancelCallback = function() {
+      if (!callbackCalled) {
+        callbackCalled = true;
+        callback(false, null, null);
+      }
+    };
+
+    if (authDetail.isOnlyPassword) {
+      // We don't handle password-only prompts, so just cancel it.
+      cancelCallback();
+      return;
+    } else { /* username and password */
+      let detail = {
+        host:     authDetail.host,
+        realm:    authDetail.realm
+      };
+
+      evt = this._createEvent('usernameandpasswordrequired', detail,
+                              /* cancelable */ true);
+      defineAndExpose(evt.detail, 'authenticate', function(username, password) {
+        if (callbackCalled)
+          return;
+        callbackCalled = true;
+        callback(true, username, password);
+      });
+    }
+
+    defineAndExpose(evt.detail, 'cancel', function() {
+      cancelCallback();
+    });
+
+    this._frameElement.dispatchEvent(evt);
+
+    if (!evt.defaultPrevented) {
+      cancelCallback();
+    }
+  },
+
+  _sendAsyncMsg: function(msg, data) {
+    try {
+      this._mm.sendAsyncMessage('browser-element-api:' + msg, data);
+    } catch (e) {
+      return false;
+    }
+    return true;
+  },
+
+  _recvHello: function(data) {
+    debug("recvHello");
+
+    // Inform our child if our owner element's document is invisible.  Note
+    // that we must do so here, rather than in the BrowserElementParent
+    // constructor, because the BrowserElementChild may not be initialized when
+    // we run our constructor.
+    if (this._window.document.hidden) {
+      this._ownerVisibilityChange();
+    }
+  },
+
+  _recvGetName: function(data) {
+    return this._frameElement.getAttribute('name');
+  },
+
+  _recvGetFullscreenAllowed: function(data) {
+    return this._frameElement.hasAttribute('allowfullscreen') ||
+           this._frameElement.hasAttribute('mozallowfullscreen');
+  },
+
+  _fireCtxMenuEvent: function(data) {
+    let evtName = data.name.substring('browser-element-api:'.length);
+    let detail = data.json;
+
+    debug('fireCtxMenuEventFromMsg: ' + evtName + ' ' + detail);
+    let evt = this._createEvent(evtName, detail);
+
+    if (detail.contextmenu) {
+      var self = this;
+      defineAndExpose(evt.detail, 'contextMenuItemSelected', function(id) {
+        self._sendAsyncMsg('fire-ctx-callback', {menuitem: id});
+      });
+    }
+    // The embedder may have default actions on context menu events, so
+    // we fire a context menu event even if the child didn't define a
+    // custom context menu
+    this._frameElement.dispatchEvent(evt);
+  },
+
+  /**
+   * Fire either a vanilla or a custom event, depending on the contents of
+   * |data|.
+   */
+  _fireEventFromMsg: function(data) {
+    let name = data.name.substring('browser-element-api:'.length);
+    let detail = data.json;
+
+    debug('fireEventFromMsg: ' + name + ', ' + detail);
+    let evt = this._createEvent(name, detail,
+                                /* cancelable = */ false);
+    this._frameElement.dispatchEvent(evt);
+  },
+
+  _handleShowModalPrompt: function(data) {
+    // Fire a showmodalprmopt event on the iframe.  When this method is called,
+    // the child is spinning in a nested event loop waiting for an
+    // unblock-modal-prompt message.
+    //
+    // If the embedder calls preventDefault() on the showmodalprompt event,
+    // we'll block the child until event.detail.unblock() is called.
+    //
+    // Otherwise, if preventDefault() is not called, we'll send the
+    // unblock-modal-prompt message to the child as soon as the event is done
+    // dispatching.
+
+    let detail = data.json;
+    debug('handleShowPrompt ' + JSON.stringify(detail));
+
+    // Strip off the windowID property from the object we send along in the
+    // event.
+    let windowID = detail.windowID;
+    delete detail.windowID;
+    debug("Event will have detail: " + JSON.stringify(detail));
+    let evt = this._createEvent('showmodalprompt', detail,
+                                /* cancelable = */ true);
+
+    let self = this;
+    let unblockMsgSent = false;
+    function sendUnblockMsg() {
+      if (unblockMsgSent) {
+        return;
+      }
+      unblockMsgSent = true;
+
+      // We don't need to sanitize evt.detail.returnValue (e.g. converting the
+      // return value of confirm() to a boolean); Gecko does that for us.
+
+      let data = { windowID: windowID,
+                   returnValue: evt.detail.returnValue };
+      self._sendAsyncMsg('unblock-modal-prompt', data);
+    }
+
+    defineAndExpose(evt.detail, 'unblock', function() {
+      sendUnblockMsg();
+    });
+
+    this._frameElement.dispatchEvent(evt);
+
+    if (!evt.defaultPrevented) {
+      // Unblock the inner frame immediately.  Otherwise we'll unblock upon
+      // evt.detail.unblock().
+      sendUnblockMsg();
+    }
+  },
+
+  _createEvent: function(evtName, detail, cancelable) {
+    // This will have to change if we ever want to send a CustomEvent with null
+    // detail.  For now, it's OK.
+    if (detail !== undefined && detail !== null) {
+      exposeAll(detail);
+      return new this._window.CustomEvent('mozbrowser' + evtName,
+                                          { bubbles: true,
+                                            cancelable: cancelable,
+                                            detail: detail });
+    }
+
+    return new this._window.Event('mozbrowser' + evtName,
+                                  { bubbles: true,
+                                    cancelable: cancelable });
+  },
+
+  /**
+   * Kick off a DOMRequest in the child process.
+   *
+   * We'll fire an event called |msgName| on the child process, passing along
+   * an object with two fields:
+   *
+   *  - id:  the ID of this request.
+   *  - arg: arguments to pass to the child along with this request.
+   *
+   * We expect the child to pass the ID back to us upon completion of the
+   * request.  See _gotDOMRequestResult.
+   */
+  _sendDOMRequest: function(msgName, args) {
+    let id = 'req_' + this._domRequestCounter++;
+    let req = Services.DOMRequest.createRequest(this._window);
+    if (this._sendAsyncMsg(msgName, {id: id, args: args})) {
+      this._pendingDOMRequests[id] = req;
+    } else {
+      Services.DOMRequest.fireErrorAsync(req, "fail");
+    }
+    return req;
+  },
+
+  /**
+   * Called when the child process finishes handling a DOMRequest.  data.json
+   * must have the fields [id, successRv], if the DOMRequest was successful, or
+   * [id, errorMsg], if the request was not successful.
+   *
+   * The fields have the following meanings:
+   *
+   *  - id:        the ID of the DOM request (see _sendDOMRequest)
+   *  - successRv: the request's return value, if the request succeeded
+   *  - errorMsg:  the message to pass to DOMRequest.fireError(), if the request
+   *               failed.
+   *
+   */
+  _gotDOMRequestResult: function(data) {
+    let req = this._pendingDOMRequests[data.json.id];
+    delete this._pendingDOMRequests[data.json.id];
+
+    if ('successRv' in data.json) {
+      debug("Successful gotDOMRequestResult.");
+      Services.DOMRequest.fireSuccess(req, data.json.successRv);
+    }
+    else {
+      debug("Got error in gotDOMRequestResult.");
+      Services.DOMRequest.fireErrorAsync(req, data.json.errorMsg);
+    }
+  },
+
+  _setVisible: function(visible) {
+    this._sendAsyncMsg('set-visible', {visible: visible});
+  },
+
+  _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: function(type, identifiers, touchesX, touchesY,
+                            radiisX, radiisY, rotationAngles, forces,
+                            count, modifiers) {
+    this._sendAsyncMsg("send-touch-event", {
+      "type": type,
+      "identifiers": identifiers,
+      "touchesX": touchesX,
+      "touchesY": touchesY,
+      "radiisX": radiisX,
+      "radiisY": radiisY,
+      "rotationAngles": rotationAngles,
+      "forces": forces,
+      "count": count,
+      "modifiers": modifiers
+    });
+  },
+
+  _goBack: function() {
+    this._sendAsyncMsg('go-back');
+  },
+
+  _goForward: function() {
+    this._sendAsyncMsg('go-forward');
+  },
+
+  _reload: function(hardReload) {
+    this._sendAsyncMsg('reload', {hardReload: hardReload});
+  },
+
+  _stop: function() {
+    this._sendAsyncMsg('stop');
+  },
+
+  _purgeHistory: function() {
+    return this._sendDOMRequest('purge-history');
+  },
+
+  _getScreenshot: function(_width, _height) {
+    let width = parseInt(_width);
+    let height = parseInt(_height);
+    if (isNaN(width) || isNaN(height) || width < 0 || height < 0) {
+      throw Components.Exception("Invalid argument",
+                                 Cr.NS_ERROR_INVALID_ARG);
+    }
+
+    return this._sendDOMRequest('get-screenshot',
+                                {width: width, height: height});
+  },
+
+  _recvNextPaint: function(data) {
+    let listeners = this._nextPaintListeners;
+    this._nextPaintListeners = [];
+    for (let listener of listeners) {
+      try {
+        listener();
+      } catch (e) {
+        // If a listener throws we'll continue.
+      }
+    }
+  },
+
+  _addNextPaintListener: function(listener) {
+    if (typeof listener != 'function')
+      throw Components.Exception("Invalid argument", Cr.NS_ERROR_INVALID_ARG);
+
+    if (this._nextPaintListeners.push(listener) == 1)
+      this._sendAsyncMsg('activate-next-paint-listener');
+  },
+
+  _removeNextPaintListener: function(listener) {
+    if (typeof listener != 'function')
+      throw Components.Exception("Invalid argument", Cr.NS_ERROR_INVALID_ARG);
+
+    for (let i = this._nextPaintListeners.length - 1; i >= 0; i--) {
+      if (this._nextPaintListeners[i] == listener) {
+        this._nextPaintListeners.splice(i, 1);
+        break;
+      }
+    }
+
+    if (this._nextPaintListeners.length == 0)
+      this._sendAsyncMsg('deactivate-next-paint-listener');
+  },
+
+  _fireKeyEvent: function(data) {
+    let evt = this._window.document.createEvent("KeyboardEvent");
+    evt.initKeyEvent(data.json.type, true, true, this._window,
+                     false, false, false, false, // modifiers
+                     data.json.keyCode,
+                     data.json.charCode);
+
+    this._frameElement.dispatchEvent(evt);
+  },
+
+  /**
+   * Called when the visibility of the window which owns this iframe changes.
+   */
+  _ownerVisibilityChange: function() {
+    this._sendAsyncMsg('owner-visibility-change',
+                       {visible: !this._window.document.hidden});
+  },
+
+  _exitFullscreen: function() {
+    this._windowUtils.exitFullscreen();
+  },
+
+  _remoteFullscreenOriginChange: function(data) {
+    let origin = data.json;
+    this._windowUtils.remoteFrameFullscreenChanged(this._frameElement, origin);
+  },
+
+  _remoteFrameFullscreenReverted: function(data) {
+    this._windowUtils.remoteFrameFullscreenReverted();
+  },
+
+  _fireFatalError: function() {
+    let evt = this._createEvent('error', {type: 'fatal'},
+                                /* cancelable = */ false);
+    this._frameElement.dispatchEvent(evt);
+  },
+
+  observe: function(subject, topic, data) {
+    switch(topic) {
+    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._hasRemoteFrame) {
+        this._sendAsyncMsg('exit-fullscreen');
+      }
+      break;
+    default:
+      debug('Unknown topic: ' + topic);
+      break;
+    };
+  },
+};
+
 this.NSGetFactory = XPCOMUtils.generateNSGetFactory([BrowserElementParentFactory]);
deleted file mode 100644
--- a/dom/browser-element/BrowserElementParent.jsm
+++ /dev/null
@@ -1,607 +0,0 @@
-/* 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";
-
-let Cu = Components.utils;
-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 = ["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.jsm - " + msg + "\n");
-}
-
-function getIntPref(prefName, def) {
-  try {
-    return Services.prefs.getIntPref(prefName);
-  }
-  catch(err) {
-    return def;
-  }
-}
-
-function exposeAll(obj) {
-  // Filter for Objects and Arrays.
-  if (typeof obj !== "object" || !obj)
-    return;
-
-  // Recursively expose our children.
-  Object.keys(obj).forEach(function(key) {
-    exposeAll(obj[key]);
-  });
-
-  // If we're not an Array, generate an __exposedProps__ object for ourselves.
-  if (obj instanceof Array)
-    return;
-  var exposed = {};
-  Object.keys(obj).forEach(function(key) {
-    exposed[key] = 'rw';
-  });
-  obj.__exposedProps__ = exposed;
-}
-
-function defineAndExpose(obj, name, value) {
-  obj[name] = value;
-  if (!('__exposedProps__' in obj))
-    obj.__exposedProps__ = {};
-  obj.__exposedProps__[name] = 'r';
-}
-
-this.BrowserElementParentBuilder = {
-  create: function create(frameLoader, hasRemoteFrame) {
-    return new BrowserElementParent(frameLoader, hasRemoteFrame);
-  }
-}
-
-function BrowserElementParent(frameLoader, hasRemoteFrame) {
-  debug("Creating new BrowserElementParent object for " + frameLoader);
-  this._domRequestCounter = 0;
-  this._pendingDOMRequests = {};
-  this._hasRemoteFrame = hasRemoteFrame;
-  this._nextPaintListeners = [];
-
-  this._frameLoader = frameLoader;
-  this._frameElement = frameLoader.QueryInterface(Ci.nsIFrameLoader).ownerElement;
-  if (!this._frameElement) {
-    debug("No frame element?");
-    return;
-  }
-
-  this._mm = frameLoader.messageManager;
-  let self = this;
-
-  // Messages we receive are handed to functions which take a (data) argument,
-  // where |data| is the message manager's data object.
-  // We use a single message and dispatch to various function based
-  // on data.msg_name
-  let mmCalls = {
-    "get-name": this._recvGetName,
-    "get-fullscreen-allowed": this._recvGetFullscreenAllowed,
-    "hello": this._recvHello,
-    "contextmenu": this._fireCtxMenuEvent,
-    "locationchange": this._fireEventFromMsg,
-    "loadstart": this._fireEventFromMsg,
-    "loadend": this._fireEventFromMsg,
-    "titlechange": this._fireEventFromMsg,
-    "iconchange": this._fireEventFromMsg,
-    "close": this._fireEventFromMsg,
-    "securitychange": this._fireEventFromMsg,
-    "error": this._fireEventFromMsg,
-    "scroll": this._fireEventFromMsg,
-    "firstpaint": this._fireEventFromMsg,
-    "documentfirstpaint": this._fireEventFromMsg,
-    "nextpaint": this._recvNextPaint,
-    "keyevent": this._fireKeyEvent,
-    "showmodalprompt": this._handleShowModalPrompt,
-    "got-purge-history": this._gotDOMRequestResult,
-    "got-screenshot": this._gotDOMRequestResult,
-    "got-can-go-back": this._gotDOMRequestResult,
-    "got-can-go-forward": this._gotDOMRequestResult,
-    "fullscreen-origin-change": this._remoteFullscreenOriginChange,
-    "rollback-fullscreen": this._remoteFrameFullscreenReverted,
-    "exit-fullscreen": this._exitFullscreen,
-    "got-visible": this._gotDOMRequestResult
-  }
-
-  this._mm.addMessageListener('browser-element-api:call', function(aMsg) {
-    if (self._isAlive() && (aMsg.data.msg_name in mmCalls)) {
-      return mmCalls[aMsg.data.msg_name].apply(self, arguments);
-    }
-  });
-
-  Services.obs.addObserver(this, 'ask-children-to-exit-fullscreen', /* ownsWeak = */ true);
-  Services.obs.addObserver(this, 'oop-frameloader-crashed', /* ownsWeak = */ true);
-
-  let defineMethod = function(name, fn) {
-    XPCNativeWrapper.unwrap(self._frameElement)[name] = function() {
-      if (self._isAlive()) {
-        return fn.apply(self, arguments);
-      }
-    };
-  }
-
-  let defineDOMRequestMethod = function(domName, msgName) {
-    XPCNativeWrapper.unwrap(self._frameElement)[domName] = function() {
-      if (self._isAlive()) {
-        return self._sendDOMRequest(msgName);
-      }
-    };
-  }
-
-  // Define methods on the frame element.
-  defineMethod('setVisible', this._setVisible);
-  defineDOMRequestMethod('getVisible', 'get-visible');
-  defineMethod('sendMouseEvent', this._sendMouseEvent);
-
-  // 0 = disabled, 1 = enabled, 2 - auto detect
-  if (getIntPref(TOUCH_EVENTS_ENABLED_PREF, 0) != 0) {
-    defineMethod('sendTouchEvent', this._sendTouchEvent);
-  }
-  defineMethod('goBack', this._goBack);
-  defineMethod('goForward', this._goForward);
-  defineMethod('reload', this._reload);
-  defineMethod('stop', this._stop);
-  defineMethod('purgeHistory', this._purgeHistory);
-  defineMethod('getScreenshot', this._getScreenshot);
-  defineMethod('addNextPaintListener', this._addNextPaintListener);
-  defineMethod('removeNextPaintListener', this._removeNextPaintListener);
-  defineDOMRequestMethod('getCanGoBack', 'get-can-go-back');
-  defineDOMRequestMethod('getCanGoForward', 'get-can-go-forward');
-
-  // Listen to visibilitychange on the iframe's owner window, and forward it
-  // down to the child.
-  this._window.addEventListener('visibilitychange',
-                                this._ownerVisibilityChange.bind(this),
-                                /* useCapture = */ false,
-                                /* wantsUntrusted = */ false);
-
-  // Insert ourself into the prompt service.
-  BrowserElementPromptService.mapFrameToBrowserElementParent(this._frameElement, this);
-
-  // If this browser represents an app then let the Webapps module register for
-  // any messages that it needs.
-  let appManifestURL =
-    this._frameElement.QueryInterface(Ci.nsIMozBrowserFrame).appManifestURL;
-  if (appManifestURL) {
-    let appId =
-      DOMApplicationRegistry.getAppLocalIdByManifestURL(appManifestURL);
-    if (appId != Ci.nsIScriptSecurityManager.NO_APP_ID) {
-      DOMApplicationRegistry.registerBrowserElementParentForApp(this, appId);
-    }
-  }
-}
-
-BrowserElementParent.prototype = {
-
-  QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver,
-                                         Ci.nsISupportsWeakReference]),
-
-  /**
-   * You shouldn't touch this._frameElement or this._window if _isAlive is
-   * false.  (You'll likely get an exception if you do.)
-   */
-  _isAlive: function() {
-    return !Cu.isDeadWrapper(this._frameElement) &&
-           !Cu.isDeadWrapper(this._frameElement.ownerDocument) &&
-           !Cu.isDeadWrapper(this._frameElement.ownerDocument.defaultView);
-  },
-
-  get _window() {
-    return this._frameElement.ownerDocument.defaultView;
-  },
-
-  get _windowUtils() {
-    return this._window.QueryInterface(Ci.nsIInterfaceRequestor)
-                       .getInterface(Ci.nsIDOMWindowUtils);
-  },
-
-  promptAuth: function(authDetail, callback) {
-    let evt;
-    let self = this;
-    let callbackCalled = false;
-    let cancelCallback = function() {
-      if (!callbackCalled) {
-        callbackCalled = true;
-        callback(false, null, null);
-      }
-    };
-
-    if (authDetail.isOnlyPassword) {
-      // We don't handle password-only prompts, so just cancel it.
-      cancelCallback();
-      return;
-    } else { /* username and password */
-      let detail = {
-        host:     authDetail.host,
-        realm:    authDetail.realm
-      };
-
-      evt = this._createEvent('usernameandpasswordrequired', detail,
-                              /* cancelable */ true);
-      defineAndExpose(evt.detail, 'authenticate', function(username, password) {
-        if (callbackCalled)
-          return;
-        callbackCalled = true;
-        callback(true, username, password);
-      });
-    }
-
-    defineAndExpose(evt.detail, 'cancel', function() {
-      cancelCallback();
-    });
-
-    this._frameElement.dispatchEvent(evt);
-
-    if (!evt.defaultPrevented) {
-      cancelCallback();
-    }
-  },
-
-  _sendAsyncMsg: function(msg, data) {
-    try {
-      if (!data) {
-        data = { };
-      }
-
-      data.msg_name = msg;
-      this._mm.sendAsyncMessage('browser-element-api:call', data);
-    } catch (e) {
-      return false;
-    }
-    return true;
-  },
-
-  _recvHello: function(data) {
-    debug("recvHello");
-
-    // Inform our child if our owner element's document is invisible.  Note
-    // that we must do so here, rather than in the BrowserElementParent
-    // constructor, because the BrowserElementChild may not be initialized when
-    // we run our constructor.
-    if (this._window.document.hidden) {
-      this._ownerVisibilityChange();
-    }
-  },
-
-  _recvGetName: function(data) {
-    return this._frameElement.getAttribute('name');
-  },
-
-  _recvGetFullscreenAllowed: function(data) {
-    return this._frameElement.hasAttribute('allowfullscreen') ||
-           this._frameElement.hasAttribute('mozallowfullscreen');
-  },
-
-  _fireCtxMenuEvent: function(data) {
-    let detail = data.json;
-    let evtName = detail.msg_name;
-
-    debug('fireCtxMenuEventFromMsg: ' + evtName + ' ' + detail);
-    let evt = this._createEvent(evtName, detail);
-
-    if (detail.contextmenu) {
-      var self = this;
-      defineAndExpose(evt.detail, 'contextMenuItemSelected', function(id) {
-        self._sendAsyncMsg('fire-ctx-callback', {menuitem: id});
-      });
-    }
-    // The embedder may have default actions on context menu events, so
-    // we fire a context menu event even if the child didn't define a
-    // custom context menu
-    this._frameElement.dispatchEvent(evt);
-  },
-
-  /**
-   * Fire either a vanilla or a custom event, depending on the contents of
-   * |data|.
-   */
-  _fireEventFromMsg: function(data) {
-    let detail = data.json;
-    let name = detail.msg_name;
-
-    // For events that send a "_payload_" property, we just want to transmit
-    // this in the event.
-    if (detail._payload_) {
-      detail = detail._payload_;
-    }
-
-    debug('fireEventFromMsg: ' + name + ', ' + JSON.stringify(detail));
-    let evt = this._createEvent(name, detail,
-                                /* cancelable = */ false);
-    this._frameElement.dispatchEvent(evt);
-  },
-
-  _handleShowModalPrompt: function(data) {
-    // Fire a showmodalprmopt event on the iframe.  When this method is called,
-    // the child is spinning in a nested event loop waiting for an
-    // unblock-modal-prompt message.
-    //
-    // If the embedder calls preventDefault() on the showmodalprompt event,
-    // we'll block the child until event.detail.unblock() is called.
-    //
-    // Otherwise, if preventDefault() is not called, we'll send the
-    // unblock-modal-prompt message to the child as soon as the event is done
-    // dispatching.
-
-    let detail = data.json;
-    debug('handleShowPrompt ' + JSON.stringify(detail));
-
-    // Strip off the windowID property from the object we send along in the
-    // event.
-    let windowID = detail.windowID;
-    delete detail.windowID;
-    debug("Event will have detail: " + JSON.stringify(detail));
-    let evt = this._createEvent('showmodalprompt', detail,
-                                /* cancelable = */ true);
-
-    let self = this;
-    let unblockMsgSent = false;
-    function sendUnblockMsg() {
-      if (unblockMsgSent) {
-        return;
-      }
-      unblockMsgSent = true;
-
-      // We don't need to sanitize evt.detail.returnValue (e.g. converting the
-      // return value of confirm() to a boolean); Gecko does that for us.
-
-      let data = { windowID: windowID,
-                   returnValue: evt.detail.returnValue };
-      self._sendAsyncMsg('unblock-modal-prompt', data);
-    }
-
-    defineAndExpose(evt.detail, 'unblock', function() {
-      sendUnblockMsg();
-    });
-
-    this._frameElement.dispatchEvent(evt);
-
-    if (!evt.defaultPrevented) {
-      // Unblock the inner frame immediately.  Otherwise we'll unblock upon
-      // evt.detail.unblock().
-      sendUnblockMsg();
-    }
-  },
-
-  _createEvent: function(evtName, detail, cancelable) {
-    // This will have to change if we ever want to send a CustomEvent with null
-    // detail.  For now, it's OK.
-    if (detail !== undefined && detail !== null) {
-      exposeAll(detail);
-      return new this._window.CustomEvent('mozbrowser' + evtName,
-                                          { bubbles: true,
-                                            cancelable: cancelable,
-                                            detail: detail });
-    }
-
-    return new this._window.Event('mozbrowser' + evtName,
-                                  { bubbles: true,
-                                    cancelable: cancelable });
-  },
-
-  /**
-   * Kick off a DOMRequest in the child process.
-   *
-   * We'll fire an event called |msgName| on the child process, passing along
-   * an object with two fields:
-   *
-   *  - id:  the ID of this request.
-   *  - arg: arguments to pass to the child along with this request.
-   *
-   * We expect the child to pass the ID back to us upon completion of the
-   * request.  See _gotDOMRequestResult.
-   */
-  _sendDOMRequest: function(msgName, args) {
-    let id = 'req_' + this._domRequestCounter++;
-    let req = Services.DOMRequest.createRequest(this._window);
-    if (this._sendAsyncMsg(msgName, {id: id, args: args})) {
-      this._pendingDOMRequests[id] = req;
-    } else {
-      Services.DOMRequest.fireErrorAsync(req, "fail");
-    }
-    return req;
-  },
-
-  /**
-   * Called when the child process finishes handling a DOMRequest.  data.json
-   * must have the fields [id, successRv], if the DOMRequest was successful, or
-   * [id, errorMsg], if the request was not successful.
-   *
-   * The fields have the following meanings:
-   *
-   *  - id:        the ID of the DOM request (see _sendDOMRequest)
-   *  - successRv: the request's return value, if the request succeeded
-   *  - errorMsg:  the message to pass to DOMRequest.fireError(), if the request
-   *               failed.
-   *
-   */
-  _gotDOMRequestResult: function(data) {
-    let req = this._pendingDOMRequests[data.json.id];
-    delete this._pendingDOMRequests[data.json.id];
-
-    if ('successRv' in data.json) {
-      debug("Successful gotDOMRequestResult.");
-      Services.DOMRequest.fireSuccess(req, data.json.successRv);
-    }
-    else {
-      debug("Got error in gotDOMRequestResult.");
-      Services.DOMRequest.fireErrorAsync(req, data.json.errorMsg);
-    }
-  },
-
-  _setVisible: function(visible) {
-    this._sendAsyncMsg('set-visible', {visible: visible});
-  },
-
-  _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: function(type, identifiers, touchesX, touchesY,
-                            radiisX, radiisY, rotationAngles, forces,
-                            count, modifiers) {
-    this._sendAsyncMsg("send-touch-event", {
-      "type": type,
-      "identifiers": identifiers,
-      "touchesX": touchesX,
-      "touchesY": touchesY,
-      "radiisX": radiisX,
-      "radiisY": radiisY,
-      "rotationAngles": rotationAngles,
-      "forces": forces,
-      "count": count,
-      "modifiers": modifiers
-    });
-  },
-
-  _goBack: function() {
-    this._sendAsyncMsg('go-back');
-  },
-
-  _goForward: function() {
-    this._sendAsyncMsg('go-forward');
-  },
-
-  _reload: function(hardReload) {
-    this._sendAsyncMsg('reload', {hardReload: hardReload});
-  },
-
-  _stop: function() {
-    this._sendAsyncMsg('stop');
-  },
-
-  _purgeHistory: function() {
-    return this._sendDOMRequest('purge-history');
-  },
-
-  _getScreenshot: function(_width, _height) {
-    let width = parseInt(_width);
-    let height = parseInt(_height);
-    if (isNaN(width) || isNaN(height) || width < 0 || height < 0) {
-      throw Components.Exception("Invalid argument",
-                                 Cr.NS_ERROR_INVALID_ARG);
-    }
-
-    return this._sendDOMRequest('get-screenshot',
-                                {width: width, height: height});
-  },
-
-  _recvNextPaint: function(data) {
-    let listeners = this._nextPaintListeners;
-    this._nextPaintListeners = [];
-    for (let listener of listeners) {
-      try {
-        listener();
-      } catch (e) {
-        // If a listener throws we'll continue.
-      }
-    }
-  },
-
-  _addNextPaintListener: function(listener) {
-    if (typeof listener != 'function')
-      throw Components.Exception("Invalid argument", Cr.NS_ERROR_INVALID_ARG);
-
-    if (this._nextPaintListeners.push(listener) == 1)
-      this._sendAsyncMsg('activate-next-paint-listener');
-  },
-
-  _removeNextPaintListener: function(listener) {
-    if (typeof listener != 'function')
-      throw Components.Exception("Invalid argument", Cr.NS_ERROR_INVALID_ARG);
-
-    for (let i = this._nextPaintListeners.length - 1; i >= 0; i--) {
-      if (this._nextPaintListeners[i] == listener) {
-        this._nextPaintListeners.splice(i, 1);
-        break;
-      }
-    }
-
-    if (this._nextPaintListeners.length == 0)
-      this._sendAsyncMsg('deactivate-next-paint-listener');
-  },
-
-  _fireKeyEvent: function(data) {
-    let evt = this._window.document.createEvent("KeyboardEvent");
-    evt.initKeyEvent(data.json.type, true, true, this._window,
-                     false, false, false, false, // modifiers
-                     data.json.keyCode,
-                     data.json.charCode);
-
-    this._frameElement.dispatchEvent(evt);
-  },
-
-  /**
-   * Called when the visibility of the window which owns this iframe changes.
-   */
-  _ownerVisibilityChange: function() {
-    this._sendAsyncMsg('owner-visibility-change',
-                       {visible: !this._window.document.hidden});
-  },
-
-  _exitFullscreen: function() {
-    this._windowUtils.exitFullscreen();
-  },
-
-  _remoteFullscreenOriginChange: function(data) {
-    let origin = data.json;
-    this._windowUtils.remoteFrameFullscreenChanged(this._frameElement, origin);
-  },
-
-  _remoteFrameFullscreenReverted: function(data) {
-    this._windowUtils.remoteFrameFullscreenReverted();
-  },
-
-  _fireFatalError: function() {
-    let evt = this._createEvent('error', {type: 'fatal'},
-                                /* cancelable = */ false);
-    this._frameElement.dispatchEvent(evt);
-  },
-
-  observe: function(subject, topic, data) {
-    switch(topic) {
-    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._hasRemoteFrame) {
-        this._sendAsyncMsg('exit-fullscreen');
-      }
-      break;
-    default:
-      debug('Unknown topic: ' + topic);
-      break;
-    };
-  },
-};