/* -*- 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 mHandle; public: explicit KillContentParentRunnable( RefPtr&& aHandle) : Runnable("KillContentParentRunnable"), mHandle(std::move(aHandle)) { MOZ_ASSERT(mHandle); } NS_IMETHOD Run() override { AssertIsOnMainThread(); if (RefPtr 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 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 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 r = NS_NewRunnableFunction( "ClientSourceParent::RecvInheritController", [clientInfo = mClientInfo, controller = mController.ref()]() { RefPtr 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 r = NS_NewRunnableFunction("ClientSourceParent::RecvNoteDOMContentLoaded", [clientInfo = mClientInfo]() { RefPtr 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 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& 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 ClientSourceParent::ExecutionReadyPromise() { // Only call if ClientSourceParent::ExecutionReady() is false; otherwise, // the promise will never resolve MOZ_ASSERT(!mExecutionReady); return mExecutionReadyPromise.Ensure(__func__); } const Maybe& 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 ClientSourceParent::StartOp( ClientOpConstructorArgs&& aArgs) { RefPtr 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