Bug 1126014 - DomainPolicy support for e10s. r=mrbkap
authorGabor Krizsanits <gkrizsanits@mozilla.com>
Tue, 24 Mar 2015 15:29:16 +0100
changeset 235356 90d9af9b861a816df7da099934525c8ead0ae270
parent 235355 80be62413c65e036ca6c2a1e30c993edfb88fff0
child 235357 7386c130fb784279ee3e4805caa08a97d7e7058e
push id28471
push userkwierso@gmail.com
push dateWed, 25 Mar 2015 01:04:03 +0000
treeherdermozilla-central@515084cb28c0 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmrbkap
bugs1126014
milestone39.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 1126014 - DomainPolicy support for e10s. r=mrbkap
caps/DomainPolicy.cpp
caps/DomainPolicy.h
caps/nsIDomainPolicy.idl
caps/nsIScriptSecurityManager.idl
caps/nsScriptSecurityManager.cpp
dom/ipc/ContentChild.cpp
dom/ipc/ContentChild.h
dom/ipc/ContentParent.cpp
dom/ipc/ContentParent.h
dom/ipc/PContent.ipdl
dom/ipc/moz.build
dom/ipc/tests/browser.ini
dom/ipc/tests/browser_domainPolicy.js
dom/ipc/tests/file_disableScript.html
dom/ipc/tests/file_domainPolicy_base.html
--- a/caps/DomainPolicy.cpp
+++ b/caps/DomainPolicy.cpp
@@ -1,26 +1,59 @@
 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
 /* vim: set ts=4 et sw=4 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "DomainPolicy.h"
+#include "mozilla/dom/ContentParent.h"
+#include "mozilla/ipc/URIUtils.h"
+#include "mozilla/unused.h"
+#include "nsIMessageManager.h"
 #include "nsScriptSecurityManager.h"
 
 namespace mozilla {
 
+using namespace ipc;
+using namespace dom;
+
 NS_IMPL_ISUPPORTS(DomainPolicy, nsIDomainPolicy)
 
-DomainPolicy::DomainPolicy() : mBlacklist(new DomainSet())
-                             , mSuperBlacklist(new DomainSet())
-                             , mWhitelist(new DomainSet())
-                             , mSuperWhitelist(new DomainSet())
-{}
+static nsresult
+BroadcastDomainSetChange(DomainSetType aSetType, DomainSetChangeType aChangeType,
+                         nsIURI* aDomain = nullptr)
+{
+    MOZ_ASSERT(XRE_GetProcessType() == GeckoProcessType_Default,
+               "DomainPolicy should only be exposed to the chrome process.");
+
+    nsTArray<ContentParent*> parents;
+    ContentParent::GetAll(parents);
+    if (!parents.Length()) {
+       return NS_OK;
+    }
+
+    OptionalURIParams uri;
+    SerializeURI(aDomain, uri);
+
+    for (uint32_t i = 0; i < parents.Length(); i++) {
+        unused << parents[i]->SendDomainSetChanged(aSetType, aChangeType, uri);
+    }
+    return NS_OK;
+}
+
+DomainPolicy::DomainPolicy() : mBlacklist(new DomainSet(BLACKLIST))
+                             , mSuperBlacklist(new DomainSet(SUPER_BLACKLIST))
+                             , mWhitelist(new DomainSet(WHITELIST))
+                             , mSuperWhitelist(new DomainSet(SUPER_WHITELIST))
+{
+    if (XRE_GetProcessType() == GeckoProcessType_Default) {
+        BroadcastDomainSetChange(NO_TYPE, ACTIVATE_POLICY);
+    }
+}
 
 DomainPolicy::~DomainPolicy()
 {
     // The SSM holds a strong ref to the DomainPolicy until Deactivate() is
     // invoked, so we should never hit the destructor until that happens.
     MOZ_ASSERT(!mBlacklist && !mSuperBlacklist &&
                !mWhitelist && !mSuperWhitelist);
 }
@@ -70,20 +103,57 @@ DomainPolicy::Deactivate()
 
     // Null them out.
     mBlacklist = nullptr;
     mSuperBlacklist = nullptr;
     mWhitelist = nullptr;
     mSuperWhitelist = nullptr;
 
     // Inform the SSM.
-    nsScriptSecurityManager::GetScriptSecurityManager()->DeactivateDomainPolicy();
+    nsScriptSecurityManager* ssm = nsScriptSecurityManager::GetScriptSecurityManager();
+    if (ssm) {
+        ssm->DeactivateDomainPolicy();
+    }
+    if (XRE_GetProcessType() == GeckoProcessType_Default) {
+        BroadcastDomainSetChange(NO_TYPE, DEACTIVATE_POLICY);
+    }
     return NS_OK;
 }
 
+void
+DomainPolicy::CloneDomainPolicy(DomainPolicyClone* aClone)
+{
+    aClone->active() = true;
+    static_cast<DomainSet*>(mBlacklist.get())->CloneSet(&aClone->blacklist());
+    static_cast<DomainSet*>(mSuperBlacklist.get())->CloneSet(&aClone->superBlacklist());
+    static_cast<DomainSet*>(mWhitelist.get())->CloneSet(&aClone->whitelist());
+    static_cast<DomainSet*>(mSuperWhitelist.get())->CloneSet(&aClone->superWhitelist());
+}
+
+static
+void
+CopyURIs(const InfallibleTArray<URIParams>& aDomains, nsIDomainSet* aSet)
+{
+    for (uint32_t i = 0; i < aDomains.Length(); i++) {
+        nsCOMPtr<nsIURI> uri = DeserializeURI(aDomains[i]);
+        aSet->Add(uri);
+    }
+}
+
+void
+DomainPolicy::ApplyClone(DomainPolicyClone* aClone)
+{
+    nsCOMPtr<nsIDomainSet> list;
+
+    CopyURIs(aClone->blacklist(), mBlacklist);
+    CopyURIs(aClone->whitelist(), mWhitelist);
+    CopyURIs(aClone->superBlacklist(), mSuperBlacklist);
+    CopyURIs(aClone->superWhitelist(), mSuperWhitelist);
+}
+
 static already_AddRefed<nsIURI>
 GetCanonicalClone(nsIURI* aURI)
 {
     nsCOMPtr<nsIURI> clone;
     nsresult rv = aURI->Clone(getter_AddRefs(clone));
     NS_ENSURE_SUCCESS(rv, nullptr);
     rv = clone->SetUserPass(EmptyCString());
     NS_ENSURE_SUCCESS(rv, nullptr);
@@ -95,32 +165,41 @@ GetCanonicalClone(nsIURI* aURI)
 NS_IMPL_ISUPPORTS(DomainSet, nsIDomainSet)
 
 NS_IMETHODIMP
 DomainSet::Add(nsIURI* aDomain)
 {
     nsCOMPtr<nsIURI> clone = GetCanonicalClone(aDomain);
     NS_ENSURE_TRUE(clone, NS_ERROR_FAILURE);
     mHashTable.PutEntry(clone);
+    if (XRE_GetProcessType() == GeckoProcessType_Default)
+        return BroadcastDomainSetChange(mType, ADD_DOMAIN, aDomain);
+
     return NS_OK;
 }
 
 NS_IMETHODIMP
 DomainSet::Remove(nsIURI* aDomain)
 {
     nsCOMPtr<nsIURI> clone = GetCanonicalClone(aDomain);
     NS_ENSURE_TRUE(clone, NS_ERROR_FAILURE);
     mHashTable.RemoveEntry(clone);
+    if (XRE_GetProcessType() == GeckoProcessType_Default)
+        return BroadcastDomainSetChange(mType, REMOVE_DOMAIN, aDomain);
+
     return NS_OK;
 }
 
 NS_IMETHODIMP
 DomainSet::Clear()
 {
     mHashTable.Clear();
+    if (XRE_GetProcessType() == GeckoProcessType_Default)
+        return BroadcastDomainSetChange(mType, CLEAR_DOMAINS);
+
     return NS_OK;
 }
 
 NS_IMETHODIMP
 DomainSet::Contains(nsIURI* aDomain, bool* aContains)
 {
     *aContains = false;
     nsCOMPtr<nsIURI> clone = GetCanonicalClone(aDomain);
@@ -155,9 +234,36 @@ DomainSet::ContainsSuperDomain(nsIURI* a
         NS_ENSURE_SUCCESS(rv, rv);
     }
 
     // No match.
     return NS_OK;
 
 }
 
+NS_IMETHODIMP
+DomainSet::GetType(uint32_t* aType)
+{
+    *aType = mType;
+    return NS_OK;
+}
+
+static
+PLDHashOperator
+DomainEnumerator(nsURIHashKey* aEntry, void* aUserArg)
+{
+    InfallibleTArray<URIParams>* uris = static_cast<InfallibleTArray<URIParams>*>(aUserArg);
+    nsIURI* key = aEntry->GetKey();
+
+    URIParams uri;
+    SerializeURI(key, uri);
+
+    uris->AppendElement(uri);
+    return PL_DHASH_NEXT;
+}
+
+void
+DomainSet::CloneSet(InfallibleTArray<URIParams>* aDomains)
+{
+    mHashTable.EnumerateEntries(DomainEnumerator, aDomains);
+}
+
 } /* namespace mozilla */
