Bug 802366 - The main event: Let a browser process inherit its app's id. r=bz,cjones
authorJustin Lebar <justin.lebar@gmail.com>
Sat, 10 Nov 2012 10:32:37 -0800
changeset 112942 f69c066923580b9fbb4377d3a7bc8c1dad805e4d
parent 112941 927c8e35b4d0d6b0ac23ffce60e45f5cab24ba17
child 112943 b8616abaacf1f6276abee50175acc13004db7026
push id23841
push userryanvm@gmail.com
push dateSat, 10 Nov 2012 21:53:54 +0000
treeherdermozilla-central@8dab31701ed1 [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 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.
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/Makefile.in
dom/ipc/PContent.ipdl
dom/ipc/TabChild.cpp
dom/ipc/TabChild.h
dom/ipc/TabContext.cpp
dom/ipc/TabContext.h
dom/ipc/TabParent.cpp
dom/ipc/TabParent.h
embedding/components/windowwatcher/src/nsWindowWatcher.cpp
gfx/thebes/gfxAndroidPlatform.cpp
xpfe/appshell/src/nsContentTreeOwner.cpp
--- a/content/base/src/nsDocument.cpp
+++ b/content/base/src/nsDocument.cpp
@@ -8371,17 +8371,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,114 @@ 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::GetContainingApp()
+{
+  // 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);
+
+  nsCOMPtr<mozIApplication> 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 +1532,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 +1633,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 = GetContainingApp();
+    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 +1992,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 +2028,28 @@ nsFrameLoader::TryRemoteBrowser()
   nsCOMPtr<nsIXULWindow> window(do_GetInterface(parentOwner));
   if (!window) {
     return false;
   }
   if (NS_FAILED(window->GetChromeFlags(&chromeFlags))) {
     return false;
   }
 
-  bool isBrowserElement = false;
-  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;
-      }
-    }
+  MutableTabContext context;
+  nsCOMPtr<mozIApplication> ownApp = GetOwnApp();
+  nsCOMPtr<mozIApplication> containingApp = GetContainingApp();
+  if (ownApp) {
+    context.SetTabContextForAppFrame(ownApp, containingApp);
+  } else if (OwnerIsBrowserFrame()) {
+    // The |else| above is unnecessary; OwnerIsBrowserFrame() implies !ownApp.
+    context.SetTabContextForBrowserFrame(containingApp);
   }
 
