Bug 749018 - Make OOP <iframe mozbrowser> pass current browser frame tests. r=smaug,cjones
authorJustin Lebar <justin.lebar@gmail.com>
Tue, 08 May 2012 09:20:35 -0700
changeset 95773 f6f8d92907b578c8bf49e32bc5d0d04d1ef17b5e
parent 95772 cfa3d6007436f6e84cbeff84113e97419fa33936
child 95774 69b20b2d47f15d42a40d5e3e60f004071e7028de
push id1439
push userlsblakk@mozilla.com
push dateMon, 04 Jun 2012 20:19:22 +0000
treeherdermozilla-aurora@ea74834dccd3 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerssmaug, cjones
bugs749018
milestone15.0a1
Bug 749018 - Make OOP <iframe mozbrowser> pass current browser frame tests. r=smaug,cjones
b2g/installer/package-manifest.in
browser/components/thumbnails/PageThumbs.jsm
browser/installer/package-manifest.in
content/base/public/nsIFrameLoader.idl
content/base/src/nsContentUtils.cpp
content/base/src/nsFrameLoader.cpp
content/base/src/nsFrameLoader.h
content/base/src/nsInProcessTabChildGlobal.cpp
content/base/src/nsInProcessTabChildGlobal.h
content/events/src/nsEventDispatcher.cpp
dom/base/BrowserElementAPI.js
dom/base/BrowserElementAPI.manifest
dom/base/BrowserElementChild.js
dom/base/BrowserElementParent.js
dom/base/BrowserElementParent.manifest
dom/base/Makefile.in
dom/base/nsGlobalWindow.cpp
dom/ipc/TabParent.cpp
dom/ipc/jar.mn
dom/tests/mochitest/browser-frame/Makefile.in
dom/tests/mochitest/browser-frame/browserFrameHelpers.js
dom/tests/mochitest/browser-frame/test_browserFrame4.html
dom/tests/mochitest/browser-frame/test_browserFrame5.html
dom/tests/mochitest/browser-frame/test_browserFrame6.html
dom/tests/mochitest/browser-frame/test_browserFrame7.html
dom/tests/mochitest/browser-frame/test_browserFrame8.html
mobile/android/installer/package-manifest.in
mobile/xul/installer/package-manifest.in
testing/mochitest/tests/SimpleTest/specialpowersAPI.js
--- a/b2g/installer/package-manifest.in
+++ b/b2g/installer/package-manifest.in
@@ -295,18 +295,18 @@
 @BINPATH@/components/xul.xpt
 @BINPATH@/components/xuldoc.xpt
 @BINPATH@/components/xultmpl.xpt
 @BINPATH@/components/zipwriter.xpt
 
 ; JavaScript components
 @BINPATH@/components/ConsoleAPI.manifest
 @BINPATH@/components/ConsoleAPI.js
-@BINPATH@/components/BrowserElementAPI.manifest
-@BINPATH@/components/BrowserElementAPI.js
+@BINPATH@/components/BrowserElementParent.manifest
+@BINPATH@/components/BrowserElementParent.js
 @BINPATH@/components/ContactManager.js
 @BINPATH@/components/ContactManager.manifest
 @BINPATH@/components/FeedProcessor.manifest
 @BINPATH@/components/FeedProcessor.js
 @BINPATH@/components/BrowserFeeds.manifest
 @BINPATH@/components/FeedConverter.js
 @BINPATH@/components/FeedWriter.js
 @BINPATH@/components/fuelApplication.manifest