--- a/caps/DomainPolicy.h
+++ b/caps/DomainPolicy.h
@@ -8,16 +8,40 @@
 #define DomainPolicy_h__
 
 #include "nsIDomainPolicy.h"
 #include "nsTHashtable.h"
 #include "nsURIHashKey.h"
 
 namespace mozilla {
 
+namespace dom {
+class nsIContentParent;
+};
+
+namespace ipc {
+class URIParams;
+};
+
+enum DomainSetChangeType{
+    ACTIVATE_POLICY,
+    DEACTIVATE_POLICY,
+    ADD_DOMAIN,
+    REMOVE_DOMAIN,
+    CLEAR_DOMAINS
+};
+
+enum DomainSetType{
+    NO_TYPE,
+    BLACKLIST,
+    SUPER_BLACKLIST,
+    WHITELIST,
+    SUPER_WHITELIST
+};
+
 class DomainPolicy : public nsIDomainPolicy
 {
 public:
     NS_DECL_ISUPPORTS
     NS_DECL_NSIDOMAINPOLICY
     DomainPolicy();
 
 private:
@@ -30,18 +54,23 @@ private:
 };
 
 class DomainSet : public nsIDomainSet
 {
 public:
     NS_DECL_ISUPPORTS
     NS_DECL_NSIDOMAINSET
 
-    DomainSet() {}
+    explicit DomainSet(DomainSetType aType)
+        : mType(aType)
+    {}
+
+    void CloneSet(InfallibleTArray<mozilla::ipc::URIParams>* aDomains);
 
 protected:
     virtual ~DomainSet() {}
     nsTHashtable<nsURIHashKey> mHashTable;
+    DomainSetType mType;
 };
 
 } /* namespace mozilla */
 
 #endif /* DomainPolicy_h__ */
--- a/caps/nsIDomainPolicy.idl
+++ b/caps/nsIDomainPolicy.idl
@@ -3,43 +3,61 @@
  * 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 "nsISupports.idl"
 
 interface nsIURI;
 interface nsIDomainSet;
 
