Bug 1250048 - CSP manifest-src doesn't override default-src. r=ckerschb,bkelly,ehsan
authorMarcos Caceres <marcos@marcosc.com>
Thu, 07 Apr 2016 14:13:09 -0700
changeset 332130 a80b31406b47c4b8dc6154f89758a16f8faa5ce3
parent 332129 e91dafc1450e6174d57f48d3633a4f351325d720
child 332131 36c71d8166907b11a064b10644409c4f5e04175f
push id1146
push userCallek@gmail.com
push dateMon, 25 Jul 2016 16:35:44 +0000
treeherdermozilla-release@a55778f9cd5a [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersckerschb, bkelly, ehsan
bugs1250048
milestone48.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 1250048 - CSP manifest-src doesn't override default-src. r=ckerschb,bkelly,ehsan MozReview-Commit-ID: Ceu3sYUcML4
dom/fetch/Fetch.cpp
dom/fetch/InternalRequest.cpp
dom/fetch/InternalRequest.h
dom/fetch/Request.cpp
dom/fetch/Request.h
dom/manifest/ManifestObtainer.jsm
dom/security/nsContentSecurityManager.cpp
dom/security/test/csp/browser.ini
dom/security/test/csp/browser_manifest-src-override-default-src.js
dom/security/test/csp/browser_test_web_manifest.js
dom/security/test/csp/browser_test_web_manifest_mixed_content.js
dom/security/test/csp/file_testserver.sjs
dom/webidl/Request.webidl
--- a/dom/fetch/Fetch.cpp
+++ b/dom/fetch/Fetch.cpp
@@ -149,16 +149,24 @@ already_AddRefed<Promise>
 FetchRequest(nsIGlobalObject* aGlobal, const RequestOrUSVString& aInput,
              const RequestInit& aInit, ErrorResult& aRv)
 {
   RefPtr<Promise> p = Promise::Create(aGlobal, aRv);
   if (NS_WARN_IF(aRv.Failed())) {
     return nullptr;
   }
 
+  // Double check that we have chrome privileges if the Request's content
+  // policy type has been overridden.  Note, we must do this before
+  // entering the global below.  Otherwise the IsCallerChrome() will
+  // always fail.
+  MOZ_ASSERT_IF(aInput.IsRequest() &&
+                aInput.GetAsRequest().IsContentPolicyTypeOverridden(),
+                nsContentUtils::IsCallerChrome());
+
   AutoJSAPI jsapi;
   jsapi.Init(aGlobal);
   JSContext* cx = jsapi.cx();
 
   JS::Rooted<JSObject*> jsGlobal(cx, aGlobal->GetGlobalJSObject());
   GlobalObject global(cx, jsGlobal);
 
   RefPtr<Request> request = Request::Constructor(global, aInput, aInit, aRv);
--- a/dom/fetch/InternalRequest.cpp
+++ b/dom/fetch/InternalRequest.cpp
@@ -34,22 +34,25 @@ InternalRequest::GetRequestConstructorCo
   // The "client" is not stored in our implementation. Fetch API users should
   // use the appropriate window/document/principal and other Gecko security
   // mechanisms as appropriate.
   copy->mSameOriginDataURL = true;
   copy->mPreserveContentCodings = true;
   // The default referrer is already about:client.
   copy->mReferrerPolicy = mReferrerPolicy;
 
-  copy->mContentPolicyType = nsIContentPolicy::TYPE_FETCH;
+  copy->mContentPolicyType = mContentPolicyTypeOverridden ?
+                             mContentPolicyType :
+                             nsIContentPolicy::TYPE_FETCH;
   copy->mMode = mMode;
   copy->mCredentialsMode = mCredentialsMode;
   copy->mCacheMode = mCacheMode;
   copy->mRedirectMode = mRedirectMode;
   copy->mCreatedByFetchEvent = mCreatedByFetchEvent;
+  copy->mContentPolicyTypeOverridden = mContentPolicyTypeOverridden;
   return copy.forget();
 }
 
 already_AddRefed<InternalRequest>
 InternalRequest::Clone()
 {
   RefPtr<InternalRequest> clone = new InternalRequest(*this);
 
@@ -88,30 +91,38 @@ InternalRequest::InternalRequest(const I
   , mForceOriginHeader(aOther.mForceOriginHeader)
   , mPreserveContentCodings(aOther.mPreserveContentCodings)
   , mSameOriginDataURL(aOther.mSameOriginDataURL)
   , mSkipServiceWorker(aOther.mSkipServiceWorker)
   , mSynchronous(aOther.mSynchronous)
   , mUnsafeRequest(aOther.mUnsafeRequest)
   , mUseURLCredentials(aOther.mUseURLCredentials)
   , mCreatedByFetchEvent(aOther.mCreatedByFetchEvent)
+  , mContentPolicyTypeOverridden(aOther.mContentPolicyTypeOverridden)
 {
   // NOTE: does not copy body stream... use the fallible Clone() for that
 }
 
 InternalRequest::~InternalRequest()
 {
 }
 
 void
 InternalRequest::SetContentPolicyType(nsContentPolicyType aContentPolicyType)
 {
   mContentPolicyType = aContentPolicyType;
 }
 
+void
+InternalRequest::OverrideContentPolicyType(nsContentPolicyType aContentPolicyType)
+{
+  SetContentPolicyType(aContentPolicyType);
+  mContentPolicyTypeOverridden = true;
+}
+
 /* static */
 RequestContext
 InternalRequest::MapContentPolicyTypeToRequestContext(nsContentPolicyType aContentPolicyType)
 {
   RequestContext context = RequestContext::Internal;
   switch (aContentPolicyType) {
   case nsIContentPolicy::TYPE_OTHER:
     context = RequestContext::Internal;
--- a/dom/fetch/InternalRequest.h
+++ b/dom/fetch/InternalRequest.h
@@ -330,16 +330,19 @@ public:
   ContentPolicyType() const
   {
     return mContentPolicyType;
   }
 
   void
   SetContentPolicyType(nsContentPolicyType aContentPolicyType);
 
+  void
+  OverrideContentPolicyType(nsContentPolicyType aContentPolicyType);
+
   RequestContext
   Context() const
   {
     return MapContentPolicyTypeToRequestContext(mContentPolicyType);
   }
 
   bool
   UnsafeRequest() const
@@ -423,16 +426,22 @@ public:
   IsWorkerRequest() const;
 
   bool
   IsClientRequest() const;
 
   void
   MaybeSkipCacheIfPerformingRevalidation();
 
+  bool
+  IsContentPolicyTypeOverridden() const
+  {
+    return mContentPolicyTypeOverridden;
+  }
+
   static RequestMode
   MapChannelToRequestMode(nsIChannel* aChannel);
 
   static RequestCredentials
   MapChannelToRequestCredentials(nsIChannel* aChannel);
 
 private:
   // Does not copy mBodyStream.  Use fallible Clone() for complete copy.
@@ -476,14 +485,18 @@ private:
   bool mSkipServiceWorker;
   bool mSynchronous;
   bool mUnsafeRequest;
   bool mUseURLCredentials;
   // This is only set when a Request object is created by a fetch event.  We
   // use it to check if Service Workers are simply fetching intercepted Request
   // objects without modifying them.
   bool mCreatedByFetchEvent = false;
+  // This is only set when Request.overrideContentPolicyType() has been set.
+  // It is illegal to pass such a Request object to a fetch() method unless
+  // if the caller has chrome privileges.
+  bool mContentPolicyTypeOverridden = false;
 };
 
 } // namespace dom
 } // namespace mozilla
 
 #endif // mozilla_dom_InternalRequest_h
--- a/dom/fetch/Request.cpp
+++ b/dom/fetch/Request.cpp
@@ -277,16 +277,17 @@ Request::Constructor(const GlobalObject&
       aRv.ThrowTypeError<MSG_FETCH_BODY_CONSUMED_ERROR>();
       return nullptr;
     }
     if (body) {
       temporaryBody = body;
     }
 
     request = inputReq->GetInternalRequest();
+
   } else {
     request = new InternalRequest();
   }
 
   request = request->GetRequestConstructorCopy(global, aRv);
   if (NS_WARN_IF(aRv.Failed())) {
     return nullptr;
   }
--- a/dom/fetch/Request.h
+++ b/dom/fetch/Request.h
@@ -81,19 +81,25 @@ public:
 
   RequestContext
   Context() const
   {
     return mRequest->Context();
   }
 
   void
-  SetContentPolicyType(nsContentPolicyType aContentPolicyType)
+  OverrideContentPolicyType(nsContentPolicyType aContentPolicyType)
   {
-    mRequest->SetContentPolicyType(aContentPolicyType);
+    mRequest->OverrideContentPolicyType(aContentPolicyType);
+  }
+
+  bool
+  IsContentPolicyTypeOverridden() const
+  {
+    return mRequest->IsContentPolicyTypeOverridden();
   }
 
   void
   GetReferrer(nsAString& aReferrer) const
   {
     mRequest->GetReferrer(aReferrer);
   }
 
--- a/dom/manifest/ManifestObtainer.jsm
+++ b/dom/manifest/ManifestObtainer.jsm
@@ -61,17 +61,22 @@ this.ManifestObtainer = { // jshint igno
    * Public interface for obtaining a web manifest from a XUL browser.
    * @param  {Window} The content Window from which to extract the manifest.
    * @return {Promise<Object>} The processed manifest.
    */
   contentObtainManifest: Task.async(function* (aContent) {
     if (!aContent || isXULBrowser(aContent)) {
       throw new TypeError("Invalid input. Expected a DOM Window.");
     }
-    const manifest = yield fetchManifest(aContent);
+    let manifest;
+    try {
+      manifest = yield fetchManifest(aContent);
+    } catch (err) {
+      throw err;
+    }
     return manifest;
   }
 )};
 
 function toError(aErrorClone) {
   let error;
   switch (aErrorClone.name) {
   case "TypeError":
@@ -129,47 +134,27 @@ const fetchManifest = Task.async(functio
   }
   const elem = aWindow.document.querySelector("link[rel~='manifest']");
   if (!elem || !elem.getAttribute("href")) {
     let msg = `No manifest to fetch at ${aWindow.location}`;
     throw new Error(msg);
   }
   // Throws on malformed URLs
   const manifestURL = new aWindow.URL(elem.href, elem.baseURI);
-  if (!canLoadManifest(elem)) {
-    let msg = `Content Security Policy: The page's settings blocked the `;
-    msg += `loading of a resource at ${elem.href}`;
-    throw new Error(msg);
-  }
   const reqInit = {
     mode: "cors"
   };
   if (elem.crossOrigin === "use-credentials") {
     reqInit.credentials = "include";
   }
-  const req = new aWindow.Request(manifestURL, reqInit);
-  req.setContentPolicyType(Ci.nsIContentPolicy.TYPE_WEB_MANIFEST);
-  const response = yield aWindow.fetch(req);
+  const request = new aWindow.Request(manifestURL, reqInit);
+  request.overrideContentPolicyType(Ci.nsIContentPolicy.TYPE_WEB_MANIFEST);
+  let response;
+  try {
+    response = yield aWindow.fetch(request);
+  } catch (err) {
+    throw err;
+  }
   const manifest = yield processResponse(response, aWindow);
   return manifest;
 });
 
-/**
- * Checks against security manager if we can load the web manifest.
- * @param  {HTMLLinkElement} aElem The HTML element to security check.
- * @return {Boolean} True if it can, false if it can't.
- */
-function canLoadManifest(aElem) {
-  const contentPolicy = Cc["@mozilla.org/layout/content-policy;1"]
-    .getService(Ci.nsIContentPolicy);
-  const mimeType = aElem.type || "application/manifest+json";
-  const elemURI = BrowserUtils.makeURI(
-    aElem.href, aElem.ownerDocument.characterSet
-  );
-  const shouldLoad = contentPolicy.shouldLoad(
-    Ci.nsIContentPolicy.TYPE_WEB_MANIFEST, elemURI,
-    aElem.ownerDocument.documentURIObject,
-    aElem, mimeType, null
-  );
-  return shouldLoad === Ci.nsIContentPolicy.ACCEPT;
-}
-
 this.EXPORTED_SYMBOLS = ["ManifestObtainer"]; // jshint ignore:line
--- a/dom/security/nsContentSecurityManager.cpp
+++ b/dom/security/nsContentSecurityManager.cpp
@@ -291,16 +291,22 @@ DoContentSecurityChecks(nsIURI* aURI, ns
       break;
     }
 
     case nsIContentPolicy::TYPE_IMAGESET: {
       MOZ_ASSERT(false, "contentPolicyType not supported yet");
       break;
     }
 
+    case nsIContentPolicy::TYPE_WEB_MANIFEST: {
+      mimeTypeGuess = NS_LITERAL_CSTRING("application/manifest+json");
+      requestingContext = aLoadInfo->LoadingNode();
+      break;
+    }
+
     default:
       // nsIContentPolicy::TYPE_INVALID
       MOZ_ASSERT(false, "can not perform security check without a valid contentType");
   }
 
   int16_t shouldLoad = nsIContentPolicy::ACCEPT;
   nsresult rv = NS_CheckContentLoadPolicy(internalContentPolicyType,
                                           aURI,
--- a/dom/security/test/csp/browser.ini
+++ b/dom/security/test/csp/browser.ini
@@ -1,5 +1,13 @@
 [DEFAULT]
 support-files =
   !/dom/security/test/csp/file_testserver.sjs
+  !/dom/security/test/csp/file_web_manifest.html
+  !/dom/security/test/csp/file_web_manifest.json
+  !/dom/security/test/csp/file_web_manifest.json^headers^
+  !/dom/security/test/csp/file_web_manifest_https.html
+  !/dom/security/test/csp/file_web_manifest_https.json
+  !/dom/security/test/csp/file_web_manifest_mixed_content.html
+  !/dom/security/test/csp/file_web_manifest_remote.html
 [browser_test_web_manifest.js]
 [browser_test_web_manifest_mixed_content.js]
+[browser_manifest-src-override-default-src.js]
new file mode 100644
--- /dev/null
+++ b/dom/security/test/csp/browser_manifest-src-override-default-src.js
@@ -0,0 +1,108 @@
+/*
+ * Description of the tests:
+ * Tests check that default-src can be overridden by manifest-src.
+ */
+/*globals Cu, is, ok*/
+"use strict";
+const {
+  ManifestObtainer
+} = Cu.import("resource://gre/modules/ManifestObtainer.jsm", {});
+const path = "/tests/dom/security/test/csp/";
+const testFile = `${path}file_web_manifest.html`;
+const mixedContentFile = `${path}file_web_manifest_mixed_content.html`;
+const server = `${path}file_testserver.sjs`;
+const defaultURL = new URL(`http://example.org${server}`);
+const mixedURL = new URL(`http://mochi.test:8888${server}`);
+const tests = [
+  // Check interaction with default-src and another origin,
+  // CSP allows fetching from example.org, so manifest should load.
+  {
+    expected: `CSP manifest-src overrides default-src of elsewhere.com`,
+    get tabURL() {
+      const url = new URL(defaultURL);
+      url.searchParams.append("file", testFile);
+      url.searchParams.append("cors", "*");
+      url.searchParams.append("csp", "default-src http://elsewhere.com; manifest-src http://example.org");
+      return url.href;
+    },
+    run(manifest) {
+      is(manifest.name, "loaded", this.expected);
+    }
+  },
+  // Check interaction with default-src none,
+  // CSP allows fetching manifest from example.org, so manifest should load.
+  {
+    expected: `CSP manifest-src overrides default-src`,
+    get tabURL() {
+      const url = new URL(mixedURL);
+      url.searchParams.append("file", mixedContentFile);
+      url.searchParams.append("cors", "http://test:80");
+      url.searchParams.append("csp", "default-src 'self'; manifest-src http://test:80");
+      return url.href;
+    },
+    run(manifest) {
+      is(manifest.name, "loaded", this.expected);
+    }
+  },
+];
+
+//jscs:disable
+add_task(function* () {
+  //jscs:enable
+  const testPromises = tests.map((test) => {
+    const tabOptions = {
+      gBrowser,
+      url: test.tabURL,
+      skipAnimation: true,
+    };
+    return BrowserTestUtils.withNewTab(tabOptions, (browser) => testObtainingManifest(browser, test));
+  });
+  yield Promise.all(testPromises);
+});
+
+function* testObtainingManifest(aBrowser, aTest) {
+  const expectsBlocked = aTest.expected.includes("block");
+  const observer = (expectsBlocked) ? createNetObserver(aTest) : null;
+  // Expect an exception (from promise rejection) if there a content policy
+  // that is violated.
+  try {
+    const manifest = yield ManifestObtainer.browserObtainManifest(aBrowser);
+    aTest.run(manifest);
+  } catch (e) {
+    const wasBlocked = e.message.includes("NetworkError when attempting to fetch resource");
+    ok(wasBlocked,`Expected promise rejection obtaining ${aTest.tabURL}: ${e.message}`);
+    if (observer) {
+      yield observer.untilFinished;
+    }
+  }
+}
+
+// Helper object used to observe policy violations. It waits 1 seconds
+// for a response, and then times out causing its associated test to fail.
+function createNetObserver(test) {
+  let finishedTest;
+  let success = false;
+  const finished = new Promise((resolver) => {
+    finishedTest = resolver;
+  });
+  const timeoutId = setTimeout(() => {
+    if (!success) {
+      test.run("This test timed out.");
+      finishedTest();
+    }
+  }, 1000);
+  var observer = {
+    get untilFinished(){
+      return finished;
+    },
+    observe(subject, topic) {
+      SpecialPowers.removeObserver(observer, "csp-on-violate-policy");
+      test.run(topic);
+      finishedTest();
+      clearTimeout(timeoutId);
+      success = true;
+    },
+  };
+  SpecialPowers.addObserver(observer, "csp-on-violate-policy", false);
+  return observer;
+}
--- a/dom/security/test/csp/browser_test_web_manifest.js
+++ b/dom/security/test/csp/browser_test_web_manifest.js
@@ -1,249 +1,237 @@
 /*
  * Description of the tests:
  *   These tests check for conformance to the CSP spec as they relate to Web Manifests.
  *
  *   In particular, the tests check that default-src and manifest-src directives are
  *   are respected by the ManifestObtainer.
  */
 /*globals Cu, is, ok*/
-'use strict';
-requestLongerTimeout(10); // e10s tests take time.
+"use strict";
 const {
   ManifestObtainer
-} = Cu.import('resource://gre/modules/ManifestObtainer.jsm', {});
-const path = '/tests/dom/security/test/csp/';
-const testFile = `file=${path}file_web_manifest.html`;
-const remoteFile = `file=${path}file_web_manifest_remote.html`;
-const httpsManifest = `file=${path}file_web_manifest_https.html`;
-const server = 'file_testserver.sjs';
-const defaultURL = `http://example.org${path}${server}`;
-const secureURL = `https://example.com:443${path}${server}`;
+} = Cu.import("resource://gre/modules/ManifestObtainer.jsm", {});
+const path = "/tests/dom/security/test/csp/";
+const testFile = `${path}file_web_manifest.html`;
+const remoteFile = `${path}file_web_manifest_remote.html`;
+const httpsManifest = `${path}file_web_manifest_https.html`;
+const server = `${path}file_testserver.sjs`;
+const defaultURL = new URL(`http://example.org${server}`);
+const secureURL = new URL(`https://example.com:443${server}`);
 const tests = [
   // CSP block everything, so trying to load a manifest
   // will result in a policy violation.
   {
-    expected: `default-src 'none' blocks fetching manifest.`,
+    expected: "default-src 'none' blocks fetching manifest.",
     get tabURL() {
-      let queryParts = [
-        `csp=default-src 'none'`,
-        testFile
-      ];
-      return `${defaultURL}?${queryParts.join('&')}`;
+      const url = new URL(defaultURL);
+      url.searchParams.append("file", testFile);
+      url.searchParams.append("csp", "default-src 'none'");
+      return url.href;
     },
     run(topic) {
-      is(topic, 'csp-on-violate-policy', this.expected);
+      is(topic, "csp-on-violate-policy", this.expected);
     }
   },
   // CSP allows fetching only from mochi.test:8888,
   // so trying to load a manifest from same origin
   // triggers a CSP violation.
   {
-    expected: `default-src mochi.test:8888 blocks manifest fetching.`,
+    expected: "default-src mochi.test:8888 blocks manifest fetching.",
     get tabURL() {
-      let queryParts = [
-        `csp=default-src mochi.test:8888`,
-        testFile
-      ];
-      return `${defaultURL}?${queryParts.join('&')}`;
+      const url = new URL(defaultURL);
+      url.searchParams.append("file", testFile);
+      url.searchParams.append("csp", "default-src mochi.test:8888");
+      return url.href;
     },
     run(topic) {
-      is(topic, 'csp-on-violate-policy', this.expected);
+      is(topic, "csp-on-violate-policy", this.expected);
     }
   },
   // CSP restricts fetching to 'self', so allowing the manifest
   // to load. The name of the manifest is then checked.
   {
-    expected: `CSP default-src 'self' allows fetch of manifest.`,
+    expected: "CSP default-src 'self' allows fetch of manifest.",
     get tabURL() {
-      let queryParts = [
-        `csp=default-src 'self'`,
-        testFile
-      ];
-      return `${defaultURL}?${queryParts.join('&')}`;
+      const url = new URL(defaultURL);
+      url.searchParams.append("file", testFile);
+      url.searchParams.append("csp", "default-src 'self'");
+      return url.href;
     },
     run(manifest) {
-      is(manifest.name, 'loaded', this.expected);
+      is(manifest.name, "loaded", this.expected);
     }
   },
   // CSP only allows fetching from mochi.test:8888 and remoteFile
   // requests a manifest from that origin, so manifest should load.
   {
-    expected: 'CSP default-src mochi.test:8888 allows fetching manifest.',
+    expected: "CSP default-src mochi.test:8888 allows fetching manifest.",
     get tabURL() {
-      let queryParts = [
-        `csp=default-src http://mochi.test:8888`,
-        remoteFile
-      ];
-      return `${defaultURL}?${queryParts.join('&')}`;
+      const url = new URL(defaultURL);
+      url.searchParams.append("file", remoteFile);
+      url.searchParams.append("csp", "default-src http://mochi.test:8888");
+      return url.href;
     },
     run(manifest) {
-      is(manifest.name, 'loaded', this.expected);
+      is(manifest.name, "loaded", this.expected);
     }
   },
   // default-src blocks everything, so any attempt to
   // fetch a manifest from another origin will trigger a
   // policy violation.
   {
-    expected: `default-src 'none' blocks mochi.test:8888`,
+    expected: "default-src 'none' blocks mochi.test:8888",
     get tabURL() {
-      let queryParts = [
-        `csp=default-src 'none'`,
-        remoteFile
-      ];
-      return `${defaultURL}?${queryParts.join('&')}`;
+      const url = new URL(defaultURL);
+      url.searchParams.append("file", remoteFile);
+      url.searchParams.append("csp", "default-src 'none'");
+      return url.href;
     },
     run(topic) {
-      is(topic, 'csp-on-violate-policy', this.expected);
+      is(topic, "csp-on-violate-policy", this.expected);
     }
   },
   // CSP allows fetching from self, so manifest should load.
   {
-    expected: `CSP manifest-src allows self`,
+    expected: "CSP manifest-src allows self",
     get tabURL() {
-      let queryParts = [
-        `csp=manifest-src 'self'`,
-        testFile
-      ];
-      return `${defaultURL}?${queryParts.join('&')}`;
+      const url = new URL(defaultURL);
+      url.searchParams.append("file", testFile);
+      url.searchParams.append("csp", "manifest-src 'self'");
+      return url.href;
     },
     run(manifest) {
-      is(manifest.name, 'loaded', this.expected);
+      is(manifest.name, "loaded", this.expected);
     }
   },
   // CSP allows fetching from example.org, so manifest should load.
   {
-    expected: `CSP manifest-src allows http://example.org`,
+    expected: "CSP manifest-src allows http://example.org",
     get tabURL() {
-      let queryParts = [
-        `csp=manifest-src http://example.org`,
-        testFile
-      ];
-      return `${defaultURL}?${queryParts.join('&')}`;
+      const url = new URL(defaultURL);
+      url.searchParams.append("file", testFile);
+      url.searchParams.append("csp", "manifest-src http://example.org");
+      return url.href;
     },
     run(manifest) {
-      is(manifest.name, 'loaded', this.expected);
+      is(manifest.name, "loaded", this.expected);
     }
-  },
-  {
-    expected: `CSP manifest-src allows mochi.test:8888`,
+  }, {
+    expected: "CSP manifest-src allows mochi.test:8888",
     get tabURL() {
-      let queryParts = [
-        `cors=*`,
-        `csp=default-src *; manifest-src http://mochi.test:8888`,
-        remoteFile
-      ];
-      return `${defaultURL}?${queryParts.join('&')}`;
+      const url = new URL(defaultURL);
+      url.searchParams.append("file", remoteFile);
+      url.searchParams.append("cors", "*");
+      url.searchParams.append("csp", "default-src *; manifest-src http://mochi.test:8888");
+      return url.href;
     },
     run(manifest) {
-      is(manifest.name, 'loaded', this.expected);
+      is(manifest.name, "loaded", this.expected);
     }
   },
   // CSP restricts fetching to mochi.test:8888, but the test
   // file is at example.org. Hence, a policy violation is
   // triggered.
   {
-    expected: `CSP blocks manifest fetching from example.org.`,
+    expected: "CSP blocks manifest fetching from example.org.",
     get tabURL() {
-      let queryParts = [
-        `csp=manifest-src mochi.test:8888`,
-        testFile
-      ];
-      return `${defaultURL}?${queryParts.join('&')}`;
+      const url = new URL(defaultURL);
+      url.searchParams.append("file", testFile);
+      url.searchParams.append("csp", "manifest-src mochi.test:8888");
+      return url.href;
     },
     run(topic) {
-      is(topic, 'csp-on-violate-policy', this.expected);
+      is(topic, "csp-on-violate-policy", this.expected);
     }
   },
   // CSP is set to only allow manifest to be loaded from same origin,
   // but the remote file attempts to load from a different origin. Thus
   // this causes a CSP violation.
   {
-    expected: `CSP manifest-src 'self' blocks cross-origin fetch.`,
+    expected: "CSP manifest-src 'self' blocks cross-origin fetch.",
     get tabURL() {
-      let queryParts = [
-        `csp=manifest-src 'self'`,
-        remoteFile
-      ];
-      return `${defaultURL}?${queryParts.join('&')}`;
+      const url = new URL(defaultURL);
+      url.searchParams.append("file", remoteFile);
+      url.searchParams.append("csp", "manifest-src 'self'");
+      return url.href;
     },
     run(topic) {
-      is(topic, 'csp-on-violate-policy', this.expected);
+      is(topic, "csp-on-violate-policy", this.expected);
     }
   },
   // CSP allows fetching over TLS from example.org, so manifest should load.
   {
-    expected: `CSP manifest-src allows example.com over TLS`,
+    expected: "CSP manifest-src allows example.com over TLS",
     get tabURL() {
-      let queryParts = [
-        'cors=*',
-        'csp=manifest-src https://example.com:443',
-        httpsManifest
-      ];
       // secureURL loads https://example.com:443
       // and gets manifest from https://example.org:443
-      return `${secureURL}?${queryParts.join('&')}`;
+      const url = new URL(secureURL);
+      url.searchParams.append("file", httpsManifest);
+      url.searchParams.append("cors", "*");
+      url.searchParams.append("csp", "manifest-src https://example.com:443");
+      return url.href;
     },
     run(manifest) {
-      is(manifest.name, 'loaded', this.expected);
+      is(manifest.name, "loaded", this.expected);
     }
   },
 ];
 //jscs:disable
 add_task(function* () {
   //jscs:enable
-  var testPromises = tests.map(
-      (test) => ([test, {gBrowser, url: test.tabURL, skipAnimation: true}])
-    ).map(
-      ([test, tabOptions]) => BrowserTestUtils.withNewTab(tabOptions, (browser) => testObtainingManifest(browser, test))
-    );
+  const testPromises = tests.map((test) => {
+    const tabOptions = {
+      gBrowser,
+      url: test.tabURL,
+      skipAnimation: true,
+    };
+    return BrowserTestUtils.withNewTab(tabOptions, (browser) => testObtainingManifest(browser, test));
+  });
   yield Promise.all(testPromises);
 });
 
 function* testObtainingManifest(aBrowser, aTest) {
-  const expectsBlocked = aTest.expected.includes('block');
+  const expectsBlocked = aTest.expected.includes("block");
   const observer = (expectsBlocked) ? createNetObserver(aTest) : null;
   // Expect an exception (from promise rejection) if there a content policy
   // that is violated.
   try {
     const manifest = yield ManifestObtainer.browserObtainManifest(aBrowser);
     aTest.run(manifest);
   } catch (e) {
-    const wasBlocked = e.message.includes('blocked the loading of a resource');
-    ok(wasBlocked,`Expected promise rejection obtaining ${aTest.tabURL}: ${e.message}`);
+    const wasBlocked = e.message.includes("NetworkError when attempting to fetch resource");
+    ok(wasBlocked, `Expected promise rejection obtaining ${aTest.tabURL}: ${e.message}`);
     if (observer) {
       yield observer.untilFinished;
-      return;
     }
-    throw e;
   }
 }
 
 // Helper object used to observe policy violations. It waits 1 seconds
 // for a response, and then times out causing its associated test to fail.
 function createNetObserver(test) {
   let finishedTest;
   let success = false;
   const finished = new Promise((resolver) => {
     finishedTest = resolver;
   });
   const timeoutId = setTimeout(() => {
     if (!success) {
-      test.run('This test timed out.');
+      test.run("This test timed out.");
       finishedTest();
     }
   }, 1000);
   var observer = {
-    get untilFinished(){
+    get untilFinished() {
       return finished;
     },
     observe(subject, topic) {
-      SpecialPowers.removeObserver(observer, 'csp-on-violate-policy');
+      SpecialPowers.removeObserver(observer, "csp-on-violate-policy");
       test.run(topic);
       finishedTest();
       clearTimeout(timeoutId);
       success = true;
     },
   };
-  SpecialPowers.addObserver(observer, 'csp-on-violate-policy', false);
+  SpecialPowers.addObserver(observer, "csp-on-violate-policy", false);
   return observer;
-}
+}
\ No newline at end of file
--- a/dom/security/test/csp/browser_test_web_manifest_mixed_content.js
+++ b/dom/security/test/csp/browser_test_web_manifest_mixed_content.js
@@ -1,54 +1,53 @@
 /*
  * Description of the test:
  *   Check that mixed content blocker works prevents fetches of
  *   mixed content manifests.
  */
