From c0a3f641c23a270d7fb153ec9dc007232145ab91 Mon Sep 17 00:00:00 2001 From: Perry Jiang Date: Tue, 13 Aug 2019 05:16:40 +0000 Subject: [PATCH] Bug 1231213 - Refactor RemoteWorkerChild to handle Service Workers' and Shared Workers' operations and to make its state data/transitions safer. r=asuth Differential Revision: https://phabricator.services.mozilla.com/D26170 --HG-- extra : moz-landing-system : lando --- .../remoteworkers/RemoteWorkerChild.cpp | 827 ++++++++++++------ dom/workers/remoteworkers/RemoteWorkerChild.h | 151 ++-- .../remoteworkers/RemoteWorkerParent.cpp | 3 +- .../remoteworkers/RemoteWorkerTypes.ipdlh | 1 - ipc/glue/BackgroundChildImpl.cpp | 2 +- 5 files changed, 641 insertions(+), 343 deletions(-) diff --git a/dom/workers/remoteworkers/RemoteWorkerChild.cpp b/dom/workers/remoteworkers/RemoteWorkerChild.cpp index 319db6b4a328..5c033e27b14a 100644 --- a/dom/workers/remoteworkers/RemoteWorkerChild.cpp +++ b/dom/workers/remoteworkers/RemoteWorkerChild.cpp @@ -5,11 +5,35 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "RemoteWorkerChild.h" + +#include + +#include "MainThreadUtils.h" +#include "nsDebug.h" +#include "nsError.h" +#include "nsIConsoleReportCollector.h" +#include "nsIInterfaceRequestor.h" +#include "nsIPrincipal.h" +#include "nsIPermissionManager.h" +#include "nsNetUtil.h" +#include "nsProxyRelease.h" +#include "nsThreadUtils.h" +#include "nsXULAppAPI.h" + #include "RemoteWorkerService.h" +#include "mozilla/Assertions.h" +#include "mozilla/Attributes.h" +#include "mozilla/BasePrincipal.h" +#include "mozilla/ErrorResult.h" +#include "mozilla/Services.h" +#include "mozilla/ScopeExit.h" +#include "mozilla/Unused.h" #include "mozilla/dom/IndexedDatabaseManager.h" #include "mozilla/dom/MessagePort.h" #include "mozilla/dom/RemoteWorkerTypes.h" +#include "mozilla/dom/ServiceWorkerDescriptor.h" #include "mozilla/dom/ServiceWorkerInterceptController.h" +#include "mozilla/dom/ServiceWorkerRegistrationDescriptor.h" #include "mozilla/dom/ServiceWorkerUtils.h" #include "mozilla/dom/workerinternals/ScriptLoader.h" #include "mozilla/dom/WorkerError.h" @@ -19,10 +43,6 @@ #include "mozilla/ipc/BackgroundUtils.h" #include "mozilla/ipc/URIUtils.h" #include "mozilla/net/CookieSettings.h" -#include "nsIConsoleReportCollector.h" -#include "nsIPrincipal.h" -#include "nsNetUtil.h" -#include "nsProxyRelease.h" namespace mozilla { @@ -71,14 +91,53 @@ NS_IMPL_ADDREF(SharedWorkerInterfaceRequestor) NS_IMPL_RELEASE(SharedWorkerInterfaceRequestor) NS_IMPL_QUERY_INTERFACE(SharedWorkerInterfaceRequestor, nsIInterfaceRequestor) +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 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 mSelf; +}; + // Normal runnable because AddPortIdentifier() is going to exec JS code. class MessagePortIdentifierRunnable final : public WorkerRunnable { public: MessagePortIdentifierRunnable(WorkerPrivate* aWorkerPrivate, - RemoteWorkerChild* aActor, + SelfHolder aActor, const MessagePortIdentifier& aPortIdentifier) : WorkerRunnable(aWorkerPrivate), - mActor(aActor), + mActor(std::move(aActor)), mPortIdentifier(aPortIdentifier) {} private: @@ -114,77 +173,158 @@ class MessagePortIdentifierRunnable final : public WorkerRunnable { return; } - RefPtr mActor; + SelfHolder mActor; MessagePortIdentifier mPortIdentifier; }; -} // namespace - -class RemoteWorkerChild::InitializeWorkerRunnable final - : public WorkerRunnable { +class ReleaseWorkerRunnable final : public WorkerRunnable { public: - InitializeWorkerRunnable(WorkerPrivate* aWorkerPrivate, - RemoteWorkerChild* aActor) - : WorkerRunnable(aWorkerPrivate), mActor(aActor) {} + ReleaseWorkerRunnable(RefPtr aWorkerPrivate, + already_AddRefed aWeakRef) + : WorkerRunnable(aWorkerPrivate), + mWorkerPrivate(std::move(aWorkerPrivate)), + mWeakRef(aWeakRef) {} private: - virtual bool WorkerRun(JSContext* aCx, - WorkerPrivate* aWorkerPrivate) override { - mActor->InitializeOnWorker(aWorkerPrivate); + bool WorkerRun(JSContext*, WorkerPrivate*) override { + mWeakRef = nullptr; + mWorkerPrivate = nullptr; + return true; } nsresult Cancel() override { - mActor->CreationFailedOnAnyThread(); - mActor->ShutdownOnWorker(); + mWeakRef = nullptr; + mWorkerPrivate = nullptr; + + return NS_OK; + } + + RefPtr mWorkerPrivate; + RefPtr mWeakRef; +}; + +} // anonymous namespace + +class RemoteWorkerChild::InitializeWorkerRunnable final + : public WorkerRunnable { + public: + InitializeWorkerRunnable(RefPtr aWorkerPrivate, + SelfHolder aActor) + : WorkerRunnable(aWorkerPrivate), + mWorkerPrivate(std::move(aWorkerPrivate)), + mActor(std::move(aActor)) { + MOZ_ASSERT(mWorkerPrivate); + MOZ_ASSERT(mActor); + } + + private: + ~InitializeWorkerRunnable() { MaybeAbort(); } + + bool WorkerRun(JSContext*, WorkerPrivate*) override { + mActor->InitializeOnWorker(mWorkerPrivate.forget()); + return true; + } + + nsresult Cancel() override { + MaybeAbort(); + return WorkerRunnable::Cancel(); } - RefPtr mActor; + // Slowly running out of synonyms for cancel, abort, terminate, etc... + void MaybeAbort() { + if (!mWorkerPrivate) { + return; + } + + nsCOMPtr target = + SystemGroup::EventTargetFor(TaskCategory::Other); + NS_ProxyRelease("InitializeWorkerRunnable::mWorkerPrivate", target, + mWorkerPrivate.forget()); + + mActor->TransitionStateToTerminated(); + mActor->CreationFailedOnAnyThread(); + mActor->ShutdownOnWorker(); + } + + RefPtr mWorkerPrivate; + SelfHolder mActor; }; -RemoteWorkerChild::RemoteWorkerChild() - : mIPCActive(true), mSharedData("RemoteWorkerChild::mSharedData") { +RemoteWorkerChild::RemoteWorkerChild(const RemoteWorkerData& aData) + : mState(VariantType(), "RemoteWorkerChild::mState"), + mIsServiceWorker(aData.serviceWorkerData().type() == + OptionalServiceWorkerData::TServiceWorkerData), + mOwningEventTarget(GetCurrentThreadSerialEventTarget()) { MOZ_ASSERT(RemoteWorkerService::Thread()->IsOnCurrentThread()); + MOZ_ASSERT(mOwningEventTarget); } -RemoteWorkerChild::SharedData::SharedData() : mWorkerState(ePending) {} - RemoteWorkerChild::~RemoteWorkerChild() { - nsCOMPtr target = - SystemGroup::EventTargetFor(TaskCategory::Other); - - const auto lock = mSharedData.Lock(); - NS_ProxyRelease("RemoteWorkerChild::mWorkerPrivate", target, - lock->mWorkerPrivate.forget()); +#ifdef DEBUG + MOZ_ASSERT(mTerminationPromise.IsEmpty()); + auto lock = mState.Lock(); + MOZ_ASSERT(lock->is()); +#endif } -void RemoteWorkerChild::ActorDestroy(ActorDestroyReason aWhy) { - MOZ_ACCESS_THREAD_BOUND(mLauncherData, data); - mIPCActive = false; - data->mPendingOps.Clear(); +nsISerialEventTarget* RemoteWorkerChild::GetOwningEventTarget() const { + return mOwningEventTarget; +} + +void RemoteWorkerChild::ActorDestroy(ActorDestroyReason) { + Unused << NS_WARN_IF(!mTerminationPromise.IsEmpty()); + mTerminationPromise.RejectIfExists(NS_ERROR_DOM_ABORT_ERR, __func__); + + MOZ_ACCESS_THREAD_BOUND(mLauncherData, launcherData); + launcherData->mIPCActive = false; + + auto lock = mState.Lock(); + + Unused << NS_WARN_IF(!lock->is()); + + if (NS_WARN_IF(lock->is())) { + nsCOMPtr r = NS_NewRunnableFunction( + __func__, + [workerPrivate = std::move(lock->as().mWorkerPrivate), + workerRef = std::move(lock->as().mWorkerRef)]() mutable { + RefPtr r = + new ReleaseWorkerRunnable(workerPrivate, workerRef.forget()); + + Unused << NS_WARN_IF(!r->Dispatch()); + + workerPrivate->Cancel(); + }); + + MOZ_ALWAYS_SUCCEEDS(SystemGroup::Dispatch(TaskCategory::Other, r.forget())); + } + + *lock = VariantType(); } void RemoteWorkerChild::ExecWorker(const RemoteWorkerData& aData) { - MOZ_ASSERT(RemoteWorkerService::Thread()->IsOnCurrentThread()); - MOZ_ASSERT(mIPCActive); +#ifdef DEBUG + MOZ_ASSERT(GetOwningEventTarget()->IsOnCurrentThread()); + MOZ_ACCESS_THREAD_BOUND(mLauncherData, launcherData); + MOZ_ASSERT(launcherData->mIPCActive); +#endif + + SelfHolder self = this; + + nsCOMPtr r = NS_NewRunnableFunction( + __func__, [self = std::move(self), data = aData]() mutable { + nsresult rv = self->ExecWorkerOnMainThread(std::move(data)); - RefPtr self = this; - nsCOMPtr r = - NS_NewRunnableFunction("RemoteWorkerChild::ExecWorker", [self, aData]() { - nsresult rv = self->ExecWorkerOnMainThread(aData); if (NS_WARN_IF(NS_FAILED(rv))) { self->CreationFailedOnAnyThread(); } }); - nsCOMPtr target = - SystemGroup::EventTargetFor(TaskCategory::Other); - target->Dispatch(r.forget(), NS_DISPATCH_NORMAL); + MOZ_ALWAYS_SUCCEEDS(SystemGroup::Dispatch(TaskCategory::Other, r.forget())); } -nsresult RemoteWorkerChild::ExecWorkerOnMainThread( - const RemoteWorkerData& aData) { +nsresult RemoteWorkerChild::ExecWorkerOnMainThread(RemoteWorkerData&& aData) { MOZ_ASSERT(NS_IsMainThread()); // Ensure that the IndexedDatabaseManager is initialized @@ -192,6 +332,8 @@ nsresult RemoteWorkerChild::ExecWorkerOnMainThread( nsresult rv = NS_OK; + auto scopeExit = MakeScopeExit([&] { TransitionStateToTerminated(); }); + nsCOMPtr principal = PrincipalInfoToPrincipal(aData.principalInfo(), &rv); if (NS_WARN_IF(NS_FAILED(rv))) { @@ -264,98 +406,210 @@ nsresult RemoteWorkerChild::ExecWorkerOnMainThread( return rv; } - // 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, - aData.isSharedWorker() ? nsIContentPolicy::TYPE_INTERNAL_SHARED_WORKER - : nsIContentPolicy::TYPE_INTERNAL_SERVICE_WORKER, - info.mCookieSettings, info.mReferrerInfo, getter_AddRefs(info.mChannel)); - if (NS_WARN_IF(NS_FAILED(rv))) { - return rv; + if (mIsServiceWorker) { + ServiceWorkerData& data = aData.serviceWorkerData().get_ServiceWorkerData(); + + nsCOMPtr permissionManager = + services::GetPermissionManager(); + + for (auto& keyAndPermissions : data.permissionsByKey()) { + permissionManager->SetPermissionsWithKey(keyAndPermissions.key(), + keyAndPermissions.permissions()); + } + + info.mServiceWorkerCacheName = data.cacheName(); + info.mServiceWorkerDescriptor.emplace(data.descriptor()); + info.mServiceWorkerRegistrationDescriptor.emplace( + data.registrationDescriptor()); + info.mLoadFlags = static_cast(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.mCookieSettings, + info.mReferrerInfo, getter_AddRefs(info.mChannel)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } } AutoJSAPI jsapi; jsapi.Init(); ErrorResult error; - const auto lock = mSharedData.Lock(); - lock->mWorkerPrivate = WorkerPrivate::Constructor( + RefPtr workerPrivate = WorkerPrivate::Constructor( jsapi.cx(), aData.originalScriptURL(), false, - aData.isSharedWorker() ? WorkerTypeShared : WorkerTypeService, - aData.name(), VoidCString(), &info, error); + mIsServiceWorker ? WorkerTypeService : WorkerTypeShared, aData.name(), + VoidCString(), &info, error); + if (NS_WARN_IF(error.Failed())) { - return error.StealNSResult(); + MOZ_ASSERT(!workerPrivate); + + rv = error.StealNSResult(); + return rv; + } + + if (mIsServiceWorker) { + } else { + workerPrivate->SetRemoteWorkerController(this); } RefPtr runnable = - new InitializeWorkerRunnable(lock->mWorkerPrivate, this); + new InitializeWorkerRunnable(std::move(workerPrivate), SelfHolder(this)); + if (NS_WARN_IF(!runnable->Dispatch())) { - return NS_ERROR_FAILURE; + rv = NS_ERROR_FAILURE; + return rv; } - lock->mWorkerPrivate->SetRemoteWorkerController(this); + scopeExit.release(); + return NS_OK; } -void RemoteWorkerChild::InitializeOnWorker(WorkerPrivate* aWorkerPrivate) { - MOZ_ASSERT(aWorkerPrivate); - aWorkerPrivate->AssertIsOnWorkerThread(); - - RefPtr self = this; +void RemoteWorkerChild::InitializeOnWorker( + already_AddRefed aWorkerPrivate) { { - const auto lock = mSharedData.Lock(); - mWorkerRef = WeakWorkerRef::Create(lock->mWorkerPrivate, - [self]() { self->ShutdownOnWorker(); }); + auto lock = mState.Lock(); + + if (lock->is()) { + nsCOMPtr target = + SystemGroup::EventTargetFor(TaskCategory::Other); + NS_ProxyRelease(__func__, target, std::move(aWorkerPrivate)); + + TransitionStateToTerminated(lock.ref()); + ShutdownOnWorker(); + return; + } } - if (NS_WARN_IF(!mWorkerRef)) { + RefPtr workerPrivate = aWorkerPrivate; + MOZ_ASSERT(workerPrivate); + workerPrivate->AssertIsOnWorkerThread(); + + RefPtr self = this; + ThreadSafeWeakPtr selfWeakRef(self); + + auto scopeExit = MakeScopeExit([&] { + MOZ_ASSERT(self); + + NS_ProxyRelease(__func__, mOwningEventTarget, self.forget()); + }); + + RefPtr workerRef = WeakWorkerRef::Create( + workerPrivate, [selfWeakRef = std::move(selfWeakRef)]() mutable { + RefPtr self(selfWeakRef); + + if (NS_WARN_IF(!self)) { + return; + } + + self->TransitionStateToTerminated(); + self->ShutdownOnWorker(); + + nsCOMPtr 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() { - const auto lock = mSharedData.Lock(); - MOZ_ASSERT(lock->mWorkerPrivate); - lock->mWorkerPrivate->AssertIsOnWorkerThread(); - - // This will release the worker. - mWorkerRef = nullptr; - - nsCOMPtr target = - SystemGroup::EventTargetFor(TaskCategory::Other); - - NS_ProxyRelease("RemoteWorkerChild::mWorkerPrivate", target, - lock->mWorkerPrivate.forget()); - RefPtr self = this; - nsCOMPtr r = - NS_NewRunnableFunction("RemoteWorkerChild::ShutdownOnWorker", - [self]() { self->WorkerTerminated(); }); - RemoteWorkerService::Thread()->Dispatch(r.forget(), NS_DISPATCH_NORMAL); + nsCOMPtr r = + NS_NewRunnableFunction(__func__, [self = std::move(self)] { + MOZ_ACCESS_THREAD_BOUND(self->mLauncherData, launcherData); + + if (!launcherData->mIPCActive) { + return; + } + + launcherData->mIPCActive = false; + Unused << self->SendClose(); + }); + + GetOwningEventTarget()->Dispatch(r.forget(), NS_DISPATCH_NORMAL); } -void RemoteWorkerChild::WorkerTerminated() { - MOZ_ACCESS_THREAD_BOUND(mLauncherData, data); +RefPtr RemoteWorkerChild::GetTerminationPromise() { + MOZ_ASSERT(GetOwningEventTarget()->IsOnCurrentThread()); - { - const auto lock = mSharedData.Lock(); - lock->mWorkerState = eTerminated; - } - data->mPendingOps.Clear(); + return mTerminationPromise.Ensure(__func__); +} - if (!mIPCActive) { +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()); + MOZ_ASSERT_IF(!aDidCreationSucceed, lock->is()); +#endif + + RefPtr self = this; + + nsCOMPtr r = NS_NewRunnableFunction( + __func__, + [self = std::move(self), didCreationSucceed = aDidCreationSucceed] { + MOZ_ACCESS_THREAD_BOUND(self->mLauncherData, launcherData); + + if (!launcherData->mIPCActive) { + return; + } + + Unused << self->SendCreated(didCreationSucceed); + }); + + Unused << GetOwningEventTarget()->Dispatch(r.forget(), NS_DISPATCH_NORMAL); +} + +void RemoteWorkerChild::CloseWorkerOnMainThread(State& aState) { + AssertIsOnMainThread(); + MOZ_ASSERT(!aState.is()); + + if (aState.is()) { + TransitionStateToPendingTerminated(aState); return; } - Unused << SendClose(); - mIPCActive = false; + // The holder will be notified by this. + if (aState.is()) { + aState.as().mWorkerPrivate->Cancel(); + } +} + +/** + * Error reporting method + */ +void RemoteWorkerChild::ErrorPropagation(const ErrorValue& aValue) { + MOZ_ASSERT(GetOwningEventTarget()->IsOnCurrentThread()); + + MOZ_ACCESS_THREAD_BOUND(mLauncherData, launcherData); + + if (!launcherData->mIPCActive) { + return; + } + + Unused << SendError(aValue); } void RemoteWorkerChild::ErrorPropagationDispatch(nsresult aError) { @@ -364,14 +618,14 @@ void RemoteWorkerChild::ErrorPropagationDispatch(nsresult aError) { RefPtr self = this; nsCOMPtr r = NS_NewRunnableFunction( "RemoteWorkerChild::ErrorPropagationDispatch", - [self, aError]() { self->ErrorPropagation(aError); }); + [self = std::move(self), aError]() { self->ErrorPropagation(aError); }); - RemoteWorkerService::Thread()->Dispatch(r.forget(), NS_DISPATCH_NORMAL); + GetOwningEventTarget()->Dispatch(r.forget(), NS_DISPATCH_NORMAL); } void RemoteWorkerChild::ErrorPropagationOnMainThread( const WorkerErrorReport* aReport, bool aIsErrorEvent) { - MOZ_ASSERT(NS_IsMainThread()); + AssertIsOnMainThread(); ErrorValue value; if (aIsErrorEvent) { @@ -393,47 +647,14 @@ void RemoteWorkerChild::ErrorPropagationOnMainThread( RefPtr self = this; nsCOMPtr r = NS_NewRunnableFunction( "RemoteWorkerChild::ErrorPropagationOnMainThread", - [self, value]() { self->ErrorPropagation(value); }); + [self = std::move(self), value]() { self->ErrorPropagation(value); }); - RemoteWorkerService::Thread()->Dispatch(r.forget(), NS_DISPATCH_NORMAL); -} - -void RemoteWorkerChild::ErrorPropagation(const ErrorValue& aValue) { - MOZ_ASSERT(RemoteWorkerService::Thread()->IsOnCurrentThread()); - - if (!mIPCActive) { - return; - } - - Unused << SendError(aValue); -} - -void RemoteWorkerChild::CloseWorkerOnMainThread() { - MOZ_ASSERT(NS_IsMainThread()); - const auto lock = mSharedData.Lock(); - - if (lock->mWorkerState == ePending) { - lock->mWorkerState = ePendingTerminated; - // Already released. - return; - } - - // The holder will be notified by this. - if (lock->mWorkerState == eRunning) { - // FIXME: mWorkerState transition and each state's associated data should - // be improved/fixed in bug 1231213. `!lock->mWorkerPrivate` implies that - // the worker state is effectively `eTerminated.` - if (!lock->mWorkerPrivate) { - return; - } - - lock->mWorkerPrivate->Cancel(); - } + GetOwningEventTarget()->Dispatch(r.forget(), NS_DISPATCH_NORMAL); } void RemoteWorkerChild::FlushReportsOnMainThread( nsIConsoleReportCollector* aReporter) { - MOZ_ASSERT(NS_IsMainThread()); + AssertIsOnMainThread(); bool reportErrorToBrowserConsole = true; @@ -453,122 +674,208 @@ void RemoteWorkerChild::FlushReportsOnMainThread( aReporter->ClearConsoleReports(); } -IPCResult RemoteWorkerChild::RecvExecOp(const RemoteWorkerOp& aOp) { - const auto lock = mSharedData.Lock(); - return ExecuteOperation(aOp, lock); +/** + * Worker state transition methods + */ +RemoteWorkerChild::Running::~Running() { + if (!mWorkerPrivate) { + return; + } + + nsCOMPtr target = + SystemGroup::EventTargetFor(TaskCategory::Other); + NS_ProxyRelease("RemoteWorkerChild::Running::mWorkerPrivate", target, + mWorkerPrivate.forget()); } -template -IPCResult RemoteWorkerChild::ExecuteOperation(const RemoteWorkerOp& aOp, - const T& aLock) { - MOZ_ACCESS_THREAD_BOUND(mLauncherData, data); +void RemoteWorkerChild::TransitionStateToPendingTerminated(State& aState) { + MOZ_ASSERT(aState.is()); - if (!mIPCActive) { - return IPC_OK(); + CancelAllPendingOps(aState); + + aState = VariantType(); +} + +void RemoteWorkerChild::TransitionStateToRunning( + already_AddRefed aWorkerPrivate, + already_AddRefed aWorkerRef) { + RefPtr workerPrivate = aWorkerPrivate; + MOZ_ASSERT(workerPrivate); + + RefPtr workerRef = aWorkerRef; + MOZ_ASSERT(workerRef); + + auto lock = mState.Lock(); + + MOZ_ASSERT(lock->is()); + + auto pendingOps = std::move(lock->as().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(); + lock->as().mWorkerPrivate = std::move(workerPrivate); + lock->as().mWorkerRef = std::move(workerRef); + + SelfHolder self = this; + + nsCOMPtr r = NS_NewRunnableFunction( + __func__, [pendingOps = std::move(pendingOps), self = std::move(self)]() { + for (auto& op : pendingOps) { + auto lock = self->mState.Lock(); + + DebugOnly 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()) { + CancelAllPendingOps(aState); } - // The worker is not ready yet. - if (aLock->mWorkerState == ePending) { - data->mPendingOps.AppendElement(aOp); - return IPC_OK(); - } + mTerminationPromise.ResolveIfExists(true, __func__); - if (aLock->mWorkerState == eTerminated || - aLock->mWorkerState == ePendingTerminated) { - // No op. - return IPC_OK(); - } + aState = VariantType(); +} - MOZ_ASSERT(aLock->mWorkerState == eRunning); +/** + * 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); + + MOZ_ACCESS_THREAD_BOUND(aOwner->mLauncherData, launcherData); + + if (NS_WARN_IF(!launcherData->mIPCActive)) { + Unused << NS_WARN_IF(!aState.is()); + +#ifdef DEBUG + mStarted = true; +#endif + + return true; + } + + if (aState.is() && !IsTerminationOp()) { + return false; + } + + if (aState.is() || aState.is()) { +#ifdef DEBUG + mStarted = true; +#endif + + return true; + } + + MOZ_ASSERT(aState.is() || IsTerminationOp()); + + RefPtr self = this; + SelfHolder owner = aOwner; - // Main-thread operations - if (aOp.type() == RemoteWorkerOp::TRemoteWorkerSuspendOp || - aOp.type() == RemoteWorkerOp::TRemoteWorkerResumeOp || - aOp.type() == RemoteWorkerOp::TRemoteWorkerFreezeOp || - aOp.type() == RemoteWorkerOp::TRemoteWorkerThawOp || - aOp.type() == RemoteWorkerOp::TRemoteWorkerTerminateOp || - aOp.type() == RemoteWorkerOp::TRemoteWorkerAddWindowIDOp || - aOp.type() == RemoteWorkerOp::TRemoteWorkerRemoveWindowIDOp) { - RefPtr self = this; nsCOMPtr r = NS_NewRunnableFunction( - "RemoteWorkerChild::RecvExecOp", - [self, aOp]() { self->RecvExecOpOnMainThread(aOp); }); + __func__, [self = std::move(self), owner = std::move(owner)]() mutable { + self->Exec(owner); + }); - nsCOMPtr target = - SystemGroup::EventTargetFor(TaskCategory::Other); - target->Dispatch(r.forget(), NS_DISPATCH_NORMAL); - return IPC_OK(); + MOZ_ALWAYS_SUCCEEDS(SystemGroup::Dispatch(TaskCategory::Other, r.forget())); + +#ifdef DEBUG + mStarted = true; +#endif + + return true; } - if (aOp.type() == RemoteWorkerOp::TRemoteWorkerPortIdentifierOp) { - const RemoteWorkerPortIdentifierOp& op = - aOp.get_RemoteWorkerPortIdentifierOp(); - RefPtr runnable = - new MessagePortIdentifierRunnable(aLock->mWorkerPrivate, this, - op.portIdentifier()); - if (NS_WARN_IF(!runnable->Dispatch())) { - ErrorPropagation(NS_ERROR_FAILURE); - } - return IPC_OK(); + void Cancel() override { + MOZ_ASSERT(!mStarted); + +#ifdef DEBUG + mStarted = true; +#endif } - MOZ_CRASH("Unknown operation."); + private: + ~SharedWorkerOp() { MOZ_ASSERT(mStarted); } - return IPC_OK(); -} + bool IsTerminationOp() const { + return mOp.type() == RemoteWorkerOp::TRemoteWorkerTerminateOp; + } -void RemoteWorkerChild::RecvExecOpOnMainThread(const RemoteWorkerOp& aOp) { - MOZ_ASSERT(NS_IsMainThread()); + void Exec(SelfHolder& aOwner) { + using Running = RemoteWorkerChild::Running; - { - const auto lock = mSharedData.Lock(); + AssertIsOnMainThread(); - if (aOp.type() == RemoteWorkerOp::TRemoteWorkerSuspendOp) { - if (lock->mWorkerPrivate) { - lock->mWorkerPrivate->ParentWindowPaused(); - } + auto lock = aOwner->mState.Lock(); + + MOZ_ASSERT(lock->is() || IsTerminationOp()); + + if (IsTerminationOp()) { + aOwner->CloseWorkerOnMainThread(lock.ref()); return; } - if (aOp.type() == RemoteWorkerOp::TRemoteWorkerResumeOp) { - if (lock->mWorkerPrivate) { - lock->mWorkerPrivate->ParentWindowResumed(); - } - return; - } + RefPtr workerPrivate = lock->as().mWorkerPrivate; - if (aOp.type() == RemoteWorkerOp::TRemoteWorkerFreezeOp) { - if (lock->mWorkerPrivate) { - lock->mWorkerPrivate->Freeze(nullptr); - } - return; - } + MOZ_ASSERT(workerPrivate); - if (aOp.type() == RemoteWorkerOp::TRemoteWorkerThawOp) { - if (lock->mWorkerPrivate) { - lock->mWorkerPrivate->Thaw(nullptr); + 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 r = + new MessagePortIdentifierRunnable( + workerPrivate, aOwner, + mOp.get_RemoteWorkerPortIdentifierOp().portIdentifier()); + + if (NS_WARN_IF(!r->Dispatch())) { + aOwner->ErrorPropagation(NS_ERROR_FAILURE); } - return; + } 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!"); } } - if (aOp.type() == RemoteWorkerOp::TRemoteWorkerTerminateOp) { - CloseWorkerOnMainThread(); - return; - } + RemoteWorkerOp mOp; - if (aOp.type() == RemoteWorkerOp::TRemoteWorkerAddWindowIDOp) { - mWindowIDs.AppendElement(aOp.get_RemoteWorkerAddWindowIDOp().windowID()); - return; - } - - if (aOp.type() == RemoteWorkerOp::TRemoteWorkerRemoveWindowIDOp) { - mWindowIDs.RemoveElement(aOp.get_RemoteWorkerRemoveWindowIDOp().windowID()); - return; - } - - MOZ_CRASH("No other operations should be scheduled on main-thread."); -} +#ifdef DEBUG + bool mStarted = false; +#endif +}; void RemoteWorkerChild::AddPortIdentifier( JSContext* aCx, WorkerPrivate* aWorkerPrivate, @@ -578,68 +885,32 @@ void RemoteWorkerChild::AddPortIdentifier( } } -void RemoteWorkerChild::CreationSucceededOnAnyThread() { - RefPtr self = this; - nsCOMPtr r = - NS_NewRunnableFunction("RemoteWorkerChild::CreationSucceededOnAnyThread", - [self]() { self->CreationSucceeded(); }); +void RemoteWorkerChild::CancelAllPendingOps(State& aState) { + MOZ_ASSERT(aState.is()); - RemoteWorkerService::Thread()->Dispatch(r.forget(), NS_DISPATCH_NORMAL); + auto pendingOps = std::move(aState.as().mPendingOps); + + for (auto& op : pendingOps) { + op->Cancel(); + } } -void RemoteWorkerChild::CreationSucceeded() { - MOZ_ACCESS_THREAD_BOUND(mLauncherData, data); +void RemoteWorkerChild::MaybeStartOp(RefPtr&& aOp) { + MOZ_ASSERT(aOp); - // The worker is created but we need to terminate it already. - const auto lock = mSharedData.Lock(); - if (lock->mWorkerState == ePendingTerminated) { - RefPtr self = this; - nsCOMPtr r = - NS_NewRunnableFunction("RemoteWorkerChild::CreationSucceeded", - [self]() { self->CloseWorkerOnMainThread(); }); + auto lock = mState.Lock(); - nsCOMPtr target = - SystemGroup::EventTargetFor(TaskCategory::Other); - target->Dispatch(r.forget(), NS_DISPATCH_NORMAL); - return; + if (!aOp->MaybeStart(this, lock.ref())) { + lock->as().mPendingOps.AppendElement(std::move(aOp)); } - - lock->mWorkerState = eRunning; - - if (!mIPCActive) { - return; - } - - for (const RemoteWorkerOp& op : data->mPendingOps) { - ExecuteOperation(op, lock); - } - - data->mPendingOps.Clear(); - - Unused << SendCreated(true); } -void RemoteWorkerChild::CreationFailedOnAnyThread() { - RefPtr self = this; - nsCOMPtr r = - NS_NewRunnableFunction("RemoteWorkerChild::CreationFailedOnAnyThread", - [self]() { self->CreationFailed(); }); +IPCResult RemoteWorkerChild::RecvExecOp(RemoteWorkerOp&& aOp) { + MOZ_ASSERT(!mIsServiceWorker); - RemoteWorkerService::Thread()->Dispatch(r.forget(), NS_DISPATCH_NORMAL); -} + MaybeStartOp(new SharedWorkerOp(std::move(aOp))); -void RemoteWorkerChild::CreationFailed() { - MOZ_ACCESS_THREAD_BOUND(mLauncherData, data); - - const auto lock = mSharedData.Lock(); - lock->mWorkerState = eTerminated; - data->mPendingOps.Clear(); - - if (!mIPCActive) { - return; - } - - Unused << SendCreated(false); + return IPC_OK(); } } // namespace dom diff --git a/dom/workers/remoteworkers/RemoteWorkerChild.h b/dom/workers/remoteworkers/RemoteWorkerChild.h index 8325b362974c..3f44c8875b89 100644 --- a/dom/workers/remoteworkers/RemoteWorkerChild.h +++ b/dom/workers/remoteworkers/RemoteWorkerChild.h @@ -7,114 +7,141 @@ #ifndef mozilla_dom_RemoteWorkerChild_h #define mozilla_dom_RemoteWorkerChild_h -#include "mozilla/dom/PRemoteWorkerChild.h" -#include "mozilla/DataMutex.h" -#include "mozilla/ThreadBound.h" -#include "mozilla/UniquePtr.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 { namespace dom { +class ErrorValue; class RemoteWorkerData; class WeakWorkerRef; class WorkerErrorReport; class WorkerPrivate; -class OptionalMessagePortIdentifier; -class RemoteWorkerChild final : public PRemoteWorkerChild { +class RemoteWorkerChild final + : public SupportsThreadSafeWeakPtr, + public PRemoteWorkerChild { friend class PRemoteWorkerChild; public: - NS_INLINE_DECL_THREADSAFE_REFCOUNTING(RemoteWorkerChild) + MOZ_DECLARE_THREADSAFEWEAKREFERENCE_TYPENAME(RemoteWorkerChild) - RemoteWorkerChild(); + MOZ_DECLARE_REFCOUNTED_TYPENAME(RemoteWorkerChild) + + explicit RemoteWorkerChild(const RemoteWorkerData& aData); + + ~RemoteWorkerChild(); + + nsISerialEventTarget* GetOwningEventTarget() const; void ExecWorker(const RemoteWorkerData& aData); - void InitializeOnWorker(WorkerPrivate* aWorkerPrivate); - - void ShutdownOnWorker(); - - void AddPortIdentifier(JSContext* aCx, WorkerPrivate* aWorkerPrivate, - const MessagePortIdentifier& aPortIdentifier); - void ErrorPropagationOnMainThread(const WorkerErrorReport* aReport, bool aIsErrorEvent); - void CloseWorkerOnMainThread(); - void FlushReportsOnMainThread(nsIConsoleReportCollector* aReporter); + void AddPortIdentifier(JSContext* aCx, WorkerPrivate* aWorkerPrivate, + const MessagePortIdentifier& aPortIdentifier); + + RefPtr GetTerminationPromise(); + + void CloseWorkerOnMainThread(); + private: class InitializeWorkerRunnable; - ~RemoteWorkerChild(); + class Op; + class SharedWorkerOp; - void ActorDestroy(ActorDestroyReason aWhy) override; + struct Pending { + nsTArray> mPendingOps; + }; - mozilla::ipc::IPCResult RecvExecOp(const RemoteWorkerOp& aOp); + struct PendingTerminated {}; - // This member is a function template because DataMutex::AutoLock - // is private, yet it must be passed by const reference into ExecuteOperation. - // There should only be one instantiation of this template. - template - mozilla::ipc::IPCResult ExecuteOperation(const RemoteWorkerOp&, - const T& aLock); + struct Running { + ~Running(); - void RecvExecOpOnMainThread(const RemoteWorkerOp& aOp); + RefPtr mWorkerPrivate; + RefPtr mWorkerRef; + }; - nsresult ExecWorkerOnMainThread(const RemoteWorkerData& aData); + struct Terminated {}; + + using State = Variant; + + DataMutex 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); + + nsresult ExecWorkerOnMainThread(RemoteWorkerData&& aData); + + void InitializeOnWorker(already_AddRefed aWorkerPrivate); + + void ShutdownOnWorker(); + + void CreationSucceededOnAnyThread(); + + void CreationFailedOnAnyThread(); + + void CreationSucceededOrFailedOnAnyThread(bool aDidCreationSucceed); + + void CloseWorkerOnMainThread(State& aState); void ErrorPropagation(const ErrorValue& aValue); void ErrorPropagationDispatch(nsresult aError); - void CreationSucceededOnAnyThread(); + void TransitionStateToPendingTerminated(State& aState); - void CreationSucceeded(); + void TransitionStateToRunning(already_AddRefed aWorkerPrivate, + already_AddRefed aWorkerRef); - void CreationFailedOnAnyThread(); + void TransitionStateToTerminated(); - void CreationFailed(); + void TransitionStateToTerminated(State& aState); - void WorkerTerminated(); + void CancelAllPendingOps(State& aState); + + void MaybeStartOp(RefPtr&& aOp); + + MozPromiseHolder mTerminationPromise; + + const bool mIsServiceWorker; + const nsCOMPtr mOwningEventTarget; // Touched on main-thread only. nsTArray mWindowIDs; - RefPtr mWorkerRef; - bool mIPCActive; - - enum WorkerState { - // CreationSucceeded/CreationFailed not called yet. - ePending, - - // The worker is not created yet, but we want to terminate as soon as - // possible. - ePendingTerminated, - - // Worker up and running. - eRunning, - - // Worker terminated. - eTerminated, - }; - - struct SharedData { - SharedData(); - - RefPtr mWorkerPrivate; - WorkerState mWorkerState; - }; - - DataMutex mSharedData; - - // Touched only on the owning thread (Worker Launcher). struct LauncherBoundData { - nsTArray mPendingOps; + bool mIPCActive = true; }; ThreadBound mLauncherData; diff --git a/dom/workers/remoteworkers/RemoteWorkerParent.cpp b/dom/workers/remoteworkers/RemoteWorkerParent.cpp index 3d05ed4fa831..fe25ba644fea 100644 --- a/dom/workers/remoteworkers/RemoteWorkerParent.cpp +++ b/dom/workers/remoteworkers/RemoteWorkerParent.cpp @@ -136,7 +136,8 @@ IPCResult RemoteWorkerParent::RecvClose() { mController->WorkerTerminated(); } - Unused << Send__delete__(this); + MaybeSendDelete(); + return IPC_OK(); } diff --git a/dom/workers/remoteworkers/RemoteWorkerTypes.ipdlh b/dom/workers/remoteworkers/RemoteWorkerTypes.ipdlh index bacd987277c1..f7475de74001 100644 --- a/dom/workers/remoteworkers/RemoteWorkerTypes.ipdlh +++ b/dom/workers/remoteworkers/RemoteWorkerTypes.ipdlh @@ -66,7 +66,6 @@ struct RemoteWorkerData StorageAccess storageAccess; - bool isSharedWorker; OptionalServiceWorkerData serviceWorkerData; }; diff --git a/ipc/glue/BackgroundChildImpl.cpp b/ipc/glue/BackgroundChildImpl.cpp index 4a6a17f4c333..4b20ed6ba73b 100644 --- a/ipc/glue/BackgroundChildImpl.cpp +++ b/ipc/glue/BackgroundChildImpl.cpp @@ -325,7 +325,7 @@ bool BackgroundChildImpl::DeallocPPendingIPCBlobChild( dom::PRemoteWorkerChild* BackgroundChildImpl::AllocPRemoteWorkerChild( const RemoteWorkerData& aData) { - RefPtr agent = new dom::RemoteWorkerChild(); + RefPtr agent = new dom::RemoteWorkerChild(aData); return agent.forget().take(); }