summaryrefslogtreecommitdiffstats
path: root/dom/clients
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 09:22:09 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 09:22:09 +0000
commit43a97878ce14b72f0981164f87f2e35e14151312 (patch)
tree620249daf56c0258faa40cbdcf9cfba06de2a846 /dom/clients
parentInitial commit. (diff)
downloadfirefox-43a97878ce14b72f0981164f87f2e35e14151312.tar.xz
firefox-43a97878ce14b72f0981164f87f2e35e14151312.zip
Adding upstream version 110.0.1.upstream/110.0.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'dom/clients')
-rw-r--r--dom/clients/api/Client.cpp206
-rw-r--r--dom/clients/api/Client.h89
-rw-r--r--dom/clients/api/ClientDOMUtil.h48
-rw-r--r--dom/clients/api/Clients.cpp292
-rw-r--r--dom/clients/api/Clients.h55
-rw-r--r--dom/clients/api/moz.build25
-rw-r--r--dom/clients/manager/ClientChannelHelper.cpp423
-rw-r--r--dom/clients/manager/ClientChannelHelper.h53
-rw-r--r--dom/clients/manager/ClientHandle.cpp195
-rw-r--r--dom/clients/manager/ClientHandle.h112
-rw-r--r--dom/clients/manager/ClientHandleChild.cpp70
-rw-r--r--dom/clients/manager/ClientHandleChild.h47
-rw-r--r--dom/clients/manager/ClientHandleOpChild.cpp44
-rw-r--r--dom/clients/manager/ClientHandleOpChild.h38
-rw-r--r--dom/clients/manager/ClientHandleOpParent.cpp96
-rw-r--r--dom/clients/manager/ClientHandleOpParent.h35
-rw-r--r--dom/clients/manager/ClientHandleParent.cpp115
-rw-r--r--dom/clients/manager/ClientHandleParent.h63
-rw-r--r--dom/clients/manager/ClientIPCTypes.ipdlh165
-rw-r--r--dom/clients/manager/ClientIPCUtils.h44
-rw-r--r--dom/clients/manager/ClientInfo.cpp131
-rw-r--r--dom/clients/manager/ClientInfo.h117
-rw-r--r--dom/clients/manager/ClientManager.cpp394
-rw-r--r--dom/clients/manager/ClientManager.h151
-rw-r--r--dom/clients/manager/ClientManagerActors.cpp37
-rw-r--r--dom/clients/manager/ClientManagerActors.h28
-rw-r--r--dom/clients/manager/ClientManagerChild.cpp137
-rw-r--r--dom/clients/manager/ClientManagerChild.h66
-rw-r--r--dom/clients/manager/ClientManagerOpChild.cpp50
-rw-r--r--dom/clients/manager/ClientManagerOpChild.h37
-rw-r--r--dom/clients/manager/ClientManagerOpParent.cpp85
-rw-r--r--dom/clients/manager/ClientManagerOpParent.h35
-rw-r--r--dom/clients/manager/ClientManagerParent.cpp125
-rw-r--r--dom/clients/manager/ClientManagerParent.h69
-rw-r--r--dom/clients/manager/ClientManagerService.cpp748
-rw-r--r--dom/clients/manager/ClientManagerService.h166
-rw-r--r--dom/clients/manager/ClientNavigateOpChild.cpp334
-rw-r--r--dom/clients/manager/ClientNavigateOpChild.h33
-rw-r--r--dom/clients/manager/ClientNavigateOpParent.cpp46
-rw-r--r--dom/clients/manager/ClientNavigateOpParent.h32
-rw-r--r--dom/clients/manager/ClientOpPromise.h31
-rw-r--r--dom/clients/manager/ClientOpenWindowUtils.cpp455
-rw-r--r--dom/clients/manager/ClientOpenWindowUtils.h25
-rw-r--r--dom/clients/manager/ClientPrincipalUtils.cpp48
-rw-r--r--dom/clients/manager/ClientPrincipalUtils.h23
-rw-r--r--dom/clients/manager/ClientSource.cpp710
-rw-r--r--dom/clients/manager/ClientSource.h193
-rw-r--r--dom/clients/manager/ClientSourceChild.cpp79
-rw-r--r--dom/clients/manager/ClientSourceChild.h50
-rw-r--r--dom/clients/manager/ClientSourceOpChild.cpp136
-rw-r--r--dom/clients/manager/ClientSourceOpChild.h45
-rw-r--r--dom/clients/manager/ClientSourceOpParent.cpp57
-rw-r--r--dom/clients/manager/ClientSourceOpParent.h35
-rw-r--r--dom/clients/manager/ClientSourceParent.cpp297
-rw-r--r--dom/clients/manager/ClientSourceParent.h89
-rw-r--r--dom/clients/manager/ClientState.cpp167
-rw-r--r--dom/clients/manager/ClientState.h125
-rw-r--r--dom/clients/manager/ClientThing.h138
-rw-r--r--dom/clients/manager/ClientValidation.cpp170
-rw-r--r--dom/clients/manager/ClientValidation.h28
-rw-r--r--dom/clients/manager/PClientHandle.ipdl33
-rw-r--r--dom/clients/manager/PClientHandleOp.ipdl21
-rw-r--r--dom/clients/manager/PClientManager.ipdl42
-rw-r--r--dom/clients/manager/PClientManagerOp.ipdl21
-rw-r--r--dom/clients/manager/PClientNavigateOp.ipdl21
-rw-r--r--dom/clients/manager/PClientSource.ipdl37
-rw-r--r--dom/clients/manager/PClientSourceOp.ipdl21
-rw-r--r--dom/clients/manager/moz.build70
-rw-r--r--dom/clients/moz.build10
69 files changed, 8213 insertions, 0 deletions
diff --git a/dom/clients/api/Client.cpp b/dom/clients/api/Client.cpp
new file mode 100644
index 0000000000..1b0b4a2a59
--- /dev/null
+++ b/dom/clients/api/Client.cpp
@@ -0,0 +1,206 @@
+/* -*- 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 "Client.h"
+
+#include "ClientDOMUtil.h"
+#include "mozilla/dom/ClientHandle.h"
+#include "mozilla/dom/ClientIPCTypes.h"
+#include "mozilla/dom/ClientManager.h"
+#include "mozilla/dom/ClientState.h"
+#include "mozilla/dom/DOMMozPromiseRequestHolder.h"
+#include "mozilla/dom/MessagePortBinding.h"
+#include "mozilla/dom/Promise.h"
+#include "mozilla/dom/WorkerScope.h"
+#include "nsIDUtils.h"
+#include "nsIGlobalObject.h"
+
+namespace mozilla::dom {
+
+using mozilla::dom::ipc::StructuredCloneData;
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(mozilla::dom::Client);
+NS_IMPL_CYCLE_COLLECTING_RELEASE(mozilla::dom::Client);
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(mozilla::dom::Client, mGlobal);
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(mozilla::dom::Client)
+ NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+void Client::EnsureHandle() {
+ NS_ASSERT_OWNINGTHREAD(mozilla::dom::Client);
+ if (!mHandle) {
+ mHandle = ClientManager::CreateHandle(
+ ClientInfo(mData->info()),
+ mGlobal->EventTargetFor(TaskCategory::Other));
+ }
+}
+
+Client::Client(nsIGlobalObject* aGlobal, const ClientInfoAndState& aData)
+ : mGlobal(aGlobal), mData(MakeUnique<ClientInfoAndState>(aData)) {
+ MOZ_DIAGNOSTIC_ASSERT(mGlobal);
+}
+
+TimeStamp Client::CreationTime() const { return mData->info().creationTime(); }
+
+TimeStamp Client::LastFocusTime() const {
+ if (mData->info().type() != ClientType::Window) {
+ return TimeStamp();
+ }
+ return mData->state().get_IPCClientWindowState().lastFocusTime();
+}
+
+StorageAccess Client::GetStorageAccess() const {
+ ClientState state(ClientState::FromIPC(mData->state()));
+ return state.GetStorageAccess();
+}
+
+JSObject* Client::WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) {
+ if (mData->info().type() == ClientType::Window) {
+ return WindowClient_Binding::Wrap(aCx, this, aGivenProto);
+ }
+ return Client_Binding::Wrap(aCx, this, aGivenProto);
+}
+
+nsIGlobalObject* Client::GetParentObject() const { return mGlobal; }
+
+void Client::GetUrl(nsAString& aUrlOut) const {
+ CopyUTF8toUTF16(mData->info().url(), aUrlOut);
+}
+
+void Client::GetId(nsAString& aIdOut) const {
+ aIdOut = NSID_TrimBracketsUTF16(mData->info().id());
+}
+
+ClientType Client::Type() const { return mData->info().type(); }
+
+FrameType Client::GetFrameType() const { return mData->info().frameType(); }
+
+void Client::PostMessage(JSContext* aCx, JS::Handle<JS::Value> aMessage,
+ const Sequence<JSObject*>& aTransferable,
+ ErrorResult& aRv) {
+ MOZ_ASSERT(!NS_IsMainThread());
+ WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate();
+ MOZ_DIAGNOSTIC_ASSERT(workerPrivate);
+ MOZ_DIAGNOSTIC_ASSERT(workerPrivate->IsServiceWorker());
+ workerPrivate->AssertIsOnWorkerThread();
+
+ JS::Rooted<JS::Value> transferable(aCx, JS::UndefinedValue());
+ aRv = nsContentUtils::CreateJSValueFromSequenceOfObject(aCx, aTransferable,
+ &transferable);
+ if (aRv.Failed()) {
+ return;
+ }
+
+ StructuredCloneData data;
+ data.Write(aCx, aMessage, transferable, JS::CloneDataPolicy(), aRv);
+ if (aRv.Failed()) {
+ return;
+ }
+
+ EnsureHandle();
+ mHandle->PostMessage(data, workerPrivate->GetServiceWorkerDescriptor());
+}
+
+void Client::PostMessage(JSContext* aCx, JS::Handle<JS::Value> aMessage,
+ const StructuredSerializeOptions& aOptions,
+ ErrorResult& aRv) {
+ PostMessage(aCx, aMessage, aOptions.mTransfer, aRv);
+}
+
+VisibilityState Client::GetVisibilityState() const {
+ return mData->state().get_IPCClientWindowState().visibilityState();
+}
+
+bool Client::Focused() const {
+ return mData->state().get_IPCClientWindowState().focused();
+}
+
+already_AddRefed<Promise> Client::Focus(CallerType aCallerType,
+ ErrorResult& aRv) {
+ MOZ_ASSERT(!NS_IsMainThread());
+ WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate();
+ MOZ_DIAGNOSTIC_ASSERT(workerPrivate);
+ MOZ_DIAGNOSTIC_ASSERT(workerPrivate->IsServiceWorker());
+ workerPrivate->AssertIsOnWorkerThread();
+
+ RefPtr<Promise> outerPromise = Promise::Create(mGlobal, aRv);
+ if (aRv.Failed()) {
+ return outerPromise.forget();
+ }
+
+ if (!workerPrivate->GlobalScope()->WindowInteractionAllowed()) {
+ outerPromise->MaybeReject(NS_ERROR_DOM_INVALID_ACCESS_ERR);
+ return outerPromise.forget();
+ }
+
+ EnsureHandle();
+
+ IPCClientInfo ipcClientInfo(mData->info());
+ auto holder =
+ MakeRefPtr<DOMMozPromiseRequestHolder<ClientStatePromise>>(mGlobal);
+
+ mHandle->Focus(aCallerType)
+ ->Then(
+ mGlobal->EventTargetFor(TaskCategory::Other), __func__,
+ [ipcClientInfo, holder, outerPromise](const ClientState& aResult) {
+ holder->Complete();
+ NS_ENSURE_TRUE_VOID(holder->GetParentObject());
+ RefPtr<Client> newClient =
+ new Client(holder->GetParentObject(),
+ ClientInfoAndState(ipcClientInfo, aResult.ToIPC()));
+ outerPromise->MaybeResolve(newClient);
+ },
+ [holder, outerPromise](const CopyableErrorResult& aResult) {
+ holder->Complete();
+ // MaybeReject needs a non-const-ref result, so make a copy.
+ outerPromise->MaybeReject(CopyableErrorResult(aResult));
+ })
+ ->Track(*holder);
+
+ return outerPromise.forget();
+}
+
+already_AddRefed<Promise> Client::Navigate(const nsAString& aURL,
+ ErrorResult& aRv) {
+ MOZ_ASSERT(!NS_IsMainThread());
+ WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate();
+ MOZ_DIAGNOSTIC_ASSERT(workerPrivate);
+ MOZ_DIAGNOSTIC_ASSERT(workerPrivate->IsServiceWorker());
+ workerPrivate->AssertIsOnWorkerThread();
+
+ RefPtr<Promise> outerPromise = Promise::Create(mGlobal, aRv);
+ if (aRv.Failed()) {
+ return outerPromise.forget();
+ }
+
+ ClientNavigateArgs args(mData->info(), NS_ConvertUTF16toUTF8(aURL),
+ workerPrivate->GetLocationInfo().mHref,
+ workerPrivate->GetServiceWorkerDescriptor().ToIPC());
+ RefPtr<Client> self = this;
+
+ StartClientManagerOp(
+ &ClientManager::Navigate, args, mGlobal,
+ [self, outerPromise](const ClientOpResult& aResult) {
+ if (aResult.type() != ClientOpResult::TClientInfoAndState) {
+ outerPromise->MaybeResolve(JS::NullHandleValue);
+ return;
+ }
+ RefPtr<Client> newClient =
+ new Client(self->mGlobal, aResult.get_ClientInfoAndState());
+ outerPromise->MaybeResolve(newClient);
+ },
+ [self, outerPromise](const CopyableErrorResult& aResult) {
+ // MaybeReject needs a non-const-ref result, so make a copy.
+ outerPromise->MaybeReject(CopyableErrorResult(aResult));
+ });
+
+ return outerPromise.forget();
+}
+
+} // namespace mozilla::dom
diff --git a/dom/clients/api/Client.h b/dom/clients/api/Client.h
new file mode 100644
index 0000000000..cd47ccd93b
--- /dev/null
+++ b/dom/clients/api/Client.h
@@ -0,0 +1,89 @@
+/* -*- 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/. */
+#ifndef _mozilla_dom_Client_h
+#define _mozilla_dom_Client_h
+
+#include "X11UndefineNone.h"
+#include "mozilla/dom/ClientBinding.h"
+#include "mozilla/StorageAccess.h"
+#include "nsCOMPtr.h"
+#include "nsContentUtils.h"
+#include "nsISupports.h"
+#include "nsWrapperCache.h"
+
+class nsIGlobalObject;
+
+namespace mozilla {
+
+class ErrorResult;
+
+namespace dom {
+
+class ClientHandle;
+class ClientInfoAndState;
+class Promise;
+template <typename t>
+class Sequence;
+struct StructuredSerializeOptions;
+
+class Client final : public nsISupports, public nsWrapperCache {
+ nsCOMPtr<nsIGlobalObject> mGlobal;
+ UniquePtr<ClientInfoAndState> mData;
+ RefPtr<ClientHandle> mHandle;
+
+ ~Client() = default;
+
+ void EnsureHandle();
+
+ public:
+ Client(nsIGlobalObject* aGlobal, const ClientInfoAndState& aData);
+
+ TimeStamp CreationTime() const;
+
+ TimeStamp LastFocusTime() const;
+
+ StorageAccess GetStorageAccess() const;
+
+ // nsWrapperCache interface methods
+ JSObject* WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) override;
+
+ // DOM bindings methods
+ nsIGlobalObject* GetParentObject() const;
+
+ // Client Bindings
+ void GetUrl(nsAString& aUrlOut) const;
+
+ void GetId(nsAString& aIdOut) const;
+
+ ClientType Type() const;
+
+ FrameType GetFrameType() const;
+
+ // WindowClient bindings
+ VisibilityState GetVisibilityState() const;
+
+ bool Focused() const;
+
+ already_AddRefed<Promise> Focus(CallerType aCallerType, ErrorResult& aRv);
+
+ already_AddRefed<Promise> Navigate(const nsAString& aURL, ErrorResult& aRv);
+
+ void PostMessage(JSContext* aCx, JS::Handle<JS::Value> aMessage,
+ const Sequence<JSObject*>& aTransferrable, ErrorResult& aRv);
+
+ void PostMessage(JSContext* aCx, JS::Handle<JS::Value> aMessage,
+ const StructuredSerializeOptions& aOptions,
+ ErrorResult& aRv);
+
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS(mozilla::dom::Client)
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // _mozilla_dom_Client_h
diff --git a/dom/clients/api/ClientDOMUtil.h b/dom/clients/api/ClientDOMUtil.h
new file mode 100644
index 0000000000..2798aeec53
--- /dev/null
+++ b/dom/clients/api/ClientDOMUtil.h
@@ -0,0 +1,48 @@
+/* -*- 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/. */
+#ifndef _mozilla_dom_ClientDOMUtil_h
+#define _mozilla_dom_ClientDOMUtil_h
+
+#include "mozilla/dom/ClientIPCTypes.h"
+#include "mozilla/dom/ClientOpPromise.h"
+#include "mozilla/dom/DOMMozPromiseRequestHolder.h"
+#include "mozilla/dom/WorkerPrivate.h"
+
+class nsIGlobalObject;
+
+namespace mozilla::dom {
+
+// Utility method to properly execute a ClientManager operation. It
+// will properly hold a worker thread alive and avoid executing callbacks
+// if the thread is shutting down.
+template <typename Func, typename Arg, typename Resolve, typename Reject>
+void StartClientManagerOp(Func aFunc, const Arg& aArg, nsIGlobalObject* aGlobal,
+ Resolve aResolve, Reject aReject) {
+ MOZ_DIAGNOSTIC_ASSERT(aGlobal);
+
+ nsCOMPtr<nsISerialEventTarget> target =
+ aGlobal->EventTargetFor(TaskCategory::Other);
+
+ auto holder =
+ MakeRefPtr<DOMMozPromiseRequestHolder<ClientOpPromise>>(aGlobal);
+
+ aFunc(aArg, target)
+ ->Then(
+ target, __func__,
+ [aResolve, holder](const ClientOpResult& aResult) {
+ holder->Complete();
+ aResolve(aResult);
+ },
+ [aReject, holder](const CopyableErrorResult& aResult) {
+ holder->Complete();
+ aReject(aResult);
+ })
+ ->Track(*holder);
+}
+
+} // namespace mozilla::dom
+
+#endif // _mozilla_dom_ClientDOMUtil_h
diff --git a/dom/clients/api/Clients.cpp b/dom/clients/api/Clients.cpp
new file mode 100644
index 0000000000..6dd88953b3
--- /dev/null
+++ b/dom/clients/api/Clients.cpp
@@ -0,0 +1,292 @@
+/* -*- 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 "Clients.h"
+
+#include "Client.h"
+#include "ClientDOMUtil.h"
+#include "mozilla/dom/ClientIPCTypes.h"
+#include "mozilla/dom/ClientManager.h"
+#include "mozilla/dom/ClientsBinding.h"
+#include "mozilla/dom/Promise.h"
+#include "mozilla/dom/ServiceWorkerDescriptor.h"
+#include "mozilla/dom/ServiceWorkerManager.h"
+#include "mozilla/dom/WorkerScope.h"
+#include "mozilla/ipc/BackgroundUtils.h"
+#include "mozilla/SchedulerGroup.h"
+#include "mozilla/StaticPrefs_privacy.h"
+#include "mozilla/StorageAccess.h"
+#include "nsIGlobalObject.h"
+#include "nsString.h"
+
+namespace mozilla::dom {
+
+using mozilla::ipc::CSPInfo;
+using mozilla::ipc::PrincipalInfo;
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(Clients);
+NS_IMPL_CYCLE_COLLECTING_RELEASE(Clients);
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(Clients, mGlobal);
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(Clients)
+ NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+Clients::Clients(nsIGlobalObject* aGlobal) : mGlobal(aGlobal) {
+ MOZ_DIAGNOSTIC_ASSERT(mGlobal);
+}
+
+JSObject* Clients::WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) {
+ return Clients_Binding::Wrap(aCx, this, aGivenProto);
+}
+
+nsIGlobalObject* Clients::GetParentObject() const { return mGlobal; }
+
+already_AddRefed<Promise> Clients::Get(const nsAString& aClientID,
+ ErrorResult& aRv) {
+ MOZ_ASSERT(!NS_IsMainThread());
+ WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate();
+ MOZ_DIAGNOSTIC_ASSERT(workerPrivate);
+ MOZ_DIAGNOSTIC_ASSERT(workerPrivate->IsServiceWorker());
+ workerPrivate->AssertIsOnWorkerThread();
+
+ RefPtr<Promise> outerPromise = Promise::Create(mGlobal, aRv);
+ if (aRv.Failed()) {
+ return outerPromise.forget();
+ }
+
+ nsID id;
+ // nsID::Parse accepts both "{...}" and "...", but we only emit the latter, so
+ // forbid strings that start with "{" to avoid inconsistency and bugs like
+ // bug 1446225.
+ if (aClientID.IsEmpty() || aClientID.CharAt(0) == '{' ||
+ !id.Parse(NS_ConvertUTF16toUTF8(aClientID).get())) {
+ // Invalid ID means we will definitely not find a match, so just
+ // resolve with undefined indicating "not found".
+ outerPromise->MaybeResolveWithUndefined();
+ return outerPromise.forget();
+ }
+
+ const PrincipalInfo& principalInfo = workerPrivate->GetPrincipalInfo();
+ nsCOMPtr<nsISerialEventTarget> target =
+ mGlobal->EventTargetFor(TaskCategory::Other);
+
+ RefPtr<ClientOpPromise> innerPromise = ClientManager::GetInfoAndState(
+ ClientGetInfoAndStateArgs(id, principalInfo), target);
+
+ nsCString scope = workerPrivate->ServiceWorkerScope();
+ auto holder =
+ MakeRefPtr<DOMMozPromiseRequestHolder<ClientOpPromise>>(mGlobal);
+
+ innerPromise
+ ->Then(
+ target, __func__,
+ [outerPromise, holder, scope](const ClientOpResult& aResult) {
+ holder->Complete();
+ NS_ENSURE_TRUE_VOID(holder->GetParentObject());
+ RefPtr<Client> client = new Client(
+ holder->GetParentObject(), aResult.get_ClientInfoAndState());
+ if (client->GetStorageAccess() == StorageAccess::eAllow ||
+ (StaticPrefs::privacy_partition_serviceWorkers() &&
+ ShouldPartitionStorage(client->GetStorageAccess()))) {
+ outerPromise->MaybeResolve(std::move(client));
+ return;
+ }
+ nsCOMPtr<nsIRunnable> r = NS_NewRunnableFunction(
+ "Clients::Get() storage denied", [scope] {
+ ServiceWorkerManager::LocalizeAndReportToAllClients(
+ scope, "ServiceWorkerGetClientStorageError",
+ nsTArray<nsString>());
+ });
+ SchedulerGroup::Dispatch(TaskCategory::Other, r.forget());
+ outerPromise->MaybeResolveWithUndefined();
+ },
+ [outerPromise, holder](const CopyableErrorResult& aResult) {
+ holder->Complete();
+ outerPromise->MaybeResolveWithUndefined();
+ })
+ ->Track(*holder);
+
+ return outerPromise.forget();
+}
+
+namespace {
+
+class MatchAllComparator final {
+ public:
+ bool LessThan(Client* aLeft, Client* aRight) const {
+ TimeStamp leftFocusTime = aLeft->LastFocusTime();
+ TimeStamp rightFocusTime = aRight->LastFocusTime();
+ // If the focus times are the same, then default to creation order.
+ // MatchAll should return oldest Clients first.
+ if (leftFocusTime == rightFocusTime) {
+ return aLeft->CreationTime() < aRight->CreationTime();
+ }
+
+ // Otherwise compare focus times. We reverse the logic here so
+ // that the most recently focused window is first in the list.
+ if (!leftFocusTime.IsNull() && rightFocusTime.IsNull()) {
+ return true;
+ }
+ if (leftFocusTime.IsNull() && !rightFocusTime.IsNull()) {
+ return false;
+ }
+ return leftFocusTime > rightFocusTime;
+ }
+
+ bool Equals(Client* aLeft, Client* aRight) const {
+ return aLeft->LastFocusTime() == aRight->LastFocusTime() &&
+ aLeft->CreationTime() == aRight->CreationTime();
+ }
+};
+
+} // anonymous namespace
+
+already_AddRefed<Promise> Clients::MatchAll(const ClientQueryOptions& aOptions,
+ ErrorResult& aRv) {
+ MOZ_ASSERT(!NS_IsMainThread());
+ WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate();
+ MOZ_DIAGNOSTIC_ASSERT(workerPrivate);
+ MOZ_DIAGNOSTIC_ASSERT(workerPrivate->IsServiceWorker());
+ workerPrivate->AssertIsOnWorkerThread();
+
+ RefPtr<Promise> outerPromise = Promise::Create(mGlobal, aRv);
+ if (aRv.Failed()) {
+ return outerPromise.forget();
+ }
+
+ nsCOMPtr<nsIGlobalObject> global = mGlobal;
+ nsCString scope = workerPrivate->ServiceWorkerScope();
+
+ ClientMatchAllArgs args(workerPrivate->GetServiceWorkerDescriptor().ToIPC(),
+ aOptions.mType, aOptions.mIncludeUncontrolled);
+ StartClientManagerOp(
+ &ClientManager::MatchAll, args, mGlobal,
+ [outerPromise, global, scope](const ClientOpResult& aResult) {
+ nsTArray<RefPtr<Client>> clientList;
+ bool storageDenied = false;
+ for (const ClientInfoAndState& value :
+ aResult.get_ClientList().values()) {
+ RefPtr<Client> client = new Client(global, value);
+ if (client->GetStorageAccess() != StorageAccess::eAllow &&
+ (!StaticPrefs::privacy_partition_serviceWorkers() ||
+ !ShouldPartitionStorage(client->GetStorageAccess()))) {
+ storageDenied = true;
+ continue;
+ }
+ clientList.AppendElement(std::move(client));
+ }
+ if (storageDenied) {
+ nsCOMPtr<nsIRunnable> r = NS_NewRunnableFunction(
+ "Clients::MatchAll() storage denied", [scope] {
+ ServiceWorkerManager::LocalizeAndReportToAllClients(
+ scope, "ServiceWorkerGetClientStorageError",
+ nsTArray<nsString>());
+ });
+ SchedulerGroup::Dispatch(TaskCategory::Other, r.forget());
+ }
+ clientList.Sort(MatchAllComparator());
+ outerPromise->MaybeResolve(clientList);
+ },
+ [outerPromise](const CopyableErrorResult& aResult) {
+ // MaybeReject needs a non-const-ref result, so make a copy.
+ outerPromise->MaybeReject(CopyableErrorResult(aResult));
+ });
+
+ return outerPromise.forget();
+}
+
+already_AddRefed<Promise> Clients::OpenWindow(const nsAString& aURL,
+ ErrorResult& aRv) {
+ MOZ_ASSERT(!NS_IsMainThread());
+ WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate();
+ MOZ_DIAGNOSTIC_ASSERT(workerPrivate);
+ MOZ_DIAGNOSTIC_ASSERT(workerPrivate->IsServiceWorker());
+ workerPrivate->AssertIsOnWorkerThread();
+
+ RefPtr<Promise> outerPromise = Promise::Create(mGlobal, aRv);
+ if (aRv.Failed()) {
+ return outerPromise.forget();
+ }
+
+ if (aURL.EqualsLiteral("about:blank")) {
+ CopyableErrorResult rv;
+ rv.ThrowTypeError(
+ "Passing \"about:blank\" to Clients.openWindow is not allowed");
+ outerPromise->MaybeReject(std::move(rv));
+ return outerPromise.forget();
+ }
+
+ if (!workerPrivate->GlobalScope()->WindowInteractionAllowed()) {
+ outerPromise->MaybeReject(NS_ERROR_DOM_INVALID_ACCESS_ERR);
+ return outerPromise.forget();
+ }
+
+ const PrincipalInfo& principalInfo = workerPrivate->GetPrincipalInfo();
+ const CSPInfo& cspInfo = workerPrivate->GetCSPInfo();
+ nsCString baseURL = workerPrivate->GetLocationInfo().mHref;
+
+ ClientOpenWindowArgs args(principalInfo, Some(cspInfo),
+ NS_ConvertUTF16toUTF8(aURL), baseURL);
+
+ nsCOMPtr<nsIGlobalObject> global = mGlobal;
+
+ StartClientManagerOp(
+ &ClientManager::OpenWindow, args, mGlobal,
+ [outerPromise, global](const ClientOpResult& aResult) {
+ if (aResult.type() != ClientOpResult::TClientInfoAndState) {
+ outerPromise->MaybeResolve(JS::NullHandleValue);
+ return;
+ }
+ RefPtr<Client> client =
+ new Client(global, aResult.get_ClientInfoAndState());
+ outerPromise->MaybeResolve(client);
+ },
+ [outerPromise](const CopyableErrorResult& aResult) {
+ // MaybeReject needs a non-const-ref result, so make a copy.
+ outerPromise->MaybeReject(CopyableErrorResult(aResult));
+ });
+
+ return outerPromise.forget();
+}
+
+already_AddRefed<Promise> Clients::Claim(ErrorResult& aRv) {
+ MOZ_ASSERT(!NS_IsMainThread());
+ WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate();
+ MOZ_DIAGNOSTIC_ASSERT(workerPrivate);
+ MOZ_DIAGNOSTIC_ASSERT(workerPrivate->IsServiceWorker());
+ workerPrivate->AssertIsOnWorkerThread();
+
+ RefPtr<Promise> outerPromise = Promise::Create(mGlobal, aRv);
+ if (aRv.Failed()) {
+ return outerPromise.forget();
+ }
+
+ const ServiceWorkerDescriptor& serviceWorker =
+ workerPrivate->GetServiceWorkerDescriptor();
+
+ if (serviceWorker.State() != ServiceWorkerState::Activating &&
+ serviceWorker.State() != ServiceWorkerState::Activated) {
+ aRv.ThrowInvalidStateError("Service worker is not active");
+ return outerPromise.forget();
+ }
+
+ StartClientManagerOp(
+ &ClientManager::Claim, ClientClaimArgs(serviceWorker.ToIPC()), mGlobal,
+ [outerPromise](const ClientOpResult& aResult) {
+ outerPromise->MaybeResolveWithUndefined();
+ },
+ [outerPromise](const CopyableErrorResult& aResult) {
+ // MaybeReject needs a non-const-ref result, so make a copy.
+ outerPromise->MaybeReject(CopyableErrorResult(aResult));
+ });
+
+ return outerPromise.forget();
+}
+
+} // namespace mozilla::dom
diff --git a/dom/clients/api/Clients.h b/dom/clients/api/Clients.h
new file mode 100644
index 0000000000..481b066655
--- /dev/null
+++ b/dom/clients/api/Clients.h
@@ -0,0 +1,55 @@
+/* -*- 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/. */
+#ifndef _mozilla_dom_Clients_h
+#define _mozilla_dom_Clients_h
+
+#include "nsCOMPtr.h"
+#include "nsISupports.h"
+#include "nsWrapperCache.h"
+
+class nsIGlobalObject;
+
+namespace mozilla {
+
+class ErrorResult;
+
+namespace dom {
+
+struct ClientQueryOptions;
+class Promise;
+
+class Clients final : public nsISupports, public nsWrapperCache {
+ nsCOMPtr<nsIGlobalObject> mGlobal;
+
+ ~Clients() = default;
+
+ public:
+ explicit Clients(nsIGlobalObject* aGlobal);
+
+ // nsWrapperCache interface methods
+ JSObject* WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) override;
+
+ // DOM bindings methods
+ nsIGlobalObject* GetParentObject() const;
+
+ already_AddRefed<Promise> Get(const nsAString& aClientID, ErrorResult& aRv);
+
+ already_AddRefed<Promise> MatchAll(const ClientQueryOptions& aOptions,
+ ErrorResult& aRv);
+
+ already_AddRefed<Promise> OpenWindow(const nsAString& aURL, ErrorResult& aRv);
+
+ already_AddRefed<Promise> Claim(ErrorResult& aRv);
+
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS(Clients)
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // _mozilla_dom_Clients_h
diff --git a/dom/clients/api/moz.build b/dom/clients/api/moz.build
new file mode 100644
index 0000000000..4e864290fd
--- /dev/null
+++ b/dom/clients/api/moz.build
@@ -0,0 +1,25 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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/.
+
+EXPORTS.mozilla.dom += [
+ "Client.h",
+ "Clients.h",
+]
+
+UNIFIED_SOURCES += [
+ "Client.cpp",
+ "Clients.cpp",
+]
+
+include("/ipc/chromium/chromium-config.mozbuild")
+
+FINAL_LIBRARY = "xul"
+
+MOCHITEST_MANIFESTS += []
+
+BROWSER_CHROME_MANIFESTS += []
+
+XPCSHELL_TESTS_MANIFESTS += []
diff --git a/dom/clients/manager/ClientChannelHelper.cpp b/dom/clients/manager/ClientChannelHelper.cpp
new file mode 100644
index 0000000000..f36cbb5319
--- /dev/null
+++ b/dom/clients/manager/ClientChannelHelper.cpp
@@ -0,0 +1,423 @@
+/* -*- 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 "ClientChannelHelper.h"
+
+#include "ClientManager.h"
+#include "ClientSource.h"
+#include "MainThreadUtils.h"
+#include "mozilla/dom/ClientsBinding.h"
+#include "mozilla/dom/ServiceWorkerDescriptor.h"
+#include "mozilla/ipc/BackgroundUtils.h"
+#include "mozilla/StaticPrefs_privacy.h"
+#include "mozilla/StoragePrincipalHelper.h"
+#include "nsContentUtils.h"
+#include "nsIAsyncVerifyRedirectCallback.h"
+#include "nsIChannel.h"
+#include "nsIChannelEventSink.h"
+#include "nsIHttpChannelInternal.h"
+#include "nsIInterfaceRequestor.h"
+#include "nsIInterfaceRequestorUtils.h"
+
+namespace mozilla::dom {
+
+using mozilla::ipc::PrincipalInfoToPrincipal;
+
+namespace {
+
+// In the default mode, ClientChannelHelper runs in the content process and
+// handles all redirects. When we use DocumentChannel, redirects aren't exposed
+// to the content process, so we run an instance of this in both processes, one
+// to handle redirects in the parent and one to handle the final channel
+// replacement (DocumentChannelChild 'redirects' to the final channel) in the
+// child.
+
+class ClientChannelHelper : public nsIInterfaceRequestor,
+ public nsIChannelEventSink {
+ protected:
+ nsCOMPtr<nsIInterfaceRequestor> mOuter;
+ nsCOMPtr<nsISerialEventTarget> mEventTarget;
+
+ virtual ~ClientChannelHelper() = default;
+
+ NS_IMETHOD
+ GetInterface(const nsIID& aIID, void** aResultOut) override {
+ if (aIID.Equals(NS_GET_IID(nsIChannelEventSink))) {
+ *aResultOut = static_cast<nsIChannelEventSink*>(this);
+ NS_ADDREF_THIS();
+ return NS_OK;
+ }
+
+ if (mOuter) {
+ return mOuter->GetInterface(aIID, aResultOut);
+ }
+
+ return NS_ERROR_NO_INTERFACE;
+ }
+
+ virtual void CreateClient(nsILoadInfo* aLoadInfo, nsIPrincipal* aPrincipal) {
+ CreateClientForPrincipal(aLoadInfo, aPrincipal, mEventTarget);
+ }
+
+ NS_IMETHOD
+ AsyncOnChannelRedirect(nsIChannel* aOldChannel, nsIChannel* aNewChannel,
+ uint32_t aFlags,
+ nsIAsyncVerifyRedirectCallback* aCallback) override {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ nsresult rv = nsContentUtils::CheckSameOrigin(aOldChannel, aNewChannel);
+ if (NS_WARN_IF(NS_FAILED(rv) && rv != NS_ERROR_DOM_BAD_URI)) {
+ return rv;
+ }
+
+ nsCOMPtr<nsILoadInfo> oldLoadInfo = aOldChannel->LoadInfo();
+ nsCOMPtr<nsILoadInfo> newLoadInfo = aNewChannel->LoadInfo();
+
+ UniquePtr<ClientSource> reservedClient =
+ oldLoadInfo->TakeReservedClientSource();
+
+ // If its a same-origin redirect we just move our reserved client to the
+ // new channel.
+ if (NS_SUCCEEDED(rv)) {
+ if (reservedClient) {
+ newLoadInfo->GiveReservedClientSource(std::move(reservedClient));
+ }
+
+ // It seems sometimes necko passes two channels with the same LoadInfo.
+ // We only need to move the reserved/initial ClientInfo over if we
+ // actually have a different LoadInfo.
+ else if (oldLoadInfo != newLoadInfo) {
+ const Maybe<ClientInfo>& reservedClientInfo =
+ oldLoadInfo->GetReservedClientInfo();
+
+ const Maybe<ClientInfo>& initialClientInfo =
+ oldLoadInfo->GetInitialClientInfo();
+
+ MOZ_DIAGNOSTIC_ASSERT(reservedClientInfo.isNothing() ||
+ initialClientInfo.isNothing());
+
+ if (reservedClientInfo.isSome()) {
+ // Create a new client for the case the controller is cleared for the
+ // new loadInfo. ServiceWorkerManager::DispatchFetchEvent() called
+ // ServiceWorkerManager::StartControllingClient() making the old
+ // client to be controlled eventually. However, the controller setting
+ // propagation to the child process could happen later than
+ // nsGlobalWindowInner::EnsureClientSource(), such that
+ // nsGlobalWindowInner will be controlled as unexpected.
+ if (oldLoadInfo->GetController().isSome() &&
+ newLoadInfo->GetController().isNothing()) {
+ nsCOMPtr<nsIPrincipal> foreignPartitionedPrincipal;
+ rv = StoragePrincipalHelper::GetPrincipal(
+ aNewChannel,
+ StaticPrefs::privacy_partition_serviceWorkers()
+ ? StoragePrincipalHelper::eForeignPartitionedPrincipal
+ : StoragePrincipalHelper::eRegularPrincipal,
+ getter_AddRefs(foreignPartitionedPrincipal));
+ NS_ENSURE_SUCCESS(rv, rv);
+ reservedClient.reset();
+ CreateClient(newLoadInfo, foreignPartitionedPrincipal);
+ } else {
+ newLoadInfo->SetReservedClientInfo(reservedClientInfo.ref());
+ }
+ }
+
+ if (initialClientInfo.isSome()) {
+ newLoadInfo->SetInitialClientInfo(initialClientInfo.ref());
+ }
+ }
+ }
+
+ // If it's a cross-origin redirect then we discard the old reserved client
+ // and create a new one.
+ else {
+ nsCOMPtr<nsIPrincipal> foreignPartitionedPrincipal;
+ rv = StoragePrincipalHelper::GetPrincipal(
+ aNewChannel,
+ StaticPrefs::privacy_partition_serviceWorkers()
+ ? StoragePrincipalHelper::eForeignPartitionedPrincipal
+ : StoragePrincipalHelper::eRegularPrincipal,
+ getter_AddRefs(foreignPartitionedPrincipal));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ reservedClient.reset();
+ CreateClient(newLoadInfo, foreignPartitionedPrincipal);
+ }
+
+ uint32_t redirectMode = nsIHttpChannelInternal::REDIRECT_MODE_MANUAL;
+ nsCOMPtr<nsIHttpChannelInternal> http = do_QueryInterface(aOldChannel);
+ if (http) {
+ MOZ_ALWAYS_SUCCEEDS(http->GetRedirectMode(&redirectMode));
+ }
+
+ // Normally we keep the controller across channel redirects, but we must
+ // clear it when a document load redirects. Only do this for real
+ // redirects, however.
+ //
+ // This is effectively described in step 4.2 of:
+ //
+ // https://fetch.spec.whatwg.org/#http-fetch
+ //
+ // The spec sets the service-workers mode to none when the request is
+ // configured to *not* follow redirects. This prevents any further
+ // service workers from intercepting. The first service worker that
+ // had a shot at the FetchEvent remains the controller in this case.
+ if (!(aFlags & nsIChannelEventSink::REDIRECT_INTERNAL) &&
+ redirectMode != nsIHttpChannelInternal::REDIRECT_MODE_FOLLOW) {
+ newLoadInfo->ClearController();
+ }
+
+ nsCOMPtr<nsIChannelEventSink> outerSink = do_GetInterface(mOuter);
+ if (outerSink) {
+ return outerSink->AsyncOnChannelRedirect(aOldChannel, aNewChannel, aFlags,
+ aCallback);
+ }
+
+ aCallback->OnRedirectVerifyCallback(NS_OK);
+ return NS_OK;
+ }
+
+ public:
+ ClientChannelHelper(nsIInterfaceRequestor* aOuter,
+ nsISerialEventTarget* aEventTarget)
+ : mOuter(aOuter), mEventTarget(aEventTarget) {}
+
+ NS_DECL_ISUPPORTS
+
+ virtual void CreateClientForPrincipal(nsILoadInfo* aLoadInfo,
+ nsIPrincipal* aPrincipal,
+ nsISerialEventTarget* aEventTarget) {
+ // Create the new ClientSource. This should only happen for window
+ // Clients since support cross-origin redirects are blocked by the
+ // same-origin security policy.
+ UniquePtr<ClientSource> reservedClient = ClientManager::CreateSource(
+ ClientType::Window, aEventTarget, aPrincipal);
+ MOZ_DIAGNOSTIC_ASSERT(reservedClient);
+
+ aLoadInfo->GiveReservedClientSource(std::move(reservedClient));
+ }
+};
+
+NS_IMPL_ISUPPORTS(ClientChannelHelper, nsIInterfaceRequestor,
+ nsIChannelEventSink);
+
+class ClientChannelHelperParent final : public ClientChannelHelper {
+ ~ClientChannelHelperParent() {
+ // This requires that if a load completes, the associated ClientSource is
+ // created and registers itself before this ClientChannelHelperParent is
+ // destroyed. Otherwise, we may incorrectly "forget" a future ClientSource
+ // which will actually be created.
+ SetFutureSourceInfo(Nothing());
+ }
+
+ void CreateClient(nsILoadInfo* aLoadInfo, nsIPrincipal* aPrincipal) override {
+ CreateClientForPrincipal(aLoadInfo, aPrincipal, mEventTarget);
+ }
+
+ void SetFutureSourceInfo(Maybe<ClientInfo>&& aClientInfo) {
+ if (mRecentFutureSourceInfo) {
+ // No-op if the corresponding ClientSource has alrady been created, but
+ // it's not known if that's the case here.
+ ClientManager::ForgetFutureSource(*mRecentFutureSourceInfo);
+ }
+
+ if (aClientInfo) {
+ Unused << NS_WARN_IF(!ClientManager::ExpectFutureSource(*aClientInfo));
+ }
+
+ mRecentFutureSourceInfo = std::move(aClientInfo);
+ }
+
+ // Keep track of the most recent ClientInfo created which isn't backed by a
+ // ClientSource, which is used to notify ClientManagerService that the
+ // ClientSource won't ever actually be constructed.
+ Maybe<ClientInfo> mRecentFutureSourceInfo;
+
+ public:
+ void CreateClientForPrincipal(nsILoadInfo* aLoadInfo,
+ nsIPrincipal* aPrincipal,
+ nsISerialEventTarget* aEventTarget) override {
+ // If we're managing redirects in the parent, then we don't want
+ // to create a new ClientSource (since those need to live with
+ // the global), so just allocate a new ClientInfo/id and we can
+ // create a ClientSource when the final channel propagates back
+ // to the child.
+ Maybe<ClientInfo> reservedInfo =
+ ClientManager::CreateInfo(ClientType::Window, aPrincipal);
+ if (reservedInfo) {
+ aLoadInfo->SetReservedClientInfo(*reservedInfo);
+ SetFutureSourceInfo(std::move(reservedInfo));
+ }
+ }
+ ClientChannelHelperParent(nsIInterfaceRequestor* aOuter,
+ nsISerialEventTarget* aEventTarget)
+ : ClientChannelHelper(aOuter, nullptr) {}
+};
+
+class ClientChannelHelperChild final : public ClientChannelHelper {
+ ~ClientChannelHelperChild() = default;
+
+ NS_IMETHOD
+ AsyncOnChannelRedirect(nsIChannel* aOldChannel, nsIChannel* aNewChannel,
+ uint32_t aFlags,
+ nsIAsyncVerifyRedirectCallback* aCallback) override {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ // All ClientInfo allocation should have been handled in the parent process
+ // by ClientChannelHelperParent, so the only remaining thing to do is to
+ // allocate a ClientSource around the ClientInfo on the channel.
+ CreateReservedSourceIfNeeded(aNewChannel, mEventTarget);
+
+ nsCOMPtr<nsIChannelEventSink> outerSink = do_GetInterface(mOuter);
+ if (outerSink) {
+ return outerSink->AsyncOnChannelRedirect(aOldChannel, aNewChannel, aFlags,
+ aCallback);
+ }
+
+ aCallback->OnRedirectVerifyCallback(NS_OK);
+ return NS_OK;
+ }
+
+ public:
+ ClientChannelHelperChild(nsIInterfaceRequestor* aOuter,
+ nsISerialEventTarget* aEventTarget)
+ : ClientChannelHelper(aOuter, aEventTarget) {}
+};
+
+} // anonymous namespace
+
+template <typename T>
+nsresult AddClientChannelHelperInternal(nsIChannel* aChannel,
+ Maybe<ClientInfo>&& aReservedClientInfo,
+ Maybe<ClientInfo>&& aInitialClientInfo,
+ nsISerialEventTarget* aEventTarget) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ Maybe<ClientInfo> initialClientInfo(std::move(aInitialClientInfo));
+ Maybe<ClientInfo> reservedClientInfo(std::move(aReservedClientInfo));
+ MOZ_DIAGNOSTIC_ASSERT(reservedClientInfo.isNothing() ||
+ initialClientInfo.isNothing());
+
+ nsCOMPtr<nsILoadInfo> loadInfo = aChannel->LoadInfo();
+
+ nsCOMPtr<nsIPrincipal> channelForeignPartitionedPrincipal;
+ nsresult rv = StoragePrincipalHelper::GetPrincipal(
+ aChannel,
+ StaticPrefs::privacy_partition_serviceWorkers()
+ ? StoragePrincipalHelper::eForeignPartitionedPrincipal
+ : StoragePrincipalHelper::eRegularPrincipal,
+ getter_AddRefs(channelForeignPartitionedPrincipal));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Only allow the initial ClientInfo to be set if the current channel
+ // principal matches.
+ if (initialClientInfo.isSome()) {
+ auto initialPrincipalOrErr =
+ PrincipalInfoToPrincipal(initialClientInfo.ref().PrincipalInfo());
+
+ bool equals = false;
+ rv = initialPrincipalOrErr.isErr()
+ ? initialPrincipalOrErr.unwrapErr()
+ : initialPrincipalOrErr.unwrap()->Equals(
+ channelForeignPartitionedPrincipal, &equals);
+ if (NS_FAILED(rv) || !equals) {
+ initialClientInfo.reset();
+ }
+ }
+
+ // Only allow the reserved ClientInfo to be set if the current channel
+ // principal matches.
+ if (reservedClientInfo.isSome()) {
+ auto reservedPrincipalOrErr =
+ PrincipalInfoToPrincipal(reservedClientInfo.ref().PrincipalInfo());
+
+ bool equals = false;
+ rv = reservedPrincipalOrErr.isErr()
+ ? reservedPrincipalOrErr.unwrapErr()
+ : reservedPrincipalOrErr.unwrap()->Equals(
+ channelForeignPartitionedPrincipal, &equals);
+ if (NS_FAILED(rv) || !equals) {
+ reservedClientInfo.reset();
+ }
+ }
+
+ nsCOMPtr<nsIInterfaceRequestor> outerCallbacks;
+ rv = aChannel->GetNotificationCallbacks(getter_AddRefs(outerCallbacks));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ RefPtr<ClientChannelHelper> helper = new T(outerCallbacks, aEventTarget);
+
+ if (initialClientInfo.isNothing() && reservedClientInfo.isNothing()) {
+ helper->CreateClientForPrincipal(
+ loadInfo, channelForeignPartitionedPrincipal, aEventTarget);
+ }
+
+ // Only set the callbacks helper if we are able to reserve the client
+ // successfully.
+ rv = aChannel->SetNotificationCallbacks(helper);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (initialClientInfo.isSome()) {
+ loadInfo->SetInitialClientInfo(initialClientInfo.ref());
+ }
+
+ if (reservedClientInfo.isSome()) {
+ loadInfo->SetReservedClientInfo(reservedClientInfo.ref());
+ }
+
+ return NS_OK;
+}
+
+nsresult AddClientChannelHelper(nsIChannel* aChannel,
+ Maybe<ClientInfo>&& aReservedClientInfo,
+ Maybe<ClientInfo>&& aInitialClientInfo,
+ nsISerialEventTarget* aEventTarget) {
+ return AddClientChannelHelperInternal<ClientChannelHelper>(
+ aChannel, std::move(aReservedClientInfo), std::move(aInitialClientInfo),
+ aEventTarget);
+}
+
+nsresult AddClientChannelHelperInParent(
+ nsIChannel* aChannel, Maybe<ClientInfo>&& aInitialClientInfo) {
+ Maybe<ClientInfo> emptyReservedInfo;
+ return AddClientChannelHelperInternal<ClientChannelHelperParent>(
+ aChannel, std::move(emptyReservedInfo), std::move(aInitialClientInfo),
+ nullptr);
+}
+
+nsresult AddClientChannelHelperInChild(nsIChannel* aChannel,
+ nsISerialEventTarget* aEventTarget) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ nsCOMPtr<nsIInterfaceRequestor> outerCallbacks;
+ nsresult rv =
+ aChannel->GetNotificationCallbacks(getter_AddRefs(outerCallbacks));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ RefPtr<ClientChannelHelper> helper =
+ new ClientChannelHelperChild(outerCallbacks, aEventTarget);
+
+ // Only set the callbacks helper if we are able to reserve the client
+ // successfully.
+ rv = aChannel->SetNotificationCallbacks(helper);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+void CreateReservedSourceIfNeeded(nsIChannel* aChannel,
+ nsISerialEventTarget* aEventTarget) {
+ nsCOMPtr<nsILoadInfo> loadInfo = aChannel->LoadInfo();
+ const Maybe<ClientInfo>& reservedClientInfo =
+ loadInfo->GetReservedClientInfo();
+
+ if (reservedClientInfo) {
+ UniquePtr<ClientSource> reservedClient =
+ ClientManager::CreateSourceFromInfo(*reservedClientInfo, aEventTarget);
+ loadInfo->GiveReservedClientSource(std::move(reservedClient));
+ }
+}
+
+} // namespace mozilla::dom
diff --git a/dom/clients/manager/ClientChannelHelper.h b/dom/clients/manager/ClientChannelHelper.h
new file mode 100644
index 0000000000..e02285cd1c
--- /dev/null
+++ b/dom/clients/manager/ClientChannelHelper.h
@@ -0,0 +1,53 @@
+/* -*- 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/. */
+#ifndef _mozilla_dom_ClientChannelHelper_h
+#define _mozilla_dom_ClientChannelHelper_h
+
+#include "mozilla/Maybe.h"
+#include "nsError.h"
+
+class nsIChannel;
+class nsISerialEventTarget;
+
+namespace mozilla::dom {
+
+class ClientInfo;
+
+// Attach a redirect listener to the given nsIChannel that will manage
+// the various client values on the channel's LoadInfo. This will
+// properly handle creating a new ClientSource on cross-origin redirect
+// and propagate the current reserved/initial client on same-origin
+// redirect.
+nsresult AddClientChannelHelper(nsIChannel* aChannel,
+ Maybe<ClientInfo>&& aReservedClientInfo,
+ Maybe<ClientInfo>&& aInitialClientInfo,
+ nsISerialEventTarget* aEventTarget);
+
+// Use this variant in the content process if redirects will be handled in the
+// parent process (by a channel with AddClientChannelHelperInParent),
+// and this process only sees a single switch to the final channel,
+// as done by DocumentChannel.
+// This variant just handles allocating a ClientSource around an existing
+// ClientInfo allocated in the parent process.
+nsresult AddClientChannelHelperInChild(nsIChannel* aChannel,
+ nsISerialEventTarget* aEventTarget);
+
+// Use this variant in the parent process if redirects are handled there.
+// Does the same as the default variant, except just allocates a ClientInfo
+// and lets the content process create the corresponding ClientSource once
+// it becomes available there.
+nsresult AddClientChannelHelperInParent(nsIChannel* aChannel,
+ Maybe<ClientInfo>&& aInitialClientInfo);
+
+// If the channel's LoadInfo has a reserved ClientInfo, but no reserved
+// ClientSource, then allocates a ClientSource using that existing
+// ClientInfo.
+void CreateReservedSourceIfNeeded(nsIChannel* aChannel,
+ nsISerialEventTarget* aEventTarget);
+
+} // namespace mozilla::dom
+
+#endif // _mozilla_dom_ClientChannelHelper_h
diff --git a/dom/clients/manager/ClientHandle.cpp b/dom/clients/manager/ClientHandle.cpp
new file mode 100644
index 0000000000..eebffae4c7
--- /dev/null
+++ b/dom/clients/manager/ClientHandle.cpp
@@ -0,0 +1,195 @@
+/* -*- 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 "ClientHandle.h"
+
+#include "ClientHandleChild.h"
+#include "ClientHandleOpChild.h"
+#include "ClientManager.h"
+#include "ClientPrincipalUtils.h"
+#include "ClientState.h"
+#include "mozilla/dom/PClientManagerChild.h"
+#include "mozilla/dom/ServiceWorkerDescriptor.h"
+#include "mozilla/dom/ipc/StructuredCloneData.h"
+
+namespace mozilla::dom {
+
+using mozilla::dom::ipc::StructuredCloneData;
+
+ClientHandle::~ClientHandle() { Shutdown(); }
+
+void ClientHandle::Shutdown() {
+ NS_ASSERT_OWNINGTHREAD(ClientSource);
+ if (IsShutdown()) {
+ return;
+ }
+
+ ShutdownThing();
+
+ mManager = nullptr;
+}
+
+void ClientHandle::StartOp(const ClientOpConstructorArgs& aArgs,
+ const ClientOpCallback&& aResolveCallback,
+ const ClientOpCallback&& aRejectCallback) {
+ // Hold a ref to the client until the remote operation completes. Otherwise
+ // the ClientHandle might get de-refed and teardown the actor before we
+ // get an answer.
+ RefPtr<ClientHandle> kungFuGrip = this;
+
+ MaybeExecute(
+ [&aArgs, kungFuGrip, aRejectCallback,
+ resolve = std::move(aResolveCallback)](ClientHandleChild* aActor) {
+ MOZ_DIAGNOSTIC_ASSERT(aActor);
+ ClientHandleOpChild* actor = new ClientHandleOpChild(
+ kungFuGrip, aArgs, std::move(resolve), std::move(aRejectCallback));
+ if (!aActor->SendPClientHandleOpConstructor(actor, aArgs)) {
+ // Constructor failure will call reject callback via ActorDestroy()
+ return;
+ }
+ },
+ [aRejectCallback] {
+ MOZ_DIAGNOSTIC_ASSERT(aRejectCallback);
+ CopyableErrorResult rv;
+ rv.ThrowInvalidStateError("Client has been destroyed");
+ aRejectCallback(rv);
+ });
+}
+
+void ClientHandle::OnShutdownThing() {
+ NS_ASSERT_OWNINGTHREAD(ClientHandle);
+ if (!mDetachPromise) {
+ return;
+ }
+ mDetachPromise->Resolve(true, __func__);
+}
+
+ClientHandle::ClientHandle(ClientManager* aManager,
+ nsISerialEventTarget* aSerialEventTarget,
+ const ClientInfo& aClientInfo)
+ : mManager(aManager),
+ mSerialEventTarget(aSerialEventTarget),
+ mClientInfo(aClientInfo) {
+ MOZ_DIAGNOSTIC_ASSERT(mManager);
+ MOZ_DIAGNOSTIC_ASSERT(mSerialEventTarget);
+ MOZ_ASSERT(mSerialEventTarget->IsOnCurrentThread());
+}
+
+void ClientHandle::Activate(PClientManagerChild* aActor) {
+ NS_ASSERT_OWNINGTHREAD(ClientHandle);
+
+ if (IsShutdown()) {
+ return;
+ }
+
+ PClientHandleChild* actor =
+ aActor->SendPClientHandleConstructor(mClientInfo.ToIPC());
+ if (!actor) {
+ Shutdown();
+ return;
+ }
+
+ ActivateThing(static_cast<ClientHandleChild*>(actor));
+}
+
+void ClientHandle::ExecutionReady(const ClientInfo& aClientInfo) {
+ mClientInfo = aClientInfo;
+}
+
+const ClientInfo& ClientHandle::Info() const { return mClientInfo; }
+
+RefPtr<GenericErrorResultPromise> ClientHandle::Control(
+ const ServiceWorkerDescriptor& aServiceWorker) {
+ RefPtr<GenericErrorResultPromise::Private> outerPromise =
+ new GenericErrorResultPromise::Private(__func__);
+
+ // We should never have a cross-origin controller. Since this would be
+ // same-origin policy violation we do a full release assertion here.
+ MOZ_RELEASE_ASSERT(ClientMatchPrincipalInfo(mClientInfo.PrincipalInfo(),
+ aServiceWorker.PrincipalInfo()));
+
+ StartOp(
+ ClientControlledArgs(aServiceWorker.ToIPC()),
+ [outerPromise](const ClientOpResult& aResult) {
+ outerPromise->Resolve(true, __func__);
+ },
+ [outerPromise](const ClientOpResult& aResult) {
+ outerPromise->Reject(aResult.get_CopyableErrorResult(), __func__);
+ });
+
+ return outerPromise;
+}
+
+RefPtr<ClientStatePromise> ClientHandle::Focus(CallerType aCallerType) {
+ RefPtr<ClientStatePromise::Private> outerPromise =
+ new ClientStatePromise::Private(__func__);
+
+ StartOp(
+ ClientFocusArgs(aCallerType),
+ [outerPromise](const ClientOpResult& aResult) {
+ outerPromise->Resolve(
+ ClientState::FromIPC(aResult.get_IPCClientState()), __func__);
+ },
+ [outerPromise](const ClientOpResult& aResult) {
+ outerPromise->Reject(aResult.get_CopyableErrorResult(), __func__);
+ });
+
+ return outerPromise;
+}
+
+RefPtr<GenericErrorResultPromise> ClientHandle::PostMessage(
+ StructuredCloneData& aData, const ServiceWorkerDescriptor& aSource) {
+ if (IsShutdown()) {
+ CopyableErrorResult rv;
+ rv.ThrowInvalidStateError("Client has been destroyed");
+ return GenericErrorResultPromise::CreateAndReject(rv, __func__);
+ }
+
+ ClientPostMessageArgs args;
+ args.serviceWorker() = aSource.ToIPC();
+
+ if (!aData.BuildClonedMessageData(args.clonedData())) {
+ CopyableErrorResult rv;
+ rv.ThrowInvalidStateError("Failed to clone data");
+ return GenericErrorResultPromise::CreateAndReject(rv, __func__);
+ }
+
+ RefPtr<GenericErrorResultPromise::Private> outerPromise =
+ new GenericErrorResultPromise::Private(__func__);
+
+ StartOp(
+ std::move(args),
+ [outerPromise](const ClientOpResult& aResult) {
+ outerPromise->Resolve(true, __func__);
+ },
+ [outerPromise](const ClientOpResult& aResult) {
+ outerPromise->Reject(aResult.get_CopyableErrorResult(), __func__);
+ });
+
+ return outerPromise;
+}
+
+RefPtr<GenericPromise> ClientHandle::OnDetach() {
+ NS_ASSERT_OWNINGTHREAD(ClientSource);
+
+ if (!mDetachPromise) {
+ mDetachPromise = new GenericPromise::Private(__func__);
+ if (IsShutdown()) {
+ mDetachPromise->Resolve(true, __func__);
+ }
+ }
+
+ return mDetachPromise;
+}
+
+void ClientHandle::EvictFromBFCache() {
+ ClientEvictBFCacheArgs args;
+ StartOp(
+ std::move(args), [](const ClientOpResult& aResult) {},
+ [](const ClientOpResult& aResult) {});
+}
+
+} // namespace mozilla::dom
diff --git a/dom/clients/manager/ClientHandle.h b/dom/clients/manager/ClientHandle.h
new file mode 100644
index 0000000000..33fa2bd1a5
--- /dev/null
+++ b/dom/clients/manager/ClientHandle.h
@@ -0,0 +1,112 @@
+/* -*- 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/. */
+#ifndef _mozilla_dom_ClientHandle_h
+#define _mozilla_dom_ClientHandle_h
+
+#include "mozilla/dom/ClientInfo.h"
+#include "mozilla/dom/ClientOpPromise.h"
+#include "mozilla/dom/ClientThing.h"
+#include "mozilla/MozPromise.h"
+
+#ifdef XP_WIN
+# undef PostMessage
+#endif
+
+namespace mozilla::dom {
+
+class ClientManager;
+class ClientHandleChild;
+class ClientOpConstructorArgs;
+class PClientManagerChild;
+class ServiceWorkerDescriptor;
+enum class CallerType : uint32_t;
+
+namespace ipc {
+class StructuredCloneData;
+}
+
+// The ClientHandle allows code to take a simple ClientInfo struct and
+// convert it into a live actor-backed object attached to a particular
+// ClientSource somewhere in the browser. If the ClientSource is
+// destroyed then the ClientHandle will simply begin to reject operations.
+// We do not currently provide a way to be notified when the ClientSource
+// is destroyed, but this could be added in the future.
+class ClientHandle final : public ClientThing<ClientHandleChild> {
+ friend class ClientManager;
+ friend class ClientHandleChild;
+
+ RefPtr<ClientManager> mManager;
+ nsCOMPtr<nsISerialEventTarget> mSerialEventTarget;
+ RefPtr<GenericPromise::Private> mDetachPromise;
+ ClientInfo mClientInfo;
+
+ ~ClientHandle();
+
+ void Shutdown();
+
+ void StartOp(const ClientOpConstructorArgs& aArgs,
+ const ClientOpCallback&& aResolveCallback,
+ const ClientOpCallback&& aRejectCallback);
+
+ // ClientThing interface
+ void OnShutdownThing() override;
+
+ // Private methods called by ClientHandleChild
+ void ExecutionReady(const ClientInfo& aClientInfo);
+
+ // Private methods called by ClientManager
+ ClientHandle(ClientManager* aManager,
+ nsISerialEventTarget* aSerialEventTarget,
+ const ClientInfo& aClientInfo);
+
+ void Activate(PClientManagerChild* aActor);
+
+ public:
+ const ClientInfo& Info() const;
+
+ // Mark the ClientSource attached to this handle as controlled by the
+ // given service worker. The promise will resolve true if the ClientSource
+ // is successfully marked or reject if the operation could not be completed.
+ RefPtr<GenericErrorResultPromise> Control(
+ const ServiceWorkerDescriptor& aServiceWorker);
+
+ // Focus the Client if possible. If successful the promise will resolve with
+ // a new ClientState snapshot after focus has completed. If focusing fails
+ // for any reason then the promise will reject.
+ RefPtr<ClientStatePromise> Focus(CallerType aCallerType);
+
+ // Send a postMessage() call to the target Client. Currently this only
+ // supports sending from a ServiceWorker source and the MessageEvent is
+ // dispatched to the Client's navigator.serviceWorker event target. The
+ // returned promise will resolve if the MessageEvent is dispatched or if
+ // it triggers an error handled in the Client's context. Other errors
+ // will result in the promise rejecting.
+ RefPtr<GenericErrorResultPromise> PostMessage(
+ ipc::StructuredCloneData& aData, const ServiceWorkerDescriptor& aSource);
+
+ // Return a Promise that resolves when the ClientHandle object is detached
+ // from its remote actors. This will happen if the ClientSource is destroyed
+ // and triggers the cleanup of the handle actors. It will also naturally
+ // happen when the ClientHandle is de-referenced and tears down its own
+ // actors.
+ //
+ // Note: This method can only be called on the ClientHandle owning thread,
+ // but the MozPromise lets you Then() to another thread.
+ RefPtr<GenericPromise> OnDetach();
+
+ // This is intended to allow the ServiceWorkerManager to evict controlled
+ // clients when their controlling registration changes. This should not be
+ // used by other holders of ClientHandles. This method can probably be removed
+ // when ServiceWorkerManager and ClientManagerService both live on the same
+ // thread.
+ void EvictFromBFCache();
+
+ NS_INLINE_DECL_REFCOUNTING(ClientHandle);
+};
+
+} // namespace mozilla::dom
+
+#endif // _mozilla_dom_ClientHandle_h
diff --git a/dom/clients/manager/ClientHandleChild.cpp b/dom/clients/manager/ClientHandleChild.cpp
new file mode 100644
index 0000000000..a0717a9954
--- /dev/null
+++ b/dom/clients/manager/ClientHandleChild.cpp
@@ -0,0 +1,70 @@
+/* -*- 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 "ClientHandleChild.h"
+
+#include "ClientHandle.h"
+#include "ClientHandleOpChild.h"
+#include "mozilla/dom/ClientIPCTypes.h"
+
+namespace mozilla::dom {
+
+using mozilla::ipc::IPCResult;
+
+IPCResult ClientHandleChild::RecvExecutionReady(
+ const IPCClientInfo& aClientInfo) {
+ if (mHandle) {
+ mHandle->ExecutionReady(ClientInfo(aClientInfo));
+ }
+ return IPC_OK();
+}
+
+void ClientHandleChild::ActorDestroy(ActorDestroyReason aReason) {
+ if (mHandle) {
+ mHandle->RevokeActor(this);
+
+ // Revoking the actor link should automatically cause the owner
+ // to call RevokeOwner() as well.
+ MOZ_DIAGNOSTIC_ASSERT(!mHandle);
+ }
+}
+
+PClientHandleOpChild* ClientHandleChild::AllocPClientHandleOpChild(
+ const ClientOpConstructorArgs& aArgs) {
+ MOZ_ASSERT_UNREACHABLE("ClientHandleOpChild must be explicitly constructed.");
+ return nullptr;
+}
+
+bool ClientHandleChild::DeallocPClientHandleOpChild(
+ PClientHandleOpChild* aActor) {
+ delete aActor;
+ return true;
+}
+
+ClientHandleChild::ClientHandleChild()
+ : mHandle(nullptr), mTeardownStarted(false) {}
+
+void ClientHandleChild::SetOwner(ClientThing<ClientHandleChild>* aThing) {
+ MOZ_DIAGNOSTIC_ASSERT(!mHandle);
+ mHandle = static_cast<ClientHandle*>(aThing);
+ MOZ_DIAGNOSTIC_ASSERT(mHandle);
+}
+
+void ClientHandleChild::RevokeOwner(ClientThing<ClientHandleChild>* aThing) {
+ MOZ_DIAGNOSTIC_ASSERT(mHandle);
+ MOZ_DIAGNOSTIC_ASSERT(mHandle == static_cast<ClientHandle*>(aThing));
+ mHandle = nullptr;
+}
+
+void ClientHandleChild::MaybeStartTeardown() {
+ if (mTeardownStarted) {
+ return;
+ }
+ mTeardownStarted = true;
+ Unused << SendTeardown();
+}
+
+} // namespace mozilla::dom
diff --git a/dom/clients/manager/ClientHandleChild.h b/dom/clients/manager/ClientHandleChild.h
new file mode 100644
index 0000000000..8aeba96712
--- /dev/null
+++ b/dom/clients/manager/ClientHandleChild.h
@@ -0,0 +1,47 @@
+/* -*- 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/. */
+#ifndef _mozilla_dom_ClientHandleChild_h
+#define _mozilla_dom_ClientHandleChild_h
+
+#include "ClientThing.h"
+#include "mozilla/dom/PClientHandleChild.h"
+
+namespace mozilla::dom {
+
+class ClientHandle;
+class ClientInfo;
+
+template <typename ActorType>
+class ClientThing;
+
+class ClientHandleChild final : public PClientHandleChild {
+ ClientHandle* mHandle;
+ bool mTeardownStarted;
+
+ // PClientHandleChild interface
+ mozilla::ipc::IPCResult RecvExecutionReady(
+ const IPCClientInfo& aClientInfo) override;
+
+ void ActorDestroy(ActorDestroyReason aReason) override;
+
+ PClientHandleOpChild* AllocPClientHandleOpChild(
+ const ClientOpConstructorArgs& aArgs) override;
+
+ bool DeallocPClientHandleOpChild(PClientHandleOpChild* aActor) override;
+
+ public:
+ ClientHandleChild();
+
+ void SetOwner(ClientThing<ClientHandleChild>* aThing);
+
+ void RevokeOwner(ClientThing<ClientHandleChild>* aThing);
+
+ void MaybeStartTeardown();
+};
+
+} // namespace mozilla::dom
+
+#endif // _mozilla_dom_ClientHandleChild_h
diff --git a/dom/clients/manager/ClientHandleOpChild.cpp b/dom/clients/manager/ClientHandleOpChild.cpp
new file mode 100644
index 0000000000..0370d5512b
--- /dev/null
+++ b/dom/clients/manager/ClientHandleOpChild.cpp
@@ -0,0 +1,44 @@
+/* -*- 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 "ClientHandleOpChild.h"
+
+#include "ClientHandle.h"
+
+namespace mozilla::dom {
+
+void ClientHandleOpChild::ActorDestroy(ActorDestroyReason aReason) {
+ mClientHandle = nullptr;
+ CopyableErrorResult rv;
+ rv.ThrowAbortError("Client load aborted");
+ mRejectCallback(rv);
+}
+
+mozilla::ipc::IPCResult ClientHandleOpChild::Recv__delete__(
+ const ClientOpResult& aResult) {
+ mClientHandle = nullptr;
+ if (aResult.type() == ClientOpResult::TCopyableErrorResult &&
+ aResult.get_CopyableErrorResult().Failed()) {
+ mRejectCallback(aResult.get_CopyableErrorResult());
+ return IPC_OK();
+ }
+ mResolveCallback(aResult);
+ return IPC_OK();
+}
+
+ClientHandleOpChild::ClientHandleOpChild(
+ ClientHandle* aClientHandle, const ClientOpConstructorArgs& aArgs,
+ const ClientOpCallback&& aResolveCallback,
+ const ClientOpCallback&& aRejectCallback)
+ : mClientHandle(aClientHandle),
+ mResolveCallback(std::move(aResolveCallback)),
+ mRejectCallback(std::move(aRejectCallback)) {
+ MOZ_DIAGNOSTIC_ASSERT(mClientHandle);
+ MOZ_DIAGNOSTIC_ASSERT(mResolveCallback);
+ MOZ_DIAGNOSTIC_ASSERT(mRejectCallback);
+}
+
+} // namespace mozilla::dom
diff --git a/dom/clients/manager/ClientHandleOpChild.h b/dom/clients/manager/ClientHandleOpChild.h
new file mode 100644
index 0000000000..220636ca57
--- /dev/null
+++ b/dom/clients/manager/ClientHandleOpChild.h
@@ -0,0 +1,38 @@
+/* -*- 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/. */
+#ifndef _mozilla_dom_ClientHandleOpChild_h
+#define _mozilla_dom_ClientHandleOpChild_h
+
+#include "mozilla/dom/ClientOpPromise.h"
+#include "mozilla/dom/PClientHandleOpChild.h"
+
+namespace mozilla::dom {
+
+class ClientHandle;
+
+class ClientHandleOpChild final : public PClientHandleOpChild {
+ RefPtr<ClientHandle> mClientHandle;
+ const ClientOpCallback mResolveCallback;
+ const ClientOpCallback mRejectCallback;
+
+ // PClientHandleOpChild interface
+ void ActorDestroy(ActorDestroyReason aReason) override;
+
+ mozilla::ipc::IPCResult Recv__delete__(
+ const ClientOpResult& aResult) override;
+
+ public:
+ ClientHandleOpChild(ClientHandle* aClientHandle,
+ const ClientOpConstructorArgs& aArgs,
+ const ClientOpCallback&& aResolveCallback,
+ const ClientOpCallback&& aRejectCallback);
+
+ ~ClientHandleOpChild() = default;
+};
+
+} // namespace mozilla::dom
+
+#endif // _mozilla_dom_ClientHandleOpChild_h
diff --git a/dom/clients/manager/ClientHandleOpParent.cpp b/dom/clients/manager/ClientHandleOpParent.cpp
new file mode 100644
index 0000000000..37f1c6258b
--- /dev/null
+++ b/dom/clients/manager/ClientHandleOpParent.cpp
@@ -0,0 +1,96 @@
+/* -*- 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 "ClientHandleOpParent.h"
+
+#include "ClientHandleParent.h"
+#include "ClientSourceParent.h"
+#include "mozilla/dom/ipc/StructuredCloneData.h"
+#include "mozilla/dom/PClientManagerParent.h"
+
+namespace mozilla::dom {
+
+ClientSourceParent* ClientHandleOpParent::GetSource() const {
+ auto handle = static_cast<ClientHandleParent*>(Manager());
+ return handle->GetSource();
+}
+
+void ClientHandleOpParent::ActorDestroy(ActorDestroyReason aReason) {
+ mPromiseRequestHolder.DisconnectIfExists();
+ mSourcePromiseRequestHolder.DisconnectIfExists();
+}
+
+void ClientHandleOpParent::Init(ClientOpConstructorArgs&& aArgs) {
+ auto handle = static_cast<ClientHandleParent*>(Manager());
+ handle->EnsureSource()
+ ->Then(
+ GetCurrentSerialEventTarget(), __func__,
+ [this, handle, args = std::move(aArgs)](bool) mutable {
+ mSourcePromiseRequestHolder.Complete();
+
+ auto source = handle->GetSource();
+ if (!source) {
+ CopyableErrorResult rv;
+ rv.ThrowAbortError("Client has been destroyed");
+ Unused << PClientHandleOpParent::Send__delete__(this, rv);
+ return;
+ }
+ RefPtr<ClientOpPromise> p;
+
+ // ClientPostMessageArgs can contain PBlob actors. This means we
+ // can't just forward the args from one PBackground manager to
+ // another. Instead, unpack the structured clone data and repack
+ // it into a new set of arguments.
+ if (args.type() ==
+ ClientOpConstructorArgs::TClientPostMessageArgs) {
+ const ClientPostMessageArgs& orig =
+ args.get_ClientPostMessageArgs();
+
+ ClientPostMessageArgs rebuild;
+ rebuild.serviceWorker() = orig.serviceWorker();
+
+ ipc::StructuredCloneData data;
+ data.BorrowFromClonedMessageData(orig.clonedData());
+ if (!data.BuildClonedMessageData(rebuild.clonedData())) {
+ CopyableErrorResult rv;
+ rv.ThrowAbortError("Aborting client operation");
+ Unused << PClientHandleOpParent::Send__delete__(this, rv);
+ return;
+ }
+
+ p = source->StartOp(std::move(rebuild));
+ }
+
+ // Other argument types can just be forwarded straight through.
+ else {
+ p = source->StartOp(std::move(args));
+ }
+
+ // Capturing 'this' is safe here because we disconnect the promise
+ // in ActorDestroy() which ensures neither lambda is called if the
+ // actor is destroyed before the source operation completes.
+ p->Then(
+ GetCurrentSerialEventTarget(), __func__,
+ [this](const ClientOpResult& aResult) {
+ mPromiseRequestHolder.Complete();
+ Unused << PClientHandleOpParent::Send__delete__(this,
+ aResult);
+ },
+ [this](const CopyableErrorResult& aRv) {
+ mPromiseRequestHolder.Complete();
+ Unused << PClientHandleOpParent::Send__delete__(this, aRv);
+ })
+ ->Track(mPromiseRequestHolder);
+ },
+ [=](const CopyableErrorResult& failure) {
+ mSourcePromiseRequestHolder.Complete();
+ Unused << PClientHandleOpParent::Send__delete__(this, failure);
+ return;
+ })
+ ->Track(mSourcePromiseRequestHolder);
+}
+
+} // namespace mozilla::dom
diff --git a/dom/clients/manager/ClientHandleOpParent.h b/dom/clients/manager/ClientHandleOpParent.h
new file mode 100644
index 0000000000..f8e7cfb89f
--- /dev/null
+++ b/dom/clients/manager/ClientHandleOpParent.h
@@ -0,0 +1,35 @@
+/* -*- 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/. */
+#ifndef _mozilla_dom_ClientHandleOpParent_h
+#define _mozilla_dom_ClientHandleOpParent_h
+
+#include "ClientOpPromise.h"
+#include "mozilla/dom/PClientHandleOpParent.h"
+#include "ClientHandleParent.h"
+
+namespace mozilla::dom {
+
+class ClientSourceParent;
+
+class ClientHandleOpParent final : public PClientHandleOpParent {
+ MozPromiseRequestHolder<ClientOpPromise> mPromiseRequestHolder;
+ MozPromiseRequestHolder<SourcePromise> mSourcePromiseRequestHolder;
+
+ ClientSourceParent* GetSource() const;
+
+ // PClientHandleOpParent interface
+ void ActorDestroy(ActorDestroyReason aReason) override;
+
+ public:
+ ClientHandleOpParent() = default;
+ ~ClientHandleOpParent() = default;
+
+ void Init(ClientOpConstructorArgs&& aArgs);
+};
+
+} // namespace mozilla::dom
+
+#endif // _mozilla_dom_ClientHandleOpParent_h
diff --git a/dom/clients/manager/ClientHandleParent.cpp b/dom/clients/manager/ClientHandleParent.cpp
new file mode 100644
index 0000000000..037a496544
--- /dev/null
+++ b/dom/clients/manager/ClientHandleParent.cpp
@@ -0,0 +1,115 @@
+/* -*- 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 "ClientHandleParent.h"
+
+#include "ClientHandleOpParent.h"
+#include "ClientManagerService.h"
+#include "ClientPrincipalUtils.h"
+#include "ClientSourceParent.h"
+#include "mozilla/dom/ClientIPCTypes.h"
+#include "mozilla/Unused.h"
+
+namespace mozilla::dom {
+
+using mozilla::ipc::IPCResult;
+
+IPCResult ClientHandleParent::RecvTeardown() {
+ Unused << Send__delete__(this);
+ return IPC_OK();
+}
+
+void ClientHandleParent::ActorDestroy(ActorDestroyReason aReason) {
+ if (mSource) {
+ mSource->DetachHandle(this);
+ mSource = nullptr;
+ } else {
+ if (!mSourcePromiseHolder.IsEmpty()) {
+ CopyableErrorResult rv;
+ rv.ThrowAbortError("Client aborted");
+ mSourcePromiseHolder.Reject(rv, __func__);
+ }
+
+ mSourcePromiseRequestHolder.DisconnectIfExists();
+ }
+}
+
+PClientHandleOpParent* ClientHandleParent::AllocPClientHandleOpParent(
+ const ClientOpConstructorArgs& aArgs) {
+ return new ClientHandleOpParent();
+}
+
+bool ClientHandleParent::DeallocPClientHandleOpParent(
+ PClientHandleOpParent* aActor) {
+ delete aActor;
+ return true;
+}
+
+IPCResult ClientHandleParent::RecvPClientHandleOpConstructor(
+ PClientHandleOpParent* aActor, const ClientOpConstructorArgs& aArgs) {
+ auto actor = static_cast<ClientHandleOpParent*>(aActor);
+ actor->Init(std::move(const_cast<ClientOpConstructorArgs&>(aArgs)));
+ return IPC_OK();
+}
+
+ClientHandleParent::ClientHandleParent()
+ : mService(ClientManagerService::GetOrCreateInstance()), mSource(nullptr) {}
+
+ClientHandleParent::~ClientHandleParent() { MOZ_DIAGNOSTIC_ASSERT(!mSource); }
+
+void ClientHandleParent::Init(const IPCClientInfo& aClientInfo) {
+ mClientId = aClientInfo.id();
+ mPrincipalInfo = aClientInfo.principalInfo();
+
+ // Callbacks are disconnected in ActorDestroy, so capturing `this` is safe.
+ mService->FindSource(aClientInfo.id(), aClientInfo.principalInfo())
+ ->Then(
+ GetCurrentSerialEventTarget(), __func__,
+ [this](bool) {
+ mSourcePromiseRequestHolder.Complete();
+ ClientSourceParent* source =
+ mService->FindExistingSource(mClientId, mPrincipalInfo);
+ if (source) {
+ FoundSource(source);
+ }
+ },
+ [this](const CopyableErrorResult&) {
+ mSourcePromiseRequestHolder.Complete();
+ Unused << Send__delete__(this);
+ })
+ ->Track(mSourcePromiseRequestHolder);
+}
+
+ClientSourceParent* ClientHandleParent::GetSource() const { return mSource; }
+
+RefPtr<SourcePromise> ClientHandleParent::EnsureSource() {
+ if (mSource) {
+ return SourcePromise::CreateAndResolve(mSource, __func__);
+ }
+
+ return mSourcePromiseHolder.Ensure(__func__);
+}
+
+void ClientHandleParent::FoundSource(ClientSourceParent* aSource) {
+ MOZ_ASSERT(aSource);
+ MOZ_ASSERT(aSource->Info().Id() == mClientId);
+ if (!ClientMatchPrincipalInfo(aSource->Info().PrincipalInfo(),
+ mPrincipalInfo)) {
+ if (mSourcePromiseHolder.IsEmpty()) {
+ CopyableErrorResult rv;
+ rv.ThrowAbortError("Client aborted");
+ mSourcePromiseHolder.Reject(rv, __func__);
+ }
+ Unused << Send__delete__(this);
+ return;
+ }
+
+ mSource = aSource;
+ mSource->AttachHandle(this);
+ mSourcePromiseHolder.ResolveIfExists(true, __func__);
+}
+
+} // namespace mozilla::dom
diff --git a/dom/clients/manager/ClientHandleParent.h b/dom/clients/manager/ClientHandleParent.h
new file mode 100644
index 0000000000..9272972f11
--- /dev/null
+++ b/dom/clients/manager/ClientHandleParent.h
@@ -0,0 +1,63 @@
+/* -*- 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/. */
+#ifndef _mozilla_dom_ClientHandleParent_h
+#define _mozilla_dom_ClientHandleParent_h
+
+#include "mozilla/dom/PClientHandleParent.h"
+
+namespace mozilla::dom {
+
+class ClientManagerService;
+class ClientSourceParent;
+
+using SourcePromise =
+ MozPromise<bool, CopyableErrorResult, /* IsExclusive = */ false>;
+
+class ClientHandleParent final : public PClientHandleParent {
+ RefPtr<ClientManagerService> mService;
+
+ // mSource and mSourcePromiseHolder are mutually exclusive.
+ ClientSourceParent* mSource;
+
+ // Operations will wait on this promise while mSource is null.
+ MozPromiseHolder<SourcePromise> mSourcePromiseHolder;
+
+ MozPromiseRequestHolder<SourcePromise> mSourcePromiseRequestHolder;
+
+ nsID mClientId;
+ mozilla::ipc::PrincipalInfo mPrincipalInfo;
+
+ // PClientHandleParent interface
+ mozilla::ipc::IPCResult RecvTeardown() override;
+
+ void ActorDestroy(ActorDestroyReason aReason) override;
+
+ PClientHandleOpParent* AllocPClientHandleOpParent(
+ const ClientOpConstructorArgs& aArgs) override;
+
+ bool DeallocPClientHandleOpParent(PClientHandleOpParent* aActor) override;
+
+ mozilla::ipc::IPCResult RecvPClientHandleOpConstructor(
+ PClientHandleOpParent* aActor,
+ const ClientOpConstructorArgs& aArgs) override;
+
+ public:
+ ClientHandleParent();
+ ~ClientHandleParent();
+
+ void Init(const IPCClientInfo& aClientInfo);
+
+ void FoundSource(ClientSourceParent* aSource);
+
+ // Should be called only once EnsureSource() has resolved. May return nullptr.
+ ClientSourceParent* GetSource() const;
+
+ RefPtr<SourcePromise> EnsureSource();
+};
+
+} // namespace mozilla::dom
+
+#endif // _mozilla_dom_ClientHandleParent_h
diff --git a/dom/clients/manager/ClientIPCTypes.ipdlh b/dom/clients/manager/ClientIPCTypes.ipdlh
new file mode 100644
index 0000000000..633d1005d3
--- /dev/null
+++ b/dom/clients/manager/ClientIPCTypes.ipdlh
@@ -0,0 +1,165 @@
+/* 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 protocol PClientSource;
+include DOMTypes;
+include PBackgroundSharedTypes;
+include IPCServiceWorkerDescriptor;
+include ProtocolTypes;
+
+include "mozilla/dom/BindingIPCUtils.h";
+include "mozilla/dom/ClientIPCUtils.h";
+include "ipc/ErrorIPCUtils.h";
+
+using mozilla::TimeStamp from "mozilla/TimeStamp.h";
+using ClientType from "mozilla/dom/ClientsBinding.h";
+using FrameType from "mozilla/dom/ClientBinding.h";
+using mozilla::StorageAccess from "mozilla/StorageAccess.h";
+using VisibilityState from "mozilla/dom/DocumentBinding.h";
+using CallerType from "mozilla/dom/BindingDeclarations.h";
+using mozilla::CopyableErrorResult from "mozilla/ErrorResult.h";
+
+namespace mozilla {
+namespace dom {
+
+struct ClientSourceConstructorArgs
+{
+ nsID id;
+ ClientType type;
+ PrincipalInfo principalInfo;
+ TimeStamp creationTime;
+};
+
+[Comparable] struct IPCClientInfo
+{
+ nsID id;
+ nsID? agentClusterId;
+ ClientType type;
+ PrincipalInfo principalInfo;
+ TimeStamp creationTime;
+ nsCString url;
+ FrameType frameType;
+ CSPInfo? cspInfo;
+ CSPInfo? preloadCspInfo;
+};
+
+struct IPCClientWindowState
+{
+ VisibilityState visibilityState;
+ TimeStamp lastFocusTime;
+ StorageAccess storageAccess;
+ bool focused;
+};
+
+struct IPCClientWorkerState
+{
+ StorageAccess storageAccess;
+};
+
+union IPCClientState
+{
+ IPCClientWindowState;
+ IPCClientWorkerState;
+};
+
+struct ClientInfoAndState
+{
+ IPCClientInfo info;
+ IPCClientState state;
+};
+
+struct ClientSourceExecutionReadyArgs
+{
+ nsCString url;
+ FrameType frameType;
+};
+
+struct ClientControlledArgs
+{
+ IPCServiceWorkerDescriptor serviceWorker;
+};
+
+struct ClientFocusArgs
+{
+ CallerType callerType;
+};
+
+struct ClientNavigateArgs
+{
+ IPCClientInfo target;
+ nsCString url;
+ nsCString baseURL;
+ IPCServiceWorkerDescriptor serviceWorker;
+};
+
+struct ClientPostMessageArgs
+{
+ ClonedMessageData clonedData;
+ IPCServiceWorkerDescriptor serviceWorker;
+};
+
+struct ClientMatchAllArgs
+{
+ IPCServiceWorkerDescriptor serviceWorker;
+ ClientType type;
+ bool includeUncontrolled;
+};
+
+struct ClientClaimArgs
+{
+ IPCServiceWorkerDescriptor serviceWorker;
+};
+
+struct ClientGetInfoAndStateArgs
+{
+ nsID id;
+ PrincipalInfo principalInfo;
+};
+
+struct ClientOpenWindowArgs
+{
+ PrincipalInfo principalInfo;
+ CSPInfo? cspInfo;
+ nsCString url;
+ nsCString baseURL;
+};
+
+struct ClientEvictBFCacheArgs
+{};
+
+union ClientOpConstructorArgs
+{
+ ClientControlledArgs;
+ ClientFocusArgs;
+ ClientNavigateArgs;
+ ClientPostMessageArgs;
+ ClientMatchAllArgs;
+ ClientClaimArgs;
+ ClientGetInfoAndStateArgs;
+ ClientOpenWindowArgs;
+ ClientEvictBFCacheArgs;
+};
+
+struct ClientList
+{
+ ClientInfoAndState[] values;
+};
+
+struct ClientNavigateOpConstructorArgs
+{
+ PClientSource target;
+ nsCString url;
+ nsCString baseURL;
+};
+
+union ClientOpResult
+{
+ CopyableErrorResult;
+ IPCClientState;
+ ClientInfoAndState;
+ ClientList;
+};
+
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/clients/manager/ClientIPCUtils.h b/dom/clients/manager/ClientIPCUtils.h
new file mode 100644
index 0000000000..7f8cf35c1b
--- /dev/null
+++ b/dom/clients/manager/ClientIPCUtils.h
@@ -0,0 +1,44 @@
+/* -*- 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/. */
+#ifndef _mozilla_dom_ClientIPCUtils_h
+#define _mozilla_dom_ClientIPCUtils_h
+
+#include "ipc/EnumSerializer.h"
+
+#include "X11UndefineNone.h"
+#include "mozilla/dom/ClientBinding.h"
+#include "mozilla/dom/ClientsBinding.h"
+#include "mozilla/dom/DocumentBinding.h"
+#include "mozilla/StorageAccess.h"
+
+namespace IPC {
+template <>
+struct ParamTraits<mozilla::dom::ClientType>
+ : public ContiguousEnumSerializer<mozilla::dom::ClientType,
+ mozilla::dom::ClientType::Window,
+ mozilla::dom::ClientType::EndGuard_> {};
+
+template <>
+struct ParamTraits<mozilla::dom::FrameType>
+ : public ContiguousEnumSerializer<mozilla::dom::FrameType,
+ mozilla::dom::FrameType::Auxiliary,
+ mozilla::dom::FrameType::EndGuard_> {};
+
+template <>
+struct ParamTraits<mozilla::dom::VisibilityState>
+ : public ContiguousEnumSerializer<
+ mozilla::dom::VisibilityState, mozilla::dom::VisibilityState::Hidden,
+ mozilla::dom::VisibilityState::EndGuard_> {};
+
+template <>
+struct ParamTraits<mozilla::StorageAccess>
+ : public ContiguousEnumSerializer<
+ mozilla::StorageAccess,
+ mozilla::StorageAccess::ePartitionForeignOrDeny,
+ mozilla::StorageAccess::eNumValues> {};
+} // namespace IPC
+
+#endif // _mozilla_dom_ClientIPCUtils_h
diff --git a/dom/clients/manager/ClientInfo.cpp b/dom/clients/manager/ClientInfo.cpp
new file mode 100644
index 0000000000..a87d337f1f
--- /dev/null
+++ b/dom/clients/manager/ClientInfo.cpp
@@ -0,0 +1,131 @@
+/* -*- 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 "ClientInfo.h"
+
+#include "mozilla/dom/ClientIPCTypes.h"
+#include "mozilla/ipc/BackgroundUtils.h"
+
+namespace mozilla::dom {
+
+using mozilla::ipc::PrincipalInfo;
+using mozilla::ipc::PrincipalInfoToPrincipal;
+
+ClientInfo::ClientInfo(const nsID& aId, ClientType aType,
+ const mozilla::ipc::PrincipalInfo& aPrincipalInfo,
+ const TimeStamp& aCreationTime)
+ : mData(MakeUnique<IPCClientInfo>(aId, mozilla::Nothing(), aType,
+ aPrincipalInfo, aCreationTime, ""_ns,
+ mozilla::dom::FrameType::None,
+ mozilla::Nothing(), mozilla::Nothing())) {
+}
+
+ClientInfo::ClientInfo(const IPCClientInfo& aData)
+ : mData(MakeUnique<IPCClientInfo>(aData)) {}
+
+ClientInfo::ClientInfo(const ClientInfo& aRight) { operator=(aRight); }
+
+ClientInfo& ClientInfo::operator=(const ClientInfo& aRight) {
+ mData.reset();
+ mData = MakeUnique<IPCClientInfo>(*aRight.mData);
+ return *this;
+}
+
+ClientInfo::ClientInfo(ClientInfo&& aRight) : mData(std::move(aRight.mData)) {}
+
+ClientInfo& ClientInfo::operator=(ClientInfo&& aRight) {
+ mData.reset();
+ mData = std::move(aRight.mData);
+ return *this;
+}
+
+ClientInfo::~ClientInfo() = default;
+
+bool ClientInfo::operator==(const ClientInfo& aRight) const {
+ return *mData == *aRight.mData;
+}
+
+bool ClientInfo::operator!=(const ClientInfo& aRight) const {
+ return *mData != *aRight.mData;
+}
+
+const nsID& ClientInfo::Id() const { return mData->id(); }
+
+void ClientInfo::SetAgentClusterId(const nsID& aId) {
+ MOZ_ASSERT(mData->agentClusterId().isNothing() ||
+ mData->agentClusterId().ref().Equals(aId));
+ mData->agentClusterId() = Some(aId);
+}
+
+const Maybe<nsID>& ClientInfo::AgentClusterId() const {
+ return mData->agentClusterId();
+}
+
+ClientType ClientInfo::Type() const { return mData->type(); }
+
+const mozilla::ipc::PrincipalInfo& ClientInfo::PrincipalInfo() const {
+ return mData->principalInfo();
+}
+
+const TimeStamp& ClientInfo::CreationTime() const {
+ return mData->creationTime();
+}
+
+const nsCString& ClientInfo::URL() const { return mData->url(); }
+
+void ClientInfo::SetURL(const nsACString& aURL) { mData->url() = aURL; }
+
+FrameType ClientInfo::FrameType() const { return mData->frameType(); }
+
+void ClientInfo::SetFrameType(mozilla::dom::FrameType aFrameType) {
+ mData->frameType() = aFrameType;
+}
+
+const IPCClientInfo& ClientInfo::ToIPC() const { return *mData; }
+
+bool ClientInfo::IsPrivateBrowsing() const {
+ switch (PrincipalInfo().type()) {
+ case PrincipalInfo::TContentPrincipalInfo: {
+ auto& p = PrincipalInfo().get_ContentPrincipalInfo();
+ return p.attrs().mPrivateBrowsingId != 0;
+ }
+ case PrincipalInfo::TSystemPrincipalInfo: {
+ return false;
+ }
+ case PrincipalInfo::TNullPrincipalInfo: {
+ auto& p = PrincipalInfo().get_NullPrincipalInfo();
+ return p.attrs().mPrivateBrowsingId != 0;
+ }
+ default: {
+ // clients should never be expanded principals
+ MOZ_CRASH("unexpected principal type!");
+ }
+ }
+}
+
+Result<nsCOMPtr<nsIPrincipal>, nsresult> ClientInfo::GetPrincipal() const {
+ MOZ_ASSERT(NS_IsMainThread());
+ return PrincipalInfoToPrincipal(PrincipalInfo());
+}
+
+const Maybe<mozilla::ipc::CSPInfo>& ClientInfo::GetCspInfo() const {
+ return mData->cspInfo();
+}
+
+void ClientInfo::SetCspInfo(const mozilla::ipc::CSPInfo& aCSPInfo) {
+ mData->cspInfo() = Some(aCSPInfo);
+}
+
+const Maybe<mozilla::ipc::CSPInfo>& ClientInfo::GetPreloadCspInfo() const {
+ return mData->preloadCspInfo();
+}
+
+void ClientInfo::SetPreloadCspInfo(
+ const mozilla::ipc::CSPInfo& aPreloadCSPInfo) {
+ mData->preloadCspInfo() = Some(aPreloadCSPInfo);
+}
+
+} // namespace mozilla::dom
diff --git a/dom/clients/manager/ClientInfo.h b/dom/clients/manager/ClientInfo.h
new file mode 100644
index 0000000000..fd5f874b09
--- /dev/null
+++ b/dom/clients/manager/ClientInfo.h
@@ -0,0 +1,117 @@
+/* -*- 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/. */
+
+#ifndef _mozilla_dom_ClientInfo_h
+#define _mozilla_dom_ClientInfo_h
+
+#include "X11UndefineNone.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/TimeStamp.h"
+#include "mozilla/UniquePtr.h"
+#include "nsCOMPtr.h"
+#include "nsString.h"
+
+class nsIPrincipal;
+struct nsID;
+
+namespace mozilla {
+
+namespace ipc {
+class CSPInfo;
+class PrincipalInfo;
+} // namespace ipc
+
+namespace dom {
+
+class IPCClientInfo;
+enum class FrameType : uint8_t;
+enum class ClientType : uint8_t;
+
+// This class provides a simple structure that represents a global living
+// in the system. Its thread safe and can be transferred across process
+// boundaries. A ClientInfo object can represent either a window or a worker.
+class ClientInfo final {
+ UniquePtr<IPCClientInfo> mData;
+
+ public:
+ ClientInfo(const nsID& aId, ClientType aType,
+ const mozilla::ipc::PrincipalInfo& aPrincipalInfo,
+ const TimeStamp& aCreationTime);
+
+ ClientInfo(const ClientInfo& aRight);
+
+ ClientInfo& operator=(const ClientInfo& aRight);
+
+ ClientInfo(ClientInfo&& aRight);
+
+ ClientInfo& operator=(ClientInfo&& aRight);
+
+ explicit ClientInfo(const IPCClientInfo& aData);
+
+ ~ClientInfo();
+
+ bool operator==(const ClientInfo& aRight) const;
+ bool operator!=(const ClientInfo& aRight) const;
+
+ // Get the unique identifier chosen at the time of the global's creation.
+ const nsID& Id() const;
+
+ // This function should only be called on EnsureClientSource().
+ // XXX Bug 1579785 will merge this into the constructor (requiring pass a
+ // AgentClusterId)
+ void SetAgentClusterId(const nsID& aId);
+ const Maybe<nsID>& AgentClusterId() const;
+
+ // Determine what kind of global this is; e.g. Window, Worker, SharedWorker,
+ // etc.
+ ClientType Type() const;
+
+ // Every global must have a principal that cannot change.
+ const mozilla::ipc::PrincipalInfo& PrincipalInfo() const;
+
+ // The time at which the global was created.
+ const TimeStamp& CreationTime() const;
+
+ // Each global has the concept of a creation URL. For the most part this
+ // does not change. The one exception is for about:blank replacement
+ // iframes. In this case the URL starts as "about:blank", but is later
+ // overriden with the final URL.
+ const nsCString& URL() const;
+
+ // Override the creation URL. This should only be used for about:blank
+ // replacement iframes.
+ void SetURL(const nsACString& aURL);
+
+ // The frame type is largely a window concept, but we track it as part
+ // of the global here because of the way the Clients WebAPI was designed.
+ // This is set at the time the global becomes execution ready. Workers
+ // will always return None.
+ mozilla::dom::FrameType FrameType() const;
+
+ // Set the frame type for the global. This should only happen once the
+ // global has become execution ready.
+ void SetFrameType(mozilla::dom::FrameType aFrameType);
+
+ // Convert to the ipdl generated type.
+ const IPCClientInfo& ToIPC() const;
+
+ // Determine if the client is in private browsing mode.
+ bool IsPrivateBrowsing() const;
+
+ // Get a main-thread nsIPrincipal for the client.
+ Result<nsCOMPtr<nsIPrincipal>, nsresult> GetPrincipal() const;
+
+ const Maybe<mozilla::ipc::CSPInfo>& GetCspInfo() const;
+ void SetCspInfo(const mozilla::ipc::CSPInfo& aCSPInfo);
+
+ const Maybe<mozilla::ipc::CSPInfo>& GetPreloadCspInfo() const;
+ void SetPreloadCspInfo(const mozilla::ipc::CSPInfo& aPreloadCSPInfo);
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // _mozilla_dom_ClientInfo_h
diff --git a/dom/clients/manager/ClientManager.cpp b/dom/clients/manager/ClientManager.cpp
new file mode 100644
index 0000000000..a2b442f9f4
--- /dev/null
+++ b/dom/clients/manager/ClientManager.cpp
@@ -0,0 +1,394 @@
+/* -*- 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 "ClientManager.h"
+
+#include "ClientHandle.h"
+#include "ClientManagerChild.h"
+#include "ClientManagerOpChild.h"
+#include "ClientSource.h"
+#include "mozilla/dom/WorkerPrivate.h"
+#include "mozilla/ipc/BackgroundChild.h"
+#include "mozilla/ipc/PBackgroundChild.h"
+#include "mozilla/ClearOnShutdown.h" // PastShutdownPhase
+#include "mozilla/StaticPrefs_dom.h"
+#include "prthread.h"
+
+namespace mozilla::dom {
+
+using mozilla::ipc::BackgroundChild;
+using mozilla::ipc::PBackgroundChild;
+using mozilla::ipc::PrincipalInfo;
+
+namespace {
+
+const uint32_t kBadThreadLocalIndex = -1;
+#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
+const uint32_t kThreadLocalMagic1 = 0x8d57eea6;
+const uint32_t kThreadLocalMagic2 = 0x59f375c9;
+#endif
+
+#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
+uint32_t sClientManagerThreadLocalMagic1 = kThreadLocalMagic1;
+#endif
+
+uint32_t sClientManagerThreadLocalIndex = kBadThreadLocalIndex;
+
+#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
+uint32_t sClientManagerThreadLocalMagic2 = kThreadLocalMagic2;
+uint32_t sClientManagerThreadLocalIndexDuplicate = kBadThreadLocalIndex;
+#endif
+
+} // anonymous namespace
+
+ClientManager::ClientManager() {
+ PBackgroundChild* parentActor =
+ BackgroundChild::GetOrCreateForCurrentThread();
+ if (NS_WARN_IF(!parentActor)) {
+ Shutdown();
+ return;
+ }
+
+ ClientManagerChild* actor = ClientManagerChild::Create();
+ if (NS_WARN_IF(!actor)) {
+ Shutdown();
+ return;
+ }
+
+ PClientManagerChild* sentActor =
+ parentActor->SendPClientManagerConstructor(actor);
+ if (NS_WARN_IF(!sentActor)) {
+ Shutdown();
+ return;
+ }
+ MOZ_DIAGNOSTIC_ASSERT(sentActor == actor);
+
+ ActivateThing(actor);
+}
+
+ClientManager::~ClientManager() {
+ NS_ASSERT_OWNINGTHREAD(ClientManager);
+
+ Shutdown();
+
+ MOZ_DIAGNOSTIC_ASSERT(sClientManagerThreadLocalMagic1 == kThreadLocalMagic1);
+ MOZ_DIAGNOSTIC_ASSERT(sClientManagerThreadLocalMagic2 == kThreadLocalMagic2);
+ MOZ_DIAGNOSTIC_ASSERT(sClientManagerThreadLocalIndex != kBadThreadLocalIndex);
+ MOZ_DIAGNOSTIC_ASSERT(sClientManagerThreadLocalIndex ==
+ sClientManagerThreadLocalIndexDuplicate);
+ MOZ_DIAGNOSTIC_ASSERT(this ==
+ PR_GetThreadPrivate(sClientManagerThreadLocalIndex));
+
+#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
+ PRStatus status =
+#endif
+ PR_SetThreadPrivate(sClientManagerThreadLocalIndex, nullptr);
+ MOZ_DIAGNOSTIC_ASSERT(status == PR_SUCCESS);
+}
+
+void ClientManager::Shutdown() {
+ NS_ASSERT_OWNINGTHREAD(ClientManager);
+
+ if (IsShutdown()) {
+ return;
+ }
+
+ ShutdownThing();
+}
+
+UniquePtr<ClientSource> ClientManager::CreateSourceInternal(
+ ClientType aType, nsISerialEventTarget* aEventTarget,
+ const PrincipalInfo& aPrincipal) {
+ NS_ASSERT_OWNINGTHREAD(ClientManager);
+
+ nsID id;
+ nsresult rv = nsID::GenerateUUIDInPlace(id);
+ MOZ_DIAGNOSTIC_ASSERT(NS_SUCCEEDED(rv));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ // If we can't even get a UUID, at least make sure not to use a garbage
+ // value. Instead return a shutdown ClientSource with a zero'd id.
+ // This should be exceptionally rare, if it happens at all.
+ id.Clear();
+ ClientSourceConstructorArgs args(id, aType, aPrincipal, TimeStamp::Now());
+ UniquePtr<ClientSource> source(new ClientSource(this, aEventTarget, args));
+ source->Shutdown();
+ return source;
+ }
+
+ ClientSourceConstructorArgs args(id, aType, aPrincipal, TimeStamp::Now());
+ UniquePtr<ClientSource> source(new ClientSource(this, aEventTarget, args));
+
+ if (IsShutdown()) {
+ source->Shutdown();
+ return source;
+ }
+
+ source->Activate(GetActor());
+
+ return source;
+}
+
+UniquePtr<ClientSource> ClientManager::CreateSourceInternal(
+ const ClientInfo& aClientInfo, nsISerialEventTarget* aEventTarget) {
+ NS_ASSERT_OWNINGTHREAD(ClientManager);
+
+ ClientSourceConstructorArgs args(aClientInfo.Id(), aClientInfo.Type(),
+ aClientInfo.PrincipalInfo(),
+ aClientInfo.CreationTime());
+ UniquePtr<ClientSource> source(new ClientSource(this, aEventTarget, args));
+
+ if (IsShutdown()) {
+ source->Shutdown();
+ return source;
+ }
+
+ source->Activate(GetActor());
+
+ return source;
+}
+
+already_AddRefed<ClientHandle> ClientManager::CreateHandleInternal(
+ const ClientInfo& aClientInfo, nsISerialEventTarget* aSerialEventTarget) {
+ NS_ASSERT_OWNINGTHREAD(ClientManager);
+ MOZ_DIAGNOSTIC_ASSERT(aSerialEventTarget);
+
+ RefPtr<ClientHandle> handle =
+ new ClientHandle(this, aSerialEventTarget, aClientInfo);
+
+ if (IsShutdown()) {
+ handle->Shutdown();
+ return handle.forget();
+ }
+
+ handle->Activate(GetActor());
+
+ return handle.forget();
+}
+
+RefPtr<ClientOpPromise> ClientManager::StartOp(
+ const ClientOpConstructorArgs& aArgs,
+ nsISerialEventTarget* aSerialEventTarget) {
+ RefPtr<ClientOpPromise::Private> promise =
+ new ClientOpPromise::Private(__func__);
+
+ // Hold a ref to the client until the remote operation completes. Otherwise
+ // the ClientHandle might get de-refed and teardown the actor before we
+ // get an answer.
+ RefPtr<ClientManager> kungFuGrip = this;
+
+ MaybeExecute(
+ [&aArgs, promise, kungFuGrip](ClientManagerChild* aActor) {
+ ClientManagerOpChild* actor =
+ new ClientManagerOpChild(kungFuGrip, aArgs, promise);
+ if (!aActor->SendPClientManagerOpConstructor(actor, aArgs)) {
+ // Constructor failure will reject promise via ActorDestroy()
+ return;
+ }
+ },
+ [promise] {
+ CopyableErrorResult rv;
+ rv.ThrowInvalidStateError("Client has been destroyed");
+ promise->Reject(rv, __func__);
+ });
+
+ return promise;
+}
+
+// static
+already_AddRefed<ClientManager> ClientManager::GetOrCreateForCurrentThread() {
+ MOZ_DIAGNOSTIC_ASSERT(sClientManagerThreadLocalMagic1 == kThreadLocalMagic1);
+ MOZ_DIAGNOSTIC_ASSERT(sClientManagerThreadLocalMagic2 == kThreadLocalMagic2);
+ MOZ_DIAGNOSTIC_ASSERT(sClientManagerThreadLocalIndex != kBadThreadLocalIndex);
+ MOZ_DIAGNOSTIC_ASSERT(sClientManagerThreadLocalIndex ==
+ sClientManagerThreadLocalIndexDuplicate);
+ RefPtr<ClientManager> cm = static_cast<ClientManager*>(
+ PR_GetThreadPrivate(sClientManagerThreadLocalIndex));
+
+ if (!cm) {
+ cm = new ClientManager();
+
+#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
+ PRStatus status =
+#endif
+ PR_SetThreadPrivate(sClientManagerThreadLocalIndex, cm.get());
+ MOZ_DIAGNOSTIC_ASSERT(status == PR_SUCCESS);
+ }
+
+ MOZ_DIAGNOSTIC_ASSERT(cm);
+
+ if (StaticPrefs::dom_workers_testing_enabled()) {
+ // Check that the ClientManager instance associated to the current thread
+ // has not been kept alive when it was expected to have been already
+ // deallocated (e.g. due to a leak ClientManager's mShutdown can have ben
+ // set to true from its RevokeActor method but never fully deallocated and
+ // unset from the thread locals).
+ MOZ_DIAGNOSTIC_ASSERT(!cm->IsShutdown());
+ }
+ return cm.forget();
+}
+
+WorkerPrivate* ClientManager::GetWorkerPrivate() const {
+ NS_ASSERT_OWNINGTHREAD(ClientManager);
+ MOZ_DIAGNOSTIC_ASSERT(GetActor());
+ return GetActor()->GetWorkerPrivate();
+}
+
+// Used to share logic between ExpectFutureSource and ForgetFutureSource.
+/* static */ bool ClientManager::ExpectOrForgetFutureSource(
+ const ClientInfo& aClientInfo,
+ bool (PClientManagerChild::*aMethod)(const IPCClientInfo&)) {
+ // Return earlier if called late in the XPCOM shutdown path,
+ // ClientManager would be already shutdown at the point.
+ if (NS_WARN_IF(PastShutdownPhase(ShutdownPhase::XPCOMShutdown))) {
+ return false;
+ }
+
+ bool rv = true;
+
+ RefPtr<ClientManager> mgr = ClientManager::GetOrCreateForCurrentThread();
+ mgr->MaybeExecute(
+ [&](ClientManagerChild* aActor) {
+ if (!(aActor->*aMethod)(aClientInfo.ToIPC())) {
+ rv = false;
+ }
+ },
+ [&] { rv = false; });
+
+ return rv;
+}
+
+/* static */ bool ClientManager::ExpectFutureSource(
+ const ClientInfo& aClientInfo) {
+ return ExpectOrForgetFutureSource(
+ aClientInfo, &PClientManagerChild::SendExpectFutureClientSource);
+}
+
+/* static */ bool ClientManager::ForgetFutureSource(
+ const ClientInfo& aClientInfo) {
+ return ExpectOrForgetFutureSource(
+ aClientInfo, &PClientManagerChild::SendForgetFutureClientSource);
+}
+
+// static
+void ClientManager::Startup() {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ MOZ_DIAGNOSTIC_ASSERT(sClientManagerThreadLocalMagic1 == kThreadLocalMagic1);
+ MOZ_DIAGNOSTIC_ASSERT(sClientManagerThreadLocalMagic2 == kThreadLocalMagic2);
+ MOZ_DIAGNOSTIC_ASSERT(sClientManagerThreadLocalIndex == kBadThreadLocalIndex);
+ MOZ_DIAGNOSTIC_ASSERT(sClientManagerThreadLocalIndex ==
+ sClientManagerThreadLocalIndexDuplicate);
+
+#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
+ PRStatus status =
+#endif
+ PR_NewThreadPrivateIndex(&sClientManagerThreadLocalIndex, nullptr);
+ MOZ_DIAGNOSTIC_ASSERT(status == PR_SUCCESS);
+
+ MOZ_DIAGNOSTIC_ASSERT(sClientManagerThreadLocalIndex != kBadThreadLocalIndex);
+#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
+ sClientManagerThreadLocalIndexDuplicate = sClientManagerThreadLocalIndex;
+#endif
+}
+
+// static
+UniquePtr<ClientSource> ClientManager::CreateSource(
+ ClientType aType, nsISerialEventTarget* aEventTarget,
+ nsIPrincipal* aPrincipal) {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(aPrincipal);
+
+ PrincipalInfo principalInfo;
+ nsresult rv = PrincipalToPrincipalInfo(aPrincipal, &principalInfo);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ MOZ_CRASH("ClientManager::CreateSource() cannot serialize bad principal");
+ }
+
+ RefPtr<ClientManager> mgr = GetOrCreateForCurrentThread();
+ return mgr->CreateSourceInternal(aType, aEventTarget, principalInfo);
+}
+
+// static
+UniquePtr<ClientSource> ClientManager::CreateSource(
+ ClientType aType, nsISerialEventTarget* aEventTarget,
+ const PrincipalInfo& aPrincipal) {
+ RefPtr<ClientManager> mgr = GetOrCreateForCurrentThread();
+ return mgr->CreateSourceInternal(aType, aEventTarget, aPrincipal);
+}
+
+// static
+UniquePtr<ClientSource> ClientManager::CreateSourceFromInfo(
+ const ClientInfo& aClientInfo, nsISerialEventTarget* aEventTarget) {
+ RefPtr<ClientManager> mgr = GetOrCreateForCurrentThread();
+ return mgr->CreateSourceInternal(aClientInfo, aEventTarget);
+}
+
+Maybe<ClientInfo> ClientManager::CreateInfo(ClientType aType,
+ nsIPrincipal* aPrincipal) {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(aPrincipal);
+
+ PrincipalInfo principalInfo;
+ nsresult rv = PrincipalToPrincipalInfo(aPrincipal, &principalInfo);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ MOZ_CRASH("ClientManager::CreateSource() cannot serialize bad principal");
+ }
+
+ nsID id;
+ rv = nsID::GenerateUUIDInPlace(id);
+ MOZ_DIAGNOSTIC_ASSERT(NS_SUCCEEDED(rv));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return Nothing();
+ }
+
+ return Some(ClientInfo(id, aType, principalInfo, TimeStamp::Now()));
+}
+
+// static
+already_AddRefed<ClientHandle> ClientManager::CreateHandle(
+ const ClientInfo& aClientInfo, nsISerialEventTarget* aSerialEventTarget) {
+ RefPtr<ClientManager> mgr = GetOrCreateForCurrentThread();
+ return mgr->CreateHandleInternal(aClientInfo, aSerialEventTarget);
+}
+
+// static
+RefPtr<ClientOpPromise> ClientManager::MatchAll(
+ const ClientMatchAllArgs& aArgs, nsISerialEventTarget* aSerialEventTarget) {
+ RefPtr<ClientManager> mgr = GetOrCreateForCurrentThread();
+ return mgr->StartOp(aArgs, aSerialEventTarget);
+}
+
+// static
+RefPtr<ClientOpPromise> ClientManager::Claim(
+ const ClientClaimArgs& aArgs, nsISerialEventTarget* aSerialEventTarget) {
+ RefPtr<ClientManager> mgr = GetOrCreateForCurrentThread();
+ return mgr->StartOp(aArgs, aSerialEventTarget);
+}
+
+// static
+RefPtr<ClientOpPromise> ClientManager::GetInfoAndState(
+ const ClientGetInfoAndStateArgs& aArgs,
+ nsISerialEventTarget* aSerialEventTarget) {
+ RefPtr<ClientManager> mgr = GetOrCreateForCurrentThread();
+ return mgr->StartOp(aArgs, aSerialEventTarget);
+}
+
+// static
+RefPtr<ClientOpPromise> ClientManager::Navigate(
+ const ClientNavigateArgs& aArgs, nsISerialEventTarget* aSerialEventTarget) {
+ RefPtr<ClientManager> mgr = GetOrCreateForCurrentThread();
+ return mgr->StartOp(aArgs, aSerialEventTarget);
+}
+
+// static
+RefPtr<ClientOpPromise> ClientManager::OpenWindow(
+ const ClientOpenWindowArgs& aArgs,
+ nsISerialEventTarget* aSerialEventTarget) {
+ RefPtr<ClientManager> mgr = GetOrCreateForCurrentThread();
+ return mgr->StartOp(aArgs, aSerialEventTarget);
+}
+
+} // namespace mozilla::dom
diff --git a/dom/clients/manager/ClientManager.h b/dom/clients/manager/ClientManager.h
new file mode 100644
index 0000000000..94504aaee8
--- /dev/null
+++ b/dom/clients/manager/ClientManager.h
@@ -0,0 +1,151 @@
+/* -*- 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/. */
+#ifndef _mozilla_dom_ClientManager_h
+#define _mozilla_dom_ClientManager_h
+
+#include "mozilla/dom/ClientOpPromise.h"
+#include "mozilla/dom/ClientThing.h"
+#include "mozilla/dom/PClientManagerChild.h"
+
+class nsIPrincipal;
+
+namespace mozilla {
+namespace ipc {
+class PBackgroundChild;
+class PrincipalInfo;
+} // namespace ipc
+namespace dom {
+
+class ClientClaimArgs;
+class ClientGetInfoAndStateArgs;
+class ClientHandle;
+class ClientInfo;
+class ClientManagerChild;
+class ClientMatchAllArgs;
+class ClientNavigateArgs;
+class ClientOpConstructorArgs;
+class ClientOpenWindowArgs;
+class ClientSource;
+enum class ClientType : uint8_t;
+class WorkerPrivate;
+
+// The ClientManager provides a per-thread singleton interface workering
+// with the client subsystem. It allows globals to create ClientSource
+// objects. It allows other parts of the system to attach to this globals
+// by creating ClientHandle objects. The ClientManager also provides
+// methods for querying the list of clients active in the system.
+class ClientManager final : public ClientThing<ClientManagerChild> {
+ friend class ClientManagerChild;
+ friend class ClientSource;
+
+ ClientManager();
+ ~ClientManager();
+
+ // Utility method to trigger a shutdown of the ClientManager. This
+ // is called in various error conditions or when the last reference
+ // is dropped.
+ void Shutdown();
+
+ UniquePtr<ClientSource> CreateSourceInternal(
+ ClientType aType, nsISerialEventTarget* aEventTarget,
+ const mozilla::ipc::PrincipalInfo& aPrincipal);
+
+ UniquePtr<ClientSource> CreateSourceInternal(
+ const ClientInfo& aClientInfo, nsISerialEventTarget* aEventTarget);
+
+ already_AddRefed<ClientHandle> CreateHandleInternal(
+ const ClientInfo& aClientInfo, nsISerialEventTarget* aSerialEventTarget);
+
+ // Utility method to perform an IPC operation. This will create a
+ // PClientManagerOp actor tied to a MozPromise. The promise will
+ // resolve or reject with the result of the remote operation.
+ [[nodiscard]] RefPtr<ClientOpPromise> StartOp(
+ const ClientOpConstructorArgs& aArgs,
+ nsISerialEventTarget* aSerialEventTarget);
+
+ // Get or create the TLS singleton. Currently this is only used
+ // internally and external code indirectly calls it by invoking
+ // static methods.
+ static already_AddRefed<ClientManager> GetOrCreateForCurrentThread();
+
+ // Private methods called by ClientSource
+ mozilla::dom::WorkerPrivate* GetWorkerPrivate() const;
+
+ // Don't use - use {Expect,Forget}FutureSource instead.
+ static bool ExpectOrForgetFutureSource(
+ const ClientInfo& aClientInfo,
+ bool (PClientManagerChild::*aMethod)(const IPCClientInfo&));
+
+ public:
+ // Asynchronously declare that a ClientSource will possibly be constructed
+ // from an equivalent ClientInfo in the future. This must be called before any
+ // any ClientHandles are created with the ClientInfo to avoid race conditions
+ // when ClientHandles query the ClientManagerService.
+ //
+ // This method exists so that the ClientManagerService can determine if a
+ // particular ClientSource can be expected to exist in the future or has
+ // already existed and been destroyed.
+ //
+ // If it's later known that the expected ClientSource will not be
+ // constructed, ForgetFutureSource must be called.
+ static bool ExpectFutureSource(const ClientInfo& aClientInfo);
+
+ // May also be called even when the "future" source has become a "real"
+ // source, in which case this is a no-op.
+ static bool ForgetFutureSource(const ClientInfo& aClientInfo);
+
+ // Initialize the ClientManager at process start. This
+ // does book-keeping like creating a TLS identifier, etc.
+ // This should only be called by process startup code.
+ static void Startup();
+
+ static UniquePtr<ClientSource> CreateSource(
+ ClientType aType, nsISerialEventTarget* aEventTarget,
+ nsIPrincipal* aPrincipal);
+
+ static UniquePtr<ClientSource> CreateSource(
+ ClientType aType, nsISerialEventTarget* aEventTarget,
+ const mozilla::ipc::PrincipalInfo& aPrincipal);
+
+ // Construct a new ClientSource from an existing ClientInfo (and id) rather
+ // than allocating a new id.
+ static UniquePtr<ClientSource> CreateSourceFromInfo(
+ const ClientInfo& aClientInfo, nsISerialEventTarget* aSerialEventTarget);
+
+ // Allocate a new ClientInfo and id without creating a ClientSource. Used
+ // when we have a redirect that isn't exposed to the process that owns
+ // the global/ClientSource.
+ static Maybe<ClientInfo> CreateInfo(ClientType aType,
+ nsIPrincipal* aPrincipal);
+
+ static already_AddRefed<ClientHandle> CreateHandle(
+ const ClientInfo& aClientInfo, nsISerialEventTarget* aSerialEventTarget);
+
+ static RefPtr<ClientOpPromise> MatchAll(const ClientMatchAllArgs& aArgs,
+ nsISerialEventTarget* aTarget);
+
+ static RefPtr<ClientOpPromise> Claim(
+ const ClientClaimArgs& aArgs, nsISerialEventTarget* aSerialEventTarget);
+
+ static RefPtr<ClientOpPromise> GetInfoAndState(
+ const ClientGetInfoAndStateArgs& aArgs,
+ nsISerialEventTarget* aSerialEventTarget);
+
+ static RefPtr<ClientOpPromise> Navigate(
+ const ClientNavigateArgs& aArgs,
+ nsISerialEventTarget* aSerialEventTarget);
+
+ static RefPtr<ClientOpPromise> OpenWindow(
+ const ClientOpenWindowArgs& aArgs,
+ nsISerialEventTarget* aSerialEventTarget);
+
+ NS_INLINE_DECL_REFCOUNTING(mozilla::dom::ClientManager)
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // _mozilla_dom_ClientManager_h
diff --git a/dom/clients/manager/ClientManagerActors.cpp b/dom/clients/manager/ClientManagerActors.cpp
new file mode 100644
index 0000000000..6ec08bf399
--- /dev/null
+++ b/dom/clients/manager/ClientManagerActors.cpp
@@ -0,0 +1,37 @@
+/* -*- 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 "ClientManagerChild.h"
+#include "ClientManagerParent.h"
+
+namespace mozilla::dom {
+
+PClientManagerChild* AllocClientManagerChild() {
+ MOZ_ASSERT_UNREACHABLE(
+ "Default ClientManagerChild allocator should not be invoked");
+ return nullptr;
+}
+
+bool DeallocClientManagerChild(PClientManagerChild* aActor) {
+ delete aActor;
+ return true;
+}
+
+PClientManagerParent* AllocClientManagerParent() {
+ return new ClientManagerParent();
+}
+
+bool DeallocClientManagerParent(PClientManagerParent* aActor) {
+ delete aActor;
+ return true;
+}
+
+void InitClientManagerParent(PClientManagerParent* aActor) {
+ auto actor = static_cast<ClientManagerParent*>(aActor);
+ actor->Init();
+}
+
+} // namespace mozilla::dom
diff --git a/dom/clients/manager/ClientManagerActors.h b/dom/clients/manager/ClientManagerActors.h
new file mode 100644
index 0000000000..b977149014
--- /dev/null
+++ b/dom/clients/manager/ClientManagerActors.h
@@ -0,0 +1,28 @@
+/* -*- 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/. */
+#ifndef _mozilla_dom_ClientManagerActors_h
+#define _mozilla_dom_ClientManagerActors_h
+
+namespace mozilla {
+namespace dom {
+
+class PClientManagerChild;
+class PClientManagerParent;
+
+PClientManagerChild* AllocClientManagerChild();
+
+bool DeallocClientManagerChild(PClientManagerChild* aActor);
+
+PClientManagerParent* AllocClientManagerParent();
+
+bool DeallocClientManagerParent(PClientManagerParent* aActor);
+
+void InitClientManagerParent(PClientManagerParent* aActor);
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // _mozilla_dom_ClientManagerActors_h
diff --git a/dom/clients/manager/ClientManagerChild.cpp b/dom/clients/manager/ClientManagerChild.cpp
new file mode 100644
index 0000000000..b51ff11897
--- /dev/null
+++ b/dom/clients/manager/ClientManagerChild.cpp
@@ -0,0 +1,137 @@
+/* -*- 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 "ClientManagerChild.h"
+
+#include "ClientHandleChild.h"
+#include "ClientManagerOpChild.h"
+#include "ClientNavigateOpChild.h"
+#include "ClientSourceChild.h"
+
+#include "mozilla/dom/WorkerCommon.h"
+#include "mozilla/dom/WorkerRef.h"
+
+namespace mozilla::dom {
+
+void ClientManagerChild::ActorDestroy(ActorDestroyReason aReason) {
+ mIPCWorkerRef = nullptr;
+
+ if (mManager) {
+ mManager->RevokeActor(this);
+
+ // Revoking the actor link should automatically cause the owner
+ // to call RevokeOwner() as well.
+ MOZ_DIAGNOSTIC_ASSERT(!mManager);
+ }
+}
+
+PClientHandleChild* ClientManagerChild::AllocPClientHandleChild(
+ const IPCClientInfo& aClientInfo) {
+ return new ClientHandleChild();
+}
+
+bool ClientManagerChild::DeallocPClientHandleChild(PClientHandleChild* aActor) {
+ delete aActor;
+ return true;
+}
+
+PClientManagerOpChild* ClientManagerChild::AllocPClientManagerOpChild(
+ const ClientOpConstructorArgs& aArgs) {
+ MOZ_ASSERT_UNREACHABLE(
+ "ClientManagerOpChild must be explicitly constructed.");
+ return nullptr;
+}
+
+bool ClientManagerChild::DeallocPClientManagerOpChild(
+ PClientManagerOpChild* aActor) {
+ delete aActor;
+ return true;
+}
+
+PClientNavigateOpChild* ClientManagerChild::AllocPClientNavigateOpChild(
+ const ClientNavigateOpConstructorArgs& aArgs) {
+ return new ClientNavigateOpChild();
+}
+
+bool ClientManagerChild::DeallocPClientNavigateOpChild(
+ PClientNavigateOpChild* aActor) {
+ delete aActor;
+ return true;
+}
+
+mozilla::ipc::IPCResult ClientManagerChild::RecvPClientNavigateOpConstructor(
+ PClientNavigateOpChild* aActor,
+ const ClientNavigateOpConstructorArgs& aArgs) {
+ auto actor = static_cast<ClientNavigateOpChild*>(aActor);
+ actor->Init(aArgs);
+ return IPC_OK();
+}
+
+PClientSourceChild* ClientManagerChild::AllocPClientSourceChild(
+ const ClientSourceConstructorArgs& aArgs) {
+ return new ClientSourceChild(aArgs);
+}
+
+bool ClientManagerChild::DeallocPClientSourceChild(PClientSourceChild* aActor) {
+ delete aActor;
+ return true;
+}
+
+// static
+ClientManagerChild* ClientManagerChild::Create() {
+ ClientManagerChild* actor = new ClientManagerChild();
+
+ if (!NS_IsMainThread()) {
+ WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate();
+ MOZ_DIAGNOSTIC_ASSERT(workerPrivate);
+
+ RefPtr<IPCWorkerRefHelper<ClientManagerChild>> helper =
+ new IPCWorkerRefHelper<ClientManagerChild>(actor);
+
+ actor->mIPCWorkerRef = IPCWorkerRef::Create(
+ workerPrivate, "ClientManagerChild",
+ [helper] { helper->Actor()->MaybeStartTeardown(); });
+
+ if (NS_WARN_IF(!actor->mIPCWorkerRef)) {
+ delete actor;
+ return nullptr;
+ }
+ }
+
+ return actor;
+}
+
+ClientManagerChild::ClientManagerChild()
+ : mManager(nullptr), mTeardownStarted(false) {}
+
+void ClientManagerChild::SetOwner(ClientThing<ClientManagerChild>* aThing) {
+ MOZ_DIAGNOSTIC_ASSERT(aThing);
+ MOZ_DIAGNOSTIC_ASSERT(!mManager);
+ mManager = aThing;
+}
+
+void ClientManagerChild::RevokeOwner(ClientThing<ClientManagerChild>* aThing) {
+ MOZ_DIAGNOSTIC_ASSERT(mManager);
+ MOZ_DIAGNOSTIC_ASSERT(mManager == aThing);
+ mManager = nullptr;
+}
+
+void ClientManagerChild::MaybeStartTeardown() {
+ if (mTeardownStarted) {
+ return;
+ }
+ mTeardownStarted = true;
+ SendTeardown();
+}
+
+WorkerPrivate* ClientManagerChild::GetWorkerPrivate() const {
+ if (!mIPCWorkerRef) {
+ return nullptr;
+ }
+ return mIPCWorkerRef->Private();
+}
+
+} // namespace mozilla::dom
diff --git a/dom/clients/manager/ClientManagerChild.h b/dom/clients/manager/ClientManagerChild.h
new file mode 100644
index 0000000000..30a0885a10
--- /dev/null
+++ b/dom/clients/manager/ClientManagerChild.h
@@ -0,0 +1,66 @@
+/* -*- 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/. */
+#ifndef _mozilla_dom_ClientManagerChild_h
+#define _mozilla_dom_ClientManagerChild_h
+
+#include "ClientThing.h"
+#include "mozilla/dom/PClientManagerChild.h"
+
+namespace mozilla::dom {
+
+class IPCWorkerRef;
+class WorkerPrivate;
+
+class ClientManagerChild final : public PClientManagerChild {
+ ClientThing<ClientManagerChild>* mManager;
+
+ RefPtr<IPCWorkerRef> mIPCWorkerRef;
+ bool mTeardownStarted;
+
+ ClientManagerChild();
+
+ // PClientManagerChild interface
+ void ActorDestroy(ActorDestroyReason aReason) override;
+
+ PClientHandleChild* AllocPClientHandleChild(
+ const IPCClientInfo& aClientInfo) override;
+
+ bool DeallocPClientHandleChild(PClientHandleChild* aActor) override;
+
+ PClientManagerOpChild* AllocPClientManagerOpChild(
+ const ClientOpConstructorArgs& aArgs) override;
+
+ bool DeallocPClientManagerOpChild(PClientManagerOpChild* aActor) override;
+
+ PClientNavigateOpChild* AllocPClientNavigateOpChild(
+ const ClientNavigateOpConstructorArgs& aArgs) override;
+
+ bool DeallocPClientNavigateOpChild(PClientNavigateOpChild* aActor) override;
+
+ mozilla::ipc::IPCResult RecvPClientNavigateOpConstructor(
+ PClientNavigateOpChild* aActor,
+ const ClientNavigateOpConstructorArgs& aArgs) override;
+
+ PClientSourceChild* AllocPClientSourceChild(
+ const ClientSourceConstructorArgs& aArgs) override;
+
+ bool DeallocPClientSourceChild(PClientSourceChild* aActor) override;
+
+ public:
+ static ClientManagerChild* Create();
+
+ void SetOwner(ClientThing<ClientManagerChild>* aThing);
+
+ void RevokeOwner(ClientThing<ClientManagerChild>* aThing);
+
+ void MaybeStartTeardown();
+
+ mozilla::dom::WorkerPrivate* GetWorkerPrivate() const;
+};
+
+} // namespace mozilla::dom
+
+#endif // _mozilla_dom_ClientManagerChild_h
diff --git a/dom/clients/manager/ClientManagerOpChild.cpp b/dom/clients/manager/ClientManagerOpChild.cpp
new file mode 100644
index 0000000000..19ed22f0f1
--- /dev/null
+++ b/dom/clients/manager/ClientManagerOpChild.cpp
@@ -0,0 +1,50 @@
+/* -*- 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 "ClientManagerOpChild.h"
+
+#include "mozilla/dom/ClientManager.h"
+#include "mozilla/ipc/ProtocolUtils.h"
+
+namespace mozilla::dom {
+
+void ClientManagerOpChild::ActorDestroy(ActorDestroyReason aReason) {
+ mClientManager = nullptr;
+ if (mPromise) {
+ CopyableErrorResult rv;
+ rv.ThrowAbortError("Client aborted");
+ mPromise->Reject(rv, __func__);
+ mPromise = nullptr;
+ }
+}
+
+mozilla::ipc::IPCResult ClientManagerOpChild::Recv__delete__(
+ const ClientOpResult& aResult) {
+ mClientManager = nullptr;
+ if (aResult.type() == ClientOpResult::TCopyableErrorResult &&
+ aResult.get_CopyableErrorResult().Failed()) {
+ mPromise->Reject(aResult.get_CopyableErrorResult(), __func__);
+ mPromise = nullptr;
+ return IPC_OK();
+ }
+ mPromise->Resolve(aResult, __func__);
+ mPromise = nullptr;
+ return IPC_OK();
+}
+
+ClientManagerOpChild::ClientManagerOpChild(ClientManager* aClientManager,
+ const ClientOpConstructorArgs& aArgs,
+ ClientOpPromise::Private* aPromise)
+ : mClientManager(aClientManager), mPromise(aPromise) {
+ MOZ_DIAGNOSTIC_ASSERT(mClientManager);
+ MOZ_DIAGNOSTIC_ASSERT(mPromise);
+}
+
+ClientManagerOpChild::~ClientManagerOpChild() {
+ MOZ_DIAGNOSTIC_ASSERT(!mPromise);
+}
+
+} // namespace mozilla::dom
diff --git a/dom/clients/manager/ClientManagerOpChild.h b/dom/clients/manager/ClientManagerOpChild.h
new file mode 100644
index 0000000000..1d13efd5be
--- /dev/null
+++ b/dom/clients/manager/ClientManagerOpChild.h
@@ -0,0 +1,37 @@
+/* -*- 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/. */
+#ifndef _mozilla_dom_ClientManagerOpChild_h
+#define _mozilla_dom_ClientManagerOpChild_h
+
+#include "mozilla/dom/ClientOpPromise.h"
+#include "mozilla/dom/PClientManagerOpChild.h"
+#include "mozilla/MozPromise.h"
+
+namespace mozilla::dom {
+
+class ClientManager;
+
+class ClientManagerOpChild final : public PClientManagerOpChild {
+ RefPtr<ClientManager> mClientManager;
+ RefPtr<ClientOpPromise::Private> mPromise;
+
+ // PClientManagerOpChild interface
+ void ActorDestroy(ActorDestroyReason aReason) override;
+
+ mozilla::ipc::IPCResult Recv__delete__(
+ const ClientOpResult& aResult) override;
+
+ public:
+ ClientManagerOpChild(ClientManager* aClientManager,
+ const ClientOpConstructorArgs& aArgs,
+ ClientOpPromise::Private* aPromise);
+
+ ~ClientManagerOpChild();
+};
+
+} // namespace mozilla::dom
+
+#endif // _mozilla_dom_ClientManagerOpChild_h
diff --git a/dom/clients/manager/ClientManagerOpParent.cpp b/dom/clients/manager/ClientManagerOpParent.cpp
new file mode 100644
index 0000000000..43e57b7178
--- /dev/null
+++ b/dom/clients/manager/ClientManagerOpParent.cpp
@@ -0,0 +1,85 @@
+/* -*- 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 "ClientManagerOpParent.h"
+
+#include "ClientManagerService.h"
+#include "mozilla/dom/PClientManagerParent.h"
+#include "mozilla/ipc/BackgroundParent.h"
+
+namespace mozilla::dom {
+
+using mozilla::ipc::BackgroundParent;
+
+template <typename Method, typename... Args>
+void ClientManagerOpParent::DoServiceOp(Method aMethod, Args&&... aArgs) {
+ ThreadsafeContentParentHandle* originContent =
+ BackgroundParent::GetContentParentHandle(Manager()->Manager());
+
+ // Note, we need perfect forarding of the template type in order
+ // to allow already_AddRefed<> to be passed as an arg.
+ RefPtr<ClientOpPromise> p =
+ (mService->*aMethod)(originContent, std::forward<Args>(aArgs)...);
+
+ // Capturing `this` is safe here because we disconnect the promise in
+ // ActorDestroy() which ensures neither lambda is called if the actor
+ // is destroyed before the source operation completes.
+ p->Then(
+ GetCurrentSerialEventTarget(), __func__,
+ [this](const mozilla::dom::ClientOpResult& aResult) {
+ mPromiseRequestHolder.Complete();
+ Unused << PClientManagerOpParent::Send__delete__(this, aResult);
+ },
+ [this](const CopyableErrorResult& aRv) {
+ mPromiseRequestHolder.Complete();
+ Unused << PClientManagerOpParent::Send__delete__(this, aRv);
+ })
+ ->Track(mPromiseRequestHolder);
+}
+
+void ClientManagerOpParent::ActorDestroy(ActorDestroyReason aReason) {
+ mPromiseRequestHolder.DisconnectIfExists();
+}
+
+ClientManagerOpParent::ClientManagerOpParent(ClientManagerService* aService)
+ : mService(aService) {
+ MOZ_DIAGNOSTIC_ASSERT(mService);
+}
+
+void ClientManagerOpParent::Init(const ClientOpConstructorArgs& aArgs) {
+ switch (aArgs.type()) {
+ case ClientOpConstructorArgs::TClientNavigateArgs: {
+ DoServiceOp(&ClientManagerService::Navigate,
+ aArgs.get_ClientNavigateArgs());
+ break;
+ }
+ case ClientOpConstructorArgs::TClientMatchAllArgs: {
+ DoServiceOp(&ClientManagerService::MatchAll,
+ aArgs.get_ClientMatchAllArgs());
+ break;
+ }
+ case ClientOpConstructorArgs::TClientClaimArgs: {
+ DoServiceOp(&ClientManagerService::Claim, aArgs.get_ClientClaimArgs());
+ break;
+ }
+ case ClientOpConstructorArgs::TClientGetInfoAndStateArgs: {
+ DoServiceOp(&ClientManagerService::GetInfoAndState,
+ aArgs.get_ClientGetInfoAndStateArgs());
+ break;
+ }
+ case ClientOpConstructorArgs::TClientOpenWindowArgs: {
+ DoServiceOp(&ClientManagerService::OpenWindow,
+ aArgs.get_ClientOpenWindowArgs());
+ break;
+ }
+ default: {
+ MOZ_ASSERT_UNREACHABLE("Unknown Client operation!");
+ break;
+ }
+ }
+}
+
+} // namespace mozilla::dom
diff --git a/dom/clients/manager/ClientManagerOpParent.h b/dom/clients/manager/ClientManagerOpParent.h
new file mode 100644
index 0000000000..0bb8257310
--- /dev/null
+++ b/dom/clients/manager/ClientManagerOpParent.h
@@ -0,0 +1,35 @@
+/* -*- 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/. */
+#ifndef _mozilla_dom_ClientManagerOpParent_h
+#define _mozilla_dom_ClientManagerOpParent_h
+
+#include "mozilla/dom/PClientManagerOpParent.h"
+#include "ClientOpPromise.h"
+
+namespace mozilla::dom {
+
+class ClientManagerService;
+
+class ClientManagerOpParent final : public PClientManagerOpParent {
+ RefPtr<ClientManagerService> mService;
+ MozPromiseRequestHolder<ClientOpPromise> mPromiseRequestHolder;
+
+ template <typename Method, typename... Args>
+ void DoServiceOp(Method aMethod, Args&&... aArgs);
+
+ // PClientManagerOpParent interface
+ void ActorDestroy(ActorDestroyReason aReason) override;
+
+ public:
+ explicit ClientManagerOpParent(ClientManagerService* aService);
+ ~ClientManagerOpParent() = default;
+
+ void Init(const ClientOpConstructorArgs& aArgs);
+};
+
+} // namespace mozilla::dom
+
+#endif // _mozilla_dom_ClientManagerOpParent_h
diff --git a/dom/clients/manager/ClientManagerParent.cpp b/dom/clients/manager/ClientManagerParent.cpp
new file mode 100644
index 0000000000..1ee0eddcb0
--- /dev/null
+++ b/dom/clients/manager/ClientManagerParent.cpp
@@ -0,0 +1,125 @@
+/* -*- 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 "ClientManagerParent.h"
+
+#include "mozilla/ipc/BackgroundParent.h"
+#include "ClientHandleParent.h"
+#include "ClientManagerOpParent.h"
+#include "ClientManagerService.h"
+#include "ClientSourceParent.h"
+#include "ClientValidation.h"
+#include "mozilla/dom/PClientNavigateOpParent.h"
+#include "mozilla/Unused.h"
+
+namespace mozilla::dom {
+
+using mozilla::ipc::IPCResult;
+
+IPCResult ClientManagerParent::RecvTeardown() {
+ Unused << Send__delete__(this);
+ return IPC_OK();
+}
+
+void ClientManagerParent::ActorDestroy(ActorDestroyReason aReason) {}
+
+PClientHandleParent* ClientManagerParent::AllocPClientHandleParent(
+ const IPCClientInfo& aClientInfo) {
+ return new ClientHandleParent();
+}
+
+bool ClientManagerParent::DeallocPClientHandleParent(
+ PClientHandleParent* aActor) {
+ delete aActor;
+ return true;
+}
+
+IPCResult ClientManagerParent::RecvPClientHandleConstructor(
+ PClientHandleParent* aActor, const IPCClientInfo& aClientInfo) {
+ ClientHandleParent* actor = static_cast<ClientHandleParent*>(aActor);
+ actor->Init(aClientInfo);
+ return IPC_OK();
+}
+
+PClientManagerOpParent* ClientManagerParent::AllocPClientManagerOpParent(
+ const ClientOpConstructorArgs& aArgs) {
+ return new ClientManagerOpParent(mService);
+}
+
+bool ClientManagerParent::DeallocPClientManagerOpParent(
+ PClientManagerOpParent* aActor) {
+ delete aActor;
+ return true;
+}
+
+IPCResult ClientManagerParent::RecvPClientManagerOpConstructor(
+ PClientManagerOpParent* aActor, const ClientOpConstructorArgs& aArgs) {
+ ClientManagerOpParent* actor = static_cast<ClientManagerOpParent*>(aActor);
+ actor->Init(aArgs);
+ return IPC_OK();
+}
+
+PClientNavigateOpParent* ClientManagerParent::AllocPClientNavigateOpParent(
+ const ClientNavigateOpConstructorArgs& aArgs) {
+ MOZ_ASSERT_UNREACHABLE(
+ "ClientNavigateOpParent should be explicitly constructed.");
+ return nullptr;
+}
+
+bool ClientManagerParent::DeallocPClientNavigateOpParent(
+ PClientNavigateOpParent* aActor) {
+ delete aActor;
+ return true;
+}
+
+PClientSourceParent* ClientManagerParent::AllocPClientSourceParent(
+ const ClientSourceConstructorArgs& aArgs) {
+ Maybe<ContentParentId> contentParentId;
+
+ uint64_t childID = ::mozilla::ipc::BackgroundParent::GetChildID(Manager());
+ if (childID) {
+ contentParentId = Some(ContentParentId(childID));
+ }
+
+ return new ClientSourceParent(aArgs, contentParentId);
+}
+
+bool ClientManagerParent::DeallocPClientSourceParent(
+ PClientSourceParent* aActor) {
+ delete aActor;
+ return true;
+}
+
+IPCResult ClientManagerParent::RecvPClientSourceConstructor(
+ PClientSourceParent* aActor, const ClientSourceConstructorArgs& aArgs) {
+ ClientSourceParent* actor = static_cast<ClientSourceParent*>(aActor);
+ actor->Init();
+ return IPC_OK();
+}
+
+ClientManagerParent::ClientManagerParent()
+ : mService(ClientManagerService::GetOrCreateInstance()) {}
+
+ClientManagerParent::~ClientManagerParent() { mService->RemoveManager(this); }
+
+void ClientManagerParent::Init() { mService->AddManager(this); }
+
+IPCResult ClientManagerParent::RecvExpectFutureClientSource(
+ const IPCClientInfo& aClientInfo) {
+ RefPtr<ClientManagerService> cms =
+ ClientManagerService::GetOrCreateInstance();
+ Unused << NS_WARN_IF(!cms->ExpectFutureSource(aClientInfo));
+ return IPC_OK();
+}
+
+IPCResult ClientManagerParent::RecvForgetFutureClientSource(
+ const IPCClientInfo& aClientInfo) {
+ RefPtr<ClientManagerService> cms = ClientManagerService::GetInstance();
+ cms->ForgetFutureSource(aClientInfo);
+ return IPC_OK();
+}
+
+} // namespace mozilla::dom
diff --git a/dom/clients/manager/ClientManagerParent.h b/dom/clients/manager/ClientManagerParent.h
new file mode 100644
index 0000000000..5991f2ff59
--- /dev/null
+++ b/dom/clients/manager/ClientManagerParent.h
@@ -0,0 +1,69 @@
+/* -*- 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/. */
+#ifndef _mozilla_dom_ClientManagerParent_h
+#define _mozilla_dom_ClientManagerParent_h
+
+#include "mozilla/dom/PClientManagerParent.h"
+
+namespace mozilla::dom {
+
+class ClientManagerService;
+
+class ClientManagerParent final : public PClientManagerParent {
+ RefPtr<ClientManagerService> mService;
+
+ // PClientManagerParent interface
+ mozilla::ipc::IPCResult RecvTeardown() override;
+
+ void ActorDestroy(ActorDestroyReason aReason) override;
+
+ PClientHandleParent* AllocPClientHandleParent(
+ const IPCClientInfo& aClientInfo) override;
+
+ bool DeallocPClientHandleParent(PClientHandleParent* aActor) override;
+
+ mozilla::ipc::IPCResult RecvPClientHandleConstructor(
+ PClientHandleParent* aActor, const IPCClientInfo& aClientInfo) override;
+
+ PClientManagerOpParent* AllocPClientManagerOpParent(
+ const ClientOpConstructorArgs& aArgs) override;
+
+ bool DeallocPClientManagerOpParent(PClientManagerOpParent* aActor) override;
+
+ mozilla::ipc::IPCResult RecvPClientManagerOpConstructor(
+ PClientManagerOpParent* aActor,
+ const ClientOpConstructorArgs& aArgs) override;
+
+ PClientNavigateOpParent* AllocPClientNavigateOpParent(
+ const ClientNavigateOpConstructorArgs& aArgs) override;
+
+ bool DeallocPClientNavigateOpParent(PClientNavigateOpParent* aActor) override;
+
+ PClientSourceParent* AllocPClientSourceParent(
+ const ClientSourceConstructorArgs& aArgs) override;
+
+ bool DeallocPClientSourceParent(PClientSourceParent* aActor) override;
+
+ mozilla::ipc::IPCResult RecvPClientSourceConstructor(
+ PClientSourceParent* aActor,
+ const ClientSourceConstructorArgs& aArgs) override;
+
+ mozilla::ipc::IPCResult RecvExpectFutureClientSource(
+ const IPCClientInfo& aClientInfo) override;
+
+ mozilla::ipc::IPCResult RecvForgetFutureClientSource(
+ const IPCClientInfo& aClientInfo) override;
+
+ public:
+ ClientManagerParent();
+ ~ClientManagerParent();
+
+ void Init();
+};
+
+} // namespace mozilla::dom
+
+#endif // _mozilla_dom_ClientManagerParent_h
diff --git a/dom/clients/manager/ClientManagerService.cpp b/dom/clients/manager/ClientManagerService.cpp
new file mode 100644
index 0000000000..5eafd6053b
--- /dev/null
+++ b/dom/clients/manager/ClientManagerService.cpp
@@ -0,0 +1,748 @@
+/* -*- 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 "ClientManagerService.h"
+
+#include "ClientHandleParent.h"
+#include "ClientManagerParent.h"
+#include "ClientNavigateOpParent.h"
+#include "ClientOpenWindowUtils.h"
+#include "ClientPrincipalUtils.h"
+#include "ClientSourceParent.h"
+#include "mozilla/dom/ContentParent.h"
+#include "mozilla/dom/ServiceWorkerManager.h"
+#include "mozilla/dom/ServiceWorkerUtils.h"
+#include "mozilla/ipc/BackgroundParent.h"
+#include "mozilla/ipc/PBackgroundSharedTypes.h"
+#include "mozilla/ClearOnShutdown.h"
+#include "mozilla/MozPromise.h"
+#include "mozilla/SchedulerGroup.h"
+#include "mozilla/ScopeExit.h"
+#include "jsfriendapi.h"
+#include "nsIAsyncShutdown.h"
+#include "nsIXULRuntime.h"
+#include "nsProxyRelease.h"
+
+namespace mozilla::dom {
+
+using mozilla::ipc::AssertIsOnBackgroundThread;
+using mozilla::ipc::PrincipalInfo;
+
+namespace {
+
+ClientManagerService* sClientManagerServiceInstance = nullptr;
+bool sClientManagerServiceShutdownRegistered = false;
+
+class ClientShutdownBlocker final : public nsIAsyncShutdownBlocker {
+ RefPtr<GenericPromise::Private> mPromise;
+
+ ~ClientShutdownBlocker() = default;
+
+ public:
+ explicit ClientShutdownBlocker(GenericPromise::Private* aPromise)
+ : mPromise(aPromise) {
+ MOZ_DIAGNOSTIC_ASSERT(mPromise);
+ }
+
+ NS_IMETHOD
+ GetName(nsAString& aNameOut) override {
+ aNameOut = nsLiteralString(
+ u"ClientManagerService: start destroying IPC actors early");
+ return NS_OK;
+ }
+
+ NS_IMETHOD
+ BlockShutdown(nsIAsyncShutdownClient* aClient) override {
+ mPromise->Resolve(true, __func__);
+ aClient->RemoveBlocker(this);
+ return NS_OK;
+ }
+
+ NS_IMETHOD
+ GetState(nsIPropertyBag**) override { return NS_OK; }
+
+ NS_DECL_ISUPPORTS
+};
+
+NS_IMPL_ISUPPORTS(ClientShutdownBlocker, nsIAsyncShutdownBlocker)
+
+// Helper function the resolves a MozPromise when we detect that the browser
+// has begun to shutdown.
+RefPtr<GenericPromise> OnShutdown() {
+ RefPtr<GenericPromise::Private> ref = new GenericPromise::Private(__func__);
+
+ nsCOMPtr<nsIRunnable> r =
+ NS_NewRunnableFunction("ClientManagerServer::OnShutdown", [ref]() {
+ nsCOMPtr<nsIAsyncShutdownService> svc =
+ services::GetAsyncShutdownService();
+ if (!svc) {
+ ref->Resolve(true, __func__);
+ return;
+ }
+
+ nsCOMPtr<nsIAsyncShutdownClient> phase;
+ MOZ_ALWAYS_SUCCEEDS(svc->GetXpcomWillShutdown(getter_AddRefs(phase)));
+ if (!phase) {
+ ref->Resolve(true, __func__);
+ return;
+ }
+
+ nsCOMPtr<nsIAsyncShutdownBlocker> blocker =
+ new ClientShutdownBlocker(ref);
+ nsresult rv =
+ phase->AddBlocker(blocker, NS_LITERAL_STRING_FROM_CSTRING(__FILE__),
+ __LINE__, u"ClientManagerService shutdown"_ns);
+
+ if (NS_FAILED(rv)) {
+ ref->Resolve(true, __func__);
+ return;
+ }
+ });
+
+ MOZ_ALWAYS_SUCCEEDS(
+ SchedulerGroup::Dispatch(TaskCategory::Other, r.forget()));
+
+ return ref;
+}
+
+} // anonymous namespace
+
+ClientManagerService::FutureClientSourceParent::FutureClientSourceParent(
+ const IPCClientInfo& aClientInfo)
+ : mPrincipalInfo(aClientInfo.principalInfo()), mAssociated(false) {}
+
+ClientManagerService::ClientManagerService() : mShutdown(false) {
+ AssertIsOnBackgroundThread();
+
+ // Only register one shutdown handler at a time. If a previous service
+ // instance did this, but shutdown has not come, then we can avoid
+ // doing it again.
+ if (!sClientManagerServiceShutdownRegistered) {
+ sClientManagerServiceShutdownRegistered = true;
+
+ // While the ClientManagerService will be gracefully terminated as windows
+ // and workers are naturally killed, this can cause us to do extra work
+ // relatively late in the shutdown process. To avoid this we eagerly begin
+ // shutdown at the first sign it has begun. Since we handle normal shutdown
+ // gracefully we don't really need to block anything here. We just begin
+ // destroying our IPC actors immediately.
+ OnShutdown()->Then(GetCurrentSerialEventTarget(), __func__, []() {
+ // Look up the latest service instance, if it exists. This may
+ // be different from the instance that registered the shutdown
+ // handler.
+ RefPtr<ClientManagerService> svc = ClientManagerService::GetInstance();
+ if (svc) {
+ svc->Shutdown();
+ }
+ });
+ }
+}
+
+ClientManagerService::~ClientManagerService() {
+ AssertIsOnBackgroundThread();
+ MOZ_DIAGNOSTIC_ASSERT(mSourceTable.Count() == 0);
+ MOZ_DIAGNOSTIC_ASSERT(mManagerList.IsEmpty());
+
+ MOZ_DIAGNOSTIC_ASSERT(sClientManagerServiceInstance == this);
+ sClientManagerServiceInstance = nullptr;
+}
+
+void ClientManagerService::Shutdown() {
+ AssertIsOnBackgroundThread();
+ MOZ_DIAGNOSTIC_ASSERT(sClientManagerServiceShutdownRegistered);
+
+ // If many ClientManagerService are created and destroyed quickly we can
+ // in theory get more than one shutdown listener calling us.
+ if (mShutdown) {
+ return;
+ }
+ mShutdown = true;
+
+ // Begin destroying our various manager actors which will in turn destroy
+ // all source, handle, and operation actors.
+ for (auto actor :
+ CopyableAutoTArray<ClientManagerParent*, 16>(mManagerList)) {
+ Unused << PClientManagerParent::Send__delete__(actor);
+ }
+
+ // Destroying manager actors should've also destroyed all source actors, so
+ // the only sources left will be future sources, which need to be aborted.
+ for (auto& entry : mSourceTable) {
+ MOZ_RELEASE_ASSERT(entry.GetData().is<FutureClientSourceParent>());
+ CopyableErrorResult rv;
+ rv.ThrowInvalidStateError("Client creation aborted.");
+ entry.GetModifiableData()
+ ->as<FutureClientSourceParent>()
+ .RejectPromiseIfExists(rv);
+ }
+ mSourceTable.Clear();
+}
+
+ClientSourceParent* ClientManagerService::MaybeUnwrapAsExistingSource(
+ const SourceTableEntry& aEntry) const {
+ AssertIsOnBackgroundThread();
+
+ if (aEntry.is<FutureClientSourceParent>()) {
+ return nullptr;
+ }
+
+ return aEntry.as<ClientSourceParent*>();
+}
+
+ClientSourceParent* ClientManagerService::FindExistingSource(
+ const nsID& aID, const PrincipalInfo& aPrincipalInfo) const {
+ AssertIsOnBackgroundThread();
+
+ auto entry = mSourceTable.Lookup(aID);
+
+ if (!entry) {
+ return nullptr;
+ }
+
+ ClientSourceParent* source = MaybeUnwrapAsExistingSource(entry.Data());
+
+ if (!source || NS_WARN_IF(!ClientMatchPrincipalInfo(
+ source->Info().PrincipalInfo(), aPrincipalInfo))) {
+ return nullptr;
+ }
+ return source;
+}
+
+// static
+already_AddRefed<ClientManagerService>
+ClientManagerService::GetOrCreateInstance() {
+ AssertIsOnBackgroundThread();
+
+ if (!sClientManagerServiceInstance) {
+ sClientManagerServiceInstance = new ClientManagerService();
+ }
+
+ RefPtr<ClientManagerService> ref(sClientManagerServiceInstance);
+ return ref.forget();
+}
+
+// static
+already_AddRefed<ClientManagerService> ClientManagerService::GetInstance() {
+ AssertIsOnBackgroundThread();
+
+ if (!sClientManagerServiceInstance) {
+ return nullptr;
+ }
+
+ RefPtr<ClientManagerService> ref(sClientManagerServiceInstance);
+ return ref.forget();
+}
+
+namespace {
+
+bool IsNullPrincipalInfo(const PrincipalInfo& aPrincipalInfo) {
+ return aPrincipalInfo.type() == PrincipalInfo::TNullPrincipalInfo;
+}
+
+bool AreBothNullPrincipals(const PrincipalInfo& aPrincipalInfo1,
+ const PrincipalInfo& aPrincipalInfo2) {
+ return IsNullPrincipalInfo(aPrincipalInfo1) &&
+ IsNullPrincipalInfo(aPrincipalInfo2);
+}
+
+} // anonymous namespace
+
+bool ClientManagerService::AddSource(ClientSourceParent* aSource) {
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(aSource);
+
+ auto entry = mSourceTable.Lookup(aSource->Info().Id());
+ if (entry) {
+ // Do not permit overwriting an existing ClientSource with the same
+ // UUID. This would allow a spoofed ClientParentSource actor to
+ // intercept postMessage() intended for the real actor.
+ if (entry.Data().is<ClientSourceParent*>()) {
+ return false;
+ }
+ FutureClientSourceParent& placeHolder =
+ entry.Data().as<FutureClientSourceParent>();
+
+ const PrincipalInfo& placeHolderPrincipalInfo = placeHolder.PrincipalInfo();
+ const PrincipalInfo& sourcePrincipalInfo = aSource->Info().PrincipalInfo();
+
+ // The placeholder FutureClientSourceParent's PrincipalInfo must match the
+ // real ClientSourceParent's PrincipalInfo. The only exception is if both
+ // are null principals (two null principals are considered unequal).
+ if (!AreBothNullPrincipals(placeHolderPrincipalInfo, sourcePrincipalInfo) &&
+ NS_WARN_IF(!ClientMatchPrincipalInfo(placeHolderPrincipalInfo,
+ sourcePrincipalInfo))) {
+ return false;
+ }
+
+ placeHolder.ResolvePromiseIfExists();
+ *entry = AsVariant(aSource);
+ return true;
+ }
+ if (!mSourceTable.WithEntryHandle(aSource->Info().Id(),
+ [aSource](auto&& entry) {
+ if (NS_WARN_IF(entry.HasEntry())) {
+ return false;
+ }
+ entry.Insert(AsVariant(aSource));
+ return true;
+ })) {
+ return false;
+ }
+ return true;
+}
+
+bool ClientManagerService::RemoveSource(ClientSourceParent* aSource) {
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(aSource);
+ auto entry = mSourceTable.Lookup(aSource->Info().Id());
+ if (NS_WARN_IF(!entry)) {
+ return false;
+ }
+ entry.Remove();
+ return true;
+}
+
+bool ClientManagerService::ExpectFutureSource(
+ const IPCClientInfo& aClientInfo) {
+ AssertIsOnBackgroundThread();
+
+ if (!mSourceTable.WithEntryHandle(
+ aClientInfo.id(), [&aClientInfo](auto&& entry) {
+ // Prevent overwrites.
+ if (entry.HasEntry()) {
+ return false;
+ }
+ entry.Insert(SourceTableEntry(
+ VariantIndex<0>(), FutureClientSourceParent(aClientInfo)));
+ return true;
+ })) {
+ return false;
+ }
+
+ return true;
+}
+
+void ClientManagerService::ForgetFutureSource(
+ const IPCClientInfo& aClientInfo) {
+ AssertIsOnBackgroundThread();
+
+ auto entry = mSourceTable.Lookup(aClientInfo.id());
+
+ if (entry) {
+ if (entry.Data().is<ClientSourceParent*>()) {
+ return;
+ }
+
+ // For non-e10s case, ClientChannelHelperParent will be freed before real
+ // ClientSourceParnet be created. In the end this methoed will be called to
+ // release the FutureClientSourceParent. That means a ClientHandle operation
+ // which waits for the FutureClientSourceParent will have no chance to
+ // connect to the ClientSourceParent. So the FutureClientSourceParent should
+ // be keep in this case.
+ // IsAssociated() makes sure there is a ClientHandle operation associated
+ // with it.
+ // More details please refer
+ // https://bugzilla.mozilla.org/show_bug.cgi?id=1730350#c2
+ if (!XRE_IsE10sParentProcess() &&
+ entry.Data().as<FutureClientSourceParent>().IsAssociated()) {
+ return;
+ }
+
+ CopyableErrorResult rv;
+ rv.ThrowInvalidStateError("Client creation aborted.");
+ entry.Data().as<FutureClientSourceParent>().RejectPromiseIfExists(rv);
+
+ entry.Remove();
+ }
+}
+
+RefPtr<SourcePromise> ClientManagerService::FindSource(
+ const nsID& aID, const PrincipalInfo& aPrincipalInfo) {
+ AssertIsOnBackgroundThread();
+
+ auto entry = mSourceTable.Lookup(aID);
+ if (!entry) {
+ CopyableErrorResult rv;
+ rv.ThrowInvalidStateError("Unknown client.");
+ return SourcePromise::CreateAndReject(rv, __func__);
+ }
+
+ if (entry.Data().is<FutureClientSourceParent>()) {
+ entry.Data().as<FutureClientSourceParent>().SetAsAssociated();
+ return entry.Data().as<FutureClientSourceParent>().Promise();
+ }
+
+ ClientSourceParent* source = entry.Data().as<ClientSourceParent*>();
+ if (NS_WARN_IF(!ClientMatchPrincipalInfo(source->Info().PrincipalInfo(),
+ aPrincipalInfo))) {
+ CopyableErrorResult rv;
+ rv.ThrowInvalidStateError("Unknown client.");
+ return SourcePromise::CreateAndReject(rv, __func__);
+ }
+
+ return SourcePromise::CreateAndResolve(true, __func__);
+}
+
+void ClientManagerService::AddManager(ClientManagerParent* aManager) {
+ AssertIsOnBackgroundThread();
+ MOZ_DIAGNOSTIC_ASSERT(aManager);
+ MOZ_ASSERT(!mManagerList.Contains(aManager));
+ mManagerList.AppendElement(aManager);
+
+ // If shutdown has already begun then immediately destroy the actor.
+ if (mShutdown) {
+ Unused << PClientManagerParent::Send__delete__(aManager);
+ }
+}
+
+void ClientManagerService::RemoveManager(ClientManagerParent* aManager) {
+ AssertIsOnBackgroundThread();
+ MOZ_DIAGNOSTIC_ASSERT(aManager);
+ DebugOnly<bool> removed = mManagerList.RemoveElement(aManager);
+ MOZ_ASSERT(removed);
+}
+
+RefPtr<ClientOpPromise> ClientManagerService::Navigate(
+ ThreadsafeContentParentHandle* aOriginContent,
+ const ClientNavigateArgs& aArgs) {
+ ClientSourceParent* source =
+ FindExistingSource(aArgs.target().id(), aArgs.target().principalInfo());
+ if (!source) {
+ CopyableErrorResult rv;
+ rv.ThrowInvalidStateError("Unknown client");
+ return ClientOpPromise::CreateAndReject(rv, __func__);
+ }
+
+ const IPCServiceWorkerDescriptor& serviceWorker = aArgs.serviceWorker();
+
+ // Per https://w3c.github.io/ServiceWorker/#dom-windowclient-navigate step 4,
+ // if the service worker does not control the client, reject with a TypeError.
+ const Maybe<ServiceWorkerDescriptor>& controller = source->GetController();
+ if (controller.isNothing() ||
+ controller.ref().Scope() != serviceWorker.scope() ||
+ controller.ref().Id() != serviceWorker.id()) {
+ CopyableErrorResult rv;
+ rv.ThrowTypeError("Client is not controlled by this Service Worker");
+ return ClientOpPromise::CreateAndReject(rv, __func__);
+ }
+
+ PClientManagerParent* manager = source->Manager();
+ MOZ_DIAGNOSTIC_ASSERT(manager);
+
+ ClientNavigateOpConstructorArgs args;
+ args.url() = aArgs.url();
+ args.baseURL() = aArgs.baseURL();
+
+ // This is safe to do because the ClientSourceChild cannot directly delete
+ // itself. Instead it sends a Teardown message to the parent which then
+ // calls delete. That means we can be sure that we are not racing with
+ // source destruction here.
+ args.targetParent() = source;
+
+ RefPtr<ClientOpPromise::Private> promise =
+ new ClientOpPromise::Private(__func__);
+
+ ClientNavigateOpParent* op = new ClientNavigateOpParent(args, promise);
+ PClientNavigateOpParent* result =
+ manager->SendPClientNavigateOpConstructor(op, args);
+ if (!result) {
+ CopyableErrorResult rv;
+ rv.ThrowInvalidStateError("Client is aborted");
+ promise->Reject(rv, __func__);
+ }
+
+ return promise;
+}
+
+namespace {
+
+class PromiseListHolder final {
+ RefPtr<ClientOpPromise::Private> mResultPromise;
+ nsTArray<RefPtr<ClientOpPromise>> mPromiseList;
+ nsTArray<ClientInfoAndState> mResultList;
+ uint32_t mOutstandingPromiseCount;
+
+ void ProcessSuccess(const ClientInfoAndState& aResult) {
+ mResultList.AppendElement(aResult);
+ ProcessCompletion();
+ }
+
+ void ProcessCompletion() {
+ MOZ_DIAGNOSTIC_ASSERT(mOutstandingPromiseCount > 0);
+ mOutstandingPromiseCount -= 1;
+ MaybeFinish();
+ }
+
+ ~PromiseListHolder() = default;
+
+ public:
+ PromiseListHolder()
+ : mResultPromise(new ClientOpPromise::Private(__func__)),
+ mOutstandingPromiseCount(0) {}
+
+ RefPtr<ClientOpPromise> GetResultPromise() {
+ RefPtr<PromiseListHolder> kungFuDeathGrip = this;
+ return mResultPromise->Then(
+ GetCurrentSerialEventTarget(), __func__,
+ [kungFuDeathGrip](const ClientOpPromise::ResolveOrRejectValue& aValue) {
+ return ClientOpPromise::CreateAndResolveOrReject(aValue, __func__);
+ });
+ }
+
+ void AddPromise(RefPtr<ClientOpPromise>&& aPromise) {
+ mPromiseList.AppendElement(std::move(aPromise));
+ MOZ_DIAGNOSTIC_ASSERT(mPromiseList.LastElement());
+ mOutstandingPromiseCount += 1;
+
+ RefPtr<PromiseListHolder> self(this);
+ mPromiseList.LastElement()->Then(
+ GetCurrentSerialEventTarget(), __func__,
+ [self](const ClientOpResult& aResult) {
+ // TODO: This is pretty clunky. Try to figure out a better
+ // wait for MatchAll() and Claim() to share this code
+ // even though they expect different return values.
+ if (aResult.type() == ClientOpResult::TClientInfoAndState) {
+ self->ProcessSuccess(aResult.get_ClientInfoAndState());
+ } else {
+ self->ProcessCompletion();
+ }
+ },
+ [self](const CopyableErrorResult& aResult) {
+ self->ProcessCompletion();
+ });
+ }
+
+ void MaybeFinish() {
+ if (!mOutstandingPromiseCount) {
+ mResultPromise->Resolve(CopyableTArray(mResultList.Clone()), __func__);
+ }
+ }
+
+ NS_INLINE_DECL_REFCOUNTING(PromiseListHolder)
+};
+
+} // anonymous namespace
+
+RefPtr<ClientOpPromise> ClientManagerService::MatchAll(
+ ThreadsafeContentParentHandle* aOriginContent,
+ const ClientMatchAllArgs& aArgs) {
+ AssertIsOnBackgroundThread();
+
+ ServiceWorkerDescriptor swd(aArgs.serviceWorker());
+ const PrincipalInfo& principalInfo = swd.PrincipalInfo();
+
+ RefPtr<PromiseListHolder> promiseList = new PromiseListHolder();
+
+ for (const auto& entry : mSourceTable) {
+ ClientSourceParent* source = MaybeUnwrapAsExistingSource(entry.GetData());
+
+ if (!source || source->IsFrozen() || !source->ExecutionReady()) {
+ continue;
+ }
+
+ if (aArgs.type() != ClientType::All &&
+ source->Info().Type() != aArgs.type()) {
+ continue;
+ }
+
+ if (!ClientMatchPrincipalInfo(source->Info().PrincipalInfo(),
+ principalInfo)) {
+ continue;
+ }
+
+ if (!aArgs.includeUncontrolled()) {
+ const Maybe<ServiceWorkerDescriptor>& controller =
+ source->GetController();
+ if (controller.isNothing()) {
+ continue;
+ }
+
+ if (controller.ref().Id() != swd.Id() ||
+ controller.ref().Scope() != swd.Scope()) {
+ continue;
+ }
+ }
+
+ promiseList->AddPromise(source->StartOp(ClientGetInfoAndStateArgs(
+ source->Info().Id(), source->Info().PrincipalInfo())));
+ }
+
+ // Maybe finish the promise now in case we didn't find any matching clients.
+ promiseList->MaybeFinish();
+
+ return promiseList->GetResultPromise();
+}
+
+namespace {
+
+RefPtr<ClientOpPromise> ClaimOnMainThread(
+ const ClientInfo& aClientInfo, const ServiceWorkerDescriptor& aDescriptor) {
+ RefPtr<ClientOpPromise::Private> promise =
+ new ClientOpPromise::Private(__func__);
+
+ nsCOMPtr<nsIRunnable> r = NS_NewRunnableFunction(
+ __func__, [promise, clientInfo = std::move(aClientInfo),
+ desc = std::move(aDescriptor)]() {
+ auto scopeExit = MakeScopeExit([&] {
+ // This will truncate the URLs if they have embedded nulls, if that
+ // can happen, but for // our purposes here that's OK.
+ nsPrintfCString err(
+ "Service worker at <%s> can't claim Client at <%s>",
+ desc.ScriptURL().get(), clientInfo.URL().get());
+ CopyableErrorResult rv;
+ rv.ThrowInvalidStateError(err);
+ promise->Reject(rv, __func__);
+ });
+
+ RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance();
+ NS_ENSURE_TRUE_VOID(swm);
+
+ RefPtr<GenericErrorResultPromise> inner =
+ swm->MaybeClaimClient(clientInfo, desc);
+ inner->Then(
+ GetMainThreadSerialEventTarget(), __func__,
+ [promise](bool aResult) {
+ promise->Resolve(CopyableErrorResult(), __func__);
+ },
+ [promise](const CopyableErrorResult& aRv) {
+ promise->Reject(aRv, __func__);
+ });
+
+ scopeExit.release();
+ });
+
+ MOZ_ALWAYS_SUCCEEDS(
+ SchedulerGroup::Dispatch(TaskCategory::Other, r.forget()));
+
+ return promise;
+}
+
+} // anonymous namespace
+
+RefPtr<ClientOpPromise> ClientManagerService::Claim(
+ ThreadsafeContentParentHandle* aOriginContent,
+ const ClientClaimArgs& aArgs) {
+ AssertIsOnBackgroundThread();
+
+ const IPCServiceWorkerDescriptor& serviceWorker = aArgs.serviceWorker();
+ const PrincipalInfo& principalInfo = serviceWorker.principalInfo();
+
+ RefPtr<PromiseListHolder> promiseList = new PromiseListHolder();
+
+ for (const auto& entry : mSourceTable) {
+ ClientSourceParent* source = MaybeUnwrapAsExistingSource(entry.GetData());
+
+ if (!source) {
+ continue;
+ }
+
+ if (!ClientMatchPrincipalInfo(source->Info().PrincipalInfo(),
+ principalInfo)) {
+ continue;
+ }
+
+ const Maybe<ServiceWorkerDescriptor>& controller = source->GetController();
+ if (controller.isSome() &&
+ controller.ref().Scope() == serviceWorker.scope() &&
+ controller.ref().Id() == serviceWorker.id()) {
+ continue;
+ }
+
+ // TODO: This logic to determine if a service worker should control
+ // a particular client should be moved to the ServiceWorkerManager.
+ // This can't happen until the SWM is moved to the parent process,
+ // though.
+ if (!source->ExecutionReady() ||
+ source->Info().Type() == ClientType::Serviceworker ||
+ source->Info().URL().Find(serviceWorker.scope()) != 0) {
+ continue;
+ }
+
+ if (source->IsFrozen()) {
+ Unused << source->SendEvictFromBFCache();
+ continue;
+ }
+
+ promiseList->AddPromise(ClaimOnMainThread(
+ source->Info(), ServiceWorkerDescriptor(serviceWorker)));
+ }
+
+ // Maybe finish the promise now in case we didn't find any matching clients.
+ promiseList->MaybeFinish();
+
+ return promiseList->GetResultPromise();
+}
+
+RefPtr<ClientOpPromise> ClientManagerService::GetInfoAndState(
+ ThreadsafeContentParentHandle* aOriginContent,
+ const ClientGetInfoAndStateArgs& aArgs) {
+ ClientSourceParent* source =
+ FindExistingSource(aArgs.id(), aArgs.principalInfo());
+
+ if (!source) {
+ CopyableErrorResult rv;
+ rv.ThrowInvalidStateError("Unknown client");
+ return ClientOpPromise::CreateAndReject(rv, __func__);
+ }
+
+ if (!source->ExecutionReady()) {
+ RefPtr<ClientManagerService> self = this;
+
+ // rejection ultimately converted to `undefined` in Clients::Get
+ return source->ExecutionReadyPromise()->Then(
+ GetCurrentSerialEventTarget(), __func__,
+ [self = std::move(self), aArgs]() -> RefPtr<ClientOpPromise> {
+ ClientSourceParent* source =
+ self->FindExistingSource(aArgs.id(), aArgs.principalInfo());
+
+ if (!source) {
+ CopyableErrorResult rv;
+ rv.ThrowInvalidStateError("Unknown client");
+ return ClientOpPromise::CreateAndReject(rv, __func__);
+ }
+
+ return source->StartOp(aArgs);
+ });
+ }
+
+ return source->StartOp(aArgs);
+}
+
+RefPtr<ClientOpPromise> ClientManagerService::OpenWindow(
+ ThreadsafeContentParentHandle* aOriginContent,
+ const ClientOpenWindowArgs& aArgs) {
+ return InvokeAsync(GetMainThreadSerialEventTarget(), __func__,
+ [originContent = RefPtr{aOriginContent}, aArgs]() {
+ return ClientOpenWindow(originContent, aArgs);
+ });
+}
+
+bool ClientManagerService::HasWindow(
+ const Maybe<ContentParentId>& aContentParentId,
+ const PrincipalInfo& aPrincipalInfo, const nsID& aClientId) {
+ AssertIsOnBackgroundThread();
+
+ ClientSourceParent* source = FindExistingSource(aClientId, aPrincipalInfo);
+ if (!source) {
+ return false;
+ }
+
+ if (!source->ExecutionReady()) {
+ return false;
+ }
+
+ if (source->Info().Type() != ClientType::Window) {
+ return false;
+ }
+
+ if (aContentParentId && !source->IsOwnedByProcess(aContentParentId.value())) {
+ return false;
+ }
+
+ return true;
+}
+
+} // namespace mozilla::dom
diff --git a/dom/clients/manager/ClientManagerService.h b/dom/clients/manager/ClientManagerService.h
new file mode 100644
index 0000000000..5d8a4b73f6
--- /dev/null
+++ b/dom/clients/manager/ClientManagerService.h
@@ -0,0 +1,166 @@
+/* -*- 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/. */
+#ifndef _mozilla_dom_ClientManagerService_h
+#define _mozilla_dom_ClientManagerService_h
+
+#include "ClientHandleParent.h"
+#include "ClientOpPromise.h"
+#include "mozilla/AlreadyAddRefed.h"
+#include "mozilla/Assertions.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/MozPromise.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/Variant.h"
+#include "mozilla/dom/ClientIPCTypes.h"
+#include "mozilla/dom/ipc/IdType.h"
+#include "nsTHashMap.h"
+#include "nsHashKeys.h"
+#include "nsISupports.h"
+#include "nsTArray.h"
+
+struct nsID;
+
+namespace mozilla {
+
+namespace ipc {
+
+class PrincipalInfo;
+
+} // namespace ipc
+
+namespace dom {
+
+class ClientManagerParent;
+class ClientSourceParent;
+class ClientHandleParent;
+class ThreadsafeContentParentHandle;
+
+// Define a singleton service to manage client activity throughout the
+// browser. This service runs on the PBackground thread. To interact
+// it with it please use the ClientManager and ClientHandle classes.
+class ClientManagerService final {
+ // Placeholder type that represents a ClientSourceParent that may be created
+ // in the future (e.g. while a redirect chain is being resolved).
+ //
+ // Each FutureClientSourceParent has a promise that callbacks may be chained
+ // to; the promise will be resolved when the associated ClientSourceParent is
+ // created or rejected when it's known that it'll never be created.
+ class FutureClientSourceParent {
+ public:
+ explicit FutureClientSourceParent(const IPCClientInfo& aClientInfo);
+
+ const mozilla::ipc::PrincipalInfo& PrincipalInfo() const {
+ return mPrincipalInfo;
+ }
+
+ already_AddRefed<SourcePromise> Promise() {
+ return mPromiseHolder.Ensure(__func__);
+ }
+
+ void ResolvePromiseIfExists() {
+ mPromiseHolder.ResolveIfExists(true, __func__);
+ }
+
+ void RejectPromiseIfExists(const CopyableErrorResult& aRv) {
+ MOZ_ASSERT(aRv.Failed());
+ mPromiseHolder.RejectIfExists(aRv, __func__);
+ }
+
+ void SetAsAssociated() { mAssociated = true; }
+
+ bool IsAssociated() const { return mAssociated; }
+
+ private:
+ const mozilla::ipc::PrincipalInfo mPrincipalInfo;
+ MozPromiseHolder<SourcePromise> mPromiseHolder;
+ RefPtr<ClientManagerService> mService = ClientManagerService::GetInstance();
+ bool mAssociated;
+ };
+
+ using SourceTableEntry =
+ Variant<FutureClientSourceParent, ClientSourceParent*>;
+
+ // Store the possible ClientSourceParent objects in a hash table. We want to
+ // optimize for insertion, removal, and lookup by UUID.
+ nsTHashMap<nsIDHashKey, SourceTableEntry> mSourceTable;
+
+ nsTArray<ClientManagerParent*> mManagerList;
+
+ bool mShutdown;
+
+ ClientManagerService();
+ ~ClientManagerService();
+
+ void Shutdown();
+
+ // Returns nullptr if aEntry isn't a ClientSourceParent (i.e. it's a
+ // FutureClientSourceParent).
+ ClientSourceParent* MaybeUnwrapAsExistingSource(
+ const SourceTableEntry& aEntry) const;
+
+ public:
+ static already_AddRefed<ClientManagerService> GetOrCreateInstance();
+
+ // Returns nullptr if the service is not already created.
+ static already_AddRefed<ClientManagerService> GetInstance();
+
+ bool AddSource(ClientSourceParent* aSource);
+
+ bool RemoveSource(ClientSourceParent* aSource);
+
+ // Returns true when a FutureClientSourceParent is successfully added.
+ bool ExpectFutureSource(const IPCClientInfo& aClientInfo);
+
+ // May still be called if it's possible that the FutureClientSourceParent
+ // no longer exists.
+ void ForgetFutureSource(const IPCClientInfo& aClientInfo);
+
+ // Returns a promise that resolves if/when the ClientSourceParent exists and
+ // rejects if/when it's known that the ClientSourceParent will never exist or
+ // if it's frozen. Note that the ClientSourceParent may not exist anymore
+ // by the time promise callbacks run.
+ RefPtr<SourcePromise> FindSource(
+ const nsID& aID, const mozilla::ipc::PrincipalInfo& aPrincipalInfo);
+
+ // Returns nullptr if the ClientSourceParent doesn't exist yet (i.e. it's a
+ // FutureClientSourceParent or has already been destroyed) or is frozen.
+ ClientSourceParent* FindExistingSource(
+ const nsID& aID, const mozilla::ipc::PrincipalInfo& aPrincipalInfo) const;
+
+ void AddManager(ClientManagerParent* aManager);
+
+ void RemoveManager(ClientManagerParent* aManager);
+
+ RefPtr<ClientOpPromise> Navigate(
+ ThreadsafeContentParentHandle* aOriginContent,
+ const ClientNavigateArgs& aArgs);
+
+ RefPtr<ClientOpPromise> MatchAll(
+ ThreadsafeContentParentHandle* aOriginContent,
+ const ClientMatchAllArgs& aArgs);
+
+ RefPtr<ClientOpPromise> Claim(ThreadsafeContentParentHandle* aOriginContent,
+ const ClientClaimArgs& aArgs);
+
+ RefPtr<ClientOpPromise> GetInfoAndState(
+ ThreadsafeContentParentHandle* aOriginContent,
+ const ClientGetInfoAndStateArgs& aArgs);
+
+ RefPtr<ClientOpPromise> OpenWindow(
+ ThreadsafeContentParentHandle* aOriginContent,
+ const ClientOpenWindowArgs& aArgs);
+
+ bool HasWindow(const Maybe<ContentParentId>& aContentParentId,
+ const mozilla::ipc::PrincipalInfo& aPrincipalInfo,
+ const nsID& aClientId);
+
+ NS_INLINE_DECL_REFCOUNTING(mozilla::dom::ClientManagerService)
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // _mozilla_dom_ClientManagerService_h
diff --git a/dom/clients/manager/ClientNavigateOpChild.cpp b/dom/clients/manager/ClientNavigateOpChild.cpp
new file mode 100644
index 0000000000..d1d6c728c5
--- /dev/null
+++ b/dom/clients/manager/ClientNavigateOpChild.cpp
@@ -0,0 +1,334 @@
+/* -*- 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 "ClientNavigateOpChild.h"
+
+#include "ClientState.h"
+#include "ClientSource.h"
+#include "ClientSourceChild.h"
+#include "mozilla/dom/Document.h"
+#include "mozilla/Unused.h"
+#include "nsIDocShell.h"
+#include "nsDocShellLoadState.h"
+#include "nsIWebNavigation.h"
+#include "nsIWebProgress.h"
+#include "nsIWebProgressListener.h"
+#include "nsNetUtil.h"
+#include "nsPIDOMWindow.h"
+#include "nsURLHelper.h"
+#include "ReferrerInfo.h"
+
+namespace mozilla::dom {
+
+namespace {
+
+class NavigateLoadListener final : public nsIWebProgressListener,
+ public nsSupportsWeakReference {
+ RefPtr<ClientOpPromise::Private> mPromise;
+ RefPtr<nsPIDOMWindowOuter> mOuterWindow;
+ nsCOMPtr<nsIURI> mBaseURL;
+
+ ~NavigateLoadListener() = default;
+
+ public:
+ NavigateLoadListener(ClientOpPromise::Private* aPromise,
+ nsPIDOMWindowOuter* aOuterWindow, nsIURI* aBaseURL)
+ : mPromise(aPromise), mOuterWindow(aOuterWindow), mBaseURL(aBaseURL) {
+ MOZ_DIAGNOSTIC_ASSERT(mPromise);
+ MOZ_DIAGNOSTIC_ASSERT(mOuterWindow);
+ MOZ_DIAGNOSTIC_ASSERT(mBaseURL);
+ }
+
+ NS_IMETHOD
+ OnStateChange(nsIWebProgress* aWebProgress, nsIRequest* aRequest,
+ uint32_t aStateFlags, nsresult aResult) override {
+ if (!(aStateFlags & STATE_IS_DOCUMENT) ||
+ !(aStateFlags & (STATE_STOP | STATE_TRANSFERRING))) {
+ return NS_OK;
+ }
+
+ aWebProgress->RemoveProgressListener(this);
+
+ nsCOMPtr<nsIChannel> channel = do_QueryInterface(aRequest);
+ if (!channel) {
+ // This is not going to happen; how could it?
+ CopyableErrorResult result;
+ result.ThrowInvalidStateError("Bad request");
+ mPromise->Reject(result, __func__);
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIURI> channelURL;
+ nsresult rv = NS_GetFinalChannelURI(channel, getter_AddRefs(channelURL));
+ if (NS_FAILED(rv)) {
+ CopyableErrorResult result;
+ // XXXbz We can't actually get here; NS_GetFinalChannelURI never fails in
+ // practice!
+ result.Throw(rv);
+ mPromise->Reject(result, __func__);
+ return NS_OK;
+ }
+
+ nsIScriptSecurityManager* ssm = nsContentUtils::GetSecurityManager();
+ MOZ_DIAGNOSTIC_ASSERT(ssm);
+
+ // If the resulting window is not same origin, then resolve immediately
+ // without returning any information about the new Client. This is
+ // step 6.10 in the Client.navigate(url) spec.
+ // todo: if you intend to update CheckSameOriginURI to log the error to the
+ // console you also need to update the 'aFromPrivateWindow' argument.
+ rv = ssm->CheckSameOriginURI(mBaseURL, channelURL, false, false);
+ if (NS_FAILED(rv)) {
+ mPromise->Resolve(CopyableErrorResult(), __func__);
+ return NS_OK;
+ }
+
+ nsPIDOMWindowInner* innerWindow = mOuterWindow->GetCurrentInnerWindow();
+ MOZ_DIAGNOSTIC_ASSERT(innerWindow);
+
+ Maybe<ClientInfo> clientInfo = innerWindow->GetClientInfo();
+ MOZ_DIAGNOSTIC_ASSERT(clientInfo.isSome());
+
+ Maybe<ClientState> clientState = innerWindow->GetClientState();
+ MOZ_DIAGNOSTIC_ASSERT(clientState.isSome());
+
+ // Otherwise, if the new window is same-origin we want to return a
+ // ClientInfoAndState object so we can provide a Client snapshot
+ // to the caller. This is step 6.11 and 6.12 in the Client.navigate(url)
+ // spec.
+ mPromise->Resolve(
+ ClientInfoAndState(clientInfo.ref().ToIPC(), clientState.ref().ToIPC()),
+ __func__);
+
+ return NS_OK;
+ }
+
+ NS_IMETHOD
+ OnProgressChange(nsIWebProgress* aWebProgress, nsIRequest* aRequest,
+ int32_t aCurSelfProgress, int32_t aMaxSelfProgress,
+ int32_t aCurTotalProgress,
+ int32_t aMaxTotalProgress) override {
+ MOZ_CRASH("Unexpected notification.");
+ return NS_OK;
+ }
+
+ NS_IMETHOD
+ OnLocationChange(nsIWebProgress* aWebProgress, nsIRequest* aRequest,
+ nsIURI* aLocation, uint32_t aFlags) override {
+ MOZ_CRASH("Unexpected notification.");
+ return NS_OK;
+ }
+
+ NS_IMETHOD
+ OnStatusChange(nsIWebProgress* aWebProgress, nsIRequest* aRequest,
+ nsresult aStatus, const char16_t* aMessage) override {
+ MOZ_CRASH("Unexpected notification.");
+ return NS_OK;
+ }
+
+ NS_IMETHOD
+ OnSecurityChange(nsIWebProgress* aWebProgress, nsIRequest* aRequest,
+ uint32_t aState) override {
+ MOZ_CRASH("Unexpected notification.");
+ return NS_OK;
+ }
+
+ NS_IMETHOD
+ OnContentBlockingEvent(nsIWebProgress* aWebProgress, nsIRequest* aRequest,
+ uint32_t aEvent) override {
+ MOZ_CRASH("Unexpected notification.");
+ return NS_OK;
+ }
+
+ NS_DECL_ISUPPORTS
+};
+
+NS_IMPL_ISUPPORTS(NavigateLoadListener, nsIWebProgressListener,
+ nsISupportsWeakReference);
+
+} // anonymous namespace
+
+RefPtr<ClientOpPromise> ClientNavigateOpChild::DoNavigate(
+ const ClientNavigateOpConstructorArgs& aArgs) {
+ nsCOMPtr<nsPIDOMWindowInner> window;
+
+ // Navigating the target client window will result in the original
+ // ClientSource being destroyed. To avoid potential UAF mistakes
+ // we use a small scope to access the ClientSource object. Once
+ // we have a strong reference to the window object we should not
+ // access the ClientSource again.
+ {
+ ClientSourceChild* targetActor =
+ static_cast<ClientSourceChild*>(aArgs.targetChild());
+ MOZ_DIAGNOSTIC_ASSERT(targetActor);
+
+ ClientSource* target = targetActor->GetSource();
+ if (!target) {
+ CopyableErrorResult rv;
+ rv.ThrowInvalidStateError("Unknown Client");
+ return ClientOpPromise::CreateAndReject(rv, __func__);
+ }
+
+ window = target->GetInnerWindow();
+ if (!window) {
+ CopyableErrorResult rv;
+ rv.ThrowInvalidStateError("Client load for a destroyed Window");
+ return ClientOpPromise::CreateAndReject(rv, __func__);
+ }
+ }
+
+ MOZ_ASSERT(NS_IsMainThread());
+
+ mSerialEventTarget = window->EventTargetFor(TaskCategory::Other);
+
+ // In theory we could do the URL work before paying the IPC overhead
+ // cost, but in practice its easier to do it here. The ClientHandle
+ // may be off-main-thread while this method is guaranteed to always
+ // be main thread.
+ nsCOMPtr<nsIURI> baseURL;
+ nsresult rv = NS_NewURI(getter_AddRefs(baseURL), aArgs.baseURL());
+ if (NS_FAILED(rv)) {
+ // This is rather unexpected: This is the worker URL we passed from the
+ // parent, so we expect this to parse fine!
+ CopyableErrorResult result;
+ result.ThrowInvalidStateError("Invalid worker URL");
+ return ClientOpPromise::CreateAndReject(result, __func__);
+ }
+
+ // There is an edge case for view-source url here. According to the wpt test
+ // windowclient-navigate.https.html, a view-source URL with a relative inner
+ // URL should be treated as an invalid URL. However, we will still resolve it
+ // into a valid view-source URL since the baseURL is involved while creating
+ // the URI. So, an invalid view-source URL will be treated as a valid URL
+ // in this case. To address this, we should not take the baseURL into account
+ // for the view-source URL.
+ bool shouldUseBaseURL = true;
+ nsAutoCString scheme;
+ if (NS_SUCCEEDED(net_ExtractURLScheme(aArgs.url(), scheme)) &&
+ scheme.LowerCaseEqualsLiteral("view-source")) {
+ shouldUseBaseURL = false;
+ }
+
+ nsCOMPtr<nsIURI> url;
+ rv = NS_NewURI(getter_AddRefs(url), aArgs.url(), nullptr,
+ shouldUseBaseURL ? baseURL.get() : nullptr);
+ if (NS_FAILED(rv)) {
+ // Per https://w3c.github.io/ServiceWorker/#dom-windowclient-navigate step
+ // 2, if the URL fails to parse, we reject with a TypeError.
+ nsPrintfCString err("Invalid URL \"%s\"", aArgs.url().get());
+ CopyableErrorResult result;
+ result.ThrowTypeError(err);
+ return ClientOpPromise::CreateAndReject(result, __func__);
+ }
+
+ if (url->GetSpecOrDefault().EqualsLiteral("about:blank")) {
+ CopyableErrorResult result;
+ result.ThrowTypeError("Navigation to \"about:blank\" is not allowed");
+ return ClientOpPromise::CreateAndReject(result, __func__);
+ }
+
+ RefPtr<Document> doc = window->GetExtantDoc();
+ if (!doc || !doc->IsActive()) {
+ CopyableErrorResult result;
+ result.ThrowInvalidStateError("Document is not active.");
+ return ClientOpPromise::CreateAndReject(result, __func__);
+ }
+
+ nsCOMPtr<nsIPrincipal> principal = doc->NodePrincipal();
+
+ nsCOMPtr<nsIDocShell> docShell = window->GetDocShell();
+ nsCOMPtr<nsIWebProgress> webProgress = do_GetInterface(docShell);
+ if (!docShell || !webProgress) {
+ CopyableErrorResult result;
+ result.ThrowInvalidStateError(
+ "Document's browsing context has been discarded");
+ return ClientOpPromise::CreateAndReject(result, __func__);
+ }
+
+ RefPtr<nsDocShellLoadState> loadState = new nsDocShellLoadState(url);
+ loadState->SetTriggeringPrincipal(principal);
+ loadState->SetTriggeringSandboxFlags(doc->GetSandboxFlags());
+ loadState->SetCsp(doc->GetCsp());
+
+ auto referrerInfo = MakeRefPtr<ReferrerInfo>(*doc);
+ loadState->SetReferrerInfo(referrerInfo);
+ loadState->SetLoadType(LOAD_STOP_CONTENT);
+ loadState->SetSourceBrowsingContext(docShell->GetBrowsingContext());
+ loadState->SetLoadFlags(nsIWebNavigation::LOAD_FLAGS_NONE);
+ loadState->SetFirstParty(true);
+ loadState->SetHasValidUserGestureActivation(
+ doc->HasValidTransientUserGestureActivation());
+ rv = docShell->LoadURI(loadState, false);
+ if (NS_FAILED(rv)) {
+ /// There are tests that try sending file:/// and mixed-content URLs
+ /// in here and expect them to reject with a TypeError. This does not match
+ /// the spec, but does match the current behavior of both us and Chrome.
+ /// https://github.com/w3c/ServiceWorker/issues/1500 tracks sorting that
+ /// out.
+ /// We now run security checks asynchronously, so these tests now
+ /// just fail to load rather than hitting this failure path. I've
+ /// marked them as failing for now until they get fixed to match the
+ /// spec.
+ nsPrintfCString err("Invalid URL \"%s\"", aArgs.url().get());
+ CopyableErrorResult result;
+ result.ThrowTypeError(err);
+ return ClientOpPromise::CreateAndReject(result, __func__);
+ }
+
+ RefPtr<ClientOpPromise::Private> promise =
+ new ClientOpPromise::Private(__func__);
+
+ nsCOMPtr<nsIWebProgressListener> listener =
+ new NavigateLoadListener(promise, window->GetOuterWindow(), baseURL);
+
+ rv = webProgress->AddProgressListener(listener,
+ nsIWebProgress::NOTIFY_STATE_DOCUMENT);
+ if (NS_FAILED(rv)) {
+ CopyableErrorResult result;
+ // XXXbz Can we throw something better here?
+ result.Throw(rv);
+ promise->Reject(result, __func__);
+ return promise;
+ }
+
+ return promise->Then(
+ mSerialEventTarget, __func__,
+ [listener](const ClientOpPromise::ResolveOrRejectValue& aValue) {
+ return ClientOpPromise::CreateAndResolveOrReject(aValue, __func__);
+ });
+}
+
+void ClientNavigateOpChild::ActorDestroy(ActorDestroyReason aReason) {
+ mPromiseRequestHolder.DisconnectIfExists();
+}
+
+void ClientNavigateOpChild::Init(const ClientNavigateOpConstructorArgs& aArgs) {
+ RefPtr<ClientOpPromise> promise = DoNavigate(aArgs);
+
+ // Normally we get the event target from the window in DoNavigate(). If a
+ // failure occurred, though, we may need to fall back to the current thread
+ // target.
+ if (!mSerialEventTarget) {
+ mSerialEventTarget = GetCurrentSerialEventTarget();
+ }
+
+ // Capturing `this` is safe here since we clear the mPromiseRequestHolder in
+ // ActorDestroy.
+ promise
+ ->Then(
+ mSerialEventTarget, __func__,
+ [this](const ClientOpResult& aResult) {
+ mPromiseRequestHolder.Complete();
+ PClientNavigateOpChild::Send__delete__(this, aResult);
+ },
+ [this](const CopyableErrorResult& aResult) {
+ mPromiseRequestHolder.Complete();
+ PClientNavigateOpChild::Send__delete__(this, aResult);
+ })
+ ->Track(mPromiseRequestHolder);
+}
+
+} // namespace mozilla::dom
diff --git a/dom/clients/manager/ClientNavigateOpChild.h b/dom/clients/manager/ClientNavigateOpChild.h
new file mode 100644
index 0000000000..9f48748b9a
--- /dev/null
+++ b/dom/clients/manager/ClientNavigateOpChild.h
@@ -0,0 +1,33 @@
+/* -*- 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/. */
+#ifndef _mozilla_dom_ClientNavigateOpChild_h
+#define _mozilla_dom_ClientNavigateOpChild_h
+
+#include "mozilla/dom/PClientNavigateOpChild.h"
+#include "ClientOpPromise.h"
+
+namespace mozilla::dom {
+
+class ClientNavigateOpChild final : public PClientNavigateOpChild {
+ MozPromiseRequestHolder<ClientOpPromise> mPromiseRequestHolder;
+ nsCOMPtr<nsISerialEventTarget> mSerialEventTarget;
+
+ [[nodiscard]] RefPtr<ClientOpPromise> DoNavigate(
+ const ClientNavigateOpConstructorArgs& aArgs);
+
+ // PClientNavigateOpChild interface
+ void ActorDestroy(ActorDestroyReason aReason) override;
+
+ public:
+ ClientNavigateOpChild() = default;
+ ~ClientNavigateOpChild() = default;
+
+ void Init(const ClientNavigateOpConstructorArgs& aArgs);
+};
+
+} // namespace mozilla::dom
+
+#endif // _mozilla_dom_ClientNavigateOpChild_h
diff --git a/dom/clients/manager/ClientNavigateOpParent.cpp b/dom/clients/manager/ClientNavigateOpParent.cpp
new file mode 100644
index 0000000000..2c52d73480
--- /dev/null
+++ b/dom/clients/manager/ClientNavigateOpParent.cpp
@@ -0,0 +1,46 @@
+/* -*- 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 "ClientNavigateOpParent.h"
+
+namespace mozilla::dom {
+
+using mozilla::ipc::IPCResult;
+
+void ClientNavigateOpParent::ActorDestroy(ActorDestroyReason aReason) {
+ if (mPromise) {
+ CopyableErrorResult rv;
+ rv.ThrowAbortError("Client aborted");
+ mPromise->Reject(rv, __func__);
+ mPromise = nullptr;
+ }
+}
+
+IPCResult ClientNavigateOpParent::Recv__delete__(
+ const ClientOpResult& aResult) {
+ if (aResult.type() == ClientOpResult::TCopyableErrorResult &&
+ aResult.get_CopyableErrorResult().Failed()) {
+ mPromise->Reject(aResult.get_CopyableErrorResult(), __func__);
+ mPromise = nullptr;
+ return IPC_OK();
+ }
+ mPromise->Resolve(aResult, __func__);
+ mPromise = nullptr;
+ return IPC_OK();
+}
+
+ClientNavigateOpParent::ClientNavigateOpParent(
+ const ClientNavigateOpConstructorArgs& aArgs,
+ ClientOpPromise::Private* aPromise)
+ : mPromise(aPromise) {
+ MOZ_DIAGNOSTIC_ASSERT(mPromise);
+}
+
+ClientNavigateOpParent::~ClientNavigateOpParent() {
+ MOZ_DIAGNOSTIC_ASSERT(!mPromise);
+}
+
+} // namespace mozilla::dom
diff --git a/dom/clients/manager/ClientNavigateOpParent.h b/dom/clients/manager/ClientNavigateOpParent.h
new file mode 100644
index 0000000000..c1a226dcbc
--- /dev/null
+++ b/dom/clients/manager/ClientNavigateOpParent.h
@@ -0,0 +1,32 @@
+/* -*- 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/. */
+#ifndef _mozilla_dom_ClientNavigateOpParent_h
+#define _mozilla_dom_ClientNavigateOpParent_h
+
+#include "mozilla/dom/ClientOpPromise.h"
+#include "mozilla/dom/PClientNavigateOpParent.h"
+
+namespace mozilla::dom {
+
+class ClientNavigateOpParent final : public PClientNavigateOpParent {
+ RefPtr<ClientOpPromise::Private> mPromise;
+
+ // PClientNavigateOpParent interface
+ void ActorDestroy(ActorDestroyReason aReason) override;
+
+ mozilla::ipc::IPCResult Recv__delete__(
+ const ClientOpResult& aResult) override;
+
+ public:
+ ClientNavigateOpParent(const ClientNavigateOpConstructorArgs& aArgs,
+ ClientOpPromise::Private* aPromise);
+
+ ~ClientNavigateOpParent();
+};
+
+} // namespace mozilla::dom
+
+#endif // _mozilla_dom_ClientNavigateOpParent_h
diff --git a/dom/clients/manager/ClientOpPromise.h b/dom/clients/manager/ClientOpPromise.h
new file mode 100644
index 0000000000..b838e04d1f
--- /dev/null
+++ b/dom/clients/manager/ClientOpPromise.h
@@ -0,0 +1,31 @@
+/* -*- 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/. */
+#ifndef _mozilla_dom_ClientOpPromise_h
+#define _mozilla_dom_ClientOpPromise_h
+
+#include "mozilla/MozPromise.h"
+
+namespace mozilla {
+class CopyableErrorResult;
+
+namespace dom {
+
+class ClientOpResult;
+class ClientState;
+
+using ClientOpPromise = MozPromise<ClientOpResult, CopyableErrorResult, false>;
+
+using ClientStatePromise = MozPromise<ClientState, CopyableErrorResult, false>;
+
+using GenericErrorResultPromise =
+ MozPromise<bool, CopyableErrorResult, /* IsExclusive = */ true>;
+
+using ClientOpCallback = std::function<void(const ClientOpResult&)>;
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // _mozilla_dom_ClientOpPromise_h
diff --git a/dom/clients/manager/ClientOpenWindowUtils.cpp b/dom/clients/manager/ClientOpenWindowUtils.cpp
new file mode 100644
index 0000000000..4c6524a425
--- /dev/null
+++ b/dom/clients/manager/ClientOpenWindowUtils.cpp
@@ -0,0 +1,455 @@
+/* -*- 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 "ClientOpenWindowUtils.h"
+
+#include "ClientInfo.h"
+#include "ClientManager.h"
+#include "ClientState.h"
+#include "mozilla/ResultExtensions.h"
+#include "mozilla/dom/ContentParent.h"
+#include "nsContentUtils.h"
+#include "nsDocShell.h"
+#include "nsDocShellLoadState.h"
+#include "nsFocusManager.h"
+#include "nsIBrowserDOMWindow.h"
+#include "nsIDocShell.h"
+#include "nsIDOMChromeWindow.h"
+#include "nsIURI.h"
+#include "nsIBrowser.h"
+#include "nsIWebProgress.h"
+#include "nsIWebProgressListener.h"
+#include "nsIWindowWatcher.h"
+#include "nsIXPConnect.h"
+#include "nsNetUtil.h"
+#include "nsPIDOMWindow.h"
+#include "nsPIWindowWatcher.h"
+#include "nsPrintfCString.h"
+#include "nsWindowWatcher.h"
+#include "nsOpenWindowInfo.h"
+
+#include "mozilla/dom/BrowserParent.h"
+#include "mozilla/dom/BrowsingContext.h"
+#include "mozilla/dom/CanonicalBrowsingContext.h"
+#include "mozilla/dom/nsCSPContext.h"
+#include "mozilla/dom/WindowGlobalParent.h"
+
+#ifdef MOZ_WIDGET_ANDROID
+# include "mozilla/java/GeckoResultWrappers.h"
+# include "mozilla/java/GeckoRuntimeWrappers.h"
+#endif
+
+namespace mozilla::dom {
+
+namespace {
+
+class WebProgressListener final : public nsIWebProgressListener,
+ public nsSupportsWeakReference {
+ public:
+ NS_DECL_ISUPPORTS
+
+ WebProgressListener(BrowsingContext* aBrowsingContext, nsIURI* aBaseURI,
+ already_AddRefed<ClientOpPromise::Private> aPromise)
+ : mPromise(aPromise),
+ mBaseURI(aBaseURI),
+ mBrowserId(aBrowsingContext->GetBrowserId()) {
+ MOZ_ASSERT(mBrowserId != 0);
+ MOZ_ASSERT(aBaseURI);
+ MOZ_ASSERT(NS_IsMainThread());
+ }
+
+ NS_IMETHOD
+ OnStateChange(nsIWebProgress* aWebProgress, nsIRequest* aRequest,
+ uint32_t aStateFlags, nsresult aStatus) override {
+ if (!(aStateFlags & STATE_IS_WINDOW) ||
+ !(aStateFlags & (STATE_STOP | STATE_TRANSFERRING))) {
+ return NS_OK;
+ }
+
+ // Our browsing context may have been discarded before finishing the load,
+ // this is a navigation error.
+ RefPtr<CanonicalBrowsingContext> browsingContext =
+ CanonicalBrowsingContext::Cast(
+ BrowsingContext::GetCurrentTopByBrowserId(mBrowserId));
+ if (!browsingContext || browsingContext->IsDiscarded()) {
+ CopyableErrorResult rv;
+ rv.ThrowInvalidStateError("Unable to open window");
+ mPromise->Reject(rv, __func__);
+ mPromise = nullptr;
+ return NS_OK;
+ }
+
+ // Our caller keeps a strong reference, so it is safe to remove the listener
+ // from the BrowsingContext's nsIWebProgress.
+ auto RemoveListener = [&] {
+ nsCOMPtr<nsIWebProgress> webProgress = browsingContext->GetWebProgress();
+ webProgress->RemoveProgressListener(this);
+ };
+
+ RefPtr<dom::WindowGlobalParent> wgp =
+ browsingContext->GetCurrentWindowGlobal();
+ if (NS_WARN_IF(!wgp)) {
+ CopyableErrorResult rv;
+ rv.ThrowInvalidStateError("Unable to open window");
+ mPromise->Reject(rv, __func__);
+ mPromise = nullptr;
+ RemoveListener();
+ return NS_OK;
+ }
+
+ if (NS_WARN_IF(wgp->IsInitialDocument())) {
+ // This is the load of the initial document, which is not the document we
+ // care about for the purposes of checking same-originness of the URL.
+ return NS_OK;
+ }
+
+ RemoveListener();
+
+ // Check same origin. If the origins do not match, resolve with null (per
+ // step 7.2.7.1 of the openWindow spec).
+ nsCOMPtr<nsIScriptSecurityManager> securityManager =
+ nsContentUtils::GetSecurityManager();
+ bool isPrivateWin =
+ wgp->DocumentPrincipal()->OriginAttributesRef().mPrivateBrowsingId > 0;
+ nsresult rv = securityManager->CheckSameOriginURI(
+ wgp->GetDocumentURI(), mBaseURI, false, isPrivateWin);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ mPromise->Resolve(CopyableErrorResult(), __func__);
+ mPromise = nullptr;
+ return NS_OK;
+ }
+
+ Maybe<ClientInfo> info = wgp->GetClientInfo();
+ if (info.isNothing()) {
+ CopyableErrorResult rv;
+ rv.ThrowInvalidStateError("Unable to open window");
+ mPromise->Reject(rv, __func__);
+ mPromise = nullptr;
+ return NS_OK;
+ }
+
+ const nsID& id = info.ref().Id();
+ const mozilla::ipc::PrincipalInfo& principal = info.ref().PrincipalInfo();
+ ClientManager::GetInfoAndState(ClientGetInfoAndStateArgs(id, principal),
+ GetCurrentSerialEventTarget())
+ ->ChainTo(mPromise.forget(), __func__);
+
+ return NS_OK;
+ }
+
+ NS_IMETHOD
+ OnProgressChange(nsIWebProgress* aWebProgress, nsIRequest* aRequest,
+ int32_t aCurSelfProgress, int32_t aMaxSelfProgress,
+ int32_t aCurTotalProgress,
+ int32_t aMaxTotalProgress) override {
+ MOZ_ASSERT(false, "Unexpected notification.");
+ return NS_OK;
+ }
+
+ NS_IMETHOD
+ OnLocationChange(nsIWebProgress* aWebProgress, nsIRequest* aRequest,
+ nsIURI* aLocation, uint32_t aFlags) override {
+ MOZ_ASSERT(false, "Unexpected notification.");
+ return NS_OK;
+ }
+
+ NS_IMETHOD
+ OnStatusChange(nsIWebProgress* aWebProgress, nsIRequest* aRequest,
+ nsresult aStatus, const char16_t* aMessage) override {
+ MOZ_ASSERT(false, "Unexpected notification.");
+ return NS_OK;
+ }
+
+ NS_IMETHOD
+ OnSecurityChange(nsIWebProgress* aWebProgress, nsIRequest* aRequest,
+ uint32_t aState) override {
+ MOZ_ASSERT(false, "Unexpected notification.");
+ return NS_OK;
+ }
+
+ NS_IMETHOD
+ OnContentBlockingEvent(nsIWebProgress* aWebProgress, nsIRequest* aRequest,
+ uint32_t aEvent) override {
+ MOZ_ASSERT(false, "Unexpected notification.");
+ return NS_OK;
+ }
+
+ private:
+ ~WebProgressListener() {
+ if (mPromise) {
+ CopyableErrorResult rv;
+ rv.ThrowAbortError("openWindow aborted");
+ mPromise->Reject(rv, __func__);
+ mPromise = nullptr;
+ }
+ }
+
+ RefPtr<ClientOpPromise::Private> mPromise;
+ nsCOMPtr<nsIURI> mBaseURI;
+ uint64_t mBrowserId;
+};
+
+NS_IMPL_ISUPPORTS(WebProgressListener, nsIWebProgressListener,
+ nsISupportsWeakReference);
+
+struct ClientOpenWindowArgsParsed {
+ nsCOMPtr<nsIURI> uri;
+ nsCOMPtr<nsIURI> baseURI;
+ nsCOMPtr<nsIPrincipal> principal;
+ nsCOMPtr<nsIContentSecurityPolicy> csp;
+ RefPtr<ThreadsafeContentParentHandle> originContent;
+};
+
+void OpenWindow(const ClientOpenWindowArgsParsed& aArgsValidated,
+ nsOpenWindowInfo* aOpenInfo, BrowsingContext** aBC,
+ ErrorResult& aRv) {
+ MOZ_DIAGNOSTIC_ASSERT(aBC);
+
+ // [[6.1 Open Window]]
+
+ // Find the most recent browser window and open a new tab in it.
+ nsCOMPtr<nsPIDOMWindowOuter> browserWindow =
+ nsContentUtils::GetMostRecentNonPBWindow();
+ if (!browserWindow) {
+ // It is possible to be running without a browser window on Mac OS, so
+ // we need to open a new chrome window.
+ // TODO(catalinb): open new chrome window. Bug 1218080
+ aRv.ThrowTypeError("Unable to open window");
+ return;
+ }
+
+ nsCOMPtr<nsIDOMChromeWindow> chromeWin = do_QueryInterface(browserWindow);
+ if (NS_WARN_IF(!chromeWin)) {
+ // XXXbz Can this actually happen? Seems unlikely.
+ aRv.ThrowTypeError("Unable to open window");
+ return;
+ }
+
+ nsCOMPtr<nsIBrowserDOMWindow> bwin;
+ chromeWin->GetBrowserDOMWindow(getter_AddRefs(bwin));
+
+ if (NS_WARN_IF(!bwin)) {
+ aRv.ThrowTypeError("Unable to open window");
+ return;
+ }
+ nsresult rv = bwin->CreateContentWindow(
+ nullptr, aOpenInfo, nsIBrowserDOMWindow::OPEN_DEFAULTWINDOW,
+ nsIBrowserDOMWindow::OPEN_NEW, aArgsValidated.principal,
+ aArgsValidated.csp, aBC);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ aRv.ThrowTypeError("Unable to open window");
+ return;
+ }
+}
+
+void WaitForLoad(const ClientOpenWindowArgsParsed& aArgsValidated,
+ BrowsingContext* aBrowsingContext,
+ ClientOpPromise::Private* aPromise) {
+ MOZ_DIAGNOSTIC_ASSERT(aBrowsingContext);
+
+ RefPtr<ClientOpPromise::Private> promise = aPromise;
+ // We can get a WebProgress off of
+ // the BrowsingContext for the <xul:browser> to listen for content
+ // events. Note that this WebProgress filters out events which don't have
+ // STATE_IS_NETWORK or STATE_IS_REDIRECTED_DOCUMENT set on them, and so this
+ // listener will only see some web progress events.
+ nsCOMPtr<nsIWebProgress> webProgress =
+ aBrowsingContext->Canonical()->GetWebProgress();
+ if (NS_WARN_IF(!webProgress)) {
+ CopyableErrorResult result;
+ result.ThrowInvalidStateError("Unable to watch window for navigation");
+ promise->Reject(result, __func__);
+ return;
+ }
+
+ // Add a progress listener before we start the load of the service worker URI
+ RefPtr<WebProgressListener> listener = new WebProgressListener(
+ aBrowsingContext, aArgsValidated.baseURI, do_AddRef(promise));
+
+ nsresult rv = webProgress->AddProgressListener(
+ listener, nsIWebProgress::NOTIFY_STATE_WINDOW);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ CopyableErrorResult result;
+ // XXXbz Can we throw something better here?
+ result.Throw(rv);
+ promise->Reject(result, __func__);
+ return;
+ }
+
+ // Load the service worker URI
+ RefPtr<nsDocShellLoadState> loadState =
+ new nsDocShellLoadState(aArgsValidated.uri);
+ loadState->SetTriggeringPrincipal(aArgsValidated.principal);
+ loadState->SetFirstParty(true);
+ loadState->SetLoadFlags(
+ nsIWebNavigation::LOAD_FLAGS_DISALLOW_INHERIT_PRINCIPAL);
+ loadState->SetTriggeringRemoteType(
+ aArgsValidated.originContent
+ ? aArgsValidated.originContent->GetRemoteType()
+ : NOT_REMOTE_TYPE);
+
+ rv = aBrowsingContext->LoadURI(loadState, true);
+ if (NS_FAILED(rv)) {
+ CopyableErrorResult result;
+ result.ThrowInvalidStateError("Unable to start the load of the actual URI");
+ promise->Reject(result, __func__);
+ return;
+ }
+
+ // Hold the listener alive until the promise settles.
+ promise->Then(
+ GetMainThreadSerialEventTarget(), __func__,
+ [listener](const ClientOpResult& aResult) {},
+ [listener](const CopyableErrorResult& aResult) {});
+}
+
+#ifdef MOZ_WIDGET_ANDROID
+
+void GeckoViewOpenWindow(const ClientOpenWindowArgsParsed& aArgsValidated,
+ ClientOpPromise::Private* aPromise) {
+ RefPtr<ClientOpPromise::Private> promise = aPromise;
+
+ // passes the request to open a new window to GeckoView. Allowing the
+ // application to decide how to hand the open window request.
+ nsAutoCString uri;
+ MOZ_ALWAYS_SUCCEEDS(aArgsValidated.uri->GetSpec(uri));
+ auto genericResult = java::GeckoRuntime::ServiceWorkerOpenWindow(uri);
+ auto typedResult = java::GeckoResult::LocalRef(std::move(genericResult));
+
+ // MozPromise containing the ID for the handling GeckoSession
+ auto promiseResult =
+ mozilla::MozPromise<nsString, nsString, false>::FromGeckoResult(
+ typedResult);
+
+ promiseResult->Then(
+ GetMainThreadSerialEventTarget(), __func__,
+ [aArgsValidated, promise](nsString sessionId) {
+ nsresult rv;
+ nsCOMPtr<nsIWindowWatcher> wwatch =
+ do_GetService(NS_WINDOWWATCHER_CONTRACTID, &rv);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ promise->Reject(rv, __func__);
+ return rv;
+ }
+
+ // Retrieve the browsing context by using the GeckoSession ID. The
+ // window is named the same as the ID of the GeckoSession it is
+ // associated with.
+ RefPtr<BrowsingContext> browsingContext =
+ static_cast<nsWindowWatcher*>(wwatch.get())
+ ->GetBrowsingContextByName(sessionId, false, nullptr);
+ if (NS_WARN_IF(!browsingContext)) {
+ promise->Reject(NS_ERROR_FAILURE, __func__);
+ return NS_ERROR_FAILURE;
+ }
+
+ WaitForLoad(aArgsValidated, browsingContext, promise);
+ return NS_OK;
+ },
+ [promise](nsString aResult) {
+ promise->Reject(NS_ERROR_FAILURE, __func__);
+ });
+}
+
+#endif // MOZ_WIDGET_ANDROID
+
+} // anonymous namespace
+
+RefPtr<ClientOpPromise> ClientOpenWindow(
+ ThreadsafeContentParentHandle* aOriginContent,
+ const ClientOpenWindowArgs& aArgs) {
+ MOZ_DIAGNOSTIC_ASSERT(XRE_IsParentProcess());
+
+ RefPtr<ClientOpPromise::Private> promise =
+ new ClientOpPromise::Private(__func__);
+
+ // [[1. Let url be the result of parsing url with entry settings object's API
+ // base URL.]]
+ nsCOMPtr<nsIURI> baseURI;
+ nsresult rv = NS_NewURI(getter_AddRefs(baseURI), aArgs.baseURL());
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ nsPrintfCString err("Invalid base URL \"%s\"", aArgs.baseURL().get());
+ CopyableErrorResult errResult;
+ errResult.ThrowTypeError(err);
+ promise->Reject(errResult, __func__);
+ return promise;
+ }
+
+ nsCOMPtr<nsIURI> uri;
+ rv = NS_NewURI(getter_AddRefs(uri), aArgs.url(), nullptr, baseURI);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ nsPrintfCString err("Invalid URL \"%s\"", aArgs.url().get());
+ CopyableErrorResult errResult;
+ errResult.ThrowTypeError(err);
+ promise->Reject(errResult, __func__);
+ return promise;
+ }
+
+ auto principalOrErr = PrincipalInfoToPrincipal(aArgs.principalInfo());
+ if (NS_WARN_IF(principalOrErr.isErr())) {
+ CopyableErrorResult errResult;
+ errResult.ThrowTypeError("Failed to obtain principal");
+ promise->Reject(errResult, __func__);
+ return promise;
+ }
+ nsCOMPtr<nsIPrincipal> principal = principalOrErr.unwrap();
+ MOZ_DIAGNOSTIC_ASSERT(principal);
+
+ nsCOMPtr<nsIContentSecurityPolicy> csp;
+ if (aArgs.cspInfo().isSome()) {
+ csp = CSPInfoToCSP(aArgs.cspInfo().ref(), nullptr);
+ }
+ ClientOpenWindowArgsParsed argsValidated{
+ .uri = uri,
+ .baseURI = baseURI,
+ .principal = principal,
+ .csp = csp,
+ .originContent = aOriginContent,
+ };
+
+#ifdef MOZ_WIDGET_ANDROID
+ // If we are on Android we are GeckoView.
+ GeckoViewOpenWindow(argsValidated, promise);
+ return promise.forget();
+#endif // MOZ_WIDGET_ANDROID
+
+ RefPtr<BrowsingContextCallbackReceivedPromise::Private>
+ browsingContextReadyPromise =
+ new BrowsingContextCallbackReceivedPromise::Private(__func__);
+ RefPtr<nsIBrowsingContextReadyCallback> callback =
+ new nsBrowsingContextReadyCallback(browsingContextReadyPromise);
+
+ RefPtr<nsOpenWindowInfo> openInfo = new nsOpenWindowInfo();
+ openInfo->mBrowsingContextReadyCallback = callback;
+ openInfo->mOriginAttributes = principal->OriginAttributesRef();
+ openInfo->mIsRemote = true;
+
+ RefPtr<BrowsingContext> bc;
+ ErrorResult errResult;
+ OpenWindow(argsValidated, openInfo, getter_AddRefs(bc), errResult);
+ if (NS_WARN_IF(errResult.Failed())) {
+ promise->Reject(errResult, __func__);
+ return promise;
+ }
+
+ browsingContextReadyPromise->Then(
+ GetCurrentSerialEventTarget(), __func__,
+ [argsValidated, promise](const RefPtr<BrowsingContext>& aBC) {
+ WaitForLoad(argsValidated, aBC, promise);
+ },
+ [promise]() {
+ // in case of failure, reject the original promise
+ CopyableErrorResult result;
+ result.ThrowTypeError("Unable to open window");
+ promise->Reject(result, __func__);
+ });
+ if (bc) {
+ browsingContextReadyPromise->Resolve(bc, __func__);
+ }
+ return promise;
+}
+
+} // namespace mozilla::dom
diff --git a/dom/clients/manager/ClientOpenWindowUtils.h b/dom/clients/manager/ClientOpenWindowUtils.h
new file mode 100644
index 0000000000..7f404f1311
--- /dev/null
+++ b/dom/clients/manager/ClientOpenWindowUtils.h
@@ -0,0 +1,25 @@
+/* -*- 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/. */
+#ifndef _mozilla_dom_ClientOpenWindowUtils_h
+#define _mozilla_dom_ClientOpenWindowUtils_h
+
+#include "ClientOpPromise.h"
+#include "mozilla/dom/ClientIPCTypes.h"
+
+namespace mozilla::dom {
+
+class ThreadsafeContentParentHandle;
+
+using BrowsingContextCallbackReceivedPromise =
+ MozPromise<RefPtr<BrowsingContext>, CopyableErrorResult, false>;
+
+[[nodiscard]] RefPtr<ClientOpPromise> ClientOpenWindow(
+ ThreadsafeContentParentHandle* aOriginContent,
+ const ClientOpenWindowArgs& aArgs);
+
+} // namespace mozilla::dom
+
+#endif // _mozilla_dom_ClientOpenWindowUtils_h
diff --git a/dom/clients/manager/ClientPrincipalUtils.cpp b/dom/clients/manager/ClientPrincipalUtils.cpp
new file mode 100644
index 0000000000..25a25b2ee9
--- /dev/null
+++ b/dom/clients/manager/ClientPrincipalUtils.cpp
@@ -0,0 +1,48 @@
+/* -*- 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 "ClientPrincipalUtils.h"
+
+#include "mozilla/ipc/PBackgroundSharedTypes.h"
+
+namespace mozilla::dom {
+
+using mozilla::ipc::ContentPrincipalInfo;
+using mozilla::ipc::PrincipalInfo;
+
+bool ClientMatchPrincipalInfo(const PrincipalInfo& aLeft,
+ const PrincipalInfo& aRight) {
+ if (aLeft.type() != aRight.type()) {
+ return false;
+ }
+
+ switch (aLeft.type()) {
+ case PrincipalInfo::TContentPrincipalInfo: {
+ const ContentPrincipalInfo& leftContent =
+ aLeft.get_ContentPrincipalInfo();
+ const ContentPrincipalInfo& rightContent =
+ aRight.get_ContentPrincipalInfo();
+ return leftContent.attrs() == rightContent.attrs() &&
+ leftContent.originNoSuffix() == rightContent.originNoSuffix();
+ }
+ case PrincipalInfo::TSystemPrincipalInfo: {
+ // system principal always matches
+ return true;
+ }
+ case PrincipalInfo::TNullPrincipalInfo: {
+ // null principal never matches
+ return false;
+ }
+ default: {
+ break;
+ }
+ }
+
+ // Clients (windows/workers) should never have an expanded principal type.
+ MOZ_CRASH("unexpected principal type!");
+}
+
+} // namespace mozilla::dom
diff --git a/dom/clients/manager/ClientPrincipalUtils.h b/dom/clients/manager/ClientPrincipalUtils.h
new file mode 100644
index 0000000000..2f8aa92123
--- /dev/null
+++ b/dom/clients/manager/ClientPrincipalUtils.h
@@ -0,0 +1,23 @@
+/* -*- 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/. */
+#ifndef _mozilla_dom_ClientPrincipaltils_h
+#define _mozilla_dom_ClientPrincipaltils_h
+
+namespace mozilla {
+
+namespace ipc {
+class PrincipalInfo;
+} // namespace ipc
+
+namespace dom {
+
+bool ClientMatchPrincipalInfo(const mozilla::ipc::PrincipalInfo& aLeft,
+ const mozilla::ipc::PrincipalInfo& aRight);
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // _mozilla_dom_ClientPrincipalUtils_h
diff --git a/dom/clients/manager/ClientSource.cpp b/dom/clients/manager/ClientSource.cpp
new file mode 100644
index 0000000000..871de5af23
--- /dev/null
+++ b/dom/clients/manager/ClientSource.cpp
@@ -0,0 +1,710 @@
+/* -*- 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 "ClientSource.h"
+
+#include "ClientManager.h"
+#include "ClientManagerChild.h"
+#include "ClientPrincipalUtils.h"
+#include "ClientSourceChild.h"
+#include "ClientState.h"
+#include "ClientValidation.h"
+#include "mozilla/dom/BlobURLProtocolHandler.h"
+#include "mozilla/dom/ClientIPCTypes.h"
+#include "mozilla/dom/DOMMozPromiseRequestHolder.h"
+#include "mozilla/dom/ipc/StructuredCloneData.h"
+#include "mozilla/dom/JSExecutionManager.h"
+#include "mozilla/dom/MessageEvent.h"
+#include "mozilla/dom/MessageEventBinding.h"
+#include "mozilla/dom/Navigator.h"
+#include "mozilla/dom/WorkerScope.h"
+#include "mozilla/dom/WorkerRef.h"
+#include "mozilla/dom/ServiceWorker.h"
+#include "mozilla/dom/ServiceWorkerContainer.h"
+#include "mozilla/dom/ServiceWorkerManager.h"
+#include "mozilla/SchedulerGroup.h"
+#include "mozilla/StaticPrefs_privacy.h"
+#include "mozilla/StorageAccess.h"
+#include "nsIContentSecurityPolicy.h"
+#include "nsContentUtils.h"
+#include "nsFocusManager.h"
+#include "nsIDocShell.h"
+#include "nsPIDOMWindow.h"
+
+#include "mozilla/ipc/BackgroundUtils.h"
+
+namespace mozilla::dom {
+
+using mozilla::dom::ipc::StructuredCloneData;
+using mozilla::ipc::CSPInfo;
+using mozilla::ipc::CSPToCSPInfo;
+using mozilla::ipc::PrincipalInfo;
+using mozilla::ipc::PrincipalInfoToPrincipal;
+
+void ClientSource::Shutdown() {
+ NS_ASSERT_OWNINGTHREAD(ClientSource);
+ if (IsShutdown()) {
+ return;
+ }
+
+ ShutdownThing();
+
+ mManager = nullptr;
+}
+
+void ClientSource::ExecutionReady(const ClientSourceExecutionReadyArgs& aArgs) {
+ // Fast fail if we don't understand this particular principal/URL combination.
+ // This can happen since we use MozURL for validation which does not handle
+ // some of the more obscure internal principal/url combinations. Normal
+ // content pages will pass this check.
+ if (NS_WARN_IF(!ClientIsValidCreationURL(mClientInfo.PrincipalInfo(),
+ aArgs.url()))) {
+ Shutdown();
+ return;
+ }
+
+ mClientInfo.SetURL(aArgs.url());
+ mClientInfo.SetFrameType(aArgs.frameType());
+ MaybeExecute([aArgs](PClientSourceChild* aActor) {
+ aActor->SendExecutionReady(aArgs);
+ });
+}
+
+Result<ClientState, ErrorResult> ClientSource::SnapshotWindowState() {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ nsPIDOMWindowInner* window = GetInnerWindow();
+ if (!window || !window->IsCurrentInnerWindow() ||
+ !window->HasActiveDocument()) {
+ return ClientState(ClientWindowState(VisibilityState::Hidden, TimeStamp(),
+ StorageAccess::eDeny, false));
+ }
+
+ Document* doc = window->GetExtantDoc();
+ ErrorResult rv;
+ if (NS_WARN_IF(!doc)) {
+ rv.ThrowInvalidStateError("Document not active");
+ return Err(std::move(rv));
+ }
+
+ bool focused = doc->HasFocus(rv);
+ if (NS_WARN_IF(rv.Failed())) {
+ return Err(std::move(rv));
+ }
+
+ StorageAccess storage = StorageAllowedForDocument(doc);
+
+ return ClientState(ClientWindowState(doc->VisibilityState(),
+ doc->LastFocusTime(), storage, focused));
+}
+
+WorkerPrivate* ClientSource::GetWorkerPrivate() const {
+ NS_ASSERT_OWNINGTHREAD(ClientSource);
+ if (!mOwner.is<WorkerPrivate*>()) {
+ return nullptr;
+ }
+ return mOwner.as<WorkerPrivate*>();
+}
+
+nsIDocShell* ClientSource::GetDocShell() const {
+ NS_ASSERT_OWNINGTHREAD(ClientSource);
+ if (!mOwner.is<nsCOMPtr<nsIDocShell>>()) {
+ return nullptr;
+ }
+ return mOwner.as<nsCOMPtr<nsIDocShell>>();
+}
+
+nsIGlobalObject* ClientSource::GetGlobal() const {
+ NS_ASSERT_OWNINGTHREAD(ClientSource);
+ nsPIDOMWindowInner* win = GetInnerWindow();
+ if (win) {
+ return win->AsGlobal();
+ }
+
+ WorkerPrivate* wp = GetWorkerPrivate();
+ if (wp) {
+ return wp->GlobalScope();
+ }
+
+ // Note, ClientSource objects attached to docshell for conceptual
+ // initial about:blank will get nullptr here. The caller should
+ // use MaybeCreateIntitialDocument() to create the window before
+ // GetGlobal() if it wants this before.
+
+ return nullptr;
+}
+
+// We want to be explicit about possible invalid states and
+// return them as errors.
+Result<bool, ErrorResult> ClientSource::MaybeCreateInitialDocument() {
+ // If there is not even a docshell, we do not expect to have a document
+ nsIDocShell* docshell = GetDocShell();
+ if (!docshell) {
+ return false;
+ }
+
+ // Force the creation of the initial document if it does not yet exist.
+ if (!docshell->GetDocument()) {
+ ErrorResult rv;
+ rv.ThrowInvalidStateError("No document available.");
+ return Err(std::move(rv));
+ }
+
+ return true;
+}
+
+ClientSource::ClientSource(ClientManager* aManager,
+ nsISerialEventTarget* aEventTarget,
+ const ClientSourceConstructorArgs& aArgs)
+ : mManager(aManager),
+ mEventTarget(aEventTarget),
+ mOwner(AsVariant(Nothing())),
+ mClientInfo(aArgs.id(), aArgs.type(), aArgs.principalInfo(),
+ aArgs.creationTime()) {
+ MOZ_ASSERT(mManager);
+ MOZ_ASSERT(mEventTarget);
+}
+
+void ClientSource::Activate(PClientManagerChild* aActor) {
+ NS_ASSERT_OWNINGTHREAD(ClientSource);
+ MOZ_ASSERT(!GetActor());
+
+ if (IsShutdown()) {
+ return;
+ }
+
+ // Fast fail if we don't understand this particular kind of PrincipalInfo.
+ // This can happen since we use MozURL for validation which does not handle
+ // some of the more obscure internal principal/url combinations. Normal
+ // content pages will pass this check.
+ if (NS_WARN_IF(!ClientIsValidPrincipalInfo(mClientInfo.PrincipalInfo()))) {
+ Shutdown();
+ return;
+ }
+
+ ClientSourceConstructorArgs args(mClientInfo.Id(), mClientInfo.Type(),
+ mClientInfo.PrincipalInfo(),
+ mClientInfo.CreationTime());
+ PClientSourceChild* actor = aActor->SendPClientSourceConstructor(args);
+ if (!actor) {
+ Shutdown();
+ return;
+ }
+
+ ActivateThing(static_cast<ClientSourceChild*>(actor));
+}
+
+ClientSource::~ClientSource() { Shutdown(); }
+
+nsPIDOMWindowInner* ClientSource::GetInnerWindow() const {
+ NS_ASSERT_OWNINGTHREAD(ClientSource);
+ if (!mOwner.is<RefPtr<nsPIDOMWindowInner>>()) {
+ return nullptr;
+ }
+ return mOwner.as<RefPtr<nsPIDOMWindowInner>>();
+}
+
+void ClientSource::WorkerExecutionReady(WorkerPrivate* aWorkerPrivate) {
+ MOZ_DIAGNOSTIC_ASSERT(aWorkerPrivate);
+ aWorkerPrivate->AssertIsOnWorkerThread();
+
+ if (IsShutdown()) {
+ return;
+ }
+
+ // A client without access to storage should never be controlled by
+ // a service worker. Check this here in case we were controlled before
+ // execution ready. We can't reliably determine what our storage policy
+ // is before execution ready, unfortunately.
+ if (mController.isSome()) {
+ MOZ_DIAGNOSTIC_ASSERT(
+ aWorkerPrivate->StorageAccess() > StorageAccess::ePrivateBrowsing ||
+ (ShouldPartitionStorage(aWorkerPrivate->StorageAccess()) &&
+ StoragePartitioningEnabled(aWorkerPrivate->StorageAccess(),
+ aWorkerPrivate->CookieJarSettings())) ||
+ StringBeginsWith(aWorkerPrivate->ScriptURL(), u"blob:"_ns));
+ }
+
+ // Its safe to store the WorkerPrivate* here because the ClientSource
+ // is explicitly destroyed by WorkerPrivate before exiting its run loop.
+ MOZ_DIAGNOSTIC_ASSERT(mOwner.is<Nothing>());
+ mOwner = AsVariant(aWorkerPrivate);
+
+ ClientSourceExecutionReadyArgs args(aWorkerPrivate->GetLocationInfo().mHref,
+ FrameType::None);
+
+ ExecutionReady(args);
+}
+
+nsresult ClientSource::WindowExecutionReady(nsPIDOMWindowInner* aInnerWindow) {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_DIAGNOSTIC_ASSERT(aInnerWindow);
+ MOZ_ASSERT(aInnerWindow->IsCurrentInnerWindow());
+ MOZ_ASSERT(aInnerWindow->HasActiveDocument());
+
+ if (IsShutdown()) {
+ return NS_OK;
+ }
+
+ Document* doc = aInnerWindow->GetExtantDoc();
+ NS_ENSURE_TRUE(doc, NS_ERROR_UNEXPECTED);
+
+ nsIURI* uri = doc->GetOriginalURI();
+ NS_ENSURE_TRUE(uri, NS_ERROR_UNEXPECTED);
+
+ // Don't use nsAutoCString here since IPC requires a full nsCString anyway.
+ nsCString spec;
+ nsresult rv = uri->GetSpec(spec);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // A client without access to storage should never be controlled by
+ // a service worker. Check this here in case we were controlled before
+ // execution ready. We can't reliably determine what our storage policy
+ // is before execution ready, unfortunately.
+ //
+ // Note, explicitly avoid checking storage policy for windows that inherit
+ // service workers from their parent. If a user opens a controlled window
+ // and then blocks storage, that window will continue to be controlled by
+ // the SW until the window is closed. Any about:blank or blob URL should
+ // continue to inherit the SW as well. We need to avoid triggering the
+ // assertion in this corner case.
+#ifdef DEBUG
+ if (mController.isSome()) {
+ auto storageAccess = StorageAllowedForWindow(aInnerWindow);
+ bool isAboutBlankURL = spec.LowerCaseEqualsLiteral("about:blank");
+ bool isBlobURL = StringBeginsWith(spec, "blob:"_ns);
+ bool isStorageAllowed = storageAccess == StorageAccess::eAllow;
+ bool isPartitionEnabled =
+ StoragePartitioningEnabled(storageAccess, doc->CookieJarSettings());
+ MOZ_ASSERT(isAboutBlankURL || isBlobURL || isStorageAllowed ||
+ (StaticPrefs::privacy_partition_serviceWorkers() &&
+ isPartitionEnabled));
+ }
+#endif
+
+ nsPIDOMWindowOuter* outer = aInnerWindow->GetOuterWindow();
+ NS_ENSURE_TRUE(outer, NS_ERROR_UNEXPECTED);
+
+ FrameType frameType = FrameType::Top_level;
+ if (!outer->GetBrowsingContext()->IsTop()) {
+ frameType = FrameType::Nested;
+ } else if (outer->GetBrowsingContext()->HadOriginalOpener()) {
+ frameType = FrameType::Auxiliary;
+ }
+
+ // We should either be setting a window execution ready for the
+ // first time or setting the same window execution ready again.
+ // The secondary calls are due to initial about:blank replacement.
+ MOZ_DIAGNOSTIC_ASSERT(mOwner.is<Nothing>() ||
+ mOwner.is<nsCOMPtr<nsIDocShell>>() ||
+ GetInnerWindow() == aInnerWindow);
+
+ // This creates a cycle with the window. It is broken when
+ // nsGlobalWindow::FreeInnerObjects() deletes the ClientSource.
+ mOwner = AsVariant(RefPtr<nsPIDOMWindowInner>(aInnerWindow));
+
+ ClientSourceExecutionReadyArgs args(spec, frameType);
+ ExecutionReady(args);
+
+ return NS_OK;
+}
+
+nsresult ClientSource::DocShellExecutionReady(nsIDocShell* aDocShell) {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_DIAGNOSTIC_ASSERT(aDocShell);
+
+ if (IsShutdown()) {
+ return NS_OK;
+ }
+
+ nsPIDOMWindowOuter* outer = aDocShell->GetWindow();
+ if (NS_WARN_IF(!outer)) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ // Note: We don't assert storage access for a controlled client. If
+ // the about:blank actually gets used then WindowExecutionReady() will
+ // get called which asserts storage access.
+
+ // TODO: dedupe this with WindowExecutionReady
+ FrameType frameType = FrameType::Top_level;
+ if (!outer->GetBrowsingContext()->IsTop()) {
+ frameType = FrameType::Nested;
+ } else if (outer->GetBrowsingContext()->HadOriginalOpener()) {
+ frameType = FrameType::Auxiliary;
+ }
+
+ MOZ_DIAGNOSTIC_ASSERT(mOwner.is<Nothing>());
+
+ // This creates a cycle with the docshell. It is broken when
+ // nsDocShell::Destroy() deletes the ClientSource.
+ mOwner = AsVariant(nsCOMPtr<nsIDocShell>(aDocShell));
+
+ ClientSourceExecutionReadyArgs args("about:blank"_ns, frameType);
+ ExecutionReady(args);
+
+ return NS_OK;
+}
+
+void ClientSource::Freeze() {
+ MaybeExecute([](PClientSourceChild* aActor) { aActor->SendFreeze(); });
+}
+
+void ClientSource::Thaw() {
+ MaybeExecute([](PClientSourceChild* aActor) { aActor->SendThaw(); });
+}
+
+void ClientSource::EvictFromBFCache() {
+ if (nsCOMPtr<nsPIDOMWindowInner> win = GetInnerWindow()) {
+ win->RemoveFromBFCacheSync();
+ } else if (WorkerPrivate* vp = GetWorkerPrivate()) {
+ vp->EvictFromBFCache();
+ }
+}
+
+RefPtr<ClientOpPromise> ClientSource::EvictFromBFCacheOp() {
+ EvictFromBFCache();
+ return ClientOpPromise::CreateAndResolve(CopyableErrorResult(), __func__);
+}
+
+const ClientInfo& ClientSource::Info() const { return mClientInfo; }
+
+void ClientSource::WorkerSyncPing(WorkerPrivate* aWorkerPrivate) {
+ NS_ASSERT_OWNINGTHREAD(ClientSource);
+ MOZ_DIAGNOSTIC_ASSERT(aWorkerPrivate);
+
+ if (IsShutdown()) {
+ return;
+ }
+
+ // We need to make sure the mainthread is unblocked.
+ AutoYieldJSThreadExecution yield;
+
+ MOZ_DIAGNOSTIC_ASSERT(aWorkerPrivate == mManager->GetWorkerPrivate());
+ aWorkerPrivate->AssertIsOnWorkerThread();
+ MOZ_DIAGNOSTIC_ASSERT(GetActor());
+
+ GetActor()->SendWorkerSyncPing();
+}
+
+void ClientSource::SetController(
+ const ServiceWorkerDescriptor& aServiceWorker) {
+ NS_ASSERT_OWNINGTHREAD(ClientSource);
+
+ // We should never have a cross-origin controller. Since this would be
+ // same-origin policy violation we do a full release assertion here.
+ MOZ_RELEASE_ASSERT(ClientMatchPrincipalInfo(mClientInfo.PrincipalInfo(),
+ aServiceWorker.PrincipalInfo()));
+
+ // A client in private browsing mode should never be controlled by
+ // a service worker. The principal origin attributes should guarantee
+ // this invariant.
+ MOZ_DIAGNOSTIC_ASSERT(!mClientInfo.IsPrivateBrowsing());
+
+ // A client without access to storage should never be controlled a
+ // a service worker. If we are already execution ready with a real
+ // window or worker, then verify assert the storage policy is correct.
+ //
+ // Note, explicitly avoid checking storage policy for clients that inherit
+ // service workers from their parent. This basically means blob: URLs
+ // and about:blank windows.
+#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
+ if (GetInnerWindow()) {
+ auto storageAccess = StorageAllowedForWindow(GetInnerWindow());
+ bool IsAboutBlankURL = Info().URL().LowerCaseEqualsLiteral("about:blank");
+ bool IsBlobURL = StringBeginsWith(Info().URL(), "blob:"_ns);
+ bool IsStorageAllowed = storageAccess == StorageAccess::eAllow;
+ bool IsPartitionEnabled =
+ GetInnerWindow()->GetExtantDoc()
+ ? StoragePartitioningEnabled(
+ storageAccess,
+ GetInnerWindow()->GetExtantDoc()->CookieJarSettings())
+ : false;
+ MOZ_DIAGNOSTIC_ASSERT(IsAboutBlankURL || IsBlobURL || IsStorageAllowed ||
+ (StaticPrefs::privacy_partition_serviceWorkers() &&
+ IsPartitionEnabled));
+ } else if (GetWorkerPrivate()) {
+ MOZ_DIAGNOSTIC_ASSERT(
+ GetWorkerPrivate()->StorageAccess() > StorageAccess::ePrivateBrowsing ||
+ StringBeginsWith(GetWorkerPrivate()->ScriptURL(), u"blob:"_ns));
+ }
+#endif
+
+ if (mController.isSome() && mController.ref() == aServiceWorker) {
+ return;
+ }
+
+ mController.reset();
+ mController.emplace(aServiceWorker);
+
+ RefPtr<ServiceWorkerContainer> swc;
+ nsPIDOMWindowInner* window = GetInnerWindow();
+ if (window) {
+ swc = window->Navigator()->ServiceWorker();
+ }
+
+ // TODO: Also self.navigator.serviceWorker on workers when its exposed there
+
+ if (swc && nsContentUtils::IsSafeToRunScript()) {
+ swc->ControllerChanged(IgnoreErrors());
+ }
+}
+
+RefPtr<ClientOpPromise> ClientSource::Control(
+ const ClientControlledArgs& aArgs) {
+ NS_ASSERT_OWNINGTHREAD(ClientSource);
+
+ // Determine if the client is allowed to be controlled. Currently we
+ // prevent service workers from controlling clients that cannot access
+ // storage. We exempt this restriction for local URL clients, like
+ // about:blank and blob:, since access to service workers is dictated by their
+ // parent.
+ //
+ // Note, we default to allowing the client to be controlled in the case
+ // where we are not execution ready yet. This can only happen if the
+ // the non-subresource load is intercepted by a service worker. Since
+ // ServiceWorkerInterceptController() uses StorageAllowedForChannel()
+ // it should be fine to accept these control messages.
+ //
+ // Its also fine to default to allowing ClientSource attached to a docshell
+ // to be controlled. These clients represent inital about:blank windows
+ // that do not have an inner window created yet. We explicitly allow initial
+ // about:blank.
+ bool controlAllowed = true;
+ if (GetInnerWindow()) {
+ // Local URL windows and windows with access to storage can be controlled.
+ auto storageAccess = StorageAllowedForWindow(GetInnerWindow());
+ bool isAboutBlankURL = Info().URL().LowerCaseEqualsLiteral("about:blank");
+ bool isBlobURL = StringBeginsWith(Info().URL(), "blob:"_ns);
+ bool isStorageAllowed = storageAccess == StorageAccess::eAllow;
+ bool isPartitionEnabled =
+ GetInnerWindow()->GetExtantDoc()
+ ? StoragePartitioningEnabled(
+ storageAccess,
+ GetInnerWindow()->GetExtantDoc()->CookieJarSettings())
+ : false;
+ controlAllowed =
+ isAboutBlankURL || isBlobURL || isStorageAllowed ||
+ (StaticPrefs::privacy_partition_serviceWorkers() && isPartitionEnabled);
+ } else if (GetWorkerPrivate()) {
+ // Local URL workers and workers with access to storage cna be controlled.
+ controlAllowed =
+ GetWorkerPrivate()->StorageAccess() > StorageAccess::ePrivateBrowsing ||
+ StringBeginsWith(GetWorkerPrivate()->ScriptURL(), u"blob:"_ns);
+ }
+
+ if (NS_WARN_IF(!controlAllowed)) {
+ CopyableErrorResult rv;
+ rv.ThrowInvalidStateError("Client cannot be controlled");
+ return ClientOpPromise::CreateAndReject(rv, __func__);
+ }
+
+ SetController(ServiceWorkerDescriptor(aArgs.serviceWorker()));
+
+ return ClientOpPromise::CreateAndResolve(CopyableErrorResult(), __func__);
+}
+
+void ClientSource::InheritController(
+ const ServiceWorkerDescriptor& aServiceWorker) {
+ NS_ASSERT_OWNINGTHREAD(ClientSource);
+
+ // Tell the parent-side ClientManagerService that the controller was
+ // inherited. This is necessary for clients.matchAll() to work properly.
+ // In parent-side intercept mode this will also note the inheritance in
+ // the parent-side SWM.
+ MaybeExecute([aServiceWorker](PClientSourceChild* aActor) {
+ aActor->SendInheritController(ClientControlledArgs(aServiceWorker.ToIPC()));
+ });
+
+ // Finally, record the new controller in our local ClientSource for any
+ // immediate synchronous access.
+ SetController(aServiceWorker);
+}
+
+const Maybe<ServiceWorkerDescriptor>& ClientSource::GetController() const {
+ return mController;
+}
+
+void ClientSource::NoteDOMContentLoaded() {
+ MaybeExecute(
+ [](PClientSourceChild* aActor) { aActor->SendNoteDOMContentLoaded(); });
+}
+
+RefPtr<ClientOpPromise> ClientSource::Focus(const ClientFocusArgs& aArgs) {
+ NS_ASSERT_OWNINGTHREAD(ClientSource);
+
+ if (mClientInfo.Type() != ClientType::Window) {
+ CopyableErrorResult rv;
+ rv.ThrowNotSupportedError("Not a Window client");
+ return ClientOpPromise::CreateAndReject(rv, __func__);
+ }
+ nsCOMPtr<nsPIDOMWindowOuter> outer;
+ nsPIDOMWindowInner* inner = GetInnerWindow();
+ if (inner) {
+ outer = inner->GetOuterWindow();
+ } else {
+ nsIDocShell* docshell = GetDocShell();
+ if (docshell) {
+ outer = docshell->GetWindow();
+ }
+ }
+
+ if (!outer) {
+ CopyableErrorResult rv;
+ rv.ThrowInvalidStateError("Browsing context discarded");
+ return ClientOpPromise::CreateAndReject(rv, __func__);
+ }
+
+ MOZ_ASSERT(NS_IsMainThread());
+ nsFocusManager::FocusWindow(outer, aArgs.callerType());
+
+ Result<ClientState, ErrorResult> state = SnapshotState();
+ if (state.isErr()) {
+ return ClientOpPromise::CreateAndReject(
+ CopyableErrorResult(state.unwrapErr()), __func__);
+ }
+
+ return ClientOpPromise::CreateAndResolve(state.inspect().ToIPC(), __func__);
+}
+
+RefPtr<ClientOpPromise> ClientSource::PostMessage(
+ const ClientPostMessageArgs& aArgs) {
+ NS_ASSERT_OWNINGTHREAD(ClientSource);
+
+ // TODO: Currently this function only supports clients whose global
+ // object is a Window; it should also support those whose global
+ // object is a WorkerGlobalScope.
+ if (nsPIDOMWindowInner* const window = GetInnerWindow()) {
+ const RefPtr<ServiceWorkerContainer> container =
+ window->Navigator()->ServiceWorker();
+
+ // Note, EvictFromBFCache() may delete the ClientSource object
+ // when bfcache lives in the child process.
+ EvictFromBFCache();
+ container->ReceiveMessage(aArgs);
+ return ClientOpPromise::CreateAndResolve(CopyableErrorResult(), __func__);
+ }
+
+ CopyableErrorResult rv;
+ rv.ThrowNotSupportedError(
+ "postMessage to non-Window clients is not supported yet");
+ return ClientOpPromise::CreateAndReject(rv, __func__);
+}
+
+RefPtr<ClientOpPromise> ClientSource::GetInfoAndState(
+ const ClientGetInfoAndStateArgs& aArgs) {
+ Result<ClientState, ErrorResult> state = SnapshotState();
+ if (state.isErr()) {
+ return ClientOpPromise::CreateAndReject(
+ CopyableErrorResult(state.unwrapErr()), __func__);
+ }
+
+ return ClientOpPromise::CreateAndResolve(
+ ClientInfoAndState(mClientInfo.ToIPC(), state.inspect().ToIPC()),
+ __func__);
+}
+
+Result<ClientState, ErrorResult> ClientSource::SnapshotState() {
+ NS_ASSERT_OWNINGTHREAD(ClientSource);
+
+ if (mClientInfo.Type() == ClientType::Window) {
+ // If there is a docshell, try to create a document, too.
+ MOZ_TRY(MaybeCreateInitialDocument());
+ // SnapshotWindowState can deal with a missing inner window
+ return SnapshotWindowState();
+ }
+
+ WorkerPrivate* workerPrivate = GetWorkerPrivate();
+ if (!workerPrivate) {
+ ErrorResult rv;
+ rv.ThrowInvalidStateError("Worker terminated");
+ return Err(std::move(rv));
+ }
+
+ return ClientState(ClientWorkerState(workerPrivate->StorageAccess()));
+}
+
+nsISerialEventTarget* ClientSource::EventTarget() const { return mEventTarget; }
+
+void ClientSource::SetCsp(nsIContentSecurityPolicy* aCsp) {
+ NS_ASSERT_OWNINGTHREAD(ClientSource);
+ if (!aCsp) {
+ return;
+ }
+
+ CSPInfo cspInfo;
+ nsresult rv = CSPToCSPInfo(aCsp, &cspInfo);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return;
+ }
+ mClientInfo.SetCspInfo(cspInfo);
+}
+
+void ClientSource::SetPreloadCsp(nsIContentSecurityPolicy* aPreloadCsp) {
+ NS_ASSERT_OWNINGTHREAD(ClientSource);
+ if (!aPreloadCsp) {
+ return;
+ }
+
+ CSPInfo cspPreloadInfo;
+ nsresult rv = CSPToCSPInfo(aPreloadCsp, &cspPreloadInfo);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return;
+ }
+ mClientInfo.SetPreloadCspInfo(cspPreloadInfo);
+}
+
+void ClientSource::SetCspInfo(const CSPInfo& aCSPInfo) {
+ NS_ASSERT_OWNINGTHREAD(ClientSource);
+ mClientInfo.SetCspInfo(aCSPInfo);
+}
+
+const Maybe<mozilla::ipc::CSPInfo>& ClientSource::GetCspInfo() {
+ NS_ASSERT_OWNINGTHREAD(ClientSource);
+ return mClientInfo.GetCspInfo();
+}
+
+void ClientSource::Traverse(nsCycleCollectionTraversalCallback& aCallback,
+ const char* aName, uint32_t aFlags) {
+ if (mOwner.is<RefPtr<nsPIDOMWindowInner>>()) {
+ ImplCycleCollectionTraverse(
+ aCallback, mOwner.as<RefPtr<nsPIDOMWindowInner>>(), aName, aFlags);
+ } else if (mOwner.is<nsCOMPtr<nsIDocShell>>()) {
+ ImplCycleCollectionTraverse(aCallback, mOwner.as<nsCOMPtr<nsIDocShell>>(),
+ aName, aFlags);
+ }
+}
+
+void ClientSource::NoteCalledRegisterForServiceWorkerScope(
+ const nsACString& aScope) {
+ if (mRegisteringScopeList.Contains(aScope)) {
+ return;
+ }
+ mRegisteringScopeList.AppendElement(aScope);
+}
+
+bool ClientSource::CalledRegisterForServiceWorkerScope(
+ const nsACString& aScope) {
+ return mRegisteringScopeList.Contains(aScope);
+}
+
+nsIPrincipal* ClientSource::GetPrincipal() {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ // We only create the principal if necessary because creating a principal is
+ // expensive.
+ if (!mPrincipal) {
+ auto principalOrErr = Info().GetPrincipal();
+ nsCOMPtr<nsIPrincipal> prin =
+ principalOrErr.isOk() ? principalOrErr.unwrap() : nullptr;
+
+ mPrincipal.emplace(prin);
+ }
+
+ return mPrincipal.ref();
+}
+
+} // namespace mozilla::dom
diff --git a/dom/clients/manager/ClientSource.h b/dom/clients/manager/ClientSource.h
new file mode 100644
index 0000000000..dc89a61175
--- /dev/null
+++ b/dom/clients/manager/ClientSource.h
@@ -0,0 +1,193 @@
+/* -*- 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/. */
+#ifndef _mozilla_dom_ClientSource_h
+#define _mozilla_dom_ClientSource_h
+
+#include "mozilla/dom/ClientInfo.h"
+#include "mozilla/dom/ClientOpPromise.h"
+#include "mozilla/dom/ClientThing.h"
+#include "mozilla/dom/ServiceWorkerDescriptor.h"
+#include "mozilla/ResultVariant.h"
+#include "mozilla/Variant.h"
+
+#ifdef XP_WIN
+# undef PostMessage
+#endif
+
+class nsIContentSecurityPolicy;
+class nsIDocShell;
+class nsIGlobalObject;
+class nsISerialEventTarget;
+class nsPIDOMWindowInner;
+
+namespace mozilla {
+class ErrorResult;
+
+namespace dom {
+
+class ClientControlledArgs;
+class ClientFocusArgs;
+class ClientGetInfoAndStateArgs;
+class ClientManager;
+class ClientPostMessageArgs;
+class ClientSourceChild;
+class ClientSourceConstructorArgs;
+class ClientSourceExecutionReadyArgs;
+class ClientState;
+class ClientWindowState;
+class PClientManagerChild;
+class WorkerPrivate;
+
+// ClientSource is an RAII style class that is designed to be held via
+// a UniquePtr<>. When created ClientSource will register the existence
+// of a client in the cross-process ClientManagerService. When the
+// ClientSource is destroyed then client entry will be removed. Code
+// that represents globals or browsing environments, such as nsGlobalWindow
+// or WorkerPrivate, should use ClientManager to create a ClientSource.
+class ClientSource final : public ClientThing<ClientSourceChild> {
+ friend class ClientManager;
+
+ NS_DECL_OWNINGTHREAD
+
+ RefPtr<ClientManager> mManager;
+ nsCOMPtr<nsISerialEventTarget> mEventTarget;
+
+ Variant<Nothing, RefPtr<nsPIDOMWindowInner>, nsCOMPtr<nsIDocShell>,
+ WorkerPrivate*>
+ mOwner;
+
+ ClientInfo mClientInfo;
+ Maybe<ServiceWorkerDescriptor> mController;
+ Maybe<nsCOMPtr<nsIPrincipal>> mPrincipal;
+
+ // Contained a de-duplicated list of ServiceWorker scope strings
+ // for which this client has called navigator.serviceWorker.register().
+ // Typically there will be either be zero or one scope strings, but
+ // there could be more. We keep this list until the client is closed.
+ AutoTArray<nsCString, 1> mRegisteringScopeList;
+
+ void Shutdown();
+
+ void ExecutionReady(const ClientSourceExecutionReadyArgs& aArgs);
+
+ WorkerPrivate* GetWorkerPrivate() const;
+
+ nsIDocShell* GetDocShell() const;
+
+ nsIGlobalObject* GetGlobal() const;
+
+ Result<bool, ErrorResult> MaybeCreateInitialDocument();
+
+ Result<ClientState, ErrorResult> SnapshotWindowState();
+
+ // Private methods called by ClientManager
+ ClientSource(ClientManager* aManager, nsISerialEventTarget* aEventTarget,
+ const ClientSourceConstructorArgs& aArgs);
+
+ void Activate(PClientManagerChild* aActor);
+
+ public:
+ ~ClientSource();
+
+ nsPIDOMWindowInner* GetInnerWindow() const;
+
+ void WorkerExecutionReady(WorkerPrivate* aWorkerPrivate);
+
+ nsresult WindowExecutionReady(nsPIDOMWindowInner* aInnerWindow);
+
+ nsresult DocShellExecutionReady(nsIDocShell* aDocShell);
+
+ void Freeze();
+
+ void Thaw();
+
+ void EvictFromBFCache();
+
+ RefPtr<ClientOpPromise> EvictFromBFCacheOp();
+
+ const ClientInfo& Info() const;
+
+ // Trigger a synchronous IPC ping to the parent process to confirm that
+ // the ClientSource actor has been created. This should only be used
+ // by the WorkerPrivate startup code to deal with a ClientHandle::Control()
+ // call racing on the main thread. Do not call this in other circumstances!
+ void WorkerSyncPing(WorkerPrivate* aWorkerPrivate);
+
+ // Synchronously mark the ClientSource as controlled by the given service
+ // worker. This can happen as a result of a remote operation or directly
+ // by local code. For example, if a client's initial network load is
+ // intercepted by a controlling service worker then this should be called
+ // immediately.
+ //
+ // Note, there is no way to clear the controlling service worker because
+ // the specification does not allow that operation.
+ void SetController(const ServiceWorkerDescriptor& aServiceWorker);
+
+ // Mark the ClientSource as controlled using the remote operation arguments.
+ // This will in turn call SetController().
+ RefPtr<ClientOpPromise> Control(const ClientControlledArgs& aArgs);
+
+ // Inherit the controller from a local parent client. This requires both
+ // setting our immediate controller field and also updating the parent-side
+ // data structure.
+ void InheritController(const ServiceWorkerDescriptor& aServiceWorker);
+
+ // Get the ClientSource's current controlling service worker, if one has
+ // been set.
+ const Maybe<ServiceWorkerDescriptor>& GetController() const;
+
+ // Note that the client has reached DOMContentLoaded. Only applies to window
+ // clients.
+ void NoteDOMContentLoaded();
+
+ // TODO: Convert Focus() to MOZ_CAN_RUN_SCRIPT
+ MOZ_CAN_RUN_SCRIPT_BOUNDARY RefPtr<ClientOpPromise> Focus(
+ const ClientFocusArgs& aArgs);
+
+ RefPtr<ClientOpPromise> PostMessage(const ClientPostMessageArgs& aArgs);
+
+ RefPtr<ClientOpPromise> GetInfoAndState(
+ const ClientGetInfoAndStateArgs& aArgs);
+
+ Result<ClientState, ErrorResult> SnapshotState();
+
+ nsISerialEventTarget* EventTarget() const;
+
+ void SetCsp(nsIContentSecurityPolicy* aCsp);
+ void SetPreloadCsp(nsIContentSecurityPolicy* aPreloadCSP);
+ void SetCspInfo(const mozilla::ipc::CSPInfo& aCSPInfo);
+ const Maybe<mozilla::ipc::CSPInfo>& GetCspInfo();
+
+ void SetAgentClusterId(const nsID& aId) {
+ mClientInfo.SetAgentClusterId(aId);
+ }
+
+ void Traverse(nsCycleCollectionTraversalCallback& aCallback,
+ const char* aName, uint32_t aFlags);
+
+ void NoteCalledRegisterForServiceWorkerScope(const nsACString& aScope);
+
+ bool CalledRegisterForServiceWorkerScope(const nsACString& aScope);
+
+ nsIPrincipal* GetPrincipal();
+};
+
+inline void ImplCycleCollectionUnlink(UniquePtr<ClientSource>& aField) {
+ aField.reset();
+}
+
+inline void ImplCycleCollectionTraverse(
+ nsCycleCollectionTraversalCallback& aCallback,
+ UniquePtr<ClientSource>& aField, const char* aName, uint32_t aFlags) {
+ if (aField) {
+ aField->Traverse(aCallback, aName, aFlags);
+ }
+}
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // _mozilla_dom_ClientSource_h
diff --git a/dom/clients/manager/ClientSourceChild.cpp b/dom/clients/manager/ClientSourceChild.cpp
new file mode 100644
index 0000000000..7aef60518c
--- /dev/null
+++ b/dom/clients/manager/ClientSourceChild.cpp
@@ -0,0 +1,79 @@
+/* -*- 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 "ClientSourceChild.h"
+
+#include "ClientSource.h"
+#include "ClientSourceOpChild.h"
+#include "mozilla/dom/ClientIPCTypes.h"
+#include "mozilla/Unused.h"
+
+namespace mozilla::dom {
+
+using mozilla::ipc::IPCResult;
+
+void ClientSourceChild::ActorDestroy(ActorDestroyReason aReason) {
+ if (mSource) {
+ mSource->RevokeActor(this);
+
+ // Revoking the actor link should automatically cause the owner
+ // to call RevokeOwner() as well.
+ MOZ_DIAGNOSTIC_ASSERT(!mSource);
+ }
+}
+
+PClientSourceOpChild* ClientSourceChild::AllocPClientSourceOpChild(
+ const ClientOpConstructorArgs& aArgs) {
+ return new ClientSourceOpChild();
+}
+
+bool ClientSourceChild::DeallocPClientSourceOpChild(
+ PClientSourceOpChild* aActor) {
+ static_cast<ClientSourceOpChild*>(aActor)->ScheduleDeletion();
+ return true;
+}
+
+IPCResult ClientSourceChild::RecvPClientSourceOpConstructor(
+ PClientSourceOpChild* aActor, const ClientOpConstructorArgs& aArgs) {
+ auto actor = static_cast<ClientSourceOpChild*>(aActor);
+ actor->Init(aArgs);
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult ClientSourceChild::RecvEvictFromBFCache() {
+ if (mSource) {
+ mSource->EvictFromBFCache();
+ }
+
+ return IPC_OK();
+}
+
+ClientSourceChild::ClientSourceChild(const ClientSourceConstructorArgs& aArgs)
+ : mSource(nullptr), mTeardownStarted(false) {}
+
+void ClientSourceChild::SetOwner(ClientThing<ClientSourceChild>* aThing) {
+ MOZ_DIAGNOSTIC_ASSERT(aThing);
+ MOZ_DIAGNOSTIC_ASSERT(!mSource);
+ mSource = static_cast<ClientSource*>(aThing);
+}
+
+void ClientSourceChild::RevokeOwner(ClientThing<ClientSourceChild>* aThing) {
+ MOZ_DIAGNOSTIC_ASSERT(mSource);
+ MOZ_DIAGNOSTIC_ASSERT(mSource == static_cast<ClientSource*>(aThing));
+ mSource = nullptr;
+}
+
+ClientSource* ClientSourceChild::GetSource() const { return mSource; }
+
+void ClientSourceChild::MaybeStartTeardown() {
+ if (mTeardownStarted) {
+ return;
+ }
+ mTeardownStarted = true;
+ Unused << SendTeardown();
+}
+
+} // namespace mozilla::dom
diff --git a/dom/clients/manager/ClientSourceChild.h b/dom/clients/manager/ClientSourceChild.h
new file mode 100644
index 0000000000..4c08701710
--- /dev/null
+++ b/dom/clients/manager/ClientSourceChild.h
@@ -0,0 +1,50 @@
+/* -*- 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/. */
+#ifndef _mozilla_dom_ClientSourceChild_h
+#define _mozilla_dom_ClientSourceChild_h
+
+#include "mozilla/dom/PClientSourceChild.h"
+
+namespace mozilla::dom {
+
+class ClientSource;
+class ClientSourceConstructorArgs;
+template <typename ActorType>
+class ClientThing;
+
+class ClientSourceChild final : public PClientSourceChild {
+ ClientSource* mSource;
+ bool mTeardownStarted;
+
+ // PClientSourceChild interface
+ void ActorDestroy(ActorDestroyReason aReason) override;
+
+ PClientSourceOpChild* AllocPClientSourceOpChild(
+ const ClientOpConstructorArgs& aArgs) override;
+
+ bool DeallocPClientSourceOpChild(PClientSourceOpChild* aActor) override;
+
+ mozilla::ipc::IPCResult RecvPClientSourceOpConstructor(
+ PClientSourceOpChild* aActor,
+ const ClientOpConstructorArgs& aArgs) override;
+
+ mozilla::ipc::IPCResult RecvEvictFromBFCache() override;
+
+ public:
+ explicit ClientSourceChild(const ClientSourceConstructorArgs& aArgs);
+
+ void SetOwner(ClientThing<ClientSourceChild>* aThing);
+
+ void RevokeOwner(ClientThing<ClientSourceChild>* aThing);
+
+ ClientSource* GetSource() const;
+
+ void MaybeStartTeardown();
+};
+
+} // namespace mozilla::dom
+
+#endif // _mozilla_dom_ClientSourceChild_h
diff --git a/dom/clients/manager/ClientSourceOpChild.cpp b/dom/clients/manager/ClientSourceOpChild.cpp
new file mode 100644
index 0000000000..01176d7dca
--- /dev/null
+++ b/dom/clients/manager/ClientSourceOpChild.cpp
@@ -0,0 +1,136 @@
+/* -*- 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 "ClientSourceOpChild.h"
+
+#include "ClientSource.h"
+#include "ClientSourceChild.h"
+#include "mozilla/Assertions.h"
+#include "mozilla/Unused.h"
+
+namespace mozilla::dom {
+
+ClientSource* ClientSourceOpChild::GetSource() const {
+ auto actor = static_cast<ClientSourceChild*>(Manager());
+ return actor->GetSource();
+}
+
+template <typename Method, typename... Args>
+void ClientSourceOpChild::DoSourceOp(Method aMethod, Args&&... aArgs) {
+ RefPtr<ClientOpPromise> promise;
+ nsCOMPtr<nsISerialEventTarget> target;
+
+ // Some ClientSource operations can cause the ClientSource to be destroyed.
+ // This means we should reference the ClientSource pointer for the minimum
+ // possible to start the operation. Use an extra block scope here to help
+ // enforce this and prevent accidental usage later in the method.
+ {
+ ClientSource* source = GetSource();
+ if (!source) {
+ CopyableErrorResult rv;
+ rv.ThrowAbortError("Unknown Client");
+ Unused << PClientSourceOpChild::Send__delete__(this, rv);
+ return;
+ }
+
+ target = source->EventTarget();
+
+ // This may cause the ClientSource object to be destroyed. Do not
+ // use the source variable after this call.
+ promise = (source->*aMethod)(std::forward<Args>(aArgs)...);
+ }
+
+ // The ClientSource methods are required to always return a promise. If
+ // they encounter an error they should just immediately resolve or reject
+ // the promise as appropriate.
+ MOZ_DIAGNOSTIC_ASSERT(promise);
+
+ // Capture 'this' is safe here because we disconnect the promise
+ // ActorDestroy() which ensures neither lambda is called if the
+ // actor is destroyed before the source operation completes.
+ //
+ // Also capture the promise to ensure it lives until we get a reaction
+ // or the actor starts shutting down and we disconnect our Thenable.
+ // If the ClientSource is doing something async it may throw away the
+ // promise on its side if the global is closed.
+ promise
+ ->Then(
+ target, __func__,
+ [this, promise](const mozilla::dom::ClientOpResult& aResult) {
+ mPromiseRequestHolder.Complete();
+ Unused << PClientSourceOpChild::Send__delete__(this, aResult);
+ },
+ [this, promise](const CopyableErrorResult& aRv) {
+ mPromiseRequestHolder.Complete();
+ Unused << PClientSourceOpChild::Send__delete__(this, aRv);
+ })
+ ->Track(mPromiseRequestHolder);
+}
+
+void ClientSourceOpChild::ActorDestroy(ActorDestroyReason aReason) {
+ Cleanup();
+}
+
+void ClientSourceOpChild::Init(const ClientOpConstructorArgs& aArgs) {
+ switch (aArgs.type()) {
+ case ClientOpConstructorArgs::TClientControlledArgs: {
+ DoSourceOp(&ClientSource::Control, aArgs.get_ClientControlledArgs());
+ break;
+ }
+ case ClientOpConstructorArgs::TClientFocusArgs: {
+ DoSourceOp(&ClientSource::Focus, aArgs.get_ClientFocusArgs());
+ break;
+ }
+ case ClientOpConstructorArgs::TClientPostMessageArgs: {
+ DoSourceOp(&ClientSource::PostMessage, aArgs.get_ClientPostMessageArgs());
+ break;
+ }
+ case ClientOpConstructorArgs::TClientClaimArgs: {
+ MOZ_ASSERT_UNREACHABLE("shouldn't happen with parent intercept");
+ break;
+ }
+ case ClientOpConstructorArgs::TClientGetInfoAndStateArgs: {
+ DoSourceOp(&ClientSource::GetInfoAndState,
+ aArgs.get_ClientGetInfoAndStateArgs());
+ break;
+ }
+ case ClientOpConstructorArgs::TClientEvictBFCacheArgs: {
+ DoSourceOp(&ClientSource::EvictFromBFCacheOp);
+ break;
+ }
+ default: {
+ MOZ_ASSERT_UNREACHABLE("unknown client operation!");
+ break;
+ }
+ }
+
+ mInitialized.Flip();
+
+ if (mDeletionRequested) {
+ Cleanup();
+ delete this;
+ }
+}
+
+void ClientSourceOpChild::ScheduleDeletion() {
+ if (mInitialized) {
+ Cleanup();
+ delete this;
+ return;
+ }
+
+ mDeletionRequested.Flip();
+}
+
+ClientSourceOpChild::~ClientSourceOpChild() {
+ MOZ_DIAGNOSTIC_ASSERT(mInitialized);
+}
+
+void ClientSourceOpChild::Cleanup() {
+ mPromiseRequestHolder.DisconnectIfExists();
+}
+
+} // namespace mozilla::dom
diff --git a/dom/clients/manager/ClientSourceOpChild.h b/dom/clients/manager/ClientSourceOpChild.h
new file mode 100644
index 0000000000..4b475cac1b
--- /dev/null
+++ b/dom/clients/manager/ClientSourceOpChild.h
@@ -0,0 +1,45 @@
+/* -*- 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/. */
+#ifndef _mozilla_dom_ClientSourceOpChild_h
+#define _mozilla_dom_ClientSourceOpChild_h
+
+#include "mozilla/dom/FlippedOnce.h"
+#include "mozilla/dom/PClientSourceOpChild.h"
+#include "ClientOpPromise.h"
+
+namespace mozilla::dom {
+
+class ClientSource;
+
+class ClientSourceOpChild final : public PClientSourceOpChild {
+ public:
+ void Init(const ClientOpConstructorArgs& aArgs);
+
+ // Deletes "this" after initialization (or immediately if already
+ // initialized.) It's UB to use "this" after calling ScheduleDeletion.
+ void ScheduleDeletion();
+
+ private:
+ ~ClientSourceOpChild();
+
+ void Cleanup();
+
+ ClientSource* GetSource() const;
+
+ template <typename Method, typename... Args>
+ void DoSourceOp(Method aMethod, Args&&... aArgs);
+
+ // PClientSourceOpChild interface
+ void ActorDestroy(ActorDestroyReason aReason) override;
+
+ MozPromiseRequestHolder<ClientOpPromise> mPromiseRequestHolder;
+ FlippedOnce<false> mDeletionRequested;
+ FlippedOnce<false> mInitialized;
+};
+
+} // namespace mozilla::dom
+
+#endif // _mozilla_dom_ClientSourceOpChild_h
diff --git a/dom/clients/manager/ClientSourceOpParent.cpp b/dom/clients/manager/ClientSourceOpParent.cpp
new file mode 100644
index 0000000000..b5da421c07
--- /dev/null
+++ b/dom/clients/manager/ClientSourceOpParent.cpp
@@ -0,0 +1,57 @@
+/* -*- 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 "ClientSourceOpParent.h"
+
+#include "ClientSourceParent.h"
+
+namespace mozilla::dom {
+
+using mozilla::ipc::IPCResult;
+
+void ClientSourceOpParent::ActorDestroy(ActorDestroyReason aReason) {
+ if (mPromise) {
+ CopyableErrorResult rv;
+ rv.ThrowAbortError("Client torn down");
+ mPromise->Reject(rv, __func__);
+ mPromise = nullptr;
+ }
+}
+
+IPCResult ClientSourceOpParent::Recv__delete__(const ClientOpResult& aResult) {
+ if (aResult.type() == ClientOpResult::TCopyableErrorResult &&
+ aResult.get_CopyableErrorResult().Failed()) {
+ // If a control message fails then clear the controller from
+ // the ClientSourceParent. We eagerly marked it controlled at
+ // the start of the operation.
+ if (mArgs.type() == ClientOpConstructorArgs::TClientControlledArgs) {
+ auto source = static_cast<ClientSourceParent*>(Manager());
+ if (source) {
+ source->ClearController();
+ }
+ }
+
+ mPromise->Reject(aResult.get_CopyableErrorResult(), __func__);
+ mPromise = nullptr;
+ return IPC_OK();
+ }
+
+ mPromise->Resolve(aResult, __func__);
+ mPromise = nullptr;
+ return IPC_OK();
+}
+
+ClientSourceOpParent::ClientSourceOpParent(ClientOpConstructorArgs&& aArgs,
+ ClientOpPromise::Private* aPromise)
+ : mArgs(std::move(aArgs)), mPromise(aPromise) {
+ MOZ_DIAGNOSTIC_ASSERT(mPromise);
+}
+
+ClientSourceOpParent::~ClientSourceOpParent() {
+ MOZ_DIAGNOSTIC_ASSERT(!mPromise);
+}
+
+} // namespace mozilla::dom
diff --git a/dom/clients/manager/ClientSourceOpParent.h b/dom/clients/manager/ClientSourceOpParent.h
new file mode 100644
index 0000000000..e3fe22b5d1
--- /dev/null
+++ b/dom/clients/manager/ClientSourceOpParent.h
@@ -0,0 +1,35 @@
+/* -*- 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/. */
+#ifndef _mozilla_dom_ClientSourceOpParent_h
+#define _mozilla_dom_ClientSourceOpParent_h
+
+#include "mozilla/dom/ClientOpPromise.h"
+#include "mozilla/dom/PClientSourceOpParent.h"
+
+namespace mozilla::dom {
+
+class ClientSourceOpParent final : public PClientSourceOpParent {
+ const ClientOpConstructorArgs mArgs;
+ RefPtr<ClientOpPromise::Private> mPromise;
+
+ // PClientSourceOpParent interface
+ void ActorDestroy(ActorDestroyReason aReason) override;
+
+ mozilla::ipc::IPCResult Recv__delete__(
+ const ClientOpResult& aResult) override;
+
+ public:
+ ClientSourceOpParent(ClientOpConstructorArgs&& aArgs,
+ ClientOpPromise::Private* aPromise);
+
+ const ClientOpConstructorArgs& Args() const { return mArgs; }
+
+ ~ClientSourceOpParent();
+};
+
+} // namespace mozilla::dom
+
+#endif // _mozilla_dom_ClientSourceOpParent_h
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
diff --git a/dom/clients/manager/ClientSourceParent.h b/dom/clients/manager/ClientSourceParent.h
new file mode 100644
index 0000000000..17841b0c8b
--- /dev/null
+++ b/dom/clients/manager/ClientSourceParent.h
@@ -0,0 +1,89 @@
+/* -*- 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/. */
+#ifndef _mozilla_dom_ClientSourceParent_h
+#define _mozilla_dom_ClientSourceParent_h
+
+#include "ClientInfo.h"
+#include "ClientOpPromise.h"
+#include "mozilla/dom/PClientSourceParent.h"
+#include "mozilla/dom/ServiceWorkerDescriptor.h"
+#include "mozilla/dom/ipc/IdType.h"
+#include "mozilla/MozPromise.h"
+
+namespace mozilla::dom {
+
+class ClientHandleParent;
+class ClientManagerService;
+
+class ClientSourceParent final : public PClientSourceParent {
+ ClientInfo mClientInfo;
+ Maybe<ServiceWorkerDescriptor> mController;
+ const Maybe<ContentParentId> mContentParentId;
+ RefPtr<ClientManagerService> mService;
+ nsTArray<ClientHandleParent*> mHandleList;
+ MozPromiseHolder<GenericNonExclusivePromise> mExecutionReadyPromise;
+ bool mExecutionReady;
+ bool mFrozen;
+
+ void KillInvalidChild();
+
+ // PClientSourceParent
+ mozilla::ipc::IPCResult RecvWorkerSyncPing() override;
+
+ mozilla::ipc::IPCResult RecvTeardown() override;
+
+ mozilla::ipc::IPCResult RecvExecutionReady(
+ const ClientSourceExecutionReadyArgs& aArgs) override;
+
+ mozilla::ipc::IPCResult RecvFreeze() override;
+
+ mozilla::ipc::IPCResult RecvThaw() override;
+
+ mozilla::ipc::IPCResult RecvInheritController(
+ const ClientControlledArgs& aArgs) override;
+
+ mozilla::ipc::IPCResult RecvNoteDOMContentLoaded() override;
+
+ void ActorDestroy(ActorDestroyReason aReason) override;
+
+ PClientSourceOpParent* AllocPClientSourceOpParent(
+ const ClientOpConstructorArgs& aArgs) override;
+
+ bool DeallocPClientSourceOpParent(PClientSourceOpParent* aActor) override;
+
+ public:
+ explicit ClientSourceParent(const ClientSourceConstructorArgs& aArgs,
+ const Maybe<ContentParentId>& aContentParentId);
+ ~ClientSourceParent();
+
+ void Init();
+
+ const ClientInfo& Info() const;
+
+ bool IsFrozen() const;
+
+ bool ExecutionReady() const;
+
+ RefPtr<GenericNonExclusivePromise> ExecutionReadyPromise();
+
+ const Maybe<ServiceWorkerDescriptor>& GetController() const;
+
+ void ClearController();
+
+ bool IsOwnedByProcess(ContentParentId aContentParentId) const {
+ return mContentParentId && mContentParentId.value() == aContentParentId;
+ }
+
+ void AttachHandle(ClientHandleParent* aClientSource);
+
+ void DetachHandle(ClientHandleParent* aClientSource);
+
+ RefPtr<ClientOpPromise> StartOp(ClientOpConstructorArgs&& aArgs);
+};
+
+} // namespace mozilla::dom
+
+#endif // _mozilla_dom_ClientSourceParent_h
diff --git a/dom/clients/manager/ClientState.cpp b/dom/clients/manager/ClientState.cpp
new file mode 100644
index 0000000000..89e43465d7
--- /dev/null
+++ b/dom/clients/manager/ClientState.cpp
@@ -0,0 +1,167 @@
+/* -*- 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 "ClientState.h"
+
+#include "mozilla/dom/ClientIPCTypes.h"
+
+namespace mozilla::dom {
+
+ClientWindowState::ClientWindowState(
+ mozilla::dom::VisibilityState aVisibilityState,
+ const TimeStamp& aLastFocusTime, StorageAccess aStorageAccess,
+ bool aFocused)
+ : mData(MakeUnique<IPCClientWindowState>(aVisibilityState, aLastFocusTime,
+ aStorageAccess, aFocused)) {}
+
+ClientWindowState::ClientWindowState(const IPCClientWindowState& aData)
+ : mData(MakeUnique<IPCClientWindowState>(aData)) {}
+
+ClientWindowState::ClientWindowState(const ClientWindowState& aRight) {
+ operator=(aRight);
+}
+
+ClientWindowState::ClientWindowState(ClientWindowState&& aRight)
+ : mData(std::move(aRight.mData)) {}
+
+ClientWindowState& ClientWindowState::operator=(
+ const ClientWindowState& aRight) {
+ mData.reset();
+ mData = MakeUnique<IPCClientWindowState>(*aRight.mData);
+ return *this;
+}
+
+ClientWindowState& ClientWindowState::operator=(ClientWindowState&& aRight) {
+ mData.reset();
+ mData = std::move(aRight.mData);
+ return *this;
+}
+
+ClientWindowState::~ClientWindowState() = default;
+
+mozilla::dom::VisibilityState ClientWindowState::VisibilityState() const {
+ return mData->visibilityState();
+}
+
+const TimeStamp& ClientWindowState::LastFocusTime() const {
+ return mData->lastFocusTime();
+}
+
+bool ClientWindowState::Focused() const { return mData->focused(); }
+
+StorageAccess ClientWindowState::GetStorageAccess() const {
+ return mData->storageAccess();
+}
+
+const IPCClientWindowState& ClientWindowState::ToIPC() const { return *mData; }
+
+ClientWorkerState::ClientWorkerState(StorageAccess aStorageAccess)
+ : mData(MakeUnique<IPCClientWorkerState>(aStorageAccess)) {}
+
+ClientWorkerState::ClientWorkerState(const IPCClientWorkerState& aData)
+ : mData(MakeUnique<IPCClientWorkerState>(aData)) {}
+
+ClientWorkerState::ClientWorkerState(ClientWorkerState&& aRight)
+ : mData(std::move(aRight.mData)) {}
+
+ClientWorkerState::ClientWorkerState(const ClientWorkerState& aRight) {
+ operator=(aRight);
+}
+
+ClientWorkerState& ClientWorkerState::operator=(
+ const ClientWorkerState& aRight) {
+ mData.reset();
+ mData = MakeUnique<IPCClientWorkerState>(*aRight.mData);
+ return *this;
+}
+
+ClientWorkerState& ClientWorkerState::operator=(ClientWorkerState&& aRight) {
+ mData.reset();
+ mData = std::move(aRight.mData);
+ return *this;
+}
+
+ClientWorkerState::~ClientWorkerState() = default;
+
+StorageAccess ClientWorkerState::GetStorageAccess() const {
+ return mData->storageAccess();
+}
+
+const IPCClientWorkerState& ClientWorkerState::ToIPC() const { return *mData; }
+
+ClientState::ClientState() = default;
+
+ClientState::ClientState(const ClientWindowState& aWindowState) {
+ mData.emplace(AsVariant(aWindowState));
+}
+
+ClientState::ClientState(const ClientWorkerState& aWorkerState) {
+ mData.emplace(AsVariant(aWorkerState));
+}
+
+ClientState::ClientState(const IPCClientWindowState& aData) {
+ mData.emplace(AsVariant(ClientWindowState(aData)));
+}
+
+ClientState::ClientState(const IPCClientWorkerState& aData) {
+ mData.emplace(AsVariant(ClientWorkerState(aData)));
+}
+
+ClientState::ClientState(ClientState&& aRight)
+ : mData(std::move(aRight.mData)) {}
+
+ClientState& ClientState::operator=(ClientState&& aRight) {
+ mData = std::move(aRight.mData);
+ return *this;
+}
+
+ClientState::~ClientState() = default;
+
+// static
+ClientState ClientState::FromIPC(const IPCClientState& aData) {
+ switch (aData.type()) {
+ case IPCClientState::TIPCClientWindowState:
+ return ClientState(aData.get_IPCClientWindowState());
+ case IPCClientState::TIPCClientWorkerState:
+ return ClientState(aData.get_IPCClientWorkerState());
+ default:
+ MOZ_CRASH("unexpected IPCClientState type");
+ }
+}
+
+bool ClientState::IsWindowState() const {
+ return mData.isSome() && mData.ref().is<ClientWindowState>();
+}
+
+const ClientWindowState& ClientState::AsWindowState() const {
+ return mData.ref().as<ClientWindowState>();
+}
+
+bool ClientState::IsWorkerState() const {
+ return mData.isSome() && mData.ref().is<ClientWorkerState>();
+}
+
+const ClientWorkerState& ClientState::AsWorkerState() const {
+ return mData.ref().as<ClientWorkerState>();
+}
+
+StorageAccess ClientState::GetStorageAccess() const {
+ if (IsWindowState()) {
+ return AsWindowState().GetStorageAccess();
+ }
+
+ return AsWorkerState().GetStorageAccess();
+}
+
+const IPCClientState ClientState::ToIPC() const {
+ if (IsWindowState()) {
+ return IPCClientState(AsWindowState().ToIPC());
+ }
+
+ return IPCClientState(AsWorkerState().ToIPC());
+}
+
+} // namespace mozilla::dom
diff --git a/dom/clients/manager/ClientState.h b/dom/clients/manager/ClientState.h
new file mode 100644
index 0000000000..2cd5a601ad
--- /dev/null
+++ b/dom/clients/manager/ClientState.h
@@ -0,0 +1,125 @@
+/* -*- 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/. */
+
+#ifndef _mozilla_dom_ClientState_h
+#define _mozilla_dom_ClientState_h
+
+#include "mozilla/dom/DocumentBinding.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/StorageAccess.h"
+#include "mozilla/TimeStamp.h"
+#include "mozilla/UniquePtr.h"
+#include "mozilla/Variant.h"
+#include "nsContentUtils.h"
+
+namespace mozilla::dom {
+
+class IPCClientState;
+class IPCClientWindowState;
+class IPCClientWorkerState;
+
+// This class defines the mutable nsGlobalWindow state we support querying
+// through the ClientManagerService. It is a snapshot of the state and
+// is not live updated.
+class ClientWindowState final {
+ UniquePtr<IPCClientWindowState> mData;
+
+ public:
+ ClientWindowState(mozilla::dom::VisibilityState aVisibilityState,
+ const TimeStamp& aLastFocusTime,
+ StorageAccess aStorageAccess, bool aFocused);
+
+ explicit ClientWindowState(const IPCClientWindowState& aData);
+
+ ClientWindowState(const ClientWindowState& aRight);
+ ClientWindowState(ClientWindowState&& aRight);
+
+ ClientWindowState& operator=(const ClientWindowState& aRight);
+
+ ClientWindowState& operator=(ClientWindowState&& aRight);
+
+ ~ClientWindowState();
+
+ mozilla::dom::VisibilityState VisibilityState() const;
+
+ const TimeStamp& LastFocusTime() const;
+
+ bool Focused() const;
+
+ StorageAccess GetStorageAccess() const;
+
+ const IPCClientWindowState& ToIPC() const;
+};
+
+// This class defines the mutable worker state we support querying
+// through the ClientManagerService. It is a snapshot of the state and
+// is not live updated. Right now, we don't actually providate any
+// worker specific state values, but we may in the future. This
+// class also services as a placeholder that the state is referring
+// to a worker in ClientState.
+class ClientWorkerState final {
+ UniquePtr<IPCClientWorkerState> mData;
+
+ public:
+ explicit ClientWorkerState(StorageAccess aStorageAccess);
+
+ explicit ClientWorkerState(const IPCClientWorkerState& aData);
+
+ ClientWorkerState(const ClientWorkerState& aRight);
+ ClientWorkerState(ClientWorkerState&& aRight);
+
+ ClientWorkerState& operator=(const ClientWorkerState& aRight);
+
+ ClientWorkerState& operator=(ClientWorkerState&& aRight);
+
+ ~ClientWorkerState();
+
+ StorageAccess GetStorageAccess() const;
+
+ const IPCClientWorkerState& ToIPC() const;
+};
+
+// This is a union of the various types of mutable state we support
+// querying in ClientManagerService. Right now it can contain either
+// window or worker states.
+class ClientState final {
+ Maybe<Variant<ClientWindowState, ClientWorkerState>> mData;
+
+ public:
+ ClientState();
+
+ explicit ClientState(const ClientWindowState& aWindowState);
+ explicit ClientState(const ClientWorkerState& aWorkerState);
+ explicit ClientState(const IPCClientWindowState& aData);
+ explicit ClientState(const IPCClientWorkerState& aData);
+
+ ClientState(const ClientState& aRight) = default;
+ ClientState(ClientState&& aRight);
+
+ ClientState& operator=(const ClientState& aRight) = default;
+
+ ClientState& operator=(ClientState&& aRight);
+
+ ~ClientState();
+
+ static ClientState FromIPC(const IPCClientState& aData);
+
+ bool IsWindowState() const;
+
+ const ClientWindowState& AsWindowState() const;
+
+ bool IsWorkerState() const;
+
+ const ClientWorkerState& AsWorkerState() const;
+
+ StorageAccess GetStorageAccess() const;
+
+ const IPCClientState ToIPC() const;
+};
+
+} // namespace mozilla::dom
+
+#endif // _mozilla_dom_ClientState_h
diff --git a/dom/clients/manager/ClientThing.h b/dom/clients/manager/ClientThing.h
new file mode 100644
index 0000000000..9b2bfcc52d
--- /dev/null
+++ b/dom/clients/manager/ClientThing.h
@@ -0,0 +1,138 @@
+/* -*- 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/. */
+#ifndef _mozilla_dom_ClientThing_h
+#define _mozilla_dom_ClientThing_h
+
+#include "nsTArray.h"
+
+namespace mozilla::dom {
+
+// Base class representing various Client "things" such as ClientHandle,
+// ClientSource, and ClientManager. Currently it provides a common set
+// of code for handling activation and shutdown of IPC actors.
+template <typename ActorType>
+class ClientThing {
+#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
+ static const uint32_t kMagic1 = 0xC9FE2C9C;
+ static const uint32_t kMagic2 = 0x832072D4;
+#endif
+
+ ActorType* mActor;
+#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
+ uint32_t mMagic1;
+ uint32_t mMagic2;
+#endif
+ bool mShutdown;
+
+ protected:
+ ClientThing()
+ : mActor(nullptr)
+#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
+ ,
+ mMagic1(kMagic1),
+ mMagic2(kMagic2)
+#endif
+ ,
+ mShutdown(false) {
+ }
+
+ ~ClientThing() {
+ AssertIsValid();
+ ShutdownThing();
+#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
+ mMagic1 = 0;
+ mMagic2 = 0;
+#endif
+ }
+
+ void AssertIsValid() const {
+ MOZ_DIAGNOSTIC_ASSERT(mMagic1 == kMagic1);
+ MOZ_DIAGNOSTIC_ASSERT(mMagic2 == kMagic2);
+ }
+
+ // Return the current actor.
+ ActorType* GetActor() const {
+ AssertIsValid();
+ return mActor;
+ }
+
+ // Returns true if ShutdownThing() has been called.
+ bool IsShutdown() const {
+ AssertIsValid();
+ return mShutdown;
+ }
+
+ // Conditionally execute the given callable based on the current state.
+ template <typename Callable>
+ void MaybeExecute(
+ const Callable& aSuccess, const std::function<void()>& aFailure = [] {}) {
+ AssertIsValid();
+ if (mShutdown) {
+ aFailure();
+ return;
+ }
+ MOZ_DIAGNOSTIC_ASSERT(mActor);
+ aSuccess(mActor);
+ }
+
+ // Attach activate the thing by attaching its underlying IPC actor. This
+ // will make the thing register as the actor's owner as well. The actor
+ // must call RevokeActor() to clear this weak back reference before its
+ // destroyed.
+ void ActivateThing(ActorType* aActor) {
+ AssertIsValid();
+ MOZ_DIAGNOSTIC_ASSERT(aActor);
+ MOZ_DIAGNOSTIC_ASSERT(!mActor);
+ MOZ_DIAGNOSTIC_ASSERT(!mShutdown);
+ mActor = aActor;
+ mActor->SetOwner(this);
+ }
+
+ // Start destroying the underlying actor and disconnect the thing.
+ void ShutdownThing() {
+ AssertIsValid();
+ if (mShutdown) {
+ return;
+ }
+ mShutdown = true;
+
+ // If we are shutdown before the actor, then clear the weak references
+ // between the actor and the thing.
+ if (mActor) {
+ mActor->RevokeOwner(this);
+ mActor->MaybeStartTeardown();
+ mActor = nullptr;
+ }
+
+ OnShutdownThing();
+ }
+
+ // Allow extending classes to take action when shutdown.
+ virtual void OnShutdownThing() {
+ // by default do nothing
+ }
+
+ public:
+ // Clear the weak references between the thing and its IPC actor.
+ void RevokeActor(ActorType* aActor) {
+ AssertIsValid();
+ MOZ_DIAGNOSTIC_ASSERT(mActor);
+ MOZ_DIAGNOSTIC_ASSERT(mActor == aActor);
+ mActor->RevokeOwner(this);
+ mActor = nullptr;
+
+ // Also consider the ClientThing shutdown. We simply set the flag
+ // instead of calling ShutdownThing() to avoid calling MaybeStartTeardown()
+ // on the destroyed actor.
+ mShutdown = true;
+
+ OnShutdownThing();
+ }
+};
+
+} // namespace mozilla::dom
+
+#endif // _mozilla_dom_ClientThing_h
diff --git a/dom/clients/manager/ClientValidation.cpp b/dom/clients/manager/ClientValidation.cpp
new file mode 100644
index 0000000000..61e9338e12
--- /dev/null
+++ b/dom/clients/manager/ClientValidation.cpp
@@ -0,0 +1,170 @@
+/* -*- 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 "ClientValidation.h"
+
+#include "mozilla/ipc/PBackgroundSharedTypes.h"
+#include "mozilla/StaticPrefs_security.h"
+#include "mozilla/net/MozURL.h"
+
+namespace mozilla::dom {
+
+using mozilla::ipc::ContentPrincipalInfo;
+using mozilla::ipc::PrincipalInfo;
+using mozilla::net::MozURL;
+
+bool ClientIsValidPrincipalInfo(const PrincipalInfo& aPrincipalInfo) {
+ // Ideally we would verify that the source process has permission to
+ // create a window or worker with the given principal, but we don't
+ // currently have any such restriction in place. Instead, at least
+ // verify the PrincipalInfo is an expected type and has a parsable
+ // origin/spec.
+ switch (aPrincipalInfo.type()) {
+ // Any system and null principal is acceptable.
+ case PrincipalInfo::TSystemPrincipalInfo:
+ case PrincipalInfo::TNullPrincipalInfo: {
+ return true;
+ }
+
+ // Validate content principals to ensure that the origin and spec are sane.
+ case PrincipalInfo::TContentPrincipalInfo: {
+ const ContentPrincipalInfo& content =
+ aPrincipalInfo.get_ContentPrincipalInfo();
+
+ // Verify the principal spec parses.
+ RefPtr<MozURL> specURL;
+ nsresult rv = MozURL::Init(getter_AddRefs(specURL), content.spec());
+ NS_ENSURE_SUCCESS(rv, false);
+
+ // Verify the principal originNoSuffix parses.
+ RefPtr<MozURL> originURL;
+ rv = MozURL::Init(getter_AddRefs(originURL), content.originNoSuffix());
+ NS_ENSURE_SUCCESS(rv, false);
+
+ nsAutoCString originOrigin;
+ originURL->Origin(originOrigin);
+
+ nsAutoCString specOrigin;
+ specURL->Origin(specOrigin);
+
+ // Linkable about URIs end up with a nested inner scheme of moz-safe-about
+ // which will have been captured in the originNoSuffix but the spec and
+ // its resulting specOrigin will not have this transformed scheme, so
+ // ignore the "moz-safe-" prefix when the originURL has that transformed
+ // scheme.
+ if (originURL->Scheme().Equals("moz-safe-about")) {
+ return specOrigin == originOrigin ||
+ specOrigin == Substring(originOrigin, 9 /*moz-safe-*/,
+ specOrigin.Length());
+ }
+
+ // For now require Clients to have a principal where both its
+ // originNoSuffix and spec have the same origin. This will
+ // exclude a variety of unusual combinations within the browser
+ // but its adequate for the features need to support right now.
+ // If necessary we could expand this function to handle more
+ // cases in the future.
+
+ return specOrigin == originOrigin;
+ }
+ default: {
+ break;
+ }
+ }
+
+ // Windows and workers should not have expanded URLs, etc.
+ return false;
+}
+
+bool ClientIsValidCreationURL(const PrincipalInfo& aPrincipalInfo,
+ const nsACString& aURL) {
+ RefPtr<MozURL> url;
+ nsresult rv = MozURL::Init(getter_AddRefs(url), aURL);
+ NS_ENSURE_SUCCESS(rv, false);
+
+ switch (aPrincipalInfo.type()) {
+ case PrincipalInfo::TContentPrincipalInfo: {
+ // Any origin can create an about:blank or about:srcdoc Client.
+ if (aURL.LowerCaseEqualsLiteral("about:blank") ||
+ aURL.LowerCaseEqualsLiteral("about:srcdoc")) {
+ return true;
+ }
+
+ const ContentPrincipalInfo& content =
+ aPrincipalInfo.get_ContentPrincipalInfo();
+
+ // Parse the principal origin URL as well. This ensures any MozURL
+ // parser issues effect both URLs equally.
+ RefPtr<MozURL> principalURL;
+ rv = MozURL::Init(getter_AddRefs(principalURL), content.originNoSuffix());
+ NS_ENSURE_SUCCESS(rv, false);
+
+ nsAutoCString origin;
+ url->Origin(origin);
+
+ nsAutoCString principalOrigin;
+ principalURL->Origin(principalOrigin);
+
+ // The vast majority of sites should simply result in the same principal
+ // and URL origin.
+ if (principalOrigin == origin) {
+ return true;
+ }
+
+ nsDependentCSubstring scheme = url->Scheme();
+
+ // Generally any origin can also open javascript: windows and workers.
+ if (scheme.LowerCaseEqualsLiteral("javascript")) {
+ return true;
+ }
+
+ // Linkable about URIs end up with a nested inner scheme of moz-safe-about
+ // but the url and its resulting origin will not have this transformed
+ // scheme, so ignore the "moz-safe-" prefix when the principal has that
+ // transformed scheme.
+ if (principalURL->Scheme().Equals("moz-safe-about")) {
+ return origin == principalOrigin ||
+ origin ==
+ Substring(principalOrigin, 9 /*moz-safe-*/, origin.Length());
+ }
+
+ // Otherwise don't support this URL type in the clients sub-system for
+ // now. This will exclude a variety of internal browser clients, but
+ // currently we don't need to support those. This function can be
+ // expanded to handle more cases as necessary.
+ return false;
+ }
+ case PrincipalInfo::TSystemPrincipalInfo: {
+ nsDependentCSubstring scheme = url->Scheme();
+
+ // While many types of documents can be created with a system principal,
+ // there are only a few that can reasonably become windows. We attempt
+ // to validate the list of known cases here with a simple scheme check.
+ return scheme.LowerCaseEqualsLiteral("about") ||
+ scheme.LowerCaseEqualsLiteral("chrome") ||
+ scheme.LowerCaseEqualsLiteral("resource") ||
+ scheme.LowerCaseEqualsLiteral("blob") ||
+ scheme.LowerCaseEqualsLiteral("javascript") ||
+ scheme.LowerCaseEqualsLiteral("view-source");
+ }
+ case PrincipalInfo::TNullPrincipalInfo: {
+ // A wide variety of clients can have a null principal. For example,
+ // sandboxed iframes can have a normal content URL. For now allow
+ // any parsable URL for null principals. This is relatively safe since
+ // null principals have unique origins and won't most ClientManagerService
+ // queries anyway.
+ return true;
+ }
+ default: {
+ break;
+ }
+ }
+
+ // Clients (windows/workers) should never have an expanded principal type.
+ return false;
+}
+
+} // namespace mozilla::dom
diff --git a/dom/clients/manager/ClientValidation.h b/dom/clients/manager/ClientValidation.h
new file mode 100644
index 0000000000..46278ad02e
--- /dev/null
+++ b/dom/clients/manager/ClientValidation.h
@@ -0,0 +1,28 @@
+/* -*- 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/. */
+#ifndef _mozilla_dom_ClientValidation_h
+#define _mozilla_dom_ClientValidation_h
+
+#include "nsStringFwd.h"
+
+namespace mozilla {
+
+namespace ipc {
+class PrincipalInfo;
+} // namespace ipc
+
+namespace dom {
+
+bool ClientIsValidPrincipalInfo(
+ const mozilla::ipc::PrincipalInfo& aPrincipalInfo);
+
+bool ClientIsValidCreationURL(const mozilla::ipc::PrincipalInfo& aPrincipalInfo,
+ const nsACString& aURL);
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // _mozilla_dom_ClientValidation_h
diff --git a/dom/clients/manager/PClientHandle.ipdl b/dom/clients/manager/PClientHandle.ipdl
new file mode 100644
index 0000000000..eb6a182390
--- /dev/null
+++ b/dom/clients/manager/PClientHandle.ipdl
@@ -0,0 +1,33 @@
+/* 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 protocol PClientManager;
+include protocol PClientHandleOp;
+include ClientIPCTypes;
+
+include "mozilla/ipc/ProtocolMessageUtils.h";
+
+namespace mozilla {
+namespace dom {
+
+[ManualDealloc, ChildImpl=virtual, ParentImpl=virtual]
+protocol PClientHandle
+{
+ manager PClientManager;
+
+ manages PClientHandleOp;
+
+parent:
+ async Teardown();
+
+ async PClientHandleOp(ClientOpConstructorArgs aArgs);
+
+child:
+ async ExecutionReady(IPCClientInfo aClientInfo);
+
+ async __delete__();
+};
+
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/clients/manager/PClientHandleOp.ipdl b/dom/clients/manager/PClientHandleOp.ipdl
new file mode 100644
index 0000000000..2962d6380f
--- /dev/null
+++ b/dom/clients/manager/PClientHandleOp.ipdl
@@ -0,0 +1,21 @@
+/* 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 protocol PClientHandle;
+include ClientIPCTypes;
+
+namespace mozilla {
+namespace dom {
+
+[ManualDealloc, ChildImpl=virtual, ParentImpl=virtual]
+protocol PClientHandleOp
+{
+ manager PClientHandle;
+
+child:
+ async __delete__(ClientOpResult aResult);
+};
+
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/clients/manager/PClientManager.ipdl b/dom/clients/manager/PClientManager.ipdl
new file mode 100644
index 0000000000..a67985083f
--- /dev/null
+++ b/dom/clients/manager/PClientManager.ipdl
@@ -0,0 +1,42 @@
+/* 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 protocol PBackground;
+include protocol PClientHandle;
+include protocol PClientManagerOp;
+include protocol PClientNavigateOp;
+include protocol PClientSource;
+include ClientIPCTypes;
+
+namespace mozilla {
+namespace dom {
+
+[ManualDealloc, ChildImpl=virtual, ParentImpl=virtual]
+sync protocol PClientManager
+{
+ manager PBackground;
+
+ manages PClientHandle;
+ manages PClientManagerOp;
+ manages PClientNavigateOp;
+ manages PClientSource;
+
+parent:
+ async Teardown();
+
+ async PClientHandle(IPCClientInfo aClientInfo);
+ async PClientManagerOp(ClientOpConstructorArgs aArgs);
+ async PClientSource(ClientSourceConstructorArgs aArgs);
+
+ async ExpectFutureClientSource(IPCClientInfo aClientInfo);
+ async ForgetFutureClientSource(IPCClientInfo aClientInfo);
+
+child:
+ async PClientNavigateOp(ClientNavigateOpConstructorArgs aArgs);
+
+ async __delete__();
+};
+
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/clients/manager/PClientManagerOp.ipdl b/dom/clients/manager/PClientManagerOp.ipdl
new file mode 100644
index 0000000000..21583db06a
--- /dev/null
+++ b/dom/clients/manager/PClientManagerOp.ipdl
@@ -0,0 +1,21 @@
+/* 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 protocol PClientManager;
+include ClientIPCTypes;
+
+namespace mozilla {
+namespace dom {
+
+[ManualDealloc, ChildImpl=virtual, ParentImpl=virtual]
+protocol PClientManagerOp
+{
+ manager PClientManager;
+
+child:
+ async __delete__(ClientOpResult aResult);
+};
+
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/clients/manager/PClientNavigateOp.ipdl b/dom/clients/manager/PClientNavigateOp.ipdl
new file mode 100644
index 0000000000..6544c838c0
--- /dev/null
+++ b/dom/clients/manager/PClientNavigateOp.ipdl
@@ -0,0 +1,21 @@
+/* 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 protocol PClientManager;
+include ClientIPCTypes;
+
+namespace mozilla {
+namespace dom {
+
+[ManualDealloc, ChildImpl=virtual, ParentImpl=virtual]
+protocol PClientNavigateOp
+{
+ manager PClientManager;
+
+parent:
+ async __delete__(ClientOpResult aResult);
+};
+
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/clients/manager/PClientSource.ipdl b/dom/clients/manager/PClientSource.ipdl
new file mode 100644
index 0000000000..869cdf9504
--- /dev/null
+++ b/dom/clients/manager/PClientSource.ipdl
@@ -0,0 +1,37 @@
+/* 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 protocol PClientManager;
+include protocol PClientSourceOp;
+include ClientIPCTypes;
+
+namespace mozilla {
+namespace dom {
+
+[ManualDealloc, ChildImpl=virtual, ParentImpl=virtual]
+sync protocol PClientSource
+{
+ manager PClientManager;
+
+ manages PClientSourceOp;
+
+parent:
+ sync WorkerSyncPing();
+ async Teardown();
+ async ExecutionReady(ClientSourceExecutionReadyArgs aArgs);
+ async Freeze();
+ async Thaw();
+ async InheritController(ClientControlledArgs aArgs);
+ async NoteDOMContentLoaded();
+
+child:
+ async PClientSourceOp(ClientOpConstructorArgs aArgs);
+
+ async EvictFromBFCache();
+
+ async __delete__();
+};
+
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/clients/manager/PClientSourceOp.ipdl b/dom/clients/manager/PClientSourceOp.ipdl
new file mode 100644
index 0000000000..e3186bc00b
--- /dev/null
+++ b/dom/clients/manager/PClientSourceOp.ipdl
@@ -0,0 +1,21 @@
+/* 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 protocol PClientSource;
+include ClientIPCTypes;
+
+namespace mozilla {
+namespace dom {
+
+[ManualDealloc, ChildImpl=virtual, ParentImpl=virtual]
+protocol PClientSourceOp
+{
+ manager PClientSource;
+
+parent:
+ async __delete__(ClientOpResult aResult);
+};
+
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/clients/manager/moz.build b/dom/clients/manager/moz.build
new file mode 100644
index 0000000000..92eca6087c
--- /dev/null
+++ b/dom/clients/manager/moz.build
@@ -0,0 +1,70 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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/.
+
+EXPORTS.mozilla.dom += [
+ "ClientChannelHelper.h",
+ "ClientHandle.h",
+ "ClientHandleParent.h",
+ "ClientInfo.h",
+ "ClientIPCUtils.h",
+ "ClientManager.h",
+ "ClientManagerActors.h",
+ "ClientManagerService.h",
+ "ClientOpenWindowUtils.h",
+ "ClientOpPromise.h",
+ "ClientSource.h",
+ "ClientState.h",
+ "ClientThing.h",
+]
+
+UNIFIED_SOURCES += [
+ "ClientChannelHelper.cpp",
+ "ClientHandle.cpp",
+ "ClientHandleChild.cpp",
+ "ClientHandleOpChild.cpp",
+ "ClientHandleOpParent.cpp",
+ "ClientHandleParent.cpp",
+ "ClientInfo.cpp",
+ "ClientManager.cpp",
+ "ClientManagerActors.cpp",
+ "ClientManagerChild.cpp",
+ "ClientManagerOpChild.cpp",
+ "ClientManagerOpParent.cpp",
+ "ClientManagerParent.cpp",
+ "ClientManagerService.cpp",
+ "ClientNavigateOpChild.cpp",
+ "ClientNavigateOpParent.cpp",
+ "ClientOpenWindowUtils.cpp",
+ "ClientPrincipalUtils.cpp",
+ "ClientSource.cpp",
+ "ClientSourceChild.cpp",
+ "ClientSourceOpChild.cpp",
+ "ClientSourceOpParent.cpp",
+ "ClientSourceParent.cpp",
+ "ClientState.cpp",
+ "ClientValidation.cpp",
+]
+
+IPDL_SOURCES += [
+ "ClientIPCTypes.ipdlh",
+ "PClientHandle.ipdl",
+ "PClientHandleOp.ipdl",
+ "PClientManager.ipdl",
+ "PClientManagerOp.ipdl",
+ "PClientNavigateOp.ipdl",
+ "PClientSource.ipdl",
+ "PClientSourceOp.ipdl",
+]
+
+include("/ipc/chromium/chromium-config.mozbuild")
+
+FINAL_LIBRARY = "xul"
+
+MOCHITEST_MANIFESTS += []
+
+BROWSER_CHROME_MANIFESTS += []
+
+XPCSHELL_TESTS_MANIFESTS += []
diff --git a/dom/clients/moz.build b/dom/clients/moz.build
new file mode 100644
index 0000000000..e90f9987f7
--- /dev/null
+++ b/dom/clients/moz.build
@@ -0,0 +1,10 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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/.
+
+DIRS += [
+ "api",
+ "manager",
+]