Bug 1523843 part 2. Use a single compartment for same-origin Realms in a single page (toplevel load). r=bholley
authorBoris Zbarsky <bzbarsky@mit.edu>
Thu, 21 Feb 2019 22:56:30 +0000
changeset 518470 1b528e3cac94793db32568726080b6cc1209b0e7
parent 518469 190cd12fb84edeb908239595b78578cffe507d70
child 518471 c486cc10016330abb5376c103172910084836298
push id10862
push userffxbld-merge
push dateMon, 11 Mar 2019 13:01:11 +0000
treeherdermozilla-beta@a2e7f5c935da [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbholley
bugs1523843
milestone67.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 1523843 part 2. Use a single compartment for same-origin Realms in a single page (toplevel load). r=bholley Differential Revision: https://phabricator.services.mozilla.com/D19799
dom/base/nsGlobalWindowOuter.cpp
js/xpconnect/src/XPCWrappedNativeScope.cpp
js/xpconnect/src/xpcprivate.h
--- a/dom/base/nsGlobalWindowOuter.cpp
+++ b/dom/base/nsGlobalWindowOuter.cpp
@@ -1781,18 +1781,55 @@ static bool InitializeLegacyNetscapeObje
 
   /* Define PrivilegeManager object with the necessary "static" methods. */
   obj = JS_DefineObject(aCx, obj, "PrivilegeManager", nullptr);
   NS_ENSURE_TRUE(obj, false);
 
   return JS_DefineFunctions(aCx, obj, EnablePrivilegeSpec);
 }
 
+struct MOZ_STACK_CLASS CompartmentFinderState {
+  explicit CompartmentFinderState(nsIPrincipal* aPrincipal)
+      : principal(aPrincipal), compartment(nullptr) {}
+
+  // Input: we look for a compartment which is same-origin with the
+  // given principal.
+  nsIPrincipal* principal;
+
+  // Output: We set this member if we find a compartment.
+  JS::Compartment* compartment;
+};
+
+static JS::CompartmentIterResult FindSameOriginCompartment(
+    JSContext* aCx, void* aData, JS::Compartment* aCompartment) {
+  auto* data = static_cast<CompartmentFinderState*>(aData);
+  MOZ_ASSERT(!data->compartment, "Why are we getting called?");
+
+  // If this compartment is not safe to share across globals, don't do
+  // anything with it; in particular we should not be getting a
+  // CompartmentPrivate from such a compartment, because it may be in
+  // the middle of being collected and its CompartmentPrivate may no
+  // longer be valid.
+  if (!js::IsSharableCompartment(aCompartment)) {
+    return JS::CompartmentIterResult::KeepGoing;
+  }
+
+  auto* compartmentPrivate = xpc::CompartmentPrivate::Get(aCompartment);
+  if (!compartmentPrivate->CanShareCompartmentWith(data->principal)) {
+    // Can't reuse this one, keep going.
+    return JS::CompartmentIterResult::KeepGoing;
+  }
+
+  // We have a winner!
+  data->compartment = aCompartment;
+  return JS::CompartmentIterResult::Stop;
+}
+
 static JS::RealmCreationOptions& SelectZone(
-    nsIPrincipal* aPrincipal, nsGlobalWindowInner* aNewInner,
+    JSContext* aCx, nsIPrincipal* aPrincipal, nsGlobalWindowInner* aNewInner,
     JS::RealmCreationOptions& aOptions) {
   // Use the shared system compartment for chrome windows.
   if (nsContentUtils::IsSystemPrincipal(aPrincipal)) {
     return aOptions.setExistingCompartment(xpc::PrivilegedJunkScope());
   }
 
   if (aNewInner->GetOuterWindow()) {
     nsGlobalWindowOuter* top = aNewInner->GetTopInternal();
@@ -1800,16 +1837,24 @@ static JS::RealmCreationOptions& SelectZ
       // We're a toplevel load.  Use a new zone.  This way, when we do
       // zone-based compartment sharing we won't share compartments
       // across navigations.
       return aOptions.setNewCompartmentAndZone();
     }
 
     // If we have a top-level window, use its zone.
     if (top && top->GetGlobalJSObject()) {
+      JS::Zone* zone = JS::GetObjectZone(top->GetGlobalJSObject());
+      // Now try to find an existing compartment that's same-origin
+      // with our principal.
+      CompartmentFinderState data(aPrincipal);
+      JS_IterateCompartmentsInZone(aCx, zone, &data, FindSameOriginCompartment);
+      if (data.compartment) {
+        return aOptions.setExistingCompartment(data.compartment);
+      }
       return aOptions.setNewCompartmentInExistingZone(top->GetGlobalJSObject());
     }
   }
 
   return aOptions.setNewCompartmentAndZone();
 }
 
 /**
@@ -1829,17 +1874,17 @@ static nsresult CreateNativeGlobalForInn
 
   // DOMWindow with nsEP is not supported, we have to make sure
   // no one creates one accidentally.
   nsCOMPtr<nsIExpandedPrincipal> nsEP = do_QueryInterface(aPrincipal);
   MOZ_RELEASE_ASSERT(!nsEP, "DOMWindow with nsEP is not supported");
 
   JS::RealmOptions options;
 
-  SelectZone(aPrincipal, aNewInner, options.creationOptions());
+  SelectZone(aCx, aPrincipal, aNewInner, options.creationOptions());
 
   options.creationOptions().setSecureContext(aIsSecureContext);
 
   xpc::InitGlobalObjectOptions(options, aPrincipal);
 
   // Determine if we need the Components object.
   bool needComponents = nsContentUtils::IsSystemPrincipal(aPrincipal) ||
                         TreatAsRemoteXUL(aPrincipal);
--- a/js/xpconnect/src/XPCWrappedNativeScope.cpp
+++ b/js/xpconnect/src/XPCWrappedNativeScope.cpp
@@ -23,42 +23,46 @@ using namespace mozilla;
 using namespace xpc;
 using namespace JS;
 
 /***************************************************************************/
 
 XPCWrappedNativeScope* XPCWrappedNativeScope::gScopes = nullptr;
 XPCWrappedNativeScope* XPCWrappedNativeScope::gDyingScopes = nullptr;
 
