Bug 1584993: Make CSP frame-ancestors work with fission enabled. r=jkt,farre,valentin
authorChristoph Kerschbaumer <ckerschb@christophkerschbaumer.com>
Tue, 22 Oct 2019 10:57:43 +0000
changeset 498573 e21ad27bfd0a2fef90919101eaef5aa5af1cc6c2
parent 498572 d9d678e7422e0fbf84160b6060452910e8deeb33
child 498574 aca2e6cff6b9c66232538f82538d3d1965be4eca
push id98525
push userbtara@mozilla.com
push dateTue, 22 Oct 2019 13:53:23 +0000
treeherderautoland@e21ad27bfd0a [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,23 +16,33 @@ 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]);
+
 function cleanup() {
   SpecialPowers.postConsoleSentinel();
   SimpleTest.finish();
 }
 
+// Since Bug 1584993 we parse the CSP in the parent too, hence the
+// same error message appears twice in the console. 
+var recordConsoleMsgOnce = false;
+
 SpecialPowers.registerConsoleListener(function ConsoleMsgListener(aMsg) {
   if (aMsg.message.indexOf(warningMsg) > -1) {
+    if (recordConsoleMsgOnce) {
+      return;
+    }
+    recordConsoleMsgOnce = true;
+
     ok(true, "report-uri not specified in Report-Only should throw a CSP warning.");
     SimpleTest.executeSoon(cleanup);
     return;
   } else {
     // if some other console message is present, we wait
     return;
   }
 });
--- 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