Bug 1487032 - Store origin/site info in CompartmentPrivate. r=bholley
authorJan de Mooij <jdemooij@mozilla.com>
Tue, 11 Sep 2018 09:01:14 +0000
changeset 484727 416aff73b2ee3d7ef3cb04aebea8f335058bfe71
parent 484652 b4d300bcaf99c1f3170eb97fb53deb7e75d46a0d
child 484728 bff55a91da7c7685a4fb7b4db7e65d7db91940da
push id241
push userfmarier@mozilla.com
push dateMon, 24 Sep 2018 21:48:02 +0000
reviewersbholley
bugs1487032
milestone64.0a1
Bug 1487032 - Store origin/site info in CompartmentPrivate. r=bholley This will let us answer the following questions (in a performant way): 1) What's the compartment's origin? Necessary to implement compartment-per-origin. 2) What's the origin's site? Necessary for the new Wrap() algorithm. 3) Has any realm in the compartment set document.domain? Necessary for the new Wrap() algorithm. Differential Revision: https://phabricator.services.mozilla.com/D5423
caps/BasePrincipal.cpp
caps/BasePrincipal.h
caps/ContentPrincipal.cpp
caps/ContentPrincipal.h
caps/ExpandedPrincipal.cpp
caps/ExpandedPrincipal.h
caps/NullPrincipal.h
caps/SystemPrincipal.h
dom/bindings/BindingUtils.cpp
js/src/gc/PublicIterators.cpp
js/src/jsapi.h
js/xpconnect/src/XPCJSRuntime.cpp
js/xpconnect/src/XPCWrappedNativeScope.cpp
js/xpconnect/src/nsXPConnect.cpp
js/xpconnect/src/xpcprivate.h
js/xpconnect/src/xpcpublic.h
--- a/caps/BasePrincipal.cpp
+++ b/caps/BasePrincipal.cpp
@@ -529,9 +529,17 @@ BasePrincipal::FinishInit(const nsACStri
   nsAutoCString originSuffix;
   mOriginAttributes.CreateSuffix(originSuffix);
   mOriginSuffix = NS_Atomize(originSuffix);
 
   MOZ_ASSERT(!aOriginNoSuffix.IsEmpty());
   mOriginNoSuffix = NS_Atomize(aOriginNoSuffix);
 }
 
+bool
+SiteIdentifier::Equals(const SiteIdentifier& aOther) const
+{
+  MOZ_ASSERT(IsInitialized());
+  MOZ_ASSERT(aOther.IsInitialized());
+  return mPrincipal->FastEquals(aOther.mPrincipal);
+}
+
 } // namespace mozilla
--- a/caps/BasePrincipal.h
+++ b/caps/BasePrincipal.h
@@ -20,16 +20,49 @@ class nsIURI;
 
 class ExpandedPrincipal;
 
 namespace mozilla {
 namespace extensions {
   class WebExtensionPolicy;
 }
 
+class BasePrincipal;
+
+// Codebase principals (and codebase principals embedded within expanded
+// principals) stored in SiteIdentifier are guaranteed to contain only the
+// eTLD+1 part of the original domain. This is used to determine whether two
+// origins are same-site: if it's possible for two origins to access each other
+// (maybe after mutating document.domain), then they must have the same site
+// identifier.
+class SiteIdentifier
+{
+public:
+  void Init(BasePrincipal* aPrincipal)
+  {
+    MOZ_ASSERT(aPrincipal);
+    mPrincipal = aPrincipal;
+  }
+
+  bool IsInitialized() const { return !!mPrincipal; }
+
+  bool Equals(const SiteIdentifier& aOther) const;
+
+private:
+  friend class ::ExpandedPrincipal;
+
+  BasePrincipal* GetPrincipal() const
+  {
+    MOZ_ASSERT(IsInitialized());
+    return mPrincipal;
+  }
+
+  RefPtr<BasePrincipal> mPrincipal;
+};
+
 /*
  * Base class from which all nsIPrincipal implementations inherit. Use this for
  * default implementations and other commonalities between principal
  * implementations.
  *
  * We should merge nsJSPrincipals into this class at some point.
  */
 class BasePrincipal : public nsJSPrincipals
@@ -161,16 +194,18 @@ public:
     // This is primarily for the sake of their stylesheets, which are usually
     // loaded from channels and cannot have expanded principals.
     return (AddonPolicy() &&
             !BasePrincipal::Cast(aDocumentPrincipal)->AddonPolicy());
   }
 
   uint32_t GetOriginNoSuffixHash() const { return mOriginNoSuffix->hash(); }
 