--- a/browser/components/thumbnails/PageThumbs.jsm
+++ b/browser/components/thumbnails/PageThumbs.jsm
@@ -102,16 +102,20 @@ let PageThumbs = {
   /**
    * Captures a thumbnail for the given window.
    * @param aWindow The DOM window to capture a thumbnail from.
    * @param aCallback The function to be called when the thumbnail has been
    *                  captured. The first argument will be the data stream
    *                  containing the image data.
    */
   capture: function PageThumbs_capture(aWindow, aCallback) {
+    if (!this._prefEnabled()) {
+      return;
+    }
+
     let telemetryCaptureTime = new Date();
     let [sw, sh, scale] = this._determineCropSize(aWindow);
 
     let canvas = this._createCanvas();
     let ctx = canvas.getContext("2d");
 
     // Scale the canvas accordingly.
     ctx.scale(scale, scale);
@@ -132,16 +136,20 @@ let PageThumbs = {
   },
 
   /**
    * Captures a thumbnail for the given browser and stores it to the cache.
    * @param aBrowser The browser to capture a thumbnail for.
    * @param aCallback The function to be called when finished (optional).
    */
   captureAndStore: function PageThumbs_captureAndStore(aBrowser, aCallback) {
+    if (!this._prefEnabled()) {
+      return;
+    }
+
     let url = aBrowser.currentURI.spec;
     let channel = aBrowser.docShell.currentDocumentChannel;
     let originalURL = channel.originalURI.spec;
 
     this.capture(aBrowser.contentWindow, function (aInputStream) {
       let telemetryStoreTime = new Date();
 
       function finish(aSuccessful) {
@@ -219,17 +227,26 @@ let PageThumbs = {
       let screenManager = Cc["@mozilla.org/gfx/screenmanager;1"]
                             .getService(Ci.nsIScreenManager);
       let left = {}, top = {}, width = {}, height = {};
       screenManager.primaryScreen.GetRect(left, top, width, height);
       this._thumbnailWidth = Math.round(width.value / 3);
       this._thumbnailHeight = Math.round(height.value / 3);
     }
     return [this._thumbnailWidth, this._thumbnailHeight];
-  }
+  },
+
+  _prefEnabled: function PageThumbs_prefEnabled() {
+    try {
+      return Services.prefs.getBoolPref("browser.pageThumbs.enabled");
+    }
+    catch (e) {
+      return true;
+    }
+  },
 };
 
 let PageThumbsStorage = {
   getFileForURL: function Storage_getFileForURL(aURL) {
     let hash = this._calculateMD5Hash(aURL);
     let parts = [THUMBNAIL_DIRECTORY, hash[0], hash[1], hash.slice(2) + ".png"];
     return FileUtils.getFile("ProfD", parts);
   },
--- a/browser/installer/package-manifest.in
+++ b/browser/installer/package-manifest.in
@@ -289,18 +289,18 @@
 @BINPATH@/components/xuldoc.xpt
 @BINPATH@/components/xultmpl.xpt
 @BINPATH@/components/zipwriter.xpt
 @BINPATH@/components/telemetry.xpt
 
 ; JavaScript components
 @BINPATH@/components/ConsoleAPI.manifest
 @BINPATH@/components/ConsoleAPI.js
-@BINPATH@/components/BrowserElementAPI.manifest
-@BINPATH@/components/BrowserElementAPI.js
+@BINPATH@/components/BrowserElementParent.manifest
+@BINPATH@/components/BrowserElementParent.js
 @BINPATH@/components/FeedProcessor.manifest
 @BINPATH@/components/FeedProcessor.js
 @BINPATH@/components/BrowserFeeds.manifest
 @BINPATH@/components/FeedConverter.js
 @BINPATH@/components/FeedWriter.js
 @BINPATH@/components/fuelApplication.manifest
 @BINPATH@/components/fuelApplication.js
 @BINPATH@/components/WebContentConverter.js
--- a/content/base/public/nsIFrameLoader.idl
+++ b/content/base/public/nsIFrameLoader.idl
@@ -39,16 +39,17 @@
 
 #include "nsISupports.idl"
 
 interface nsIDocShell;
 interface nsIURI;
 interface nsIFrame;
 interface nsIChromeFrameMessageManager;
 interface nsIVariant;
+interface nsIDOMElement;
 
 typedef unsigned long long nsContentViewId;
 
 /**
  * These interfaces do *not* scroll or scale the content document;
  * instead they set a "goal" scroll/scale wrt the current content
  * view.  When the content document is painted, the scroll*
  * attributes are used to set a compensating transform.  If the
@@ -136,17 +137,17 @@ interface nsIContentViewManager : nsISup
                          [retval, array, size_is(aLength)] out nsIContentView aResult);
 
   /**
    * The root content view.
    */
   readonly attribute nsIContentView rootContentView;
 };
 
-[scriptable, uuid(efc0b731-45dc-4189-8ffa-d3eeeb850977)]
+[scriptable, uuid(fc338eea-47dc-475e-add7-a3933fcfa07c)]
 interface nsIFrameLoader : nsISupports
 {
   /**
    * Get the docshell from the frame loader.
    */
   readonly attribute nsIDocShell docShell;
 
   /**
@@ -267,16 +268,24 @@ interface nsIFrameLoader : nsISupports
   attribute boolean clipSubdocument;
 
   /**
    * If false, then the subdocument's scroll coordinates will not be clamped
    * to their scroll boundaries.
    * Defaults to true.
    */
   attribute boolean clampScrollPosition;
+
+  /**
+   * The element which owns this frame loader.
+   *
+   * For example, if this is a frame loader for an <iframe>, this attribute
+   * returns the iframe element.
+   */
+  readonly attribute nsIDOMElement ownerElement;
 };
 
 native alreadyAddRefed_nsFrameLoader(already_AddRefed<nsFrameLoader>);
 
 [scriptable, uuid(5879040e-83e9-40e3-b2bb-5ddf43b76e47)]
 interface nsIFrameLoaderOwner : nsISupports
 {
   /**
--- a/content/base/src/nsContentUtils.cpp
+++ b/content/base/src/nsContentUtils.cpp
@@ -3446,29 +3446,20 @@ nsContentUtils::DispatchChromeEvent(nsID
                                   aCancelable, true, getter_AddRefs(event),
                                   getter_AddRefs(target));
   NS_ENSURE_SUCCESS(rv, rv);
 
   NS_ASSERTION(aDoc, "GetEventAndTarget lied?");
   if (!aDoc->GetWindow())
     return NS_ERROR_INVALID_ARG;
 
-  nsIDOMEventTarget* piTarget = aDoc->GetWindow()->GetChromeEventHandler();
+  nsIDOMEventTarget* piTarget = aDoc->GetWindow()->GetParentTarget();
   if (!piTarget)
     return NS_ERROR_INVALID_ARG;
 
-  nsCOMPtr<nsIFrameLoaderOwner> flo = do_QueryInterface(piTarget);
-  if (flo) {
-    nsRefPtr<nsFrameLoader> fl = flo->GetFrameLoader();
-    if (fl) {
-      nsIDOMEventTarget* t = fl->GetTabChildGlobalAsEventTarget();
-      piTarget = t ? t : piTarget;
-    }
-  }
-
   nsEventStatus status = nsEventStatus_eIgnore;
   rv = piTarget->DispatchDOMEvent(nsnull, event, nsnull, &status);
   if (aDefaultAction) {
     *aDefaultAction = (status != nsEventStatus_eConsumeNoDefault);
   }
   return rv;
 }
 
--- a/content/base/src/nsFrameLoader.cpp
+++ b/content/base/src/nsFrameLoader.cpp
@@ -928,16 +928,22 @@ nsFrameLoader::ShowRemoteFrame(const nsI
       // This is just not going to work.
       return false;
     }
 
     mRemoteBrowser->Show(size);
     mRemoteBrowserShown = true;
 
     EnsureMessageManager();
+
+    nsCOMPtr<nsIObserverService> os = services::GetObserverService();
+    if (OwnerIsBrowserFrame() && os) {
+      os->NotifyObservers(NS_ISUPPORTS_CAST(nsIFrameLoader*, this),
+                          "remote-browser-frame-shown", NULL);
+    }
   } else {
     nsRect dimensions;
     NS_ENSURE_SUCCESS(GetWindowDimensions(dimensions), false);
     mRemoteBrowser->UpdateDimensions(dimensions, size);
   }
 
   return true;
 }
@@ -1507,21 +1513,21 @@ nsFrameLoader::MaybeCreateDocShell()
       // handler from it and use that for our shell as well.
 
       parentShell->GetChromeEventHandler(getter_AddRefs(chromeEventHandler));
     }
 
     mDocShell->SetChromeEventHandler(chromeEventHandler);
   }
 
-  nsCOMPtr<nsIMozBrowserFrame> browserFrame = do_QueryInterface(mOwnerContent);
-  if (browserFrame) {
-    bool isBrowserFrame = false;
-    browserFrame->GetReallyIsBrowser(&isBrowserFrame);
-    mDocShell->SetIsBrowserFrame(isBrowserFrame);
+  nsCOMPtr<nsIObserverService> os = services::GetObserverService();
+  if (OwnerIsBrowserFrame() && os) {
+    mDocShell->SetIsBrowserFrame(true);
+    os->NotifyObservers(NS_ISUPPORTS_CAST(nsIFrameLoader*, this),
+                        "in-process-browser-frame-shown", NULL);
   }
 
   // This is nasty, this code (the do_GetInterface(mDocShell) below)
   // *must* come *after* the above call to
   // mDocShell->SetChromeEventHandler() for the global window to get
   // the right chrome event handler.
 
   // Tell the window about the frame that hosts it.
@@ -2177,17 +2183,17 @@ nsFrameLoader::EnsureMessageManager()
 {
   NS_ENSURE_STATE(mOwnerContent);
 
   nsresult rv = MaybeCreateDocShell();
   if (NS_FAILED(rv)) {
     return rv;
   }
 
-  if (!mIsTopLevelContent && !mRemoteFrame) {
+  if (!mIsTopLevelContent && !OwnerIsBrowserFrame() && !mRemoteFrame) {
     return NS_OK;
   }
 
   if (mMessageManager) {
     if (ShouldUseRemoteProcess()) {
       mMessageManager->SetCallbackData(mRemoteBrowserShown ? this : nsnull);
     }
     return NS_OK;
@@ -2212,32 +2218,36 @@ nsFrameLoader::EnsureMessageManager()
   if (ShouldUseRemoteProcess()) {
     mMessageManager = new nsFrameMessageManager(true,
                                                 nsnull,
                                                 SendAsyncMessageToChild,
                                                 LoadScript,
                                                 mRemoteBrowserShown ? this : nsnull,
                                                 static_cast<nsFrameMessageManager*>(parentManager.get()),
                                                 cx);
-    NS_ENSURE_TRUE(mMessageManager, NS_ERROR_OUT_OF_MEMORY);
-  } else
-  {
-
+  } else {
     mMessageManager = new nsFrameMessageManager(true,
                                                 nsnull,
                                                 SendAsyncMessageToChild,
                                                 LoadScript,
                                                 nsnull,
                                                 static_cast<nsFrameMessageManager*>(parentManager.get()),
                                                 cx);
-    NS_ENSURE_TRUE(mMessageManager, NS_ERROR_OUT_OF_MEMORY);
     mChildMessageManager =
       new nsInProcessTabChildGlobal(mDocShell, mOwnerContent, mMessageManager);
     mMessageManager->SetCallbackData(this);
   }
   return NS_OK;
 }
 
 nsIDOMEventTarget*
 nsFrameLoader::GetTabChildGlobalAsEventTarget()
 {
   return static_cast<nsInProcessTabChildGlobal*>(mChildMessageManager.get());
 }
+
+NS_IMETHODIMP
+nsFrameLoader::GetOwnerElement(nsIDOMElement **aElement)
+{
+  nsCOMPtr<nsIDOMElement> ownerElement = do_QueryInterface(mOwnerContent);
+  ownerElement.forget(aElement);
+  return NS_OK;
+}
--- a/content/base/src/nsFrameLoader.h
+++ b/content/base/src/nsFrameLoader.h
@@ -280,24 +280,24 @@ public:
    */
   void SetCurrentRemoteFrame(RenderFrameParent* aFrame)
   {
     mCurrentRemoteFrame = aFrame;
   }
   nsFrameMessageManager* GetFrameMessageManager() { return mMessageManager; }
 
   mozilla::dom::Element* GetOwnerContent() { return mOwnerContent; }
-  void SetOwnerContent(mozilla::dom::Element* aContent);
-
   bool ShouldClipSubdocument() { return mClipSubdocument; }
 
   bool ShouldClampScrollPosition() { return mClampScrollPosition; }
 
 private:
 
+  void SetOwnerContent(mozilla::dom::Element* aContent);
+
   bool ShouldUseRemoteProcess();
 
   /**
    * Is this a frameloader for a bona fide <iframe mozbrowser>?  (I.e., does
    * the frame return true for nsIMozBrowserFrame::GetReallyIsBrowser()?)
    */
   bool OwnerIsBrowserFrame();
 
--- a/content/base/src/nsInProcessTabChildGlobal.cpp
+++ b/content/base/src/nsInProcessTabChildGlobal.cpp
@@ -46,16 +46,17 @@
 #include "nsIJSRuntimeService.h"
 #include "nsComponentManagerUtils.h"
 #include "nsNetUtil.h"
 #include "nsScriptLoader.h"
 #include "nsIJSContextStack.h"
 #include "nsFrameLoader.h"
 #include "nsIPrivateDOMEvent.h"
 #include "xpcpublic.h"
+#include "nsIMozBrowserFrame.h"
 
 bool SendSyncMessageToParent(void* aCallbackData,
                              const nsAString& aMessage,
                              const nsAString& aJSON,
                              InfallibleTArray<nsString>* aJSONRetVal)
 {
   nsInProcessTabChildGlobal* tabChild =
     static_cast<nsInProcessTabChildGlobal*>(aCallbackData);
@@ -110,16 +111,25 @@ bool SendAsyncMessageToParent(void* aCal
 }
 
 nsInProcessTabChildGlobal::nsInProcessTabChildGlobal(nsIDocShell* aShell,
                                                      nsIContent* aOwner,
                                                      nsFrameMessageManager* aChrome)
 : mDocShell(aShell), mInitialized(false), mLoadingScript(false),
   mDelayedDisconnect(false), mOwner(aOwner), mChromeMessageManager(aChrome)
 {
+
+  // If owner corresponds to an <iframe mozbrowser>, we'll have to tweak our
+  // PreHandleEvent implementation.
+  nsCOMPtr<nsIMozBrowserFrame> browserFrame = do_QueryInterface(mOwner);
+  bool isBrowser = false;
+  if (browserFrame) {
+    browserFrame->GetReallyIsBrowser(&isBrowser);
+  }
+  mIsBrowserFrame = isBrowser;
 }
 
 nsInProcessTabChildGlobal::~nsInProcessTabChildGlobal()
 {
   NS_ASSERTION(!mCx, "Couldn't release JSContext?!?");
 }
 
 nsresult
@@ -265,17 +275,27 @@ nsInProcessTabChildGlobal::GetOwnerConte
 {
   return mOwner;
 }
 
 nsresult
 nsInProcessTabChildGlobal::PreHandleEvent(nsEventChainPreVisitor& aVisitor)
 {
   aVisitor.mCanHandle = true;
-  aVisitor.mParentTarget = mOwner;
+
+  if (mIsBrowserFrame) {
+    if (mOwner) {
+      nsPIDOMWindow* innerWindow = mOwner->OwnerDoc()->GetInnerWindow();
+      if (innerWindow) {
+        aVisitor.mParentTarget = innerWindow->GetParentTarget();
+      }
+    }
+  } else {
+    aVisitor.mParentTarget = mOwner;
+  }
 
 #ifdef DEBUG
   if (mOwner) {
     nsCOMPtr<nsIFrameLoaderOwner> owner = do_QueryInterface(mOwner);
     nsRefPtr<nsFrameLoader> fl = owner->GetFrameLoader();
     if (fl) {
       NS_ASSERTION(this == fl->GetTabChildGlobalAsEventTarget(),
                    "Wrong event target!");
--- a/content/base/src/nsInProcessTabChildGlobal.h
+++ b/content/base/src/nsInProcessTabChildGlobal.h
@@ -137,15 +137,19 @@ public:
 protected:
   nsresult Init();
   nsresult InitTabChildGlobal();
   nsCOMPtr<nsIContentFrameMessageManager> mMessageManager;
   nsCOMPtr<nsIDocShell> mDocShell;
   bool mInitialized;
   bool mLoadingScript;
   bool mDelayedDisconnect;
+
+  // Is this the message manager for an in-process <iframe mozbrowser>?  This
+  // affects where events get sent, so it affects PreHandleEvent.
+  bool mIsBrowserFrame;
 public:
   nsIContent* mOwner;
   nsFrameMessageManager* mChromeMessageManager;
   nsTArray<nsCOMPtr<nsIRunnable> > mASyncMessages;
 };
 
 #endif
--- a/content/events/src/nsEventDispatcher.cpp
+++ b/content/events/src/nsEventDispatcher.cpp
@@ -525,27 +525,18 @@ nsEventDispatcher::Dispatch(nsISupports*
       }
     }
 
     NS_ENSURE_STATE(node);
     nsIDocument* doc = node->OwnerDoc();
     if (!nsContentUtils::IsChromeDoc(doc)) {
       nsPIDOMWindow* win = doc ? doc->GetInnerWindow() : nsnull;
       // If we can't dispatch the event to chrome, do nothing.
-      nsIDOMEventTarget* piTarget = win ? win->GetChromeEventHandler() : nsnull;
+      nsIDOMEventTarget* piTarget = win ? win->GetParentTarget() : nsnull;
       NS_ENSURE_TRUE(piTarget, NS_OK);
-
-      nsCOMPtr<nsIFrameLoaderOwner> flo = do_QueryInterface(piTarget);
-      if (flo) {
-        nsRefPtr<nsFrameLoader> fl = flo->GetFrameLoader();
-        if (fl) {
-          nsIDOMEventTarget* t = fl->GetTabChildGlobalAsEventTarget();
-          piTarget = t ? t : piTarget;
-        }
-      }
       
       // Set the target to be the original dispatch target,
       aEvent->target = target;
       // but use chrome event handler or TabChildGlobal for event target chain.
       target = piTarget;
     }
   }
 
deleted file mode 100644
--- a/dom/base/BrowserElementAPI.js
+++ /dev/null
@@ -1,296 +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;
-Cu.import("resource://gre/modules/XPCOMUtils.jsm");
-
-const NS_PREFBRANCH_PREFCHANGE_TOPIC_ID = "nsPref:changed";
-const BROWSER_FRAMES_ENABLED_PREF = "dom.mozBrowserFramesEnabled";
-
-/**
- * The BrowserElementAPI implements <iframe mozbrowser>.
- *
- * We detect windows and docshells contained inside <iframe mozbrowser>s and
- * alter their behavior so that the page inside the iframe can't tell that it's
- * framed and the page outside the iframe can observe changes within the iframe
- * (e.g. loadstart/loadstart, locationchange).
- */
-
-function BrowserElementAPI() {}
-BrowserElementAPI.prototype = {
-  classID: Components.ID("{5d6fcab3-6c12-4db6-80fb-352df7a41602}"),
-  QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver,
-                                         Ci.nsISupportsWeakReference]),
-
-  /**
-   * The keys of this map are the set of chrome event handlers we've observed
-   * which contain a mozbrowser window.
-   *
-   * The values in this map are ignored.
-   */
-  _chromeEventHandlersWatching: new WeakMap(),
-
-  /**
-   * The keys of this map are the set of windows we've observed that are
-   * directly contained in <iframe mozbrowser>s.
-   *
-   * The values in this map are ignored.
-   */
-  _topLevelBrowserWindows: new WeakMap(),
-
-  _browserFramesPrefEnabled: function BA_browserFramesPrefEnabled() {
-    var prefs = Cc["@mozilla.org/preferences-service;1"].getService(Ci.nsIPrefBranch);
-    try {
-      return prefs.getBoolPref(BROWSER_FRAMES_ENABLED_PREF);
-    }
-    catch(e) {
-      return false;
-    }
-  },
-
-  /**
-   * Called on browser start, and also when we observe a change in
-   * the browser-frames-enabled pref.
-   */
-  _init: function BA_init() {
-    if (this._initialized) {
-      return;
-    }
-
-    // If browser frames are disabled, watch the pref so we can enable
-    // ourselves if the pref is flipped.  This is important for tests, if
-    // nothing else.
-    if (!this._browserFramesPrefEnabled()) {
-      var prefs = Cc["@mozilla.org/preferences-service;1"].getService(Ci.nsIPrefBranch);
-      prefs.addObserver(BROWSER_FRAMES_ENABLED_PREF, this, /* ownsWeak = */ true);
-      return;
-    }
-
-    this._initialized = true;
-    this._progressListener._browserElementAPI = this;
-
-    var os = Cc["@mozilla.org/observer-service;1"].getService(Ci.nsIObserverService);
-    os.addObserver(this, 'content-document-global-created',  /* ownsWeak = */ true);
-    os.addObserver(this, 'docshell-marked-as-browser-frame', /* ownsWeak = */ true);
-  },
-
-  /**
-   * Called when we observe a docshell-marked-as-browser-frame event, which
-   * happens when a docshell is created inside an <iframe mozbrowser>.
-   *
-   * A docshell may not be un-marked as a browser frame -- this ensures that
-   * this event will never fire twice for the same docshell, which guarantees
-   * that we'll never register duplicate listeners.
-   */
-  _observeDocshellMarkedAsBrowserFrame: function BA_observeDocshellMarkedAsBrowserFrame(docshell) {
-    docshell.QueryInterface(Ci.nsIWebProgress)
-            .addProgressListener(this._progressListener,
-                                 Ci.nsIWebProgress.NOTIFY_LOCATION |
-                                 Ci.nsIWebProgress.NOTIFY_STATE_WINDOW);
-  },
-
-  /**
-   * Called when a content window is created.  If the window is directly or
-   * indirectly contained in an <iframe mozbrowser>, we'll modify it.
-   */
-  _observeContentGlobalCreated: function BA_observeContentGlobalCreated(win) {
-    var docshell = win.QueryInterface(Ci.nsIInterfaceRequestor)
-                      .getInterface(Ci.nsIWebNavigation)
-                      .QueryInterface(Ci.nsIDocShell);
-
-    // If this window is not directly or indirectly inside an
-    // <iframe mozbrowser>, BrowserElementAPI does nothing to it.
-    if (!docshell.containedInBrowserFrame) {
-      return;
-    }
-
-    this._initBrowserWindow(win, docshell.isBrowserFrame);
-
-    // If this window is directly contained in an <iframe mozbrowser>, do some
-    // extra work.
-    if (docshell.isBrowserFrame) {
-      this._topLevelBrowserWindows.set(win, true);
-      this._initTopLevelBrowserWindow(win);
-    }
-  },
-
-  /**
-   * Initialize a content window which is indirectly or directly contained by
-   * an <iframe mozbrowser>.
-   *
-   * |isTopLevel| is true iff |win| is directly contained by an
-   * <iframe mozbrowser>.
-   */
-  _initBrowserWindow: function BA_initBrowserWindow(win, isTopLevel) {
-    // XPCNativeWrapper.unwrap gets us the object that content sees; this is
-    // the object object that we must define properties on.  Otherwise, the
-    // properties will be visible only to chrome!
-    var unwrappedWin = XPCNativeWrapper.unwrap(win);
-
-    // This property should exist only on the x-ray wrapped object, not on the
-    // unwrapped object, so we define it on |win|, not |unwrappedWin|.
-    Object.defineProperty(win, 'browserFrameTop', {
-      get: function() {
-        if (isTopLevel) {
-          return win;
-        }
-
-        if ('browserFrameTop' in win.parent) {
-          return win.parent.browserFrameTop;
-        }
-
-        // This shouldn't happen, but let's at least throw a semi-meaningful
-        // error message if it does.
-        throw new Error('Internal error in window.browserFrameTop.');
-      }
-    });
-
-    Object.defineProperty(unwrappedWin, 'top', {
-      get: function() {
-        return win.browserFrameTop;
-      }
-    });
-
-    Object.defineProperty(unwrappedWin, 'parent', {
-      get: function() {
-        if (isTopLevel) {
-          return win;
-        }
-        return win.parent;
-      }
-    });
-
-    Object.defineProperty(unwrappedWin, 'frameElement', {
-      get: function() {
-        if (isTopLevel) {
-          return null;
-        }
-        return win.frameElement;
-      }
-    });
-  },
-
-  /**
-   * Initialize a content window directly contained by an <iframe mozbrowser>.
-   */
-  _initTopLevelBrowserWindow: function BA_initTopLevelBrowserWindow(win) {
-    // If we haven't seen this window's chrome event handler before, register
-    // listeners on it.
-    var chromeHandler = win.QueryInterface(Ci.nsIInterfaceRequestor)
-                           .getInterface(Ci.nsIWebNavigation)
-                           .QueryInterface(Ci.nsIDocShell)
-                           .chromeEventHandler;
-
-    if (chromeHandler && !this._chromeEventHandlersWatching.has(chromeHandler)) {
-      this._chromeEventHandlersWatching.set(chromeHandler, true);
-      this._addChromeEventHandlerListeners(chromeHandler);
-    }
-  },
-
-  /**
-   * Add some listeners to a chrome event handler.  Don't call this twice for
-   * the same chrome event handler or we'll get duplicate listeners!
-   */
-  _addChromeEventHandlerListeners: function BA_addChromeEventHandlerListeners(chromeHandler) {
-    var browserElementAPI = this;
-
-    // Listen for DOMTitleChanged events on top-level <iframe mozbrowser>
-    // windows.  (The chrome event handler handles
-    chromeHandler.addEventListener(
-      'DOMTitleChanged',
-      function(e) {
-        var win = e.target.defaultView;
-        if (browserElementAPI._topLevelBrowserWindows.has(win)) {
-          browserElementAPI._fireCustomEvent('titlechange', e.target.title,
-                                             win, win.frameElement);
-        }
-      },
-      /* useCapture = */ false,
-      /* wantsUntrusted = */ false);
-  },
-
-  /**
-   * Asynchronously fire a vanilla event at the given window's frame element.
-   * (Presumably, the window's frame element is an <iframe mozbrowser>.)
-   *
-   * We'll prepend 'mozbrowser' to the event's name.
-   */
-  _fireEvent: function BA_fireEvent(name, win) {
-    // Because we're chrome, win.frameElement ignores <iframe mozbrowser>
-    // boundaries, as desired.
-    var evt = new win.Event('mozbrowser' + name);
-    win.setTimeout(function() { win.frameElement.dispatchEvent(evt) }, 0);
-  },
-
-  /**
-   * Like _fireEvent, but fire a customevent with the given data, instead of a
-   * vanilla event.
-   */
-  _fireCustomEvent: function BA_fireCustomEvent(name, data, win) {
-    var evt = new win.CustomEvent('mozbrowser' + name, {detail: data});
-    win.setTimeout(function() { win.frameElement.dispatchEvent(evt) }, 0);
-  },
-
-  /**
-   * An nsIWebProgressListener registered on docshells directly contained in an
-   * <iframe mozbrowser>.
-   */
-  _progressListener: {
-    QueryInterface: XPCOMUtils.generateQI([Ci.nsIWebProgressListener,
-                                           Ci.nsISupportsWeakReference,
-                                           Ci.nsISupports]),
-
-    _getWindow: function(webProgress) {
-      return webProgress.QueryInterface(Ci.nsIInterfaceRequestor)
-                         .getInterface(Ci.nsIDOMWindow);
-    },
-
-    onLocationChange: function(webProgress, request, location, flags) {
-      this._browserElementAPI._fireCustomEvent('locationchange', location.spec,
-                                               this._getWindow(webProgress));
-    },
-
-    onStateChange: function(webProgress, request, stateFlags, status) {
-      if (stateFlags & Ci.nsIWebProgressListener.STATE_START) {
-        this._browserElementAPI._fireEvent('loadstart', this._getWindow(webProgress));
-      }
-      if (stateFlags & Ci.nsIWebProgressListener.STATE_STOP) {
-        this._browserElementAPI._fireEvent('loadend', this._getWindow(webProgress));
-      }
-    },
-
-    onStatusChange: function(webProgress, request, status, message) {},
-    onProgressChange: function(webProgress, request, curSelfProgress,
-                               maxSelfProgress, curTotalProgress, maxTotalProgress) {},
-    onSecurityChange: function(webProgress, request, aState) {}
-  },
-
-  /**
-   * nsIObserver::Observe
-   */
-  observe: function BA_observe(subject, topic, data) {
-    switch(topic) {
-    case 'app-startup':
-      this._init();
-      break;
-    case 'content-document-global-created':
-      this._observeContentGlobalCreated(subject);
-      break;
-    case 'docshell-marked-as-browser-frame':
-      this._observeDocshellMarkedAsBrowserFrame(subject);
-      break;
-    case NS_PREFBRANCH_PREFCHANGE_TOPIC_ID:
-      if (data == BROWSER_FRAMES_ENABLED_PREF) {
-        this._init();
-      }
-      break;
-    }
-  },
-};
-
-var NSGetFactory = XPCOMUtils.generateNSGetFactory([BrowserElementAPI]);
deleted file mode 100644
--- a/dom/base/BrowserElementAPI.manifest
+++ /dev/null
@@ -1,3 +0,0 @@
-component {5d6fcab3-6c12-4db6-80fb-352df7a41602} BrowserElementAPI.js
-contract @mozilla.org/browser-element-api;1 {5d6fcab3-6c12-4db6-80fb-352df7a41602}
-category app-startup BrowserElementAPI service,@mozilla.org/browser-element-api;1
new file mode 100644
--- /dev/null
+++ b/dom/base/BrowserElementChild.js
@@ -0,0 +1,109 @@
+/* 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;
+
+function debug(msg) {
+  dump("BrowserElementChild - " + msg + "\n");
+}
+
+function sendAsyncMsg(msg, data) {
+  sendAsyncMessage('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().
+ *
+ * Our job here is to listen for events within this frame and bubble them up to
+ * the parent process.
+ */
+
+function BrowserElementChild() {
+  this._init();
+};
+
+BrowserElementChild.prototype = {
+  _init: function() {
+    debug("Starting up.");
+    sendAsyncMsg("hello");
+
+    docShell.QueryInterface(Ci.nsIWebProgress)
+            .addProgressListener(this._progressListener,
+                                 Ci.nsIWebProgress.NOTIFY_LOCATION |
+                                 Ci.nsIWebProgress.NOTIFY_STATE_WINDOW);
+
+    addEventListener('DOMTitleChanged',
+                     this._titleChangedHandler.bind(this),
+                     /* useCapture = */ true,
+                     /* wantsUntrusted = */ false);
+  },
+
+  _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', e.target.title);
+    }
+    else {
+      debug("Not top level!");
+    }
+  },
+
+  // The docShell keeps a weak reference to the progress listener, so we need
+  // to keep a strong ref to it ourselves.
+  _progressListener: {
+    QueryInterface: XPCOMUtils.generateQI([Ci.nsIWebProgressListener,
+                                           Ci.nsISupportsWeakReference,
+                                           Ci.nsISupports]),
+    _seenLoadStart: false,
+
+    onLocationChange: function(webProgress, request, location, flags) {
+      // We get progress events from subshells here, which is kind of weird.
+      if (webProgress != docShell) {
+        return;
+      }
+
+      // Ignore locationchange events which occur before the first loadstart.
+      // These are usually about:blank loads we don't care about.
+      if (!this._seenLoadStart) {
+        return;
+      }
+
+      sendAsyncMsg('locationchange', location.spec);
+    },
+
+    onStateChange: function(webProgress, request, stateFlags, status) {
+      if (webProgress != docShell) {
+        return;
+      }
+
+      if (stateFlags & Ci.nsIWebProgressListener.STATE_START) {
+        this._seenLoadStart = true;
+        sendAsyncMsg('loadstart');
+      }
+
+      if (stateFlags & Ci.nsIWebProgressListener.STATE_STOP) {
+        sendAsyncMsg('loadend');
+      }
+    },
+
+    onStatusChange: function(webProgress, request, status, message) {},
+    onProgressChange: function(webProgress, request, curSelfProgress,
+                               maxSelfProgress, curTotalProgress, maxTotalProgress) {},
+    onSecurityChange: function(webProgress, request, aState) {}
+  },
+};
+
+var api = new BrowserElementChild();
new file mode 100644
--- /dev/null
+++ b/dom/base/BrowserElementParent.js
@@ -0,0 +1,158 @@
+/* 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;
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+
+const NS_PREFBRANCH_PREFCHANGE_TOPIC_ID = "nsPref:changed";
+const BROWSER_FRAMES_ENABLED_PREF = "dom.mozBrowserFramesEnabled";
+
+function debug(msg) {
+  dump("BrowserElementParent - " + msg + "\n");
+}
+
+/**
+ * The BrowserElementParent implements one half of <iframe mozbrowser>.
+ * (The other half is, unsurprisingly, BrowserElementChild.)
+ *
+ * We detect windows and docshells contained inside <iframe mozbrowser>s and
+ * inject script to listen for certain events in the child.  We then listen to
+ * messages from the child script and take appropriate action here.
+ */
+
+function BrowserElementParent() {}
+BrowserElementParent.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() {
+    debug("_init");
+
+    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()) {
+      var prefs = Cc["@mozilla.org/preferences-service;1"].getService(Ci.nsIPrefBranch);
+      prefs.addObserver(BROWSER_FRAMES_ENABLED_PREF, this, /* ownsWeak = */ true);
+      return;
+    }
+
+    this._initialized = 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-frame-shown', /* ownsWeak = */ true);
+  },
+
+  _browserFramesPrefEnabled: function() {
+    var prefs = Cc["@mozilla.org/preferences-service;1"].getService(Ci.nsIPrefBranch);
+    try {
+      return prefs.getBoolPref(BROWSER_FRAMES_ENABLED_PREF);
+    }
+    catch(e) {
+      return false;
+    }
+  },
+
+  _observeInProcessBrowserFrameShown: function(frameLoader) {
+    debug("In-process browser frame shown " + frameLoader);
+    this._setUpMessageManagerListeners(frameLoader);
+  },
+
+  _observeRemoteBrowserFrameShown: function(frameLoader) {
+    debug("Remote browser frame shown " + frameLoader);
+    this._setUpMessageManagerListeners(frameLoader);
+  },
+
+  _setUpMessageManagerListeners: function(frameLoader) {
+    let frameElement = frameLoader.QueryInterface(Ci.nsIFrameLoader).ownerElement;
+    if (!frameElement) {
+      debug("No frame element?");
+      return;
+    }
+
+    let mm = frameLoader.messageManager;
+
+    // Messages we receive are handled by functions with parameters
+    // (frameElement, data), where |data| is the message manager's data object.
+
+    function addMessageListener(msg, handler) {
+      mm.addMessageListener('browser-element-api:' + msg, handler.bind(this, frameElement));
+    }
+
+    addMessageListener("hello", this._recvHello);
+    addMessageListener("locationchange", this._fireEventFromMsg);
+    addMessageListener("loadstart", this._fireEventFromMsg);
+    addMessageListener("loadend", this._fireEventFromMsg);
+    addMessageListener("titlechange", this._fireEventFromMsg);
+
+    mm.loadFrameScript("chrome://global/content/BrowserElementChild.js",
+                       /* allowDelayedLoad = */ true);
+  },
+
+  _recvHello: function(frameElement, data) {
+    debug("recvHello " + frameElement);
+  },
+
+  /**
+   * Fire either a vanilla or a custom event, depending on the contents of
+   * |data|.
+   */
+  _fireEventFromMsg: function(frameElement, data) {
+    let name = data.name;
+    let detail = data.json;
+
+    debug('fireEventFromMsg: ' + name + ' ' + detail);
+    let evtName = name.substring('browser-element-api:'.length);
+    let win = frameElement.ownerDocument.defaultView;
+    let evt;
+
+    // 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) {
+      evt = new win.CustomEvent('mozbrowser' + evtName, {detail: detail});
+    }
+    else {
+      evt = new win.Event('mozbrowser' + evtName);
+    }
+
+    frameElement.dispatchEvent(evt);
+  },
+
+  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-frame-shown':
+      this._observeRemoteBrowserFrameShown(subject);
+      break;
+    case 'in-process-browser-frame-shown':
+      this._observeInProcessBrowserFrameShown(subject);
+      break;
+    case 'content-document-global-created':
+      this._observeContentGlobalCreated(subject);
+      break;
+    }
+  },
+};
+
+var NSGetFactory = XPCOMUtils.generateNSGetFactory([BrowserElementParent]);
new file mode 100644
--- /dev/null
+++ b/dom/base/BrowserElementParent.manifest
@@ -0,0 +1,3 @@
+component {ddeafdac-cb39-47c4-9cb8-c9027ee36d26} BrowserElementParent.js
+contract @mozilla.org/browser-element-parent;1 {ddeafdac-cb39-47c4-9cb8-c9027ee36d26}
+category app-startup BrowserElementParent service,@mozilla.org/browser-element-parent;1
--- a/dom/base/Makefile.in
+++ b/dom/base/Makefile.in
@@ -49,18 +49,18 @@ FORCE_STATIC_LIB = 1
 
 DIRS = \
   test \
   $(NULL)
 
 EXTRA_PP_COMPONENTS = \
   ConsoleAPI.js \
   ConsoleAPI.manifest \
