forked from mirrors/gecko-dev
		
	
		
			
				
	
	
		
			1830 lines
		
	
	
	
		
			64 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			1830 lines
		
	
	
	
		
			64 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 | ||
| /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 | ||
| /* This Source Code Form is subject to the terms of the Mozilla Public
 | ||
|  * License, v. 2.0. If a copy of the MPL was not distributed with this
 | ||
|  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 | ||
| 
 | ||
| #include "ScriptLoader.h"
 | ||
| 
 | ||
| #include <algorithm>
 | ||
| #include <type_traits>
 | ||
| 
 | ||
| #include "nsIChannel.h"
 | ||
| #include "nsIContentPolicy.h"
 | ||
| #include "nsIContentSecurityPolicy.h"
 | ||
| #include "nsICookieJarSettings.h"
 | ||
| #include "nsIDocShell.h"
 | ||
| #include "nsIHttpChannel.h"
 | ||
| #include "nsIHttpChannelInternal.h"
 | ||
| #include "nsIIOService.h"
 | ||
| #include "nsIOService.h"
 | ||
| #include "nsIPrincipal.h"
 | ||
| #include "nsIProtocolHandler.h"
 | ||
| #include "nsIScriptError.h"
 | ||
| #include "nsIScriptSecurityManager.h"
 | ||
| #include "nsIStreamListenerTee.h"
 | ||
| #include "nsIThreadRetargetableRequest.h"
 | ||
| #include "nsIURI.h"
 | ||
| #include "nsIXPConnect.h"
 | ||
| 
 | ||
| #include "jsapi.h"
 | ||
| #include "jsfriendapi.h"
 | ||
| #include "js/CompilationAndEvaluation.h"
 | ||
| #include "js/Exception.h"
 | ||
| #include "js/SourceText.h"
 | ||
| #include "js/TypeDecls.h"
 | ||
| #include "nsError.h"
 | ||
| #include "nsComponentManagerUtils.h"
 | ||
| #include "nsContentSecurityManager.h"
 | ||
| #include "nsContentPolicyUtils.h"
 | ||
| #include "nsContentUtils.h"
 | ||
| #include "nsDocShellCID.h"
 | ||
| #include "nsJSEnvironment.h"
 | ||
| #include "nsNetUtil.h"
 | ||
| #include "nsIPipe.h"
 | ||
| #include "nsIOutputStream.h"
 | ||
| #include "nsPrintfCString.h"
 | ||
| #include "nsString.h"
 | ||
| #include "nsTArray.h"
 | ||
| #include "nsThreadUtils.h"
 | ||
| #include "nsXPCOM.h"
 | ||
| #include "xpcpublic.h"
 | ||
| 
 | ||
| #include "mozilla/AntiTrackingUtils.h"
 | ||
| #include "mozilla/ArrayAlgorithm.h"
 | ||
| #include "mozilla/Assertions.h"
 | ||
| #include "mozilla/Encoding.h"
 | ||
| #include "mozilla/LoadContext.h"
 | ||
| #include "mozilla/Maybe.h"
 | ||
| #include "mozilla/ipc/BackgroundUtils.h"
 | ||
| #include "mozilla/dom/ClientChannelHelper.h"
 | ||
| #include "mozilla/dom/ClientInfo.h"
 | ||
| #include "mozilla/dom/Exceptions.h"
 | ||
| #include "mozilla/dom/nsCSPService.h"
 | ||
| #include "mozilla/dom/nsCSPUtils.h"
 | ||
| #include "mozilla/dom/PerformanceStorage.h"
 | ||
| #include "mozilla/dom/Response.h"
 | ||
| #include "mozilla/dom/ReferrerInfo.h"
 | ||
| #include "mozilla/dom/ScriptSettings.h"
 | ||
| #include "mozilla/dom/SerializedStackHolder.h"
 | ||
| #include "mozilla/dom/workerinternals/CacheLoadHandler.h"
 | ||
| #include "mozilla/dom/workerinternals/NetworkLoadHandler.h"
 | ||
| #include "mozilla/dom/workerinternals/ScriptResponseHeaderProcessor.h"
 | ||
| #include "mozilla/Result.h"
 | ||
| #include "mozilla/ResultExtensions.h"
 | ||
| #include "mozilla/StaticPrefs_browser.h"
 | ||
| #include "mozilla/UniquePtr.h"
 | ||
| #include "WorkerRunnable.h"
 | ||
| #include "WorkerScope.h"
 | ||
| 
 | ||
| #define MAX_CONCURRENT_SCRIPTS 1000
 | ||
| 
 | ||
| using JS::loader::ScriptKind;
 | ||
| using JS::loader::ScriptLoadRequest;
 | ||
| using mozilla::ipc::PrincipalInfo;
 | ||
| 
 | ||
| namespace mozilla::dom::workerinternals {
 | ||
| namespace {
 | ||
| 
 | ||
| nsresult ConstructURI(const nsAString& aScriptURL, nsIURI* baseURI,
 | ||
|                       const mozilla::Encoding* aDocumentEncoding,
 | ||
|                       nsIURI** aResult) {
 | ||
|   nsresult rv;
 | ||
|   // Only top level workers' main script use the document charset for the
 | ||
|   // script uri encoding. Otherwise, default encoding (UTF-8) is applied.
 | ||
|   if (aDocumentEncoding) {
 | ||
|     nsAutoCString charset;
 | ||
|     aDocumentEncoding->Name(charset);
 | ||
|     rv = NS_NewURI(aResult, aScriptURL, charset.get(), baseURI);
 | ||
|   } else {
 | ||
|     rv = NS_NewURI(aResult, aScriptURL, nullptr, baseURI);
 | ||
|   }
 | ||
| 
 | ||
|   if (NS_FAILED(rv)) {
 | ||
|     return NS_ERROR_DOM_SYNTAX_ERR;
 | ||
|   }
 | ||
|   return NS_OK;
 | ||
| }
 | ||
| 
 | ||
| nsresult ChannelFromScriptURL(
 | ||
|     nsIPrincipal* principal, Document* parentDoc, WorkerPrivate* aWorkerPrivate,
 | ||
|     nsILoadGroup* loadGroup, nsIIOService* ios,
 | ||
|     nsIScriptSecurityManager* secMan, nsIURI* aScriptURL,
 | ||
|     const Maybe<ClientInfo>& aClientInfo,
 | ||
|     const Maybe<ServiceWorkerDescriptor>& aController, bool aIsMainScript,
 | ||
|     WorkerScriptType aWorkerScriptType, nsContentPolicyType aContentPolicyType,
 | ||
|     nsLoadFlags aLoadFlags, uint32_t aSecFlags,
 | ||
|     nsICookieJarSettings* aCookieJarSettings, nsIReferrerInfo* aReferrerInfo,
 | ||
|     nsIChannel** aChannel) {
 | ||
|   AssertIsOnMainThread();
 | ||
| 
 | ||
|   nsresult rv;
 | ||
|   nsCOMPtr<nsIURI> uri = aScriptURL;
 | ||
| 
 | ||
|   // Only use the document when its principal matches the principal of the
 | ||
|   // current request. This means scripts fetched using the Workers' own
 | ||
|   // principal won't inherit properties of the document, in particular the CSP.
 | ||
|   if (parentDoc && parentDoc->NodePrincipal() != principal) {
 | ||
|     parentDoc = nullptr;
 | ||
|   }
 | ||
| 
 | ||
|   // The main service worker script should never be loaded over the network
 | ||
|   // in this path.  It should always be offlined by ServiceWorkerScriptCache.
 | ||
|   // We assert here since this error should also be caught by the runtime
 | ||
|   // check in CacheLoadHandler.
 | ||
|   //
 | ||
|   // Note, if we ever allow service worker scripts to be loaded from network
 | ||
|   // here we need to configure the channel properly.  For example, it must
 | ||
|   // not allow redirects.
 | ||
|   MOZ_DIAGNOSTIC_ASSERT(aContentPolicyType !=
 | ||
|                         nsIContentPolicy::TYPE_INTERNAL_SERVICE_WORKER);
 | ||
| 
 | ||
|   nsCOMPtr<nsIChannel> channel;
 | ||
|   if (parentDoc) {
 | ||
|     // This is the path for top level dedicated worker scripts with a document
 | ||
|     rv = NS_NewChannel(getter_AddRefs(channel), uri, parentDoc, aSecFlags,
 | ||
|                        aContentPolicyType,
 | ||
|                        nullptr,  // aPerformanceStorage
 | ||
|                        loadGroup,
 | ||
|                        nullptr,  // aCallbacks
 | ||
|                        aLoadFlags, ios);
 | ||
|     NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_SECURITY_ERR);
 | ||
|   } else {
 | ||
|     // This branch is used in the following cases:
 | ||
|     //    * Shared and ServiceWorkers (who do not have a doc)
 | ||
|     //    * Static Module Imports
 | ||
|     //    * ImportScripts
 | ||
| 
 | ||
|     // We must have a loadGroup with a load context for the principal to
 | ||
|     // traverse the channel correctly.
 | ||
| 
 | ||
|     MOZ_ASSERT(loadGroup);
 | ||
|     MOZ_ASSERT(NS_LoadGroupMatchesPrincipal(loadGroup, principal));
 | ||
| 
 | ||
|     RefPtr<PerformanceStorage> performanceStorage;
 | ||
|     nsCOMPtr<nsICSPEventListener> cspEventListener;
 | ||
|     if (aWorkerPrivate && !aIsMainScript) {
 | ||
|       performanceStorage = aWorkerPrivate->GetPerformanceStorage();
 | ||
|       cspEventListener = aWorkerPrivate->CSPEventListener();
 | ||
|     }
 | ||
| 
 | ||
|     if (aClientInfo.isSome()) {
 | ||
|       // If we have an existing clientInfo (true for all modules and
 | ||
|       // importScripts), we will use this branch
 | ||
|       rv = NS_NewChannel(getter_AddRefs(channel), uri, principal,
 | ||
|                          aClientInfo.ref(), aController, aSecFlags,
 | ||
|                          aContentPolicyType, aCookieJarSettings,
 | ||
|                          performanceStorage, loadGroup, nullptr,  // aCallbacks
 | ||
|                          aLoadFlags, ios);
 | ||
|     } else {
 | ||
|       rv = NS_NewChannel(getter_AddRefs(channel), uri, principal, aSecFlags,
 | ||
|                          aContentPolicyType, aCookieJarSettings,
 | ||
|                          performanceStorage, loadGroup, nullptr,  // aCallbacks
 | ||
|                          aLoadFlags, ios);
 | ||
|     }
 | ||
| 
 | ||
|     NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_SECURITY_ERR);
 | ||
| 
 | ||
|     if (cspEventListener) {
 | ||
|       nsCOMPtr<nsILoadInfo> loadInfo = channel->LoadInfo();
 | ||
|       rv = loadInfo->SetCspEventListener(cspEventListener);
 | ||
|       NS_ENSURE_SUCCESS(rv, rv);
 | ||
|     }
 | ||
|   }
 | ||
| 
 | ||
|   if (aReferrerInfo) {
 | ||
|     nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(channel);
 | ||
|     if (httpChannel) {
 | ||
|       rv = httpChannel->SetReferrerInfo(aReferrerInfo);
 | ||
|       if (NS_WARN_IF(NS_FAILED(rv))) {
 | ||
|         return rv;
 | ||
|       }
 | ||
|     }
 | ||
|   }
 | ||
| 
 | ||
|   channel.forget(aChannel);
 | ||
|   return rv;
 | ||
| }
 | ||
| 
 | ||
| void LoadAllScripts(WorkerPrivate* aWorkerPrivate,
 | ||
|                     UniquePtr<SerializedStackHolder> aOriginStack,
 | ||
|                     const nsTArray<nsString>& aScriptURLs, bool aIsMainScript,
 | ||
|                     WorkerScriptType aWorkerScriptType, ErrorResult& aRv,
 | ||
|                     const mozilla::Encoding* aDocumentEncoding = nullptr) {
 | ||
|   aWorkerPrivate->AssertIsOnWorkerThread();
 | ||
|   NS_ASSERTION(!aScriptURLs.IsEmpty(), "Bad arguments!");
 | ||
| 
 | ||
|   AutoSyncLoopHolder syncLoop(aWorkerPrivate, Canceling);
 | ||
|   nsCOMPtr<nsISerialEventTarget> syncLoopTarget =
 | ||
|       syncLoop.GetSerialEventTarget();
 | ||
|   if (!syncLoopTarget) {
 | ||
|     aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
 | ||
|     return;
 | ||
|   }
 | ||
| 
 | ||
|   RefPtr<loader::WorkerScriptLoader> loader =
 | ||
|       new loader::WorkerScriptLoader(aWorkerPrivate, std::move(aOriginStack),
 | ||
|                                      syncLoopTarget, aWorkerScriptType, aRv);
 | ||
| 
 | ||
|   if (NS_WARN_IF(aRv.Failed())) {
 | ||
|     return;
 | ||
|   }
 | ||
| 
 | ||
|   bool ok = loader->CreateScriptRequests(aScriptURLs, aDocumentEncoding,
 | ||
|                                          aIsMainScript);
 | ||
| 
 | ||
|   if (!ok) {
 | ||
|     return;
 | ||
|   }
 | ||
|   // Bug 1817259 - For now, we force loading the debugger script as Classic,
 | ||
|   // even if the debugged worker is a Module.
 | ||
|   if (aWorkerPrivate->WorkerType() == WorkerType::Module &&
 | ||
|       aWorkerScriptType != DebuggerScript) {
 | ||
|     if (!StaticPrefs::dom_workers_modules_enabled()) {
 | ||
|       aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
 | ||
|       return;
 | ||
|     }
 | ||
|     MOZ_ASSERT(aIsMainScript);
 | ||
|     // Module Load
 | ||
|     RefPtr<JS::loader::ScriptLoadRequest> mainScript = loader->GetMainScript();
 | ||
|     if (mainScript && mainScript->IsModuleRequest()) {
 | ||
|       if (NS_FAILED(mainScript->AsModuleRequest()->StartModuleLoad())) {
 | ||
|         return;
 | ||
|       }
 | ||
|       syncLoop.Run();
 | ||
|       return;
 | ||
|     }
 | ||
|   }
 | ||
| 
 | ||
|   if (loader->DispatchLoadScripts()) {
 | ||
|     syncLoop.Run();
 | ||
|   }
 | ||
| }
 | ||
| 
 | ||
| class ChannelGetterRunnable final : public WorkerMainThreadRunnable {
 | ||
|   const nsAString& mScriptURL;
 | ||
|   const WorkerType& mWorkerType;
 | ||
|   const RequestCredentials& mCredentials;
 | ||
|   const ClientInfo mClientInfo;
 | ||
|   WorkerLoadInfo& mLoadInfo;
 | ||
|   nsresult mResult;
 | ||
| 
 | ||
|  public:
 | ||
|   ChannelGetterRunnable(WorkerPrivate* aParentWorker,
 | ||
|                         const nsAString& aScriptURL,
 | ||
|                         const WorkerType& aWorkerType,
 | ||
|                         const RequestCredentials& aCredentials,
 | ||
|                         WorkerLoadInfo& aLoadInfo)
 | ||
|       : WorkerMainThreadRunnable(aParentWorker,
 | ||
|                                  "ScriptLoader :: ChannelGetter"_ns),
 | ||
|         mScriptURL(aScriptURL)
 | ||
|         // ClientInfo should always be present since this should not be called
 | ||
|         // if parent's status is greater than Running.
 | ||
|         ,
 | ||
|         mWorkerType(aWorkerType),
 | ||
|         mCredentials(aCredentials),
 | ||
|         mClientInfo(aParentWorker->GlobalScope()->GetClientInfo().ref()),
 | ||
|         mLoadInfo(aLoadInfo),
 | ||
|         mResult(NS_ERROR_FAILURE) {
 | ||
|     MOZ_ASSERT(aParentWorker);
 | ||
|     aParentWorker->AssertIsOnWorkerThread();
 | ||
|   }
 | ||
| 
 | ||
|   virtual bool MainThreadRun() override {
 | ||
|     AssertIsOnMainThread();
 | ||
| 
 | ||
|     // Initialize the WorkerLoadInfo principal to our triggering principal
 | ||
|     // before doing anything else.  Normally we do this in the WorkerPrivate
 | ||
|     // Constructor, but we can't do so off the main thread when creating
 | ||
|     // a nested worker.  So do it here instead.
 | ||
|     mLoadInfo.mLoadingPrincipal = mWorkerPrivate->GetPrincipal();
 | ||
|     MOZ_DIAGNOSTIC_ASSERT(mLoadInfo.mLoadingPrincipal);
 | ||
| 
 | ||
|     mLoadInfo.mPrincipal = mLoadInfo.mLoadingPrincipal;
 | ||
| 
 | ||
|     // Figure out our base URI.
 | ||
|     nsCOMPtr<nsIURI> baseURI = mWorkerPrivate->GetBaseURI();
 | ||
|     MOZ_ASSERT(baseURI);
 | ||
| 
 | ||
|     // May be null.
 | ||
|     nsCOMPtr<Document> parentDoc = mWorkerPrivate->GetDocument();
 | ||
| 
 | ||
|     mLoadInfo.mLoadGroup = mWorkerPrivate->GetLoadGroup();
 | ||
|     mLoadInfo.mCookieJarSettings = mWorkerPrivate->CookieJarSettings();
 | ||
| 
 | ||
|     // Nested workers use default uri encoding.
 | ||
|     nsCOMPtr<nsIURI> url;
 | ||
|     mResult = ConstructURI(mScriptURL, baseURI, nullptr, getter_AddRefs(url));
 | ||
|     NS_ENSURE_SUCCESS(mResult, true);
 | ||
| 
 | ||
|     Maybe<ClientInfo> clientInfo;
 | ||
|     clientInfo.emplace(mClientInfo);
 | ||
| 
 | ||
|     nsCOMPtr<nsIChannel> channel;
 | ||
|     nsCOMPtr<nsIReferrerInfo> referrerInfo =
 | ||
|         ReferrerInfo::CreateForFetch(mLoadInfo.mLoadingPrincipal, nullptr);
 | ||
|     mLoadInfo.mReferrerInfo =
 | ||
|         static_cast<ReferrerInfo*>(referrerInfo.get())
 | ||
|             ->CloneWithNewPolicy(mWorkerPrivate->GetReferrerPolicy());
 | ||
| 
 | ||
|     mResult = workerinternals::ChannelFromScriptURLMainThread(
 | ||
|         mLoadInfo.mLoadingPrincipal, parentDoc, mLoadInfo.mLoadGroup, url,
 | ||
|         mWorkerType, mCredentials, clientInfo,
 | ||
|         // Nested workers are always dedicated.
 | ||
|         nsIContentPolicy::TYPE_INTERNAL_WORKER, mLoadInfo.mCookieJarSettings,
 | ||
|         mLoadInfo.mReferrerInfo, getter_AddRefs(channel));
 | ||
|     NS_ENSURE_SUCCESS(mResult, true);
 | ||
| 
 | ||
|     mResult = mLoadInfo.SetPrincipalsAndCSPFromChannel(channel);
 | ||
|     NS_ENSURE_SUCCESS(mResult, true);
 | ||
| 
 | ||
|     mLoadInfo.mChannel = std::move(channel);
 | ||
|     return true;
 | ||
|   }
 | ||
| 
 | ||
|   nsresult GetResult() const { return mResult; }
 | ||
| 
 | ||
|  private:
 | ||
|   virtual ~ChannelGetterRunnable() = default;
 | ||
| };
 | ||
| 
 | ||
| nsresult GetCommonSecFlags(bool aIsMainScript, nsIURI* uri,
 | ||
|                            nsIPrincipal* principal,
 | ||
|                            WorkerScriptType aWorkerScriptType,
 | ||
|                            uint32_t& secFlags) {
 | ||
|   bool inheritAttrs = nsContentUtils::ChannelShouldInheritPrincipal(
 | ||
|       principal, uri, true /* aInheritForAboutBlank */,
 | ||
