Bug 1873144 - Disabled test_conformance__textures__misc__texture-npot-video.html...
[gecko.git] / dom / workers / ScriptLoader.cpp
blob73997b2725715a3be52e8831b0d88d829a4357c8
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 "mozilla/dom/RequestBinding.h"
13 #include "nsIChannel.h"
14 #include "nsIContentPolicy.h"
15 #include "nsIContentSecurityPolicy.h"
16 #include "nsICookieJarSettings.h"
17 #include "nsIDocShell.h"
18 #include "nsIHttpChannel.h"
19 #include "nsIHttpChannelInternal.h"
20 #include "nsIIOService.h"
21 #include "nsIOService.h"
22 #include "nsIPrincipal.h"
23 #include "nsIProtocolHandler.h"
24 #include "nsIScriptError.h"
25 #include "nsIScriptSecurityManager.h"
26 #include "nsIStreamListenerTee.h"
27 #include "nsIThreadRetargetableRequest.h"
28 #include "nsIURI.h"
29 #include "nsIXPConnect.h"
31 #include "jsapi.h"
32 #include "jsfriendapi.h"
33 #include "js/CompilationAndEvaluation.h"
34 #include "js/Exception.h"
35 #include "js/SourceText.h"
36 #include "js/TypeDecls.h"
37 #include "nsError.h"
38 #include "nsComponentManagerUtils.h"
39 #include "nsContentSecurityManager.h"
40 #include "nsContentPolicyUtils.h"
41 #include "nsContentUtils.h"
42 #include "nsDocShellCID.h"
43 #include "nsJSEnvironment.h"
44 #include "nsNetUtil.h"
45 #include "nsIPipe.h"
46 #include "nsIOutputStream.h"
47 #include "nsPrintfCString.h"
48 #include "nsString.h"
49 #include "nsTArray.h"
50 #include "nsThreadUtils.h"
51 #include "nsXPCOM.h"
52 #include "xpcpublic.h"
54 #include "mozilla/AntiTrackingUtils.h"
55 #include "mozilla/ArrayAlgorithm.h"
56 #include "mozilla/Assertions.h"
57 #include "mozilla/Encoding.h"
58 #include "mozilla/LoadContext.h"
59 #include "mozilla/Maybe.h"
60 #include "mozilla/ipc/BackgroundUtils.h"
61 #include "mozilla/dom/ClientChannelHelper.h"
62 #include "mozilla/dom/ClientInfo.h"
63 #include "mozilla/dom/Exceptions.h"
64 #include "mozilla/dom/nsCSPService.h"
65 #include "mozilla/dom/nsCSPUtils.h"
66 #include "mozilla/dom/PerformanceStorage.h"
67 #include "mozilla/dom/Response.h"
68 #include "mozilla/dom/ReferrerInfo.h"
69 #include "mozilla/dom/ScriptSettings.h"
70 #include "mozilla/dom/SerializedStackHolder.h"
71 #include "mozilla/dom/workerinternals/CacheLoadHandler.h"
72 #include "mozilla/dom/workerinternals/NetworkLoadHandler.h"
73 #include "mozilla/dom/workerinternals/ScriptResponseHeaderProcessor.h"
74 #include "mozilla/Result.h"
75 #include "mozilla/ResultExtensions.h"
76 #include "mozilla/StaticPrefs_browser.h"
77 #include "mozilla/UniquePtr.h"
78 #include "WorkerRunnable.h"
79 #include "WorkerScope.h"
81 #define MAX_CONCURRENT_SCRIPTS 1000
83 using JS::loader::ParserMetadata;
84 using JS::loader::ScriptKind;
85 using JS::loader::ScriptLoadRequest;
86 using mozilla::ipc::PrincipalInfo;
88 namespace mozilla::dom::workerinternals {
89 namespace {
91 nsresult ConstructURI(const nsAString& aScriptURL, nsIURI* baseURI,
92 const mozilla::Encoding* aDocumentEncoding,
93 nsIURI** aResult) {
94 nsresult rv;
95 // Only top level workers' main script use the document charset for the
96 // script uri encoding. Otherwise, default encoding (UTF-8) is applied.
97 if (aDocumentEncoding) {
98 nsAutoCString charset;
99 aDocumentEncoding->Name(charset);
100 rv = NS_NewURI(aResult, aScriptURL, charset.get(), baseURI);
101 } else {
102 rv = NS_NewURI(aResult, aScriptURL, nullptr, baseURI);
105 if (NS_FAILED(rv)) {
106 return NS_ERROR_DOM_SYNTAX_ERR;
108 return NS_OK;
111 nsresult ChannelFromScriptURL(
112 nsIPrincipal* principal, Document* parentDoc, WorkerPrivate* aWorkerPrivate,
113 nsILoadGroup* loadGroup, nsIIOService* ios,
114 nsIScriptSecurityManager* secMan, nsIURI* aScriptURL,
115 const Maybe<ClientInfo>& aClientInfo,
116 const Maybe<ServiceWorkerDescriptor>& aController, bool aIsMainScript,
117 WorkerScriptType aWorkerScriptType, nsContentPolicyType aContentPolicyType,
118 nsLoadFlags aLoadFlags, uint32_t aSecFlags,
119 nsICookieJarSettings* aCookieJarSettings, nsIReferrerInfo* aReferrerInfo,
120 nsIChannel** aChannel) {
121 AssertIsOnMainThread();
123 nsresult rv;
124 nsCOMPtr<nsIURI> uri = aScriptURL;
126 // Only use the document when its principal matches the principal of the
127 // current request. This means scripts fetched using the Workers' own
128 // principal won't inherit properties of the document, in particular the CSP.
129 if (parentDoc && parentDoc->NodePrincipal() != principal) {
130 parentDoc = nullptr;
133 // The main service worker script should never be loaded over the network
134 // in this path. It should always be offlined by ServiceWorkerScriptCache.
135 // We assert here since this error should also be caught by the runtime
136 // check in CacheLoadHandler.
138 // Note, if we ever allow service worker scripts to be loaded from network
139 // here we need to configure the channel properly. For example, it must
140 // not allow redirects.
141 MOZ_DIAGNOSTIC_ASSERT(aContentPolicyType !=
142 nsIContentPolicy::TYPE_INTERNAL_SERVICE_WORKER);
144 nsCOMPtr<nsIChannel> channel;
145 if (parentDoc) {
146 // This is the path for top level dedicated worker scripts with a document
147 rv = NS_NewChannel(getter_AddRefs(channel), uri, parentDoc, aSecFlags,
148 aContentPolicyType,
149 nullptr, // aPerformanceStorage
150 loadGroup,
151 nullptr, // aCallbacks
152 aLoadFlags, ios);
153 NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_SECURITY_ERR);
154 } else {
155 // This branch is used in the following cases:
156 // * Shared and ServiceWorkers (who do not have a doc)
157 // * Static Module Imports
158 // * ImportScripts
160 // We must have a loadGroup with a load context for the principal to
161 // traverse the channel correctly.
163 MOZ_ASSERT(loadGroup);
164 MOZ_ASSERT(NS_LoadGroupMatchesPrincipal(loadGroup, principal));
166 RefPtr<PerformanceStorage> performanceStorage;
167 nsCOMPtr<nsICSPEventListener> cspEventListener;
168 if (aWorkerPrivate && !aIsMainScript) {
169 performanceStorage = aWorkerPrivate->GetPerformanceStorage();
170 cspEventListener = aWorkerPrivate->CSPEventListener();
173 if (aClientInfo.isSome()) {
174 // If we have an existing clientInfo (true for all modules and
175 // importScripts), we will use this branch
176 rv = NS_NewChannel(getter_AddRefs(channel), uri, principal,
177 aClientInfo.ref(), aController, aSecFlags,
178 aContentPolicyType, aCookieJarSettings,
179 performanceStorage, loadGroup, nullptr, // aCallbacks
180 aLoadFlags, ios);
181 } else {
182 rv = NS_NewChannel(getter_AddRefs(channel), uri, principal, aSecFlags,
183 aContentPolicyType, aCookieJarSettings,
184 performanceStorage, loadGroup, nullptr, // aCallbacks
185 aLoadFlags, ios);
188 NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_SECURITY_ERR);
190 if (cspEventListener) {
191 nsCOMPtr<nsILoadInfo> loadInfo = channel->LoadInfo();
192 rv = loadInfo->SetCspEventListener(cspEventListener);
193 NS_ENSURE_SUCCESS(rv, rv);
197 if (aReferrerInfo) {
198 nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(channel);
199 if (httpChannel) {
200 rv = httpChannel->SetReferrerInfo(aReferrerInfo);
201 if (NS_WARN_IF(NS_FAILED(rv))) {
202 return rv;
207 channel.forget(aChannel);
208 return rv;
211 void LoadAllScripts(WorkerPrivate* aWorkerPrivate,
212 UniquePtr<SerializedStackHolder> aOriginStack,
213 const nsTArray<nsString>& aScriptURLs, bool aIsMainScript,
214 WorkerScriptType aWorkerScriptType, ErrorResult& aRv,
215 const mozilla::Encoding* aDocumentEncoding = nullptr) {
216 aWorkerPrivate->AssertIsOnWorkerThread();
217 NS_ASSERTION(!aScriptURLs.IsEmpty(), "Bad arguments!");
219 AutoSyncLoopHolder syncLoop(aWorkerPrivate, Canceling);
220 nsCOMPtr<nsISerialEventTarget> syncLoopTarget =
221 syncLoop.GetSerialEventTarget();
222 if (!syncLoopTarget) {
223 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
224 return;
227 RefPtr<loader::WorkerScriptLoader> loader =
228 loader::WorkerScriptLoader::Create(
229 aWorkerPrivate, std::move(aOriginStack), syncLoopTarget,
230 aWorkerScriptType, aRv);
232 if (NS_WARN_IF(aRv.Failed())) {
233 return;
236 bool ok = loader->CreateScriptRequests(aScriptURLs, aDocumentEncoding,
237 aIsMainScript);
239 if (!ok) {
240 return;
242 // Bug 1817259 - For now, we force loading the debugger script as Classic,
243 // even if the debugged worker is a Module.
244 if (aWorkerPrivate->WorkerType() == WorkerType::Module &&
245 aWorkerScriptType != DebuggerScript) {
246 if (!StaticPrefs::dom_workers_modules_enabled()) {
247 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
248 return;
250 MOZ_ASSERT(aIsMainScript);
251 // Module Load
252 RefPtr<JS::loader::ScriptLoadRequest> mainScript = loader->GetMainScript();
253 if (mainScript && mainScript->IsModuleRequest()) {
254 if (NS_FAILED(mainScript->AsModuleRequest()->StartModuleLoad())) {
255 return;
257 syncLoop.Run();
258 return;
262 if (loader->DispatchLoadScripts()) {
263 syncLoop.Run();
267 class ChannelGetterRunnable final : public WorkerMainThreadRunnable {
268 const nsAString& mScriptURL;
269 const WorkerType& mWorkerType;
270 const RequestCredentials& mCredentials;
271 const ClientInfo mClientInfo;
272 WorkerLoadInfo& mLoadInfo;
273 nsresult mResult;
275 public:
276 ChannelGetterRunnable(WorkerPrivate* aParentWorker,
277 const nsAString& aScriptURL,
278 const WorkerType& aWorkerType,
279 const RequestCredentials& aCredentials,
280 WorkerLoadInfo& aLoadInfo)
281 : WorkerMainThreadRunnable(aParentWorker,
282 "ScriptLoader :: ChannelGetter"_ns),
283 mScriptURL(aScriptURL)
284 // ClientInfo should always be present since this should not be called
285 // if parent's status is greater than Running.
287 mWorkerType(aWorkerType),
288 mCredentials(aCredentials),
289 mClientInfo(aParentWorker->GlobalScope()->GetClientInfo().ref()),
290 mLoadInfo(aLoadInfo),
291 mResult(NS_ERROR_FAILURE) {
292 MOZ_ASSERT(aParentWorker);
293 aParentWorker->AssertIsOnWorkerThread();
296 virtual bool MainThreadRun() override {
297 AssertIsOnMainThread();
299 // Initialize the WorkerLoadInfo principal to our triggering principal
300 // before doing anything else. Normally we do this in the WorkerPrivate
301 // Constructor, but we can't do so off the main thread when creating
302 // a nested worker. So do it here instead.
303 mLoadInfo.mLoadingPrincipal = mWorkerPrivate->GetPrincipal();
304 MOZ_DIAGNOSTIC_ASSERT(mLoadInfo.mLoadingPrincipal);
306 mLoadInfo.mPrincipal = mLoadInfo.mLoadingPrincipal;
308 // Figure out our base URI.
309 nsCOMPtr<nsIURI> baseURI = mWorkerPrivate->GetBaseURI();
310 MOZ_ASSERT(baseURI);
312 // May be null.
313 nsCOMPtr<Document> parentDoc = mWorkerPrivate->GetDocument();
315 mLoadInfo.mLoadGroup = mWorkerPrivate->GetLoadGroup();
316 mLoadInfo.mCookieJarSettings = mWorkerPrivate->CookieJarSettings();
318 // Nested workers use default uri encoding.
319 nsCOMPtr<nsIURI> url;
320 mResult = ConstructURI(mScriptURL, baseURI, nullptr, getter_AddRefs(url));
321 NS_ENSURE_SUCCESS(mResult, true);
323 Maybe<ClientInfo> clientInfo;
324 clientInfo.emplace(mClientInfo);
326 nsCOMPtr<nsIChannel> channel;
327 nsCOMPtr<nsIReferrerInfo> referrerInfo =
328 ReferrerInfo::CreateForFetch(mLoadInfo.mLoadingPrincipal, nullptr);
329 mLoadInfo.mReferrerInfo =
330 static_cast<ReferrerInfo*>(referrerInfo.get())
331 ->CloneWithNewPolicy(mWorkerPrivate->GetReferrerPolicy());
333 mResult = workerinternals::ChannelFromScriptURLMainThread(
334 mLoadInfo.mLoadingPrincipal, parentDoc, mLoadInfo.mLoadGroup, url,
335 mWorkerType, mCredentials, clientInfo,
336 // Nested workers are always dedicated.
337 nsIContentPolicy::TYPE_INTERNAL_WORKER, mLoadInfo.mCookieJarSettings,
338 mLoadInfo.mReferrerInfo, getter_AddRefs(channel));
339 NS_ENSURE_SUCCESS(mResult, true);
341 mResult = mLoadInfo.SetPrincipalsAndCSPFromChannel(channel);
342 NS_ENSURE_SUCCESS(mResult, true);
344 mLoadInfo.mChannel = std::move(channel);
345 return true;
348 nsresult GetResult() const { return mResult; }
350 private:
351 virtual ~ChannelGetterRunnable() = default;
354 nsresult GetCommonSecFlags(bool aIsMainScript, nsIURI* uri,
355 nsIPrincipal* principal,
356 WorkerScriptType aWorkerScriptType,
357 uint32_t& secFlags) {
358 bool inheritAttrs = nsContentUtils::ChannelShouldInheritPrincipal(
359 principal, uri, true /* aInheritForAboutBlank */,
360 false /* aForceInherit */);
362 bool isData = uri->SchemeIs("data");
363 if (inheritAttrs && !isData) {
364 secFlags |= nsILoadInfo::SEC_FORCE_INHERIT_PRINCIPAL;
367 if (aWorkerScriptType == DebuggerScript) {
368 // A DebuggerScript needs to be a local resource like chrome: or resource:
369 bool isUIResource = false;
370 nsresult rv = NS_URIChainHasFlags(
371 uri, nsIProtocolHandler::URI_IS_UI_RESOURCE, &isUIResource);
372 if (NS_WARN_IF(NS_FAILED(rv))) {
373 return rv;
376 if (!isUIResource) {
377 return NS_ERROR_DOM_SECURITY_ERR;
380 secFlags |= nsILoadInfo::SEC_ALLOW_CHROME;
383 // Note: this is for backwards compatibility and goes against spec.
384 // We should find a better solution.
385 if (aIsMainScript && isData) {
386 secFlags = nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL;
389 return NS_OK;
392 nsresult GetModuleSecFlags(bool aIsTopLevel, nsIPrincipal* principal,
393 WorkerScriptType aWorkerScriptType, nsIURI* aURI,
394 RequestCredentials aCredentials,
395 uint32_t& secFlags) {
396 // Implements "To fetch a single module script,"
397 // Step 9. If destination is "worker", "sharedworker", or "serviceworker",
398 // and the top-level module fetch flag is set, then set request's
399 // mode to "same-origin".
401 // Step 8. Let request be a new request whose [...] mode is "cors" [...]
402 secFlags = aIsTopLevel ? nsILoadInfo::SEC_REQUIRE_SAME_ORIGIN_DATA_IS_BLOCKED
403 : nsILoadInfo::SEC_REQUIRE_CORS_INHERITS_SEC_CONTEXT;
405 // This implements the same Cookie settings as nsContentSecurityManager's
406 // ComputeSecurityFlags. The main difference is the line above, Step 9,
407 // setting to same origin.
409 if (aCredentials == RequestCredentials::Include) {
410 secFlags |= nsILoadInfo::nsILoadInfo::SEC_COOKIES_INCLUDE;
411 } else if (aCredentials == RequestCredentials::Same_origin) {
412 secFlags |= nsILoadInfo::nsILoadInfo::SEC_COOKIES_SAME_ORIGIN;
413 } else if (aCredentials == RequestCredentials::Omit) {
414 secFlags |= nsILoadInfo::nsILoadInfo::SEC_COOKIES_OMIT;
417 return GetCommonSecFlags(aIsTopLevel, aURI, principal, aWorkerScriptType,
418 secFlags);
421 nsresult GetClassicSecFlags(bool aIsMainScript, nsIURI* uri,
422 nsIPrincipal* principal,
423 WorkerScriptType aWorkerScriptType,
424 uint32_t& secFlags) {
425 secFlags = aIsMainScript
426 ? nsILoadInfo::SEC_REQUIRE_SAME_ORIGIN_DATA_IS_BLOCKED
427 : nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_INHERITS_SEC_CONTEXT;
429 return GetCommonSecFlags(aIsMainScript, uri, principal, aWorkerScriptType,
430 secFlags);
433 } // anonymous namespace
435 namespace loader {
437 class ScriptExecutorRunnable final : public MainThreadWorkerSyncRunnable {
438 RefPtr<WorkerScriptLoader> mScriptLoader;
439 const Span<RefPtr<ThreadSafeRequestHandle>> mLoadedRequests;
441 public:
442 ScriptExecutorRunnable(WorkerScriptLoader* aScriptLoader,
443 WorkerPrivate* aWorkerPrivate,
444 nsISerialEventTarget* aSyncLoopTarget,
445 Span<RefPtr<ThreadSafeRequestHandle>> aLoadedRequests);
447 private:
448 ~ScriptExecutorRunnable() = default;
450 virtual bool IsDebuggerRunnable() const override;
452 virtual bool PreRun(WorkerPrivate* aWorkerPrivate) override;
454 bool ProcessModuleScript(JSContext* aCx, WorkerPrivate* aWorkerPrivate);
456 bool ProcessClassicScripts(JSContext* aCx, WorkerPrivate* aWorkerPrivate);
458 virtual bool WorkerRun(JSContext* aCx,
459 WorkerPrivate* aWorkerPrivate) override;
461 nsresult Cancel() override;
464 static bool EvaluateSourceBuffer(JSContext* aCx, JS::Handle<JSScript*> aScript,
465 JS::loader::ClassicScript* aClassicScript) {
466 if (aClassicScript) {
467 aClassicScript->AssociateWithScript(aScript);
470 JS::Rooted<JS::Value> unused(aCx);
471 return JS_ExecuteScript(aCx, aScript, &unused);
474 WorkerScriptLoader::WorkerScriptLoader(
475 UniquePtr<SerializedStackHolder> aOriginStack,
476 nsISerialEventTarget* aSyncLoopTarget, WorkerScriptType aWorkerScriptType,
477 ErrorResult& aRv)
478 : mOriginStack(std::move(aOriginStack)),
479 mSyncLoopTarget(aSyncLoopTarget),
480 mWorkerScriptType(aWorkerScriptType),
481 mRv(aRv),
482 mLoadingModuleRequestCount(0),
483 mCleanedUp(false),
484 mCleanUpLock("cleanUpLock") {}
486 already_AddRefed<WorkerScriptLoader> WorkerScriptLoader::Create(
487 WorkerPrivate* aWorkerPrivate,
488 UniquePtr<SerializedStackHolder> aOriginStack,
489 nsISerialEventTarget* aSyncLoopTarget, WorkerScriptType aWorkerScriptType,
490 ErrorResult& aRv) {
491 aWorkerPrivate->AssertIsOnWorkerThread();
493 RefPtr<WorkerScriptLoader> self = new WorkerScriptLoader(
494 std::move(aOriginStack), aSyncLoopTarget, aWorkerScriptType, aRv);
496 RefPtr<StrongWorkerRef> workerRef = StrongWorkerRef::Create(
497 aWorkerPrivate, "WorkerScriptLoader::Create", [self]() {
498 // Requests that are in flight are covered by the worker references
499 // in DispatchLoadScript(s), so we do not need to do additional
500 // cleanup, but just in case we are ready/aborted we can try to
501 // shutdown here, too.
502 self->TryShutdown();
505 if (workerRef) {
506 self->mWorkerRef = new ThreadSafeWorkerRef(workerRef);
507 } else {
508 self->mRv.Throw(NS_ERROR_FAILURE);
509 return nullptr;
512 nsIGlobalObject* global = self->GetGlobal();
513 self->mController = global->GetController();
515 if (!StaticPrefs::dom_workers_modules_enabled()) {
516 return self.forget();
519 // Set up the module loader, if it has not been initialzied yet.
520 if (!aWorkerPrivate->IsServiceWorker()) {
521 self->InitModuleLoader();
524 return self.forget();
527 ScriptLoadRequest* WorkerScriptLoader::GetMainScript() {
528 mWorkerRef->Private()->AssertIsOnWorkerThread();
529 ScriptLoadRequest* request = mLoadingRequests.getFirst();
530 if (request->GetWorkerLoadContext()->IsTopLevel()) {
531 return request;
533 return nullptr;
536 void WorkerScriptLoader::InitModuleLoader() {
537 mWorkerRef->Private()->AssertIsOnWorkerThread();
538 if (GetGlobal()->GetModuleLoader(nullptr)) {
539 return;
541 RefPtr<WorkerModuleLoader> moduleLoader =
542 new WorkerModuleLoader(this, GetGlobal());
543 if (mWorkerScriptType == WorkerScript) {
544 mWorkerRef->Private()->GlobalScope()->InitModuleLoader(moduleLoader);
545 return;
547 mWorkerRef->Private()->DebuggerGlobalScope()->InitModuleLoader(moduleLoader);
550 bool WorkerScriptLoader::CreateScriptRequests(
551 const nsTArray<nsString>& aScriptURLs,
552 const mozilla::Encoding* aDocumentEncoding, bool aIsMainScript) {
553 mWorkerRef->Private()->AssertIsOnWorkerThread();
554 // If a worker has been loaded as a module worker, ImportScripts calls are
555 // disallowed -- then the operation is invalid.
557 // 10.3.1 Importing scripts and libraries.
558 // Step 1. If worker global scope's type is "module", throw a TypeError
559 // exception.
561 // Also, for now, the debugger script is always loaded as Classic,
562 // even if the debugged worker is a Module. We still want to allow
563 // it to use importScripts.
564 if (mWorkerRef->Private()->WorkerType() == WorkerType::Module &&
565 !aIsMainScript && !IsDebuggerScript()) {
566 // This should only run for non-main scripts, as only these are
567 // importScripts
568 mRv.ThrowTypeError(
569 "Using `ImportScripts` inside a Module Worker is "
570 "disallowed.");
571 return false;
573 for (const nsString& scriptURL : aScriptURLs) {
574 RefPtr<ScriptLoadRequest> request =
575 CreateScriptLoadRequest(scriptURL, aDocumentEncoding, aIsMainScript);
576 if (!request) {
577 return false;
579 mLoadingRequests.AppendElement(request);
582 return true;
585 nsTArray<RefPtr<ThreadSafeRequestHandle>> WorkerScriptLoader::GetLoadingList() {
586 mWorkerRef->Private()->AssertIsOnWorkerThread();
587 nsTArray<RefPtr<ThreadSafeRequestHandle>> list;
588 for (ScriptLoadRequest* req = mLoadingRequests.getFirst(); req;
589 req = req->getNext()) {
590 RefPtr<ThreadSafeRequestHandle> handle =
591 new ThreadSafeRequestHandle(req, mSyncLoopTarget.get());
592 list.AppendElement(handle.forget());
594 return list;
597 bool WorkerScriptLoader::IsDynamicImport(ScriptLoadRequest* aRequest) {
598 return aRequest->IsModuleRequest() &&
599 aRequest->AsModuleRequest()->IsDynamicImport();
602 nsContentPolicyType WorkerScriptLoader::GetContentPolicyType(
603 ScriptLoadRequest* aRequest) {
604 if (aRequest->GetWorkerLoadContext()->IsTopLevel()) {
605 // Implements https://html.spec.whatwg.org/#worker-processing-model
606 // Step 13: Let destination be "sharedworker" if is shared is true, and
607 // "worker" otherwise.
608 return mWorkerRef->Private()->ContentPolicyType();
610 if (aRequest->IsModuleRequest()) {
611 if (aRequest->AsModuleRequest()->IsDynamicImport()) {
612 return nsIContentPolicy::TYPE_INTERNAL_MODULE;
615 // Implements the destination for Step 14 in
616 // https://html.spec.whatwg.org/#worker-processing-model
618 // We need a special subresource type in order to correctly implement
619 // the graph fetch, where the destination is set to "worker" or
620 // "sharedworker".
621 return nsIContentPolicy::TYPE_INTERNAL_WORKER_STATIC_MODULE;
623 // For script imported in worker's importScripts().
624 return nsIContentPolicy::TYPE_INTERNAL_WORKER_IMPORT_SCRIPTS;
627 already_AddRefed<ScriptLoadRequest> WorkerScriptLoader::CreateScriptLoadRequest(
628 const nsString& aScriptURL, const mozilla::Encoding* aDocumentEncoding,
629 bool aIsMainScript) {
630 mWorkerRef->Private()->AssertIsOnWorkerThread();
631 WorkerLoadContext::Kind kind =
632 WorkerLoadContext::GetKind(aIsMainScript, IsDebuggerScript());
634 Maybe<ClientInfo> clientInfo = GetGlobal()->GetClientInfo();
636 // (For non-serviceworkers, this variable does not matter, but false best
637 // captures their behavior.)
638 bool onlyExistingCachedResourcesAllowed = false;
639 if (mWorkerRef->Private()->IsServiceWorker()) {
640 // https://w3c.github.io/ServiceWorker/#importscripts step 4:
641 // > 4. If serviceWorker’s state is not "parsed" or "installing":
642 // > 1. Return map[url] if it exists and a network error otherwise.
644 // So if our state is beyond installing, it's too late to make a request
645 // that would perform a new fetch which would be cached.
646 onlyExistingCachedResourcesAllowed =
647 mWorkerRef->Private()->GetServiceWorkerDescriptor().State() >
648 ServiceWorkerState::Installing;
650 RefPtr<WorkerLoadContext> loadContext = new WorkerLoadContext(
651 kind, clientInfo, this, onlyExistingCachedResourcesAllowed);
653 // Create ScriptLoadRequests for this WorkerScriptLoader
654 ReferrerPolicy referrerPolicy = mWorkerRef->Private()->GetReferrerPolicy();
656 // Only top level workers' main script use the document charset for the
657 // script uri encoding. Otherwise, default encoding (UTF-8) is applied.
658 MOZ_ASSERT_IF(bool(aDocumentEncoding),
659 aIsMainScript && !mWorkerRef->Private()->GetParent());
660 nsCOMPtr<nsIURI> baseURI = aIsMainScript ? GetInitialBaseURI() : GetBaseURI();
661 nsCOMPtr<nsIURI> uri;
662 bool setErrorResult = false;
663 nsresult rv =
664 ConstructURI(aScriptURL, baseURI, aDocumentEncoding, getter_AddRefs(uri));
665 // If we failed to construct the URI, handle it in the LoadContext so it is
666 // thrown in the right order.
667 if (NS_WARN_IF(NS_FAILED(rv))) {
668 setErrorResult = true;
669 loadContext->mLoadResult = rv;
672 // https://html.spec.whatwg.org/multipage/webappapis.html#fetch-a-classic-worker-script
673 // Step 2.5. Let script be the result [...] and the default classic script
674 // fetch options.
676 // https://html.spec.whatwg.org/multipage/webappapis.html#fetch-a-worklet/module-worker-script-graph
677 // Step 1. Let options be a script fetch options whose cryptographic nonce is
678 // the empty string, integrity metadata is the empty string, parser metadata
679 // is "not-parser-inserted", credentials mode is credentials mode, referrer
680 // policy is the empty string, and fetch priority is "auto".
681 RefPtr<ScriptFetchOptions> fetchOptions = new ScriptFetchOptions(
682 CORSMode::CORS_NONE, /* aNonce = */ u""_ns, RequestPriority::Auto,
683 ParserMetadata::NotParserInserted, nullptr);
685 RefPtr<ScriptLoadRequest> request = nullptr;
686 // Bug 1817259 - For now the debugger scripts are always loaded a Classic.
687 if (mWorkerRef->Private()->WorkerType() == WorkerType::Classic ||
688 IsDebuggerScript()) {
689 request = new ScriptLoadRequest(ScriptKind::eClassic, uri, referrerPolicy,
690 fetchOptions, SRIMetadata(),
691 nullptr, // mReferrer
692 loadContext);
693 } else {
694 // Implements part of "To fetch a worklet/module worker script graph"
695 // including, setting up the request with a credentials mode,
696 // destination.
698 // Step 1. Let options be a script fetch options.
699 // We currently don't track credentials in our ScriptFetchOptions
700 // implementation, so we are defaulting the fetchOptions object defined
701 // above. This behavior is handled fully in GetModuleSecFlags.
703 if (!StaticPrefs::dom_workers_modules_enabled()) {
704 mRv.ThrowTypeError("Modules in workers are currently disallowed.");
705 return nullptr;
707 RefPtr<WorkerModuleLoader::ModuleLoaderBase> moduleLoader =
708 GetGlobal()->GetModuleLoader(nullptr);
710 // Implements the referrer for "To fetch a single module script"
711 // Our implementation does not have a "client" as a referrer.
712 // However, when client is resolved (per 8.3. Determine request’s
713 // Referrer in
714 // https://w3c.github.io/webappsec-referrer-policy/#determine-requests-referrer)
715 // This should result in the referrer source being the creation URL.
717 // In subresource modules, the referrer is the importing script.
718 nsCOMPtr<nsIURI> referrer =
719 mWorkerRef->Private()->GetReferrerInfo()->GetOriginalReferrer();
721 // Part of Step 2. This sets the Top-level flag to true
722 request = new ModuleLoadRequest(
723 uri, referrerPolicy, fetchOptions, SRIMetadata(), referrer, loadContext,
724 true, /* is top level */
725 false, /* is dynamic import */
726 moduleLoader, ModuleLoadRequest::NewVisitedSetForTopLevelImport(uri),
727 nullptr);
730 // Set the mURL, it will be used for error handling and debugging.
731 request->mURL = NS_ConvertUTF16toUTF8(aScriptURL);
733 if (setErrorResult) {
734 request->SetPendingFetchingError();
735 } else {
736 request->NoCacheEntryFound();
739 return request.forget();
742 bool WorkerScriptLoader::DispatchLoadScript(ScriptLoadRequest* aRequest) {
743 mWorkerRef->Private()->AssertIsOnWorkerThread();
745 IncreaseLoadingModuleRequestCount();
747 nsTArray<RefPtr<ThreadSafeRequestHandle>> scriptLoadList;
748 RefPtr<ThreadSafeRequestHandle> handle =
749 new ThreadSafeRequestHandle(aRequest, mSyncLoopTarget.get());
750 scriptLoadList.AppendElement(handle.forget());
752 RefPtr<ScriptLoaderRunnable> runnable =
753 new ScriptLoaderRunnable(this, std::move(scriptLoadList));
755 RefPtr<StrongWorkerRef> workerRef = StrongWorkerRef::Create(
756 mWorkerRef->Private(), "WorkerScriptLoader::DispatchLoadScript",
757 [runnable]() {
758 NS_DispatchToMainThread(NewRunnableMethod(
759 "ScriptLoaderRunnable::CancelMainThreadWithBindingAborted",
760 runnable,
761 &ScriptLoaderRunnable::CancelMainThreadWithBindingAborted));
764 if (NS_FAILED(NS_DispatchToMainThread(runnable))) {
765 NS_ERROR("Failed to dispatch!");
766 mRv.Throw(NS_ERROR_FAILURE);
767 return false;
769 return true;
772 bool WorkerScriptLoader::DispatchLoadScripts() {
773 mWorkerRef->Private()->AssertIsOnWorkerThread();
775 nsTArray<RefPtr<ThreadSafeRequestHandle>> scriptLoadList = GetLoadingList();
777 RefPtr<ScriptLoaderRunnable> runnable =
778 new ScriptLoaderRunnable(this, std::move(scriptLoadList));
780 RefPtr<StrongWorkerRef> workerRef = StrongWorkerRef::Create(
781 mWorkerRef->Private(), "WorkerScriptLoader::DispatchLoadScripts",
782 [runnable]() {
783 NS_DispatchToMainThread(NewRunnableMethod(
784 "ScriptLoaderRunnable::CancelMainThreadWithBindingAborted",
785 runnable,
786 &ScriptLoaderRunnable::CancelMainThreadWithBindingAborted));
789 if (NS_FAILED(NS_DispatchToMainThread(runnable))) {
790 NS_ERROR("Failed to dispatch!");
791 mRv.Throw(NS_ERROR_FAILURE);
792 return false;
794 return true;
797 nsIURI* WorkerScriptLoader::GetInitialBaseURI() {
798 MOZ_ASSERT(mWorkerRef->Private());
799 nsIURI* baseURI;
800 WorkerPrivate* parentWorker = mWorkerRef->Private()->GetParent();
801 if (parentWorker) {
802 baseURI = parentWorker->GetBaseURI();
803 } else {
804 // May be null.
805 baseURI = mWorkerRef->Private()->GetBaseURI();
808 return baseURI;
811 nsIURI* WorkerScriptLoader::GetBaseURI() const {
812 MOZ_ASSERT(mWorkerRef);
813 nsIURI* baseURI;
814 baseURI = mWorkerRef->Private()->GetBaseURI();
815 NS_ASSERTION(baseURI, "Should have been set already!");
817 return baseURI;
820 nsIGlobalObject* WorkerScriptLoader::GetGlobal() {
821 mWorkerRef->Private()->AssertIsOnWorkerThread();
822 return mWorkerScriptType == WorkerScript
823 ? static_cast<nsIGlobalObject*>(
824 mWorkerRef->Private()->GlobalScope())
825 : mWorkerRef->Private()->DebuggerGlobalScope();
828 void WorkerScriptLoader::MaybeMoveToLoadedList(ScriptLoadRequest* aRequest) {
829 mWorkerRef->Private()->AssertIsOnWorkerThread();
830 // Only set to ready for regular scripts. Module loader will set the script to
831 // ready if it is a Module Request.
832 if (!aRequest->IsModuleRequest()) {
833 aRequest->SetReady();
836 // If the request is not in a list, we are in an illegal state.
837 MOZ_RELEASE_ASSERT(aRequest->isInList());
839 while (!mLoadingRequests.isEmpty()) {
840 ScriptLoadRequest* request = mLoadingRequests.getFirst();
841 // We need to move requests in post order. If prior requests have not
842 // completed, delay execution.
843 if (!request->IsFinished()) {
844 break;
847 RefPtr<ScriptLoadRequest> req = mLoadingRequests.Steal(request);
848 mLoadedRequests.AppendElement(req);
852 bool WorkerScriptLoader::StoreCSP() {
853 // We must be on the same worker as we started on.
854 mWorkerRef->Private()->AssertIsOnWorkerThread();
856 if (!mWorkerRef->Private()->GetJSContext()) {
857 return false;
860 MOZ_ASSERT(!mRv.Failed());
862 // Move the CSP from the workerLoadInfo in the corresponding Client
863 // where the CSP code expects it!
864 mWorkerRef->Private()->StoreCSPOnClient();
865 return true;
868 bool WorkerScriptLoader::ProcessPendingRequests(JSContext* aCx) {
869 mWorkerRef->Private()->AssertIsOnWorkerThread();
870 // Don't run if something else has already failed.
871 if (mExecutionAborted) {
872 mLoadedRequests.CancelRequestsAndClear();
873 TryShutdown();
874 return true;
877 // If nothing else has failed, our ErrorResult better not be a failure
878 // either.
879 MOZ_ASSERT(!mRv.Failed(), "Who failed it and why?");
881 // Slightly icky action at a distance, but there's no better place to stash
882 // this value, really.
883 JS::Rooted<JSObject*> global(aCx, JS::CurrentGlobalOrNull(aCx));
884 MOZ_ASSERT(global);
886 while (!mLoadedRequests.isEmpty()) {
887 RefPtr<ScriptLoadRequest> req = mLoadedRequests.StealFirst();
888 // We don't have a ProcessRequest method (like we do on the DOM), as there
889 // isn't much processing that we need to do per request that isn't related
890 // to evaluation (the processsing done for the DOM is handled in
891 // DataRecievedFrom{Cache,Network} for workers.
892 // So, this inner loop calls EvaluateScript directly. This will change
893 // once modules are introduced as we will have some extra work to do.
894 if (!EvaluateScript(aCx, req)) {
895 req->Cancel();
896 mExecutionAborted = true;
897 WorkerLoadContext* loadContext = req->GetWorkerLoadContext();
898 mMutedErrorFlag = loadContext->mMutedErrorFlag.valueOr(true);
899 mLoadedRequests.CancelRequestsAndClear();
900 break;
904 TryShutdown();
905 return true;
908 nsresult WorkerScriptLoader::LoadScript(
909 ThreadSafeRequestHandle* aRequestHandle) {
910 AssertIsOnMainThread();
912 WorkerLoadContext* loadContext = aRequestHandle->GetContext();
913 ScriptLoadRequest* request = aRequestHandle->GetRequest();
914 MOZ_ASSERT_IF(loadContext->IsTopLevel(), !IsDebuggerScript());
916 // The URL passed to us for loading was invalid, stop loading at this point.
917 if (loadContext->mLoadResult != NS_ERROR_NOT_INITIALIZED) {
918 return loadContext->mLoadResult;
921 WorkerPrivate* parentWorker = mWorkerRef->Private()->GetParent();
923 // For JavaScript debugging, the devtools server must run on the same
924 // thread as the debuggee, indicating the worker uses content principal.
925 // However, in Bug 863246, web content will no longer be able to load
926 // resource:// URIs by default, so we need system principal to load
927 // debugger scripts.
928 nsIPrincipal* principal = (IsDebuggerScript())
929 ? nsContentUtils::GetSystemPrincipal()
930 : mWorkerRef->Private()->GetPrincipal();
932 nsCOMPtr<nsILoadGroup> loadGroup = mWorkerRef->Private()->GetLoadGroup();
933 MOZ_DIAGNOSTIC_ASSERT(principal);
935 NS_ENSURE_TRUE(NS_LoadGroupMatchesPrincipal(loadGroup, principal),
936 NS_ERROR_FAILURE);
938 // May be null.
939 nsCOMPtr<Document> parentDoc = mWorkerRef->Private()->GetDocument();
941 nsCOMPtr<nsIChannel> channel;
942 if (loadContext->IsTopLevel()) {
943 // May be null.
944 channel = mWorkerRef->Private()->ForgetWorkerChannel();
947 nsCOMPtr<nsIIOService> ios(do_GetIOService());
949 nsIScriptSecurityManager* secMan = nsContentUtils::GetSecurityManager();
950 NS_ASSERTION(secMan, "This should never be null!");
952 nsresult& rv = loadContext->mLoadResult;
954 nsLoadFlags loadFlags = mWorkerRef->Private()->GetLoadFlags();
956 // Get the top-level worker.
957 WorkerPrivate* topWorkerPrivate = mWorkerRef->Private();
958 WorkerPrivate* parent = topWorkerPrivate->GetParent();
959 while (parent) {
960 topWorkerPrivate = parent;
961 parent = topWorkerPrivate->GetParent();
964 // If the top-level worker is a dedicated worker and has a window, and the
965 // window has a docshell, the caching behavior of this worker should match
966 // that of that docshell.
967 if (topWorkerPrivate->IsDedicatedWorker()) {
968 nsCOMPtr<nsPIDOMWindowInner> window = topWorkerPrivate->GetWindow();
969 if (window) {
970 nsCOMPtr<nsIDocShell> docShell = window->GetDocShell();
971 if (docShell) {
972 nsresult rv = docShell->GetDefaultLoadFlags(&loadFlags);
973 NS_ENSURE_SUCCESS(rv, rv);
978 if (!channel) {
979 nsCOMPtr<nsIReferrerInfo> referrerInfo;
980 uint32_t secFlags;
981 if (request->IsModuleRequest()) {
982 // https://fetch.spec.whatwg.org/#concept-main-fetch
983 // Step 8. If request’s referrer policy is the empty string, then set
984 // request’s referrer policy to request’s policy container’s
985 // referrer policy.
986 ReferrerPolicy policy =
987 request->ReferrerPolicy() == ReferrerPolicy::_empty
988 ? mWorkerRef->Private()->GetReferrerPolicy()
989 : request->ReferrerPolicy();
991 referrerInfo = new ReferrerInfo(request->mReferrer, policy);
993 // https://html.spec.whatwg.org/multipage/webappapis.html#default-classic-script-fetch-options
994 // The default classic script fetch options are a script fetch options
995 // whose ... credentials mode is "same-origin", ....
996 RequestCredentials credentials =
997 mWorkerRef->Private()->WorkerType() == WorkerType::Classic
998 ? RequestCredentials::Same_origin
999 : mWorkerRef->Private()->WorkerCredentials();
1001 rv = GetModuleSecFlags(loadContext->IsTopLevel(), principal,
1002 mWorkerScriptType, request->mURI, credentials,
1003 secFlags);
1004 } else {
1005 referrerInfo = ReferrerInfo::CreateForFetch(principal, nullptr);
1006 if (parentWorker && !loadContext->IsTopLevel()) {
1007 referrerInfo =
1008 static_cast<ReferrerInfo*>(referrerInfo.get())
1009 ->CloneWithNewPolicy(parentWorker->GetReferrerPolicy());
1011 rv = GetClassicSecFlags(loadContext->IsTopLevel(), request->mURI,
1012 principal, mWorkerScriptType, secFlags);
1015 if (NS_WARN_IF(NS_FAILED(rv))) {
1016 return rv;
1019 nsContentPolicyType contentPolicyType = GetContentPolicyType(request);
1021 rv = ChannelFromScriptURL(
1022 principal, parentDoc, mWorkerRef->Private(), loadGroup, ios, secMan,
1023 request->mURI, loadContext->mClientInfo, mController,
1024 loadContext->IsTopLevel(), mWorkerScriptType, contentPolicyType,
1025 loadFlags, secFlags, mWorkerRef->Private()->CookieJarSettings(),
1026 referrerInfo, getter_AddRefs(channel));
1027 if (NS_WARN_IF(NS_FAILED(rv))) {
1028 return rv;
1032 // Associate any originating stack with the channel.
1033 if (!mOriginStackJSON.IsEmpty()) {
1034 NotifyNetworkMonitorAlternateStack(channel, mOriginStackJSON);
1037 // We need to know which index we're on in OnStreamComplete so we know
1038 // where to put the result.
1039 RefPtr<NetworkLoadHandler> listener =
1040 new NetworkLoadHandler(this, aRequestHandle);
1042 RefPtr<ScriptResponseHeaderProcessor> headerProcessor = nullptr;
1044 // For each debugger script, a non-debugger script load of the same script
1045 // should have occured prior that processed the headers.
1046 if (!IsDebuggerScript()) {
1047 headerProcessor = MakeRefPtr<ScriptResponseHeaderProcessor>(
1048 mWorkerRef->Private(),
1049 loadContext->IsTopLevel() && !IsDynamicImport(request),
1050 GetContentPolicyType(request) ==
1051 nsIContentPolicy::TYPE_INTERNAL_WORKER_IMPORT_SCRIPTS);
1054 nsCOMPtr<nsIStreamLoader> loader;
1055 rv = NS_NewStreamLoader(getter_AddRefs(loader), listener, headerProcessor);
1056 if (NS_WARN_IF(NS_FAILED(rv))) {
1057 return rv;
1060 if (loadContext->IsTopLevel()) {
1061 MOZ_DIAGNOSTIC_ASSERT(loadContext->mClientInfo.isSome());
1063 // In order to get the correct foreign partitioned prinicpal, we need to
1064 // set the `IsThirdPartyContextToTopWindow` to the channel's loadInfo.
1065 // This flag reflects the fact that if the worker is created under a
1066 // third-party context.
1067 nsCOMPtr<nsILoadInfo> loadInfo = channel->LoadInfo();
1068 loadInfo->SetIsThirdPartyContextToTopWindow(
1069 mWorkerRef->Private()->IsThirdPartyContextToTopWindow());
1071 Maybe<ClientInfo> clientInfo;
1072 clientInfo.emplace(loadContext->mClientInfo.ref());
1073 rv = AddClientChannelHelper(channel, std::move(clientInfo),
1074 Maybe<ClientInfo>(),
1075 mWorkerRef->Private()->HybridEventTarget());
1076 if (NS_WARN_IF(NS_FAILED(rv))) {
1077 return rv;
1081 if (StaticPrefs::browser_tabs_remote_useCrossOriginEmbedderPolicy()) {
1082 nsILoadInfo::CrossOriginEmbedderPolicy respectedCOEP =
1083 mWorkerRef->Private()->GetEmbedderPolicy();
1084 if (mWorkerRef->Private()->IsDedicatedWorker() &&
1085 respectedCOEP == nsILoadInfo::EMBEDDER_POLICY_NULL) {
1086 respectedCOEP = mWorkerRef->Private()->GetOwnerEmbedderPolicy();
1089 nsCOMPtr<nsILoadInfo> channelLoadInfo = channel->LoadInfo();
1090 channelLoadInfo->SetLoadingEmbedderPolicy(respectedCOEP);
1093 if (loadContext->mCacheStatus != WorkerLoadContext::ToBeCached) {
1094 rv = channel->AsyncOpen(loader);
1095 if (NS_WARN_IF(NS_FAILED(rv))) {
1096 return rv;
1098 } else {
1099 nsCOMPtr<nsIOutputStream> writer;
1101 // In case we return early.
1102 loadContext->mCacheStatus = WorkerLoadContext::Cancel;
1104 NS_NewPipe(getter_AddRefs(loadContext->mCacheReadStream),
1105 getter_AddRefs(writer), 0,
1106 UINT32_MAX, // unlimited size to avoid writer WOULD_BLOCK case
1107 true, false); // non-blocking reader, blocking writer
1109 nsCOMPtr<nsIStreamListenerTee> tee =
1110 do_CreateInstance(NS_STREAMLISTENERTEE_CONTRACTID);
1111 rv = tee->Init(loader, writer, listener);
1112 if (NS_WARN_IF(NS_FAILED(rv))) {
1113 return rv;
1116 nsresult rv = channel->AsyncOpen(tee);
1117 if (NS_WARN_IF(NS_FAILED(rv))) {
1118 return rv;
1122 loadContext->mChannel.swap(channel);
1124 return NS_OK;
1127 nsresult WorkerScriptLoader::FillCompileOptionsForRequest(
1128 JSContext* cx, ScriptLoadRequest* aRequest, JS::CompileOptions* aOptions,
1129 JS::MutableHandle<JSScript*> aIntroductionScript) {
1130 // The full URL shouldn't be exposed to the debugger. See Bug 1634872
1131 aOptions->setFileAndLine(aRequest->mURL.get(), 1);
1132 aOptions->setNoScriptRval(true);
1134 aOptions->setMutedErrors(
1135 aRequest->GetWorkerLoadContext()->mMutedErrorFlag.value());
1137 if (aRequest->mSourceMapURL) {
1138 aOptions->setSourceMapURL(aRequest->mSourceMapURL->get());
1141 return NS_OK;
1144 bool WorkerScriptLoader::EvaluateScript(JSContext* aCx,
1145 ScriptLoadRequest* aRequest) {
1146 mWorkerRef->Private()->AssertIsOnWorkerThread();
1147 MOZ_ASSERT(!IsDynamicImport(aRequest));
1149 WorkerLoadContext* loadContext = aRequest->GetWorkerLoadContext();
1151 NS_ASSERTION(!loadContext->mChannel, "Should no longer have a channel!");
1152 NS_ASSERTION(aRequest->IsFinished(), "Should be scheduled!");
1154 MOZ_ASSERT(!mRv.Failed(), "Who failed it and why?");
1155 mRv.MightThrowJSException();
1156 if (NS_FAILED(loadContext->mLoadResult)) {
1157 ReportErrorToConsole(aRequest, loadContext->mLoadResult);
1158 return false;
1161 // If this is a top level script that succeeded, then mark the
1162 // Client execution ready and possible controlled by a service worker.
1163 if (loadContext->IsTopLevel()) {
1164 if (mController.isSome()) {
1165 MOZ_ASSERT(mWorkerScriptType == WorkerScript,
1166 "Debugger clients can't be controlled.");
1167 mWorkerRef->Private()->GlobalScope()->Control(mController.ref());
1169 mWorkerRef->Private()->ExecutionReady();
1172 if (aRequest->IsModuleRequest()) {
1173 // Only the top level module of the module graph will be executed from here,
1174 // the rest will be executed from SpiderMonkey as part of the execution of
1175 // the module graph.
1176 MOZ_ASSERT(aRequest->IsTopLevel());
1177 ModuleLoadRequest* request = aRequest->AsModuleRequest();
1178 if (!request->mModuleScript) {
1179 return false;
1182 // https://html.spec.whatwg.org/#run-a-worker
1183 // if script's error to rethrow is non-null, then:
1184 // Queue a global task on the DOM manipulation task source given worker's
1185 // relevant global object to fire an event named error at worker.
1187 // The event will be dispatched in CompileScriptRunnable.
1188 if (request->mModuleScript->HasParseError()) {
1189 // Here we assign an error code that is not a JS Exception, so
1190 // CompileRunnable can dispatch the event.
1191 mRv.Throw(NS_ERROR_DOM_SYNTAX_ERR);
1192 return false;
1195 // Implements To fetch a worklet/module worker script graph
1196 // Step 5. Fetch the descendants of and link result.
1197 if (!request->InstantiateModuleGraph()) {
1198 return false;
1201 if (request->mModuleScript->HasErrorToRethrow()) {
1202 // See the comments when we check HasParseError() above.
1203 mRv.Throw(NS_ERROR_DOM_SYNTAX_ERR);
1204 return false;
1207 nsresult rv = request->EvaluateModule();
1208 return NS_SUCCEEDED(rv);
1211 JS::CompileOptions options(aCx);
1212 // The introduction script is used by the DOM script loader as a way
1213 // to fill the Debugger Metadata for the JS Execution context. We don't use
1214 // the JS Execution context as we are not making use of async compilation
1215 // (delegation to another worker to produce bytecode or compile a string to a
1216 // JSScript), so it is not used in this context.
1217 JS::Rooted<JSScript*> unusedIntroductionScript(aCx);
1218 nsresult rv = FillCompileOptionsForRequest(aCx, aRequest, &options,
1219 &unusedIntroductionScript);
1221 MOZ_ASSERT(NS_SUCCEEDED(rv), "Filling compile options should not fail");
1223 // Our ErrorResult still shouldn't be a failure.
1224 MOZ_ASSERT(!mRv.Failed(), "Who failed it and why?");
1226 // Get the source text.
1227 ScriptLoadRequest::MaybeSourceText maybeSource;
1228 rv = aRequest->GetScriptSource(aCx, &maybeSource,
1229 aRequest->mLoadContext.get());
1230 if (NS_FAILED(rv)) {
1231 mRv.StealExceptionFromJSContext(aCx);
1232 return false;
1235 RefPtr<JS::loader::ClassicScript> classicScript = nullptr;
1236 if (StaticPrefs::dom_workers_modules_enabled() &&
1237 !mWorkerRef->Private()->IsServiceWorker()) {
1238 // We need a LoadedScript to be associated with the JSScript in order to
1239 // correctly resolve the referencing private for dynamic imports. In turn
1240 // this allows us to correctly resolve the BaseURL.
1242 // Dynamic import is disallowed on service workers. Additionally, causes
1243 // crashes because the life cycle isn't completed for service workers. To
1244 // keep things simple, we don't create a classic script for ServiceWorkers.
1245 // If this changes then we will need to ensure that the reference that is
1246 // held is released appropriately.
1247 nsCOMPtr<nsIURI> requestBaseURI;
1248 if (loadContext->mMutedErrorFlag.valueOr(false)) {
1249 NS_NewURI(getter_AddRefs(requestBaseURI), "about:blank"_ns);
1250 } else {
1251 requestBaseURI = aRequest->mBaseURL;
1253 MOZ_ASSERT(aRequest->mLoadedScript->IsClassicScript());
1254 MOZ_ASSERT(aRequest->mLoadedScript->GetFetchOptions() ==
1255 aRequest->mFetchOptions);
1256 aRequest->mLoadedScript->SetBaseURL(requestBaseURI);
1257 classicScript = aRequest->mLoadedScript->AsClassicScript();
1260 JS::Rooted<JSScript*> script(aCx);
1261 script = aRequest->IsUTF8Text()
1262 ? JS::Compile(aCx, options,
1263 maybeSource.ref<JS::SourceText<Utf8Unit>>())
1264 : JS::Compile(aCx, options,
1265 maybeSource.ref<JS::SourceText<char16_t>>());
1266 if (!script) {
1267 if (loadContext->IsTopLevel()) {
1268 // This is a top-level worker script,
1270 // https://html.spec.whatwg.org/#run-a-worker
1271 // If script is null or if script's error to rethrow is non-null, then:
1272 // Queue a global task on the DOM manipulation task source given
1273 // worker's relevant global object to fire an event named error at
1274 // worker.
1275 JS_ClearPendingException(aCx);
1276 mRv.Throw(NS_ERROR_DOM_SYNTAX_ERR);
1277 } else {
1278 // This is a script which is loaded by importScripts().
1280 // https://html.spec.whatwg.org/#import-scripts-into-worker-global-scope
1281 // For each url in the resulting URL records:
1282 // Fetch a classic worker-imported script given url and settings object,
1283 // passing along performFetch if provided. If this succeeds, let script
1284 // be the result. Otherwise, rethrow the exception.
1285 mRv.StealExceptionFromJSContext(aCx);
1288 return false;
1291 bool successfullyEvaluated = EvaluateSourceBuffer(aCx, script, classicScript);
1292 if (aRequest->IsCanceled()) {
1293 return false;
1295 if (!successfullyEvaluated) {
1296 mRv.StealExceptionFromJSContext(aCx);
1297 return false;
1299 // steal the loadContext so that the cycle is broken and cycle collector can
1300 // collect the scriptLoadRequest.
1301 return true;
1304 void WorkerScriptLoader::TryShutdown() {
1306 MutexAutoLock lock(CleanUpLock());
1307 if (CleanedUp()) {
1308 return;
1312 if (AllScriptsExecuted() && AllModuleRequestsLoaded()) {
1313 ShutdownScriptLoader(!mExecutionAborted, mMutedErrorFlag);
1317 void WorkerScriptLoader::ShutdownScriptLoader(bool aResult, bool aMutedError) {
1318 MOZ_ASSERT(AllScriptsExecuted());
1319 MOZ_ASSERT(AllModuleRequestsLoaded());
1320 mWorkerRef->Private()->AssertIsOnWorkerThread();
1322 if (!aResult) {
1323 // At this point there are two possibilities:
1325 // 1) mRv.Failed(). In that case we just want to leave it
1326 // as-is, except if it has a JS exception and we need to mute JS
1327 // exceptions. In that case, we log the exception without firing any
1328 // events and then replace it on the ErrorResult with a NetworkError,
1329 // per spec.
1331 // 2) mRv succeeded. As far as I can tell, this can only
1332 // happen when loading the main worker script and
1333 // GetOrCreateGlobalScope() fails or if ScriptExecutorRunnable::Cancel
1334 // got called. Does it matter what we throw in this case? I'm not
1335 // sure...
1336 if (mRv.Failed()) {
1337 if (aMutedError && mRv.IsJSException()) {
1338 LogExceptionToConsole(mWorkerRef->Private()->GetJSContext(),
1339 mWorkerRef->Private());
1340 mRv.Throw(NS_ERROR_DOM_NETWORK_ERR);
1342 } else {
1343 mRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
1347 // Lock, shutdown, and cleanup state. After this the Loader is closed.
1349 MutexAutoLock lock(CleanUpLock());
1351 if (CleanedUp()) {
1352 return;
1355 mWorkerRef->Private()->AssertIsOnWorkerThread();
1356 // Module loader doesn't use sync loop for dynamic import
1357 if (mSyncLoopTarget) {
1358 mWorkerRef->Private()->MaybeStopSyncLoop(
1359 mSyncLoopTarget, aResult ? NS_OK : NS_ERROR_FAILURE);
1360 mSyncLoopTarget = nullptr;
1363 // Signal cleanup
1364 mCleanedUp = true;
1366 // Allow worker shutdown.
1367 mWorkerRef = nullptr;
1371 void WorkerScriptLoader::ReportErrorToConsole(ScriptLoadRequest* aRequest,
1372 nsresult aResult) const {
1373 nsAutoString url = NS_ConvertUTF8toUTF16(aRequest->mURL);
1374 workerinternals::ReportLoadError(mRv, aResult, url);
1377 void WorkerScriptLoader::LogExceptionToConsole(JSContext* aCx,
1378 WorkerPrivate* aWorkerPrivate) {
1379 aWorkerPrivate->AssertIsOnWorkerThread();
1381 MOZ_ASSERT(mRv.IsJSException());
1383 JS::Rooted<JS::Value> exn(aCx);
1384 if (!ToJSValue(aCx, std::move(mRv), &exn)) {
1385 return;
1388 // Now the exception state should all be in exn.
1389 MOZ_ASSERT(!JS_IsExceptionPending(aCx));
1390 MOZ_ASSERT(!mRv.Failed());
1392 JS::ExceptionStack exnStack(aCx, exn, nullptr);
1393 JS::ErrorReportBuilder report(aCx);
1394 if (!report.init(aCx, exnStack, JS::ErrorReportBuilder::WithSideEffects)) {
1395 JS_ClearPendingException(aCx);
1396 return;
1399 RefPtr<xpc::ErrorReport> xpcReport = new xpc::ErrorReport();
1400 xpcReport->Init(report.report(), report.toStringResult().c_str(),
1401 aWorkerPrivate->IsChromeWorker(), aWorkerPrivate->WindowID());
1403 RefPtr<AsyncErrorReporter> r = new AsyncErrorReporter(xpcReport);
1404 NS_DispatchToMainThread(r);
1407 bool WorkerScriptLoader::AllModuleRequestsLoaded() const {
1408 mWorkerRef->Private()->AssertIsOnWorkerThread();
1409 return mLoadingModuleRequestCount == 0;
1412 void WorkerScriptLoader::IncreaseLoadingModuleRequestCount() {
1413 mWorkerRef->Private()->AssertIsOnWorkerThread();
1414 ++mLoadingModuleRequestCount;
1417 void WorkerScriptLoader::DecreaseLoadingModuleRequestCount() {
1418 mWorkerRef->Private()->AssertIsOnWorkerThread();
1419 --mLoadingModuleRequestCount;
1422 NS_IMPL_ISUPPORTS(ScriptLoaderRunnable, nsIRunnable, nsINamed)
1424 NS_IMPL_ISUPPORTS(WorkerScriptLoader, nsINamed)
1426 ScriptLoaderRunnable::ScriptLoaderRunnable(
1427 WorkerScriptLoader* aScriptLoader,
1428 nsTArray<RefPtr<ThreadSafeRequestHandle>> aLoadingRequests)
1429 : mScriptLoader(aScriptLoader),
1430 mWorkerRef(aScriptLoader->mWorkerRef),
1431 mLoadingRequests(std::move(aLoadingRequests)),
1432 mCancelMainThread(Nothing()) {
1433 MOZ_ASSERT(aScriptLoader);
1436 nsresult ScriptLoaderRunnable::Run() {
1437 AssertIsOnMainThread();
1439 // Convert the origin stack to JSON (which must be done on the main
1440 // thread) explicitly, so that we can use the stack to notify the net
1441 // monitor about every script we load. We do this, rather than pass
1442 // the stack directly to the netmonitor, in order to be able to use this
1443 // for all subsequent scripts.
1444 if (mScriptLoader->mOriginStack &&
1445 mScriptLoader->mOriginStackJSON.IsEmpty()) {
1446 ConvertSerializedStackToJSON(std::move(mScriptLoader->mOriginStack),
1447 mScriptLoader->mOriginStackJSON);
1450 if (!mWorkerRef->Private()->IsServiceWorker() ||
1451 mScriptLoader->IsDebuggerScript()) {
1452 for (ThreadSafeRequestHandle* handle : mLoadingRequests) {
1453 handle->mRunnable = this;
1456 for (ThreadSafeRequestHandle* handle : mLoadingRequests) {
1457 nsresult rv = mScriptLoader->LoadScript(handle);
1458 if (NS_WARN_IF(NS_FAILED(rv))) {
1459 LoadingFinished(handle, rv);
1460 CancelMainThread(rv);
1461 return rv;
1465 return NS_OK;
1468 MOZ_ASSERT(!mCacheCreator);
1469 mCacheCreator = new CacheCreator(mWorkerRef->Private());
1471 for (ThreadSafeRequestHandle* handle : mLoadingRequests) {
1472 handle->mRunnable = this;
1473 WorkerLoadContext* loadContext = handle->GetContext();
1474 mCacheCreator->AddLoader(MakeNotNull<RefPtr<CacheLoadHandler>>(
1475 mWorkerRef, handle, loadContext->IsTopLevel(),
1476 loadContext->mOnlyExistingCachedResourcesAllowed, mScriptLoader));
1479 // The worker may have a null principal on first load, but in that case its
1480 // parent definitely will have one.
1481 nsIPrincipal* principal = mWorkerRef->Private()->GetPrincipal();
1482 if (!principal) {
1483 WorkerPrivate* parentWorker = mWorkerRef->Private()->GetParent();
1484 MOZ_ASSERT(parentWorker, "Must have a parent!");
1485 principal = parentWorker->GetPrincipal();
1488 nsresult rv = mCacheCreator->Load(principal);
1489 if (NS_WARN_IF(NS_FAILED(rv))) {
1490 CancelMainThread(rv);
1491 return rv;
1494 return NS_OK;
1497 nsresult ScriptLoaderRunnable::OnStreamComplete(
1498 ThreadSafeRequestHandle* aRequestHandle, nsresult aStatus) {
1499 AssertIsOnMainThread();
1501 LoadingFinished(aRequestHandle, aStatus);
1502 return NS_OK;
1505 void ScriptLoaderRunnable::LoadingFinished(
1506 ThreadSafeRequestHandle* aRequestHandle, nsresult aRv) {
1507 AssertIsOnMainThread();
1509 WorkerLoadContext* loadContext = aRequestHandle->GetContext();
1511 loadContext->mLoadResult = aRv;
1512 MOZ_ASSERT(!loadContext->mLoadingFinished);
1513 loadContext->mLoadingFinished = true;
1515 if (loadContext->IsTopLevel() && NS_SUCCEEDED(aRv)) {
1516 MOZ_DIAGNOSTIC_ASSERT(
1517 mWorkerRef->Private()->PrincipalURIMatchesScriptURL());
1520 MaybeExecuteFinishedScripts(aRequestHandle);
1523 void ScriptLoaderRunnable::MaybeExecuteFinishedScripts(
1524 ThreadSafeRequestHandle* aRequestHandle) {
1525 AssertIsOnMainThread();
1527 // We execute the last step if we don't have a pending operation with the
1528 // cache and the loading is completed.
1529 WorkerLoadContext* loadContext = aRequestHandle->GetContext();
1530 if (!loadContext->IsAwaitingPromise()) {
1531 if (aRequestHandle->GetContext()->IsTopLevel()) {
1532 mWorkerRef->Private()->WorkerScriptLoaded();
1534 DispatchProcessPendingRequests();
1538 void ScriptLoaderRunnable::CancelMainThreadWithBindingAborted() {
1539 AssertIsOnMainThread();
1540 CancelMainThread(NS_BINDING_ABORTED);
1543 void ScriptLoaderRunnable::CancelMainThread(nsresult aCancelResult) {
1544 AssertIsOnMainThread();
1545 if (IsCancelled()) {
1546 return;
1550 MutexAutoLock lock(mScriptLoader->CleanUpLock());
1552 // Check if we have already cancelled, or if the worker has been killed
1553 // before we cancel.
1554 if (mScriptLoader->CleanedUp()) {
1555 return;
1558 mCancelMainThread = Some(aCancelResult);
1560 for (ThreadSafeRequestHandle* handle : mLoadingRequests) {
1561 if (handle->IsEmpty()) {
1562 continue;
1565 bool callLoadingFinished = true;
1567 WorkerLoadContext* loadContext = handle->GetContext();
1568 if (!loadContext) {
1569 continue;
1572 if (loadContext->IsAwaitingPromise()) {
1573 MOZ_ASSERT(mWorkerRef->Private()->IsServiceWorker());
1574 loadContext->mCachePromise->MaybeReject(NS_BINDING_ABORTED);
1575 loadContext->mCachePromise = nullptr;
1576 callLoadingFinished = false;
1578 if (loadContext->mChannel) {
1579 if (NS_SUCCEEDED(loadContext->mChannel->Cancel(aCancelResult))) {
1580 callLoadingFinished = false;
1581 } else {
1582 NS_WARNING("Failed to cancel channel!");
1585 if (callLoadingFinished && !loadContext->mLoadingFinished) {
1586 LoadingFinished(handle, aCancelResult);
1589 DispatchProcessPendingRequests();
1593 void ScriptLoaderRunnable::DispatchProcessPendingRequests() {
1594 AssertIsOnMainThread();
1596 const auto begin = mLoadingRequests.begin();
1597 const auto end = mLoadingRequests.end();
1598 using Iterator = decltype(begin);
1599 const auto maybeRangeToExecute =
1600 [begin, end]() -> Maybe<std::pair<Iterator, Iterator>> {
1601 // firstItToExecute is the first loadInfo where mExecutionScheduled is
1602 // unset.
1603 auto firstItToExecute = std::find_if(
1604 begin, end, [](const RefPtr<ThreadSafeRequestHandle>& requestHandle) {
1605 return !requestHandle->mExecutionScheduled;
1608 if (firstItToExecute == end) {
1609 return Nothing();
1612 // firstItUnexecutable is the first loadInfo that is not yet finished.
1613 // Update mExecutionScheduled on the ones we're about to schedule for
1614 // execution.
1615 const auto firstItUnexecutable =
1616 std::find_if(firstItToExecute, end,
1617 [](RefPtr<ThreadSafeRequestHandle>& requestHandle) {
1618 MOZ_ASSERT(!requestHandle->IsEmpty());
1619 if (!requestHandle->Finished()) {
1620 return true;
1623 // We can execute this one.
1624 requestHandle->mExecutionScheduled = true;
1626 return false;
1629 return firstItUnexecutable == firstItToExecute
1630 ? Nothing()
1631 : Some(std::pair(firstItToExecute, firstItUnexecutable));
1632 }();
1634 // If there are no unexecutable load infos, we can unuse things before the
1635 // execution of the scripts and the stopping of the sync loop.
1636 if (maybeRangeToExecute) {
1637 if (maybeRangeToExecute->second == end) {
1638 mCacheCreator = nullptr;
1641 RefPtr<ScriptExecutorRunnable> runnable = new ScriptExecutorRunnable(
1642 mScriptLoader, mWorkerRef->Private(), mScriptLoader->mSyncLoopTarget,
1643 Span<RefPtr<ThreadSafeRequestHandle>>{maybeRangeToExecute->first,
1644 maybeRangeToExecute->second});
1646 if (!runnable->Dispatch() && mScriptLoader->mSyncLoopTarget) {
1647 MOZ_ASSERT(false, "This should never fail!");
1652 ScriptExecutorRunnable::ScriptExecutorRunnable(
1653 WorkerScriptLoader* aScriptLoader, WorkerPrivate* aWorkerPrivate,
1654 nsISerialEventTarget* aSyncLoopTarget,
1655 Span<RefPtr<ThreadSafeRequestHandle>> aLoadedRequests)
1656 : MainThreadWorkerSyncRunnable(aWorkerPrivate, aSyncLoopTarget,
1657 "ScriptExecutorRunnable"),
1658 mScriptLoader(aScriptLoader),
1659 mLoadedRequests(aLoadedRequests) {}
1661 bool ScriptExecutorRunnable::IsDebuggerRunnable() const {
1662 // ScriptExecutorRunnable is used to execute both worker and debugger scripts.
1663 // In the latter case, the runnable needs to be dispatched to the debugger
1664 // queue.
1665 return mScriptLoader->IsDebuggerScript();
1668 bool ScriptExecutorRunnable::PreRun(WorkerPrivate* aWorkerPrivate) {
1669 aWorkerPrivate->AssertIsOnWorkerThread();
1671 // We must be on the same worker as we started on.
1672 MOZ_ASSERT(
1673 mScriptLoader->mSyncLoopTarget == mSyncLoopTarget,
1674 "Unexpected SyncLoopTarget. Check if the sync loop was closed early");
1677 // There is a possibility that we cleaned up while this task was waiting to
1678 // run. If this has happened, return and exit.
1679 MutexAutoLock lock(mScriptLoader->CleanUpLock());
1680 if (mScriptLoader->CleanedUp()) {
1681 return true;
1684 const auto& requestHandle = mLoadedRequests[0];
1685 // Check if the request is still valid.
1686 if (requestHandle->IsEmpty() ||
1687 !requestHandle->GetContext()->IsTopLevel()) {
1688 return true;
1692 return mScriptLoader->StoreCSP();
1695 bool ScriptExecutorRunnable::ProcessModuleScript(
1696 JSContext* aCx, WorkerPrivate* aWorkerPrivate) {
1697 // We should only ever have one script when processing modules
1698 MOZ_ASSERT(mLoadedRequests.Length() == 1);
1699 RefPtr<ScriptLoadRequest> request;
1701 // There is a possibility that we cleaned up while this task was waiting to
1702 // run. If this has happened, return and exit.
1703 MutexAutoLock lock(mScriptLoader->CleanUpLock());
1704 if (mScriptLoader->CleanedUp()) {
1705 return true;
1708 MOZ_ASSERT(mLoadedRequests.Length() == 1);
1709 const auto& requestHandle = mLoadedRequests[0];
1710 // The request must be valid.
1711 MOZ_ASSERT(!requestHandle->IsEmpty());
1713 // Release the request to the worker. From this point on, the Request Handle
1714 // is empty.
1715 request = requestHandle->ReleaseRequest();
1717 // release lock. We will need it later if we cleanup.
1720 MOZ_ASSERT(request->IsModuleRequest());
1722 WorkerLoadContext* loadContext = request->GetWorkerLoadContext();
1723 ModuleLoadRequest* moduleRequest = request->AsModuleRequest();
1725 // DecreaseLoadingModuleRequestCount must be called before OnFetchComplete.
1726 // OnFetchComplete will call ProcessPendingRequests, and in
1727 // ProcessPendingRequests it will try to shutdown if
1728 // AllModuleRequestsLoaded() returns true.
1729 mScriptLoader->DecreaseLoadingModuleRequestCount();
1730 moduleRequest->OnFetchComplete(loadContext->mLoadResult);
1732 if (NS_FAILED(loadContext->mLoadResult)) {
1733 if (moduleRequest->IsDynamicImport()) {
1734 if (request->isInList()) {
1735 moduleRequest->CancelDynamicImport(loadContext->mLoadResult);
1736 mScriptLoader->TryShutdown();
1738 } else if (!moduleRequest->IsTopLevel()) {
1739 moduleRequest->Cancel();
1740 mScriptLoader->TryShutdown();
1741 } else {
1742 moduleRequest->LoadFailed();
1745 return true;
1748 bool ScriptExecutorRunnable::ProcessClassicScripts(
1749 JSContext* aCx, WorkerPrivate* aWorkerPrivate) {
1750 // There is a possibility that we cleaned up while this task was waiting to
1751 // run. If this has happened, return and exit.
1753 MutexAutoLock lock(mScriptLoader->CleanUpLock());
1754 if (mScriptLoader->CleanedUp()) {
1755 return true;
1758 for (const auto& requestHandle : mLoadedRequests) {
1759 // The request must be valid.
1760 MOZ_ASSERT(!requestHandle->IsEmpty());
1762 // Release the request to the worker. From this point on, the Request
1763 // Handle is empty.
1764 RefPtr<ScriptLoadRequest> request = requestHandle->ReleaseRequest();
1765 mScriptLoader->MaybeMoveToLoadedList(request);
1768 return mScriptLoader->ProcessPendingRequests(aCx);
1771 bool ScriptExecutorRunnable::WorkerRun(JSContext* aCx,
1772 WorkerPrivate* aWorkerPrivate) {
1773 aWorkerPrivate->AssertIsOnWorkerThread();
1775 // We must be on the same worker as we started on.
1776 MOZ_ASSERT(
1777 mScriptLoader->mSyncLoopTarget == mSyncLoopTarget,
1778 "Unexpected SyncLoopTarget. Check if the sync loop was closed early");
1780 if (mLoadedRequests.begin()->get()->GetRequest()->IsModuleRequest()) {
1781 return ProcessModuleScript(aCx, aWorkerPrivate);
1784 return ProcessClassicScripts(aCx, aWorkerPrivate);
1787 nsresult ScriptExecutorRunnable::Cancel() {
1788 if (mScriptLoader->AllScriptsExecuted() &&
1789 mScriptLoader->AllModuleRequestsLoaded()) {
1790 mScriptLoader->ShutdownScriptLoader(false, false);
1792 return NS_OK;
1795 } /* namespace loader */
1797 nsresult ChannelFromScriptURLMainThread(
1798 nsIPrincipal* aPrincipal, Document* aParentDoc, nsILoadGroup* aLoadGroup,
1799 nsIURI* aScriptURL, const WorkerType& aWorkerType,
1800 const RequestCredentials& aCredentials,
1801 const Maybe<ClientInfo>& aClientInfo,
1802 nsContentPolicyType aMainScriptContentPolicyType,
1803 nsICookieJarSettings* aCookieJarSettings, nsIReferrerInfo* aReferrerInfo,
1804 nsIChannel** aChannel) {
1805 AssertIsOnMainThread();
1807 nsCOMPtr<nsIIOService> ios(do_GetIOService());
1809 nsIScriptSecurityManager* secMan = nsContentUtils::GetSecurityManager();
1810 NS_ASSERTION(secMan, "This should never be null!");
1812 uint32_t secFlags;
1813 nsresult rv;
1814 if (aWorkerType == WorkerType::Module) {
1815 rv = GetModuleSecFlags(true, aPrincipal, WorkerScript, aScriptURL,
1816 aCredentials, secFlags);
1817 } else {
1818 rv = GetClassicSecFlags(true, aScriptURL, aPrincipal, WorkerScript,
1819 secFlags);
1821 if (NS_FAILED(rv)) {
1822 return rv;
1825 return ChannelFromScriptURL(
1826 aPrincipal, aParentDoc, nullptr, aLoadGroup, ios, secMan, aScriptURL,
1827 aClientInfo, Maybe<ServiceWorkerDescriptor>(), true, WorkerScript,
1828 aMainScriptContentPolicyType, nsIRequest::LOAD_NORMAL, secFlags,
1829 aCookieJarSettings, aReferrerInfo, aChannel);
1832 nsresult ChannelFromScriptURLWorkerThread(
1833 JSContext* aCx, WorkerPrivate* aParent, const nsAString& aScriptURL,
1834 const WorkerType& aWorkerType, const RequestCredentials& aCredentials,
1835 WorkerLoadInfo& aLoadInfo) {
1836 aParent->AssertIsOnWorkerThread();
1838 RefPtr<ChannelGetterRunnable> getter = new ChannelGetterRunnable(
1839 aParent, aScriptURL, aWorkerType, aCredentials, aLoadInfo);
1841 ErrorResult rv;
1842 getter->Dispatch(Canceling, rv);
1843 if (rv.Failed()) {
1844 NS_ERROR("Failed to dispatch!");
1845 return rv.StealNSResult();
1848 return getter->GetResult();
1851 void ReportLoadError(ErrorResult& aRv, nsresult aLoadResult,
1852 const nsAString& aScriptURL) {
1853 MOZ_ASSERT(!aRv.Failed());
1855 nsPrintfCString err("Failed to load worker script at \"%s\"",
1856 NS_ConvertUTF16toUTF8(aScriptURL).get());
1858 switch (aLoadResult) {
1859 case NS_ERROR_FILE_NOT_FOUND:
1860 case NS_ERROR_NOT_AVAILABLE:
1861 case NS_ERROR_CORRUPTED_CONTENT:
1862 aRv.Throw(NS_ERROR_DOM_NETWORK_ERR);
1863 break;
1865 case NS_ERROR_MALFORMED_URI:
1866 case NS_ERROR_DOM_SYNTAX_ERR:
1867 aRv.ThrowSyntaxError(err);
1868 break;
1870 case NS_BINDING_ABORTED:
1871 // Note: we used to pretend like we didn't set an exception for
1872 // NS_BINDING_ABORTED, but then ShutdownScriptLoader did it anyway. The
1873 // other callsite, in WorkerPrivate::Constructor, never passed in
1874 // NS_BINDING_ABORTED. So just throw it directly here. Consumers will
1875 // deal as needed. But note that we do NOT want to use one of the
1876 // Throw*Error() methods on ErrorResult for this case, because that will
1877 // make it impossible for consumers to realize that our error was
1878 // NS_BINDING_ABORTED.
1879 aRv.Throw(aLoadResult);
1880 return;
1882 case NS_ERROR_DOM_BAD_URI:
1883 // This is actually a security error.
1884 case NS_ERROR_DOM_SECURITY_ERR:
1885 aRv.ThrowSecurityError(err);
1886 break;
1888 default:
1889 // For lack of anything better, go ahead and throw a NetworkError here.
1890 // We don't want to throw a JS exception, because for toplevel script
1891 // loads that would get squelched.
1892 aRv.ThrowNetworkError(nsPrintfCString(
1893 "Failed to load worker script at %s (nsresult = 0x%" PRIx32 ")",
1894 NS_ConvertUTF16toUTF8(aScriptURL).get(),
1895 static_cast<uint32_t>(aLoadResult)));
1896 return;
1900 void LoadMainScript(WorkerPrivate* aWorkerPrivate,
1901 UniquePtr<SerializedStackHolder> aOriginStack,
1902 const nsAString& aScriptURL,
1903 WorkerScriptType aWorkerScriptType, ErrorResult& aRv,
1904 const mozilla::Encoding* aDocumentEncoding) {
1905 nsTArray<nsString> scriptURLs;
1907 scriptURLs.AppendElement(aScriptURL);
1909 LoadAllScripts(aWorkerPrivate, std::move(aOriginStack), scriptURLs, true,
1910 aWorkerScriptType, aRv, aDocumentEncoding);
1913 void Load(WorkerPrivate* aWorkerPrivate,
1914 UniquePtr<SerializedStackHolder> aOriginStack,
1915 const nsTArray<nsString>& aScriptURLs,
1916 WorkerScriptType aWorkerScriptType, ErrorResult& aRv) {
1917 const uint32_t urlCount = aScriptURLs.Length();
1919 if (!urlCount) {
1920 return;
1923 if (urlCount > MAX_CONCURRENT_SCRIPTS) {
1924 aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
1925 return;
1928 LoadAllScripts(aWorkerPrivate, std::move(aOriginStack), aScriptURLs, false,
1929 aWorkerScriptType, aRv);
1932 } // namespace mozilla::dom::workerinternals