Bug 725796 - Make window.{top,parent,frameElement} respect <iframe mozbrowser>. r=bz
authorJustin Lebar <justin.lebar@gmail.com>
Sun, 04 Mar 2012 11:02:00 -0500
changeset 88224 ab7ed8a7a885178298f104969c35d30967f519c1
parent 88223 e0f3f797e0fd03b1613a29c8a4b96112a096a525
child 88225 058ac6b6930be019d8605512b387ac907d56f82a
push id22179
push userbmo@edmorley.co.uk
push dateSun, 04 Mar 2012 22:43:30 +0000
treeherdermozilla-central@433cfbd2a0da [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbz
bugs725796
milestone13.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 725796 - Make window.{top,parent,frameElement} respect <iframe mozbrowser>. r=bz
content/base/src/nsFrameLoader.cpp
content/html/content/src/nsGenericHTMLFrameElement.cpp
content/html/content/src/nsGenericHTMLFrameElement.h
docshell/base/nsDocShell.cpp
docshell/base/nsDocShell.h
docshell/base/nsIDocShell.idl
dom/base/nsGlobalWindow.cpp
dom/base/nsGlobalWindow.h
dom/interfaces/base/nsIDOMWindow.idl
dom/interfaces/html/Makefile.in
dom/interfaces/html/nsIMozBrowserFrame.idl
dom/tests/mochitest/general/Makefile.in
dom/tests/mochitest/general/test_browserFrame7.html
--- a/content/base/src/nsFrameLoader.cpp
+++ b/content/base/src/nsFrameLoader.cpp
@@ -83,16 +83,17 @@
 #include "nsEventDispatcher.h"
 #include "nsISHistory.h"
 #include "nsISHistoryInternal.h"
 #include "nsIDocShellHistory.h"
 #include "nsIDOMHTMLDocument.h"
 #include "nsIXULWindow.h"
 #include "nsIEditor.h"
 #include "nsIEditorDocShell.h"
+#include "nsIMozBrowserFrame.h"
 
 #include "nsLayoutUtils.h"
 #include "nsIView.h"
 #include "nsAsyncDOMEvent.h"
 
 #include "nsIURI.h"
 #include "nsIURL.h"
 #include "nsNetUtil.h"
@@ -975,27 +976,27 @@ nsFrameLoader::SwapWithOtherLoader(nsFra
   bool equal;
   nsresult rv =
     ourContent->NodePrincipal()->Equals(otherContent->NodePrincipal(), &equal);
   if (NS_FAILED(rv) || !equal) {
     // Security problems loom.  Just bail on it all
     return NS_ERROR_DOM_SECURITY_ERR;
   }
 
-  nsCOMPtr<nsIDocShell> ourDochell = GetExistingDocShell();
+  nsCOMPtr<nsIDocShell> ourDocshell = GetExistingDocShell();
   nsCOMPtr<nsIDocShell> otherDocshell = aOther->GetExistingDocShell();
-  if (!ourDochell || !otherDocshell) {
+  if (!ourDocshell || !otherDocshell) {
     // How odd
     return NS_ERROR_NOT_IMPLEMENTED;
   }
 
   // To avoid having to mess with session history, avoid swapping
   // frameloaders that don't correspond to root same-type docshells,
   // unless both roots have session history disabled.
-  nsCOMPtr<nsIDocShellTreeItem> ourTreeItem = do_QueryInterface(ourDochell);
+  nsCOMPtr<nsIDocShellTreeItem> ourTreeItem = do_QueryInterface(ourDocshell);
   nsCOMPtr<nsIDocShellTreeItem> otherTreeItem =
     do_QueryInterface(otherDocshell);
   nsCOMPtr<nsIDocShellTreeItem> ourRootTreeItem, otherRootTreeItem;
   ourTreeItem->GetSameTypeRootTreeItem(getter_AddRefs(ourRootTreeItem));
   otherTreeItem->GetSameTypeRootTreeItem(getter_AddRefs(otherRootTreeItem));
   nsCOMPtr<nsIWebNavigation> ourRootWebnav =
     do_QueryInterface(ourRootTreeItem);
   nsCOMPtr<nsIWebNavigation> otherRootWebnav =
@@ -1053,17 +1054,17 @@ nsFrameLoader::SwapWithOtherLoader(nsFra
   PRInt32 ourParentType = nsIDocShellTreeItem::typeContent;
   PRInt32 otherParentType = nsIDocShellTreeItem::typeContent;
   ourParentItem->GetItemType(&ourParentType);
   otherParentItem->GetItemType(&otherParentType);
   if (ourParentType != otherParentType) {
     return NS_ERROR_NOT_IMPLEMENTED;
   }
 
-  nsCOMPtr<nsPIDOMWindow> ourWindow = do_GetInterface(ourDochell);
+  nsCOMPtr<nsPIDOMWindow> ourWindow = do_GetInterface(ourDocshell);
   nsCOMPtr<nsPIDOMWindow> otherWindow = do_GetInterface(otherDocshell);
 
   nsCOMPtr<nsIDOMElement> ourFrameElement =
     ourWindow->GetFrameElementInternal();
   nsCOMPtr<nsIDOMElement> otherFrameElement =
     otherWindow->GetFrameElementInternal();
 
   nsCOMPtr<nsIDOMEventTarget> ourChromeEventHandler =
@@ -1103,16 +1104,24 @@ nsFrameLoader::SwapWithOtherLoader(nsFra
   NS_ASSERTION(otherDoc == otherParentDocument, "Unexpected parent document");
 
   nsIPresShell* ourShell = ourDoc->GetShell();
   nsIPresShell* otherShell = otherDoc->GetShell();
   if (!ourShell || !otherShell) {
     return NS_ERROR_NOT_IMPLEMENTED;
   }
 
+  bool weAreBrowserFrame = false;
+  bool otherIsBrowserFrame = false;
+  ourDocshell->GetIsBrowserFrame(&weAreBrowserFrame);
+  otherDocshell->GetIsBrowserFrame(&otherIsBrowserFrame);
+  if (weAreBrowserFrame != otherIsBrowserFrame) {
+    return NS_ERROR_NOT_IMPLEMENTED;
+  }
+
   if (mInSwap || aOther->mInSwap) {
     return NS_ERROR_NOT_IMPLEMENTED;
   }
   mInSwap = aOther->mInSwap = true;
 
   // Fire pageshow events on still-loading pages, and then fire pagehide
   // events.  Note that we do NOT fire these in the normal way, but just fire
   // them on the chrome event handlers.
@@ -1476,16 +1485,23 @@ 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);
+  }
+
   // 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.
   nsCOMPtr<nsIDOMElement> frame_element(do_QueryInterface(mOwnerContent));
   NS_ASSERTION(frame_element, "frame loader owner element not a DOM element!");
--- a/content/html/content/src/nsGenericHTMLFrameElement.cpp
+++ b/content/html/content/src/nsGenericHTMLFrameElement.cpp
@@ -24,19 +24,20 @@ using namespace mozilla::dom;
 
 NS_IMPL_CYCLE_COLLECTION_CLASS(nsGenericHTMLFrameElement)
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(nsGenericHTMLFrameElement,
                                                   nsGenericHTMLElement)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR_AMBIGUOUS(mFrameLoader, nsIFrameLoader)
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
 
 NS_INTERFACE_TABLE_HEAD(nsGenericHTMLFrameElement)
-  NS_INTERFACE_TABLE_INHERITED3(nsGenericHTMLFrameElement,
+  NS_INTERFACE_TABLE_INHERITED4(nsGenericHTMLFrameElement,
                                 nsIFrameLoaderOwner,
                                 nsIDOMMozBrowserFrame,
+                                nsIMozBrowserFrame,
                                 nsIWebProgressListener)
   NS_INTERFACE_TABLE_TO_MAP_SEGUE_CYCLE_COLLECTION(nsGenericHTMLFrameElement)
 NS_INTERFACE_MAP_END_INHERITING(nsGenericHTMLElement)
 
 NS_IMPL_INT_ATTR(nsGenericHTMLFrameElement, TabIndex, tabindex)
 
 nsGenericHTMLFrameElement::~nsGenericHTMLFrameElement()
 {
@@ -280,30 +281,30 @@ nsGenericHTMLFrameElement::SetMozbrowser
   nsresult rv = SetBoolAttr(nsGkAtoms::mozbrowser, aValue);
   if (NS_SUCCEEDED(rv)) {
     MaybeEnsureBrowserFrameListenersRegistered();
   }
   return rv;
 }
 
 /*
- * If this frame element is allowed to be a browser frame (because it passes
- * BrowserFrameSecurityCheck()), then make sure that it has the appropriate
+ * If this frame element is allowed to be a browser frame (i.e.,
+ * GetReallyIsBrowser returns true), then make sure that it has the appropriate
  * event listeners enabled.
  */
 void
 nsGenericHTMLFrameElement::MaybeEnsureBrowserFrameListenersRegistered()
 {
   if (mBrowserFrameListenersRegistered) {
     return;
   }
 
   // If this frame passes the browser frame security check, ensure that its
   // listeners are active.
-  if (!BrowserFrameSecurityCheck()) {
+  if (!GetReallyIsBrowser()) {
     return;
   }
 
   // Not much we can do without a frameLoader.  But EnsureFrameLoader will call
   // this function, so we'll get a chance to pass this test.
   if (!mFrameLoader) {
     return;
   }
@@ -346,17 +347,17 @@ nsGenericHTMLFrameElement::MaybeEnsureBr
                                         /* wantsUntrusted = */ false);
 }
 
 /**
  * Return true if this frame element has permission to send mozbrowser
  * events, and false otherwise.
  */
 bool
-nsGenericHTMLFrameElement::BrowserFrameSecurityCheck()
+nsGenericHTMLFrameElement::GetReallyIsBrowser()
 {
   // Fail if browser frames are globally disabled.
   if (!Preferences::GetBool("dom.mozBrowserFramesEnabled")) {
     return false;
   }
 
   // Fail if this frame doesn't have the mozbrowser attribute.
   bool isBrowser = false;
@@ -373,16 +374,23 @@ nsGenericHTMLFrameElement::BrowserFrameS
                                            "dom.mozBrowserFramesWhitelist")) {
     return false;
   }
 
   // Otherwise, succeed.
   return true;
 }
 
+NS_IMETHODIMP
+nsGenericHTMLFrameElement::GetReallyIsBrowser(bool *aResult)
+{
+  *aResult = GetReallyIsBrowser();
+  return NS_OK;
+}
+
 /**
  * Fire a mozbrowser event, if we have permission.
  *
  * @param aEventName the event name (e.g. "locationchange").  "mozbrowser" is
  *        added to the beginning of aEventName automatically.
  * @param aEventType the event type.  Must be either "event" or "customevent".
  * @param aValue the value passed along with the event.  This value will be
  *        set as the event's "detail" property.  This must be empty if
@@ -394,17 +402,17 @@ nsGenericHTMLFrameElement::MaybeFireBrow
   const nsAString &aEventType,
   const nsAString &aValue /* = EmptyString() */)
 {
   MOZ_ASSERT(aEventType.EqualsLiteral("event") ||
              aEventType.EqualsLiteral("customevent"));
   MOZ_ASSERT_IF(aEventType.EqualsLiteral("event"),
                 aValue.IsEmpty());
 
-  if (!BrowserFrameSecurityCheck()) {
+  if (!GetReallyIsBrowser()) {
     return NS_OK;
   }
 
   nsAutoString eventName;
   eventName.AppendLiteral("mozbrowser");
   eventName.Append(aEventName);
 
   nsCOMPtr<nsIDOMEvent> domEvent;
--- a/content/html/content/src/nsGenericHTMLFrameElement.h
+++ b/content/html/content/src/nsGenericHTMLFrameElement.h
@@ -2,42 +2,43 @@
 /* vim:set tw=80 expandtab softtabstop=2 ts=2 sw=2: */
 
 /* 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/. */
 
 #include "nsGenericHTMLElement.h"
 #include "nsIDOMHTMLFrameElement.h"
-#include "nsIDOMMozBrowserFrame.h"
+#include "nsIMozBrowserFrame.h"
 #include "nsIDOMEventListener.h"
 #include "nsIWebProgressListener.h"
 
 /**
  * A helper class for frame elements
  */
 class nsGenericHTMLFrameElement : public nsGenericHTMLElement,
                                   public nsIFrameLoaderOwner,
-                                  public nsIDOMMozBrowserFrame,
+                                  public nsIMozBrowserFrame,
                                   public nsIWebProgressListener
 {
 public:
   nsGenericHTMLFrameElement(already_AddRefed<nsINodeInfo> aNodeInfo,
                             mozilla::dom::FromParser aFromParser)
     : nsGenericHTMLElement(aNodeInfo)
     , mNetworkCreated(aFromParser == mozilla::dom::FROM_PARSER_NETWORK)
     , mBrowserFrameListenersRegistered(false)
   {
   }
 
   virtual ~nsGenericHTMLFrameElement();
 
   NS_IMETHOD QueryInterface(REFNSIID aIID, void** aInstancePtr);
   NS_DECL_NSIFRAMELOADEROWNER
   NS_DECL_NSIDOMMOZBROWSERFRAME
+  NS_DECL_NSIMOZBROWSERFRAME
   NS_DECL_NSIWEBPROGRESSLISTENER
 
   // nsIContent
   virtual bool IsHTMLFocusable(bool aWithMouse, bool *aIsFocusable, PRInt32 *aTabIndex);
   virtual nsresult BindToTree(nsIDocument* aDocument, nsIContent* aParent,
                               nsIContent* aBindingParent,
                               bool aCompileEventHandlers);
   virtual void UnbindFromTree(bool aDeep = true,
@@ -56,16 +57,19 @@ public:
 
   // nsIDOMHTMLElement
   NS_IMETHOD GetTabIndex(PRInt32 *aTabIndex);
   NS_IMETHOD SetTabIndex(PRInt32 aTabIndex);
 
   NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED_NO_UNLINK(nsGenericHTMLFrameElement,
                                                      nsGenericHTMLElement)
 
+  // Non-COM version of nsIMozBrowserFrame::GetReallyIsBrowser.
+  bool GetReallyIsBrowser();
+
 protected:
   /**
    * Listens to titlechanged events from the document inside the iframe and
    * forwards them along to the iframe so it can fire a mozbrowsertitlechange
    * event if appropriate.
    */
   class TitleChangedListener MOZ_FINAL : public nsIDOMEventListener
   {
@@ -87,17 +91,16 @@ protected:
   // This doesn't really ensure a frame loade in all cases, only when
   // it makes sense.
   nsresult EnsureFrameLoader();
   nsresult LoadSrc();
   nsresult GetContentDocument(nsIDOMDocument** aContentDocument);
   nsresult GetContentWindow(nsIDOMWindow** aContentWindow);
 
   void MaybeEnsureBrowserFrameListenersRegistered();
-  bool BrowserFrameSecurityCheck();
   nsresult MaybeFireBrowserEvent(const nsAString &aEventName,
                                  const nsAString &aEventType,
                                  const nsAString &aValue = EmptyString());
 
   nsRefPtr<nsFrameLoader> mFrameLoader;
   nsRefPtr<TitleChangedListener> mTitleChangedListener;
 
   // True when the element is created by the parser
--- a/docshell/base/nsDocShell.cpp
+++ b/docshell/base/nsDocShell.cpp
@@ -11751,8 +11751,23 @@ nsDocShell::GetCanExecuteScripts(bool *a
             NS_ERROR("cannot get a docshell from a treeItem!");
           }
 #endif // DEBUG
       } while (treeItem && docshell);
   }
 
   return NS_OK;
 }
+
+NS_IMETHODIMP
+nsDocShell::GetIsBrowserFrame(bool *aOut)
+{
+  NS_ENSURE_ARG_POINTER(aOut);
+  *aOut = mIsBrowserFrame;
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocShell::SetIsBrowserFrame(bool aValue)
+{
+  mIsBrowserFrame = aValue;
+  return NS_OK;
+}
--- a/docshell/base/nsDocShell.h
+++ b/docshell/base/nsDocShell.h
@@ -799,16 +799,17 @@ protected:
     bool                       mObserveErrorPages;
     bool                       mAllowAuth;
     bool                       mAllowKeywordFixup;
     bool                       mIsOffScreenBrowser;
     bool                       mIsActive;
     bool                       mIsAppTab;
     bool                       mUseGlobalHistory;
     bool                       mInPrivateBrowsing;
+    bool                       mIsBrowserFrame;
 
     // This boolean is set to true right before we fire pagehide and generally
     // unset when we embed a new content viewer.  While it's true no navigation
     // is allowed in this docshell.
     bool                       mFiredUnloadEvent;
 
     // this flag is for bug #21358. a docshell may load many urls
     // which don't result in new documents being created (i.e. a new
--- a/docshell/base/nsIDocShell.idl
+++ b/docshell/base/nsIDocShell.idl
@@ -67,17 +67,17 @@ interface nsIRequest;
 interface nsISHEntry;
 interface nsILayoutHistoryState;
 interface nsISecureBrowserUI;
 interface nsIDOMStorage;
 interface nsIPrincipal;
 interface nsIWebBrowserPrint;
 interface nsIVariant;
 
-[scriptable, uuid(0615d1a6-313f-11e1-a043-6c626d69675c)]
+[scriptable, uuid(DBD39C21-5788-4C68-9D97-0FCEE289BCE1)]
 interface nsIDocShell : nsISupports
 {
   /**
    * Loads a given URI.  This will give priority to loading the requested URI
    * in the object implementing	this interface.  If it can't be loaded here
    * however, the URL dispatcher will go through its normal process of content
    * loading.
    *
@@ -597,9 +597,21 @@ interface nsIDocShell : nsISupports
    */
   attribute nsIAtom parentCharset;
 
   /*
    * In a child docshell, this is the source of parentCharset
    * @see nsIParser
    */
   attribute PRInt32 parentCharsetSource;
+
+  /*
+   * Is this docshell a browser frame (i.e., does it correspond to an <iframe
+   * mozbrowser>)?  The frameloader is responsible for setting this property
+   * when it initializes the docshell.
+   *
+   * If so, this docshell should act like a chrome/content boundary for the
+   * purposes of window.top and window.parent.
+   *
+   * See also nsIMozBrowserFrame.
+   */
+  attribute bool isBrowserFrame;
 };
--- a/dom/base/nsGlobalWindow.cpp
+++ b/dom/base/nsGlobalWindow.cpp
@@ -2976,24 +2976,57 @@ nsGlobalWindow::GetPerformance(nsIDOMPer
         mPerformance = new nsPerformance(timing, timedChannel);
       }
     }
     NS_IF_ADDREF(*aPerformance = mPerformance);
   }
   return NS_OK;
 }
 
-NS_IMETHODIMP
-nsGlobalWindow::GetParent(nsIDOMWindow** aParent)
-{
-  FORWARD_TO_OUTER(GetParent, (aParent), NS_ERROR_NOT_INITIALIZED);
+/**
+ * GetScriptableParent is called when script reads window.parent.
+ *
+ * In contrast to GetRealParent, GetScriptableParent respects <iframe
+ * mozbrowser> boundaries, so if |this| is contained by an <iframe
+ * mozbrowser>, we will return |this| as its own parent.
+ */
+NS_IMETHODIMP
+nsGlobalWindow::GetScriptableParent(nsIDOMWindow** aParent)
+{
+  FORWARD_TO_OUTER(GetScriptableParent, (aParent), NS_ERROR_NOT_INITIALIZED);
+
+  *aParent = NULL;
+  if (!mDocShell) {
+    return NS_OK;
+  }
+
+  bool isMozBrowser = false;
+  mDocShell->GetIsBrowserFrame(&isMozBrowser);
+  if (isMozBrowser) {
+    nsCOMPtr<nsIDOMWindow> parent = static_cast<nsIDOMWindow*>(this);
+    parent.swap(*aParent);
+    return NS_OK;
+  }
+
+  return GetRealParent(aParent);
+}
+
+/**
+ * nsIDOMWindow::GetParent (when called from C++) is just a wrapper around
+ * GetRealParent.
+ */
+NS_IMETHODIMP
+nsGlobalWindow::GetRealParent(nsIDOMWindow** aParent)
+{
+  FORWARD_TO_OUTER(GetRealParent, (aParent), NS_ERROR_NOT_INITIALIZED);
 
   *aParent = nsnull;
-  if (!mDocShell)
+  if (!mDocShell) {
     return NS_OK;
+  }
 
   nsCOMPtr<nsIDocShellTreeItem> docShellAsItem(do_QueryInterface(mDocShell));
   NS_ENSURE_TRUE(docShellAsItem, NS_ERROR_FAILURE);
 
   nsCOMPtr<nsIDocShellTreeItem> parent;
   docShellAsItem->GetSameTypeParent(getter_AddRefs(parent));
 
   if (parent) {
@@ -3003,31 +3036,72 @@ nsGlobalWindow::GetParent(nsIDOMWindow**
   }
   else {
     *aParent = static_cast<nsIDOMWindow*>(this);
     NS_ADDREF(*aParent);
   }
   return NS_OK;
 }
 
-NS_IMETHODIMP
-nsGlobalWindow::GetTop(nsIDOMWindow** aTop)
-{
-  FORWARD_TO_OUTER(GetTop, (aTop), NS_ERROR_NOT_INITIALIZED);
-
+/**
+ * GetScriptableTop is called when script reads window.top.
+ *
+ * In contrast to GetRealTop, GetScriptableTop respects <iframe mozbrowser>
+ * boundaries.  If we encounter a window owned by an <iframe mozbrowser> while
+ * walking up the window hierarchy, we'll stop and return that window.
+ */
+NS_IMETHODIMP
+nsGlobalWindow::GetScriptableTop(nsIDOMWindow **aTop)
+{
+  return GetTopImpl(aTop, /* aScriptable = */ true);
+}
+
+/**
+ * nsIDOMWindow::GetTop (when called from C++) is just a wrapper around
+ * GetRealTop.
+ */
+NS_IMETHODIMP
+nsGlobalWindow::GetRealTop(nsIDOMWindow** aTop)
+{
+  return GetTopImpl(aTop, /* aScriptable = */ false);
+}
+
+nsresult
+nsGlobalWindow::GetTopImpl(nsIDOMWindow** aTop, bool aScriptable)
+{
+  FORWARD_TO_OUTER(GetTopImpl, (aTop, aScriptable), NS_ERROR_NOT_INITIALIZED);
   *aTop = nsnull;
-  if (mDocShell) {
-    nsCOMPtr<nsIDocShellTreeItem> docShellAsItem(do_QueryInterface(mDocShell));
-    nsCOMPtr<nsIDocShellTreeItem> root;
-    docShellAsItem->GetSameTypeRootTreeItem(getter_AddRefs(root));
-
-    if (root) {
-      nsCOMPtr<nsIDOMWindow> top(do_GetInterface(root));
-      top.swap(*aTop);
-    }
+
+  // Walk up the parent chain.
+
+  nsCOMPtr<nsIDOMWindow> prevParent = this;
+  nsCOMPtr<nsIDOMWindow> parent = this;
+  do {
+    if (!parent) {
+      break;
+    }
+
+    prevParent = parent;
+
+    nsCOMPtr<nsIDOMWindow> newParent;
+    nsresult rv;
+    if (aScriptable) {
+      rv = parent->GetScriptableParent(getter_AddRefs(newParent));
+    }
+    else {
+      rv = parent->GetParent(getter_AddRefs(newParent));
+    }
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    parent = newParent;
+
+  } while (parent != prevParent);
+
+  if (parent) {
+    parent.swap(*aTop);
   }
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsGlobalWindow::GetContent(nsIDOMWindow** aContent)
 {
@@ -6902,22 +6976,53 @@ nsGlobalWindow::CacheXBLPrototypeHandler
       NS_ERROR("nsContentUtils::HoldJSObjects failed!");
       return;
     }
   }
 
   mCachedXBLPrototypeHandlers.Put(aKey, aHandler.get());
 }
 
-NS_IMETHODIMP
-nsGlobalWindow::GetFrameElement(nsIDOMElement** aFrameElement)
-{
-  FORWARD_TO_OUTER(GetFrameElement, (aFrameElement), NS_ERROR_NOT_INITIALIZED);
-
-  *aFrameElement = nsnull;
+/**
+ * GetScriptableFrameElement is called when script reads
+ * nsIGlobalWindow::frameElement.
+ *
+ * In contrast to GetRealFrameElement, GetScriptableFrameElement says that the
+ * window contained by an <iframe mozbrowser> has no frame element
+ * (effectively treating a mozbrowser the same as a content/chrome boundary).
+ */
+NS_IMETHODIMP
+nsGlobalWindow::GetScriptableFrameElement(nsIDOMElement** aFrameElement)
+{
+  FORWARD_TO_OUTER(GetScriptableFrameElement, (aFrameElement), NS_ERROR_NOT_INITIALIZED);
+  *aFrameElement = NULL;
+
+  if (!mDocShell) {
+    return NS_OK;
+  }
+
+  bool isMozBrowser = false;
+  mDocShell->GetIsBrowserFrame(&isMozBrowser);
+  if (isMozBrowser) {
+    return NS_OK;
+  }
+
+  return GetFrameElement(aFrameElement);
+}
+
+/**
+ * nsIGlobalWindow::GetFrameElement (when called from C++) is just a wrapper
+ * around GetRealFrameElement.
+ */
+NS_IMETHODIMP
+nsGlobalWindow::GetRealFrameElement(nsIDOMElement** aFrameElement)
+{
+  FORWARD_TO_OUTER(GetRealFrameElement, (aFrameElement), NS_ERROR_NOT_INITIALIZED);
+
+  *aFrameElement = NULL;
 
   nsCOMPtr<nsIDocShellTreeItem> docShellTI(do_QueryInterface(mDocShell));
 
   if (!docShellTI) {
     return NS_OK;
   }
 
   nsCOMPtr<nsIDocShellTreeItem> parent;
--- a/dom/base/nsGlobalWindow.h
+++ b/dom/base/nsGlobalWindow.h
@@ -421,16 +421,26 @@ public:
     // Make sure this matches the casts we do in QueryInterface().
     return (nsISupports *)(nsIScriptGlobalObject *)win;
   }
   static nsGlobalWindow *FromWrapper(nsIXPConnectWrappedNative *wrapper)
   {
     return FromSupports(wrapper->Native());
   }
 
+  /**
+   * Wrap nsIDOMWindow::GetTop so we can overload the inline GetTop()
+   * implementation below.  (nsIDOMWindow::GetTop simply calls
+   * nsIDOMWindow::GetRealTop().)
+   */
+  nsresult GetTop(nsIDOMWindow **aWindow)
+  {
+    return nsIDOMWindow::GetTop(aWindow);
+  }
+
   inline nsGlobalWindow *GetTop()
   {
     nsCOMPtr<nsIDOMWindow> top;
     GetTop(getter_AddRefs(top));
     if (top)
       return static_cast<nsGlobalWindow *>(static_cast<nsIDOMWindow *>(top.get()));
     return nsnull;
   }
@@ -583,16 +593,19 @@ public:
   void UnmarkGrayTimers();
 private:
   // Enable updates for the accelerometer.
   void EnableDeviceMotionUpdates();
 
   // Disables updates for the accelerometer.
   void DisableDeviceMotionUpdates();
 
+  // Implements Get{Real,Scriptable}Top.
+  nsresult GetTopImpl(nsIDOMWindow **aWindow, bool aScriptable);
+
 protected:
   friend class HashchangeCallback;
   friend class nsBarProp;
 
   // Object Management
   virtual ~nsGlobalWindow();
   void CleanUp(bool aIgnoreModalDialog);
   void ClearControllers();
--- a/dom/interfaces/base/nsIDOMWindow.idl
+++ b/dom/interfaces/base/nsIDOMWindow.idl
@@ -65,17 +65,17 @@ interface nsIDOMMozURLProperty : nsISupp
  * The nsIDOMWindow interface is the primary interface for a DOM
  * window object. It represents a single window object that may
  * contain child windows if the document in the window contains a
  * HTML frameset document or if the document contains iframe elements.
  *
  * @see <http://www.whatwg.org/html/#window>
  */
 
-[scriptable, uuid(f6e3b10d-d5f4-4fcd-aa4c-5f98626d428a)]
+[scriptable, uuid(17400E2B-F78B-4E69-B500-C2A3135A40FD)]
 interface nsIDOMWindow : nsISupports
 {
   // the current browsing context
   readonly attribute nsIDOMWindow                       window;
 
   /* [replaceable] self */
   readonly attribute nsIDOMWindow                       self;
 
@@ -129,34 +129,100 @@ interface nsIDOMWindow : nsISupports
   void                      blur();
 
 
   // other browsing contexts
   /* [replaceable] length */
   readonly attribute unsigned long                      length;
 
   /**
-   * Accessor for the root of this hierarchy of windows. This root may
-   * be the window itself if there is no parent, or if the parent is
-   * of different type (i.e. this does not cross chrome-content
-   * boundaries).
+   * |top| gets the root of the window hierarchy.
+   *
+   * This function does not cross chrome-content boundaries, so if this
+   * window's parent is of a different type, |top| will return this window.
+   *
+   * When script reads the top property, we run GetScriptableTop, which
+   * will not cross an <iframe mozbrowser> boundary.
+   *
+   * In contrast, C++ calls to GetTop are forwarded to GetRealTop, which
+   * ignores <iframe mozbrowser> boundaries.
    *
-   * This property is "replaceable" in JavaScript */
+   * This property is "replaceable" in JavaScript.
+   */
+  [binaryname(ScriptableTop)]
   readonly attribute nsIDOMWindow                       top;
 
+  /**
+   * You shouldn't need to call this function directly; call GetTop instead.
+   */
+  [noscript]
+  readonly attribute nsIDOMWindow                       realTop;
+
+  %{C++
+  nsresult GetTop(nsIDOMWindow **aWindow)
+  {
+    return GetRealTop(aWindow);
+  }
+  %}
+
+  /**
+   * |parent| gets this window's parent window.  If this window has no parent,
+   * we return the window itself.
+   *
+   * This property does not cross chrome-content boundaries, so if this
+   * window's parent is of a different type, we return the window itself as its
+   * parent.
+   *
+   * When script reads the property (or when C++ calls ScriptableTop), this
+   * property does not cross <iframe mozbrowser> boundaries.  In contrast, when
+   * C++ calls GetParent, we ignore the mozbrowser attribute.
+   */
+  [binaryname(ScriptableParent)]
+  readonly attribute nsIDOMWindow                       parent;
+
+  /**
+   * You shouldn't need to read this property directly; call GetParent instead.
+   */
+  [noscript]
+  readonly attribute nsIDOMWindow                       realParent;
+
+  %{C++
+  inline nsresult GetParent(nsIDOMWindow **aWindow)
+  {
+    return GetRealParent(aWindow);
+  }
+  %}
+
            attribute nsIDOMWindow                       opener;
 
   /**
-   * Accessor for this window's parent window, or the window itself if
-   * there is no parent, or if the parent is of different type
-   * (i.e. this does not cross chrome-content boundaries).
+   * |frameElement| gets this window's <iframe> or <frame> element, if it has
+   * one.
+   *
+   * When script reads this property (or when C++ calls
+   * ScriptableFrameElement), we return |null| if the window is inside an
+   * <iframe mozbrowser>.  In contrast, when C++ calls GetFrameElement, we
+   * ignore the mozbrowser attribute.
    */
-  readonly attribute nsIDOMWindow                       parent;
+  [binaryname(ScriptableFrameElement)]
+  readonly attribute nsIDOMElement                      frameElement;
 
-  readonly attribute nsIDOMElement                      frameElement;
+  /**
+   * You shouldn't need to read this property directly; call GetFrameElement
+   * instead.
+   */
+  [noscript]
+  readonly attribute nsIDOMElement                      realFrameElement;
+
+  %{C++
+  inline nsresult GetFrameElement(nsIDOMElement **aElement)
+  {
+    return GetRealFrameElement(aElement);
+  }
+  %}
 
 
   // the user agent
   readonly attribute nsIDOMNavigator                    navigator;
 
   /**
    * Get the application cache object for this window.
    */
--- a/dom/interfaces/html/Makefile.in
+++ b/dom/interfaces/html/Makefile.in
@@ -117,11 +117,12 @@ SDK_XPIDLSRCS =					\
 	nsIDOMValidityState.idl		\
 	nsIDOMDOMStringMap.idl		\
 	nsIDOMMozBrowserFrame.idl		\
 	$(NULL)
 
 XPIDLSRCS = 					\
 	nsIDOMHTMLCanvasElement.idl		\
 	nsIDOMHTMLUnknownElement.idl \
+	nsIMozBrowserFrame.idl \
 	$(NULL)
 
 include $(topsrcdir)/config/rules.mk
new file mode 100644
--- /dev/null
+++ b/dom/interfaces/html/nsIMozBrowserFrame.idl
@@ -0,0 +1,21 @@
+/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set tw=80 expandtab softtabstop=2 ts=2 sw=2: */
+
+/* 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/. */
+
+#include "nsIDOMMozBrowserFrame.idl"
+
+[scriptable, uuid(076AD76C-2DF6-4760-B914-21D554F0A2B6)]
+interface nsIMozBrowserFrame : nsIDOMMozBrowserFrame
+{
+  /**
+   * Gets whether this frame really is a browser frame.
+   *
+   * In order to really be a browser frame, this frame's
+   * nsIDOMMozBrowserFrame::mozbrowser attribute must be true, and the frame
+   * may have to pass various security checks.
+   */
+  readonly attribute boolean reallyIsBrowser;
+};
--- a/dom/tests/mochitest/general/Makefile.in
+++ b/dom/tests/mochitest/general/Makefile.in
@@ -74,16 +74,17 @@ include $(topsrcdir)/config/rules.mk
 		test_vibrator.html \
 		browserFrameHelpers.js \
 		test_browserFrame1.html \
 		test_browserFrame2.html \
 		test_browserFrame3.html \
 		test_browserFrame4.html \
 		test_browserFrame5.html \
 		test_browserFrame6.html \
+		test_browserFrame7.html \
 		test_for_of.html \
 		test_focus_legend_noparent.html \
 		$(NULL)
 
 _CHROME_FILES = \
 		test_innerScreen.xul \
 		test_offsets.xul \
 		test_offsets.js \
new file mode 100644
--- /dev/null
+++ b/dom/tests/mochitest/general/test_browserFrame7.html
@@ -0,0 +1,48 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=725796
+-->
+<head>
+  <title>Test for Bug 725796</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=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.mozbrowser = true;
+document.body.appendChild(iframe);
+
+var innerIframe = document.createElement('iframe');
+iframe.contentDocument.body.appendChild(innerIframe);
+
+var iframeCw = iframe.contentWindow;
+var innerCw = innerIframe.contentWindow;
+
+is(iframeCw.top, iframeCw, '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');
+
+</script>
+</body>
+</html>