-  if ((mRemoteBrowser = ContentParent::CreateBrowser(app, isBrowserElement))) {
+  mRemoteBrowser = ContentParent::CreateBrowserOrApp(context);
+  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 +2349,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,50 @@ 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 app which contains this frame.  This is the app associated with
+   * the frame element's principal.
+   */
+  already_AddRefed<mozIApplication> GetContainingApp();
+
+  /**
    * 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
@@ -725,16 +725,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),
@@ -762,17 +763,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);
@@ -2145,29 +2146,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) &&
@@ -2194,17 +2195,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;
     }
@@ -2806,17 +2807,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;
 
@@ -2928,29 +2929,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;
@@ -5224,17 +5217,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
@@ -12306,146 +12299,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
@@ -660,23 +660,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;
@@ -803,17 +802,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
@@ -840,17 +838,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
@@ -2919,17 +2919,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);
 }
 
@@ -3024,19 +3024,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,
@@ -6483,17 +6483,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) {
@@ -7002,26 +7002,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
@@ -226,23 +226,21 @@ ConsoleListener::Observe(nsIConsoleMessa
     NS_ENSURE_SUCCESS(rv, rv);
     mChild->SendConsoleMessage(msg);
     return NS_OK;
 }
 
 ContentChild* ContentChild::sSingleton;
 
 ContentChild::ContentChild()
- :
-   mID(uint64_t(-1))
+ : TabContext()
+ , mID(uint64_t(-1))
 #ifdef ANDROID
    ,mScreenSize(0, 0)
 #endif
-   , mIsForApp(false)
-   , mIsForBrowser(false)
 {
     // This process is a content process, so it's clearly running in
     // multiprocess mode!
     nsDebugImpl::SetMultiprocessMode("Child");
 }
 
 ContentChild::~ContentChild()
 {
@@ -296,17 +294,17 @@ ContentChild::Init(MessageLoop* aIOLoop,
     bool startBackground = true;
     SendGetProcessAttributes(&mID, &startBackground,
                              &mIsForApp, &mIsForBrowser);
     hal::SetProcessPriority(
         GetCurrentProcId(),
         startBackground ? hal::PROCESS_PRIORITY_BACKGROUND:
                           hal::PROCESS_PRIORITY_FOREGROUND);
     if (mIsForApp && !mIsForBrowser) {
-        SetProcessName(NS_LITERAL_STRING("(App)"));
+        SetProcessName(NS_LITERAL_STRING("(Preallocated app)"));
     } else {
         SetProcessName(NS_LITERAL_STRING("Browser"));
     }
 
     return true;
 }
 
 void
@@ -484,27 +482,31 @@ ContentChild::AllocPImageBridge(mozilla:
 }
 
 static void FirstIdle(void)
 {
     ContentChild::GetSingleton()->SendFirstIdle();
 }
 
 PBrowserChild*
-ContentChild::AllocPBrowser(const uint32_t& aChromeFlags,
-                            const bool& aIsBrowserElement, const AppId& aApp)
+ContentChild::AllocPBrowser(const IPCTabContext& aContext,
+                            const uint32_t& aChromeFlags)
 {
     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());
+    // We'll happily accept any kind of IPCTabContext here; we don't need to
+    // check that it's of a certain type for security purposes, because we
+    // believe whatever the parent process tells us.
+
+    nsRefPtr<TabChild> child = TabChild::Create(TabContext(aContext), aChromeFlags);
+
     // 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
@@ -4,16 +4,17 @@
  * 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/. */
 
 #ifndef mozilla_dom_ContentChild_h
 #define mozilla_dom_ContentChild_h
 
 #include "mozilla/Attributes.h"
 #include "mozilla/dom/PContentChild.h"
+#include "mozilla/dom/TabContext.h"
 #include "mozilla/dom/ipc/Blob.h"
 
 #include "nsTArray.h"
 #include "nsIConsoleListener.h"
 
 struct ChromePackage;
 class nsIDOMBlob;
 class nsIObserver;
@@ -35,16 +36,17 @@ namespace dom {
 
 class AlertObserver;
 class PrefObserver;
 class ConsoleListener;
 class PStorageChild;
 class ClonedMessageData;
 
 class ContentChild : public PContentChild
+                   , public TabContext
 {
     typedef mozilla::dom::ClonedMessageData ClonedMessageData;
     typedef mozilla::ipc::OptionalURIParams OptionalURIParams;
     typedef mozilla::ipc::URIParams URIParams;
 
 public:
     ContentChild();
     virtual ~ContentChild();
@@ -73,19 +75,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);
+    virtual PBrowserChild* AllocPBrowser(const IPCTabContext &aContext,
+                                         const uint32_t &chromeFlags);
     virtual bool DeallocPBrowser(PBrowserChild*);
 
     virtual PDeviceStorageRequestChild* AllocPDeviceStorageRequest(const DeviceStorageParams&);
     virtual bool DeallocPDeviceStorageRequest(PDeviceStorageRequestChild*);
 
     virtual PBlobChild* AllocPBlob(const BlobConstructorParams& aParams);
     virtual bool DeallocPBlob(PBlobChild*);
 
@@ -190,19 +191,16 @@ public:
 #endif
 
     // Get the directory for IndexedDB files. We query the parent for this and
     // cache the value
     nsString &GetIndexedDBPath();
 
     uint64_t GetID() { return mID; }
 
-    bool IsForApp() { return mIsForApp; }
-    bool IsForBrowser() { return mIsForBrowser; }
-
     BlobChild* GetOrCreateActorForBlob(nsIDOMBlob* aBlob);
 
 private:
     virtual void ActorDestroy(ActorDestroyReason why) MOZ_OVERRIDE;
 
     virtual void ProcessingError(Result what) MOZ_OVERRIDE;
 
     /**
--- a/dom/ipc/ContentParent.cpp
+++ b/dom/ipc/ContentParent.cpp
@@ -50,17 +50,16 @@
 #include "nsConsoleService.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 "nsIDOMApplicationRegistry.h"
 #include "nsIDOMGeoGeolocation.h"
 #include "nsIDOMWindow.h"
 #include "nsIFilePicker.h"
 #include "nsIMemoryReporter.h"
 #include "nsIObserverService.h"
 #include "nsIPresShell.h"
@@ -312,90 +311,74 @@ AppNeedsInheritedOSPrivileges(mozIApplic
         } else if (needsInherit) {
             return true;
         }
     }
     return false;
 }
 
 /*static*/ TabParent*
-ContentParent::CreateBrowser(mozIApplication* aApp, bool aIsBrowserElement)
+ContentParent::CreateBrowserOrApp(const TabContext& aContext)
 {
-    // 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);
-
-    if (!aApp) {
-        if (ContentParent* cp = GetNewOrUsed(aIsBrowserElement)) {
-            nsRefPtr<TabParent> tp(new TabParent(aApp, aIsBrowserElement));
-            return static_cast<TabParent*>(
-                cp->SendPBrowserConstructor(
-                    // DeallocPBrowserParent() releases the ref we take here
-                    tp.forget().get(),
-                    /*chromeFlags*/0,
-                    aIsBrowserElement, nsIScriptSecurityManager::NO_APP_ID));
+    if (aContext.IsBrowserElement() || !aContext.HasOwnApp()) {
+        if (ContentParent* cp = GetNewOrUsed(aContext.IsBrowserElement())) {
+            nsRefPtr<TabParent> tp(new TabParent(aContext));
+            PBrowserParent* browser = cp->SendPBrowserConstructor(
+                tp.forget().get(), // DeallocPBrowserParent() releases this ref.
+                aContext.AsIPCTabContext(),
+                /* chromeFlags */ 0);
+            return static_cast<TabParent*>(browser);
         }
         return nullptr;
     }
 
+    // If we got here, we have an app and we're not a browser element.  ownApp
+    // shouldn't be null, because we otherwise would have gone into the
+    // !HasOwnApp() branch above.
+    nsCOMPtr<mozIApplication> ownApp = aContext.GetOwnApp();
+
     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));
-    return static_cast<TabParent*>(
-        // DeallocPBrowserParent() releases the ref we take here
-        p->SendPBrowserConstructor(tp.forget().get(),
-                                   /*chromeFlags*/0,
-                                   aIsBrowserElement, appId));
+    nsRefPtr<TabParent> tp = new TabParent(aContext);
+    PBrowserParent* browser = p->SendPBrowserConstructor(
+        tp.forget().get(), // DeallocPBrowserParent() releases this ref.
+        aContext.AsIPCTabContext(),
+        /* chromeFlags */ 0);
+    return static_cast<TabParent*>(browser);
 }
 
 static PLDHashOperator
 AppendToTArray(const nsAString& aKey, ContentParent* aValue, void* aArray)
 {
     nsTArray<ContentParent*> *array =
         static_cast<nsTArray<ContentParent*>*>(aArray);
     array->AppendElement(aValue);
@@ -1158,40 +1141,47 @@ ContentParent::RecvGetXPCOMProcessAttrib
     NS_ASSERTION(io, "No IO service?");
     DebugOnly<nsresult> rv = io->GetOffline(aIsOffline);
     NS_ASSERTION(NS_SUCCEEDED(rv), "Failed getting offline?");
 
     return true;
 }
 
 PBrowserParent*
-ContentParent::AllocPBrowser(const uint32_t& aChromeFlags,
-                             const bool& aIsBrowserElement, const AppId& aApp)
+ContentParent::AllocPBrowser(const IPCTabContext& aContext,
+                             const uint32_t &aChromeFlags)
 {
-    // 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()) {
-        NS_ERROR("Content process attempting to forge app ID");
-        return nullptr;
-    }
-    TabParent* opener = static_cast<TabParent*>(aApp.get_PBrowserParent());
+    unused << aChromeFlags;
 
-    // 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");
+    // We don't trust the IPCTabContext we receive from the child, so we'll bail
+    // if we receive an IPCTabContext that's not a PopupIPCTabContext.
+    // (PopupIPCTabContext lets the child process prove that it has access to
+    // the app it's trying to open.)
+    if (aContext.type() != IPCTabContext::TPopupIPCTabContext) {
+        NS_ERROR("Unexpected IPCTabContext type.  Aborting AllocPBrowser.");
         return nullptr;
     }
 
-    TabParent* parent = new TabParent(opener ? opener->GetApp() : nullptr,
-                                      aIsBrowserElement);
+    const PopupIPCTabContext& popupContext = aContext.get_PopupIPCTabContext();
+    TabParent* opener = static_cast<TabParent*>(popupContext.openerParent());
+    if (!opener) {
+        NS_ERROR("Got null opener from child; aborting AllocPBrowser.");
+        return nullptr;
+    }
+
+    // Popup windows of isBrowser frames must be 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 (!popupContext.isBrowserElement() && opener->IsBrowserElement()) {
+        NS_ERROR("Child trying to escalate privileges!  Aborting AllocPBrowser.");
+        return nullptr;
+    }
+
+    TabParent* parent = new TabParent(TabContext(aContext));
+
     // 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
@@ -6,16 +6,17 @@
 
 #ifndef mozilla_dom_ContentParent_h
 #define mozilla_dom_ContentParent_h
 
 #include "base/waitable_event_watcher.h"
 
 #include "mozilla/dom/PContentParent.h"
 #include "mozilla/dom/PMemoryReportRequestParent.h"
+#include "mozilla/dom/TabContext.h"
 #include "mozilla/ipc/GeckoChildProcessHost.h"
 #include "mozilla/dom/ipc/Blob.h"
 #include "mozilla/Attributes.h"
 
 #include "nsFrameMessageManager.h"
 #include "nsIObserver.h"
 #include "nsIThreadInternal.h"
 #include "nsNetUtil.h"
@@ -69,25 +70,19 @@ 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.
-     *
-     * Currently apps are given their own process, and browser tabs
-     * share processes.
+     * Get or create a content process for the given TabContext.
      */
-    static TabParent* CreateBrowser(mozIApplication* aApp,
-                                    bool aIsBrowserFrame);
+    static TabParent* CreateBrowserOrApp(const TabContext& aContext);
 
     static void GetAll(nsTArray<ContentParent*>& aArray);
 
     NS_DECL_ISUPPORTS
     NS_DECL_NSIOBSERVER
     NS_DECL_NSITHREADOBSERVER
     NS_DECL_NSIDOMGEOPOSITIONCALLBACK
 
@@ -184,19 +179,18 @@ private:
                       base::ProcessId aOtherProcess) MOZ_OVERRIDE;
 
     virtual bool RecvGetProcessAttributes(uint64_t* aId,
                                           bool* aStartBackground,
                                           bool* aIsForApp,
                                           bool* aIsForBrowser) MOZ_OVERRIDE;
     virtual bool RecvGetXPCOMProcessAttributes(bool* aIsOffline) MOZ_OVERRIDE;
 