+static bool RemoteXULForbidsXBLScopeForPrincipal(nsIPrincipal* aPrincipal) {
+  // AllowXULXBLForPrincipal will return true for system principal, but we
+  // don't want that here.
+  MOZ_ASSERT(nsContentUtils::IsInitialized());
+  if (aPrincipal->IsSystemPrincipal()) {
+    return false;
+  }
+
+  // If this domain isn't whitelisted, we're done.
+  if (!nsContentUtils::AllowXULXBLForPrincipal(aPrincipal)) {
+    return false;
+  }
+
+  // Check the pref to determine how we should behave.
+  return !Preferences::GetBool("dom.use_xbl_scopes_for_remote_xul", false);
+}
+
 static bool RemoteXULForbidsXBLScope(HandleObject aFirstGlobal) {
   MOZ_ASSERT(aFirstGlobal);
 
   // Certain singleton sandoxes are created very early in startup - too early
   // to call into AllowXULXBLForPrincipal. We never create XBL scopes for
   // sandboxes anway, and certainly not for these singleton scopes. So we just
   // short-circuit here.
   if (IsSandbox(aFirstGlobal)) {
     return false;
   }
 
-  // AllowXULXBLForPrincipal will return true for system principal, but we
-  // don't want that here.
   nsIPrincipal* principal = xpc::GetObjectPrincipal(aFirstGlobal);
-  MOZ_ASSERT(nsContentUtils::IsInitialized());
-  if (nsContentUtils::IsSystemPrincipal(principal)) {
-    return false;
-  }
-
-  // If this domain isn't whitelisted, we're done.
-  if (!nsContentUtils::AllowXULXBLForPrincipal(principal)) {
-    return false;
-  }
-
-  // Check the pref to determine how we should behave.
-  return !Preferences::GetBool("dom.use_xbl_scopes_for_remote_xul", false);
+  return RemoteXULForbidsXBLScopeForPrincipal(principal);
 }
 
 XPCWrappedNativeScope::XPCWrappedNativeScope(JS::Compartment* aCompartment,
                                              JS::HandleObject aFirstGlobal)
     : mWrappedNativeMap(Native2WrappedNativeMap::newMap(XPC_NATIVE_MAP_LENGTH)),
       mWrappedNativeProtoMap(
           ClassInfo2WrappedNativeProtoMap::newMap(XPC_NATIVE_PROTO_MAP_LENGTH)),
       mComponents(nullptr),
