Bug 1529068 - Implementation of the navigate-to CSP directive as defined in CSP Level 3. r=ckerschb,mccr8
☠☠ backed out by d1181cbf7840 ☠ ☠
authorBenjamin <beriksson@mozilla.com>
Wed, 04 Sep 2019 20:29:37 +0000
changeset 491741 890bcaee9b7db0922f4bdf55e39646da0dafdc1a
parent 491740 d69418f996f763d62de45408f65c1c4cc112143d
child 491742 63f962127985e6f95afcfb753683e3aae743f66d
push id114032
push userbtara@mozilla.com
push dateThu, 05 Sep 2019 03:53:00 +0000
treeherdermozilla-inbound@df1cc95342be [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersckerschb, mccr8
bugs1529068
milestone71.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 1529068 - Implementation of the navigate-to CSP directive as defined in CSP Level 3. r=ckerschb,mccr8 https://www.w3.org/TR/CSP3/#directive-navigate-to Differential Revision: https://phabricator.services.mozilla.com/D37139
docshell/base/nsDocShell.cpp
dom/base/Document.cpp
dom/interfaces/security/nsIContentSecurityPolicy.idl
dom/locales/en-US/chrome/security/csp.properties
dom/security/nsCSPContext.cpp
dom/security/nsCSPParser.cpp
dom/security/nsCSPService.cpp
dom/security/nsCSPUtils.cpp
dom/security/nsCSPUtils.h
dom/security/test/csp/file_navigate_to.html
dom/security/test/csp/file_navigate_to.sjs
dom/security/test/csp/file_navigate_to_request.html
dom/security/test/csp/mochitest.ini
dom/security/test/csp/test_navigate_to.html
dom/security/test/gtest/TestCSPParser.cpp
modules/libpref/init/StaticPrefList.yaml
testing/web-platform/meta/content-security-policy/navigate-to/__dir__.ini
testing/web-platform/meta/content-security-policy/navigate-to/child-navigates-parent-allowed.html.ini
testing/web-platform/meta/content-security-policy/navigate-to/child-navigates-parent-blocked.sub.html.ini
testing/web-platform/meta/content-security-policy/navigate-to/form-blocked.sub.html.ini
testing/web-platform/meta/content-security-policy/navigate-to/form-cross-origin-blocked.sub.html.ini
testing/web-platform/meta/content-security-policy/navigate-to/form-redirected-blocked.sub.html.ini
testing/web-platform/meta/content-security-policy/navigate-to/href-location-blocked.sub.html.ini
testing/web-platform/meta/content-security-policy/navigate-to/href-location-cross-origin-blocked.sub.html.ini
testing/web-platform/meta/content-security-policy/navigate-to/href-location-redirected-blocked.sub.html.ini
testing/web-platform/meta/content-security-policy/navigate-to/link-click-blocked.sub.html.ini
testing/web-platform/meta/content-security-policy/navigate-to/link-click-cross-origin-blocked.sub.html.ini
testing/web-platform/meta/content-security-policy/navigate-to/link-click-redirected-blocked.sub.html.ini
testing/web-platform/meta/content-security-policy/navigate-to/meta-refresh-blocked.sub.html.ini
testing/web-platform/meta/content-security-policy/navigate-to/meta-refresh-cross-origin-blocked.sub.html.ini
testing/web-platform/meta/content-security-policy/navigate-to/meta-refresh-redirected-blocked.sub.html.ini
testing/web-platform/meta/content-security-policy/navigate-to/parent-navigates-child-blocked.html.ini
testing/web-platform/meta/content-security-policy/navigate-to/spv-only-sent-to-initiator.sub.html.ini
testing/web-platform/meta/content-security-policy/navigate-to/unsafe-allow-redirects/blocked-end-of-chain.sub.html.ini
xpcom/base/ErrorList.py
--- a/docshell/base/nsDocShell.cpp
+++ b/docshell/base/nsDocShell.cpp
@@ -4020,17 +4020,18 @@ nsDocShell::DisplayLoadError(nsresult aE
              NS_ERROR_PROXY_GATEWAY_TIMEOUT == aError) {
     NS_ENSURE_ARG_POINTER(aURI);
     // Get the host
     nsAutoCString host;
     aURI->GetHost(host);
     CopyUTF8toUTF16(host, *formatStrs.AppendElement());
     error = "netTimeout";
   } else if (NS_ERROR_CSP_FRAME_ANCESTOR_VIOLATION == aError ||
-             NS_ERROR_CSP_FORM_ACTION_VIOLATION == aError) {
+             NS_ERROR_CSP_FORM_ACTION_VIOLATION == aError ||
+             NS_ERROR_CSP_NAVIGATE_TO_VIOLATION == aError) {
     // CSP error
     cssClass.AssignLiteral("neterror");
     error = "cspBlocked";
   } else if (NS_ERROR_GET_MODULE(aError) == NS_ERROR_MODULE_SECURITY) {
     nsCOMPtr<nsINSSErrorsService> nsserr =
         do_GetService(NS_NSS_ERRORS_SERVICE_CONTRACTID);
 
     uint32_t errorClass;
@@ -9862,16 +9863,31 @@ static bool HasHttpScheme(nsIURI* aURI) 
     NS_ENSURE_SUCCESS(aRv, false);
     nsCOMPtr<nsIInputStreamChannel> isc = do_QueryInterface(channel);
     MOZ_ASSERT(isc);
     isc->SetBaseURI(baseURI);
   }
 
   nsCOMPtr<nsIContentSecurityPolicy> csp = aLoadState->Csp();
   if (csp) {
+    // Check CSP navigate-to
+    bool allowsNavigateTo = false;
+    aRv = csp->GetAllowsNavigateTo(aLoadState->URI(), aLoadInfo,
+                                   false, /* aWasRedirected */
+                                   false, /* aEnforceWhitelist */
+                                   &allowsNavigateTo);
+    if (NS_FAILED(aRv)) {
+      return false;
+    }
+
+    if (!allowsNavigateTo) {
+      aRv = NS_ERROR_CSP_NAVIGATE_TO_VIOLATION;
+      return false;
+    }
+
     // Navigational requests that are same origin need to be upgraded in case
     // upgrade-insecure-requests is present.
     bool upgradeInsecureRequests = false;
     csp->GetUpgradeInsecureRequests(&upgradeInsecureRequests);
     if (upgradeInsecureRequests) {
       // only upgrade if the navigation is same origin
       nsCOMPtr<nsIPrincipal> resultPrincipal;
       aRv = nsContentUtils::GetSecurityManager()->GetChannelResultPrincipal(
--- a/dom/base/Document.cpp
+++ b/dom/base/Document.cpp
@@ -3000,16 +3000,36 @@ nsresult Document::StartDocumentLoad(con
       mUpgradeInsecurePreloads =
           mUpgradeInsecureRequests || doc->GetUpgradeInsecureRequests(true);
     }
   }
 
   nsresult rv = InitReferrerInfo(aChannel);
   NS_ENSURE_SUCCESS(rv, rv);
 
+  // Check CSP navigate-to
+  // We need to enforce the CSP of the document that initiated the load,
+  // which is the CSP to inherit.
+  nsCOMPtr<nsIContentSecurityPolicy> cspToInherit = loadInfo->GetCspToInherit();
+  if (cspToInherit) {
+    bool allowsNavigateTo = false;
+    rv = cspToInherit->GetAllowsNavigateTo(
+        mDocumentURI, loadInfo,
+        !loadInfo->RedirectChainIncludingInternalRedirects()
+             .IsEmpty(), /* aWasRedirected */
+        true,            /* aEnforceWhitelist */
+        &allowsNavigateTo);
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    if (!allowsNavigateTo) {
+      aChannel->Cancel(NS_ERROR_CSP_NAVIGATE_TO_VIOLATION);
+      return NS_OK;
+    }
+  }
+
   rv = InitCSP(aChannel);
   NS_ENSURE_SUCCESS(rv, rv);
 
   // Initialize FeaturePolicy
   rv = InitFeaturePolicy(aChannel);
   NS_ENSURE_SUCCESS(rv, rv);
 
   // XFO needs to be checked after CSP because it is ignored if
--- a/dom/interfaces/security/nsIContentSecurityPolicy.idl
+++ b/dom/interfaces/security/nsIContentSecurityPolicy.idl
@@ -57,16 +57,17 @@ interface nsIContentSecurityPolicy : nsI
   const unsigned short BASE_URI_DIRECTIVE             = 13;
   const unsigned short FORM_ACTION_DIRECTIVE          = 14;
   const unsigned short WEB_MANIFEST_SRC_DIRECTIVE     = 15;
   const unsigned short UPGRADE_IF_INSECURE_DIRECTIVE  = 16;
   const unsigned short CHILD_SRC_DIRECTIVE            = 17;
   const unsigned short BLOCK_ALL_MIXED_CONTENT        = 18;
   const unsigned short SANDBOX_DIRECTIVE              = 19;
   const unsigned short WORKER_SRC_DIRECTIVE           = 20;
+  const unsigned short NAVIGATE_TO_DIRECTIVE          = 21;
 
   /**
    * Accessor method for a read-only string version of the policy at a given
    * index.
    */
   [binaryname(GetPolicyString)] AString getPolicy(in unsigned long index);
 
   /**
@@ -140,16 +141,34 @@ interface nsIContentSecurityPolicy : nsI
                           in AString aNonce,
                           in boolean aParserCreated,
                           in Element aTriggeringElement,
                           in nsICSPEventListener aCSPEventListener,
                           in AString aContentOfPseudoScript,
                           in unsigned long aLineNumber,
                           in unsigned long aColumnNumber);
 
+  /*
+   * Whether this policy allows a navigation subject to the navigate-to
+   * policy.
+   * @param aURI The target URI
+   * @param aLoadInfo used to check if the navigation was initiated by a form submission. This
+   *        is important since the form-action directive overrides navigate-to in that case.  
+   * @param aWasRedirect True if a redirect has happened. Important for path-sensitivity.
+   * @param aEnforceWhitelist True if the whitelist of allowed targets must be enforced. If 
+   *        this is true, the whitelist must be enforced even if 'unsafe-allow-redirects' is 
+   *        used. If 'unsafe-allow-redirects' is not used then the whitelist is always enforced
+   * @return
+   *     Whether or not the effects of the navigation is allowed
+   */
+  boolean getAllowsNavigateTo(in nsIURI aURI, 
+                              in nsILoadInfo aLoadInfo,
+                              in boolean aWasRedirected,
+                              in boolean aEnforceWhitelist);
+
   /**
    * whether this policy allows eval and eval-like functions
    * such as setTimeout("code string", time).
    * @param shouldReportViolations
    *     Whether or not the use of eval should be reported.
    *     This function returns "true" when violating report-only policies, but
    *     when any policy (report-only or otherwise) is violated,
    *     shouldReportViolations is true as well.
--- a/dom/locales/en-US/chrome/security/csp.properties
+++ b/dom/locales/en-US/chrome/security/csp.properties
@@ -90,17 +90,21 @@ ignoringDirectiveWithNoValues = Ignoring ‘%1$S’ since it does not contain any parameters.
 ignoringReportOnlyDirective = Ignoring sandbox directive when delivered in a report-only policy ‘%1$S’
 # LOCALIZATION NOTE (deprecatedReferrerDirective):
 # %1$S is the value of the deprecated Referrer Directive.
 deprecatedReferrerDirective = Referrer Directive ‘%1$S’ has been deprecated. Please use the Referrer-Policy header instead.
 # LOCALIZATION NOTE (IgnoringSrcBecauseOfDirective):
 # %1$S is the name of the src that is ignored.
 # %2$S is the name of the directive that causes the src to be ignored.
 IgnoringSrcBecauseOfDirective=Ignoring ‘%1$S’ because of ‘%2$S’ directive.
-
+# LOCALIZATION NOTE (IgnoringSourceWithinDirective):
+# %1$S is the ignored src
+# %2$S is the directive  which supports src
+IgnoringSourceWithinDirective = Ignoring source “%1$S” (Not supported within ‘%2$S’).
+ 
 # CSP Errors:
 # LOCALIZATION NOTE (couldntParseInvalidSource):
 # %1$S is the source that could not be parsed
 couldntParseInvalidSource = Couldn’t parse invalid source %1$S
 # LOCALIZATION NOTE (couldntParseInvalidHost):
 # %1$S is the host that's invalid
 couldntParseInvalidHost = Couldn’t parse invalid host %1$S
 # LOCALIZATION NOTE (couldntParseScheme):
--- a/dom/security/nsCSPContext.cpp
+++ b/dom/security/nsCSPContext.cpp
@@ -592,16 +592,101 @@ nsCSPContext::GetAllowsInline(nsContentP
       reportInlineViolation(aContentType, aTriggeringElement, aCSPEventListener,
                             aNonce, reportSample ? content : EmptyString(),
                             violatedDirective, i, aLineNumber, aColumnNumber);
     }
   }
   return NS_OK;
 }
 
+NS_IMETHODIMP
+nsCSPContext::GetAllowsNavigateTo(nsIURI* aURI, nsILoadInfo* aLoadInfo,
+                                  bool aWasRedirected, bool aEnforceWhitelist,
+                                  bool* outAllowsNavigateTo) {
+  /*
+   * The matrix below shows the different values of (aWasRedirect,
+   * aEnforceWhitelist) for the three different checks we do.
+   *
+   *  Navigation    | Start Loading  | Initiate Redirect | Document
+   *                | (nsDocShell)   | (nsCSPService)    |
+   *  -----------------------------------------------------------------
+   *  A -> B          (false,false)    -                   (false,true)
+   *  A -> ... -> B   (false,false)    (true,false)        (true,true)
+   */
+  *outAllowsNavigateTo = false;
+
+  EnsureIPCPoliciesRead();
+  // The 'form-action' directive overrules 'navigate-to' for form submissions.
+  // So in case this is a form submission and the directive 'form-action' is
+  // present then there is nothing for us to do here, see: 6.3.3.1.2
+  // https://www.w3.org/TR/CSP3/#navigate-to-pre-navigate
+  if (aLoadInfo->GetIsFormSubmission()) {
+    for (unsigned long i = 0; i < mPolicies.Length(); i++) {
+      if (mPolicies[i]->hasDirective(
+              nsIContentSecurityPolicy::FORM_ACTION_DIRECTIVE)) {
+        *outAllowsNavigateTo = true;
+        return NS_OK;
+      }
+    }
+  }
+
+  bool atLeastOneBlock = false;
+  for (unsigned long i = 0; i < mPolicies.Length(); i++) {
+    if (!mPolicies[i]->allowsNavigateTo(aURI, aWasRedirected,
+                                        aEnforceWhitelist)) {
+      if (!mPolicies[i]->getReportOnlyFlag()) {
+        atLeastOneBlock = true;
+      }
+
+      // If the load encountered a server side redirect, the spec suggests to
+      // remove the path component from the URI, see:
+      // https://www.w3.org/TR/CSP3/#source-list-paths-and-redirects
+      nsCOMPtr<nsIURI> blockedURIForReporting = aURI;
+      if (aWasRedirected) {
+        nsAutoCString prePathStr;
+        nsCOMPtr<nsIURI> prePathURI;
+        nsresult rv = aURI->GetPrePath(prePathStr);
+        NS_ENSURE_SUCCESS(rv, rv);
+        rv = NS_NewURI(getter_AddRefs(blockedURIForReporting), prePathStr);
+        NS_ENSURE_SUCCESS(rv, rv);
+      }
+
+      // Lines numbers and source file for the violation report
+      uint32_t lineNumber = 0;
+      uint32_t columnNumber = 0;
+      nsAutoCString spec;
+      JSContext* cx = nsContentUtils::GetCurrentJSContext();
+      if (cx) {
+        nsJSUtils::GetCallingLocation(cx, spec, &lineNumber, &columnNumber);
+        // If GetCallingLocation fails linenumber & columnNumber are set to 0
+        // anyway so we can skip checking if that is the case.
+      }
+
+      // Report the violation
+      nsresult rv = AsyncReportViolation(
+          nullptr,                                    // aTriggeringElement
+          nullptr,                                    // aCSPEventListener
+          blockedURIForReporting,                     // aBlockedURI
+          nsCSPContext::BlockedContentSource::eSelf,  // aBlockedSource
+          nullptr,                                    // aOriginalURI
+          NS_LITERAL_STRING("navigate-to"),           // aViolatedDirective
+          i,                                          // aViolatedPolicyIndex
+          EmptyString(),                              // aObserverSubject
+          NS_ConvertUTF8toUTF16(spec),                // aSourceFile
+          EmptyString(),                              // aScriptSample
+          lineNumber,                                 // aLineNum
+          columnNumber);                              // aColumnNum
+      NS_ENSURE_SUCCESS(rv, rv);
+    }
+  }
+
+  *outAllowsNavigateTo = !atLeastOneBlock;
+  return NS_OK;
+}
+
 /**
  * Reduces some code repetition for the various logging situations in
  * LogViolationDetails.
  *
  * Call-sites for the eval/inline checks recieve two return values: allows
  * and violates.  Based on those, they must choose whether to call
  * LogViolationDetails or not.  Policies that are report-only allow the
  * loads/compilations but violations should still be reported.  Not all
--- a/dom/security/nsCSPParser.cpp
+++ b/dom/security/nsCSPParser.cpp
@@ -462,16 +462,32 @@ nsCSPBaseSrc* nsCSPParser::keywordSource
   if (CSP_IsKeyword(mCurToken, CSP_UNSAFE_EVAL)) {
     nsWeakPtr ctx = mCSPContext->GetLoadingContext();
     nsCOMPtr<Document> doc = do_QueryReferent(ctx);
     if (doc) {
       doc->SetHasUnsafeEvalCSP(true);
     }
     return new nsCSPKeywordSrc(CSP_UTF16KeywordToEnum(mCurToken));
   }
+
+  if (CSP_IsKeyword(mCurToken, CSP_UNSAFE_ALLOW_REDIRECTS)) {
+    if (!CSP_IsDirective(mCurDir[0],
+                         nsIContentSecurityPolicy::NAVIGATE_TO_DIRECTIVE)) {
+      // Only allow 'unsafe-allow-redirects' within navigate-to.
+      AutoTArray<nsString, 2> params = {
+          NS_LITERAL_STRING("unsafe-allow-redirects"),
+          NS_LITERAL_STRING("navigate-to")};
+      logWarningErrorToConsole(nsIScriptError::warningFlag,
+                               "IgnoringSourceWithinDirective", params);
+      return nullptr;
+    }
+
+    return new nsCSPKeywordSrc(CSP_UTF16KeywordToEnum(mCurToken));
+  }
+
   return nullptr;
 }
 
 // host-source = [ scheme "://" ] host [ port ] [ path ]
 nsCSPHostSrc* nsCSPParser::hostSource() {
   CSPPARSERLOG(("nsCSPParser::hostSource, mCurToken: %s, mCurValue: %s",
                 NS_ConvertUTF16toUTF8(mCurToken).get(),
                 NS_ConvertUTF16toUTF8(mCurValue).get()));
@@ -853,16 +869,29 @@ nsCSPDirective* nsCSPParser::directiveNa
   if (CSP_IsDirective(mCurToken,
                       nsIContentSecurityPolicy::REFLECTED_XSS_DIRECTIVE)) {
     AutoTArray<nsString, 1> params = {mCurToken};
     logWarningErrorToConsole(nsIScriptError::warningFlag,
                              "notSupportingDirective", params);
     return nullptr;
   }
 
+  // Bug 1529068: Implement navigate-to directive.
+  // Once all corner cases are resolved we can remove that special
+  // if-handling here and let the parser just fall through to
+  // return new nsCSPDirective.
+  if (CSP_IsDirective(mCurToken,
+                      nsIContentSecurityPolicy::NAVIGATE_TO_DIRECTIVE) &&
+      !StaticPrefs::security_csp_enableNavigateTo()) {
+    AutoTArray<nsString, 1> params = {mCurToken};
+    logWarningErrorToConsole(nsIScriptError::warningFlag,
+                             "couldNotProcessUnknownDirective", params);
+    return nullptr;
+  }
+
   // Make sure the directive does not already exist
   // (see http://www.w3.org/TR/CSP11/#parsing)
   if (mPolicy->hasDirective(CSP_StringToCSPDirective(mCurToken))) {
     AutoTArray<nsString, 1> params = {mCurToken};
     logWarningErrorToConsole(nsIScriptError::warningFlag, "duplicateDirective",
                              params);
     return nullptr;
   }
--- a/dom/security/nsCSPService.cpp
+++ b/dom/security/nsCSPService.cpp
@@ -247,16 +247,34 @@ CSPService::AsyncOnChannelRedirect(nsICh
   }
 
   nsCOMPtr<nsIURI> newUri;
   nsresult rv = newChannel->GetURI(getter_AddRefs(newUri));
   NS_ENSURE_SUCCESS(rv, rv);
 
   nsCOMPtr<nsILoadInfo> loadInfo = oldChannel->LoadInfo();
 
+  // Check CSP navigate-to
+  // We need to enforce the CSP of the document that initiated the load,
+  // which is the CSP to inherit.
+  nsCOMPtr<nsIContentSecurityPolicy> cspToInherit = loadInfo->GetCspToInherit();
+  if (cspToInherit) {
+    bool allowsNavigateTo = false;
+    rv = cspToInherit->GetAllowsNavigateTo(newUri, loadInfo,
+                                           true,  /* aWasRedirected */
+                                           false, /* aEnforceWhitelist */
+                                           &allowsNavigateTo);
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    if (!allowsNavigateTo) {
+      oldChannel->Cancel(NS_ERROR_CSP_NAVIGATE_TO_VIOLATION);
+      return NS_OK;
+    }
+  }
+
   // No need to continue processing if CSP is disabled or if the protocol
   // is *not* subject to CSP.
   // Please note, the correct way to opt-out of CSP using a custom
   // protocolHandler is to set one of the nsIProtocolHandler flags
   // that are whitelistet in subjectToCSP()
   nsContentPolicyType policyType = loadInfo->InternalContentPolicyType();
   if (!StaticPrefs::security_csp_enable() ||
       !subjectToCSP(newUri, policyType)) {
--- a/dom/security/nsCSPUtils.cpp
+++ b/dom/security/nsCSPUtils.cpp
@@ -311,16 +311,17 @@ CSPDirective CSP_ContentTypeToDirective(
     // CSP can not block csp reports
     case nsIContentPolicy::TYPE_CSP_REPORT:
       return nsIContentSecurityPolicy::NO_DIRECTIVE;
 
     case nsIContentPolicy::TYPE_SAVEAS_DOWNLOAD:
       return nsIContentSecurityPolicy::NO_DIRECTIVE;
 
     // Fall through to error for all other directives
+    // Note that we should never end up here for navigate-to
     default:
       MOZ_ASSERT(false, "Can not map nsContentPolicyType to CSPDirective");
   }
   return nsIContentSecurityPolicy::DEFAULT_SRC_DIRECTIVE;
 }
 
 nsCSPHostSrc* CSP_CreateHostSrcFromSelfURI(nsIURI* aSelfURI) {
   // Create the host first
@@ -1468,16 +1469,41 @@ bool nsCSPPolicy::hasDirective(CSPDirect
   for (uint32_t i = 0; i < mDirectives.Length(); i++) {
     if (mDirectives[i]->equals(aDir)) {
       return true;
     }
   }
   return false;
 }
 
+bool nsCSPPolicy::allowsNavigateTo(nsIURI* aURI, bool aWasRedirected,
+                                   bool aEnforceWhitelist) const {
+  bool allowsNavigateTo = true;
+
+  for (unsigned long i = 0; i < mDirectives.Length(); i++) {
+    if (mDirectives[i]->equals(
+            nsIContentSecurityPolicy::NAVIGATE_TO_DIRECTIVE)) {
+      // Early return if we can skip the whitelist AND 'unsafe-allow-redirects'
+      // is present.
+      if (!aEnforceWhitelist &&
+          mDirectives[i]->allows(CSP_UNSAFE_ALLOW_REDIRECTS, EmptyString(),
+                                 false)) {
+        return true;
+      }
+      // Otherwise, check against the whitelist.
+      if (!mDirectives[i]->permits(aURI, EmptyString(), aWasRedirected, false,
+                                   false, false)) {
+        allowsNavigateTo = false;
+      }
+    }
+  }
+
+  return allowsNavigateTo;
+}
+
 /*
  * Use this function only after ::allows() returned 'false'. Most and
  * foremost it's used to get the violated directive before sending reports.
  * The parameter outDirective is the equivalent of 'outViolatedDirective'
  * for the ::permits() function family.
  */
 void nsCSPPolicy::getDirectiveStringAndReportSampleForContentType(
     nsContentPolicyType aContentType, nsAString& outDirective,
--- a/dom/security/nsCSPUtils.h
+++ b/dom/security/nsCSPUtils.h
@@ -82,17 +82,18 @@ static const char* CSPStrDirectives[] = 
     "reflected-xss",              // REFLECTED_XSS_DIRECTIVE
     "base-uri",                   // BASE_URI_DIRECTIVE
     "form-action",                // FORM_ACTION_DIRECTIVE
     "manifest-src",               // MANIFEST_SRC_DIRECTIVE
     "upgrade-insecure-requests",  // UPGRADE_IF_INSECURE_DIRECTIVE
     "child-src",                  // CHILD_SRC_DIRECTIVE
     "block-all-mixed-content",    // BLOCK_ALL_MIXED_CONTENT
     "sandbox",                    // SANDBOX_DIRECTIVE
-    "worker-src"                  // WORKER_SRC_DIRECTIVE
+    "worker-src",                 // WORKER_SRC_DIRECTIVE
+    "navigate-to"                 // NAVIGATE_TO_DIRECTIVE
 };
 
 inline const char* CSP_CSPDirectiveToString(CSPDirective aDir) {
   return CSPStrDirectives[static_cast<uint32_t>(aDir)];
 }
 
 inline CSPDirective CSP_StringToCSPDirective(const nsAString& aDir) {
   nsString lowerDir = PromiseFlatString(aDir);
@@ -103,24 +104,25 @@ inline CSPDirective CSP_StringToCSPDirec
     if (lowerDir.EqualsASCII(CSPStrDirectives[i])) {
       return static_cast<CSPDirective>(i);
     }
   }
   NS_ASSERTION(false, "Can not convert unknown Directive to Integer");
   return nsIContentSecurityPolicy::NO_DIRECTIVE;
 }
 
