Bug 1321868 - Add an API to nsIDocument to determine whether a script is on the tracking protection list; r=bkelly
authorEhsan Akhgari <ehsan@mozilla.com>
Tue, 22 Nov 2016 18:28:37 -0500
changeset 325164 eb3ac9d2775b8c4e7b089cab4d33d953d6605f0e
parent 325163 5f494ff3b83be2b5e8529e859d80ca3efacf19f7
child 325165 72cd631fdd7957fccec0beea95f6b89df27fe65a
push id24
push usermaklebus@msu.edu
push dateTue, 20 Dec 2016 03:11:33 +0000
reviewersbkelly
bugs1321868
milestone53.0a1
Bug 1321868 - Add an API to nsIDocument to determine whether a script is on the tracking protection list; r=bkelly In order to be able to put timeouts in the correct bucket as soon as they are scheduled, we need to be able to synchronously tell whether a timeout is coming from a script that is on the tracking protection list. But the URL Classifier API which we use for this task is asynchronous. Because of this, and to avoid unnecessary IPC from the content to the parent process every time we need to know where a script came from, we cache this information in nsIDocument. The hash table mTrackingScripts will have one entry per script loaded in the document which is on the tracking protection list. For performance reasons, we coalesce querying whether a script source URL is on the tracking protection list with the load of the script from the network. In most cases we'll have the response from the URL Classifier service before the script load is finished, but on the off chance that the load finishes first, we wait before finishing the script load to get the response from the URL Classifier service.
dom/base/nsDocument.cpp
dom/base/nsIDocument.h
dom/base/nsScriptLoader.cpp
dom/base/nsScriptLoader.h
--- a/dom/base/nsDocument.cpp
+++ b/dom/base/nsDocument.cpp
@@ -2899,16 +2899,32 @@ already_AddRefed<nsIEventTarget>
 nsIDocument::EventTargetFor(TaskCategory aCategory) const
 {
   if (mDocGroup) {
     return mDocGroup->EventTargetFor(aCategory);
   }
   return DispatcherTrait::EventTargetFor(aCategory);
 }
 
+void
+nsIDocument::NoteScriptTrackingStatus(const nsACString& aURL, bool aIsTracking)
+{
+  if (aIsTracking) {
+    mTrackingScripts.PutEntry(aURL);
+  } else {
+    MOZ_ASSERT(!mTrackingScripts.Contains(aURL));
+  }
+}
+
+bool
+nsIDocument::IsScriptTracking(const nsACString& aURL) const
+{
+  return mTrackingScripts.Contains(aURL);
+}
+
 NS_IMETHODIMP
 nsDocument::GetApplicationCache(nsIApplicationCache **aApplicationCache)
 {
   NS_IF_ADDREF(*aApplicationCache = mApplicationCache);
 
   return NS_OK;
 }
 
--- a/dom/base/nsIDocument.h
+++ b/dom/base/nsIDocument.h
@@ -2870,16 +2870,22 @@ public:
   // Dispatch a runnable related to the document.
   virtual nsresult Dispatch(const char* aName,
                             mozilla::dom::TaskCategory aCategory,
                             already_AddRefed<nsIRunnable>&& aRunnable) override;
 
   virtual already_AddRefed<nsIEventTarget>
   EventTargetFor(mozilla::dom::TaskCategory aCategory) const override;
 
