diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 00:47:55 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 00:47:55 +0000 |
commit | 26a029d407be480d791972afb5975cf62c9360a6 (patch) | |
tree | f435a8308119effd964b339f76abb83a57c29483 /dom/clients | |
parent | Initial commit. (diff) | |
download | firefox-e51783d008170d9ab27d25da98ca3a38b0a41b67.tar.xz firefox-e51783d008170d9ab27d25da98ca3a38b0a41b67.zip |
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'dom/clients')
69 files changed, 8167 insertions, 0 deletions
diff --git a/dom/clients/api/Client.cpp b/dom/clients/api/Client.cpp new file mode 100644 index 0000000000..c3dd6175de --- /dev/null +++ b/dom/clients/api/Client.cpp @@ -0,0 +1,205 @@ +/* -*- 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->SerialEventTarget()); + } +} + +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->SerialEventTarget(), __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..a1ad6a49ed --- /dev/null +++ b/dom/clients/api/ClientDOMUtil.h @@ -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/. */ +#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->SerialEventTarget(); + 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..b08a7fa598 --- /dev/null +++ b/dom/clients/api/Clients.cpp @@ -0,0 +1,290 @@ +/* -*- 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->SerialEventTarget(); + 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(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(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..7aa777b1c6 --- /dev/null +++ b/dom/clients/manager/ClientHandle.cpp @@ -0,0 +1,194 @@ +/* -*- 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; + } + + RefPtr<ClientHandleChild> actor = new ClientHandleChild(); + if (!aActor->SendPClientHandleConstructor(actor, mClientInfo.ToIPC())) { + Shutdown(); + return; + } + + ActivateThing(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..273c14b825 --- /dev/null +++ b/dom/clients/manager/ClientHandleChild.h @@ -0,0 +1,51 @@ +/* -*- 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; + + ~ClientHandleChild() = default; + + // 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: + NS_INLINE_DECL_REFCOUNTING(ClientHandleChild, override) + + 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..c5bd4d6586 --- /dev/null +++ b/dom/clients/manager/ClientHandleOpParent.cpp @@ -0,0 +1,97 @@ +/* -*- 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) { + RefPtr<ClientHandleParent> 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..c3432dc2b4 --- /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__, + [self = RefPtr{this}](bool) { + self->mSourcePromiseRequestHolder.Complete(); + ClientSourceParent* source = self->mService->FindExistingSource( + self->mClientId, self->mPrincipalInfo); + if (source) { + self->FoundSource(source); + } + }, + [self = RefPtr{this}](const CopyableErrorResult&) { + self->mSourcePromiseRequestHolder.Complete(); + Unused << Send__delete__(self); + }) + ->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..0b875c89a4 --- /dev/null +++ b/dom/clients/manager/ClientHandleParent.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_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; + + ~ClientHandleParent(); + + // 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: + NS_INLINE_DECL_REFCOUNTING(ClientHandleParent, override) + + 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..46097c1342 --- /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 mozilla::dom::ClientType from "mozilla/dom/ClientsBinding.h"; +using mozilla::dom::FrameType from "mozilla/dom/ClientBinding.h"; +using mozilla::StorageAccess from "mozilla/StorageAccess.h"; +using mozilla::dom::VisibilityState from "mozilla/dom/DocumentBinding.h"; +using mozilla::dom::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..6fdcbcce2c --- /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) noexcept + : mData(std::move(aRight.mData)) {} + +ClientInfo& ClientInfo::operator=(ClientInfo&& aRight) noexcept { + 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: { + const auto& p = PrincipalInfo().get_ContentPrincipalInfo(); + return p.attrs().mPrivateBrowsingId != 0; + } + case PrincipalInfo::TSystemPrincipalInfo: { + return false; + } + case PrincipalInfo::TNullPrincipalInfo: { + const 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 { + 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..0c0ca859e5 --- /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) noexcept; + + ClientInfo& operator=(ClientInfo&& aRight) noexcept; + + 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..e7049cc851 --- /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; + } + + RefPtr<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..3935157460 --- /dev/null +++ b/dom/clients/manager/ClientManagerActors.cpp @@ -0,0 +1,21 @@ +/* -*- 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 { + +already_AddRefed<PClientManagerParent> AllocClientManagerParent() { + return MakeAndAddRef<ClientManagerParent>(); +} + +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..780e662f3e --- /dev/null +++ b/dom/clients/manager/ClientManagerActors.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_ClientManagerActors_h +#define _mozilla_dom_ClientManagerActors_h + +#include "mozilla/AlreadyAddRefed.h" + +namespace mozilla { +namespace dom { + +class PClientManagerParent; + +already_AddRefed<PClientManagerParent> AllocClientManagerParent(); + +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..744758f48d --- /dev/null +++ b/dom/clients/manager/ClientManagerChild.cpp @@ -0,0 +1,118 @@ +/* -*- 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); + } +} + +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(); +} + +// static +already_AddRefed<ClientManagerChild> ClientManagerChild::Create() { + RefPtr<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)) { + return nullptr; + } + } + + return actor.forget(); +} + +ClientManagerChild::ClientManagerChild() + : mManager(nullptr), mTeardownStarted(false) {} + +ClientManagerChild::~ClientManagerChild() = default; + +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..98fc65b0e4 --- /dev/null +++ b/dom/clients/manager/ClientManagerChild.h @@ -0,0 +1,59 @@ +/* -*- 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(); + ~ClientManagerChild(); + + // PClientManagerChild interface + void ActorDestroy(ActorDestroyReason aReason) 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; + + public: + NS_INLINE_DECL_REFCOUNTING(ClientManagerChild, override) + + static already_AddRefed<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..d2dabd1616 --- /dev/null +++ b/dom/clients/manager/ClientManagerParent.cpp @@ -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/. */ + +#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) { + mService->RemoveManager(this); +} + +already_AddRefed<PClientHandleParent> +ClientManagerParent::AllocPClientHandleParent( + const IPCClientInfo& aClientInfo) { + return MakeAndAddRef<ClientHandleParent>(); +} + +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; +} + +already_AddRefed<PClientSourceParent> +ClientManagerParent::AllocPClientSourceParent( + const ClientSourceConstructorArgs& aArgs) { + Maybe<ContentParentId> contentParentId; + + uint64_t childID = ::mozilla::ipc::BackgroundParent::GetChildID(Manager()); + if (childID) { + contentParentId = Some(ContentParentId(childID)); + } + + return MakeAndAddRef<ClientSourceParent>(aArgs, contentParentId); +} + +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() = default; + +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..5b4c586a44 --- /dev/null +++ b/dom/clients/manager/ClientManagerParent.h @@ -0,0 +1,68 @@ +/* -*- 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; + + ~ClientManagerParent(); + + // PClientManagerParent interface + mozilla::ipc::IPCResult RecvTeardown() override; + + void ActorDestroy(ActorDestroyReason aReason) override; + + already_AddRefed<PClientHandleParent> AllocPClientHandleParent( + const IPCClientInfo& aClientInfo) 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; + + already_AddRefed<PClientSourceParent> AllocPClientSourceParent( + const ClientSourceConstructorArgs& aArgs) 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: + NS_INLINE_DECL_REFCOUNTING(ClientManagerParent, override) + + 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..79bfa35a98 --- /dev/null +++ b/dom/clients/manager/ClientManagerService.cpp @@ -0,0 +1,743 @@ +/* -*- 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(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); + + // 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. + ClientNavigateOpConstructorArgs args(WrapNotNull(source), aArgs.url(), + aArgs.baseURL()); + + 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(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..4d47dd826e --- /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.target().AsChild().get()); + 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 = GetMainThreadSerialEventTarget(); + + // 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..e70ecb8a6d --- /dev/null +++ b/dom/clients/manager/ClientOpenWindowUtils.cpp @@ -0,0 +1,463 @@ +/* -*- 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 "nsGlobalWindowOuter.h" +#include "nsIBrowserDOMWindow.h" +#include "nsIDocShell.h" +#include "nsIDocShellTreeOwner.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; + } + + if (NS_WARN_IF(!nsGlobalWindowOuter::Cast(browserWindow)->IsChromeWindow())) { + // XXXbz Can this actually happen? Seems unlikely. + aRv.ThrowTypeError("Unable to open window"); + return; + } + + nsCOMPtr<nsIBrowserDOMWindow> bwin = + nsGlobalWindowOuter::Cast(browserWindow)->GetBrowserDOMWindow(); + + 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) { + // Retrieve the primary content BrowsingContext using the GeckoSession + // ID. The chrome window is named the same as the ID of the GeckoSession + // it is associated with. + RefPtr<BrowsingContext> browsingContext; + nsresult rv = [&sessionId, &browsingContext]() -> nsresult { + nsresult rv; + nsCOMPtr<nsIWindowWatcher> wwatch = + do_GetService(NS_WINDOWWATCHER_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + nsCOMPtr<mozIDOMWindowProxy> chromeWindow; + rv = wwatch->GetWindowByName(sessionId, getter_AddRefs(chromeWindow)); + NS_ENSURE_SUCCESS(rv, rv); + NS_ENSURE_TRUE(chromeWindow, NS_ERROR_FAILURE); + nsCOMPtr<nsIDocShellTreeOwner> treeOwner = + nsPIDOMWindowOuter::From(chromeWindow)->GetTreeOwner(); + NS_ENSURE_TRUE(treeOwner, NS_ERROR_FAILURE); + rv = treeOwner->GetPrimaryContentBrowsingContext( + getter_AddRefs(browsingContext)); + NS_ENSURE_SUCCESS(rv, rv); + NS_ENSURE_TRUE(browsingContext, NS_ERROR_FAILURE); + return NS_OK; + }(); + if (NS_WARN_IF(NS_FAILED(rv))) { + promise->Reject(rv, __func__); + return rv; + } + + 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..0b3e4d9a8e --- /dev/null +++ b/dom/clients/manager/ClientSource.cpp @@ -0,0 +1,711 @@ +/* -*- 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/Try.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()); + RefPtr<ClientSourceChild> actor = new ClientSourceChild(args); + if (!aActor->SendPClientSourceConstructor(actor, args)) { + Shutdown(); + return; + } + + ActivateThing(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..2d5dcd219a --- /dev/null +++ b/dom/clients/manager/ClientSourceChild.h @@ -0,0 +1,54 @@ +/* -*- 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; + + ~ClientSourceChild() = default; + + // 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: + NS_INLINE_DECL_REFCOUNTING(ClientSourceChild, override) + + 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..8364871cd4 --- /dev/null +++ b/dom/clients/manager/ClientSourceParent.cpp @@ -0,0 +1,294 @@ +/* -*- 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(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(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(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..8f3ef78603 --- /dev/null +++ b/dom/clients/manager/ClientSourceParent.h @@ -0,0 +1,92 @@ +/* -*- 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(); + + ~ClientSourceParent(); + + // 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: + NS_INLINE_DECL_REFCOUNTING(ClientSourceParent, override) + + explicit ClientSourceParent(const ClientSourceConstructorArgs& aArgs, + const Maybe<ContentParentId>& aContentParentId); + + 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..b40a841f53 --- /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 { + +[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..0978ffdea8 --- /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 { + +[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..74997e26b3 --- /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 { + +[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", +] |