diff options
Diffstat (limited to 'dom/clients/manager/ClientSourceParent.cpp')
-rw-r--r-- | dom/clients/manager/ClientSourceParent.cpp | 297 |
1 files changed, 297 insertions, 0 deletions
diff --git a/dom/clients/manager/ClientSourceParent.cpp b/dom/clients/manager/ClientSourceParent.cpp new file mode 100644 index 0000000000..8d94a501d4 --- /dev/null +++ b/dom/clients/manager/ClientSourceParent.cpp @@ -0,0 +1,297 @@ +/* -*- 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 "ClientSourceParent.h" + +#include "ClientHandleParent.h" +#include "ClientManagerService.h" +#include "ClientSourceOpParent.h" +#include "ClientValidation.h" +#include "mozilla/dom/ClientIPCTypes.h" +#include "mozilla/dom/ContentParent.h" +#include "mozilla/dom/PClientManagerParent.h" +#include "mozilla/dom/ServiceWorkerManager.h" +#include "mozilla/dom/ServiceWorkerUtils.h" +#include "mozilla/ipc/BackgroundParent.h" +#include "mozilla/SchedulerGroup.h" +#include "mozilla/Unused.h" + +namespace mozilla::dom { + +using mozilla::ipc::AssertIsOnBackgroundThread; +using mozilla::ipc::BackgroundParent; +using mozilla::ipc::IPCResult; +using mozilla::ipc::PrincipalInfo; + +namespace { + +// It would be nice to use a lambda instead of this class, but we cannot +// move capture in lambdas yet and ContentParent cannot be AddRef'd off +// the main thread. +class KillContentParentRunnable final : public Runnable { + RefPtr<ThreadsafeContentParentHandle> mHandle; + + public: + explicit KillContentParentRunnable( + RefPtr<ThreadsafeContentParentHandle>&& aHandle) + : Runnable("KillContentParentRunnable"), mHandle(std::move(aHandle)) { + MOZ_ASSERT(mHandle); + } + + NS_IMETHOD + Run() override { + AssertIsOnMainThread(); + if (RefPtr<ContentParent> contentParent = mHandle->GetContentParent()) { + contentParent->KillHard("invalid ClientSourceParent actor"); + } + return NS_OK; + } +}; + +} // anonymous namespace + +void ClientSourceParent::KillInvalidChild() { + // Try to get the content process before we destroy the actor below. + RefPtr<ThreadsafeContentParentHandle> process = + BackgroundParent::GetContentParentHandle(Manager()->Manager()); + + // First, immediately teardown the ClientSource actor. No matter what + // we want to start this process as soon as possible. + Unused << ClientSourceParent::Send__delete__(this); + + // If we are running in non-e10s, then there is nothing else to do here. + // There is no child process and we don't want to crash the entire browser + // in release builds. In general, though, this should not happen in non-e10s + // so we do assert this condition. + if (!process) { + MOZ_DIAGNOSTIC_ASSERT(false, "invalid ClientSourceParent in non-e10s"); + return; + } + + // In e10s mode we also want to kill the child process. Validation failures + // typically mean someone sent us bogus data over the IPC link. We can't + // trust that process any more. We have to do this on the main thread, so + // there is a small window of time before we kill the process. This is why + // we start the actor destruction immediately above. + nsCOMPtr<nsIRunnable> r = new KillContentParentRunnable(std::move(process)); + MOZ_ALWAYS_SUCCEEDS( + SchedulerGroup::Dispatch(TaskCategory::Other, r.forget())); +} + +mozilla::ipc::IPCResult ClientSourceParent::RecvWorkerSyncPing() { + AssertIsOnBackgroundThread(); + // Do nothing here. This is purely a sync message allowing the child to + // confirm that the actor has been created on the parent process. + return IPC_OK(); +} + +IPCResult ClientSourceParent::RecvTeardown() { + Unused << Send__delete__(this); + return IPC_OK(); +} + +IPCResult ClientSourceParent::RecvExecutionReady( + const ClientSourceExecutionReadyArgs& aArgs) { + // Now that we have the creation URL for the Client we can do some validation + // to make sure the child actor is not giving us garbage. Since we validate + // on the child side as well we treat a failure here as fatal. + if (!ClientIsValidCreationURL(mClientInfo.PrincipalInfo(), aArgs.url())) { + KillInvalidChild(); + return IPC_OK(); + } + + mClientInfo.SetURL(aArgs.url()); + mClientInfo.SetFrameType(aArgs.frameType()); + mExecutionReady = true; + + for (ClientHandleParent* handle : mHandleList) { + Unused << handle->SendExecutionReady(mClientInfo.ToIPC()); + } + + mExecutionReadyPromise.ResolveIfExists(true, __func__); + + return IPC_OK(); +}; + +IPCResult ClientSourceParent::RecvFreeze() { +#ifdef FUZZING_SNAPSHOT + if (mFrozen) { + return IPC_FAIL(this, "Freezing when already frozen"); + } +#endif + MOZ_DIAGNOSTIC_ASSERT(!mFrozen); + mFrozen = true; + + return IPC_OK(); +} + +IPCResult ClientSourceParent::RecvThaw() { +#ifdef FUZZING_SNAPSHOT + if (!mFrozen) { + return IPC_FAIL(this, "Thawing when not already frozen"); + } +#endif + MOZ_DIAGNOSTIC_ASSERT(mFrozen); + mFrozen = false; + return IPC_OK(); +} + +IPCResult ClientSourceParent::RecvInheritController( + const ClientControlledArgs& aArgs) { + mController.reset(); + mController.emplace(aArgs.serviceWorker()); + + // We must tell the parent-side SWM about this controller inheritance. + nsCOMPtr<nsIRunnable> r = NS_NewRunnableFunction( + "ClientSourceParent::RecvInheritController", + [clientInfo = mClientInfo, controller = mController.ref()]() { + RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance(); + NS_ENSURE_TRUE_VOID(swm); + + swm->NoteInheritedController(clientInfo, controller); + }); + + MOZ_ALWAYS_SUCCEEDS( + SchedulerGroup::Dispatch(TaskCategory::Other, r.forget())); + + return IPC_OK(); +} + +IPCResult ClientSourceParent::RecvNoteDOMContentLoaded() { + if (mController.isSome()) { + nsCOMPtr<nsIRunnable> r = + NS_NewRunnableFunction("ClientSourceParent::RecvNoteDOMContentLoaded", + [clientInfo = mClientInfo]() { + RefPtr<ServiceWorkerManager> swm = + ServiceWorkerManager::GetInstance(); + NS_ENSURE_TRUE_VOID(swm); + + swm->MaybeCheckNavigationUpdate(clientInfo); + }); + + MOZ_ALWAYS_SUCCEEDS( + SchedulerGroup::Dispatch(TaskCategory::Other, r.forget())); + } + return IPC_OK(); +} + +void ClientSourceParent::ActorDestroy(ActorDestroyReason aReason) { + DebugOnly<bool> removed = mService->RemoveSource(this); + MOZ_ASSERT(removed); + + for (ClientHandleParent* handle : mHandleList.Clone()) { + // This should trigger DetachHandle() to be called removing + // the entry from the mHandleList. + Unused << ClientHandleParent::Send__delete__(handle); + } + MOZ_DIAGNOSTIC_ASSERT(mHandleList.IsEmpty()); +} + +PClientSourceOpParent* ClientSourceParent::AllocPClientSourceOpParent( + const ClientOpConstructorArgs& aArgs) { + MOZ_ASSERT_UNREACHABLE( + "ClientSourceOpParent should be explicitly constructed."); + return nullptr; +} + +bool ClientSourceParent::DeallocPClientSourceOpParent( + PClientSourceOpParent* aActor) { + delete aActor; + return true; +} + +ClientSourceParent::ClientSourceParent( + const ClientSourceConstructorArgs& aArgs, + const Maybe<ContentParentId>& aContentParentId) + : mClientInfo(aArgs.id(), aArgs.type(), aArgs.principalInfo(), + aArgs.creationTime()), + mContentParentId(aContentParentId), + mService(ClientManagerService::GetOrCreateInstance()), + mExecutionReady(false), + mFrozen(false) {} + +ClientSourceParent::~ClientSourceParent() { + MOZ_DIAGNOSTIC_ASSERT(mHandleList.IsEmpty()); + + mExecutionReadyPromise.RejectIfExists(NS_ERROR_FAILURE, __func__); +} + +void ClientSourceParent::Init() { + // Ensure the principal is reasonable before adding ourself to the service. + // Since we validate the principal on the child side as well, any failure + // here is treated as fatal. + if (NS_WARN_IF(!ClientIsValidPrincipalInfo(mClientInfo.PrincipalInfo()))) { + mService->ForgetFutureSource(mClientInfo.ToIPC()); + KillInvalidChild(); + return; + } + + // Its possible for AddSource() to fail if there is already an entry for + // our UUID. This should not normally happen, but could if someone is + // spoofing IPC messages. + if (NS_WARN_IF(!mService->AddSource(this))) { + KillInvalidChild(); + return; + } +} + +const ClientInfo& ClientSourceParent::Info() const { return mClientInfo; } + +bool ClientSourceParent::IsFrozen() const { return mFrozen; } + +bool ClientSourceParent::ExecutionReady() const { return mExecutionReady; } + +RefPtr<GenericNonExclusivePromise> ClientSourceParent::ExecutionReadyPromise() { + // Only call if ClientSourceParent::ExecutionReady() is false; otherwise, + // the promise will never resolve + MOZ_ASSERT(!mExecutionReady); + return mExecutionReadyPromise.Ensure(__func__); +} + +const Maybe<ServiceWorkerDescriptor>& ClientSourceParent::GetController() + const { + return mController; +} + +void ClientSourceParent::ClearController() { mController.reset(); } + +void ClientSourceParent::AttachHandle(ClientHandleParent* aClientHandle) { + MOZ_DIAGNOSTIC_ASSERT(aClientHandle); + MOZ_ASSERT(!mHandleList.Contains(aClientHandle)); + mHandleList.AppendElement(aClientHandle); +} + +void ClientSourceParent::DetachHandle(ClientHandleParent* aClientHandle) { + MOZ_DIAGNOSTIC_ASSERT(aClientHandle); + MOZ_ASSERT(mHandleList.Contains(aClientHandle)); + mHandleList.RemoveElement(aClientHandle); +} + +RefPtr<ClientOpPromise> ClientSourceParent::StartOp( + ClientOpConstructorArgs&& aArgs) { + RefPtr<ClientOpPromise::Private> promise = + new ClientOpPromise::Private(__func__); + + // If we are being controlled, remember that data before propagating + // on to the ClientSource. This must be set prior to triggering + // the controllerchange event from the ClientSource since some tests + // expect matchAll() to find the controlled client immediately after. + // If the control operation fails, then we reset the controller value + // to reflect the final state. + if (aArgs.type() == ClientOpConstructorArgs::TClientControlledArgs) { + mController.reset(); + mController.emplace(aArgs.get_ClientControlledArgs().serviceWorker()); + } + + // Constructor failure will reject the promise via ActorDestroy(). + ClientSourceOpParent* actor = + new ClientSourceOpParent(std::move(aArgs), promise); + Unused << SendPClientSourceOpConstructor(actor, actor->Args()); + + return promise; +} + +} // namespace mozilla::dom |