+  // The URLs passed to these functions should match what
+  // JS::DescribeScriptedCaller() returns, since these APIs are used to
+  // determine whether some code is being called from a tracking script.
+  void NoteScriptTrackingStatus(const nsACString& aURL, bool isTracking);
+  bool IsScriptTracking(const nsACString& aURL) const;
+
 protected:
   bool GetUseCounter(mozilla::UseCounter aUseCounter)
   {
     return mUseCounters[aUseCounter];
   }
 
   void SetChildDocumentUseCounter(mozilla::UseCounter aUseCounter)
   {
@@ -3328,16 +3334,21 @@ protected:
   std::bitset<mozilla::eUseCounter_Count> mNotifiedPageForUseCounter;
 
   // Whether the user has interacted with the document or not:
   bool mUserHasInteracted;
 
   mozilla::TimeStamp mPageUnloadingEventTimeStamp;
 
   RefPtr<mozilla::dom::DocGroup> mDocGroup;
+
+  // The set of all the tracking script URLs.  URLs are added to this set by
+  // calling NoteScriptTrackingStatus().  Currently we assume that a URL not
+  // existing in the set means the corresponding script isn't a tracking script.
+  nsTHashtable<nsCStringHashKey> mTrackingScripts;
 };
 
 NS_DEFINE_STATIC_IID_ACCESSOR(nsIDocument, NS_IDOCUMENT_IID)
 
 /**
  * mozAutoSubtreeModified batches DOM mutations so that a DOMSubtreeModified
  * event is dispatched, if necessary, when the outermost mozAutoSubtreeModified
  * object is deleted.
--- a/dom/base/nsScriptLoader.cpp
+++ b/dom/base/nsScriptLoader.cpp
@@ -30,23 +30,23 @@
 #include "nsJSPrincipals.h"
 #include "nsContentPolicyUtils.h"
 #include "nsIHttpChannel.h"
 #include "nsIHttpChannelInternal.h"
 #include "nsIClassOfService.h"
 #include "nsITimedChannel.h"
 #include "nsIScriptElement.h"
 #include "nsIDOMHTMLScriptElement.h"
-#include "nsIDocShell.h"
 #include "nsContentUtils.h"
 #include "nsUnicharUtils.h"
 #include "nsAutoPtr.h"
 #include "nsIXPConnect.h"
 #include "nsError.h"
 #include "nsThreadUtils.h"
+#include "nsDocShell.h"
 #include "nsDocShellCID.h"
 #include "nsIContentSecurityPolicy.h"
 #include "mozilla/Logging.h"
 #include "nsCRT.h"
 #include "nsContentCreatorFunctions.h"
 #include "nsProxyRelease.h"
 #include "nsSandboxFlags.h"
 #include "nsContentTypeParser.h"
@@ -1296,16 +1296,18 @@ nsScriptLoader::StartLoad(nsScriptLoadRe
       mDocument->GetDocumentURI()->GetAsciiSpec(sourceUri);
     }
     sriDataVerifier = new SRICheckDataVerifier(aRequest->mIntegrity, sourceUri,
                                                mReporter);
   }
 
   RefPtr<nsScriptLoadHandler> handler =
       new nsScriptLoadHandler(this, aRequest, sriDataVerifier.forget());
+  rv = handler->Init();
+  NS_ENSURE_SUCCESS(rv, rv);
 
   nsCOMPtr<nsIIncrementalStreamLoader> loader;
   rv = NS_NewIncrementalStreamLoader(getter_AddRefs(loader), handler);
   NS_ENSURE_SUCCESS(rv, rv);
 
   return channel->AsyncOpen2(loader);
 }
 
@@ -2083,16 +2085,20 @@ nsScriptLoader::FillCompileOptionsForReq
   // aRequest ended up getting script data from, as the script filename.
   nsresult rv;
   nsContentUtils::GetWrapperSafeScriptFilename(mDocument, aRequest->mURI,
                                                aRequest->mURL, &rv);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
+  if (mDocument) {
+    mDocument->NoteScriptTrackingStatus(aRequest->mURL, aRequest->IsTracking());
+  }
+
   bool isScriptElement = !aRequest->IsModuleRequest() ||
                          aRequest->AsModuleRequest()->IsTopLevel();
   aOptions->setIntroductionType(isScriptElement ? "scriptElement"
                                                 : "importedModule");
   aOptions->setFileAndLine(aRequest->mURL.get(), aRequest->mLineNo);
   aOptions->setVersion(JSVersion(aRequest->mJSVersion));
   aOptions->setIsRunOnce(true);
   // We only need the setNoScriptRval bit when compiling off-thread here, since
@@ -2445,50 +2451,45 @@ nsScriptLoader::ConvertToUTF16(nsIChanne
     js_free(aBufOut);
     aBufOut = nullptr;
     aLengthOut = 0;
   }
   return rv;
 }
 
 nsresult
-nsScriptLoader::OnStreamComplete(nsIIncrementalStreamLoader* aLoader,
+nsScriptLoader::OnStreamComplete(nsIChannel* aChannel,
                                  nsISupports* aContext,
                                  nsresult aChannelStatus,
                                  nsresult aSRIStatus,
                                  mozilla::Vector<char16_t> &aString,
                                  mozilla::dom::SRICheckDataVerifier* aSRIDataVerifier)
 {
   nsScriptLoadRequest* request = static_cast<nsScriptLoadRequest*>(aContext);
   NS_ASSERTION(request, "null request in stream complete handler");
   NS_ENSURE_TRUE(request, NS_ERROR_FAILURE);
 
-  nsCOMPtr<nsIRequest> channelRequest;
-  aLoader->GetRequest(getter_AddRefs(channelRequest));
-  nsCOMPtr<nsIChannel> channel;
-  channel = do_QueryInterface(channelRequest);
-
   nsresult rv = NS_OK;
   if (!request->mIntegrity.IsEmpty() &&
       NS_SUCCEEDED((rv = aSRIStatus))) {
     MOZ_ASSERT(aSRIDataVerifier);
     MOZ_ASSERT(mReporter);
 
     nsAutoCString sourceUri;
     if (mDocument && mDocument->GetDocumentURI()) {
       mDocument->GetDocumentURI()->GetAsciiSpec(sourceUri);
     }
-    rv = aSRIDataVerifier->Verify(request->mIntegrity, channel, sourceUri,
+    rv = aSRIDataVerifier->Verify(request->mIntegrity, aChannel, sourceUri,
                                   mReporter);
     mReporter->FlushConsoleReports(mDocument);
     if (NS_FAILED(rv)) {
       rv = NS_ERROR_SRI_CORRUPT;
     }
   } else {
-    nsCOMPtr<nsILoadInfo> loadInfo = channel->GetLoadInfo();
+    nsCOMPtr<nsILoadInfo> loadInfo = aChannel->GetLoadInfo();
 
     if (loadInfo->GetEnforceSRI()) {
       MOZ_LOG(SRILogHelper::GetSriLog(), mozilla::LogLevel::Debug,
              ("nsScriptLoader::OnStreamComplete, required SRI not found"));
       nsCOMPtr<nsIContentSecurityPolicy> csp;
       loadInfo->LoadingPrincipal()->GetCsp(getter_AddRefs(csp));
       nsAutoCString violationURISpec;
       mDocument->GetDocumentURI()->GetAsciiSpec(violationURISpec);
@@ -2497,17 +2498,17 @@ nsScriptLoader::OnStreamComplete(nsIIncr
         nsIContentSecurityPolicy::VIOLATION_TYPE_REQUIRE_SRI_FOR_SCRIPT,
         NS_ConvertUTF8toUTF16(violationURISpec),
         EmptyString(), lineNo, EmptyString(), EmptyString());
       rv = NS_ERROR_SRI_CORRUPT;
     }
   }
 
   if (NS_SUCCEEDED(rv)) {
-    rv = PrepareLoadedRequest(request, aLoader, aChannelStatus, aString);
+    rv = PrepareLoadedRequest(request, aChannel, aChannelStatus, aString);
   }
 
   if (NS_FAILED(rv)) {
     /*
      * Handle script not loading error because source was a tracking URL.
      * We make a note of this script node by including it in a dedicated
      * array of blocked tracking nodes under its parent document.
      */