-#define FOR_EACH_CSP_KEYWORD(MACRO)           \
-  MACRO(CSP_SELF, "'self'")                   \
-  MACRO(CSP_UNSAFE_INLINE, "'unsafe-inline'") \
-  MACRO(CSP_UNSAFE_EVAL, "'unsafe-eval'")     \
-  MACRO(CSP_NONE, "'none'")                   \
-  MACRO(CSP_NONCE, "'nonce-")                 \
-  MACRO(CSP_REPORT_SAMPLE, "'report-sample'") \
-  MACRO(CSP_STRICT_DYNAMIC, "'strict-dynamic'")
+#define FOR_EACH_CSP_KEYWORD(MACRO)             \
+  MACRO(CSP_SELF, "'self'")                     \
+  MACRO(CSP_UNSAFE_INLINE, "'unsafe-inline'")   \
+  MACRO(CSP_UNSAFE_EVAL, "'unsafe-eval'")       \
+  MACRO(CSP_NONE, "'none'")                     \
+  MACRO(CSP_NONCE, "'nonce-")                   \
+  MACRO(CSP_REPORT_SAMPLE, "'report-sample'")   \
+  MACRO(CSP_STRICT_DYNAMIC, "'strict-dynamic'") \
+  MACRO(CSP_UNSAFE_ALLOW_REDIRECTS, "'unsafe-allow-redirects'")
 
 enum CSPKeyword {
 #define KEYWORD_ENUM(id_, string_) id_,
   FOR_EACH_CSP_KEYWORD(KEYWORD_ENUM)
 #undef KEYWORD_ENUM
 
   // CSP_LAST_KEYWORD_VALUE always needs to be the last element in the enum
   // because we use it to calculate the size for the char* array.
@@ -657,16 +659,19 @@ class nsCSPPolicy {
   void getDirectiveAsString(CSPDirective aDir, nsAString& outDirective) const;
 
   uint32_t getSandboxFlags() const;
 
   inline uint32_t getNumDirectives() const { return mDirectives.Length(); }
 
   bool visitDirectiveSrcs(CSPDirective aDir, nsCSPSrcVisitor* aVisitor) const;
 
+  bool allowsNavigateTo(nsIURI* aURI, bool aWasRedirected,
+                        bool aEnforceWhitelist) const;
+
  private:
   nsUpgradeInsecureDirective* mUpgradeInsecDir;
   nsTArray<nsCSPDirective*> mDirectives;
   bool mReportOnly;
   bool mDeliveredViaMetaTag;
 };
 
 #endif /* nsCSPUtils_h___ */
new file mode 100644
--- /dev/null
+++ b/dom/security/test/csp/file_navigate_to.html
@@ -0,0 +1,11 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <title>Bug 1529068 Implement CSP 'navigate-to' directive</title>
+</head>
+<body>
+  <script type="text/javascript">
+  window.location = "http://www.example.com/";
+  </script>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/security/test/csp/file_navigate_to.sjs
@@ -0,0 +1,49 @@
+// Custom *.sjs file specifically for the needs of
+// https://bugzilla.mozilla.org/show_bug.cgi?id=1529068
+
+"use strict";
+Components.utils.importGlobalProperties(["URLSearchParams"]);
+
+const TEST_NAVIGATION_HEAD = `
+  <!DOCTYPE HTML>
+  <html>
+  <head>
+    <title>Bug 1529068 Implement CSP 'navigate-to' directive</title>`;
+
+const TEST_NAVIGATION_AFTER_META = `
+  </head>
+  <body>
+    <script type="text/javascript">
+    window.location = "`;
+
+const TEST_NAVIGATION_FOOT = `";
+    </script>
+  </body>
+  </html>
+  `;
+
+function handleRequest(request, response) {
+  const query = new URLSearchParams(request.queryString);
+
+  response.setHeader("Cache-Control", "no-cache", false);
+  response.setHeader("Content-Type", "text/html", false);
+
+  if (query.get("redir")) {
+    response.setStatusLine(request.httpVersion, "302", "Found");
+    response.setHeader("Location", query.get("redir"), false);
+    return;
+  }
+
+  response.write(TEST_NAVIGATION_HEAD);
+
+  // We need meta to set multiple CSP headers.
+  if (query.get("csp")) {
+    response.write("<meta http-equiv=\"Content-Security-Policy\" content=\""+query.get("csp")+"\">");
+  }
+  if (query.get("csp2")) {
+    response.write("<meta http-equiv=\"Content-Security-Policy\" content=\""+query.get("csp2")+"\">");
+  }
+
+  response.write(TEST_NAVIGATION_AFTER_META + query.get("target") + TEST_NAVIGATION_FOOT);
+
+}
new file mode 100644
--- /dev/null
+++ b/dom/security/test/csp/file_navigate_to_request.html
@@ -0,0 +1,18 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <meta charset="utf-8">
+</head>
+<script type="text/javascript">
+  // The idea with this file is to convert responses into requests. 
+  // This is needed because we don't have 
+  // specialpowers-http-notify-response
+
+  // Response from this file => request to www.example.com => Allowed
+  // CSP error               => Blocked
+
+  fetch('http://www.example.com/');
+</script>
+<body>
+</body>
+</html>
--- a/dom/security/test/csp/mochitest.ini
+++ b/dom/security/test/csp/mochitest.ini
@@ -398,8 +398,12 @@ skip-if =
   (toolkit == 'android') || (os == 'win' && bits == 32) ||
   fission # Crashes: @ mozilla::dom::ContentParent::CommonCreateWindow(mozilla::dom::PBrowserParent*, bool, unsigned int const&, bool const&, bool const&, bool const&, nsIURI*, nsTString<char> const&, float const&, unsigned long, nsTString<char16_t> const&, nsresult&, nsCOMPtr<nsIRemoteTab>&, bool*, int&, nsIPrincipal*, nsIReferrerInfo*, bool, nsIContentSecurityPolicy*)
 support-files =
   file_reloadInFreshProcess.sjs
 [test_parent_location_js.html]
 support-files =
   file_parent_location_js.html
   file_iframe_parent_location_js.html
+[test_navigate_to.html]
+support-files =
+  file_navigate_to.sjs
+  file_navigate_to_request.html
new file mode 100644
--- /dev/null
+++ b/dom/security/test/csp/test_navigate_to.html
@@ -0,0 +1,165 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <title>Bug 1529068 Implement CSP 'navigate-to' directive</title>
+  <!-- Including SimpleTest.js so we can use waitForExplicitFinish !-->
+  <script src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+  <p id="display"></p>
+  <div id="content">
+    <iframe style="width:100%;" id="testframe"></iframe>
+  </div>
+
+<script class="testbody" type="text/javascript">
+
+/*
+ * Description of the test:
+ *   We load a page with a given CSP and verify that navigations are correctly
+ *   evaluated through the "navigate-to" directive.
+ */
+SpecialPowers.pushPrefEnv({"set": [["security.csp.enableNavigateTo", true]]});
+SimpleTest.waitForExplicitFinish();
+
+// Note: The final website for the navigation chain must always be: www.example.com
+var tests = [
+  {
+    result : "blocked",
+    policy : "navigate-to www.mozilla.com",
+    target : "http://www.example.com/"
+  },
+  {
+    result : "allowed",
+    policy : "navigate-to www.example.com",
+    target : "http://www.example.com/"
+  },
+  {
+  	// Test path-sensitivity 
+    result : "blocked",
+    policy : "navigate-to http://www.example.com/full/path/to/file",
+    target : "http://www.example.com/"
+  },
+  {
+    // Test path-sensitivity  with 'unsafe-allow-redirects'
+    // Allowed to make the request, but blocked when the response is received. 
+    result : "blocked",
+    policy : "navigate-to 'unsafe-allow-redirects' http://www.example.com/tests/dom/security/test/csp/WRONG/",
+    target : "http://www.example.com/tests/dom/security/test/csp/file_navigate_to_request.html"
+  },
+  {
+  	// Test scheme
+    result : "blocked",
+    policy : "navigate-to https://www.example.com/",
+    target : "http://www.example.com/"
+  },
+  {
+  	// Redirect from tracking.example.com to www.example.com
+    result : "blocked",
+    policy : "navigate-to www.example.com",
+    target : "http://tracking.example.com/tests/dom/security/test/csp/file_navigate_to.sjs?redir=http://www.example.com/"
+  },
+  {
+ 	// Redirect from tracking.example.com to www.example.com (Explicitly allowed)
+    result : "allowed",
+    policy : "navigate-to tracking.example.com www.example.com",
+    target : "http://tracking.example.com/tests/dom/security/test/csp/file_navigate_to.sjs?redir=http://www.example.com/"
+  },
+  {
+ 	// Redirect from tracking.example.com to www.example.com ('unsafe-allow-redirects')
+    result : "allowed",
+    policy : "navigate-to 'unsafe-allow-redirects' www.example.com",
+    target : "http://tracking.example.com/tests/dom/security/test/csp/file_navigate_to.sjs?redir=http://www.example.com/"
+  },
+  // No path-sensitivity after redirect
+  {
+    result : "allowed",
+    policy : "navigate-to tracking.example.com http://www.example.com/full/path/to/file",
+    target : "http://tracking.example.com/tests/dom/security/test/csp/file_navigate_to.sjs?redir=http://www.example.com/"
+  },
+  // Multiple CSP directives, first block (origin) second allow
+  {
+    result : "allowed",
+    policy : "img-src 'none'; navigate-to www.example.com",
+    target : "http://www.example.com/"
+  },
+  // Multiple CSP directives, first allow (origin) second block
+  {
+    result : "blocked",
+    policy : "img-src www.example.com mochi.test:8888; navigate-to www.mozilla.com",
+    target : "http://www.example.com/"
+  },
+  // Multiple CSPs, first allow second block
+  {
+    result  : "blocked",
+    policy  : "navigate-to www.example.com",
+    policy2 : "navigate-to www.mozilla.com",
+    target  : "http://www.example.com/"
+  },
+  // Multiple CSPs, first block second allow
+  {
+    result  : "blocked",
+    policy  : "navigate-to www.mozilla.com",
+    policy2 : "navigate-to www.example.com",
+    target  : "http://www.example.com/"
+  },
+];
+
+// initializing to -1 so we start at index 0 when we start the test
+var counter = -1;
+
+function checkResult(aResult) {
+  is(aResult, tests[counter].result, "should be " + tests[counter].result + " in test " + counter + 
+    "(" + tests[counter].policy + ", " + tests[counter].target + ")!");
+  loadNextTest();
+}
+
+// We use the examiner to identify requests that hit the wire and requests
+// that are blocked by CSP and bubble up the result to the including iframe
+// document (parent).
+function examiner() {
+  SpecialPowers.addObserver(this, "csp-on-violate-policy");
+  SpecialPowers.addObserver(this, "specialpowers-http-notify-request");
+}
+examiner.prototype  = {
+  observe(subject, topic, data) {
+    if (topic === "specialpowers-http-notify-request" && data === "http://www.example.com/" ) {
+      checkResult("allowed");
+    }
+    if (topic === "csp-on-violate-policy" && data === "navigate-to") {
+      checkResult("blocked");
+    }
+
+  },
+  remove() {
+    SpecialPowers.removeObserver(this, "csp-on-violate-policy");
+    SpecialPowers.removeObserver(this, "specialpowers-http-notify-request");
+  }
+}
+window.NavigationActionExaminer = new examiner();
+
+function loadNextTest() {
+  counter++;
+  if (counter == tests.length) {
+    window.NavigationActionExaminer.remove();
+    SimpleTest.finish();
+    return;
+  }
+
+  var src = "file_navigate_to.sjs";
+  // append the CSP that should be used to serve the file
+  src += "?csp=" + escape(tests[counter].policy);
+  if( tests[counter].policy2 ) {
+     src += "&csp2=" + escape(tests[counter].policy2);
+  }
+  src += "&target=" + escape(tests[counter].target);
+
+  document.getElementById("testframe").src = src;
+}
+
+// start running the tests
+loadNextTest();
+
+</script>
+</body>
+</html>
--- a/dom/security/test/gtest/TestCSPParser.cpp
+++ b/dom/security/test/gtest/TestCSPParser.cpp
@@ -144,37 +144,42 @@ nsresult runTest(
 // ============================= run Tests ========================
 
 nsresult runTestSuite(const PolicyTest* aPolicies, uint32_t aPolicyCount,
                       uint32_t aExpectedPolicyCount) {
   nsresult rv;
   nsCOMPtr<nsIPrefBranch> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID);
   bool experimentalEnabledCache = false;
   bool strictDynamicEnabledCache = false;
+  bool navigateTo = false;
   if (prefs) {
     prefs->GetBoolPref("security.csp.experimentalEnabled",
                        &experimentalEnabledCache);
     prefs->SetBoolPref("security.csp.experimentalEnabled", true);
 
     prefs->GetBoolPref("security.csp.enableStrictDynamic",
                        &strictDynamicEnabledCache);
     prefs->SetBoolPref("security.csp.enableStrictDynamic", true);
+
+    prefs->GetBoolPref("security.csp.enableNavigateTo", &navigateTo);
+    prefs->SetBoolPref("security.csp.enableNavigateTo", true);
   }
 
   for (uint32_t i = 0; i < aPolicyCount; i++) {
     rv = runTest(aExpectedPolicyCount, aPolicies[i].policy,
                  aPolicies[i].expectedResult);
     NS_ENSURE_SUCCESS(rv, rv);
   }
 
   if (prefs) {
     prefs->SetBoolPref("security.csp.experimentalEnabled",
                        experimentalEnabledCache);
     prefs->SetBoolPref("security.csp.enableStrictDynamic",
                        strictDynamicEnabledCache);
+    prefs->SetBoolPref("security.csp.enableNavigateTo", navigateTo);
   }
 
   return NS_OK;
 }
 
 // ============================= TestDirectives ========================
 
 TEST(CSPParser, Directives)
@@ -216,16 +221,22 @@ TEST(CSPParser, Directives)
     { "script-src 'nonce-foo' 'strict-dynamic' 'unsafe-inline' https:  ",
       "script-src 'nonce-foo' 'strict-dynamic' 'unsafe-inline' https:" },
     { "default-src 'sha256-siVR8' 'strict-dynamic' 'unsafe-inline' https:  ",
       "default-src 'sha256-siVR8' 'unsafe-inline' https:" },
     { "worker-src https://example.com",
       "worker-src https://example.com" },
     { "worker-src http://worker.com; frame-src http://frame.com; child-src http://child.com",
       "worker-src http://worker.com; frame-src http://frame.com; child-src http://child.com" },
+    { "navigate-to http://example.com",
+      "navigate-to http://example.com"},
+    { "navigate-to 'unsafe-allow-redirects' http://example.com",
+      "navigate-to 'unsafe-allow-redirects' http://example.com"},
+    { "script-src 'unsafe-allow-redirects' http://example.com",
+      "script-src http://example.com"},
       // clang-format on
   };
 
   uint32_t policyCount = sizeof(policies) / sizeof(PolicyTest);
   ASSERT_TRUE(NS_SUCCEEDED(runTestSuite(policies, policyCount, 1)));
 }
 
 // ============================= TestKeywords ========================
