Bug 1584993: Make CSP frame-ancestors work with fission enabled. r=jkt,farre,valentin
☠☠ backed out by 9201fb4f420d ☠ ☠
authorChristoph Kerschbaumer <ckerschb@christophkerschbaumer.com>
Tue, 22 Oct 2019 08:53:47 +0000
changeset 498536 8705284b50d4905110df9b1fab7f00d9d77d06e6
parent 498535 141755ff04e1b75522dcf5209784df636a573984
child 498537 32edc29b094cb26a46d31116980a5e780de4a02f
push id98505
push userbtara@mozilla.com
push dateTue, 22 Oct 2019 09:02:39 +0000
treeherderautoland@8705284b50d4 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjkt, farre, valentin
bugs1584993
milestone72.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 1584993: Make CSP frame-ancestors work with fission enabled. r=jkt,farre,valentin Differential Revision: https://phabricator.services.mozilla.com/D49147
dom/base/Document.cpp
dom/interfaces/security/nsIContentSecurityPolicy.idl
dom/security/DOMSecurityManager.cpp
dom/security/DOMSecurityManager.h
dom/security/moz.build
dom/security/nsCSPContext.cpp
dom/security/test/csp/mochitest.ini
dom/security/test/csp/test_frameancestors.html
dom/security/test/csp/test_frameancestors_userpass.html
dom/security/test/csp/test_report_uri_missing_in_report_only_header.html
layout/build/nsLayoutStatics.cpp
testing/specialpowers/content/SpecialPowersChild.jsm
testing/specialpowers/content/SpecialPowersParent.jsm
testing/web-platform/meta/content-security-policy/frame-ancestors/frame-ancestors-from-serviceworker.https.html.ini
--- a/dom/base/Document.cpp
+++ b/dom/base/Document.cpp
@@ -3281,31 +3281,16 @@ nsresult Document::InitCSP(nsIChannel* a
 
   mSandboxFlags |= cspSandboxFlags;
 
   if (needNewNullPrincipal) {
     principal = NullPrincipal::CreateWithInheritedAttributes(principal);
     SetPrincipals(principal, principal);
   }
 
-  // ----- Enforce frame-ancestor policy on any applied policies
-  nsCOMPtr<nsIDocShell> docShell(mDocumentContainer);
-  if (docShell) {
-    bool safeAncestry = false;
-
-    // PermitsAncestry sends violation reports when necessary
-    rv = mCSP->PermitsAncestry(docShell, &safeAncestry);
-
-    if (NS_FAILED(rv) || !safeAncestry) {
-      MOZ_LOG(gCspPRLog, LogLevel::Debug,
-              ("CSP doesn't like frame's ancestry, not loading."));
-      // stop!  ERROR page!
-      aChannel->Cancel(NS_ERROR_CSP_FRAME_ANCESTOR_VIOLATION);
-    }
-  }
   ApplySettingsFromCSP(false);
   return NS_OK;
 }
 
 nsresult Document::InitFeaturePolicy(nsIChannel* aChannel) {
   MOZ_ASSERT(mFeaturePolicy, "we should only call init once");
 
   mFeaturePolicy->ResetDeclaredPolicy();
--- a/dom/interfaces/security/nsIContentSecurityPolicy.idl
+++ b/dom/interfaces/security/nsIContentSecurityPolicy.idl
@@ -1,18 +1,18 @@
 /* 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 "nsISerializable.idl"
 #include "nsIContentPolicy.idl"
 
 interface nsIURI;
-interface nsIDocShell;
 interface nsIEventTarget;
+interface nsILoadInfo;
 interface nsIPrincipal;
 interface nsICSPEventListener;
 
 webidl Element;
 webidl Document;
 
 /**
  * nsIContentSecurityPolicy
@@ -270,24 +270,24 @@ interface nsIContentSecurityPolicy : nsI
 
 
   /**
    * Verifies ancestry as permitted by the policy.
    *
    * NOTE: Calls to this may trigger violation reports when queried, so this
    * value should not be cached.
    *
-   * @param docShell
-   *    containing the protected resource
+   * @param aLoadInfo
+   *    The loadinfo of the channel containing the protected resource
    * @return
    *    true if the frame's ancestors are all allowed by policy (except for
    *    report-only policies, which will send reports and then return true
    *    here when violated).
    */
-  boolean permitsAncestry(in nsIDocShell docShell);
+  boolean permitsAncestry(in nsILoadInfo aLoadInfo);
 
 
   /**
    * Checks if a specific directive permits loading of a URI.
    *
    * NOTE: Calls to this may trigger violation reports when queried, so the
    * return value should not be cached.
    *
new file mode 100644
--- /dev/null
+++ b/dom/security/DOMSecurityManager.cpp
@@ -0,0 +1,202 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 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 "DOMSecurityManager.h"
+#include "nsCSPContext.h"
+#include "mozilla/dom/WindowGlobalParent.h"
+
+#include "nsIMultiPartChannel.h"
+#include "nsIObserverService.h"
+#include "nsIHttpProtocolHandler.h"
+
+using namespace mozilla;
+
+namespace {
+StaticRefPtr<DOMSecurityManager> gDOMSecurityManager;
+}  // namespace
+
+static nsresult GetHttpChannelHelper(nsIChannel* aChannel,
+                                     nsIHttpChannel** aHttpChannel) {
+  nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(aChannel);
+  if (httpChannel) {
+    httpChannel.forget(aHttpChannel);
+    return NS_OK;
+  }
+
+  nsCOMPtr<nsIMultiPartChannel> multipart = do_QueryInterface(aChannel);
+  if (!multipart) {
+    *aHttpChannel = nullptr;
+    return NS_OK;
+  }
+
+  nsCOMPtr<nsIChannel> baseChannel;
+  nsresult rv = multipart->GetBaseChannel(getter_AddRefs(baseChannel));
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  httpChannel = do_QueryInterface(baseChannel);
+  httpChannel.forget(aHttpChannel);
+
+  return NS_OK;
+}
+
+NS_INTERFACE_MAP_BEGIN(DOMSecurityManager)
+  NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIObserver)
+  NS_INTERFACE_MAP_ENTRY(nsIObserver)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_ADDREF(DOMSecurityManager)
+NS_IMPL_RELEASE(DOMSecurityManager)
+
+/* static */
+void DOMSecurityManager::Initialize() {
+  MOZ_ASSERT(!gDOMSecurityManager);
+
+  MOZ_ASSERT(NS_IsMainThread());
+
+  if (!XRE_IsParentProcess()) {
+    return;
+  }
+
+  RefPtr<DOMSecurityManager> service = new DOMSecurityManager();
+
+  nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
+  if (NS_WARN_IF(!obs)) {
+    return;
+  }
+
+  obs->AddObserver(service, NS_HTTP_ON_EXAMINE_RESPONSE_TOPIC, false);
+  obs->AddObserver(service, NS_XPCOM_SHUTDOWN_OBSERVER_ID, false);
+  gDOMSecurityManager = service.forget();
+}
+
+/* static */
+void DOMSecurityManager::Shutdown() {
+  MOZ_ASSERT(NS_IsMainThread());
+
+  if (!gDOMSecurityManager) {
+    return;
+  }
+
+  RefPtr<DOMSecurityManager> service = gDOMSecurityManager.forget();
+
+  nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
+  if (NS_WARN_IF(!obs)) {
+    return;
+  }
+
+  obs->RemoveObserver(service, NS_HTTP_ON_EXAMINE_RESPONSE_TOPIC);
+  obs->RemoveObserver(service, NS_XPCOM_SHUTDOWN_OBSERVER_ID);
+}
+
+NS_IMETHODIMP
+DOMSecurityManager::Observe(nsISupports* aSubject, const char* aTopic,
+                            const char16_t* aData) {
+  if (!strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID)) {
+    Shutdown();
+    return NS_OK;
+  }
+
+  MOZ_ASSERT(!strcmp(aTopic, NS_HTTP_ON_EXAMINE_RESPONSE_TOPIC));
+
+  nsCOMPtr<nsIChannel> channel = do_QueryInterface(aSubject);
+  if (NS_WARN_IF(!channel)) {
+    return NS_OK;
+  }
+
+  nsresult rv = ParseCSPAndEnforceFrameAncestorCheck(channel);
+  if (NS_FAILED(rv)) {
+    return rv;
+  }
+
+  return NS_OK;
+}
+
+nsresult DOMSecurityManager::ParseCSPAndEnforceFrameAncestorCheck(
+    nsIChannel* aChannel) {
+  MOZ_ASSERT(aChannel);
+
+  // CSP can only hang off an http channel, if this channel is not
+  // an http channel then there is nothing to do here.
+  nsCOMPtr<nsIHttpChannel> httpChannel;
+  nsresult rv = GetHttpChannelHelper(aChannel, getter_AddRefs(httpChannel));
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  if (!httpChannel) {
+    return NS_OK;
+  }
+
+  nsCOMPtr<nsILoadInfo> loadInfo = aChannel->LoadInfo();
+  nsContentPolicyType contentType = loadInfo->GetExternalContentPolicyType();
+  // frame-ancestor check only makes sense for subdocument loads, if this is
+  // not a load of such type, there is nothing to do here.
+  if (contentType != nsIContentPolicy::TYPE_SUBDOCUMENT) {
+    return NS_OK;
+  }
+
+  nsAutoCString tCspHeaderValue, tCspROHeaderValue;
+
+  Unused << httpChannel->GetResponseHeader(
+      NS_LITERAL_CSTRING("content-security-policy"), tCspHeaderValue);
+
+  Unused << httpChannel->GetResponseHeader(
+      NS_LITERAL_CSTRING("content-security-policy-report-only"),
+      tCspROHeaderValue);
+
+  // if there are no CSP values, then there is nothing to do here.
+  if (tCspHeaderValue.IsEmpty() && tCspROHeaderValue.IsEmpty()) {
+    return NS_OK;
+  }
+
+  NS_ConvertASCIItoUTF16 cspHeaderValue(tCspHeaderValue);
+  NS_ConvertASCIItoUTF16 cspROHeaderValue(tCspROHeaderValue);
+
+  RefPtr<nsCSPContext> csp = new nsCSPContext();
+  nsCOMPtr<nsIPrincipal> resultPrincipal;
+  rv = nsContentUtils::GetSecurityManager()->GetChannelResultPrincipal(
+      aChannel, getter_AddRefs(resultPrincipal));
+  NS_ENSURE_SUCCESS(rv, rv);
+  nsCOMPtr<nsIURI> selfURI;
+  aChannel->GetURI(getter_AddRefs(selfURI));
+
+  nsCOMPtr<nsIReferrerInfo> referrerInfo = httpChannel->GetReferrerInfo();
+  nsAutoString referrerSpec;
+  referrerInfo->GetComputedReferrerSpec(referrerSpec);
+  uint64_t innerWindowID = loadInfo->GetInnerWindowID();
+
+  rv = csp->SetRequestContextWithPrincipal(resultPrincipal, selfURI,
+                                           referrerSpec, innerWindowID);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  // ----- if there's a full-strength CSP header, apply it.
+  if (!cspHeaderValue.IsEmpty()) {
+    rv = CSP_AppendCSPFromHeader(csp, cspHeaderValue, false);
+    NS_ENSURE_SUCCESS(rv, rv);
+  }
+
+  // ----- if there's a report-only CSP header, apply it.
+  if (!cspROHeaderValue.IsEmpty()) {
+    rv = CSP_AppendCSPFromHeader(csp, cspROHeaderValue, true);
+    NS_ENSURE_SUCCESS(rv, rv);
+  }
+
+  // ----- Enforce frame-ancestor policy on any applied policies
+  bool safeAncestry = false;
+  // PermitsAncestry sends violation reports when necessary
+  rv = csp->PermitsAncestry(loadInfo, &safeAncestry);
+
+  if (NS_FAILED(rv) || !safeAncestry) {
+    // stop!  ERROR page!
+    aChannel->Cancel(NS_ERROR_CSP_FRAME_ANCESTOR_VIOLATION);
+  }
+
+  return NS_OK;
+}
new file mode 100644
--- /dev/null
+++ b/dom/security/DOMSecurityManager.h
@@ -0,0 +1,31 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 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/. */
+
+#ifndef mozilla_dom_DOMSecurityManager_h
+#define mozilla_dom_DOMSecurityManager_h
+
+#include "nsIObserver.h"
+
+class DOMSecurityManager final : public nsIObserver {
+ public:
+  NS_DECL_ISUPPORTS
+  NS_DECL_NSIOBSERVER
+
+  static void Initialize();
+
+ private:
+  DOMSecurityManager() = default;
+  ~DOMSecurityManager() = default;
+
+  // Only enforces the frame-anecstor check which needs to happen in
+  // the parent because we can only access the window global in the
+  // parent. The actual CSP gets parsed and applied in content.
+  nsresult ParseCSPAndEnforceFrameAncestorCheck(nsIChannel* aChannel);
+
+  static void Shutdown();
+};
+
+#endif /* mozilla_dom_DOMSecurityManager_h */
--- a/dom/security/moz.build
+++ b/dom/security/moz.build
@@ -8,16 +8,17 @@ with Files('*'):
     BUG_COMPONENT = ('Core', 'DOM: Security')
 
 TEST_DIRS += ['test']
 
 DIRS += [ 'featurepolicy' ]
 
 EXPORTS.mozilla.dom += [
     'CSPEvalChecker.h',
+    'DOMSecurityManager.h',
     'FramingChecker.h',
     'nsContentSecurityManager.h',
     'nsContentSecurityUtils.h',
     'nsCSPContext.h',
     'nsCSPService.h',
     'nsCSPUtils.h',
     'nsMixedContentBlocker.h',
     'PolicyTokenizer.h',
@@ -31,16 +32,17 @@ EXPORTS += [
     'nsContentSecurityManager.h',
     'nsContentSecurityUtils.h',
     'nsMixedContentBlocker.h',
     'ReferrerInfo.h',
 ]
 
 UNIFIED_SOURCES += [
     'CSPEvalChecker.cpp',
+    'DOMSecurityManager.cpp',
     'FramingChecker.cpp',
     'nsContentSecurityManager.cpp',
     'nsContentSecurityUtils.cpp',
     'nsCSPContext.cpp',
     'nsCSPParser.cpp',
     'nsCSPService.cpp',
     'nsCSPUtils.cpp',
     'nsMixedContentBlocker.cpp',
--- a/dom/security/nsCSPContext.cpp
+++ b/dom/security/nsCSPContext.cpp
@@ -11,18 +11,16 @@
 #include "nsContentPolicyUtils.h"
 #include "nsContentUtils.h"
 #include "nsCSPContext.h"
 #include "nsCSPParser.h"
 #include "nsCSPService.h"
 #include "nsError.h"
 #include "nsIAsyncVerifyRedirectCallback.h"
 #include "nsIClassInfoImpl.h"
-#include "nsIDocShell.h"
-#include "nsIDocShellTreeItem.h"
 #include "mozilla/dom/Document.h"
 #include "nsIHttpChannel.h"
 #include "nsIInterfaceRequestor.h"
 #include "nsIInterfaceRequestorUtils.h"
 #include "nsIObjectInputStream.h"
 #include "nsIObjectOutputStream.h"
 #include "nsIObserver.h"
 #include "nsIObserverService.h"
@@ -1517,89 +1515,62 @@ nsresult nsCSPContext::AsyncReportViolat
     }
   }
 
   NS_DispatchToMainThread(task.forget());
   return NS_OK;
 }
 
 /**
- * Based on the given docshell, determines if this CSP context allows the
+ * Based on the given loadinfo, determines if this CSP context allows the
  * ancestry.
  *
  * In order to determine the URI of the parent document (one causing the load
- * of this protected document), this function obtains the docShellTreeItem,
- * then walks up the hierarchy until it finds a privileged (chrome) tree item.
- * Getting the a tree item's URI looks like this in pseudocode:
- *
- * nsIDocShellTreeItem->GetDocument()->GetDocumentURI();
- *
- * aDocShell is the docShell for the protected document.
+ * of this protected document), this function traverses all Browsing Contexts
+ * until it reaches the top level browsing context.
  */
 NS_IMETHODIMP