+%{ C++
+namespace mozilla {
+namespace dom {
+class DomainPolicyClone;
+}
+}
+%}
+
+[ptr] native DomainPolicyClonePtr(mozilla::dom::DomainPolicyClone);
+
 /*
  * When a domain policy is instantiated by invoking activateDomainPolicy() on
  * nsIScriptSecurityManager, these domain sets are consulted when each new
  * global is created (they have no effect on already-created globals).
  * If javascript is globally enabled with |javascript.enabled|, the blacklists
  * are consulted. If globally disabled, the whitelists are consulted. Lookups
  * on blacklist and whitelist happen with contains(), and lookups on
  * superBlacklist and superWhitelist happen with containsSuperDomain().
  *
  * When deactivate() is invoked, the domain sets are emptied, and the
  * nsIDomainPolicy ceases to have any effect on the system.
  */
-[scriptable, builtinclass, uuid(27b10f54-f34b-42b7-8594-4348d3ad7953)]
+[scriptable, builtinclass, uuid(82b24a20-6701-4d40-a0f9-f5dc7321b555)]
 interface nsIDomainPolicy : nsISupports
 {
     readonly attribute nsIDomainSet blacklist;
     readonly attribute nsIDomainSet superBlacklist;
     readonly attribute nsIDomainSet whitelist;
     readonly attribute nsIDomainSet superWhitelist;
 
     void deactivate();
+
+    [noscript, notxpcom] void cloneDomainPolicy(in DomainPolicyClonePtr aClone);
+    [noscript, notxpcom] void applyClone(in DomainPolicyClonePtr aClone);
 };
 