--- a/modules/libpref/init/StaticPrefList.yaml
+++ b/modules/libpref/init/StaticPrefList.yaml
@@ -6756,16 +6756,22 @@
   mirror: always
 
 # Hardware Origin-bound Second Factor Support
 - name: security.webauth.webauthn
   type: bool
   value: true
   mirror: always
 
+# Navigate-to CSP 3 directive
+- name: security.csp.enableNavigateTo
+  type: bool 
+  value: false
+  mirror: always
+
 # No way to enable on Android, Bug 1552602
 - name: security.webauth.u2f
   type: bool
 #if defined(MOZ_WIDGET_ANDROID)
   value: false
 #else
   value: true
 #endif
--- a/testing/web-platform/meta/content-security-policy/navigate-to/__dir__.ini
+++ b/testing/web-platform/meta/content-security-policy/navigate-to/__dir__.ini
@@ -1,2 +1,3 @@
+prefs: [security.csp.enableNavigateTo:true]
 disabled:
   if (os == "win"): https://bugzilla.mozilla.org/show_bug.cgi?id=1450635
--- a/testing/web-platform/meta/content-security-policy/navigate-to/child-navigates-parent-allowed.html.ini
+++ b/testing/web-platform/meta/content-security-policy/navigate-to/child-navigates-parent-allowed.html.ini
@@ -1,3 +1,8 @@
-[child-navigates-parent-allowed.html]
+[child-navigates-parent-allowed.html]	
   disabled:
     if os == "linux": https://bugzilla.mozilla.org/show_bug.cgi?id=1450660
