Bug 1231213 - Refactor RemoteWorkerController to handle Service Workers' and Shared Workers' operations. r=asuth

Differential Revision: https://phabricator.services.mozilla.com/D26169

--HG--
extra : moz-landing-system : lando
This commit is contained in:
Perry Jiang 2019-08-15 17:26:51 +00:00
Родитель 07c822e09b
Коммит 81d15b2970
5 изменённых файлов: 337 добавлений и 191 удалений

Просмотреть файл

@ -11,6 +11,7 @@ with Files("**"):
EXPORTS.mozilla.dom += [
'ServiceWorker.h',
'ServiceWorkerActors.h',
'ServiceWorkerCloneData.h',
'ServiceWorkerContainer.h',
'ServiceWorkerDescriptor.h',
'ServiceWorkerEvents.h',

Просмотреть файл

@ -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 <utility>
#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> RemoteWorkerController::Create(
const RemoteWorkerData& aData, RemoteWorkerObserver* aObserver,
base::ProcessId aProcessId) {
AssertIsInMainProcess();
AssertIsOnBackgroundThread();
MOZ_ASSERT(XRE_IsParentProcess());
MOZ_ASSERT(aObserver);
RefPtr<RemoteWorkerController> 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>& 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<bool> 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 <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(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<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) {
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<ServiceWorkerCloneData> 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

Просмотреть файл

@ -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 <typename... Args>
void MaybeStartSharedWorkerOp(Args&&... aArgs);
void NoteDeadWorker();
RefPtr<RemoteWorkerObserver> mObserver;
RefPtr<RemoteWorkerParent> 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<UniquePtr<Op>> mPendingOps;
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;
};
nsTArray<UniquePtr<PendingOp>> mPendingOps;
};
} // namespace dom

Просмотреть файл

@ -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());

Просмотреть файл

@ -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<RemoteWorkerController> mController;
};