Bug 1226909 part 4: Make AsyncOpen2 set taining information on channels. Use this information in XHR and fetch(). r=bkelly
authorJonas Sicking <jonas@sicking.cc>
Sun, 06 Dec 2015 18:33:15 -0500
changeset 309959 989bbde310f5d1cdc5bbdde44340ceb06f87e003
parent 309958 64f42cace9f354a50dc568fe5b73ffb64cf713a1
child 309960 7b9b0ce58fbf2acad28fd85a2a0ff8a3978cb7ea
push id5513
push userraliiev@mozilla.com
push dateMon, 25 Jan 2016 13:55:34 +0000
treeherdermozilla-beta@5ee97dd05b5c [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbkelly
bugs1226909
milestone45.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 1226909 part 4: Make AsyncOpen2 set taining information on channels. Use this information in XHR and fetch(). r=bkelly
dom/base/nsXMLHttpRequest.cpp
dom/base/nsXMLHttpRequest.h
dom/base/test/test_XHRDocURI.html
dom/fetch/FetchDriver.cpp
dom/fetch/FetchDriver.h
dom/security/nsContentSecurityManager.cpp
ipc/glue/BackgroundUtils.cpp
netwerk/base/LoadInfo.cpp
netwerk/base/LoadInfo.h
netwerk/base/nsNetUtil.cpp
netwerk/base/nsNetUtil.h
netwerk/ipc/NeckoChannelParams.ipdlh
--- a/dom/base/nsXMLHttpRequest.cpp
+++ b/dom/base/nsXMLHttpRequest.cpp
@@ -117,21 +117,20 @@ using namespace mozilla::dom;
                                                    // terms.
 // The above states are mutually exclusive, change with ChangeState() only.
 // The states below can be combined.
 #define XML_HTTP_REQUEST_ABORTED        (1 << 7)  // Internal
 #define XML_HTTP_REQUEST_ASYNC          (1 << 8)  // Internal
 #define XML_HTTP_REQUEST_PARSEBODY      (1 << 9)  // Internal
 #define XML_HTTP_REQUEST_SYNCLOOPING    (1 << 10) // Internal
 #define XML_HTTP_REQUEST_BACKGROUND     (1 << 13) // Internal
-#define XML_HTTP_REQUEST_USE_XSITE_AC   (1 << 14) // Internal
-#define XML_HTTP_REQUEST_HAD_UPLOAD_LISTENERS_ON_SEND (1 << 15) // Internal
-#define XML_HTTP_REQUEST_AC_WITH_CREDENTIALS (1 << 16) // Internal
-#define XML_HTTP_REQUEST_TIMED_OUT (1 << 17) // Internal
-#define XML_HTTP_REQUEST_DELETED (1 << 18) // Internal
+#define XML_HTTP_REQUEST_HAD_UPLOAD_LISTENERS_ON_SEND (1 << 14) // Internal
+#define XML_HTTP_REQUEST_AC_WITH_CREDENTIALS (1 << 15) // Internal
+#define XML_HTTP_REQUEST_TIMED_OUT (1 << 16) // Internal
+#define XML_HTTP_REQUEST_DELETED (1 << 17) // Internal
 
 #define XML_HTTP_REQUEST_LOADSTATES         \
   (XML_HTTP_REQUEST_UNSENT |                \
    XML_HTTP_REQUEST_OPENED |                \
    XML_HTTP_REQUEST_HEADERS_RECEIVED |      \
    XML_HTTP_REQUEST_LOADING |               \
    XML_HTTP_REQUEST_DONE |                  \
    XML_HTTP_REQUEST_SENT)
@@ -1065,19 +1064,32 @@ nsXMLHttpRequest::GetResponse(JSContext*
   default:
     NS_ERROR("Should not happen");
   }
 
   aResponse.setNull();
 }
 
 bool