-  BrowserElementAPI.js \
-  BrowserElementAPI.manifest \
+  BrowserElementParent.js \
+  BrowserElementParent.manifest \
   $(NULL)
 
 EXTRA_JS_MODULES = ConsoleAPIStorage.jsm \
   $(NULL)
 
 EXTRA_COMPONENTS = \
   Webapps.js \
   Webapps.manifest \
--- a/dom/base/nsGlobalWindow.cpp
+++ b/dom/base/nsGlobalWindow.cpp
@@ -2336,29 +2336,56 @@ nsGlobalWindow::SetOpenerWindow(nsIDOMWi
     mHadOriginalOpener = true;
   }
 
 #ifdef DEBUG
   mSetOpenerWindowCalled = true;
 #endif
 }
 
+static
+already_AddRefed<nsIDOMEventTarget>
+TryGetTabChildGlobalAsEventTarget(nsISupports *aFrom)
+{
+  nsCOMPtr<nsIFrameLoaderOwner> frameLoaderOwner = do_QueryInterface(aFrom);
+  if (!frameLoaderOwner) {
+    return NULL;
+  }
+
+  nsRefPtr<nsFrameLoader> frameLoader = frameLoaderOwner->GetFrameLoader();
+  if (!frameLoader) {
+    return NULL;
+  }
+
+  nsCOMPtr<nsIDOMEventTarget> eventTarget =
+    frameLoader->GetTabChildGlobalAsEventTarget();
+  return eventTarget.forget();
+}
+
 void
 nsGlobalWindow::UpdateParentTarget()
 {
-  nsCOMPtr<nsIFrameLoaderOwner> flo = do_QueryInterface(mChromeEventHandler);
-  if (flo) {
-    nsRefPtr<nsFrameLoader> fl = flo->GetFrameLoader();
-    if (fl) {
-      mParentTarget = fl->GetTabChildGlobalAsEventTarget();
-    }
-  }
-  if (!mParentTarget) {
-    mParentTarget = mChromeEventHandler;
-  }
+  // Try to get our frame element's tab child global (its in-process message
+  // manager).  If that fails, fall back to the chrome event handler's tab
+  // child global, and if it doesn't have one, just use the chrome event
+  // handler itself.
+
+  nsCOMPtr<nsIDOMElement> frameElement = GetFrameElementInternal();
+  nsCOMPtr<nsIDOMEventTarget> eventTarget =
+    TryGetTabChildGlobalAsEventTarget(frameElement);
+
+  if (!eventTarget) {
+    eventTarget = TryGetTabChildGlobalAsEventTarget(mChromeEventHandler);
+  }
+
+  if (!eventTarget) {
+    eventTarget = mChromeEventHandler;
+  }
+
+  mParentTarget = eventTarget;
 }
 
 bool
 nsGlobalWindow::GetIsTabModalPromptAllowed()
 {
   bool allowTabModal = true;
   if (mDocShell) {
     nsCOMPtr<nsIContentViewer> cv;
--- a/dom/ipc/TabParent.cpp
+++ b/dom/ipc/TabParent.cpp
@@ -624,17 +624,18 @@ TabParent::RecvSetInputContext(const PRI
 
   return true;
 }
 
 bool
 TabParent::RecvGetDPI(float* aValue)
 {
   TryCacheDPI();
-  NS_ABORT_IF_FALSE(mDPI > 0, 
+
+  NS_ABORT_IF_FALSE(mDPI > 0,
                     "Must not ask for DPI before OwnerElement is received!");
   *aValue = mDPI;
   return true;
 }
 
 bool
 TabParent::RecvGetWidgetNativeData(WindowsHandle* aValue)
 {
@@ -859,16 +860,27 @@ TabParent::GetFrameLoader() const
 void
 TabParent::TryCacheDPI()
 {
   if (mDPI > 0) {
     return;
   }
 
   nsCOMPtr<nsIWidget> widget = GetWidget();
+
+  if (!widget && mFrameElement) {
+    // Even if we don't have a widget (e.g. because we're display:none), there's
+    // probably a widget somewhere in the hierarchy our frame element lives in.
+    nsCOMPtr<nsIDOMDocument> ownerDoc;
+    mFrameElement->GetOwnerDocument(getter_AddRefs(ownerDoc));
+
+    nsCOMPtr<nsIDocument> doc = do_QueryInterface(ownerDoc);
+    widget = nsContentUtils::WidgetForDocument(doc);
+  }
+
   if (widget) {
     mDPI = widget->GetDPI();
   }
 }
 
 already_AddRefed<nsIWidget>
 TabParent::GetWidget() const
 {
--- a/dom/ipc/jar.mn
+++ b/dom/ipc/jar.mn
@@ -1,3 +1,4 @@
 toolkit.jar:
         content/global/test-ipc.xul (test.xul)
         content/global/remote-test-ipc.js (remote-test.js)
+        content/global/BrowserElementChild.js (../base/BrowserElementChild.js)
--- a/dom/tests/mochitest/browser-frame/Makefile.in
+++ b/dom/tests/mochitest/browser-frame/Makefile.in
@@ -49,13 +49,12 @@ include $(topsrcdir)/config/rules.mk
 		browserFrameHelpers.js \
 		test_browserFrame1.html \
 		test_browserFrame2.html \
 		test_browserFrame3.html \
 		test_browserFrame4.html \
 		test_browserFrame5.html \
 		test_browserFrame6.html \
 		test_browserFrame7.html \
-		test_browserFrame8.html \
 		$(NULL)
 
 libs:: $(_TEST_FILES)
 	$(INSTALL) $(foreach f,$^,"$f") $(DEPTH)/_tests/testing/mochitest/tests/$(relativesrcdir)
--- a/dom/tests/mochitest/browser-frame/browserFrameHelpers.js
+++ b/dom/tests/mochitest/browser-frame/browserFrameHelpers.js
@@ -1,95 +1,120 @@
 "use strict";
 
 // Helpers for managing the browser frame preferences.
 
 const browserFrameHelpers = {
-  'getEnabledPref': function() {
+  _getBoolPref: function(pref) {
     try {
-      return SpecialPowers.getBoolPref('dom.mozBrowserFramesEnabled');
+      return SpecialPowers.getBoolPref(pref);
     }
-    catch(e) {
+    catch (e) {
       return undefined;
     }
   },
 
-  'getWhitelistPref': function() {
-    try {
-      return SpecialPowers.getCharPref('dom.mozBrowserFramesWhitelist');
+  _setBoolPref: function(pref, value) {
+    if (value !== undefined) {
+      SpecialPowers.setBoolPref(pref, value);
     }
-    catch(e) {
-      return undefined;
+    else {
+      SpecialPowers.clearUserPref(pref);
     }
   },
 
-  'getOOPDisabledPref': function() {
+  _getCharPref: function(pref) {
     try {
-      return SpecialPowers.getBoolPref('dom.ipc.tabs.disabled');
+      return SpecialPowers.getCharPref(pref);
     }
-    catch(e) {
+    catch (e) {
       return undefined;
     }
   },
 
-  'setEnabledPref': function(enabled) {
-    if (enabled !== undefined) {
-      SpecialPowers.setBoolPref('dom.mozBrowserFramesEnabled', enabled);
+  _setCharPref: function(pref, value) {
+    if (value !== undefined) {
+      SpecialPowers.setCharPref(pref, value);
     }
     else {
-      SpecialPowers.clearUserPref('dom.mozBrowserFramesEnabled');
-    }
-  },
-
-  'setWhitelistPref': function(whitelist) {
-    if (whitelist !== undefined) {
-      SpecialPowers.setCharPref('dom.mozBrowserFramesWhitelist', whitelist);
-    }
-    else {
-      SpecialPowers.clearUserPref('dom.mozBrowserFramesWhitelist');
+      SpecialPowers.clearUserPref(pref);
     }
   },
 
-  'setOOPDisabledPref': function(value) {
-    if (value !== undefined) {
-      SpecialPowers.setBoolPref('dom.ipc.tabs.disabled', value);
-    }
-    else {
-      SpecialPowers.clearUserPref('dom.ipc.tabs.disabled');
-    }
+  getEnabledPref: function() {
+    return this._getBoolPref('dom.mozBrowserFramesEnabled');
+  },
+
+  setEnabledPref: function(value) {
+    this._setBoolPref('dom.mozBrowserFramesEnabled', value);
+  },
+
+  getWhitelistPref: function() {
+    return this._getCharPref('dom.mozBrowserFramesWhitelist');
+  },
+
+  setWhitelistPref: function(whitelist) {
+    this._setCharPref('dom.mozBrowserFramesWhitelist', whitelist);
+  },
+
+  getOOPDisabledPref: function() {
+    return this._getBoolPref('dom.ipc.tabs.disabled');
   },
 
-  'addToWhitelist': function() {
-    var whitelist = browserFrameHelpers.getWhitelistPref();
-    whitelist += ',  http://' + window.location.host + ',  ';
-    browserFrameHelpers.setWhitelistPref(whitelist);
+  setOOPDisabledPref: function(value) {
+    this._setBoolPref('dom.ipc.tabs.disabled', value);
+  },
+
+  getPageThumbsEnabledPref: function() {
+    return this._getBoolPref('browser.pageThumbs.enabled');
+  },
+
+  setPageThumbsEnabledPref: function(value) {
+    this._setBoolPref('browser.pageThumbs.enabled', value);
   },
 
-  'restoreOriginalPrefs': function() {
-    browserFrameHelpers.setEnabledPref(browserFrameHelpers.origEnabledPref);
-    browserFrameHelpers.setWhitelistPref(browserFrameHelpers.origWhitelistPref);
-    browserFrameHelpers.setOOPDisabledPref(browserFrameHelpers.origOOPDisabledPref);
+  addToWhitelist: function() {
+    var whitelist = this.getWhitelistPref();
+    whitelist += ',  http://' + window.location.host + ',  ';
+    this.setWhitelistPref(whitelist);
+  },
+
+  restoreOriginalPrefs: function() {
+    this.setEnabledPref(this.origEnabledPref);
+    this.setWhitelistPref(this.origWhitelistPref);
+    this.setOOPDisabledPref(this.origOOPDisabledPref);
+    this.setPageThumbsEnabledPref(this.origPageThumbsEnabledPref);
   },
 
   'origEnabledPref': null,
   'origWhitelistPref': null,
   'origOOPDisabledPref': null,
+  'origPageThumbsEnabledPref': null,
 
   // Two basically-empty pages from two different domains you can load.
   'emptyPage1': 'http://example.com' +
                 window.location.pathname.substring(0, window.location.pathname.lastIndexOf('/')) +
                 '/file_empty.html',
   'emptyPage2': 'http://example.org' +
                 window.location.pathname.substring(0, window.location.pathname.lastIndexOf('/')) +
                 '/file_empty.html',
 };
 
 browserFrameHelpers.origEnabledPref = browserFrameHelpers.getEnabledPref();
 browserFrameHelpers.origWhitelistPref = browserFrameHelpers.getWhitelistPref();
 browserFrameHelpers.origOOPDisabledPref = browserFrameHelpers.getOOPDisabledPref();
+browserFrameHelpers.origPageThumbsEnabledPref = browserFrameHelpers.getPageThumbsEnabledPref();
 
-// Set OOPDisabledPref to true, because none of these tests pass with OOP
-// browser frames, at the moment.
-browserFrameHelpers.setOOPDisabledPref(true);
+// Disable tab view; it seriously messes us up.
+browserFrameHelpers.setPageThumbsEnabledPref(false);
+
+// OOP must be disabled on Windows; it doesn't work there.  Enable it
+// everywhere else.
+if (navigator.platform.indexOf('Win') != -1) {
+  browserFrameHelpers.setOOPDisabledPref(true);
+}
+else {
+  browserFrameHelpers.setOOPDisabledPref(false);
+}
 
 addEventListener('unload', function() {
   browserFrameHelpers.restoreOriginalPrefs();
 });
--- a/dom/tests/mochitest/browser-frame/test_browserFrame4.html
+++ b/dom/tests/mochitest/browser-frame/test_browserFrame4.html
@@ -26,126 +26,105 @@ SimpleTest.waitForExplicitFinish();
 
 function runTest() {
   browserFrameHelpers.setEnabledPref(true);
   browserFrameHelpers.addToWhitelist();
 
   // Load emptypage1 into the iframe, wait for that to finish loading, then
   // call runTest2.
   //
-  // This should trigger loadstart, load, and loadend events, but not a
-  // locationchange, because this is the initial load into the iframe.
+  // This should trigger loadstart, locationchange, and loadend events.
 
-  var seenLoad = false;
   var seenLoadEnd = false;
   var seenLoadStart = false;
+  var seenLocationChange = false;
 
   var iframe = document.createElement('iframe');
   iframe.mozbrowser = true;
   iframe.id = 'iframe';
   iframe.src = browserFrameHelpers.emptyPage1;
 
   function loadstart(e) {
     ok(e.isTrusted, 'Event should be trusted.');
-    ok(!seenLoad, 'loadstart before load.');
     ok(!seenLoadEnd, 'loadstart before loadend.');
     ok(!seenLoadStart, 'Just one loadstart event.');
     seenLoadStart = true;
   }
 
   function locationchange(e) {
     ok(e.isTrusted, 'Event should be trusted.');
-    ok(false, 'Unexpected locationchange.');
+    ok(!seenLocationChange, 'Just one locationchange event.');
+    seenLocationChange = true;
+    ok(seenLoadStart, 'Location change after load start.');
+    ok(!seenLoadEnd, 'Location change before load end.');
+    ok(e.detail, browserFrameHelpers.emptyPage1, "event's reported location");
   }
 
   function loadend(e) {
     ok(e.isTrusted, 'Event should be trusted.');
     ok(seenLoadStart, 'loadend after loadstart.');
-    ok(seenLoad, 'loadend after load.');
     ok(!seenLoadEnd, 'Just one loadend event.');
     seenLoadEnd = true;
   }
 
-  function load(e) {
-    ok(e.isTrusted, 'Event should be trusted.');
-    ok(seenLoadStart, 'load after loadstart.');
-    ok(!seenLoad, 'Just one load event.');
-    ok(!seenLoadEnd, 'load before loadend.');
-    seenLoad = true;
-  }
-
   iframe.addEventListener('mozbrowserloadstart', loadstart);
   iframe.addEventListener('mozbrowserlocationchange', locationchange);
   iframe.addEventListener('mozbrowserloadend', loadend);
-  iframe.addEventListener('load', load);
 
   function waitForAllCallbacks() {
-    if (!seenLoadStart || !seenLoad || !seenLoadEnd) {
+    if (!seenLoadStart || !seenLoadEnd) {
       SimpleTest.executeSoon(waitForAllCallbacks);
       return;
     }
 
     iframe.removeEventListener('mozbrowserloadstart', loadstart);
     iframe.removeEventListener('mozbrowserlocationchange', locationchange);
     iframe.removeEventListener('mozbrowserloadend', loadend);
-    iframe.removeEventListener('load', load);
     runTest2();
   }
 
   document.body.appendChild(iframe);
   waitForAllCallbacks();
 }
 
 function runTest2() {
   var seenLoadStart = false;
-  var seenLoad = false;
   var seenLoadEnd = false;
   var seenLocationChange = false;
 
   var iframe = document.getElementById('iframe');
   iframe.addEventListener('mozbrowserloadstart', function(e) {
     ok(e.isTrusted, 'Event should be trusted.');
     ok(!seenLoadStart, 'Just one loadstart event.');
     seenLoadStart = true;
-    ok(!seenLoad, 'Got mozbrowserloadstart event before load.');
     ok(!seenLoadEnd, 'Got mozbrowserloadstart before loadend.');
     ok(!seenLocationChange, 'Got mozbrowserloadstart before locationchange.');
   });
 
   iframe.addEventListener('mozbrowserlocationchange', function(e) {
     ok(e.isTrusted, 'Event should be trusted.');
     ok(!seenLocationChange, 'Just one locationchange event.');
     seenLocationChange = true;
     ok(seenLoadStart, 'Location change after load start.');
-    ok(!seenLoad, 'Location change before load.');
     ok(!seenLoadEnd, 'Location change before load end.');
     ok(e.detail, browserFrameHelpers.emptyPage2, "event's reported location");
   });
 
-  iframe.addEventListener('load', function(e) {
-    ok(e.isTrusted, 'Event should be trusted.');
-    ok(!seenLoad, 'Just one load event.');
-    seenLoad = true;
-    ok(seenLoadStart, 'Load after loadstart.');
-    ok(seenLocationChange, 'Load after locationchange.');
-    ok(!seenLoadEnd, 'Load before loadend.');
-  });
-
   iframe.addEventListener('mozbrowserloadend', function(e) {
     ok(e.isTrusted, 'Event should be trusted.');
     ok(!seenLoadEnd, 'Just one load end event.');
     seenLoadEnd = true;
     ok(seenLoadStart, 'Load end after load start.');
     ok(seenLocationChange, 'Load end after location change.');
   });
 
   iframe.src = browserFrameHelpers.emptyPage2;
 
   function waitForAllCallbacks() {
-    if (!seenLoadStart || !seenLoad || !seenLoadEnd || !seenLocationChange) {
+    if (!seenLoadStart || !seenLoadEnd || !seenLocationChange) {
       SimpleTest.executeSoon(waitForAllCallbacks);
       return;
     }
 
     SimpleTest.finish();
   }
 
   waitForAllCallbacks();
--- a/dom/tests/mochitest/browser-frame/test_browserFrame5.html
+++ b/dom/tests/mochitest/browser-frame/test_browserFrame5.html
@@ -22,47 +22,48 @@ SimpleTest.waitForExplicitFinish();
 
 function runTest() {
   browserFrameHelpers.setEnabledPref(true);
   browserFrameHelpers.addToWhitelist();
 
   var iframe1 = document.createElement('iframe');
   iframe1.mozbrowser = true;
   iframe1.id = 'iframe1';
-  iframe1.addEventListener('load', function() {
-    iframe1.removeEventListener('load', arguments.callee);
+  iframe1.addEventListener('mozbrowserloadend', function() {
+    iframe1.removeEventListener('mozbrowserloadend', arguments.callee);
+    ok(true, 'Got first loadend event.');
     SimpleTest.executeSoon(runTest2);
   });
   iframe1.src = browserFrameHelpers.emptyPage1;
   document.body.appendChild(iframe1);
 }
 
 function runTest2() {
   var iframe1 = document.getElementById('iframe1');
   var iframe2 = document.getElementById('iframe2');
 
-  var sawLoad = false;
+  var sawLoadEnd = false;
   var sawLocationChange = false;
 
   iframe1.addEventListener('mozbrowserlocationchange', function(e) {
     ok(e.isTrusted, 'Event should be trusted.');
     ok(!sawLocationChange, 'Just one locationchange event.');
-    ok(!sawLoad, 'locationchange before load.');
+    ok(!sawLoadEnd, 'locationchange before load.');
     is(e.detail, 'data:text/html,1', "event's reported location");
     sawLocationChange = true;
   });
 
-  iframe1.addEventListener('load', function() {
-    ok(sawLocationChange, 'Load after locationchange.');
-    ok(!sawLoad, 'Just one load event.');
-    sawLoad = true;
+  iframe1.addEventListener('mozbrowserloadend', function() {
+    ok(sawLocationChange, 'Loadend after locationchange.');
+    ok(!sawLoadEnd, 'Just one loadend event.');
+    sawLoadEnd = true;
   });
 
   function iframe2Load() {
-    if (!sawLoad || !sawLocationChange) {
+    if (!sawLoadEnd || !sawLocationChange) {
       // Spin if iframe1 hasn't loaded yet.
       SimpleTest.executeSoon(iframe2Load);
       return;
     }
     ok(true, 'Got iframe2 load.');
     SimpleTest.finish();
   }
   iframe2.addEventListener('load', iframe2Load);
--- a/dom/tests/mochitest/browser-frame/test_browserFrame6.html
+++ b/dom/tests/mochitest/browser-frame/test_browserFrame6.html
@@ -39,22 +39,30 @@ function runTest() {
   // iframe3 is another red herring.  It's not a mozbrowser, so we shouldn't
   // get any titlechange events on it.
   var iframe3 = document.createElement('iframe');
   document.body.appendChild(iframe3);
 
   var numTitleChanges = 0;
 
   iframe1.addEventListener('mozbrowsertitlechange', function(e) {
+    // Ignore empty titles; these come from about:blank.
+    if (e.detail == '')
+      return;
+
     numTitleChanges++;
 
     if (numTitleChanges == 1) {
       is(e.detail, 'Title');
-      iframe1.contentDocument.title = 'New title';
-      iframe2.contentDocument.title = 'BAD TITLE 2';
+      SpecialPowers.getBrowserFrameMessageManager(iframe1)
+                   .loadFrameScript("data:,content.document.title='New title';",
+                                    /* allowDelayedLoad = */ false);
+      SpecialPowers.getBrowserFrameMessageManager(iframe2)
+                   .loadFrameScript("data:,content.document.title='BAD TITLE 2';",
+                                    /* allowDelayedLoad = */ false);
     }
     else if (numTitleChanges == 2) {
       is(e.detail, 'New title');
       iframe1.src = 'data:text/html,<html><head><title>Title 3</title></head><body></body></html>';
     }
     else if (numTitleChanges == 3) {
       is(e.detail, 'Title 3');
       SimpleTest.finish();
--- a/dom/tests/mochitest/browser-frame/test_browserFrame7.html
+++ b/dom/tests/mochitest/browser-frame/test_browserFrame7.html
@@ -9,49 +9,95 @@ https://bugzilla.mozilla.org/show_bug.cg
   <script type="application/javascript" src="browserFrameHelpers.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
 </head>
 <body>
 <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=725796">Mozilla Bug 725796</a>
 
 <!--
   Test that an <iframe mozbrowser> is a window.{top,parent,frameElement} barrier.
-
-  If <iframe mozbrowser> is changed to run in a separate process, this test
-  will not work.
 -->
 
 <script type="application/javascript;version=1.7">
 "use strict";
 
 browserFrameHelpers.setEnabledPref(true);
 browserFrameHelpers.addToWhitelist();
 
-var iframe = document.createElement('iframe');
-iframe.addEventListener('load', function() {
-  outerIframeLoaded();
-});
-iframe.mozbrowser = true;
-iframe.src = 'data:text/html,Outer iframe';
-document.body.appendChild(iframe);
+// This test works only with OOP browser frames.  If browserFramesHelpers
+// didn't decide to enable OOP browser frames, just skip this test.
+if (browserFrameHelpers.getOOPDisabledPref()) {
+  ok(true, "Skipping test because OOP browser frames are disabled.");
+}
+else {
+  runTest();
+}
 
-SimpleTest.waitForExplicitFinish();
+var iframe;
+function runTest() {
+  iframe = document.createElement('iframe');
+  iframe.addEventListener('mozbrowserloadend', function() {
+    try {
+      outerIframeLoaded();
+    } catch(e) {
+      dump("Got error: " + e + '\n');
+    }
+  });
+  iframe.mozbrowser = true;
+  iframe.src = 'data:text/html,Outer iframe <iframe id="inner-iframe"></iframe>';
+  // For kicks, this test uses a display:none iframe.  This shouldn't make a
+  // difference in anything.
+  iframe.style.display = 'none';
+  document.body.appendChild(iframe);
 
+  SimpleTest.waitForExplicitFinish();
+}
+
+var numMsgReceived = 0;
 function outerIframeLoaded() {
-  var innerIframe = iframe.contentDocument.createElement('iframe');
-  iframe.contentDocument.body.appendChild(innerIframe);
+  var injectedScript = 
+    "data:,function is(a, b, desc) {                                     \
+      if (a == b) {                                                      \
+        sendAsyncMessage('test:test-pass', desc);                        \
+      } else {                                                           \
+        sendAsyncMessage('test:test-fail', desc + ' ' + a + ' != ' + b); \
+      }                                                                  \
+    }                                                                    \
+    is(content.window.top, content.window, 'top');                       \
+    is(content.window.parent, content.window, 'parent');                 \
+    is(content.window.frameElement, null, 'frameElement');               \
+    var innerIframe = content.document.getElementById('inner-iframe');   \
+    var innerWindow = innerIframe.contentWindow;                         \
+    is(innerWindow.top, content.window, 'inner top');                    \
+    is(innerWindow.parent, content.window, 'inner parent');              \
+    is(innerWindow.frameElement, innerIframe, 'inner frameElement');"
 
-  var iframeCw = iframe.contentWindow;
-  var innerCw = innerIframe.contentWindow;
+  var mm = SpecialPowers.getBrowserFrameMessageManager(iframe);
 
-  is(iframeCw.top, iframeCw, 'iframe top');
-  is(iframeCw.parent, iframeCw, 'iframe parent');
-  is(iframeCw.frameElement, null, 'iframe frameElement');
+  function onRecvTestPass(msg) {
+    numMsgReceived++;
+    ok(true, msg.json);
+  }
+  mm.addMessageListener('test:test-pass', onRecvTestPass); 
+
+  function onRecvTestFail(msg) {
+    numMsgReceived++;
+    ok(false, msg.json);
+  }
+  mm.addMessageListener('test:test-fail', onRecvTestFail);
 
-  is(innerCw.top, iframeCw, 'inner iframe top');
-  is(innerCw.parent, iframeCw, 'inner iframe parent');
-  is(innerCw.frameElement, innerIframe, 'inner iframe frameElement');
+  mm.loadFrameScript(injectedScript, /* allowDelayedLoad = */ false);
+
+  waitForMessages(6);
+}
+
+function waitForMessages(num) {
+  if (numMsgReceived < num) {
+    SimpleTest.executeSoon(function() { waitForMessages(num); });
+    return;
+  }
+  
   SimpleTest.finish();
 }
 
 </script>
 </body>
 </html>
deleted file mode 100644
--- a/dom/tests/mochitest/browser-frame/test_browserFrame8.html
+++ /dev/null
@@ -1,62 +0,0 @@
-<!DOCTYPE HTML>
-<html>
-<!--
-https://bugzilla.mozilla.org/show_bug.cgi?id=742448
--->
-<head>
-  <title>Test for Bug 742448</title>
-  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
-  <script type="application/javascript" src="browserFrameHelpers.js"></script>
-  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
-</head>
-<body>
-<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=742448">Mozilla Bug 742448</a>
-
-<!--
-  Test that <iframe mozbrowser>'s window.{top,parent,frameElement} methods are
-  not confused if we override window.top ourselves.
--->
-
-<script type="application/javascript;version=1.7">
-"use strict";
-
-browserFrameHelpers.setEnabledPref(true);
-browserFrameHelpers.addToWhitelist();
-
-var iframe = document.createElement('iframe');
-iframe.addEventListener('load', function() {
-  outerIframeLoaded();
-});
-iframe.mozbrowser = true;
-iframe.src = 'data:text/html,Outer iframe';
-document.body.appendChild(iframe);
-
-SimpleTest.waitForExplicitFinish();
-
-function outerIframeLoaded() {
-  var innerIframe = iframe.contentDocument.createElement('iframe');
-  iframe.contentDocument.body.appendChild(innerIframe);
-
-  var iframeCw = iframe.contentWindow;
-  var innerCw = innerIframe.contentWindow;
-
-  // Override window.top in the parent iframe.  This shouldn't confuse the
-  // inner one.
-  Object.defineProperty(iframe.contentWindow, 'top', {
-    get: function() { return 42; }
-  });
-
-  is(iframeCw.top, 42, 'iframe top');
-  is(iframeCw.parent, iframeCw, 'iframe parent');
-  is(iframeCw.frameElement, null, 'iframe frameElement');
-
-  is(innerCw.top, iframeCw, 'inner iframe top');
-  is(innerCw.parent, iframeCw, 'inner iframe parent');
-  is(innerCw.frameElement, innerIframe, 'inner iframe frameElement');
-  SimpleTest.finish();
-}
-
-</script>
-</body>
-</html>
-
--- a/mobile/android/installer/package-manifest.in
+++ b/mobile/android/installer/package-manifest.in
@@ -289,18 +289,18 @@
 @BINPATH@/components/xul.xpt
 @BINPATH@/components/xuldoc.xpt
 @BINPATH@/components/xultmpl.xpt
 @BINPATH@/components/zipwriter.xpt
 
 ; JavaScript components
 @BINPATH@/components/ConsoleAPI.manifest
 @BINPATH@/components/ConsoleAPI.js
-@BINPATH@/components/BrowserElementAPI.manifest
-@BINPATH@/components/BrowserElementAPI.js
+@BINPATH@/components/BrowserElementParent.manifest
+@BINPATH@/components/BrowserElementParent.js
 @BINPATH@/components/FeedProcessor.manifest
 @BINPATH@/components/FeedProcessor.js
 @BINPATH@/components/BrowserFeeds.manifest
 @BINPATH@/components/FeedConverter.js
 @BINPATH@/components/FeedWriter.js
 @BINPATH@/components/fuelApplication.manifest
 @BINPATH@/components/fuelApplication.js
 @BINPATH@/components/WebContentConverter.js
--- a/mobile/xul/installer/package-manifest.in
+++ b/mobile/xul/installer/package-manifest.in
@@ -291,18 +291,18 @@
 @BINPATH@/components/xul.xpt
 @BINPATH@/components/xuldoc.xpt
 @BINPATH@/components/xultmpl.xpt
 @BINPATH@/components/zipwriter.xpt
 
 ; JavaScript components
 @BINPATH@/components/ConsoleAPI.manifest
 @BINPATH@/components/ConsoleAPI.js
-@BINPATH@/components/BrowserElementAPI.manifest
-@BINPATH@/components/BrowserElementAPI.js
+@BINPATH@/components/BrowserElementParent.manifest
+@BINPATH@/components/BrowserElementParent.js
 @BINPATH@/components/FeedProcessor.manifest
 @BINPATH@/components/FeedProcessor.js
 @BINPATH@/components/BrowserFeeds.manifest
 @BINPATH@/components/FeedConverter.js
 @BINPATH@/components/FeedWriter.js
 @BINPATH@/components/fuelApplication.manifest
 @BINPATH@/components/fuelApplication.js
 @BINPATH@/components/WebContentConverter.js
--- a/testing/mochitest/tests/SimpleTest/specialpowersAPI.js
+++ b/testing/mochitest/tests/SimpleTest/specialpowersAPI.js
@@ -1055,10 +1055,19 @@ SpecialPowersAPI.prototype = {
     var el = this._getElement(aWindow, target);
     return el.dispatchEvent(event);
   },
 
   get isDebugBuild() {
     delete this.isDebugBuild;
     var debug = Cc["@mozilla.org/xpcom/debug;1"].getService(Ci.nsIDebug2);
     return this.isDebugBuild = debug.isDebugBuild;
-  }
+  },
+
+  /**
+   * Get the message manager associated with an <iframe mozbrowser>.
+   */
+  getBrowserFrameMessageManager: function(aFrameElement) {
+    return this.wrap(aFrameElement.QueryInterface(Ci.nsIFrameLoaderOwner)
+                                  .frameLoader
+                                  .messageManager);
+  },
 };