@@ -2610,17 +2611,17 @@ nsScriptLoader::MaybeMoveToLoadedList(ns
       RefPtr<nsScriptLoadRequest> req = mLoadingAsyncRequests.Steal(aRequest);
       mLoadedAsyncRequests.AppendElement(req);
     }
   }
 }
 
 nsresult
 nsScriptLoader::PrepareLoadedRequest(nsScriptLoadRequest* aRequest,
-                                     nsIIncrementalStreamLoader* aLoader,
+                                     nsIChannel* aChannel,
                                      nsresult aStatus,
                                      mozilla::Vector<char16_t> &aString)
 {
   if (NS_FAILED(aStatus)) {
     return aStatus;
   }
 
   if (aRequest->IsCanceled()) {
@@ -2628,45 +2629,39 @@ nsScriptLoader::PrepareLoadedRequest(nsS
   }
 
   // If we don't have a document, then we need to abort further
   // evaluation.
   if (!mDocument) {
     return NS_ERROR_NOT_AVAILABLE;
   }
 
-  // If the load returned an error page, then we need to abort
-  nsCOMPtr<nsIRequest> req;
-  nsresult rv = aLoader->GetRequest(getter_AddRefs(req));
-  NS_ASSERTION(req, "StreamLoader's request went away prematurely");
-  NS_ENSURE_SUCCESS(rv, rv);
-
-  nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(req);
+  nsresult rv;
+  nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(aChannel);
   if (httpChannel) {
     bool requestSucceeded;
     rv = httpChannel->GetRequestSucceeded(&requestSucceeded);
     if (NS_SUCCEEDED(rv) && !requestSucceeded) {
       return NS_ERROR_NOT_AVAILABLE;
     }
 
     nsAutoCString sourceMapURL;
     rv = httpChannel->GetResponseHeader(NS_LITERAL_CSTRING("X-SourceMap"), sourceMapURL);
     if (NS_SUCCEEDED(rv)) {
       aRequest->mHasSourceMapURL = true;
       aRequest->mSourceMapURL = NS_ConvertUTF8toUTF16(sourceMapURL);
     }
   }
 
-  nsCOMPtr<nsIChannel> channel = do_QueryInterface(req);
   // If this load was subject to a CORS check; don't flag it with a
   // separate origin principal, so that it will treat our document's
   // principal as the origin principal
   if (aRequest->mCORSMode == CORS_NONE) {
     rv = nsContentUtils::GetSecurityManager()->
-      GetChannelResultPrincipal(channel, getter_AddRefs(aRequest->mOriginPrincipal));
+      GetChannelResultPrincipal(aChannel, getter_AddRefs(aRequest->mOriginPrincipal));
     NS_ENSURE_SUCCESS(rv, rv);
   }
 
   if (!aString.empty()) {
     aRequest->mScriptTextLength = aString.length();
     aRequest->mScriptTextBuf = aString.extractOrCopyRawBuffer();
   }
 
@@ -2686,23 +2681,23 @@ nsScriptLoader::PrepareLoadedRequest(nsS
                "aRequest should be pending!");
 
   if (aRequest->IsModuleRequest()) {
     nsModuleLoadRequest* request = aRequest->AsModuleRequest();
 
     // When loading a module, only responses with a JavaScript MIME type are
     // acceptable.
     nsAutoCString mimeType;
-    channel->GetContentType(mimeType);
+    aChannel->GetContentType(mimeType);
     NS_ConvertUTF8toUTF16 typeString(mimeType);
     if (!nsContentUtils::IsJavascriptMIMEType(typeString)) {
       return NS_ERROR_FAILURE;
     }
 
-    channel->GetURI(getter_AddRefs(request->mBaseURL));
+    aChannel->GetURI(getter_AddRefs(request->mBaseURL));
 
     // Attempt to compile off main thread.
     rv = AttemptAsyncScriptCompile(request);
     if (NS_SUCCEEDED(rv)) {
       return rv;
     }
 
     // Otherwise compile it right away and start fetching descendents.
@@ -2839,25 +2834,63 @@ nsScriptLoader::MaybeRemovedDeferRequest
 //////////////////////////////////////////////////////////////
 
 nsScriptLoadHandler::nsScriptLoadHandler(nsScriptLoader *aScriptLoader,
                                          nsScriptLoadRequest *aRequest,
                                          mozilla::dom::SRICheckDataVerifier *aSRIDataVerifier)
   : mScriptLoader(aScriptLoader),
     mRequest(aRequest),
     mSRIDataVerifier(aSRIDataVerifier),
+    mChannelStatus(NS_OK),
     mSRIStatus(NS_OK),
+    mClassificationStatus(NS_ERROR_NOT_INITIALIZED),
     mDecoder(),
     mBuffer()
-{}
+{
+}
+
+nsresult
+nsScriptLoadHandler::Init()
+{
+  nsCOMPtr<nsIURIClassifier> uriClassifier =
+    do_GetService(NS_URICLASSIFIERSERVICE_CONTRACTID);
+  if (!uriClassifier) {
+    return NS_ERROR_FAILURE;
+  }
+
+  PrincipalOriginAttributes attrs;
+  nsIDocShell* docShell = nullptr;
+  if (auto doc = mScriptLoader->GetDocument()) {
+    docShell = doc->GetDocShell();
+  }
+  if (!docShell) {
+    return NS_ERROR_FAILURE;
+  }
+  attrs.InheritFromDocShellToDoc(nsDocShell::Cast(docShell)->GetOriginAttributes(), nullptr);
+  nsCOMPtr<nsIPrincipal> prin =
+    BasePrincipal::CreateCodebasePrincipal(mRequest->mURI, attrs);
+  NS_ENSURE_TRUE(prin, NS_ERROR_FAILURE);
+
+  bool expectCallback = false;
+  uriClassifier->Classify(prin, /* aTrackingProtectionEnabled = */ true,
+                          this, &expectCallback);
+  if (!expectCallback) {
+    // If we don't expect to receive a callback, set the classification status
+    // eagerly.
+    mClassificationStatus = NS_OK;
+  }
+  return NS_OK;
+}
 
 nsScriptLoadHandler::~nsScriptLoadHandler()
 {}
 
-NS_IMPL_ISUPPORTS(nsScriptLoadHandler, nsIIncrementalStreamLoaderObserver)
+NS_IMPL_ISUPPORTS(nsScriptLoadHandler,
+                  nsIIncrementalStreamLoaderObserver,
+                  nsIURIClassifierCallback)
 
 NS_IMETHODIMP
 nsScriptLoadHandler::OnIncrementalData(nsIIncrementalStreamLoader* aLoader,
                                        nsISupports* aContext,
                                        uint32_t aDataLength,
                                        const uint8_t* aData,
                                        uint32_t *aConsumedLength)
 {
@@ -3023,12 +3056,38 @@ nsScriptLoadHandler::OnStreamComplete(ns
                                               /* aEndOfStream = */ true);
 
     // If SRI is required for this load, appending new bytes to the hash.
     if (mSRIDataVerifier && NS_SUCCEEDED(mSRIStatus)) {
       mSRIStatus = mSRIDataVerifier->Update(aDataLength, aData);
     }
   }
 
-  // we have to mediate and use mRequest.
-  return mScriptLoader->OnStreamComplete(aLoader, mRequest, aStatus, mSRIStatus,
-                                         mBuffer, mSRIDataVerifier);
+  nsCOMPtr<nsIRequest> request;
+  aLoader->GetRequest(getter_AddRefs(request));
+  MOZ_ASSERT(request, "How can we not have a request here?!");
+  mChannel = do_QueryInterface(request);
+  mChannelStatus = aStatus;
+  return MaybeInvokeOnStreamComplete();
 }
+
+NS_IMETHODIMP
+nsScriptLoadHandler::OnClassifyComplete(nsresult aResult)
+{
+  MOZ_ASSERT(mClassificationStatus == NS_ERROR_NOT_INITIALIZED);
+  MOZ_ASSERT(!mRequest->mIsTracking);
+  mClassificationStatus = aResult;
+  mRequest->mIsTracking = mClassificationStatus == NS_ERROR_TRACKING_URI;
+  return MaybeInvokeOnStreamComplete();
+}
+
+nsresult
+nsScriptLoadHandler::MaybeInvokeOnStreamComplete()
+{
+  // Run the script loader's callback if both the load and classification have
+  // been finished.
+  if (mChannel && mClassificationStatus != NS_ERROR_NOT_INITIALIZED) {
+    // we have to mediate and use mRequest.
+    return mScriptLoader->OnStreamComplete(mChannel, mRequest, mChannelStatus,
+                                           mSRIStatus, mBuffer, mSRIDataVerifier);
+  }
+  return NS_OK;
+}
--- a/dom/base/nsScriptLoader.h
+++ b/dom/base/nsScriptLoader.h
@@ -16,16 +16,17 @@
 #include "nsIUnicodeDecoder.h"
 #include "nsIScriptElement.h"
 #include "nsCOMArray.h"
 #include "nsCycleCollectionParticipant.h"
 #include "nsTArray.h"
 #include "nsAutoPtr.h"
 #include "nsIDocument.h"
 #include "nsIIncrementalStreamLoader.h"
+#include "nsIURIClassifier.h"
 #include "nsURIHashKey.h"
 #include "mozilla/CORSMode.h"
 #include "mozilla/dom/SRIMetadata.h"
 #include "mozilla/dom/SRICheck.h"
 #include "mozilla/LinkedList.h"
 #include "mozilla/MozPromise.h"
 #include "mozilla/net/ReferrerPolicy.h"
 #include "mozilla/Vector.h"
@@ -78,16 +79,17 @@ public:
       mIsInline(true),
       mHasSourceMapURL(false),
       mIsDefer(false),
       mIsAsync(false),
       mIsNonAsyncScriptInserted(false),
       mIsXSLT(false),
       mIsCanceled(false),
       mWasCompiledOMT(false),
+      mIsTracking(false),
       mOffThreadToken(nullptr),
       mScriptTextBuf(nullptr),
       mScriptTextLength(0),
       mJSVersion(aVersion),
       mLineNo(1),
       mCORSMode(aCORSMode),
       mIntegrity(aIntegrity),
       mReferrerPolicy(mozilla::net::RP_Default)
@@ -127,16 +129,21 @@ public:
 
   virtual void SetReady();
 
   void** OffThreadTokenPtr()
   {
     return mOffThreadToken ?  &mOffThreadToken : nullptr;
   }
 
+  bool IsTracking() const
+  {
+    return mIsTracking;
+  }
+
   enum class Progress {
     Loading,
     Compiling,
     FetchingImports,
     Ready
   };
   bool IsReadyToRun() const {
     return mProgress == Progress::Ready;
@@ -160,16 +167,17 @@ public:
   bool mIsInline;         // Is the script inline or loaded?
   bool mHasSourceMapURL;  // Does the HTTP header have a source map url?
   bool mIsDefer;          // True if we live in mDeferRequests.
   bool mIsAsync;          // True if we live in mLoadingAsyncRequests or mLoadedAsyncRequests.
   bool mIsNonAsyncScriptInserted; // True if we live in mNonAsyncExternalScriptInsertedRequests
   bool mIsXSLT;           // True if we live in mXSLTRequests.
   bool mIsCanceled;       // True if we have been explicitly canceled.
   bool mWasCompiledOMT;   // True if the script has been compiled off main thread.
+  bool mIsTracking;       // True if the script comes from a source on our tracking protection list.
   void* mOffThreadToken;  // Off-thread parsing token.
   nsString mSourceMapURL; // Holds source map url for loaded scripts
   char16_t* mScriptTextBuf; // Holds script text for non-inline scripts. Don't
   size_t mScriptTextLength; // use nsString so we can give ownership to jsapi.
   uint32_t mJSVersion;
   nsCOMPtr<nsIURI> mURI;
   nsCOMPtr<nsIPrincipal> mOriginPrincipal;
   nsAutoCString mURL;     // Keep the URI's filename alive during off thread parsing.
@@ -265,16 +273,24 @@ public:
    * be dropped.
    */
   void DropDocumentReference()
   {
     mDocument = nullptr;
   }
 
   /**
+   * Returns the document reference.
+   */
+  nsIDocument* GetDocument() const
+  {
+    return mDocument;
+  }
+
+  /**
    * Add an observer for all scripts loaded through this loader.
    *
    * @param aObserver observer for all script processing.
    */
   nsresult AddObserver(nsIScriptLoaderObserver* aObserver)
   {
     return mObservers.AppendObject(aObserver) ? NS_OK :
       NS_ERROR_OUT_OF_MEMORY;
@@ -393,17 +409,17 @@ public:
                                  nsIDocument* aDocument,
                                  char16_t*& aBufOut, size_t& aLengthOut);
 
   /**
    * Handle the completion of a stream.  This is called by the
    * nsScriptLoadHandler object which observes the IncrementalStreamLoader
    * loading the script.
    */
-  nsresult OnStreamComplete(nsIIncrementalStreamLoader* aLoader,
+  nsresult OnStreamComplete(nsIChannel* aChannel,
                             nsISupports* aContext,
                             nsresult aChannelStatus,
                             nsresult aSRIStatus,
                             mozilla::Vector<char16_t> &aString,
                             mozilla::dom::SRICheckDataVerifier* aSRIDataVerifier);
 
   /**
    * Processes any pending requests that are ready for processing.
@@ -556,17 +572,17 @@ private:
   already_AddRefed<nsIScriptGlobalObject> GetScriptGlobalObject();
   nsresult FillCompileOptionsForRequest(const mozilla::dom::AutoJSAPI& jsapi,
                                         nsScriptLoadRequest* aRequest,
                                         JS::Handle<JSObject*> aScopeChain,
                                         JS::CompileOptions* aOptions);
 
   uint32_t NumberOfProcessors();
   nsresult PrepareLoadedRequest(nsScriptLoadRequest* aRequest,
-                                nsIIncrementalStreamLoader* aLoader,
+                                nsIChannel* aChannel,
                                 nsresult aStatus,
                                 mozilla::Vector<char16_t> &aString);
 
   void AddDeferRequest(nsScriptLoadRequest* aRequest);
   bool MaybeRemovedDeferRequests();
 
   void MaybeMoveToLoadedList(nsScriptLoadRequest* aRequest);
 
@@ -642,27 +658,32 @@ private:
 
   // Module map
   nsRefPtrHashtable<nsURIHashKey, mozilla::GenericPromise::Private> mFetchingModules;
   nsRefPtrHashtable<nsURIHashKey, nsModuleScript> mFetchedModules;
 
   nsCOMPtr<nsIConsoleReportCollector> mReporter;
 };
 
-class nsScriptLoadHandler final : public nsIIncrementalStreamLoaderObserver
+class nsScriptLoadHandler final : public nsIIncrementalStreamLoaderObserver,
+                                  private nsIURIClassifierCallback
 {
 public:
   explicit nsScriptLoadHandler(nsScriptLoader* aScriptLoader,
                                nsScriptLoadRequest *aRequest,
                                mozilla::dom::SRICheckDataVerifier *aSRIDataVerifier);
 
+  nsresult Init();
+
   NS_DECL_ISUPPORTS
   NS_DECL_NSIINCREMENTALSTREAMLOADEROBSERVER
 
 private:
+  NS_DECL_NSIURICLASSIFIERCALLBACK
+
   virtual ~nsScriptLoadHandler();
 
   /*
    * Try to decode some raw data.
    */
   nsresult TryDecodeRawData(const uint8_t* aData, uint32_t aDataLength,
                             bool aEndOfStream);
 
@@ -670,33 +691,48 @@ private:
    * Discover the charset by looking at the stream data, the script
    * tag, and other indicators.  Returns true if charset has been
    * discovered.
    */
   bool EnsureDecoder(nsIIncrementalStreamLoader *aLoader,
                      const uint8_t* aData, uint32_t aDataLength,
                      bool aEndOfStream);
 
+  /*
+   * Call the script loader OnClassifyComplete if both the load and the
+   * classification have finished.
+   */
+  nsresult MaybeInvokeOnStreamComplete();
+
   // ScriptLoader which will handle the parsed script.
   RefPtr<nsScriptLoader>        mScriptLoader;
 
   // The nsScriptLoadRequest for this load.
   RefPtr<nsScriptLoadRequest>   mRequest;
 
   // SRI data verifier.
   nsAutoPtr<mozilla::dom::SRICheckDataVerifier> mSRIDataVerifier;
 
+  // Status of the channel load.
+  nsresult                      mChannelStatus;
+
   // Status of SRI data operations.
   nsresult                      mSRIStatus;
 
+  // status of the classification of the script URI.
+  nsresult                      mClassificationStatus;
+
   // Unicode decoder for charset.
   nsCOMPtr<nsIUnicodeDecoder>   mDecoder;
 
   // Accumulated decoded char buffer.
   mozilla::Vector<char16_t>     mBuffer;
+
+  // The channel we loaded the script from.
+  nsCOMPtr<nsIChannel>          mChannel;
 };
 
 class nsAutoScriptLoaderDisabler
 {
 public:
   explicit nsAutoScriptLoaderDisabler(nsIDocument* aDoc)
   {
     mLoader = aDoc->ScriptLoader();