|       false /* aForceInherit */);
 | ||
| 
 | ||
|   bool isData = uri->SchemeIs("data");
 | ||
|   if (inheritAttrs && !isData) {
 | ||
|     secFlags |= nsILoadInfo::SEC_FORCE_INHERIT_PRINCIPAL;
 | ||
|   }
 | ||
| 
 | ||
|   if (aWorkerScriptType == DebuggerScript) {
 | ||
|     // A DebuggerScript needs to be a local resource like chrome: or resource:
 | ||
|     bool isUIResource = false;
 | ||
|     nsresult rv = NS_URIChainHasFlags(
 | ||
|         uri, nsIProtocolHandler::URI_IS_UI_RESOURCE, &isUIResource);
 | ||
|     if (NS_WARN_IF(NS_FAILED(rv))) {
 | ||
|       return rv;
 | ||
|     }
 | ||
| 
 | ||
|     if (!isUIResource) {
 | ||
|       return NS_ERROR_DOM_SECURITY_ERR;
 | ||
|     }
 | ||
| 
 | ||
|     secFlags |= nsILoadInfo::SEC_ALLOW_CHROME;
 | ||
|   }
 | ||
| 
 | ||
|   // Note: this is for backwards compatibility and goes against spec.
 | ||
|   // We should find a better solution.
 | ||
|   if (aIsMainScript && isData) {
 | ||
|     secFlags = nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL;
 | ||
|   }
 | ||
| 
 | ||
|   return NS_OK;
 | ||
| }
 | ||
| 
 | ||
| nsresult GetModuleSecFlags(bool aIsTopLevel, nsIPrincipal* principal,
 | ||
|                            WorkerScriptType aWorkerScriptType, nsIURI* aURI,
 | ||
|                            RequestCredentials aCredentials,
 | ||
|                            uint32_t& secFlags) {
 | ||
|   // Implements "To fetch a single module script,"
 | ||
|   // Step 9. If destination is "worker", "sharedworker", or "serviceworker",
 | ||
|   //         and the top-level module fetch flag is set, then set request's
 | ||
|   //         mode to "same-origin".
 | ||
|   secFlags = aIsTopLevel
 | ||
|                  ? nsILoadInfo::SEC_REQUIRE_SAME_ORIGIN_DATA_IS_BLOCKED
 | ||
|                  : nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_INHERITS_SEC_CONTEXT;
 | ||
| 
 | ||
|   // Step 8. Let request be a new request whose [...] mode is "cors" [...]
 | ||
|   // This implements the same Cookie settings as  nsContentSecurityManager's
 | ||
|   // ComputeSecurityFlags. The main difference is the line above, Step 9,
 | ||
|   // setting to same origin.
 | ||
|   if (aCredentials == RequestCredentials::Include) {
 | ||
|     secFlags |= nsILoadInfo::nsILoadInfo::SEC_COOKIES_INCLUDE;
 | ||
|   } else if (aCredentials == RequestCredentials::Same_origin) {
 | ||
|     secFlags |= nsILoadInfo::nsILoadInfo::SEC_COOKIES_SAME_ORIGIN;
 | ||
|   } else if (aCredentials == RequestCredentials::Omit) {
 | ||
|     secFlags |= nsILoadInfo::nsILoadInfo::SEC_COOKIES_OMIT;
 | ||
|   }
 | ||
| 
 | ||
|   return GetCommonSecFlags(aIsTopLevel, aURI, principal, aWorkerScriptType,
 | ||
|                            secFlags);
 | ||
| }
 | ||
| 
 | ||
| nsresult GetClassicSecFlags(bool aIsMainScript, nsIURI* uri,
 | ||
|                             nsIPrincipal* principal,
 | ||
|                             WorkerScriptType aWorkerScriptType,
 | ||
|                             uint32_t& secFlags) {
 | ||
|   secFlags = aIsMainScript
 | ||
|                  ? nsILoadInfo::SEC_REQUIRE_SAME_ORIGIN_DATA_IS_BLOCKED
 | ||
|                  : nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_INHERITS_SEC_CONTEXT;
 | ||
| 
 | ||
|   return GetCommonSecFlags(aIsMainScript, uri, principal, aWorkerScriptType,
 | ||
|                            secFlags);
 | ||
| }
 | ||
| 
 | ||
| }  //  anonymous namespace
 | ||
| 
 | ||
