Bug 1126014 - DomainPolicy support for e10s. r=mrbkap
authorGabor Krizsanits <gkrizsanits@mozilla.com>
Tue, 24 Mar 2015 15:29:16 +0100
changeset 235431 90d9af9b861a816df7da099934525c8ead0ae270
parent 235430 80be62413c65e036ca6c2a1e30c993edfb88fff0
child 235432 7386c130fb784279ee3e4805caa08a97d7e7058e
push id11965
push userkwierso@gmail.com
push dateWed, 25 Mar 2015 01:38:51 +0000
treeherderfx-team@1b09c8d75388 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmrbkap
bugs1126014
milestone39.0a1
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>