/* -*- 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; 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())) { return IPC_FAIL(this, "Invalid creation URL!"); } 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(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(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__); } IPCResult 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()); return IPC_FAIL(Manager(), "Invalid PrincipalInfo!"); } // 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))) { return IPC_FAIL(Manager(), "Already registered!"); } return IPC_OK(); } 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