-nsCSPContext::PermitsAncestry(nsIDocShell* aDocShell,
+nsCSPContext::PermitsAncestry(nsILoadInfo* aLoadInfo,
                               bool* outPermitsAncestry) {
+  MOZ_ASSERT(XRE_IsParentProcess(), "frame-ancestor check only in parent");
+
   nsresult rv;
 
-  // Can't check ancestry without a docShell.
-  if (aDocShell == nullptr) {
-    return NS_ERROR_FAILURE;
-  }
+  *outPermitsAncestry = true;
 
-  *outPermitsAncestry = true;
+  RefPtr<mozilla::dom::BrowsingContext> ctx;
+  aLoadInfo->GetBrowsingContext(getter_AddRefs(ctx));
 
   // extract the ancestry as an array
   nsCOMArray<nsIURI> ancestorsArray;
-
-  nsCOMPtr<nsIInterfaceRequestor> ir(do_QueryInterface(aDocShell));
-  nsCOMPtr<nsIDocShellTreeItem> treeItem(do_GetInterface(ir));
-  nsCOMPtr<nsIDocShellTreeItem> parentTreeItem;
-  nsCOMPtr<nsIURI> currentURI;
   nsCOMPtr<nsIURI> uriClone;
 
-  // iterate through each docShell parent item
-  while (NS_SUCCEEDED(
-             treeItem->GetInProcessParent(getter_AddRefs(parentTreeItem))) &&
-         parentTreeItem != nullptr) {
-    // stop when reaching chrome
-    if (parentTreeItem->ItemType() == nsIDocShellTreeItem::typeChrome) {
-      break;
-    }
-
-    Document* doc = parentTreeItem->GetDocument();
-    NS_ASSERTION(doc,
-                 "Could not get Document from nsIDocShellTreeItem in "
-                 "nsCSPContext::PermitsAncestry");
-    NS_ENSURE_TRUE(doc, NS_ERROR_FAILURE);
-
-    currentURI = doc->GetDocumentURI();
+  while (ctx) {
+    WindowGlobalParent* window = ctx->Canonical()->GetCurrentWindowGlobal();
+    if (window) {
+      nsCOMPtr<nsIURI> currentURI = window->GetDocumentURI();
+      if (currentURI) {
+        nsAutoCString spec;
+        currentURI->GetSpec(spec);
+        // delete the userpass from the URI.
+        rv = NS_MutateURI(currentURI)
+                 .SetRef(EmptyCString())
+                 .SetUserPass(EmptyCString())
+                 .Finalize(uriClone);
 
-    if (currentURI) {
-      // delete the userpass from the URI.
-      rv = NS_MutateURI(currentURI)
-               .SetRef(EmptyCString())
-               .SetUserPass(EmptyCString())
-               .Finalize(uriClone);
-
-      // If setUserPass fails for some reason, just return a clone of the
-      // current URI
-      if (NS_FAILED(rv)) {
-        rv = NS_GetURIWithoutRef(currentURI, getter_AddRefs(uriClone));
-        NS_ENSURE_SUCCESS(rv, rv);
+        // If setUserPass fails for some reason, just return a clone of the
+        // current URI
+        if (NS_FAILED(rv)) {
+          rv = NS_GetURIWithoutRef(currentURI, getter_AddRefs(uriClone));
+          NS_ENSURE_SUCCESS(rv, rv);
+        }
+        ancestorsArray.AppendElement(uriClone);
       }
-
-      if (CSPCONTEXTLOGENABLED()) {
-        CSPCONTEXTLOG(("nsCSPContext::PermitsAncestry, found ancestor: %s",
-                       uriClone->GetSpecOrDefault().get()));
-      }
-      ancestorsArray.AppendElement(uriClone);
     }
-
-    // next ancestor
-    treeItem = parentTreeItem;
+    ctx = ctx->GetParent();
   }
 
   nsAutoString violatedDirective;
 
   // Now that we've got the ancestry chain in ancestorsArray, time to check
   // them against any CSP.
   // NOTE:  the ancestors are not allowed to be sent cross origin; this is a
   // restriction not placed on subresource loads.
--- a/dom/security/test/csp/mochitest.ini
+++ b/dom/security/test/csp/mochitest.ini
@@ -252,19 +252,17 @@ prefs =
 [test_bug885433.html]
 [test_bug888172.html]
 [test_bug1505412.html]
 skip-if = !debug
 [test_evalscript.html]
 [test_evalscript_blocked_by_strict_dynamic.html]
 [test_evalscript_allowed_by_strict_dynamic.html]
 [test_frameancestors.html]
-skip-if = fission
 [test_frameancestors_userpass.html]
-skip-if = fission
 [test_inlinescript.html]
 [test_inlinestyle.html]
 [test_invalid_source_expression.html]
 [test_bug836922_npolicies.html]
 skip-if = verify
 [test_bug886164.html]
 [test_redirects.html]
 [test_bug910139.html]
--- a/dom/security/test/csp/test_frameancestors.html
+++ b/dom/security/test/csp/test_frameancestors.html
@@ -46,20 +46,24 @@ var framesThatShouldLoad = {
 //  abb2_block   "frame-ancestors b" (outer frame and test harness)
 //
 // so that results in 2 extra violation notifications due to the test harness.
 // expected = 6, total = 8
 //
 // Number of tests that pass for this file should be 12 (8 violations 4 loads)
 var expectedViolationsLeft = 8;
 
+// CSP frame-ancestor checks happen in the parent, hence we have to
+// proxy the csp violation notifications.
+SpecialPowers.registerObservers("csp-on-violate-policy");
+
 // This is used to watch the blocked data bounce off CSP and allowed data
 // get sent out to the wire.
 function examiner() {
-  SpecialPowers.addObserver(this, "csp-on-violate-policy");
+  SpecialPowers.addObserver(this, "specialpowers-csp-on-violate-policy");
 }
 examiner.prototype  = {
   observe(subject, topic, data) {
     // subject should be an nsURI... though could be null since CSP
     // prohibits cross-origin URI reporting during frame ancestors checks.
     if (subject && !SpecialPowers.can_QI(subject))
       return;
 
@@ -76,26 +80,26 @@ examiner.prototype  = {
       if (asciiSpec.includes("test_frameancestors.html")) {
         return;
       }
     } catch (ex) {
       // was not an nsIURI, so it was probably a cross-origin report.
     }
 
 
-    if (topic === "csp-on-violate-policy") {
+    if (topic === "specialpowers-csp-on-violate-policy") {
       //these were blocked... record that they were blocked
       window.frameBlocked(asciiSpec, data);
     }
   },
 
   // must eventually call this to remove the listener,
   // or mochitests might get borked.
   remove() {
-    SpecialPowers.removeObserver(this, "csp-on-violate-policy");
+    SpecialPowers.removeObserver(this, "specialpowers-csp-on-violate-policy");
   }
 }
 
 // called when a frame is loaded
 // -- if it's not enumerated above, it should not load!
 var frameLoaded = function(testname, uri) {
   //test already complete.... forget it... remember the first result.
   if (window.framesThatShouldLoad[testname] != -1)
--- a/dom/security/test/csp/test_frameancestors_userpass.html
+++ b/dom/security/test/csp/test_frameancestors_userpass.html
@@ -17,20 +17,24 @@
 var framesThatShouldLoad = {
   frame_a: -1,    /* frame a allowed */
   frame_b: -1,    /* frame b allowed */
 };
 
 // Number of tests that pass for this file should be 1
 var expectedViolationsLeft = 1;
 
+// CSP frame-ancestor checks happen in the parent, hence we have to
+// proxy the csp violation notifications.
+SpecialPowers.registerObservers("csp-on-violate-policy");
+
 // This is used to watch the blocked data bounce off CSP and allowed data
 // get sent out to the wire.
 function examiner() {
-  SpecialPowers.addObserver(this, "csp-on-violate-policy");
+  SpecialPowers.addObserver(this, "specialpowers-csp-on-violate-policy");
 }
 examiner.prototype  = {
   observe(subject, topic, data) {
     // subject should be an nsURI... though could be null since CSP
     // prohibits cross-origin URI reporting during frame ancestors checks.
     if (subject && !SpecialPowers.can_QI(subject))
       return;
 
@@ -46,27 +50,26 @@ examiner.prototype  = {
       // can cause this test to over-succeed (but only in specific cases).
       if (asciiSpec.includes("test_frameancestors_userpass.html")) {
         return;
       }
     } catch (ex) {
       // was not an nsIURI, so it was probably a cross-origin report.
     }
 
-
-    if (topic === "csp-on-violate-policy") {
+    if (topic === "specialpowers-csp-on-violate-policy") {
       //these were blocked... record that they were blocked
       window.frameBlocked(asciiSpec, data);
     }
   },
 
   // must eventually call this to remove the listener,
   // or mochitests might get borked.
   remove() {
-    SpecialPowers.removeObserver(this, "csp-on-violate-policy");
+    SpecialPowers.removeObserver(this, "specialpowers-csp-on-violate-policy");
   }
 }
 
 // called when a frame is loaded
 // -- if it's not enumerated above, it should not load!
 var frameLoaded = function(testname, uri) {
   //test already complete.... forget it... remember the first result.
   if (window.framesThatShouldLoad[testname] != -1)
--- a/dom/security/test/csp/test_report_uri_missing_in_report_only_header.html
+++ b/dom/security/test/csp/test_report_uri_missing_in_report_only_header.html
@@ -16,17 +16,26 @@ https://bugzilla.mozilla.org/show_bug.cg
 <iframe id="cspframe"></iframe>
 
 <pre id="test">
 <script class="testbody" type="text/javascript">
 var stringBundleService = SpecialPowers.Cc["@mozilla.org/intl/stringbundle;1"]
                           .getService(SpecialPowers.Ci.nsIStringBundleService);
 var localizer = stringBundleService.createBundle("chrome://global/locale/security/csp.properties");
 var warningMsg = localizer.formatStringFromName("reportURInotInReportOnlyHeader", [window.location.origin]);
+
+// Since Bug 1584993 we parse the CSP in the parent too, hence the
+// same error message appears twice in the console. 
+var callCleanUpOnce = false;
+
 function cleanup() {
+  if (callCleanUpOnce) {
+    return;
+  }
+  callCleanUpOnce = true;
   SpecialPowers.postConsoleSentinel();
   SimpleTest.finish();
 }
 
 SpecialPowers.registerConsoleListener(function ConsoleMsgListener(aMsg) {
   if (aMsg.message.indexOf(warningMsg) > -1) {
     ok(true, "report-uri not specified in Report-Only should throw a CSP warning.");
     SimpleTest.executeSoon(cleanup);
--- a/layout/build/nsLayoutStatics.cpp
+++ b/layout/build/nsLayoutStatics.cpp
@@ -104,16 +104,17 @@
 #include "mozilla/EventDispatcher.h"
 #include "mozilla/IMEStateManager.h"
 #include "mozilla/dom/HTMLVideoElement.h"
 #include "ThirdPartyUtil.h"
 #include "TouchManager.h"
 #include "DecoderDoctorLogger.h"
 #include "MediaDecoder.h"
 #include "mozilla/ClearSiteData.h"
+#include "mozilla/dom/DOMSecurityManager.h"
 #include "mozilla/EditorController.h"
 #include "mozilla/Fuzzyfox.h"
 #include "mozilla/HTMLEditorController.h"
 #include "mozilla/ServoBindings.h"
 #include "mozilla/StaticPresData.h"
 #include "mozilla/dom/Document.h"
 #include "mozilla/dom/IPCBlobInputStreamStorage.h"
 #include "mozilla/dom/WebIDLGlobalNameHash.h"
@@ -303,16 +304,18 @@ nsresult nsLayoutStatics::Initialize() {
   }
 
   nsThreadManager::InitializeShutdownObserver();
 
   mozilla::Fuzzyfox::Start();
 
   ClearSiteData::Initialize();
 
+  DOMSecurityManager::Initialize();
+
   // Reporting API.
   ReportingHeader::Initialize();
 
   if (XRE_IsParentProcess()) {
     InitializeQuotaManager();
     InitializeLocalStorage();
   }
 
--- a/testing/specialpowers/content/SpecialPowersChild.jsm
+++ b/testing/specialpowers/content/SpecialPowersChild.jsm
@@ -2250,16 +2250,35 @@ SpecialPowersChild.prototype._proxiedObs
       "specialpowers-http-notify-request",
       uri
     );
   },
 
   "specialpowers-service-worker-shutdown": function(aMessage) {
     Services.obs.notifyObservers(null, "specialpowers-service-worker-shutdown");
   },
+
+  "specialpowers-csp-on-violate-policy": function(aMessage) {
+    let subject = null;
+
+    try {
+      subject = Services.io.newURI(aMessage.data.subject);
+    } catch (ex) {
+      // if it's not a valid URI it must be an nsISupportsCString
+      subject = Cc["@mozilla.org/supports-cstring;1"].createInstance(
+        Ci.nsISupportsCString
+      );
+      subject.data = aMessage.data.subject;
+    }
+    Services.obs.notifyObservers(
+      subject,
+      "specialpowers-csp-on-violate-policy",
+      aMessage.data.data
+    );
+  },
 };
 
 SpecialPowersChild.prototype.permissionObserverProxy = {
   // 'this' in permChangedObserverProxy is the permChangedObserverProxy
   // object itself. The '_specialPowersAPI' will be set to the 'SpecialPowersChild'
   // object to call the member function in SpecialPowersChild.
   _specialPowersAPI: null,
   observe(aSubject, aTopic, aData) {
--- a/testing/specialpowers/content/SpecialPowersParent.jsm
+++ b/testing/specialpowers/content/SpecialPowersParent.jsm
@@ -155,17 +155,34 @@ class SpecialPowersParent extends JSWind
             // We need to ensure that it looks the same as a real permission,
             // so we fake these properties.
             msg.permission = {
               principal: {
                 originAttributes: {},
               },
               type: permission.type,
             };
-          // fall through
+            this._self.sendAsyncMessage("specialpowers-" + aTopic, msg);
+            return;
+          case "csp-on-violate-policy":
+            // the subject is either an nsIURI or an nsISupportsCString
+            let subject = null;
+            if (aSubject instanceof Ci.nsIURI) {
+              subject = aSubject.asciiSpec;
+            } else if (aSubject instanceof Ci.nsISupportsCString) {
+              subject = aSubject.data;
+            } else {
+              throw new Error("Subject must be nsIURI or nsISupportsCString");
+            }
+            msg = {
+              subject,
+              data: aData,
+            };
+            this._self.sendAsyncMessage("specialpowers-" + aTopic, msg);
+            return;
           default:
             this._self.sendAsyncMessage("specialpowers-" + aTopic, msg);
         }
       },
     };
 
     this.init();
 
--- a/testing/web-platform/meta/content-security-policy/frame-ancestors/frame-ancestors-from-serviceworker.https.html.ini
+++ b/testing/web-platform/meta/content-security-policy/frame-ancestors/frame-ancestors-from-serviceworker.https.html.ini
@@ -1,5 +1,4 @@
 [frame-ancestors-from-serviceworker.https.html]
-  expected: TIMEOUT
   [A 'frame-ancestors' CSP directive set from a serviceworker response with a value 'none' should block rendering.]
-    expected: TIMEOUT
+    expected: FAIL