| namespace loader {
 | ||
| 
 | ||
| class ScriptExecutorRunnable final : public MainThreadWorkerSyncRunnable {
 | ||
|   RefPtr<WorkerScriptLoader> mScriptLoader;
 | ||
|   const Span<RefPtr<ThreadSafeRequestHandle>> mLoadedRequests;
 | ||
| 
 | ||
|  public:
 | ||
|   ScriptExecutorRunnable(WorkerScriptLoader* aScriptLoader,
 | ||
|                          WorkerPrivate* aWorkerPrivate,
 | ||
|                          nsISerialEventTarget* aSyncLoopTarget,
 | ||
|                          Span<RefPtr<ThreadSafeRequestHandle>> aLoadedRequests);
 | ||
| 
 | ||
|  private:
 | ||
|   ~ScriptExecutorRunnable() = default;
 | ||
| 
 | ||
|   virtual bool IsDebuggerRunnable() const override;
 | ||
| 
 | ||
|   virtual bool PreRun(WorkerPrivate* aWorkerPrivate) override;
 | ||
| 
 | ||
|   bool ProcessModuleScript(JSContext* aCx, WorkerPrivate* aWorkerPrivate);
 | ||
| 
 | ||
|   bool ProcessClassicScripts(JSContext* aCx, WorkerPrivate* aWorkerPrivate);
 | ||
| 
 | ||
|   virtual bool WorkerRun(JSContext* aCx,
 | ||
|                          WorkerPrivate* aWorkerPrivate) override;
 | ||
| 
 | ||
|   nsresult Cancel() override;
 | ||
| };
 | ||
| 
 | ||
| template <typename Unit>
 | ||
| static bool EvaluateSourceBuffer(JSContext* aCx,
 | ||
|                                  const JS::CompileOptions& aOptions,
 | ||
|                                  JS::loader::ClassicScript* aClassicScript,
 | ||
|                                  JS::SourceText<Unit>& aSourceBuffer) {
 | ||
|   static_assert(std::is_same<Unit, char16_t>::value ||
 | ||
|                     std::is_same<Unit, Utf8Unit>::value,
 | ||
|                 "inferred units must be UTF-8 or UTF-16");
 | ||
| 
 | ||
|   JS::Rooted<JSScript*> script(aCx, JS::Compile(aCx, aOptions, aSourceBuffer));
 | ||
| 
 | ||
|   if (!script) {
 | ||
|     return false;
 | ||
|   }
 | ||
| 
 | ||
|   if (aClassicScript) {
 | ||
|     aClassicScript->AssociateWithScript(script);
 | ||
|   }
 | ||
| 
 | ||
|   JS::Rooted<JS::Value> unused(aCx);
 | ||
|   return JS_ExecuteScript(aCx, script, &unused);
 | ||
| }
 | ||
| 
 | ||
| WorkerScriptLoader::WorkerScriptLoader(
 | ||
|     WorkerPrivate* aWorkerPrivate,
 | ||
|     UniquePtr<SerializedStackHolder> aOriginStack,
 | ||
|     nsISerialEventTarget* aSyncLoopTarget, WorkerScriptType aWorkerScriptType,
 | ||
|     ErrorResult& aRv)
 | ||
|     : mOriginStack(std::move(aOriginStack)),
 | ||
|       mSyncLoopTarget(aSyncLoopTarget),
 | ||
|       mWorkerScriptType(aWorkerScriptType),
 | ||
|       mRv(aRv),
 | ||
|       mLoadingModuleRequestCount(0),
 | ||
|       mCleanedUp(false),
 | ||
|       mCleanUpLock("cleanUpLock") {
 | ||
|   aWorkerPrivate->AssertIsOnWorkerThread();
 | ||
| 
 | ||
|   RefPtr<StrongWorkerRef> workerRef =
 | ||
|       StrongWorkerRef::Create(aWorkerPrivate, "ScriptLoader");
 | ||
| 
 | ||
|   if (workerRef) {
 | ||
|     mWorkerRef = new ThreadSafeWorkerRef(workerRef);
 | ||
|   } else {
 | ||
|     mRv.Throw(NS_ERROR_FAILURE);
 | ||
|     return;
 | ||
|   }
 | ||
| 
 | ||
|   nsIGlobalObject* global = GetGlobal();
 | ||
|   mController = global->GetController();
 | ||
| 
 | ||
|   if (!StaticPrefs::dom_workers_modules_enabled()) {
 | ||
|     return;
 | ||
|   }
 | ||
| 
 | ||
|   // Set up the module loader, if it has not been initialzied yet.
 | ||
|   if (!aWorkerPrivate->IsServiceWorker()) {
 | ||
|     InitModuleLoader();
 | ||
|   }
 | ||
| }
 | ||
| 
 | ||
| ScriptLoadRequest* WorkerScriptLoader::GetMainScript() {
 | ||
|   mWorkerRef->Private()->AssertIsOnWorkerThread();
 | ||
|   ScriptLoadRequest* request = mLoadingRequests.getFirst();
 | ||
|   if (request->GetWorkerLoadContext()->IsTopLevel()) {
 | ||
|     return request;
 | ||
|   }
 | ||
|   return nullptr;
 | ||
| }
 | ||
| 
 | ||
| void WorkerScriptLoader::InitModuleLoader() {
 | ||
|   mWorkerRef->Private()->AssertIsOnWorkerThread();
 | ||
|   if (GetGlobal()->GetModuleLoader(nullptr)) {
 | ||
|     return;
 | ||
|   }
 | ||
|   RefPtr<WorkerModuleLoader> moduleLoader =
 | ||
|       new WorkerModuleLoader(this, GetGlobal(), mSyncLoopTarget.get());
 | ||
|   if (mWorkerScriptType == WorkerScript) {
 | ||
|     mWorkerRef->Private()->GlobalScope()->InitModuleLoader(moduleLoader);
 | ||
|     return;
 | ||
|   }
 | ||
|   mWorkerRef->Private()->DebuggerGlobalScope()->InitModuleLoader(moduleLoader);
 | ||
| }
 | ||
| 
 | ||
| bool WorkerScriptLoader::CreateScriptRequests(
 | ||
|     const nsTArray<nsString>& aScriptURLs,
 | ||
|     const mozilla::Encoding* aDocumentEncoding, bool aIsMainScript) {
 | ||
|   mWorkerRef->Private()->AssertIsOnWorkerThread();
 | ||
|   // If a worker has been loaded as a module worker, ImportScripts calls are
 | ||
|   // disallowed -- then the operation is invalid.
 | ||
|   //
 | ||
|   // 10.3.1 Importing scripts and libraries.
 | ||
|   // Step 1. If worker global scope's type is "module", throw a TypeError
 | ||
|   //         exception.
 | ||
|   //
 | ||
|   // Also, for now, the debugger script is always loaded as Classic,
 | ||
|   // even if the debugged worker is a Module. We still want to allow
 | ||
|   // it to use importScripts.
 | ||
|   if (mWorkerRef->Private()->WorkerType() == WorkerType::Module &&
 | ||
|       !aIsMainScript && !IsDebuggerScript()) {
 | ||
|     // This should only run for non-main scripts, as only these are
 | ||
|     // importScripts
 | ||
|     mRv.ThrowTypeError(
 | ||
|         "Using `ImportScripts` inside a Module Worker is "
 | ||
|         "disallowed.");
 | ||
|     return false;
 | ||
|   }
 | ||
|   for (const nsString& scriptURL : aScriptURLs) {
 | ||
|     RefPtr<ScriptLoadRequest> request =
 | ||
|         CreateScriptLoadRequest(scriptURL, aDocumentEncoding, aIsMainScript);
 | ||
|     if (!request) {
 | ||
|       return false;
 | ||
|     }
 | ||
|     mLoadingRequests.AppendElement(request);
 | ||
|   }
 | ||
| 
 | ||
|   return true;
 | ||
| }
 | ||
| 
 | ||
| nsTArray<RefPtr<ThreadSafeRequestHandle>> WorkerScriptLoader::GetLoadingList() {
 | ||
|   mWorkerRef->Private()->AssertIsOnWorkerThread();
 | ||
|   nsTArray<RefPtr<ThreadSafeRequestHandle>> list;
 | ||
|   for (ScriptLoadRequest* req = mLoadingRequests.getFirst(); req;
 | ||
|        req = req->getNext()) {
 | ||
|     RefPtr<ThreadSafeRequestHandle> handle =
 | ||
|         new ThreadSafeRequestHandle(req, mSyncLoopTarget.get());
 | ||
|     list.AppendElement(handle.forget());
 | ||
|   }
 | ||
|   return list;
 | ||
| }
 | ||
| 
 | ||
| bool WorkerScriptLoader::IsDynamicImport(ScriptLoadRequest* aRequest) {
 | ||
|   return aRequest->IsModuleRequest() &&
 | ||
|          aRequest->AsModuleRequest()->IsDynamicImport();
 | ||
| }
 | ||
| 
 | ||
| nsContentPolicyType WorkerScriptLoader::GetContentPolicyType(
 | ||
|     ScriptLoadRequest* aRequest) {
 | ||
|   if (aRequest->GetWorkerLoadContext()->IsTopLevel()) {
 | ||
|     // Implements https://html.spec.whatwg.org/#worker-processing-model
 | ||
|     // Step 13: Let destination be "sharedworker" if is shared is true, and
 | ||
|     // "worker" otherwise.
 | ||
|     return mWorkerRef->Private()->ContentPolicyType();
 | ||
|   }
 | ||
|   if (aRequest->IsModuleRequest()) {
 | ||
|     // Implements the destination for Step 14 in
 | ||
|     // https://html.spec.whatwg.org/#worker-processing-model
 | ||
|     //
 | ||
|     // We need a special subresource type in order to correctly implement
 | ||
|     // the graph fetch, where the destination is set to "worker" or
 | ||
|     // "sharedworker".
 | ||
|     return nsIContentPolicy::TYPE_INTERNAL_WORKER_STATIC_MODULE;
 | ||
|   }
 | ||
|   return nsIContentPolicy::TYPE_INTERNAL_WORKER_IMPORT_SCRIPTS;
 | ||
| }
 | ||
| 
 | ||
| already_AddRefed<ScriptLoadRequest> WorkerScriptLoader::CreateScriptLoadRequest(
 | ||
|     const nsString& aScriptURL, const mozilla::Encoding* aDocumentEncoding,
 | ||
|     bool aIsMainScript) {
 | ||
|   mWorkerRef->Private()->AssertIsOnWorkerThread();
 | ||
|   WorkerLoadContext::Kind kind =
 | ||
|       WorkerLoadContext::GetKind(aIsMainScript, IsDebuggerScript());
 | ||
| 
 | ||
|   Maybe<ClientInfo> clientInfo = GetGlobal()->GetClientInfo();
 | ||
| 
 | ||
|   RefPtr<WorkerLoadContext> loadContext =
 | ||
|       new WorkerLoadContext(kind, clientInfo, this);
 | ||
| 
 | ||
|   // Create ScriptLoadRequests for this WorkerScriptLoader
 | ||
|   ReferrerPolicy referrerPolicy = mWorkerRef->Private()->GetReferrerPolicy();
 | ||
| 
 | ||
|   // Only top level workers' main script use the document charset for the
 | ||
|   // script uri encoding. Otherwise, default encoding (UTF-8) is applied.
 | ||
|   MOZ_ASSERT_IF(bool(aDocumentEncoding),
 | ||
|                 aIsMainScript && !mWorkerRef->Private()->GetParent());
 | ||
|   nsCOMPtr<nsIURI> baseURI = aIsMainScript ? GetInitialBaseURI() : GetBaseURI();
 | ||
|   nsCOMPtr<nsIURI> uri;
 | ||
|   nsresult rv =
 | ||
|       ConstructURI(aScriptURL, baseURI, aDocumentEncoding, getter_AddRefs(uri));
 | ||
|   // If we failed to construct the URI, handle it in the LoadContext so it is
 | ||
|   // thrown in the right order.
 | ||
|   if (NS_WARN_IF(NS_FAILED(rv))) {
 | ||
|     loadContext->mLoadResult = rv;
 | ||
|   }
 | ||
| 
 | ||
|   RefPtr<ScriptFetchOptions> fetchOptions =
 | ||
|       new ScriptFetchOptions(CORSMode::CORS_NONE, referrerPolicy, nullptr);
 | ||
| 
 | ||
|   RefPtr<ScriptLoadRequest> request = nullptr;
 | ||
|   // Bug 1817259 - For now the debugger scripts are always loaded a Classic.
 | ||
|   if (mWorkerRef->Private()->WorkerType() == WorkerType::Classic ||
 | ||
|       IsDebuggerScript()) {
 | ||
|     request = new ScriptLoadRequest(ScriptKind::eClassic, uri, fetchOptions,
 | ||
|                                     SRIMetadata(), nullptr,  // mReferrer
 | ||
|                                     loadContext);
 | ||
|   } else {
 | ||
|     // Implements part of "To fetch a worklet/module worker script graph"
 | ||
|     // including, setting up the request with a credentials mode,
 | ||
|     // destination.
 | ||
| 
 | ||
|     // Step 1. Let options be a script fetch options.
 | ||
|     // We currently don't track credentials in our ScriptFetchOptions
 | ||
|     // implementation, so we are defaulting the fetchOptions object defined
 | ||
|     // above. This behavior is handled fully in GetModuleSecFlags.
 | ||
| 
 | ||
|     if (!StaticPrefs::dom_workers_modules_enabled()) {
 | ||
|       mRv.ThrowTypeError("Modules in workers are currently disallowed.");
 | ||
|       return nullptr;
 | ||
|     }
 | ||
|     RefPtr<WorkerModuleLoader::ModuleLoaderBase> moduleLoader =
 | ||
|         GetGlobal()->GetModuleLoader(nullptr);
 | ||
| 
 | ||
|     // Implements the referrer for "To fetch a single module script"
 | ||
|     // Our implementation does not have a "client" as a referrer.
 | ||
|     // However, when client is resolved (per 8.3. Determine request’s
 | ||
|     // Referrer in
 | ||
|     // https://w3c.github.io/webappsec-referrer-policy/#determine-requests-referrer)
 | ||
|     // This should result in the referrer source being the creation URL.
 | ||
|     //
 | ||
|     // In subresource modules, the referrer is the importing script.
 | ||
|     nsCOMPtr<nsIURI> referrer =
 | ||
|         mWorkerRef->Private()->GetReferrerInfo()->GetOriginalReferrer();
 | ||
| 
 | ||
|     // Part of Step 2. This sets the Top-level flag to true
 | ||
|     request = new ModuleLoadRequest(
 | ||
|         uri, fetchOptions, SRIMetadata(), referrer, loadContext,
 | ||
|         true,  /* is top level */
 | ||
|         false, /* is dynamic import */
 | ||
|         moduleLoader, ModuleLoadRequest::NewVisitedSetForTopLevelImport(uri),
 | ||
|         nullptr);
 | ||
|   }
 | ||
| 
 | ||
|   // Set the mURL, it will be used for error handling and debugging.
 | ||
|   request->mURL = NS_ConvertUTF16toUTF8(aScriptURL);
 | ||
| 
 | ||
|   return request.forget();
 | ||
| }
 | ||
| 
 | ||
| bool WorkerScriptLoader::DispatchLoadScript(ScriptLoadRequest* aRequest) {
 | ||
|   mWorkerRef->Private()->AssertIsOnWorkerThread();
 | ||
| 
 | ||
|   IncreaseLoadingModuleRequestCount();
 | ||
| 
 | ||
|   nsTArray<RefPtr<ThreadSafeRequestHandle>> scriptLoadList;
 | ||
|   RefPtr<ThreadSafeRequestHandle> handle =
 | ||
|       new ThreadSafeRequestHandle(aRequest, mSyncLoopTarget.get());
 | ||
|   scriptLoadList.AppendElement(handle.forget());
 | ||
| 
 | ||
|   RefPtr<ScriptLoaderRunnable> runnable =
 | ||
|       new ScriptLoaderRunnable(this, std::move(scriptLoadList));
 | ||
| 
 | ||
|   RefPtr<StrongWorkerRef> workerRef = StrongWorkerRef::Create(
 | ||
|       mWorkerRef->Private(), "ScriptLoader", [runnable]() {
 | ||
|         NS_DispatchToMainThread(NewRunnableMethod(
 | ||
|             "ScriptLoaderRunnable::CancelMainThreadWithBindingAborted",
 | ||
|             runnable,
 | ||
|             &ScriptLoaderRunnable::CancelMainThreadWithBindingAborted));
 | ||
|       });
 | ||
| 
 | ||
|   if (NS_FAILED(NS_DispatchToMainThread(runnable))) {
 | ||
|     NS_ERROR("Failed to dispatch!");
 | ||
|     mRv.Throw(NS_ERROR_FAILURE);
 | ||
|     return false;
 | ||
|   }
 | ||
|   return true;
 | ||
| }
 | ||
| 
 | ||
| bool WorkerScriptLoader::DispatchLoadScripts() {
 | ||
|   mWorkerRef->Private()->AssertIsOnWorkerThread();
 | ||
| 
 | ||
|   nsTArray<RefPtr<ThreadSafeRequestHandle>> scriptLoadList = GetLoadingList();
 | ||
| 
 | ||
|   RefPtr<ScriptLoaderRunnable> runnable =
 | ||
|       new ScriptLoaderRunnable(this, std::move(scriptLoadList));
 | ||
| 
 | ||
|   RefPtr<StrongWorkerRef> workerRef = StrongWorkerRef::Create(
 | ||
|       mWorkerRef->Private(), "ScriptLoader", [runnable]() {
 | ||
|         NS_DispatchToMainThread(NewRunnableMethod(
 | ||
|             "ScriptLoaderRunnable::CancelMainThreadWithBindingAborted",
 | ||
|             runnable,
 | ||
|             &ScriptLoaderRunnable::CancelMainThreadWithBindingAborted));
 | ||
|       });
 | ||
| 
 | ||
|   if (NS_FAILED(NS_DispatchToMainThread(runnable))) {
 | ||
|     NS_ERROR("Failed to dispatch!");
 | ||
|     mRv.Throw(NS_ERROR_FAILURE);
 | ||
|     return false;
 | ||
|   }
 | ||
|   return true;
 | ||
| }
 | ||
| 
 | ||
| nsIURI* WorkerScriptLoader::GetInitialBaseURI() {
 | ||
|   MOZ_ASSERT(mWorkerRef->Private());
 | ||
|   nsIURI* baseURI;
 | ||
|   WorkerPrivate* parentWorker = mWorkerRef->Private()->GetParent();
 | ||
|   if (parentWorker) {
 | ||
|     baseURI = parentWorker->GetBaseURI();
 | ||
|   } else {
 | ||
|     // May be null.
 | ||
|     baseURI = mWorkerRef->Private()->GetBaseURI();
 | ||
|   }
 | ||
| 
 | ||
|   return baseURI;
 | ||
| }
 | ||
| 
 | ||
| nsIURI* WorkerScriptLoader::GetBaseURI() const {
 | ||
|   MOZ_ASSERT(mWorkerRef);
 | ||
|   nsIURI* baseURI;
 | ||
|   baseURI = mWorkerRef->Private()->GetBaseURI();
 | ||
|   NS_ASSERTION(baseURI, "Should have been set already!");
 | ||
| 
 | ||
|   return baseURI;
 | ||
| }
 | ||
| 
 | ||
| nsIGlobalObject* WorkerScriptLoader::GetGlobal() {
 | ||
|   mWorkerRef->Private()->AssertIsOnWorkerThread();
 | ||
|   return mWorkerScriptType == WorkerScript
 | ||
|              ? static_cast<nsIGlobalObject*>(
 | ||
|                    mWorkerRef->Private()->GlobalScope())
 | ||
|              : mWorkerRef->Private()->DebuggerGlobalScope();
 | ||
| }
 | ||
| 
 | ||
| void WorkerScriptLoader::MaybeMoveToLoadedList(ScriptLoadRequest* aRequest) {
 | ||
|   mWorkerRef->Private()->AssertIsOnWorkerThread();
 | ||
|   // Only set to ready for regular scripts. Module loader will set the script to
 | ||
|   // ready if it is a Module Request.
 | ||
|   if (!aRequest->IsModuleRequest()) {
 | ||
|     aRequest->SetReady();
 | ||
|   }
 | ||
| 
 | ||
|   // If the request is not in a list, we are in an illegal state.
 | ||
|   MOZ_RELEASE_ASSERT(aRequest->isInList());
 | ||
| 
 | ||
|   while (!mLoadingRequests.isEmpty()) {
 | ||
|     ScriptLoadRequest* request = mLoadingRequests.getFirst();
 | ||
|     // We need to move requests in post order. If prior requests have not
 | ||
|     // completed, delay execution.
 | ||
|     if (!request->IsReadyToRun()) {
 | ||
|       break;
 | ||
|     }
 | ||
| 
 | ||
|     RefPtr<ScriptLoadRequest> req = mLoadingRequests.Steal(request);
 | ||
|     mLoadedRequests.AppendElement(req);
 | ||
|   }
 | ||
| }
 | ||
| 
 | ||
| bool WorkerScriptLoader::StoreCSP() {
 | ||
|   // We must be on the same worker as we started on.
 | ||
|   mWorkerRef->Private()->AssertIsOnWorkerThread();
 | ||
| 
 | ||
|   if (!mWorkerRef->Private()->GetJSContext()) {
 | ||
|     return false;
 | ||
|   }
 | ||
| 
 | ||
|   MOZ_ASSERT(!mRv.Failed());
 | ||
| 
 | ||
|   // Move the CSP from the workerLoadInfo in the corresponding Client
 | ||
|   // where the CSP code expects it!
 | ||
|   mWorkerRef->Private()->StoreCSPOnClient();
 | ||
|   return true;
 | ||
| }
 | ||
| 
 | ||
| bool WorkerScriptLoader::ProcessPendingRequests(JSContext* aCx) {
 | ||
|   mWorkerRef->Private()->AssertIsOnWorkerThread();
 | ||
|   // Don't run if something else has already failed.
 | ||
|   if (mExecutionAborted) {
 | ||
|     mLoadedRequests.CancelRequestsAndClear();
 | ||
|     TryShutdown();
 | ||
|     return true;
 | ||
|   }
 | ||
| 
 | ||
|   // If nothing else has failed, our ErrorResult better not be a failure
 | ||
|   // either.
 | ||
|   MOZ_ASSERT(!mRv.Failed(), "Who failed it and why?");
 | ||
| 
 | ||
|   // Slightly icky action at a distance, but there's no better place to stash
 | ||
|   // this value, really.
 | ||
|   JS::Rooted<JSObject*> global(aCx, JS::CurrentGlobalOrNull(aCx));
 | ||
|   MOZ_ASSERT(global);
 | ||
| 
 | ||
|   while (!mLoadedRequests.isEmpty()) {
 | ||
|     // Take a reference, but do not remove it from the list yet. There is a
 | ||
|     // possibility that this will need to be cancelled.
 | ||
|     RefPtr<ScriptLoadRequest> req = mLoadedRequests.getFirst();
 | ||
|     // We don't have a ProcessRequest method (like we do on the DOM), as there
 | ||
|     // isn't much processing that we need to do per request that isn't related
 | ||
|     // to evaluation (the processsing done for the DOM is handled in
 | ||
|     // DataRecievedFrom{Cache,Network} for workers.
 | ||
|     // So, this inner loop calls EvaluateScript directly. This will change
 | ||
|     // once modules are introduced as we will have some extra work to do.
 | ||
|     if (!EvaluateScript(aCx, req)) {
 | ||
|       mExecutionAborted = true;
 | ||
|       WorkerLoadContext* loadContext = req->GetWorkerLoadContext();
 | ||
|       mMutedErrorFlag = loadContext->mMutedErrorFlag.valueOr(true);
 | ||
|       mLoadedRequests.CancelRequestsAndClear();
 | ||
|       break;
 | ||
|     }
 | ||
|     // remove the element from the list.
 | ||
|     mLoadedRequests.Remove(req);
 | ||
|   }
 | ||
| 
 | ||
|   TryShutdown();
 | ||
|   return true;
 | ||
| }
 | ||
| 
 | ||
| nsresult WorkerScriptLoader::LoadScript(
 | ||
|     ThreadSafeRequestHandle* aRequestHandle) {
 | ||
|   AssertIsOnMainThread();
 | ||
| 
 | ||
|   WorkerLoadContext* loadContext = aRequestHandle->GetContext();
 | ||
|   ScriptLoadRequest* request = aRequestHandle->GetRequest();
 | ||
|   MOZ_ASSERT_IF(loadContext->IsTopLevel(), !IsDebuggerScript());
 | ||
| 
 | ||
|   // The URL passed to us for loading was invalid, stop loading at this point.
 | ||
|   if (loadContext->mLoadResult != NS_ERROR_NOT_INITIALIZED) {
 | ||
|     return loadContext->mLoadResult;
 | ||
|   }
 | ||
| 
 | ||
|   WorkerPrivate* parentWorker = mWorkerRef->Private()->GetParent();
 | ||
| 
 | ||
|   // For JavaScript debugging, the devtools server must run on the same
 | ||
|   // thread as the debuggee, indicating the worker uses content principal.
 | ||
|   // However, in Bug 863246, web content will no longer be able to load
 | ||
|   // resource:// URIs by default, so we need system principal to load
 | ||
|   // debugger scripts.
 | ||
|   nsIPrincipal* principal = (IsDebuggerScript())
 | ||
|                                 ? nsContentUtils::GetSystemPrincipal()
 | ||
|                                 : mWorkerRef->Private()->GetPrincipal();
 | ||
| 
 | ||
|   nsCOMPtr<nsILoadGroup> loadGroup = mWorkerRef->Private()->GetLoadGroup();
 | ||
|   MOZ_DIAGNOSTIC_ASSERT(principal);
 | ||
| 
 | ||
|   NS_ENSURE_TRUE(NS_LoadGroupMatchesPrincipal(loadGroup, principal),
 | ||
|                  NS_ERROR_FAILURE);
 | ||
| 
 | ||
|   // May be null.
 | ||
|   nsCOMPtr<Document> parentDoc = mWorkerRef->Private()->GetDocument();
 | ||
| 
 | ||
|   nsCOMPtr<nsIChannel> channel;
 | ||
|   if (loadContext->IsTopLevel()) {
 | ||
|     // May be null.
 | ||
|     channel = mWorkerRef->Private()->ForgetWorkerChannel();
 | ||
|   }
 | ||
| 
 | ||
|   nsCOMPtr<nsIIOService> ios(do_GetIOService());
 | ||
| 
 | ||
|   nsIScriptSecurityManager* secMan = nsContentUtils::GetSecurityManager();
 | ||
|   NS_ASSERTION(secMan, "This should never be null!");
 | ||
| 
 | ||
|   nsresult& rv = loadContext->mLoadResult;
 | ||
| 
 | ||
|   nsLoadFlags loadFlags = mWorkerRef->Private()->GetLoadFlags();
 | ||
| 
 | ||
|   // Get the top-level worker.
 | ||
|   WorkerPrivate* topWorkerPrivate = mWorkerRef->Private();
 | ||
|   WorkerPrivate* parent = topWorkerPrivate->GetParent();
 | ||
|   while (parent) {
 | ||
|     topWorkerPrivate = parent;
 | ||
|     parent = topWorkerPrivate->GetParent();
 | ||
|   }
 | ||
| 
 | ||
|   // If the top-level worker is a dedicated worker and has a window, and the
 | ||
|   // window has a docshell, the caching behavior of this worker should match
 | ||
|   // that of that docshell.
 | ||
|   if (topWorkerPrivate->IsDedicatedWorker()) {
 | ||
|     nsCOMPtr<nsPIDOMWindowInner> window = topWorkerPrivate->GetWindow();
 | ||
|     if (window) {
 | ||
|       nsCOMPtr<nsIDocShell> docShell = window->GetDocShell();
 | ||
|       if (docShell) {
 | ||
|         nsresult rv = docShell->GetDefaultLoadFlags(&loadFlags);
 | ||
|         NS_ENSURE_SUCCESS(rv, rv);
 | ||
|       }
 | ||
|     }
 | ||
|   }
 | ||
| 
 | ||
|   if (!channel) {
 | ||
|     nsCOMPtr<nsIReferrerInfo> referrerInfo;
 | ||
|     uint32_t secFlags;
 | ||
|     if (request->IsModuleRequest()) {
 | ||
|       // https://fetch.spec.whatwg.org/#concept-main-fetch
 | ||
|       // Step 8. If request’s referrer policy is the empty string, then set
 | ||
|       //         request’s referrer policy to request’s policy container’s
 | ||
|       //         referrer policy.
 | ||
|       ReferrerPolicy policy =
 | ||
|           request->ReferrerPolicy() == ReferrerPolicy::_empty
 | ||
|               ? mWorkerRef->Private()->GetReferrerPolicy()
 | ||
|               : request->ReferrerPolicy();
 | ||
| 
 | ||
|       referrerInfo = new ReferrerInfo(request->mReferrer, policy);
 | ||
| 
 | ||
|       // https://html.spec.whatwg.org/multipage/webappapis.html#default-classic-script-fetch-options
 | ||
|       // The default classic script fetch options are a script fetch options
 | ||
|       // whose ... credentials mode is "same-origin", ....
 | ||
|       RequestCredentials credentials =
 | ||
|           mWorkerRef->Private()->WorkerType() == WorkerType::Classic
 | ||
|               ? RequestCredentials::Same_origin
 | ||
|               : mWorkerRef->Private()->WorkerCredentials();
 | ||
| 
 | ||
|       rv = GetModuleSecFlags(loadContext->IsTopLevel(), principal,
 | ||
|                              mWorkerScriptType, request->mURI, credentials,
 | ||
|                              secFlags);
 | ||
|     } else {
 | ||
|       referrerInfo = ReferrerInfo::CreateForFetch(principal, nullptr);
 | ||
|       if (parentWorker && !loadContext->IsTopLevel()) {
 | ||
|         referrerInfo =
 | ||
|             static_cast<ReferrerInfo*>(referrerInfo.get())
 | ||
|                 ->CloneWithNewPolicy(parentWorker->GetReferrerPolicy());
 | ||
|       }
 | ||
|       rv = GetClassicSecFlags(loadContext->IsTopLevel(), request->mURI,
 | ||
|                               principal, mWorkerScriptType, secFlags);
 | ||
|     }
 | ||
| 
 | ||
|     if (NS_WARN_IF(NS_FAILED(rv))) {
 | ||
|       return rv;
 | ||
|     }
 | ||
| 
 | ||
|     nsContentPolicyType contentPolicyType = GetContentPolicyType(request);
 | ||
| 
 | ||
|     rv = ChannelFromScriptURL(
 | ||
|         principal, parentDoc, mWorkerRef->Private(), loadGroup, ios, secMan,
 | ||
|         request->mURI, loadContext->mClientInfo, mController,
 | ||
|         loadContext->IsTopLevel(), mWorkerScriptType, contentPolicyType,
 | ||
|         loadFlags, secFlags, mWorkerRef->Private()->CookieJarSettings(),
 | ||
|         referrerInfo, getter_AddRefs(channel));
 | ||
|     if (NS_WARN_IF(NS_FAILED(rv))) {
 | ||
|       return rv;
 | ||
|     }
 | ||
|   }
 | ||
| 
 | ||
|   // Associate any originating stack with the channel.
 | ||
|   if (!mOriginStackJSON.IsEmpty()) {
 | ||
|     NotifyNetworkMonitorAlternateStack(channel, mOriginStackJSON);
 | ||
|   }
 | ||
| 
 | ||
|   // We need to know which index we're on in OnStreamComplete so we know
 | ||
|   // where to put the result.
 | ||
|   RefPtr<NetworkLoadHandler> listener =
 | ||
|       new NetworkLoadHandler(this, aRequestHandle);
 | ||
| 
 | ||
|   RefPtr<ScriptResponseHeaderProcessor> headerProcessor = nullptr;
 | ||
| 
 | ||
|   // For each debugger script, a non-debugger script load of the same script
 | ||
|   // should have occured prior that processed the headers.
 | ||
|   if (!IsDebuggerScript()) {
 | ||
|     headerProcessor = MakeRefPtr<ScriptResponseHeaderProcessor>(
 | ||
|         mWorkerRef->Private(),
 | ||
|         loadContext->IsTopLevel() && !IsDynamicImport(request),
 | ||
|         GetContentPolicyType(request) ==
 | ||
|             nsIContentPolicy::TYPE_INTERNAL_WORKER_IMPORT_SCRIPTS);
 | ||
|   }
 | ||
| 
 | ||
|   nsCOMPtr<nsIStreamLoader> loader;
 | ||
|   rv = NS_NewStreamLoader(getter_AddRefs(loader), listener, headerProcessor);
 | ||
|   if (NS_WARN_IF(NS_FAILED(rv))) {
 | ||
|     return rv;
 | ||
|   }
 | ||
| 
 | ||
|   if (loadContext->IsTopLevel()) {
 | ||
|     MOZ_DIAGNOSTIC_ASSERT(loadContext->mClientInfo.isSome());
 | ||
| 
 | ||
|     // In order to get the correct foreign partitioned prinicpal, we need to
 | ||
|     // set the `IsThirdPartyContextToTopWindow` to the channel's loadInfo.
 | ||
|     // This flag reflects the fact that if the worker is created under a
 | ||
|     // third-party context.
 | ||
|     nsCOMPtr<nsILoadInfo> loadInfo = channel->LoadInfo();
 | ||
|     loadInfo->SetIsThirdPartyContextToTopWindow(
 | ||
|         mWorkerRef->Private()->IsThirdPartyContextToTopWindow());
 | ||
| 
 | ||
|     Maybe<ClientInfo> clientInfo;
 | ||
|     clientInfo.emplace(loadContext->mClientInfo.ref());
 | ||
|     rv = AddClientChannelHelper(channel, std::move(clientInfo),
 | ||
|                                 Maybe<ClientInfo>(),
 | ||
|                                 mWorkerRef->Private()->HybridEventTarget());
 | ||
|     if (NS_WARN_IF(NS_FAILED(rv))) {
 | ||
|       return rv;
 | ||
|     }
 | ||
|   }
 | ||
| 
 | ||
|   if (StaticPrefs::browser_tabs_remote_useCrossOriginEmbedderPolicy()) {
 | ||
|     nsILoadInfo::CrossOriginEmbedderPolicy respectedCOEP =
 | ||
|         mWorkerRef->Private()->GetEmbedderPolicy();
 | ||
|     if (mWorkerRef->Private()->IsDedicatedWorker() &&
 | ||
|         respectedCOEP == nsILoadInfo::EMBEDDER_POLICY_NULL) {
 | ||
|       respectedCOEP = mWorkerRef->Private()->GetOwnerEmbedderPolicy();
 | ||
|     }
 | ||
| 
 | ||
|     nsCOMPtr<nsILoadInfo> channelLoadInfo = channel->LoadInfo();
 | ||
|     channelLoadInfo->SetLoadingEmbedderPolicy(respectedCOEP);
 | ||
|   }
 | ||
| 
 | ||
|   if (loadContext->mCacheStatus != WorkerLoadContext::ToBeCached) {
 | ||
|     rv = channel->AsyncOpen(loader);
 | ||
|     if (NS_WARN_IF(NS_FAILED(rv))) {
 | ||
|       return rv;
 | ||
|     }
 | ||
|   } else {
 | ||
|     nsCOMPtr<nsIOutputStream> writer;
 | ||
| 
 | ||
|     // In case we return early.
 | ||
|     loadContext->mCacheStatus = WorkerLoadContext::Cancel;
 | ||
| 
 | ||
|     NS_NewPipe(getter_AddRefs(loadContext->mCacheReadStream),
 | ||
|                getter_AddRefs(writer), 0,
 | ||
|                UINT32_MAX,    // unlimited size to avoid writer WOULD_BLOCK case
 | ||
|                true, false);  // non-blocking reader, blocking writer
 | ||
| 
 | ||
|     nsCOMPtr<nsIStreamListenerTee> tee =
 | ||
|         do_CreateInstance(NS_STREAMLISTENERTEE_CONTRACTID);
 | ||
|     rv = tee->Init(loader, writer, listener);
 | ||
|     if (NS_WARN_IF(NS_FAILED(rv))) {
 | ||
|       return rv;
 | ||
|     }
 | ||
| 
 | ||
|     nsresult rv = channel->AsyncOpen(tee);
 | ||
|     if (NS_WARN_IF(NS_FAILED(rv))) {
 | ||
|       return rv;
 | ||
|     }
 | ||
|   }
 | ||
| 
 | ||
|   loadContext->mChannel.swap(channel);
 | ||
| 
 | ||
|   return NS_OK;
 | ||
| }
 | ||
| 
 | ||
| nsresult WorkerScriptLoader::FillCompileOptionsForRequest(
 | ||
|     JSContext* cx, ScriptLoadRequest* aRequest, JS::CompileOptions* aOptions,
 | ||
|     JS::MutableHandle<JSScript*> aIntroductionScript) {
 | ||
|   // The full URL shouldn't be exposed to the debugger. See Bug 1634872
 | ||
|   aOptions->setFileAndLine(aRequest->mURL.get(), 1);
 | ||
|   aOptions->setNoScriptRval(true);
 | ||
| 
 | ||
|   aOptions->setMutedErrors(
 | ||
|       aRequest->GetWorkerLoadContext()->mMutedErrorFlag.value());
 | ||
| 
 | ||
|   if (aRequest->mSourceMapURL) {
 | ||
|     aOptions->setSourceMapURL(aRequest->mSourceMapURL->get());
 | ||
|   }
 | ||
| 
 | ||
|   return NS_OK;
 | ||
| }
 | ||
| 
 | ||
| bool WorkerScriptLoader::EvaluateScript(JSContext* aCx,
 | ||
|                                         ScriptLoadRequest* aRequest) {
 | ||
|   mWorkerRef->Private()->AssertIsOnWorkerThread();
 | ||
|   MOZ_ASSERT(!IsDynamicImport(aRequest));
 | ||
| 
 | ||
|   WorkerLoadContext* loadContext = aRequest->GetWorkerLoadContext();
 | ||
| 
 | ||
|   NS_ASSERTION(!loadContext->mChannel, "Should no longer have a channel!");
 | ||
|   NS_ASSERTION(aRequest->IsReadyToRun(), "Should be scheduled!");
 | ||
| 
 | ||
|   MOZ_ASSERT(!mRv.Failed(), "Who failed it and why?");
 | ||
|   mRv.MightThrowJSException();
 | ||
|   if (NS_FAILED(loadContext->mLoadResult)) {
 | ||
|     ReportErrorToConsole(aRequest, loadContext->mLoadResult);
 | ||
|     return false;
 | ||
|   }
 | ||
| 
 | ||
|   // If this is a top level script that succeeded, then mark the
 | ||
|   // Client execution ready and possible controlled by a service worker.
 | ||
|   if (loadContext->IsTopLevel()) {
 | ||
|     if (mController.isSome()) {
 | ||
|       MOZ_ASSERT(mWorkerScriptType == WorkerScript,
 | ||
|                  "Debugger clients can't be controlled.");
 | ||
|       mWorkerRef->Private()->GlobalScope()->Control(mController.ref());
 | ||
|     }
 | ||
|     mWorkerRef->Private()->ExecutionReady();
 | ||
|   }
 | ||
| 
 | ||
|   if (aRequest->IsModuleRequest()) {
 | ||
|     // Only the top level module of the module graph will be executed from here,
 | ||
|     // the rest will be executed from SpiderMonkey as part of the execution of
 | ||
|     // the module graph.
 | ||
|     MOZ_ASSERT(aRequest->IsTopLevel());
 | ||
|     ModuleLoadRequest* request = aRequest->AsModuleRequest();
 | ||
|     if (!request->mModuleScript) {
 | ||
|       return false;
 | ||
|     }
 | ||
| 
 | ||
|     // Implements To fetch a worklet/module worker script graph
 | ||
|     // Step 5. Fetch the descendants of and link result.
 | ||
|     if (!request->InstantiateModuleGraph()) {
 | ||
|       return false;
 | ||
|     }
 | ||
| 
 | ||
|     nsresult rv = request->EvaluateModule();
 | ||
|     return NS_SUCCEEDED(rv);
 | ||
|   }
 | ||
| 
 | ||
|   JS::CompileOptions options(aCx);
 | ||
|   // The introduction script is used by the DOM script loader as a way
 | ||
|   // to fill the Debugger Metadata for the JS Execution context. We don't use
 | ||
|   // the JS Execution context as we are not making use of async compilation
 | ||
|   // (delegation to another worker to produce bytecode or compile a string to a
 | ||
|   // JSScript), so it is not used in this context.
 | ||
|   JS::Rooted<JSScript*> unusedIntroductionScript(aCx);
 | ||
|   nsresult rv = FillCompileOptionsForRequest(aCx, aRequest, &options,
 | ||
|                                              &unusedIntroductionScript);
 | ||
| 
 | ||
|   MOZ_ASSERT(NS_SUCCEEDED(rv), "Filling compile options should not fail");
 | ||
| 
 | ||
|   // Our ErrorResult still shouldn't be a failure.
 | ||
|   MOZ_ASSERT(!mRv.Failed(), "Who failed it and why?");
 | ||
| 
 | ||
|   // Get the source text.
 | ||
|   ScriptLoadRequest::MaybeSourceText maybeSource;
 | ||
|   rv = aRequest->GetScriptSource(aCx, &maybeSource);
 | ||
|   if (NS_FAILED(rv)) {
 | ||
|     mRv.StealExceptionFromJSContext(aCx);
 | ||
|     return false;
 | ||
|   }
 | ||
| 
 | ||
|   RefPtr<JS::loader::ClassicScript> classicScript = nullptr;
 | ||
|   if (StaticPrefs::dom_workers_modules_enabled() &&
 | ||
|       !mWorkerRef->Private()->IsServiceWorker()) {
 | ||
|     // We need a LoadedScript to be associated with the JSScript in order to
 | ||
|     // correctly resolve the referencing private for dynamic imports. In turn
 | ||
|     // this allows us to correctly resolve the BaseURL.
 | ||
|     //
 | ||
|     // Dynamic import is disallowed on service workers.  Additionally, causes
 | ||
|     // crashes because the life cycle isn't completed for service workers.  To
 | ||
|     // keep things simple, we don't create a classic script for ServiceWorkers.
 | ||
|     // If this changes then we will need to ensure that the reference that is
 | ||
|     // held is released appropriately.
 | ||
|     nsCOMPtr<nsIURI> requestBaseURI;
 | ||
|     if (loadContext->mMutedErrorFlag.valueOr(false)) {
 | ||
|       NS_NewURI(getter_AddRefs(requestBaseURI), "about:blank"_ns);
 | ||
|     } else {
 | ||
|       requestBaseURI = aRequest->mBaseURL;
 | ||
|     }
 | ||
|     classicScript =
 | ||
|         new JS::loader::ClassicScript(aRequest->mFetchOptions, requestBaseURI);
 | ||
|   }
 | ||
| 
 | ||
|   bool successfullyEvaluated =
 | ||
|       aRequest->IsUTF8Text()
 | ||
|           ? EvaluateSourceBuffer(aCx, options, classicScript,
 | ||
|                                  maybeSource.ref<JS::SourceText<Utf8Unit>>())
 | ||
|           : EvaluateSourceBuffer(aCx, options, classicScript,
 | ||
|                                  maybeSource.ref<JS::SourceText<char16_t>>());
 | ||
| 
 | ||
|   if (aRequest->IsCanceled()) {
 | ||
|     return false;
 | ||
|   }
 | ||
|   if (!successfullyEvaluated) {
 | ||
|     mRv.StealExceptionFromJSContext(aCx);
 | ||
|     return false;
 | ||
|   }
 | ||
|   // steal the loadContext so that the cycle is broken and cycle collector can
 | ||
|   // collect the scriptLoadRequest.
 | ||
|   return true;
 | ||
| }
 | ||
| 
 | ||
| void WorkerScriptLoader::TryShutdown() {
 | ||
|   if (AllScriptsExecuted() && AllModuleRequestsLoaded()) {
 | ||
|     ShutdownScriptLoader(!mExecutionAborted, mMutedErrorFlag);
 | ||
|   }
 | ||
| }
 | ||
| 
 | ||
| void WorkerScriptLoader::ShutdownScriptLoader(bool aResult, bool aMutedError) {
 | ||
|   MOZ_ASSERT(AllScriptsExecuted());
 | ||
|   MOZ_ASSERT(AllModuleRequestsLoaded());
 | ||
|   mWorkerRef->Private()->AssertIsOnWorkerThread();
 | ||
| 
 | ||
|   if (!aResult) {
 | ||
|     // At this point there are two possibilities:
 | ||
|     //
 | ||
|     // 1) mRv.Failed().  In that case we just want to leave it
 | ||
|     //    as-is, except if it has a JS exception and we need to mute JS
 | ||
|     //    exceptions.  In that case, we log the exception without firing any
 | ||
|     //    events and then replace it on the ErrorResult with a NetworkError,
 | ||
|     //    per spec.
 | ||
|     //
 | ||
|     // 2) mRv succeeded.  As far as I can tell, this can only
 | ||
|     //    happen when loading the main worker script and
 | ||
|     //    GetOrCreateGlobalScope() fails or if ScriptExecutorRunnable::Cancel
 | ||
|     //    got called.  Does it matter what we throw in this case?  I'm not
 | ||
|     //    sure...
 | ||
|     if (mRv.Failed()) {
 | ||
|       if (aMutedError && mRv.IsJSException()) {
 | ||
|         LogExceptionToConsole(mWorkerRef->Private()->GetJSContext(),
 | ||
|                               mWorkerRef->Private());
 | ||
|         mRv.Throw(NS_ERROR_DOM_NETWORK_ERR);
 | ||
|       }
 | ||
|     } else {
 | ||
|       mRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
 | ||
|     }
 | ||
|   }
 | ||
| 
 | ||
|   // Lock, shutdown, and cleanup state. After this the Loader is closed.
 | ||
|   {
 | ||
|     MutexAutoLock lock(CleanUpLock());
 | ||
| 
 | ||
|     if (CleanedUp()) {
 | ||
|       return;
 | ||
|     }
 | ||
| 
 | ||
|     mWorkerRef->Private()->AssertIsOnWorkerThread();
 | ||
|     // Module loader doesn't use sync loop for dynamic import
 | ||
|     if (mSyncLoopTarget) {
 | ||
|       mWorkerRef->Private()->StopSyncLoop(mSyncLoopTarget,
 | ||
|                                           aResult ? NS_OK : NS_ERROR_FAILURE);
 | ||
|     }
 | ||
| 
 | ||
|     // Signal cleanup
 | ||
|     mCleanedUp = true;
 | ||
| 
 | ||
|     // Allow worker shutdown.
 | ||
|     mWorkerRef = nullptr;
 | ||
|   }
 | ||
| }
 | ||
| 
 | ||
| void WorkerScriptLoader::ReportErrorToConsole(ScriptLoadRequest* aRequest,
 | ||
|                                               nsresult aResult) const {
 | ||
|   nsAutoString url = NS_ConvertUTF8toUTF16(aRequest->mURL);
 | ||
|   workerinternals::ReportLoadError(mRv, aResult, url);
 | ||
| }
 | ||
| 
 | ||
| void WorkerScriptLoader::LogExceptionToConsole(JSContext* aCx,
 | ||
|                                                WorkerPrivate* aWorkerPrivate) {
 | ||
|   aWorkerPrivate->AssertIsOnWorkerThread();
 | ||
| 
 | ||
|   MOZ_ASSERT(mRv.IsJSException());
 | ||
| 
 | ||
|   JS::Rooted<JS::Value> exn(aCx);
 | ||
|   if (!ToJSValue(aCx, std::move(mRv), &exn)) {
 | ||
|     return;
 | ||
|   }
 | ||
| 
 | ||
|   // Now the exception state should all be in exn.
 | ||
|   MOZ_ASSERT(!JS_IsExceptionPending(aCx));
 | ||
|   MOZ_ASSERT(!mRv.Failed());
 | ||
| 
 | ||
|   JS::ExceptionStack exnStack(aCx, exn, nullptr);
 | ||
|   JS::ErrorReportBuilder report(aCx);
 | ||
|   if (!report.init(aCx, exnStack, JS::ErrorReportBuilder::WithSideEffects)) {
 | ||
|     JS_ClearPendingException(aCx);
 | ||
|     return;
 | ||
|   }
 | ||
| 
 | ||
|   RefPtr<xpc::ErrorReport> xpcReport = new xpc::ErrorReport();
 | ||
|   xpcReport->Init(report.report(), report.toStringResult().c_str(),
 | ||
|                   aWorkerPrivate->IsChromeWorker(), aWorkerPrivate->WindowID());
 | ||
| 
 | ||
|   RefPtr<AsyncErrorReporter> r = new AsyncErrorReporter(xpcReport);
 | ||
|   NS_DispatchToMainThread(r);
 | ||
| }
 | ||
| 
 | ||
| bool WorkerScriptLoader::AllModuleRequestsLoaded() const {
 | ||
|   mWorkerRef->Private()->AssertIsOnWorkerThread();
 | ||
|   return mLoadingModuleRequestCount == 0;
 | ||
| }
 | ||
| 
 | ||
| void WorkerScriptLoader::IncreaseLoadingModuleRequestCount() {
 | ||
|   mWorkerRef->Private()->AssertIsOnWorkerThread();
 | ||
|   ++mLoadingModuleRequestCount;
 | ||
| }
 | ||
| 
 | ||
| void WorkerScriptLoader::DecreaseLoadingModuleRequestCount() {
 | ||
|   mWorkerRef->Private()->AssertIsOnWorkerThread();
 | ||
|   --mLoadingModuleRequestCount;
 | ||
| }
 | ||
| 
 | ||
| NS_IMPL_ISUPPORTS(ScriptLoaderRunnable, nsIRunnable, nsINamed)
 | ||
| 
 | ||
| NS_IMPL_ISUPPORTS(WorkerScriptLoader, nsINamed)
 | ||
| 
 | ||
| ScriptLoaderRunnable::ScriptLoaderRunnable(
 | ||
|     WorkerScriptLoader* aScriptLoader,
 | ||
|     nsTArray<RefPtr<ThreadSafeRequestHandle>> aLoadingRequests)
 | ||
|     : mScriptLoader(aScriptLoader),
 | ||
|       mWorkerRef(aScriptLoader->mWorkerRef),
 | ||
|       mLoadingRequests(std::move(aLoadingRequests)),
 | ||
|       mCancelMainThread(Nothing()) {
 | ||
|   MOZ_ASSERT(aScriptLoader);
 | ||
| }
 | ||
| 
 | ||
| nsresult ScriptLoaderRunnable::Run() {
 | ||
|   AssertIsOnMainThread();
 | ||
| 
 | ||
|   // Convert the origin stack to JSON (which must be done on the main
 | ||
|   // thread) explicitly, so that we can use the stack to notify the net
 | ||
|   // monitor about every script we load. We do this, rather than pass
 | ||
|   // the stack directly to the netmonitor, in order to be able to use this
 | ||
|   // for all subsequent scripts.
 | ||
|   if (mScriptLoader->mOriginStack &&
 | ||
|       mScriptLoader->mOriginStackJSON.IsEmpty()) {
 | ||
|     ConvertSerializedStackToJSON(std::move(mScriptLoader->mOriginStack),
 | ||
|                                  mScriptLoader->mOriginStackJSON);
 | ||
|   }
 | ||
| 
 | ||
|   if (!mWorkerRef->Private()->IsServiceWorker() ||
 | ||
|       mScriptLoader->IsDebuggerScript()) {
 | ||
|     for (ThreadSafeRequestHandle* handle : mLoadingRequests) {
 | ||
|       handle->mRunnable = this;
 | ||
|     }
 | ||
| 
 | ||
|     for (ThreadSafeRequestHandle* handle : mLoadingRequests) {
 | ||
|       nsresult rv = mScriptLoader->LoadScript(handle);
 | ||
|       if (NS_WARN_IF(NS_FAILED(rv))) {
 | ||
|         LoadingFinished(handle, rv);
 | ||
|         CancelMainThread(rv);
 | ||
|         return rv;
 | ||
|       }
 | ||
|     }
 | ||
| 
 | ||
|     return NS_OK;
 | ||
|   }
 | ||
| 
 | ||
|   MOZ_ASSERT(!mCacheCreator);
 | ||
|   mCacheCreator = new CacheCreator(mWorkerRef->Private());
 | ||
| 
 | ||
|   for (ThreadSafeRequestHandle* handle : mLoadingRequests) {
 | ||
|     handle->mRunnable = this;
 | ||
|     WorkerLoadContext* loadContext = handle->GetContext();
 | ||
|     mCacheCreator->AddLoader(MakeNotNull<RefPtr<CacheLoadHandler>>(
 | ||
|         mWorkerRef, handle, loadContext->IsTopLevel(), mScriptLoader));
 | ||
|   }
 | ||
| 
 | ||
|   // The worker may have a null principal on first load, but in that case its
 | ||
|   // parent definitely will have one.
 | ||
|   nsIPrincipal* principal = mWorkerRef->Private()->GetPrincipal();
 | ||
|   if (!principal) {
 | ||
|     WorkerPrivate* parentWorker = mWorkerRef->Private()->GetParent();
 | ||
|     MOZ_ASSERT(parentWorker, "Must have a parent!");
 | ||
|     principal = parentWorker->GetPrincipal();
 | ||
|   }
 | ||
| 
 | ||
|   nsresult rv = mCacheCreator->Load(principal);
 | ||
|   if (NS_WARN_IF(NS_FAILED(rv))) {
 | ||
|     CancelMainThread(rv);
 | ||
|     return rv;
 | ||
|   }
 | ||
| 
 | ||
|   return NS_OK;
 | ||
| }
 | ||
| 
 | ||
| nsresult ScriptLoaderRunnable::OnStreamComplete(
 | ||
|     ThreadSafeRequestHandle* aRequestHandle, nsresult aStatus) {
 | ||
|   AssertIsOnMainThread();
 | ||
| 
 | ||
|   LoadingFinished(aRequestHandle, aStatus);
 | ||
|   return NS_OK;
 | ||
| }
 | ||
| 
 | ||
| void ScriptLoaderRunnable::LoadingFinished(
 | ||
|     ThreadSafeRequestHandle* aRequestHandle, nsresult aRv) {
 | ||
|   AssertIsOnMainThread();
 | ||
| 
 | ||
|   WorkerLoadContext* loadContext = aRequestHandle->GetContext();
 | ||
| 
 | ||
|   loadContext->mLoadResult = aRv;
 | ||
|   MOZ_ASSERT(!loadContext->mLoadingFinished);
 | ||
|   loadContext->mLoadingFinished = true;
 | ||
| 
 | ||
|   if (loadContext->IsTopLevel() && NS_SUCCEEDED(aRv)) {
 | ||
|     MOZ_DIAGNOSTIC_ASSERT(
 | ||
|         mWorkerRef->Private()->PrincipalURIMatchesScriptURL());
 | ||
|   }
 | ||
| 
 | ||
|   MaybeExecuteFinishedScripts(aRequestHandle);
 | ||
| }
 | ||
| 
 | ||
| void ScriptLoaderRunnable::MaybeExecuteFinishedScripts(
 | ||
|     ThreadSafeRequestHandle* aRequestHandle) {
 | ||
|   AssertIsOnMainThread();
 | ||
| 
 | ||
|   // We execute the last step if we don't have a pending operation with the
 | ||
|   // cache and the loading is completed.
 | ||
|   WorkerLoadContext* loadContext = aRequestHandle->GetContext();
 | ||
|   if (!loadContext->IsAwaitingPromise()) {
 | ||
|     if (aRequestHandle->GetContext()->IsTopLevel()) {
 | ||
|       mWorkerRef->Private()->WorkerScriptLoaded();
 | ||
|     }
 | ||
|     DispatchProcessPendingRequests();
 | ||
|   }
 | ||
| }
 | ||
| 
 | ||
| void ScriptLoaderRunnable::CancelMainThreadWithBindingAborted() {
 | ||
|   AssertIsOnMainThread();
 | ||
|   CancelMainThread(NS_BINDING_ABORTED);
 | ||
| }
 | ||
| 
 | ||
| void ScriptLoaderRunnable::CancelMainThread(nsresult aCancelResult) {
 | ||
|   AssertIsOnMainThread();
 | ||
|   if (IsCancelled()) {
 | ||
|     return;
 | ||
|   }
 | ||
| 
 | ||
|   {
 | ||
|     MutexAutoLock lock(mScriptLoader->CleanUpLock());
 | ||
| 
 | ||
|     // Check if we have already cancelled, or if the worker has been killed
 | ||
|     // before we cancel.
 | ||
|     if (mScriptLoader->CleanedUp()) {
 | ||
|       return;
 | ||
|     }
 | ||
| 
 | ||
|     mCancelMainThread = Some(aCancelResult);
 | ||
| 
 | ||
|     for (ThreadSafeRequestHandle* handle : mLoadingRequests) {
 | ||
|       if (handle->IsEmpty()) {
 | ||
|         continue;
 | ||
|       }
 | ||
| 
 | ||
|       bool callLoadingFinished = true;
 | ||
| 
 | ||
|       WorkerLoadContext* loadContext = handle->GetContext();
 | ||
|       if (!loadContext) {
 | ||
|         continue;
 | ||
|       }
 | ||
| 
 | ||
|       if (loadContext->IsAwaitingPromise()) {
 | ||
|         MOZ_ASSERT(mWorkerRef->Private()->IsServiceWorker());
 | ||
|         loadContext->mCachePromise->MaybeReject(NS_BINDING_ABORTED);
 | ||
|         loadContext->mCachePromise = nullptr;
 | ||
|         callLoadingFinished = false;
 | ||
|       }
 | ||
|       if (loadContext->mChannel) {
 | ||
|         if (NS_SUCCEEDED(loadContext->mChannel->Cancel(aCancelResult))) {
 | ||
|           callLoadingFinished = false;
 | ||
|         } else {
 | ||
|           NS_WARNING("Failed to cancel channel!");
 | ||
|         }
 | ||
|       }
 | ||
|       if (callLoadingFinished && !loadContext->mLoadingFinished) {
 | ||
|         LoadingFinished(handle, aCancelResult);
 | ||
|       }
 | ||
|     }
 | ||
|     DispatchProcessPendingRequests();
 | ||
|   }
 | ||
| }
 | ||
| 
 | ||
| void ScriptLoaderRunnable::DispatchProcessPendingRequests() {
 | ||
|   AssertIsOnMainThread();
 | ||
| 
 | ||
|   const auto begin = mLoadingRequests.begin();
 | ||
|   const auto end = mLoadingRequests.end();
 | ||
|   using Iterator = decltype(begin);
 | ||
|   const auto maybeRangeToExecute =
 | ||
|       [begin, end]() -> Maybe<std::pair<Iterator, Iterator>> {
 | ||
|     // firstItToExecute is the first loadInfo where mExecutionScheduled is
 | ||
|     // unset.
 | ||
|     auto firstItToExecute = std::find_if(
 | ||
|         begin, end, [](const RefPtr<ThreadSafeRequestHandle>& requestHandle) {
 | ||
|           return !requestHandle->mExecutionScheduled;
 | ||
|         });
 | ||
| 
 | ||
|     if (firstItToExecute == end) {
 | ||
|       return Nothing();
 | ||
|     }
 | ||
| 
 | ||
|     // firstItUnexecutable is the first loadInfo that is not yet finished.
 | ||
|     // Update mExecutionScheduled on the ones we're about to schedule for
 | ||
|     // execution.
 | ||
|     const auto firstItUnexecutable =
 | ||
|         std::find_if(firstItToExecute, end,
 | ||
|                      [](RefPtr<ThreadSafeRequestHandle>& requestHandle) {
 | ||
|                        MOZ_ASSERT(!requestHandle->IsEmpty());
 | ||
|                        if (!requestHandle->Finished()) {
 | ||
|                          return true;
 | ||
|                        }
 | ||
| 
 | ||
|                        // We can execute this one.
 | ||
|                        requestHandle->mExecutionScheduled = true;
 | ||
| 
 | ||
|                        return false;
 | ||
|                      });
 | ||
| 
 | ||
|     return firstItUnexecutable == firstItToExecute
 | ||
|                ? Nothing()
 | ||
|                : Some(std::pair(firstItToExecute, firstItUnexecutable));
 | ||
|   }();
 | ||
| 
 | ||
|   // If there are no unexecutable load infos, we can unuse things before the
 | ||
|   // execution of the scripts and the stopping of the sync loop.
 | ||
|   if (maybeRangeToExecute) {
 | ||
|     if (maybeRangeToExecute->second == end) {
 | ||
|       mCacheCreator = nullptr;
 | ||
|     }
 | ||
| 
 | ||
|     RefPtr<ScriptExecutorRunnable> runnable = new ScriptExecutorRunnable(
 | ||
|         mScriptLoader, mWorkerRef->Private(), mScriptLoader->mSyncLoopTarget,
 | ||
|         Span<RefPtr<ThreadSafeRequestHandle>>{maybeRangeToExecute->first,
 | ||
|                                               maybeRangeToExecute->second});
 | ||
| 
 | ||
|     if (!runnable->Dispatch() && mScriptLoader->mSyncLoopTarget) {
 | ||
|       MOZ_ASSERT(false, "This should never fail!");
 | ||
|     }
 | ||
|   }
 | ||
| }
 | ||
| 
 | ||
| ScriptExecutorRunnable::ScriptExecutorRunnable(
 | ||
|     WorkerScriptLoader* aScriptLoader, WorkerPrivate* aWorkerPrivate,
 | ||
|     nsISerialEventTarget* aSyncLoopTarget,
 | ||
|     Span<RefPtr<ThreadSafeRequestHandle>> aLoadedRequests)
 | ||
|     : MainThreadWorkerSyncRunnable(aWorkerPrivate, aSyncLoopTarget),
 | ||
|       mScriptLoader(aScriptLoader),
 | ||
|       mLoadedRequests(aLoadedRequests) {}
 | ||
| 
 | ||
| bool ScriptExecutorRunnable::IsDebuggerRunnable() const {
 | ||
|   // ScriptExecutorRunnable is used to execute both worker and debugger scripts.
 | ||
|   // In the latter case, the runnable needs to be dispatched to the debugger
 | ||
|   // queue.
 | ||
|   return mScriptLoader->IsDebuggerScript();
 | ||
| }
 | ||
| 
 | ||
| bool ScriptExecutorRunnable::PreRun(WorkerPrivate* aWorkerPrivate) {
 | ||
|   aWorkerPrivate->AssertIsOnWorkerThread();
 | ||
| 
 | ||
|   // We must be on the same worker as we started on.
 | ||
|   MOZ_ASSERT(
 | ||
|       mScriptLoader->mSyncLoopTarget == mSyncLoopTarget,
 | ||
|       "Unexpected SyncLoopTarget. Check if the sync loop was closed early");
 | ||
| 
 | ||
|   {
 | ||
|     // There is a possibility that we cleaned up while this task was waiting to
 | ||
|     // run. If this has happened, return and exit.
 | ||
|     MutexAutoLock lock(mScriptLoader->CleanUpLock());
 | ||
|     if (mScriptLoader->CleanedUp()) {
 | ||
|       return true;
 | ||
|     }
 | ||
| 
 | ||
|     const auto& requestHandle = mLoadedRequests[0];
 | ||
|     // Check if the request is still valid.
 | ||
|     if (requestHandle->IsEmpty() ||
 | ||
|         !requestHandle->GetContext()->IsTopLevel()) {
 | ||
|       return true;
 | ||
|     }
 | ||
|   }
 | ||
| 
 | ||
|   return mScriptLoader->StoreCSP();
 | ||
| }
 | ||
| 
 | ||
| bool ScriptExecutorRunnable::ProcessModuleScript(
 | ||
|     JSContext* aCx, WorkerPrivate* aWorkerPrivate) {
 | ||
|   // We should only ever have one script when processing modules
 | ||
|   MOZ_ASSERT(mLoadedRequests.Length() == 1);
 | ||
|   RefPtr<ScriptLoadRequest> request;
 | ||
|   {
 | ||
|     // There is a possibility that we cleaned up while this task was waiting to
 | ||
|     // run. If this has happened, return and exit.
 | ||
|     MutexAutoLock lock(mScriptLoader->CleanUpLock());
 | ||
|     if (mScriptLoader->CleanedUp()) {
 | ||
|       return true;
 | ||
|     }
 | ||
| 
 | ||
|     MOZ_ASSERT(mLoadedRequests.Length() == 1);
 | ||
|     const auto& requestHandle = mLoadedRequests[0];
 | ||
|     // The request must be valid.
 | ||
|     MOZ_ASSERT(!requestHandle->IsEmpty());
 | ||
| 
 | ||
|     // Release the request to the worker. From this point on, the Request Handle
 | ||
|     // is empty.
 | ||
|     request = requestHandle->ReleaseRequest();
 | ||
| 
 | ||
|     // release lock. We will need it later if we cleanup.
 | ||
|   }
 | ||
| 
 | ||
|   MOZ_ASSERT(request->IsModuleRequest());
 | ||
| 
 | ||
|   WorkerLoadContext* loadContext = request->GetWorkerLoadContext();
 | ||
|   ModuleLoadRequest* moduleRequest = request->AsModuleRequest();
 | ||
| 
 | ||
|   // DecreaseLoadingModuleRequestCount must be called before OnFetchComplete.
 | ||
|   // OnFetchComplete will call ProcessPendingRequests, and in
 | ||
|   // ProcessPendingRequests it will try to shutdown if
 | ||
|   // AllModuleRequestsLoaded() returns true.
 | ||
|   mScriptLoader->DecreaseLoadingModuleRequestCount();
 | ||
|   moduleRequest->OnFetchComplete(loadContext->mLoadResult);
 | ||
| 
 | ||
|   if (NS_FAILED(loadContext->mLoadResult)) {
 | ||
|     if (moduleRequest->IsDynamicImport()) {
 | ||
|       if (request->isInList()) {
 | ||
|         moduleRequest->CancelDynamicImport(loadContext->mLoadResult);
 | ||
|         mScriptLoader->TryShutdown();
 | ||
|       }
 | ||
|     } else if (!moduleRequest->IsTopLevel()) {
 | ||
|       moduleRequest->Cancel();
 | ||
|       mScriptLoader->TryShutdown();
 | ||
|     } else {
 | ||
|       moduleRequest->LoadFailed();
 | ||
|     }
 | ||
|   }
 | ||
|   return true;
 | ||
| }
 | ||
| 
 | ||
| bool ScriptExecutorRunnable::ProcessClassicScripts(
 | ||
|     JSContext* aCx, WorkerPrivate* aWorkerPrivate) {
 | ||
|   // There is a possibility that we cleaned up while this task was waiting to
 | ||
|   // run. If this has happened, return and exit.
 | ||
|   {
 | ||
|     MutexAutoLock lock(mScriptLoader->CleanUpLock());
 | ||
|     if (mScriptLoader->CleanedUp()) {
 | ||
|       return true;
 | ||
|     }
 | ||
| 
 | ||
|     for (const auto& requestHandle : mLoadedRequests) {
 | ||
|       // The request must be valid.
 | ||
|       MOZ_ASSERT(!requestHandle->IsEmpty());
 | ||
| 
 | ||
|       // Release the request to the worker. From this point on, the Request
 | ||
|       // Handle is empty.
 | ||
|       RefPtr<ScriptLoadRequest> request = requestHandle->ReleaseRequest();
 | ||
|       mScriptLoader->MaybeMoveToLoadedList(request);
 | ||
|     }
 | ||
|   }
 | ||
|   return mScriptLoader->ProcessPendingRequests(aCx);
 | ||
| }
 | ||
| 
 | ||
| bool ScriptExecutorRunnable::WorkerRun(JSContext* aCx,
 | ||
|                                        WorkerPrivate* aWorkerPrivate) {
 | ||
|   aWorkerPrivate->AssertIsOnWorkerThread();
 | ||
| 
 | ||
|   // We must be on the same worker as we started on.
 | ||
|   MOZ_ASSERT(
 | ||
|       mScriptLoader->mSyncLoopTarget == mSyncLoopTarget,
 | ||
|       "Unexpected SyncLoopTarget. Check if the sync loop was closed early");
 | ||
| 
 | ||
|   if (mLoadedRequests.begin()->get()->GetRequest()->IsModuleRequest()) {
 | ||
|     return ProcessModuleScript(aCx, aWorkerPrivate);
 | ||
|   }
 | ||
| 
 | ||
|   return ProcessClassicScripts(aCx, aWorkerPrivate);
 | ||
| }
 | ||
| 
 | ||
| nsresult ScriptExecutorRunnable::Cancel() {
 | ||
|   if (mScriptLoader->AllScriptsExecuted() &&
 | ||
|       mScriptLoader->AllModuleRequestsLoaded()) {
 | ||
|     mScriptLoader->ShutdownScriptLoader(false, false);
 | ||
|   }
 | ||
|   return NS_OK;
 | ||
| }
 | ||
| 
 | ||
| } /* namespace loader */
 | ||
