Bug 802366 - The main event: Let a browser process inherit its app's id. r=bz,cjones DONTBUILD
authorJustin Lebar <justin.lebar@gmail.com>
Tue, 30 Oct 2012 16:13:21 -0400
changeset 111811 d718d87d4bb542aede8a10040f5d71e69f65d772
parent 111810 f9a9450a82afddf0ed3d65f671a13ddfd4e734c5
child 111812 9a2165ff980314f34ed60f41401e2c4b612d88fc
push id23776
push useremorley@mozilla.com
push dateWed, 31 Oct 2012 14:08:00 +0000
treeherdermozilla-central@3393586a210b [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbz, cjones
bugs802366
milestone19.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 802366 - The main event: Let a browser process inherit its app's id. r=bz,cjones DONTBUILD The main bug fixed here is that in half of our interfaces, we use "is browser frame/element" to mean "browser or app", and in the other half, we use it to mean "is browser not app". There's a related, functional bug also fixed here, which is that a browser process doesn't inherit its parent's app-id. This causes problems e.g. for IndexedDB: If a browser inside an app uses IndexedDB, the DB should have the app's app-id. I also modified Tab{Parent,Child} and nsFrameLoader to call "app" "ownOrContainingApp", to emphasize that we might have inherited the app from a parent process. I left nsIDocShell::appId alone, because changing that would have necessitated changing nsILoadGroup and therefore a /lot/ of users in Necko; it's also not clear it would have clarified anything in those cases. (Re-landing changeset a6a847452dbf, backed out in 5091aa6083c4, because it was originally landed with the incorrect bug number.)
content/base/src/nsDocument.cpp
content/base/src/nsFrameLoader.cpp
content/base/src/nsFrameLoader.h
content/base/src/nsInProcessTabChildGlobal.cpp
content/base/src/nsInProcessTabChildGlobal.h
content/events/src/nsEventStateManager.cpp
content/html/content/src/nsGenericHTMLFrameElement.cpp
docshell/base/nsDSURIContentListener.cpp
docshell/base/nsDocShell.cpp
docshell/base/nsDocShell.h
docshell/base/nsIDocShell.idl
dom/base/nsGlobalWindow.cpp
dom/interfaces/html/nsIMozBrowserFrame.idl
dom/ipc/AppProcessPermissions.cpp
dom/ipc/ContentChild.cpp
dom/ipc/ContentChild.h
dom/ipc/ContentParent.cpp
dom/ipc/ContentParent.h
dom/ipc/PContent.ipdl
dom/ipc/TabChild.cpp
dom/ipc/TabChild.h
dom/ipc/TabParent.cpp
dom/ipc/TabParent.h
embedding/components/windowwatcher/src/nsWindowWatcher.cpp
xpfe/appshell/src/nsContentTreeOwner.cpp
--- a/content/base/src/nsDocument.cpp
+++ b/content/base/src/nsDocument.cpp
@@ -8354,17 +8354,17 @@ HasCrossProcessParent(nsIDocument* aDocu
   nsPIDOMWindow* win = aDocument->GetWindow();
   if (!win) {
     return false;
   }
   nsCOMPtr<nsIDocShell> docShell = win->GetDocShell();
   if (!docShell) {
     return false;
   }
-  return docShell->GetIsContentBoundary();
+  return docShell->GetIsBrowserOrApp();
 }
 
 static bool
 ResetFullScreen(nsIDocument* aDocument, void* aData)
 {
   if (aDocument->IsFullScreenDoc()) {
     static_cast<nsDocument*>(aDocument)->CleanupFullscreenState();
     NS_ASSERTION(!aDocument->IsFullScreenDoc(), "Should reset full-screen");
--- a/content/base/src/nsFrameLoader.cpp
+++ b/content/base/src/nsFrameLoader.cpp
@@ -939,17 +939,17 @@ nsFrameLoader::ShowRemoteFrame(const nsI
     }
 
     mRemoteBrowser->Show(size);
     mRemoteBrowserShown = true;
 
     EnsureMessageManager();
 
     nsCOMPtr<nsIObserverService> os = services::GetObserverService();
-    if (OwnerIsBrowserFrame() && os && !mRemoteBrowserInitialized) {
+    if (OwnerIsBrowserOrAppFrame() && os && !mRemoteBrowserInitialized) {
       os->NotifyObservers(NS_ISUPPORTS_CAST(nsIFrameLoader*, this),
                           "remote-browser-frame-shown", NULL);
       mRemoteBrowserInitialized = true;
     }
   } else {
     nsRect dimensions;
     NS_ENSURE_SUCCESS(GetWindowDimensions(dimensions), false);
     mRemoteBrowser->UpdateDimensions(dimensions, size);
@@ -1388,72 +1388,119 @@ nsFrameLoader::SetOwnerContent(Element* 
   }
   mOwnerContent = aContent;
   if (RenderFrameParent* rfp = GetCurrentRemoteFrame()) {
     rfp->OwnerContentChanged(aContent);
   }
 }
 
 bool
-nsFrameLoader::OwnerIsBrowserFrame()
+nsFrameLoader::OwnerIsBrowserOrAppFrame()
 {
   nsCOMPtr<nsIMozBrowserFrame> browserFrame = do_QueryInterface(mOwnerContent);
-  bool isBrowser = false;
-  if (browserFrame) {
-    browserFrame->GetReallyIsBrowser(&isBrowser);
-  }
-  return isBrowser;
+  return browserFrame ? browserFrame->GetReallyIsBrowserOrApp() : false;
 }
 
 bool
 nsFrameLoader::OwnerIsAppFrame()
 {
   nsCOMPtr<nsIMozBrowserFrame> browserFrame = do_QueryInterface(mOwnerContent);
-  bool isApp = false;
-  if (browserFrame) {
-    browserFrame->GetReallyIsApp(&isApp);
-  }
-  return isApp;
+  return browserFrame ? browserFrame->GetReallyIsApp() : false;
+}
+
+bool
+nsFrameLoader::OwnerIsBrowserFrame()
+{
+  return OwnerIsBrowserOrAppFrame() && !OwnerIsAppFrame();
 }
 
 void
 nsFrameLoader::GetOwnerAppManifestURL(nsAString& aOut)
 {
   aOut.Truncate();
   nsCOMPtr<nsIMozBrowserFrame> browserFrame = do_QueryInterface(mOwnerContent);
   if (browserFrame) {
     browserFrame->GetAppManifestURL(aOut);
   }
 }
 
+already_AddRefed<mozIApplication>
+nsFrameLoader::GetOwnApp()
+{
+  nsAutoString manifest;
+  GetOwnerAppManifestURL(manifest);
+  if (manifest.IsEmpty()) {
+    return nullptr;
+  }
+
+  nsCOMPtr<nsIAppsService> appsService = do_GetService(APPS_SERVICE_CONTRACTID);
+  NS_ENSURE_TRUE(appsService, nullptr);
+
+  nsCOMPtr<mozIDOMApplication> domApp;
+  appsService->GetAppByManifestURL(manifest, getter_AddRefs(domApp));
+
+  nsCOMPtr<mozIApplication> app = do_QueryInterface(domApp);
+  MOZ_ASSERT_IF(domApp, app);
+  return app.forget();
+}
+
+already_AddRefed<mozIApplication>
+nsFrameLoader::GetOwnOrContainingApp()
+{
+  nsCOMPtr<mozIApplication> app = GetOwnApp();
+  if (app) {
+    return app.forget();
+  }
+
+  // See if our owner content's principal has an associated app.
+  uint32_t appId = mOwnerContent->NodePrincipal()->GetAppId();
+  MOZ_ASSERT(appId != nsIScriptSecurityManager::UNKNOWN_APP_ID);
+
+  if (appId == nsIScriptSecurityManager::NO_APP_ID ||
+      appId == nsIScriptSecurityManager::UNKNOWN_APP_ID) {
+    return nullptr;
+  }
+
+  nsCOMPtr<nsIAppsService> appsService = do_GetService(APPS_SERVICE_CONTRACTID);
+  NS_ENSURE_TRUE(appsService, nullptr);
+
+  nsCOMPtr<mozIDOMApplication> domApp;
+  appsService->GetAppByLocalId(appId, getter_AddRefs(domApp));
+  MOZ_ASSERT(domApp);
+
+  app = do_QueryInterface(domApp);
+  MOZ_ASSERT_IF(domApp, app);
+  return app.forget();
+}
+
 bool
 nsFrameLoader::ShouldUseRemoteProcess()
 {
   if (PR_GetEnv("MOZ_DISABLE_OOP_TABS") ||
       Preferences::GetBool("dom.ipc.tabs.disabled", false)) {
     return false;
   }
 
   // If we're inside a content process, don't use a remote process for this
   // frame; it won't work properly until bug 761935 is fixed.
   if (XRE_GetProcessType() == GeckoProcessType_Content) {
     return false;
   }
 
   // If we're an <iframe mozbrowser> and we don't have a "remote" attribute,
   // fall back to the default.
-  if (OwnerIsBrowserFrame() &&
+  if (OwnerIsBrowserOrAppFrame() &&
       !mOwnerContent->HasAttr(kNameSpaceID_None, nsGkAtoms::Remote)) {
 
     return Preferences::GetBool("dom.ipc.browser_frames.oop_by_default", false);
   }
 
   // Otherwise, we're remote if we have "remote=true" and we're either a
   // browser frame or a XUL element.
-  return (OwnerIsBrowserFrame() ||
+  return (OwnerIsBrowserOrAppFrame() ||
           mOwnerContent->GetNameSpaceID() == kNameSpaceID_XUL) &&
          mOwnerContent->AttrValueIs(kNameSpaceID_None,
                                     nsGkAtoms::Remote,
                                     nsGkAtoms::_true,
                                     eCaseMatters);
 }
 
 nsresult
@@ -1490,34 +1537,16 @@ nsFrameLoader::MaybeCreateDocShell()
     doc->GetContainer();
   nsCOMPtr<nsIWebNavigation> parentAsWebNav = do_QueryInterface(container);
   NS_ENSURE_STATE(parentAsWebNav);
 
   // Create the docshell...
   mDocShell = do_CreateInstance("@mozilla.org/docshell;1");
   NS_ENSURE_TRUE(mDocShell, NS_ERROR_FAILURE);
 
-  if (OwnerIsBrowserFrame()) {
-    nsAutoString manifest;
-    GetOwnerAppManifestURL(manifest);
-    if (!manifest.IsEmpty()) {
-      nsCOMPtr<nsIAppsService> appsService =
-        do_GetService(APPS_SERVICE_CONTRACTID);
-      if (!appsService) {
-        NS_ERROR("Apps Service is not available!");
-        return NS_ERROR_FAILURE;
-      }
-
-      uint32_t appId;
-      appsService->GetAppLocalIdByManifestURL(manifest, &appId);
-
-      mDocShell->SetAppId(appId);
-    }
-  }
-
   if (!mNetworkCreated) {
     nsCOMPtr<nsIDocShellHistory> history = do_QueryInterface(mDocShell);
     if (history) {
       history->SetCreatedDynamically(true);
     }
   }
 
   // Get the frame name and tell the docshell about it.
@@ -1609,19 +1638,44 @@ nsFrameLoader::MaybeCreateDocShell()
   if (NS_FAILED(base_win->Create()) || !win_private) {
     // Do not call Destroy() here. See bug 472312.
     NS_WARNING("Something wrong when creating the docshell for a frameloader!");
     return NS_ERROR_FAILURE;
   }
 
   EnsureMessageManager();
 
+  if (OwnerIsAppFrame()) {
+    // You can't be both an app and a browser frame.
+    MOZ_ASSERT(!OwnerIsBrowserFrame());
+
+    nsCOMPtr<mozIApplication> ownApp = GetOwnApp();
+    MOZ_ASSERT(ownApp);
+    uint32_t ownAppId = nsIScriptSecurityManager::NO_APP_ID;
+    if (ownApp) {
+      NS_ENSURE_SUCCESS(ownApp->GetLocalId(&ownAppId), NS_ERROR_FAILURE);
+    }
+
+    mDocShell->SetIsApp(ownAppId);
+  }
+
   if (OwnerIsBrowserFrame()) {
-    mDocShell->SetIsBrowserElement();
+    // You can't be both a browser and an app frame.
+    MOZ_ASSERT(!OwnerIsAppFrame());
 
+    nsCOMPtr<mozIApplication> containingApp = GetOwnOrContainingApp();
+    uint32_t containingAppId = nsIScriptSecurityManager::NO_APP_ID;
+    if (containingApp) {
+      NS_ENSURE_SUCCESS(containingApp->GetLocalId(&containingAppId),
+                        NS_ERROR_FAILURE);
+    }
+    mDocShell->SetIsBrowserInsideApp(containingAppId);
+  }
+
+  if (OwnerIsBrowserOrAppFrame()) {
     nsCOMPtr<nsIObserverService> os = services::GetObserverService();
     if (os) {
       os->NotifyObservers(NS_ISUPPORTS_CAST(nsIFrameLoader*, this),
                           "in-process-browser-or-app-frame-shown", NULL);
     }
 
     if (mMessageManager) {
       mMessageManager->LoadFrameScript(
@@ -1943,17 +1997,17 @@ nsFrameLoader::TryRemoteBrowser()
 
   if (!parentAsWebNav) {
     return false;
   }
 
   nsCOMPtr<nsIDocShellTreeItem> parentAsItem(do_QueryInterface(parentAsWebNav));
 
   // <iframe mozbrowser> gets to skip these checks.
-  if (!OwnerIsBrowserFrame()) {
+  if (!OwnerIsBrowserOrAppFrame()) {
     int32_t parentType;
     parentAsItem->GetItemType(&parentType);
 
     if (parentType != nsIDocShellTreeItem::typeChrome) {
       return false;
     }
 
     if (!mOwnerContent->IsXUL()) {
@@ -1979,45 +2033,28 @@ nsFrameLoader::TryRemoteBrowser()
   nsCOMPtr<nsIXULWindow> window(do_GetInterface(parentOwner));
   if (!window) {
     return false;
   }
   if (NS_FAILED(window->GetChromeFlags(&chromeFlags))) {
     return false;
   }
 
-  bool isBrowserElement = false;
+  // Get our own or containing app only if we're a browser or app frame.  We
+  // want to avoid the situation where, if we're a remote frame that's neither
+  // a browser nor an app frame, we pass our containing app to
+  // ContentParent::CreateBrowserOrApp below, because that would effectively
+  // give this non-browser-or-app frame app privileges.
   nsCOMPtr<mozIApplication> app;
-  if (OwnerIsBrowserFrame()) {
-    isBrowserElement = true;
-
-    nsAutoString manifest;
-    GetOwnerAppManifestURL(manifest);
-    if (!manifest.IsEmpty()) {
-      nsCOMPtr<nsIAppsService> appsService = do_GetService(APPS_SERVICE_CONTRACTID);
-      if (!appsService) {
-        NS_ERROR("Apps Service is not available!");
-        return false;
-      }
-
-      nsCOMPtr<mozIDOMApplication> domApp;
-      appsService->GetAppByManifestURL(manifest, getter_AddRefs(domApp));
-      // If the frame is actually an app, we should not mark it as a
-      // browser.  This is to identify the data store: since <app>s
-      // and <browser>s-within-<app>s have different stores, we want
-      // to ensure the <app> uses its store, not the one for its
-      // <browser>s.
-      app = do_QueryInterface(domApp);
-      if (app) {
-        isBrowserElement = false;
-      }
-    }
+  if (OwnerIsBrowserOrAppFrame()) {
+    app = GetOwnOrContainingApp();
   }
 
-  if ((mRemoteBrowser = ContentParent::CreateBrowser(app, isBrowserElement))) {
+  mRemoteBrowser = ContentParent::CreateBrowserOrApp(app, OwnerIsBrowserFrame());
+  if (mRemoteBrowser) {
     nsCOMPtr<nsIDOMElement> element = do_QueryInterface(mOwnerContent);
     mRemoteBrowser->SetOwnerElement(element);
 
     nsCOMPtr<nsIDocShellTreeItem> rootItem;
     parentAsItem->GetRootTreeItem(getter_AddRefs(rootItem));
     nsCOMPtr<nsIDOMWindow> rootWin = do_GetInterface(rootItem);
     nsCOMPtr<nsIDOMChromeWindow> rootChromeWin = do_QueryInterface(rootWin);
     NS_ABORT_IF_FALSE(rootChromeWin, "How did we not get a chrome window here?");
@@ -2317,17 +2354,17 @@ nsFrameLoader::EnsureMessageManager()
 {
   NS_ENSURE_STATE(mOwnerContent);
 
   nsresult rv = MaybeCreateDocShell();
   if (NS_FAILED(rv)) {
     return rv;
   }
 
-  if (!mIsTopLevelContent && !OwnerIsBrowserFrame() && !mRemoteFrame) {
+  if (!mIsTopLevelContent && !OwnerIsBrowserOrAppFrame() && !mRemoteFrame) {
     return NS_OK;
   }
 
   if (mMessageManager) {
     if (ShouldUseRemoteProcess()) {
       mMessageManager->SetCallback(mRemoteBrowserShown ? this : nullptr);
     }
     return NS_OK;
--- a/content/base/src/nsFrameLoader.h
+++ b/content/base/src/nsFrameLoader.h
@@ -28,16 +28,17 @@ class nsIURI;
 class nsSubDocumentFrame;
 class nsIView;
 class nsIInProcessContentFrameMessageManager;
 class AutoResetInShow;
 class nsITabParent;
 class nsIDocShellTreeItem;
 class nsIDocShellTreeOwner;
 class nsIDocShellTreeNode;
+class mozIApplication;
 
 namespace mozilla {
 namespace dom {
 class PBrowserParent;
 class TabParent;
 struct StructuredCloneData;
 }
 
@@ -308,33 +309,52 @@ private:
 
   void SetOwnerContent(mozilla::dom::Element* aContent);
 
   bool ShouldUseRemoteProcess();
 
   /**
    * Is this a frameloader for a bona fide <iframe mozbrowser> or
    * <iframe mozapp>?  (I.e., does the frame return true for
-   * nsIMozBrowserFrame::GetReallyIsBrowser()?)
+   * nsIMozBrowserFrame::GetReallyIsBrowserOrApp()?)
    */
-  bool OwnerIsBrowserFrame();
+  bool OwnerIsBrowserOrAppFrame();
 
   /**
    * Is this a frameloader for a bona fide <iframe mozapp>?  (I.e., does the
    * frame return true for nsIMozBrowserFrame::GetReallyIsApp()?)
    */
   bool OwnerIsAppFrame();
 
   /**
+   * Is this a frame loader for a bona fide <iframe mozbrowser>?
+   */
+  bool OwnerIsBrowserFrame();
+
+  /**
    * Get our owning element's app manifest URL, or return the empty string if
    * our owning element doesn't have an app manifest URL.
    */
   void GetOwnerAppManifestURL(nsAString& aOut);
 
   /**
+   * Get the app for our frame.  This is the app whose manifest is returned by
+   * GetOwnerAppManifestURL.
+   */
+  already_AddRefed<mozIApplication> GetOwnApp();
+
+  /**
+   * Get the closest app to our frame.  This is either the frame returned by
+   * GetOwnApp() or the app associated with our owner element's principal.
+   *
+   * That is, this method returns the app that our owner content is inside.
+   */
+  already_AddRefed<mozIApplication> GetOwnOrContainingApp();
+
+  /**
    * If we are an IPC frame, set mRemoteFrame. Otherwise, create and
    * initialize mDocShell.
    */
   nsresult MaybeCreateDocShell();
   nsresult EnsureMessageManager();
   NS_HIDDEN_(void) GetURL(nsString& aURL);
 
   // Properly retrieves documentSize of any subdocument type.
--- a/content/base/src/nsInProcessTabChildGlobal.cpp
+++ b/content/base/src/nsInProcessTabChildGlobal.cpp
@@ -92,24 +92,25 @@ nsInProcessTabChildGlobal::DoSendAsyncMe
 
 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.
+  // If owner corresponds to an <iframe mozbrowser> or <iframe mozapp>, we'll
+  // have to tweak our PreHandleEvent implementation.
   nsCOMPtr<nsIMozBrowserFrame> browserFrame = do_QueryInterface(mOwner);
-  bool isBrowser = false;
   if (browserFrame) {
-    browserFrame->GetReallyIsBrowser(&isBrowser);
+    mIsBrowserOrAppFrame = browserFrame->GetReallyIsBrowserOrApp();
   }
-  mIsBrowserFrame = isBrowser;
+  else {
+    mIsBrowserOrAppFrame = false;
+  }
 }
 
 nsInProcessTabChildGlobal::~nsInProcessTabChildGlobal()
 {
   NS_ASSERTION(!mCx, "Couldn't release JSContext?!?");
 }
 
 /* [notxpcom] boolean markForCC (); */
@@ -263,17 +264,17 @@ nsInProcessTabChildGlobal::GetOwnerConte
   return mOwner;
 }
 
 nsresult
 nsInProcessTabChildGlobal::PreHandleEvent(nsEventChainPreVisitor& aVisitor)
 {
   aVisitor.mCanHandle = true;
 
-  if (mIsBrowserFrame &&
+  if (mIsBrowserOrAppFrame &&
       (!mOwner || !nsContentUtils::IsInChromeDocshell(mOwner->OwnerDoc()))) {
     if (mOwner) {
       nsPIDOMWindow* innerWindow = mOwner->OwnerDoc()->GetInnerWindow();
       if (innerWindow) {
         aVisitor.mParentTarget = innerWindow->GetParentTarget();
       }
     }
   } else {
--- a/content/base/src/nsInProcessTabChildGlobal.h
+++ b/content/base/src/nsInProcessTabChildGlobal.h
@@ -117,18 +117,19 @@ 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;
+  // Is this the message manager for an in-process <iframe mozbrowser> or
+  // <iframe mozapp>?  This affects where events get sent, so it affects
+  // PreHandleEvent.
+  bool mIsBrowserOrAppFrame;
 public:
   nsIContent* mOwner;
   nsFrameMessageManager* mChromeMessageManager;
   nsTArray<nsCOMPtr<nsIRunnable> > mASyncMessages;
 };
 
 #endif
--- a/content/events/src/nsEventStateManager.cpp
+++ b/content/events/src/nsEventStateManager.cpp
@@ -1565,24 +1565,20 @@ nsEventStateManager::IsRemoteTarget(nsIC
   if ((target->Tag() == nsGkAtoms::browser ||
        target->Tag() == nsGkAtoms::iframe) &&
       target->IsXUL() &&
       target->AttrValueIs(kNameSpaceID_None, nsGkAtoms::Remote,
                           nsGkAtoms::_true, eIgnoreCase)) {
     return true;
   }
 
-  // <frame/iframe mozbrowser>
+  // <frame/iframe mozbrowser/mozapp>
   nsCOMPtr<nsIMozBrowserFrame> browserFrame = do_QueryInterface(target);
-  if (browserFrame) {
-    bool isBrowser = false;
-    browserFrame->GetReallyIsBrowser(&isBrowser);
-    if (isBrowser) {
-      return !!TabParent::GetFrom(target);
-    }
+  if (browserFrame && browserFrame->GetReallyIsBrowserOrApp()) {
+    return !!TabParent::GetFrom(target);
   }
 
   return false;
 }
 
 /*static*/ void
 nsEventStateManager::MapEventCoordinatesForChildProcess(nsFrameLoader* aFrameLoader,
                                                         nsEvent* aEvent)
--- a/content/html/content/src/nsGenericHTMLFrameElement.cpp
+++ b/content/html/content/src/nsGenericHTMLFrameElement.cpp
@@ -273,73 +273,71 @@ nsGenericHTMLFrameElement::IsHTMLFocusab
   return false;
 }
 
 /**
  * Return true if this frame element really is a mozbrowser or mozapp.  (It
  * needs to have the right attributes, and its creator must have the right
  * permissions.)
  */
-nsresult
-nsGenericHTMLFrameElement::GetReallyIsBrowser(bool *aOut)
+/* [infallible] */ nsresult
+nsGenericHTMLFrameElement::GetReallyIsBrowserOrApp(bool *aOut)
 {
   *aOut = false;
 
   // Fail if browser frames are globally disabled.
   if (!Preferences::GetBool("dom.mozBrowserFramesEnabled")) {
     return NS_OK;
   }
 
   // Fail if this frame doesn't have the mozbrowser attribute.
-  bool isBrowser = false;
-  GetMozbrowser(&isBrowser);
-  if (!isBrowser) {
+  bool hasMozbrowser = false;
+  GetMozbrowser(&hasMozbrowser);
+  if (!hasMozbrowser) {
     return NS_OK;
   }
 
   // Fail if the node principal isn't trusted.
   nsIPrincipal *principal = NodePrincipal();
   nsCOMPtr<nsIPermissionManager> permMgr =
     do_GetService(NS_PERMISSIONMANAGER_CONTRACTID);
-  NS_ENSURE_STATE(permMgr);
+  NS_ENSURE_TRUE(permMgr, NS_OK);
 
   uint32_t permission = nsIPermissionManager::DENY_ACTION;
   nsresult rv = permMgr->TestPermissionFromPrincipal(principal, "browser", &permission);
   NS_ENSURE_SUCCESS(rv, NS_OK);
   *aOut = permission == nsIPermissionManager::ALLOW_ACTION;
   return NS_OK;
 }
 
-NS_IMETHODIMP
+/* [infallible] */ NS_IMETHODIMP
 nsGenericHTMLFrameElement::GetReallyIsApp(bool *aOut)
 {
   nsAutoString manifestURL;
   GetAppManifestURL(manifestURL);
 
   *aOut = !manifestURL.IsEmpty();
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsGenericHTMLFrameElement::GetAppManifestURL(nsAString& aOut)
 {
   aOut.Truncate();
 
   // At the moment, you can't be an app without being a browser.
-  bool isBrowser = false;
-  GetReallyIsBrowser(&isBrowser);
-  if (!isBrowser) {
+  if (!nsIMozBrowserFrame::GetReallyIsBrowserOrApp()) {
     return NS_OK;
   }
 
   // Check permission.
   nsIPrincipal *principal = NodePrincipal();
   nsCOMPtr<nsIPermissionManager> permMgr =
     do_GetService(NS_PERMISSIONMANAGER_CONTRACTID);
-  NS_ENSURE_STATE(permMgr);
+  NS_ENSURE_TRUE(permMgr, NS_OK);
 
   uint32_t permission = nsIPermissionManager::DENY_ACTION;
   nsresult rv = permMgr->TestPermissionFromPrincipal(principal,
                                                      "embed-apps",
                                                      &permission);
   NS_ENSURE_SUCCESS(rv, NS_OK);
   if (permission != nsIPermissionManager::ALLOW_ACTION) {
     return NS_OK;
@@ -347,21 +345,20 @@ nsGenericHTMLFrameElement::GetAppManifes
 
   nsAutoString manifestURL;
   GetAttr(kNameSpaceID_None, nsGkAtoms::mozapp, manifestURL);
   if (manifestURL.IsEmpty()) {
     return NS_OK;
   }
 
   nsCOMPtr<nsIAppsService> appsService = do_GetService(APPS_SERVICE_CONTRACTID);
-  NS_ENSURE_STATE(appsService);
+  NS_ENSURE_TRUE(appsService, NS_OK);
 
   nsCOMPtr<mozIDOMApplication> app;
   appsService->GetAppByManifestURL(manifestURL, getter_AddRefs(app));
-
   if (app) {
     aOut.Assign(manifestURL);
   }
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
--- a/docshell/base/nsDSURIContentListener.cpp
+++ b/docshell/base/nsDSURIContentListener.cpp
@@ -321,17 +321,17 @@ bool nsDSURIContentListener::CheckOneFra
 
     // Traverse up the parent chain and stop when we see a docshell whose
     // parent has a system principal, or a docshell corresponding to
     // <iframe mozbrowser>.
     while (NS_SUCCEEDED(curDocShellItem->GetParent(getter_AddRefs(parentDocShellItem))) &&
            parentDocShellItem) {
 
         nsCOMPtr<nsIDocShell> curDocShell = do_QueryInterface(curDocShellItem);
-        if (curDocShell && curDocShell->GetIsContentBoundary()) {
+        if (curDocShell && curDocShell->GetIsBrowserOrApp()) {
           break;
         }
 
         bool system = false;
         topDoc = do_GetInterface(parentDocShellItem);
         if (topDoc) {
             if (NS_SUCCEEDED(ssm->IsSystemPrincipal(topDoc->NodePrincipal(),
                                                     &system)) && system) {
--- a/docshell/base/nsDocShell.cpp
+++ b/docshell/base/nsDocShell.cpp
@@ -724,16 +724,17 @@ static uint64_t gDocshellIDCounter = 0;
 nsDocShell::nsDocShell():
     nsDocLoader(),
     mDefaultScrollbarPref(Scrollbar_Auto, Scrollbar_Auto),
     mTreeOwner(nullptr),
     mChromeEventHandler(nullptr),
     mCharsetReloadState(eCharsetReloadInit),
     mChildOffset(0),
     mBusyFlags(BUSY_FLAGS_NONE),
+    mFrameType(eFrameTypeRegular),
     mAppType(nsIDocShell::APP_TYPE_UNKNOWN),
     mLoadType(0),
     mMarginWidth(-1),
     mMarginHeight(-1),
     mItemType(typeContent),
     mPreviousTransIndex(-1),
     mLoadedTransIndex(-1),
     mSandboxFlags(0),
@@ -761,17 +762,17 @@ nsDocShell::nsDocShell():
     mURIResultedInDocument(false),
     mIsBeingDestroyed(false),
     mIsExecutingOnLoadHandler(false),
     mIsPrintingOrPP(false),
     mSavingOldViewer(false),
 #ifdef DEBUG
     mInEnsureScriptEnv(false),
 #endif
-    mAppId(nsIScriptSecurityManager::NO_APP_ID),
+    mOwnOrContainingAppId(nsIScriptSecurityManager::UNKNOWN_APP_ID),
     mParentCharsetSource(0)
 {
     mHistoryID = ++gDocshellIDCounter;
     if (gDocShellCount++ == 0) {
         NS_ASSERTION(sURIFixup == nullptr,
                      "Huh, sURIFixup not null in first nsDocShell ctor!");
 
         CallGetService(NS_URIFIXUP_CONTRACTID, &sURIFixup);
@@ -2130,29 +2131,29 @@ NS_IMETHODIMP nsDocShell::SetAllowWindow
     return NS_OK;
 }
 
 NS_IMETHODIMP
 nsDocShell::GetFullscreenAllowed(bool* aFullscreenAllowed)
 {
     NS_ENSURE_ARG_POINTER(aFullscreenAllowed);
 
-    // Content boundaries have their mFullscreenAllowed retrieved from their
+    // Browsers and apps have their mFullscreenAllowed retrieved from their
     // corresponding iframe in their parent upon creation.
     if (mFullscreenAllowed != CHECK_ATTRIBUTES) {
         *aFullscreenAllowed = (mFullscreenAllowed == PARENT_ALLOWS);
         return NS_OK;
     }
 
     // Assume false until we determine otherwise...
     *aFullscreenAllowed = false;
 
-    // For non-content boundaries, check that the enclosing iframe element
+    // For non-browsers/apps, check that the enclosing iframe element
     // has the allowfullscreen attribute set to true. If any ancestor
-    // iframe does not have allowfullscreen=true, then fullscreen is
+    // iframe does not have mozallowfullscreen=true, then fullscreen is
     // prohibited.
     nsCOMPtr<nsPIDOMWindow> win = do_GetInterface(GetAsSupports(this));
     if (!win) {
         return NS_OK;
     }
     nsCOMPtr<nsIContent> frameElement = do_QueryInterface(win->GetFrameElementInternal());
     if (frameElement &&
         frameElement->IsHTML(nsGkAtoms::iframe) &&
@@ -2179,17 +2180,17 @@ nsDocShell::GetFullscreenAllowed(bool* a
     NS_ENSURE_TRUE(parent, NS_OK);
     
     return parent->GetFullscreenAllowed(aFullscreenAllowed);
 }
 
 NS_IMETHODIMP
 nsDocShell::SetFullscreenAllowed(bool aFullscreenAllowed)
 {
-    if (!nsIDocShell::GetIsContentBoundary()) {
+    if (!nsIDocShell::GetIsBrowserOrApp()) {
         // Only allow setting of fullscreenAllowed on content/process boundaries.
         // At non-boundaries the fullscreenAllowed attribute is calculated based on
         // whether all enclosing frames have the "mozFullscreenAllowed" attribute
         // set to "true". fullscreenAllowed is set at the process boundaries to
         // propagate the value of the parent's "mozFullscreenAllowed" attribute
         // across process boundaries.
         return NS_ERROR_UNEXPECTED;
     }
@@ -2791,17 +2792,17 @@ nsDocShell::SetDocLoaderParent(nsDocLoad
 }
 
 NS_IMETHODIMP
 nsDocShell::GetSameTypeParent(nsIDocShellTreeItem ** aParent)
 {
     NS_ENSURE_ARG_POINTER(aParent);
     *aParent = nullptr;
 
-    if (mIsBrowserFrame) {
+    if (nsIDocShell::GetIsBrowserOrApp()) {
         return NS_OK;
     }
 
     nsCOMPtr<nsIDocShellTreeItem> parent =
         do_QueryInterface(GetAsSupports(mParent));
     if (!parent)
         return NS_OK;
 
@@ -2913,29 +2914,21 @@ nsDocShell::CanAccessItem(nsIDocShellTre
 
     nsCOMPtr<nsIDocShell> targetDS = do_QueryInterface(aTargetItem);
     nsCOMPtr<nsIDocShell> accessingDS = do_QueryInterface(aAccessingItem);
     if (!!targetDS != !!accessingDS) {
         // We must be able to convert both or neither to nsIDocShell.
         return false;
     }
 
-    if (targetDS && accessingDS) {
-        bool targetInBrowser = false, accessingInBrowser = false;
-        targetDS->GetIsInBrowserElement(&targetInBrowser);
-        accessingDS->GetIsInBrowserElement(&accessingInBrowser);
-
-        uint32_t targetAppId = 0, accessingAppId = 0;
-        targetDS->GetAppId(&targetAppId);
-        accessingDS->GetAppId(&accessingAppId);
-
-        if (targetInBrowser != accessingInBrowser ||
-            targetAppId != accessingAppId) {
-            return false;
-        }
+    if (targetDS && accessingDS &&
+        (targetDS->GetIsInBrowserElement() !=
+           accessingDS->GetIsInBrowserElement() ||
+         targetDS->GetAppId() != accessingDS->GetAppId())) {
+        return false;
     }
 
     nsCOMPtr<nsIDocShellTreeItem> accessingRoot;
     aAccessingItem->GetSameTypeRootTreeItem(getter_AddRefs(accessingRoot));
 
     if (aTargetItem == accessingRoot) {
         // A frame can navigate its root.
         return true;
@@ -5209,17 +5202,17 @@ nsDocShell::SetIsActive(bool aIsActive)
   // children; they handle their state separately.
   int32_t n = mChildList.Count();
   for (int32_t i = 0; i < n; ++i) {
       nsCOMPtr<nsIDocShell> docshell = do_QueryInterface(ChildAt(i));
       if (!docshell) {
           continue;
       }
 
-      if (!docshell->GetIsContentBoundary()) {
+      if (!docshell->GetIsBrowserOrApp()) {
           docshell->SetIsActive(aIsActive);
       }
   }
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
@@ -12285,146 +12278,116 @@ nsDocShell::GetCanExecuteScripts(bool *a
 #endif // DEBUG
       } while (treeItem && docshell);
   }
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
-nsDocShell::SetIsBrowserElement()
-{
-    if (mIsBrowserFrame) {
-        NS_ERROR("You should not call SetIsBrowserElement() more than once.");
-        return NS_OK;
-    }
-
-    mIsBrowserFrame = true;
-
-    nsCOMPtr<nsIObserverService> os = services::GetObserverService();
-    if (os) {
-        os->NotifyObservers(GetAsSupports(this),
-                            "docshell-marked-as-browser-frame", NULL);
+nsDocShell::SetIsApp(uint32_t aOwnAppId)
+{
+    mOwnOrContainingAppId = aOwnAppId;
+    if (aOwnAppId != nsIScriptSecurityManager::NO_APP_ID &&
+        aOwnAppId != nsIScriptSecurityManager::UNKNOWN_APP_ID) {
+        mFrameType = eFrameTypeApp;
+    } else {
+        mFrameType = eFrameTypeRegular;
+    }
+
+    return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocShell::SetIsBrowserInsideApp(uint32_t aContainingAppId)
+{
+    mOwnOrContainingAppId = aContainingAppId;
+    mFrameType = eFrameTypeBrowser;
+    return NS_OK;
+}
+
+/* [infallible] */ NS_IMETHODIMP
+nsDocShell::GetIsBrowserElement(bool* aIsBrowser)
+{
+    *aIsBrowser = (mFrameType == eFrameTypeBrowser);
+    return NS_OK;
+}
+
+/* [infallible] */ NS_IMETHODIMP
+nsDocShell::GetIsApp(bool* aIsApp)
+{
+    *aIsApp = (mFrameType == eFrameTypeApp);
+    return NS_OK;
+}
+
+/* [infallible] */ NS_IMETHODIMP
+nsDocShell::GetIsBrowserOrApp(bool* aIsBrowserOrApp)
+{
+    switch (mFrameType) {
+        case eFrameTypeRegular:
+            *aIsBrowserOrApp = false;
+            break;
+        case eFrameTypeBrowser:
+        case eFrameTypeApp:
+            *aIsBrowserOrApp = true;
+            break;
     }
 
     return NS_OK;
 }
 
 nsDocShell::FrameType
 nsDocShell::GetInheritedFrameType()
 {
-    FrameType type = GetFrameType();
-
-    if (type != eFrameTypeRegular) {
-        return type;
+    if (mFrameType != eFrameTypeRegular) {
+        return mFrameType;
     }
 
     nsCOMPtr<nsIDocShellTreeItem> parentAsItem;
     GetSameTypeParent(getter_AddRefs(parentAsItem));
 
     nsCOMPtr<nsIDocShell> parent = do_QueryInterface(parentAsItem);
     if (!parent) {
         return eFrameTypeRegular;
     }
 
     return static_cast<nsDocShell*>(parent.get())->GetInheritedFrameType();
 }
 
-nsDocShell::FrameType
-nsDocShell::GetFrameType()
-{
-    if (mAppId != nsIScriptSecurityManager::NO_APP_ID) {
-        return eFrameTypeApp;
-    }
-
-    return mIsBrowserFrame ? eFrameTypeBrowser : eFrameTypeRegular;
-}
-
-/* [infallible] */ NS_IMETHODIMP
-nsDocShell::GetIsBrowserElement(bool* aIsBrowser)
-{
-    *aIsBrowser = (GetFrameType() == eFrameTypeBrowser);
-    return NS_OK;
-}
-
-/* [infallible] */ NS_IMETHODIMP
-nsDocShell::GetIsApp(bool* aIsApp)
-{
-    *aIsApp = (GetFrameType() == eFrameTypeApp);
-    return NS_OK;
-}
-
-/* [infallible] */ NS_IMETHODIMP
-nsDocShell::GetIsContentBoundary(bool* aIsContentBoundary)
-{
-    switch (GetFrameType()) {
-        case eFrameTypeRegular:
-            *aIsContentBoundary = false;
-            break;
-        case eFrameTypeBrowser:
-        case eFrameTypeApp:
-            *aIsContentBoundary = true;
-            break;
-    }
-
-    return NS_OK;
-}
-
 /* [infallible] */ NS_IMETHODIMP
 nsDocShell::GetIsInBrowserElement(bool* aIsInBrowserElement)
 {
     *aIsInBrowserElement = (GetInheritedFrameType() == eFrameTypeBrowser);
     return NS_OK;
 }
 
 /* [infallible] */ NS_IMETHODIMP
-nsDocShell::GetIsInApp(bool* aIsInApp)
-{
-    *aIsInApp = (GetInheritedFrameType() == eFrameTypeApp);
-    return NS_OK;
-}
-
-/* [infallible] */ NS_IMETHODIMP
-nsDocShell::GetIsBelowContentBoundary(bool* aIsInContentBoundary)
+nsDocShell::GetIsInBrowserOrApp(bool* aIsInBrowserOrApp)
 {
     switch (GetInheritedFrameType()) {
         case eFrameTypeRegular:
-            *aIsInContentBoundary = false;
+            *aIsInBrowserOrApp = false;
             break;
         case eFrameTypeBrowser:
         case eFrameTypeApp:
-            *aIsInContentBoundary = true;
+            *aIsInBrowserOrApp = true;
             break;
     }
 
     return NS_OK;
 }
 
-NS_IMETHODIMP
-nsDocShell::SetAppId(uint32_t aAppId)
-{
-    MOZ_ASSERT(mAppId == nsIScriptSecurityManager::NO_APP_ID);
-    MOZ_ASSERT(aAppId != nsIScriptSecurityManager::UNKNOWN_APP_ID);
-
-    mAppId = aAppId;
-    return NS_OK;
-}
-
 /* [infallible] */ NS_IMETHODIMP
 nsDocShell::GetAppId(uint32_t* aAppId)
 {
-    if (mAppId != nsIScriptSecurityManager::NO_APP_ID) {
-        MOZ_ASSERT(GetFrameType() == eFrameTypeApp);
-
-        *aAppId = mAppId;
+    if (mOwnOrContainingAppId != nsIScriptSecurityManager::UNKNOWN_APP_ID) {
+        *aAppId = mOwnOrContainingAppId;
         return NS_OK;
     }
 
-    MOZ_ASSERT(GetFrameType() != eFrameTypeApp);
-
     nsCOMPtr<nsIDocShell> parent;
     GetSameTypeParentIgnoreBrowserAndAppBoundaries(getter_AddRefs(parent));
 
     if (!parent) {
         *aAppId = nsIScriptSecurityManager::NO_APP_ID;
         return NS_OK;
     }
 
--- a/docshell/base/nsDocShell.h
+++ b/docshell/base/nsDocShell.h
@@ -659,23 +659,22 @@ protected:
         void Revoke() { mDocShell = nullptr; }
     private:
         nsRefPtr<nsDocShell> mDocShell;
     };
 
     bool JustStartedNetworkLoad();
 
     enum FrameType {
-        eFrameTypeRegular  = 0x0, // 0000
-        eFrameTypeBrowser  = 0x1, // 0001
-        eFrameTypeApp      = 0x2  // 0010
+        eFrameTypeRegular,
+        eFrameTypeBrowser,
+        eFrameTypeApp
     };
 
     FrameType GetInheritedFrameType();
-    FrameType GetFrameType();
 
     // hash of session storages, keyed by domain
     nsInterfaceHashtable<nsCStringHashKey, nsIDOMStorage> mStorages;
 
     // Dimensions of the docshell
     nsIntRect                  mBounds;
     nsString                   mName;
     nsString                   mTitle;
@@ -802,17 +801,16 @@ 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
@@ -839,17 +837,28 @@ protected:
     bool                       mInEnsureScriptEnv;
 #endif
     uint64_t                   mHistoryID;
 
     static nsIURIFixup *sURIFixup;
 
     nsRefPtr<nsDOMNavigationTiming> mTiming;
 
-    uint32_t mAppId;
+    // Are we a regular frame, a browser frame, or an app frame?
+    FrameType mFrameType;
+
+    // We only expect mOwnOrContainingAppId to be something other than
+    // UNKNOWN_APP_ID if mFrameType != eFrameTypeRegular.  For vanilla iframes
+    // inside an app, we'll retrieve the containing app-id by walking up the
+    // docshell hierarchy.
+    //
+    // (This needs to be the docshell's own /or containing/ app id because the
+    // containing app frame might be in another process, in which case we won't
+    // find it by walking up the docshell hierarchy.)
+    uint32_t mOwnOrContainingAppId;
 
 private:
     nsCOMPtr<nsIAtom> mForcedCharset;
     nsCOMPtr<nsIAtom> mParentCharset;
     nsTObserverArray<nsWeakPtr> mPrivacyObservers;
     int32_t           mParentCharsetSource;
     nsCString         mOriginalUriString;
 
--- a/docshell/base/nsIDocShell.idl
+++ b/docshell/base/nsIDocShell.idl
@@ -34,17 +34,17 @@ interface nsISHEntry;
 interface nsILayoutHistoryState;
 interface nsISecureBrowserUI;
 interface nsIDOMStorage;
 interface nsIPrincipal;
 interface nsIWebBrowserPrint;
 interface nsIVariant;
 interface nsIPrivacyTransitionObserver;
 
-[scriptable, builtinclass, uuid(0132C0BE-ACB5-4D61-9B19-01C005E030DA)]
+[scriptable, builtinclass, uuid(318CE516-3F7A-41F6-8F3D-3661650F7A46)]
 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.
    *
@@ -574,82 +574,98 @@ interface nsIDocShell : nsISupports
 
   /**
    * Add an observer to the list of parties to be notified when this docshell's
    * private browsing status is changed. |obs| must support weak references.
    */
   void addWeakPrivacyTransitionObserver(in nsIPrivacyTransitionObserver obs);
 
   /**
-   * Mark the docshell as a browser frame.
-   * This should be used for <iframe mozbrowser> but not for <iframe mozapp>.
-   *
-   * This method should not be called more than once.
-   */
-  void setIsBrowserElement();
-
-  /**
-   * Returns true iff the docshell is marked as a browser frame.
+   * Returns true if this docshell corresponds to an <iframe mozbrowser>.
+   * (<iframe mozapp mozbrowser> is not considered a browser.)
    */
   [infallible] readonly attribute boolean isBrowserElement;
 
   /**
-   * Returns true iif the docshell is marked as an app frame.
+   * Returns true iff the docshell corresponds to an <iframe mozapp>.
    */
   [infallible] readonly attribute boolean isApp;
 
   /**
-   * Returns true iif the docshell is marked as a type that behaves like a
-   * content boundary.
+   * Returns isBrowserElement || isApp.
    */
-  [infallible] readonly attribute boolean isContentBoundary;
+  [infallible] readonly attribute boolean isBrowserOrApp;
 
   /**
-   * Returns true iif the docshell is inside a browser element.
+   * Returns true if this docshell corresponds to an <iframe mozbrowser> or if
+   * the docshell is contained in an <iframe mozbrowser>.  (<iframe mozapp
+   * mozbrowser> does not count as a browser.)
+   *
+   * Our notion here of "contained in" means: Walk up the docshell hierarchy in
+   * this process until we hit an <iframe mozapp> or <iframe mozbrowser> (or
+   * until the hierarchy ends).  Return true iff the docshell we stopped on has
+   * isBrowserElement == true.
    */
   [infallible] readonly attribute boolean isInBrowserElement;
 
   /**
-   * Returns true iif the docshell is inside an application.  However, it will
-   * return false if the docshell is inside a browser element that is inside
-   * an application.
-   *
-   * Note: Do not use this method for permissions checks!  An app may contain
-   * an <iframe> pointing at arbitrary web code.  This iframe's docshell will
-   * have isInApp() == true, but the iframe's content is not "app code", and
-   * so should not be granted more trust than vanilla web content.
+   * Returns true if this docshell corresponds to an <iframe mozbrowser> or
+   * <iframe mozap>, or if this docshell is contained in an <iframe mozbrowser>
+   * or <iframe mozapp>.
    *
-   * (For example, suppose when web content calls API method X, we show a
-   * permission prompt, but when "app code" calls method X, we don't.  In this
-   * case, it would be /incorrect/ to show the permission prompt if
-   * !isInApp().)
-   *
-   * If you're doing a security check, use the content's principal instead of
-   * this method.
+   * To compute this value, we walk up the docshell hierarchy.  If we encounter
+   * a docshell with isBrowserElement or isApp before we hit the end of the
+   * hierarchy, we return true.  Otherwise, we return false.
    */
-  [infallible] readonly attribute boolean isInApp;
+  [infallible] readonly attribute boolean isInBrowserOrApp;
+
+   /**
+    * Indicate that this docshell corresponds to an app with the given app id.
+    *
+    * You may pass NO_APP_ID or UNKNOWN_APP_ID for containingAppId.  If you
+    * pass NO_APP_ID, then this docshell will return NO_APP_ID for appId.  If
+    * you pass UNKNOWN_APP_ID, then this docshell will search its hiearchy for
+    * an app frame and use that frame's appId.
+    *
+    * You can call this method more than once, but there's no guarantee that
+    * other components will update their view of the world if you change a
+    * docshell's app id, so tread lightly.
+    *
+    * If you call this method after calling setIsBrowserInsideApp, this
+    * docshell will forget the fact that it was a browser.
+    */
+   void setIsApp(in unsigned long ownAppId);
+
+   /**
+    * Indicate that this docshell corresponds to a browser inside an app with
+    * the given ID.  As with setIsApp, you may pass NO_APP_ID or
+    * UNKNOWN_APP_ID.
+    *
+    * As with setIsApp, you may call this more than once, but it's kind of a
+    * hack, so be careful.
+    */
+   void setIsBrowserInsideApp(in unsigned long containingAppId);
 
   /**
-   * Returns if the docshell has a docshell that behaves as a content boundary
-   * in his parent hierarchy.
+   * Returns the id of the app associated with this docshell.  If this docshell
+   * is an <iframe mozbrowser> inside an <iframe mozapp>, we return the app's
+   * appId.
+   *
+   * We compute this value by walking up the docshell hierarchy until we find a
+   * docshell on which setIsApp(x) or setIsBrowserInsideApp(x) was called
+   * (ignoring those docshells where x == UNKNOWN_APP_ID).  We return the app
+   * id x.
+   *
+   * If we don't find a docshell with an associated app id in our hierarchy, we
+   * return NO_APP_ID.  We never return UNKNOWN_APP_ID.
+   *
+   * Notice that a docshell may have an associated app even if it returns true
+   * for isBrowserElement!
    */
-  [infallible] readonly attribute boolean isBelowContentBoundary;
-
-  /**
-   * Set the app id this docshell is associated with. The id has to be a valid
-   * app id. If the docshell isn't associated with any app, the value should be
-   * nsIScriptSecurityManager::NO_APP_ID. However, this is the default value if
-   * nothing is et.
-   *
-   * This method is [noscript] to reduce the scope. It should be used at very
-   * specific moments.
-   *
-   * Calling setAppId() will mark the frame as an app frame.
-   */
-  [noscript] void setAppId(in unsigned long appId);
+  [infallible] readonly attribute unsigned long appId;
 
   /**
    * Like nsIDocShellTreeItem::GetSameTypeParent, except this ignores <iframe
    * mozbrowser> and <iframe mozapp> boundaries.
    */
   nsIDocShell getSameTypeParentIgnoreBrowserAndAppBoundaries();
 
   /** 
--- a/dom/base/nsGlobalWindow.cpp
+++ b/dom/base/nsGlobalWindow.cpp
@@ -3003,17 +3003,17 @@ nsGlobalWindow::GetScriptableParent(nsID
 {
   FORWARD_TO_OUTER(GetScriptableParent, (aParent), NS_ERROR_NOT_INITIALIZED);
 
   *aParent = NULL;
   if (!mDocShell) {
     return NS_OK;
   }
 
-  if (mDocShell->GetIsContentBoundary()) {
+  if (mDocShell->GetIsBrowserOrApp()) {
     nsCOMPtr<nsIDOMWindow> parent = static_cast<nsIDOMWindow*>(this);
     parent.swap(*aParent);
     return NS_OK;
   }
 
   return GetRealParent(aParent);
 }
 
@@ -3108,19 +3108,19 @@ nsGlobalWindow::GetTopImpl(nsIDOMWindow*
 }
 
 NS_IMETHODIMP
 nsGlobalWindow::GetContent(nsIDOMWindow** aContent)
 {
   FORWARD_TO_OUTER(GetContent, (aContent), NS_ERROR_NOT_INITIALIZED);
   *aContent = nullptr;
 
-  // If we're contained in <iframe mozbrowser>, then GetContent is the same as
-  // window.top.
-  if (mDocShell && mDocShell->GetIsBelowContentBoundary()) {
+  // If we're contained in <iframe mozbrowser> or <iframe mozapp>, then
+  // GetContent is the same as window.top.
+  if (mDocShell && mDocShell->GetIsInBrowserOrApp()) {
     return GetScriptableTop(aContent);
   }
 
   nsCOMPtr<nsIDocShellTreeItem> primaryContent;
   if (!nsContentUtils::IsCallerChrome()) {
     // If we're called by non-chrome code, make sure we don't return
     // the primary content window if the calling tab is hidden. In
     // such a case we return the same-type root in the hidden tab,
@@ -6556,17 +6556,17 @@ nsGlobalWindow::CanClose()
 }
 
 NS_IMETHODIMP
 nsGlobalWindow::Close()
 {
   FORWARD_TO_OUTER(Close, (), NS_ERROR_NOT_INITIALIZED);
 
   if (!mDocShell || IsInModalState() ||
-      (IsFrame() && !mDocShell->GetIsContentBoundary())) {
+      (IsFrame() && !mDocShell->GetIsBrowserOrApp())) {
     // window.close() is called on a frame in a frameset, on a window
     // that's already closed, or on a window for which there's
     // currently a modal dialog open. Ignore such calls.
 
     return NS_OK;
   }
 
   if (mHavePendingClose) {
@@ -7075,26 +7075,27 @@ nsGlobalWindow::CacheXBLPrototypeHandler
   mCachedXBLPrototypeHandlers.Put(aKey, aHandler.get());
 }
 
 /**
  * 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).
+ * window contained by an <iframe mozbrowser> or <iframe mozapp> 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 || mDocShell->GetIsContentBoundary()) {
+  if (!mDocShell || mDocShell->GetIsBrowserOrApp()) {
     return NS_OK;
   }
 
   return GetFrameElement(aFrameElement);
 }
 
 /**
  * nsIGlobalWindow::GetFrameElement (when called from C++) is just a wrapper
--- a/dom/interfaces/html/nsIMozBrowserFrame.idl
+++ b/dom/interfaces/html/nsIMozBrowserFrame.idl
@@ -4,40 +4,42 @@
 /* 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"
 
 interface nsITabParent;
 
-[scriptable, uuid(6f043e42-02c9-4e8f-8f8d-1b83c6102827)]
+[scriptable, builtinclass, uuid(929AED00-3E15-49B7-8CA2-75003715B7E7)]
 interface nsIMozBrowserFrame : nsIDOMMozBrowserFrame
 {
   /**
    * Gets whether this frame really is a browser or app 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;
+  [infallible] readonly attribute boolean reallyIsBrowserOrApp;
 
   /**
    * Gets whether this frame really is an app frame.
    *
    * In order to really be an app frame, this frame must really be a browser
    * frame (this requirement will go away eventually), and the frame's mozapp
    * attribute must point to the manifest of a valid app.
    */
-  readonly attribute boolean reallyIsApp;
+  [infallible] readonly attribute boolean reallyIsApp;
 
   /**
    * Gets this frame's app manifest URL, if the frame really is an app frame.
    * Otherwise, returns the empty string.
+   *
+   * This method is guaranteed not to fail.
    */
   readonly attribute AString appManifestURL;
 
   /**
    * Normally, a frame tries to create its frame loader when its src is
    * modified, or its contentWindow is accessed.
    *
    * disallowCreateFrameLoader prevents the frame element from creating its
--- a/dom/ipc/AppProcessPermissions.cpp
+++ b/dom/ipc/AppProcessPermissions.cpp
@@ -22,17 +22,17 @@ bool
 AssertAppProcessPermission(PBrowserParent* aActor, const char* aPermission)
 {
   if (!aActor) {
     NS_WARNING("Testing permissions for null actor");
     return false;
   }
 
   TabParent* tab = static_cast<TabParent*>(aActor);
-  nsCOMPtr<mozIApplication> app = tab->GetApp();
+  nsCOMPtr<mozIApplication> app = tab->GetOwnOrContainingApp();
   bool hasPermission = false;
 
   // isBrowser frames inherit their app descriptor to identify their
   // data storage, but they don't inherit the permissions associated
   // with that descriptor.
   if (app && !tab->IsBrowserElement()) {
     if (!NS_SUCCEEDED(app->HasPermission(aPermission, &hasPermission))) {
       hasPermission = false;
--- a/dom/ipc/ContentChild.cpp
+++ b/dom/ipc/ContentChild.cpp
@@ -482,26 +482,30 @@ ContentChild::AllocPImageBridge(mozilla:
 
 static void FirstIdle(void)
 {
     ContentChild::GetSingleton()->SendFirstIdle();
 }
 
 PBrowserChild*
 ContentChild::AllocPBrowser(const uint32_t& aChromeFlags,
-                            const bool& aIsBrowserElement, const AppId& aApp)
+                            const AppToken& aOwnOrContainingAppToken,
+                            const bool& aIsBrowserElement)
 {
     static bool firstIdleTaskPosted = false;
     if (!firstIdleTaskPosted) {
         MessageLoop::current()->PostIdleTask(FROM_HERE, NewRunnableFunction(FirstIdle));
         firstIdleTaskPosted = true;
     }
 
-    nsRefPtr<TabChild> child =
-        TabChild::Create(aChromeFlags, aIsBrowserElement, aApp.get_uint32_t());
+    nsRefPtr<TabChild> child = TabChild::Create(
+        aChromeFlags,
+        aOwnOrContainingAppToken.get_uint32_t(),
+        aIsBrowserElement);
+
     // The ref here is released below.
     return child.forget().get();
 }
 
 bool
 ContentChild::DeallocPBrowser(PBrowserChild* iframe)
 {
     TabChild* child = static_cast<TabChild*>(iframe);
--- a/dom/ipc/ContentChild.h
+++ b/dom/ipc/ContentChild.h
@@ -74,18 +74,18 @@ public:
     PCompositorChild*
     AllocPCompositor(mozilla::ipc::Transport* aTransport,
                      base::ProcessId aOtherProcess) MOZ_OVERRIDE;
     PImageBridgeChild*
     AllocPImageBridge(mozilla::ipc::Transport* aTransport,
                       base::ProcessId aOtherProcess) MOZ_OVERRIDE;
 
     virtual PBrowserChild* AllocPBrowser(const uint32_t& aChromeFlags,
-                                         const bool& aIsBrowserElement,
-                                         const AppId& aAppId);
+                                         const AppToken& aOwnOrContainingAppToken,
+                                         const bool& aIsBrowserElement);
     virtual bool DeallocPBrowser(PBrowserChild*);
 
     virtual PDeviceStorageRequestChild* AllocPDeviceStorageRequest(const DeviceStorageParams&);
     virtual bool DeallocPDeviceStorageRequest(PDeviceStorageRequestChild*);
 
     virtual PBlobChild* AllocPBlob(const BlobConstructorParams& aParams);
     virtual bool DeallocPBlob(PBlobChild*);
 
--- a/dom/ipc/ContentParent.cpp
+++ b/dom/ipc/ContentParent.cpp
@@ -49,17 +49,16 @@
 #include "nsConsoleMessage.h"
 #include "nsDebugImpl.h"
 #include "nsDirectoryServiceDefs.h"
 #include "nsDOMFile.h"
 #include "nsExternalHelperAppService.h"
 #include "nsFrameMessageManager.h"
 #include "nsHashPropertyBag.h"
 #include "nsIAlertsService.h"
-#include "nsIAppsService.h"
 #include "nsIClipboard.h"
 #include "nsIConsoleService.h"
 #include "nsIDOMApplicationRegistry.h"
 #include "nsIDOMGeoGeolocation.h"
 #include "nsIDOMWindow.h"
 #include "nsIFilePicker.h"
 #include "nsIMemoryReporter.h"
 #include "nsIObserverService.h"
@@ -313,90 +312,85 @@ AppNeedsInheritedOSPrivileges(mozIApplic
         } else if (needsInherit) {
             return true;
         }
     }
     return false;
 }
 
 /*static*/ TabParent*
-ContentParent::CreateBrowser(mozIApplication* aApp, bool aIsBrowserElement)
+ContentParent::CreateBrowserOrApp(mozIApplication* aOwnOrContainingApp,
+                                  bool aIsBrowserElement)
 {
-    // We currently don't set the <app> ancestor for <browser> content
-    // correctly.  This assertion is to notify the person who fixes
-    // this code that they need to reevaluate places here where we may
-    // make bad assumptions based on that bug.
-    MOZ_ASSERT(!aApp || !aIsBrowserElement);
+    uint32_t ownOrContainingAppId = nsIScriptSecurityManager::NO_APP_ID;
+    if (aOwnOrContainingApp) {
+        NS_ENSURE_SUCCESS(aOwnOrContainingApp->GetLocalId(&ownOrContainingAppId),
+                          nullptr);
+    }
 
-    if (!aApp) {
+    if (aIsBrowserElement || !aOwnOrContainingApp) {
         if (ContentParent* cp = GetNewOrUsed(aIsBrowserElement)) {
-            nsRefPtr<TabParent> tp(new TabParent(aApp, aIsBrowserElement));
+            nsRefPtr<TabParent> tp(new TabParent(aOwnOrContainingApp, aIsBrowserElement));
             return static_cast<TabParent*>(
                 cp->SendPBrowserConstructor(
                     // DeallocPBrowserParent() releases the ref we take here
                     tp.forget().get(),
                     /*chromeFlags*/0,
-                    aIsBrowserElement, nsIScriptSecurityManager::NO_APP_ID));
+                    ownOrContainingAppId, aIsBrowserElement));
         }
         return nullptr;
     }
 
+    // If we got here, we have an app and we're not a browser element.  In this
+    // case, we assume the app is our own app, not a containing one.  That is,
+    // if you're a remote iframe inside an app, you must either be a browser or
+    // a new app; you can't be a non-browser non-app iframe.
+    nsCOMPtr<mozIApplication> ownApp = aOwnOrContainingApp;
+    uint32_t ownAppId = ownOrContainingAppId;
+
     if (!gAppContentParents) {
         gAppContentParents =
             new nsDataHashtable<nsStringHashKey, ContentParent*>();
         gAppContentParents->Init();
     }
 
     // Each app gets its own ContentParent instance.
     nsAutoString manifestURL;
-    if (NS_FAILED(aApp->GetManifestURL(manifestURL))) {
+    if (NS_FAILED(ownApp->GetManifestURL(manifestURL))) {
         NS_ERROR("Failed to get manifest URL");
         return nullptr;
     }
 
-    nsCOMPtr<nsIAppsService> appsService = do_GetService(APPS_SERVICE_CONTRACTID);
-    if (!appsService) {
-        NS_ERROR("Failed to get apps service");
-        return nullptr;
-    }
-
-    // Send the local app ID to the new TabChild so it knows what app
-    // it is.
-    uint32_t appId;
-    if (NS_FAILED(appsService->GetAppLocalIdByManifestURL(manifestURL, &appId))) {
-        NS_ERROR("Failed to get local app ID");
-        return nullptr;
-    }
-
     nsRefPtr<ContentParent> p = gAppContentParents->Get(manifestURL);
     if (!p) {
-        if (AppNeedsInheritedOSPrivileges(aApp)) {
-            p = new ContentParent(manifestURL, aIsBrowserElement,
+        if (AppNeedsInheritedOSPrivileges(ownApp)) {
+            p = new ContentParent(manifestURL, /* isBrowserElement = */ false,
                                   base::PRIVILEGES_INHERIT);
             p->Init();
         } else {
             p = MaybeTakePreallocatedAppProcess();
             if (p) {
                 p->SetManifestFromPreallocated(manifestURL);
             } else {
                 NS_WARNING("Unable to use pre-allocated app process");
-                p = new ContentParent(manifestURL, aIsBrowserElement,
+                p = new ContentParent(manifestURL, /* isBrowserElement = */ false,
                                       base::PRIVILEGES_DEFAULT);
                 p->Init();
             }
         }
         gAppContentParents->Put(manifestURL, p);
     }
 
-    nsRefPtr<TabParent> tp(new TabParent(aApp, aIsBrowserElement));
+    nsRefPtr<TabParent> tp(new TabParent(ownApp, /* isBrowserElement = */ false));
     return static_cast<TabParent*>(
         // DeallocPBrowserParent() releases the ref we take here
         p->SendPBrowserConstructor(tp.forget().get(),
-                                   /*chromeFlags*/0,
-                                   aIsBrowserElement, appId));
+                                   /* chromeFlags = */ 0,
+                                   ownAppId,
+                                   /* isBrowserElement = */ false));
 }
 
 static PLDHashOperator
 AppendToTArray(const nsAString& aKey, ContentParent* aValue, void* aArray)
 {
     nsTArray<ContentParent*> *array =
         static_cast<nsTArray<ContentParent*>*>(aArray);
     array->AppendElement(aValue);
@@ -1145,39 +1139,42 @@ ContentParent::RecvGetProcessAttributes(
         (mAppManifestURL == MAGIC_PREALLOCATED_APP_MANIFEST_URL);
     *aIsForApp = IsForApp();
     *aIsForBrowser = mIsForBrowser;
     return true;
 }
 
 PBrowserParent*
 ContentParent::AllocPBrowser(const uint32_t& aChromeFlags,
-                             const bool& aIsBrowserElement, const AppId& aApp)
+                             const AppToken& aOwnOrContainingAppToken,
+                             const bool& aIsBrowserElement)
 {
     // We only use this Alloc() method when the content processes asks
     // us to open a window.  In that case, we're expecting to see the
     // opening PBrowser as its app descriptor, and we can trust the data
     // associated with that PBrowser since it's fully owned by this
     // process.
-    if (AppId::TPBrowserParent != aApp.type()) {
+    if (AppToken::TPBrowserParent != aOwnOrContainingAppToken.type()) {
         NS_ERROR("Content process attempting to forge app ID");
         return nullptr;
     }
-    TabParent* opener = static_cast<TabParent*>(aApp.get_PBrowserParent());
+    TabParent* opener = static_cast<TabParent*>(
+      aOwnOrContainingAppToken.get_PBrowserParent());
 
     // Popup windows of isBrowser frames are isBrowser if the parent
     // isBrowser.  Allocating a !isBrowser frame with same app ID
     // would allow the content to access data it's not supposed to.
     if (opener && opener->IsBrowserElement() && !aIsBrowserElement) {
         NS_ERROR("Content process attempting to escalate data access privileges");
         return nullptr;
     }
 
-    TabParent* parent = new TabParent(opener ? opener->GetApp() : nullptr,
-                                      aIsBrowserElement);
+    nsCOMPtr<mozIApplication> app = opener ? opener->GetOwnOrContainingApp() : nullptr;
+    TabParent* parent = new TabParent(app, aIsBrowserElement);
+
     // We release this ref in DeallocPBrowser()
     NS_ADDREF(parent);
     return parent;
 }
 
 bool
 ContentParent::DeallocPBrowser(PBrowserParent* frame)
 {
--- a/dom/ipc/ContentParent.h
+++ b/dom/ipc/ContentParent.h
@@ -68,25 +68,25 @@ public:
      */
     static void StartUp();
     /** Shut down the content-process machinery. */
     static void ShutDown();
 
     static ContentParent* GetNewOrUsed(bool aForBrowserElement = false);
 
     /**
-     * Get or create a content process for the given app descriptor,
-     * which may be null.  This function will assign processes to app
-     * or non-app browsers by internal heuristics.
+     * Get or create a content process for the given (app, is-browser)
+     * descriptor.
      *
-     * Currently apps are given their own process, and browser tabs
-     * share processes.
+     * The app here is inherited -- that is, <iframe mozbrowser> inside <iframe
+     * mozapp> must pass a non-null app.  aIsBrowserElement should be true
+     * only for <iframe mozbrowser> frames that are not app frames.
      */
-    static TabParent* CreateBrowser(mozIApplication* aApp,
-                                    bool aIsBrowserFrame);
+    static TabParent* CreateBrowserOrApp(mozIApplication* aOwnOrContainingApp,
+                                         bool aIsBrowserElement);
 
     static void GetAll(nsTArray<ContentParent*>& aArray);
 
     NS_DECL_ISUPPORTS
     NS_DECL_NSIOBSERVER
     NS_DECL_NSITHREADOBSERVER
     NS_DECL_NSIDOMGEOPOSITIONCALLBACK
 
@@ -183,18 +183,18 @@ private:
                       base::ProcessId aOtherProcess) MOZ_OVERRIDE;
 
     virtual bool RecvGetProcessAttributes(uint64_t* aId,
                                           bool* aStartBackground,
                                           bool* aIsForApp,
                                           bool* aIsForBrowser) MOZ_OVERRIDE;
 
     virtual PBrowserParent* AllocPBrowser(const uint32_t& aChromeFlags,
-                                          const bool& aIsBrowserElement,
-                                          const AppId& aApp);
+                                          const AppToken& aOwnOrContainingAppToken,
+                                          const bool& aIsBrowserElement);
     virtual bool DeallocPBrowser(PBrowserParent* frame);
 
     virtual PDeviceStorageRequestParent* AllocPDeviceStorageRequest(const DeviceStorageParams&);
     virtual bool DeallocPDeviceStorageRequest(PDeviceStorageRequestParent*);
 
     virtual PBlobParent* AllocPBlob(const BlobConstructorParams& aParams);
     virtual bool DeallocPBlob(PBlobParent*);
 
--- a/dom/ipc/PContent.ipdl
+++ b/dom/ipc/PContent.ipdl
@@ -130,17 +130,19 @@ struct MysteryBlobConstructorParams
 union BlobConstructorParams
 {
   NormalBlobConstructorParams;
   FileBlobConstructorParams;
   SlicedBlobConstructorParams;
   MysteryBlobConstructorParams;
 };
 
-union AppId {
+// This struct identifies an app, by providing either a uint32_t (an appID) or a
+// pointer to a PBrowser (whose appID we use to identify the app).
+union AppToken {
   uint32_t;
   nullable PBrowser;
 };
 
 union PrefValue {
   nsCString;
   int32_t;
   bool;
@@ -180,23 +182,23 @@ rpc protocol PContent
 both:
     // Depending on exactly how the new browser is being created, it might be
     // created from either the child or parent process!
     //
     // The child creates the PBrowser as part of
     // TabChild::BrowserFrameProvideWindow, and the parent creates the
     // PBrowser as part of ContentParent::CreateTab.
     //
-    // When the parent constructs a PBrowser, the app ID handed to the
-    // child side is trusted.  In that case, |appId| is uint32_t.
-    // However, when the child side constructs a PBrowser, for
-    // window.open(), the parent must validate the app ID used on the
-    // parent side.  To do so, the child process must pass a valid
-    // PBrowser as its |AppId|.
-    async PBrowser(uint32_t chromeFlags, bool isBrowserElement, AppId appId);
+    // When the parent constructs a PBrowser, the app token handed to the child
+    // side is trusted.  In that case, |ownOrContainingApp| is a uint32_t.
+    // However, when the child side constructs a PBrowser, for window.open(),
+    // the parent must validate the app ID used on the parent side.  To do so,
+    // the child process must pass a valid PBrowser as its |ownOrContainingApp|.
+    async PBrowser(uint32_t chromeFlags, AppToken ownOrContainingApp,
+                   bool isBrowserFrame);
 
     async PBlob(BlobConstructorParams params);
 
 child:
     PMemoryReportRequest();
 
     /**
      * Dump the contents of about:memory to a file in our temp directory.
--- a/dom/ipc/TabChild.cpp
+++ b/dom/ipc/TabChild.cpp
@@ -129,50 +129,52 @@ TabChild::PreloadSlowThings()
     tab->TryCacheLoadAndCompileScript(BROWSER_ELEMENT_CHILD_SCRIPT);
 
     sPreallocatedTab = tab;
     ClearOnShutdown(&sPreallocatedTab);
 }
 
 /*static*/ already_AddRefed<TabChild>
 TabChild::Create(uint32_t aChromeFlags,
-                 bool aIsBrowserElement, uint32_t aAppId)
+                 uint32_t aOwnOrContainingAppId,
+                 bool aIsBrowserElement)
 {
     if (sPreallocatedTab &&
         sPreallocatedTab->mChromeFlags == aChromeFlags &&
-        (aIsBrowserElement || 
-         aAppId != nsIScriptSecurityManager::NO_APP_ID)) {
+        (aIsBrowserElement ||
+         aOwnOrContainingAppId != nsIScriptSecurityManager::NO_APP_ID)) {
         nsRefPtr<TabChild> child = sPreallocatedTab.get();
         sPreallocatedTab = nullptr;
 
         MOZ_ASSERT(!child->mTriedBrowserInit);
 
-        child->SetAppBrowserConfig(aIsBrowserElement, aAppId);
+        child->SetAppBrowserConfig(aOwnOrContainingAppId, aIsBrowserElement);
 
         return child.forget();
     }
 
-    nsRefPtr<TabChild> iframe = new TabChild(aChromeFlags, aIsBrowserElement,
-                                             aAppId);
+    nsRefPtr<TabChild> iframe =
+        new TabChild(aChromeFlags, aOwnOrContainingAppId, aIsBrowserElement);
     return NS_SUCCEEDED(iframe->Init()) ? iframe.forget() : nullptr;
 }
 
 
-TabChild::TabChild(uint32_t aChromeFlags, bool aIsBrowserElement,
-                   uint32_t aAppId)
+TabChild::TabChild(uint32_t aChromeFlags,
+                   uint32_t aOwnOrContainingAppId,
+                   bool aIsBrowserElement)
   : mRemoteFrame(nullptr)
   , mTabChildGlobal(nullptr)
   , mChromeFlags(aChromeFlags)
   , mOuterRect(0, 0, 0, 0)
   , mInnerSize(0, 0)
   , mOldViewportWidth(0.0f)
   , mLastBackgroundColor(NS_RGB(255, 255, 255))
-  , mAppId(aAppId)
+  , mOwnOrContainingAppId(aOwnOrContainingAppId)
+  , mIsBrowserElement(aIsBrowserElement)
   , mDidFakeShow(false)
-  , mIsBrowserElement(aIsBrowserElement)
   , mNotified(false)
   , mContentDocumentIsDisplayed(false)
   , mTriedBrowserInit(false)
 {
     printf("creating %d!\n", NS_IsMainThread());
 }
 
 NS_IMETHODIMP
@@ -525,17 +527,17 @@ TabChild::Init()
     nsIntRect(nsIntPoint(0, 0), nsIntSize(0, 0)),
     nullptr,                 // HandleWidgetEvent
     nullptr                  // nsDeviceContext
   );
 
   baseWindow->InitWindow(0, mWidget, 0, 0, 0, 0);
   baseWindow->Create();
 
-  SetAppBrowserConfig(mIsBrowserElement, mAppId);
+  SetAppBrowserConfig(mOwnOrContainingAppId, mIsBrowserElement);
 
   // IPC uses a WebBrowser object for which DNS prefetching is turned off
   // by default. But here we really want it, so enable it explicitly
   nsCOMPtr<nsIWebBrowserSetup> webBrowserSetup =
     do_QueryInterface(baseWindow);
   if (webBrowserSetup) {
     webBrowserSetup->SetProperty(nsIWebBrowserSetup::SETUP_ALLOW_DNS_PREFETCH,
                                  true);
@@ -549,28 +551,37 @@ TabChild::Init()
   nsCOMPtr<nsIWebProgress> webProgress = do_GetInterface(docShell);
   NS_ENSURE_TRUE(webProgress, NS_ERROR_FAILURE);
   webProgress->AddProgressListener(this, nsIWebProgress::NOTIFY_LOCATION);
 
   return NS_OK;
 }
 
 void
-TabChild::SetAppBrowserConfig(bool aIsBrowserElement, uint32_t aAppId)
+TabChild::SetAppBrowserConfig(uint32_t aOwnOrContainingAppId, bool aIsBrowserElement)
 {
+    mOwnOrContainingAppId = aOwnOrContainingAppId;
     mIsBrowserElement = aIsBrowserElement;
-    mAppId = aAppId;
 
     nsCOMPtr<nsIDocShell> docShell = do_GetInterface(mWebNav);
     MOZ_ASSERT(docShell);
 
+    // We assume here that if !aIsBrowserElement and aOwnOrContainingAppId is
+    // not NO_APP_ID or UNKNOWN_APP_ID then the app id is our own app id, and
+    // not the id of a containing app.  That is, a TabChild that's contained
+    // inside an app must itself be an app or a browser.  Put another way, you
+    // can't have a remote non-app non-browser iframe contained inside an app.
+
     if (docShell) {
-        docShell->SetAppId(mAppId);
-        if (mIsBrowserElement) {
-            docShell->SetIsBrowserElement();
+        // nsDocShell will do the right thing if we pass NO_APP_ID or
+        // UNKNOWN_APP_ID for aOwnOrContainingAppId.
+        if (aIsBrowserElement) {
+          docShell->SetIsBrowserInsideApp(aOwnOrContainingAppId);
+        } else {
+          docShell->SetIsApp(aOwnOrContainingAppId);
         }
     }
 }
 
 NS_INTERFACE_MAP_BEGIN(TabChild)
   NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIWebBrowserChrome)
   NS_INTERFACE_MAP_ENTRY(nsIWebBrowserChrome)
   NS_INTERFACE_MAP_ENTRY(nsIWebBrowserChrome2)
@@ -784,21 +795,21 @@ TabChild::ProvideWindow(nsIDOMWindow* aP
                         bool aCalledFromJS,
                         bool aPositionSpecified, bool aSizeSpecified,
                         nsIURI* aURI, const nsAString& aName,
                         const nsACString& aFeatures, bool* aWindowIsNew,
                         nsIDOMWindow** aReturn)
 {
     *aReturn = nullptr;
 
-    // If aParent is inside an <iframe mozbrowser> and this isn't a request to
-    // open a modal-type window, we're going to create a new <iframe mozbrowser>
-    // and return its window here.
+    // If aParent is inside an <iframe mozbrowser> or <iframe mozapp> and this
+    // isn't a request to open a modal-type window, we're going to create a new
+    // <iframe mozbrowser/mozapp> and return its window here.
     nsCOMPtr<nsIDocShell> docshell = do_GetInterface(aParent);
-    if (docshell && docshell->GetIsBelowContentBoundary() &&
+    if (docshell && docshell->GetIsInBrowserOrApp() &&
         !(aChromeFlags & (nsIWebBrowserChrome::CHROME_MODAL |
                           nsIWebBrowserChrome::CHROME_OPENAS_DIALOG |
                           nsIWebBrowserChrome::CHROME_OPENAS_CHROME))) {
 
       // Note that BrowserFrameProvideWindow may return NS_ERROR_ABORT if the
       // open window call was canceled.  It's important that we pass this error
       // code back to our caller.
       return BrowserFrameProvideWindow(aParent, aURI, aName, aFeatures,
@@ -824,25 +835,25 @@ TabChild::BrowserFrameProvideWindow(nsID
                                     const nsAString& aName,
                                     const nsACString& aFeatures,
                                     bool* aWindowIsNew,
                                     nsIDOMWindow** aReturn)
 {
   *aReturn = nullptr;
 
   uint32_t chromeFlags = 0;
-  nsRefPtr<TabChild> newChild = new TabChild(chromeFlags,
-                                             mIsBrowserElement, mAppId);
+  nsRefPtr<TabChild> newChild =
+      new TabChild(chromeFlags, mOwnOrContainingAppId, mIsBrowserElement);
   if (!NS_SUCCEEDED(newChild->Init())) {
       return NS_ERROR_ABORT;
   }
   unused << Manager()->SendPBrowserConstructor(
       // We release this ref in DeallocPBrowserChild
       nsRefPtr<TabChild>(newChild).forget().get(),
-      chromeFlags, mIsBrowserElement, this);
+      chromeFlags, this, mIsBrowserElement);
   nsAutoCString spec;
   if (aURI) {
     aURI->GetSpec(spec);
   }
 
   NS_ConvertUTF8toUTF16 url(spec);
   nsString name(aName);
   NS_ConvertUTF8toUTF16 features(aFeatures);
@@ -993,28 +1004,29 @@ TabChild::~TabChild()
       }
       mTabChildGlobal->mTabChild = nullptr;
     }
 }
 
 void
 TabChild::SetProcessNameToAppName()
 {
-  if (mIsBrowserElement || (mAppId == nsIScriptSecurityManager::NO_APP_ID)) {
+  if (IsBrowserOrApp()) {
     return;
   }
+
   nsCOMPtr<nsIAppsService> appsService =
     do_GetService(APPS_SERVICE_CONTRACTID);
   if (!appsService) {
     NS_WARNING("No AppsService");
     return;
   }
   nsresult rv;
   nsCOMPtr<mozIDOMApplication> domApp;
-  rv = appsService->GetAppByLocalId(mAppId, getter_AddRefs(domApp));
+  rv = appsService->GetAppByLocalId(mOwnOrContainingAppId, getter_AddRefs(domApp));
   if (NS_FAILED(rv) || !domApp) {
     NS_WARNING("GetAppByLocalId failed");
     return;
   }
   nsCOMPtr<mozIApplication> app = do_QueryInterface(domApp);
   if (!app) {
     NS_WARNING("app isn't a mozIApplication");
     return;
@@ -1025,21 +1037,28 @@ TabChild::SetProcessNameToAppName()
     NS_WARNING("Failed to retrieve app name");
     return;
   }
 
   ContentChild::GetSingleton()->SetProcessName(appName);
 }
 
 bool
+TabChild::IsBrowserOrApp()
+{
+    return mIsBrowserElement ||
+           mOwnOrContainingAppId != nsIScriptSecurityManager::NO_APP_ID;
+}
+
+bool
 TabChild::IsRootContentDocument()
 {
-    if (mIsBrowserElement || mAppId == nsIScriptSecurityManager::NO_APP_ID) {
-        // We're the child side of a browser element.  This always
-        // behaves like a root content document.
+    if (IsBrowserOrApp()) {
+        // The child side of a browser or app element always behaves like a root
+        // content document.
         return true;
     }
 
     // Otherwise, we're the child side of an <html:app remote=true>
     // embedded in an outer <html:app>.  These don't behave like root
     // content documents in nested contexts.  Because of bug 761935,
     // <html:browser remote> and <html:app remote> can't nest, so we
     // assume this isn't the root.  When that bug is fixed, we need to
@@ -1682,17 +1701,17 @@ TabChild::InitTabChildGlobal(FrameScript
 
     chromeHandler->AddEventListener(NS_LITERAL_STRING("DOMMetaAdded"), this, false);
   }
 
   if (aScriptLoading != DONT_LOAD_SCRIPTS && !mTriedBrowserInit) {
     mTriedBrowserInit = true;
     // Initialize the child side of the browser element machinery,
     // if appropriate.
-    if (mIsBrowserElement || mAppId != nsIScriptSecurityManager::NO_APP_ID) {
+    if (IsBrowserOrApp()) {
       RecvLoadRemoteScript(BROWSER_ELEMENT_CHILD_SCRIPT);
     }
   }
 
   return true;
 }
 
 bool
--- a/dom/ipc/TabChild.h
+++ b/dom/ipc/TabChild.h
@@ -159,21 +159,25 @@ public:
      * This is expected to be called off the critical path to content
      * startup.  This is an opportunity to load things that are slow
      * on the critical path.
      */
     static void PreloadSlowThings();
 
     /** Return a TabChild with the given attributes. */
     static already_AddRefed<TabChild> 
-    Create(uint32_t aChromeFlags, bool aIsBrowserElement, uint32_t aAppId);
+    Create(uint32_t aChromeFlags, uint32_t aOwnOrContainingAppId, bool aIsBrowserElement);
 
     virtual ~TabChild();
 
-    uint32_t GetAppId() { return mAppId; }
+    uint32_t GetOwnOrContainingAppId() { return mOwnOrContainingAppId; }
+
+    // Does this TabChild correspond to a browser or app frame?
+    // Equivalent to mIsBrowserElement || GetOwnOrContainingAppId() != NO_APP_ID.
+    bool IsBrowserOrApp();
 
     bool IsRootContentDocument();
 
     NS_DECL_ISUPPORTS
     NS_DECL_NSIWEBBROWSERCHROME
     NS_DECL_NSIWEBBROWSERCHROME2
     NS_DECL_NSIEMBEDDINGSITEWINDOW
     NS_DECL_NSIWEBBROWSERCHROMEFOCUS
@@ -306,25 +310,26 @@ protected:
                                              bool* /* aAllowed */);
 
     virtual bool DeallocPIndexedDB(PIndexedDBChild* aActor);
 
 private:
     /**
      * Create a new TabChild object.
      *
-     * |aIsBrowserElement| indicates whether the tab is inside an <iframe mozbrowser>.
-     * |aAppId| is the app id of the app containing this tab. If the tab isn't
-     * contained in an app, aAppId will be nsIScriptSecurityManager::NO_APP_ID.
+     * |aOwnOrContainingAppId| is the app-id of our frame or of the closest app
+     * frame in the hierarchy which contains us.
+     *
+     * |aIsBrowserElement| indicates whether we're a browser (but not an app).
      */
-    TabChild(uint32_t aChromeFlags, bool aIsBrowserElement, uint32_t aAppId);
+    TabChild(uint32_t aChromeFlags, uint32_t aOwnOrContainingAppId, bool aIsBrowserElement);
 
     nsresult Init();
 
-    void SetAppBrowserConfig(bool aIsBrowserElement, uint32_t aAppId);
+    void SetAppBrowserConfig(uint32_t aOwnOrContainingAppId, bool aIsBrowserElement);
 
     bool UseDirectCompositor();
 
     void ActorDestroy(ActorDestroyReason why);
 
     enum FrameScriptLoading { DONT_LOAD_SCRIPTS, DEFAULT_LOAD_SCRIPTS };
     bool InitTabChildGlobal(FrameScriptLoading aScriptLoading = DEFAULT_LOAD_SCRIPTS);
     bool InitRenderingState();
@@ -378,19 +383,19 @@ private:
     RenderFrameChild* mRemoteFrame;
     nsRefPtr<TabChildGlobal> mTabChildGlobal;
     uint32_t mChromeFlags;
     nsIntRect mOuterRect;
     nsIntSize mInnerSize;
     float mOldViewportWidth;
     nscolor mLastBackgroundColor;
     ScrollingBehavior mScrolling;
-    uint32_t mAppId;
+    uint32_t mOwnOrContainingAppId;
+    bool mIsBrowserElement;
     bool mDidFakeShow;
-    bool mIsBrowserElement;
     bool mNotified;
     bool mContentDocumentIsDisplayed;
     bool mTriedBrowserInit;
 
     DISALLOW_EVIL_CONSTRUCTORS(TabChild);
 };
 
 inline TabChild*
--- a/dom/ipc/TabParent.cpp
+++ b/dom/ipc/TabParent.cpp
@@ -68,29 +68,29 @@ namespace mozilla {
 namespace dom {
 
 TabParent* sEventCapturer;
 
 TabParent *TabParent::mIMETabParent = nullptr;
 
 NS_IMPL_ISUPPORTS3(TabParent, nsITabParent, nsIAuthPromptProvider, nsISecureBrowserUI)
 
-TabParent::TabParent(mozIApplication* aApp, bool aIsBrowserElement)
+TabParent::TabParent(mozIApplication* aOwnOrContainingApp, bool aIsBrowserElement)
   : mFrameElement(NULL)
-  , mApp(aApp)
+  , mOwnOrContainingApp(aOwnOrContainingApp)
+  , mIsBrowserElement(aIsBrowserElement)
   , mIMESelectionAnchor(0)
   , mIMESelectionFocus(0)
   , mIMEComposing(false)
   , mIMECompositionEnding(false)
   , mIMECompositionStart(0)
   , mIMESeqno(0)
   , mEventCaptureDepth(0)
   , mDimensions(0, 0)
   , mDPI(0)
-  , mIsBrowserElement(aIsBrowserElement)
   , mShown(false)
 {
 }
 
 TabParent::~TabParent()
 {
 }
 
@@ -183,17 +183,17 @@ TabParent::RecvEvent(const RemoteDOMEven
 bool
 TabParent::AnswerCreateWindow(PBrowserParent** retval)
 {
     if (!mBrowserDOMWindow) {
         return false;
     }
 
     // Only non-app, non-browser processes may call CreateWindow.
-    if (GetApp() || IsBrowserElement()) {
+    if (IsBrowserOrApp()) {
         return false;
     }
 
     // Get a new rendering area from the browserDOMWin.  We don't want
     // to be starting any loads here, so get it with a null URI.
     nsCOMPtr<nsIFrameLoaderOwner> frameLoaderOwner;
     mBrowserDOMWindow->OpenURIInFrame(nullptr, nullptr,
                                       nsIBrowserDOMWindow::OPEN_NEWTAB,
@@ -887,19 +887,19 @@ TabParent::RecvPIndexedDBConstructor(PIn
     NS_RUNTIMEABORT("Not supported yet!");
   }
 
   nsresult rv;
 
   // XXXbent Need to make sure we have a whitelist for chrome databases!
 
   // Verify the appID in the origin first.
-  if (mApp && !aASCIIOrigin.EqualsLiteral("chrome")) {
+  if (mOwnOrContainingApp && !aASCIIOrigin.EqualsLiteral("chrome")) {
     uint32_t appId;
-    rv = mApp->GetLocalId(&appId);
+    rv = mOwnOrContainingApp->GetLocalId(&appId);
     NS_ENSURE_SUCCESS(rv, false);
 
     if (!IndexedDatabaseManager::OriginMatchesApp(aASCIIOrigin, appId)) {
       NS_WARNING("App attempted to open databases that it does not have "
                  "permission to access!");
       return false;
     }
   }
@@ -1154,37 +1154,31 @@ TabParent::GetWidget() const
   if (!frame)
     return nullptr;
 
   nsCOMPtr<nsIWidget> widget = frame->GetNearestWidget();
   return widget.forget();
 }
 
 bool
-TabParent::IsForMozBrowser()
+TabParent::IsForBrowserOrApp()
 {
-  nsCOMPtr<nsIContent> content = do_QueryInterface(mFrameElement);
-  nsCOMPtr<nsIMozBrowserFrame> browserFrame = do_QueryInterface(content);
-  if (browserFrame) {
-    bool isBrowser = false;
-    browserFrame->GetReallyIsBrowser(&isBrowser);
-    return isBrowser;
-  }
-  return false;
+  nsCOMPtr<nsIMozBrowserFrame> browserFrame = do_QueryInterface(mFrameElement);
+  return browserFrame ? browserFrame->GetReallyIsBrowserOrApp() : false;
 }
 
 bool
 TabParent::UseAsyncPanZoom()
 {
   bool usingOffMainThreadCompositing = !!CompositorParent::CompositorLoop();
   bool asyncPanZoomEnabled =
     Preferences::GetBool("layers.async-pan-zoom.enabled", false);
   ContentParent* cp = static_cast<ContentParent*>(Manager());
   return (usingOffMainThreadCompositing &&
-          !cp->IsForApp() && IsForMozBrowser() &&
+          !cp->IsForApp() && IsForBrowserOrApp() &&
           asyncPanZoomEnabled);
 }
 
 void
 TabParent::MaybeForwardEventToRenderFrame(const nsInputEvent& aEvent,
                                           nsInputEvent* aOutEvent)
 {
   if (RenderFrameParent* rfp = GetRenderFrame()) {
--- a/dom/ipc/TabParent.h
+++ b/dom/ipc/TabParent.h
@@ -49,27 +49,28 @@ class ContentDialogParent : public PCont
 class TabParent : public PBrowserParent 
                 , public nsITabParent 
                 , public nsIAuthPromptProvider
                 , public nsISecureBrowserUI
 {
     typedef mozilla::dom::ClonedMessageData ClonedMessageData;
 
 public:
-    TabParent(mozIApplication* aApp, bool aIsBrowserElement);
+    TabParent(mozIApplication* aOwnOrContainingApp, bool aIsBrowserElement);
     virtual ~TabParent();
     nsIDOMElement* GetOwnerElement() { return mFrameElement; }
     void SetOwnerElement(nsIDOMElement* aElement);
     nsIBrowserDOMWindow *GetBrowserDOMWindow() { return mBrowserDOMWindow; }
     void SetBrowserDOMWindow(nsIBrowserDOMWindow* aBrowserDOMWindow) {
         mBrowserDOMWindow = aBrowserDOMWindow;
     }
  
-    mozIApplication* GetApp() { return mApp; }
+    mozIApplication* GetOwnOrContainingApp() { return mOwnOrContainingApp; }
     bool IsBrowserElement() { return mIsBrowserElement; }
+    bool IsBrowserOrApp() { return GetOwnOrContainingApp() || IsBrowserElement(); }
 
     /**
      * Return the TabParent that has decided it wants to capture an
      * event series for fast-path dispatch to its subprocess, if one
      * has.
      *
      * DOM event dispatch and widget are free to ignore capture
      * requests from TabParents; the end result wrt remote content is
@@ -256,17 +257,19 @@ protected:
     bool AllowContentIME();
 
     virtual PRenderFrameParent* AllocPRenderFrame(ScrollingBehavior* aScrolling,
                                                   LayersBackend* aBackend,
                                                   int32_t* aMaxTextureSize,
                                                   uint64_t* aLayersId) MOZ_OVERRIDE;
     virtual bool DeallocPRenderFrame(PRenderFrameParent* aFrame) MOZ_OVERRIDE;
 
-    nsCOMPtr<mozIApplication> mApp;
+    nsCOMPtr<mozIApplication> mOwnOrContainingApp;
+    bool mIsBrowserElement;
+
     // IME
     static TabParent *mIMETabParent;
     nsString mIMECacheText;
     uint32_t mIMESelectionAnchor;
     uint32_t mIMESelectionFocus;
     bool mIMEComposing;
     bool mIMECompositionEnding;
     // Buffer to store composition text during ResetInputState
@@ -275,27 +278,26 @@ protected:
     uint32_t mIMECompositionStart;
     uint32_t mIMESeqno;
 
     // The number of event series we're currently capturing.
     int32_t mEventCaptureDepth;
 
     nsIntSize mDimensions;
     float mDPI;
-    bool mIsBrowserElement;
     bool mShown;
 
 private:
     already_AddRefed<nsFrameLoader> GetFrameLoader() const;
     already_AddRefed<nsIWidget> GetWidget() const;
     layout::RenderFrameParent* GetRenderFrame();
     void TryCacheDPI();
-    // Return true iff this TabParent was created for a mozbrowser
+    // Return true iff this TabParent was created for a mozbrowser or mozapp
     // frame.
-    bool IsForMozBrowser();
+    bool IsForBrowserOrApp();
     // When true, we create a pan/zoom controller for our frame and
     // notify it of input events targeting us.
     bool UseAsyncPanZoom();
     // If we have a render frame currently, notify it that we're about
     // to dispatch |aEvent| to our child.  If there's a relevant
     // transform in place, |aOutEvent| is the transformed |aEvent| to
     // dispatch to content.
     void MaybeForwardEventToRenderFrame(const nsInputEvent& aEvent,
--- a/embedding/components/windowwatcher/src/nsWindowWatcher.cpp
+++ b/embedding/components/windowwatcher/src/nsWindowWatcher.cpp
@@ -1628,22 +1628,18 @@ uint32_t nsWindowWatcher::CalculateChrom
   if (!(chromeFlags & nsIWebBrowserChrome::CHROME_OPENAS_CHROME)) {
     // Remove the dependent flag if we're not opening as chrome
     chromeFlags &= ~nsIWebBrowserChrome::CHROME_DEPENDENT;
   }
 
   // Disable CHROME_OPENAS_DIALOG if the window is inside <iframe mozbrowser>.
   // It's up to the embedder to interpret what dialog=1 means.
   nsCOMPtr<nsIDocShell> docshell = do_GetInterface(aParent);
-  if (docshell) {
-    bool belowContentBoundary = false;
-    docshell->GetIsBelowContentBoundary(&belowContentBoundary);
-    if (belowContentBoundary) {
-      chromeFlags &= ~nsIWebBrowserChrome::CHROME_OPENAS_DIALOG;
-    }
+  if (docshell && docshell->GetIsInBrowserOrApp()) {
+    chromeFlags &= ~nsIWebBrowserChrome::CHROME_OPENAS_DIALOG;
   }
 
   return chromeFlags;
 }
 
 // static
 int32_t
 nsWindowWatcher::WinHasOption(const char *aOptions, const char *aName,
--- a/xpfe/appshell/src/nsContentTreeOwner.cpp
+++ b/xpfe/appshell/src/nsContentTreeOwner.cpp
@@ -846,17 +846,17 @@ nsContentTreeOwner::ProvideWindow(nsIDOM
                                static_cast<nsIDocShellTreeOwner*>(this)),
                "Parent from wrong docshell tree?");
 #endif
 
   // If aParent is inside an <iframe mozbrowser> and this isn't a request to
   // open a modal-type window, we're going to create a new <iframe mozbrowser>
   // and return its window here.
   nsCOMPtr<nsIDocShell> docshell = do_GetInterface(aParent);
-  if (docshell && docshell->GetIsBelowContentBoundary() &&
+  if (docshell && docshell->GetIsInBrowserOrApp() &&
       !(aChromeFlags & (nsIWebBrowserChrome::CHROME_MODAL |
                         nsIWebBrowserChrome::CHROME_OPENAS_DIALOG |
                         nsIWebBrowserChrome::CHROME_OPENAS_CHROME))) {
     *aWindowIsNew =
       BrowserElementParent::OpenWindowInProcess(aParent, aURI, aName,
                                                 aFeatures, aReturn);
 
     // If OpenWindowInProcess failed (perhaps because the embedder blocked the