+  
+  expected: TIMEOUT
+
+  [Test that the child can navigate the parent because the relevant policy belongs to the navigation initiator (in this case the child, which has the policy `navigate-to 'self'`)]
+    expected: NOTRUN
--- a/testing/web-platform/meta/content-security-policy/navigate-to/child-navigates-parent-blocked.sub.html.ini
+++ b/testing/web-platform/meta/content-security-policy/navigate-to/child-navigates-parent-blocked.sub.html.ini
@@ -1,12 +1,15 @@
 [child-navigates-parent-blocked.sub.html]
   disabled:
     if (os == "android") and not e10s: https://bugzilla.mozilla.org/show_bug.cgi?id=1511193
+
+  expected: TIMEOUT 
+  
   [Violation report status OK.]
     expected: FAIL
 
   [Test that the child can't navigate the parent because the relevant policy belongs to the navigation initiator (in this case the child)]
     expected: FAIL
 
   [Test that the child can't navigate the parent because the relevant policy belongs to the navigation initiator (in this case the child which has the policy `navigate-to 'none'`)]
-    expected: FAIL
+    expected: NOTRUN
 
deleted file mode 100644
--- a/testing/web-platform/meta/content-security-policy/navigate-to/form-blocked.sub.html.ini
+++ /dev/null
@@ -1,7 +0,0 @@
-[form-blocked.sub.html]
-  [Violation report status OK.]
-    expected: FAIL
-
-  [Test that the child iframe navigation is not allowed]
-    expected: FAIL
-
deleted file mode 100644
--- a/testing/web-platform/meta/content-security-policy/navigate-to/form-cross-origin-blocked.sub.html.ini
+++ /dev/null
@@ -1,10 +0,0 @@
-[form-cross-origin-blocked.sub.html]
-  [Test that the child iframe navigation is blocked]
-    expected: FAIL
-
-  [Violation report status OK.]
-    expected: FAIL
-
-  [Test that the child iframe navigation is not allowed]
-    expected: FAIL
-
--- a/testing/web-platform/meta/content-security-policy/navigate-to/form-redirected-blocked.sub.html.ini
+++ b/testing/web-platform/meta/content-security-policy/navigate-to/form-redirected-blocked.sub.html.ini
@@ -1,10 +1,10 @@
 [form-redirected-blocked.sub.html]
+  expected:
+    if (os == "android"): TIMEOUT
   [Test that the child iframe navigation is blocked]
-    expected: FAIL
-
-  [Violation report status OK.]
-    expected: FAIL
+    expected:
+      if (os == "android"): NOTRUN
 
   [Test that the child iframe navigation is not allowed]
-    expected: FAIL
-
+    expected:
+      if (os == "android"): NOTRUN
deleted file mode 100644
--- a/testing/web-platform/meta/content-security-policy/navigate-to/href-location-blocked.sub.html.ini
+++ /dev/null
@@ -1,7 +0,0 @@
-[href-location-blocked.sub.html]
-  [Violation report status OK.]
-    expected: FAIL
-
-  [Test that the child iframe navigation is not allowed]
-    expected: FAIL
-
--- a/testing/web-platform/meta/content-security-policy/navigate-to/href-location-cross-origin-blocked.sub.html.ini
+++ b/testing/web-platform/meta/content-security-policy/navigate-to/href-location-cross-origin-blocked.sub.html.ini
@@ -1,10 +1,3 @@
 [href-location-cross-origin-blocked.sub.html]
-  [Test that the child iframe navigation is blocked]
-    expected: FAIL
-
-  [Violation report status OK.]
-    expected: FAIL
-
-  [Test that the child iframe navigation is not allowed]
-    expected: FAIL
-
+  disabled:
+    if (os == "android"): Passes on debug but fails on optimized
--- a/testing/web-platform/meta/content-security-policy/navigate-to/href-location-redirected-blocked.sub.html.ini
+++ b/testing/web-platform/meta/content-security-policy/navigate-to/href-location-redirected-blocked.sub.html.ini
@@ -1,10 +1,10 @@
 [href-location-redirected-blocked.sub.html]
+  expected:
+    if (os == "android"): TIMEOUT
   [Test that the child iframe navigation is blocked]
-    expected: FAIL
-
-  [Violation report status OK.]
-    expected: FAIL
+    expected:
+      if (os == "android"): NOTRUN
 
   [Test that the child iframe navigation is not allowed]
-    expected: FAIL
-
+    expected:
+      if (os == "android"): NOTRUN
deleted file mode 100644
--- a/testing/web-platform/meta/content-security-policy/navigate-to/link-click-blocked.sub.html.ini
+++ /dev/null
@@ -1,7 +0,0 @@
-[link-click-blocked.sub.html]
-  [Violation report status OK.]
-    expected: FAIL
-
-  [Test that the child iframe navigation is not allowed]
-    expected: FAIL
-
deleted file mode 100644
--- a/testing/web-platform/meta/content-security-policy/navigate-to/link-click-cross-origin-blocked.sub.html.ini
+++ /dev/null
@@ -1,10 +0,0 @@
-[link-click-cross-origin-blocked.sub.html]
-  [Test that the child iframe navigation is blocked]
-    expected: FAIL
-
-  [Violation report status OK.]
-    expected: FAIL
-
-  [Test that the child iframe navigation is not allowed]
-    expected: FAIL
-
--- a/testing/web-platform/meta/content-security-policy/navigate-to/link-click-redirected-blocked.sub.html.ini
+++ b/testing/web-platform/meta/content-security-policy/navigate-to/link-click-redirected-blocked.sub.html.ini
@@ -1,10 +1,10 @@
 [link-click-redirected-blocked.sub.html]
+  expected:
+    if (os == "android"): TIMEOUT
   [Test that the child iframe navigation is blocked]
-    expected: FAIL
-
-  [Violation report status OK.]
-    expected: FAIL
+    expected:
+      if (os == "android"): NOTRUN
 
   [Test that the child iframe navigation is not allowed]
-    expected: FAIL
-
+    expected:
+      if (os == "android"): NOTRUN
deleted file mode 100644
--- a/testing/web-platform/meta/content-security-policy/navigate-to/meta-refresh-blocked.sub.html.ini
+++ /dev/null
@@ -1,7 +0,0 @@
-[meta-refresh-blocked.sub.html]
-  [Violation report status OK.]
-    expected: FAIL
-
-  [Test that the child iframe navigation is not allowed]
-    expected: FAIL
-
deleted file mode 100644
--- a/testing/web-platform/meta/content-security-policy/navigate-to/meta-refresh-cross-origin-blocked.sub.html.ini
+++ /dev/null
@@ -1,10 +0,0 @@
-[meta-refresh-cross-origin-blocked.sub.html]
-  [Test that the child iframe navigation is blocked]
-    expected: FAIL
-
-  [Violation report status OK.]
-    expected: FAIL
-
-  [Test that the child iframe navigation is not allowed]
-    expected: FAIL
-
--- a/testing/web-platform/meta/content-security-policy/navigate-to/meta-refresh-redirected-blocked.sub.html.ini
+++ b/testing/web-platform/meta/content-security-policy/navigate-to/meta-refresh-redirected-blocked.sub.html.ini
@@ -1,10 +1,11 @@
 [meta-refresh-redirected-blocked.sub.html]
+  expected:
+    if (os == "android"): TIMEOUT
+
   [Test that the child iframe navigation is blocked]
-    expected: FAIL
-
-  [Violation report status OK.]
-    expected: FAIL
+    expected:
+      if (os == "android"): NOTRUN
 
   [Test that the child iframe navigation is not allowed]
-    expected: FAIL
-
+    expected:
+      if (os == "android"): NOTRUN
deleted file mode 100644
--- a/testing/web-platform/meta/content-security-policy/navigate-to/parent-navigates-child-blocked.html.ini
+++ /dev/null
@@ -1,10 +0,0 @@
-[parent-navigates-child-blocked.html]
-  [Test that the parent can't navigate the child because the relevant policy belongs to the navigation initiator (in this case the parent)]
-    expected: FAIL
-
-  [Violation report status OK.]
-    expected: FAIL
-
-  [Test that the parent can't navigate the child because the relevant policy belongs to the navigation initiator (in this case the parent, which has the policy `navigate-to support/wait_for_navigation.html;`)]
-    expected: FAIL
-
--- a/testing/web-platform/meta/content-security-policy/navigate-to/spv-only-sent-to-initiator.sub.html.ini
+++ b/testing/web-platform/meta/content-security-policy/navigate-to/spv-only-sent-to-initiator.sub.html.ini
@@ -1,5 +1,7 @@
 [spv-only-sent-to-initiator.sub.html]
-  expected: TIMEOUT
   [Test that no spv event is raised]
-    expected: NOTRUN
+    expected: FAIL
 
+  [Violation report status OK.]
+    expected: FAIL
+
--- a/testing/web-platform/meta/content-security-policy/navigate-to/unsafe-allow-redirects/blocked-end-of-chain.sub.html.ini
+++ b/testing/web-platform/meta/content-security-policy/navigate-to/unsafe-allow-redirects/blocked-end-of-chain.sub.html.ini
@@ -1,6 +1,9 @@
 [blocked-end-of-chain.sub.html]
   disabled:
     if os == "android" and not e10s: https://bugzilla.mozilla.org/show_bug.cgi?id=1511193
+
+  expected: TIMEOUT
+
   [Test that the child iframe navigation is blocked]
-    expected: FAIL
+    expected: NOTRUN
 
--- a/xpcom/base/ErrorList.py
+++ b/xpcom/base/ErrorList.py
@@ -787,16 +787,17 @@ with modules["PROFILE"]:
     errors["NS_ERROR_DATABASE_CHANGED"] = FAILURE(202)
 
 
 # =======================================================================
 # 21: NS_ERROR_MODULE_SECURITY
 # =======================================================================
 with modules["SECURITY"]:
     # Error code for CSP
+    errors["NS_ERROR_CSP_NAVIGATE_TO_VIOLATION"] = FAILURE(97)
     errors["NS_ERROR_CSP_FORM_ACTION_VIOLATION"] = FAILURE(98)
     errors["NS_ERROR_CSP_FRAME_ANCESTOR_VIOLATION"] = FAILURE(99)
 
     # Error code for Sub-Resource Integrity
     errors["NS_ERROR_SRI_CORRUPT"] = FAILURE(200)
     errors["NS_ERROR_SRI_DISABLED"] = FAILURE(201)
     errors["NS_ERROR_SRI_NOT_ELIGIBLE"] = FAILURE(202)
     errors["NS_ERROR_SRI_UNEXPECTED_HASH_TYPE"] = FAILURE(203)