diff --git a/dom/serviceworkers/moz.build b/dom/serviceworkers/moz.build index 4f711f902c4c..40d91255a029 100644 --- a/dom/serviceworkers/moz.build +++ b/dom/serviceworkers/moz.build @@ -11,6 +11,7 @@ with Files("**"): EXPORTS.mozilla.dom += [ 'ServiceWorker.h', 'ServiceWorkerActors.h', + 'ServiceWorkerCloneData.h', 'ServiceWorkerContainer.h', 'ServiceWorkerDescriptor.h', 'ServiceWorkerEvents.h', diff --git a/dom/workers/remoteworkers/RemoteWorkerController.cpp b/dom/workers/remoteworkers/RemoteWorkerController.cpp index a81b8e002326..df927ea27ddf 100644 --- a/dom/workers/remoteworkers/RemoteWorkerController.cpp +++ b/dom/workers/remoteworkers/RemoteWorkerController.cpp @@ -4,11 +4,20 @@ * 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 "mozilla/dom/MessagePort.h" +#include "RemoteWorkerController.h" + +#include + +#include "nsDebug.h" + +#include "mozilla/Assertions.h" +#include "mozilla/DebugOnly.h" +#include "mozilla/ScopeExit.h" #include "mozilla/dom/MessagePortParent.h" #include "mozilla/dom/RemoteWorkerTypes.h" +#include "mozilla/dom/ServiceWorkerCloneData.h" #include "mozilla/ipc/BackgroundParent.h" -#include "RemoteWorkerController.h" +#include "RemoteWorkerControllerParent.h" #include "RemoteWorkerManager.h" #include "RemoteWorkerParent.h" @@ -22,8 +31,8 @@ namespace dom { already_AddRefed RemoteWorkerController::Create( const RemoteWorkerData& aData, RemoteWorkerObserver* aObserver, base::ProcessId aProcessId) { + AssertIsInMainProcess(); AssertIsOnBackgroundThread(); - MOZ_ASSERT(XRE_IsParentProcess()); MOZ_ASSERT(aObserver); RefPtr controller = @@ -45,23 +54,36 @@ RemoteWorkerController::RemoteWorkerController(const RemoteWorkerData& aData, OptionalServiceWorkerData::TServiceWorkerData) { AssertIsInMainProcess(); AssertIsOnBackgroundThread(); - MOZ_ASSERT(XRE_IsParentProcess()); } RemoteWorkerController::~RemoteWorkerController() { AssertIsOnBackgroundThread(); - MOZ_ASSERT(XRE_IsParentProcess()); + MOZ_DIAGNOSTIC_ASSERT(mPendingOps.IsEmpty()); } void RemoteWorkerController::SetWorkerActor(RemoteWorkerParent* aActor) { AssertIsOnBackgroundThread(); - MOZ_ASSERT(XRE_IsParentProcess()); 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()); @@ -74,13 +96,13 @@ void RemoteWorkerController::CreationFailed() { return; } - Shutdown(); + NoteDeadWorker(); + mObserver->CreationFailed(); } void RemoteWorkerController::CreationSucceeded() { AssertIsOnBackgroundThread(); - MOZ_ASSERT(XRE_IsParentProcess()); MOZ_ASSERT(mState == ePending || mState == eTerminated); if (mState == eTerminated) { @@ -95,238 +117,301 @@ void RemoteWorkerController::CreationSucceeded() { mObserver->CreationSucceeded(); - for (UniquePtr& op : mPendingOps) { - switch (op->mType) { - case Op::eTerminate: - Terminate(); - break; + auto pendingOps = std::move(mPendingOps); - case Op::eSuspend: - Suspend(); - break; - - case Op::eResume: - Resume(); - break; - - case Op::eFreeze: - Freeze(); - break; - - case Op::eThaw: - Thaw(); - break; - - case Op::ePortIdentifier: - AddPortIdentifier(op->mPortIdentifier); - break; - - case Op::eAddWindowID: - AddWindowID(op->mWindowID); - break; - - case Op::eRemoveWindowID: - RemoveWindowID(op->mWindowID); - break; - - default: - MOZ_CRASH("Unknown op."); - } - - op->Completed(); + for (auto& op : pendingOps) { + DebugOnly started = op->MaybeStart(this); + MOZ_ASSERT(started); } - - mPendingOps.Clear(); } void RemoteWorkerController::ErrorPropagation(const ErrorValue& aValue) { AssertIsOnBackgroundThread(); - MOZ_ASSERT(XRE_IsParentProcess()); mObserver->ErrorReceived(aValue); } void RemoteWorkerController::WorkerTerminated() { AssertIsOnBackgroundThread(); - MOZ_ASSERT(XRE_IsParentProcess()); - MOZ_ASSERT(mState == eReady); + + NoteDeadWorker(); mObserver->Terminated(); - Shutdown(); +} + +void RemoteWorkerController::CancelAllPendingOps() { + AssertIsOnBackgroundThread(); + + auto pendingOps = std::move(mPendingOps); + + for (auto& op : pendingOps) { + op->Cancel(); + } } void RemoteWorkerController::Shutdown() { AssertIsOnBackgroundThread(); - MOZ_ASSERT(XRE_IsParentProcess()); - MOZ_ASSERT(mState == ePending || mState == eReady); + Unused << NS_WARN_IF(mIsServiceWorker && !mPendingOps.IsEmpty()); + + if (mState == eTerminated) { + MOZ_ASSERT(mPendingOps.IsEmpty()); + return; + } mState = eTerminated; - mPendingOps.Clear(); + CancelAllPendingOps(); - if (mActor) { - mActor->SetController(nullptr); + 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; + } + + 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 +void RemoteWorkerController::MaybeStartSharedWorkerOp(Args&&... aArgs) { + AssertIsOnBackgroundThread(); + MOZ_ASSERT(!mIsServiceWorker); + + UniquePtr op = + MakeUnique(std::forward(aArgs)...); + + if (!op->MaybeStart(this)) { + mPendingOps.AppendElement(std::move(op)); } } void RemoteWorkerController::AddWindowID(uint64_t aWindowID) { AssertIsOnBackgroundThread(); - MOZ_ASSERT(XRE_IsParentProcess()); MOZ_ASSERT(aWindowID); - if (mState == ePending) { - mPendingOps.AppendElement(new Op(Op::eAddWindowID, aWindowID)); - return; - } - - if (mState == eTerminated) { - return; - } - - MOZ_ASSERT(mState == eReady); - Unused << mActor->SendExecOp(RemoteWorkerAddWindowIDOp(aWindowID)); + MaybeStartSharedWorkerOp(PendingSharedWorkerOp::eAddWindowID, aWindowID); } void RemoteWorkerController::RemoveWindowID(uint64_t aWindowID) { AssertIsOnBackgroundThread(); - MOZ_ASSERT(XRE_IsParentProcess()); MOZ_ASSERT(aWindowID); - if (mState == ePending) { - mPendingOps.AppendElement(new Op(Op::eRemoveWindowID, aWindowID)); - return; - } - - if (mState == eTerminated) { - return; - } - - MOZ_ASSERT(mState == eReady); - Unused << mActor->SendExecOp(RemoteWorkerRemoveWindowIDOp(aWindowID)); + MaybeStartSharedWorkerOp(PendingSharedWorkerOp::eRemoveWindowID, aWindowID); } void RemoteWorkerController::AddPortIdentifier( const MessagePortIdentifier& aPortIdentifier) { AssertIsOnBackgroundThread(); - MOZ_ASSERT(XRE_IsParentProcess()); - if (mState == ePending) { - mPendingOps.AppendElement(new Op(aPortIdentifier)); - return; - } - - if (mState == eTerminated) { - return; - } - - MOZ_ASSERT(mState == eReady); - Unused << mActor->SendExecOp(RemoteWorkerPortIdentifierOp(aPortIdentifier)); -} - -void RemoteWorkerController::ForgetActorAndTerminate() { - AssertIsOnBackgroundThread(); - MOZ_ASSERT(XRE_IsParentProcess()); - - // The actor has been destroyed without a proper close() notification. Let's - // inform the observer. - if (mState == eReady) { - mObserver->Terminated(); - } - - mActor = nullptr; - Terminate(); + MaybeStartSharedWorkerOp(aPortIdentifier); } void RemoteWorkerController::Terminate() { AssertIsOnBackgroundThread(); - MOZ_ASSERT(XRE_IsParentProcess()); - if (mState == eTerminated) { - return; - } - - Shutdown(); + MaybeStartSharedWorkerOp(PendingSharedWorkerOp::eTerminate); } void RemoteWorkerController::Suspend() { AssertIsOnBackgroundThread(); - MOZ_ASSERT(XRE_IsParentProcess()); - if (mState == ePending) { - mPendingOps.AppendElement(new Op(Op::eSuspend)); - return; - } - - if (mState == eTerminated) { - return; - } - - MOZ_ASSERT(mState == eReady); - Unused << mActor->SendExecOp(RemoteWorkerSuspendOp()); + MaybeStartSharedWorkerOp(PendingSharedWorkerOp::eSuspend); } void RemoteWorkerController::Resume() { AssertIsOnBackgroundThread(); - MOZ_ASSERT(XRE_IsParentProcess()); - if (mState == ePending) { - mPendingOps.AppendElement(new Op(Op::eResume)); - return; - } - - if (mState == eTerminated) { - return; - } - - MOZ_ASSERT(mState == eReady); - Unused << mActor->SendExecOp(RemoteWorkerResumeOp()); + MaybeStartSharedWorkerOp(PendingSharedWorkerOp::eResume); } void RemoteWorkerController::Freeze() { AssertIsOnBackgroundThread(); - MOZ_ASSERT(XRE_IsParentProcess()); - if (mState == ePending) { - mPendingOps.AppendElement(new Op(Op::eFreeze)); - return; - } - - if (mState == eTerminated) { - return; - } - - MOZ_ASSERT(mState == eReady); - Unused << mActor->SendExecOp(RemoteWorkerFreezeOp()); + MaybeStartSharedWorkerOp(PendingSharedWorkerOp::eFreeze); } void RemoteWorkerController::Thaw() { AssertIsOnBackgroundThread(); - MOZ_ASSERT(XRE_IsParentProcess()); - if (mState == ePending) { - mPendingOps.AppendElement(new Op(Op::eThaw)); - return; - } - - if (mState == eTerminated) { - return; - } - - MOZ_ASSERT(mState == eReady); - Unused << mActor->SendExecOp(RemoteWorkerThawOp()); + MaybeStartSharedWorkerOp(PendingSharedWorkerOp::eThaw); } -RemoteWorkerController::Op::~Op() { - MOZ_COUNT_DTOR(Op); +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 (!mCompleted && mType == ePortIdentifier) { + if (mType == ePortIdentifier) { MessagePortParent::ForceClose(mPortIdentifier.uuid(), mPortIdentifier.destinationUuid(), mPortIdentifier.sequenceId()); } + + mCompleted = true; +} + +RemoteWorkerController::PendingServiceWorkerOp::PendingServiceWorkerOp( + ServiceWorkerOpArgs&& aArgs, + RefPtr 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) { + 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; + } + + if (mArgs.type() == ServiceWorkerOpArgs::TServiceWorkerMessageEventOpArgs) { + auto& args = mArgs.get_ServiceWorkerMessageEventOpArgs(); + + ServiceWorkerMessageEventOpArgs copyArgs; + copyArgs.clientInfoAndState() = std::move(args.clientInfoAndState()); + + RefPtr copyData = new ServiceWorkerCloneData(); + copyData->StealFromClonedMessageDataForBackgroundParent(args.clonedData()); + + if (!copyData->BuildClonedMessageDataForBackgroundParent( + aOwner->mActor->Manager(), copyArgs.clonedData())) { + mPromise->Reject(NS_ERROR_DOM_DATA_CLONE_ERR, __func__); + mPromise = nullptr; + return true; + } + + mArgs = std::move(copyArgs); + } + + return true; +} + +void RemoteWorkerController::PendingServiceWorkerOp::Cancel() { + AssertIsOnBackgroundThread(); + MOZ_ASSERT(mPromise); + + mPromise->Reject(NS_ERROR_DOM_ABORT_ERR, __func__); + mPromise = nullptr; } } // namespace dom diff --git a/dom/workers/remoteworkers/RemoteWorkerController.h b/dom/workers/remoteworkers/RemoteWorkerController.h index 3493c4fa6c19..8063d2b71968 100644 --- a/dom/workers/remoteworkers/RemoteWorkerController.h +++ b/dom/workers/remoteworkers/RemoteWorkerController.h @@ -8,6 +8,13 @@ #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 { namespace dom { @@ -79,7 +86,6 @@ namespace dom { */ class ErrorValue; -class MessagePortIdentifier; class RemoteWorkerControllerParent; class RemoteWorkerData; class RemoteWorkerManager; @@ -134,18 +140,25 @@ class RemoteWorkerController final { void SetWorkerActor(RemoteWorkerParent* aActor); + void NoteDeadWorkerActor(); + void ErrorPropagation(const ErrorValue& aValue); void WorkerTerminated(); - void ForgetActorAndTerminate(); - void Shutdown(); void CreationFailed(); void CreationSucceeded(); + void CancelAllPendingOps(); + + template + void MaybeStartSharedWorkerOp(Args&&... aArgs); + + void NoteDeadWorker(); + RefPtr mObserver; RefPtr mActor; @@ -157,7 +170,33 @@ class RemoteWorkerController final { const bool mIsServiceWorker; - struct Op { + /** + * `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 and `false` otherwise. + * + * Starting execution may depend the state of `aOwner.` + */ + virtual bool MaybeStart(RemoteWorkerController* const aOwner) = 0; + + virtual void Cancel() = 0; + }; + + class PendingSharedWorkerOp final : public PendingOp { + public: enum Type { eTerminate, eSuspend, @@ -169,34 +208,41 @@ class RemoteWorkerController final { eRemoveWindowID, }; - explicit Op(Type aType, uint64_t aWindowID = 0) - : mType(aType), mWindowID(aWindowID), mCompleted(false) { - MOZ_COUNT_CTOR(Op); - } + explicit PendingSharedWorkerOp(Type aType, uint64_t aWindowID = 0); - explicit Op(const MessagePortIdentifier& aPortIdentifier) - : mType(ePortIdentifier), - mPortIdentifier(aPortIdentifier), - mCompleted(false) { - MOZ_COUNT_CTOR(Op); - } + explicit PendingSharedWorkerOp( + const MessagePortIdentifier& aPortIdentifier); - // This object cannot be copied. - Op(Op const&) = delete; - Op& operator=(Op const&) = delete; + ~PendingSharedWorkerOp(); - ~Op(); + bool MaybeStart(RemoteWorkerController* const aOwner) override; - void Completed() { mCompleted = true; } + void Cancel() override; - Type mType; - - MessagePortIdentifier mPortIdentifier; - uint64_t mWindowID; - bool mCompleted; + private: + const Type mType; + const MessagePortIdentifier mPortIdentifier; + const uint64_t mWindowID = 0; + bool mCompleted = false; }; - nsTArray> mPendingOps; + class PendingServiceWorkerOp final : public PendingOp { + public: + PendingServiceWorkerOp(ServiceWorkerOpArgs&& aArgs, + RefPtr aPromise); + + ~PendingServiceWorkerOp(); + + bool MaybeStart(RemoteWorkerController* const aOwner) override; + + void Cancel() override; + + private: + ServiceWorkerOpArgs mArgs; + RefPtr mPromise; + }; + + nsTArray> mPendingOps; }; } // namespace dom diff --git a/dom/workers/remoteworkers/RemoteWorkerParent.cpp b/dom/workers/remoteworkers/RemoteWorkerParent.cpp index 81ae3765fa19..3d05ed4fa831 100644 --- a/dom/workers/remoteworkers/RemoteWorkerParent.cpp +++ b/dom/workers/remoteworkers/RemoteWorkerParent.cpp @@ -84,7 +84,7 @@ void RemoteWorkerParent::ActorDestroy(IProtocol::ActorDestroyReason) { } if (mController) { - mController->ForgetActorAndTerminate(); + mController->NoteDeadWorkerActor(); mController = nullptr; } } @@ -117,6 +117,17 @@ IPCResult RemoteWorkerParent::RecvError(const ErrorValue& aValue) { 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()); diff --git a/dom/workers/remoteworkers/RemoteWorkerParent.h b/dom/workers/remoteworkers/RemoteWorkerParent.h index 1db6816ae4cb..2f0b6f546692 100644 --- a/dom/workers/remoteworkers/RemoteWorkerParent.h +++ b/dom/workers/remoteworkers/RemoteWorkerParent.h @@ -26,6 +26,8 @@ class RemoteWorkerParent final : public PRemoteWorkerParent { void SetController(RemoteWorkerController* aController); + void MaybeSendDelete(); + private: ~RemoteWorkerParent(); @@ -37,6 +39,7 @@ class RemoteWorkerParent final : public PRemoteWorkerParent { mozilla::ipc::IPCResult RecvCreated(const bool& aStatus); + bool mDeleteSent = false; RefPtr mController; };