summaryrefslogtreecommitdiffstats
path: root/dom/clients/manager/ClientSourceParent.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'dom/clients/manager/ClientSourceParent.cpp')
-rw-r--r--dom/clients/manager/ClientSourceParent.cpp297
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