diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 09:22:09 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 09:22:09 +0000 |
commit | 43a97878ce14b72f0981164f87f2e35e14151312 (patch) | |
tree | 620249daf56c0258faa40cbdcf9cfba06de2a846 /dom/workers/remoteworkers/RemoteWorkerChild.cpp | |
parent | Initial commit. (diff) | |
download | firefox-43a97878ce14b72f0981164f87f2e35e14151312.tar.xz firefox-43a97878ce14b72f0981164f87f2e35e14151312.zip |
Adding upstream version 110.0.1.upstream/110.0.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'dom/workers/remoteworkers/RemoteWorkerChild.cpp')
-rw-r--r-- | dom/workers/remoteworkers/RemoteWorkerChild.cpp | 1119 |
1 files changed, 1119 insertions, 0 deletions
diff --git a/dom/workers/remoteworkers/RemoteWorkerChild.cpp b/dom/workers/remoteworkers/RemoteWorkerChild.cpp new file mode 100644 index 0000000000..71e77f6030 --- /dev/null +++ b/dom/workers/remoteworkers/RemoteWorkerChild.cpp @@ -0,0 +1,1119 @@ +/* -*- 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/. */ + +#include "RemoteWorkerChild.h" + +#include <utility> + +#include "MainThreadUtils.h" +#include "nsCOMPtr.h" +#include "nsDebug.h" +#include "nsError.h" +#include "nsIConsoleReportCollector.h" +#include "nsIInterfaceRequestor.h" +#include "nsIPrincipal.h" +#include "nsNetUtil.h" +#include "nsProxyRelease.h" +#include "nsThreadUtils.h" +#include "nsXULAppAPI.h" + +#include "RemoteWorkerService.h" +#include "mozilla/ArrayAlgorithm.h" +#include "mozilla/Assertions.h" +#include "mozilla/Attributes.h" +#include "mozilla/BasePrincipal.h" +#include "mozilla/ErrorResult.h" +#include "mozilla/SchedulerGroup.h" +#include "mozilla/Services.h" +#include "mozilla/ScopeExit.h" +#include "mozilla/Unused.h" +#include "mozilla/dom/FetchEventOpProxyChild.h" +#include "mozilla/dom/IndexedDatabaseManager.h" +#include "mozilla/dom/MessagePort.h" +#include "mozilla/dom/RemoteWorkerManager.h" // RemoteWorkerManager::IsRemoteTypeAllowed +#include "mozilla/dom/RemoteWorkerTypes.h" +#include "mozilla/dom/ServiceWorkerDescriptor.h" +#include "mozilla/dom/ServiceWorkerInterceptController.h" +#include "mozilla/dom/ServiceWorkerOp.h" +#include "mozilla/dom/ServiceWorkerRegistrationDescriptor.h" +#include "mozilla/dom/ServiceWorkerShutdownState.h" +#include "mozilla/dom/ServiceWorkerUtils.h" +#include "mozilla/dom/workerinternals/ScriptLoader.h" +#include "mozilla/dom/WorkerError.h" +#include "mozilla/dom/WorkerPrivate.h" +#include "mozilla/dom/WorkerRef.h" +#include "mozilla/dom/WorkerRunnable.h" +#include "mozilla/ipc/BackgroundUtils.h" +#include "mozilla/ipc/URIUtils.h" +#include "mozilla/net/CookieJarSettings.h" +#include "mozilla/PermissionManager.h" + +namespace mozilla { + +using namespace ipc; + +namespace dom { + +using workerinternals::ChannelFromScriptURLMainThread; + +// TODO: Remove this ad hoc class when bug 1805830 is fixed. +class SelfHolder { + public: + MOZ_IMPLICIT SelfHolder(RemoteWorkerChild* aSelf) : mSelf(aSelf) { + MOZ_ASSERT(mSelf); + } + + SelfHolder(const SelfHolder&) = default; + + SelfHolder& operator=(const SelfHolder&) = default; + + SelfHolder(SelfHolder&&) = default; + + SelfHolder& operator=(SelfHolder&&) = default; + + ~SelfHolder() { + if (!mSelf) { + return; + } + + nsCOMPtr<nsISerialEventTarget> target = mSelf->GetOwningEventTarget(); + NS_ProxyRelease("SelfHolder::mSelf", target, mSelf.forget()); + } + + RemoteWorkerChild* get() const { + MOZ_ASSERT(mSelf); + + return mSelf.get(); + } + + RemoteWorkerChild* operator->() const MOZ_NO_ADDREF_RELEASE_ON_RETURN { + return get(); + } + + bool operator!() { return !mSelf.get(); } + + private: + RefPtr<RemoteWorkerChild> mSelf; +}; + +namespace { + +class SharedWorkerInterfaceRequestor final : public nsIInterfaceRequestor { + public: + NS_DECL_ISUPPORTS + + SharedWorkerInterfaceRequestor() { + // This check must match the code nsDocShell::Create. + if (XRE_IsParentProcess()) { + mSWController = new ServiceWorkerInterceptController(); + } + } + + NS_IMETHOD + GetInterface(const nsIID& aIID, void** aSink) override { + MOZ_ASSERT(NS_IsMainThread()); + + if (mSWController && + aIID.Equals(NS_GET_IID(nsINetworkInterceptController))) { + // If asked for the network intercept controller, ask the outer requestor, + // which could be the docshell. + RefPtr<ServiceWorkerInterceptController> swController = mSWController; + swController.forget(aSink); + return NS_OK; + } + + return NS_NOINTERFACE; + } + + private: + ~SharedWorkerInterfaceRequestor() = default; + + RefPtr<ServiceWorkerInterceptController> mSWController; +}; + +NS_IMPL_ADDREF(SharedWorkerInterfaceRequestor) +NS_IMPL_RELEASE(SharedWorkerInterfaceRequestor) +NS_IMPL_QUERY_INTERFACE(SharedWorkerInterfaceRequestor, nsIInterfaceRequestor) + +// Normal runnable because AddPortIdentifier() is going to exec JS code. +class MessagePortIdentifierRunnable final : public WorkerRunnable { + public: + MessagePortIdentifierRunnable(WorkerPrivate* aWorkerPrivate, + SelfHolder aActor, + const MessagePortIdentifier& aPortIdentifier) + : WorkerRunnable(aWorkerPrivate), + mActor(std::move(aActor)), + mPortIdentifier(aPortIdentifier) {} + + private: + bool WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override { + mActor->AddPortIdentifier(aCx, aWorkerPrivate, mPortIdentifier); + return true; + } + + SelfHolder mActor; + UniqueMessagePortId mPortIdentifier; +}; + +// This is used to release WeakWorkerRefs which can only have their refcount +// modified on the owning thread (worker thread in this case). It also keeps +// alive the associated WorkerPrivate until the WeakWorkerRef is released. +class ReleaseWorkerRunnable final : public WorkerControlRunnable { + public: + ReleaseWorkerRunnable(RefPtr<WorkerPrivate>&& aWorkerPrivate, + RefPtr<WeakWorkerRef>&& aWeakRef) + : WorkerControlRunnable(aWorkerPrivate, WorkerThreadUnchangedBusyCount), + mWorkerPrivate(std::move(aWorkerPrivate)), + mWeakRef(std::move(aWeakRef)) { + MOZ_ASSERT(mWorkerPrivate); + MOZ_ASSERT(!mWorkerPrivate->IsOnWorkerThread()); + MOZ_ASSERT(mWeakRef); + } + + private: + ~ReleaseWorkerRunnable() { ReleaseMembers(); } + + bool WorkerRun(JSContext*, WorkerPrivate*) override { + ReleaseMembers(); + return true; + } + + nsresult Cancel() override { + // We need to check first if cancel is called twice + nsresult rv = WorkerRunnable::Cancel(); + NS_ENSURE_SUCCESS(rv, rv); + + ReleaseMembers(); + return NS_OK; + } + + void ReleaseMembers() { + if (!mWorkerPrivate) { + MOZ_ASSERT(!mWeakRef); + return; + } + + mWeakRef = nullptr; + + NS_ReleaseOnMainThread("ReleaseWorkerRunnable::mWorkerPrivate", + mWorkerPrivate.forget()); + } + + RefPtr<WorkerPrivate> mWorkerPrivate; + RefPtr<WeakWorkerRef> mWeakRef; +}; + +} // anonymous namespace + +class RemoteWorkerChild::InitializeWorkerRunnable final + : public WorkerRunnable { + public: + InitializeWorkerRunnable(WorkerPrivate* aWorkerPrivate, SelfHolder aActor) + : WorkerRunnable(aWorkerPrivate), mActor(std::move(aActor)) { + MOZ_ASSERT(mActor); + } + + private: + ~InitializeWorkerRunnable() { MaybeAbort(); } + + bool WorkerRun(JSContext*, WorkerPrivate*) override { + MOZ_ASSERT(mActor); + + mActor->InitializeOnWorker(); + + SelfHolder holder = std::move(mActor); + MOZ_ASSERT(!mActor); + + return true; + } + + nsresult Cancel() override { + // We need to check first if cancel is called twice + nsresult rv = WorkerRunnable::Cancel(); + NS_ENSURE_SUCCESS(rv, rv); + + MaybeAbort(); + + return NS_OK; + } + + // Slowly running out of synonyms for cancel, abort, terminate, etc... + void MaybeAbort() { + if (!mActor) { + return; + } + + mActor->TransitionStateToTerminated(); + mActor->CreationFailedOnAnyThread(); + mActor->ShutdownOnWorker(); + + SelfHolder holder = std::move(mActor); + MOZ_ASSERT(!mActor); + } + + // Falsy indicates that WorkerRun or MaybeAbort has already been called. + SelfHolder mActor; +}; + +RemoteWorkerChild::RemoteWorkerChild(const RemoteWorkerData& aData) + : mState(VariantType<Pending>(), "RemoteWorkerChild::mState"), + mIsServiceWorker(aData.serviceWorkerData().type() == + OptionalServiceWorkerData::TServiceWorkerData), + mOwningEventTarget(GetCurrentSerialEventTarget()) { + MOZ_ASSERT(RemoteWorkerService::Thread()->IsOnCurrentThread()); + MOZ_ASSERT(mOwningEventTarget); +} + +RemoteWorkerChild::~RemoteWorkerChild() { +#ifdef DEBUG + auto lock = mState.Lock(); + MOZ_ASSERT(lock->is<Terminated>()); +#endif +} + +nsISerialEventTarget* RemoteWorkerChild::GetOwningEventTarget() const { + return mOwningEventTarget; +} + +void RemoteWorkerChild::ActorDestroy(ActorDestroyReason) { + auto launcherData = mLauncherData.Access(); + launcherData->mIPCActive = false; + + Unused << NS_WARN_IF(!launcherData->mTerminationPromise.IsEmpty()); + launcherData->mTerminationPromise.RejectIfExists(NS_ERROR_DOM_ABORT_ERR, + __func__); + + auto lock = mState.Lock(); + + Unused << NS_WARN_IF(!lock->is<Terminated>()); + + *lock = VariantType<Terminated>(); +} + +void RemoteWorkerChild::ExecWorker(const RemoteWorkerData& aData) { +#ifdef DEBUG + MOZ_ASSERT(GetOwningEventTarget()->IsOnCurrentThread()); + auto launcherData = mLauncherData.Access(); + MOZ_ASSERT(launcherData->mIPCActive); +#endif + + SelfHolder self = this; + + nsCOMPtr<nsIRunnable> r = NS_NewRunnableFunction( + __func__, [self = std::move(self), data = aData]() mutable { + nsresult rv = self->ExecWorkerOnMainThread(std::move(data)); + + if (NS_WARN_IF(NS_FAILED(rv))) { + self->CreationFailedOnAnyThread(); + } + }); + + MOZ_ALWAYS_SUCCEEDS( + SchedulerGroup::Dispatch(TaskCategory::Other, r.forget())); +} + +nsresult RemoteWorkerChild::ExecWorkerOnMainThread(RemoteWorkerData&& aData) { + MOZ_ASSERT(NS_IsMainThread()); + + // Ensure that the IndexedDatabaseManager is initialized + Unused << NS_WARN_IF(!IndexedDatabaseManager::GetOrCreate()); + + auto scopeExit = MakeScopeExit([&] { TransitionStateToTerminated(); }); + + // Verify the the RemoteWorker should be really allowed to run in this + // process, and fail if it shouldn't (This shouldn't normally happen, + // unless the RemoteWorkerData has been tempered in the process it was + // sent from). + if (!RemoteWorkerManager::IsRemoteTypeAllowed(aData)) { + return NS_ERROR_UNEXPECTED; + } + + auto principalOrErr = PrincipalInfoToPrincipal(aData.principalInfo()); + if (NS_WARN_IF(principalOrErr.isErr())) { + return principalOrErr.unwrapErr(); + } + + nsCOMPtr<nsIPrincipal> principal = principalOrErr.unwrap(); + + auto loadingPrincipalOrErr = + PrincipalInfoToPrincipal(aData.loadingPrincipalInfo()); + if (NS_WARN_IF(loadingPrincipalOrErr.isErr())) { + return loadingPrincipalOrErr.unwrapErr(); + } + + auto partitionedPrincipalOrErr = + PrincipalInfoToPrincipal(aData.partitionedPrincipalInfo()); + if (NS_WARN_IF(partitionedPrincipalOrErr.isErr())) { + return partitionedPrincipalOrErr.unwrapErr(); + } + + WorkerLoadInfo info; + info.mBaseURI = DeserializeURI(aData.baseScriptURL()); + info.mResolvedScriptURI = DeserializeURI(aData.resolvedScriptURL()); + + info.mPrincipalInfo = MakeUnique<PrincipalInfo>(aData.principalInfo()); + info.mPartitionedPrincipalInfo = + MakeUnique<PrincipalInfo>(aData.partitionedPrincipalInfo()); + + info.mReferrerInfo = aData.referrerInfo(); + info.mDomain = aData.domain(); + info.mTrials = aData.originTrials(); + info.mPrincipal = principal; + info.mPartitionedPrincipal = partitionedPrincipalOrErr.unwrap(); + info.mLoadingPrincipal = loadingPrincipalOrErr.unwrap(); + info.mStorageAccess = aData.storageAccess(); + info.mUseRegularPrincipal = aData.useRegularPrincipal(); + info.mHasStorageAccessPermissionGranted = + aData.hasStorageAccessPermissionGranted(); + info.mIsThirdPartyContextToTopWindow = aData.isThirdPartyContextToTopWindow(); + info.mOriginAttributes = + BasePrincipal::Cast(principal)->OriginAttributesRef(); + info.mShouldResistFingerprinting = aData.shouldResistFingerprinting(); + net::CookieJarSettings::Deserialize(aData.cookieJarSettings(), + getter_AddRefs(info.mCookieJarSettings)); + + // Default CSP permissions for now. These will be overrided if necessary + // based on the script CSP headers during load in ScriptLoader. + info.mEvalAllowed = true; + info.mReportEvalCSPViolations = false; + info.mWasmEvalAllowed = true; + info.mReportWasmEvalCSPViolations = false; + info.mSecureContext = aData.isSecureContext() + ? WorkerLoadInfo::eSecureContext + : WorkerLoadInfo::eInsecureContext; + + WorkerPrivate::OverrideLoadInfoLoadGroup(info, info.mLoadingPrincipal); + + RefPtr<SharedWorkerInterfaceRequestor> requestor = + new SharedWorkerInterfaceRequestor(); + info.mInterfaceRequestor->SetOuterRequestor(requestor); + + Maybe<ClientInfo> clientInfo; + if (aData.clientInfo().isSome()) { + clientInfo.emplace(ClientInfo(aData.clientInfo().ref())); + } + + nsresult rv = NS_OK; + + if (clientInfo.isSome()) { + Maybe<mozilla::ipc::CSPInfo> cspInfo = clientInfo.ref().GetCspInfo(); + if (cspInfo.isSome()) { + info.mCSP = CSPInfoToCSP(cspInfo.ref(), nullptr); + info.mCSPInfo = MakeUnique<CSPInfo>(); + rv = CSPToCSPInfo(info.mCSP, info.mCSPInfo.get()); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + } + } + + rv = info.SetPrincipalsAndCSPOnMainThread( + info.mPrincipal, info.mPartitionedPrincipal, info.mLoadGroup, info.mCSP); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + nsString workerPrivateId; + + if (mIsServiceWorker) { + ServiceWorkerData& data = aData.serviceWorkerData().get_ServiceWorkerData(); + + MOZ_ASSERT(!data.id().IsEmpty()); + workerPrivateId = std::move(data.id()); + + info.mServiceWorkerCacheName = data.cacheName(); + info.mServiceWorkerDescriptor.emplace(data.descriptor()); + info.mServiceWorkerRegistrationDescriptor.emplace( + data.registrationDescriptor()); + info.mLoadFlags = static_cast<nsLoadFlags>(data.loadFlags()); + } else { + // Top level workers' main script use the document charset for the script + // uri encoding. + rv = ChannelFromScriptURLMainThread( + info.mLoadingPrincipal, nullptr /* parent document */, info.mLoadGroup, + info.mResolvedScriptURI, clientInfo, + nsIContentPolicy::TYPE_INTERNAL_SHARED_WORKER, info.mCookieJarSettings, + info.mReferrerInfo, getter_AddRefs(info.mChannel)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + } + + info.mAgentClusterId = aData.agentClusterId(); + + AutoJSAPI jsapi; + jsapi.Init(); + + ErrorResult error; + RefPtr<WorkerPrivate> workerPrivate = WorkerPrivate::Constructor( + jsapi.cx(), aData.originalScriptURL(), false, + mIsServiceWorker ? WorkerKindService : WorkerKindShared, aData.name(), + VoidCString(), &info, error, std::move(workerPrivateId)); + + if (NS_WARN_IF(error.Failed())) { + MOZ_ASSERT(!workerPrivate); + + rv = error.StealNSResult(); + return rv; + } + + if (mIsServiceWorker) { + RefPtr<RemoteWorkerChild> self = this; + workerPrivate->SetRemoteWorkerControllerWeakRef( + ThreadSafeWeakPtr<RemoteWorkerChild>(self)); + } else { + workerPrivate->SetRemoteWorkerController(this); + } + + RefPtr<InitializeWorkerRunnable> runnable = + new InitializeWorkerRunnable(workerPrivate, SelfHolder(this)); + + { + MOZ_ASSERT(workerPrivate); + auto lock = mState.Lock(); + // We MUST be pending here, so direct access is ok. + lock->as<Pending>().mWorkerPrivate = std::move(workerPrivate); + } + + if (mIsServiceWorker) { + SelfHolder self = this; + + nsCOMPtr<nsIRunnable> r = NS_NewRunnableFunction( + __func__, [initializeWorkerRunnable = std::move(runnable), + self = std::move(self)] { + // Checking RemoteWorkerChild.mState + bool isPending; + { + auto lock = self->mState.Lock(); + isPending = lock->is<Pending>(); + } + if (NS_WARN_IF(!isPending || !initializeWorkerRunnable->Dispatch())) { + self->TransitionStateToTerminated(); + self->CreationFailedOnAnyThread(); + } + }); + + RefPtr<PermissionManager> permissionManager = + PermissionManager::GetInstance(); + if (!permissionManager) { + return NS_ERROR_FAILURE; + } + permissionManager->WhenPermissionsAvailable(principal, r); + } else { + if (NS_WARN_IF(!runnable->Dispatch())) { + rv = NS_ERROR_FAILURE; + return rv; + } + } + + scopeExit.release(); + + return NS_OK; +} + +void RemoteWorkerChild::InitializeOnWorker() { + RefPtr<WorkerPrivate> workerPrivate; + + { + auto lock = mState.Lock(); + + if (lock->is<PendingTerminated>()) { + TransitionStateToTerminated(lock.ref()); + ShutdownOnWorker(); + return; + } + + // XXX: Are we sure we cannot be in any other state here? + // We are executed as part of the InitializeWorkerRunnable + // which is scheduled from ExecWorkerOnMainThread, so we + // assume that our state remains <Pending> as it was before + // the dispatch. There seem to be no crashes here, so for + // now this assumption holds apparently. + workerPrivate = std::move(lock->as<Pending>().mWorkerPrivate); + } + + MOZ_ASSERT(workerPrivate); + workerPrivate->AssertIsOnWorkerThread(); + + RefPtr<RemoteWorkerChild> self = this; + ThreadSafeWeakPtr<RemoteWorkerChild> selfWeakRef(self); + + auto scopeExit = MakeScopeExit([&] { + MOZ_ASSERT(self); + + NS_ProxyRelease(__func__, mOwningEventTarget, self.forget()); + }); + + // Let RemoteWorkerChild own the WorkerPrivate; RemoteWorkerChild's state + // transitions should guarantee the WorkerPrivate is cleaned up correctly. + // This also reduces some complexity around thread lifetimes guarantees that + // RemoteWorkerChild's state transitions rely on (e.g. the worker thread + // terminating unexpectedly). + RefPtr<StrongWorkerRef> strongRef = + StrongWorkerRef::Create(workerPrivate, __func__); + + RefPtr<WeakWorkerRef> workerRef = WeakWorkerRef::Create( + workerPrivate, [selfWeakRef = std::move(selfWeakRef), + strongRef = std::move(strongRef)]() mutable { + RefPtr<RemoteWorkerChild> self(selfWeakRef); + + if (NS_WARN_IF(!self)) { + return; + } + + self->TransitionStateToTerminated(); + self->ShutdownOnWorker(); + + nsCOMPtr<nsISerialEventTarget> target = self->GetOwningEventTarget(); + NS_ProxyRelease(__func__, target, self.forget()); + }); + + if (NS_WARN_IF(!workerRef)) { + TransitionStateToTerminated(); + CreationFailedOnAnyThread(); + ShutdownOnWorker(); + + return; + } + + TransitionStateToRunning(workerPrivate.forget(), workerRef.forget()); + CreationSucceededOnAnyThread(); +} + +void RemoteWorkerChild::ShutdownOnWorker() { + RefPtr<RemoteWorkerChild> self = this; + + nsCOMPtr<nsIRunnable> r = + NS_NewRunnableFunction(__func__, [self = std::move(self)] { + auto launcherData = self->mLauncherData.Access(); + + if (!launcherData->mIPCActive) { + return; + } + + launcherData->mIPCActive = false; + Unused << self->SendClose(); + }); + + GetOwningEventTarget()->Dispatch(r.forget(), NS_DISPATCH_NORMAL); +} + +RefPtr<GenericNonExclusivePromise> RemoteWorkerChild::GetTerminationPromise() { + auto launcherData = mLauncherData.Access(); + return launcherData->mTerminationPromise.Ensure(__func__); +} + +void RemoteWorkerChild::CreationSucceededOnAnyThread() { + CreationSucceededOrFailedOnAnyThread(true); +} + +void RemoteWorkerChild::CreationFailedOnAnyThread() { + CreationSucceededOrFailedOnAnyThread(false); +} + +void RemoteWorkerChild::CreationSucceededOrFailedOnAnyThread( + bool aDidCreationSucceed) { +#ifdef DEBUG + { + auto lock = mState.Lock(); + MOZ_ASSERT_IF(aDidCreationSucceed, lock->is<Running>()); + MOZ_ASSERT_IF(!aDidCreationSucceed, lock->is<Terminated>()); + } +#endif + + RefPtr<RemoteWorkerChild> self = this; + + nsCOMPtr<nsIRunnable> r = NS_NewRunnableFunction( + __func__, + [self = std::move(self), didCreationSucceed = aDidCreationSucceed] { + auto launcherData = self->mLauncherData.Access(); + + if (!launcherData->mIPCActive) { + return; + } + + Unused << self->SendCreated(didCreationSucceed); + }); + + GetOwningEventTarget()->Dispatch(r.forget(), NS_DISPATCH_NORMAL); +} + +void RemoteWorkerChild::CloseWorkerOnMainThread(State& aState) { + AssertIsOnMainThread(); + MOZ_ASSERT(!aState.is<PendingTerminated>()); + + // WeakWorkerRef callback will be asynchronously invoked after + // WorkerPrivate::Cancel. + + if (aState.is<Pending>()) { + // SharedWorkerOp::MaybeStart would not block terminate operation while + // RemoteWorkerChild::mState is still Pending, and the + // Pending.mWorkerPrivate is still nullptr. For the case, just switching the + // State to PendingTerminated. + if (aState.as<Pending>().mWorkerPrivate) { + aState.as<Pending>().mWorkerPrivate->Cancel(); + } + TransitionStateToPendingTerminated(aState); + return; + } + + if (aState.is<Running>()) { + aState.as<Running>().mWorkerPrivate->Cancel(); + } +} + +/** + * Error reporting method + */ +void RemoteWorkerChild::ErrorPropagation(const ErrorValue& aValue) { + MOZ_ASSERT(GetOwningEventTarget()->IsOnCurrentThread()); + + auto launcherData = mLauncherData.Access(); + + if (!launcherData->mIPCActive) { + return; + } + + Unused << SendError(aValue); +} + +void RemoteWorkerChild::ErrorPropagationDispatch(nsresult aError) { + MOZ_ASSERT(NS_FAILED(aError)); + + RefPtr<RemoteWorkerChild> self = this; + nsCOMPtr<nsIRunnable> r = NS_NewRunnableFunction( + "RemoteWorkerChild::ErrorPropagationDispatch", + [self = std::move(self), aError]() { self->ErrorPropagation(aError); }); + + GetOwningEventTarget()->Dispatch(r.forget(), NS_DISPATCH_NORMAL); +} + +void RemoteWorkerChild::ErrorPropagationOnMainThread( + const WorkerErrorReport* aReport, bool aIsErrorEvent) { + AssertIsOnMainThread(); + + ErrorValue value; + if (aIsErrorEvent) { + ErrorData data( + aReport->mIsWarning, aReport->mLineNumber, aReport->mColumnNumber, + aReport->mMessage, aReport->mFilename, aReport->mLine, + TransformIntoNewArray(aReport->mNotes, [](const WorkerErrorNote& note) { + return ErrorDataNote(note.mLineNumber, note.mColumnNumber, + note.mMessage, note.mFilename); + })); + value = data; + } else { + value = void_t(); + } + + RefPtr<RemoteWorkerChild> self = this; + nsCOMPtr<nsIRunnable> r = NS_NewRunnableFunction( + "RemoteWorkerChild::ErrorPropagationOnMainThread", + [self = std::move(self), value]() { self->ErrorPropagation(value); }); + + GetOwningEventTarget()->Dispatch(r.forget(), NS_DISPATCH_NORMAL); +} + +void RemoteWorkerChild::NotifyLock(bool aCreated) { + nsCOMPtr<nsIRunnable> r = + NS_NewRunnableFunction(__func__, [self = RefPtr(this), aCreated] { + auto launcherData = self->mLauncherData.Access(); + + if (!launcherData->mIPCActive) { + return; + } + + Unused << self->SendNotifyLock(aCreated); + }); + + GetOwningEventTarget()->Dispatch(r.forget(), NS_DISPATCH_NORMAL); +} + +void RemoteWorkerChild::FlushReportsOnMainThread( + nsIConsoleReportCollector* aReporter) { + AssertIsOnMainThread(); + + bool reportErrorToBrowserConsole = true; + + // Flush the reports. + for (uint32_t i = 0, len = mWindowIDs.Length(); i < len; ++i) { + aReporter->FlushReportsToConsole( + mWindowIDs[i], nsIConsoleReportCollector::ReportAction::Save); + reportErrorToBrowserConsole = false; + } + + // Finally report to browser console if there is no any window. + if (reportErrorToBrowserConsole) { + aReporter->FlushReportsToConsole(0); + return; + } + + aReporter->ClearConsoleReports(); +} + +/** + * Worker state transition methods + */ +RemoteWorkerChild::WorkerPrivateAccessibleState:: + ~WorkerPrivateAccessibleState() { + // mWorkerPrivate can be safely released on the main thread. + if (!mWorkerPrivate || NS_IsMainThread()) { + return; + } + + NS_ReleaseOnMainThread( + "RemoteWorkerChild::WorkerPrivateAccessibleState::mWorkerPrivate", + mWorkerPrivate.forget()); +} + +RemoteWorkerChild::Running::~Running() { + // This can occur if the current object is a temporary. + if (!mWorkerPrivate) { + return; + } + + if (mWorkerPrivate->IsOnWorkerThread()) { + return; + } + + RefPtr<ReleaseWorkerRunnable> runnable = new ReleaseWorkerRunnable( + std::move(mWorkerPrivate), std::move(mWorkerRef)); + + nsCOMPtr<nsIRunnable> dispatchWorkerRunnableRunnable = + NS_NewRunnableFunction(__func__, [runnable = std::move(runnable)] { + Unused << NS_WARN_IF(!runnable->Dispatch()); + }); + + if (NS_IsMainThread()) { + dispatchWorkerRunnableRunnable->Run(); + } else { + SchedulerGroup::Dispatch(TaskCategory::Other, + dispatchWorkerRunnableRunnable.forget()); + } +} + +void RemoteWorkerChild::TransitionStateToPendingTerminated(State& aState) { + MOZ_ASSERT(aState.is<Pending>()); + + CancelAllPendingOps(aState); + + aState = VariantType<PendingTerminated>(); +} + +void RemoteWorkerChild::TransitionStateToRunning( + already_AddRefed<WorkerPrivate> aWorkerPrivate, + already_AddRefed<WeakWorkerRef> aWorkerRef) { + RefPtr<WorkerPrivate> workerPrivate = aWorkerPrivate; + MOZ_ASSERT(workerPrivate); + + RefPtr<WeakWorkerRef> workerRef = aWorkerRef; + MOZ_ASSERT(workerRef); + + auto lock = mState.Lock(); + + // TransitionStateToRunning is supposed to be used only + // in InitializeOnWorker where we know we are <Pending>. + auto pendingOps = std::move(lock->as<Pending>().mPendingOps); + + /** + * I'd initialize the WorkerPrivate and WeakWorkerRef in the constructor, + * but mozilla::Variant attempts to call the thread-unsafe `AddRef()` on + * WorkerPrivate. + */ + *lock = VariantType<Running>(); + lock->as<Running>().mWorkerPrivate = std::move(workerPrivate); + lock->as<Running>().mWorkerRef = std::move(workerRef); + + SelfHolder self = this; + + nsCOMPtr<nsIRunnable> r = NS_NewRunnableFunction( + __func__, [pendingOps = std::move(pendingOps), self = std::move(self)]() { + for (auto& op : pendingOps) { + // XXX: Is it on purpose that we renew the lock for each pending op ? + auto lock = self->mState.Lock(); + + DebugOnly<bool> started = op->MaybeStart(self.get(), lock.ref()); + MOZ_ASSERT(started); + } + }); + + MOZ_ALWAYS_SUCCEEDS( + mOwningEventTarget->Dispatch(r.forget(), NS_DISPATCH_NORMAL)); +} + +void RemoteWorkerChild::TransitionStateToTerminated() { + auto lock = mState.Lock(); + + TransitionStateToTerminated(lock.ref()); +} + +void RemoteWorkerChild::TransitionStateToTerminated(State& aState) { + if (aState.is<Pending>()) { + CancelAllPendingOps(aState); + } + + nsCOMPtr<nsIRunnable> r = + NS_NewRunnableFunction(__func__, [self = SelfHolder(this)]() { + auto launcherData = self->mLauncherData.Access(); + launcherData->mTerminationPromise.ResolveIfExists(true, __func__); + }); + + if (GetOwningEventTarget()->IsOnCurrentThread()) { + r->Run(); + } else { + GetOwningEventTarget()->Dispatch(r.forget(), NS_DISPATCH_NORMAL); + } + + aState = VariantType<Terminated>(); +} + +/** + * Operation execution classes/methods + */ +class RemoteWorkerChild::SharedWorkerOp : public RemoteWorkerChild::Op { + public: + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(SharedWorkerOp, override) + + explicit SharedWorkerOp(RemoteWorkerOp&& aOp) : mOp(std::move(aOp)) {} + + bool MaybeStart(RemoteWorkerChild* aOwner, + RemoteWorkerChild::State& aState) override { + MOZ_ASSERT(!mStarted); + MOZ_ASSERT(aOwner); + + auto launcherData = aOwner->mLauncherData.Access(); + + if (NS_WARN_IF(!launcherData->mIPCActive)) { + Unused << NS_WARN_IF(!aState.is<Terminated>()); + +#ifdef DEBUG + mStarted = true; +#endif + + return true; + } + + if (aState.is<Pending>() && !IsTerminationOp()) { + return false; + } + + if (aState.is<PendingTerminated>() || aState.is<Terminated>()) { +#ifdef DEBUG + mStarted = true; +#endif + + return true; + } + + MOZ_ASSERT(aState.is<Running>() || IsTerminationOp()); + + RefPtr<SharedWorkerOp> self = this; + SelfHolder owner = aOwner; + + nsCOMPtr<nsIRunnable> r = NS_NewRunnableFunction( + __func__, [self = std::move(self), owner = std::move(owner)]() mutable { + { + auto lock = owner->mState.Lock(); + + if (NS_WARN_IF(lock->is<Terminated>())) { + self->Cancel(); + return; + } + // XXX: All other possible states are ok here? + } + + self->Exec(owner); + }); + + MOZ_ALWAYS_SUCCEEDS( + SchedulerGroup::Dispatch(TaskCategory::Other, r.forget())); + +#ifdef DEBUG + mStarted = true; +#endif + + return true; + } + + void Cancel() override { +#ifdef DEBUG + mStarted = true; +#endif + } + + private: + ~SharedWorkerOp() { MOZ_ASSERT(mStarted); } + + bool IsTerminationOp() const { + return mOp.type() == RemoteWorkerOp::TRemoteWorkerTerminateOp; + } + + void Exec(SelfHolder& aOwner) { + using Running = RemoteWorkerChild::Running; + + AssertIsOnMainThread(); + + auto lock = aOwner->mState.Lock(); + + if (IsTerminationOp()) { + aOwner->CloseWorkerOnMainThread(lock.ref()); + return; + } + + MOZ_ASSERT(lock->is<Running>()); + if (!lock->is<Running>()) { + aOwner->ErrorPropagationDispatch(NS_ERROR_DOM_INVALID_STATE_ERR); + return; + } + + RefPtr<WorkerPrivate> workerPrivate = lock->as<Running>().mWorkerPrivate; + + MOZ_ASSERT(workerPrivate); + + if (mOp.type() == RemoteWorkerOp::TRemoteWorkerSuspendOp) { + workerPrivate->ParentWindowPaused(); + } else if (mOp.type() == RemoteWorkerOp::TRemoteWorkerResumeOp) { + workerPrivate->ParentWindowResumed(); + } else if (mOp.type() == RemoteWorkerOp::TRemoteWorkerFreezeOp) { + workerPrivate->Freeze(nullptr); + } else if (mOp.type() == RemoteWorkerOp::TRemoteWorkerThawOp) { + workerPrivate->Thaw(nullptr); + } else if (mOp.type() == RemoteWorkerOp::TRemoteWorkerPortIdentifierOp) { + RefPtr<MessagePortIdentifierRunnable> r = + new MessagePortIdentifierRunnable( + workerPrivate, aOwner, + mOp.get_RemoteWorkerPortIdentifierOp().portIdentifier()); + + if (NS_WARN_IF(!r->Dispatch())) { + aOwner->ErrorPropagationDispatch(NS_ERROR_FAILURE); + } + } else if (mOp.type() == RemoteWorkerOp::TRemoteWorkerAddWindowIDOp) { + aOwner->mWindowIDs.AppendElement( + mOp.get_RemoteWorkerAddWindowIDOp().windowID()); + } else if (mOp.type() == RemoteWorkerOp::TRemoteWorkerRemoveWindowIDOp) { + aOwner->mWindowIDs.RemoveElement( + mOp.get_RemoteWorkerRemoveWindowIDOp().windowID()); + } else { + MOZ_CRASH("Unknown RemoteWorkerOp type!"); + } + } + + RemoteWorkerOp mOp; + +#ifdef DEBUG + bool mStarted = false; +#endif +}; + +void RemoteWorkerChild::AddPortIdentifier( + JSContext* aCx, WorkerPrivate* aWorkerPrivate, + UniqueMessagePortId& aPortIdentifier) { + if (NS_WARN_IF(!aWorkerPrivate->ConnectMessagePort(aCx, aPortIdentifier))) { + ErrorPropagationDispatch(NS_ERROR_FAILURE); + } +} + +void RemoteWorkerChild::CancelAllPendingOps(State& aState) { + MOZ_ASSERT(aState.is<Pending>()); + + auto pendingOps = std::move(aState.as<Pending>().mPendingOps); + + for (auto& op : pendingOps) { + op->Cancel(); + } +} + +void RemoteWorkerChild::MaybeStartOp(RefPtr<Op>&& aOp) { + MOZ_ASSERT(aOp); + + auto lock = mState.Lock(); + + if (!aOp->MaybeStart(this, lock.ref())) { + // Maybestart returns false only if we are <Pending>. + lock->as<Pending>().mPendingOps.AppendElement(std::move(aOp)); + } +} + +IPCResult RemoteWorkerChild::RecvExecOp(RemoteWorkerOp&& aOp) { + MOZ_ASSERT(!mIsServiceWorker); + + MaybeStartOp(new SharedWorkerOp(std::move(aOp))); + + return IPC_OK(); +} + +IPCResult RemoteWorkerChild::RecvExecServiceWorkerOp( + ServiceWorkerOpArgs&& aArgs, ExecServiceWorkerOpResolver&& aResolve) { + MOZ_ASSERT(mIsServiceWorker); + MOZ_ASSERT( + aArgs.type() != + ServiceWorkerOpArgs::TParentToChildServiceWorkerFetchEventOpArgs, + "FetchEvent operations should be sent via PFetchEventOp(Proxy) actors!"); + + MaybeReportServiceWorkerShutdownProgress(aArgs); + + MaybeStartOp(ServiceWorkerOp::Create(std::move(aArgs), std::move(aResolve))); + + return IPC_OK(); +} + +RefPtr<GenericPromise> +RemoteWorkerChild::MaybeSendSetServiceWorkerSkipWaitingFlag() { + RefPtr<GenericPromise::Private> promise = + new GenericPromise::Private(__func__); + + RefPtr<RemoteWorkerChild> self = this; + + nsCOMPtr<nsIRunnable> r = NS_NewRunnableFunction(__func__, [self = std::move( + self), + promise] { + auto launcherData = self->mLauncherData.Access(); + + if (!launcherData->mIPCActive) { + promise->Reject(NS_ERROR_DOM_ABORT_ERR, __func__); + return; + } + + self->SendSetServiceWorkerSkipWaitingFlag()->Then( + GetCurrentSerialEventTarget(), __func__, + [promise]( + const SetServiceWorkerSkipWaitingFlagPromise::ResolveOrRejectValue& + aResult) { + if (NS_WARN_IF(aResult.IsReject())) { + promise->Reject(NS_ERROR_DOM_ABORT_ERR, __func__); + return; + } + + promise->Resolve(aResult.ResolveValue(), __func__); + }); + }); + + GetOwningEventTarget()->Dispatch(r.forget(), NS_DISPATCH_NORMAL); + + return promise; +} + +/** + * PFetchEventOpProxy methods + */ +already_AddRefed<PFetchEventOpProxyChild> +RemoteWorkerChild::AllocPFetchEventOpProxyChild( + const ParentToChildServiceWorkerFetchEventOpArgs& aArgs) { + return RefPtr{new FetchEventOpProxyChild()}.forget(); +} + +IPCResult RemoteWorkerChild::RecvPFetchEventOpProxyConstructor( + PFetchEventOpProxyChild* aActor, + const ParentToChildServiceWorkerFetchEventOpArgs& aArgs) { + MOZ_ASSERT(aActor); + + (static_cast<FetchEventOpProxyChild*>(aActor))->Initialize(aArgs); + + return IPC_OK(); +} + +} // namespace dom +} // namespace mozilla |