-[scriptable, builtinclass, uuid(946a01ff-6525-4007-a2c2-447ebe1875d3)]
+[scriptable, builtinclass, uuid(665c981b-0a0f-4229-ac06-a826e02d4f69)]
 interface nsIDomainSet : nsISupports
 {
     /*
+     * The type of the set. See: DomainSetType
+     */
+    [noscript] readonly attribute uint32_t type;
+
+    /*
      * Add a domain to the set. No-op if it already exists.
      */
     void add(in nsIURI aDomain);
 
     /*
      * Remove a domain from the set. No-op if it doesn't exist.
      */
     void remove(in nsIURI aDomain);
--- a/caps/nsIScriptSecurityManager.idl
+++ b/caps/nsIScriptSecurityManager.idl
@@ -9,22 +9,29 @@ interface nsIURI;
 interface nsIChannel;
 interface nsIClassInfo;
 interface nsIDocShell;
 interface nsIDomainPolicy;
 interface nsILoadContext;
 
 %{ C++
 #include "jspubtd.h"
+
+namespace mozilla {
+namespace dom {
+class DomainPolicyClone;
+}
+}
 %}
 
 [ptr] native JSContextPtr(JSContext);
 [ptr] native JSObjectPtr(JSObject);
+[ptr] native DomainPolicyClonePtr(mozilla::dom::DomainPolicyClone);
 
-[scriptable, uuid(f649959d-dae3-4027-83fd-5b7f8c8a8815)]
+[scriptable, uuid(ba602ca6-dc7a-457e-a57a-ee5b343fd863)]
 interface nsIScriptSecurityManager : nsISupports
 {
     /**
      * For each of these hooks returning NS_OK means 'let the action continue'.
      * Returning an error code means 'veto the action'. XPConnect will return
      * false to the js engine if the action is vetoed. The implementor of this
      * interface is responsible for setting a JS exception into the JSContext
      * if that is appropriate.
@@ -236,16 +243,32 @@ interface nsIScriptSecurityManager : nsI
      * nsIDomainPolicy returned from activateDomainPolicy(). At this point,
      * domainPolicyActive becomes false again, and a new consumer may acquire
      * control of the system by invoking activateDomainPolicy().
      */
     nsIDomainPolicy activateDomainPolicy();
     readonly attribute boolean domainPolicyActive;
 
     /**
+     * Only the parent process can directly access domain policies, child
+     * processes only have a read-only mirror to the one in the parent.
+     * For child processes the mirror is updated via messages
+     * and ContentChild will hold the DomainPolicy by calling
+     * ActivateDomainPolicyInternal directly. New consumer to this
+     * function should not be addded.
+     */
+    [noscript] nsIDomainPolicy activateDomainPolicyInternal();
+
+    /**
+     * This function is for internal use only. Every time a child process is spawned, we
+     * must clone any active domain policies in the parent to the new child.
+     */
+    [noscript, notxpcom] void cloneDomainPolicy(in DomainPolicyClonePtr aClone);
+
+    /**
      * Query mechanism for the above policy.
      *
      * If domainPolicyEnabled is false, this simply returns the current value
      * of javascript.enabled. Otherwise, it returns the same value, but taking
      * the various blacklist/whitelist exceptions into account.
      */
     bool policyAllowsScript(in nsIURI aDomain);
 };
--- a/caps/nsScriptSecurityManager.cpp
+++ b/caps/nsScriptSecurityManager.cpp
@@ -1305,19 +1305,24 @@ nsresult nsScriptSecurityManager::Init()
     return NS_OK;
 }
 
 static StaticRefPtr<nsScriptSecurityManager> gScriptSecMan;
 
 nsScriptSecurityManager::~nsScriptSecurityManager(void)
 {
     Preferences::RemoveObservers(this, kObservedPrefs);
-    if (mDomainPolicy)
+    if (mDomainPolicy) {
         mDomainPolicy->Deactivate();
-    MOZ_ASSERT(!mDomainPolicy);
+    }
+    // ContentChild might hold a reference to the domain policy,
+    // and it might release it only after the security manager is
+    // gone. But we can still assert this for the main process.
+    MOZ_ASSERT_IF(XRE_GetProcessType() == GeckoProcessType_Default,
+                  !mDomainPolicy);
 }
 
 void
 nsScriptSecurityManager::Shutdown()
 {
     if (sRuntime) {
         JS_SetSecurityCallbacks(sRuntime, nullptr);
         JS_SetTrustedPrincipals(sRuntime, nullptr);
@@ -1523,16 +1528,26 @@ nsScriptSecurityManager::GetDomainPolicy
 {
     *aRv = !!mDomainPolicy;
     return NS_OK;
 }
 
 NS_IMETHODIMP
 nsScriptSecurityManager::ActivateDomainPolicy(nsIDomainPolicy** aRv)
 {
+    if (XRE_GetProcessType() != GeckoProcessType_Default) {
+        return NS_ERROR_SERVICE_NOT_AVAILABLE;
+    }
+
+    return ActivateDomainPolicyInternal(aRv);
+}
+
+NS_IMETHODIMP
+nsScriptSecurityManager::ActivateDomainPolicyInternal(nsIDomainPolicy** aRv)
+{
     // We only allow one domain policy at a time. The holder of the previous
     // policy must explicitly deactivate it first.
     if (mDomainPolicy) {
         return NS_ERROR_SERVICE_NOT_AVAILABLE;
     }
 
     mDomainPolicy = new DomainPolicy();
     nsCOMPtr<nsIDomainPolicy> ptr = mDomainPolicy;
@@ -1543,16 +1558,27 @@ nsScriptSecurityManager::ActivateDomainP
 // Intentionally non-scriptable. Script must have a reference to the
 // nsIDomainPolicy to deactivate it.
 void
 nsScriptSecurityManager::DeactivateDomainPolicy()
 {
     mDomainPolicy = nullptr;
 }
 
+void
+nsScriptSecurityManager::CloneDomainPolicy(DomainPolicyClone* aClone)
+{
+    MOZ_ASSERT(aClone);
+    if (mDomainPolicy) {
+        mDomainPolicy->CloneDomainPolicy(aClone);
+    } else {
+        aClone->active() = false;
+    }
+}
+
 NS_IMETHODIMP
 nsScriptSecurityManager::PolicyAllowsScript(nsIURI* aURI, bool *aRv)
 {
     nsresult rv;
 
     // Compute our rule. If we don't have any domain policy set up that might
     // provide exceptions to this rule, we're done.
     *aRv = mIsJavaScriptEnabled;
--- a/dom/ipc/ContentChild.cpp
+++ b/dom/ipc/ContentChild.cpp
@@ -23,16 +23,17 @@
 #ifdef ACCESSIBILITY
 #include "mozilla/a11y/DocAccessibleChild.h"
 #endif
 #include "mozilla/Preferences.h"
 #include "mozilla/ProcessHangMonitorIPC.h"
 #include "mozilla/docshell/OfflineCacheUpdateChild.h"
 #include "mozilla/dom/ContentBridgeChild.h"
 #include "mozilla/dom/ContentBridgeParent.h"
+#include "mozilla/dom/ContentParent.h"
 #include "mozilla/dom/DOMStorageIPC.h"
 #include "mozilla/dom/ExternalHelperAppChild.h"
 #include "mozilla/dom/PCrashReporterChild.h"
 #include "mozilla/dom/ProcessGlobal.h"
 #include "mozilla/dom/Promise.h"
 #include "mozilla/dom/asmjscache/AsmJSCache.h"
 #include "mozilla/dom/asmjscache/PAsmJSCacheEntryChild.h"
 #include "mozilla/dom/nsIContentChild.h"
@@ -105,18 +106,19 @@
 
 #include "nsChromeRegistryContent.h"
 #include "nsFrameMessageManager.h"
 
 #include "nsIGeolocationProvider.h"
 #include "mozilla/dom/PMemoryReportRequestChild.h"
 #include "mozilla/dom/PCycleCollectWithLogsChild.h"
 
+#include "nsIScriptSecurityManager.h"
+
 #ifdef MOZ_PERMISSIONS
-#include "nsIScriptSecurityManager.h"
 #include "nsPermission.h"
 #include "nsPermissionManager.h"
 #endif
 
 #include "PermissionMessageUtils.h"
 
 #if defined(MOZ_WIDGET_ANDROID)
 #include "APKOpen.h"
@@ -163,16 +165,17 @@
 
 #include "ProcessUtils.h"
 #include "StructuredCloneUtils.h"
 #include "URIUtils.h"
 #include "nsContentUtils.h"
 #include "nsIPrincipal.h"
 #include "nsDeviceStorage.h"
 #include "AudioChannelService.h"
+#include "DomainPolicy.h"
 #include "mozilla/dom/DataStoreService.h"
 #include "mozilla/dom/telephony/PTelephonyChild.h"
 #include "mozilla/dom/time/DateCacheCleaner.h"
 #include "mozilla/dom/voicemail/VoicemailIPCService.h"
 #include "mozilla/net/NeckoMessageUtils.h"
 #include "mozilla/RemoteSpellCheckEngineChild.h"
 
 using namespace mozilla;
@@ -781,19 +784,31 @@ ContentChild::InitXPCOM()
     }
 
     mConsoleListener = new ConsoleListener(this);
     if (NS_FAILED(svc->RegisterListener(mConsoleListener)))
         NS_WARNING("Couldn't register console listener for child process");
 
     bool isOffline;
     ClipboardCapabilities clipboardCaps;
-    SendGetXPCOMProcessAttributes(&isOffline, &mAvailableDictionaries, &clipboardCaps);
+    DomainPolicyClone domainPolicy;
+
+    SendGetXPCOMProcessAttributes(&isOffline, &mAvailableDictionaries, &clipboardCaps, &domainPolicy);
     RecvSetOffline(isOffline);
 
+    if (domainPolicy.active()) {
+        nsIScriptSecurityManager* ssm = nsContentUtils::GetSecurityManager();
+        MOZ_ASSERT(ssm);
+        ssm->ActivateDomainPolicyInternal(getter_AddRefs(mPolicy));
+        if (!mPolicy) {
+            MOZ_CRASH("Failed to activate domain policy.");
+        }
+        mPolicy->ApplyClone(&domainPolicy);
+    }
+
     nsCOMPtr<nsIClipboard> clipboard(do_GetService("@mozilla.org/widget/clipboard;1"));
     if (nsCOMPtr<nsIClipboardProxy> clipboardProxy = do_QueryInterface(clipboard)) {
         clipboardProxy->SetCapabilities(clipboardCaps);
     }
 
     DebugOnly<FileUpdateDispatcher*> observer = FileUpdateDispatcher::GetSingleton();
     NS_ASSERTION(observer, "FileUpdateDispatcher is null");
 
@@ -2591,18 +2606,92 @@ bool
 ContentChild::RecvAssociatePluginId(const uint32_t& aPluginId,
                                     const base::ProcessId& aProcessId)
 {
     plugins::PluginModuleContentParent::AssociatePluginId(aPluginId, aProcessId);
     return true;
 }
 
 bool
+ContentChild::RecvDomainSetChanged(const uint32_t& aSetType, const uint32_t& aChangeType,
+                                   const OptionalURIParams& aDomain)
+{
+    if (aChangeType == ACTIVATE_POLICY) {
+        if (mPolicy) {
+            return true;
+        }
+        nsIScriptSecurityManager* ssm = nsContentUtils::GetSecurityManager();
+        MOZ_ASSERT(ssm);
+        ssm->ActivateDomainPolicyInternal(getter_AddRefs(mPolicy));
+        return !!mPolicy;
+    } else if (!mPolicy) {
+        MOZ_ASSERT_UNREACHABLE("If the domain policy is not active yet,"
+                               " the first message should be ACTIVATE_POLICY");
+        return false;
+    }
+
+    NS_ENSURE_TRUE(mPolicy, false);
+
+    if (aChangeType == DEACTIVATE_POLICY) {
+        mPolicy->Deactivate();
+        mPolicy = nullptr;
+        return true;
+    }
+
+    nsCOMPtr<nsIDomainSet> set;
+    switch(aSetType) {
+        case BLACKLIST:
+            mPolicy->GetBlacklist(getter_AddRefs(set));
+            break;
+        case SUPER_BLACKLIST:
+            mPolicy->GetSuperBlacklist(getter_AddRefs(set));
+            break;
+        case WHITELIST:
+            mPolicy->GetWhitelist(getter_AddRefs(set));
+            break;
+        case SUPER_WHITELIST:
+            mPolicy->GetSuperWhitelist(getter_AddRefs(set));
+            break;
+        default:
+            NS_NOTREACHED("Unexpected setType");
+            return false;
+    }
+
+    MOZ_ASSERT(set);
+
+    nsCOMPtr<nsIURI> uri = DeserializeURI(aDomain);
+
+    switch(aChangeType) {
+        case ADD_DOMAIN:
+            NS_ENSURE_TRUE(uri, false);
+            set->Add(uri);
+            break;
+        case REMOVE_DOMAIN:
+            NS_ENSURE_TRUE(uri, false);
+            set->Remove(uri);
+            break;
+        case CLEAR_DOMAINS:
+            set->Clear();
+            break;
+        default:
+            NS_NOTREACHED("Unexpected changeType");
+            return false;
+    }
+
+    return true;
+}
+
+bool
 ContentChild::RecvShutdown()
 {
+    if (mPolicy) {
+        mPolicy->Deactivate();
+        mPolicy = nullptr;
+    }
+
     nsCOMPtr<nsIObserverService> os = services::GetObserverService();
     if (os) {
         os->NotifyObservers(this, "content-child-shutdown", nullptr);
     }
 
     GetIPCChannel()->SetAbortOnError(false);
 
     // Ignore errors here. If this fails, the parent will kill us after a
--- a/dom/ipc/ContentChild.h
+++ b/dom/ipc/ContentChild.h
@@ -19,16 +19,17 @@
 #include "nsWeakPtr.h"
 
 
 struct ChromePackage;
 class nsIDOMBlob;
 class nsIObserver;
 struct ResourceMapping;
 struct OverrideMapping;
+class nsIDomainPolicy;
 
 namespace mozilla {
 class RemoteSpellcheckEngineChild;
 
 namespace ipc {
 class OptionalURIParams;
 class PFileDescriptorSetChild;
 class URIParams;
@@ -379,16 +380,18 @@ public:
                                       const bool& aResult) override;
 
     virtual bool RecvStartProfiler(const uint32_t& aEntries,
                                    const double& aInterval,
                                    nsTArray<nsCString>&& aFeatures,
                                    nsTArray<nsCString>&& aThreadNameFilters) override;
     virtual bool RecvStopProfiler() override;
     virtual bool RecvGetProfile(nsCString* aProfile) override;
+    virtual bool RecvDomainSetChanged(const uint32_t& aSetType, const uint32_t& aChangeType,
+                                      const OptionalURIParams& aDomain) override;
     virtual bool RecvShutdown() override;
 
 #ifdef ANDROID
     gfxIntSize GetScreenSize() { return mScreenSize; }
 #endif
 
     // Get the directory for IndexedDB files. We query the parent for this and
     // cache the value
@@ -476,16 +479,18 @@ private:
     bool mIsForApp;
     bool mIsForBrowser;
     bool mCanOverrideProcessName;
     bool mIsAlive;
     nsString mProcessName;
 
     static ContentChild* sSingleton;
 
+    nsCOMPtr<nsIDomainPolicy> mPolicy;
+
     DISALLOW_EVIL_CONSTRUCTORS(ContentChild);
 };
 
 uint64_t
 NextWindowID();
 
 } // namespace dom
 } // namespace mozilla
--- a/dom/ipc/ContentParent.cpp
+++ b/dom/ipc/ContentParent.cpp
@@ -2777,18 +2777,19 @@ ContentParent::RecvAddNewProcess(const u
     for (size_t i = 0; i < numNuwaPrefUpdates; i++) {
         mozilla::unused << content->SendPreferenceUpdate(sNuwaPrefUpdates->ElementAt(i));
     }
 
     // Update offline settings.
     bool isOffline;
     InfallibleTArray<nsString> unusedDictionaries;
     ClipboardCapabilities clipboardCaps;
+    DomainPolicyClone domainPolicy;
     RecvGetXPCOMProcessAttributes(&isOffline, &unusedDictionaries,
-                                  &clipboardCaps);
+                                  &clipboardCaps, &domainPolicy);
     mozilla::unused << content->SendSetOffline(isOffline);
     MOZ_ASSERT(!clipboardCaps.supportsSelectionClipboard() &&
                !clipboardCaps.supportsFindClipboard(),
                "Unexpected values");
 
     PreallocatedProcessManager::PublishSpareProcess(content);
     return true;
 #else
@@ -3114,17 +3115,18 @@ ContentParent::RecvGetProcessAttributes(
     *aIsForBrowser = mIsForBrowser;
 
     return true;
 }
 
 bool
 ContentParent::RecvGetXPCOMProcessAttributes(bool* aIsOffline,
                                              InfallibleTArray<nsString>* dictionaries,
-                                             ClipboardCapabilities* clipboardCaps)
+                                             ClipboardCapabilities* clipboardCaps,
+                                             DomainPolicyClone* domainPolicy)
 {
     nsCOMPtr<nsIIOService> io(do_GetIOService());
     MOZ_ASSERT(io, "No IO service?");
     DebugOnly<nsresult> rv = io->GetOffline(aIsOffline);
     MOZ_ASSERT(NS_SUCCEEDED(rv), "Failed getting offline?");
 
     nsCOMPtr<nsISpellChecker> spellChecker(do_GetService(NS_SPELLCHECKER_CONTRACTID));
     MOZ_ASSERT(spellChecker, "No spell checker?");
@@ -3135,16 +3137,21 @@ ContentParent::RecvGetXPCOMProcessAttrib
     MOZ_ASSERT(clipboard, "No clipboard?");
 
     rv = clipboard->SupportsSelectionClipboard(&clipboardCaps->supportsSelectionClipboard());
     MOZ_ASSERT(NS_SUCCEEDED(rv));
 
     rv = clipboard->SupportsFindClipboard(&clipboardCaps->supportsFindClipboard());
     MOZ_ASSERT(NS_SUCCEEDED(rv));
 
+    // Let's copy the domain policy from the parent to the child (if it's active).
+    nsIScriptSecurityManager* ssm = nsContentUtils::GetSecurityManager();
+    NS_ENSURE_TRUE(ssm, false);
+    ssm->CloneDomainPolicy(domainPolicy);
+
     return true;
 }
 
 mozilla::jsipc::PJavaScriptParent *
 ContentParent::AllocPJavaScriptParent()
 {
     MOZ_ASSERT(!ManagedPJavaScriptParent().Length());
     return nsIContentParent::AllocPJavaScriptParent();
--- a/dom/ipc/ContentParent.h
+++ b/dom/ipc/ContentParent.h
@@ -506,17 +506,18 @@ private:
     AllocPProcessHangMonitorParent(Transport* aTransport,
                                    ProcessId aOtherProcess) override;
 
     virtual bool RecvGetProcessAttributes(ContentParentId* aCpId,
                                           bool* aIsForApp,
                                           bool* aIsForBrowser) override;
     virtual bool RecvGetXPCOMProcessAttributes(bool* aIsOffline,
                                                InfallibleTArray<nsString>* dictionaries,
-                                               ClipboardCapabilities* clipboardCaps)
+                                               ClipboardCapabilities* clipboardCaps,
+                                               DomainPolicyClone* domainPolicy)
         override;
 
     virtual bool DeallocPJavaScriptParent(mozilla::jsipc::PJavaScriptParent*) override;
 
     virtual bool DeallocPRemoteSpellcheckEngineParent(PRemoteSpellcheckEngineParent*) override;
     virtual PBrowserParent* AllocPBrowserParent(const TabId& aTabId,
                                                 const IPCTabContext& aContext,
                                                 const uint32_t& aChromeFlags,
--- a/dom/ipc/PContent.ipdl
+++ b/dom/ipc/PContent.ipdl
@@ -341,16 +341,25 @@ union FileDescOrError {
 };
 
 union OptionalContentId
 {
   ContentParentId;
   void_t;
 };
 
+struct DomainPolicyClone
+{
+    bool        active;
+    URIParams[] blacklist;
+    URIParams[] whitelist;
+    URIParams[] superBlacklist;
+    URIParams[] superWhitelist;
+};
+
 prio(normal upto urgent) sync protocol PContent
 {
     parent spawns PPluginModule;
 
     parent opens PCompositor;
     parent opens PProcessHangMonitor;
     parent opens PSharedBufferManager;
     parent opens PImageBridge;
@@ -547,16 +556,18 @@ child:
     async StartProfiler(uint32_t aEntries, double aInterval, nsCString[] aFeatures,
                         nsCString[] aThreadNameFilters);
     async StopProfiler();
     prio(high) sync GetProfile()
       returns (nsCString aProfile);
 
     NuwaFreeze();
 
+    async DomainSetChanged(uint32_t aSetType, uint32_t aChangeType, OptionalURIParams aDomain);
+
     /**
      * Notify the child to shutdown. The child will in turn call FinishShutdown
      * and let the parent close the channel.
      */
     async Shutdown();
 
     async LoadProcessScript(nsString url);
 
@@ -581,17 +592,18 @@ parent:
      * !isForBrowser|, we're probably loading <xul:browser remote>.
      *
      * Keep the return values in sync with PBrowser()!
      */
     sync GetProcessAttributes()
         returns (ContentParentId cpId, bool isForApp, bool isForBrowser);
     sync GetXPCOMProcessAttributes()
         returns (bool isOffline, nsString[] dictionaries,
-                 ClipboardCapabilities clipboardCaps);
+                 ClipboardCapabilities clipboardCaps,
+                 DomainPolicyClone domainPolicy);
 
     sync CreateChildProcess(IPCTabContext context,
                             ProcessPriority priority,
                             TabId openerTabId)
         returns (ContentParentId cpId, bool isForApp, bool isForBrowser, TabId tabId);
     sync BridgeToChildProcess(ContentParentId cpId);
 
     /**
--- a/dom/ipc/moz.build
+++ b/dom/ipc/moz.build
@@ -149,12 +149,13 @@ if CONFIG['MOZ_TOOLKIT_SEARCH']:
     DEFINES['MOZ_TOOLKIT_SEARCH'] = True
 
 for var in ('MOZ_PERMISSIONS', 'MOZ_CHILD_PERMISSIONS'):
     if CONFIG[var]:
         DEFINES[var] = True
 
 JAR_MANIFESTS += ['jar.mn']
 
+BROWSER_CHROME_MANIFESTS += ['tests/browser.ini']
 MOCHITEST_CHROME_MANIFESTS += ['tests/chrome.ini']
 MOCHITEST_MANIFESTS += ['tests/mochitest.ini']
 
 CXXFLAGS += CONFIG['TK_CFLAGS']
new file mode 100644
--- /dev/null
+++ b/dom/ipc/tests/browser.ini
@@ -0,0 +1,6 @@
+[DEFAULT]
+support-files =
+  file_disableScript.html
+  file_domainPolicy_base.html
+
+[browser_domainPolicy.js]
new file mode 100644
--- /dev/null
+++ b/dom/ipc/tests/browser_domainPolicy.js
@@ -0,0 +1,240 @@
+var policy; // To make sure we never leave up an activated domain policy after a failed test, let's make this global.
+function activateDomainPolicy() {
+  const ssm = Services.scriptSecurityManager;
+  policy = ssm.activateDomainPolicy();
+}
+
+function deactivateDomainPolicy() {
+  if (policy) {
+    policy.deactivate();
+    policy = null;
+  }
+}
+
+function* test_domainPolicy() {
+
+  XPCOMUtils.defineLazyModuleGetter(this, "Promise", "resource://gre/modules/Promise.jsm");
+  let deferred = Promise.defer();
+  let currentTask = deferred.promise;
+  SpecialPowers.pushPrefEnv(
+    {set: [["dom.ipc.browser_frames.oop_by_default", false],
+          ["browser.pagethumbnails.capturing_disabled", false],
+          ["dom.mozBrowserFramesEnabled", false]]},
+    () => { return deferred.resolve()});
+  yield currentTask;
+
+  // Create tab
+  let tab;
+
+  // Init test
+  function initProcess() {
+    tab = gBrowser.addTab();
+    gBrowser.selectedTab = tab;
+
+    let initPromise = ContentTask.spawn(tab.linkedBrowser, null, function() {
+      Cu.import("resource://gre/modules/PromiseUtils.jsm");
+      function loadBase() {
+        let deferred = PromiseUtils.defer();
+        let listener = (event) => {
+          removeEventListener("DOMDocElementInserted", listener, true);
+          let listener2 = (event) => {
+            content.removeEventListener('load', listener2);
+            deferred.resolve();
+          }
+          content.addEventListener('load', listener2);
+        };
+        addEventListener("DOMDocElementInserted", listener, true);
+        return deferred.promise;
+      }
+
+      return loadBase();
+    });
+    tab.linkedBrowser.loadURI("http://mochi.test:8888/browser/dom/ipc/tests/file_domainPolicy_base.html");
+    return initPromise;
+  }
+
+  // We use ContentTask for the tests, but we also want to pass some data and some helper functions too.
+  // To do that, we serialize an input object via JSON |ipcArgs| and some shared helper functions |initUtils|
+  // and eval them in the content process.
+  var ipcArgs = {};
+  function initUtils(obj) {
+    obj.checkScriptEnabled = function(win, expectEnabled) {
+      win.wrappedJSObject.gFiredOnclick = false;
+      win.document.body.dispatchEvent(new win.Event('click'));
+      return { passed: win.wrappedJSObject.gFiredOnclick == expectEnabled,
+               msg: `Checking script-enabled for ${win.name} (${win.location})`};
+    }
+
+    obj.navigateFrame = function(ifr, src) {
+      let deferred = PromiseUtils.defer();
+      function onload() {
+        ifr.removeEventListener('load', onload);
+        deferred.resolve();
+      }
+      ifr.addEventListener('load', onload, false);
+      ifr.setAttribute('src', src);
+      return deferred.promise;
+    }
+  };
+
+  function runTest(test) {
+    return ContentTask.spawn(tab.linkedBrowser,
+      'ipcArgs = ' + JSON.stringify(ipcArgs) + '; (' + initUtils.toSource() + ')(utils)', test);
+  }
+
+  function checkAndCleanup(result) {
+    result = [].concat(result);
+    for (var i in result)
+      ok(result[i].passed, result[i].msg);
+    gBrowser.removeTab(tab);
+    deactivateDomainPolicy();
+    ipcArgs = {};
+  }
+
+  function testDomain(domain) {
+    ipcArgs.domain = domain;
+    return (aUtils) => {
+      Cu.import("resource://gre/modules/PromiseUtils.jsm");
+      var ipcArgs;
+      var utils = {};
+      eval(aUtils);
+
+      let path = '/browser/dom/ipc/tests/file_disableScript.html';
+      let deferred = PromiseUtils.defer();
+      var rootFrame = content.document.getElementById('root');
+      utils.navigateFrame(rootFrame, ipcArgs.domain + path).then(() => {
+        deferred.resolve(utils.checkScriptEnabled(rootFrame.contentWindow, false));
+      });
+      return deferred.promise;
+    }
+  }
+
+  info("Testing simple blacklist policy");
+
+  info("Creating child process first, activating domainPolicy after");
+  currentTask = initProcess();
+  yield currentTask;
+  activateDomainPolicy();
+  var bl = policy.blacklist;
+  bl.add(Services.io.newURI('http://example.com', null, null));
+  currentTask = runTest(testDomain("http://example.com"));
+  checkAndCleanup(yield currentTask);
+
+  info("Activating domainPolicy first, creating child process after");
+  activateDomainPolicy();
+  var bl = policy.blacklist;
+  bl.add(Services.io.newURI('http://example.com', null, null));
+  currentTask = initProcess();
+  yield currentTask;
+  currentTask = runTest(testDomain("http://example.com"));
+  checkAndCleanup(yield currentTask);
+
+  function testList(expectEnabled, list) {
+    ipcArgs.expectEnabled = expectEnabled;
+    ipcArgs.list = list;
+    return (aUtils) => {
+      Cu.import("resource://gre/modules/PromiseUtils.jsm");
+      var ipcArgs;
+      var utils = {};
+      eval(aUtils);
+
+      var results = [];
+      var testListInternal = function(expectEnabled, list, idx) {
+        idx = idx || 0;
+        let deferred = PromiseUtils.defer();
+        let path = '/browser/dom/ipc/tests/file_disableScript.html';
+        let target = list[idx] + path;
+        var rootFrame = content.document.getElementById('root');
+        utils.navigateFrame(rootFrame, target).then(function() {
+          results.push(utils.checkScriptEnabled(rootFrame.contentWindow, expectEnabled));
+          if (idx == list.length - 1)
+            deferred.resolve(results);
+          else
+            testListInternal(expectEnabled, list, idx + 1).then(function(retArg) { deferred.resolve(retArg); });
+        });
+        return deferred.promise;
+      }
+      return testListInternal(ipcArgs.expectEnabled, ipcArgs.list);
+    }
+  }
+
+  let testPolicy = {
+     exceptions: ['http://test1.example.com', 'http://example.com'],
+     superExceptions: ['http://test2.example.org', 'https://test1.example.com'],
+     exempt: ['http://test1.example.com', 'http://example.com',
+              'http://test2.example.org', 'http://sub1.test2.example.org',
+              'https://sub1.test1.example.com'],
+     notExempt: ['http://test2.example.com', 'http://sub1.test1.example.com',
+                 'http://www.example.com', 'https://test2.example.com',
+                 'https://example.com', 'http://test1.example.org'],
+  };
+
+  function activate(isBlack, exceptions, superExceptions) {
+    activateDomainPolicy();
+    let set = isBlack ? policy.blacklist : policy.whitelist;
+    let superSet = isBlack ? policy.superBlacklist : policy.superWhitelist;
+    for (var e of exceptions)
+      set.add(makeURI(e));
+    for (var e of superExceptions)
+      superSet.add(makeURI(e));
+  };
+
+  info("Testing Blacklist-style Domain Policy");
+  info("Activating domainPolicy first, creating child process after");
+  activate(true, testPolicy.exceptions, testPolicy.superExceptions);
+  currentTask = initProcess();
+  yield currentTask;
+  let results = [];
+  currentTask = runTest(testList(true, testPolicy.notExempt));
+  results = results.concat(yield currentTask);
+  currentTask = runTest(testList(false, testPolicy.exempt));
+  results = results.concat(yield currentTask);
+  checkAndCleanup(results);
+
+  info("Creating child process first, activating domainPolicy after");
+  currentTask = initProcess();
+  yield currentTask;
+  activate(true, testPolicy.exceptions, testPolicy.superExceptions);
+  results = [];
+  currentTask = runTest(testList(true, testPolicy.notExempt));
+  results = results.concat(yield currentTask);
+  currentTask = runTest(testList(false, testPolicy.exempt));
+  results = results.concat(yield currentTask);
+  checkAndCleanup(results);
+
+  info("Testing Whitelist-style Domain Policy");
+  deferred = Promise.defer();
+  currentTask = deferred.promise;
+  SpecialPowers.pushPrefEnv({set:[["javascript.enabled", false]]}, () => { return deferred.resolve()});
+  yield currentTask;
+
+  info("Activating domainPolicy first, creating child process after");
+  activate(false, testPolicy.exceptions, testPolicy.superExceptions);
+  currentTask = initProcess();
+  yield currentTask;
+  results = [];
+  currentTask = runTest(testList(false, testPolicy.notExempt));
+  results = results.concat(yield currentTask);
+  currentTask = runTest(testList(true, testPolicy.exempt));
+  results = results.concat(yield currentTask);
+  checkAndCleanup(results);
+
+  info("Creating child process first, activating domainPolicy after");
+  currentTask = initProcess();
+  yield currentTask;
+  activate(false, testPolicy.exceptions, testPolicy.superExceptions);
+  results = [];
+  currentTask = runTest(testList(false, testPolicy.notExempt));
+  results = results.concat(yield currentTask);
+  currentTask = runTest(testList(true, testPolicy.exempt));
+  results = results.concat(yield currentTask);
+  checkAndCleanup(results);
+  finish();
+}
+
+
+add_task(test_domainPolicy);
+
+registerCleanupFunction(()=>{
+  deactivateDomainPolicy();
+})
\ No newline at end of file
new file mode 100644
--- /dev/null
+++ b/dom/ipc/tests/file_disableScript.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<html>
+<head>
+<script>
+var gFiredOnload = false;
+var gFiredOnclick = false;
+</script>
+</head>
+<body onload="gFiredOnload = true;" onclick="gFiredOnclick = true;">
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/ipc/tests/file_domainPolicy_base.html
@@ -0,0 +1,8 @@
+<!DOCTYPE html>
+<html>
+<head>
+</head>
+<body>
+<iframe id="root" name="root"/>
+</body>
+</html>