In the test file, worker_bug1824498.mjs, it imports two module scripts, "foo" and "bar", both are invalid specifiers. The ModuleLoadRequest of "foo" will be canceled, which in turn will: 1. Cancel all imports of the parent ModuleLoadRequest (worker_bug1824498.mjs) 2. Cancel the ModuleLoadRequest of "bar". After the step 1, WorkerModuleLoader::OnModuleLoadComplete will be called, and will shutdown the script loader. The shutdown causes two problems: 1. When step 2 is executed, it will reject the mReady promise in ModuleLoadRequest, however when the MozPromise is dispatched, its event target has been shutdown so an assertion failure is triggered. 2. Also when the ScriptLoaderRunnable of "bar" is received, it also triggers the assertion failure of the valid SyncLoopEventTarget. To fix the problem, we delay the shutdown until there's no ongoing module requests. Differential Revision: https://phabricator.services.mozilla.com/D175476
376 lines
13 KiB
C++
376 lines
13 KiB
C++
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
|
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
|
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
|
|
#ifndef mozilla_dom_workers_scriptloader_h__
|
|
#define mozilla_dom_workers_scriptloader_h__
|
|
|
|
#include "js/loader/ScriptLoadRequest.h"
|
|
#include "js/loader/ModuleLoadRequest.h"
|
|
#include "js/loader/ModuleLoaderBase.h"
|
|
#include "mozilla/dom/WorkerBinding.h"
|
|
#include "mozilla/dom/WorkerCommon.h"
|
|
#include "mozilla/dom/WorkerLoadContext.h"
|
|
#include "mozilla/dom/WorkerRef.h"
|
|
#include "mozilla/dom/workerinternals/WorkerModuleLoader.h"
|
|
#include "mozilla/Maybe.h"
|
|
#include "nsIContentPolicy.h"
|
|
#include "nsStringFwd.h"
|
|
#include "nsTArrayForwardDeclare.h"
|
|
|
|
class nsIChannel;
|
|
class nsICookieJarSettings;
|
|
class nsILoadGroup;
|
|
class nsIPrincipal;
|
|
class nsIReferrerInfo;
|
|
class nsIURI;
|
|
|
|
namespace mozilla {
|
|
|
|
class ErrorResult;
|
|
|
|
namespace dom {
|
|
|
|
class ClientInfo;
|
|
class Document;
|
|
struct WorkerLoadInfo;
|
|
class WorkerPrivate;
|
|
class SerializedStackHolder;
|
|
|
|
enum WorkerScriptType { WorkerScript, DebuggerScript };
|
|
|
|
namespace workerinternals {
|
|
|
|
namespace loader {
|
|
class ScriptExecutorRunnable;
|
|
class ScriptLoaderRunnable;
|
|
class CachePromiseHandler;
|
|
class CacheLoadHandler;
|
|
class CacheCreator;
|
|
class NetworkLoadHandler;
|
|
|
|
/*
|
|
* [DOMDOC] WorkerScriptLoader
|
|
*
|
|
* The WorkerScriptLoader is the primary class responsible for loading all
|
|
* Workers, including: ServiceWorkers, SharedWorkers, RemoteWorkers, and
|
|
* dedicated Workers. Our implementation also includes a subtype of dedicated
|
|
* workers: ChromeWorker, which exposes information that isn't normally
|
|
* accessible on a dedicated worker. See [1] for more information.
|
|
*
|
|
* Due to constraints around fetching, this class currently delegates the
|
|
* "Fetch" portion of its work load to the main thread. Unlike the DOM
|
|
* ScriptLoader, the WorkerScriptLoader is not persistent and is not reused for
|
|
* subsequent loads. That means for each iteration of loading (for example,
|
|
* loading the main script, followed by a load triggered by ImportScripts), we
|
|
* recreate this class, and handle the case independently.
|
|
*
|
|
* The flow of requests across the boundaries looks like this:
|
|
*
|
|
* +----------------------------+
|
|
* | new WorkerScriptLoader(..) |
|
|
* +----------------------------+
|
|
* |
|
|
* V
|
|
* +-------------------------------------------+
|
|
* | WorkerScriptLoader::DispatchLoadScripts() |
|
|
* +-------------------------------------------+
|
|
* |
|
|
* V
|
|
* +............................+
|
|
* | new ScriptLoaderRunnable() |
|
|
* +............................+
|
|
* :
|
|
* V
|
|
* #####################################################################
|
|
* Enter Main thread
|
|
* #####################################################################
|
|
* :
|
|
* V
|
|
* +.............................+ For each: Is a normal Worker?
|
|
* | ScriptLoaderRunnable::Run() |----------------------------------+
|
|
* +.............................+ |
|
|
* | V
|
|
* | +----------------------------------+
|
|
* | | WorkerScriptLoader::LoadScript() |
|
|
* | +----------------------------------+
|
|
* | |
|
|
* | For each request: Is a ServiceWorker? |
|
|
* | |
|
|
* V V
|
|
* +==================+ No script in cache? +====================+
|
|
* | CacheLoadHandler |------------------------>| NetworkLoadHandler |
|
|
* +==================+ +====================+
|
|
* : :
|
|
* : Loaded from Cache : Loaded by Network
|
|
* : +..........................................+ :
|
|
* +---| ScriptLoaderRunnable::OnStreamComplete() |<----+
|
|
* +..........................................+
|
|
* |
|
|
* | A request is ready, is it in post order?
|
|
* |
|
|
* | call DispatchPendingProcessRequests()
|
|
* | This creates ScriptExecutorRunnable
|
|
* +..............................+
|
|
* | new ScriptLoaderExecutable() |
|
|
* +..............................+
|
|
* :
|
|
* V
|
|
* #####################################################################
|
|
* Enter worker thread
|
|
* #####################################################################
|
|
* :
|
|
* V
|
|
* +...............................+ All Scripts Executed?
|
|
* | ScriptLoaderExecutable::Run() | -------------+
|
|
* +...............................+ :
|
|
* :
|
|
* : yes. Do execution
|
|
* : then shutdown.
|
|
* :
|
|
* V
|
|
* +--------------------------------------------+
|
|
* | WorkerScriptLoader::ShutdownScriptLoader() |
|
|
* +--------------------------------------------+
|
|
*/
|
|
|
|
class WorkerScriptLoader : public JS::loader::ScriptLoaderInterface,
|
|
public nsINamed {
|
|
friend class ScriptExecutorRunnable;
|
|
friend class ScriptLoaderRunnable;
|
|
friend class CachePromiseHandler;
|
|
friend class CacheLoadHandler;
|
|
friend class CacheCreator;
|
|
friend class NetworkLoadHandler;
|
|
friend class WorkerModuleLoader;
|
|
|
|
RefPtr<ThreadSafeWorkerRef> mWorkerRef;
|
|
UniquePtr<SerializedStackHolder> mOriginStack;
|
|
nsString mOriginStackJSON;
|
|
nsCOMPtr<nsISerialEventTarget> mSyncLoopTarget;
|
|
ScriptLoadRequestList mLoadingRequests;
|
|
ScriptLoadRequestList mLoadedRequests;
|
|
Maybe<ServiceWorkerDescriptor> mController;
|
|
WorkerScriptType mWorkerScriptType;
|
|
ErrorResult& mRv;
|
|
bool mExecutionAborted = false;
|
|
bool mMutedErrorFlag = false;
|
|
|
|
// Count of loading module requests. mLoadingRequests doesn't keep track of
|
|
// child module requests.
|
|
// This member should be accessed on worker thread.
|
|
uint32_t mLoadingModuleRequestCount;
|
|
|
|
// Worker cancellation related Mutex
|
|
//
|
|
// Modified on the worker thread.
|
|
// It is ok to *read* this without a lock on the worker.
|
|
// Main thread must always acquire a lock.
|
|
bool mCleanedUp MOZ_GUARDED_BY(
|
|
mCleanUpLock); // To specify if the cleanUp() has been done.
|
|
|
|
Mutex& CleanUpLock() MOZ_RETURN_CAPABILITY(mCleanUpLock) {
|
|
return mCleanUpLock;
|
|
}
|
|
|
|
bool CleanedUp() const MOZ_REQUIRES(mCleanUpLock) {
|
|
mCleanUpLock.AssertCurrentThreadOwns();
|
|
return mCleanedUp;
|
|
}
|
|
|
|
// Ensure the worker and the main thread won't race to access |mCleanedUp|.
|
|
// Should be a MutexSingleWriter, but that causes a lot of issues when you
|
|
// expose the lock via Lock().
|
|
Mutex mCleanUpLock;
|
|
|
|
public:
|
|
NS_DECL_THREADSAFE_ISUPPORTS
|
|
|
|
WorkerScriptLoader(WorkerPrivate* aWorkerPrivate,
|
|
UniquePtr<SerializedStackHolder> aOriginStack,
|
|
nsISerialEventTarget* aSyncLoopTarget,
|
|
WorkerScriptType aWorkerScriptType, ErrorResult& aRv);
|
|
|
|
bool CreateScriptRequests(const nsTArray<nsString>& aScriptURLs,
|
|
const mozilla::Encoding* aDocumentEncoding,
|
|
bool aIsMainScript);
|
|
|
|
ScriptLoadRequest* GetMainScript();
|
|
|
|
already_AddRefed<ScriptLoadRequest> CreateScriptLoadRequest(
|
|
const nsString& aScriptURL, const mozilla::Encoding* aDocumentEncoding,
|
|
bool aIsMainScript);
|
|
|
|
bool DispatchLoadScript(ScriptLoadRequest* aRequest);
|
|
|
|
bool DispatchLoadScripts();
|
|
|
|
void TryShutdown();
|
|
|
|
WorkerScriptType GetWorkerScriptType() { return mWorkerScriptType; }
|
|
|
|
protected:
|
|
nsIURI* GetBaseURI() const override;
|
|
|
|
nsIURI* GetInitialBaseURI();
|
|
|
|
nsIGlobalObject* GetGlobal();
|
|
|
|
void MaybeMoveToLoadedList(ScriptLoadRequest* aRequest);
|
|
|
|
bool StoreCSP();
|
|
|
|
bool ProcessPendingRequests(JSContext* aCx);
|
|
|
|
bool AllScriptsExecuted() {
|
|
return mLoadingRequests.isEmpty() && mLoadedRequests.isEmpty();
|
|
}
|
|
|
|
bool IsDebuggerScript() const { return mWorkerScriptType == DebuggerScript; }
|
|
|
|
void SetController(const Maybe<ServiceWorkerDescriptor>& aDescriptor) {
|
|
mController = aDescriptor;
|
|
}
|
|
|
|
Maybe<ServiceWorkerDescriptor>& GetController() { return mController; }
|
|
|
|
nsresult LoadScript(ThreadSafeRequestHandle* aRequestHandle);
|
|
|
|
void ShutdownScriptLoader(bool aResult, bool aMutedError);
|
|
|
|
private:
|
|
~WorkerScriptLoader() = default;
|
|
|
|
NS_IMETHOD
|
|
GetName(nsACString& aName) override {
|
|
aName.AssignLiteral("WorkerScriptLoader");
|
|
return NS_OK;
|
|
}
|
|
|
|
void InitModuleLoader();
|
|
|
|
nsTArray<RefPtr<ThreadSafeRequestHandle>> GetLoadingList();
|
|
|
|
bool IsDynamicImport(ScriptLoadRequest* aRequest);
|
|
|
|
nsContentPolicyType GetContentPolicyType(ScriptLoadRequest* aRequest);
|
|
|
|
bool EvaluateScript(JSContext* aCx, ScriptLoadRequest* aRequest);
|
|
|
|
nsresult FillCompileOptionsForRequest(
|
|
JSContext* cx, ScriptLoadRequest* aRequest, JS::CompileOptions* aOptions,
|
|
JS::MutableHandle<JSScript*> aIntroductionScript) override;
|
|
|
|
void ReportErrorToConsole(ScriptLoadRequest* aRequest,
|
|
nsresult aResult) const override;
|
|
|
|
// Only used by import maps, crash if we get here.
|
|
void ReportWarningToConsole(
|
|
ScriptLoadRequest* aRequest, const char* aMessageName,
|
|
const nsTArray<nsString>& aParams = nsTArray<nsString>()) const override {
|
|
MOZ_CRASH("Import maps have not been implemented for this context");
|
|
}
|
|
|
|
void LogExceptionToConsole(JSContext* aCx, WorkerPrivate* aWorkerPrivate);
|
|
|
|
bool AllModuleRequestsLoaded() const;
|
|
void IncreaseLoadingModuleRequestCount();
|
|
void DecreaseLoadingModuleRequestCount();
|
|
};
|
|
|
|
/* ScriptLoaderRunnable
|
|
*
|
|
* Responsibilities of this class:
|
|
* - the actual dispatch
|
|
* - delegating the load back to WorkerScriptLoader
|
|
* - handling the collections of scripts being requested
|
|
* - handling main thread cancellation
|
|
* - dispatching back to the worker thread
|
|
*/
|
|
class ScriptLoaderRunnable final : public nsIRunnable, public nsINamed {
|
|
RefPtr<WorkerScriptLoader> mScriptLoader;
|
|
RefPtr<ThreadSafeWorkerRef> mWorkerRef;
|
|
nsTArrayView<RefPtr<ThreadSafeRequestHandle>> mLoadingRequests;
|
|
Maybe<nsresult> mCancelMainThread;
|
|
RefPtr<CacheCreator> mCacheCreator;
|
|
|
|
public:
|
|
NS_DECL_THREADSAFE_ISUPPORTS
|
|
|
|
explicit ScriptLoaderRunnable(
|
|
WorkerScriptLoader* aScriptLoader,
|
|
nsTArray<RefPtr<ThreadSafeRequestHandle>> aLoadingRequests);
|
|
|
|
nsresult OnStreamComplete(ThreadSafeRequestHandle* aRequestHandle,
|
|
nsresult aStatus);
|
|
|
|
void LoadingFinished(ThreadSafeRequestHandle* aRequestHandle, nsresult aRv);
|
|
|
|
void MaybeExecuteFinishedScripts(ThreadSafeRequestHandle* aRequestHandle);
|
|
|
|
bool IsCancelled() { return mCancelMainThread.isSome(); }
|
|
|
|
nsresult GetCancelResult() {
|
|
return (IsCancelled()) ? mCancelMainThread.ref() : NS_OK;
|
|
}
|
|
|
|
void CancelMainThreadWithBindingAborted();
|
|
|
|
CacheCreator* GetCacheCreator() { return mCacheCreator; };
|
|
|
|
private:
|
|
~ScriptLoaderRunnable() = default;
|
|
|
|
void CancelMainThread(nsresult aCancelResult);
|
|
|
|
void DispatchProcessPendingRequests();
|
|
|
|
NS_IMETHOD
|
|
Run() override;
|
|
|
|
NS_IMETHOD
|
|
GetName(nsACString& aName) override {
|
|
aName.AssignLiteral("ScriptLoaderRunnable");
|
|
return NS_OK;
|
|
}
|
|
};
|
|
|
|
} // namespace loader
|
|
|
|
nsresult ChannelFromScriptURLMainThread(
|
|
nsIPrincipal* aPrincipal, Document* aParentDoc, nsILoadGroup* aLoadGroup,
|
|
nsIURI* aScriptURL, const WorkerType& aWorkerType,
|
|
const RequestCredentials& aCredentials,
|
|
const Maybe<ClientInfo>& aClientInfo,
|
|
nsContentPolicyType aContentPolicyType,
|
|
nsICookieJarSettings* aCookieJarSettings, nsIReferrerInfo* aReferrerInfo,
|
|
nsIChannel** aChannel);
|
|
|
|
nsresult ChannelFromScriptURLWorkerThread(
|
|
JSContext* aCx, WorkerPrivate* aParent, const nsAString& aScriptURL,
|
|
const WorkerType& aWorkerType, const RequestCredentials& aCredentials,
|
|
WorkerLoadInfo& aLoadInfo);
|
|
|
|
void ReportLoadError(ErrorResult& aRv, nsresult aLoadResult,
|
|
const nsAString& aScriptURL);
|
|
|
|
void LoadMainScript(WorkerPrivate* aWorkerPrivate,
|
|
UniquePtr<SerializedStackHolder> aOriginStack,
|
|
const nsAString& aScriptURL,
|
|
WorkerScriptType aWorkerScriptType, ErrorResult& aRv,
|
|
const mozilla::Encoding* aDocumentEncoding);
|
|
|
|
void Load(WorkerPrivate* aWorkerPrivate,
|
|
UniquePtr<SerializedStackHolder> aOriginStack,
|
|
const nsTArray<nsString>& aScriptURLs,
|
|
WorkerScriptType aWorkerScriptType, ErrorResult& aRv);
|
|
|
|
} // namespace workerinternals
|
|
|
|
} // namespace dom
|
|
} // namespace mozilla
|
|
|
|
#endif /* mozilla_dom_workers_scriptloader_h__ */
|