-    virtual PBrowserParent* AllocPBrowser(const uint32_t& aChromeFlags,
-                                          const bool& aIsBrowserElement,
-                                          const AppId& aApp);
+    virtual PBrowserParent* AllocPBrowser(const IPCTabContext& aContext,
+                                          const uint32_t& aChromeFlags);
     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/Makefile.in
+++ b/dom/ipc/Makefile.in
@@ -37,16 +37,17 @@ EXPORTS_mozilla/dom = \
   ContentParent.h \
   ContentProcess.h \
   CrashReporterChild.h \
   CrashReporterParent.h \
   PermissionMessageUtils.h \
   StructuredCloneUtils.h \
   TabParent.h \
   TabChild.h \
+  TabContext.h \
   TabMessageUtils.h \
   $(NULL)
 
 EXPORTS_mozilla/dom/ipc = \
   Blob.h \
   ProcessPriorityManager.h \
   nsIRemoteBlob.h \
   $(NULL)
@@ -59,16 +60,17 @@ CPPSRCS = \
   ContentChild.cpp \
   CrashReporterParent.cpp \
   CrashReporterChild.cpp \
   PermissionMessageUtils.cpp \
   ProcessPriorityManager.cpp \
   StructuredCloneUtils.cpp \
   TabParent.cpp \
   TabChild.cpp \
+  TabContext.cpp \
   TabMessageUtils.cpp \
   $(NULL)
 
 ifdef MOZ_SYDNEYAUDIO
 EXPORTS_mozilla/dom += \
   AudioChild.h \
   AudioParent.h \
   $(NULL)
--- a/dom/ipc/PContent.ipdl
+++ b/dom/ipc/PContent.ipdl
@@ -130,19 +130,70 @@ struct MysteryBlobConstructorParams
 union BlobConstructorParams
 {
   NormalBlobConstructorParams;
   FileBlobConstructorParams;
   SlicedBlobConstructorParams;
   MysteryBlobConstructorParams;
 };
 
-union AppId {
-  uint32_t;
-  nullable PBrowser;
+// An IPCTabContext which corresponds to a PBrowser opened by a child when it
+// receives window.open().
+//
+// If isBrowserElement is false, this PopupIPCTabContext corresponds to an app
+// frame, and the frame's app-id and app-frame-owner-app-id will be equal to the
+// opener's values.
+//
+// If isBrowserElement is true, the frame's browserFrameOwnerAppId will be equal
+// to the opener's app-id.
+//
+// It's an error to set isBrowserElement == false if opener is a browser
+// element.  Such a PopupIPCTabContext should be rejected by code which receives
+// it.
+struct PopupIPCTabContext
+{
+  PBrowser opener;
+  bool isBrowserElement;
+};
+
+// An IPCTabContext which corresponds to an app frame.
+struct AppFrameIPCTabContext
+{
+  // The ID of the app this frame corresponds to.  May be NO_APP_ID.
+  uint32_t ownAppId;
+
+  // The ID of the app containing this frame.  May be NO_APP_ID.
+  uint32_t appFrameOwnerAppId;
+};
+
+// An IPCTabContext which corresponds to a browser frame.
+struct BrowserFrameIPCTabContext
+{
+  // The ID of the app which contains this browser frame.  May be NO_APP_ID.
+  uint32_t browserFrameOwnerAppId;
+};
+
+// This is equivalent to AppFrameIPCTabContext with all fields set to NO_APP_ID.
+struct VanillaFrameIPCTabContext
+{};
+
+// IPCTabContext is an analog to mozilla::dom::TabContext.  Both specify an
+// iframe/PBrowser's own and containing app-ids and tell you whether the
+// iframe/PBrowser is a browser frame.  But only IPCTabContext is allowed to
+// travel over IPC.
+//
+// We need IPCTabContext (specifically, PopupIPCTabContext) to prevent a
+// privilege escalation attack by a compromised child process.  See the comment
+// on AllocPBrowser for details.
+union IPCTabContext
+{
+  PopupIPCTabContext;
+  AppFrameIPCTabContext;
+  BrowserFrameIPCTabContext;
+  VanillaFrameIPCTabContext;
 };
 
 union PrefValue {
   nsCString;
   int32_t;
   bool;
 };
 
@@ -177,26 +228,36 @@ rpc protocol PContent
     manages PStorage;
     manages PTestShell;
 
 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.
+    // TabChild::BrowserFrameProvideWindow (which happens when the child's
+    // content calls window.open()), and the parent creates the PBrowser as part
+    // of ContentParent::CreateTab.
+    //
+    // When the parent constructs a PBrowser, the child trusts the app token it
+    // receives from the parent.  In that case, context can be any of the
+    // IPCTabContext subtypes.
     //
-    // 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 child constructs a PBrowser, the parent doesn't trust the app
+    // token it receives from the child.  In this case, context must have type
+    // PopupIPCTabContext.  The browser created using a PopupIPCTabContext has
+    // the opener PBrowser's app-id and containing-app-id.  The parent checks
+    // that if the opener is a browser element, the context is also for a
+    // browser element.
+    //
+    // This allows the parent to prevent a malicious child from escalating its
+    // privileges by requesting a PBrowser corresponding to a highly-privileged
+    // app; the child can only request privileges for an app which the child has
+    // access to (in the form of a TabChild).
+    async PBrowser(IPCTabContext context, uint32_t chromeFlags);
 
     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
@@ -115,64 +115,59 @@ public:
 
 StaticRefPtr<TabChild> sPreallocatedTab;
 
 /*static*/ void
 TabChild::PreloadSlowThings()
 {
     MOZ_ASSERT(!sPreallocatedTab);
 
-    nsRefPtr<TabChild> tab(new TabChild(0, false,
-                                        nsIScriptSecurityManager::NO_APP_ID));
+    nsRefPtr<TabChild> tab(new TabChild(TabContext(), /* chromeFlags */ 0));
     if (!NS_SUCCEEDED(tab->Init()) ||
         !tab->InitTabChildGlobal(DONT_LOAD_SCRIPTS)) {
         return;
     }
     tab->TryCacheLoadAndCompileScript(BROWSER_ELEMENT_CHILD_SCRIPT);
 
     sPreallocatedTab = tab;
     ClearOnShutdown(&sPreallocatedTab);
 }
 
 /*static*/ already_AddRefed<TabChild>
-TabChild::Create(uint32_t aChromeFlags,
-                 bool aIsBrowserElement, uint32_t aAppId)
+TabChild::Create(const TabContext &aContext, uint32_t aChromeFlags)
 {
     if (sPreallocatedTab &&
         sPreallocatedTab->mChromeFlags == aChromeFlags &&
-        (aIsBrowserElement || 
-         aAppId != nsIScriptSecurityManager::NO_APP_ID)) {
+        aContext.IsBrowserOrApp()) {
+
         nsRefPtr<TabChild> child = sPreallocatedTab.get();
         sPreallocatedTab = nullptr;
 
         MOZ_ASSERT(!child->mTriedBrowserInit);
 
-        child->SetAppBrowserConfig(aIsBrowserElement, aAppId);
-
+        child->SetTabContext(aContext);
+        child->NotifyTabContextUpdated();
         return child.forget();
     }
 
-    nsRefPtr<TabChild> iframe = new TabChild(aChromeFlags, aIsBrowserElement,
-                                             aAppId);
+    nsRefPtr<TabChild> iframe = new TabChild(aContext, aChromeFlags);
     return NS_SUCCEEDED(iframe->Init()) ? iframe.forget() : nullptr;
 }
 
 
-TabChild::TabChild(uint32_t aChromeFlags, bool aIsBrowserElement,
-                   uint32_t aAppId)
-  : mRemoteFrame(nullptr)
+TabChild::TabChild(const TabContext& aContext, uint32_t aChromeFlags)
+  : TabContext(aContext)
+  , 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)
   , mDidFakeShow(false)