| 
 | ||
| nsresult ChannelFromScriptURLMainThread(
 | ||
|     nsIPrincipal* aPrincipal, Document* aParentDoc, nsILoadGroup* aLoadGroup,
 | ||
|     nsIURI* aScriptURL, const WorkerType& aWorkerType,
 | ||
|     const RequestCredentials& aCredentials,
 | ||
|     const Maybe<ClientInfo>& aClientInfo,
 | ||
|     nsContentPolicyType aMainScriptContentPolicyType,
 | ||
|     nsICookieJarSettings* aCookieJarSettings, nsIReferrerInfo* aReferrerInfo,
 | ||
|     nsIChannel** aChannel) {
 | ||
|   AssertIsOnMainThread();
 | ||
| 
 | ||
|   nsCOMPtr<nsIIOService> ios(do_GetIOService());
 | ||
| 
 | ||
|   nsIScriptSecurityManager* secMan = nsContentUtils::GetSecurityManager();
 | ||
|   NS_ASSERTION(secMan, "This should never be null!");
 | ||
| 
 | ||
|   uint32_t secFlags;
 | ||
|   nsresult rv;
 | ||
|   if (aWorkerType == WorkerType::Module) {
 | ||
|     rv = GetModuleSecFlags(true, aPrincipal, WorkerScript, aScriptURL,
 | ||
|                            aCredentials, secFlags);
 | ||
|   } else {
 | ||
|     rv = GetClassicSecFlags(true, aScriptURL, aPrincipal, WorkerScript,
 | ||
|                             secFlags);
 | ||
|   }
 | ||
|   if (NS_FAILED(rv)) {
 | ||
|     return rv;
 | ||
|   }
 | ||
| 
 | ||
|   return ChannelFromScriptURL(
 | ||
|       aPrincipal, aParentDoc, nullptr, aLoadGroup, ios, secMan, aScriptURL,
 | ||
|       aClientInfo, Maybe<ServiceWorkerDescriptor>(), true, WorkerScript,
 | ||
|       aMainScriptContentPolicyType, nsIRequest::LOAD_NORMAL, secFlags,
 | ||
|       aCookieJarSettings, aReferrerInfo, aChannel);
 | ||
| }
 | ||
