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