+  virtual nsresult GetSiteIdentifier(SiteIdentifier& aSite) = 0;
+
 protected:
   virtual ~BasePrincipal();
 
   // Note that this does not check OriginAttributes. Callers that depend on
   // those must call Subsumes instead.
   virtual bool SubsumesInternal(nsIPrincipal* aOther, DocumentDomainConsideration aConsider) = 0;
 
   // Internal, side-effect-free check to determine whether the concrete
--- a/caps/ContentPrincipal.cpp
+++ b/caps/ContentPrincipal.cpp
@@ -362,98 +362,128 @@ ContentPrincipal::SetDomain(nsIURI* aDom
   JSPrincipals *principals = nsJSPrincipals::get(static_cast<nsIPrincipal*>(this));
   bool success = js::RecomputeWrappers(cx, js::ContentCompartmentsOnly(),
                                        js::CompartmentsWithPrincipals(principals));
   NS_ENSURE_TRUE(success, NS_ERROR_FAILURE);
   success = js::RecomputeWrappers(cx, js::CompartmentsWithPrincipals(principals),
                                   js::ContentCompartmentsOnly());
   NS_ENSURE_TRUE(success, NS_ERROR_FAILURE);
 
+  // Set the changed-document-domain flag on compartments containing realms
+  // using this principal.
+  auto cb = [](JSContext*, void*, JS::Handle<JS::Realm*> aRealm) {
+    JS::Compartment* comp = JS::GetCompartmentForRealm(aRealm);
+    xpc::SetCompartmentChangedDocumentDomain(comp);
+  };
+  JS::IterateRealmsWithPrincipals(cx, principals, nullptr, cb);
+
   return NS_OK;
 }
 