| 
 | ||
| nsresult ChannelFromScriptURLWorkerThread(
 | ||
|     JSContext* aCx, WorkerPrivate* aParent, const nsAString& aScriptURL,
 | ||
|     const WorkerType& aWorkerType, const RequestCredentials& aCredentials,
 | ||
|     WorkerLoadInfo& aLoadInfo) {
 | ||
|   aParent->AssertIsOnWorkerThread();
 | ||
| 
 | ||
|   RefPtr<ChannelGetterRunnable> getter = new ChannelGetterRunnable(
 | ||
|       aParent, aScriptURL, aWorkerType, aCredentials, aLoadInfo);
 | ||
| 
 | ||
|   ErrorResult rv;
 | ||
|   getter->Dispatch(Canceling, rv);
 | ||
|   if (rv.Failed()) {
 | ||
|     NS_ERROR("Failed to dispatch!");
 | ||
|     return rv.StealNSResult();
 | ||
|   }
 | ||
| 
 | ||
|   return getter->GetResult();
 | ||
| }
 | ||
| 
 | ||
| void ReportLoadError(ErrorResult& aRv, nsresult aLoadResult,
 | ||
|                      const nsAString& aScriptURL) {
 | ||
|   MOZ_ASSERT(!aRv.Failed());
 | ||
| 
 | ||
|   nsPrintfCString err("Failed to load worker script at \"%s\"",
 | ||
|                       NS_ConvertUTF16toUTF8(aScriptURL).get());
 | ||
| 
 | ||
|   switch (aLoadResult) {
 | ||
|     case NS_ERROR_FILE_NOT_FOUND:
 | ||
|     case NS_ERROR_NOT_AVAILABLE:
 | ||
|     case NS_ERROR_CORRUPTED_CONTENT:
 | ||
|       aRv.Throw(NS_ERROR_DOM_NETWORK_ERR);
 | ||
|       break;
 | ||
| 
 | ||
|     case NS_ERROR_MALFORMED_URI:
 | ||
|     case NS_ERROR_DOM_SYNTAX_ERR:
 | ||
|       aRv.ThrowSyntaxError(err);
 | ||
|       break;
 | ||
| 
 | ||
|     case NS_BINDING_ABORTED:
 | ||
|       // Note: we used to pretend like we didn't set an exception for
 | ||
|       // NS_BINDING_ABORTED, but then ShutdownScriptLoader did it anyway.  The
 | ||
|       // other callsite, in WorkerPrivate::Constructor, never passed in
 | ||
|       // NS_BINDING_ABORTED.  So just throw it directly here.  Consumers will
 | ||
|       // deal as needed.  But note that we do NOT want to use one of the
 | ||
|       // Throw*Error() methods on ErrorResult for this case, because that will
 | ||
|       // make it impossible for consumers to realize that our error was
 | ||
|       // NS_BINDING_ABORTED.
 | ||
|       aRv.Throw(aLoadResult);
 | ||
|       return;
 | ||
| 
 | ||
|     case NS_ERROR_DOM_BAD_URI:
 | ||
|       // This is actually a security error.
 | ||
|     case NS_ERROR_DOM_SECURITY_ERR:
 | ||
|       aRv.ThrowSecurityError(err);
 | ||
|       break;
 | ||
| 
 | ||
|     default:
 | ||
|       // For lack of anything better, go ahead and throw a NetworkError here.
 | ||
|       // We don't want to throw a JS exception, because for toplevel script
 | ||
|       // loads that would get squelched.
 | ||
|       aRv.ThrowNetworkError(nsPrintfCString(
 | ||
|           "Failed to load worker script at %s (nsresult = 0x%" PRIx32 ")",
 | ||
|           NS_ConvertUTF16toUTF8(aScriptURL).get(),
 | ||
|           static_cast<uint32_t>(aLoadResult)));
 | ||
|       return;
 | ||
|   }
 | ||
| }
 | ||