@@ -203,16 +207,21 @@ JSObject* XPCWrappedNativeScope::EnsureC
                     "nsXBLPrototypeScript compilation scope"));
 
   // We can probably remove EnsureContentXBLScope and clean up all its callers,
   // but a bunch (all?) of those callers will just go away when we remove XBL
   // support, so it's simpler to just leave it here as a no-op.
   return global;
 }
 
+bool XPCWrappedNativeScope::XBLScopeStateMatches(nsIPrincipal* aPrincipal) {
+  return mAllowContentXBLScope ==
+         !RemoteXULForbidsXBLScopeForPrincipal(aPrincipal);
+}
+
 bool XPCWrappedNativeScope::AllowContentXBLScope(Realm* aRealm) {
   // We only disallow XBL scopes in remote XUL situations.
   MOZ_ASSERT_IF(!mAllowContentXBLScope, nsContentUtils::AllowXULXBLForPrincipal(
                                             xpc::GetRealmPrincipal(aRealm)));
   return mAllowContentXBLScope;
 }
 
 namespace xpc {
--- a/js/xpconnect/src/xpcprivate.h
+++ b/js/xpconnect/src/xpcprivate.h
@@ -889,16 +889,21 @@ class XPCWrappedNativeScope final {
   static bool IsDyingScope(XPCWrappedNativeScope* scope);
 
   // Gets the appropriate scope object for XBL in this compartment. This method
   // relies on compartment-per-global still (and release-asserts this). The
   // context must be same-realm with this compartment's single global upon
   // entering, and the scope object is wrapped into this compartment.
   JSObject* EnsureContentXBLScope(JSContext* cx);
 
+  // Check whether our mAllowContentXBLScope state matches the given
+  // principal.  This is used to avoid sharing compartments on
+  // mismatch.
+  bool XBLScopeStateMatches(nsIPrincipal* aPrincipal);
+
   XPCWrappedNativeScope(JS::Compartment* aCompartment,
                         JS::HandleObject aFirstGlobal);
 
   nsAutoPtr<JSObject2JSObjectMap> mWaiverWrapperMap;
 
   JS::Compartment* Compartment() const { return mCompartment; }
 
   // Returns the global to use for new WrappedNative objects allocated in this
@@ -2681,16 +2686,28 @@ class CompartmentPrivate {
     return Get(compartment);
   }
 
   static CompartmentPrivate* Get(JSObject* object) {
     JS::Compartment* compartment = js::GetObjectCompartment(object);
     return Get(compartment);
   }
 
+  bool CanShareCompartmentWith(nsIPrincipal* principal) {
+    // Only share if we're same-origin with the principal.
+    if (!originInfo.IsSameOrigin(principal)) {
+      return false;
+    }
+
+    // Don't share if we have any weird state set.
+    return !wantXrays && !isWebExtensionContentScript &&
+           !isContentXBLCompartment && !isUAWidgetCompartment &&
+           !universalXPConnectEnabled && scope->XBLScopeStateMatches(principal);
+  }
+
   CompartmentOriginInfo originInfo;
 
   // Our XPCWrappedNativeScope.
   XPCWrappedNativeScope* scope;
 
   // Controls whether this compartment gets Xrays to same-origin. This behavior
   // is deprecated, but is still the default for sandboxes for compatibity
   // reasons.