-NS_IMETHODIMP
-ContentPrincipal::GetBaseDomain(nsACString& aBaseDomain)
+static nsresult
+GetBaseDomainHelper(const nsCOMPtr<nsIURI>& aCodebase,
+                    bool* aHasBaseDomain,
+                    nsACString& aBaseDomain)
 {
+  *aHasBaseDomain = false;
+
   // For a file URI, we return the file path.
-  if (NS_URIIsLocalFile(mCodebase)) {
-    nsCOMPtr<nsIURL> url = do_QueryInterface(mCodebase);
+  if (NS_URIIsLocalFile(aCodebase)) {
+    nsCOMPtr<nsIURL> url = do_QueryInterface(aCodebase);
 
     if (url) {
       return url->GetFilePath(aBaseDomain);
     }
   }
 
   bool hasNoRelativeFlag;
-  nsresult rv = NS_URIChainHasFlags(mCodebase,
+  nsresult rv = NS_URIChainHasFlags(aCodebase,
                                     nsIProtocolHandler::URI_NORELATIVE,
                                     &hasNoRelativeFlag);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
   if (hasNoRelativeFlag) {
-    return mCodebase->GetSpec(aBaseDomain);
+    return aCodebase->GetSpec(aBaseDomain);
   }
 
+  *aHasBaseDomain = true;
+
   // For everything else, we ask the TLD service via
   // the ThirdPartyUtil.
   nsCOMPtr<mozIThirdPartyUtil> thirdPartyUtil =
     do_GetService(THIRDPARTYUTIL_CONTRACTID);
   if (thirdPartyUtil) {
-    return thirdPartyUtil->GetBaseDomain(mCodebase, aBaseDomain);
+    return thirdPartyUtil->GetBaseDomain(aCodebase, aBaseDomain);
   }
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
+ContentPrincipal::GetBaseDomain(nsACString& aBaseDomain)
+{
+  bool hasBaseDomain;
+  return GetBaseDomainHelper(mCodebase, &hasBaseDomain, aBaseDomain);
+}
+
+NS_IMETHODIMP
 ContentPrincipal::GetSiteOrigin(nsACString& aSiteOrigin)
 {
-  // Get the eTLDService & determine our base domain. If we don't have a valid
-  // BaseDomain, we can fall-back to GetOrigin.
-  nsCOMPtr<nsIEffectiveTLDService> tldService =
-    do_GetService(NS_EFFECTIVETLDSERVICE_CONTRACTID);
-  if (NS_WARN_IF(!tldService)) {
-    return GetOrigin(aSiteOrigin);
-  }
+  // Determine our base domain.
+  bool hasBaseDomain;
+  nsAutoCString baseDomain;
+  nsresult rv = GetBaseDomainHelper(mCodebase, &hasBaseDomain, baseDomain);
+  NS_ENSURE_SUCCESS(rv, rv);
 
-  nsAutoCString baseDomain;
-  nsresult rv = tldService->GetBaseDomain(mCodebase, 0, baseDomain);
-  if (NS_FAILED(rv)) {
+  if (!hasBaseDomain) {
+    // This is a special URI ("file:", "about:", "view-source:", etc). Just
+    // return the origin.
     return GetOrigin(aSiteOrigin);
   }
 
   // NOTE: Calling `SetHostPort` with a portless domain is insufficient to clear
   // the port, so an extra `SetPort` call has to be made.
   nsCOMPtr<nsIURI> siteUri;
   rv = NS_MutateURI(mCodebase)
     .SetUserPass(EmptyCString())
     .SetPort(-1)
     .SetHostPort(baseDomain)
     .Finalize(siteUri);
   MOZ_ASSERT(NS_SUCCEEDED(rv), "failed to create siteUri");
-  if (NS_FAILED(rv)) {
-    return GetOrigin(aSiteOrigin);
-  }
+  NS_ENSURE_SUCCESS(rv, rv);
 
   rv = GenerateOriginNoSuffixFromURI(siteUri, aSiteOrigin);
   MOZ_ASSERT(NS_SUCCEEDED(rv), "failed to create siteOriginNoSuffix");
-  if (NS_FAILED(rv)) {
-    return GetOrigin(aSiteOrigin);
-  }
+  NS_ENSURE_SUCCESS(rv, rv);
 
   nsAutoCString suffix;
   rv = GetOriginSuffix(suffix);
   MOZ_ASSERT(NS_SUCCEEDED(rv), "failed to create suffix");
-  if (NS_FAILED(rv)) {
-    return GetOrigin(aSiteOrigin);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  aSiteOrigin.Append(suffix);
+  return NS_OK;
+}
+
+nsresult
+ContentPrincipal::GetSiteIdentifier(SiteIdentifier& aSite)
+{
+  nsCString siteOrigin;
+  nsresult rv = GetSiteOrigin(siteOrigin);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  RefPtr<BasePrincipal> principal = CreateCodebasePrincipal(siteOrigin);
+  if (!principal) {
+    NS_WARNING("could not instantiate codebase principal");
+    return NS_ERROR_FAILURE;
   }
 
-  aSiteOrigin.Append(suffix);
+  aSite.Init(principal);
   return NS_OK;
 }
 
 WebExtensionPolicy*
 ContentPrincipal::AddonPolicy()
 {
   if (!mAddon.isSome()) {
     NS_ENSURE_TRUE(mCodebase, nullptr);
--- a/caps/ContentPrincipal.h
+++ b/caps/ContentPrincipal.h
@@ -38,16 +38,18 @@ public:
 
   // Init() must be called before the principal is in a usable state.
   nsresult Init(nsIURI* aCodebase,
                 const OriginAttributes& aOriginAttributes,
                 const nsACString& aOriginNoSuffix);
 
   virtual nsresult GetScriptLocation(nsACString& aStr) override;
 
+  nsresult GetSiteIdentifier(SiteIdentifier& aSite) override;
+
   static nsresult
   GenerateOriginNoSuffixFromURI(nsIURI* aURI, nsACString& aOrigin);
 
   extensions::WebExtensionPolicy* AddonPolicy();
 
   nsCOMPtr<nsIURI> mDomain;
   nsCOMPtr<nsIURI> mCodebase;
 
--- a/caps/ExpandedPrincipal.cpp
+++ b/caps/ExpandedPrincipal.cpp
@@ -304,8 +304,30 @@ ExpandedPrincipal::Write(nsIObjectOutput
     rv = aStream->WriteObject(principal, true);
     if (NS_FAILED(rv)) {
       return rv;
     }
   }
 
   return NS_OK;
 }
+
+nsresult
+ExpandedPrincipal::GetSiteIdentifier(SiteIdentifier& aSite)
+{
+  // Call GetSiteIdentifier on each of our principals and return a new
+  // ExpandedPrincipal.
+
+  nsTArray<nsCOMPtr<nsIPrincipal>> whitelist;
+  for (const auto& principal : mPrincipals) {
+    SiteIdentifier site;
+    nsresult rv = Cast(principal)->GetSiteIdentifier(site);
+    NS_ENSURE_SUCCESS(rv, rv);
+    whitelist.AppendElement(site.GetPrincipal());
+  }
+
+  RefPtr<ExpandedPrincipal> expandedPrincipal =
+    ExpandedPrincipal::Create(whitelist, OriginAttributesRef());
+  MOZ_ASSERT(expandedPrincipal, "ExpandedPrincipal::Create returned nullptr?");
+
+  aSite.Init(expandedPrincipal);
+  return NS_OK;
+}
--- a/caps/ExpandedPrincipal.h
+++ b/caps/ExpandedPrincipal.h
@@ -42,16 +42,18 @@ public:
   virtual nsresult GetScriptLocation(nsACString &aStr) override;
 
   bool AddonAllowsLoad(nsIURI* aURI, bool aExplicit = false);
 
   // Returns the principal to inherit when this principal requests the given
   // URL. See BasePrincipal::PrincipalToInherit.
   nsIPrincipal* PrincipalToInherit(nsIURI* aRequestedURI = nullptr);
 
+  nsresult GetSiteIdentifier(mozilla::SiteIdentifier& aSite) override;
+
 protected:
   explicit ExpandedPrincipal(nsTArray<nsCOMPtr<nsIPrincipal>> &aWhiteList);
 
   virtual ~ExpandedPrincipal();
 
   bool SubsumesInternal(nsIPrincipal* aOther,
                         DocumentDomainConsideration aConsideration) override;
 
--- a/caps/NullPrincipal.h
+++ b/caps/NullPrincipal.h
@@ -72,16 +72,21 @@ public:
   static already_AddRefed<NullPrincipal>
   CreateWithoutOriginAttributes();
 
   nsresult Init(const OriginAttributes& aOriginAttributes = OriginAttributes(),
                 nsIURI* aURI = nullptr);
 
   virtual nsresult GetScriptLocation(nsACString &aStr) override;
 
+  nsresult GetSiteIdentifier(SiteIdentifier& aSite) override {
+    aSite.Init(this);
+    return NS_OK;
+  }
+
  protected:
   virtual ~NullPrincipal() = default;
 
   bool SubsumesInternal(nsIPrincipal* aOther, DocumentDomainConsideration aConsideration) override
   {
     return aOther == this;
   }
 
--- a/caps/SystemPrincipal.h
+++ b/caps/SystemPrincipal.h
@@ -44,16 +44,21 @@ public:
   NS_IMETHOD EnsureCSP(nsIDocument* aDocument, nsIContentSecurityPolicy** aCSP) override;
   NS_IMETHOD GetPreloadCsp(nsIContentSecurityPolicy** aPreloadCSP) override;
   NS_IMETHOD EnsurePreloadCSP(nsIDocument* aDocument, nsIContentSecurityPolicy** aCSP) override;
   NS_IMETHOD GetBaseDomain(nsACString& aBaseDomain) override;
   NS_IMETHOD GetAddonId(nsAString& aAddonId) override;
 
   virtual nsresult GetScriptLocation(nsACString &aStr) override;
 
+  nsresult GetSiteIdentifier(SiteIdentifier& aSite) override {
+    aSite.Init(this);
+    return NS_OK;
+  }
+
 protected:
   virtual ~SystemPrincipal(void) {}
 
   bool SubsumesInternal(nsIPrincipal *aOther,
                         DocumentDomainConsideration aConsideration) override
   {
     return true;
   }
--- a/dom/bindings/BindingUtils.cpp
+++ b/dom/bindings/BindingUtils.cpp
@@ -3372,19 +3372,29 @@ CreateGlobalOptionsWithXPConnect::TraceG
   xpc::TraceXPCGlobal(aTrc, aObj);
 }
 
 /* static */
 bool
 CreateGlobalOptionsWithXPConnect::PostCreateGlobal(JSContext* aCx,
                                                    JS::Handle<JSObject*> aGlobal)
 {
+  JSPrincipals* principals =
+    JS::GetRealmPrincipals(js::GetNonCCWObjectRealm(aGlobal));
+  nsIPrincipal* principal = nsJSPrincipals::get(principals);
+
+  // We create the SiteIdentifier here instead of in the XPCWrappedNativeScope
+  // constructor because this is fallible.
+  SiteIdentifier site;
+  nsresult rv = BasePrincipal::Cast(principal)->GetSiteIdentifier(site);
+  NS_ENSURE_SUCCESS(rv, false);
+
   // Invoking the XPCWrappedNativeScope constructor automatically hooks it
-  // up to the compartment of aGlobal.
-  (void) new XPCWrappedNativeScope(aCx, aGlobal);
+  // up to the realm of aGlobal.
+  (void) new XPCWrappedNativeScope(aCx, aGlobal, site);
   return true;
 }
 
 static bool sRegisteredDOMNames = false;
 
 static void
 RegisterDOMNames()
 {
--- a/js/src/gc/PublicIterators.cpp
+++ b/js/src/gc/PublicIterators.cpp
@@ -235,16 +235,34 @@ JS::IterateRealms(JSContext* cx, void* d
     Rooted<Realm*> realm(cx);
     for (RealmsIter r(cx->runtime()); !r.done(); r.next()) {
         realm = r;
         (*realmCallback)(cx, data, realm);
     }
 }
 
 JS_PUBLIC_API(void)
+JS::IterateRealmsWithPrincipals(JSContext* cx, JSPrincipals* principals, void* data,
+                                JS::IterateRealmCallback realmCallback)
+{
+    MOZ_ASSERT(principals);
+
+    AutoTraceSession session(cx->runtime());
+
+    Rooted<Realm*> realm(cx);
+    for (RealmsIter r(cx->runtime()); !r.done(); r.next()) {
+        if (r->principals() != principals) {
+            continue;
+        }
+        realm = r;
+        (*realmCallback)(cx, data, realm);
+    }
+}
+
+JS_PUBLIC_API(void)
 JS::IterateRealmsInCompartment(JSContext* cx, JS::Compartment* compartment, void* data,
                                JS::IterateRealmCallback realmCallback)
 {
     AutoTraceSession session(cx->runtime());
 
     Rooted<Realm*> realm(cx);
     for (RealmsInCompartmentIter r(compartment); !r.done(); r.next()) {
         realm = r;
--- a/js/src/jsapi.h
+++ b/js/src/jsapi.h
@@ -899,16 +899,23 @@ using IterateRealmCallback = void (*)(JS
  * This function calls |realmCallback| on every realm. Beware that there is no
  * guarantee that the realm will survive after the callback returns. Also,
  * barriers are disabled via the TraceSession.
  */
 extern JS_PUBLIC_API(void)
 IterateRealms(JSContext* cx, void* data, IterateRealmCallback realmCallback);
 
 /**
+ * Like IterateRealms, but only call the callback for realms using |principals|.
+ */
+extern JS_PUBLIC_API(void)
+IterateRealmsWithPrincipals(JSContext* cx, JSPrincipals* principals, void* data,
+                            IterateRealmCallback realmCallback);
+
+/**
  * Like IterateRealms, but only iterates realms in |compartment|.
  */
 extern JS_PUBLIC_API(void)
 IterateRealmsInCompartment(JSContext* cx, JS::Compartment* compartment, void* data,
                            IterateRealmCallback realmCallback);
 
 } // namespace JS
 
--- a/js/xpconnect/src/XPCJSRuntime.cpp
+++ b/js/xpconnect/src/XPCJSRuntime.cpp
@@ -175,18 +175,20 @@ public:
 public:
   bool mContinuation;
   bool mActive;
   bool mPurge;
 };
 
 namespace xpc {
 
-CompartmentPrivate::CompartmentPrivate(JS::Compartment* c)
-    : wantXrays(false)
+CompartmentPrivate::CompartmentPrivate(JS::Compartment* c, mozilla::BasePrincipal* origin,
+                                       const SiteIdentifier& site)
+    : originInfo(origin, site)
+    , wantXrays(false)
     , allowWaivers(true)
     , isWebExtensionContentScript(false)
     , allowCPOWs(false)
     , isContentXBLCompartment(false)
     , isUAWidgetCompartment(false)
     , isSandboxCompartment(false)
     , isAddonCompartment(false)
     , universalXPConnectEnabled(false)
@@ -564,16 +566,30 @@ EnableUniversalXPConnect(JSContext* cx)
     XPCWrappedNativeScope* scope = RealmPrivate::Get(realm)->scope;
     if (!scope) {
         return true;
     }
     scope->ForcePrivilegedComponents();
     return scope->AttachComponentsObject(cx);
 }
 
+bool
+CompartmentOriginInfo::IsSameOrigin(nsIPrincipal* aOther) const
+{
+    return mOrigin->FastEquals(aOther);
+}
+
+void
+SetCompartmentChangedDocumentDomain(JS::Compartment* compartment)
+{
+    CompartmentPrivate* priv = CompartmentPrivate::Get(compartment);
+    MOZ_ASSERT(priv);
+    priv->originInfo.SetChangedDocumentDomain();
+}
+
 JSObject*
 UnprivilegedJunkScope()
 {
     return XPCJSRuntime::Get()->UnprivilegedJunkScope();
 }
 
 JSObject*
 PrivilegedJunkScope()
--- a/js/xpconnect/src/XPCWrappedNativeScope.cpp
+++ b/js/xpconnect/src/XPCWrappedNativeScope.cpp
@@ -52,17 +52,18 @@ RemoteXULForbidsXBLScope(nsIPrincipal* a
       return false;
   }
 
   // Check the pref to determine how we should behave.
   return !Preferences::GetBool("dom.use_xbl_scopes_for_remote_xul", false);
 }
 
 XPCWrappedNativeScope::XPCWrappedNativeScope(JSContext* cx,
-                                             JS::HandleObject aGlobal)
+                                             JS::HandleObject aGlobal,
+                                             const mozilla::SiteIdentifier& aSite)
       : mWrappedNativeMap(Native2WrappedNativeMap::newMap(XPC_NATIVE_MAP_LENGTH)),
         mWrappedNativeProtoMap(ClassInfo2WrappedNativeProtoMap::newMap(XPC_NATIVE_PROTO_MAP_LENGTH)),
         mComponents(nullptr),
         mNext(nullptr),
         mGlobalJSObject(aGlobal)
 {
     // add ourselves to the scopes list
     {
@@ -78,32 +79,34 @@ XPCWrappedNativeScope::XPCWrappedNativeS
 #endif
 
         mNext = gScopes;
         gScopes = this;
     }
 
     MOZ_COUNT_CTOR(XPCWrappedNativeScope);
 
+    nsIPrincipal* principal = GetPrincipal();
+
     // Create the compartment private.
     JS::Compartment* c = js::GetObjectCompartment(aGlobal);
     MOZ_ASSERT(!JS_GetCompartmentPrivate(c));
-    CompartmentPrivate* priv = new CompartmentPrivate(c);
+    CompartmentPrivate* priv =
+        new CompartmentPrivate(c, BasePrincipal::Cast(principal), aSite);
     JS_SetCompartmentPrivate(c, priv);
 
     // Attach ourselves to the realm private.
     Realm* realm = JS::GetObjectRealmOrNull(aGlobal);
     RealmPrivate* realmPriv = new RealmPrivate(realm);
     realmPriv->scope = this;
     JS::SetRealmPrivate(realm, realmPriv);
 
     // Determine whether we would allow an XBL scope in this situation.
     // In addition to being pref-controlled, we also disable XBL scopes for
     // remote XUL domains, _except_ if we have an additional pref override set.
-    nsIPrincipal* principal = GetPrincipal();
     mAllowContentXBLScope = !RemoteXULForbidsXBLScope(principal, aGlobal);
 
     // Determine whether to use an XBL scope.
     mUseContentXBLScope = mAllowContentXBLScope;
     if (mUseContentXBLScope) {
         const js::Class* clasp = js::GetObjectClass(mGlobalJSObject);
         mUseContentXBLScope = !strcmp(clasp->name, "Window");
     }
--- a/js/xpconnect/src/nsXPConnect.cpp
+++ b/js/xpconnect/src/nsXPConnect.cpp
@@ -461,27 +461,31 @@ CreateGlobalObject(JSContext* cx, const 
                    JS::RealmOptions& aOptions)
 {
     MOZ_ASSERT(NS_IsMainThread(), "using a principal off the main thread?");
     MOZ_ASSERT(principal);
 
     MOZ_RELEASE_ASSERT(principal != nsContentUtils::GetNullSubjectPrincipal(),
                        "The null subject principal is getting inherited - fix that!");
 
+    SiteIdentifier site;
+    nsresult rv = BasePrincipal::Cast(principal)->GetSiteIdentifier(site);
+    NS_ENSURE_SUCCESS(rv, nullptr);
+
     RootedObject global(cx,
                         JS_NewGlobalObject(cx, clasp, nsJSPrincipals::get(principal),
                                            JS::DontFireOnNewGlobalHook, aOptions));
     if (!global) {
         return nullptr;
     }
     JSAutoRealm ar(cx, global);
 
-    // The constructor automatically attaches the scope to the compartment private
+    // The constructor automatically attaches the scope to the realm private
     // of |global|.
-    (void) new XPCWrappedNativeScope(cx, global);
+    (void) new XPCWrappedNativeScope(cx, global, site);
 
     if (clasp->flags & JSCLASS_DOM_GLOBAL) {
 #ifdef DEBUG
         // Verify that the right trace hook is called. Note that this doesn't
         // work right for wrapped globals, since the tracing situation there is
         // more complicated. Manual inspection shows that they do the right
         // thing.  Also note that we only check this for JSCLASS_DOM_GLOBAL
         // classes because xpc::TraceXPCGlobal won't call
--- a/js/xpconnect/src/xpcprivate.h
+++ b/js/xpconnect/src/xpcprivate.h
@@ -930,17 +930,18 @@ public:
     static bool
     IsDyingScope(XPCWrappedNativeScope* scope);
 
     // Gets the appropriate scope object for XBL in this scope. The context
     // must be same-compartment with the global upon entering, and the scope
     // object is wrapped into the compartment of the global.
     JSObject* EnsureContentXBLScope(JSContext* cx);
 
-    XPCWrappedNativeScope(JSContext* cx, JS::HandleObject aGlobal);
+    XPCWrappedNativeScope(JSContext* cx, JS::HandleObject aGlobal,
+                          const mozilla::SiteIdentifier& aSite);
 
     nsAutoPtr<JSObject2JSObjectMap> mWaiverWrapperMap;
 
     JS::Compartment* Compartment() const { return js::GetObjectCompartment(mGlobalJSObject); }
 
     bool IsContentXBLScope() { return xpc::IsContentXBLCompartment(Compartment()); }
     bool AllowContentXBLScope();
     bool UseContentXBLScope() { return mUseContentXBLScope; }
@@ -2909,43 +2910,91 @@ namespace xpc {
 
 enum WrapperDenialType {
     WrapperDenialForXray = 0,
     WrapperDenialForCOW,
     WrapperDenialTypeCount
 };
 bool ReportWrapperDenial(JSContext* cx, JS::HandleId id, WrapperDenialType type, const char* reason);
 
+class CompartmentOriginInfo
+{
+public:
+    CompartmentOriginInfo(const CompartmentOriginInfo&) = delete;
+
+    CompartmentOriginInfo(mozilla::BasePrincipal* aOrigin,
+                          const mozilla::SiteIdentifier& aSite)
+      : mOrigin(aOrigin)
+      , mSite(aSite)
+    {
+        MOZ_ASSERT(aOrigin);
+        MOZ_ASSERT(aSite.IsInitialized());
+    }
+
+    bool IsSameOrigin(nsIPrincipal* aOther) const;
+
+    const mozilla::SiteIdentifier& SiteRef() const {
+        return mSite;
+    }
+
+    bool HasChangedDocumentDomain() const {
+        return mChangedDocumentDomain;
+    }
+    void SetChangedDocumentDomain() {
+        mChangedDocumentDomain = true;
+    }
+
+private:
+    // All globals in the compartment must have this origin. Note that
+    // individual globals and principals can have their domain changed via
+    // document.domain, so this principal must not be used for things like
+    // subsumesConsideringDomain or equalsConsideringDomain. Use the realm's
+    // principal for that.
+    RefPtr<mozilla::BasePrincipal> mOrigin;
+
+    // In addition to the origin we also store the SiteIdentifier. When realms
+    // in different compartments can become same-origin (via document.domain),
+    // these compartments must have equal SiteIdentifiers. (This is derived from
+    // mOrigin but we cache it here for performance reasons.)
+    mozilla::SiteIdentifier mSite;
+
+    // True if any global in this compartment mutated document.domain.
+    bool mChangedDocumentDomain = false;
+};
+
 // The CompartmentPrivate contains XPConnect-specific stuff related to each JS
 // compartment. Since compartments are trust domains, this means mostly
 // information needed to select the right security policy for cross-compartment
 // wrappers.
 class CompartmentPrivate
 {
     CompartmentPrivate() = delete;
     CompartmentPrivate(const CompartmentPrivate&) = delete;
 
 public:
-    explicit CompartmentPrivate(JS::Compartment* c);
+    CompartmentPrivate(JS::Compartment* c, mozilla::BasePrincipal* origin,
+                       const mozilla::SiteIdentifier& site);
 
     ~CompartmentPrivate();
 
     static CompartmentPrivate* Get(JS::Compartment* compartment)
     {
         MOZ_ASSERT(compartment);
         void* priv = JS_GetCompartmentPrivate(compartment);
         return static_cast<CompartmentPrivate*>(priv);
     }
 
     static CompartmentPrivate* Get(JSObject* object)
     {
         JS::Compartment* compartment = js::GetObjectCompartment(object);
         return Get(compartment);
     }
 
+    CompartmentOriginInfo originInfo;
+
     // Controls whether this compartment gets Xrays to same-origin. This behavior
     // is deprecated, but is still the default for sandboxes for compatibity
     // reasons.
     bool wantXrays;
 
     // Controls whether this compartment is allowed to waive Xrays to content
     // that it subsumes. This should generally be true, except in cases where we
     // want to prevent code from depending on Xray Waivers (which might make it
--- a/js/xpconnect/src/xpcpublic.h
+++ b/js/xpconnect/src/xpcpublic.h
@@ -87,16 +87,18 @@ bool IsContentXBLScope(JS::Realm* realm)
 bool IsInContentXBLScope(JSObject* obj);
 
 bool IsUAWidgetCompartment(JS::Compartment* compartment);
 bool IsUAWidgetScope(JS::Realm* realm);
 bool IsInUAWidgetScope(JSObject* obj);
 
 bool IsInSandboxCompartment(JSObject* obj);
 
+void SetCompartmentChangedDocumentDomain(JS::Compartment* compartment);
+
 // Return a raw XBL scope object corresponding to contentScope, which must
 // be an object whose global is a DOM window.
 //
 // The return value is not wrapped into cx->compartment, so be sure to enter
 // its compartment before doing anything meaningful.
 //
 // Also note that XBL scopes are lazily created, so the return-value should be
 // null-checked unless the caller can ensure that the scope must already