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 | |
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')
23 files changed, 4480 insertions, 0 deletions
diff --git a/dom/workers/remoteworkers/PRemoteWorker.ipdl b/dom/workers/remoteworkers/PRemoteWorker.ipdl new file mode 100644 index 0000000000..fcc8b79ff6 --- /dev/null +++ b/dom/workers/remoteworkers/PRemoteWorker.ipdl @@ -0,0 +1,89 @@ +/* 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 protocol PBackground; +include protocol PFetchEventOpProxy; + +include DOMTypes; +include ServiceWorkerOpArgs; +include RemoteWorkerTypes; + +namespace mozilla { +namespace dom { + +struct RemoteWorkerSuspendOp +{}; + +struct RemoteWorkerResumeOp +{}; + +struct RemoteWorkerFreezeOp +{}; + +struct RemoteWorkerThawOp +{}; + +struct RemoteWorkerTerminateOp +{}; + +struct RemoteWorkerPortIdentifierOp +{ + MessagePortIdentifier portIdentifier; +}; + +struct RemoteWorkerAddWindowIDOp +{ + uint64_t windowID; +}; + +struct RemoteWorkerRemoveWindowIDOp +{ + uint64_t windowID; +}; + +union RemoteWorkerOp { + RemoteWorkerSuspendOp; + RemoteWorkerResumeOp; + RemoteWorkerFreezeOp; + RemoteWorkerThawOp; + RemoteWorkerTerminateOp; + RemoteWorkerPortIdentifierOp; + RemoteWorkerAddWindowIDOp; + RemoteWorkerRemoveWindowIDOp; +}; + +// This protocol is used to make a remote worker controllable from the parent +// process. The parent process will receive operations from the +// PRemoteWorkerController protocol. +[ManualDealloc] +protocol PRemoteWorker +{ + manager PBackground; + + manages PFetchEventOpProxy; + +parent: + async Created(bool aStatus); + + async Error(ErrorValue aValue); + + async NotifyLock(bool aCreated); + + async Close(); + + async SetServiceWorkerSkipWaitingFlag() returns (bool aOk); + +child: + async PFetchEventOpProxy(ParentToChildServiceWorkerFetchEventOpArgs aArgs); + + async __delete__(); + + async ExecOp(RemoteWorkerOp op); + + async ExecServiceWorkerOp(ServiceWorkerOpArgs aArgs) + returns (ServiceWorkerOpResult aResult); +}; + +} // namespace dom +} // namespace mozilla diff --git a/dom/workers/remoteworkers/PRemoteWorkerController.ipdl b/dom/workers/remoteworkers/PRemoteWorkerController.ipdl new file mode 100644 index 0000000000..5c3818a27d --- /dev/null +++ b/dom/workers/remoteworkers/PRemoteWorkerController.ipdl @@ -0,0 +1,49 @@ +/* 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 protocol PBackground; +include protocol PFetchEventOp; + +include RemoteWorkerTypes; +include ServiceWorkerOpArgs; + +namespace mozilla { +namespace dom { + +/** + * Proxy protocol used by ServiceWorkerManager whose canonical state exists on + * the main thread to control/receive feedback from RemoteWorkers which are + * canonically controlled from the PBackground thread. Exclusively for use from + * the parent process main thread to the parent process PBackground thread. + */ +[ManualDealloc] +protocol PRemoteWorkerController { + manager PBackground; + + manages PFetchEventOp; + + child: + async CreationFailed(); + + async CreationSucceeded(); + + async ErrorReceived(ErrorValue aError); + + async Terminated(); + + async SetServiceWorkerSkipWaitingFlag() returns (bool aOk); + + parent: + async PFetchEventOp(ParentToParentServiceWorkerFetchEventOpArgs aArgs); + + async __delete__(); + + async Shutdown() returns (bool aOk); + + async ExecServiceWorkerOp(ServiceWorkerOpArgs aArgs) + returns (ServiceWorkerOpResult aResult); +}; + +} // namespace dom +} // namespace mozilla diff --git a/dom/workers/remoteworkers/PRemoteWorkerService.ipdl b/dom/workers/remoteworkers/PRemoteWorkerService.ipdl new file mode 100644 index 0000000000..7c82759df9 --- /dev/null +++ b/dom/workers/remoteworkers/PRemoteWorkerService.ipdl @@ -0,0 +1,26 @@ +/* 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 protocol PBackground; + +include ProtocolTypes; +include RemoteWorkerTypes; + +namespace mozilla { +namespace dom { + +// Simple protocol to register any active RemoteWorkerService running on any +// process. Initialization/registration is delayed for preallocated processes +// until the process takes on its final remoteType. +[ManualDealloc] +protocol PRemoteWorkerService +{ + manager PBackground; + +parent: + async __delete__(); +}; + +} // namespace dom +} // namespace mozilla 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 diff --git a/dom/workers/remoteworkers/RemoteWorkerChild.h b/dom/workers/remoteworkers/RemoteWorkerChild.h new file mode 100644 index 0000000000..5d21e23bd5 --- /dev/null +++ b/dom/workers/remoteworkers/RemoteWorkerChild.h @@ -0,0 +1,177 @@ +/* -*- 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_RemoteWorkerChild_h +#define mozilla_dom_RemoteWorkerChild_h + +#include "nsCOMPtr.h" +#include "nsISupportsImpl.h" +#include "nsTArray.h" + +#include "mozilla/DataMutex.h" +#include "mozilla/MozPromise.h" +#include "mozilla/RefPtr.h" +#include "mozilla/ThreadBound.h" +#include "mozilla/ThreadSafeWeakPtr.h" +#include "mozilla/dom/PRemoteWorkerChild.h" +#include "mozilla/dom/ServiceWorkerOpArgs.h" + +class nsISerialEventTarget; +class nsIConsoleReportCollector; + +namespace mozilla::dom { + +class ErrorValue; +class FetchEventOpProxyChild; +class RemoteWorkerData; +class ServiceWorkerOp; +class UniqueMessagePortId; +class WeakWorkerRef; +class WorkerErrorReport; +class WorkerPrivate; + +/** + * Background-managed "Worker Launcher"-thread-resident created via the + * RemoteWorkerManager to actually spawn the worker. Currently, the worker will + * be spawned from the main thread due to nsIPrincipal not being able to be + * created on background threads and other ownership invariants, most of which + * can be relaxed in the future. + */ +class RemoteWorkerChild final + : public SupportsThreadSafeWeakPtr<RemoteWorkerChild>, + public PRemoteWorkerChild { + friend class FetchEventOpProxyChild; + friend class PRemoteWorkerChild; + friend class ServiceWorkerOp; + + public: + MOZ_DECLARE_REFCOUNTED_TYPENAME(RemoteWorkerChild) + + explicit RemoteWorkerChild(const RemoteWorkerData& aData); + + ~RemoteWorkerChild(); + + nsISerialEventTarget* GetOwningEventTarget() const; + + void ExecWorker(const RemoteWorkerData& aData); + + void ErrorPropagationOnMainThread(const WorkerErrorReport* aReport, + bool aIsErrorEvent); + + void NotifyLock(bool aCreated); + + void FlushReportsOnMainThread(nsIConsoleReportCollector* aReporter); + + void AddPortIdentifier(JSContext* aCx, WorkerPrivate* aWorkerPrivate, + UniqueMessagePortId& aPortIdentifier); + + RefPtr<GenericNonExclusivePromise> GetTerminationPromise(); + + RefPtr<GenericPromise> MaybeSendSetServiceWorkerSkipWaitingFlag(); + + const nsTArray<uint64_t>& WindowIDs() const { return mWindowIDs; } + + private: + class InitializeWorkerRunnable; + + class Op; + class SharedWorkerOp; + + struct WorkerPrivateAccessibleState { + ~WorkerPrivateAccessibleState(); + RefPtr<WorkerPrivate> mWorkerPrivate; + }; + + struct Pending : WorkerPrivateAccessibleState { + nsTArray<RefPtr<Op>> mPendingOps; + }; + + struct PendingTerminated {}; + + struct Running : WorkerPrivateAccessibleState { + ~Running(); + RefPtr<WeakWorkerRef> mWorkerRef; + }; + + struct Terminated {}; + + using State = Variant<Pending, Running, PendingTerminated, Terminated>; + + DataMutex<State> mState; + + class Op { + public: + NS_INLINE_DECL_PURE_VIRTUAL_REFCOUNTING + + virtual ~Op() = default; + + virtual bool MaybeStart(RemoteWorkerChild* aOwner, State& aState) = 0; + + virtual void Cancel() = 0; + }; + + void ActorDestroy(ActorDestroyReason) override; + + mozilla::ipc::IPCResult RecvExecOp(RemoteWorkerOp&& aOp); + + mozilla::ipc::IPCResult RecvExecServiceWorkerOp( + ServiceWorkerOpArgs&& aArgs, ExecServiceWorkerOpResolver&& aResolve); + + already_AddRefed<PFetchEventOpProxyChild> AllocPFetchEventOpProxyChild( + const ParentToChildServiceWorkerFetchEventOpArgs& aArgs); + + mozilla::ipc::IPCResult RecvPFetchEventOpProxyConstructor( + PFetchEventOpProxyChild* aActor, + const ParentToChildServiceWorkerFetchEventOpArgs& aArgs) override; + + nsresult ExecWorkerOnMainThread(RemoteWorkerData&& aData); + + void InitializeOnWorker(); + + void ShutdownOnWorker(); + + void CreationSucceededOnAnyThread(); + + void CreationFailedOnAnyThread(); + + void CreationSucceededOrFailedOnAnyThread(bool aDidCreationSucceed); + + void CloseWorkerOnMainThread(State& aState); + + void ErrorPropagation(const ErrorValue& aValue); + + void ErrorPropagationDispatch(nsresult aError); + + void TransitionStateToPendingTerminated(State& aState); + + void TransitionStateToRunning(already_AddRefed<WorkerPrivate> aWorkerPrivate, + already_AddRefed<WeakWorkerRef> aWorkerRef); + + void TransitionStateToTerminated(); + + void TransitionStateToTerminated(State& aState); + + void CancelAllPendingOps(State& aState); + + void MaybeStartOp(RefPtr<Op>&& aOp); + + const bool mIsServiceWorker; + const nsCOMPtr<nsISerialEventTarget> mOwningEventTarget; + + // Touched on main-thread only. + nsTArray<uint64_t> mWindowIDs; + + struct LauncherBoundData { + bool mIPCActive = true; + MozPromiseHolder<GenericNonExclusivePromise> mTerminationPromise; + }; + + ThreadBound<LauncherBoundData> mLauncherData; +}; + +} // namespace mozilla::dom + +#endif // mozilla_dom_RemoteWorkerChild_h diff --git a/dom/workers/remoteworkers/RemoteWorkerController.cpp b/dom/workers/remoteworkers/RemoteWorkerController.cpp new file mode 100644 index 0000000000..a6e6737d43 --- /dev/null +++ b/dom/workers/remoteworkers/RemoteWorkerController.cpp @@ -0,0 +1,566 @@ +/* -*- 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 "RemoteWorkerController.h" + +#include <utility> + +#include "nsDebug.h" + +#include "mozilla/Assertions.h" +#include "mozilla/DebugOnly.h" +#include "mozilla/Maybe.h" +#include "mozilla/RemoteLazyInputStreamStorage.h" +#include "mozilla/dom/FetchEventOpParent.h" +#include "mozilla/dom/FetchEventOpProxyParent.h" +#include "mozilla/dom/MessagePortParent.h" +#include "mozilla/dom/RemoteWorkerTypes.h" +#include "mozilla/dom/ServiceWorkerCloneData.h" +#include "mozilla/dom/ServiceWorkerShutdownState.h" +#include "mozilla/ipc/BackgroundParent.h" +#include "RemoteWorkerControllerParent.h" +#include "RemoteWorkerManager.h" +#include "RemoteWorkerParent.h" + +namespace mozilla { + +using namespace ipc; + +namespace dom { + +/* static */ +already_AddRefed<RemoteWorkerController> RemoteWorkerController::Create( + const RemoteWorkerData& aData, RemoteWorkerObserver* aObserver, + base::ProcessId aProcessId) { + AssertIsInMainProcess(); + AssertIsOnBackgroundThread(); + MOZ_ASSERT(aObserver); + + RefPtr<RemoteWorkerController> controller = + new RemoteWorkerController(aData, aObserver); + + RefPtr<RemoteWorkerManager> manager = RemoteWorkerManager::GetOrCreate(); + MOZ_ASSERT(manager); + + manager->Launch(controller, aData, aProcessId); + + return controller.forget(); +} + +RemoteWorkerController::RemoteWorkerController(const RemoteWorkerData& aData, + RemoteWorkerObserver* aObserver) + : mObserver(aObserver), + mState(ePending), + mIsServiceWorker(aData.serviceWorkerData().type() == + OptionalServiceWorkerData::TServiceWorkerData) { + AssertIsInMainProcess(); + AssertIsOnBackgroundThread(); +} + +RemoteWorkerController::~RemoteWorkerController() { + AssertIsOnBackgroundThread(); + MOZ_DIAGNOSTIC_ASSERT(mPendingOps.IsEmpty()); +} + +void RemoteWorkerController::SetWorkerActor(RemoteWorkerParent* aActor) { + AssertIsOnBackgroundThread(); + MOZ_ASSERT(!mActor); + MOZ_ASSERT(aActor); + + mActor = aActor; +} + +void RemoteWorkerController::NoteDeadWorkerActor() { + AssertIsOnBackgroundThread(); + MOZ_ASSERT(mActor); + + // The actor has been destroyed without a proper close() notification. Let's + // inform the observer. + if (mState == eReady) { + mObserver->Terminated(); + } + + mActor = nullptr; + + Shutdown(); +} + +void RemoteWorkerController::CreationFailed() { + AssertIsOnBackgroundThread(); + MOZ_ASSERT(XRE_IsParentProcess()); + MOZ_ASSERT(mState == ePending || mState == eTerminated); + + if (mState == eTerminated) { + MOZ_ASSERT(!mActor); + MOZ_ASSERT(mPendingOps.IsEmpty()); + // Nothing to do. + return; + } + + NoteDeadWorker(); + + mObserver->CreationFailed(); +} + +void RemoteWorkerController::CreationSucceeded() { + AssertIsOnBackgroundThread(); + MOZ_ASSERT(mState == ePending || mState == eTerminated); + + if (mState == eTerminated) { + MOZ_ASSERT(!mActor); + MOZ_ASSERT(mPendingOps.IsEmpty()); + // Nothing to do. + return; + } + + MOZ_ASSERT(mActor); + mState = eReady; + + mObserver->CreationSucceeded(); + + auto pendingOps = std::move(mPendingOps); + + for (auto& op : pendingOps) { + DebugOnly<bool> started = op->MaybeStart(this); + MOZ_ASSERT(started); + } +} + +void RemoteWorkerController::ErrorPropagation(const ErrorValue& aValue) { + AssertIsOnBackgroundThread(); + + mObserver->ErrorReceived(aValue); +} + +void RemoteWorkerController::NotifyLock(bool aCreated) { + AssertIsOnBackgroundThread(); + + mObserver->LockNotified(aCreated); +} + +void RemoteWorkerController::WorkerTerminated() { + AssertIsOnBackgroundThread(); + + NoteDeadWorker(); + + mObserver->Terminated(); +} + +void RemoteWorkerController::CancelAllPendingOps() { + AssertIsOnBackgroundThread(); + + auto pendingOps = std::move(mPendingOps); + + for (auto& op : pendingOps) { + op->Cancel(); + } +} + +void RemoteWorkerController::Shutdown() { + AssertIsOnBackgroundThread(); + Unused << NS_WARN_IF(mIsServiceWorker && !mPendingOps.IsEmpty()); + + if (mState == eTerminated) { + MOZ_ASSERT(mPendingOps.IsEmpty()); + return; + } + + mState = eTerminated; + + CancelAllPendingOps(); + + if (!mActor) { + return; + } + + mActor->SetController(nullptr); + + /** + * The "non-remote-side" of the Service Worker will have ensured that the + * remote worker is terminated before calling `Shutdown().` + */ + if (mIsServiceWorker) { + mActor->MaybeSendDelete(); + } else { + Unused << mActor->SendExecOp(RemoteWorkerTerminateOp()); + } + + mActor = nullptr; +} + +void RemoteWorkerController::NoteDeadWorker() { + AssertIsOnBackgroundThread(); + + CancelAllPendingOps(); + + /** + * The "non-remote-side" of the Service Worker will initiate `Shutdown()` + * once it's notified that all dispatched operations have either completed + * or canceled. That is, it'll explicitly call `Shutdown()` later. + */ + if (!mIsServiceWorker) { + Shutdown(); + } +} + +template <typename... Args> +void RemoteWorkerController::MaybeStartSharedWorkerOp(Args&&... aArgs) { + AssertIsOnBackgroundThread(); + MOZ_ASSERT(!mIsServiceWorker); + + UniquePtr<PendingSharedWorkerOp> op = + MakeUnique<PendingSharedWorkerOp>(std::forward<Args>(aArgs)...); + + if (!op->MaybeStart(this)) { + mPendingOps.AppendElement(std::move(op)); + } +} + +void RemoteWorkerController::AddWindowID(uint64_t aWindowID) { + AssertIsOnBackgroundThread(); + MOZ_ASSERT(aWindowID); + + MaybeStartSharedWorkerOp(PendingSharedWorkerOp::eAddWindowID, aWindowID); +} + +void RemoteWorkerController::RemoveWindowID(uint64_t aWindowID) { + AssertIsOnBackgroundThread(); + MOZ_ASSERT(aWindowID); + + MaybeStartSharedWorkerOp(PendingSharedWorkerOp::eRemoveWindowID, aWindowID); +} + +void RemoteWorkerController::AddPortIdentifier( + const MessagePortIdentifier& aPortIdentifier) { + AssertIsOnBackgroundThread(); + + MaybeStartSharedWorkerOp(aPortIdentifier); +} + +void RemoteWorkerController::Terminate() { + AssertIsOnBackgroundThread(); + + MaybeStartSharedWorkerOp(PendingSharedWorkerOp::eTerminate); +} + +void RemoteWorkerController::Suspend() { + AssertIsOnBackgroundThread(); + + MaybeStartSharedWorkerOp(PendingSharedWorkerOp::eSuspend); +} + +void RemoteWorkerController::Resume() { + AssertIsOnBackgroundThread(); + + MaybeStartSharedWorkerOp(PendingSharedWorkerOp::eResume); +} + +void RemoteWorkerController::Freeze() { + AssertIsOnBackgroundThread(); + + MaybeStartSharedWorkerOp(PendingSharedWorkerOp::eFreeze); +} + +void RemoteWorkerController::Thaw() { + AssertIsOnBackgroundThread(); + + MaybeStartSharedWorkerOp(PendingSharedWorkerOp::eThaw); +} + +RefPtr<ServiceWorkerOpPromise> RemoteWorkerController::ExecServiceWorkerOp( + ServiceWorkerOpArgs&& aArgs) { + AssertIsOnBackgroundThread(); + MOZ_ASSERT(mIsServiceWorker); + + RefPtr<ServiceWorkerOpPromise::Private> promise = + new ServiceWorkerOpPromise::Private(__func__); + + UniquePtr<PendingServiceWorkerOp> op = + MakeUnique<PendingServiceWorkerOp>(std::move(aArgs), promise); + + if (!op->MaybeStart(this)) { + mPendingOps.AppendElement(std::move(op)); + } + + return promise; +} + +RefPtr<ServiceWorkerFetchEventOpPromise> +RemoteWorkerController::ExecServiceWorkerFetchEventOp( + const ParentToParentServiceWorkerFetchEventOpArgs& aArgs, + RefPtr<FetchEventOpParent> aReal) { + AssertIsOnBackgroundThread(); + MOZ_ASSERT(mIsServiceWorker); + + RefPtr<ServiceWorkerFetchEventOpPromise::Private> promise = + new ServiceWorkerFetchEventOpPromise::Private(__func__); + + UniquePtr<PendingSWFetchEventOp> op = + MakeUnique<PendingSWFetchEventOp>(aArgs, promise, std::move(aReal)); + + if (!op->MaybeStart(this)) { + mPendingOps.AppendElement(std::move(op)); + } + + return promise; +} + +RefPtr<GenericPromise> RemoteWorkerController::SetServiceWorkerSkipWaitingFlag() + const { + AssertIsOnBackgroundThread(); + MOZ_ASSERT(mObserver); + + RefPtr<GenericPromise::Private> promise = + new GenericPromise::Private(__func__); + + static_cast<RemoteWorkerControllerParent*>(mObserver.get()) + ->MaybeSendSetServiceWorkerSkipWaitingFlag( + [promise](bool aOk) { promise->Resolve(aOk, __func__); }); + + return promise; +} + +bool RemoteWorkerController::IsTerminated() const { + return mState == eTerminated; +} + +RemoteWorkerController::PendingSharedWorkerOp::PendingSharedWorkerOp( + Type aType, uint64_t aWindowID) + : mType(aType), mWindowID(aWindowID) { + AssertIsOnBackgroundThread(); +} + +RemoteWorkerController::PendingSharedWorkerOp::PendingSharedWorkerOp( + const MessagePortIdentifier& aPortIdentifier) + : mType(ePortIdentifier), mPortIdentifier(aPortIdentifier) { + AssertIsOnBackgroundThread(); +} + +RemoteWorkerController::PendingSharedWorkerOp::~PendingSharedWorkerOp() { + AssertIsOnBackgroundThread(); + MOZ_DIAGNOSTIC_ASSERT(mCompleted); +} + +bool RemoteWorkerController::PendingSharedWorkerOp::MaybeStart( + RemoteWorkerController* const aOwner) { + AssertIsOnBackgroundThread(); + MOZ_ASSERT(!mCompleted); + MOZ_ASSERT(aOwner); + + if (aOwner->mState == RemoteWorkerController::eTerminated) { + Cancel(); + return true; + } + + if (aOwner->mState == RemoteWorkerController::ePending && + mType != eTerminate) { + return false; + } + + switch (mType) { + case eTerminate: + aOwner->Shutdown(); + break; + case eSuspend: + Unused << aOwner->mActor->SendExecOp(RemoteWorkerSuspendOp()); + break; + case eResume: + Unused << aOwner->mActor->SendExecOp(RemoteWorkerResumeOp()); + break; + case eFreeze: + Unused << aOwner->mActor->SendExecOp(RemoteWorkerFreezeOp()); + break; + case eThaw: + Unused << aOwner->mActor->SendExecOp(RemoteWorkerThawOp()); + break; + case ePortIdentifier: + Unused << aOwner->mActor->SendExecOp( + RemoteWorkerPortIdentifierOp(mPortIdentifier)); + break; + case eAddWindowID: + Unused << aOwner->mActor->SendExecOp( + RemoteWorkerAddWindowIDOp(mWindowID)); + break; + case eRemoveWindowID: + Unused << aOwner->mActor->SendExecOp( + RemoteWorkerRemoveWindowIDOp(mWindowID)); + break; + default: + MOZ_CRASH("Unknown op."); + } + + mCompleted = true; + + return true; +} + +void RemoteWorkerController::PendingSharedWorkerOp::Cancel() { + AssertIsOnBackgroundThread(); + MOZ_ASSERT(!mCompleted); + + // We don't want to leak the port if the operation has not been processed. + if (mType == ePortIdentifier) { + MessagePortParent::ForceClose(mPortIdentifier.uuid(), + mPortIdentifier.destinationUuid(), + mPortIdentifier.sequenceId()); + } + + mCompleted = true; +} + +RemoteWorkerController::PendingServiceWorkerOp::PendingServiceWorkerOp( + ServiceWorkerOpArgs&& aArgs, + RefPtr<ServiceWorkerOpPromise::Private> aPromise) + : mArgs(std::move(aArgs)), mPromise(std::move(aPromise)) { + AssertIsOnBackgroundThread(); + MOZ_ASSERT(mPromise); +} + +RemoteWorkerController::PendingServiceWorkerOp::~PendingServiceWorkerOp() { + AssertIsOnBackgroundThread(); + MOZ_DIAGNOSTIC_ASSERT(!mPromise); +} + +bool RemoteWorkerController::PendingServiceWorkerOp::MaybeStart( + RemoteWorkerController* const aOwner) { + AssertIsOnBackgroundThread(); + MOZ_ASSERT(mPromise); + MOZ_ASSERT(aOwner); + + if (NS_WARN_IF(aOwner->mState == RemoteWorkerController::eTerminated)) { + mPromise->Reject(NS_ERROR_DOM_ABORT_ERR, __func__); + mPromise = nullptr; + return true; + } + + // The target content process must still be starting up. + if (!aOwner->mActor) { + // We can avoid starting the worker at all if we know it should be + // terminated. + MOZ_ASSERT(aOwner->mState == RemoteWorkerController::ePending); + if (mArgs.type() == + ServiceWorkerOpArgs::TServiceWorkerTerminateWorkerOpArgs) { + aOwner->CancelAllPendingOps(); + Cancel(); + + aOwner->mState = RemoteWorkerController::eTerminated; + + return true; + } + + return false; + } + + /** + * Allow termination operations to pass through while pending because the + * remote Service Worker can be terminated while still starting up. + */ + if (aOwner->mState == RemoteWorkerController::ePending && + mArgs.type() != + ServiceWorkerOpArgs::TServiceWorkerTerminateWorkerOpArgs) { + return false; + } + + MaybeReportServiceWorkerShutdownProgress(mArgs); + + aOwner->mActor->SendExecServiceWorkerOp(mArgs)->Then( + GetCurrentSerialEventTarget(), __func__, + [promise = std::move(mPromise)]( + PRemoteWorkerParent::ExecServiceWorkerOpPromise:: + ResolveOrRejectValue&& aResult) { + if (NS_WARN_IF(aResult.IsReject())) { + promise->Reject(NS_ERROR_DOM_ABORT_ERR, __func__); + return; + } + + promise->Resolve(std::move(aResult.ResolveValue()), __func__); + }); + + return true; +} + +void RemoteWorkerController::PendingServiceWorkerOp::Cancel() { + AssertIsOnBackgroundThread(); + MOZ_ASSERT(mPromise); + + mPromise->Reject(NS_ERROR_DOM_ABORT_ERR, __func__); + mPromise = nullptr; +} + +RemoteWorkerController::PendingSWFetchEventOp::PendingSWFetchEventOp( + const ParentToParentServiceWorkerFetchEventOpArgs& aArgs, + RefPtr<ServiceWorkerFetchEventOpPromise::Private> aPromise, + RefPtr<FetchEventOpParent>&& aReal) + : mArgs(aArgs), mPromise(std::move(aPromise)), mReal(aReal) { + AssertIsOnBackgroundThread(); + MOZ_ASSERT(mPromise); + + // If there is a TParentToParentStream in the request body, we need to + // save it to our stream. + IPCInternalRequest& req = mArgs.common().internalRequest(); + if (req.body().isSome() && + req.body().ref().type() == BodyStreamVariant::TParentToParentStream) { + nsCOMPtr<nsIInputStream> stream; + auto streamLength = req.bodySize(); + const auto& uuid = req.body().ref().get_ParentToParentStream().uuid(); + + auto storage = RemoteLazyInputStreamStorage::Get().unwrapOr(nullptr); + MOZ_DIAGNOSTIC_ASSERT(storage); + storage->GetStream(uuid, 0, streamLength, getter_AddRefs(mBodyStream)); + storage->ForgetStream(uuid); + + MOZ_DIAGNOSTIC_ASSERT(mBodyStream); + + req.body() = Nothing(); + } +} + +RemoteWorkerController::PendingSWFetchEventOp::~PendingSWFetchEventOp() { + AssertIsOnBackgroundThread(); + MOZ_DIAGNOSTIC_ASSERT(!mPromise); +} + +bool RemoteWorkerController::PendingSWFetchEventOp::MaybeStart( + RemoteWorkerController* const aOwner) { + AssertIsOnBackgroundThread(); + MOZ_ASSERT(mPromise); + MOZ_ASSERT(aOwner); + + if (NS_WARN_IF(aOwner->mState == RemoteWorkerController::eTerminated)) { + mPromise->Reject(NS_ERROR_DOM_ABORT_ERR, __func__); + mPromise = nullptr; + // Because the worker has transitioned to terminated, this operation is moot + // and so we should return true because there's no need to queue it. + return true; + } + + // The target content process must still be starting up. + if (!aOwner->mActor) { + MOZ_ASSERT(aOwner->mState == RemoteWorkerController::ePending); + return false; + } + + // At this point we are handing off responsibility for the promise to the + // actor. + FetchEventOpProxyParent::Create(aOwner->mActor.get(), std::move(mPromise), + mArgs, std::move(mReal), + std::move(mBodyStream)); + + return true; +} + +void RemoteWorkerController::PendingSWFetchEventOp::Cancel() { + AssertIsOnBackgroundThread(); + MOZ_ASSERT(mPromise); + + if (mPromise) { + mPromise->Reject(NS_ERROR_DOM_ABORT_ERR, __func__); + mPromise = nullptr; + } +} + +} // namespace dom +} // namespace mozilla diff --git a/dom/workers/remoteworkers/RemoteWorkerController.h b/dom/workers/remoteworkers/RemoteWorkerController.h new file mode 100644 index 0000000000..0b15364e7a --- /dev/null +++ b/dom/workers/remoteworkers/RemoteWorkerController.h @@ -0,0 +1,319 @@ +/* -*- 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_RemoteWorkerController_h +#define mozilla_dom_RemoteWorkerController_h + +#include "nsISupportsImpl.h" +#include "nsTArray.h" + +#include "mozilla/RefPtr.h" +#include "mozilla/UniquePtr.h" +#include "mozilla/dom/DOMTypes.h" +#include "mozilla/dom/ServiceWorkerOpArgs.h" +#include "mozilla/dom/ServiceWorkerOpPromise.h" + +namespace mozilla::dom { + +/* Here's a graph about this remote workers are spawned. + * + * _________________________________ | ________________________________ + * | | | | | + * | Parent process | IPC | Creation of Process X | + * | PBackground thread | | | | + * | | | | [RemoteWorkerService::Init()] | + * | | | | | | + * | | | | | (1) | + * | [RemoteWorkerManager:: (2) | | | V | + * | RegisterActor()]<-------- [new RemoteWorkerServiceChild] | + * | | | | | + * | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | | |________________________________| + * | | | + * | new SharedWorker/ServiceWorker | | + * | | ^ | IPC + * | (3) | (4)| | + * | V | | | + * | [RemoteWorkerController:: | | + * | | Create(data)] | | + * | | (5) | | + * | V | | + * | [RemoteWorkerManager::Launch()] | | + * | | | IPC _____________________________ + * | | (6) | | | | + * | | | | Selected content process | + * | V | (7) | | + * | [SendPRemoteWorkerConstructor()]--------->[new RemoteWorkerChild()] | + * | | | | | | | + * | | (8) | | | | | + * | V | | | V | + * | [RemoteWorkerController-> | | | RemoteWorkerChild->Exec() | + * | | SetControllerActor()] | | |_____________________________| + * | (9) | | IPC + * | V | | + * | [RemoteWorkerObserver-> | | + * | CreationCompleted()] | | + * |_________________________________| | + * | + * + * 1. When a new process starts, it creates a RemoteWorkerService singleton. + * This service creates a new thread (Worker Launcher) and from there, it + * starts a PBackground RemoteWorkerServiceChild actor. + * 2. On the parent process, PBackground thread, RemoteWorkerServiceParent + * actors are registered into the RemoteWorkerManager service. + * + * 3. At some point, a SharedWorker or a ServiceWorker must be executed. + * RemoteWorkerController::Create() is used to start the launching. This + * method must be called on the parent process, on the PBackground thread. + * 4. RemoteWorkerController object is immediately returned to the caller. Any + * operation done with this controller object will be stored in a queue, + * until the launching is correctly executed. + * 5. RemoteWorkerManager has the list of active RemoteWorkerServiceParent + * actors. From them, it picks one. + * In case we don't have any content process to select, a new one is + * spawned. If this happens, the operation is suspended until a new + * RemoteWorkerServiceParent is registered. + * 6. RemoteWorkerServiceParent is used to create a RemoteWorkerParent. + * 7. RemoteWorkerChild is created on a selected process and it executes the + * WorkerPrivate. + * 8. The RemoteWorkerParent actor is passed to the RemoteWorkerController. + * 9. RemoteWorkerController now is ready to continue and it called + * RemoteWorkerObserver to inform that the operation is completed. + * In case there were pending operations, they are now executed. + */ + +class ErrorValue; +class FetchEventOpParent; +class RemoteWorkerControllerParent; +class RemoteWorkerData; +class RemoteWorkerManager; +class RemoteWorkerParent; + +class RemoteWorkerObserver { + public: + NS_INLINE_DECL_PURE_VIRTUAL_REFCOUNTING + + virtual void CreationFailed() = 0; + + virtual void CreationSucceeded() = 0; + + virtual void ErrorReceived(const ErrorValue& aValue) = 0; + + virtual void LockNotified(bool aCreated) = 0; + + virtual void Terminated() = 0; +}; + +/** + * PBackground instance created by static RemoteWorkerController::Create that + * builds on RemoteWorkerManager. Interface to control the remote worker as well + * as receive events via the RemoteWorkerObserver interface that the owner + * (SharedWorkerManager in this case) must implement to hear about errors, + * termination, and whether the initial spawning succeeded/failed. + * + * Its methods may be called immediately after creation even though the worker + * is created asynchronously; an internal operation queue makes this work. + * Communicates with the remote worker via owned RemoteWorkerParent over + * PRemoteWorker protocol. + */ +class RemoteWorkerController final { + friend class RemoteWorkerControllerParent; + friend class RemoteWorkerManager; + friend class RemoteWorkerParent; + + public: + NS_INLINE_DECL_REFCOUNTING(RemoteWorkerController) + + static already_AddRefed<RemoteWorkerController> Create( + const RemoteWorkerData& aData, RemoteWorkerObserver* aObserver, + base::ProcessId = 0); + + void AddWindowID(uint64_t aWindowID); + + void RemoveWindowID(uint64_t aWindowID); + + void AddPortIdentifier(const MessagePortIdentifier& aPortIdentifier); + + void Terminate(); + + void Suspend(); + + void Resume(); + + void Freeze(); + + void Thaw(); + + RefPtr<ServiceWorkerOpPromise> ExecServiceWorkerOp( + ServiceWorkerOpArgs&& aArgs); + + RefPtr<ServiceWorkerFetchEventOpPromise> ExecServiceWorkerFetchEventOp( + const ParentToParentServiceWorkerFetchEventOpArgs& aArgs, + RefPtr<FetchEventOpParent> aReal); + + RefPtr<GenericPromise> SetServiceWorkerSkipWaitingFlag() const; + + bool IsTerminated() const; + + private: + RemoteWorkerController(const RemoteWorkerData& aData, + RemoteWorkerObserver* aObserver); + + ~RemoteWorkerController(); + + void SetWorkerActor(RemoteWorkerParent* aActor); + + void NoteDeadWorkerActor(); + + void ErrorPropagation(const ErrorValue& aValue); + + void NotifyLock(bool aCreated); + + void WorkerTerminated(); + + void Shutdown(); + + void CreationFailed(); + + void CreationSucceeded(); + + void CancelAllPendingOps(); + + template <typename... Args> + void MaybeStartSharedWorkerOp(Args&&... aArgs); + + void NoteDeadWorker(); + + RefPtr<RemoteWorkerObserver> mObserver; + RefPtr<RemoteWorkerParent> mActor; + + enum { + ePending, + eReady, + eTerminated, + } mState; + + const bool mIsServiceWorker; + + /** + * `PendingOp` is responsible for encapsulating logic for starting and + * canceling pending remote worker operations, as this logic may vary + * depending on the type of the remote worker and the type of the operation. + */ + class PendingOp { + public: + PendingOp() = default; + + PendingOp(const PendingOp&) = delete; + + PendingOp& operator=(const PendingOp&) = delete; + + virtual ~PendingOp() = default; + + /** + * Returns `true` if execution has started or the operation is moot and + * doesn't need to be queued, `false` if execution hasn't started and the + * operation should be queued. In general, operations should only return + * false when a remote worker is first starting up. Operations may also + * somewhat non-intuitively return true without doing anything if the worker + * has already been told to shutdown. + * + * Starting execution may depend the state of `aOwner.` + */ + virtual bool MaybeStart(RemoteWorkerController* const aOwner) = 0; + + /** + * Invoked if the operation will never have MaybeStart() called again + * because the RemoteWorkerController has terminated (or will never start). + * This should be used by PendingOps to clean up any resources they own and + * may also be called internally by their MaybeStart() methods if they + * determine the worker has been terminated. This should be idempotent. + */ + virtual void Cancel() = 0; + }; + + class PendingSharedWorkerOp final : public PendingOp { + public: + enum Type { + eTerminate, + eSuspend, + eResume, + eFreeze, + eThaw, + ePortIdentifier, + eAddWindowID, + eRemoveWindowID, + }; + + explicit PendingSharedWorkerOp(Type aType, uint64_t aWindowID = 0); + + explicit PendingSharedWorkerOp( + const MessagePortIdentifier& aPortIdentifier); + + ~PendingSharedWorkerOp(); + + bool MaybeStart(RemoteWorkerController* const aOwner) override; + + void Cancel() override; + + private: + const Type mType; + const MessagePortIdentifier mPortIdentifier; + const uint64_t mWindowID = 0; + bool mCompleted = false; + }; + + class PendingServiceWorkerOp final : public PendingOp { + public: + PendingServiceWorkerOp(ServiceWorkerOpArgs&& aArgs, + RefPtr<ServiceWorkerOpPromise::Private> aPromise); + + ~PendingServiceWorkerOp(); + + bool MaybeStart(RemoteWorkerController* const aOwner) override; + + void Cancel() override; + + private: + ServiceWorkerOpArgs mArgs; + RefPtr<ServiceWorkerOpPromise::Private> mPromise; + }; + + /** + * Custom pending op type to deal with the complexities of FetchEvents having + * their own actor. + * + * FetchEvent Ops have their own actor type because their lifecycle is more + * complex than IPDL's async return value mechanism allows. Additionally, + * its IPC struct potentially has to serialize RemoteLazyStreams which + * requires us to hold an nsIInputStream when at rest and serialize it when + * eventually sending. + */ + class PendingSWFetchEventOp final : public PendingOp { + public: + PendingSWFetchEventOp( + const ParentToParentServiceWorkerFetchEventOpArgs& aArgs, + RefPtr<ServiceWorkerFetchEventOpPromise::Private> aPromise, + RefPtr<FetchEventOpParent>&& aReal); + + ~PendingSWFetchEventOp(); + + bool MaybeStart(RemoteWorkerController* const aOwner) override; + + void Cancel() override; + + private: + ParentToParentServiceWorkerFetchEventOpArgs mArgs; + RefPtr<ServiceWorkerFetchEventOpPromise::Private> mPromise; + RefPtr<FetchEventOpParent> mReal; + nsCOMPtr<nsIInputStream> mBodyStream; + }; + + nsTArray<UniquePtr<PendingOp>> mPendingOps; +}; + +} // namespace mozilla::dom + +#endif // mozilla_dom_RemoteWorkerController_h diff --git a/dom/workers/remoteworkers/RemoteWorkerControllerChild.cpp b/dom/workers/remoteworkers/RemoteWorkerControllerChild.cpp new file mode 100644 index 0000000000..1b7f159fa5 --- /dev/null +++ b/dom/workers/remoteworkers/RemoteWorkerControllerChild.cpp @@ -0,0 +1,149 @@ +/* -*- 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 "RemoteWorkerControllerChild.h" + +#include <utility> + +#include "MainThreadUtils.h" +#include "nsError.h" +#include "nsThreadUtils.h" + +#include "ServiceWorkerPrivate.h" +#include "mozilla/Assertions.h" +#include "mozilla/RefPtr.h" +#include "mozilla/Unused.h" +#include "mozilla/dom/PFetchEventOpChild.h" + +namespace mozilla { + +using ipc::IPCResult; + +namespace dom { + +RemoteWorkerControllerChild::RemoteWorkerControllerChild( + RefPtr<RemoteWorkerObserver> aObserver) + : mObserver(std::move(aObserver)) { + AssertIsOnMainThread(); + MOZ_ASSERT(mObserver); +} + +PFetchEventOpChild* RemoteWorkerControllerChild::AllocPFetchEventOpChild( + const ParentToParentServiceWorkerFetchEventOpArgs& aArgs) { + MOZ_CRASH("PFetchEventOpChild actors must be manually constructed!"); + return nullptr; +} + +bool RemoteWorkerControllerChild::DeallocPFetchEventOpChild( + PFetchEventOpChild* aActor) { + AssertIsOnMainThread(); + MOZ_ASSERT(aActor); + + delete aActor; + return true; +} + +void RemoteWorkerControllerChild::ActorDestroy(ActorDestroyReason aReason) { + AssertIsOnMainThread(); + + mIPCActive = false; + + if (NS_WARN_IF(mObserver)) { + mObserver->ErrorReceived(NS_ERROR_DOM_ABORT_ERR); + } +} + +IPCResult RemoteWorkerControllerChild::RecvCreationFailed() { + AssertIsOnMainThread(); + + if (mObserver) { + mObserver->CreationFailed(); + } + + return IPC_OK(); +} + +IPCResult RemoteWorkerControllerChild::RecvCreationSucceeded() { + AssertIsOnMainThread(); + + if (mObserver) { + mObserver->CreationSucceeded(); + } + + return IPC_OK(); +} + +IPCResult RemoteWorkerControllerChild::RecvErrorReceived( + const ErrorValue& aError) { + AssertIsOnMainThread(); + + if (mObserver) { + mObserver->ErrorReceived(aError); + } + + return IPC_OK(); +} + +IPCResult RemoteWorkerControllerChild::RecvTerminated() { + AssertIsOnMainThread(); + + if (mObserver) { + mObserver->Terminated(); + } + + return IPC_OK(); +} + +IPCResult RemoteWorkerControllerChild::RecvSetServiceWorkerSkipWaitingFlag( + SetServiceWorkerSkipWaitingFlagResolver&& aResolve) { + AssertIsOnMainThread(); + + if (mObserver) { + static_cast<ServiceWorkerPrivate*>(mObserver.get()) + ->SetSkipWaitingFlag() + ->Then(GetCurrentSerialEventTarget(), __func__, + [resolve = std::move(aResolve)]( + const GenericPromise::ResolveOrRejectValue& aResult) { + resolve(aResult.IsResolve() ? aResult.ResolveValue() : false); + }); + + return IPC_OK(); + } + + aResolve(false); + + return IPC_OK(); +} + +void RemoteWorkerControllerChild::RevokeObserver( + RemoteWorkerObserver* aObserver) { + AssertIsOnMainThread(); + MOZ_ASSERT(aObserver); + MOZ_ASSERT(aObserver == mObserver); + + mObserver = nullptr; +} + +void RemoteWorkerControllerChild::MaybeSendDelete() { + AssertIsOnMainThread(); + + if (!mIPCActive) { + return; + } + + RefPtr<RemoteWorkerControllerChild> self = this; + + SendShutdown()->Then( + GetCurrentSerialEventTarget(), __func__, + [self = std::move(self)](const ShutdownPromise::ResolveOrRejectValue&) { + if (self->mIPCActive) { + Unused << self->Send__delete__(self); + } + }); +} + +} // namespace dom +} // namespace mozilla diff --git a/dom/workers/remoteworkers/RemoteWorkerControllerChild.h b/dom/workers/remoteworkers/RemoteWorkerControllerChild.h new file mode 100644 index 0000000000..049d67ad56 --- /dev/null +++ b/dom/workers/remoteworkers/RemoteWorkerControllerChild.h @@ -0,0 +1,64 @@ +/* -*- 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_remoteworkercontrollerchild_h__ +#define mozilla_dom_remoteworkercontrollerchild_h__ + +#include "nsISupportsImpl.h" + +#include "RemoteWorkerController.h" +#include "mozilla/RefPtr.h" +#include "mozilla/dom/PRemoteWorkerControllerChild.h" + +namespace mozilla::dom { + +/** + * Parent-process main-thread proxy used by ServiceWorkerManager to control + * RemoteWorkerController instances on the parent-process PBackground thread. + */ +class RemoteWorkerControllerChild final : public PRemoteWorkerControllerChild { + friend class PRemoteWorkerControllerChild; + + public: + NS_INLINE_DECL_REFCOUNTING(RemoteWorkerControllerChild) + + explicit RemoteWorkerControllerChild(RefPtr<RemoteWorkerObserver> aObserver); + + void Initialize(); + + void RevokeObserver(RemoteWorkerObserver* aObserver); + + void MaybeSendDelete(); + + private: + ~RemoteWorkerControllerChild() = default; + + PFetchEventOpChild* AllocPFetchEventOpChild( + const ParentToParentServiceWorkerFetchEventOpArgs& aArgs); + + bool DeallocPFetchEventOpChild(PFetchEventOpChild* aActor); + + void ActorDestroy(ActorDestroyReason aReason) override; + + mozilla::ipc::IPCResult RecvCreationFailed(); + + mozilla::ipc::IPCResult RecvCreationSucceeded(); + + mozilla::ipc::IPCResult RecvErrorReceived(const ErrorValue& aError); + + mozilla::ipc::IPCResult RecvTerminated(); + + mozilla::ipc::IPCResult RecvSetServiceWorkerSkipWaitingFlag( + SetServiceWorkerSkipWaitingFlagResolver&& aResolve); + + RefPtr<RemoteWorkerObserver> mObserver; + + bool mIPCActive = true; +}; + +} // namespace mozilla::dom + +#endif // mozilla_dom_remoteworkercontrollerchild_h__ diff --git a/dom/workers/remoteworkers/RemoteWorkerControllerParent.cpp b/dom/workers/remoteworkers/RemoteWorkerControllerParent.cpp new file mode 100644 index 0000000000..7ac901f655 --- /dev/null +++ b/dom/workers/remoteworkers/RemoteWorkerControllerParent.cpp @@ -0,0 +1,215 @@ +/* -*- 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 "RemoteWorkerControllerParent.h" + +#include <utility> + +#include "nsCOMPtr.h" +#include "nsDebug.h" +#include "nsError.h" +#include "nsThreadUtils.h" + +#include "mozilla/Assertions.h" +#include "mozilla/Unused.h" +#include "mozilla/dom/FetchEventOpParent.h" +#include "mozilla/dom/RemoteWorkerParent.h" +#include "mozilla/dom/ServiceWorkerOpPromise.h" +#include "mozilla/ipc/BackgroundParent.h" + +namespace mozilla { + +using namespace ipc; + +namespace dom { + +RemoteWorkerControllerParent::RemoteWorkerControllerParent( + const RemoteWorkerData& aRemoteWorkerData) + : mRemoteWorkerController(RemoteWorkerController::Create( + aRemoteWorkerData, this, 0 /* random process ID */)) { + AssertIsInMainProcess(); + AssertIsOnBackgroundThread(); + MOZ_ASSERT(mRemoteWorkerController); +} + +RefPtr<RemoteWorkerParent> RemoteWorkerControllerParent::GetRemoteWorkerParent() + const { + AssertIsOnBackgroundThread(); + MOZ_ASSERT(mRemoteWorkerController); + + return mRemoteWorkerController->mActor; +} + +void RemoteWorkerControllerParent::MaybeSendSetServiceWorkerSkipWaitingFlag( + std::function<void(bool)>&& aCallback) { + AssertIsOnBackgroundThread(); + MOZ_ASSERT(aCallback); + + if (!mIPCActive) { + aCallback(false); + return; + } + + SendSetServiceWorkerSkipWaitingFlag()->Then( + GetCurrentSerialEventTarget(), __func__, + [callback = std::move(aCallback)]( + const SetServiceWorkerSkipWaitingFlagPromise::ResolveOrRejectValue& + aResult) { + callback(aResult.IsResolve() ? aResult.ResolveValue() : false); + }); +} + +RemoteWorkerControllerParent::~RemoteWorkerControllerParent() { + AssertIsOnBackgroundThread(); + MOZ_ASSERT(!mIPCActive); + MOZ_ASSERT(!mRemoteWorkerController); +} + +PFetchEventOpParent* RemoteWorkerControllerParent::AllocPFetchEventOpParent( + const ParentToParentServiceWorkerFetchEventOpArgs& aArgs) { + AssertIsOnBackgroundThread(); + + RefPtr<FetchEventOpParent> actor = new FetchEventOpParent(); + return actor.forget().take(); +} + +IPCResult RemoteWorkerControllerParent::RecvPFetchEventOpConstructor( + PFetchEventOpParent* aActor, + const ParentToParentServiceWorkerFetchEventOpArgs& aArgs) { + AssertIsOnBackgroundThread(); + MOZ_ASSERT(aActor); + + RefPtr<FetchEventOpParent> realFetchOp = + static_cast<FetchEventOpParent*>(aActor); + mRemoteWorkerController->ExecServiceWorkerFetchEventOp(aArgs, realFetchOp) + ->Then( + GetCurrentSerialEventTarget(), __func__, + [fetchOp = std::move(realFetchOp)]( + ServiceWorkerFetchEventOpPromise::ResolveOrRejectValue&& + aResult) { + if (NS_WARN_IF(aResult.IsReject())) { + MOZ_ASSERT(NS_FAILED(aResult.RejectValue())); + Unused << fetchOp->Send__delete__(fetchOp, aResult.RejectValue()); + return; + } + + Unused << fetchOp->Send__delete__(fetchOp, aResult.ResolveValue()); + }); + + return IPC_OK(); +} + +bool RemoteWorkerControllerParent::DeallocPFetchEventOpParent( + PFetchEventOpParent* aActor) { + AssertIsOnBackgroundThread(); + MOZ_ASSERT(aActor); + + RefPtr<FetchEventOpParent> actor = + dont_AddRef(static_cast<FetchEventOpParent*>(aActor)); + return true; +} + +IPCResult RemoteWorkerControllerParent::RecvExecServiceWorkerOp( + ServiceWorkerOpArgs&& aArgs, ExecServiceWorkerOpResolver&& aResolve) { + AssertIsOnBackgroundThread(); + MOZ_ASSERT(mIPCActive); + MOZ_ASSERT(mRemoteWorkerController); + + mRemoteWorkerController->ExecServiceWorkerOp(std::move(aArgs)) + ->Then(GetCurrentSerialEventTarget(), __func__, + [resolve = std::move(aResolve)]( + ServiceWorkerOpPromise::ResolveOrRejectValue&& aResult) { + if (NS_WARN_IF(aResult.IsReject())) { + MOZ_ASSERT(NS_FAILED(aResult.RejectValue())); + resolve(aResult.RejectValue()); + return; + } + + resolve(aResult.ResolveValue()); + }); + + return IPC_OK(); +} + +IPCResult RemoteWorkerControllerParent::RecvShutdown( + ShutdownResolver&& aResolve) { + AssertIsOnBackgroundThread(); + MOZ_ASSERT(mIPCActive); + MOZ_ASSERT(mRemoteWorkerController); + + mIPCActive = false; + + mRemoteWorkerController->Shutdown(); + mRemoteWorkerController = nullptr; + + aResolve(true); + + return IPC_OK(); +} + +IPCResult RemoteWorkerControllerParent::Recv__delete__() { + AssertIsOnBackgroundThread(); + MOZ_ASSERT(!mIPCActive); + MOZ_ASSERT(!mRemoteWorkerController); + + return IPC_OK(); +} + +void RemoteWorkerControllerParent::ActorDestroy(ActorDestroyReason aReason) { + AssertIsOnBackgroundThread(); + + if (NS_WARN_IF(mIPCActive)) { + mIPCActive = false; + } + + if (NS_WARN_IF(mRemoteWorkerController)) { + mRemoteWorkerController->Shutdown(); + mRemoteWorkerController = nullptr; + } +} + +void RemoteWorkerControllerParent::CreationFailed() { + AssertIsOnBackgroundThread(); + + if (!mIPCActive) { + return; + } + + Unused << SendCreationFailed(); +} + +void RemoteWorkerControllerParent::CreationSucceeded() { + AssertIsOnBackgroundThread(); + + if (!mIPCActive) { + return; + } + + Unused << SendCreationSucceeded(); +} + +void RemoteWorkerControllerParent::ErrorReceived(const ErrorValue& aValue) { + AssertIsOnBackgroundThread(); + + if (!mIPCActive) { + return; + } + + Unused << SendErrorReceived(aValue); +} + +void RemoteWorkerControllerParent::Terminated() { + AssertIsOnBackgroundThread(); + + if (!mIPCActive) { + return; + } + + Unused << SendTerminated(); +} + +} // namespace dom +} // namespace mozilla diff --git a/dom/workers/remoteworkers/RemoteWorkerControllerParent.h b/dom/workers/remoteworkers/RemoteWorkerControllerParent.h new file mode 100644 index 0000000000..04b0c02096 --- /dev/null +++ b/dom/workers/remoteworkers/RemoteWorkerControllerParent.h @@ -0,0 +1,82 @@ +/* -*- 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_remoteworkercontrollerparent_h__ +#define mozilla_dom_remoteworkercontrollerparent_h__ + +#include <functional> + +#include "nsISupportsImpl.h" + +#include "RemoteWorkerController.h" +#include "mozilla/RefPtr.h" +#include "mozilla/dom/PRemoteWorkerControllerParent.h" + +namespace mozilla::dom { + +/** + * PBackground-resident proxy used by ServiceWorkerManager because canonical + * ServiceWorkerManager state exists on the parent process main thread but the + * RemoteWorkerController API is used from the parent process PBackground + * thread. + */ +class RemoteWorkerControllerParent final : public PRemoteWorkerControllerParent, + public RemoteWorkerObserver { + friend class PRemoteWorkerControllerParent; + + public: + NS_INLINE_DECL_REFCOUNTING(RemoteWorkerControllerParent, override) + + explicit RemoteWorkerControllerParent( + const RemoteWorkerData& aRemoteWorkerData); + + // Returns the corresponding RemoteWorkerParent (if any). + RefPtr<RemoteWorkerParent> GetRemoteWorkerParent() const; + + void MaybeSendSetServiceWorkerSkipWaitingFlag( + std::function<void(bool)>&& aCallback); + + private: + ~RemoteWorkerControllerParent(); + + PFetchEventOpParent* AllocPFetchEventOpParent( + const ParentToParentServiceWorkerFetchEventOpArgs& aArgs); + + mozilla::ipc::IPCResult RecvPFetchEventOpConstructor( + PFetchEventOpParent* aActor, + const ParentToParentServiceWorkerFetchEventOpArgs& aArgs) override; + + bool DeallocPFetchEventOpParent(PFetchEventOpParent* aActor); + + mozilla::ipc::IPCResult RecvExecServiceWorkerOp( + ServiceWorkerOpArgs&& aArgs, ExecServiceWorkerOpResolver&& aResolve); + + mozilla::ipc::IPCResult RecvShutdown(ShutdownResolver&& aResolve); + + mozilla::ipc::IPCResult Recv__delete__() override; + + void ActorDestroy(ActorDestroyReason aReason) override; + + void CreationFailed() override; + + void CreationSucceeded() override; + + void ErrorReceived(const ErrorValue& aValue) override; + + void LockNotified(bool aCreated) final { + // no-op for service workers + } + + void Terminated() override; + + RefPtr<RemoteWorkerController> mRemoteWorkerController; + + bool mIPCActive = true; +}; + +} // namespace mozilla::dom + +#endif // mozilla_dom_remoteworkercontrollerparent_h__ diff --git a/dom/workers/remoteworkers/RemoteWorkerManager.cpp b/dom/workers/remoteworkers/RemoteWorkerManager.cpp new file mode 100644 index 0000000000..1169a02d76 --- /dev/null +++ b/dom/workers/remoteworkers/RemoteWorkerManager.cpp @@ -0,0 +1,723 @@ +/* -*- 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 "RemoteWorkerManager.h" + +#include <utility> + +#include "mozilla/SchedulerGroup.h" +#include "mozilla/ScopeExit.h" +#include "mozilla/dom/ContentChild.h" // ContentChild::GetSingleton +#include "mozilla/dom/RemoteWorkerController.h" +#include "mozilla/dom/RemoteWorkerParent.h" +#include "mozilla/ipc/BackgroundParent.h" +#include "mozilla/ipc/BackgroundUtils.h" +#include "mozilla/ipc/PBackgroundParent.h" +#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED +# include "mozilla/dom/DOMException.h" +# include "mozilla/CycleCollectedJSContext.h" +# include "mozilla/Sprintf.h" // SprintfLiteral +# include "nsIXPConnect.h" // nsIXPConnectWrappedJS +#endif +#include "mozilla/StaticPrefs_extensions.h" +#include "nsCOMPtr.h" +#include "nsIE10SUtils.h" +#include "nsImportModule.h" +#include "nsIXULRuntime.h" +#include "nsTArray.h" +#include "nsThreadUtils.h" +#include "RemoteWorkerServiceParent.h" + +mozilla::LazyLogModule gRemoteWorkerManagerLog("RemoteWorkerManager"); + +#ifdef LOG +# undef LOG +#endif +#define LOG(fmt) \ + MOZ_LOG(gRemoteWorkerManagerLog, mozilla::LogLevel::Verbose, fmt) + +namespace mozilla { + +using namespace ipc; + +namespace dom { + +namespace { + +// Raw pointer because this object is kept alive by RemoteWorkerServiceParent +// actors. +RemoteWorkerManager* sRemoteWorkerManager; + +bool IsServiceWorker(const RemoteWorkerData& aData) { + return aData.serviceWorkerData().type() == + OptionalServiceWorkerData::TServiceWorkerData; +} + +void TransmitPermissionsAndBlobURLsForPrincipalInfo( + ContentParent* aContentParent, const PrincipalInfo& aPrincipalInfo) { + AssertIsOnMainThread(); + MOZ_ASSERT(aContentParent); + + auto principalOrErr = PrincipalInfoToPrincipal(aPrincipalInfo); + + if (NS_WARN_IF(principalOrErr.isErr())) { + return; + } + + nsCOMPtr<nsIPrincipal> principal = principalOrErr.unwrap(); + + aContentParent->TransmitBlobURLsForPrincipal(principal); + + MOZ_ALWAYS_SUCCEEDS( + aContentParent->TransmitPermissionsForPrincipal(principal)); +} + +} // namespace + +// static +bool RemoteWorkerManager::MatchRemoteType(const nsACString& processRemoteType, + const nsACString& workerRemoteType) { + LOG(("MatchRemoteType [processRemoteType=%s, workerRemoteType=%s]", + PromiseFlatCString(processRemoteType).get(), + PromiseFlatCString(workerRemoteType).get())); + + // Respecting COOP and COEP requires processing headers in the parent + // process in order to choose an appropriate content process, but the + // workers' ScriptLoader processes headers in content processes. An + // intermediary step that provides security guarantees is to simply never + // allow SharedWorkers and ServiceWorkers to exist in a COOP+COEP process. + // The ultimate goal is to allow these worker types to be put in such + // processes based on their script response headers. + // https://bugzilla.mozilla.org/show_bug.cgi?id=1595206 + // + // RemoteWorkerManager::GetRemoteType should not select this remoteType + // and so workerRemoteType is not expected to be set to a coop+coep + // remoteType and here we can just assert that it is not happening. + MOZ_ASSERT(!IsWebCoopCoepRemoteType(workerRemoteType)); + + return processRemoteType.Equals(workerRemoteType); +} + +// static +Result<nsCString, nsresult> RemoteWorkerManager::GetRemoteType( + const nsCOMPtr<nsIPrincipal>& aPrincipal, WorkerKind aWorkerKind) { + AssertIsOnMainThread(); + + MOZ_ASSERT_IF(aWorkerKind == WorkerKind::WorkerKindService, + aPrincipal->GetIsContentPrincipal()); + + nsCOMPtr<nsIE10SUtils> e10sUtils = do_ImportESModule( + "resource://gre/modules/E10SUtils.sys.mjs", "E10SUtils", fallible); + if (NS_WARN_IF(!e10sUtils)) { + LOG(("GetRemoteType Abort: could not import E10SUtils")); + return Err(NS_ERROR_DOM_ABORT_ERR); + } + + nsCString preferredRemoteType = DEFAULT_REMOTE_TYPE; + if (aWorkerKind == WorkerKind::WorkerKindShared) { + if (auto* contentChild = ContentChild::GetSingleton()) { + // For a shared worker set the preferred remote type to the content + // child process remote type. + preferredRemoteType = contentChild->GetRemoteType(); + } else if (aPrincipal->IsSystemPrincipal()) { + preferredRemoteType = NOT_REMOTE_TYPE; + } + } + + nsIE10SUtils::RemoteWorkerType workerType; + + switch (aWorkerKind) { + case WorkerKind::WorkerKindService: + workerType = nsIE10SUtils::REMOTE_WORKER_TYPE_SERVICE; + break; + case WorkerKind::WorkerKindShared: + workerType = nsIE10SUtils::REMOTE_WORKER_TYPE_SHARED; + break; + default: + // This method isn't expected to be called for worker types that + // aren't remote workers (currently Service and Shared workers). + LOG(("GetRemoteType Error on unexpected worker type")); + MOZ_DIAGNOSTIC_ASSERT(false, "Unexpected worker type"); + return Err(NS_ERROR_DOM_ABORT_ERR); + } + + // Here we do not have access to the window and so we can't use its + // useRemoteTabs and useRemoteSubframes flags (for the service + // worker there may not even be a window associated to the worker + // yet), and so we have to use the prefs instead. + bool isMultiprocess = BrowserTabsRemoteAutostart(); + bool isFission = FissionAutostart(); + + nsCString remoteType = NOT_REMOTE_TYPE; + + nsresult rv = e10sUtils->GetRemoteTypeForWorkerPrincipal( + aPrincipal, workerType, isMultiprocess, isFission, preferredRemoteType, + remoteType); + if (NS_WARN_IF(NS_FAILED(rv))) { + LOG( + ("GetRemoteType Abort: E10SUtils.getRemoteTypeForWorkerPrincipal " + "exception")); +#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED + nsCString principalTypeOrScheme; + if (aPrincipal->IsSystemPrincipal()) { + principalTypeOrScheme = "system"_ns; + } else if (aPrincipal->GetIsExpandedPrincipal()) { + principalTypeOrScheme = "expanded"_ns; + } else if (aPrincipal->GetIsNullPrincipal()) { + principalTypeOrScheme = "null"_ns; + } else { + nsCOMPtr<nsIURI> uri = aPrincipal->GetURI(); + nsresult rv2 = uri->GetScheme(principalTypeOrScheme); + if (NS_FAILED(rv2)) { + principalTypeOrScheme = "content"_ns; + } + } + + nsCString processRemoteType = "parent"_ns; + if (auto* contentChild = ContentChild::GetSingleton()) { + // RemoteTypePrefix make sure that we are not going to include + // the full origin that may be part of the current remote type. + processRemoteType = RemoteTypePrefix(contentChild->GetRemoteType()); + } + + // Convert the error code into an error name. + nsAutoCString errorName; + GetErrorName(rv, errorName); + + // Try to retrieve the line number from the exception. + nsAutoCString errorFilename("(unknown)"_ns); + uint32_t jsmErrorLineNumber = 0; + + if (auto* context = CycleCollectedJSContext::Get()) { + if (RefPtr<Exception> exn = context->GetPendingException()) { + nsAutoString filename(u"(unknown)"_ns); + + if (rv == NS_ERROR_XPC_JAVASCRIPT_ERROR_WITH_DETAILS) { + // When the failure is a Javascript Error, the line number retrieved + // from the Exception instance isn't going to be the E10SUtils.sys.mjs + // line that originated the failure, and so we fallback to retrieve it + // from the nsIScriptError. + nsCOMPtr<nsIScriptError> scriptError = + do_QueryInterface(exn->GetData()); + if (scriptError) { + scriptError->GetLineNumber(&jsmErrorLineNumber); + scriptError->GetSourceName(filename); + } + } else { + nsCOMPtr<nsIXPConnectWrappedJS> wrapped = + do_QueryInterface(e10sUtils); + dom::AutoJSAPI jsapi; + if (jsapi.Init(wrapped->GetJSObjectGlobal())) { + auto* cx = jsapi.cx(); + jsmErrorLineNumber = exn->LineNumber(cx); + exn->GetFilename(cx, filename); + } + } + + errorFilename = NS_ConvertUTF16toUTF8(filename); + } + } + + char buf[1024]; + SprintfLiteral( + buf, + "workerType=%s, principal=%s, preferredRemoteType=%s, " + "processRemoteType=%s, errorName=%s, errorLocation=%s:%d", + aWorkerKind == WorkerKind::WorkerKindService ? "service" : "shared", + principalTypeOrScheme.get(), + PromiseFlatCString(RemoteTypePrefix(preferredRemoteType)).get(), + processRemoteType.get(), errorName.get(), errorFilename.get(), + jsmErrorLineNumber); + MOZ_CRASH_UNSAFE_PRINTF( + "E10SUtils.getRemoteTypeForWorkerPrincipal did throw: %s", buf); +#endif + return Err(NS_ERROR_DOM_ABORT_ERR); + } + + if (MOZ_LOG_TEST(gRemoteWorkerManagerLog, LogLevel::Verbose)) { + nsCString principalOrigin; + aPrincipal->GetOrigin(principalOrigin); + + LOG( + ("GetRemoteType workerType=%s, principal=%s, " + "preferredRemoteType=%s, selectedRemoteType=%s", + aWorkerKind == WorkerKind::WorkerKindService ? "service" : "shared", + principalOrigin.get(), preferredRemoteType.get(), remoteType.get())); + } + + return remoteType; +} + +// static +bool RemoteWorkerManager::HasExtensionPrincipal(const RemoteWorkerData& aData) { + auto principalInfo = aData.principalInfo(); + return principalInfo.type() == PrincipalInfo::TContentPrincipalInfo && + // This helper method is also called from the background thread and so + // we can't check if the principal does have an addonPolicy object + // associated and we have to resort to check the url scheme instead. + StringBeginsWith(principalInfo.get_ContentPrincipalInfo().spec(), + "moz-extension://"_ns); +} + +// static +bool RemoteWorkerManager::IsRemoteTypeAllowed(const RemoteWorkerData& aData) { + AssertIsOnMainThread(); + + // If Gecko is running in single process mode, there is no child process + // to select and we have to just consider it valid (if it should haven't + // been launched it should have been already prevented before reaching + // a RemoteWorkerChild instance). + if (!BrowserTabsRemoteAutostart()) { + return true; + } + + const auto& principalInfo = aData.principalInfo(); + + auto* contentChild = ContentChild::GetSingleton(); + if (!contentChild) { + // If e10s isn't disabled, only workers related to the system principal + // should be allowed to run in the parent process, and extension principals + // if extensions.webextensions.remote is false. + return principalInfo.type() == PrincipalInfo::TSystemPrincipalInfo || + (!StaticPrefs::extensions_webextensions_remote() && + aData.remoteType().Equals(NOT_REMOTE_TYPE) && + HasExtensionPrincipal(aData)); + } + + auto principalOrErr = PrincipalInfoToPrincipal(principalInfo); + if (NS_WARN_IF(principalOrErr.isErr())) { + return false; + } + nsCOMPtr<nsIPrincipal> principal = principalOrErr.unwrap(); + + // Recompute the remoteType based on the principal, to double-check that it + // has not been tempered to select a different child process than the one + // expected. + bool isServiceWorker = aData.serviceWorkerData().type() == + OptionalServiceWorkerData::TServiceWorkerData; + auto remoteType = GetRemoteType( + principal, isServiceWorker ? WorkerKindService : WorkerKindShared); + if (NS_WARN_IF(remoteType.isErr())) { + LOG(("IsRemoteTypeAllowed: Error to retrieve remote type")); + return false; + } + + return MatchRemoteType(remoteType.unwrap(), contentChild->GetRemoteType()); +} + +/* static */ +already_AddRefed<RemoteWorkerManager> RemoteWorkerManager::GetOrCreate() { + AssertIsInMainProcess(); + AssertIsOnBackgroundThread(); + + if (!sRemoteWorkerManager) { + sRemoteWorkerManager = new RemoteWorkerManager(); + } + + RefPtr<RemoteWorkerManager> rwm = sRemoteWorkerManager; + return rwm.forget(); +} + +RemoteWorkerManager::RemoteWorkerManager() : mParentActor(nullptr) { + AssertIsInMainProcess(); + AssertIsOnBackgroundThread(); + MOZ_ASSERT(!sRemoteWorkerManager); +} + +RemoteWorkerManager::~RemoteWorkerManager() { + AssertIsInMainProcess(); + AssertIsOnBackgroundThread(); + MOZ_ASSERT(sRemoteWorkerManager == this); + sRemoteWorkerManager = nullptr; +} + +void RemoteWorkerManager::RegisterActor(RemoteWorkerServiceParent* aActor) { + AssertIsInMainProcess(); + AssertIsOnBackgroundThread(); + MOZ_ASSERT(aActor); + + if (!BackgroundParent::IsOtherProcessActor(aActor->Manager())) { + MOZ_ASSERT(!mParentActor); + mParentActor = aActor; + MOZ_ASSERT(mPendings.IsEmpty()); + return; + } + + MOZ_ASSERT(!mChildActors.Contains(aActor)); + mChildActors.AppendElement(aActor); + + if (!mPendings.IsEmpty()) { + const auto& processRemoteType = aActor->GetRemoteType(); + nsTArray<Pending> unlaunched; + + // Flush pending launching. + for (Pending& p : mPendings) { + if (p.mController->IsTerminated()) { + continue; + } + + const auto& workerRemoteType = p.mData.remoteType(); + + if (MatchRemoteType(processRemoteType, workerRemoteType)) { + LOG(("RegisterActor - Launch Pending, workerRemoteType=%s", + workerRemoteType.get())); + LaunchInternal(p.mController, aActor, p.mData); + } else { + unlaunched.AppendElement(std::move(p)); + continue; + } + } + + std::swap(mPendings, unlaunched); + + // AddRef is called when the first Pending object is added to mPendings, so + // the balancing Release is called when the last Pending object is removed. + // RemoteWorkerServiceParents will hold strong references to + // RemoteWorkerManager. + if (mPendings.IsEmpty()) { + Release(); + } + + LOG(("RegisterActor - mPendings length: %zu", mPendings.Length())); + } +} + +void RemoteWorkerManager::UnregisterActor(RemoteWorkerServiceParent* aActor) { + AssertIsInMainProcess(); + AssertIsOnBackgroundThread(); + MOZ_ASSERT(aActor); + + if (aActor == mParentActor) { + mParentActor = nullptr; + } else { + MOZ_ASSERT(mChildActors.Contains(aActor)); + mChildActors.RemoveElement(aActor); + } +} + +void RemoteWorkerManager::Launch(RemoteWorkerController* aController, + const RemoteWorkerData& aData, + base::ProcessId aProcessId) { + AssertIsInMainProcess(); + AssertIsOnBackgroundThread(); + + RemoteWorkerServiceParent* targetActor = SelectTargetActor(aData, aProcessId); + + // If there is not an available actor, let's store the data, and let's spawn a + // new process. + if (!targetActor) { + // If this is the first time we have a pending launching, we must keep alive + // the manager. + if (mPendings.IsEmpty()) { + AddRef(); + } + + Pending* pending = mPendings.AppendElement(); + pending->mController = aController; + pending->mData = aData; + + LaunchNewContentProcess(aData); + return; + } + + /** + * If a target actor for the remote worker has been selected, the actor has + * already been registered with the corresponding `ContentParent` and we + * should not increment the `mRemoteWorkerActorData`'s `mCount` again (see + * `SelectTargetActorForServiceWorker()` / + * `SelectTargetActorForSharedWorker()`). + */ + LaunchInternal(aController, targetActor, aData, true); +} + +void RemoteWorkerManager::LaunchInternal( + RemoteWorkerController* aController, + RemoteWorkerServiceParent* aTargetActor, const RemoteWorkerData& aData, + bool aRemoteWorkerAlreadyRegistered) { + AssertIsInMainProcess(); + AssertIsOnBackgroundThread(); + MOZ_ASSERT(aController); + MOZ_ASSERT(aTargetActor); + MOZ_ASSERT(aTargetActor == mParentActor || + mChildActors.Contains(aTargetActor)); + + // We need to send permissions to content processes, but not if we're spawning + // the worker here in the parent process. + if (aTargetActor != mParentActor) { + RefPtr<ThreadsafeContentParentHandle> contentHandle = + BackgroundParent::GetContentParentHandle(aTargetActor->Manager()); + + // This won't cause any race conditions because the content process + // should wait for the permissions to be received before executing the + // Service Worker. + nsCOMPtr<nsIRunnable> r = NS_NewRunnableFunction( + __func__, [contentHandle = std::move(contentHandle), + principalInfo = aData.principalInfo()] { + AssertIsOnMainThread(); + if (RefPtr<ContentParent> contentParent = + contentHandle->GetContentParent()) { + TransmitPermissionsAndBlobURLsForPrincipalInfo(contentParent, + principalInfo); + } + }); + + MOZ_ALWAYS_SUCCEEDS( + SchedulerGroup::Dispatch(TaskCategory::Other, r.forget())); + } + + RemoteWorkerParent* workerActor = static_cast<RemoteWorkerParent*>( + aTargetActor->Manager()->SendPRemoteWorkerConstructor(aData)); + if (NS_WARN_IF(!workerActor)) { + AsyncCreationFailed(aController); + return; + } + + workerActor->Initialize(aRemoteWorkerAlreadyRegistered); + + // This makes the link better the 2 actors. + aController->SetWorkerActor(workerActor); + workerActor->SetController(aController); +} + +void RemoteWorkerManager::AsyncCreationFailed( + RemoteWorkerController* aController) { + RefPtr<RemoteWorkerController> controller = aController; + nsCOMPtr<nsIRunnable> r = + NS_NewRunnableFunction("RemoteWorkerManager::AsyncCreationFailed", + [controller]() { controller->CreationFailed(); }); + + NS_DispatchToCurrentThread(r.forget()); +} + +template <typename Callback> +void RemoteWorkerManager::ForEachActor( + Callback&& aCallback, const nsACString& aRemoteType, + Maybe<base::ProcessId> aProcessId) const { + AssertIsOnBackgroundThread(); + + const auto length = mChildActors.Length(); + + auto end = static_cast<uint32_t>(rand()) % length; + if (aProcessId) { + // Start from the actor with the given processId instead of starting from + // a random index. + for (auto j = length - 1; j > 0; j--) { + if (mChildActors[j]->OtherPid() == *aProcessId) { + end = j; + break; + } + } + } + + uint32_t i = end; + + do { + MOZ_ASSERT(i < mChildActors.Length()); + RemoteWorkerServiceParent* actor = mChildActors[i]; + + if (MatchRemoteType(actor->GetRemoteType(), aRemoteType)) { + ThreadsafeContentParentHandle* contentHandle = + BackgroundParent::GetContentParentHandle(actor->Manager()); + + if (!aCallback(actor, contentHandle)) { + break; + } + } + + i = (i + 1) % length; + } while (i != end); +} + +/** + * When selecting a target actor for a given remote worker, we have to consider + * that: + * + * - Service Workers can spawn even when their registering page/script isn't + * active (e.g. push notifications), so we don't attempt to spawn the worker + * in its registering script's process. We search linearly and choose the + * search's starting position randomly. + * + * - When Fission is enabled, Shared Workers may have to be spawned into + * different child process from the one where it has been registered from, and + * that child process may be going to be marked as dead and shutdown. + * + * Spawning the workers in a random process makes the process selection criteria + * a little tricky, as a candidate process may imminently shutdown due to a + * remove worker actor unregistering + * (see `ContentParent::UnregisterRemoveWorkerActor`). + * + * In `ContentParent::MaybeAsyncSendShutDownMessage` we only dispatch a runnable + * to call `ContentParent::ShutDownProcess` if there are no registered remote + * worker actors, and we ensure that the check for the number of registered + * actors and the dispatching of the runnable are atomic. That happens on the + * main thread, so here on the background thread, while + * `ContentParent::mRemoteWorkerActorData` is locked, if `mCount` > 0, we can + * register a remote worker actor "early" and guarantee that the corresponding + * content process will not shutdown. + */ +RemoteWorkerServiceParent* RemoteWorkerManager::SelectTargetActorInternal( + const RemoteWorkerData& aData, base::ProcessId aProcessId) const { + AssertIsOnBackgroundThread(); + MOZ_ASSERT(!mChildActors.IsEmpty()); + + RemoteWorkerServiceParent* actor = nullptr; + + const auto& workerRemoteType = aData.remoteType(); + + ForEachActor( + [&](RemoteWorkerServiceParent* aActor, + ThreadsafeContentParentHandle* aContentHandle) { + // Make sure to choose an actor related to a child process that is not + // going to shutdown while we are still in the process of launching the + // remote worker. + // + // ForEachActor will start from the child actor coming from the child + // process with a pid equal to aProcessId if any, otherwise it would + // start from a random actor in the mChildActors array, this guarantees + // that we will choose that actor if it does also match the remote type. + if (aContentHandle->MaybeRegisterRemoteWorkerActor( + [&](uint32_t count, bool shutdownStarted) -> bool { + return (count || !shutdownStarted) && + (aActor->OtherPid() == aProcessId || !actor); + })) { + actor = aActor; + return false; + } + MOZ_ASSERT(!actor); + return true; + }, + workerRemoteType, IsServiceWorker(aData) ? Nothing() : Some(aProcessId)); + + return actor; +} + +RemoteWorkerServiceParent* RemoteWorkerManager::SelectTargetActor( + const RemoteWorkerData& aData, base::ProcessId aProcessId) { + AssertIsInMainProcess(); + AssertIsOnBackgroundThread(); + + // System principal workers should run on the parent process. + if (aData.principalInfo().type() == PrincipalInfo::TSystemPrincipalInfo) { + MOZ_ASSERT(mParentActor); + return mParentActor; + } + + // Extension principal workers are allowed to run on the parent process + // when "extension.webextensions.remote" pref is false. + if (aProcessId == base::GetCurrentProcId() && + aData.remoteType().Equals(NOT_REMOTE_TYPE) && + !StaticPrefs::extensions_webextensions_remote() && + HasExtensionPrincipal(aData)) { + MOZ_ASSERT(mParentActor); + return mParentActor; + } + + // If e10s is off, use the parent process. + if (!BrowserTabsRemoteAutostart()) { + MOZ_ASSERT(mParentActor); + return mParentActor; + } + + // We shouldn't have to worry about content-principal parent-process workers. + MOZ_ASSERT(aProcessId != base::GetCurrentProcId()); + + if (mChildActors.IsEmpty()) { + return nullptr; + } + + return SelectTargetActorInternal(aData, aProcessId); +} + +void RemoteWorkerManager::LaunchNewContentProcess( + const RemoteWorkerData& aData) { + AssertIsInMainProcess(); + AssertIsOnBackgroundThread(); + + nsCOMPtr<nsISerialEventTarget> bgEventTarget = GetCurrentSerialEventTarget(); + + using CallbackParamType = ContentParent::LaunchPromise::ResolveOrRejectValue; + + // A new content process must be requested on the main thread. On success, + // the success callback will also run on the main thread. On failure, however, + // the failure callback must be run on the background thread - it uses + // RemoteWorkerManager, and RemoteWorkerManager isn't threadsafe, so the + // promise callback will just dispatch the "real" failure callback to the + // background thread. + auto processLaunchCallback = [principalInfo = aData.principalInfo(), + bgEventTarget = std::move(bgEventTarget), + self = RefPtr<RemoteWorkerManager>(this)]( + const CallbackParamType& aValue, + const nsCString& remoteType) mutable { + if (aValue.IsResolve()) { + LOG(("LaunchNewContentProcess: successfully got child process")); + + // The failure callback won't run, and we're on the main thread, so + // we need to properly release the thread-unsafe RemoteWorkerManager. + NS_ProxyRelease(__func__, bgEventTarget, self.forget()); + } else { + // The "real" failure callback. + nsCOMPtr<nsIRunnable> r = NS_NewRunnableFunction( + __func__, [self = std::move(self), remoteType] { + nsTArray<Pending> uncancelled; + auto pendings = std::move(self->mPendings); + + for (const auto& pending : pendings) { + const auto& workerRemoteType = pending.mData.remoteType(); + if (self->MatchRemoteType(remoteType, workerRemoteType)) { + LOG( + ("LaunchNewContentProcess: Cancel pending with " + "workerRemoteType=%s", + workerRemoteType.get())); + pending.mController->CreationFailed(); + } else { + uncancelled.AppendElement(pending); + } + } + + std::swap(self->mPendings, uncancelled); + }); + + bgEventTarget->Dispatch(r.forget(), NS_DISPATCH_NORMAL); + } + }; + + LOG(("LaunchNewContentProcess: remoteType=%s", aData.remoteType().get())); + + nsCOMPtr<nsIRunnable> r = NS_NewRunnableFunction( + __func__, [callback = std::move(processLaunchCallback), + workerRemoteType = aData.remoteType()]() mutable { + auto remoteType = + workerRemoteType.IsEmpty() ? DEFAULT_REMOTE_TYPE : workerRemoteType; + + // Request a process making sure to specify aPreferUsed=true. For a + // given remoteType there's a pool size limit. If we pass aPreferUsed + // here, then if there's any process in the pool already, we will use + // that. If we pass false (which is the default if omitted), then this + // call will spawn a new process if the pool isn't at its limit yet. + // + // (Our intent is never to grow the pool size here. Our logic gets here + // because our current logic on PBackground is only aware of + // RemoteWorkerServiceParent actors that have registered themselves, + // which is fundamentally unaware of processes that will match in the + // future when they register. So we absolutely are fine with and want + // any existing processes.) + ContentParent::GetNewOrUsedBrowserProcessAsync( + /* aRemoteType = */ remoteType, + /* aGroup */ nullptr, + hal::ProcessPriority::PROCESS_PRIORITY_FOREGROUND, + /* aPreferUsed */ true) + ->Then(GetCurrentSerialEventTarget(), __func__, + [callback = std::move(callback), + remoteType](const CallbackParamType& aValue) mutable { + callback(aValue, remoteType); + }); + }); + + SchedulerGroup::Dispatch(TaskCategory::Other, r.forget()); +} + +} // namespace dom +} // namespace mozilla diff --git a/dom/workers/remoteworkers/RemoteWorkerManager.h b/dom/workers/remoteworkers/RemoteWorkerManager.h new file mode 100644 index 0000000000..5ff11ee6e6 --- /dev/null +++ b/dom/workers/remoteworkers/RemoteWorkerManager.h @@ -0,0 +1,118 @@ +/* -*- 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_RemoteWorkerManager_h +#define mozilla_dom_RemoteWorkerManager_h + +#include "base/process.h" +#include "mozilla/RefPtr.h" +#include "mozilla/dom/ContentParent.h" +#include "mozilla/dom/RemoteWorkerTypes.h" +#include "mozilla/dom/WorkerPrivate.h" // WorkerKind enum +#include "nsISupportsImpl.h" +#include "nsTArray.h" + +namespace mozilla::dom { + +class RemoteWorkerController; +class RemoteWorkerServiceParent; + +/** + * PBackground instance that keeps tracks of RemoteWorkerServiceParent actors + * (1 per process, including the main process) and pending + * RemoteWorkerController requests to spawn remote workers if the spawn request + * can't be immediately fulfilled. Decides which RemoteWorkerServerParent to use + * internally via SelectTargetActor in order to select a BackgroundParent + * manager on which to create a RemoteWorkerParent. + */ +class RemoteWorkerManager final { + public: + NS_INLINE_DECL_REFCOUNTING(RemoteWorkerManager) + + static already_AddRefed<RemoteWorkerManager> GetOrCreate(); + + void RegisterActor(RemoteWorkerServiceParent* aActor); + + void UnregisterActor(RemoteWorkerServiceParent* aActor); + + void Launch(RemoteWorkerController* aController, + const RemoteWorkerData& aData, base::ProcessId aProcessId); + + static bool MatchRemoteType(const nsACString& processRemoteType, + const nsACString& workerRemoteType); + + /** + * Get the child process RemoteType where a RemoteWorker should be + * launched. + */ + static Result<nsCString, nsresult> GetRemoteType( + const nsCOMPtr<nsIPrincipal>& aPrincipal, WorkerKind aWorkerKind); + + /** + * Verify if a remote worker should be allowed to run in the current + * child process remoteType. + */ + static bool IsRemoteTypeAllowed(const RemoteWorkerData& aData); + + static bool HasExtensionPrincipal(const RemoteWorkerData& aData); + + private: + RemoteWorkerManager(); + ~RemoteWorkerManager(); + + RemoteWorkerServiceParent* SelectTargetActor(const RemoteWorkerData& aData, + base::ProcessId aProcessId); + + RemoteWorkerServiceParent* SelectTargetActorInternal( + const RemoteWorkerData& aData, base::ProcessId aProcessId) const; + + void LaunchInternal(RemoteWorkerController* aController, + RemoteWorkerServiceParent* aTargetActor, + const RemoteWorkerData& aData, + bool aRemoteWorkerAlreadyRegistered = false); + + void LaunchNewContentProcess(const RemoteWorkerData& aData); + + void AsyncCreationFailed(RemoteWorkerController* aController); + + // Iterate through all RemoteWorkerServiceParent actors with the given + // remoteType, starting from the actor related to a child process with pid + // aProcessId if needed and available or from a random index otherwise (as if + // iterating through a circular array). + // + // aCallback should be a invokable object with a function signature of + // bool (RemoteWorkerServiceParent*, RefPtr<ContentParent>&&) + // + // aCallback is called with the actor and corresponding ContentParent, should + // return false to abort iteration before all actors have been traversed (e.g. + // if the desired actor is found), and must not mutate mChildActors (which + // shouldn't be an issue because this function is const). aCallback also + // doesn't need to worry about proxy-releasing the ContentParent if it isn't + // moved out of the parameter. + template <typename Callback> + void ForEachActor(Callback&& aCallback, const nsACString& aRemoteType, + Maybe<base::ProcessId> aProcessId = Nothing()) const; + + // The list of existing RemoteWorkerServiceParent actors for child processes. + // Raw pointers because RemoteWorkerServiceParent actors unregister themselves + // when destroyed. + // XXX For Fission, where we could have a lot of child actors, should we maybe + // instead keep either a hash table (PID->actor) or perhaps store the actors + // in order, sorted by PID, to avoid linear lookup times? + nsTArray<RemoteWorkerServiceParent*> mChildActors; + RemoteWorkerServiceParent* mParentActor; + + struct Pending { + RefPtr<RemoteWorkerController> mController; + RemoteWorkerData mData; + }; + + nsTArray<Pending> mPendings; +}; + +} // namespace mozilla::dom + +#endif // mozilla_dom_RemoteWorkerManager_h diff --git a/dom/workers/remoteworkers/RemoteWorkerParent.cpp b/dom/workers/remoteworkers/RemoteWorkerParent.cpp new file mode 100644 index 0000000000..be874f0627 --- /dev/null +++ b/dom/workers/remoteworkers/RemoteWorkerParent.cpp @@ -0,0 +1,190 @@ +/* -*- 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 "RemoteWorkerParent.h" +#include "RemoteWorkerController.h" +#include "mozilla/dom/ContentParent.h" +#include "mozilla/dom/PFetchEventOpProxyParent.h" +#include "mozilla/ipc/BackgroundParent.h" +#include "mozilla/SchedulerGroup.h" +#include "mozilla/Unused.h" +#include "nsProxyRelease.h" + +namespace mozilla { + +using namespace ipc; + +namespace dom { + +namespace { + +class UnregisterActorRunnable final : public Runnable { + public: + explicit UnregisterActorRunnable( + already_AddRefed<ThreadsafeContentParentHandle> aParent) + : Runnable("UnregisterActorRunnable"), mContentHandle(aParent) { + AssertIsOnBackgroundThread(); + } + + NS_IMETHOD + Run() override { + AssertIsOnMainThread(); + if (RefPtr<ContentParent> contentParent = + mContentHandle->GetContentParent()) { + contentParent->UnregisterRemoveWorkerActor(); + } + + return NS_OK; + } + + private: + RefPtr<ThreadsafeContentParentHandle> mContentHandle; +}; + +} // namespace + +RemoteWorkerParent::RemoteWorkerParent() { + AssertIsOnBackgroundThread(); + MOZ_ASSERT(XRE_IsParentProcess()); +} + +RemoteWorkerParent::~RemoteWorkerParent() { + AssertIsOnBackgroundThread(); + MOZ_ASSERT(XRE_IsParentProcess()); +} + +void RemoteWorkerParent::Initialize(bool aAlreadyRegistered) { + RefPtr<ThreadsafeContentParentHandle> parent = + BackgroundParent::GetContentParentHandle(Manager()); + + // Parent is null if the child actor runs on the parent process. + if (parent) { + if (!aAlreadyRegistered) { + parent->RegisterRemoteWorkerActor(); + } + + NS_ReleaseOnMainThread("RemoteWorkerParent::Initialize ContentParent", + parent.forget()); + } +} + +already_AddRefed<PFetchEventOpProxyParent> +RemoteWorkerParent::AllocPFetchEventOpProxyParent( + const ParentToChildServiceWorkerFetchEventOpArgs& aArgs) { + MOZ_CRASH("PFetchEventOpProxyParent actors must be manually constructed!"); + return nullptr; +} + +void RemoteWorkerParent::ActorDestroy(IProtocol::ActorDestroyReason) { + AssertIsOnBackgroundThread(); + MOZ_ASSERT(XRE_IsParentProcess()); + + RefPtr<ThreadsafeContentParentHandle> parent = + BackgroundParent::GetContentParentHandle(Manager()); + + // Parent is null if the child actor runs on the parent process. + if (parent) { + RefPtr<UnregisterActorRunnable> r = + new UnregisterActorRunnable(parent.forget()); + + SchedulerGroup::Dispatch(TaskCategory::Other, r.forget()); + } + + if (mController) { + mController->NoteDeadWorkerActor(); + mController = nullptr; + } +} + +IPCResult RemoteWorkerParent::RecvCreated(const bool& aStatus) { + AssertIsOnBackgroundThread(); + MOZ_ASSERT(XRE_IsParentProcess()); + + if (!mController) { + return IPC_OK(); + } + + if (aStatus) { + mController->CreationSucceeded(); + } else { + mController->CreationFailed(); + } + + return IPC_OK(); +} + +IPCResult RemoteWorkerParent::RecvError(const ErrorValue& aValue) { + AssertIsOnBackgroundThread(); + MOZ_ASSERT(XRE_IsParentProcess()); + + if (mController) { + mController->ErrorPropagation(aValue); + } + + return IPC_OK(); +} + +IPCResult RemoteWorkerParent::RecvNotifyLock(const bool& aCreated) { + AssertIsOnBackgroundThread(); + MOZ_ASSERT(XRE_IsParentProcess()); + + if (mController) { + mController->NotifyLock(aCreated); + } + + return IPC_OK(); +} + +void RemoteWorkerParent::MaybeSendDelete() { + if (mDeleteSent) { + return; + } + + // For some reason, if the following two lines are swapped, ASan says there's + // a UAF... + mDeleteSent = true; + Unused << Send__delete__(this); +} + +IPCResult RemoteWorkerParent::RecvClose() { + AssertIsOnBackgroundThread(); + MOZ_ASSERT(XRE_IsParentProcess()); + + if (mController) { + mController->WorkerTerminated(); + } + + MaybeSendDelete(); + + return IPC_OK(); +} + +void RemoteWorkerParent::SetController(RemoteWorkerController* aController) { + AssertIsOnBackgroundThread(); + MOZ_ASSERT(XRE_IsParentProcess()); + + mController = aController; +} + +IPCResult RemoteWorkerParent::RecvSetServiceWorkerSkipWaitingFlag( + SetServiceWorkerSkipWaitingFlagResolver&& aResolve) { + AssertIsOnBackgroundThread(); + MOZ_ASSERT(XRE_IsParentProcess()); + + if (mController) { + mController->SetServiceWorkerSkipWaitingFlag()->Then( + GetCurrentSerialEventTarget(), __func__, + [resolve = aResolve](bool /* unused */) { resolve(true); }, + [resolve = aResolve](nsresult /* unused */) { resolve(false); }); + } else { + aResolve(false); + } + + return IPC_OK(); +} + +} // namespace dom +} // namespace mozilla diff --git a/dom/workers/remoteworkers/RemoteWorkerParent.h b/dom/workers/remoteworkers/RemoteWorkerParent.h new file mode 100644 index 0000000000..7bb91bad2a --- /dev/null +++ b/dom/workers/remoteworkers/RemoteWorkerParent.h @@ -0,0 +1,60 @@ +/* -*- 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_RemoteWorkerParent_h +#define mozilla_dom_RemoteWorkerParent_h + +#include "mozilla/dom/PRemoteWorkerParent.h" + +namespace mozilla::dom { + +class RemoteWorkerController; + +/** + * PBackground-managed parent actor that is mutually associated with a single + * RemoteWorkerController. Relays error/close events to the controller and in + * turns is told life-cycle events. + */ +class RemoteWorkerParent final : public PRemoteWorkerParent { + friend class PRemoteWorkerParent; + + public: + NS_INLINE_DECL_REFCOUNTING(RemoteWorkerParent) + + RemoteWorkerParent(); + + void Initialize(bool aAlreadyRegistered = false); + + void SetController(RemoteWorkerController* aController); + + void MaybeSendDelete(); + + private: + ~RemoteWorkerParent(); + + already_AddRefed<PFetchEventOpProxyParent> AllocPFetchEventOpProxyParent( + const ParentToChildServiceWorkerFetchEventOpArgs& aArgs); + + void ActorDestroy(mozilla::ipc::IProtocol::ActorDestroyReason) override; + + mozilla::ipc::IPCResult RecvError(const ErrorValue& aValue); + + mozilla::ipc::IPCResult RecvNotifyLock(const bool& aCreated); + + mozilla::ipc::IPCResult RecvClose(); + + mozilla::ipc::IPCResult RecvCreated(const bool& aStatus); + + mozilla::ipc::IPCResult RecvSetServiceWorkerSkipWaitingFlag( + SetServiceWorkerSkipWaitingFlagResolver&& aResolve); + + bool mDeleteSent = false; + RefPtr<RemoteWorkerController> mController; +}; + +} // namespace mozilla::dom + +#endif // mozilla_dom_RemoteWorkerParent_h diff --git a/dom/workers/remoteworkers/RemoteWorkerService.cpp b/dom/workers/remoteworkers/RemoteWorkerService.cpp new file mode 100644 index 0000000000..870a326388 --- /dev/null +++ b/dom/workers/remoteworkers/RemoteWorkerService.cpp @@ -0,0 +1,189 @@ +/* -*- 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 "RemoteWorkerService.h" + +#include "mozilla/dom/PRemoteWorkerParent.h" +#include "mozilla/ipc/BackgroundChild.h" +#include "mozilla/ipc/BackgroundParent.h" +#include "mozilla/ipc/PBackgroundChild.h" +#include "mozilla/ipc/PBackgroundParent.h" +#include "mozilla/SchedulerGroup.h" +#include "mozilla/Services.h" +#include "mozilla/StaticMutex.h" +#include "mozilla/StaticPtr.h" +#include "nsIObserverService.h" +#include "nsIThread.h" +#include "nsThreadUtils.h" +#include "nsXPCOMPrivate.h" +#include "RemoteWorkerController.h" +#include "RemoteWorkerServiceChild.h" +#include "RemoteWorkerServiceParent.h" + +namespace mozilla { + +using namespace ipc; + +namespace dom { + +namespace { + +StaticMutex sRemoteWorkerServiceMutex; +StaticRefPtr<RemoteWorkerService> sRemoteWorkerService; + +} // namespace + +/* static */ +void RemoteWorkerService::Initialize() { + MOZ_ASSERT(NS_IsMainThread()); + + StaticMutexAutoLock lock(sRemoteWorkerServiceMutex); + MOZ_ASSERT(!sRemoteWorkerService); + + RefPtr<RemoteWorkerService> service = new RemoteWorkerService(); + + if (!XRE_IsParentProcess()) { + nsresult rv = service->InitializeOnMainThread(); + if (NS_WARN_IF(NS_FAILED(rv))) { + return; + } + + sRemoteWorkerService = service; + return; + } + + nsCOMPtr<nsIObserverService> obs = services::GetObserverService(); + if (NS_WARN_IF(!obs)) { + return; + } + + nsresult rv = obs->AddObserver(service, "profile-after-change", false); + if (NS_WARN_IF(NS_FAILED(rv))) { + return; + } + + sRemoteWorkerService = service; +} + +/* static */ +nsIThread* RemoteWorkerService::Thread() { + StaticMutexAutoLock lock(sRemoteWorkerServiceMutex); + MOZ_ASSERT(sRemoteWorkerService); + MOZ_ASSERT(sRemoteWorkerService->mThread); + return sRemoteWorkerService->mThread; +} + +nsresult RemoteWorkerService::InitializeOnMainThread() { + // I would like to call this thread "DOM Remote Worker Launcher", but the max + // length is 16 chars. + nsresult rv = NS_NewNamedThread("Worker Launcher", getter_AddRefs(mThread)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + nsCOMPtr<nsIObserverService> obs = services::GetObserverService(); + if (NS_WARN_IF(!obs)) { + return NS_ERROR_FAILURE; + } + + rv = obs->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, false); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + RefPtr<RemoteWorkerService> self = this; + nsCOMPtr<nsIRunnable> r = NS_NewRunnableFunction( + "InitializeThread", [self]() { self->InitializeOnTargetThread(); }); + + rv = mThread->Dispatch(r.forget(), NS_DISPATCH_NORMAL); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + return NS_OK; +} + +RemoteWorkerService::RemoteWorkerService() { MOZ_ASSERT(NS_IsMainThread()); } + +RemoteWorkerService::~RemoteWorkerService() = default; + +void RemoteWorkerService::InitializeOnTargetThread() { + MOZ_ASSERT(mThread); + MOZ_ASSERT(mThread->IsOnCurrentThread()); + + PBackgroundChild* actorChild = BackgroundChild::GetOrCreateForCurrentThread(); + if (NS_WARN_IF(!actorChild)) { + return; + } + + RemoteWorkerServiceChild* actor = static_cast<RemoteWorkerServiceChild*>( + actorChild->SendPRemoteWorkerServiceConstructor()); + if (NS_WARN_IF(!actor)) { + return; + } + + // Now we are ready! + mActor = actor; +} + +void RemoteWorkerService::CloseActorOnTargetThread() { + MOZ_ASSERT(mThread); + MOZ_ASSERT(mThread->IsOnCurrentThread()); + + // If mActor is nullptr it means that initialization failed. + if (mActor) { + // Here we need to shutdown the IPC protocol. + mActor->Send__delete__(mActor); + mActor = nullptr; + } +} + +NS_IMETHODIMP +RemoteWorkerService::Observe(nsISupports* aSubject, const char* aTopic, + const char16_t* aData) { + if (!strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID)) { + MOZ_ASSERT(mThread); + + nsCOMPtr<nsIObserverService> obs = services::GetObserverService(); + if (obs) { + obs->RemoveObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID); + } + + RefPtr<RemoteWorkerService> self = this; + nsCOMPtr<nsIRunnable> r = + NS_NewRunnableFunction("RemoteWorkerService::CloseActorOnTargetThread", + [self]() { self->CloseActorOnTargetThread(); }); + + mThread->Dispatch(r.forget(), NS_DISPATCH_NORMAL); + + // We've posted a shutdown message; now shutdown the thread. This will spin + // a nested event loop waiting for the thread to process all pending events + // (including the just dispatched CloseActorOnTargetThread which will close + // the actor), ensuring to block main thread shutdown long enough to avoid + // races. + mThread->Shutdown(); + mThread = nullptr; + + StaticMutexAutoLock lock(sRemoteWorkerServiceMutex); + sRemoteWorkerService = nullptr; + return NS_OK; + } + + MOZ_ASSERT(!strcmp(aTopic, "profile-after-change")); + MOZ_ASSERT(XRE_IsParentProcess()); + + nsCOMPtr<nsIObserverService> obs = services::GetObserverService(); + if (obs) { + obs->RemoveObserver(this, "profile-after-change"); + } + + return InitializeOnMainThread(); +} + +NS_IMPL_ISUPPORTS(RemoteWorkerService, nsIObserver) + +} // namespace dom +} // namespace mozilla diff --git a/dom/workers/remoteworkers/RemoteWorkerService.h b/dom/workers/remoteworkers/RemoteWorkerService.h new file mode 100644 index 0000000000..1d5e50ee87 --- /dev/null +++ b/dom/workers/remoteworkers/RemoteWorkerService.h @@ -0,0 +1,59 @@ +/* -*- 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_RemoteWorkerService_h +#define mozilla_dom_RemoteWorkerService_h + +#include "nsCOMPtr.h" +#include "nsIObserver.h" + +class nsIThread; + +namespace mozilla::dom { + +class RemoteWorkerServiceChild; + +/** + * Every process has a RemoteWorkerService which does the actual spawning of + * RemoteWorkerChild instances. The RemoteWorkerService creates a "Worker + * Launcher" thread at initialization on which it creates a + * RemoteWorkerServiceChild to service spawn requests. The thread is exposed as + * RemoteWorkerService::Thread(). A new/distinct thread is used because we + * (eventually) don't want to deal with main-thread contention, content + * processes have no equivalent of a PBackground thread, and actors are bound to + * specific threads. + * + * (Disclaimer: currently most RemoteWorkerOps need to happen on the main thread + * because the main-thread ends up as the owner of the worker and all + * manipulation of the worker must happen from the owning thread.) + */ +class RemoteWorkerService final : public nsIObserver { + public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIOBSERVER + + // To be called when a process is initialized on main-thread. + static void Initialize(); + + static nsIThread* Thread(); + + private: + RemoteWorkerService(); + ~RemoteWorkerService(); + + nsresult InitializeOnMainThread(); + + void InitializeOnTargetThread(); + + void CloseActorOnTargetThread(); + + nsCOMPtr<nsIThread> mThread; + RefPtr<RemoteWorkerServiceChild> mActor; +}; + +} // namespace mozilla::dom + +#endif // mozilla_dom_RemoteWorkerService_h diff --git a/dom/workers/remoteworkers/RemoteWorkerServiceChild.cpp b/dom/workers/remoteworkers/RemoteWorkerServiceChild.cpp new file mode 100644 index 0000000000..100908064e --- /dev/null +++ b/dom/workers/remoteworkers/RemoteWorkerServiceChild.cpp @@ -0,0 +1,16 @@ +/* -*- 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 "RemoteWorkerServiceChild.h" +#include "RemoteWorkerController.h" + +namespace mozilla::dom { + +RemoteWorkerServiceChild::RemoteWorkerServiceChild() = default; + +RemoteWorkerServiceChild::~RemoteWorkerServiceChild() = default; + +} // namespace mozilla::dom diff --git a/dom/workers/remoteworkers/RemoteWorkerServiceChild.h b/dom/workers/remoteworkers/RemoteWorkerServiceChild.h new file mode 100644 index 0000000000..d21c9a2ab3 --- /dev/null +++ b/dom/workers/remoteworkers/RemoteWorkerServiceChild.h @@ -0,0 +1,34 @@ +/* -*- 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_RemoteWorkerServiceChild_h +#define mozilla_dom_RemoteWorkerServiceChild_h + +#include "mozilla/dom/PRemoteWorkerServiceChild.h" +#include "nsISupportsImpl.h" + +namespace mozilla::dom { + +class RemoteWorkerController; +class RemoteWorkerData; + +/** + * "Worker Launcher"-thread child actor created by the RemoteWorkerService to + * register itself with the PBackground RemoteWorkerManager in the parent. + */ +class RemoteWorkerServiceChild final : public PRemoteWorkerServiceChild { + public: + NS_INLINE_DECL_REFCOUNTING(RemoteWorkerServiceChild) + + RemoteWorkerServiceChild(); + + private: + ~RemoteWorkerServiceChild(); +}; + +} // namespace mozilla::dom + +#endif // mozilla_dom_RemoteWorkerServiceChild_h diff --git a/dom/workers/remoteworkers/RemoteWorkerServiceParent.cpp b/dom/workers/remoteworkers/RemoteWorkerServiceParent.cpp new file mode 100644 index 0000000000..c7fd5ea6c8 --- /dev/null +++ b/dom/workers/remoteworkers/RemoteWorkerServiceParent.cpp @@ -0,0 +1,34 @@ +/* -*- 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 "RemoteWorkerServiceParent.h" +#include "RemoteWorkerManager.h" +#include "mozilla/ipc/BackgroundParent.h" + +namespace mozilla { + +using namespace ipc; + +namespace dom { + +RemoteWorkerServiceParent::RemoteWorkerServiceParent() + : mManager(RemoteWorkerManager::GetOrCreate()) {} + +RemoteWorkerServiceParent::~RemoteWorkerServiceParent() = default; + +void RemoteWorkerServiceParent::Initialize(const nsACString& aRemoteType) { + AssertIsOnBackgroundThread(); + mRemoteType = aRemoteType; + mManager->RegisterActor(this); +} + +void RemoteWorkerServiceParent::ActorDestroy(IProtocol::ActorDestroyReason) { + AssertIsOnBackgroundThread(); + mManager->UnregisterActor(this); +} + +} // namespace dom +} // namespace mozilla diff --git a/dom/workers/remoteworkers/RemoteWorkerServiceParent.h b/dom/workers/remoteworkers/RemoteWorkerServiceParent.h new file mode 100644 index 0000000000..4d9b0015e4 --- /dev/null +++ b/dom/workers/remoteworkers/RemoteWorkerServiceParent.h @@ -0,0 +1,39 @@ +/* -*- 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_RemoteWorkerServiceParent_h +#define mozilla_dom_RemoteWorkerServiceParent_h + +#include "mozilla/dom/PRemoteWorkerServiceParent.h" +#include "mozilla/dom/RemoteType.h" + +namespace mozilla::dom { + +class RemoteWorkerManager; + +/** + * PBackground parent actor that registers with the PBackground + * RemoteWorkerManager and used to relay spawn requests. + */ +class RemoteWorkerServiceParent final : public PRemoteWorkerServiceParent { + public: + RemoteWorkerServiceParent(); + ~RemoteWorkerServiceParent(); + + void ActorDestroy(mozilla::ipc::IProtocol::ActorDestroyReason) override; + + void Initialize(const nsACString& aRemoteType); + + nsCString GetRemoteType() const { return mRemoteType; } + + private: + RefPtr<RemoteWorkerManager> mManager; + nsCString mRemoteType = NOT_REMOTE_TYPE; +}; + +} // namespace mozilla::dom + +#endif // mozilla_dom_RemoteWorkerServiceParent_h diff --git a/dom/workers/remoteworkers/RemoteWorkerTypes.ipdlh b/dom/workers/remoteworkers/RemoteWorkerTypes.ipdlh new file mode 100644 index 0000000000..cde1771970 --- /dev/null +++ b/dom/workers/remoteworkers/RemoteWorkerTypes.ipdlh @@ -0,0 +1,118 @@ +/* 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 ClientIPCTypes; +include IPCServiceWorkerDescriptor; +include IPCServiceWorkerRegistrationDescriptor; +include PBackgroundSharedTypes; +include URIParams; +include DOMTypes; +include NeckoChannelParams; +include ProtocolTypes; + +include "mozilla/dom/ClientIPCUtils.h"; +include "mozilla/dom/ReferrerInfoUtils.h"; + +using struct mozilla::void_t from "mozilla/ipc/IPCCore.h"; +using mozilla::StorageAccess from "mozilla/StorageAccess.h"; +using mozilla::OriginTrials from "mozilla/OriginTrialsIPCUtils.h"; + +namespace mozilla { +namespace dom { + +struct ServiceWorkerData { + IPCServiceWorkerDescriptor descriptor; + IPCServiceWorkerRegistrationDescriptor registrationDescriptor; + nsString cacheName; + uint32_t loadFlags; + nsString id; +}; + +union OptionalServiceWorkerData { + void_t; + ServiceWorkerData; +}; + +struct RemoteWorkerData +{ + // This should only be used for devtools. + nsString originalScriptURL; + + // It is important to pass these as URIParams instead of strings for blob + // URLs: they carry an additional bit of state with them (mIsRevoked) that + // gives us a chance to use them, even after they've been revoked. Because + // we're asynchronously calling into the parent process before potentially + // loading the worker, it is important to keep this state. Note that this + // isn't a panacea: once the URL has been revoked, it'll give the worker 5 + // seconds to actually load it; so it's possible to still fail to load the + // blob URL if it takes too long to do the round trip. + URIParams baseScriptURL; + URIParams resolvedScriptURL; + + nsString name; + + PrincipalInfo loadingPrincipalInfo; + PrincipalInfo principalInfo; + PrincipalInfo partitionedPrincipalInfo; + + bool useRegularPrincipal; + bool hasStorageAccessPermissionGranted; + + CookieJarSettingsArgs cookieJarSettings; + + nsCString domain; + + bool isSecureContext; + + IPCClientInfo? clientInfo; + + nsIReferrerInfo referrerInfo; + + StorageAccess storageAccess; + + bool isThirdPartyContextToTopWindow; + + bool shouldResistFingerprinting; + + OriginTrials originTrials; + + OptionalServiceWorkerData serviceWorkerData; + + nsID agentClusterId; + + // Child process remote type where the worker should only run on. + nsCString remoteType; +}; + +// ErrorData/ErrorDataNote correspond to WorkerErrorReport/WorkerErrorNote +// which in turn correspond to JSErrorReport/JSErrorNotes which allows JS to +// report complicated errors such as redeclarations that involve multiple +// distinct lines. For more generic error-propagation IPC structures, see bug +// 1357463 on making ErrorResult usable over IPC. + +struct ErrorDataNote { + uint32_t lineNumber; + uint32_t columnNumber; + nsString message; + nsString filename; +}; + +struct ErrorData { + bool isWarning; + uint32_t lineNumber; + uint32_t columnNumber; + nsString message; + nsString filename; + nsString line; + ErrorDataNote[] notes; +}; + +union ErrorValue { + nsresult; + ErrorData; + void_t; +}; + +} // namespace dom +} // namespace mozilla diff --git a/dom/workers/remoteworkers/moz.build b/dom/workers/remoteworkers/moz.build new file mode 100644 index 0000000000..9983b7dd10 --- /dev/null +++ b/dom/workers/remoteworkers/moz.build @@ -0,0 +1,45 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# 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/. + +EXPORTS.mozilla.dom += [ + "RemoteWorkerChild.h", + "RemoteWorkerController.h", + "RemoteWorkerControllerChild.h", + "RemoteWorkerControllerParent.h", + "RemoteWorkerManager.h", + "RemoteWorkerParent.h", + "RemoteWorkerService.h", + "RemoteWorkerServiceChild.h", + "RemoteWorkerServiceParent.h", +] + +UNIFIED_SOURCES += [ + "RemoteWorkerChild.cpp", + "RemoteWorkerController.cpp", + "RemoteWorkerControllerChild.cpp", + "RemoteWorkerControllerParent.cpp", + "RemoteWorkerManager.cpp", + "RemoteWorkerParent.cpp", + "RemoteWorkerService.cpp", + "RemoteWorkerServiceChild.cpp", + "RemoteWorkerServiceParent.cpp", +] + +LOCAL_INCLUDES += [ + "/dom/serviceworkers", + "/xpcom/build", +] + +IPDL_SOURCES += [ + "PRemoteWorker.ipdl", + "PRemoteWorkerController.ipdl", + "PRemoteWorkerService.ipdl", + "RemoteWorkerTypes.ipdlh", +] + +include("/ipc/chromium/chromium-config.mozbuild") + +FINAL_LIBRARY = "xul" |