| 
 | ||
| void LoadMainScript(WorkerPrivate* aWorkerPrivate,
 | ||
|                     UniquePtr<SerializedStackHolder> aOriginStack,
 | ||
|                     const nsAString& aScriptURL,
 | ||
|                     WorkerScriptType aWorkerScriptType, ErrorResult& aRv,
 | ||
|                     const mozilla::Encoding* aDocumentEncoding) {
 | ||
|   nsTArray<nsString> scriptURLs;
 | ||
| 
 | ||
|   scriptURLs.AppendElement(aScriptURL);
 | ||
| 
 | ||
|   LoadAllScripts(aWorkerPrivate, std::move(aOriginStack), scriptURLs, true,
 | ||
|                  aWorkerScriptType, aRv, aDocumentEncoding);
 | ||
| }
 | ||
| 
 | ||
| void Load(WorkerPrivate* aWorkerPrivate,
 | ||
|           UniquePtr<SerializedStackHolder> aOriginStack,
 | ||
|           const nsTArray<nsString>& aScriptURLs,
 | ||
|           WorkerScriptType aWorkerScriptType, ErrorResult& aRv) {
 | ||
|   const uint32_t urlCount = aScriptURLs.Length();
 | ||
| 
 | ||
|   if (!urlCount) {
 | ||
|     return;
 | ||
|   }
 | ||
| 
 | ||
|   if (urlCount > MAX_CONCURRENT_SCRIPTS) {
 | ||
|     aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
 | ||
|     return;
 | ||
|   }
 | ||
| 
 | ||
|   LoadAllScripts(aWorkerPrivate, std::move(aOriginStack), aScriptURLs, false,
 | ||
|                  aWorkerScriptType, aRv);
 | ||
| }
 | ||
| 
 | ||
| }  // namespace mozilla::dom::workerinternals
 | 