-  , mIsBrowserElement(aIsBrowserElement)
   , mNotified(false)
   , mContentDocumentIsDisplayed(false)
   , mTriedBrowserInit(false)
 {
     printf("creating %d!\n", NS_IsMainThread());
 }
 
 NS_IMETHODIMP
@@ -525,17 +520,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);
+  NotifyTabContextUpdated();
 
   // 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 +544,28 @@ 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::NotifyTabContextUpdated()
 {
-    mIsBrowserElement = aIsBrowserElement;
-    mAppId = aAppId;
-
     nsCOMPtr<nsIDocShell> docShell = do_GetInterface(mWebNav);
     MOZ_ASSERT(docShell);
 
     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 (IsBrowserElement()) {
+          docShell->SetIsBrowserInsideApp(BrowserOwnerAppId());
+        } else {
+          docShell->SetIsApp(OwnAppId());
         }
     }
 }
 
 NS_INTERFACE_MAP_BEGIN(TabChild)
   NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIWebBrowserChrome)
   NS_INTERFACE_MAP_ENTRY(nsIWebBrowserChrome)
   NS_INTERFACE_MAP_ENTRY(nsIWebBrowserChrome2)
@@ -784,21 +779,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,
@@ -823,26 +818,35 @@ TabChild::BrowserFrameProvideWindow(nsID
                                     nsIURI* aURI,
                                     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(/* TabContext */ *this, /* chromeFlags */ 0);
   if (!NS_SUCCEEDED(newChild->Init())) {
       return NS_ERROR_ABORT;
   }
+
+  // We must use PopupIPCTabContext here; ContentParent will not accept the
+  // result of this->AsIPCTabContext() (which will be a
+  // BrowserFrameIPCTabContext or an AppFrameIPCTabContext), for security
+  // reasons.
+  PopupIPCTabContext context;
+  context.openerChild() = this;
+  context.isBrowserElement() = IsBrowserElement();
+
   unused << Manager()->SendPBrowserConstructor(
       // We release this ref in DeallocPBrowserChild
       nsRefPtr<TabChild>(newChild).forget().get(),
-      chromeFlags, mIsBrowserElement, this);
+      context, /* chromeFlags */ 0);
+
   nsAutoCString spec;
   if (aURI) {
     aURI->GetSpec(spec);
   }
 
   NS_ConvertUTF8toUTF16 url(spec);
   nsString name(aName);
   NS_ConvertUTF8toUTF16 features(aFeatures);
@@ -993,63 +997,53 @@ TabChild::~TabChild()
       }
       mTabChildGlobal->mTabChild = nullptr;
     }
 }
 
 void
 TabChild::SetProcessNameToAppName()
 {
-  if (mIsBrowserElement || (mAppId == nsIScriptSecurityManager::NO_APP_ID)) {
-    return;
-  }
-  nsCOMPtr<nsIAppsService> appsService =
-    do_GetService(APPS_SERVICE_CONTRACTID);
-  if (!appsService) {
-    NS_WARNING("No AppsService");
+  nsCOMPtr<mozIApplication> app = GetOwnApp();
+  if (!app) {
     return;
   }
-  nsresult rv;
-  nsCOMPtr<mozIDOMApplication> domApp;
-  rv = appsService->GetAppByLocalId(mAppId, 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;
-  }
+
   nsAutoString appName;
-  rv = app->GetName(appName);
+  nsresult rv = app->GetName(appName);
   if (NS_FAILED(rv)) {
     NS_WARNING("Failed to retrieve app name");
     return;
   }
 
   ContentChild::GetSingleton()->SetProcessName(appName);
 }
 
 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.
-        return true;
-    }
+    // A TabChild is a "root content document" if it's
+    //
+    //  - <iframe mozapp> not inside another <iframe mozapp>,
+    //  - <iframe mozbrowser> (not mozapp), or
+    //  - a vanilla remote frame (<html:iframe remote=true> or <xul:browser
+    //    remote=true>).
+    //
+    // Put another way, an iframe is /not/ a "root content document" iff it's a
+    // mozapp inside a mozapp.  (This corresponds exactly to !HasAppOwnerApp.)
+    //
+    // Note that we're lying through our teeth here (thus the scare quotes).
+    // <html:iframe remote=true> or <xul:browser remote=true> inside another
+    // content iframe is not actually a root content document, but we say it is.
+    //
+    // We do this because we make a remote frame opaque iff
+    // IsRootContentDocument(), and making vanilla remote frames transparent
+    // breaks our remote reftests.
 
