Bug 896890 - TabContent::HasOwnApp() should be true iff TabContext::GetOwnApp() is non-null. r=khuey
☠☠ backed out by 9805018b868e ☠ ☠
authorJustin Lebar <justin.lebar@gmail.com>
Tue, 30 Jul 2013 11:51:44 -0700
changeset 152879 4aeb00521c9dc231465abbd7f74a693b0641d4f0
parent 152878 dd1908156c2f3abbb60b0e1d97cc2a3356725a93
child 152880 10730c6907fc0d92ac853b3691b63f29a71fb78c
push id2859
push userakeybl@mozilla.com
push dateMon, 16 Sep 2013 19:14:59 +0000
treeherdermozilla-beta@87d3c51cd2bf [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerskhuey
bugs896890
milestone25.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 896890 - TabContent::HasOwnApp() should be true iff TabContext::GetOwnApp() is non-null. r=khuey This change reworks how TabContext stores its data. Before, it stored an app-id and translated that into an app; now we do the reverse. This lets us guarantee that HasOwnApp() is true iff GetOwnApp() is non-null. We added a new class, MaybeInvalidTabContext to assist with converting from an IPCTabContext to a TabContext. This allows us to ensure that a TabContext object is never invalid.
content/base/src/nsFrameLoader.cpp
dom/ipc/ContentChild.cpp
dom/ipc/ContentParent.cpp
dom/ipc/TabContext.cpp
dom/ipc/TabContext.h
--- a/content/base/src/nsFrameLoader.cpp
+++ b/content/base/src/nsFrameLoader.cpp
@@ -2051,22 +2051,25 @@ nsFrameLoader::TryRemoteBrowser()
   nsCOMPtr<mozIApplication> containingApp = GetContainingApp();
   ScrollingBehavior scrollingBehavior = DEFAULT_SCROLLING;
   if (mOwnerContent->AttrValueIs(kNameSpaceID_None,
                                  nsGkAtoms::mozasyncpanzoom,
                                  nsGkAtoms::_true,
                                  eCaseMatters)) {
     scrollingBehavior = ASYNC_PAN_ZOOM;
   }
+
+  bool rv = true;
   if (ownApp) {
-    context.SetTabContextForAppFrame(ownApp, containingApp, scrollingBehavior);
+    rv = context.SetTabContextForAppFrame(ownApp, containingApp, scrollingBehavior);
   } else if (OwnerIsBrowserFrame()) {
     // The |else| above is unnecessary; OwnerIsBrowserFrame() implies !ownApp.
-    context.SetTabContextForBrowserFrame(containingApp, scrollingBehavior);
+    rv = context.SetTabContextForBrowserFrame(containingApp, scrollingBehavior);
   }
+  NS_ENSURE_TRUE(rv, false);
 
   nsCOMPtr<Element> ownerElement = mOwnerContent;
   mRemoteBrowser = ContentParent::CreateBrowserOrApp(context, ownerElement);
   if (mRemoteBrowser) {
     nsCOMPtr<nsIDocShellTreeItem> rootItem;
     parentAsItem->GetRootTreeItem(getter_AddRefs(rootItem));
     nsCOMPtr<nsIDOMWindow> rootWin = do_GetInterface(rootItem);
     nsCOMPtr<nsIDOMChromeWindow> rootChromeWin = do_QueryInterface(rootWin);
--- a/dom/ipc/ContentChild.cpp
+++ b/dom/ipc/ContentChild.cpp
@@ -586,17 +586,25 @@ ContentChild::DeallocPJavaScriptChild(PJ
 PBrowserChild*
 ContentChild::AllocPBrowserChild(const IPCTabContext& aContext,
                                  const uint32_t& aChromeFlags)
 {
     // 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(this, TabContext(aContext), aChromeFlags);
+    MaybeInvalidTabContext tc(aContext);
+    if (!tc.IsValid()) {
+        NS_ERROR(nsPrintfCString("Received an invalid TabContext from "
+                                 "the parent process. (%s)  Crashing...",
+                                 tc.InvalidReason()));
+        MOZ_CRASH("Invalid TabContext received from the parent process.");
+    }
+
+    nsRefPtr<TabChild> child = TabChild::Create(this, tc.GetTabContext(), aChromeFlags);
 
     // The ref here is released in DeallocPBrowserChild.
     return child.forget().get();
 }
 
 bool
 ContentChild::RecvPBrowserConstructor(PBrowserChild* actor,
                                       const IPCTabContext& context,
--- a/dom/ipc/ContentParent.cpp
+++ b/dom/ipc/ContentParent.cpp
@@ -1752,17 +1752,25 @@ ContentParent::AllocPBrowserParent(const
     // 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 AllocPBrowserParent.");
         return nullptr;
     }
 
-    TabParent* parent = new TabParent(this, TabContext(aContext));
+    MaybeInvalidTabContext tc(aContext);
+    if (!tc.IsValid()) {
+        NS_ERROR(nsPrintfCString("Child passed us an invalid TabContext.  (%s)  "
+                                 "Aborting AllocPBrowserParent.",
+                                 tc.InvalidReason()));
+        return nullptr;
+    }
+
+    TabParent* parent = new TabParent(this, tc.GetTabContext());
 
     // We release this ref in DeallocPBrowserParent()
     NS_ADDREF(parent);
     return parent;
 }
 
 bool
 ContentParent::DeallocPBrowserParent(PBrowserParent* frame)
--- a/dom/ipc/TabContext.cpp
+++ b/dom/ipc/TabContext.cpp
@@ -4,110 +4,33 @@
  * 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"
 
+#define NO_APP_ID (nsIScriptSecurityManager::NO_APP_ID)
+
 using namespace mozilla::dom::ipc;
 using namespace mozilla::layout;
 
 namespace mozilla {
 namespace dom {
 
 TabContext::TabContext()
   : mInitialized(false)
-  , mOwnAppId(nsIScriptSecurityManager::NO_APP_ID)
-  , mContainingAppId(nsIScriptSecurityManager::NO_APP_ID)
+  , mOwnAppId(NO_APP_ID)
+  , mContainingAppId(NO_APP_ID)
   , mScrollingBehavior(DEFAULT_SCROLLING)
   , mIsBrowser(false)
 {
 }
 
-TabContext::TabContext(const IPCTabContext& aParams)
-  : mInitialized(true)
-{
-  const IPCTabAppBrowserContext& appBrowser = aParams.appBrowserContext();
-  switch(appBrowser.type()) {
-    case IPCTabAppBrowserContext::TPopupIPCTabContext: {
-      const PopupIPCTabContext &ipcContext = appBrowser.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();
-      }
-
-      // Browser elements can't nest other browser elements.  So if
-      // our opener is browser element, we must be a new DOM window
-      // opened by it.  In that case we inherit our containing app ID
-      // (if any).
-      //
-      // Otherwise, we're a new app window and we inherit from our
-      // opener app.
-      if (ipcContext.isBrowserElement()) {
-        mIsBrowser = true;
-        mOwnAppId = nsIScriptSecurityManager::NO_APP_ID;
-        mContainingAppId = context->OwnOrContainingAppId();
-      }
-      else {
-        mIsBrowser = false;
-        mOwnAppId = context->mOwnAppId;
-        mContainingAppId = context->mContainingAppId;
-      }
-      break;
-    }
-    case IPCTabAppBrowserContext::TAppFrameIPCTabContext: {
-      const AppFrameIPCTabContext &ipcContext =
-        appBrowser.get_AppFrameIPCTabContext();
-
-      mIsBrowser = false;
-      mOwnAppId = ipcContext.ownAppId();
-      mContainingAppId = ipcContext.appFrameOwnerAppId();
-      break;
-    }
-    case IPCTabAppBrowserContext::TBrowserFrameIPCTabContext: {
-      const BrowserFrameIPCTabContext &ipcContext =
-        appBrowser.get_BrowserFrameIPCTabContext();
-
-      mIsBrowser = true;
-      mOwnAppId = nsIScriptSecurityManager::NO_APP_ID;
-      mContainingAppId = ipcContext.browserFrameOwnerAppId();
-      break;
-    }
-    case IPCTabAppBrowserContext::TVanillaFrameIPCTabContext: {
-      mIsBrowser = false;
-      mOwnAppId = nsIScriptSecurityManager::NO_APP_ID;
-      mContainingAppId = nsIScriptSecurityManager::NO_APP_ID;
-      break;
-    }
-    default: {
-      MOZ_CRASH();
-    }
-  }
-
-  mScrollingBehavior = aParams.scrollingBehavior();
-}
-
 bool
 TabContext::IsBrowserElement() const
 {
   return mIsBrowser;
 }
 
 bool
 TabContext::IsBrowserOrApp() const
@@ -119,216 +42,325 @@ uint32_t
 TabContext::OwnAppId() const
 {
   return mOwnAppId;
 }
 
 already_AddRefed<mozIApplication>
 TabContext::GetOwnApp() const
 {
-  return GetAppForId(OwnAppId());
+  nsCOMPtr<mozIApplication> ownApp = mOwnApp;
+  return ownApp.forget();
 }
 
 bool
 TabContext::HasOwnApp() const
 {
-  return mOwnAppId != nsIScriptSecurityManager::NO_APP_ID;
+  nsCOMPtr<mozIApplication> ownApp = GetOwnApp();
+  return !!ownApp;
 }
 
 uint32_t
 TabContext::BrowserOwnerAppId() const
 {
-  if (mIsBrowser) {
+  if (IsBrowserElement()) {
     return mContainingAppId;
   }
-  return nsIScriptSecurityManager::NO_APP_ID;
+  return NO_APP_ID;
 }
 
 already_AddRefed<mozIApplication>
 TabContext::GetBrowserOwnerApp() const
 {
-  return GetAppForId(BrowserOwnerAppId());
+  nsCOMPtr<mozIApplication> ownerApp;
+  if (IsBrowserElement()) {
+    ownerApp = mContainingApp;
+  }
+  return ownerApp.forget();
 }
 
 bool
 TabContext::HasBrowserOwnerApp() const
 {
-  return BrowserOwnerAppId() != nsIScriptSecurityManager::NO_APP_ID;
+  nsCOMPtr<mozIApplication> ownerApp = GetBrowserOwnerApp();
+  return !!ownerApp;
 }
 
 uint32_t
 TabContext::AppOwnerAppId() const
 {
-  if (mOwnAppId != nsIScriptSecurityManager::NO_APP_ID) {
+  if (HasOwnApp()) {
     return mContainingAppId;
   }
-  return nsIScriptSecurityManager::NO_APP_ID;
+  return NO_APP_ID;
 }
 
 already_AddRefed<mozIApplication>
 TabContext::GetAppOwnerApp() const
 {
-  return GetAppForId(AppOwnerAppId());
+  nsCOMPtr<mozIApplication> ownerApp;
+  if (HasOwnApp()) {
+    ownerApp = mContainingApp;
+  }
+  return ownerApp.forget();
 }
 
 bool
 TabContext::HasAppOwnerApp() const
 {
-  return AppOwnerAppId() != nsIScriptSecurityManager::NO_APP_ID;
+  nsCOMPtr<mozIApplication> ownerApp = GetAppOwnerApp();
+  return !!ownerApp;
 }
 
 uint32_t
 TabContext::OwnOrContainingAppId() const
 {
-  if (mIsBrowser) {
-    MOZ_ASSERT(mOwnAppId == nsIScriptSecurityManager::NO_APP_ID);
-    return mContainingAppId;
-  }
-
-  if (mOwnAppId) {
+  if (HasOwnApp()) {
     return mOwnAppId;
   }
 
   return mContainingAppId;
 }
 
 already_AddRefed<mozIApplication>
 TabContext::GetOwnOrContainingApp() const
 {
-  return GetAppForId(OwnOrContainingAppId());
+  nsCOMPtr<mozIApplication> ownOrContainingApp;
+  if (HasOwnApp()) {
+    ownOrContainingApp = mOwnApp;
+  } else {
+    ownOrContainingApp = mContainingApp;
+  }
+
+  return ownOrContainingApp.forget();
 }
 
 bool
 TabContext::HasOwnOrContainingApp() const
 {
-  return OwnOrContainingAppId() != nsIScriptSecurityManager::NO_APP_ID;
+  nsCOMPtr<mozIApplication> ownOrContainingApp = GetOwnOrContainingApp();
+  return !!ownOrContainingApp;
 }
 
 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);
-  }
+  *this = aContext;
+  mInitialized = true;
 
-  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;
-  mScrollingBehavior = aContext.mScrollingBehavior;
   return true;
 }
 
 bool
-TabContext::SetTabContextForAppFrame(mozIApplication* aOwnApp, mozIApplication* aAppFrameOwnerApp,
+TabContext::SetTabContextForAppFrame(mozIApplication* aOwnApp,
+                                     mozIApplication* aAppFrameOwnerApp,
                                      ScrollingBehavior aRequestedBehavior)
 {
   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;
+  uint32_t ownAppId = NO_APP_ID;
   if (aOwnApp) {
     nsresult rv = aOwnApp->GetLocalId(&ownAppId);
     NS_ENSURE_SUCCESS(rv, false);
+    NS_ENSURE_TRUE(ownAppId != NO_APP_ID, false);
   }
 
-  uint32_t containingAppId = nsIScriptSecurityManager::NO_APP_ID;
+  uint32_t containingAppId = NO_APP_ID;
   if (aAppFrameOwnerApp) {
     nsresult rv = aOwnApp->GetLocalId(&containingAppId);
     NS_ENSURE_SUCCESS(rv, false);
+    NS_ENSURE_TRUE(containingAppId != NO_APP_ID, false);
   }
 
   mInitialized = true;
   mIsBrowser = false;
   mOwnAppId = ownAppId;
   mContainingAppId = containingAppId;
   mScrollingBehavior = aRequestedBehavior;
+  mOwnApp = aOwnApp;
+  mContainingApp = aAppFrameOwnerApp;
   return true;
 }
 
 bool
 TabContext::SetTabContextForBrowserFrame(mozIApplication* aBrowserFrameOwnerApp,
                                          ScrollingBehavior aRequestedBehavior)
 {
   NS_ENSURE_FALSE(mInitialized, false);
 
-  uint32_t containingAppId = nsIScriptSecurityManager::NO_APP_ID;
+  uint32_t containingAppId = NO_APP_ID;
   if (aBrowserFrameOwnerApp) {
     nsresult rv = aBrowserFrameOwnerApp->GetLocalId(&containingAppId);
     NS_ENSURE_SUCCESS(rv, false);
+    NS_ENSURE_TRUE(containingAppId != NO_APP_ID, false);
   }
 
   mInitialized = true;
   mIsBrowser = true;
-  mOwnAppId = nsIScriptSecurityManager::NO_APP_ID;
+  mOwnAppId = NO_APP_ID;
   mContainingAppId = containingAppId;
   mScrollingBehavior = aRequestedBehavior;
+  mContainingApp = aBrowserFrameOwnerApp;
   return true;
 }
 
 IPCTabContext
 TabContext::AsIPCTabContext() const
 {
   if (mIsBrowser) {
     return IPCTabContext(BrowserFrameIPCTabContext(mContainingAppId),
                          mScrollingBehavior);
   }
 
   return IPCTabContext(AppFrameIPCTabContext(mOwnAppId, mContainingAppId),
                        mScrollingBehavior);
 }
 
-already_AddRefed<mozIApplication>
-TabContext::GetAppForId(uint32_t aAppId) const
-{
-  if (aAppId == nsIScriptSecurityManager::NO_APP_ID) {
-    return nullptr;
-  }
-
-  // This application caching is needed to avoid numerous unecessary application clones.
-  // See Bug 853632 for details.
-
-  if (aAppId == mOwnAppId) {
-    if (!mOwnApp) {
-      mOwnApp = GetAppForIdNoCache(aAppId);
-    }
-    nsCOMPtr<mozIApplication> ownApp = mOwnApp;
-    return ownApp.forget();
-  }
-
-  if (aAppId == mContainingAppId) {
-    if (!mContainingApp) {
-      mContainingApp = GetAppForIdNoCache(mContainingAppId);
-    }
-    nsCOMPtr<mozIApplication> containingApp = mContainingApp;
-    return containingApp.forget();
-  }
-  // We need the fallthrough here because mOwnAppId/mContainingAppId aren't always
-  // set before calling GetAppForId().
-  return GetAppForIdNoCache(aAppId);
-}
-
-already_AddRefed<mozIApplication>
-TabContext::GetAppForIdNoCache(uint32_t aAppId) const
+static already_AddRefed<mozIApplication>
+GetAppForId(uint32_t aAppId)
 {
   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();
 }
 
+MaybeInvalidTabContext::MaybeInvalidTabContext(const IPCTabContext& aParams)
+  : mInvalidReason(nullptr)
+{
+  bool isBrowser = false;
+  uint32_t ownAppId = NO_APP_ID;
+  uint32_t containingAppId = NO_APP_ID;
+
+  const IPCTabAppBrowserContext& appBrowser = aParams.appBrowserContext();
+  switch(appBrowser.type()) {
+    case IPCTabAppBrowserContext::TPopupIPCTabContext: {
+      const PopupIPCTabContext &ipcContext = appBrowser.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.
+          mInvalidReason = "Child is-browser process tried to "
+                           "open a non-browser tab.";
+          return;
+        }
+      } else if (ipcContext.openerChild()) {
+        context = static_cast<TabChild*>(ipcContext.openerChild());
+      } else {
+        // This should be unreachable because PopupIPCTabContext::opener is not a
+        // nullable field.
+        mInvalidReason = "PopupIPCTabContext::opener was null (?!).";
+        return;
+      }
+
+      // Browser elements can't nest other browser elements.  So if
+      // our opener is browser element, we must be a new DOM window
+      // opened by it.  In that case we inherit our containing app ID
+      // (if any).
+      //
+      // Otherwise, we're a new app window and we inherit from our
+      // opener app.
+      if (ipcContext.isBrowserElement()) {
+        isBrowser = true;
+        ownAppId = NO_APP_ID;
+        containingAppId = context->OwnOrContainingAppId();
+      } else {
+        isBrowser = false;
+        ownAppId = context->mOwnAppId;
+        containingAppId = context->mContainingAppId;
+      }
+      break;
+    }
+    case IPCTabAppBrowserContext::TAppFrameIPCTabContext: {
+      const AppFrameIPCTabContext &ipcContext =
+        appBrowser.get_AppFrameIPCTabContext();
+
+      isBrowser = false;
+      ownAppId = ipcContext.ownAppId();
+      containingAppId = ipcContext.appFrameOwnerAppId();
+      break;
+    }
+    case IPCTabAppBrowserContext::TBrowserFrameIPCTabContext: {
+      const BrowserFrameIPCTabContext &ipcContext =
+        appBrowser.get_BrowserFrameIPCTabContext();
+
+      isBrowser = true;
+      ownAppId = NO_APP_ID;
+      containingAppId = ipcContext.browserFrameOwnerAppId();
+      break;
+    }
+    case IPCTabAppBrowserContext::TVanillaFrameIPCTabContext: {
+      isBrowser = false;
+      ownAppId = NO_APP_ID;
+      containingAppId = NO_APP_ID;
+      break;
+    }
+    default: {
+      MOZ_CRASH();
+    }
+  }
+
+  nsCOMPtr<mozIApplication> ownApp = GetAppForId(ownAppId);
+  if (ownApp == nullptr !=
+      ownAppId == NO_APP_ID) {
+    mInvalidReason = "Got an ownAppId that didn't correspond to an app.";
+    return;
+  }
+
+  nsCOMPtr<mozIApplication> containingApp = GetAppForId(containingAppId);
+  if (containingApp == nullptr !=
+      containingAppId == NO_APP_ID) {
+    mInvalidReason = "Got a containingAppId that didn't correspond to an app.";
+    return;
+  }
+
+  bool rv;
+  if (isBrowser) {
+    rv = mTabContext.SetTabContextForBrowserFrame(containingApp,
+                                                  aParams.scrollingBehavior());
+  } else {
+    rv = mTabContext.SetTabContextForAppFrame(ownApp,
+                                              containingApp,
+                                              aParams.scrollingBehavior());
+  }
+
+  if (!rv) {
+    mInvalidReason = "Couldn't initialize TabContext.";
+  }
+}
+
+bool
+MaybeInvalidTabContext::IsValid()
+{
+  return mInvalidReason == nullptr;
+}
+
+const char*
+MaybeInvalidTabContext::GetInvalidReason()
+{
+  return mInvalidReason;
+}
+
+const TabContext&
+MaybeInvalidTabContext::GetTabContext()
+{
+  if (!IsValid()) {
+    MOZ_CRASH("Can't GetTabContext() if !IsValid().");
+  }
+
+  return mTabContext;
+}
+
 } // namespace dom
 } // namespace mozilla
--- a/dom/ipc/TabContext.h
+++ b/dom/ipc/TabContext.h
@@ -30,34 +30,19 @@ namespace dom {
  * also MutableTabContext.
  */
 class TabContext
 {
 protected:
   typedef mozilla::layout::ScrollingBehavior ScrollingBehavior;
 
 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);
+  /* (The implicit copy-constructor and operator= are fine.) */
 
   /**
    * Generates IPCTabContext of type BrowserFrameIPCTabContext or
    * AppFrameIPCTabContext from this TabContext's information.
    */
   IPCTabContext AsIPCTabContext() const;
 
   /**
@@ -123,16 +108,18 @@ public:
   bool HasOwnOrContainingApp() const;
 
   /**
    * Return the requested scrolling behavior for this frame.
    */
   ScrollingBehavior GetScrollingBehavior() const { return mScrollingBehavior; }
 
 protected:
+  friend class MaybeInvalidTabContext;
+
   /**
    * 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.
    */
@@ -154,74 +141,61 @@ protected:
    * Set this TabContext to be a browser frame inside the given app (which may
    * be null).
    */
   bool SetTabContextForBrowserFrame(mozIApplication* aBrowserFrameOwnerApp,
                                     ScrollingBehavior aRequestedBehavior);
 
 private:
   /**
-   * Translate an appId into a mozIApplication, using lazy caching.
-   */
-  already_AddRefed<mozIApplication> GetAppForId(uint32_t aAppId) const;
-
-  /**
-   * Translate an appId into a mozIApplication.
-   */
-  already_AddRefed<mozIApplication> GetAppForIdNoCache(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.
+   * This TabContext's own app.  If this is non-null, then this
+   * TabContext corresponds to an app, and mIsBrowser must be false.
+   */
+  nsCOMPtr<mozIApplication> mOwnApp;
+
+  /**
+   * A cache of mOwnApp->GetLocalId().  Speed really does matter here, since we
+   * read this ID often during process startup.
    */
   uint32_t mOwnAppId;
 
   /**
-   * Cache of this TabContext's own app.  If mOwnAppId is NO_APP_ID, this is
-   * guaranteed to be nullptr.  Otherwise, it may or may not be null.
+   * This TabContext's containing app.  If mIsBrowser, this corresponds to the
+   * app which contains the browser frame; otherwise, this corresponds to the
+   * app which contains the app frame.
    */
-  mutable nsCOMPtr<mozIApplication> mOwnApp;
+  nsCOMPtr<mozIApplication> mContainingApp;
 
-  /**
-   * 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.
+  /*
+   * Cache of mContainingApp->GetLocalId().
    */
   uint32_t mContainingAppId;
 
   /**
-   * Cache of the app that contains this TabContext's frame.  If mContainingAppId
-   * is NO_APP_ID, this is guaranteed to be nullptr.  Otherwise, it may or may not
-   * be null.
-   */
-  mutable nsCOMPtr<mozIApplication> mContainingApp;
-
-  /**
    * The requested scrolling behavior for this frame.
    */
   ScrollingBehavior mScrollingBehavior;
 
   /**
    * Does this TabContext correspond to a browser element?
    *
-   * If this is true, mOwnAppId must be NO_APP_ID.
+   * If this is true, mOwnApp must be null.
    */
   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.
+ * MutableTabContext is the same as MaybeInvalidTabContext, 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);
   }
@@ -236,12 +210,71 @@ public:
   bool SetTabContextForBrowserFrame(mozIApplication* aBrowserFrameOwnerApp,
                                     ScrollingBehavior aRequestedBehavior)
   {
     return TabContext::SetTabContextForBrowserFrame(aBrowserFrameOwnerApp,
                                                     aRequestedBehavior);
   }
 };
 
