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 492061 416aff73b2ee3d7ef3cb04aebea8f335058bfe71
parent 492047 b4d300bcaf99c1f3170eb97fb53deb7e75d46a0d
child 492062 bff55a91da7c7685a4fb7b4db7e65d7db91940da
push id9984
push userffxbld-merge
push dateMon, 15 Oct 2018 21:07:35 +0000
treeherdermozilla-beta@183d27ea8570 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbholley
bugs1487032
milestone64.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 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