-'use strict';
+/*globals Cu, ok*/
+"use strict";
 const {
   ManifestObtainer
-} = Cu.import('resource://gre/modules/ManifestObtainer.jsm', {});
-const path = '/tests/dom/security/test/csp/';
-const mixedContent = `file=${path}file_web_manifest_mixed_content.html`;
-const server = 'file_testserver.sjs';
-const secureURL = `https://example.com${path}${server}`;
+} = Cu.import("resource://gre/modules/ManifestObtainer.jsm", {});
+const path = "/tests/dom/security/test/csp/";
+const mixedContent = `${path}file_web_manifest_mixed_content.html`;
+const server = `${path}file_testserver.sjs`;
+const secureURL = new URL(`https://example.com${server}`);
 const tests = [
   // Trying to load mixed content in file_web_manifest_mixed_content.html
   // needs to result in an error.
   {
-    expected: `Mixed Content Blocker prevents fetching manifest.`,
+    expected: "Mixed Content Blocker prevents fetching manifest.",
     get tabURL() {
-      let queryParts = [
-        mixedContent
-      ];
-      return `${secureURL}?${queryParts.join('&')}`;
+      const url = new URL(secureURL);
+      url.searchParams.append("file", mixedContent);
+      return url.href;
     },
     run(error) {
       // Check reason for error.
-      const check = /blocked the loading of a resource/.test(error.message);
+      const check = /NetworkError when attempting to fetch resource/.test(error.message);
       ok(check, this.expected);
     }
   }
 ];
 
 //jscs:disable