+/**
+ * MaybeInvalidTabContext is a simple class that lets you transform an
+ * IPCTabContext into a TabContext.
+ *
+ * The issue is that an IPCTabContext is not necessarily valid; for example, it
+ * might specify an app-id which doesn't exist.  So to convert an IPCTabContext
+ * into a TabContext, you construct a MaybeInvalidTabContext, check whether it's
+ * valid, and, if so, read out your TabContext.
+ *
+ * Example usage:
+ *
+ *   void UseTabContext(const TabContext& aTabContext);
+ *
+ *   void CreateTab(const IPCTabContext& aContext) {
+ *     MaybeInvalidTabContext tc(aContext);
+ *     if (!tc.IsValid()) {
+ *       NS_ERROR(nsPrintfCString("Got an invalid IPCTabContext: %s",
+ *                                tc.GetInvalidReason()));
+ *       return;
+ *     }
+ *     UseTabContext(tc.GetTabContext());
+ *   }
+ */
+class MaybeInvalidTabContext
+{
+public:
+  /**
+   * This constructor copies the information in aContext and sets IsValid() as
+   * appropriate.
+   */
+  MaybeInvalidTabContext(const IPCTabContext& aContext);
+
+  /**
+   * Was the IPCTabContext we received in our constructor valid?
+   */
+  bool IsValid();
+
+  /**
+   * If IsValid(), this function returns null.  Otherwise, it returns a
+   * human-readable string indicating why the IPCTabContext passed to our
+   * constructor was not valid.
+   */
+  const char* GetInvalidReason();
+
+  /**
+   * If IsValid(), this function returns a reference to a TabContext
+   * corresponding to the IPCTabContext passed to our constructor.  If
+   * !IsValid(), this function crashes.
+   */
+  const TabContext& GetTabContext();
+
+private:
+  MaybeInvalidTabContext(const MaybeInvalidTabContext&) MOZ_DELETE;
+  MaybeInvalidTabContext& operator=(const MaybeInvalidTabContext&) MOZ_DELETE;
+
+  const char* mInvalidReason;
+  MutableTabContext mTabContext;
+};
+
 } // namespace dom
 } // namespace mozilla
 
 #endif