-nsXMLHttpRequest::IsDeniedCrossSiteRequest()
+nsXMLHttpRequest::IsCrossSiteCORSRequest()
 {
-  if ((mState & XML_HTTP_REQUEST_USE_XSITE_AC) && mChannel) {
+  if (!mChannel) {
+    return false;
+  }
+
+  nsCOMPtr<nsILoadInfo> loadInfo = mChannel->GetLoadInfo();
+  MOZ_ASSERT(loadInfo);
+
+  return loadInfo->GetTainting() == LoadTainting::CORS;
+}
+
+bool
+nsXMLHttpRequest::IsDeniedCrossSiteCORSRequest()
+{
+  if (IsCrossSiteCORSRequest()) {
     nsresult rv;
     mChannel->GetStatus(&rv);
     if (NS_FAILED(rv)) {
       return true;
     }
   }
   return false;
 }
@@ -1089,17 +1101,17 @@ nsXMLHttpRequest::GetResponseURL(nsAStri
 
   uint16_t readyState = ReadyState();
   if ((readyState == UNSENT || readyState == OPENED) || !mChannel) {
     return;
   }
 
   // Make sure we don't leak responseURL information from denied cross-site
   // requests.
-  if (IsDeniedCrossSiteRequest()) {
+  if (IsDeniedCrossSiteCORSRequest()) {
     return;
   }
 
   nsCOMPtr<nsIURI> responseUrl;
   mChannel->GetURI(getter_AddRefs(responseUrl));
 
   if (!responseUrl) {
     return;
@@ -1117,17 +1129,17 @@ nsXMLHttpRequest::GetStatus(uint32_t *aS
   return NS_OK;
 }
 
 uint32_t
 nsXMLHttpRequest::Status()
 {
   // Make sure we don't leak status information from denied cross-site
   // requests.
-  if (IsDeniedCrossSiteRequest()) {
+  if (IsDeniedCrossSiteCORSRequest()) {
     return 0;
   }
 
   uint16_t readyState = ReadyState();
   if (readyState == UNSENT || readyState == OPENED) {
     return 0;
   }
 
@@ -1167,17 +1179,17 @@ IMPL_CSTRING_GETTER(GetStatusText)
 void
 nsXMLHttpRequest::GetStatusText(nsCString& aStatusText)
 {
   // Return an empty status text on all error loads.
   aStatusText.Truncate();
 
   // Make sure we don't leak status information from denied cross-site
   // requests.
-  if (IsDeniedCrossSiteRequest()) {
+  if (IsDeniedCrossSiteCORSRequest()) {
     return;
   }
 
   // Check the current XHR state to see if it is valid to obtain the statusText
   // value.  This check is to prevent the status text for redirects from being
   // available before all the redirects have been followed and HTTP headers have
   // been received.
   uint16_t readyState = ReadyState();
@@ -1262,17 +1274,17 @@ bool
 nsXMLHttpRequest::IsSafeHeader(const nsACString& header, nsIHttpChannel* httpChannel)
 {
   // See bug #380418. Hide "Set-Cookie" headers from non-chrome scripts.
   if (!IsSystemXHR() && nsContentUtils::IsForbiddenResponseHeader(header)) {
     NS_WARNING("blocked access to response header");
     return false;
   }
   // if this is not a CORS call all headers are safe
-  if (!(mState & XML_HTTP_REQUEST_USE_XSITE_AC)){
+  if (!IsCrossSiteCORSRequest()) {
     return true;
   }
   // Check for dangerous headers
   // Make sure we don't leak header information from denied cross-site
   // requests.
   if (mChannel) {
     nsresult status;
     mChannel->GetStatus(&status);
@@ -1524,27 +1536,16 @@ nsXMLHttpRequest::GetCurrentJARChannel()
 }
 
 bool
 nsXMLHttpRequest::IsSystemXHR()
 {
   return mIsSystem || nsContentUtils::IsSystemPrincipal(mPrincipal);
 }
 
-void
-nsXMLHttpRequest::CheckChannelForCrossSiteRequest(nsIChannel* aChannel)
-{
-  // A system XHR (chrome code or a web app with the right permission) can
-  // load anything, and same-origin loads are always allowed.
-  if (!IsSystemXHR() &&
-      !nsContentUtils::CheckMayLoad(mPrincipal, aChannel, true)) {
-    mState |= XML_HTTP_REQUEST_USE_XSITE_AC;
-  }
-}
-
 NS_IMETHODIMP
 nsXMLHttpRequest::Open(const nsACString& method, const nsACString& url,
                        bool async, const nsAString& user,
                        const nsAString& password, uint8_t optional_argc)
 {
   if (!optional_argc) {
     // No optional arguments were passed in. Default async to true.
     async = true;
@@ -1711,18 +1712,17 @@ nsXMLHttpRequest::Open(const nsACString&
                        nsIContentPolicy::TYPE_INTERNAL_XMLHTTPREQUEST,
                        loadGroup,
                        nullptr,   // aCallbacks
                        loadFlags);
   }
 
   NS_ENSURE_SUCCESS(rv, rv);
 
-  mState &= ~(XML_HTTP_REQUEST_USE_XSITE_AC |
-              XML_HTTP_REQUEST_HAD_UPLOAD_LISTENERS_ON_SEND);
+  mState &= ~XML_HTTP_REQUEST_HAD_UPLOAD_LISTENERS_ON_SEND;
 
   nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(mChannel));
   if (httpChannel) {
     rv = httpChannel->SetRequestMethod(method);
     NS_ENSURE_SUCCESS(rv, rv);
 
     // Set the initiator type
     nsCOMPtr<nsITimedChannel> timedChannel(do_QueryInterface(httpChannel));
@@ -1919,22 +1919,16 @@ nsXMLHttpRequest::OnStartRequest(nsIRequ
     mRequestObserver->OnStartRequest(request, ctxt);
   }
 
   if (request != mChannel) {
     // Can this still happen?
     return NS_OK;
   }
 
-  // Always treat tainted channels as cross-origin.
-  nsCOMPtr<nsILoadInfo> loadInfo = mChannel->GetLoadInfo();
-  if (loadInfo && loadInfo->GetTainting() != LoadTainting::Basic) {
-    mState |= XML_HTTP_REQUEST_USE_XSITE_AC;
-  }
-
   // Don't do anything if we have been aborted
   if (mState & XML_HTTP_REQUEST_UNSENT)
     return NS_OK;
 
   /* Apparently, Abort() should set XML_HTTP_REQUEST_UNSENT.  See bug 361773.
      XHR2 spec says this is correct. */
   if (mState & XML_HTTP_REQUEST_ABORTED) {
     NS_ERROR("Ugh, still getting data on an aborted XMLHttpRequest!");
@@ -2110,30 +2104,34 @@ nsXMLHttpRequest::OnStartRequest(nsIRequ
     mResponseXML = do_QueryInterface(responseDoc);
     mResponseXML->SetChromeXHRDocURI(chromeXHRDocURI);
     mResponseXML->SetChromeXHRDocBaseURI(chromeXHRDocBaseURI);
 
     if (nsContentUtils::IsSystemPrincipal(mPrincipal)) {
       mResponseXML->ForceEnableXULXBL();
     }
 
-    if (mState & XML_HTTP_REQUEST_USE_XSITE_AC) {
+    nsCOMPtr<nsILoadInfo> loadInfo = mChannel->GetLoadInfo();
+    MOZ_ASSERT(loadInfo);
+    bool isCrossSite = loadInfo->GetTainting() != LoadTainting::Basic;
+
+    if (isCrossSite) {
       nsCOMPtr<nsIHTMLDocument> htmlDoc = do_QueryInterface(mResponseXML);
       if (htmlDoc) {
         htmlDoc->DisableCookieAccess();
       }
     }
 
     nsCOMPtr<nsIStreamListener> listener;
     nsCOMPtr<nsILoadGroup> loadGroup;
     channel->GetLoadGroup(getter_AddRefs(loadGroup));
 
     rv = mResponseXML->StartDocumentLoad(kLoadAsData, channel, loadGroup,
                                          nullptr, getter_AddRefs(listener),
-                                         !(mState & XML_HTTP_REQUEST_USE_XSITE_AC));
+                                         !isCrossSite);
     NS_ENSURE_SUCCESS(rv, rv);
 
     mXMLParserStreamListener = listener;
     rv = mXMLParserStreamListener->OnStartRequest(request, ctxt);
     NS_ENSURE_SUCCESS(rv, rv);
   }
 
   // We won't get any progress events anyway if we didn't have progress
@@ -2791,18 +2789,16 @@ nsXMLHttpRequest::Send(nsIVariant* aVari
         // Reset the method to its original value
         httpChannel->SetRequestMethod(method);
       }
     }
   }
 
   ResetResponse();
 
-  CheckChannelForCrossSiteRequest(mChannel);
-
   bool withCredentials = !!(mState & XML_HTTP_REQUEST_AC_WITH_CREDENTIALS);
 
   if (!IsSystemXHR() && withCredentials) {
     // This is quite sad. We have to create the channel in .open(), since the
     // chrome-only xhr.channel API depends on that. However .withCredentials
     // can be modified after, so we don't know what to set the
     // SEC_REQUIRE_CORS_WITH_CREDENTIALS flag to when the channel is
     // created. So set it here using a hacky internal API.
@@ -3303,125 +3299,80 @@ nsXMLHttpRequest::ChangeState(uint32_t a
     NS_ENSURE_SUCCESS(rv, rv);
 
     DispatchDOMEvent(nullptr, event, nullptr, nullptr);
   }
 
   return rv;
 }
 
-/*
- * Simple helper class that just forwards the redirect callback back
- * to the nsXMLHttpRequest.
- */
-class AsyncVerifyRedirectCallbackForwarder final : public nsIAsyncVerifyRedirectCallback
-{
-public:
-  explicit AsyncVerifyRedirectCallbackForwarder(nsXMLHttpRequest* xhr)
-    : mXHR(xhr)
-  {
-  }
-
-  NS_DECL_CYCLE_COLLECTING_ISUPPORTS
-  NS_DECL_CYCLE_COLLECTION_CLASS(AsyncVerifyRedirectCallbackForwarder)
-
-  // nsIAsyncVerifyRedirectCallback implementation
-  NS_IMETHOD OnRedirectVerifyCallback(nsresult result) override
-  {
-    mXHR->OnRedirectVerifyCallback(result);
-
-    return NS_OK;
-  }
-
-private:
-  ~AsyncVerifyRedirectCallbackForwarder() {}
-
-  RefPtr<nsXMLHttpRequest> mXHR;
-};
-
-NS_IMPL_CYCLE_COLLECTION(AsyncVerifyRedirectCallbackForwarder, mXHR)
-
-NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(AsyncVerifyRedirectCallbackForwarder)
-  NS_INTERFACE_MAP_ENTRY(nsISupports)
-  NS_INTERFACE_MAP_ENTRY(nsIAsyncVerifyRedirectCallback)
-NS_INTERFACE_MAP_END
-
-NS_IMPL_CYCLE_COLLECTING_ADDREF(AsyncVerifyRedirectCallbackForwarder)
-NS_IMPL_CYCLE_COLLECTING_RELEASE(AsyncVerifyRedirectCallbackForwarder)
-
-
 /////////////////////////////////////////////////////
 // nsIChannelEventSink methods:
 //
 NS_IMETHODIMP
 nsXMLHttpRequest::AsyncOnChannelRedirect(nsIChannel *aOldChannel,
                                          nsIChannel *aNewChannel,
                                          uint32_t    aFlags,
                                          nsIAsyncVerifyRedirectCallback *callback)
 {
   NS_PRECONDITION(aNewChannel, "Redirect without a channel?");
 
-  nsresult rv;
-
-  if (!NS_IsInternalSameURIRedirect(aOldChannel, aNewChannel, aFlags)) {
-    CheckChannelForCrossSiteRequest(aNewChannel);
-  }
-
   // Prepare to receive callback
   mRedirectCallback = callback;
   mNewRedirectChannel = aNewChannel;
 
   if (mChannelEventSink) {
-    RefPtr<AsyncVerifyRedirectCallbackForwarder> fwd =
-      new AsyncVerifyRedirectCallbackForwarder(this);
-
-    rv = mChannelEventSink->AsyncOnChannelRedirect(aOldChannel,
-                                                   aNewChannel,
-                                                   aFlags, fwd);
+    nsCOMPtr<nsIAsyncVerifyRedirectCallback> fwd =
+      EnsureXPCOMifier();
+
+    nsresult rv = mChannelEventSink->AsyncOnChannelRedirect(aOldChannel,
+                                                            aNewChannel,
+                                                            aFlags, fwd);
     if (NS_FAILED(rv)) {
         mRedirectCallback = nullptr;
         mNewRedirectChannel = nullptr;
     }
     return rv;
   }
   OnRedirectVerifyCallback(NS_OK);
   return NS_OK;
 }
 
-void
+nsresult
 nsXMLHttpRequest::OnRedirectVerifyCallback(nsresult result)
 {
   NS_ASSERTION(mRedirectCallback, "mRedirectCallback not set in callback");
   NS_ASSERTION(mNewRedirectChannel, "mNewRedirectChannel not set in callback");
 
   if (NS_SUCCEEDED(result)) {
     mChannel = mNewRedirectChannel;
 
     nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(mChannel));
     if (httpChannel) {
       // Ensure all original headers are duplicated for the new channel (bug #553888)
-      for (uint32_t i = mModifiedRequestHeaders.Length(); i > 0; ) {
-        --i;
-        if (mModifiedRequestHeaders[i].value.IsEmpty()) {
-          httpChannel->SetEmptyRequestHeader(mModifiedRequestHeaders[i].header);
+      for (RequestHeader& requestHeader : mModifiedRequestHeaders) {
+        if (requestHeader.value.IsEmpty()) {
+          httpChannel->SetEmptyRequestHeader(requestHeader.header);
         } else {
-          httpChannel->SetRequestHeader(mModifiedRequestHeaders[i].header,
-                                        mModifiedRequestHeaders[i].value,
+          httpChannel->SetRequestHeader(requestHeader.header,
+                                        requestHeader.value,
                                         false);
         }
       }
     }
   } else {
     mErrorLoad = true;
   }
 
   mNewRedirectChannel = nullptr;
 
   mRedirectCallback->OnRedirectVerifyCallback(result);
   mRedirectCallback = nullptr;
+
+  return result;
 }
 
 /////////////////////////////////////////////////////
 // nsIProgressEventSink methods:
 //
 
 void
 nsXMLHttpRequest::MaybeDispatchProgressEvents(bool aFinalProgress)
@@ -3515,17 +3466,17 @@ nsXMLHttpRequest::OnStatus(nsIRequest *a
   }
 
   return NS_OK;
 }
 
 bool
 nsXMLHttpRequest::AllowUploadProgress()
 {
-  return !(mState & XML_HTTP_REQUEST_USE_XSITE_AC) ||
+  return !IsCrossSiteCORSRequest() ||
     (mState & XML_HTTP_REQUEST_HAD_UPLOAD_LISTENERS_ON_SEND);
 }
 
 /////////////////////////////////////////////////////
 // nsIInterfaceRequestor methods:
 //
 NS_IMETHODIMP
 nsXMLHttpRequest::GetInterface(const nsIID & aIID, void **aResult)
@@ -3576,17 +3527,17 @@ nsXMLHttpRequest::GetInterface(const nsI
 
     // Verify that it's ok to prompt for credentials here, per spec
     // http://xhr.spec.whatwg.org/#the-send%28%29-method
     bool showPrompt = true;
 
     // If authentication fails, XMLHttpRequest origin and
     // the request URL are same origin, ...
     /* Disabled - bug: 799540
-    if (mState & XML_HTTP_REQUEST_USE_XSITE_AC) {
+    if (IsCrossSiteCORSRequest()) {
       showPrompt = false;
     }
     */
 
     // ... Authorization is not in the list of author request headers, ...
     if (showPrompt) {
       for (uint32_t i = 0, len = mModifiedRequestHeaders.Length(); i < len; ++i) {
         if (mModifiedRequestHeaders[i].header.
@@ -3783,16 +3734,17 @@ nsHeaderVisitor::VisitHeader(const nsACS
   return NS_OK;
 }
 
 // nsXMLHttpRequestXPCOMifier implementation
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsXMLHttpRequestXPCOMifier)
   NS_INTERFACE_MAP_ENTRY(nsIStreamListener)
   NS_INTERFACE_MAP_ENTRY(nsIRequestObserver)
   NS_INTERFACE_MAP_ENTRY(nsIChannelEventSink)
+  NS_INTERFACE_MAP_ENTRY(nsIAsyncVerifyRedirectCallback)
   NS_INTERFACE_MAP_ENTRY(nsIProgressEventSink)
   NS_INTERFACE_MAP_ENTRY(nsIInterfaceRequestor)
   NS_INTERFACE_MAP_ENTRY(nsITimerCallback)
   NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIStreamListener)
 NS_INTERFACE_MAP_END
 
 NS_IMPL_CYCLE_COLLECTING_ADDREF(nsXMLHttpRequestXPCOMifier)
 NS_IMPL_CYCLE_COLLECTING_RELEASE(nsXMLHttpRequestXPCOMifier)
--- a/dom/base/nsXMLHttpRequest.h
+++ b/dom/base/nsXMLHttpRequest.h
@@ -36,17 +36,16 @@
 #include "mozilla/dom/XMLHttpRequestBinding.h"
 
 #ifdef Status
 /* Xlib headers insist on this for some reason... Nuke it because
    it'll override our member name */
 #undef Status
 #endif
 
-class AsyncVerifyRedirectCallbackForwarder;
 class nsFormData;
 class nsIJARChannel;
 class nsILoadGroup;
 class nsIUnicodeDecoder;
 class nsIJSID;
 
 namespace mozilla {
 
@@ -421,17 +420,18 @@ private:
   {
     return Send(nullptr, aBody);
   }
   nsresult Send(const RequestBody& aBody)
   {
     return Send(Nullable<RequestBody>(aBody));
   }
 
-  bool IsDeniedCrossSiteRequest();
+  bool IsCrossSiteCORSRequest();
+  bool IsDeniedCrossSiteCORSRequest();
 
   // Tell our channel what network interface ID we were told to use.
   // If it's an HTTP channel and we were told to use a non-default
   // interface ID.
   void PopulateNetworkInterfaceId();
 
 public:
   void Send(JSContext* /*aCx*/, ErrorResult& aRv)
@@ -615,28 +615,19 @@ protected:
 
   already_AddRefed<nsIHttpChannel> GetCurrentHttpChannel();
   already_AddRefed<nsIJARChannel> GetCurrentJARChannel();
 
   bool IsSystemXHR();
 
   void ChangeStateToDone();
 
-  /**
-   * Check if aChannel is ok for a cross-site request by making sure no
-   * inappropriate headers are set, and no username/password is set.
-   *
-   * Also updates the XML_HTTP_REQUEST_USE_XSITE_AC bit.
-   */
-  void CheckChannelForCrossSiteRequest(nsIChannel* aChannel);
-
   void StartProgressEventTimer();
 
-  friend class AsyncVerifyRedirectCallbackForwarder;
-  void OnRedirectVerifyCallback(nsresult result);
+  nsresult OnRedirectVerifyCallback(nsresult result);
 
   nsresult Open(const nsACString& method, const nsACString& url, bool async,
                 const mozilla::dom::Optional<nsAString>& user,
                 const mozilla::dom::Optional<nsAString>& password);
 
   already_AddRefed<nsXMLHttpRequestXPCOMifier> EnsureXPCOMifier();
 
   nsCOMPtr<nsISupports> mContext;
@@ -835,16 +826,17 @@ public:
 private:
   bool mOldVal;
 };
 
 // A shim class designed to expose the non-DOM interfaces of
 // XMLHttpRequest via XPCOM stuff.
 class nsXMLHttpRequestXPCOMifier final : public nsIStreamListener,
                                          public nsIChannelEventSink,
+                                         public nsIAsyncVerifyRedirectCallback,
                                          public nsIProgressEventSink,
                                          public nsIInterfaceRequestor,
                                          public nsITimerCallback
 {
   NS_DECL_CYCLE_COLLECTING_ISUPPORTS
   NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(nsXMLHttpRequestXPCOMifier,
                                            nsIStreamListener)
 
@@ -859,16 +851,17 @@ private:
       mXHR->mXPCOMifier = nullptr;
     }
   }
 
 public:
   NS_FORWARD_NSISTREAMLISTENER(mXHR->)
   NS_FORWARD_NSIREQUESTOBSERVER(mXHR->)
   NS_FORWARD_NSICHANNELEVENTSINK(mXHR->)
+  NS_FORWARD_NSIASYNCVERIFYREDIRECTCALLBACK(mXHR->)
   NS_FORWARD_NSIPROGRESSEVENTSINK(mXHR->)
   NS_FORWARD_NSITIMERCALLBACK(mXHR->)
 
   NS_DECL_NSIINTERFACEREQUESTOR
 
 private:
   RefPtr<nsXMLHttpRequest> mXHR;
 };
--- a/dom/base/test/test_XHRDocURI.html
+++ b/dom/base/test/test_XHRDocURI.html
@@ -366,31 +366,30 @@ function runTest() {
     if (xhr.readyState == 4) {
       gen.next();
     }
   };
   xhr.send();
   yield undefined;
 
 
-  // use chrome XHR and access URI properties from chrome privileged script
+  // use the systemXHR special privilege
   SpecialPowers.addPermission("systemXHR", true, document);
   xhr = new XMLHttpRequest({mozAnon: false, mozSystem: true});
   xhr.open("GET", "http://mochi.test:8888/tests/dom/base/test/file_XHRDocURI.xml");
   xhr.onreadystatechange = function(e) {
     if (!xhr.responseXML) {
       return;
     }
     var expects = {
       documentURI: "http://mochi.test:8888/tests/dom/base/test/file_XHRDocURI.xml",
       baseURI: "http://mochi.test:8888/tests/dom/base/test/file_XHRDocURI.xml",
       elementBaseURI: "http://www.example.com/"
     };
-    var xml = SpecialPowers.wrap(xhr.responseXML);
-    testChromeXMLDocURI(xml, expects);
+    testXMLDocURI(xhr.responseXML, expects);
     if (xhr.readyState == 4) {
       gen.next();
     }
   };
   xhr.send();
   yield undefined;
 
   xhr = new XMLHttpRequest({mozAnon: false, mozSystem: true});
@@ -399,18 +398,17 @@ function runTest() {
   xhr.onreadystatechange = function(e) {
     if (!xhr.response) {
       return;
     }
     var expects = {
       documentURI: "http://mochi.test:8888/tests/dom/base/test/file_XHRDocURI.html",
       baseURI: "http://mochi.test:8888/tests/dom/base/test/file_XHRDocURI.html"
     };
-    var doc = SpecialPowers.wrap(xhr.response);
-    testChromeHTMLDocURI(doc, "http://mochi.test:8888/tests/dom/base/test/file_XHRDocURI.html", expects);
+    testHTMLDocURI(xhr.response, expects);
     if (xhr.readyState == 4) {
       gen.next();
     }
   };
   xhr.send();
   yield undefined;
 
   xhr = new XMLHttpRequest({mozAnon: false, mozSystem: true});
@@ -419,18 +417,17 @@ function runTest() {
     if (!xhr.responseXML) {
       return;
     }
     var expects = {
       documentURI: "http://example.com/tests/dom/base/test/file_XHRDocURI.xml",
       baseURI: "http://example.com/tests/dom/base/test/file_XHRDocURI.xml",
       elementBaseURI: "http://www.example.com/"
     };
-    var xml = SpecialPowers.wrap(xhr.responseXML);
-    testChromeXMLDocURI(xml, expects);
+    testXMLDocURI(xhr.responseXML, expects);
     if (xhr.readyState == 4) {
       gen.next();
     }
   };
   xhr.send();
   yield undefined;
 
   xhr = new XMLHttpRequest({mozAnon: false, mozSystem: true});
@@ -439,18 +436,17 @@ function runTest() {
   xhr.onreadystatechange = function(e) {
     if (!xhr.response) {
       return;
     }
     var expects = {
       documentURI: "http://example.com/tests/dom/base/test/file_XHRDocURI.html",
       baseURI: "http://example.com/tests/dom/base/test/file_XHRDocURI.html"
     };
-    var doc = SpecialPowers.wrap(xhr.response);
-    testChromeHTMLDocURI(doc, "http://example.com/tests/dom/base/test/file_XHRDocURI.html", expects);
+    testHTMLDocURI(xhr.response, expects);
     if (xhr.readyState == 4) {
       gen.next();
     }
   };
   xhr.send();
   yield undefined;
 
   xhr = new XMLHttpRequest({mozAnon: false, mozSystem: true});
@@ -459,18 +455,17 @@ function runTest() {
     if (!xhr.responseXML) {
       return;
     }
     var expects = {
       documentURI: "http://example.com/tests/dom/base/test/file_XHRDocURI.xml",
       baseURI: "http://example.com/tests/dom/base/test/file_XHRDocURI.xml",
       elementBaseURI: "http://www.example.com/"
     };
-    var xml = SpecialPowers.wrap(xhr.responseXML);
-    testChromeXMLDocURI(xml, expects);
+    testXMLDocURI(xhr.responseXML, expects);
     if (xhr.readyState == 4) {
       gen.next();
     }
   };
   xhr.send();
   yield undefined;
 
   xhr = new XMLHttpRequest({mozAnon: false, mozSystem: true});
@@ -479,18 +474,17 @@ function runTest() {
   xhr.onreadystatechange = function(e) {
     if (!xhr.response) {
       return;
     }
     var expects = {
       documentURI: "http://example.com/tests/dom/base/test/file_XHRDocURI.html",
       baseURI: "http://example.com/tests/dom/base/test/file_XHRDocURI.html"
     };
-    var doc = SpecialPowers.wrap(xhr.response);
-    testChromeHTMLDocURI(doc, "http://example.com/tests/dom/base/test/file_XHRDocURI.html", expects);
+    testHTMLDocURI(xhr.response, expects);
     if (xhr.readyState == 4) {
       gen.next();
     }
   };
   xhr.send();
   yield undefined;
 
   history.pushState({}, "pushStateTest", window.location.href + "/pushStateTest");
--- a/dom/fetch/FetchDriver.cpp
+++ b/dom/fetch/FetchDriver.cpp
@@ -46,17 +46,16 @@ NS_IMPL_ISUPPORTS(FetchDriver,
                   nsIStreamListener, nsIChannelEventSink, nsIInterfaceRequestor,
                   nsIThreadRetargetableStreamListener)
 
 FetchDriver::FetchDriver(InternalRequest* aRequest, nsIPrincipal* aPrincipal,
                          nsILoadGroup* aLoadGroup)
   : mPrincipal(aPrincipal)
   , mLoadGroup(aLoadGroup)
   , mRequest(aRequest)
-  , mHasBeenCrossSite(false)
   , mResponseAvailableCalled(false)
   , mFetchCalled(false)
 {
 }
 
 FetchDriver::~FetchDriver()
 {
   // We assert this since even on failures, we should call
@@ -82,83 +81,38 @@ FetchDriver::Fetch(FetchDriverObserver* 
                      "Synchronous fetch not supported");
 
   nsCOMPtr<nsIRunnable> r =
     NS_NewRunnableMethod(this, &FetchDriver::ContinueFetch);
   return NS_DispatchToCurrentThread(r);
 }
 
 nsresult
-FetchDriver::SetTainting()
-{
-  workers::AssertIsOnMainThread();
-
-  // If we've already been cross-site then we should be fully updated
-  if (mHasBeenCrossSite) {
-    return NS_OK;
-  }
-
-  nsAutoCString url;
-  mRequest->GetURL(url);
-  nsCOMPtr<nsIURI> requestURI;
-  nsresult rv = NS_NewURI(getter_AddRefs(requestURI), url,
-                          nullptr, nullptr);
-  NS_ENSURE_SUCCESS(rv, rv);
-
-  // Begin Step 8 of the Main Fetch algorithm
-  // https://fetch.spec.whatwg.org/#fetching
-
-  // request's current url's origin is request's origin and the CORS flag is unset
-  // request's current url's scheme is "data" and request's same-origin data-URL flag is set
-  // request's current url's scheme is "about"
-
-  // We have to manually check about:blank here since it's not treated as
-  // an inheriting URL by CheckMayLoad.
-  if (NS_IsAboutBlank(requestURI) ||
-       NS_SUCCEEDED(mPrincipal->CheckMayLoad(requestURI, false /* report */,
-                                             true /*allowIfInheritsPrincipal*/))) {
-    // What the spec calls "basic fetch" is handled within our necko channel
-    // code.  Therefore everything goes through HTTP Fetch
-    return NS_OK;
-  }
-
-  mHasBeenCrossSite = true;
-
-  // request's mode is "same-origin"
-  if (mRequest->Mode() == RequestMode::Same_origin) {
-    return NS_ERROR_DOM_BAD_URI;
-  }
-
-  // request's mode is "no-cors"
-  if (mRequest->Mode() == RequestMode::No_cors) {
-    mRequest->MaybeIncreaseResponseTainting(LoadTainting::Opaque);
-    // What the spec calls "basic fetch" is handled within our necko channel
-    // code.  Therefore everything goes through HTTP Fetch
-    return NS_OK;
-  }
-
-  // Otherwise
-  mRequest->MaybeIncreaseResponseTainting(LoadTainting::CORS);
-
-  return NS_OK;
-}
-
-nsresult
 FetchDriver::ContinueFetch()
 {
   workers::AssertIsOnMainThread();
 
   nsresult rv = HttpFetch();
   if (NS_FAILED(rv)) {
     FailWithNetworkError();
   }
  
   return rv;
 }
 
+static void
+AddLoadFlags(nsIRequest *aRequest, nsLoadFlags aNewFlags)
+{
+  MOZ_ASSERT(aRequest);
+  nsLoadFlags flags;
+  aRequest->GetLoadFlags(&flags);
+  flags |= aNewFlags;
+  aRequest->SetLoadFlags(flags);
+}
+
 // This function implements the "HTTP Fetch" algorithm from the Fetch spec.
 // Functionality is often split between here, the CORS listener proxy and the
 // Necko HTTP implementation.
 nsresult
 FetchDriver::HttpFetch()
 {
   // Step 1. "Let response be null."
   mResponse = nullptr;
@@ -181,19 +135,16 @@ FetchDriver::HttpFetch()
   if (mRequest->Mode() == RequestMode::No_cors &&
       mRequest->UnsafeRequest() &&
       (!mRequest->HasSimpleMethod() ||
        !mRequest->Headers()->HasOnlySimpleHeaders())) {
     MOZ_ASSERT(false, "The API should have caught this");
     return NS_ERROR_DOM_BAD_URI;
   }
 
-  rv = SetTainting();
-  NS_ENSURE_SUCCESS(rv, rv);
-
   // Step 2 deals with letting ServiceWorkers intercept requests. This is
   // handled by Necko after the channel is opened.
   // FIXME(nsm): Bug 1119026: The channel's skip service worker flag should be
   // set based on the Request's flag.
 
   // Step 3.1 "If the CORS preflight flag is set and one of these conditions is
   // true..." is handled by the CORS proxy.
   //
@@ -206,28 +157,16 @@ FetchDriver::HttpFetch()
   // request too.
 
   // Step 3.3 "Let credentials flag be set if one of
   //  - request's credentials mode is "include"
   //  - request's credentials mode is "same-origin" and either the CORS flag
   //    is unset or response tainting is "opaque"
   // is true, and unset otherwise."
 
-  // This is effectivetly the opposite of the use credentials flag in "HTTP
-  // network or cache fetch" in the spec and decides whether to transmit
-  // cookies and other identifying information. LOAD_ANONYMOUS also prevents
-  // new cookies sent by the server from being stored.  This value will
-  // propagate across redirects, which is what we want.
-  const nsLoadFlags credentialsFlag =
-    (mRequest->GetCredentialsMode() == RequestCredentials::Omit ||
-    (mHasBeenCrossSite &&
-     mRequest->GetCredentialsMode() == RequestCredentials::Same_origin &&
-     mRequest->Mode() == RequestMode::No_cors)) ?
-    nsIRequest::LOAD_ANONYMOUS : 0;
-
   // Set skip serviceworker flag.
   // While the spec also gates on the client being a ServiceWorker, we can't
   // infer that here. Instead we rely on callers to set the flag correctly.
   const nsLoadFlags bypassFlag = mRequest->SkipServiceWorker() ?
                                  nsIChannel::LOAD_BYPASS_SERVICE_WORKER : 0;
 
   nsSecurityFlags secFlags;
   if (mRequest->Mode() == RequestMode::Cors &&
@@ -249,17 +188,17 @@ FetchDriver::HttpFetch()
     secFlags |= nsILoadInfo::SEC_DONT_FOLLOW_REDIRECTS;
   }
 
   // From here on we create a channel and set its properties with the
   // information from the InternalRequest. This is an implementation detail.
   MOZ_ASSERT(mLoadGroup);
   nsCOMPtr<nsIChannel> chan;
 
-  nsLoadFlags loadFlags = nsIRequest::LOAD_NORMAL | credentialsFlag |
+  nsLoadFlags loadFlags = nsIRequest::LOAD_NORMAL |
     bypassFlag | nsIChannel::LOAD_CLASSIFY_URI;
   if (mDocument) {
     MOZ_ASSERT(mDocument->NodePrincipal() == mPrincipal);
     rv = NS_NewChannel(getter_AddRefs(chan),
                        uri,
                        mDocument,
                        secFlags |
                          nsILoadInfo::SEC_ABOUT_BLANK_INHERITS,
@@ -290,16 +229,27 @@ FetchDriver::HttpFetch()
   {
     nsCOMPtr<nsIInterfaceRequestor> notificationCallbacks;
     chan->GetNotificationCallbacks(getter_AddRefs(notificationCallbacks));
     MOZ_ASSERT(!notificationCallbacks);
   }
 #endif
   chan->SetNotificationCallbacks(this);
 
+  // This is effectivetly the opposite of the use credentials flag in "HTTP
+  // network or cache fetch" in the spec and decides whether to transmit
+  // cookies and other identifying information. LOAD_ANONYMOUS also prevents
+  // new cookies sent by the server from being stored.  This value will
+  // propagate across redirects, which is what we want.
+  if (mRequest->GetCredentialsMode() == RequestCredentials::Omit ||
+      (mRequest->GetCredentialsMode() == RequestCredentials::Same_origin &&
+       NS_HasBeenCrossOrigin(chan))) {
+    AddLoadFlags(chan, nsIRequest::LOAD_ANONYMOUS);
+  }
+
   // FIXME(nsm): Bug 1120715.
   // Step 3.4 "If request's cache mode is default and request's header list
   // contains a header named `If-Modified-Since`, `If-None-Match`,
   // `If-Unmodified-Since`, `If-Match`, or `If-Range`, set request's cache mode
   // to no-store."
 
   // Step 3.5 begins "HTTP network or cache fetch".
   // HTTP network or cache fetch
@@ -541,16 +491,33 @@ FetchDriver::OnStartRequest(nsIRequest* 
   MOZ_ASSERT(!mPipeOutputStream);
   MOZ_ASSERT(mObserver);
 
   RefPtr<InternalResponse> response;
   nsCOMPtr<nsIChannel> channel = do_QueryInterface(aRequest);
   nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(aRequest);
   nsCOMPtr<nsIJARChannel> jarChannel = do_QueryInterface(aRequest);
 
+  // On a successful redirect we perform the following substeps of HTTP Fetch,
+  // step 5, "redirect status", step 11.
+
+  // Step 11.5 "Append locationURL to request's url list." so that when we set the
+  // Response's URL from the Request's URL in Main Fetch, step 15, we get the
+  // final value. Note, we still use a single URL value instead of a list.
+  // Because of that we only need to do this after the request finishes.
+  nsCOMPtr<nsIURI> newURI;
+  rv = NS_GetFinalChannelURI(channel, getter_AddRefs(newURI));
+  if (NS_FAILED(rv)) {
+    FailWithNetworkError();
+    return rv;
+  }
+  nsAutoCString newUrl;
+  newURI->GetSpec(newUrl);
+  mRequest->SetURL(newUrl);
+
   bool foundOpaqueRedirect = false;
 
   if (httpChannel) {
     uint32_t responseStatus;
     httpChannel->GetResponseStatus(&responseStatus);
 
     if (mozilla::net::nsHttpChannel::IsRedirectStatus(responseStatus)) {
       if (mRequest->GetRedirectMode() == RequestRedirect::Error) {
@@ -647,28 +614,23 @@ FetchDriver::OnStartRequest(nsIRequest* 
 
   nsCOMPtr<nsILoadInfo> loadInfo;
   rv = channel->GetLoadInfo(getter_AddRefs(loadInfo));
   if (NS_WARN_IF(NS_FAILED(rv))) {
     FailWithNetworkError();
     return rv;
   }
 
-  LoadTainting channelTainting = LoadTainting::Basic;
-  if (loadInfo) {
-    channelTainting = loadInfo->GetTainting();
-  }
-
   // Propagate any tainting from the channel back to our response here.  This
   // step is not reflected in the spec because the spec is written such that
   // FetchEvent.respondWith() just passes the already-tainted Response back to
   // the outer fetch().  In gecko, however, we serialize the Response through
   // the channel and must regenerate the tainting from the channel in the
   // interception case.
-  mRequest->MaybeIncreaseResponseTainting(channelTainting);
+  mRequest->MaybeIncreaseResponseTainting(loadInfo->GetTainting());
 
   // Resolves fetch() promise which may trigger code running in a worker.  Make
   // sure the Response is fully initialized before calling this.
   mResponse = BeginAndGetFilteredResponse(response, channelURI,
                                           foundOpaqueRedirect);
 
   nsCOMPtr<nsIEventTarget> sts = do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID, &rv);
   if (NS_WARN_IF(NS_FAILED(rv))) {
@@ -740,25 +702,21 @@ FetchDriver::OnStopRequest(nsIRequest* a
 NS_IMETHODIMP
 FetchDriver::AsyncOnChannelRedirect(nsIChannel* aOldChannel,
                                     nsIChannel* aNewChannel,
                                     uint32_t aFlags,
                                     nsIAsyncVerifyRedirectCallback *aCallback)
 {
   NS_PRECONDITION(aNewChannel, "Redirect without a channel?");
 
-  if (NS_IsInternalSameURIRedirect(aOldChannel, aNewChannel, aFlags) ||
-      NS_IsHSTSUpgradeRedirect(aOldChannel, aNewChannel, aFlags)) {
-    aCallback->OnRedirectVerifyCallback(NS_OK);
-    return NS_OK;
-  }
-
   // We should only ever get here if we use a "follow" redirect policy,
   // or if if we set an "error" policy as a result of a CORS policy.
-  MOZ_ASSERT(mRequest->GetRedirectMode() == RequestRedirect::Follow);
+  MOZ_ASSERT(NS_IsInternalSameURIRedirect(aOldChannel, aNewChannel, aFlags) ||
+             NS_IsHSTSUpgradeRedirect(aOldChannel, aNewChannel, aFlags) ||
+             mRequest->GetRedirectMode() == RequestRedirect::Follow);
 
   // HTTP Fetch step 5, "redirect status", step 1 is done by necko
 
   // HTTP Fetch step 5, "redirect status", steps 2 through 6 are automatically
   // handled by necko before calling AsyncOnChannelRedirect() with the new
   // nsIChannel.
 
   // HTTP Fetch step 5, "redirect status", steps 7 and 8 enforcing a redirect
@@ -775,79 +733,44 @@ FetchDriver::AsyncOnChannelRedirect(nsIC
   // The following steps are from HTTP Fetch step 5, "redirect status", step 11
   // which requires the RequestRedirect to be "follow". We asserted that we're
   // in either "follow" or "error" mode here.
 
   // HTTP Fetch step 5, "redirect status", steps 11.1 and 11.2 block redirecting
   // to a URL with credentials in CORS mode.  This is implemented in
   // nsCORSListenerProxy.
 
-  // On a successful redirect we perform the following substeps of HTTP Fetch,
-  // step 5, "redirect status", step 11.
-
-  // Step 11.5 "Append locationURL to request's url list." so that when we set the
-  // Response's URL from the Request's URL in Main Fetch, step 15, we get the
-  // final value. Note, we still use a single URL value instead of a list.
-  nsCOMPtr<nsIURI> newURI;
-  nsresult rv = NS_GetFinalChannelURI(aNewChannel, getter_AddRefs(newURI));
-  if (NS_FAILED(rv)) {
-    aOldChannel->Cancel(rv);
-    return rv;
-  }
-
-  // We need to update our request's URL.
-  nsAutoCString newUrl;
-  newURI->GetSpec(newUrl);
-  mRequest->SetURL(newUrl);
-
   // Implement Main Fetch step 8 again on redirect.
-  rv = SetTainting();
-  if (NS_FAILED(rv)) {
-    aOldChannel->Cancel(rv);
-    return rv;
-  }
 
   // Requests that require preflight are not permitted to redirect.
   // Fetch spec section 4.2 "HTTP Fetch", step 4.9 just uses the manual
   // redirect flag to decide whether to execute step 4.10 or not. We do not
   // represent it in our implementation.
   // This is handled by nsCORSListenerProxy.
 
   // Otherwise, we rely on necko and the CORS proxy to do the right thing
   // as the redirect is followed.  In general this means http
   // fetch.  If we've ever been CORS, we need to stay CORS.
 
   // Possibly set the LOAD_ANONYMOUS flag on the channel.
-  if (mHasBeenCrossSite &&
-      mRequest->GetCredentialsMode() == RequestCredentials::Same_origin &&
-      mRequest->Mode() == RequestMode::No_cors) {
-    // In the case of a "no-cors" mode request with "same-origin" credentials,
-    // we have to set LOAD_ANONYMOUS manually here in order to avoid sending
-    // credentials on a cross-origin redirect.
-    nsLoadFlags flags;
-    rv = aNewChannel->GetLoadFlags(&flags);
-    if (NS_SUCCEEDED(rv)) {
-      flags |= nsIRequest::LOAD_ANONYMOUS;
-      rv = aNewChannel->SetLoadFlags(flags);
-    }
-    if (NS_FAILED(rv)) {
-      aOldChannel->Cancel(rv);
-      return rv;
-    }
+  if (mRequest->GetCredentialsMode() == RequestCredentials::Same_origin &&
+      NS_HasBeenCrossOrigin(aNewChannel)) {
+    AddLoadFlags(aNewChannel, nsIRequest::LOAD_ANONYMOUS);
   }
+
 #ifdef DEBUG
   {
     // Make sure nothing in the redirect chain screws up our credentials
     // settings. LOAD_ANONYMOUS must be set if we RequestCredentials is "omit"
     // or "same-origin".
     nsLoadFlags flags;
     aNewChannel->GetLoadFlags(&flags);
     bool shouldBeAnon =
       mRequest->GetCredentialsMode() == RequestCredentials::Omit ||
-      (mHasBeenCrossSite &&
+      (NS_HasBeenCrossOrigin(aNewChannel) &&
        mRequest->GetCredentialsMode() == RequestCredentials::Same_origin);
     MOZ_ASSERT(!!(flags & nsIRequest::LOAD_ANONYMOUS) == shouldBeAnon);
   }
 #endif
 
   aCallback->OnRedirectVerifyCallback(NS_OK);
 
   return NS_OK;
--- a/dom/fetch/FetchDriver.h
+++ b/dom/fetch/FetchDriver.h
@@ -77,27 +77,25 @@ public:
 private:
   nsCOMPtr<nsIPrincipal> mPrincipal;
   nsCOMPtr<nsILoadGroup> mLoadGroup;
   RefPtr<InternalRequest> mRequest;
   RefPtr<InternalResponse> mResponse;
   nsCOMPtr<nsIOutputStream> mPipeOutputStream;
   RefPtr<FetchDriverObserver> mObserver;
   nsCOMPtr<nsIDocument> mDocument;
-  bool mHasBeenCrossSite;
 
   DebugOnly<bool> mResponseAvailableCalled;
   DebugOnly<bool> mFetchCalled;
 
   FetchDriver() = delete;
   FetchDriver(const FetchDriver&) = delete;
   FetchDriver& operator=(const FetchDriver&) = delete;
   ~FetchDriver();
 
-  nsresult SetTainting();
   nsresult ContinueFetch();
   nsresult HttpFetch();
   // Returns the filtered response sent to the observer.
   // Callers who don't have access to a channel can pass null for aFinalURI.
   already_AddRefed<InternalResponse>
   BeginAndGetFilteredResponse(InternalResponse* aResponse, nsIURI* aFinalURI,
                               bool aFoundOpaqueRedirect);
   // Utility since not all cases need to do any post processing of the filtered
--- a/dom/security/nsContentSecurityManager.cpp
+++ b/dom/security/nsContentSecurityManager.cpp
@@ -86,38 +86,29 @@ URIHasFlags(nsIURI* aURI, uint32_t aURIF
   bool hasFlags;
   nsresult rv = NS_URIChainHasFlags(aURI, aURIFlags, &hasFlags);
   NS_ENSURE_SUCCESS(rv, false);
 
   return hasFlags;
 }
 
 static nsresult
-DoSOPChecks(nsIURI* aURI, nsILoadInfo* aLoadInfo)
+DoSOPChecks(nsIURI* aURI, nsILoadInfo* aLoadInfo, nsIChannel* aChannel)
 {
   if (aLoadInfo->GetAllowChrome() &&
       (URIHasFlags(aURI, nsIProtocolHandler::URI_IS_UI_RESOURCE) ||
        SchemeIs(aURI, "moz-safe-about"))) {
     // UI resources are allowed.
     return DoCheckLoadURIChecks(aURI, aLoadInfo);
   }
 
-  nsIPrincipal* loadingPrincipal = aLoadInfo->LoadingPrincipal();
-  bool sameOriginDataInherits =
-    aLoadInfo->GetSecurityMode() == nsILoadInfo::SEC_REQUIRE_SAME_ORIGIN_DATA_INHERITS;
+  NS_ENSURE_FALSE(NS_HasBeenCrossOrigin(aChannel, true),
+                  NS_ERROR_DOM_BAD_URI);
 
-  if (sameOriginDataInherits &&
-      aLoadInfo->GetAboutBlankInherits() &&
-      NS_IsAboutBlank(aURI)) {
-    return NS_OK;
-  }
-
-  return loadingPrincipal->CheckMayLoad(aURI,
-                                        true, // report to console
-                                        sameOriginDataInherits);
+  return NS_OK;
 }
 
 static nsresult
 DoCORSChecks(nsIChannel* aChannel, nsILoadInfo* aLoadInfo,
              nsCOMPtr<nsIStreamListener>& aInAndOutListener)
 {
   MOZ_RELEASE_ASSERT(aInAndOutListener, "can not perform CORS checks without a listener");
   nsIPrincipal* loadingPrincipal = aLoadInfo->LoadingPrincipal();
@@ -362,20 +353,19 @@ nsContentSecurityManager::doContentSecur
   // we just set the flag again.
   rv = loadInfo->SetEnforceSecurity(true);
   NS_ENSURE_SUCCESS(rv, rv);
 
   if (loadInfo->GetSecurityMode() == nsILoadInfo::SEC_REQUIRE_CORS_DATA_INHERITS) {
     rv = DoCORSChecks(aChannel, loadInfo, aInAndOutListener);
     NS_ENSURE_SUCCESS(rv, rv);
   }
-  else {
-    rv = CheckChannel(aChannel);
-    NS_ENSURE_SUCCESS(rv, rv);
-  }
+
+  rv = CheckChannel(aChannel);
+  NS_ENSURE_SUCCESS(rv, rv);
 
   nsCOMPtr<nsIURI> finalChannelURI;
   rv = NS_GetFinalChannelURI(aChannel, getter_AddRefs(finalChannelURI));
   NS_ENSURE_SUCCESS(rv, rv);
 
   // Perform all ContentPolicy checks (MixedContent, CSP, ...)
   rv = DoContentSecurityChecks(finalChannelURI, loadInfo);
   NS_ENSURE_SUCCESS(rv, rv);
@@ -437,36 +427,43 @@ nsContentSecurityManager::AsyncOnChannel
  * if this requesst should not be permitted.
  */
 nsresult
 nsContentSecurityManager::CheckChannel(nsIChannel* aChannel)
 {
   nsCOMPtr<nsILoadInfo> loadInfo = aChannel->GetLoadInfo();
   MOZ_ASSERT(loadInfo);
 
+  nsSecurityFlags securityMode = loadInfo->GetSecurityMode();
+
   // CORS mode is handled by nsCORSListenerProxy
-  nsSecurityFlags securityMode = loadInfo->GetSecurityMode();
   if (securityMode == nsILoadInfo::SEC_REQUIRE_CORS_DATA_INHERITS) {
+    if (NS_HasBeenCrossOrigin(aChannel)) {
+      loadInfo->MaybeIncreaseTainting(LoadTainting::CORS);
+    }
     return NS_OK;
   }
 
   nsCOMPtr<nsIURI> uri;
   nsresult rv = NS_GetFinalChannelURI(aChannel, getter_AddRefs(uri));
   NS_ENSURE_SUCCESS(rv, rv);
 
 
   // if none of the REQUIRE_SAME_ORIGIN flags are set, then SOP does not apply
   if ((securityMode == nsILoadInfo::SEC_REQUIRE_SAME_ORIGIN_DATA_INHERITS) ||
       (securityMode == nsILoadInfo::SEC_REQUIRE_SAME_ORIGIN_DATA_IS_BLOCKED)) {
-    rv = DoSOPChecks(uri, loadInfo);
+    rv = DoSOPChecks(uri, loadInfo, aChannel);
     NS_ENSURE_SUCCESS(rv, rv);
   }
 
   if ((securityMode == nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_INHERITS) ||
       (securityMode == nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL)) {
+    if (NS_HasBeenCrossOrigin(aChannel)) {
+      loadInfo->MaybeIncreaseTainting(LoadTainting::Opaque);
+    }
     // Please note that DoCheckLoadURIChecks should only be enforced for
     // cross origin requests. If the flag SEC_REQUIRE_CORS_DATA_INHERITS is set
     // within the loadInfo, then then CheckLoadURIWithPrincipal is performed
     // within nsCorsListenerProxy
     rv = DoCheckLoadURIChecks(uri, loadInfo);
     NS_ENSURE_SUCCESS(rv, rv);
   }
 
--- a/ipc/glue/BackgroundUtils.cpp
+++ b/ipc/glue/BackgroundUtils.cpp
@@ -237,16 +237,17 @@ LoadInfoToLoadInfoArgs(nsILoadInfo *aLoa
   }
 
   *aOptionalLoadInfoArgs =
     LoadInfoArgs(
       requestingPrincipalInfo,
       triggeringPrincipalInfo,
       aLoadInfo->GetSecurityFlags(),
       aLoadInfo->InternalContentPolicyType(),
+      static_cast<uint32_t>(aLoadInfo->GetTainting()),
       aLoadInfo->GetUpgradeInsecureRequests(),
       aLoadInfo->GetUpgradeInsecurePreloads(),
       aLoadInfo->GetInnerWindowID(),
       aLoadInfo->GetOuterWindowID(),
       aLoadInfo->GetParentOuterWindowID(),
       aLoadInfo->GetEnforceSecurity(),
       aLoadInfo->GetInitialSecurityCheckDone(),
       aLoadInfo->GetIsInThirdPartyContext(),
@@ -296,16 +297,17 @@ LoadInfoArgsToLoadInfo(const OptionalLoa
     redirectChain.AppendElement(redirectedPrincipal.forget());
   }
 
   nsCOMPtr<nsILoadInfo> loadInfo =
     new mozilla::LoadInfo(requestingPrincipal,
                           triggeringPrincipal,
                           loadInfoArgs.securityFlags(),
                           loadInfoArgs.contentPolicyType(),
+                          static_cast<LoadTainting>(loadInfoArgs.tainting()),
                           loadInfoArgs.upgradeInsecureRequests(),
                           loadInfoArgs.upgradeInsecurePreloads(),
                           loadInfoArgs.innerWindowID(),
                           loadInfoArgs.outerWindowID(),
                           loadInfoArgs.parentOuterWindowID(),
                           loadInfoArgs.enforceSecurity(),
                           loadInfoArgs.initialSecurityCheckDone(),
                           loadInfoArgs.isInThirdPartyContext(),
--- a/netwerk/base/LoadInfo.cpp
+++ b/netwerk/base/LoadInfo.cpp
@@ -118,16 +118,17 @@ LoadInfo::LoadInfo(const LoadInfo& rhs)
   , mIsPreflight(rhs.mIsPreflight)
 {
 }
 
 LoadInfo::LoadInfo(nsIPrincipal* aLoadingPrincipal,
                    nsIPrincipal* aTriggeringPrincipal,
                    nsSecurityFlags aSecurityFlags,
                    nsContentPolicyType aContentPolicyType,
+                   LoadTainting aTainting,
                    bool aUpgradeInsecureRequests,
                    bool aUpgradeInsecurePreloads,
                    uint64_t aInnerWindowID,
                    uint64_t aOuterWindowID,
                    uint64_t aParentOuterWindowID,
                    bool aEnforceSecurity,
                    bool aInitialSecurityCheckDone,
                    bool aIsThirdPartyContext,
@@ -136,17 +137,17 @@ LoadInfo::LoadInfo(nsIPrincipal* aLoadin
                    nsTArray<nsCOMPtr<nsIPrincipal>>& aRedirectChain,
                    const nsTArray<nsCString>& aCorsUnsafeHeaders,
                    bool aForcePreflight,
                    bool aIsPreflight)
   : mLoadingPrincipal(aLoadingPrincipal)
   , mTriggeringPrincipal(aTriggeringPrincipal)
   , mSecurityFlags(aSecurityFlags)
   , mInternalContentPolicyType(aContentPolicyType)
-  , mTainting(LoadTainting::Basic)
+  , mTainting(aTainting)
   , mUpgradeInsecureRequests(aUpgradeInsecureRequests)
   , mUpgradeInsecurePreloads(aUpgradeInsecurePreloads)
   , mInnerWindowID(aInnerWindowID)
   , mOuterWindowID(aOuterWindowID)
   , mParentOuterWindowID(aParentOuterWindowID)
   , mEnforceSecurity(aEnforceSecurity)
   , mInitialSecurityCheckDone(aInitialSecurityCheckDone)
   , mIsThirdPartyContext(aIsThirdPartyContext)
--- a/netwerk/base/LoadInfo.h
+++ b/netwerk/base/LoadInfo.h
@@ -67,16 +67,17 @@ private:
   // private constructor that is only allowed to be called from within
   // HttpChannelParent and FTPChannelParent declared as friends undeneath.
   // In e10s we can not serialize nsINode, hence we store the innerWindowID.
   // Please note that aRedirectChain uses swapElements.
   LoadInfo(nsIPrincipal* aLoadingPrincipal,
            nsIPrincipal* aTriggeringPrincipal,
            nsSecurityFlags aSecurityFlags,
            nsContentPolicyType aContentPolicyType,
+           LoadTainting aTainting,
            bool aUpgradeInsecureRequests,
            bool aUpgradeInsecurePreloads,
            uint64_t aInnerWindowID,
            uint64_t aOuterWindowID,
            uint64_t aParentOuterWindowID,
            bool aEnforceSecurity,
            bool aInitialSecurityCheckDone,
            bool aIsThirdPartyRequest,
--- a/netwerk/base/nsNetUtil.cpp
+++ b/netwerk/base/nsNetUtil.cpp
@@ -1247,16 +1247,65 @@ NS_GetAppInfo(nsIChannel *aChannel,
     NS_ENSURE_SUCCESS(rv, false);
 
     rv = loadContext->GetIsInBrowserElement(aIsInBrowserElement);
     NS_ENSURE_SUCCESS(rv, false);
 
     return true;
 }
 
+bool
+NS_HasBeenCrossOrigin(nsIChannel* aChannel, bool aReport)
+{
+  nsCOMPtr<nsILoadInfo> loadInfo = aChannel->GetLoadInfo();
+  MOZ_RELEASE_ASSERT(loadInfo, "Origin tracking only works for channels created with a loadinfo");
+
+  // Always treat tainted channels as cross-origin.
+  if (loadInfo->GetTainting() != LoadTainting::Basic) {
+    return true;
+  }
+
+  nsCOMPtr<nsIPrincipal> loadingPrincipal = loadInfo->LoadingPrincipal();
+  uint32_t mode = loadInfo->GetSecurityMode();
+  bool dataInherits =
+    mode == nsILoadInfo::SEC_REQUIRE_SAME_ORIGIN_DATA_INHERITS ||
+    mode == nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_INHERITS ||
+    mode == nsILoadInfo::SEC_REQUIRE_CORS_DATA_INHERITS;
+
+  bool aboutBlankInherits = dataInherits && loadInfo->GetAboutBlankInherits();
+
+  for (nsIPrincipal* principal : loadInfo->RedirectChain()) {
+    nsCOMPtr<nsIURI> uri;
+    principal->GetURI(getter_AddRefs(uri));
+    if (!uri) {
+      return true;
+    }
+
+    if (aboutBlankInherits && NS_IsAboutBlank(uri)) {
+      continue;
+    }
+
+    if (NS_FAILED(loadingPrincipal->CheckMayLoad(uri, aReport, dataInherits))) {
+      return true;
+    }
+  }
+
+  nsCOMPtr<nsIURI> uri;
+  NS_GetFinalChannelURI(aChannel, getter_AddRefs(uri));
+  if (!uri) {
+    return true;
+  }
+
+  if (aboutBlankInherits && NS_IsAboutBlank(uri)) {
+    return false;
+  }
+
+  return NS_FAILED(loadingPrincipal->CheckMayLoad(uri, aReport, dataInherits));
+}
+
 nsresult
 NS_GetAppInfoFromClearDataNotification(nsISupports *aSubject,
                                        uint32_t *aAppID,
                                        bool *aBrowserOnly)
 {
     nsresult rv;
 
     nsCOMPtr<mozIApplicationClearPrivateDataParams>
--- a/netwerk/base/nsNetUtil.h
+++ b/netwerk/base/nsNetUtil.h
@@ -686,16 +686,22 @@ NS_QueryNotificationCallbacks(nsIInterfa
 bool NS_UsePrivateBrowsing(nsIChannel *channel);
 
 /**
  * Extract the NeckoOriginAttributes from the channel's triggering principal.
  */
 bool NS_GetOriginAttributes(nsIChannel *aChannel,
                             mozilla::NeckoOriginAttributes &aAttributes);
 
+/**
+ * Returns true if the channel has visited any cross-origin URLs on any
+ * URLs that it was redirected through.
+ */
+bool NS_HasBeenCrossOrigin(nsIChannel* aChannel, bool aReport = false);
+
 // Constants duplicated from nsIScriptSecurityManager so we avoid having necko
 // know about script security manager.
 #define NECKO_NO_APP_ID 0
 #define NECKO_UNKNOWN_APP_ID UINT32_MAX
 // special app id reserved for separating the safebrowsing cookie
 #define NECKO_SAFEBROWSING_APP_ID UINT32_MAX - 1
 
 /**
--- a/netwerk/ipc/NeckoChannelParams.ipdlh
+++ b/netwerk/ipc/NeckoChannelParams.ipdlh
@@ -26,16 +26,17 @@ namespace net {
 //-----------------------------------------------------------------------------
 
 struct LoadInfoArgs
 {
   PrincipalInfo    requestingPrincipalInfo;
   PrincipalInfo    triggeringPrincipalInfo;
   uint32_t         securityFlags;
   uint32_t         contentPolicyType;
+  uint32_t         tainting;
   bool             upgradeInsecureRequests;
   bool             upgradeInsecurePreloads;
   uint64_t         innerWindowID;
   uint64_t         outerWindowID;
   uint64_t         parentOuterWindowID;
   bool             enforceSecurity;
   bool             initialSecurityCheckDone;
   bool             isInThirdPartyContext;