-add_task(function*() {
+add_task(function* () {
   //jscs:enable
-  for (let test of tests) {
-    let tabOptions = {
-      gBrowser: gBrowser,
+  const testPromises = tests.map((test) => {
+    const tabOptions = {
+      gBrowser,
       url: test.tabURL,
+      skipAnimation: true,
     };
-    yield BrowserTestUtils.withNewTab(
-      tabOptions,
-      browser => testObtainingManifest(browser, test)
-    );
-  }
+    return BrowserTestUtils.withNewTab(tabOptions, (browser) => testObtainingManifest(browser, test));
+  });
+  yield Promise.all(testPromises);
+});
 
-  function* testObtainingManifest(aBrowser, aTest) {
-    try {
-      yield ManifestObtainer.browserObtainManifest(aBrowser);
-    } catch (e) {
-      aTest.run(e)
-    }
+function* testObtainingManifest(aBrowser, aTest) {
+  try {
+    yield ManifestObtainer.browserObtainManifest(aBrowser);
+  } catch (e) {
+    aTest.run(e);
   }
-});
+}
--- a/dom/security/test/csp/file_testserver.sjs
+++ b/dom/security/test/csp/file_testserver.sjs
@@ -1,53 +1,50 @@
 // SJS file for CSP mochitests
-
+"use strict";
 Components.utils.import("resource://gre/modules/NetUtil.jsm");
+Components.utils.importGlobalProperties(["URLSearchParams"]);
 
 function loadHTMLFromFile(path) {
   // Load the HTML to return in the response from file.
   // Since it's relative to the cwd of the test runner, we start there and
   // append to get to the actual path of the file.
-  var testHTMLFile =
+  const testHTMLFile =
     Components.classes["@mozilla.org/file/directory_service;1"].
     getService(Components.interfaces.nsIProperties).
     get("CurWorkD", Components.interfaces.nsILocalFile);
-  var dirs = path.split("/");
-  for (var i = 0; i < dirs.length; i++) {
-    testHTMLFile.append(dirs[i]);
-  }
-  var testHTMLFileStream =
+
+  const testHTMLFileStream =
     Components.classes["@mozilla.org/network/file-input-stream;1"].
     createInstance(Components.interfaces.nsIFileInputStream);
+
+  path
+    .split("/")
+    .filter(path => path)
+    .reduce((file, path) => {
+      testHTMLFile.append(path)
+      return testHTMLFile;
+    }, testHTMLFile);
   testHTMLFileStream.init(testHTMLFile, -1, 0, 0);
-  var testHTML = NetUtil.readInputStreamToString(testHTMLFileStream, testHTMLFileStream.available());
-  return testHTML;
+  const isAvailable = testHTMLFileStream.available();
+  return NetUtil.readInputStreamToString(testHTMLFileStream, isAvailable);
 }
 
-
-function handleRequest(request, response)
-{
-  var query = {};
-  request.queryString.split('&').forEach(function (val) {
-    var [name, value] = val.split('=');
-    query[name] = unescape(value);
-  });
-
-  var csp = (query['csp']) ? unescape(query['csp']) : "";
-  var file = unescape(query['file']);
-  var cors = unescape(query['cors']);
+function handleRequest(request, response) {
+  const query = new URLSearchParams(request.queryString);
 
   // avoid confusing cache behaviors
   response.setHeader("Cache-Control", "no-cache", false);
 
-  // Deliver the CSP policy encoded in the URI
-  if(csp){
-    response.setHeader("Content-Security-Policy", csp, false);
+  // Deliver the CSP policy encoded in the URL
+  if(query.has("csp")){
+    response.setHeader("Content-Security-Policy", query.get("csp"), false);
   }
-  // Deliver the CORS header in the URI
-  if(cors){
-    response.setHeader("Access-Control-Allow-Origin", cors, false);
+
+  // Deliver the CORS header in the URL
+  if(query.has("cors")){
+    response.setHeader("Access-Control-Allow-Origin", query.get("cors"), false);
   }
+
   // Send HTML to test allowed/blocked behaviors
   response.setHeader("Content-Type", "text/html", false);
-
-  response.write(loadHTMLFromFile(file));
+  response.write(loadHTMLFromFile(query.get("file")));
 }
--- a/dom/webidl/Request.webidl
+++ b/dom/webidl/Request.webidl
@@ -26,17 +26,17 @@ interface Request {
   readonly attribute RequestCache cache;
   readonly attribute RequestRedirect redirect;
 
   [Throws,
    NewObject] Request clone();
 
   // Bug 1124638 - Allow chrome callers to set the context.
   [ChromeOnly]
-  void setContentPolicyType(nsContentPolicyType context);
+  void overrideContentPolicyType(nsContentPolicyType context);
 };
 Request implements Body;
 
 dictionary RequestInit {
   ByteString method;
   HeadersInit headers;
   BodyInit? body;
   USVString referrer;