-    // 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
-    // revisit that assumption.
-    return false;
+    return !HasAppOwnerApp();
 }
 
 bool
 TabChild::RecvLoadURL(const nsCString& uri)
 {
     printf("loading %s, %d\n", uri.get(), NS_IsMainThread());
     SetProcessNameToAppName();
 
@@ -1703,17 +1697,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
@@ -46,16 +46,17 @@
 #include "nsIScriptObjectPrincipal.h"
 #include "nsIScriptContext.h"
 #include "nsPIDOMWindow.h"
 #include "nsWeakReference.h"
 #include "nsITabChild.h"
 #include "mozilla/Attributes.h"
 #include "FrameMetrics.h"
 #include "ProcessUtils.h"
+#include "mozilla/dom/TabContext.h"
 
 struct gfxMatrix;
 
 namespace mozilla {
 namespace layout {
 class RenderFrameChild;
 }
 
@@ -144,37 +145,36 @@ class TabChild : public PBrowserChild,
                  public nsIInterfaceRequestor,
                  public nsIWindowProvider,
                  public nsIDOMEventListener,
                  public nsIWebProgressListener,
                  public nsSupportsWeakReference,
                  public nsIDialogCreator,
                  public nsITabChild,
                  public nsIObserver,
-                 public mozilla::dom::ipc::MessageManagerCallback
+                 public ipc::MessageManagerCallback,
+                 public TabContext
 {
     typedef mozilla::layout::RenderFrameChild RenderFrameChild;
     typedef mozilla::dom::ClonedMessageData ClonedMessageData;
 
 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(const TabContext& aContext, uint32_t aChromeFlags);
 
     virtual ~TabChild();
 
-    uint32_t GetAppId() { return mAppId; }
-
     bool IsRootContentDocument();
 
     NS_DECL_ISUPPORTS
     NS_DECL_NSIWEBBROWSERCHROME
     NS_DECL_NSIWEBBROWSERCHROME2
     NS_DECL_NSIEMBEDDINGSITEWINDOW
     NS_DECL_NSIWEBBROWSERCHROMEFOCUS
     NS_DECL_NSIINTERFACEREQUESTOR
@@ -315,25 +315,31 @@ 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(const TabContext& aContext, uint32_t aChromeFlags);
 
     nsresult Init();
 
-    void SetAppBrowserConfig(bool aIsBrowserElement, uint32_t aAppId);
+    // Notify others that our TabContext has been updated.  (At the moment, this
+    // sets the appropriate app-id and is-browser flags on our docshell.)
+    //
+    // You should call this after calling TabContext::SetTabContext().  We also
+    // call this during Init().
+    void NotifyTabContextUpdated();
 
     bool UseDirectCompositor();
 
     void ActorDestroy(ActorDestroyReason why);
 
     enum FrameScriptLoading { DONT_LOAD_SCRIPTS, DEFAULT_LOAD_SCRIPTS };
     bool InitTabChildGlobal(FrameScriptLoading aScriptLoading = DEFAULT_LOAD_SCRIPTS);
     bool InitRenderingState();
@@ -387,19 +393,17 @@ private:
     RenderFrameChild* mRemoteFrame;
     nsRefPtr<TabChildGlobal> mTabChildGlobal;
     uint32_t mChromeFlags;
     nsIntRect mOuterRect;
     nsIntSize mInnerSize;
     float mOldViewportWidth;
     nscolor mLastBackgroundColor;
     ScrollingBehavior mScrolling;
-    uint32_t mAppId;
     bool mDidFakeShow;
-    bool mIsBrowserElement;
     bool mNotified;
     bool mContentDocumentIsDisplayed;
     bool mTriedBrowserInit;
 
     DISALLOW_EVIL_CONSTRUCTORS(TabChild);
 };
 
 inline TabChild*
new file mode 100644
--- /dev/null
+++ b/dom/ipc/TabContext.cpp
@@ -0,0 +1,292 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 ts=8 et tw=80 : */
+/* 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 "mozilla/dom/TabContext.h"
+#include "mozilla/dom/TabParent.h"
+#include "mozilla/dom/TabChild.h"
+#include "nsIAppsService.h"
+
+using namespace mozilla::dom::ipc;
+
+namespace mozilla {
+namespace dom {
+
+TabContext::TabContext()
+  : mInitialized(false)
+  , mOwnAppId(nsIScriptSecurityManager::NO_APP_ID)
+  , mContainingAppId(nsIScriptSecurityManager::NO_APP_ID)
+  , mIsBrowser(false)
+{
+}
+
+TabContext::TabContext(const IPCTabContext& aParams)
+  : mInitialized(true)
+{
+  switch(aParams.type()) {
+    case IPCTabContext::TPopupIPCTabContext: {
+      const PopupIPCTabContext &ipcContext = aParams.get_PopupIPCTabContext();
+
+      TabContext *context;
+      if (ipcContext.openerParent()) {
+        context = static_cast<TabParent*>(ipcContext.openerParent());
+        if (context->IsBrowserElement() && !ipcContext.isBrowserElement()) {
+          // If the TabParent corresponds to a browser element, then it can only
+          // open other browser elements, for security reasons.  We should have
+          // checked this before calling the TabContext constructor, so this is
+          // a fatal error.
+          MOZ_CRASH();
+        }
+      }
+      else if (ipcContext.openerChild()) {
+        context = static_cast<TabChild*>(ipcContext.openerChild());
+      }
+      else {
+        // This should be unreachable because PopupIPCTabContext::opener is not a
+        // nullable field.
+        MOZ_CRASH();
+      }
+
+      // If ipcContext is a browser element, then the opener's app-id becomes
+      // our containing app-id.  Otherwise, our own and containing app-ids are
+      // directly inherited from our opener.
+      if (ipcContext.isBrowserElement()) {
+        mIsBrowser = true;
+        mOwnAppId = nsIScriptSecurityManager::NO_APP_ID;
+        mContainingAppId = context->OwnAppId();
+      }
+      else {
+        mIsBrowser = false;
+        mOwnAppId = context->mOwnAppId;
+        mContainingAppId = context->mContainingAppId;
+      }
+      break;
+    }
+    case IPCTabContext::TAppFrameIPCTabContext: {
+      const AppFrameIPCTabContext &ipcContext =
+        aParams.get_AppFrameIPCTabContext();
+
+      mIsBrowser = false;
+      mOwnAppId = ipcContext.ownAppId();
+      mContainingAppId = ipcContext.appFrameOwnerAppId();
+      break;
+    }
+    case IPCTabContext::TBrowserFrameIPCTabContext: {
+      const BrowserFrameIPCTabContext &ipcContext =
+        aParams.get_BrowserFrameIPCTabContext();
+
+      mIsBrowser = true;
+      mOwnAppId = nsIScriptSecurityManager::NO_APP_ID;
+      mContainingAppId = ipcContext.browserFrameOwnerAppId();
+      break;
+    }
+    case IPCTabContext::TVanillaFrameIPCTabContext: {
+      mIsBrowser = false;
+      mOwnAppId = nsIScriptSecurityManager::NO_APP_ID;
+      mContainingAppId = nsIScriptSecurityManager::NO_APP_ID;
+      break;
+    }
+    default: {
+      MOZ_CRASH();
+    }
+  }
+}
+
+bool
+TabContext::IsBrowserElement() const
+{
+  return mIsBrowser;
+}
+
+bool
+TabContext::IsBrowserOrApp() const
+{
+  return HasOwnApp() || IsBrowserElement();
+}
+
+uint32_t
+TabContext::OwnAppId() const
+{
+  return mOwnAppId;
+}
+
+already_AddRefed<mozIApplication>
+TabContext::GetOwnApp() const
+{
+  return GetAppForId(OwnAppId());
+}
+
+bool
+TabContext::HasOwnApp() const
+{
+  return mOwnAppId != nsIScriptSecurityManager::NO_APP_ID;
+}
+
+uint32_t
+TabContext::BrowserOwnerAppId() const
+{
+  if (mIsBrowser) {
+    return mContainingAppId;
+  }
+  return nsIScriptSecurityManager::NO_APP_ID;
+}
+
+already_AddRefed<mozIApplication>
+TabContext::GetBrowserOwnerApp() const
+{
+  return GetAppForId(BrowserOwnerAppId());
+}
+
+bool
+TabContext::HasBrowserOwnerApp() const
+{
+  return BrowserOwnerAppId() != nsIScriptSecurityManager::NO_APP_ID;
+}
+
+uint32_t
+TabContext::AppOwnerAppId() const
+{
+  if (mOwnAppId != nsIScriptSecurityManager::NO_APP_ID) {
+    return mContainingAppId;
+  }
+  return nsIScriptSecurityManager::NO_APP_ID;
+}
+
+already_AddRefed<mozIApplication>
+TabContext::GetAppOwnerApp() const
+{
+  return GetAppForId(AppOwnerAppId());
+}
+
+bool
+TabContext::HasAppOwnerApp() const
+{
+  return AppOwnerAppId() != nsIScriptSecurityManager::NO_APP_ID;
+}
+
+uint32_t
+TabContext::OwnOrContainingAppId() const
+{
+  if (mIsBrowser) {
+    MOZ_ASSERT(mOwnAppId == nsIScriptSecurityManager::NO_APP_ID);
+    return mContainingAppId;
+  }
+
+  if (mOwnAppId) {
+    return mOwnAppId;
+  }
+
+  return mContainingAppId;
+}
+
+already_AddRefed<mozIApplication>
+TabContext::GetOwnOrContainingApp() const
+{
+  return GetAppForId(OwnOrContainingAppId());
+}
+
+bool
+TabContext::HasOwnOrContainingApp() const
+{
+  return OwnOrContainingAppId() != nsIScriptSecurityManager::NO_APP_ID;
+}
+
+bool
+TabContext::SetTabContext(const TabContext& aContext)
+{
+  NS_ENSURE_FALSE(mInitialized, false);
+
+  // Verify that we can actually get apps for the given ids.  This step gives us
+  // confidence that HasX() returns true iff GetX() returns true.
+  if (aContext.mOwnAppId != nsIScriptSecurityManager::NO_APP_ID) {
+    nsCOMPtr<mozIApplication> app = GetAppForId(aContext.mOwnAppId);
+    NS_ENSURE_TRUE(app, false);
+  }
+
+  if (aContext.mContainingAppId != nsIScriptSecurityManager::NO_APP_ID) {
+    nsCOMPtr<mozIApplication> app = GetAppForId(aContext.mContainingAppId);
+    NS_ENSURE_TRUE(app, false);
+  }
+
+  mInitialized = true;
+  mIsBrowser = aContext.mIsBrowser;
+  mOwnAppId = aContext.mOwnAppId;
+  mContainingAppId = aContext.mContainingAppId;
+  return true;
+}
+
+bool
+TabContext::SetTabContextForAppFrame(mozIApplication* aOwnApp, mozIApplication* aAppFrameOwnerApp)
+{
+  NS_ENSURE_FALSE(mInitialized, false);
+
+  // Get ids for both apps and only write to our member variables after we've
+  // verified that this worked.
+  uint32_t ownAppId = nsIScriptSecurityManager::NO_APP_ID;
+  if (aOwnApp) {
+    nsresult rv = aOwnApp->GetLocalId(&ownAppId);
+    NS_ENSURE_SUCCESS(rv, false);
+  }
+
+  uint32_t containingAppId = nsIScriptSecurityManager::NO_APP_ID;
+  if (aAppFrameOwnerApp) {
+    nsresult rv = aOwnApp->GetLocalId(&containingAppId);
+    NS_ENSURE_SUCCESS(rv, false);
+  }
+
+  mInitialized = true;
+  mIsBrowser = false;
+  mOwnAppId = ownAppId;
+  mContainingAppId = containingAppId;
+  return true;
+}
+
+bool
+TabContext::SetTabContextForBrowserFrame(mozIApplication* aBrowserFrameOwnerApp)
+{
+  NS_ENSURE_FALSE(mInitialized, false);
+
+  uint32_t containingAppId = nsIScriptSecurityManager::NO_APP_ID;
+  if (aBrowserFrameOwnerApp) {
+    nsresult rv = aBrowserFrameOwnerApp->GetLocalId(&containingAppId);
+    NS_ENSURE_SUCCESS(rv, false);
+  }
+
+  mInitialized = true;
+  mIsBrowser = true;
+  mOwnAppId = nsIScriptSecurityManager::NO_APP_ID;
+  mContainingAppId = containingAppId;
+  return true;
+}
+
+IPCTabContext
+TabContext::AsIPCTabContext() const
+{
+  if (mIsBrowser) {
+    return BrowserFrameIPCTabContext(mContainingAppId);
+  }
+
+  return AppFrameIPCTabContext(mOwnAppId, mContainingAppId);
+}
+
+already_AddRefed<mozIApplication>
+TabContext::GetAppForId(uint32_t aAppId) const
+{
+  if (aAppId == nsIScriptSecurityManager::NO_APP_ID) {
+    return nullptr;
+  }
+
+  nsCOMPtr<nsIAppsService> appsService = do_GetService(APPS_SERVICE_CONTRACTID);
+  NS_ENSURE_TRUE(appsService, nullptr);
+
+  nsCOMPtr<mozIDOMApplication> domApp;
+  appsService->GetAppByLocalId(aAppId, getter_AddRefs(domApp));
+
+  nsCOMPtr<mozIApplication> app = do_QueryInterface(domApp);
+  return app.forget();
+}
+
+} // namespace dom
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/dom/ipc/TabContext.h
@@ -0,0 +1,209 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 ts=8 et tw=80 : */
+/* 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/. */
+
+#ifndef mozilla_dom_TabContext_h
+#define mozilla_dom_TabContext_h
+
+#include "mozilla/Assertions.h"
+#include "mozilla/dom/PContent.h"
+#include "mozilla/dom/PBrowser.h"
+#include "nsIScriptSecurityManager.h"
+#include "mozIApplication.h"
+
+namespace mozilla {
+namespace dom {
+
+/**
+ * TabContext encapsulates information about an iframe that may be a mozbrowser
+ * or mozapp.  You can ask whether a TabContext correspodns to a mozbrowser or
+ * mozapp, get the app that contains the browser, and so on.
+ *
+ * TabParent and TabChild both inherit from TabContext, and you can also have
+ * standalone TabContext objects.
+ *
+ * This class is immutable except by calling one of the protected
+ * SetTabContext*() methods (and those methods can only be called once).  See
+ * also MutableTabContext.
+ */
+class TabContext
+{
+public:
+  /**
+   * This constructor sets is-browser to false, and sets all relevant apps to
+   * NO_APP_ID.  If you inherit from TabContext, you can mutate this object
+   * exactly once by calling one of the protected SetTabContext*() methods.
+   */
+  TabContext();
+
+  /**
+   * This constructor copies the information in aContext.  The TabContext is
+   * immutable after calling this method; you won't be able call any of the
+   * protected SetTabContext*() methods on an object constructed using this
+   * constructor.
+   *
+   * If aContext is a PopupIPCTabContext with isBrowserElement false and whose
+   * openerParent is a browser element, this constructor will crash (even in
+   * release builds).  So please check that case before calling this method.
+   */
+  TabContext(const IPCTabContext& aContext);
+
+  /**
+   * Generates IPCTabContext of type BrowserFrameIPCTabContext or
+   * AppFrameIPCTabContext from this TabContext's information.
+   */
+  IPCTabContext AsIPCTabContext() const;
+
+  /**
+   * Does this TabContext correspond to a mozbrowser?  (<iframe mozbrowser
+   * mozapp> is not a browser.)
+   *
+   * If IsBrowserElement() is true, HasOwnApp() and HasAppOwnerApp() are
+   * guaranteed to be false.
+   *
+   * If IsBrowserElement() is false, HasBrowserOwnerApp() is guaranteed to be
+   * false.
+   */
+  bool IsBrowserElement() const;
+
+  /**
+   * Does this TabContext correspond to a mozbrowser or mozapp?  This is
+   * equivalent to IsBrowserElement() || HasOwnApp().
+   */
+  bool IsBrowserOrApp() const;
+
+  /**
+   * OwnAppId() returns the id of the app which directly corresponds to this
+   * context's frame.  GetOwnApp() returns the corresponding app object, and
+   * HasOwnApp() returns true iff GetOwnApp() would return a non-null value.
+   *
+   * If HasOwnApp() is true, IsBrowserElement() is guaranteed to be false.
+   */
+  uint32_t OwnAppId() const;
+  already_AddRefed<mozIApplication> GetOwnApp() const;
+  bool HasOwnApp() const;
+
+  /**
+   * BrowserOwnerAppId() gets the ID of the app which contains this browser
+   * frame.  If this is not a browser frame (i.e., if !IsBrowserElement()), then
+   * BrowserOwnerAppId() is guaranteed to return NO_APP_ID.
+   *
+   * Even if we are a browser frame, BrowserOwnerAppId() may still return
+   * NO_APP_ID, if this browser frame is not contained inside an app.
+   */
+  uint32_t BrowserOwnerAppId() const;
+  already_AddRefed<mozIApplication> GetBrowserOwnerApp() const;
+  bool HasBrowserOwnerApp() const;
+
+  /**
+   * AppOwnerAppId() gets the ID of the app which contains this app frame.  If
+   * this is not an app frame (i.e., if !HasOwnApp()), then AppOwnerAppId() is
+   * guaranteed to return NO_APP_ID.
+   *
+   * Even if we are an app frame, AppOwnerAppId() may still return NO_APP_ID, if
+   * this app frame is not contained inside an app.
+   */
+  uint32_t AppOwnerAppId() const;
+  already_AddRefed<mozIApplication> GetAppOwnerApp() const;
+  bool HasAppOwnerApp() const;
+
+  /**
+   * OwnOrContainingAppId() gets the ID of this frame, if HasOwnApp().  If this
+   * frame does not have its own app, it gets the ID of the app which contains
+   * this frame (i.e., the result of {Browser,App}OwnerAppId(), as applicable).
+   */
+  uint32_t OwnOrContainingAppId() const;
+  already_AddRefed<mozIApplication> GetOwnOrContainingApp() const;
+  bool HasOwnOrContainingApp() const;
+
+protected:
+  /**
+   * These protected mutator methods let you modify a TabContext once.  Further
+   * attempts to modify a given TabContext will fail (the method will return
+   * false).
+   *
+   * These mutators will also fail if the TabContext was created with anything
+   * other than the no-args constructor.
+   */
+
+  /**
+   * Set this TabContext to match the given TabContext.
+   */
+  bool SetTabContext(const TabContext& aContext);
+
+  /**
+   * Set this TabContext to be an app frame (with the given own app) inside the
+   * given app.  Either or both apps may be null.
+   */
+  bool SetTabContextForAppFrame(mozIApplication* aOwnApp,
+                                mozIApplication* aAppFrameOwnerApp);
+
+  /**
+   * Set this TabContext to be a browser frame inside the given app (which may
+   * be null).
+   */
+  bool SetTabContextForBrowserFrame(mozIApplication* aBrowserFrameOwnerApp);
+
+private:
+  /**
+   * Translate an appId into a mozIApplication.
+   */
+  already_AddRefed<mozIApplication> GetAppForId(uint32_t aAppId) const;
+
+  /**
+   * Has this TabContext been initialized?  If so, mutator methods will fail.
+   */
+  bool mInitialized;
+
+  /**
+   * This TabContext's own app id.  If this is something other than NO_APP_ID,
+   * then this TabContext corresponds to an app, and mIsBrowser must be false.
+   */
+  uint32_t mOwnAppId;
+
+  /**
+   * The id of the app which contains this TabContext's frame.  If mIsBrowser,
+   * this corresponds to the ID of the app which contains the browser frame;
+   * otherwise, this correspodns to the ID of the app which contains the app
+   * frame.
+   */
+  uint32_t mContainingAppId;
+
+  /**
+   * Does this TabContext correspond to a browser element?
+   *
+   * If this is true, mOwnAppId must be NO_APP_ID.
+   */
+  bool mIsBrowser;
+};
+
+/**
+ * MutableTabContext is the same as TabContext, except the mutation methods are
+ * public instead of protected.  You can still only call these mutation methods
+ * once on a given object.
+ */
+class MutableTabContext : public TabContext
+{
+public:
+  bool SetTabContext(const TabContext& aContext)
+  {
+    return TabContext::SetTabContext(aContext);
+  }
+
+  bool SetTabContextForAppFrame(mozIApplication* aOwnApp, mozIApplication* aAppFrameOwnerApp)
+  {
+    return TabContext::SetTabContextForAppFrame(aOwnApp, aAppFrameOwnerApp);
+  }
+
+  bool SetTabContextForBrowserFrame(mozIApplication* aBrowserFrameOwnerApp)
+  {
+    return TabContext::SetTabContextForBrowserFrame(aBrowserFrameOwnerApp);
+  }
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif
--- a/dom/ipc/TabParent.cpp
+++ b/dom/ipc/TabParent.cpp
@@ -68,29 +68,28 @@ namespace mozilla {
 namespace dom {
 
 TabParent* sEventCapturer;
 
 TabParent *TabParent::mIMETabParent = nullptr;
 
 NS_IMPL_ISUPPORTS3(TabParent, nsITabParent, nsIAuthPromptProvider, nsISecureBrowserUI)
 
-TabParent::TabParent(mozIApplication* aApp, bool aIsBrowserElement)
-  : mFrameElement(NULL)
-  , mApp(aApp)
+TabParent::TabParent(const TabContext& aContext)
+  : TabContext(aContext)
+  , mFrameElement(NULL)
   , 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 +182,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,
@@ -892,19 +891,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;
     }
   }
@@ -1159,38 +1158,23 @@ TabParent::GetWidget() const
   if (!frame)
     return nullptr;
 
   nsCOMPtr<nsIWidget> widget = frame->GetNearestWidget();
   return widget.forget();
 }
 
 bool
-TabParent::IsForMozBrowser()
-{
-  nsCOMPtr<nsIContent> content = do_QueryInterface(mFrameElement);
-  nsCOMPtr<nsIMozBrowserFrame> browserFrame = do_QueryInterface(content);
-  if (browserFrame) {
-    bool isBrowser = false;
-    browserFrame->GetReallyIsBrowser(&isBrowser);
-    return isBrowser;
-  }
-  return 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() &&
-          asyncPanZoomEnabled);
+          IsBrowserElement() && asyncPanZoomEnabled);
 }
 
 void
 TabParent::MaybeForwardEventToRenderFrame(const nsInputEvent& aEvent,
                                           nsInputEvent* aOutEvent)
 {
   if (RenderFrameParent* rfp = GetRenderFrame()) {
     rfp->NotifyInputEvent(aEvent, aOutEvent);
--- a/dom/ipc/TabParent.h
+++ b/dom/ipc/TabParent.h
@@ -7,16 +7,17 @@
 #ifndef mozilla_tabs_TabParent_h
 #define mozilla_tabs_TabParent_h
 
 #include "base/basictypes.h"
 
 #include "jsapi.h"
 #include "mozilla/dom/PBrowserParent.h"
 #include "mozilla/dom/PContentDialogParent.h"
+#include "mozilla/dom/TabContext.h"
 #include "mozilla/ipc/GeckoChildProcessHost.h"
 #include "nsCOMPtr.h"
 #include "nsIAuthPromptProvider.h"
 #include "nsIBrowserDOMWindow.h"
 #include "nsIDialogParamBlock.h"
 #include "nsISecureBrowserUI.h"
 #include "nsITabParent.h"
 #include "nsWeakReference.h"
@@ -45,31 +46,29 @@ class ClonedMessageData;
 struct StructuredCloneData;
 
 class ContentDialogParent : public PContentDialogParent {};
 
 class TabParent : public PBrowserParent 
                 , public nsITabParent 
                 , public nsIAuthPromptProvider
                 , public nsISecureBrowserUI
+                , public TabContext
 {
     typedef mozilla::dom::ClonedMessageData ClonedMessageData;
 
 public:
-    TabParent(mozIApplication* aApp, bool aIsBrowserElement);
+    TabParent(const TabContext& aContext);
     virtual ~TabParent();
     nsIDOMElement* GetOwnerElement() { return mFrameElement; }
     void SetOwnerElement(nsIDOMElement* aElement);
     nsIBrowserDOMWindow *GetBrowserDOMWindow() { return mBrowserDOMWindow; }
     void SetBrowserDOMWindow(nsIBrowserDOMWindow* aBrowserDOMWindow) {
         mBrowserDOMWindow = aBrowserDOMWindow;
     }
- 
-    mozIApplication* GetApp() { return mApp; }
-    bool IsBrowserElement() { return mIsBrowserElement; }
 
     /**
      * 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
@@ -257,17 +256,16 @@ 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;
     // IME
     static TabParent *mIMETabParent;
     nsString mIMECacheText;
     uint32_t mIMESelectionAnchor;
     uint32_t mIMESelectionFocus;
     bool mIMEComposing;
     bool mIMECompositionEnding;
     // Buffer to store composition text during ResetInputState
@@ -276,27 +274,24 @@ 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
-    // frame.
-    bool IsForMozBrowser();
+
     // 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/gfx/thebes/gfxAndroidPlatform.cpp
+++ b/gfx/thebes/gfxAndroidPlatform.cpp
@@ -191,19 +191,18 @@ gfxAndroidPlatform::FontHintingEnabled()
     // hinting.
     // 
     // XXX when gecko-android-java is used as an "app runtime", we'll
     // want to re-enable hinting.
     return false;
 #else
     // Otherwise, enable hinting unless we're in a content process
     // that might be used for non-reflowing zoom.
-    return (XRE_GetProcessType() != GeckoProcessType_Content ||
-            (ContentChild::GetSingleton()->IsForApp() &&
-             !ContentChild::GetSingleton()->IsForBrowser()));
+    return XRE_GetProcessType() != GeckoProcessType_Content ||
+           ContentChild::GetSingleton()->HasOwnApp();
 #endif //  MOZ_USING_ANDROID_JAVA_WIDGETS
 }
 
 int
 gfxAndroidPlatform::GetScreenDepth() const
 {
     return mScreenDepth;
 }
--- 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