/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set ts=8 sts=2 et sw=2 tw=80: */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
// Only use the document when its principal matches the principal of the // current request. This means scripts fetched using the Workers' own // principal won't inherit properties of the document, in particular the CSP. if (parentDoc && parentDoc->NodePrincipal() != principal) {
parentDoc = nullptr;
}
// The main service worker script should never be loaded over the network // in this path. It should always be offlined by ServiceWorkerScriptCache. // We assert here since this error should also be caught by the runtime // check in CacheLoadHandler. // // Note, if we ever allow service worker scripts to be loaded from network // here we need to configure the channel properly. For example, it must // not allow redirects.
MOZ_DIAGNOSTIC_ASSERT(aContentPolicyType !=
nsIContentPolicy::TYPE_INTERNAL_SERVICE_WORKER);
nsCOMPtr<nsIChannel> channel; if (parentDoc) { // This is the path for top level dedicated worker scripts with a document
rv = NS_NewChannel(getter_AddRefs(channel), uri, parentDoc, aSecFlags,
aContentPolicyType,
nullptr, // aPerformanceStorage
loadGroup,
nullptr, // aCallbacks
aLoadFlags, ios);
NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_SECURITY_ERR);
} else { // This branch is used in the following cases: // * Shared and ServiceWorkers (who do not have a doc) // * Static Module Imports // * ImportScripts
// We must have a loadGroup with a load context for the principal to // traverse the channel correctly.
if (aClientInfo.isSome()) { // If we have an existing clientInfo (true for all modules and // importScripts), we will use this branch
rv = NS_NewChannel(getter_AddRefs(channel), uri, principal,
aClientInfo.ref(), aController, aSecFlags,
aContentPolicyType, aCookieJarSettings,
performanceStorage, loadGroup, nullptr, // aCallbacks
aLoadFlags, ios);
} else {
rv = NS_NewChannel(getter_AddRefs(channel), uri, principal, aSecFlags,
aContentPolicyType, aCookieJarSettings,
performanceStorage, loadGroup, nullptr, // aCallbacks
aLoadFlags, ios);
}
bool ok = loader->CreateScriptRequests(aScriptURLs, aDocumentEncoding,
aIsMainScript);
if (!ok) { return;
} // Bug 1817259 - For now, we force loading the debugger script as Classic, // even if the debugged worker is a Module. if (aWorkerPrivate->WorkerType() == WorkerType::Module &&
aWorkerScriptType != DebuggerScript) {
MOZ_ASSERT(aIsMainScript); // Module Load
RefPtr<JS::loader::ScriptLoadRequest> mainScript = loader->GetMainScript(); if (mainScript && mainScript->IsModuleRequest()) { if (NS_FAILED(mainScript->AsModuleRequest()->StartModuleLoad())) { return;
}
syncLoop.Run(); return;
}
}
if (loader->DispatchLoadScripts()) {
syncLoop.Run();
}
}
class ChannelGetterRunnable final : public WorkerMainThreadRunnable { const nsAString& mScriptURL; const WorkerType& mWorkerType; const RequestCredentials& mCredentials; const ClientInfo mClientInfo;
WorkerLoadInfo& mLoadInfo;
nsresult mResult;
public:
ChannelGetterRunnable(WorkerPrivate* aParentWorker, const nsAString& aScriptURL, const WorkerType& aWorkerType, const RequestCredentials& aCredentials,
WorkerLoadInfo& aLoadInfo)
: WorkerMainThreadRunnable(aParentWorker, "ScriptLoader :: ChannelGetter"_ns),
mScriptURL(aScriptURL) // ClientInfo should always be present since this should not be called // if parent's status is greater than Running.
,
mWorkerType(aWorkerType),
mCredentials(aCredentials),
mClientInfo(aParentWorker->GlobalScope()->GetClientInfo().ref()),
mLoadInfo(aLoadInfo),
mResult(NS_ERROR_FAILURE) {
MOZ_ASSERT(aParentWorker);
aParentWorker->AssertIsOnWorkerThread();
}
// Initialize the WorkerLoadInfo principal to our triggering principal // before doing anything else. Normally we do this in the WorkerPrivate // Constructor, but we can't do so off the main thread when creating // a nested worker. So do it here instead.
mLoadInfo.mLoadingPrincipal = workerPrivate->GetPrincipal();
MOZ_DIAGNOSTIC_ASSERT(mLoadInfo.mLoadingPrincipal);
if (aWorkerScriptType == DebuggerScript) { // A DebuggerScript needs to be a local resource like chrome: or resource: bool isUIResource = false;
nsresult rv = NS_URIChainHasFlags(
uri, nsIProtocolHandler::URI_IS_UI_RESOURCE, &isUIResource); if (NS_WARN_IF(NS_FAILED(rv))) { return rv;
}
if (!isUIResource) { return NS_ERROR_DOM_SECURITY_ERR;
}
secFlags |= nsILoadInfo::SEC_ALLOW_CHROME;
}
// Note: this is for backwards compatibility and goes against spec. // We should find a better solution. if (aIsMainScript && isData) {
secFlags = nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL;
}
return NS_OK;
}
nsresult GetModuleSecFlags(bool aIsTopLevel, nsIPrincipal* principal,
WorkerScriptType aWorkerScriptType, nsIURI* aURI,
RequestCredentials aCredentials,
uint32_t& secFlags) { // Implements "To fetch a single module script," // Step 9. If destination is "worker", "sharedworker", or "serviceworker", // and the top-level module fetch flag is set, then set request's // mode to "same-origin".
// Step 8. Let request be a new request whose [...] mode is "cors" [...]
secFlags = aIsTopLevel ? nsILoadInfo::SEC_REQUIRE_SAME_ORIGIN_DATA_IS_BLOCKED
: nsILoadInfo::SEC_REQUIRE_CORS_INHERITS_SEC_CONTEXT;
// This implements the same Cookie settings as nsContentSecurityManager's // ComputeSecurityFlags. The main difference is the line above, Step 9, // setting to same origin.
class ScriptExecutorRunnable final : public MainThreadWorkerSyncRunnable {
RefPtr<WorkerScriptLoader> mScriptLoader; const Span<RefPtr<ThreadSafeRequestHandle>> mLoadedRequests;
RefPtr<WorkerScriptLoader> self = new WorkerScriptLoader(
std::move(aOriginStack), aSyncLoopTarget, aWorkerScriptType, aRv);
RefPtr<StrongWorkerRef> workerRef = StrongWorkerRef::Create(
aWorkerPrivate, "WorkerScriptLoader::Create", [self]() { // Requests that are in flight are covered by the worker references // in DispatchLoadScript(s), so we do not need to do additional // cleanup, but just in case we are ready/aborted we can try to // shutdown here, too.
self->TryShutdown();
});
if (workerRef) {
self->mWorkerRef = new ThreadSafeWorkerRef(workerRef);
} else {
self->mRv.Throw(NS_ERROR_FAILURE); return nullptr;
}
nsIGlobalObject* global = self->GetGlobal();
self->mController = global->GetController();
// Set up the module loader, if it has not been initialzied yet.
self->InitModuleLoader();
void WorkerScriptLoader::InitModuleLoader() {
mWorkerRef->Private()->AssertIsOnWorkerThread(); if (GetGlobal()->GetModuleLoader(nullptr)) { return;
}
RefPtr<WorkerModuleLoader> moduleLoader = new WorkerModuleLoader(this, GetGlobal()); if (mWorkerScriptType == WorkerScript) {
mWorkerRef->Private()->GlobalScope()->InitModuleLoader(moduleLoader); return;
}
mWorkerRef->Private()->DebuggerGlobalScope()->InitModuleLoader(moduleLoader);
}
bool WorkerScriptLoader::CreateScriptRequests( const nsTArray<nsString>& aScriptURLs, const mozilla::Encoding* aDocumentEncoding, bool aIsMainScript) {
mWorkerRef->Private()->AssertIsOnWorkerThread(); // If a worker has been loaded as a module worker, ImportScripts calls are // disallowed -- then the operation is invalid. // // 10.3.1 Importing scripts and libraries. // Step 1. If worker global scope's type is "module", throw a TypeError // exception. // // Also, for now, the debugger script is always loaded as Classic, // even if the debugged worker is a Module. We still want to allow // it to use importScripts. if (mWorkerRef->Private()->WorkerType() == WorkerType::Module &&
!aIsMainScript && !IsDebuggerScript()) { // This should only run for non-main scripts, as only these are // importScripts
mRv.ThrowTypeError( "Using `ImportScripts` inside a Module Worker is " "disallowed."); returnfalse;
} for (const nsString& scriptURL : aScriptURLs) {
RefPtr<ScriptLoadRequest> request =
CreateScriptLoadRequest(scriptURL, aDocumentEncoding, aIsMainScript); if (!request) { returnfalse;
}
mLoadingRequests.AppendElement(request);
}
nsContentPolicyType WorkerScriptLoader::GetContentPolicyType(
ScriptLoadRequest* aRequest) { if (aRequest->GetWorkerLoadContext()->IsTopLevel()) { // Implements https://html.spec.whatwg.org/#worker-processing-model // Step 13: Let destination be "sharedworker" if is shared is true, and // "worker" otherwise. return mWorkerRef->Private()->ContentPolicyType();
} if (aRequest->IsModuleRequest()) { if (aRequest->AsModuleRequest()->IsDynamicImport()) { return nsIContentPolicy::TYPE_INTERNAL_MODULE;
}
// Implements the destination for Step 14 in // https://html.spec.whatwg.org/#worker-processing-model // // We need a special subresource type in order to correctly implement // the graph fetch, where the destination is set to "worker" or // "sharedworker". return nsIContentPolicy::TYPE_INTERNAL_WORKER_STATIC_MODULE;
} // For script imported in worker's importScripts(). return nsIContentPolicy::TYPE_INTERNAL_WORKER_IMPORT_SCRIPTS;
}
// (For non-serviceworkers, this variable does not matter, but false best // captures their behavior.) bool onlyExistingCachedResourcesAllowed = false; if (mWorkerRef->Private()->IsServiceWorker()) { // https://w3c.github.io/ServiceWorker/#importscripts step 4: // > 4. If serviceWorker’s state is not "parsed" or "installing": // > 1. Return map[url] if it exists and a network error otherwise. // // So if our state is beyond installing, it's too late to make a request // that would perform a new fetch which would be cached.
onlyExistingCachedResourcesAllowed =
mWorkerRef->Private()->GetServiceWorkerDescriptor().State() >
ServiceWorkerState::Installing;
}
RefPtr<WorkerLoadContext> loadContext = new WorkerLoadContext(
kind, clientInfo, this, onlyExistingCachedResourcesAllowed);
// Create ScriptLoadRequests for this WorkerScriptLoader
ReferrerPolicy referrerPolicy = mWorkerRef->Private()->GetReferrerPolicy();
// Only top level workers' main script use the document charset for the // script uri encoding. Otherwise, default encoding (UTF-8) is applied.
MOZ_ASSERT_IF(bool(aDocumentEncoding),
aIsMainScript && !mWorkerRef->Private()->GetParent());
nsCOMPtr<nsIURI> baseURI = aIsMainScript ? GetInitialBaseURI() : GetBaseURI();
nsCOMPtr<nsIURI> uri; bool setErrorResult = false;
nsresult rv =
ConstructURI(aScriptURL, baseURI, aDocumentEncoding, getter_AddRefs(uri)); // If we failed to construct the URI, handle it in the LoadContext so it is // thrown in the right order. if (NS_WARN_IF(NS_FAILED(rv))) {
setErrorResult = true;
loadContext->mLoadResult = rv;
}
// https://html.spec.whatwg.org/multipage/webappapis.html#fetch-a-classic-worker-script // Step 2.5. Let script be the result [...] and the default classic script // fetch options. // // https://html.spec.whatwg.org/multipage/webappapis.html#fetch-a-worklet/module-worker-script-graph // Step 1. Let options be a script fetch options whose cryptographic nonce is // the empty string, integrity metadata is the empty string, parser metadata // is "not-parser-inserted", credentials mode is credentials mode, referrer // policy is the empty string, and fetch priority is "auto".
RefPtr<ScriptFetchOptions> fetchOptions = new ScriptFetchOptions(
CORSMode::CORS_NONE, /* aNonce = */ u""_ns, RequestPriority::Auto,
ParserMetadata::NotParserInserted, nullptr);
RefPtr<ScriptLoadRequest> request = nullptr; // Bug 1817259 - For now the debugger scripts are always loaded a Classic. if (mWorkerRef->Private()->WorkerType() == WorkerType::Classic ||
IsDebuggerScript()) {
request = new ScriptLoadRequest(ScriptKind::eClassic, uri, referrerPolicy,
fetchOptions, SRIMetadata(),
nullptr, // mReferrer
loadContext);
} else { // Implements part of "To fetch a worklet/module worker script graph" // including, setting up the request with a credentials mode, // destination.
// Step 1. Let options be a script fetch options. // We currently don't track credentials in our ScriptFetchOptions // implementation, so we are defaulting the fetchOptions object defined // above. This behavior is handled fully in GetModuleSecFlags.
// Implements the referrer for "To fetch a single module script" // Our implementation does not have a "client" as a referrer. // However, when client is resolved (per 8.3. Determine request’s // Referrer in // https://w3c.github.io/webappsec-referrer-policy/#determine-requests-referrer) // This should result in the referrer source being the creation URL. // // In subresource modules, the referrer is the importing script.
nsCOMPtr<nsIURI> referrer =
mWorkerRef->Private()->GetReferrerInfo()->GetOriginalReferrer();
// Part of Step 2. This sets the Top-level flag to true
request = new ModuleLoadRequest(
uri, JS::ModuleType::JavaScript, referrerPolicy, fetchOptions,
SRIMetadata(), referrer, loadContext, true, /* is top level */ false, /* is dynamic import */
moduleLoader, visitedSet, nullptr);
}
// Set the mURL, it will be used for error handling and debugging.
request->mURL = NS_ConvertUTF16toUTF8(aScriptURL);
if (setErrorResult) {
request->SetPendingFetchingError();
} else {
request->NoCacheEntryFound();
}
void WorkerScriptLoader::MaybeMoveToLoadedList(ScriptLoadRequest* aRequest) {
mWorkerRef->Private()->AssertIsOnWorkerThread(); // Only set to ready for regular scripts. Module loader will set the script to // ready if it is a Module Request. if (!aRequest->IsModuleRequest()) {
aRequest->SetReady();
}
// If the request is not in a list, we are in an illegal state.
MOZ_RELEASE_ASSERT(aRequest->isInList());
while (!mLoadingRequests.isEmpty()) {
ScriptLoadRequest* request = mLoadingRequests.getFirst(); // We need to move requests in post order. If prior requests have not // completed, delay execution. if (!request->IsFinished()) { break;
}
bool WorkerScriptLoader::StoreCSP() { // We must be on the same worker as we started on.
mWorkerRef->Private()->AssertIsOnWorkerThread();
if (!mWorkerRef->Private()->GetJSContext()) { returnfalse;
}
MOZ_ASSERT(!mRv.Failed());
// Move the CSP from the workerLoadInfo in the corresponding Client // where the CSP code expects it!
mWorkerRef->Private()->StoreCSPOnClient(); returntrue;
}
bool WorkerScriptLoader::ProcessPendingRequests(JSContext* aCx) {
mWorkerRef->Private()->AssertIsOnWorkerThread(); // Don't run if something else has already failed. if (mExecutionAborted) {
mLoadedRequests.CancelRequestsAndClear();
TryShutdown(); returntrue;
}
// If nothing else has failed, our ErrorResult better not be a failure // either.
MOZ_ASSERT(!mRv.Failed(), "Who failed it and why?");
// Slightly icky action at a distance, but there's no better place to stash // this value, really.
JS::Rooted<JSObject*> global(aCx, JS::CurrentGlobalOrNull(aCx));
MOZ_ASSERT(global);
while (!mLoadedRequests.isEmpty()) {
RefPtr<ScriptLoadRequest> req = mLoadedRequests.StealFirst(); // We don't have a ProcessRequest method (like we do on the DOM), as there // isn't much processing that we need to do per request that isn't related // to evaluation (the processsing done for the DOM is handled in // DataRecievedFrom{Cache,Network} for workers. // So, this inner loop calls EvaluateScript directly. This will change // once modules are introduced as we will have some extra work to do. if (!EvaluateScript(aCx, req)) {
req->Cancel();
mExecutionAborted = true;
WorkerLoadContext* loadContext = req->GetWorkerLoadContext();
mMutedErrorFlag = loadContext->mMutedErrorFlag.valueOr(true);
mLoadedRequests.CancelRequestsAndClear(); break;
}
}
// The URL passed to us for loading was invalid, stop loading at this point. if (loadContext->mLoadResult != NS_ERROR_NOT_INITIALIZED) { return loadContext->mLoadResult;
}
// For JavaScript debugging, the devtools server must run on the same // thread as the debuggee, indicating the worker uses content principal. // However, in Bug 863246, web content will no longer be able to load // resource:// URIs by default, so we need system principal to load // debugger scripts.
nsIPrincipal* principal = (IsDebuggerScript())
? nsContentUtils::GetSystemPrincipal()
: mWorkerRef->Private()->GetPrincipal();
// Get the top-level worker.
WorkerPrivate* topWorkerPrivate = mWorkerRef->Private();
WorkerPrivate* parent = topWorkerPrivate->GetParent(); while (parent) {
topWorkerPrivate = parent;
parent = topWorkerPrivate->GetParent();
}
// If the top-level worker is a dedicated worker and has a window, and the // window has a docshell, the caching behavior of this worker should match // that of that docshell. if (topWorkerPrivate->IsDedicatedWorker()) {
nsCOMPtr<nsPIDOMWindowInner> window = topWorkerPrivate->GetWindow(); if (window) {
nsCOMPtr<nsIDocShell> docShell = window->GetDocShell(); if (docShell) {
nsresult rv = docShell->GetDefaultLoadFlags(&loadFlags);
NS_ENSURE_SUCCESS(rv, rv);
}
}
}
if (!channel) {
nsCOMPtr<nsIReferrerInfo> referrerInfo;
uint32_t secFlags; if (request->IsModuleRequest()) { // https://fetch.spec.whatwg.org/#concept-main-fetch // Step 8. If request’s referrer policy is the empty string, then set // request’s referrer policy to request’s policy container’s // referrer policy.
ReferrerPolicy policy =
request->ReferrerPolicy() == ReferrerPolicy::_empty
? mWorkerRef->Private()->GetReferrerPolicy()
: request->ReferrerPolicy();
referrerInfo = new ReferrerInfo(request->mReferrer, policy);
// https://html.spec.whatwg.org/multipage/webappapis.html#default-classic-script-fetch-options // The default classic script fetch options are a script fetch options // whose ... credentials mode is "same-origin", ....
RequestCredentials credentials =
mWorkerRef->Private()->WorkerType() == WorkerType::Classic
? RequestCredentials::Same_origin
: mWorkerRef->Private()->WorkerCredentials();
// Set the IsInThirdPartyContext for the channel's loadInfo according to the // partitionKey of the principal. The worker is foreign if it's using // partitioned principal, i.e. the partitionKey is not empty. In this case, // we need to set the bit to the channel's loadInfo. if (!principal->OriginAttributesRef().mPartitionKey.IsEmpty()) {
nsCOMPtr<nsILoadInfo> loadInfo = channel->LoadInfo();
rv = loadInfo->SetIsInThirdPartyContext(true); if (NS_WARN_IF(NS_FAILED(rv))) { return rv;
}
}
}
// Associate any originating stack with the channel. if (!mOriginStackJSON.IsEmpty()) {
NotifyNetworkMonitorAlternateStack(channel, mOriginStackJSON);
}
// We need to know which index we're on in OnStreamComplete so we know // where to put the result.
RefPtr<NetworkLoadHandler> listener = new NetworkLoadHandler(this, aRequestHandle);
// For each debugger script, a non-debugger script load of the same script // should have occured prior that processed the headers. if (!IsDebuggerScript()) {
JS::ModuleType moduleType = request->IsModuleRequest()
? request->AsModuleRequest()->mModuleType
: JS::ModuleType::JavaScript;
if (loadContext->IsTopLevel()) {
MOZ_DIAGNOSTIC_ASSERT(loadContext->mClientInfo.isSome());
// In order to get the correct foreign partitioned prinicpal, we need to // set the `IsThirdPartyContextToTopWindow` to the channel's loadInfo. // This flag reflects the fact that if the worker is created under a // third-party context.
nsCOMPtr<nsILoadInfo> loadInfo = channel->LoadInfo();
loadInfo->SetIsInThirdPartyContext(
mWorkerRef->Private()->IsThirdPartyContext());
nsresult WorkerScriptLoader::FillCompileOptionsForRequest(
JSContext* cx, ScriptLoadRequest* aRequest, JS::CompileOptions* aOptions,
JS::MutableHandle<JSScript*> aIntroductionScript) { // The full URL shouldn't be exposed to the debugger. See Bug 1634872
aOptions->setFileAndLine(aRequest->mURL.get(), 1);
aOptions->setNoScriptRval(true);
NS_ASSERTION(!loadContext->mChannel, "Should no longer have a channel!");
NS_ASSERTION(aRequest->IsFinished(), "Should be scheduled!");
MOZ_ASSERT(!mRv.Failed(), "Who failed it and why?");
mRv.MightThrowJSException(); if (NS_FAILED(loadContext->mLoadResult)) {
ReportErrorToConsole(aRequest, loadContext->mLoadResult); returnfalse;
}
// If this is a top level script that succeeded, then mark the // Client execution ready and possible controlled by a service worker. if (loadContext->IsTopLevel()) { if (mController.isSome()) {
MOZ_ASSERT(mWorkerScriptType == WorkerScript, "Debugger clients can't be controlled.");
mWorkerRef->Private()->GlobalScope()->Control(mController.ref());
}
mWorkerRef->Private()->ExecutionReady();
}
if (aRequest->IsModuleRequest()) { // Only the top level module of the module graph will be executed from here, // the rest will be executed from SpiderMonkey as part of the execution of // the module graph.
MOZ_ASSERT(aRequest->IsTopLevel());
ModuleLoadRequest* request = aRequest->AsModuleRequest(); if (!request->mModuleScript) { returnfalse;
}
// https://html.spec.whatwg.org/#run-a-worker // if script's error to rethrow is non-null, then: // Queue a global task on the DOM manipulation task source given worker's // relevant global object to fire an event named error at worker. // // The event will be dispatched in CompileScriptRunnable. if (request->mModuleScript->HasParseError()) { // Here we assign an error code that is not a JS Exception, so // CompileRunnable can dispatch the event.
mRv.Throw(NS_ERROR_DOM_SYNTAX_ERR); returnfalse;
}
// Implements To fetch a worklet/module worker script graph // Step 5. Fetch the descendants of and link result. if (!request->InstantiateModuleGraph()) { returnfalse;
}
if (request->mModuleScript->HasErrorToRethrow()) { // See the comments when we check HasParseError() above.
mRv.Throw(NS_ERROR_DOM_SYNTAX_ERR); returnfalse;
}
JS::CompileOptions options(aCx); // The introduction script is used by the DOM script loader as a way // to fill the Debugger Metadata for the JS Execution context. We don't use // the JS Execution context as we are not making use of async compilation // (delegation to another worker to produce bytecode or compile a string to a // JSScript), so it is not used in this context.
JS::Rooted<JSScript*> unusedIntroductionScript(aCx);
nsresult rv = FillCompileOptionsForRequest(aCx, aRequest, &options,
&unusedIntroductionScript);
MOZ_ASSERT(NS_SUCCEEDED(rv), "Filling compile options should not fail");
// Our ErrorResult still shouldn't be a failure.
MOZ_ASSERT(!mRv.Failed(), "Who failed it and why?");
// Get the source text.
ScriptLoadRequest::MaybeSourceText maybeSource;
rv = aRequest->GetScriptSource(aCx, &maybeSource,
aRequest->mLoadContext.get()); if (NS_FAILED(rv)) {
mRv.StealExceptionFromJSContext(aCx); returnfalse;
}
RefPtr<JS::loader::ClassicScript> classicScript = nullptr; if (!mWorkerRef->Private()->IsServiceWorker()) { // We need a LoadedScript to be associated with the JSScript in order to // correctly resolve the referencing private for dynamic imports. In turn // this allows us to correctly resolve the BaseURL. // // Dynamic import is disallowed on service workers. Additionally, causes // crashes because the life cycle isn't completed for service workers. To // keep things simple, we don't create a classic script for ServiceWorkers. // If this changes then we will need to ensure that the reference that is // held is released appropriately.
nsCOMPtr<nsIURI> requestBaseURI; if (loadContext->mMutedErrorFlag.valueOr(false)) {
NS_NewURI(getter_AddRefs(requestBaseURI), "about:blank"_ns);
} else {
requestBaseURI = aRequest->mBaseURL;
}
MOZ_ASSERT(aRequest->mLoadedScript->IsClassicScript());
MOZ_ASSERT(aRequest->mLoadedScript->GetFetchOptions() ==
aRequest->mFetchOptions);
aRequest->mLoadedScript->SetBaseURL(requestBaseURI);
classicScript = aRequest->mLoadedScript->AsClassicScript();
}
JS::Rooted<JSScript*> script(aCx);
script = aRequest->IsUTF8Text()
? JS::Compile(aCx, options,
maybeSource.ref<JS::SourceText<Utf8Unit>>())
: JS::Compile(aCx, options,
maybeSource.ref<JS::SourceText<char16_t>>()); if (!script) { if (loadContext->IsTopLevel()) { // This is a top-level worker script, // // https://html.spec.whatwg.org/#run-a-worker // If script is null or if script's error to rethrow is non-null, then: // Queue a global task on the DOM manipulation task source given // worker's relevant global object to fire an event named error at // worker.
JS_ClearPendingException(aCx);
mRv.Throw(NS_ERROR_DOM_SYNTAX_ERR);
} else { // This is a script which is loaded by importScripts(). // // https://html.spec.whatwg.org/#import-scripts-into-worker-global-scope // For each url in the resulting URL records: // Fetch a classic worker-imported script given url and settings object, // passing along performFetch if provided. If this succeeds, let script // be the result. Otherwise, rethrow the exception.
mRv.StealExceptionFromJSContext(aCx);
}
returnfalse;
}
bool successfullyEvaluated = EvaluateSourceBuffer(aCx, script, classicScript); if (aRequest->IsCanceled()) { returnfalse;
} if (!successfullyEvaluated) {
mRv.StealExceptionFromJSContext(aCx); returnfalse;
} // steal the loadContext so that the cycle is broken and cycle collector can // collect the scriptLoadRequest. returntrue;
}
if (!aResult) { // At this point there are two possibilities: // // 1) mRv.Failed(). In that case we just want to leave it // as-is, except if it has a JS exception and we need to mute JS // exceptions. In that case, we log the exception without firing any // events and then replace it on the ErrorResult with a NetworkError, // per spec. // // 2) mRv succeeded. As far as I can tell, this can only // happen when loading the main worker script and // GetOrCreateGlobalScope() fails or if ScriptExecutorRunnable::Cancel // got called. Does it matter what we throw in this case? I'm not // sure... if (mRv.Failed()) { if (aMutedError && mRv.IsJSException()) {
LogExceptionToConsole(mWorkerRef->Private()->GetJSContext(),
mWorkerRef->Private());
mRv.Throw(NS_ERROR_DOM_NETWORK_ERR);
}
} else {
mRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
}
}
// Lock, shutdown, and cleanup state. After this the Loader is closed.
{
MutexAutoLock lock(CleanUpLock());
if (CleanedUp()) { return;
}
mWorkerRef->Private()->AssertIsOnWorkerThread(); // Module loader doesn't use sync loop for dynamic import if (mSyncLoopTarget) {
mWorkerRef->Private()->MaybeStopSyncLoop(
mSyncLoopTarget, aResult ? NS_OK : NS_ERROR_FAILURE);
mSyncLoopTarget = nullptr;
}
// Convert the origin stack to JSON (which must be done on the main // thread) explicitly, so that we can use the stack to notify the net // monitor about every script we load. We do this, rather than pass // the stack directly to the netmonitor, in order to be able to use this // for all subsequent scripts. if (mScriptLoader->mOriginStack &&
mScriptLoader->mOriginStackJSON.IsEmpty()) {
ConvertSerializedStackToJSON(std::move(mScriptLoader->mOriginStack),
mScriptLoader->mOriginStackJSON);
}
if (!mWorkerRef->Private()->IsServiceWorker() ||
mScriptLoader->IsDebuggerScript()) { for (ThreadSafeRequestHandle* handle : mLoadingRequests) {
handle->mRunnable = this;
}
for (ThreadSafeRequestHandle* handle : mLoadingRequests) {
nsresult rv = mScriptLoader->LoadScript(handle); if (NS_WARN_IF(NS_FAILED(rv))) {
LoadingFinished(handle, rv);
CancelMainThread(rv); return rv;
}
}
return NS_OK;
}
MOZ_ASSERT(!mCacheCreator);
mCacheCreator = new CacheCreator(mWorkerRef->Private());
// The worker may have a null principal on first load, but in that case its // parent definitely will have one.
nsIPrincipal* principal = mWorkerRef->Private()->GetPrincipal(); if (!principal) {
WorkerPrivate* parentWorker = mWorkerRef->Private()->GetParent();
MOZ_ASSERT(parentWorker, "Must have a parent!");
principal = parentWorker->GetPrincipal();
}
// We execute the last step if we don't have a pending operation with the // cache and the loading is completed.
WorkerLoadContext* loadContext = aRequestHandle->GetContext(); if (!loadContext->IsAwaitingPromise()) { if (aRequestHandle->GetContext()->IsTopLevel()) {
mWorkerRef->Private()->WorkerScriptLoaded();
}
DispatchProcessPendingRequests();
}
}
constauto begin = mLoadingRequests.begin(); constauto end = mLoadingRequests.end(); using Iterator = decltype(begin); constauto maybeRangeToExecute =
[begin, end]() -> Maybe<std::pair<Iterator, Iterator>> { // firstItToExecute is the first loadInfo where mExecutionScheduled is // unset. auto firstItToExecute = std::find_if(
begin, end, [](const RefPtr<ThreadSafeRequestHandle>& requestHandle) { return !requestHandle->mExecutionScheduled;
});
if (firstItToExecute == end) { return Nothing();
}
// firstItUnexecutable is the first loadInfo that is not yet finished. // Update mExecutionScheduled on the ones we're about to schedule for // execution. constauto firstItUnexecutable =
std::find_if(firstItToExecute, end,
[](RefPtr<ThreadSafeRequestHandle>& requestHandle) {
MOZ_ASSERT(!requestHandle->IsEmpty()); if (!requestHandle->Finished()) { returntrue;
}
// We can execute this one.
requestHandle->mExecutionScheduled = true;
// If there are no unexecutable load infos, we can unuse things before the // execution of the scripts and the stopping of the sync loop. if (maybeRangeToExecute) { if (maybeRangeToExecute->second == end) {
mCacheCreator = nullptr;
}
RefPtr<ScriptExecutorRunnable> runnable = new ScriptExecutorRunnable(
mScriptLoader, mWorkerRef->Private(), mScriptLoader->mSyncLoopTarget,
Span<RefPtr<ThreadSafeRequestHandle>>{maybeRangeToExecute->first,
maybeRangeToExecute->second});
if (!runnable->Dispatch(mWorkerRef->Private()) &&
mScriptLoader->mSyncLoopTarget) {
MOZ_ASSERT(false, "This should never fail!");
}
}
}
bool ScriptExecutorRunnable::IsDebuggerRunnable() const { // ScriptExecutorRunnable is used to execute both worker and debugger scripts. // In the latter case, the runnable needs to be dispatched to the debugger // queue. return mScriptLoader->IsDebuggerScript();
}
// We must be on the same worker as we started on.
MOZ_ASSERT(
mScriptLoader->mSyncLoopTarget == mSyncLoopTarget, "Unexpected SyncLoopTarget. Check if the sync loop was closed early");
{ // There is a possibility that we cleaned up while this task was waiting to // run. If this has happened, return and exit.
MutexAutoLock lock(mScriptLoader->CleanUpLock()); if (mScriptLoader->CleanedUp()) { returntrue;
}
constauto& requestHandle = mLoadedRequests[0]; // Check if the request is still valid. if (requestHandle->IsEmpty() ||
!requestHandle->GetContext()->IsTopLevel()) { returntrue;
}
}
return mScriptLoader->StoreCSP();
}
bool ScriptExecutorRunnable::ProcessModuleScript(
JSContext* aCx, WorkerPrivate* aWorkerPrivate) { // We should only ever have one script when processing modules
MOZ_ASSERT(mLoadedRequests.Length() == 1);
RefPtr<ScriptLoadRequest> request;
{ // There is a possibility that we cleaned up while this task was waiting to // run. If this has happened, return and exit.
MutexAutoLock lock(mScriptLoader->CleanUpLock()); if (mScriptLoader->CleanedUp()) { returntrue;
}
MOZ_ASSERT(mLoadedRequests.Length() == 1); constauto& requestHandle = mLoadedRequests[0]; // The request must be valid.
MOZ_ASSERT(!requestHandle->IsEmpty());
// Release the request to the worker. From this point on, the Request Handle // is empty.
request = requestHandle->ReleaseRequest();
// release lock. We will need it later if we cleanup.
}
// DecreaseLoadingModuleRequestCount must be called before OnFetchComplete. // OnFetchComplete will call ProcessPendingRequests, and in // ProcessPendingRequests it will try to shutdown if // AllModuleRequestsLoaded() returns true.
mScriptLoader->DecreaseLoadingModuleRequestCount();
moduleRequest->OnFetchComplete(loadContext->mLoadResult);
if (NS_FAILED(loadContext->mLoadResult)) { if (moduleRequest->IsDynamicImport()) { if (request->isInList()) {
moduleRequest->CancelDynamicImport(loadContext->mLoadResult);
mScriptLoader->TryShutdown();
}
} elseif (!moduleRequest->IsTopLevel()) {
moduleRequest->Cancel();
mScriptLoader->TryShutdown();
} else {
moduleRequest->LoadFailed();
}
} returntrue;
}
bool ScriptExecutorRunnable::ProcessClassicScripts(
JSContext* aCx, WorkerPrivate* aWorkerPrivate) { // There is a possibility that we cleaned up while this task was waiting to // run. If this has happened, return and exit.
{
MutexAutoLock lock(mScriptLoader->CleanUpLock()); if (mScriptLoader->CleanedUp()) { returntrue;
}
for (constauto& requestHandle : mLoadedRequests) { // The request must be valid.
MOZ_ASSERT(!requestHandle->IsEmpty());
// Release the request to the worker. From this point on, the Request // Handle is empty.
RefPtr<ScriptLoadRequest> request = requestHandle->ReleaseRequest();
mScriptLoader->MaybeMoveToLoadedList(request);
}
} return mScriptLoader->ProcessPendingRequests(aCx);
}
// We must be on the same worker as we started on.
MOZ_ASSERT(
mScriptLoader->mSyncLoopTarget == mSyncLoopTarget, "Unexpected SyncLoopTarget. Check if the sync loop was closed early");
if (mLoadedRequests.begin()->get()->GetRequest()->IsModuleRequest()) { return ProcessModuleScript(aCx, aWorkerPrivate);
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung ist noch experimentell.