Bug 1843838 - Rename ScriptLoadRequest::IsReadyToRun to IsFinished since this returns...
[gecko.git] / dom / workers / ScriptLoader.cpp
blobd02f0171b8fd61fb33432d686d11d05f62db98a9
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "ScriptLoader.h"
9 #include <algorithm>
10 #include <type_traits>
12 #include "nsIChannel.h"
13 #include "nsIContentPolicy.h"
14 #include "nsIContentSecurityPolicy.h"
15 #include "nsICookieJarSettings.h"
16 #include "nsIDocShell.h"
17 #include "nsIHttpChannel.h"
18 #include "nsIHttpChannelInternal.h"
19 #include "nsIIOService.h"
20 #include "nsIOService.h"
21 #include "nsIPrincipal.h"
22 #include "nsIProtocolHandler.h"
23 #include "nsIScriptError.h"
24 #include "nsIScriptSecurityManager.h"
25 #include "nsIStreamListenerTee.h"
26 #include "nsIThreadRetargetableRequest.h"
27 #include "nsIURI.h"
28 #include "nsIXPConnect.h"
30 #include "jsapi.h"
31 #include "jsfriendapi.h"
32 #include "js/CompilationAndEvaluation.h"
33 #include "js/Exception.h"
34 #include "js/SourceText.h"
35 #include "js/TypeDecls.h"
36 #include "nsError.h"
37 #include "nsComponentManagerUtils.h"
38 #include "nsContentSecurityManager.h"
39 #include "nsContentPolicyUtils.h"
40 #include "nsContentUtils.h"
41 #include "nsDocShellCID.h"
42 #include "nsJSEnvironment.h"
43 #include "nsNetUtil.h"
44 #include "nsIPipe.h"
45 #include "nsIOutputStream.h"
46 #include "nsPrintfCString.h"
47 #include "nsString.h"
48 #include "nsTArray.h"
49 #include "nsThreadUtils.h"
50 #include "nsXPCOM.h"
51 #include "xpcpublic.h"
53 #include "mozilla/AntiTrackingUtils.h"
54 #include "mozilla/ArrayAlgorithm.h"
55 #include "mozilla/Assertions.h"
56 #include "mozilla/Encoding.h"
57 #include "mozilla/LoadContext.h"
58 #include "mozilla/Maybe.h"
59 #include "mozilla/ipc/BackgroundUtils.h"
60 #include "mozilla/dom/ClientChannelHelper.h"
61 #include "mozilla/dom/ClientInfo.h"
62 #include "mozilla/dom/Exceptions.h"
63 #include "mozilla/dom/nsCSPService.h"
64 #include "mozilla/dom/nsCSPUtils.h"
65 #include "mozilla/dom/PerformanceStorage.h"
66 #include "mozilla/dom/Response.h"
67 #include "mozilla/dom/ReferrerInfo.h"
68 #include "mozilla/dom/ScriptSettings.h"
69 #include "mozilla/dom/SerializedStackHolder.h"
70 #include "mozilla/dom/workerinternals/CacheLoadHandler.h"
71 #include "mozilla/dom/workerinternals/NetworkLoadHandler.h"
72 #include "mozilla/dom/workerinternals/ScriptResponseHeaderProcessor.h"
73 #include "mozilla/Result.h"
74 #include "mozilla/ResultExtensions.h"
75 #include "mozilla/StaticPrefs_browser.h"
76 #include "mozilla/UniquePtr.h"
77 #include "WorkerRunnable.h"
78 #include "WorkerScope.h"
80 #define MAX_CONCURRENT_SCRIPTS 1000
82 using JS::loader::ParserMetadata;
83 using JS::loader::ScriptKind;
84 using JS::loader::ScriptLoadRequest;
85 using mozilla::ipc::PrincipalInfo;
87 namespace mozilla::dom::workerinternals {
88 namespace {
90 nsresult ConstructURI(const nsAString& aScriptURL, nsIURI* baseURI,
91 const mozilla::Encoding* aDocumentEncoding,
92 nsIURI** aResult) {
93 nsresult rv;
94 // Only top level workers' main script use the document charset for the
95 // script uri encoding. Otherwise, default encoding (UTF-8) is applied.
96 if (aDocumentEncoding) {
97 nsAutoCString charset;
98 aDocumentEncoding->Name(charset);
99 rv = NS_NewURI(aResult, aScriptURL, charset.get(), baseURI);
100 } else {
101 rv = NS_NewURI(aResult, aScriptURL, nullptr, baseURI);
104 if (NS_FAILED(rv)) {
105 return NS_ERROR_DOM_SYNTAX_ERR;
107 return NS_OK;
110 nsresult ChannelFromScriptURL(
111 nsIPrincipal* principal, Document* parentDoc, WorkerPrivate* aWorkerPrivate,
112 nsILoadGroup* loadGroup, nsIIOService* ios,
113 nsIScriptSecurityManager* secMan, nsIURI* aScriptURL,
114 const Maybe<ClientInfo>& aClientInfo,
115 const Maybe<ServiceWorkerDescriptor>& aController, bool aIsMainScript,
116 WorkerScriptType aWorkerScriptType, nsContentPolicyType aContentPolicyType,
117 nsLoadFlags aLoadFlags, uint32_t aSecFlags,
118 nsICookieJarSettings* aCookieJarSettings, nsIReferrerInfo* aReferrerInfo,
119 nsIChannel** aChannel) {
120 AssertIsOnMainThread();
122 nsresult rv;
123 nsCOMPtr<nsIURI> uri = aScriptURL;
125 // Only use the document when its principal matches the principal of the
126 // current request. This means scripts fetched using the Workers' own
127 // principal won't inherit properties of the document, in particular the CSP.
128 if (parentDoc && parentDoc->NodePrincipal() != principal) {
129 parentDoc = nullptr;
132 // The main service worker script should never be loaded over the network
133 // in this path. It should always be offlined by ServiceWorkerScriptCache.
134 // We assert here since this error should also be caught by the runtime
135 // check in CacheLoadHandler.
137 // Note, if we ever allow service worker scripts to be loaded from network
138 // here we need to configure the channel properly. For example, it must
139 // not allow redirects.
140 MOZ_DIAGNOSTIC_ASSERT(aContentPolicyType !=
141 nsIContentPolicy::TYPE_INTERNAL_SERVICE_WORKER);
143 nsCOMPtr<nsIChannel> channel;
144 if (parentDoc) {
145 // This is the path for top level dedicated worker scripts with a document
146 rv = NS_NewChannel(getter_AddRefs(channel), uri, parentDoc, aSecFlags,
147 aContentPolicyType,
148 nullptr, // aPerformanceStorage
149 loadGroup,
150 nullptr, // aCallbacks
151 aLoadFlags, ios);
152 NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_SECURITY_ERR);
153 } else {
154 // This branch is used in the following cases:
155 // * Shared and ServiceWorkers (who do not have a doc)
156 // * Static Module Imports
157 // * ImportScripts
159 // We must have a loadGroup with a load context for the principal to
160 // traverse the channel correctly.
162 MOZ_ASSERT(loadGroup);
163 MOZ_ASSERT(NS_LoadGroupMatchesPrincipal(loadGroup, principal));
165 RefPtr<PerformanceStorage> performanceStorage;
166 nsCOMPtr<nsICSPEventListener> cspEventListener;
167 if (aWorkerPrivate && !aIsMainScript) {
168 performanceStorage = aWorkerPrivate->GetPerformanceStorage();
169 cspEventListener = aWorkerPrivate->CSPEventListener();
172 if (aClientInfo.isSome()) {
173 // If we have an existing clientInfo (true for all modules and
174 // importScripts), we will use this branch
175 rv = NS_NewChannel(getter_AddRefs(channel), uri, principal,
176 aClientInfo.ref(), aController, aSecFlags,
177 aContentPolicyType, aCookieJarSettings,
178 performanceStorage, loadGroup, nullptr, // aCallbacks
179 aLoadFlags, ios);
180 } else {
181 rv = NS_NewChannel(getter_AddRefs(channel), uri, principal, aSecFlags,
182 aContentPolicyType, aCookieJarSettings,
183 performanceStorage, loadGroup, nullptr, // aCallbacks
184 aLoadFlags, ios);
187 NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_SECURITY_ERR);
189 if (cspEventListener) {
190 nsCOMPtr<nsILoadInfo> loadInfo = channel->LoadInfo();
191 rv = loadInfo->SetCspEventListener(cspEventListener);
192 NS_ENSURE_SUCCESS(rv, rv);
196 if (aReferrerInfo) {
197 nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(channel);
198 if (httpChannel) {
199 rv = httpChannel->SetReferrerInfo(aReferrerInfo);
200 if (NS_WARN_IF(NS_FAILED(rv))) {
201 return rv;
206 channel.forget(aChannel);
207 return rv;
210 void LoadAllScripts(WorkerPrivate* aWorkerPrivate,
211 UniquePtr<SerializedStackHolder> aOriginStack,
212 const nsTArray<nsString>& aScriptURLs, bool aIsMainScript,
213 WorkerScriptType aWorkerScriptType, ErrorResult& aRv,
214 const mozilla::Encoding* aDocumentEncoding = nullptr) {
215 aWorkerPrivate->AssertIsOnWorkerThread();
216 NS_ASSERTION(!aScriptURLs.IsEmpty(), "Bad arguments!");
218 AutoSyncLoopHolder syncLoop(aWorkerPrivate, Canceling);
219 nsCOMPtr<nsISerialEventTarget> syncLoopTarget =
220 syncLoop.GetSerialEventTarget();
221 if (!syncLoopTarget) {
222 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
223 return;
226 RefPtr<loader::WorkerScriptLoader> loader =
227 new loader::WorkerScriptLoader(aWorkerPrivate, std::move(aOriginStack),
228 syncLoopTarget, aWorkerScriptType, aRv);
230 if (NS_WARN_IF(aRv.Failed())) {
231 return;
234 bool ok = loader->CreateScriptRequests(aScriptURLs, aDocumentEncoding,
235 aIsMainScript);
237 if (!ok) {
238 return;
240 // Bug 1817259 - For now, we force loading the debugger script as Classic,
241 // even if the debugged worker is a Module.
242 if (aWorkerPrivate->WorkerType() == WorkerType::Module &&
243 aWorkerScriptType != DebuggerScript) {
244 if (!StaticPrefs::dom_workers_modules_enabled()) {
245 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
246 return;
248 MOZ_ASSERT(aIsMainScript);
249 // Module Load
250 RefPtr<JS::loader::ScriptLoadRequest> mainScript = loader->GetMainScript();
251 if (mainScript && mainScript->IsModuleRequest()) {
252 if (NS_FAILED(mainScript->AsModuleRequest()->StartModuleLoad())) {
253 return;
255 syncLoop.Run();
256 return;
260 if (loader->DispatchLoadScripts()) {
261 syncLoop.Run();
265 class ChannelGetterRunnable final : public WorkerMainThreadRunnable {
266 const nsAString& mScriptURL;
267 const WorkerType& mWorkerType;
268 const RequestCredentials& mCredentials;
269 const ClientInfo mClientInfo;
270 WorkerLoadInfo& mLoadInfo;
271 nsresult mResult;
273 public:
274 ChannelGetterRunnable(WorkerPrivate* aParentWorker,
275 const nsAString& aScriptURL,
276 const WorkerType& aWorkerType,
277 const RequestCredentials& aCredentials,
278 WorkerLoadInfo& aLoadInfo)
279 : WorkerMainThreadRunnable(aParentWorker,
280 "ScriptLoader :: ChannelGetter"_ns),
281 mScriptURL(aScriptURL)
282 // ClientInfo should always be present since this should not be called
283 // if parent's status is greater than Running.
285 mWorkerType(aWorkerType),
286 mCredentials(aCredentials),
287 mClientInfo(aParentWorker->GlobalScope()->GetClientInfo().ref()),
288 mLoadInfo(aLoadInfo),
289 mResult(NS_ERROR_FAILURE) {
290 MOZ_ASSERT(aParentWorker);
291 aParentWorker->AssertIsOnWorkerThread();
294 virtual bool MainThreadRun() override {
295 AssertIsOnMainThread();
297 // Initialize the WorkerLoadInfo principal to our triggering principal
298 // before doing anything else. Normally we do this in the WorkerPrivate
299 // Constructor, but we can't do so off the main thread when creating
300 // a nested worker. So do it here instead.
301 mLoadInfo.mLoadingPrincipal = mWorkerPrivate->GetPrincipal();
302 MOZ_DIAGNOSTIC_ASSERT(mLoadInfo.mLoadingPrincipal);
304 mLoadInfo.mPrincipal = mLoadInfo.mLoadingPrincipal;
306 // Figure out our base URI.
307 nsCOMPtr<nsIURI> baseURI = mWorkerPrivate->GetBaseURI();
308 MOZ_ASSERT(baseURI);
310 // May be null.
311 nsCOMPtr<Document> parentDoc = mWorkerPrivate->GetDocument();
313 mLoadInfo.mLoadGroup = mWorkerPrivate->GetLoadGroup();
314 mLoadInfo.mCookieJarSettings = mWorkerPrivate->CookieJarSettings();
316 // Nested workers use default uri encoding.
317 nsCOMPtr<nsIURI> url;
318 mResult = ConstructURI(mScriptURL, baseURI, nullptr, getter_AddRefs(url));
319 NS_ENSURE_SUCCESS(mResult, true);
321 Maybe<ClientInfo> clientInfo;
322 clientInfo.emplace(mClientInfo);
324 nsCOMPtr<nsIChannel> channel;
325 nsCOMPtr<nsIReferrerInfo> referrerInfo =
326 ReferrerInfo::CreateForFetch(mLoadInfo.mLoadingPrincipal, nullptr);
327 mLoadInfo.mReferrerInfo =
328 static_cast<ReferrerInfo*>(referrerInfo.get())
329 ->CloneWithNewPolicy(mWorkerPrivate->GetReferrerPolicy());
331 mResult = workerinternals::ChannelFromScriptURLMainThread(
332 mLoadInfo.mLoadingPrincipal, parentDoc, mLoadInfo.mLoadGroup, url,
333 mWorkerType, mCredentials, clientInfo,
334 // Nested workers are always dedicated.
335 nsIContentPolicy::TYPE_INTERNAL_WORKER, mLoadInfo.mCookieJarSettings,
336 mLoadInfo.mReferrerInfo, getter_AddRefs(channel));
337 NS_ENSURE_SUCCESS(mResult, true);
339 mResult = mLoadInfo.SetPrincipalsAndCSPFromChannel(channel);
340 NS_ENSURE_SUCCESS(mResult, true);
342 mLoadInfo.mChannel = std::move(channel);
343 return true;
346 nsresult GetResult() const { return mResult; }
348 private:
349 virtual ~ChannelGetterRunnable() = default;
352 nsresult GetCommonSecFlags(bool aIsMainScript, nsIURI* uri,
353 nsIPrincipal* principal,
354 WorkerScriptType aWorkerScriptType,
355 uint32_t& secFlags) {
356 bool inheritAttrs = nsContentUtils::ChannelShouldInheritPrincipal(
357 principal, uri, true /* aInheritForAboutBlank */,
358 false /* aForceInherit */);
360 bool isData = uri->SchemeIs("data");
361 if (inheritAttrs && !isData) {
362 secFlags |= nsILoadInfo::SEC_FORCE_INHERIT_PRINCIPAL;
365 if (aWorkerScriptType == DebuggerScript) {
366 // A DebuggerScript needs to be a local resource like chrome: or resource:
367 bool isUIResource = false;
368 nsresult rv = NS_URIChainHasFlags(
369 uri, nsIProtocolHandler::URI_IS_UI_RESOURCE, &isUIResource);
370 if (NS_WARN_IF(NS_FAILED(rv))) {
371 return rv;
374 if (!isUIResource) {
375 return NS_ERROR_DOM_SECURITY_ERR;
378 secFlags |= nsILoadInfo::SEC_ALLOW_CHROME;
381 // Note: this is for backwards compatibility and goes against spec.
382 // We should find a better solution.
383 if (aIsMainScript && isData) {
384 secFlags = nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL;
387 return NS_OK;
390 nsresult GetModuleSecFlags(bool aIsTopLevel, nsIPrincipal* principal,
391 WorkerScriptType aWorkerScriptType, nsIURI* aURI,
392 RequestCredentials aCredentials,
393 uint32_t& secFlags) {
394 // Implements "To fetch a single module script,"
395 // Step 9. If destination is "worker", "sharedworker", or "serviceworker",
396 // and the top-level module fetch flag is set, then set request's
397 // mode to "same-origin".
398 secFlags = aIsTopLevel
399 ? nsILoadInfo::SEC_REQUIRE_SAME_ORIGIN_DATA_IS_BLOCKED
400 : nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_INHERITS_SEC_CONTEXT;
402 // Step 8. Let request be a new request whose [...] mode is "cors" [...]
403 // This implements the same Cookie settings as nsContentSecurityManager's
404 // ComputeSecurityFlags. The main difference is the line above, Step 9,
405 // setting to same origin.
406 if (aCredentials == RequestCredentials::Include) {
407 secFlags |= nsILoadInfo::nsILoadInfo::SEC_COOKIES_INCLUDE;
408 } else if (aCredentials == RequestCredentials::Same_origin) {
409 secFlags |= nsILoadInfo::nsILoadInfo::SEC_COOKIES_SAME_ORIGIN;
410 } else if (aCredentials == RequestCredentials::Omit) {
411 secFlags |= nsILoadInfo::nsILoadInfo::SEC_COOKIES_OMIT;
414 return GetCommonSecFlags(aIsTopLevel, aURI, principal, aWorkerScriptType,
415 secFlags);
418 nsresult GetClassicSecFlags(bool aIsMainScript, nsIURI* uri,
419 nsIPrincipal* principal,
420 WorkerScriptType aWorkerScriptType,
421 uint32_t& secFlags) {
422 secFlags = aIsMainScript
423 ? nsILoadInfo::SEC_REQUIRE_SAME_ORIGIN_DATA_IS_BLOCKED
424 : nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_INHERITS_SEC_CONTEXT;
426 return GetCommonSecFlags(aIsMainScript, uri, principal, aWorkerScriptType,
427 secFlags);
430 } // anonymous namespace
432 namespace loader {
434 class ScriptExecutorRunnable final : public MainThreadWorkerSyncRunnable {
435 RefPtr<WorkerScriptLoader> mScriptLoader;
436 const Span<RefPtr<ThreadSafeRequestHandle>> mLoadedRequests;
438 public:
439 ScriptExecutorRunnable(WorkerScriptLoader* aScriptLoader,
440 WorkerPrivate* aWorkerPrivate,
441 nsISerialEventTarget* aSyncLoopTarget,
442 Span<RefPtr<ThreadSafeRequestHandle>> aLoadedRequests);
444 private:
445 ~ScriptExecutorRunnable() = default;
447 virtual bool IsDebuggerRunnable() const override;
449 virtual bool PreRun(WorkerPrivate* aWorkerPrivate) override;
451 bool ProcessModuleScript(JSContext* aCx, WorkerPrivate* aWorkerPrivate);
453 bool ProcessClassicScripts(JSContext* aCx, WorkerPrivate* aWorkerPrivate);
455 virtual bool WorkerRun(JSContext* aCx,
456 WorkerPrivate* aWorkerPrivate) override;
458 nsresult Cancel() override;
461 template <typename Unit>
462 static bool EvaluateSourceBuffer(JSContext* aCx,
463 const JS::CompileOptions& aOptions,
464 JS::loader::ClassicScript* aClassicScript,
465 JS::SourceText<Unit>& aSourceBuffer) {
466 static_assert(std::is_same<Unit, char16_t>::value ||
467 std::is_same<Unit, Utf8Unit>::value,
468 "inferred units must be UTF-8 or UTF-16");
470 JS::Rooted<JSScript*> script(aCx, JS::Compile(aCx, aOptions, aSourceBuffer));
472 if (!script) {
473 return false;
476 if (aClassicScript) {
477 aClassicScript->AssociateWithScript(script);
480 JS::Rooted<JS::Value> unused(aCx);
481 return JS_ExecuteScript(aCx, script, &unused);
484 WorkerScriptLoader::WorkerScriptLoader(
485 WorkerPrivate* aWorkerPrivate,
486 UniquePtr<SerializedStackHolder> aOriginStack,
487 nsISerialEventTarget* aSyncLoopTarget, WorkerScriptType aWorkerScriptType,
488 ErrorResult& aRv)
489 : mOriginStack(std::move(aOriginStack)),
490 mSyncLoopTarget(aSyncLoopTarget),
491 mWorkerScriptType(aWorkerScriptType),
492 mRv(aRv),
493 mLoadingModuleRequestCount(0),
494 mCleanedUp(false),
495 mCleanUpLock("cleanUpLock") {
496 aWorkerPrivate->AssertIsOnWorkerThread();
498 RefPtr<StrongWorkerRef> workerRef =
499 StrongWorkerRef::Create(aWorkerPrivate, "ScriptLoader");
501 if (workerRef) {
502 mWorkerRef = new ThreadSafeWorkerRef(workerRef);
503 } else {
504 mRv.Throw(NS_ERROR_FAILURE);
505 return;
508 nsIGlobalObject* global = GetGlobal();
509 mController = global->GetController();
511 if (!StaticPrefs::dom_workers_modules_enabled()) {
512 return;
515 // Set up the module loader, if it has not been initialzied yet.
516 if (!aWorkerPrivate->IsServiceWorker()) {
517 InitModuleLoader();
521 ScriptLoadRequest* WorkerScriptLoader::GetMainScript() {
522 mWorkerRef->Private()->AssertIsOnWorkerThread();
523 ScriptLoadRequest* request = mLoadingRequests.getFirst();
524 if (request->GetWorkerLoadContext()->IsTopLevel()) {
525 return request;
527 return nullptr;
530 void WorkerScriptLoader::InitModuleLoader() {
531 mWorkerRef->Private()->AssertIsOnWorkerThread();
532 if (GetGlobal()->GetModuleLoader(nullptr)) {
533 return;
535 RefPtr<WorkerModuleLoader> moduleLoader =
536 new WorkerModuleLoader(this, GetGlobal(), mSyncLoopTarget.get());
537 if (mWorkerScriptType == WorkerScript) {
538 mWorkerRef->Private()->GlobalScope()->InitModuleLoader(moduleLoader);
539 return;
541 mWorkerRef->Private()->DebuggerGlobalScope()->InitModuleLoader(moduleLoader);
544 bool WorkerScriptLoader::CreateScriptRequests(
545 const nsTArray<nsString>& aScriptURLs,
546 const mozilla::Encoding* aDocumentEncoding, bool aIsMainScript) {
547 mWorkerRef->Private()->AssertIsOnWorkerThread();
548 // If a worker has been loaded as a module worker, ImportScripts calls are
549 // disallowed -- then the operation is invalid.
551 // 10.3.1 Importing scripts and libraries.
552 // Step 1. If worker global scope's type is "module", throw a TypeError
553 // exception.
555 // Also, for now, the debugger script is always loaded as Classic,
556 // even if the debugged worker is a Module. We still want to allow
557 // it to use importScripts.
558 if (mWorkerRef->Private()->WorkerType() == WorkerType::Module &&
559 !aIsMainScript && !IsDebuggerScript()) {
560 // This should only run for non-main scripts, as only these are
561 // importScripts
562 mRv.ThrowTypeError(
563 "Using `ImportScripts` inside a Module Worker is "
564 "disallowed.");
565 return false;
567 for (const nsString& scriptURL : aScriptURLs) {
568 RefPtr<ScriptLoadRequest> request =
569 CreateScriptLoadRequest(scriptURL, aDocumentEncoding, aIsMainScript);
570 if (!request) {
571 return false;
573 mLoadingRequests.AppendElement(request);
576 return true;
579 nsTArray<RefPtr<ThreadSafeRequestHandle>> WorkerScriptLoader::GetLoadingList() {
580 mWorkerRef->Private()->AssertIsOnWorkerThread();
581 nsTArray<RefPtr<ThreadSafeRequestHandle>> list;
582 for (ScriptLoadRequest* req = mLoadingRequests.getFirst(); req;
583 req = req->getNext()) {
584 RefPtr<ThreadSafeRequestHandle> handle =
585 new ThreadSafeRequestHandle(req, mSyncLoopTarget.get());
586 list.AppendElement(handle.forget());
588 return list;
591 bool WorkerScriptLoader::IsDynamicImport(ScriptLoadRequest* aRequest) {
592 return aRequest->IsModuleRequest() &&
593 aRequest->AsModuleRequest()->IsDynamicImport();
596 nsContentPolicyType WorkerScriptLoader::GetContentPolicyType(
597 ScriptLoadRequest* aRequest) {
598 if (aRequest->GetWorkerLoadContext()->IsTopLevel()) {
599 // Implements https://html.spec.whatwg.org/#worker-processing-model
600 // Step 13: Let destination be "sharedworker" if is shared is true, and
601 // "worker" otherwise.
602 return mWorkerRef->Private()->ContentPolicyType();
604 if (aRequest->IsModuleRequest()) {
605 // Implements the destination for Step 14 in
606 // https://html.spec.whatwg.org/#worker-processing-model
608 // We need a special subresource type in order to correctly implement
609 // the graph fetch, where the destination is set to "worker" or
610 // "sharedworker".
611 return nsIContentPolicy::TYPE_INTERNAL_WORKER_STATIC_MODULE;
613 return nsIContentPolicy::TYPE_INTERNAL_WORKER_IMPORT_SCRIPTS;
616 already_AddRefed<ScriptLoadRequest> WorkerScriptLoader::CreateScriptLoadRequest(
617 const nsString& aScriptURL, const mozilla::Encoding* aDocumentEncoding,
618 bool aIsMainScript) {
619 mWorkerRef->Private()->AssertIsOnWorkerThread();
620 WorkerLoadContext::Kind kind =
621 WorkerLoadContext::GetKind(aIsMainScript, IsDebuggerScript());
623 Maybe<ClientInfo> clientInfo = GetGlobal()->GetClientInfo();
625 RefPtr<WorkerLoadContext> loadContext =
626 new WorkerLoadContext(kind, clientInfo, this);
628 // Create ScriptLoadRequests for this WorkerScriptLoader
629 ReferrerPolicy referrerPolicy = mWorkerRef->Private()->GetReferrerPolicy();
631 // Only top level workers' main script use the document charset for the
632 // script uri encoding. Otherwise, default encoding (UTF-8) is applied.
633 MOZ_ASSERT_IF(bool(aDocumentEncoding),
634 aIsMainScript && !mWorkerRef->Private()->GetParent());
635 nsCOMPtr<nsIURI> baseURI = aIsMainScript ? GetInitialBaseURI() : GetBaseURI();
636 nsCOMPtr<nsIURI> uri;
637 nsresult rv =
638 ConstructURI(aScriptURL, baseURI, aDocumentEncoding, getter_AddRefs(uri));
639 // If we failed to construct the URI, handle it in the LoadContext so it is
640 // thrown in the right order.
641 if (NS_WARN_IF(NS_FAILED(rv))) {
642 loadContext->mLoadResult = rv;
645 // https://html.spec.whatwg.org/multipage/webappapis.html#fetch-a-classic-worker-script
646 // Step 2.5. Let script be the result [...] and the default classic script
647 // fetch options.
649 // https://html.spec.whatwg.org/multipage/webappapis.html#fetch-a-worklet/module-worker-script-graph
650 // Step 1. Let options be a script fetch options whose cryptographic nonce is
651 // the empty string, integrity metadata is the empty string, parser metadata
652 // is "not-parser-inserted", credentials mode is credentials mode, referrer
653 // policy is the empty string, and fetch priority is "auto".
654 RefPtr<ScriptFetchOptions> fetchOptions = new ScriptFetchOptions(
655 CORSMode::CORS_NONE, referrerPolicy, /* aNonce = */ u""_ns,
656 ParserMetadata::NotParserInserted, nullptr);
658 RefPtr<ScriptLoadRequest> request = nullptr;
659 // Bug 1817259 - For now the debugger scripts are always loaded a Classic.
660 if (mWorkerRef->Private()->WorkerType() == WorkerType::Classic ||
661 IsDebuggerScript()) {
662 request = new ScriptLoadRequest(ScriptKind::eClassic, uri, fetchOptions,
663 SRIMetadata(), nullptr, // mReferrer
664 loadContext);
665 } else {
666 // Implements part of "To fetch a worklet/module worker script graph"
667 // including, setting up the request with a credentials mode,
668 // destination.
670 // Step 1. Let options be a script fetch options.
671 // We currently don't track credentials in our ScriptFetchOptions
672 // implementation, so we are defaulting the fetchOptions object defined
673 // above. This behavior is handled fully in GetModuleSecFlags.
675 if (!StaticPrefs::dom_workers_modules_enabled()) {
676 mRv.ThrowTypeError("Modules in workers are currently disallowed.");
677 return nullptr;
679 RefPtr<WorkerModuleLoader::ModuleLoaderBase> moduleLoader =
680 GetGlobal()->GetModuleLoader(nullptr);
682 // Implements the referrer for "To fetch a single module script"
683 // Our implementation does not have a "client" as a referrer.
684 // However, when client is resolved (per 8.3. Determine request’s
685 // Referrer in
686 // https://w3c.github.io/webappsec-referrer-policy/#determine-requests-referrer)
687 // This should result in the referrer source being the creation URL.
689 // In subresource modules, the referrer is the importing script.
690 nsCOMPtr<nsIURI> referrer =
691 mWorkerRef->Private()->GetReferrerInfo()->GetOriginalReferrer();
693 // Part of Step 2. This sets the Top-level flag to true
694 request = new ModuleLoadRequest(
695 uri, fetchOptions, SRIMetadata(), referrer, loadContext,
696 true, /* is top level */
697 false, /* is dynamic import */
698 moduleLoader, ModuleLoadRequest::NewVisitedSetForTopLevelImport(uri),
699 nullptr);
702 // Set the mURL, it will be used for error handling and debugging.
703 request->mURL = NS_ConvertUTF16toUTF8(aScriptURL);
705 return request.forget();
708 bool WorkerScriptLoader::DispatchLoadScript(ScriptLoadRequest* aRequest) {
709 mWorkerRef->Private()->AssertIsOnWorkerThread();
711 IncreaseLoadingModuleRequestCount();
713 nsTArray<RefPtr<ThreadSafeRequestHandle>> scriptLoadList;
714 RefPtr<ThreadSafeRequestHandle> handle =
715 new ThreadSafeRequestHandle(aRequest, mSyncLoopTarget.get());
716 scriptLoadList.AppendElement(handle.forget());
718 RefPtr<ScriptLoaderRunnable> runnable =
719 new ScriptLoaderRunnable(this, std::move(scriptLoadList));
721 RefPtr<StrongWorkerRef> workerRef = StrongWorkerRef::Create(
722 mWorkerRef->Private(), "ScriptLoader", [runnable]() {
723 NS_DispatchToMainThread(NewRunnableMethod(
724 "ScriptLoaderRunnable::CancelMainThreadWithBindingAborted",
725 runnable,
726 &ScriptLoaderRunnable::CancelMainThreadWithBindingAborted));
729 if (NS_FAILED(NS_DispatchToMainThread(runnable))) {
730 NS_ERROR("Failed to dispatch!");
731 mRv.Throw(NS_ERROR_FAILURE);
732 return false;
734 return true;
737 bool WorkerScriptLoader::DispatchLoadScripts() {
738 mWorkerRef->Private()->AssertIsOnWorkerThread();
740 nsTArray<RefPtr<ThreadSafeRequestHandle>> scriptLoadList = GetLoadingList();
742 RefPtr<ScriptLoaderRunnable> runnable =
743 new ScriptLoaderRunnable(this, std::move(scriptLoadList));
745 RefPtr<StrongWorkerRef> workerRef = StrongWorkerRef::Create(
746 mWorkerRef->Private(), "ScriptLoader", [runnable]() {
747 NS_DispatchToMainThread(NewRunnableMethod(
748 "ScriptLoaderRunnable::CancelMainThreadWithBindingAborted",
749 runnable,
750 &ScriptLoaderRunnable::CancelMainThreadWithBindingAborted));
753 if (NS_FAILED(NS_DispatchToMainThread(runnable))) {
754 NS_ERROR("Failed to dispatch!");
755 mRv.Throw(NS_ERROR_FAILURE);
756 return false;
758 return true;
761 nsIURI* WorkerScriptLoader::GetInitialBaseURI() {
762 MOZ_ASSERT(mWorkerRef->Private());
763 nsIURI* baseURI;
764 WorkerPrivate* parentWorker = mWorkerRef->Private()->GetParent();
765 if (parentWorker) {
766 baseURI = parentWorker->GetBaseURI();
767 } else {
768 // May be null.
769 baseURI = mWorkerRef->Private()->GetBaseURI();
772 return baseURI;
775 nsIURI* WorkerScriptLoader::GetBaseURI() const {
776 MOZ_ASSERT(mWorkerRef);
777 nsIURI* baseURI;
778 baseURI = mWorkerRef->Private()->GetBaseURI();
779 NS_ASSERTION(baseURI, "Should have been set already!");
781 return baseURI;
784 nsIGlobalObject* WorkerScriptLoader::GetGlobal() {
785 mWorkerRef->Private()->AssertIsOnWorkerThread();
786 return mWorkerScriptType == WorkerScript
787 ? static_cast<nsIGlobalObject*>(
788 mWorkerRef->Private()->GlobalScope())
789 : mWorkerRef->Private()->DebuggerGlobalScope();
792 void WorkerScriptLoader::MaybeMoveToLoadedList(ScriptLoadRequest* aRequest) {
793 mWorkerRef->Private()->AssertIsOnWorkerThread();
794 // Only set to ready for regular scripts. Module loader will set the script to
795 // ready if it is a Module Request.
796 if (!aRequest->IsModuleRequest()) {
797 aRequest->SetReady();
800 // If the request is not in a list, we are in an illegal state.
801 MOZ_RELEASE_ASSERT(aRequest->isInList());
803 while (!mLoadingRequests.isEmpty()) {
804 ScriptLoadRequest* request = mLoadingRequests.getFirst();
805 // We need to move requests in post order. If prior requests have not
806 // completed, delay execution.
807 if (!request->IsFinished()) {
808 break;
811 RefPtr<ScriptLoadRequest> req = mLoadingRequests.Steal(request);
812 mLoadedRequests.AppendElement(req);
816 bool WorkerScriptLoader::StoreCSP() {
817 // We must be on the same worker as we started on.
818 mWorkerRef->Private()->AssertIsOnWorkerThread();
820 if (!mWorkerRef->Private()->GetJSContext()) {
821 return false;
824 MOZ_ASSERT(!mRv.Failed());
826 // Move the CSP from the workerLoadInfo in the corresponding Client
827 // where the CSP code expects it!
828 mWorkerRef->Private()->StoreCSPOnClient();
829 return true;
832 bool WorkerScriptLoader::ProcessPendingRequests(JSContext* aCx) {
833 mWorkerRef->Private()->AssertIsOnWorkerThread();
834 // Don't run if something else has already failed.
835 if (mExecutionAborted) {
836 mLoadedRequests.CancelRequestsAndClear();
837 TryShutdown();
838 return true;
841 // If nothing else has failed, our ErrorResult better not be a failure
842 // either.
843 MOZ_ASSERT(!mRv.Failed(), "Who failed it and why?");
845 // Slightly icky action at a distance, but there's no better place to stash
846 // this value, really.
847 JS::Rooted<JSObject*> global(aCx, JS::CurrentGlobalOrNull(aCx));
848 MOZ_ASSERT(global);
850 while (!mLoadedRequests.isEmpty()) {
851 // Take a reference, but do not remove it from the list yet. There is a
852 // possibility that this will need to be cancelled.
853 RefPtr<ScriptLoadRequest> req = mLoadedRequests.getFirst();
854 // We don't have a ProcessRequest method (like we do on the DOM), as there
855 // isn't much processing that we need to do per request that isn't related
856 // to evaluation (the processsing done for the DOM is handled in
857 // DataRecievedFrom{Cache,Network} for workers.
858 // So, this inner loop calls EvaluateScript directly. This will change
859 // once modules are introduced as we will have some extra work to do.
860 if (!EvaluateScript(aCx, req)) {
861 mExecutionAborted = true;
862 WorkerLoadContext* loadContext = req->GetWorkerLoadContext();
863 mMutedErrorFlag = loadContext->mMutedErrorFlag.valueOr(true);
864 mLoadedRequests.CancelRequestsAndClear();
865 break;
867 // remove the element from the list.
868 mLoadedRequests.Remove(req);
871 TryShutdown();
872 return true;
875 nsresult WorkerScriptLoader::LoadScript(
876 ThreadSafeRequestHandle* aRequestHandle) {
877 AssertIsOnMainThread();
879 WorkerLoadContext* loadContext = aRequestHandle->GetContext();
880 ScriptLoadRequest* request = aRequestHandle->GetRequest();
881 MOZ_ASSERT_IF(loadContext->IsTopLevel(), !IsDebuggerScript());
883 // The URL passed to us for loading was invalid, stop loading at this point.
884 if (loadContext->mLoadResult != NS_ERROR_NOT_INITIALIZED) {
885 return loadContext->mLoadResult;
888 WorkerPrivate* parentWorker = mWorkerRef->Private()->GetParent();
890 // For JavaScript debugging, the devtools server must run on the same
891 // thread as the debuggee, indicating the worker uses content principal.
892 // However, in Bug 863246, web content will no longer be able to load
893 // resource:// URIs by default, so we need system principal to load
894 // debugger scripts.
895 nsIPrincipal* principal = (IsDebuggerScript())
896 ? nsContentUtils::GetSystemPrincipal()
897 : mWorkerRef->Private()->GetPrincipal();
899 nsCOMPtr<nsILoadGroup> loadGroup = mWorkerRef->Private()->GetLoadGroup();
900 MOZ_DIAGNOSTIC_ASSERT(principal);
902 NS_ENSURE_TRUE(NS_LoadGroupMatchesPrincipal(loadGroup, principal),
903 NS_ERROR_FAILURE);
905 // May be null.
906 nsCOMPtr<Document> parentDoc = mWorkerRef->Private()->GetDocument();
908 nsCOMPtr<nsIChannel> channel;
909 if (loadContext->IsTopLevel()) {
910 // May be null.
911 channel = mWorkerRef->Private()->ForgetWorkerChannel();
914 nsCOMPtr<nsIIOService> ios(do_GetIOService());
916 nsIScriptSecurityManager* secMan = nsContentUtils::GetSecurityManager();
917 NS_ASSERTION(secMan, "This should never be null!");
919 nsresult& rv = loadContext->mLoadResult;
921 nsLoadFlags loadFlags = mWorkerRef->Private()->GetLoadFlags();
923 // Get the top-level worker.
924 WorkerPrivate* topWorkerPrivate = mWorkerRef->Private();
925 WorkerPrivate* parent = topWorkerPrivate->GetParent();
926 while (parent) {
927 topWorkerPrivate = parent;
928 parent = topWorkerPrivate->GetParent();
931 // If the top-level worker is a dedicated worker and has a window, and the
932 // window has a docshell, the caching behavior of this worker should match
933 // that of that docshell.
934 if (topWorkerPrivate->IsDedicatedWorker()) {
935 nsCOMPtr<nsPIDOMWindowInner> window = topWorkerPrivate->GetWindow();
936 if (window) {
937 nsCOMPtr<nsIDocShell> docShell = window->GetDocShell();
938 if (docShell) {
939 nsresult rv = docShell->GetDefaultLoadFlags(&loadFlags);
940 NS_ENSURE_SUCCESS(rv, rv);
945 if (!channel) {
946 nsCOMPtr<nsIReferrerInfo> referrerInfo;
947 uint32_t secFlags;
948 if (request->IsModuleRequest()) {
949 // https://fetch.spec.whatwg.org/#concept-main-fetch
950 // Step 8. If request’s referrer policy is the empty string, then set
951 // request’s referrer policy to request’s policy container’s
952 // referrer policy.
953 ReferrerPolicy policy =
954 request->ReferrerPolicy() == ReferrerPolicy::_empty
955 ? mWorkerRef->Private()->GetReferrerPolicy()
956 : request->ReferrerPolicy();
958 referrerInfo = new ReferrerInfo(request->mReferrer, policy);
960 // https://html.spec.whatwg.org/multipage/webappapis.html#default-classic-script-fetch-options
961 // The default classic script fetch options are a script fetch options
962 // whose ... credentials mode is "same-origin", ....
963 RequestCredentials credentials =
964 mWorkerRef->Private()->WorkerType() == WorkerType::Classic
965 ? RequestCredentials::Same_origin
966 : mWorkerRef->Private()->WorkerCredentials();
968 rv = GetModuleSecFlags(loadContext->IsTopLevel(), principal,
969 mWorkerScriptType, request->mURI, credentials,
970 secFlags);
971 } else {
972 referrerInfo = ReferrerInfo::CreateForFetch(principal, nullptr);
973 if (parentWorker && !loadContext->IsTopLevel()) {
974 referrerInfo =
975 static_cast<ReferrerInfo*>(referrerInfo.get())
976 ->CloneWithNewPolicy(parentWorker->GetReferrerPolicy());
978 rv = GetClassicSecFlags(loadContext->IsTopLevel(), request->mURI,
979 principal, mWorkerScriptType, secFlags);
982 if (NS_WARN_IF(NS_FAILED(rv))) {
983 return rv;
986 nsContentPolicyType contentPolicyType = GetContentPolicyType(request);
988 rv = ChannelFromScriptURL(
989 principal, parentDoc, mWorkerRef->Private(), loadGroup, ios, secMan,
990 request->mURI, loadContext->mClientInfo, mController,
991 loadContext->IsTopLevel(), mWorkerScriptType, contentPolicyType,
992 loadFlags, secFlags, mWorkerRef->Private()->CookieJarSettings(),
993 referrerInfo, getter_AddRefs(channel));
994 if (NS_WARN_IF(NS_FAILED(rv))) {
995 return rv;
999 // Associate any originating stack with the channel.
1000 if (!mOriginStackJSON.IsEmpty()) {
1001 NotifyNetworkMonitorAlternateStack(channel, mOriginStackJSON);
1004 // We need to know which index we're on in OnStreamComplete so we know
1005 // where to put the result.
1006 RefPtr<NetworkLoadHandler> listener =
1007 new NetworkLoadHandler(this, aRequestHandle);
1009 RefPtr<ScriptResponseHeaderProcessor> headerProcessor = nullptr;
1011 // For each debugger script, a non-debugger script load of the same script
1012 // should have occured prior that processed the headers.
1013 if (!IsDebuggerScript()) {
1014 headerProcessor = MakeRefPtr<ScriptResponseHeaderProcessor>(
1015 mWorkerRef->Private(),
1016 loadContext->IsTopLevel() && !IsDynamicImport(request),
1017 GetContentPolicyType(request) ==
1018 nsIContentPolicy::TYPE_INTERNAL_WORKER_IMPORT_SCRIPTS);
1021 nsCOMPtr<nsIStreamLoader> loader;
1022 rv = NS_NewStreamLoader(getter_AddRefs(loader), listener, headerProcessor);
1023 if (NS_WARN_IF(NS_FAILED(rv))) {
1024 return rv;
1027 if (loadContext->IsTopLevel()) {
1028 MOZ_DIAGNOSTIC_ASSERT(loadContext->mClientInfo.isSome());
1030 // In order to get the correct foreign partitioned prinicpal, we need to
1031 // set the `IsThirdPartyContextToTopWindow` to the channel's loadInfo.
1032 // This flag reflects the fact that if the worker is created under a
1033 // third-party context.
1034 nsCOMPtr<nsILoadInfo> loadInfo = channel->LoadInfo();
1035 loadInfo->SetIsThirdPartyContextToTopWindow(
1036 mWorkerRef->Private()->IsThirdPartyContextToTopWindow());
1038 Maybe<ClientInfo> clientInfo;
1039 clientInfo.emplace(loadContext->mClientInfo.ref());
1040 rv = AddClientChannelHelper(channel, std::move(clientInfo),
1041 Maybe<ClientInfo>(),
1042 mWorkerRef->Private()->HybridEventTarget());
1043 if (NS_WARN_IF(NS_FAILED(rv))) {
1044 return rv;
1048 if (StaticPrefs::browser_tabs_remote_useCrossOriginEmbedderPolicy()) {
1049 nsILoadInfo::CrossOriginEmbedderPolicy respectedCOEP =
1050 mWorkerRef->Private()->GetEmbedderPolicy();
1051 if (mWorkerRef->Private()->IsDedicatedWorker() &&
1052 respectedCOEP == nsILoadInfo::EMBEDDER_POLICY_NULL) {
1053 respectedCOEP = mWorkerRef->Private()->GetOwnerEmbedderPolicy();
1056 nsCOMPtr<nsILoadInfo> channelLoadInfo = channel->LoadInfo();
1057 channelLoadInfo->SetLoadingEmbedderPolicy(respectedCOEP);
1060 if (loadContext->mCacheStatus != WorkerLoadContext::ToBeCached) {
1061 rv = channel->AsyncOpen(loader);
1062 if (NS_WARN_IF(NS_FAILED(rv))) {
1063 return rv;
1065 } else {
1066 nsCOMPtr<nsIOutputStream> writer;
1068 // In case we return early.
1069 loadContext->mCacheStatus = WorkerLoadContext::Cancel;
1071 NS_NewPipe(getter_AddRefs(loadContext->mCacheReadStream),
1072 getter_AddRefs(writer), 0,
1073 UINT32_MAX, // unlimited size to avoid writer WOULD_BLOCK case
1074 true, false); // non-blocking reader, blocking writer
1076 nsCOMPtr<nsIStreamListenerTee> tee =
1077 do_CreateInstance(NS_STREAMLISTENERTEE_CONTRACTID);
1078 rv = tee->Init(loader, writer, listener);
1079 if (NS_WARN_IF(NS_FAILED(rv))) {
1080 return rv;
1083 nsresult rv = channel->AsyncOpen(tee);
1084 if (NS_WARN_IF(NS_FAILED(rv))) {
1085 return rv;
1089 loadContext->mChannel.swap(channel);
1091 return NS_OK;
1094 nsresult WorkerScriptLoader::FillCompileOptionsForRequest(
1095 JSContext* cx, ScriptLoadRequest* aRequest, JS::CompileOptions* aOptions,
1096 JS::MutableHandle<JSScript*> aIntroductionScript) {
1097 // The full URL shouldn't be exposed to the debugger. See Bug 1634872
1098 aOptions->setFileAndLine(aRequest->mURL.get(), 1);
1099 aOptions->setNoScriptRval(true);
1101 aOptions->setMutedErrors(
1102 aRequest->GetWorkerLoadContext()->mMutedErrorFlag.value());
1104 if (aRequest->mSourceMapURL) {
1105 aOptions->setSourceMapURL(aRequest->mSourceMapURL->get());
1108 return NS_OK;
1111 bool WorkerScriptLoader::EvaluateScript(JSContext* aCx,
1112 ScriptLoadRequest* aRequest) {
1113 mWorkerRef->Private()->AssertIsOnWorkerThread();
1114 MOZ_ASSERT(!IsDynamicImport(aRequest));
1116 WorkerLoadContext* loadContext = aRequest->GetWorkerLoadContext();
1118 NS_ASSERTION(!loadContext->mChannel, "Should no longer have a channel!");
1119 NS_ASSERTION(aRequest->IsFinished(), "Should be scheduled!");
1121 MOZ_ASSERT(!mRv.Failed(), "Who failed it and why?");
1122 mRv.MightThrowJSException();
1123 if (NS_FAILED(loadContext->mLoadResult)) {
1124 ReportErrorToConsole(aRequest, loadContext->mLoadResult);
1125 return false;
1128 // If this is a top level script that succeeded, then mark the
1129 // Client execution ready and possible controlled by a service worker.
1130 if (loadContext->IsTopLevel()) {
1131 if (mController.isSome()) {
1132 MOZ_ASSERT(mWorkerScriptType == WorkerScript,
1133 "Debugger clients can't be controlled.");
1134 mWorkerRef->Private()->GlobalScope()->Control(mController.ref());
1136 mWorkerRef->Private()->ExecutionReady();
1139 if (aRequest->IsModuleRequest()) {
1140 // Only the top level module of the module graph will be executed from here,
1141 // the rest will be executed from SpiderMonkey as part of the execution of
1142 // the module graph.
1143 MOZ_ASSERT(aRequest->IsTopLevel());
1144 ModuleLoadRequest* request = aRequest->AsModuleRequest();
1145 if (!request->mModuleScript) {
1146 return false;
1149 // Implements To fetch a worklet/module worker script graph
1150 // Step 5. Fetch the descendants of and link result.
1151 if (!request->InstantiateModuleGraph()) {
1152 return false;
1155 nsresult rv = request->EvaluateModule();
1156 return NS_SUCCEEDED(rv);
1159 JS::CompileOptions options(aCx);
1160 // The introduction script is used by the DOM script loader as a way
1161 // to fill the Debugger Metadata for the JS Execution context. We don't use
1162 // the JS Execution context as we are not making use of async compilation
1163 // (delegation to another worker to produce bytecode or compile a string to a
1164 // JSScript), so it is not used in this context.
1165 JS::Rooted<JSScript*> unusedIntroductionScript(aCx);
1166 nsresult rv = FillCompileOptionsForRequest(aCx, aRequest, &options,
1167 &unusedIntroductionScript);
1169 MOZ_ASSERT(NS_SUCCEEDED(rv), "Filling compile options should not fail");
1171 // Our ErrorResult still shouldn't be a failure.
1172 MOZ_ASSERT(!mRv.Failed(), "Who failed it and why?");
1174 // Get the source text.
1175 ScriptLoadRequest::MaybeSourceText maybeSource;
1176 rv = aRequest->GetScriptSource(aCx, &maybeSource);
1177 if (NS_FAILED(rv)) {
1178 mRv.StealExceptionFromJSContext(aCx);
1179 return false;
1182 RefPtr<JS::loader::ClassicScript> classicScript = nullptr;
1183 if (StaticPrefs::dom_workers_modules_enabled() &&
1184 !mWorkerRef->Private()->IsServiceWorker()) {
1185 // We need a LoadedScript to be associated with the JSScript in order to
1186 // correctly resolve the referencing private for dynamic imports. In turn
1187 // this allows us to correctly resolve the BaseURL.
1189 // Dynamic import is disallowed on service workers. Additionally, causes
1190 // crashes because the life cycle isn't completed for service workers. To
1191 // keep things simple, we don't create a classic script for ServiceWorkers.
1192 // If this changes then we will need to ensure that the reference that is
1193 // held is released appropriately.
1194 nsCOMPtr<nsIURI> requestBaseURI;
1195 if (loadContext->mMutedErrorFlag.valueOr(false)) {
1196 NS_NewURI(getter_AddRefs(requestBaseURI), "about:blank"_ns);
1197 } else {
1198 requestBaseURI = aRequest->mBaseURL;
1200 classicScript =
1201 new JS::loader::ClassicScript(aRequest->mFetchOptions, requestBaseURI);
1204 bool successfullyEvaluated =
1205 aRequest->IsUTF8Text()
1206 ? EvaluateSourceBuffer(aCx, options, classicScript,
1207 maybeSource.ref<JS::SourceText<Utf8Unit>>())
1208 : EvaluateSourceBuffer(aCx, options, classicScript,
1209 maybeSource.ref<JS::SourceText<char16_t>>());
1211 if (aRequest->IsCanceled()) {
1212 return false;
1214 if (!successfullyEvaluated) {
1215 mRv.StealExceptionFromJSContext(aCx);
1216 return false;
1218 // steal the loadContext so that the cycle is broken and cycle collector can
1219 // collect the scriptLoadRequest.
1220 return true;
1223 void WorkerScriptLoader::TryShutdown() {
1225 MutexAutoLock lock(CleanUpLock());
1226 if (CleanedUp()) {
1227 return;
1231 if (AllScriptsExecuted() && AllModuleRequestsLoaded()) {
1232 ShutdownScriptLoader(!mExecutionAborted, mMutedErrorFlag);
1236 void WorkerScriptLoader::ShutdownScriptLoader(bool aResult, bool aMutedError) {
1237 MOZ_ASSERT(AllScriptsExecuted());
1238 MOZ_ASSERT(AllModuleRequestsLoaded());
1239 mWorkerRef->Private()->AssertIsOnWorkerThread();
1241 if (!aResult) {
1242 // At this point there are two possibilities:
1244 // 1) mRv.Failed(). In that case we just want to leave it
1245 // as-is, except if it has a JS exception and we need to mute JS
1246 // exceptions. In that case, we log the exception without firing any
1247 // events and then replace it on the ErrorResult with a NetworkError,
1248 // per spec.
1250 // 2) mRv succeeded. As far as I can tell, this can only
1251 // happen when loading the main worker script and
1252 // GetOrCreateGlobalScope() fails or if ScriptExecutorRunnable::Cancel
1253 // got called. Does it matter what we throw in this case? I'm not
1254 // sure...
1255 if (mRv.Failed()) {
1256 if (aMutedError && mRv.IsJSException()) {
1257 LogExceptionToConsole(mWorkerRef->Private()->GetJSContext(),
1258 mWorkerRef->Private());
1259 mRv.Throw(NS_ERROR_DOM_NETWORK_ERR);
1261 } else {
1262 mRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
1266 // Lock, shutdown, and cleanup state. After this the Loader is closed.
1268 MutexAutoLock lock(CleanUpLock());
1270 if (CleanedUp()) {
1271 return;
1274 mWorkerRef->Private()->AssertIsOnWorkerThread();
1275 // Module loader doesn't use sync loop for dynamic import
1276 if (mSyncLoopTarget) {
1277 mWorkerRef->Private()->StopSyncLoop(mSyncLoopTarget,
1278 aResult ? NS_OK : NS_ERROR_FAILURE);
1281 // Signal cleanup
1282 mCleanedUp = true;
1284 // Allow worker shutdown.
1285 mWorkerRef = nullptr;
1289 void WorkerScriptLoader::ReportErrorToConsole(ScriptLoadRequest* aRequest,
1290 nsresult aResult) const {
1291 nsAutoString url = NS_ConvertUTF8toUTF16(aRequest->mURL);
1292 workerinternals::ReportLoadError(mRv, aResult, url);
1295 void WorkerScriptLoader::LogExceptionToConsole(JSContext* aCx,
1296 WorkerPrivate* aWorkerPrivate) {
1297 aWorkerPrivate->AssertIsOnWorkerThread();
1299 MOZ_ASSERT(mRv.IsJSException());
1301 JS::Rooted<JS::Value> exn(aCx);
1302 if (!ToJSValue(aCx, std::move(mRv), &exn)) {
1303 return;
1306 // Now the exception state should all be in exn.
1307 MOZ_ASSERT(!JS_IsExceptionPending(aCx));
1308 MOZ_ASSERT(!mRv.Failed());
1310 JS::ExceptionStack exnStack(aCx, exn, nullptr);
1311 JS::ErrorReportBuilder report(aCx);
1312 if (!report.init(aCx, exnStack, JS::ErrorReportBuilder::WithSideEffects)) {
1313 JS_ClearPendingException(aCx);
1314 return;
1317 RefPtr<xpc::ErrorReport> xpcReport = new xpc::ErrorReport();
1318 xpcReport->Init(report.report(), report.toStringResult().c_str(),
1319 aWorkerPrivate->IsChromeWorker(), aWorkerPrivate->WindowID());
1321 RefPtr<AsyncErrorReporter> r = new AsyncErrorReporter(xpcReport);
1322 NS_DispatchToMainThread(r);
1325 bool WorkerScriptLoader::AllModuleRequestsLoaded() const {
1326 mWorkerRef->Private()->AssertIsOnWorkerThread();
1327 return mLoadingModuleRequestCount == 0;
1330 void WorkerScriptLoader::IncreaseLoadingModuleRequestCount() {
1331 mWorkerRef->Private()->AssertIsOnWorkerThread();
1332 ++mLoadingModuleRequestCount;
1335 void WorkerScriptLoader::DecreaseLoadingModuleRequestCount() {
1336 mWorkerRef->Private()->AssertIsOnWorkerThread();
1337 --mLoadingModuleRequestCount;
1340 NS_IMPL_ISUPPORTS(ScriptLoaderRunnable, nsIRunnable, nsINamed)
1342 NS_IMPL_ISUPPORTS(WorkerScriptLoader, nsINamed)
1344 ScriptLoaderRunnable::ScriptLoaderRunnable(
1345 WorkerScriptLoader* aScriptLoader,
1346 nsTArray<RefPtr<ThreadSafeRequestHandle>> aLoadingRequests)
1347 : mScriptLoader(aScriptLoader),
1348 mWorkerRef(aScriptLoader->mWorkerRef),
1349 mLoadingRequests(std::move(aLoadingRequests)),
1350 mCancelMainThread(Nothing()) {
1351 MOZ_ASSERT(aScriptLoader);
1354 nsresult ScriptLoaderRunnable::Run() {
1355 AssertIsOnMainThread();
1357 // Convert the origin stack to JSON (which must be done on the main
1358 // thread) explicitly, so that we can use the stack to notify the net
1359 // monitor about every script we load. We do this, rather than pass
1360 // the stack directly to the netmonitor, in order to be able to use this
1361 // for all subsequent scripts.
1362 if (mScriptLoader->mOriginStack &&
1363 mScriptLoader->mOriginStackJSON.IsEmpty()) {
1364 ConvertSerializedStackToJSON(std::move(mScriptLoader->mOriginStack),
1365 mScriptLoader->mOriginStackJSON);
1368 if (!mWorkerRef->Private()->IsServiceWorker() ||
1369 mScriptLoader->IsDebuggerScript()) {
1370 for (ThreadSafeRequestHandle* handle : mLoadingRequests) {
1371 handle->mRunnable = this;
1374 for (ThreadSafeRequestHandle* handle : mLoadingRequests) {
1375 nsresult rv = mScriptLoader->LoadScript(handle);
1376 if (NS_WARN_IF(NS_FAILED(rv))) {
1377 LoadingFinished(handle, rv);
1378 CancelMainThread(rv);
1379 return rv;
1383 return NS_OK;
1386 MOZ_ASSERT(!mCacheCreator);
1387 mCacheCreator = new CacheCreator(mWorkerRef->Private());
1389 for (ThreadSafeRequestHandle* handle : mLoadingRequests) {
1390 handle->mRunnable = this;
1391 WorkerLoadContext* loadContext = handle->GetContext();
1392 mCacheCreator->AddLoader(MakeNotNull<RefPtr<CacheLoadHandler>>(
1393 mWorkerRef, handle, loadContext->IsTopLevel(), mScriptLoader));
1396 // The worker may have a null principal on first load, but in that case its
1397 // parent definitely will have one.
1398 nsIPrincipal* principal = mWorkerRef->Private()->GetPrincipal();
1399 if (!principal) {
1400 WorkerPrivate* parentWorker = mWorkerRef->Private()->GetParent();
1401 MOZ_ASSERT(parentWorker, "Must have a parent!");
1402 principal = parentWorker->GetPrincipal();
1405 nsresult rv = mCacheCreator->Load(principal);
1406 if (NS_WARN_IF(NS_FAILED(rv))) {
1407 CancelMainThread(rv);
1408 return rv;
1411 return NS_OK;
1414 nsresult ScriptLoaderRunnable::OnStreamComplete(
1415 ThreadSafeRequestHandle* aRequestHandle, nsresult aStatus) {
1416 AssertIsOnMainThread();
1418 LoadingFinished(aRequestHandle, aStatus);
1419 return NS_OK;
1422 void ScriptLoaderRunnable::LoadingFinished(
1423 ThreadSafeRequestHandle* aRequestHandle, nsresult aRv) {
1424 AssertIsOnMainThread();
1426 WorkerLoadContext* loadContext = aRequestHandle->GetContext();
1428 loadContext->mLoadResult = aRv;
1429 MOZ_ASSERT(!loadContext->mLoadingFinished);
1430 loadContext->mLoadingFinished = true;
1432 if (loadContext->IsTopLevel() && NS_SUCCEEDED(aRv)) {
1433 MOZ_DIAGNOSTIC_ASSERT(
1434 mWorkerRef->Private()->PrincipalURIMatchesScriptURL());
1437 MaybeExecuteFinishedScripts(aRequestHandle);
1440 void ScriptLoaderRunnable::MaybeExecuteFinishedScripts(
1441 ThreadSafeRequestHandle* aRequestHandle) {
1442 AssertIsOnMainThread();
1444 // We execute the last step if we don't have a pending operation with the
1445 // cache and the loading is completed.
1446 WorkerLoadContext* loadContext = aRequestHandle->GetContext();
1447 if (!loadContext->IsAwaitingPromise()) {
1448 if (aRequestHandle->GetContext()->IsTopLevel()) {
1449 mWorkerRef->Private()->WorkerScriptLoaded();
1451 DispatchProcessPendingRequests();
1455 void ScriptLoaderRunnable::CancelMainThreadWithBindingAborted() {
1456 AssertIsOnMainThread();
1457 CancelMainThread(NS_BINDING_ABORTED);
1460 void ScriptLoaderRunnable::CancelMainThread(nsresult aCancelResult) {
1461 AssertIsOnMainThread();
1462 if (IsCancelled()) {
1463 return;
1467 MutexAutoLock lock(mScriptLoader->CleanUpLock());
1469 // Check if we have already cancelled, or if the worker has been killed
1470 // before we cancel.
1471 if (mScriptLoader->CleanedUp()) {
1472 return;
1475 mCancelMainThread = Some(aCancelResult);
1477 for (ThreadSafeRequestHandle* handle : mLoadingRequests) {
1478 if (handle->IsEmpty()) {
1479 continue;
1482 bool callLoadingFinished = true;
1484 WorkerLoadContext* loadContext = handle->GetContext();
1485 if (!loadContext) {
1486 continue;
1489 if (loadContext->IsAwaitingPromise()) {
1490 MOZ_ASSERT(mWorkerRef->Private()->IsServiceWorker());
1491 loadContext->mCachePromise->MaybeReject(NS_BINDING_ABORTED);
1492 loadContext->mCachePromise = nullptr;
1493 callLoadingFinished = false;
1495 if (loadContext->mChannel) {
1496 if (NS_SUCCEEDED(loadContext->mChannel->Cancel(aCancelResult))) {
1497 callLoadingFinished = false;
1498 } else {
1499 NS_WARNING("Failed to cancel channel!");
1502 if (callLoadingFinished && !loadContext->mLoadingFinished) {
1503 LoadingFinished(handle, aCancelResult);
1506 DispatchProcessPendingRequests();
1510 void ScriptLoaderRunnable::DispatchProcessPendingRequests() {
1511 AssertIsOnMainThread();
1513 const auto begin = mLoadingRequests.begin();
1514 const auto end = mLoadingRequests.end();
1515 using Iterator = decltype(begin);
1516 const auto maybeRangeToExecute =
1517 [begin, end]() -> Maybe<std::pair<Iterator, Iterator>> {
1518 // firstItToExecute is the first loadInfo where mExecutionScheduled is
1519 // unset.
1520 auto firstItToExecute = std::find_if(
1521 begin, end, [](const RefPtr<ThreadSafeRequestHandle>& requestHandle) {
1522 return !requestHandle->mExecutionScheduled;
1525 if (firstItToExecute == end) {
1526 return Nothing();
1529 // firstItUnexecutable is the first loadInfo that is not yet finished.
1530 // Update mExecutionScheduled on the ones we're about to schedule for
1531 // execution.
1532 const auto firstItUnexecutable =
1533 std::find_if(firstItToExecute, end,
1534 [](RefPtr<ThreadSafeRequestHandle>& requestHandle) {
1535 MOZ_ASSERT(!requestHandle->IsEmpty());
1536 if (!requestHandle->Finished()) {
1537 return true;
1540 // We can execute this one.
1541 requestHandle->mExecutionScheduled = true;
1543 return false;
1546 return firstItUnexecutable == firstItToExecute
1547 ? Nothing()
1548 : Some(std::pair(firstItToExecute, firstItUnexecutable));
1549 }();
1551 // If there are no unexecutable load infos, we can unuse things before the
1552 // execution of the scripts and the stopping of the sync loop.
1553 if (maybeRangeToExecute) {
1554 if (maybeRangeToExecute->second == end) {
1555 mCacheCreator = nullptr;
1558 RefPtr<ScriptExecutorRunnable> runnable = new ScriptExecutorRunnable(
1559 mScriptLoader, mWorkerRef->Private(), mScriptLoader->mSyncLoopTarget,
1560 Span<RefPtr<ThreadSafeRequestHandle>>{maybeRangeToExecute->first,
1561 maybeRangeToExecute->second});
1563 if (!runnable->Dispatch() && mScriptLoader->mSyncLoopTarget) {
1564 MOZ_ASSERT(false, "This should never fail!");
1569 ScriptExecutorRunnable::ScriptExecutorRunnable(
1570 WorkerScriptLoader* aScriptLoader, WorkerPrivate* aWorkerPrivate,
1571 nsISerialEventTarget* aSyncLoopTarget,
1572 Span<RefPtr<ThreadSafeRequestHandle>> aLoadedRequests)
1573 : MainThreadWorkerSyncRunnable(aWorkerPrivate, aSyncLoopTarget),
1574 mScriptLoader(aScriptLoader),
1575 mLoadedRequests(aLoadedRequests) {}
1577 bool ScriptExecutorRunnable::IsDebuggerRunnable() const {
1578 // ScriptExecutorRunnable is used to execute both worker and debugger scripts.
1579 // In the latter case, the runnable needs to be dispatched to the debugger
1580 // queue.
1581 return mScriptLoader->IsDebuggerScript();
1584 bool ScriptExecutorRunnable::PreRun(WorkerPrivate* aWorkerPrivate) {
1585 aWorkerPrivate->AssertIsOnWorkerThread();
1587 // We must be on the same worker as we started on.
1588 MOZ_ASSERT(
1589 mScriptLoader->mSyncLoopTarget == mSyncLoopTarget,
1590 "Unexpected SyncLoopTarget. Check if the sync loop was closed early");
1593 // There is a possibility that we cleaned up while this task was waiting to
1594 // run. If this has happened, return and exit.
1595 MutexAutoLock lock(mScriptLoader->CleanUpLock());
1596 if (mScriptLoader->CleanedUp()) {
1597 return true;
1600 const auto& requestHandle = mLoadedRequests[0];
1601 // Check if the request is still valid.
1602 if (requestHandle->IsEmpty() ||
1603 !requestHandle->GetContext()->IsTopLevel()) {
1604 return true;
1608 return mScriptLoader->StoreCSP();
1611 bool ScriptExecutorRunnable::ProcessModuleScript(
1612 JSContext* aCx, WorkerPrivate* aWorkerPrivate) {
1613 // We should only ever have one script when processing modules
1614 MOZ_ASSERT(mLoadedRequests.Length() == 1);
1615 RefPtr<ScriptLoadRequest> request;
1617 // There is a possibility that we cleaned up while this task was waiting to
1618 // run. If this has happened, return and exit.
1619 MutexAutoLock lock(mScriptLoader->CleanUpLock());
1620 if (mScriptLoader->CleanedUp()) {
1621 return true;
1624 MOZ_ASSERT(mLoadedRequests.Length() == 1);
1625 const auto& requestHandle = mLoadedRequests[0];
1626 // The request must be valid.
1627 MOZ_ASSERT(!requestHandle->IsEmpty());
1629 // Release the request to the worker. From this point on, the Request Handle
1630 // is empty.
1631 request = requestHandle->ReleaseRequest();
1633 // release lock. We will need it later if we cleanup.
1636 MOZ_ASSERT(request->IsModuleRequest());
1638 WorkerLoadContext* loadContext = request->GetWorkerLoadContext();
1639 ModuleLoadRequest* moduleRequest = request->AsModuleRequest();
1641 // DecreaseLoadingModuleRequestCount must be called before OnFetchComplete.
1642 // OnFetchComplete will call ProcessPendingRequests, and in
1643 // ProcessPendingRequests it will try to shutdown if
1644 // AllModuleRequestsLoaded() returns true.
1645 mScriptLoader->DecreaseLoadingModuleRequestCount();
1646 moduleRequest->OnFetchComplete(loadContext->mLoadResult);
1648 if (NS_FAILED(loadContext->mLoadResult)) {
1649 if (moduleRequest->IsDynamicImport()) {
1650 if (request->isInList()) {
1651 moduleRequest->CancelDynamicImport(loadContext->mLoadResult);
1652 mScriptLoader->TryShutdown();
1654 } else if (!moduleRequest->IsTopLevel()) {
1655 moduleRequest->Cancel();
1656 mScriptLoader->TryShutdown();
1657 } else {
1658 moduleRequest->LoadFailed();
1661 return true;
1664 bool ScriptExecutorRunnable::ProcessClassicScripts(
1665 JSContext* aCx, WorkerPrivate* aWorkerPrivate) {
1666 // There is a possibility that we cleaned up while this task was waiting to
1667 // run. If this has happened, return and exit.
1669 MutexAutoLock lock(mScriptLoader->CleanUpLock());
1670 if (mScriptLoader->CleanedUp()) {
1671 return true;
1674 for (const auto& requestHandle : mLoadedRequests) {
1675 // The request must be valid.
1676 MOZ_ASSERT(!requestHandle->IsEmpty());
1678 // Release the request to the worker. From this point on, the Request
1679 // Handle is empty.
1680 RefPtr<ScriptLoadRequest> request = requestHandle->ReleaseRequest();
1681 mScriptLoader->MaybeMoveToLoadedList(request);
1684 return mScriptLoader->ProcessPendingRequests(aCx);
1687 bool ScriptExecutorRunnable::WorkerRun(JSContext* aCx,
1688 WorkerPrivate* aWorkerPrivate) {
1689 aWorkerPrivate->AssertIsOnWorkerThread();
1691 // We must be on the same worker as we started on.
1692 MOZ_ASSERT(
1693 mScriptLoader->mSyncLoopTarget == mSyncLoopTarget,
1694 "Unexpected SyncLoopTarget. Check if the sync loop was closed early");
1696 if (mLoadedRequests.begin()->get()->GetRequest()->IsModuleRequest()) {
1697 return ProcessModuleScript(aCx, aWorkerPrivate);
1700 return ProcessClassicScripts(aCx, aWorkerPrivate);
1703 nsresult ScriptExecutorRunnable::Cancel() {
1704 if (mScriptLoader->AllScriptsExecuted() &&
1705 mScriptLoader->AllModuleRequestsLoaded()) {
1706 mScriptLoader->ShutdownScriptLoader(false, false);
1708 return NS_OK;
1711 } /* namespace loader */
1713 nsresult ChannelFromScriptURLMainThread(
1714 nsIPrincipal* aPrincipal, Document* aParentDoc, nsILoadGroup* aLoadGroup,
1715 nsIURI* aScriptURL, const WorkerType& aWorkerType,
1716 const RequestCredentials& aCredentials,
1717 const Maybe<ClientInfo>& aClientInfo,
1718 nsContentPolicyType aMainScriptContentPolicyType,
1719 nsICookieJarSettings* aCookieJarSettings, nsIReferrerInfo* aReferrerInfo,
1720 nsIChannel** aChannel) {
1721 AssertIsOnMainThread();
1723 nsCOMPtr<nsIIOService> ios(do_GetIOService());
1725 nsIScriptSecurityManager* secMan = nsContentUtils::GetSecurityManager();
1726 NS_ASSERTION(secMan, "This should never be null!");
1728 uint32_t secFlags;
1729 nsresult rv;
1730 if (aWorkerType == WorkerType::Module) {
1731 rv = GetModuleSecFlags(true, aPrincipal, WorkerScript, aScriptURL,
1732 aCredentials, secFlags);
1733 } else {
1734 rv = GetClassicSecFlags(true, aScriptURL, aPrincipal, WorkerScript,
1735 secFlags);
1737 if (NS_FAILED(rv)) {
1738 return rv;
1741 return ChannelFromScriptURL(
1742 aPrincipal, aParentDoc, nullptr, aLoadGroup, ios, secMan, aScriptURL,
1743 aClientInfo, Maybe<ServiceWorkerDescriptor>(), true, WorkerScript,
1744 aMainScriptContentPolicyType, nsIRequest::LOAD_NORMAL, secFlags,
1745 aCookieJarSettings, aReferrerInfo, aChannel);
1748 nsresult ChannelFromScriptURLWorkerThread(
1749 JSContext* aCx, WorkerPrivate* aParent, const nsAString& aScriptURL,
1750 const WorkerType& aWorkerType, const RequestCredentials& aCredentials,
1751 WorkerLoadInfo& aLoadInfo) {
1752 aParent->AssertIsOnWorkerThread();
1754 RefPtr<ChannelGetterRunnable> getter = new ChannelGetterRunnable(
1755 aParent, aScriptURL, aWorkerType, aCredentials, aLoadInfo);
1757 ErrorResult rv;
1758 getter->Dispatch(Canceling, rv);
1759 if (rv.Failed()) {
1760 NS_ERROR("Failed to dispatch!");
1761 return rv.StealNSResult();
1764 return getter->GetResult();
1767 void ReportLoadError(ErrorResult& aRv, nsresult aLoadResult,
1768 const nsAString& aScriptURL) {
1769 MOZ_ASSERT(!aRv.Failed());
1771 nsPrintfCString err("Failed to load worker script at \"%s\"",
1772 NS_ConvertUTF16toUTF8(aScriptURL).get());
1774 switch (aLoadResult) {
1775 case NS_ERROR_FILE_NOT_FOUND:
1776 case NS_ERROR_NOT_AVAILABLE:
1777 case NS_ERROR_CORRUPTED_CONTENT:
1778 aRv.Throw(NS_ERROR_DOM_NETWORK_ERR);
1779 break;
1781 case NS_ERROR_MALFORMED_URI:
1782 case NS_ERROR_DOM_SYNTAX_ERR:
1783 aRv.ThrowSyntaxError(err);
1784 break;
1786 case NS_BINDING_ABORTED:
1787 // Note: we used to pretend like we didn't set an exception for
1788 // NS_BINDING_ABORTED, but then ShutdownScriptLoader did it anyway. The
1789 // other callsite, in WorkerPrivate::Constructor, never passed in
1790 // NS_BINDING_ABORTED. So just throw it directly here. Consumers will
1791 // deal as needed. But note that we do NOT want to use one of the
1792 // Throw*Error() methods on ErrorResult for this case, because that will
1793 // make it impossible for consumers to realize that our error was
1794 // NS_BINDING_ABORTED.
1795 aRv.Throw(aLoadResult);
1796 return;
1798 case NS_ERROR_DOM_BAD_URI:
1799 // This is actually a security error.
1800 case NS_ERROR_DOM_SECURITY_ERR:
1801 aRv.ThrowSecurityError(err);
1802 break;
1804 default:
1805 // For lack of anything better, go ahead and throw a NetworkError here.
1806 // We don't want to throw a JS exception, because for toplevel script
1807 // loads that would get squelched.
1808 aRv.ThrowNetworkError(nsPrintfCString(
1809 "Failed to load worker script at %s (nsresult = 0x%" PRIx32 ")",
1810 NS_ConvertUTF16toUTF8(aScriptURL).get(),
1811 static_cast<uint32_t>(aLoadResult)));
1812 return;
1816 void LoadMainScript(WorkerPrivate* aWorkerPrivate,
1817 UniquePtr<SerializedStackHolder> aOriginStack,
1818 const nsAString& aScriptURL,
1819 WorkerScriptType aWorkerScriptType, ErrorResult& aRv,
1820 const mozilla::Encoding* aDocumentEncoding) {
1821 nsTArray<nsString> scriptURLs;
1823 scriptURLs.AppendElement(aScriptURL);
1825 LoadAllScripts(aWorkerPrivate, std::move(aOriginStack), scriptURLs, true,
1826 aWorkerScriptType, aRv, aDocumentEncoding);
1829 void Load(WorkerPrivate* aWorkerPrivate,
1830 UniquePtr<SerializedStackHolder> aOriginStack,
1831 const nsTArray<nsString>& aScriptURLs,
1832 WorkerScriptType aWorkerScriptType, ErrorResult& aRv) {
1833 const uint32_t urlCount = aScriptURLs.Length();
1835 if (!urlCount) {
1836 return;
1839 if (urlCount > MAX_CONCURRENT_SCRIPTS) {
1840 aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
1841 return;
1844 LoadAllScripts(aWorkerPrivate, std::move(aOriginStack), aScriptURLs, false,
1845 aWorkerScriptType, aRv);
1848 } // namespace mozilla::dom::workerinternals