diff options
Diffstat (limited to 'dom/workers/remoteworkers/RemoteWorkerChild.cpp')
-rw-r--r-- | dom/workers/remoteworkers/RemoteWorkerChild.cpp | 1071 |
1 files changed, 1071 insertions, 0 deletions
diff --git a/dom/workers/remoteworkers/RemoteWorkerChild.cpp b/dom/workers/remoteworkers/RemoteWorkerChild.cpp new file mode 100644 index 0000000000..a644439a8d --- /dev/null +++ b/dom/workers/remoteworkers/RemoteWorkerChild.cpp @@ -0,0 +1,1071 @@ +/* -*- 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 "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/dom/WorkerScope.h" +#include "mozilla/ipc/BackgroundUtils.h" +#include "mozilla/ipc/URIUtils.h" +#include "mozilla/net/CookieJarSettings.h" +#include "mozilla/PermissionManager.h" + +mozilla::LazyLogModule gRemoteWorkerChildLog("RemoteWorkerChild"); + +#ifdef LOG +# undef LOG +#endif +#define LOG(fmt) MOZ_LOG(gRemoteWorkerChildLog, mozilla::LogLevel::Verbose, fmt) + +namespace mozilla { + +using namespace ipc; + +namespace dom { + +using workerinternals::ChannelFromScriptURLMainThread; + +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, + RemoteWorkerChild* aActor, + const MessagePortIdentifier& aPortIdentifier) + : WorkerRunnable(aWorkerPrivate, "MessagePortIdentifierRunnable"), + mActor(aActor), + mPortIdentifier(aPortIdentifier) {} + + private: + bool WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override { + if (aWorkerPrivate->GlobalScope()->IsDying()) { + mPortIdentifier.ForceClose(); + return true; + } + mActor->AddPortIdentifier(aCx, aWorkerPrivate, mPortIdentifier); + return true; + } + + RefPtr<RemoteWorkerChild> mActor; + UniqueMessagePortId mPortIdentifier; +}; + +// This is used to propagate the CSP violation when loading the SharedWorker +// main-script and nothing else. +class RemoteWorkerCSPEventListener final : public nsICSPEventListener { + public: + NS_DECL_ISUPPORTS + + explicit RemoteWorkerCSPEventListener(RemoteWorkerChild* aActor) + : mActor(aActor){}; + + NS_IMETHOD OnCSPViolationEvent(const nsAString& aJSON) override { + mActor->CSPViolationPropagationOnMainThread(aJSON); + return NS_OK; + } + + private: + ~RemoteWorkerCSPEventListener() = default; + + RefPtr<RemoteWorkerChild> mActor; +}; + +NS_IMPL_ISUPPORTS(RemoteWorkerCSPEventListener, nsICSPEventListener) + +} // anonymous namespace + +RemoteWorkerChild::RemoteWorkerChild(const RemoteWorkerData& aData) + : mState(VariantType<Pending>(), "RemoteWorkerChild::mState"), + mServiceKeepAlive(RemoteWorkerService::MaybeGetKeepAlive()), + mIsServiceWorker(aData.serviceWorkerData().type() == + OptionalServiceWorkerData::TServiceWorkerData) { + MOZ_ASSERT(RemoteWorkerService::Thread()->IsOnCurrentThread()); +} + +RemoteWorkerChild::~RemoteWorkerChild() { +#ifdef DEBUG + auto lock = mState.Lock(); + MOZ_ASSERT(lock->is<Killed>()); +#endif +} + +void RemoteWorkerChild::ActorDestroy(ActorDestroyReason) { + auto launcherData = mLauncherData.Access(); + + Unused << NS_WARN_IF(!launcherData->mTerminationPromise.IsEmpty()); + launcherData->mTerminationPromise.RejectIfExists(NS_ERROR_DOM_ABORT_ERR, + __func__); + + auto lock = mState.Lock(); + + // If the worker hasn't shutdown or begun shutdown, we need to ensure it gets + // canceled. + if (NS_WARN_IF(!lock->is<Killed>() && !lock->is<Canceled>())) { + // In terms of strong references to this RemoteWorkerChild, at this moment: + // - IPC is holding a strong reference that will be dropped in the near + // future after this method returns. + // - If the worker has been started by ExecWorkerOnMainThread, then + // WorkerPrivate::mRemoteWorkerController is a strong reference to us. + // If the worker has not been started, ExecWorker's runnable lambda will + // have a strong reference that will cover the call to + // ExecWorkerOnMainThread. + // - The WorkerPrivate cancellation and termination callbacks will also + // hold strong references, but those callbacks will not outlive the + // WorkerPrivate and are not exposed to callers like + // mRemoteWorkerController is. + // + // Note that this call to RequestWorkerCancellation can still race worker + // cancellation, in which case the strong reference obtained by + // NewRunnableMethod can end up being the last strong reference. + // (RequestWorkerCancellation handles the case that the Worker is already + // canceled if this happens.) + RefPtr<nsIRunnable> runnable = + NewRunnableMethod("RequestWorkerCancellation", this, + &RemoteWorkerChild::RequestWorkerCancellation); + MOZ_ALWAYS_SUCCEEDS(SchedulerGroup::Dispatch(runnable.forget())); + } +} + +void RemoteWorkerChild::ExecWorker(const RemoteWorkerData& aData) { +#ifdef DEBUG + MOZ_ASSERT(GetActorEventTarget()->IsOnCurrentThread()); + auto launcherData = mLauncherData.Access(); + MOZ_ASSERT(CanSend()); +#endif + + RefPtr<RemoteWorkerChild> self = this; + + nsCOMPtr<nsIRunnable> r = NS_NewRunnableFunction( + __func__, [self = std::move(self), data = aData]() mutable { + nsresult rv = self->ExecWorkerOnMainThread(std::move(data)); + + // Creation failure will already have been reported via the method + // above internally using ScopeExit. + Unused << NS_WARN_IF(NS_FAILED(rv)); + }); + + MOZ_ALWAYS_SUCCEEDS(SchedulerGroup::Dispatch(r.forget())); +} + +nsresult RemoteWorkerChild::ExecWorkerOnMainThread(RemoteWorkerData&& aData) { + MOZ_ASSERT(NS_IsMainThread()); + + // Ensure that the IndexedDatabaseManager is initialized so that if any + // workers do any IndexedDB calls that all of IDB's prefs/etc. are + // initialized. + Unused << NS_WARN_IF(!IndexedDatabaseManager::GetOrCreate()); + + auto scopeExit = + MakeScopeExit([&] { ExceptionalErrorTransitionDuringExecWorker(); }); + + // 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.mUsingStorageAccess = aData.usingStorageAccess(); + info.mIsThirdPartyContextToTopWindow = aData.isThirdPartyContextToTopWindow(); + info.mOriginAttributes = + BasePrincipal::Cast(principal)->OriginAttributesRef(); + info.mShouldResistFingerprinting = aData.shouldResistFingerprinting(); + Maybe<RFPTarget> overriddenFingerprintingSettings; + if (aData.overriddenFingerprintingSettings().isSome()) { + overriddenFingerprintingSettings.emplace( + RFPTarget(aData.overriddenFingerprintingSettings().ref())); + } + info.mOverriddenFingerprintingSettings = overriddenFingerprintingSettings; + net::CookieJarSettings::Deserialize(aData.cookieJarSettings(), + getter_AddRefs(info.mCookieJarSettings)); + info.mCookieJarSettingsArgs = aData.cookieJarSettings(); + + // 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, aData.type(), aData.credentials(), clientInfo, + nsIContentPolicy::TYPE_INTERNAL_SHARED_WORKER, info.mCookieJarSettings, + info.mReferrerInfo, getter_AddRefs(info.mChannel)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + nsCOMPtr<nsILoadInfo> loadInfo = info.mChannel->LoadInfo(); + + auto* cspEventListener = new RemoteWorkerCSPEventListener(this); + rv = loadInfo->SetCspEventListener(cspEventListener); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + } + + info.mAgentClusterId = aData.agentClusterId(); + + AutoJSAPI jsapi; + jsapi.Init(); + + ErrorResult error; + RefPtr<RemoteWorkerChild> self = this; + RefPtr<WorkerPrivate> workerPrivate = WorkerPrivate::Constructor( + jsapi.cx(), aData.originalScriptURL(), false, + mIsServiceWorker ? WorkerKindService : WorkerKindShared, + aData.credentials(), aData.type(), aData.name(), VoidCString(), &info, + error, std::move(workerPrivateId), + [self](bool aEverRan) { + self->OnWorkerCancellationTransitionStateFromPendingOrRunningToCanceled(); + }, + // This will be invoked here on the main thread when the worker is already + // fully shutdown. This replaces a prior approach where we would + // begin to transition when the worker thread would reach the Canceling + // state. This lambda ensures that we not only wait for the Killing state + // to be reached but that the global shutdown has already occurred. + [self]() { self->TransitionStateFromCanceledToKilled(); }); + + if (NS_WARN_IF(error.Failed())) { + MOZ_ASSERT(!workerPrivate); + + rv = error.StealNSResult(); + return rv; + } + + workerPrivate->SetRemoteWorkerController(this); + + // This wants to run as a normal task sequentially after the top level script + // evaluation, so the hybrid target is the correct choice between hybrid and + // `ControlEventTarget`. + nsCOMPtr<nsISerialEventTarget> workerTarget = + workerPrivate->HybridEventTarget(); + + nsCOMPtr<nsIRunnable> runnable = NewCancelableRunnableMethod( + "InitialzeOnWorker", this, &RemoteWorkerChild::InitializeOnWorker); + + { + 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) { + nsCOMPtr<nsIRunnable> r = NS_NewRunnableFunction( + __func__, [workerTarget, + initializeWorkerRunnable = std::move(runnable)]() mutable { + Unused << NS_WARN_IF(NS_FAILED( + workerTarget->Dispatch(initializeWorkerRunnable.forget()))); + }); + + RefPtr<PermissionManager> permissionManager = + PermissionManager::GetInstance(); + if (!permissionManager) { + return NS_ERROR_FAILURE; + } + permissionManager->WhenPermissionsAvailable(principal, r); + } else { + if (NS_WARN_IF(NS_FAILED(workerTarget->Dispatch(runnable.forget())))) { + rv = NS_ERROR_FAILURE; + return rv; + } + } + + scopeExit.release(); + + return NS_OK; +} + +void RemoteWorkerChild::RequestWorkerCancellation() { + MOZ_ASSERT(NS_IsMainThread()); + + LOG(("RequestWorkerCancellation[this=%p]", this)); + + // We want to ensure that we've requested the worker be canceled. So if the + // worker is running, cancel it. We can't do this with the lock held, + // however, because our lambdas will want to manipulate the state. + RefPtr<WorkerPrivate> cancelWith; + { + auto lock = mState.Lock(); + if (lock->is<Pending>()) { + cancelWith = lock->as<Pending>().mWorkerPrivate; + } else if (lock->is<Running>()) { + cancelWith = lock->as<Running>().mWorkerPrivate; + } + } + + if (cancelWith) { + cancelWith->Cancel(); + } +} + +// This method will be invoked on the worker after the top-level +// CompileScriptRunnable task has succeeded and as long as the worker has not +// been closed/canceled. There are edge-cases related to cancellation, but we +// have our caller ensure that we are only called as long as the worker's state +// is Running. +// +// (https://bugzilla.mozilla.org/show_bug.cgi?id=1800659 will eliminate +// cancellation, and the documentation around that bug / in design documents +// helps provide more context about this.) +void RemoteWorkerChild::InitializeOnWorker() { + nsCOMPtr<nsIRunnable> r = + NewRunnableMethod("TransitionStateToRunning", this, + &RemoteWorkerChild::TransitionStateToRunning); + MOZ_ALWAYS_SUCCEEDS(SchedulerGroup::Dispatch(r.forget())); +} + +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<Killed>()); + } +#endif + + RefPtr<RemoteWorkerChild> self = this; + + nsCOMPtr<nsIRunnable> r = NS_NewRunnableFunction( + __func__, + [self = std::move(self), didCreationSucceed = aDidCreationSucceed] { + auto launcherData = self->mLauncherData.Access(); + + if (!self->CanSend() || launcherData->mDidSendCreated) { + return; + } + + Unused << self->SendCreated(didCreationSucceed); + launcherData->mDidSendCreated = true; + }); + + GetActorEventTarget()->Dispatch(r.forget(), NS_DISPATCH_NORMAL); +} + +void RemoteWorkerChild::CloseWorkerOnMainThread() { + AssertIsOnMainThread(); + + LOG(("CloseWorkerOnMainThread[this=%p]", this)); + + // We can't hold the state lock while calling WorkerPrivate::Cancel because + // the lambda callback will want to touch the state, so save off the + // WorkerPrivate so we can cancel it (if we need to cancel it). + RefPtr<WorkerPrivate> cancelWith; + { + auto lock = mState.Lock(); + + if (lock->is<Pending>()) { + cancelWith = lock->as<Pending>().mWorkerPrivate; + // There should be no way for this code to run before we + // ExecWorkerOnMainThread runs, which means that either it should have + // set a WorkerPrivate on Pending, or its error handling should already + // have transitioned us to Canceled and Killing in that order. (It's + // also possible that it assigned a WorkerPrivate and subsequently we + // transitioned to Running, which would put us in the next branch.) + MOZ_DIAGNOSTIC_ASSERT(cancelWith); + } else if (lock->is<Running>()) { + cancelWith = lock->as<Running>().mWorkerPrivate; + } + } + + // It's very okay for us to not have a WorkerPrivate here if we've already + // canceled the worker or if errors happened. + if (cancelWith) { + cancelWith->Cancel(); + } +} + +/** + * Error reporting method + */ +void RemoteWorkerChild::ErrorPropagation(const ErrorValue& aValue) { + MOZ_ASSERT(GetActorEventTarget()->IsOnCurrentThread()); + + if (!CanSend()) { + 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); }); + + GetActorEventTarget()->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); }); + + GetActorEventTarget()->Dispatch(r.forget(), NS_DISPATCH_NORMAL); +} + +void RemoteWorkerChild::CSPViolationPropagationOnMainThread( + const nsAString& aJSON) { + AssertIsOnMainThread(); + + RefPtr<RemoteWorkerChild> self = this; + nsCOMPtr<nsIRunnable> r = NS_NewRunnableFunction( + "RemoteWorkerChild::ErrorPropagationDispatch", + [self = std::move(self), json = nsString(aJSON)]() { + CSPViolation violation(json); + self->ErrorPropagation(violation); + }); + + GetActorEventTarget()->Dispatch(r.forget(), NS_DISPATCH_NORMAL); +} + +void RemoteWorkerChild::NotifyLock(bool aCreated) { + nsCOMPtr<nsIRunnable> r = + NS_NewRunnableFunction(__func__, [self = RefPtr(this), aCreated] { + if (!self->CanSend()) { + return; + } + + Unused << self->SendNotifyLock(aCreated); + }); + + GetActorEventTarget()->Dispatch(r.forget(), NS_DISPATCH_NORMAL); +} + +void RemoteWorkerChild::NotifyWebTransport(bool aCreated) { + nsCOMPtr<nsIRunnable> r = + NS_NewRunnableFunction(__func__, [self = RefPtr(this), aCreated] { + if (!self->CanSend()) { + return; + } + + Unused << self->SendNotifyWebTransport(aCreated); + }); + + GetActorEventTarget()->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() { + // We should now only be performing state transitions on the main thread, so + // we should assert we're only releasing on the main thread. + MOZ_ASSERT(!mWorkerPrivate || NS_IsMainThread()); + // mWorkerPrivate can be safely released on the main thread. + if (!mWorkerPrivate || NS_IsMainThread()) { + return; + } + + // But as a backstop, do proxy the release to the main thread. + NS_ReleaseOnMainThread( + "RemoteWorkerChild::WorkerPrivateAccessibleState::mWorkerPrivate", + mWorkerPrivate.forget()); +} + +void RemoteWorkerChild:: + OnWorkerCancellationTransitionStateFromPendingOrRunningToCanceled() { + auto lock = mState.Lock(); + + LOG(("TransitionStateFromPendingOrRunningToCanceled[this=%p]", this)); + + if (lock->is<Pending>()) { + TransitionStateFromPendingToCanceled(lock.ref()); + } else if (lock->is<Running>()) { + *lock = VariantType<Canceled>(); + } else { + MOZ_ASSERT(false, "State should have been Pending or Running"); + } +} + +void RemoteWorkerChild::TransitionStateFromPendingToCanceled(State& aState) { + AssertIsOnMainThread(); + MOZ_ASSERT(aState.is<Pending>()); + LOG(("TransitionStateFromPendingToCanceled[this=%p]", this)); + + CancelAllPendingOps(aState); + + aState = VariantType<Canceled>(); +} + +void RemoteWorkerChild::TransitionStateFromCanceledToKilled() { + AssertIsOnMainThread(); + + LOG(("TransitionStateFromCanceledToKilled[this=%p]", this)); + + auto lock = mState.Lock(); + MOZ_ASSERT(lock->is<Canceled>()); + + *lock = VariantType<Killed>(); + + RefPtr<RemoteWorkerChild> self = this; + nsCOMPtr<nsIRunnable> r = NS_NewRunnableFunction(__func__, [self]() { + auto launcherData = self->mLauncherData.Access(); + + // (We maintain the historical ordering of resolving this promise prior to + // calling SendClose, however the previous code used 2 separate dispatches + // to this thread for the resolve and SendClose, and there inherently + // would be a race between the runnables resulting from the resolved + // promise and the promise containing the call to SendClose. Now it's + // entirely clear that our call to SendClose will effectively run before + // any of the resolved promises are able to do anything.) + launcherData->mTerminationPromise.ResolveIfExists(true, __func__); + + if (self->CanSend()) { + Unused << self->SendClose(); + } + }); + + GetActorEventTarget()->Dispatch(r.forget(), NS_DISPATCH_NORMAL); +} + +void RemoteWorkerChild::TransitionStateToRunning() { + AssertIsOnMainThread(); + + LOG(("TransitionStateToRunning[this=%p]", this)); + + nsTArray<RefPtr<Op>> pendingOps; + + { + auto lock = mState.Lock(); + + // Because this is an async notification sent from the worker to the main + // thread, it's very possible that we've already decided on the main thread + // to transition to the Canceled state, in which case there is nothing for + // us to do here. + if (!lock->is<Pending>()) { + LOG(("State is already not pending in TransitionStateToRunning[this=%p]!", + this)); + return; + } + + RefPtr<WorkerPrivate> workerPrivate = + std::move(lock->as<Pending>().mWorkerPrivate); + pendingOps = std::move(lock->as<Pending>().mPendingOps); + + // Move the worker private into place to avoid gratuitous ref churn; prior + // comments here suggest the Variant can't accept a move. + *lock = VariantType<Running>(); + lock->as<Running>().mWorkerPrivate = std::move(workerPrivate); + } + + CreationSucceededOnAnyThread(); + + RefPtr<RemoteWorkerChild> self = this; + for (auto& op : pendingOps) { + op->StartOnMainThread(self); + } +} + +void RemoteWorkerChild::ExceptionalErrorTransitionDuringExecWorker() { + AssertIsOnMainThread(); + + LOG(("ExceptionalErrorTransitionDuringExecWorker[this=%p]", this)); + + // This method is called synchronously by ExecWorkerOnMainThread in the event + // of any error. Because we only transition to Running on the main thread + // as the result of a notification from the worker, we know our state will be + // Pending, but mWorkerPrivate may or may not be null, as we may not have + // gotten to spawning the worker. + // + // In the event the worker exists, we need to Cancel() it. We must do this + // without the lock held because our call to Cancel() will invoke the + // cancellation callback we created which will call TransitionStateToCanceled, + // and we can't be holding the lock when that happens. + + RefPtr<WorkerPrivate> cancelWith; + + { + auto lock = mState.Lock(); + + MOZ_ASSERT(lock->is<Pending>()); + if (lock->is<Pending>()) { + cancelWith = lock->as<Pending>().mWorkerPrivate; + if (!cancelWith) { + // The worker wasn't actually created, so we should synthetically + // transition to canceled and onward. Since we have the lock, + // perform the transition now for clarity, but we'll handle the rest of + // this case after dropping the lock. + TransitionStateFromPendingToCanceled(lock.ref()); + } + } + } + + if (cancelWith) { + cancelWith->Cancel(); + } else { + TransitionStateFromCanceledToKilled(); + CreationFailedOnAnyThread(); + } +} + +/** + * 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); + // Thread: We are on the Worker Launcher thread. + + // Return false, indicating we should queue this op if our current state is + // pending and this isn't a termination op (which should skip the line). + if (aState.is<Pending>() && !IsTerminationOp()) { + return false; + } + + // If the worker is already shutting down (which should be unexpected + // because we should be told new operations after a termination op), just + // return true to indicate the op should be discarded. + if (aState.is<Canceled>() || aState.is<Killed>()) { +#ifdef DEBUG + mStarted = true; +#endif + if (mOp.type() == RemoteWorkerOp::TRemoteWorkerPortIdentifierOp) { + MessagePort::ForceClose( + mOp.get_RemoteWorkerPortIdentifierOp().portIdentifier()); + } + return true; + } + + MOZ_ASSERT(aState.is<Running>() || IsTerminationOp()); + + RefPtr<SharedWorkerOp> self = this; + RefPtr<RemoteWorkerChild> 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<Canceled>() || lock->is<Killed>())) { + self->Cancel(); + // Worker has already canceled, force close the MessagePort. + if (self->mOp.type() == + RemoteWorkerOp::TRemoteWorkerPortIdentifierOp) { + MessagePort::ForceClose( + self->mOp.get_RemoteWorkerPortIdentifierOp() + .portIdentifier()); + } + return; + } + } + + self->StartOnMainThread(owner); + }); + + MOZ_ALWAYS_SUCCEEDS(SchedulerGroup::Dispatch(r.forget())); + +#ifdef DEBUG + mStarted = true; +#endif + + return true; + } + + void StartOnMainThread(RefPtr<RemoteWorkerChild>& aOwner) final { + using Running = RemoteWorkerChild::Running; + + AssertIsOnMainThread(); + + if (IsTerminationOp()) { + aOwner->CloseWorkerOnMainThread(); + return; + } + + auto lock = aOwner->mState.Lock(); + 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!"); + } + } + + void Cancel() override { +#ifdef DEBUG + mStarted = true; +#endif + } + + private: + ~SharedWorkerOp() { MOZ_ASSERT(mStarted); } + + bool IsTerminationOp() const { + return mOp.type() == RemoteWorkerOp::TRemoteWorkerTerminateOp; + } + + 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] { + if (!self->CanSend()) { + 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__); + }); + }); + + GetActorEventTarget()->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 |