From 2aa4a82499d4becd2284cdb482213d541b8804dd Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 28 Apr 2024 16:29:10 +0200 Subject: Adding upstream version 86.0.1. Signed-off-by: Daniel Baumann --- dom/clients/manager/ClientChannelHelper.cpp | 371 ++++++++++++ dom/clients/manager/ClientChannelHelper.h | 55 ++ dom/clients/manager/ClientHandle.cpp | 189 ++++++ dom/clients/manager/ClientHandle.h | 108 ++++ dom/clients/manager/ClientHandleChild.cpp | 70 +++ dom/clients/manager/ClientHandleChild.h | 49 ++ dom/clients/manager/ClientHandleOpChild.cpp | 44 ++ dom/clients/manager/ClientHandleOpChild.h | 40 ++ dom/clients/manager/ClientHandleOpParent.cpp | 90 +++ dom/clients/manager/ClientHandleOpParent.h | 37 ++ dom/clients/manager/ClientHandleParent.cpp | 108 ++++ dom/clients/manager/ClientHandleParent.h | 63 ++ dom/clients/manager/ClientIPCTypes.ipdlh | 161 ++++++ dom/clients/manager/ClientIPCUtils.h | 44 ++ dom/clients/manager/ClientInfo.cpp | 131 +++++ dom/clients/manager/ClientInfo.h | 117 ++++ dom/clients/manager/ClientManager.cpp | 348 +++++++++++ dom/clients/manager/ClientManager.h | 128 +++++ dom/clients/manager/ClientManagerActors.cpp | 37 ++ dom/clients/manager/ClientManagerActors.h | 28 + dom/clients/manager/ClientManagerChild.cpp | 137 +++++ dom/clients/manager/ClientManagerChild.h | 68 +++ dom/clients/manager/ClientManagerOpChild.cpp | 50 ++ dom/clients/manager/ClientManagerOpChild.h | 39 ++ dom/clients/manager/ClientManagerOpParent.cpp | 81 +++ dom/clients/manager/ClientManagerOpParent.h | 37 ++ dom/clients/manager/ClientManagerParent.cpp | 109 ++++ dom/clients/manager/ClientManagerParent.h | 65 +++ dom/clients/manager/ClientManagerService.cpp | 613 ++++++++++++++++++++ dom/clients/manager/ClientManagerService.h | 104 ++++ dom/clients/manager/ClientNavigateOpChild.cpp | 334 +++++++++++ dom/clients/manager/ClientNavigateOpChild.h | 35 ++ dom/clients/manager/ClientNavigateOpParent.cpp | 46 ++ dom/clients/manager/ClientNavigateOpParent.h | 34 ++ dom/clients/manager/ClientOpPromise.h | 31 + dom/clients/manager/ClientOpenWindowUtils.cpp | 434 ++++++++++++++ dom/clients/manager/ClientOpenWindowUtils.h | 24 + dom/clients/manager/ClientPrincipalUtils.cpp | 48 ++ dom/clients/manager/ClientPrincipalUtils.h | 23 + dom/clients/manager/ClientSource.cpp | 765 +++++++++++++++++++++++++ dom/clients/manager/ClientSource.h | 187 ++++++ dom/clients/manager/ClientSourceChild.cpp | 71 +++ dom/clients/manager/ClientSourceChild.h | 50 ++ dom/clients/manager/ClientSourceOpChild.cpp | 132 +++++ dom/clients/manager/ClientSourceOpChild.h | 47 ++ dom/clients/manager/ClientSourceOpParent.cpp | 57 ++ dom/clients/manager/ClientSourceOpParent.h | 37 ++ dom/clients/manager/ClientSourceParent.cpp | 297 ++++++++++ dom/clients/manager/ClientSourceParent.h | 91 +++ dom/clients/manager/ClientState.cpp | 167 ++++++ dom/clients/manager/ClientState.h | 127 ++++ dom/clients/manager/ClientThing.h | 140 +++++ dom/clients/manager/ClientValidation.cpp | 148 +++++ dom/clients/manager/ClientValidation.h | 28 + dom/clients/manager/PClientHandle.ipdl | 36 ++ dom/clients/manager/PClientHandleOp.ipdl | 20 + dom/clients/manager/PClientManager.ipdl | 42 ++ dom/clients/manager/PClientManagerOp.ipdl | 20 + dom/clients/manager/PClientNavigateOp.ipdl | 20 + dom/clients/manager/PClientSource.ipdl | 38 ++ dom/clients/manager/PClientSourceOp.ipdl | 20 + dom/clients/manager/moz.build | 69 +++ 62 files changed, 7139 insertions(+) create mode 100644 dom/clients/manager/ClientChannelHelper.cpp create mode 100644 dom/clients/manager/ClientChannelHelper.h create mode 100644 dom/clients/manager/ClientHandle.cpp create mode 100644 dom/clients/manager/ClientHandle.h create mode 100644 dom/clients/manager/ClientHandleChild.cpp create mode 100644 dom/clients/manager/ClientHandleChild.h create mode 100644 dom/clients/manager/ClientHandleOpChild.cpp create mode 100644 dom/clients/manager/ClientHandleOpChild.h create mode 100644 dom/clients/manager/ClientHandleOpParent.cpp create mode 100644 dom/clients/manager/ClientHandleOpParent.h create mode 100644 dom/clients/manager/ClientHandleParent.cpp create mode 100644 dom/clients/manager/ClientHandleParent.h create mode 100644 dom/clients/manager/ClientIPCTypes.ipdlh create mode 100644 dom/clients/manager/ClientIPCUtils.h create mode 100644 dom/clients/manager/ClientInfo.cpp create mode 100644 dom/clients/manager/ClientInfo.h create mode 100644 dom/clients/manager/ClientManager.cpp create mode 100644 dom/clients/manager/ClientManager.h create mode 100644 dom/clients/manager/ClientManagerActors.cpp create mode 100644 dom/clients/manager/ClientManagerActors.h create mode 100644 dom/clients/manager/ClientManagerChild.cpp create mode 100644 dom/clients/manager/ClientManagerChild.h create mode 100644 dom/clients/manager/ClientManagerOpChild.cpp create mode 100644 dom/clients/manager/ClientManagerOpChild.h create mode 100644 dom/clients/manager/ClientManagerOpParent.cpp create mode 100644 dom/clients/manager/ClientManagerOpParent.h create mode 100644 dom/clients/manager/ClientManagerParent.cpp create mode 100644 dom/clients/manager/ClientManagerParent.h create mode 100644 dom/clients/manager/ClientManagerService.cpp create mode 100644 dom/clients/manager/ClientManagerService.h create mode 100644 dom/clients/manager/ClientNavigateOpChild.cpp create mode 100644 dom/clients/manager/ClientNavigateOpChild.h create mode 100644 dom/clients/manager/ClientNavigateOpParent.cpp create mode 100644 dom/clients/manager/ClientNavigateOpParent.h create mode 100644 dom/clients/manager/ClientOpPromise.h create mode 100644 dom/clients/manager/ClientOpenWindowUtils.cpp create mode 100644 dom/clients/manager/ClientOpenWindowUtils.h create mode 100644 dom/clients/manager/ClientPrincipalUtils.cpp create mode 100644 dom/clients/manager/ClientPrincipalUtils.h create mode 100644 dom/clients/manager/ClientSource.cpp create mode 100644 dom/clients/manager/ClientSource.h create mode 100644 dom/clients/manager/ClientSourceChild.cpp create mode 100644 dom/clients/manager/ClientSourceChild.h create mode 100644 dom/clients/manager/ClientSourceOpChild.cpp create mode 100644 dom/clients/manager/ClientSourceOpChild.h create mode 100644 dom/clients/manager/ClientSourceOpParent.cpp create mode 100644 dom/clients/manager/ClientSourceOpParent.h create mode 100644 dom/clients/manager/ClientSourceParent.cpp create mode 100644 dom/clients/manager/ClientSourceParent.h create mode 100644 dom/clients/manager/ClientState.cpp create mode 100644 dom/clients/manager/ClientState.h create mode 100644 dom/clients/manager/ClientThing.h create mode 100644 dom/clients/manager/ClientValidation.cpp create mode 100644 dom/clients/manager/ClientValidation.h create mode 100644 dom/clients/manager/PClientHandle.ipdl create mode 100644 dom/clients/manager/PClientHandleOp.ipdl create mode 100644 dom/clients/manager/PClientManager.ipdl create mode 100644 dom/clients/manager/PClientManagerOp.ipdl create mode 100644 dom/clients/manager/PClientNavigateOp.ipdl create mode 100644 dom/clients/manager/PClientSource.ipdl create mode 100644 dom/clients/manager/PClientSourceOp.ipdl create mode 100644 dom/clients/manager/moz.build (limited to 'dom/clients/manager') diff --git a/dom/clients/manager/ClientChannelHelper.cpp b/dom/clients/manager/ClientChannelHelper.cpp new file mode 100644 index 0000000000..ec23981380 --- /dev/null +++ b/dom/clients/manager/ClientChannelHelper.cpp @@ -0,0 +1,371 @@ +/* -*- 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 "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 mOuter; + nsCOMPtr mEventTarget; + + virtual ~ClientChannelHelper() = default; + + NS_IMETHOD + GetInterface(const nsIID& aIID, void** aResultOut) override { + if (aIID.Equals(NS_GET_IID(nsIChannelEventSink))) { + *aResultOut = static_cast(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 oldLoadInfo = aOldChannel->LoadInfo(); + nsCOMPtr newLoadInfo = aNewChannel->LoadInfo(); + + UniquePtr 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& reservedClientInfo = + oldLoadInfo->GetReservedClientInfo(); + + const Maybe& initialClientInfo = + oldLoadInfo->GetInitialClientInfo(); + + MOZ_DIAGNOSTIC_ASSERT(reservedClientInfo.isNothing() || + initialClientInfo.isNothing()); + + if (reservedClientInfo.isSome()) { + 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 { + // If CheckSameOrigin() worked, then the security manager must exist. + nsIScriptSecurityManager* ssm = nsContentUtils::GetSecurityManager(); + MOZ_DIAGNOSTIC_ASSERT(ssm); + + nsCOMPtr principal; + rv = ssm->GetChannelResultPrincipal(aNewChannel, + getter_AddRefs(principal)); + NS_ENSURE_SUCCESS(rv, rv); + + reservedClient.reset(); + CreateClient(newLoadInfo, principal); + } + + uint32_t redirectMode = nsIHttpChannelInternal::REDIRECT_MODE_MANUAL; + nsCOMPtr 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 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 + + static 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 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() = default; + + void CreateClient(nsILoadInfo* aLoadInfo, nsIPrincipal* aPrincipal) override { + CreateClientForPrincipal(aLoadInfo, aPrincipal, mEventTarget); + } + + public: + static void CreateClientForPrincipal(nsILoadInfo* aLoadInfo, + nsIPrincipal* aPrincipal, + nsISerialEventTarget* aEventTarget) { + // 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 reservedInfo = + ClientManager::CreateInfo(ClientType::Window, aPrincipal); + if (reservedInfo) { + aLoadInfo->SetReservedClientInfo(*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 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 +nsresult AddClientChannelHelperInternal(nsIChannel* aChannel, + Maybe&& aReservedClientInfo, + Maybe&& aInitialClientInfo, + nsISerialEventTarget* aEventTarget) { + MOZ_ASSERT(NS_IsMainThread()); + + Maybe initialClientInfo(std::move(aInitialClientInfo)); + Maybe reservedClientInfo(std::move(aReservedClientInfo)); + MOZ_DIAGNOSTIC_ASSERT(reservedClientInfo.isNothing() || + initialClientInfo.isNothing()); + + nsCOMPtr loadInfo = aChannel->LoadInfo(); + + nsIScriptSecurityManager* ssm = nsContentUtils::GetSecurityManager(); + NS_ENSURE_TRUE(ssm, NS_ERROR_FAILURE); + + nsCOMPtr channelPrincipal; + nsresult rv = ssm->GetChannelResultPrincipal( + aChannel, getter_AddRefs(channelPrincipal)); + 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( + channelPrincipal, &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(channelPrincipal, + &equals); + if (NS_FAILED(rv) || !equals) { + reservedClientInfo.reset(); + } + } + + nsCOMPtr outerCallbacks; + rv = aChannel->GetNotificationCallbacks(getter_AddRefs(outerCallbacks)); + NS_ENSURE_SUCCESS(rv, rv); + + if (initialClientInfo.isNothing() && reservedClientInfo.isNothing()) { + T::CreateClientForPrincipal(loadInfo, channelPrincipal, aEventTarget); + } + + RefPtr helper = new T(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); + + if (initialClientInfo.isSome()) { + loadInfo->SetInitialClientInfo(initialClientInfo.ref()); + } + + if (reservedClientInfo.isSome()) { + loadInfo->SetReservedClientInfo(reservedClientInfo.ref()); + } + + return NS_OK; +} + +nsresult AddClientChannelHelper(nsIChannel* aChannel, + Maybe&& aReservedClientInfo, + Maybe&& aInitialClientInfo, + nsISerialEventTarget* aEventTarget) { + return AddClientChannelHelperInternal( + aChannel, std::move(aReservedClientInfo), std::move(aInitialClientInfo), + aEventTarget); +} + +nsresult AddClientChannelHelperInParent( + nsIChannel* aChannel, Maybe&& aInitialClientInfo) { + Maybe emptyReservedInfo; + return AddClientChannelHelperInternal( + aChannel, std::move(emptyReservedInfo), std::move(aInitialClientInfo), + nullptr); +} + +nsresult AddClientChannelHelperInChild(nsIChannel* aChannel, + nsISerialEventTarget* aEventTarget) { + MOZ_ASSERT(NS_IsMainThread()); + + nsCOMPtr outerCallbacks; + nsresult rv = + aChannel->GetNotificationCallbacks(getter_AddRefs(outerCallbacks)); + NS_ENSURE_SUCCESS(rv, rv); + + RefPtr 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 loadInfo = aChannel->LoadInfo(); + const Maybe& reservedClientInfo = + loadInfo->GetReservedClientInfo(); + + if (reservedClientInfo) { + UniquePtr 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..2a65ed06fe --- /dev/null +++ b/dom/clients/manager/ClientChannelHelper.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_ClientChannelHelper_h +#define _mozilla_dom_ClientChannelHelper_h + +#include "mozilla/Maybe.h" +#include "nsError.h" + +class nsIChannel; +class nsISerialEventTarget; + +namespace mozilla { +namespace 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&& aReservedClientInfo, + Maybe&& 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&& 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 dom +} // namespace mozilla + +#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..be3b322030 --- /dev/null +++ b/dom/clients/manager/ClientHandle.cpp @@ -0,0 +1,189 @@ +/* -*- 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 kungFuGrip = this; + + MaybeExecute( + [&aArgs, kungFuGrip, aRejectCallback, + resolve = std::move(aResolveCallback)](ClientHandleChild* aActor) { + MOZ_DIAGNOSTIC_ASSERT(aActor); + ClientHandleOpChild* actor = new ClientHandleOpChild( + kungFuGrip, aArgs, std::move(resolve), std::move(aRejectCallback)); + if (!aActor->SendPClientHandleOpConstructor(actor, aArgs)) { + // Constructor failure will call reject callback via ActorDestroy() + return; + } + }, + [aRejectCallback] { + MOZ_DIAGNOSTIC_ASSERT(aRejectCallback); + CopyableErrorResult rv; + rv.ThrowInvalidStateError("Client has been destroyed"); + aRejectCallback(rv); + }); +} + +void ClientHandle::OnShutdownThing() { + NS_ASSERT_OWNINGTHREAD(ClientHandle); + if (!mDetachPromise) { + return; + } + mDetachPromise->Resolve(true, __func__); +} + +ClientHandle::ClientHandle(ClientManager* aManager, + nsISerialEventTarget* aSerialEventTarget, + const ClientInfo& aClientInfo) + : mManager(aManager), + mSerialEventTarget(aSerialEventTarget), + mClientInfo(aClientInfo) { + MOZ_DIAGNOSTIC_ASSERT(mManager); + MOZ_DIAGNOSTIC_ASSERT(mSerialEventTarget); + MOZ_ASSERT(mSerialEventTarget->IsOnCurrentThread()); +} + +void ClientHandle::Activate(PClientManagerChild* aActor) { + NS_ASSERT_OWNINGTHREAD(ClientHandle); + + if (IsShutdown()) { + return; + } + + PClientHandleChild* actor = + aActor->SendPClientHandleConstructor(mClientInfo.ToIPC()); + if (!actor) { + Shutdown(); + return; + } + + ActivateThing(static_cast(actor)); +} + +void ClientHandle::ExecutionReady(const ClientInfo& aClientInfo) { + mClientInfo = aClientInfo; +} + +const ClientInfo& ClientHandle::Info() const { return mClientInfo; } + +RefPtr ClientHandle::Control( + const ServiceWorkerDescriptor& aServiceWorker) { + RefPtr 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 ClientHandle::Focus(CallerType aCallerType) { + RefPtr 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 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.BuildClonedMessageDataForBackgroundChild( + GetActor()->Manager()->Manager(), args.clonedData())) { + CopyableErrorResult rv; + rv.ThrowInvalidStateError("Failed to clone data"); + return GenericErrorResultPromise::CreateAndReject(rv, __func__); + } + + RefPtr 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 ClientHandle::OnDetach() { + NS_ASSERT_OWNINGTHREAD(ClientSource); + + if (!mDetachPromise) { + mDetachPromise = new GenericPromise::Private(__func__); + if (IsShutdown()) { + mDetachPromise->Resolve(true, __func__); + } + } + + return mDetachPromise; +} + +} // namespace mozilla::dom diff --git a/dom/clients/manager/ClientHandle.h b/dom/clients/manager/ClientHandle.h new file mode 100644 index 0000000000..18b9dfe917 --- /dev/null +++ b/dom/clients/manager/ClientHandle.h @@ -0,0 +1,108 @@ +/* -*- 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 { + +namespace 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 { + friend class ClientManager; + friend class ClientHandleChild; + + RefPtr mManager; + nsCOMPtr mSerialEventTarget; + RefPtr 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 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 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 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 OnDetach(); + + NS_INLINE_DECL_REFCOUNTING(ClientHandle); +}; + +} // namespace dom +} // namespace mozilla + +#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* aThing) { + MOZ_DIAGNOSTIC_ASSERT(!mHandle); + mHandle = static_cast(aThing); + MOZ_DIAGNOSTIC_ASSERT(mHandle); +} + +void ClientHandleChild::RevokeOwner(ClientThing* aThing) { + MOZ_DIAGNOSTIC_ASSERT(mHandle); + MOZ_DIAGNOSTIC_ASSERT(mHandle == static_cast(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..8508edc069 --- /dev/null +++ b/dom/clients/manager/ClientHandleChild.h @@ -0,0 +1,49 @@ +/* -*- 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 { +namespace dom { + +class ClientHandle; +class ClientInfo; + +template +class ClientThing; + +class ClientHandleChild final : public PClientHandleChild { + ClientHandle* mHandle; + bool mTeardownStarted; + + // PClientHandleChild interface + mozilla::ipc::IPCResult RecvExecutionReady( + const IPCClientInfo& aClientInfo) override; + + void ActorDestroy(ActorDestroyReason aReason) override; + + PClientHandleOpChild* AllocPClientHandleOpChild( + const ClientOpConstructorArgs& aArgs) override; + + bool DeallocPClientHandleOpChild(PClientHandleOpChild* aActor) override; + + public: + ClientHandleChild(); + + void SetOwner(ClientThing* aThing); + + void RevokeOwner(ClientThing* aThing); + + void MaybeStartTeardown(); +}; + +} // namespace dom +} // namespace mozilla + +#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..864a32b700 --- /dev/null +++ b/dom/clients/manager/ClientHandleOpChild.h @@ -0,0 +1,40 @@ +/* -*- 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 { +namespace dom { + +class ClientHandle; + +class ClientHandleOpChild final : public PClientHandleOpChild { + RefPtr 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 dom +} // namespace mozilla + +#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..0b41fca232 --- /dev/null +++ b/dom/clients/manager/ClientHandleOpParent.cpp @@ -0,0 +1,90 @@ +/* -*- 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(Manager()); + return handle->GetSource(); +} + +void ClientHandleOpParent::ActorDestroy(ActorDestroyReason aReason) { + mPromiseRequestHolder.DisconnectIfExists(); + mSourcePromiseRequestHolder.DisconnectIfExists(); +} + +void ClientHandleOpParent::Init(ClientOpConstructorArgs&& aArgs) { + auto handle = static_cast(Manager()); + handle->EnsureSource() + ->Then( + GetCurrentSerialEventTarget(), __func__, + [this, args = std::move(aArgs)](ClientSourceParent* source) mutable { + mSourcePromiseRequestHolder.Complete(); + RefPtr 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.BorrowFromClonedMessageDataForBackgroundParent( + orig.clonedData()); + if (!data.BuildClonedMessageDataForBackgroundParent( + source->Manager()->Manager(), 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..b157b5fc06 --- /dev/null +++ b/dom/clients/manager/ClientHandleOpParent.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_ClientHandleOpParent_h +#define _mozilla_dom_ClientHandleOpParent_h + +#include "ClientOpPromise.h" +#include "mozilla/dom/PClientHandleOpParent.h" +#include "ClientHandleParent.h" + +namespace mozilla { +namespace dom { + +class ClientSourceParent; + +class ClientHandleOpParent final : public PClientHandleOpParent { + MozPromiseRequestHolder mPromiseRequestHolder; + MozPromiseRequestHolder mSourcePromiseRequestHolder; + + ClientSourceParent* GetSource() const; + + // PClientHandleOpParent interface + void ActorDestroy(ActorDestroyReason aReason) override; + + public: + ClientHandleOpParent() = default; + ~ClientHandleOpParent() = default; + + void Init(ClientOpConstructorArgs&& aArgs); +}; + +} // namespace dom +} // namespace mozilla + +#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..a2da92934c --- /dev/null +++ b/dom/clients/manager/ClientHandleParent.cpp @@ -0,0 +1,108 @@ +/* -*- 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 { + mService->StopWaitingForSource(this, mClientId); + } + + if (mSourcePromise) { + CopyableErrorResult rv; + rv.ThrowAbortError("Client aborted"); + mSourcePromise->Reject(rv, __func__); + } +} + +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(aActor); + actor->Init(std::move(const_cast(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(); + mSource = mService->FindSource(aClientInfo.id(), aClientInfo.principalInfo()); + if (!mSource) { + mService->WaitForSource(this, aClientInfo.id()); + return; + } + + mSource->AttachHandle(this); +} + +ClientSourceParent* ClientHandleParent::GetSource() const { return mSource; } + +RefPtr ClientHandleParent::EnsureSource() { + if (mSource) { + return SourcePromise::CreateAndResolve(mSource, __func__); + } + + if (!mSourcePromise) { + mSourcePromise = new SourcePromise::Private(__func__); + } + return mSourcePromise; +} + +void ClientHandleParent::FoundSource(ClientSourceParent* aSource) { + MOZ_ASSERT(aSource->Info().Id() == mClientId); + if (!ClientMatchPrincipalInfo(aSource->Info().PrincipalInfo(), + mPrincipalInfo)) { + if (mSourcePromise) { + CopyableErrorResult rv; + rv.ThrowAbortError("Client aborted"); + mSourcePromise->Reject(rv, __func__); + } + Unused << Send__delete__(this); + return; + } + + mSource = aSource; + mSource->AttachHandle(this); + if (mSourcePromise) { + mSourcePromise->Resolve(aSource, __func__); + } +} + +} // namespace mozilla::dom diff --git a/dom/clients/manager/ClientHandleParent.h b/dom/clients/manager/ClientHandleParent.h new file mode 100644 index 0000000000..3b0219a480 --- /dev/null +++ b/dom/clients/manager/ClientHandleParent.h @@ -0,0 +1,63 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#ifndef _mozilla_dom_ClientHandleParent_h +#define _mozilla_dom_ClientHandleParent_h + +#include "mozilla/dom/PClientHandleParent.h" + +namespace mozilla { +namespace dom { + +class ClientManagerService; +class ClientSourceParent; + +typedef MozPromise + SourcePromise; + +class ClientHandleParent final : public PClientHandleParent { + RefPtr mService; + ClientSourceParent* mSource; + + nsID mClientId; + PrincipalInfo mPrincipalInfo; + + // A promise for HandleOps that want to access our ClientSourceParent. + // Resolved once FoundSource is called and we have a ClientSourceParent + // available. + RefPtr mSourcePromise; + + // PClientHandleParent interface + mozilla::ipc::IPCResult RecvTeardown() override; + + void ActorDestroy(ActorDestroyReason aReason) override; + + PClientHandleOpParent* AllocPClientHandleOpParent( + const ClientOpConstructorArgs& aArgs) override; + + bool DeallocPClientHandleOpParent(PClientHandleOpParent* aActor) override; + + mozilla::ipc::IPCResult RecvPClientHandleOpConstructor( + PClientHandleOpParent* aActor, + const ClientOpConstructorArgs& aArgs) override; + + public: + ClientHandleParent(); + ~ClientHandleParent(); + + void Init(const IPCClientInfo& aClientInfo); + + void FoundSource(ClientSourceParent* aSource); + + ClientSourceParent* GetSource() const; + + RefPtr EnsureSource(); +}; + +} // namespace dom +} // namespace mozilla + +#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..379d6d19a8 --- /dev/null +++ b/dom/clients/manager/ClientIPCTypes.ipdlh @@ -0,0 +1,161 @@ +/* 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 class mozilla::TimeStamp from "mozilla/TimeStamp.h"; +using ClientType from "mozilla/dom/ClientsBinding.h"; +using FrameType from "mozilla/dom/ClientBinding.h"; +using mozilla::StorageAccess from "mozilla/StorageAccess.h"; +using VisibilityState from "mozilla/dom/DocumentBinding.h"; +using CallerType from "mozilla/dom/BindingDeclarations.h"; +using mozilla::CopyableErrorResult from "mozilla/ErrorResult.h"; + +namespace mozilla { +namespace dom { + +struct ClientSourceConstructorArgs +{ + nsID id; + ClientType type; + PrincipalInfo principalInfo; + TimeStamp creationTime; +}; + +comparable struct IPCClientInfo +{ + nsID id; + nsID? agentClusterId; + ClientType type; + PrincipalInfo principalInfo; + TimeStamp creationTime; + nsCString url; + FrameType frameType; + CSPInfo? cspInfo; + CSPInfo? preloadCspInfo; +}; + +struct IPCClientWindowState +{ + VisibilityState visibilityState; + TimeStamp lastFocusTime; + StorageAccess storageAccess; + bool focused; +}; + +struct IPCClientWorkerState +{ + StorageAccess storageAccess; +}; + +union IPCClientState +{ + IPCClientWindowState; + IPCClientWorkerState; +}; + +struct ClientInfoAndState +{ + IPCClientInfo info; + IPCClientState state; +}; + +struct ClientSourceExecutionReadyArgs +{ + nsCString url; + FrameType frameType; +}; + +struct ClientControlledArgs +{ + IPCServiceWorkerDescriptor serviceWorker; +}; + +struct ClientFocusArgs +{ + CallerType callerType; +}; + +struct ClientNavigateArgs +{ + IPCClientInfo target; + nsCString url; + nsCString baseURL; + IPCServiceWorkerDescriptor serviceWorker; +}; + +struct ClientPostMessageArgs +{ + ClonedMessageData clonedData; + IPCServiceWorkerDescriptor serviceWorker; +}; + +struct ClientMatchAllArgs +{ + IPCServiceWorkerDescriptor serviceWorker; + ClientType type; + bool includeUncontrolled; +}; + +struct ClientClaimArgs +{ + IPCServiceWorkerDescriptor serviceWorker; +}; + +struct ClientGetInfoAndStateArgs +{ + nsID id; + PrincipalInfo principalInfo; +}; + +struct ClientOpenWindowArgs +{ + PrincipalInfo principalInfo; + CSPInfo? cspInfo; + nsCString url; + nsCString baseURL; +}; + +union ClientOpConstructorArgs +{ + ClientControlledArgs; + ClientFocusArgs; + ClientNavigateArgs; + ClientPostMessageArgs; + ClientMatchAllArgs; + ClientClaimArgs; + ClientGetInfoAndStateArgs; + ClientOpenWindowArgs; +}; + +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 + : public ContiguousEnumSerializer {}; + +template <> +struct ParamTraits + : public ContiguousEnumSerializer {}; + +template <> +struct ParamTraits + : public ContiguousEnumSerializer< + mozilla::dom::VisibilityState, mozilla::dom::VisibilityState::Hidden, + mozilla::dom::VisibilityState::EndGuard_> {}; + +template <> +struct ParamTraits + : public ContiguousEnumSerializer< + mozilla::StorageAccess, + mozilla::StorageAccess::ePartitionForeignOrDeny, + mozilla::StorageAccess::eNumValues> {}; +} // namespace IPC + +#endif // _mozilla_dom_ClientIPCUtils_h diff --git a/dom/clients/manager/ClientInfo.cpp b/dom/clients/manager/ClientInfo.cpp new file mode 100644 index 0000000000..a87d337f1f --- /dev/null +++ b/dom/clients/manager/ClientInfo.cpp @@ -0,0 +1,131 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "ClientInfo.h" + +#include "mozilla/dom/ClientIPCTypes.h" +#include "mozilla/ipc/BackgroundUtils.h" + +namespace mozilla::dom { + +using mozilla::ipc::PrincipalInfo; +using mozilla::ipc::PrincipalInfoToPrincipal; + +ClientInfo::ClientInfo(const nsID& aId, ClientType aType, + const mozilla::ipc::PrincipalInfo& aPrincipalInfo, + const TimeStamp& aCreationTime) + : mData(MakeUnique(aId, mozilla::Nothing(), aType, + aPrincipalInfo, aCreationTime, ""_ns, + mozilla::dom::FrameType::None, + mozilla::Nothing(), mozilla::Nothing())) { +} + +ClientInfo::ClientInfo(const IPCClientInfo& aData) + : mData(MakeUnique(aData)) {} + +ClientInfo::ClientInfo(const ClientInfo& aRight) { operator=(aRight); } + +ClientInfo& ClientInfo::operator=(const ClientInfo& aRight) { + mData.reset(); + mData = MakeUnique(*aRight.mData); + return *this; +} + +ClientInfo::ClientInfo(ClientInfo&& aRight) : mData(std::move(aRight.mData)) {} + +ClientInfo& ClientInfo::operator=(ClientInfo&& aRight) { + mData.reset(); + mData = std::move(aRight.mData); + return *this; +} + +ClientInfo::~ClientInfo() = default; + +bool ClientInfo::operator==(const ClientInfo& aRight) const { + return *mData == *aRight.mData; +} + +bool ClientInfo::operator!=(const ClientInfo& aRight) const { + return *mData != *aRight.mData; +} + +const nsID& ClientInfo::Id() const { return mData->id(); } + +void ClientInfo::SetAgentClusterId(const nsID& aId) { + MOZ_ASSERT(mData->agentClusterId().isNothing() || + mData->agentClusterId().ref().Equals(aId)); + mData->agentClusterId() = Some(aId); +} + +const Maybe& ClientInfo::AgentClusterId() const { + return mData->agentClusterId(); +} + +ClientType ClientInfo::Type() const { return mData->type(); } + +const mozilla::ipc::PrincipalInfo& ClientInfo::PrincipalInfo() const { + return mData->principalInfo(); +} + +const TimeStamp& ClientInfo::CreationTime() const { + return mData->creationTime(); +} + +const nsCString& ClientInfo::URL() const { return mData->url(); } + +void ClientInfo::SetURL(const nsACString& aURL) { mData->url() = aURL; } + +FrameType ClientInfo::FrameType() const { return mData->frameType(); } + +void ClientInfo::SetFrameType(mozilla::dom::FrameType aFrameType) { + mData->frameType() = aFrameType; +} + +const IPCClientInfo& ClientInfo::ToIPC() const { return *mData; } + +bool ClientInfo::IsPrivateBrowsing() const { + switch (PrincipalInfo().type()) { + case PrincipalInfo::TContentPrincipalInfo: { + auto& p = PrincipalInfo().get_ContentPrincipalInfo(); + return p.attrs().mPrivateBrowsingId != 0; + } + case PrincipalInfo::TSystemPrincipalInfo: { + return false; + } + case PrincipalInfo::TNullPrincipalInfo: { + auto& p = PrincipalInfo().get_NullPrincipalInfo(); + return p.attrs().mPrivateBrowsingId != 0; + } + default: { + // clients should never be expanded principals + MOZ_CRASH("unexpected principal type!"); + } + } +} + +Result, nsresult> ClientInfo::GetPrincipal() const { + MOZ_ASSERT(NS_IsMainThread()); + return PrincipalInfoToPrincipal(PrincipalInfo()); +} + +const Maybe& ClientInfo::GetCspInfo() const { + return mData->cspInfo(); +} + +void ClientInfo::SetCspInfo(const mozilla::ipc::CSPInfo& aCSPInfo) { + mData->cspInfo() = Some(aCSPInfo); +} + +const Maybe& ClientInfo::GetPreloadCspInfo() const { + return mData->preloadCspInfo(); +} + +void ClientInfo::SetPreloadCspInfo( + const mozilla::ipc::CSPInfo& aPreloadCSPInfo) { + mData->preloadCspInfo() = Some(aPreloadCSPInfo); +} + +} // namespace mozilla::dom diff --git a/dom/clients/manager/ClientInfo.h b/dom/clients/manager/ClientInfo.h new file mode 100644 index 0000000000..fd5f874b09 --- /dev/null +++ b/dom/clients/manager/ClientInfo.h @@ -0,0 +1,117 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef _mozilla_dom_ClientInfo_h +#define _mozilla_dom_ClientInfo_h + +#include "X11UndefineNone.h" +#include "mozilla/Maybe.h" +#include "mozilla/TimeStamp.h" +#include "mozilla/UniquePtr.h" +#include "nsCOMPtr.h" +#include "nsString.h" + +class nsIPrincipal; +struct nsID; + +namespace mozilla { + +namespace ipc { +class CSPInfo; +class PrincipalInfo; +} // namespace ipc + +namespace dom { + +class IPCClientInfo; +enum class FrameType : uint8_t; +enum class ClientType : uint8_t; + +// This class provides a simple structure that represents a global living +// in the system. Its thread safe and can be transferred across process +// boundaries. A ClientInfo object can represent either a window or a worker. +class ClientInfo final { + UniquePtr mData; + + public: + ClientInfo(const nsID& aId, ClientType aType, + const mozilla::ipc::PrincipalInfo& aPrincipalInfo, + const TimeStamp& aCreationTime); + + ClientInfo(const ClientInfo& aRight); + + ClientInfo& operator=(const ClientInfo& aRight); + + ClientInfo(ClientInfo&& aRight); + + ClientInfo& operator=(ClientInfo&& aRight); + + explicit ClientInfo(const IPCClientInfo& aData); + + ~ClientInfo(); + + bool operator==(const ClientInfo& aRight) const; + bool operator!=(const ClientInfo& aRight) const; + + // Get the unique identifier chosen at the time of the global's creation. + const nsID& Id() const; + + // This function should only be called on EnsureClientSource(). + // XXX Bug 1579785 will merge this into the constructor (requiring pass a + // AgentClusterId) + void SetAgentClusterId(const nsID& aId); + const Maybe& 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, nsresult> GetPrincipal() const; + + const Maybe& GetCspInfo() const; + void SetCspInfo(const mozilla::ipc::CSPInfo& aCSPInfo); + + const Maybe& 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..b7af6bc225 --- /dev/null +++ b/dom/clients/manager/ClientManager.cpp @@ -0,0 +1,348 @@ +/* -*- 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 "nsContentUtils.h" +#include "prthread.h" + +namespace mozilla::dom { + +using mozilla::ipc::BackgroundChild; +using mozilla::ipc::PBackgroundChild; +using mozilla::ipc::PrincipalInfo; + +namespace { + +const uint32_t kBadThreadLocalIndex = -1; +#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED +const uint32_t kThreadLocalMagic1 = 0x8d57eea6; +const uint32_t kThreadLocalMagic2 = 0x59f375c9; +#endif + +#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED +uint32_t sClientManagerThreadLocalMagic1 = kThreadLocalMagic1; +#endif + +uint32_t sClientManagerThreadLocalIndex = kBadThreadLocalIndex; + +#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED +uint32_t sClientManagerThreadLocalMagic2 = kThreadLocalMagic2; +uint32_t sClientManagerThreadLocalIndexDuplicate = kBadThreadLocalIndex; +#endif + +} // anonymous namespace + +ClientManager::ClientManager() { + PBackgroundChild* parentActor = + BackgroundChild::GetOrCreateForCurrentThread(); + if (NS_WARN_IF(!parentActor)) { + Shutdown(); + return; + } + + ClientManagerChild* actor = ClientManagerChild::Create(); + if (NS_WARN_IF(!actor)) { + Shutdown(); + return; + } + + PClientManagerChild* sentActor = + parentActor->SendPClientManagerConstructor(actor); + if (NS_WARN_IF(!sentActor)) { + Shutdown(); + return; + } + MOZ_DIAGNOSTIC_ASSERT(sentActor == actor); + + ActivateThing(actor); +} + +ClientManager::~ClientManager() { + NS_ASSERT_OWNINGTHREAD(ClientManager); + + Shutdown(); + + MOZ_DIAGNOSTIC_ASSERT(sClientManagerThreadLocalMagic1 == kThreadLocalMagic1); + MOZ_DIAGNOSTIC_ASSERT(sClientManagerThreadLocalMagic2 == kThreadLocalMagic2); + MOZ_DIAGNOSTIC_ASSERT(sClientManagerThreadLocalIndex != kBadThreadLocalIndex); + MOZ_DIAGNOSTIC_ASSERT(sClientManagerThreadLocalIndex == + sClientManagerThreadLocalIndexDuplicate); + MOZ_DIAGNOSTIC_ASSERT(this == + PR_GetThreadPrivate(sClientManagerThreadLocalIndex)); + +#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED + PRStatus status = +#endif + PR_SetThreadPrivate(sClientManagerThreadLocalIndex, nullptr); + MOZ_DIAGNOSTIC_ASSERT(status == PR_SUCCESS); +} + +void ClientManager::Shutdown() { + NS_ASSERT_OWNINGTHREAD(ClientManager); + + if (IsShutdown()) { + return; + } + + ShutdownThing(); +} + +UniquePtr ClientManager::CreateSourceInternal( + ClientType aType, nsISerialEventTarget* aEventTarget, + const PrincipalInfo& aPrincipal) { + NS_ASSERT_OWNINGTHREAD(ClientManager); + + nsID id; + nsresult rv = nsContentUtils::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 source(new ClientSource(this, aEventTarget, args)); + source->Shutdown(); + return source; + } + + ClientSourceConstructorArgs args(id, aType, aPrincipal, TimeStamp::Now()); + UniquePtr source(new ClientSource(this, aEventTarget, args)); + + if (IsShutdown()) { + source->Shutdown(); + return source; + } + + source->Activate(GetActor()); + + return source; +} + +UniquePtr ClientManager::CreateSourceInternal( + const ClientInfo& aClientInfo, nsISerialEventTarget* aEventTarget) { + NS_ASSERT_OWNINGTHREAD(ClientManager); + + ClientSourceConstructorArgs args(aClientInfo.Id(), aClientInfo.Type(), + aClientInfo.PrincipalInfo(), + aClientInfo.CreationTime()); + UniquePtr source(new ClientSource(this, aEventTarget, args)); + + if (IsShutdown()) { + source->Shutdown(); + return source; + } + + source->Activate(GetActor()); + + return source; +} + +already_AddRefed ClientManager::CreateHandleInternal( + const ClientInfo& aClientInfo, nsISerialEventTarget* aSerialEventTarget) { + NS_ASSERT_OWNINGTHREAD(ClientManager); + MOZ_DIAGNOSTIC_ASSERT(aSerialEventTarget); + + RefPtr handle = + new ClientHandle(this, aSerialEventTarget, aClientInfo); + + if (IsShutdown()) { + handle->Shutdown(); + return handle.forget(); + } + + handle->Activate(GetActor()); + + return handle.forget(); +} + +RefPtr ClientManager::StartOp( + const ClientOpConstructorArgs& aArgs, + nsISerialEventTarget* aSerialEventTarget) { + RefPtr 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 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::GetOrCreateForCurrentThread() { + MOZ_DIAGNOSTIC_ASSERT(sClientManagerThreadLocalMagic1 == kThreadLocalMagic1); + MOZ_DIAGNOSTIC_ASSERT(sClientManagerThreadLocalMagic2 == kThreadLocalMagic2); + MOZ_DIAGNOSTIC_ASSERT(sClientManagerThreadLocalIndex != kBadThreadLocalIndex); + MOZ_DIAGNOSTIC_ASSERT(sClientManagerThreadLocalIndex == + sClientManagerThreadLocalIndexDuplicate); + RefPtr cm = static_cast( + 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); + return cm.forget(); +} + +WorkerPrivate* ClientManager::GetWorkerPrivate() const { + NS_ASSERT_OWNINGTHREAD(ClientManager); + MOZ_DIAGNOSTIC_ASSERT(GetActor()); + return GetActor()->GetWorkerPrivate(); +} + +// 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 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 mgr = GetOrCreateForCurrentThread(); + return mgr->CreateSourceInternal(aType, aEventTarget, principalInfo); +} + +// static +UniquePtr ClientManager::CreateSource( + ClientType aType, nsISerialEventTarget* aEventTarget, + const PrincipalInfo& aPrincipal) { + RefPtr mgr = GetOrCreateForCurrentThread(); + return mgr->CreateSourceInternal(aType, aEventTarget, aPrincipal); +} + +// static +UniquePtr ClientManager::CreateSourceFromInfo( + const ClientInfo& aClientInfo, nsISerialEventTarget* aEventTarget) { + RefPtr mgr = GetOrCreateForCurrentThread(); + return mgr->CreateSourceInternal(aClientInfo, aEventTarget); +} + +Maybe 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 = nsContentUtils::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 ClientManager::CreateHandle( + const ClientInfo& aClientInfo, nsISerialEventTarget* aSerialEventTarget) { + RefPtr mgr = GetOrCreateForCurrentThread(); + return mgr->CreateHandleInternal(aClientInfo, aSerialEventTarget); +} + +// static +RefPtr ClientManager::MatchAll( + const ClientMatchAllArgs& aArgs, nsISerialEventTarget* aSerialEventTarget) { + RefPtr mgr = GetOrCreateForCurrentThread(); + return mgr->StartOp(aArgs, aSerialEventTarget); +} + +// static +RefPtr ClientManager::Claim( + const ClientClaimArgs& aArgs, nsISerialEventTarget* aSerialEventTarget) { + RefPtr mgr = GetOrCreateForCurrentThread(); + return mgr->StartOp(aArgs, aSerialEventTarget); +} + +// static +RefPtr ClientManager::GetInfoAndState( + const ClientGetInfoAndStateArgs& aArgs, + nsISerialEventTarget* aSerialEventTarget) { + RefPtr mgr = GetOrCreateForCurrentThread(); + return mgr->StartOp(aArgs, aSerialEventTarget); +} + +// static +RefPtr ClientManager::Navigate( + const ClientNavigateArgs& aArgs, nsISerialEventTarget* aSerialEventTarget) { + RefPtr mgr = GetOrCreateForCurrentThread(); + return mgr->StartOp(aArgs, aSerialEventTarget); +} + +// static +RefPtr ClientManager::OpenWindow( + const ClientOpenWindowArgs& aArgs, + nsISerialEventTarget* aSerialEventTarget) { + RefPtr 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..cb8c4ccf02 --- /dev/null +++ b/dom/clients/manager/ClientManager.h @@ -0,0 +1,128 @@ +/* -*- 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" + +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 { + 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 CreateSourceInternal( + ClientType aType, nsISerialEventTarget* aEventTarget, + const mozilla::ipc::PrincipalInfo& aPrincipal); + + UniquePtr CreateSourceInternal( + const ClientInfo& aClientInfo, nsISerialEventTarget* aEventTarget); + + already_AddRefed 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. + MOZ_MUST_USE RefPtr 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 GetOrCreateForCurrentThread(); + + // Private methods called by ClientSource + mozilla::dom::WorkerPrivate* GetWorkerPrivate() const; + + public: + // 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 CreateSource( + ClientType aType, nsISerialEventTarget* aEventTarget, + nsIPrincipal* aPrincipal); + + static UniquePtr 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 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 CreateInfo(ClientType aType, + nsIPrincipal* aPrincipal); + + static already_AddRefed CreateHandle( + const ClientInfo& aClientInfo, nsISerialEventTarget* aSerialEventTarget); + + static RefPtr MatchAll(const ClientMatchAllArgs& aArgs, + nsISerialEventTarget* aTarget); + + static RefPtr Claim( + const ClientClaimArgs& aArgs, nsISerialEventTarget* aSerialEventTarget); + + static RefPtr GetInfoAndState( + const ClientGetInfoAndStateArgs& aArgs, + nsISerialEventTarget* aSerialEventTarget); + + static RefPtr Navigate( + const ClientNavigateArgs& aArgs, + nsISerialEventTarget* aSerialEventTarget); + + static RefPtr OpenWindow( + const ClientOpenWindowArgs& aArgs, + nsISerialEventTarget* aSerialEventTarget); + + NS_INLINE_DECL_REFCOUNTING(mozilla::dom::ClientManager) +}; + +} // namespace dom +} // namespace mozilla + +#endif // _mozilla_dom_ClientManager_h diff --git a/dom/clients/manager/ClientManagerActors.cpp b/dom/clients/manager/ClientManagerActors.cpp new file mode 100644 index 0000000000..6ec08bf399 --- /dev/null +++ b/dom/clients/manager/ClientManagerActors.cpp @@ -0,0 +1,37 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "ClientManagerChild.h" +#include "ClientManagerParent.h" + +namespace mozilla::dom { + +PClientManagerChild* AllocClientManagerChild() { + MOZ_ASSERT_UNREACHABLE( + "Default ClientManagerChild allocator should not be invoked"); + return nullptr; +} + +bool DeallocClientManagerChild(PClientManagerChild* aActor) { + delete aActor; + return true; +} + +PClientManagerParent* AllocClientManagerParent() { + return new ClientManagerParent(); +} + +bool DeallocClientManagerParent(PClientManagerParent* aActor) { + delete aActor; + return true; +} + +void InitClientManagerParent(PClientManagerParent* aActor) { + auto actor = static_cast(aActor); + actor->Init(); +} + +} // namespace mozilla::dom diff --git a/dom/clients/manager/ClientManagerActors.h b/dom/clients/manager/ClientManagerActors.h new file mode 100644 index 0000000000..b977149014 --- /dev/null +++ b/dom/clients/manager/ClientManagerActors.h @@ -0,0 +1,28 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#ifndef _mozilla_dom_ClientManagerActors_h +#define _mozilla_dom_ClientManagerActors_h + +namespace mozilla { +namespace dom { + +class PClientManagerChild; +class PClientManagerParent; + +PClientManagerChild* AllocClientManagerChild(); + +bool DeallocClientManagerChild(PClientManagerChild* aActor); + +PClientManagerParent* AllocClientManagerParent(); + +bool DeallocClientManagerParent(PClientManagerParent* aActor); + +void InitClientManagerParent(PClientManagerParent* aActor); + +} // namespace dom +} // namespace mozilla + +#endif // _mozilla_dom_ClientManagerActors_h diff --git a/dom/clients/manager/ClientManagerChild.cpp b/dom/clients/manager/ClientManagerChild.cpp new file mode 100644 index 0000000000..b51ff11897 --- /dev/null +++ b/dom/clients/manager/ClientManagerChild.cpp @@ -0,0 +1,137 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "ClientManagerChild.h" + +#include "ClientHandleChild.h" +#include "ClientManagerOpChild.h" +#include "ClientNavigateOpChild.h" +#include "ClientSourceChild.h" + +#include "mozilla/dom/WorkerCommon.h" +#include "mozilla/dom/WorkerRef.h" + +namespace mozilla::dom { + +void ClientManagerChild::ActorDestroy(ActorDestroyReason aReason) { + mIPCWorkerRef = nullptr; + + if (mManager) { + mManager->RevokeActor(this); + + // Revoking the actor link should automatically cause the owner + // to call RevokeOwner() as well. + MOZ_DIAGNOSTIC_ASSERT(!mManager); + } +} + +PClientHandleChild* ClientManagerChild::AllocPClientHandleChild( + const IPCClientInfo& aClientInfo) { + return new ClientHandleChild(); +} + +bool ClientManagerChild::DeallocPClientHandleChild(PClientHandleChild* aActor) { + delete aActor; + return true; +} + +PClientManagerOpChild* ClientManagerChild::AllocPClientManagerOpChild( + const ClientOpConstructorArgs& aArgs) { + MOZ_ASSERT_UNREACHABLE( + "ClientManagerOpChild must be explicitly constructed."); + return nullptr; +} + +bool ClientManagerChild::DeallocPClientManagerOpChild( + PClientManagerOpChild* aActor) { + delete aActor; + return true; +} + +PClientNavigateOpChild* ClientManagerChild::AllocPClientNavigateOpChild( + const ClientNavigateOpConstructorArgs& aArgs) { + return new ClientNavigateOpChild(); +} + +bool ClientManagerChild::DeallocPClientNavigateOpChild( + PClientNavigateOpChild* aActor) { + delete aActor; + return true; +} + +mozilla::ipc::IPCResult ClientManagerChild::RecvPClientNavigateOpConstructor( + PClientNavigateOpChild* aActor, + const ClientNavigateOpConstructorArgs& aArgs) { + auto actor = static_cast(aActor); + actor->Init(aArgs); + return IPC_OK(); +} + +PClientSourceChild* ClientManagerChild::AllocPClientSourceChild( + const ClientSourceConstructorArgs& aArgs) { + return new ClientSourceChild(aArgs); +} + +bool ClientManagerChild::DeallocPClientSourceChild(PClientSourceChild* aActor) { + delete aActor; + return true; +} + +// static +ClientManagerChild* ClientManagerChild::Create() { + ClientManagerChild* actor = new ClientManagerChild(); + + if (!NS_IsMainThread()) { + WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate(); + MOZ_DIAGNOSTIC_ASSERT(workerPrivate); + + RefPtr> helper = + new IPCWorkerRefHelper(actor); + + actor->mIPCWorkerRef = IPCWorkerRef::Create( + workerPrivate, "ClientManagerChild", + [helper] { helper->Actor()->MaybeStartTeardown(); }); + + if (NS_WARN_IF(!actor->mIPCWorkerRef)) { + delete actor; + return nullptr; + } + } + + return actor; +} + +ClientManagerChild::ClientManagerChild() + : mManager(nullptr), mTeardownStarted(false) {} + +void ClientManagerChild::SetOwner(ClientThing* aThing) { + MOZ_DIAGNOSTIC_ASSERT(aThing); + MOZ_DIAGNOSTIC_ASSERT(!mManager); + mManager = aThing; +} + +void ClientManagerChild::RevokeOwner(ClientThing* 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..79e2a9d8f5 --- /dev/null +++ b/dom/clients/manager/ClientManagerChild.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_ClientManagerChild_h +#define _mozilla_dom_ClientManagerChild_h + +#include "ClientThing.h" +#include "mozilla/dom/PClientManagerChild.h" + +namespace mozilla { +namespace dom { + +class IPCWorkerRef; +class WorkerPrivate; + +class ClientManagerChild final : public PClientManagerChild { + ClientThing* mManager; + + RefPtr mIPCWorkerRef; + bool mTeardownStarted; + + ClientManagerChild(); + + // PClientManagerChild interface + void ActorDestroy(ActorDestroyReason aReason) override; + + PClientHandleChild* AllocPClientHandleChild( + const IPCClientInfo& aClientInfo) override; + + bool DeallocPClientHandleChild(PClientHandleChild* aActor) override; + + PClientManagerOpChild* AllocPClientManagerOpChild( + const ClientOpConstructorArgs& aArgs) override; + + bool DeallocPClientManagerOpChild(PClientManagerOpChild* aActor) override; + + PClientNavigateOpChild* AllocPClientNavigateOpChild( + const ClientNavigateOpConstructorArgs& aArgs) override; + + bool DeallocPClientNavigateOpChild(PClientNavigateOpChild* aActor) override; + + mozilla::ipc::IPCResult RecvPClientNavigateOpConstructor( + PClientNavigateOpChild* aActor, + const ClientNavigateOpConstructorArgs& aArgs) override; + + PClientSourceChild* AllocPClientSourceChild( + const ClientSourceConstructorArgs& aArgs) override; + + bool DeallocPClientSourceChild(PClientSourceChild* aActor) override; + + public: + static ClientManagerChild* Create(); + + void SetOwner(ClientThing* aThing); + + void RevokeOwner(ClientThing* aThing); + + void MaybeStartTeardown(); + + mozilla::dom::WorkerPrivate* GetWorkerPrivate() const; +}; + +} // namespace dom +} // namespace mozilla + +#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..8ad56bf922 --- /dev/null +++ b/dom/clients/manager/ClientManagerOpChild.h @@ -0,0 +1,39 @@ +/* -*- 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 { +namespace dom { + +class ClientManager; + +class ClientManagerOpChild final : public PClientManagerOpChild { + RefPtr mClientManager; + RefPtr 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 dom +} // namespace mozilla + +#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..db5c676728 --- /dev/null +++ b/dom/clients/manager/ClientManagerOpParent.cpp @@ -0,0 +1,81 @@ +/* -*- 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/ipc/BackgroundParent.h" + +namespace mozilla::dom { + +using mozilla::ipc::BackgroundParent; + +template +void ClientManagerOpParent::DoServiceOp(Method aMethod, Args&&... aArgs) { + // Note, we need perfect forarding of the template type in order + // to allow already_AddRefed<> to be passed as an arg. + RefPtr p = + (mService->*aMethod)(std::forward(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..3de023a290 --- /dev/null +++ b/dom/clients/manager/ClientManagerOpParent.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_ClientManagerOpParent_h +#define _mozilla_dom_ClientManagerOpParent_h + +#include "mozilla/dom/PClientManagerOpParent.h" +#include "ClientOpPromise.h" + +namespace mozilla { +namespace dom { + +class ClientManagerService; + +class ClientManagerOpParent final : public PClientManagerOpParent { + RefPtr mService; + MozPromiseRequestHolder mPromiseRequestHolder; + + template + 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 dom +} // namespace mozilla + +#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..b5f19a4e80 --- /dev/null +++ b/dom/clients/manager/ClientManagerParent.cpp @@ -0,0 +1,109 @@ +/* -*- 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 "BackgroundParent.h" +#include "ClientHandleParent.h" +#include "ClientManagerOpParent.h" +#include "ClientManagerService.h" +#include "ClientSourceParent.h" +#include "mozilla/dom/PClientNavigateOpParent.h" +#include "mozilla/Unused.h" + +namespace mozilla::dom { + +using mozilla::ipc::IPCResult; + +IPCResult ClientManagerParent::RecvTeardown() { + Unused << Send__delete__(this); + return IPC_OK(); +} + +void ClientManagerParent::ActorDestroy(ActorDestroyReason aReason) {} + +PClientHandleParent* ClientManagerParent::AllocPClientHandleParent( + const IPCClientInfo& aClientInfo) { + return new ClientHandleParent(); +} + +bool ClientManagerParent::DeallocPClientHandleParent( + PClientHandleParent* aActor) { + delete aActor; + return true; +} + +IPCResult ClientManagerParent::RecvPClientHandleConstructor( + PClientHandleParent* aActor, const IPCClientInfo& aClientInfo) { + ClientHandleParent* actor = static_cast(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(aActor); + actor->Init(aArgs); + return IPC_OK(); +} + +PClientNavigateOpParent* ClientManagerParent::AllocPClientNavigateOpParent( + const ClientNavigateOpConstructorArgs& aArgs) { + MOZ_ASSERT_UNREACHABLE( + "ClientNavigateOpParent should be explicitly constructed."); + return nullptr; +} + +bool ClientManagerParent::DeallocPClientNavigateOpParent( + PClientNavigateOpParent* aActor) { + delete aActor; + return true; +} + +PClientSourceParent* ClientManagerParent::AllocPClientSourceParent( + const ClientSourceConstructorArgs& aArgs) { + Maybe contentParentId; + + uint64_t childID = ::mozilla::ipc::BackgroundParent::GetChildID(Manager()); + if (childID) { + contentParentId = Some(ContentParentId(childID)); + } + + return new ClientSourceParent(aArgs, contentParentId); +} + +bool ClientManagerParent::DeallocPClientSourceParent( + PClientSourceParent* aActor) { + delete aActor; + return true; +} + +IPCResult ClientManagerParent::RecvPClientSourceConstructor( + PClientSourceParent* aActor, const ClientSourceConstructorArgs& aArgs) { + ClientSourceParent* actor = static_cast(aActor); + actor->Init(); + return IPC_OK(); +} + +ClientManagerParent::ClientManagerParent() + : mService(ClientManagerService::GetOrCreateInstance()) {} + +ClientManagerParent::~ClientManagerParent() { mService->RemoveManager(this); } + +void ClientManagerParent::Init() { mService->AddManager(this); } + +} // namespace mozilla::dom diff --git a/dom/clients/manager/ClientManagerParent.h b/dom/clients/manager/ClientManagerParent.h new file mode 100644 index 0000000000..edc0604021 --- /dev/null +++ b/dom/clients/manager/ClientManagerParent.h @@ -0,0 +1,65 @@ +/* -*- 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 { +namespace dom { + +class ClientManagerService; + +class ClientManagerParent final : public PClientManagerParent { + RefPtr mService; + + // PClientManagerParent interface + mozilla::ipc::IPCResult RecvTeardown() override; + + void ActorDestroy(ActorDestroyReason aReason) override; + + PClientHandleParent* AllocPClientHandleParent( + const IPCClientInfo& aClientInfo) override; + + bool DeallocPClientHandleParent(PClientHandleParent* aActor) override; + + mozilla::ipc::IPCResult RecvPClientHandleConstructor( + PClientHandleParent* aActor, const IPCClientInfo& aClientInfo) override; + + PClientManagerOpParent* AllocPClientManagerOpParent( + const ClientOpConstructorArgs& aArgs) override; + + bool DeallocPClientManagerOpParent(PClientManagerOpParent* aActor) override; + + mozilla::ipc::IPCResult RecvPClientManagerOpConstructor( + PClientManagerOpParent* aActor, + const ClientOpConstructorArgs& aArgs) override; + + PClientNavigateOpParent* AllocPClientNavigateOpParent( + const ClientNavigateOpConstructorArgs& aArgs) override; + + bool DeallocPClientNavigateOpParent(PClientNavigateOpParent* aActor) override; + + PClientSourceParent* AllocPClientSourceParent( + const ClientSourceConstructorArgs& aArgs) override; + + bool DeallocPClientSourceParent(PClientSourceParent* aActor) override; + + mozilla::ipc::IPCResult RecvPClientSourceConstructor( + PClientSourceParent* aActor, + const ClientSourceConstructorArgs& aArgs) override; + + public: + ClientManagerParent(); + ~ClientManagerParent(); + + void Init(); +}; + +} // namespace dom +} // namespace mozilla + +#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..b4f87e0a26 --- /dev/null +++ b/dom/clients/manager/ClientManagerService.cpp @@ -0,0 +1,613 @@ +/* -*- 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 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 OnShutdown() { + RefPtr ref = new GenericPromise::Private(__func__); + + nsCOMPtr r = + NS_NewRunnableFunction("ClientManagerServer::OnShutdown", [ref]() { + nsCOMPtr svc = + services::GetAsyncShutdownService(); + if (!svc) { + ref->Resolve(true, __func__); + return; + } + + nsCOMPtr phase; + MOZ_ALWAYS_SUCCEEDS(svc->GetXpcomWillShutdown(getter_AddRefs(phase))); + if (!phase) { + ref->Resolve(true, __func__); + return; + } + + nsCOMPtr blocker = + new ClientShutdownBlocker(ref); + nsresult rv = + phase->AddBlocker(blocker, NS_LITERAL_STRING_FROM_CSTRING(__FILE__), + __LINE__, u"ClientManagerService shutdown"_ns); + + if (NS_FAILED(rv)) { + ref->Resolve(true, __func__); + return; + } + }); + + MOZ_ALWAYS_SUCCEEDS( + SchedulerGroup::Dispatch(TaskCategory::Other, r.forget())); + + return ref; +} + +} // anonymous namespace + +ClientManagerService::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 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(mManagerList)) { + Unused << PClientManagerParent::Send__delete__(actor); + } +} + +// static +already_AddRefed +ClientManagerService::GetOrCreateInstance() { + AssertIsOnBackgroundThread(); + + if (!sClientManagerServiceInstance) { + sClientManagerServiceInstance = new ClientManagerService(); + } + + RefPtr ref(sClientManagerServiceInstance); + return ref.forget(); +} + +// static +already_AddRefed ClientManagerService::GetInstance() { + AssertIsOnBackgroundThread(); + + if (!sClientManagerServiceInstance) { + return nullptr; + } + + RefPtr ref(sClientManagerServiceInstance); + return ref.forget(); +} + +bool ClientManagerService::AddSource(ClientSourceParent* aSource) { + AssertIsOnBackgroundThread(); + MOZ_ASSERT(aSource); + auto entry = mSourceTable.LookupForAdd(aSource->Info().Id()); + // 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 (NS_WARN_IF(!!entry)) { + return false; + } + entry.OrInsert([&] { return aSource; }); + + // Now that we've been created, notify any handles that were + // waiting on us. + auto* handles = mPendingHandles.GetValue(aSource->Info().Id()); + if (handles) { + for (auto handle : *handles) { + handle->FoundSource(aSource); + } + } + mPendingHandles.Remove(aSource->Info().Id()); + 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; +} + +ClientSourceParent* ClientManagerService::FindSource( + const nsID& aID, const PrincipalInfo& aPrincipalInfo) { + AssertIsOnBackgroundThread(); + + auto entry = mSourceTable.Lookup(aID); + if (!entry) { + return nullptr; + } + + ClientSourceParent* source = entry.Data(); + if (source->IsFrozen() || + !ClientMatchPrincipalInfo(source->Info().PrincipalInfo(), + aPrincipalInfo)) { + return nullptr; + } + + return source; +} + +void ClientManagerService::WaitForSource(ClientHandleParent* aHandle, + const nsID& aID) { + auto& entry = mPendingHandles.GetOrInsert(aID); + entry.AppendElement(aHandle); +} + +void ClientManagerService::StopWaitingForSource(ClientHandleParent* aHandle, + const nsID& aID) { + auto* entry = mPendingHandles.GetValue(aID); + if (entry) { + entry->RemoveElement(aHandle); + } +} + +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 removed = mManagerList.RemoveElement(aManager); + MOZ_ASSERT(removed); +} + +RefPtr ClientManagerService::Navigate( + const ClientNavigateArgs& aArgs) { + ClientSourceParent* source = + FindSource(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& controller = source->GetController(); + if (controller.isNothing() || + controller.ref().Scope() != serviceWorker.scope() || + controller.ref().Id() != serviceWorker.id()) { + CopyableErrorResult rv; + rv.ThrowTypeError("Client is not controlled by this Service Worker"); + return ClientOpPromise::CreateAndReject(rv, __func__); + } + + PClientManagerParent* manager = source->Manager(); + MOZ_DIAGNOSTIC_ASSERT(manager); + + ClientNavigateOpConstructorArgs args; + args.url() = aArgs.url(); + args.baseURL() = aArgs.baseURL(); + + // This is safe to do because the ClientSourceChild cannot directly delete + // itself. Instead it sends a Teardown message to the parent which then + // calls delete. That means we can be sure that we are not racing with + // source destruction here. + args.targetParent() = source; + + RefPtr 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 mResultPromise; + nsTArray> mPromiseList; + nsTArray 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 GetResultPromise() { + RefPtr kungFuDeathGrip = this; + return mResultPromise->Then( + GetCurrentSerialEventTarget(), __func__, + [kungFuDeathGrip](const ClientOpPromise::ResolveOrRejectValue& aValue) { + return ClientOpPromise::CreateAndResolveOrReject(aValue, __func__); + }); + } + + void AddPromise(RefPtr&& aPromise) { + mPromiseList.AppendElement(std::move(aPromise)); + MOZ_DIAGNOSTIC_ASSERT(mPromiseList.LastElement()); + mOutstandingPromiseCount += 1; + + RefPtr 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 ClientManagerService::MatchAll( + const ClientMatchAllArgs& aArgs) { + AssertIsOnBackgroundThread(); + + ServiceWorkerDescriptor swd(aArgs.serviceWorker()); + const PrincipalInfo& principalInfo = swd.PrincipalInfo(); + + RefPtr promiseList = new PromiseListHolder(); + + for (auto iter = mSourceTable.Iter(); !iter.Done(); iter.Next()) { + ClientSourceParent* source = iter.UserData(); + MOZ_DIAGNOSTIC_ASSERT(source); + + if (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& 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 ClaimOnMainThread( + const ClientInfo& aClientInfo, const ServiceWorkerDescriptor& aDescriptor) { + RefPtr promise = + new ClientOpPromise::Private(__func__); + + nsCOMPtr 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 swm = ServiceWorkerManager::GetInstance(); + NS_ENSURE_TRUE_VOID(swm); + + RefPtr inner = + swm->MaybeClaimClient(clientInfo, desc); + inner->Then( + GetMainThreadSerialEventTarget(), __func__, + [promise](bool aResult) { + promise->Resolve(CopyableErrorResult(), __func__); + }, + [promise](const CopyableErrorResult& aRv) { + promise->Reject(aRv, __func__); + }); + + scopeExit.release(); + }); + + MOZ_ALWAYS_SUCCEEDS( + SchedulerGroup::Dispatch(TaskCategory::Other, r.forget())); + + return promise; +} + +} // anonymous namespace + +RefPtr ClientManagerService::Claim( + const ClientClaimArgs& aArgs) { + AssertIsOnBackgroundThread(); + + const IPCServiceWorkerDescriptor& serviceWorker = aArgs.serviceWorker(); + const PrincipalInfo& principalInfo = serviceWorker.principalInfo(); + + RefPtr promiseList = new PromiseListHolder(); + + for (auto iter = mSourceTable.Iter(); !iter.Done(); iter.Next()) { + ClientSourceParent* source = iter.UserData(); + MOZ_DIAGNOSTIC_ASSERT(source); + + if (source->IsFrozen()) { + continue; + } + + if (!ClientMatchPrincipalInfo(source->Info().PrincipalInfo(), + principalInfo)) { + continue; + } + + const Maybe& 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 (ServiceWorkerParentInterceptEnabled()) { + promiseList->AddPromise(ClaimOnMainThread( + source->Info(), ServiceWorkerDescriptor(serviceWorker))); + } else { + promiseList->AddPromise(source->StartOp(aArgs)); + } + } + + // Maybe finish the promise now in case we didn't find any matching clients. + promiseList->MaybeFinish(); + + return promiseList->GetResultPromise(); +} + +RefPtr ClientManagerService::GetInfoAndState( + const ClientGetInfoAndStateArgs& aArgs) { + ClientSourceParent* source = FindSource(aArgs.id(), aArgs.principalInfo()); + + if (!source) { + CopyableErrorResult rv; + rv.ThrowInvalidStateError("Unknown client"); + return ClientOpPromise::CreateAndReject(rv, __func__); + } + + if (!source->ExecutionReady()) { + RefPtr self = this; + + // rejection ultimately converted to `undefined` in Clients::Get + return source->ExecutionReadyPromise()->Then( + GetCurrentSerialEventTarget(), __func__, + [self, aArgs]() -> RefPtr { + ClientSourceParent* source = + self->FindSource(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 ClientManagerService::OpenWindow( + const ClientOpenWindowArgs& aArgs) { + return InvokeAsync(GetMainThreadSerialEventTarget(), __func__, + [aArgs]() { return ClientOpenWindow(aArgs); }); +} + +bool ClientManagerService::HasWindow( + const Maybe& aContentParentId, + const PrincipalInfo& aPrincipalInfo, const nsID& aClientId) { + AssertIsOnBackgroundThread(); + + ClientSourceParent* source = FindSource(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..695f926aae --- /dev/null +++ b/dom/clients/manager/ClientManagerService.h @@ -0,0 +1,104 @@ +/* -*- 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 "mozilla/dom/ClientIPCTypes.h" +#include "mozilla/dom/ipc/IdType.h" +#include "ClientOpPromise.h" +#include "mozilla/AlreadyAddRefed.h" +#include "mozilla/Assertions.h" +#include "mozilla/Maybe.h" +#include "mozilla/RefPtr.h" +#include "mozilla/dom/ClientIPCTypes.h" +#include "mozilla/dom/ipc/IdType.h" +#include "nsDataHashtable.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; + +// 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 { + // Store the ClientSourceParent objects in a hash table. We want to + // optimize for insertion, removal, and lookup by UUID. + nsDataHashtable mSourceTable; + + // The set of handles waiting for their corresponding ClientSourceParent + // to be created. + nsDataHashtable> mPendingHandles; + + nsTArray mManagerList; + + bool mShutdown; + + ClientManagerService(); + ~ClientManagerService(); + + void Shutdown(); + + public: + static already_AddRefed GetOrCreateInstance(); + + // Returns nullptr if the service is not already created. + static already_AddRefed GetInstance(); + + bool AddSource(ClientSourceParent* aSource); + + bool RemoveSource(ClientSourceParent* aSource); + + ClientSourceParent* FindSource( + const nsID& aID, const mozilla::ipc::PrincipalInfo& aPrincipalInfo); + + // Called when a ClientHandle is created before the corresponding + // ClientSource. Will call FoundSource on the ClientHandleParent when it + // becomes available. + void WaitForSource(ClientHandleParent* aHandle, const nsID& aID); + void StopWaitingForSource(ClientHandleParent* aHandle, const nsID& aID); + + void AddManager(ClientManagerParent* aManager); + + void RemoveManager(ClientManagerParent* aManager); + + RefPtr Navigate(const ClientNavigateArgs& aArgs); + + RefPtr MatchAll(const ClientMatchAllArgs& aArgs); + + RefPtr Claim(const ClientClaimArgs& aArgs); + + RefPtr GetInfoAndState( + const ClientGetInfoAndStateArgs& aArgs); + + RefPtr OpenWindow(const ClientOpenWindowArgs& aArgs); + + bool HasWindow(const Maybe& aContentParentId, + const mozilla::ipc::PrincipalInfo& aPrincipalInfo, + const nsID& aClientId); + + NS_INLINE_DECL_REFCOUNTING(mozilla::dom::ClientManagerService) +}; + +} // namespace dom +} // namespace mozilla + +#endif // _mozilla_dom_ClientManagerService_h diff --git a/dom/clients/manager/ClientNavigateOpChild.cpp b/dom/clients/manager/ClientNavigateOpChild.cpp new file mode 100644 index 0000000000..d1d6c728c5 --- /dev/null +++ b/dom/clients/manager/ClientNavigateOpChild.cpp @@ -0,0 +1,334 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "ClientNavigateOpChild.h" + +#include "ClientState.h" +#include "ClientSource.h" +#include "ClientSourceChild.h" +#include "mozilla/dom/Document.h" +#include "mozilla/Unused.h" +#include "nsIDocShell.h" +#include "nsDocShellLoadState.h" +#include "nsIWebNavigation.h" +#include "nsIWebProgress.h" +#include "nsIWebProgressListener.h" +#include "nsNetUtil.h" +#include "nsPIDOMWindow.h" +#include "nsURLHelper.h" +#include "ReferrerInfo.h" + +namespace mozilla::dom { + +namespace { + +class NavigateLoadListener final : public nsIWebProgressListener, + public nsSupportsWeakReference { + RefPtr mPromise; + RefPtr mOuterWindow; + nsCOMPtr 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 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 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 = innerWindow->GetClientInfo(); + MOZ_DIAGNOSTIC_ASSERT(clientInfo.isSome()); + + Maybe 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 ClientNavigateOpChild::DoNavigate( + const ClientNavigateOpConstructorArgs& aArgs) { + nsCOMPtr 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(aArgs.targetChild()); + MOZ_DIAGNOSTIC_ASSERT(targetActor); + + ClientSource* target = targetActor->GetSource(); + if (!target) { + CopyableErrorResult rv; + rv.ThrowInvalidStateError("Unknown Client"); + return ClientOpPromise::CreateAndReject(rv, __func__); + } + + window = target->GetInnerWindow(); + if (!window) { + CopyableErrorResult rv; + rv.ThrowInvalidStateError("Client load for a destroyed Window"); + return ClientOpPromise::CreateAndReject(rv, __func__); + } + } + + MOZ_ASSERT(NS_IsMainThread()); + + mSerialEventTarget = window->EventTargetFor(TaskCategory::Other); + + // In theory we could do the URL work before paying the IPC overhead + // cost, but in practice its easier to do it here. The ClientHandle + // may be off-main-thread while this method is guaranteed to always + // be main thread. + nsCOMPtr 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 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 doc = window->GetExtantDoc(); + if (!doc || !doc->IsActive()) { + CopyableErrorResult result; + result.ThrowInvalidStateError("Document is not active."); + return ClientOpPromise::CreateAndReject(result, __func__); + } + + nsCOMPtr principal = doc->NodePrincipal(); + + nsCOMPtr docShell = window->GetDocShell(); + nsCOMPtr webProgress = do_GetInterface(docShell); + if (!docShell || !webProgress) { + CopyableErrorResult result; + result.ThrowInvalidStateError( + "Document's browsing context has been discarded"); + return ClientOpPromise::CreateAndReject(result, __func__); + } + + RefPtr loadState = new nsDocShellLoadState(url); + loadState->SetTriggeringPrincipal(principal); + loadState->SetTriggeringSandboxFlags(doc->GetSandboxFlags()); + loadState->SetCsp(doc->GetCsp()); + + auto referrerInfo = MakeRefPtr(*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 promise = + new ClientOpPromise::Private(__func__); + + nsCOMPtr 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 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..58402082ba --- /dev/null +++ b/dom/clients/manager/ClientNavigateOpChild.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_ClientNavigateOpChild_h +#define _mozilla_dom_ClientNavigateOpChild_h + +#include "mozilla/dom/PClientNavigateOpChild.h" +#include "ClientOpPromise.h" + +namespace mozilla { +namespace dom { + +class ClientNavigateOpChild final : public PClientNavigateOpChild { + MozPromiseRequestHolder mPromiseRequestHolder; + nsCOMPtr mSerialEventTarget; + + MOZ_MUST_USE RefPtr DoNavigate( + const ClientNavigateOpConstructorArgs& aArgs); + + // PClientNavigateOpChild interface + void ActorDestroy(ActorDestroyReason aReason) override; + + public: + ClientNavigateOpChild() = default; + ~ClientNavigateOpChild() = default; + + void Init(const ClientNavigateOpConstructorArgs& aArgs); +}; + +} // namespace dom +} // namespace mozilla + +#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..3b0c79c4d3 --- /dev/null +++ b/dom/clients/manager/ClientNavigateOpParent.h @@ -0,0 +1,34 @@ +/* -*- 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 { +namespace dom { + +class ClientNavigateOpParent final : public PClientNavigateOpParent { + RefPtr 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 dom +} // namespace mozilla + +#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..3658a8aabc --- /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; + +typedef MozPromise ClientOpPromise; + +typedef MozPromise ClientStatePromise; + +typedef MozPromise + GenericErrorResultPromise; + +typedef std::function ClientOpCallback; + +} // 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..98f6c7e79d --- /dev/null +++ b/dom/clients/manager/ClientOpenWindowUtils.cpp @@ -0,0 +1,434 @@ +/* -*- 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 "nsContentUtils.h" +#include "nsDocShell.h" +#include "nsDocShellLoadState.h" +#include "nsFocusManager.h" +#include "nsIBrowserDOMWindow.h" +#include "nsIDocShell.h" +#include "nsIDOMChromeWindow.h" +#include "nsIURI.h" +#include "nsIBrowser.h" +#include "nsIWebProgress.h" +#include "nsIWebProgressListener.h" +#include "nsIWindowWatcher.h" +#include "nsIXPConnect.h" +#include "nsNetUtil.h" +#include "nsPIDOMWindow.h" +#include "nsPIWindowWatcher.h" +#include "nsPrintfCString.h" +#include "nsWindowWatcher.h" +#include "nsOpenWindowInfo.h" + +#include "mozilla/dom/BrowserParent.h" +#include "mozilla/dom/BrowsingContext.h" +#include "mozilla/dom/CanonicalBrowsingContext.h" +#include "mozilla/dom/nsCSPContext.h" +#include "mozilla/dom/WindowGlobalParent.h" + +#ifdef MOZ_WIDGET_ANDROID +# include "mozilla/java/GeckoResultWrappers.h" +# include "mozilla/java/GeckoRuntimeWrappers.h" +#endif + +namespace mozilla::dom { + +namespace { + +class WebProgressListener final : public nsIWebProgressListener, + public nsSupportsWeakReference { + public: + NS_DECL_ISUPPORTS + + WebProgressListener(BrowsingContext* aBrowsingContext, nsIURI* aBaseURI, + already_AddRefed 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 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. + nsCOMPtr webProgress = browsingContext->GetWebProgress(); + webProgress->RemoveProgressListener(this); + + RefPtr wgp = + browsingContext->GetCurrentWindowGlobal(); + if (NS_WARN_IF(!wgp)) { + CopyableErrorResult rv; + rv.ThrowInvalidStateError("Unable to open window"); + mPromise->Reject(rv, __func__); + mPromise = nullptr; + return NS_OK; + } + + // Check same origin. If the origins do not match, resolve with null (per + // step 7.2.7.1 of the openWindow spec). + nsCOMPtr securityManager = + nsContentUtils::GetSecurityManager(); + bool isPrivateWin = + wgp->DocumentPrincipal()->OriginAttributesRef().mPrivateBrowsingId > 0; + nsresult rv = securityManager->CheckSameOriginURI( + wgp->GetDocumentURI(), mBaseURI, false, isPrivateWin); + if (NS_FAILED(rv)) { + mPromise->Resolve(CopyableErrorResult(), __func__); + mPromise = nullptr; + return NS_OK; + } + + Maybe 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 mPromise; + nsCOMPtr mBaseURI; + uint64_t mBrowserId; +}; + +NS_IMPL_ISUPPORTS(WebProgressListener, nsIWebProgressListener, + nsISupportsWeakReference); + +struct ClientOpenWindowArgsParsed { + nsCOMPtr uri; + nsCOMPtr baseURI; + nsCOMPtr principal; + nsCOMPtr csp; +}; + +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 browserWindow = + nsContentUtils::GetMostRecentNonPBWindow(); + if (!browserWindow) { + // It is possible to be running without a browser window on Mac OS, so + // we need to open a new chrome window. + // TODO(catalinb): open new chrome window. Bug 1218080 + aRv.ThrowTypeError("Unable to open window"); + return; + } + + nsCOMPtr chromeWin = do_QueryInterface(browserWindow); + if (NS_WARN_IF(!chromeWin)) { + // XXXbz Can this actually happen? Seems unlikely. + aRv.ThrowTypeError("Unable to open window"); + return; + } + + nsCOMPtr bwin; + chromeWin->GetBrowserDOMWindow(getter_AddRefs(bwin)); + + if (NS_WARN_IF(!bwin)) { + aRv.ThrowTypeError("Unable to open window"); + return; + } + nsresult rv = bwin->CreateContentWindow( + nullptr, aOpenInfo, nsIBrowserDOMWindow::OPEN_DEFAULTWINDOW, + nsIBrowserDOMWindow::OPEN_NEW, aArgsValidated.principal, + aArgsValidated.csp, aBC); + if (NS_WARN_IF(NS_FAILED(rv))) { + aRv.ThrowTypeError("Unable to open window"); + return; + } +} + +void WaitForLoad(const ClientOpenWindowArgsParsed& aArgsValidated, + BrowsingContext* aBrowsingContext, + ClientOpPromise::Private* aPromise) { + MOZ_DIAGNOSTIC_ASSERT(aBrowsingContext); + + RefPtr promise = aPromise; + // We can get a WebProgress off of + // the BrowsingContext for the 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 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 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 loadState = + new nsDocShellLoadState(aArgsValidated.uri); + loadState->SetTriggeringPrincipal(aArgsValidated.principal); + loadState->SetFirstParty(true); + loadState->SetLoadFlags( + nsIWebNavigation::LOAD_FLAGS_DISALLOW_INHERIT_PRINCIPAL); + + 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 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::FromGeckoResult( + typedResult); + + promiseResult->Then( + GetMainThreadSerialEventTarget(), __func__, + [aArgsValidated, promise](nsString sessionId) { + nsresult rv; + nsCOMPtr wwatch = + do_GetService(NS_WINDOWWATCHER_CONTRACTID, &rv); + if (NS_WARN_IF(NS_FAILED(rv))) { + promise->Reject(rv, __func__); + return rv; + } + + // Retrieve the browsing context by using the GeckoSession ID. The + // window is named the same as the ID of the GeckoSession it is + // associated with. + RefPtr browsingContext = + static_cast(wwatch.get()) + ->GetBrowsingContextByName(sessionId, false, nullptr); + if (NS_WARN_IF(!browsingContext)) { + promise->Reject(NS_ERROR_FAILURE, __func__); + return NS_ERROR_FAILURE; + } + + WaitForLoad(aArgsValidated, browsingContext, promise); + return NS_OK; + }, + [promise](nsString aResult) { + promise->Reject(NS_ERROR_FAILURE, __func__); + }); +} + +#endif // MOZ_WIDGET_ANDROID + +} // anonymous namespace + +RefPtr ClientOpenWindow(const ClientOpenWindowArgs& aArgs) { + MOZ_DIAGNOSTIC_ASSERT(XRE_IsParentProcess()); + + RefPtr promise = + new ClientOpPromise::Private(__func__); + + // [[1. Let url be the result of parsing url with entry settings object's API + // base URL.]] + nsCOMPtr 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 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 principal = principalOrErr.unwrap(); + MOZ_DIAGNOSTIC_ASSERT(principal); + + nsCOMPtr csp; + if (aArgs.cspInfo().isSome()) { + csp = CSPInfoToCSP(aArgs.cspInfo().ref(), nullptr); + } + ClientOpenWindowArgsParsed argsValidated; + argsValidated.uri = uri; + argsValidated.baseURI = baseURI; + argsValidated.principal = principal; + argsValidated.csp = csp; + +#ifdef MOZ_WIDGET_ANDROID + // If we are on Android we are GeckoView. + GeckoViewOpenWindow(argsValidated, promise); + return promise.forget(); +#endif // MOZ_WIDGET_ANDROID + + RefPtr + browsingContextReadyPromise = + new BrowsingContextCallbackReceivedPromise::Private(__func__); + RefPtr callback = + new nsBrowsingContextReadyCallback(browsingContextReadyPromise); + + RefPtr openInfo = new nsOpenWindowInfo(); + openInfo->mBrowsingContextReadyCallback = callback; + openInfo->mOriginAttributes = principal->OriginAttributesRef(); + openInfo->mIsRemote = true; + + RefPtr 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& 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..213ea34ad4 --- /dev/null +++ b/dom/clients/manager/ClientOpenWindowUtils.h @@ -0,0 +1,24 @@ +/* -*- 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 { +namespace dom { + +typedef MozPromise, CopyableErrorResult, false> + BrowsingContextCallbackReceivedPromise; + +MOZ_MUST_USE RefPtr ClientOpenWindow( + const ClientOpenWindowArgs& aArgs); + +} // namespace dom +} // namespace mozilla + +#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..3c96006fe4 --- /dev/null +++ b/dom/clients/manager/ClientSource.cpp @@ -0,0 +1,765 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "ClientSource.h" + +#include "ClientManager.h" +#include "ClientManagerChild.h" +#include "ClientPrincipalUtils.h" +#include "ClientSourceChild.h" +#include "ClientState.h" +#include "ClientValidation.h" +#include "mozilla/dom/BlobURLProtocolHandler.h" +#include "mozilla/dom/ClientIPCTypes.h" +#include "mozilla/dom/DOMMozPromiseRequestHolder.h" +#include "mozilla/dom/ipc/StructuredCloneData.h" +#include "mozilla/dom/JSExecutionManager.h" +#include "mozilla/dom/MessageEvent.h" +#include "mozilla/dom/MessageEventBinding.h" +#include "mozilla/dom/Navigator.h" +#include "mozilla/dom/WorkerPrivate.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/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 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()) { + return nullptr; + } + return mOwner.as(); +} + +nsIDocShell* ClientSource::GetDocShell() const { + NS_ASSERT_OWNINGTHREAD(ClientSource); + if (!mOwner.is>()) { + return nullptr; + } + return mOwner.as>(); +} + +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 ClientSource::MaybeCreateInitialDocument() { + // If there is not even a docshell, we do not expect to have a document + nsIDocShell* docshell = GetDocShell(); + if (!docshell) { + return false; + } + + // Force the creation of the initial document if it does not yet exist. + if (!docshell->GetDocument()) { + ErrorResult rv; + rv.ThrowInvalidStateError("No document available."); + return Err(std::move(rv)); + } + + return true; +} + +ClientSource::ClientSource(ClientManager* aManager, + nsISerialEventTarget* aEventTarget, + const ClientSourceConstructorArgs& aArgs) + : mManager(aManager), + mEventTarget(aEventTarget), + mOwner(AsVariant(Nothing())), + mClientInfo(aArgs.id(), aArgs.type(), aArgs.principalInfo(), + aArgs.creationTime()) { + MOZ_ASSERT(mManager); + MOZ_ASSERT(mEventTarget); +} + +void ClientSource::Activate(PClientManagerChild* aActor) { + NS_ASSERT_OWNINGTHREAD(ClientSource); + MOZ_ASSERT(!GetActor()); + + if (IsShutdown()) { + return; + } + + // Fast fail if we don't understand this particular kind of PrincipalInfo. + // This can happen since we use MozURL for validation which does not handle + // some of the more obscure internal principal/url combinations. Normal + // content pages will pass this check. + if (NS_WARN_IF(!ClientIsValidPrincipalInfo(mClientInfo.PrincipalInfo()))) { + Shutdown(); + return; + } + + ClientSourceConstructorArgs args(mClientInfo.Id(), mClientInfo.Type(), + mClientInfo.PrincipalInfo(), + mClientInfo.CreationTime()); + PClientSourceChild* actor = aActor->SendPClientSourceConstructor(args); + if (!actor) { + Shutdown(); + return; + } + + ActivateThing(static_cast(actor)); +} + +ClientSource::~ClientSource() { Shutdown(); } + +nsPIDOMWindowInner* ClientSource::GetInnerWindow() const { + NS_ASSERT_OWNINGTHREAD(ClientSource); + if (!mOwner.is>()) { + return nullptr; + } + return mOwner.as>(); +} + +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()); + 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. + if (mController.isSome()) { + MOZ_ASSERT(spec.LowerCaseEqualsLiteral("about:blank") || + StringBeginsWith(spec, "blob:"_ns) || + StorageAllowedForWindow(aInnerWindow) == StorageAccess::eAllow); + } + + 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->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() || + mOwner.is>() || + GetInnerWindow() == aInnerWindow); + + // This creates a cycle with the window. It is broken when + // nsGlobalWindow::FreeInnerObjects() deletes the ClientSource. + mOwner = AsVariant(RefPtr(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->HadOriginalOpener()) { + frameType = FrameType::Auxiliary; + } + + MOZ_DIAGNOSTIC_ASSERT(mOwner.is()); + + // This creates a cycle with the docshell. It is broken when + // nsDocShell::Destroy() deletes the ClientSource. + mOwner = AsVariant(nsCOMPtr(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(); }); +} + +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. + if (GetInnerWindow()) { + MOZ_DIAGNOSTIC_ASSERT(Info().URL().LowerCaseEqualsLiteral("about:blank") || + StringBeginsWith(Info().URL(), "blob:"_ns) || + StorageAllowedForWindow(GetInnerWindow()) == + StorageAccess::eAllow); + } else if (GetWorkerPrivate()) { + MOZ_DIAGNOSTIC_ASSERT( + GetWorkerPrivate()->StorageAccess() > StorageAccess::ePrivateBrowsing || + StringBeginsWith(GetWorkerPrivate()->ScriptURL(), u"blob:"_ns)); + } + + if (mController.isSome() && mController.ref() == aServiceWorker) { + return; + } + + mController.reset(); + mController.emplace(aServiceWorker); + + RefPtr 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 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. + controlAllowed = + Info().URL().LowerCaseEqualsLiteral("about:blank") || + StringBeginsWith(Info().URL(), "blob:"_ns) || + StorageAllowedForWindow(GetInnerWindow()) == StorageAccess::eAllow; + } 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); + + // If we are in legacy child-side intercept mode then we must tell the current + // process SWM that this client inherited a controller. This will only update + // the local SWM data and not send any messages to the ClientManagerService. + if (!ServiceWorkerParentInterceptEnabled()) { + if (GetDocShell()) { + AssertIsOnMainThread(); + + RefPtr swm = ServiceWorkerManager::GetInstance(); + if (swm) { + swm->NoteInheritedController(mClientInfo, aServiceWorker); + } + } else { + WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate(); + MOZ_ASSERT(workerPrivate); + + RefPtr strongWorkerRef = StrongWorkerRef::Create( + workerPrivate, + NS_ConvertUTF16toUTF8(workerPrivate->WorkerName()).get()); + auto threadSafeWorkerRef = + MakeRefPtr(strongWorkerRef); + + nsCOMPtr r = NS_NewRunnableFunction( + __func__, [workerRef = threadSafeWorkerRef, clientInfo = mClientInfo, + serviceWorker = aServiceWorker]() { + MOZ_ASSERT(IsBlobURI(workerRef->Private()->GetBaseURI())); + + RefPtr swm = + ServiceWorkerManager::GetInstance(); + + if (swm) { + swm->NoteInheritedController(clientInfo, serviceWorker); + } + }); + + Unused << NS_WARN_IF( + NS_FAILED(workerPrivate->DispatchToMainThread(r.forget()))); + } + } + + // Also 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& ClientSource::GetController() const { + return mController; +} + +void ClientSource::NoteDOMContentLoaded() { + if (mController.isSome() && !ServiceWorkerParentInterceptEnabled()) { + AssertIsOnMainThread(); + RefPtr swm = ServiceWorkerManager::GetInstance(); + if (swm) { + swm->MaybeCheckNavigationUpdate(mClientInfo); + } + } + + MaybeExecute( + [](PClientSourceChild* aActor) { aActor->SendNoteDOMContentLoaded(); }); +} + +RefPtr 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__); + } + nsPIDOMWindowOuter* outer = nullptr; + + 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 state = SnapshotState(); + if (state.isErr()) { + return ClientOpPromise::CreateAndReject( + CopyableErrorResult(state.unwrapErr()), __func__); + } + + return ClientOpPromise::CreateAndResolve(state.inspect().ToIPC(), __func__); +} + +RefPtr 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 container = + window->Navigator()->ServiceWorker(); + 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 ClientSource::Claim(const ClientClaimArgs& aArgs) { + // The ClientSource::Claim method is only needed in the legacy + // mode where the ServiceWorkerManager is run in each child-process. + // In parent-process mode this method should not be called. + MOZ_DIAGNOSTIC_ASSERT(!ServiceWorkerParentInterceptEnabled()); + + nsIGlobalObject* global = GetGlobal(); + if (NS_WARN_IF(!global)) { + CopyableErrorResult rv; + rv.ThrowInvalidStateError("Browsing context torn down"); + return ClientOpPromise::CreateAndReject(rv, __func__); + } + + // Note, we cannot just mark the ClientSource controlled. We must go through + // the SWM so that it can keep track of which clients are controlled by each + // registration. We must tell the child-process SWM in legacy child-process + // mode. In parent-process service worker mode the SWM is notified in the + // parent-process in ClientManagerService::Claim(). + + RefPtr innerPromise = + new GenericErrorResultPromise::Private(__func__); + ServiceWorkerDescriptor swd(aArgs.serviceWorker()); + + nsCOMPtr r = NS_NewRunnableFunction( + "ClientSource::Claim", + [innerPromise, clientInfo = mClientInfo, swd]() mutable { + RefPtr swm = ServiceWorkerManager::GetInstance(); + if (NS_WARN_IF(!swm)) { + CopyableErrorResult rv; + rv.ThrowInvalidStateError("Browser shutting down"); + innerPromise->Reject(rv, __func__); + return; + } + + RefPtr p = + swm->MaybeClaimClient(clientInfo, swd); + p->ChainTo(innerPromise.forget(), __func__); + }); + + if (NS_IsMainThread()) { + r->Run(); + } else { + MOZ_ALWAYS_SUCCEEDS( + SchedulerGroup::Dispatch(TaskCategory::Other, r.forget())); + } + + RefPtr outerPromise = + new ClientOpPromise::Private(__func__); + + auto holder = + MakeRefPtr>(global); + + innerPromise + ->Then( + mEventTarget, __func__, + [outerPromise, holder](bool aResult) { + holder->Complete(); + outerPromise->Resolve(CopyableErrorResult(), __func__); + }, + [outerPromise, holder](const CopyableErrorResult& aResult) { + holder->Complete(); + outerPromise->Reject(aResult, __func__); + }) + ->Track(*holder); + + return outerPromise; +} + +RefPtr ClientSource::GetInfoAndState( + const ClientGetInfoAndStateArgs& aArgs) { + Result state = SnapshotState(); + if (state.isErr()) { + return ClientOpPromise::CreateAndReject( + CopyableErrorResult(state.unwrapErr()), __func__); + } + + return ClientOpPromise::CreateAndResolve( + ClientInfoAndState(mClientInfo.ToIPC(), state.inspect().ToIPC()), + __func__); +} + +Result 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& ClientSource::GetCspInfo() { + NS_ASSERT_OWNINGTHREAD(ClientSource); + return mClientInfo.GetCspInfo(); +} + +void ClientSource::Traverse(nsCycleCollectionTraversalCallback& aCallback, + const char* aName, uint32_t aFlags) { + if (mOwner.is>()) { + ImplCycleCollectionTraverse( + aCallback, mOwner.as>(), aName, aFlags); + } else if (mOwner.is>()) { + ImplCycleCollectionTraverse(aCallback, mOwner.as>(), + 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); +} + +} // namespace mozilla::dom diff --git a/dom/clients/manager/ClientSource.h b/dom/clients/manager/ClientSource.h new file mode 100644 index 0000000000..844f714a29 --- /dev/null +++ b/dom/clients/manager/ClientSource.h @@ -0,0 +1,187 @@ +/* -*- 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 ClientClaimArgs; +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 { + friend class ClientManager; + + NS_DECL_OWNINGTHREAD + + RefPtr mManager; + nsCOMPtr mEventTarget; + + Variant, nsCOMPtr, + WorkerPrivate*> + mOwner; + + ClientInfo mClientInfo; + Maybe mController; + + // 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 mRegisteringScopeList; + + void Shutdown(); + + void ExecutionReady(const ClientSourceExecutionReadyArgs& aArgs); + + WorkerPrivate* GetWorkerPrivate() const; + + nsIDocShell* GetDocShell() const; + + nsIGlobalObject* GetGlobal() const; + + Result MaybeCreateInitialDocument(); + + Result 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(); + + 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 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& GetController() const; + + // Note that the client has reached DOMContentLoaded. Only applies to window + // clients. + void NoteDOMContentLoaded(); + + RefPtr Focus(const ClientFocusArgs& aArgs); + + RefPtr PostMessage(const ClientPostMessageArgs& aArgs); + + RefPtr Claim(const ClientClaimArgs& aArgs); + + RefPtr GetInfoAndState( + const ClientGetInfoAndStateArgs& aArgs); + + Result SnapshotState(); + + nsISerialEventTarget* EventTarget() const; + + void SetCsp(nsIContentSecurityPolicy* aCsp); + void SetPreloadCsp(nsIContentSecurityPolicy* aPreloadCSP); + void SetCspInfo(const mozilla::ipc::CSPInfo& aCSPInfo); + const Maybe& 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); +}; + +inline void ImplCycleCollectionUnlink(UniquePtr& aField) { + aField.reset(); +} + +inline void ImplCycleCollectionTraverse( + nsCycleCollectionTraversalCallback& aCallback, + UniquePtr& 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..98bc4fba5b --- /dev/null +++ b/dom/clients/manager/ClientSourceChild.cpp @@ -0,0 +1,71 @@ +/* -*- 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(aActor)->ScheduleDeletion(); + return true; +} + +IPCResult ClientSourceChild::RecvPClientSourceOpConstructor( + PClientSourceOpChild* aActor, const ClientOpConstructorArgs& aArgs) { + auto actor = static_cast(aActor); + actor->Init(aArgs); + return IPC_OK(); +} + +ClientSourceChild::ClientSourceChild(const ClientSourceConstructorArgs& aArgs) + : mSource(nullptr), mTeardownStarted(false) {} + +void ClientSourceChild::SetOwner(ClientThing* aThing) { + MOZ_DIAGNOSTIC_ASSERT(aThing); + MOZ_DIAGNOSTIC_ASSERT(!mSource); + mSource = static_cast(aThing); +} + +void ClientSourceChild::RevokeOwner(ClientThing* aThing) { + MOZ_DIAGNOSTIC_ASSERT(mSource); + MOZ_DIAGNOSTIC_ASSERT(mSource == static_cast(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..fe6f423e0d --- /dev/null +++ b/dom/clients/manager/ClientSourceChild.h @@ -0,0 +1,50 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#ifndef _mozilla_dom_ClientSourceChild_h +#define _mozilla_dom_ClientSourceChild_h + +#include "mozilla/dom/PClientSourceChild.h" + +namespace mozilla { +namespace dom { + +class ClientSource; +class ClientSourceConstructorArgs; +template +class ClientThing; + +class ClientSourceChild final : public PClientSourceChild { + ClientSource* mSource; + bool mTeardownStarted; + + // PClientSourceChild interface + void ActorDestroy(ActorDestroyReason aReason) override; + + PClientSourceOpChild* AllocPClientSourceOpChild( + const ClientOpConstructorArgs& aArgs) override; + + bool DeallocPClientSourceOpChild(PClientSourceOpChild* aActor) override; + + mozilla::ipc::IPCResult RecvPClientSourceOpConstructor( + PClientSourceOpChild* aActor, + const ClientOpConstructorArgs& aArgs) override; + + public: + explicit ClientSourceChild(const ClientSourceConstructorArgs& aArgs); + + void SetOwner(ClientThing* aThing); + + void RevokeOwner(ClientThing* aThing); + + ClientSource* GetSource() const; + + void MaybeStartTeardown(); +}; + +} // namespace dom +} // namespace mozilla + +#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..8dadbedec0 --- /dev/null +++ b/dom/clients/manager/ClientSourceOpChild.cpp @@ -0,0 +1,132 @@ +/* -*- 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(Manager()); + return actor->GetSource(); +} + +template +void ClientSourceOpChild::DoSourceOp(Method aMethod, const Args& aArgs) { + RefPtr promise; + nsCOMPtr 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)(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: { + DoSourceOp(&ClientSource::Claim, aArgs.get_ClientClaimArgs()); + break; + } + case ClientOpConstructorArgs::TClientGetInfoAndStateArgs: { + DoSourceOp(&ClientSource::GetInfoAndState, + aArgs.get_ClientGetInfoAndStateArgs()); + 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..2fc975d84d --- /dev/null +++ b/dom/clients/manager/ClientSourceOpChild.h @@ -0,0 +1,47 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#ifndef _mozilla_dom_ClientSourceOpChild_h +#define _mozilla_dom_ClientSourceOpChild_h + +#include "mozilla/dom/FlippedOnce.h" +#include "mozilla/dom/PClientSourceOpChild.h" +#include "ClientOpPromise.h" + +namespace mozilla { +namespace 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 + void DoSourceOp(Method aMethod, const Args& aArgs); + + // PClientSourceOpChild interface + void ActorDestroy(ActorDestroyReason aReason) override; + + MozPromiseRequestHolder mPromiseRequestHolder; + FlippedOnce mDeletionRequested; + FlippedOnce mInitialized; +}; + +} // namespace dom +} // namespace mozilla + +#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(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..55a64b8ea0 --- /dev/null +++ b/dom/clients/manager/ClientSourceOpParent.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_ClientSourceOpParent_h +#define _mozilla_dom_ClientSourceOpParent_h + +#include "mozilla/dom/ClientOpPromise.h" +#include "mozilla/dom/PClientSourceOpParent.h" + +namespace mozilla { +namespace dom { + +class ClientSourceOpParent final : public PClientSourceOpParent { + const ClientOpConstructorArgs mArgs; + RefPtr 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 dom +} // namespace mozilla + +#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..7fbca38ad1 --- /dev/null +++ b/dom/clients/manager/ClientSourceParent.cpp @@ -0,0 +1,297 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "ClientSourceParent.h" + +#include "ClientHandleParent.h" +#include "ClientManagerService.h" +#include "ClientSourceOpParent.h" +#include "ClientValidation.h" +#include "mozilla/dom/ClientIPCTypes.h" +#include "mozilla/dom/ContentParent.h" +#include "mozilla/dom/PClientManagerParent.h" +#include "mozilla/dom/ServiceWorkerManager.h" +#include "mozilla/dom/ServiceWorkerUtils.h" +#include "mozilla/ipc/BackgroundParent.h" +#include "mozilla/SchedulerGroup.h" +#include "mozilla/Unused.h" + +namespace mozilla::dom { + +using mozilla::ipc::AssertIsOnBackgroundThread; +using mozilla::ipc::BackgroundParent; +using mozilla::ipc::IPCResult; +using mozilla::ipc::PrincipalInfo; + +namespace { + +// It would be nice to use a lambda instead of this class, but we cannot +// move capture in lambdas yet and ContentParent cannot be AddRef'd off +// the main thread. +class KillContentParentRunnable final : public Runnable { + RefPtr mContentParent; + + public: + explicit KillContentParentRunnable(RefPtr&& aContentParent) + : Runnable("KillContentParentRunnable"), + mContentParent(std::move(aContentParent)) { + MOZ_ASSERT(mContentParent); + } + + NS_IMETHOD + Run() override { + MOZ_ASSERT(NS_IsMainThread()); + mContentParent->KillHard("invalid ClientSourceParent actor"); + mContentParent = nullptr; + return NS_OK; + } +}; + +} // anonymous namespace + +void ClientSourceParent::KillInvalidChild() { + // Try to get the content process before we destroy the actor below. + RefPtr process = + BackgroundParent::GetContentParent(Manager()->Manager()); + + // First, immediately teardown the ClientSource actor. No matter what + // we want to start this process as soon as possible. + Unused << ClientSourceParent::Send__delete__(this); + + // If we are running in non-e10s, then there is nothing else to do here. + // There is no child process and we don't want to crash the entire browser + // in release builds. In general, though, this should not happen in non-e10s + // so we do assert this condition. + if (!process) { + MOZ_DIAGNOSTIC_ASSERT(false, "invalid ClientSourceParent in non-e10s"); + return; + } + + // In e10s mode we also want to kill the child process. Validation failures + // typically mean someone sent us bogus data over the IPC link. We can't + // trust that process any more. We have to do this on the main thread, so + // there is a small window of time before we kill the process. This is why + // we start the actor destruction immediately above. + nsCOMPtr r = new KillContentParentRunnable(std::move(process)); + MOZ_ALWAYS_SUCCEEDS( + SchedulerGroup::Dispatch(TaskCategory::Other, r.forget())); +} + +mozilla::ipc::IPCResult ClientSourceParent::RecvWorkerSyncPing() { + AssertIsOnBackgroundThread(); + // Do nothing here. This is purely a sync message allowing the child to + // confirm that the actor has been created on the parent process. + return IPC_OK(); +} + +IPCResult ClientSourceParent::RecvTeardown() { + Unused << Send__delete__(this); + return IPC_OK(); +} + +IPCResult ClientSourceParent::RecvExecutionReady( + const ClientSourceExecutionReadyArgs& aArgs) { + // Now that we have the creation URL for the Client we can do some validation + // to make sure the child actor is not giving us garbage. Since we validate + // on the child side as well we treat a failure here as fatal. + if (!ClientIsValidCreationURL(mClientInfo.PrincipalInfo(), aArgs.url())) { + KillInvalidChild(); + return IPC_OK(); + } + + mClientInfo.SetURL(aArgs.url()); + mClientInfo.SetFrameType(aArgs.frameType()); + mExecutionReady = true; + + for (ClientHandleParent* handle : mHandleList) { + Unused << handle->SendExecutionReady(mClientInfo.ToIPC()); + } + + mExecutionReadyPromise.ResolveIfExists(true, __func__); + + return IPC_OK(); +}; + +IPCResult ClientSourceParent::RecvFreeze() { + MOZ_DIAGNOSTIC_ASSERT(!mFrozen); + mFrozen = true; + + // Frozen clients should not be observable. Act as if the client has + // been destroyed. + for (ClientHandleParent* handle : mHandleList.Clone()) { + Unused << ClientHandleParent::Send__delete__(handle); + } + + return IPC_OK(); +} + +IPCResult ClientSourceParent::RecvThaw() { + MOZ_DIAGNOSTIC_ASSERT(mFrozen); + mFrozen = false; + return IPC_OK(); +} + +IPCResult ClientSourceParent::RecvInheritController( + const ClientControlledArgs& aArgs) { + mController.reset(); + mController.emplace(aArgs.serviceWorker()); + + // In parent-side intercept mode we must tell the parent-side SWM about + // this controller inheritence. In legacy client-side mode this is done + // from the ClientSource instead. + if (ServiceWorkerParentInterceptEnabled()) { + nsCOMPtr r = NS_NewRunnableFunction( + "ClientSourceParent::RecvInheritController", + [clientInfo = mClientInfo, controller = mController.ref()]() { + RefPtr swm = + ServiceWorkerManager::GetInstance(); + NS_ENSURE_TRUE_VOID(swm); + + swm->NoteInheritedController(clientInfo, controller); + }); + + MOZ_ALWAYS_SUCCEEDS( + SchedulerGroup::Dispatch(TaskCategory::Other, r.forget())); + } + + return IPC_OK(); +} + +IPCResult ClientSourceParent::RecvNoteDOMContentLoaded() { + if (mController.isSome() && ServiceWorkerParentInterceptEnabled()) { + nsCOMPtr r = + NS_NewRunnableFunction("ClientSourceParent::RecvNoteDOMContentLoaded", + [clientInfo = mClientInfo]() { + RefPtr swm = + ServiceWorkerManager::GetInstance(); + NS_ENSURE_TRUE_VOID(swm); + + swm->MaybeCheckNavigationUpdate(clientInfo); + }); + + MOZ_ALWAYS_SUCCEEDS( + SchedulerGroup::Dispatch(TaskCategory::Other, r.forget())); + } + return IPC_OK(); +} + +void ClientSourceParent::ActorDestroy(ActorDestroyReason aReason) { + DebugOnly removed = mService->RemoveSource(this); + MOZ_ASSERT(removed); + + for (ClientHandleParent* handle : mHandleList.Clone()) { + // This should trigger DetachHandle() to be called removing + // the entry from the mHandleList. + Unused << ClientHandleParent::Send__delete__(handle); + } + MOZ_DIAGNOSTIC_ASSERT(mHandleList.IsEmpty()); +} + +PClientSourceOpParent* ClientSourceParent::AllocPClientSourceOpParent( + const ClientOpConstructorArgs& aArgs) { + MOZ_ASSERT_UNREACHABLE( + "ClientSourceOpParent should be explicitly constructed."); + return nullptr; +} + +bool ClientSourceParent::DeallocPClientSourceOpParent( + PClientSourceOpParent* aActor) { + delete aActor; + return true; +} + +ClientSourceParent::ClientSourceParent( + const ClientSourceConstructorArgs& aArgs, + const Maybe& aContentParentId) + : mClientInfo(aArgs.id(), aArgs.type(), aArgs.principalInfo(), + aArgs.creationTime()), + mContentParentId(aContentParentId), + mService(ClientManagerService::GetOrCreateInstance()), + mExecutionReady(false), + mFrozen(false) {} + +ClientSourceParent::~ClientSourceParent() { + MOZ_DIAGNOSTIC_ASSERT(mHandleList.IsEmpty()); + + mExecutionReadyPromise.RejectIfExists(NS_ERROR_FAILURE, __func__); +} + +void ClientSourceParent::Init() { + // Ensure the principal is reasonable before adding ourself to the service. + // Since we validate the principal on the child side as well, any failure + // here is treated as fatal. + if (NS_WARN_IF(!ClientIsValidPrincipalInfo(mClientInfo.PrincipalInfo()))) { + KillInvalidChild(); + return; + } + + // Its possible for AddSource() to fail if there is already an entry for + // our UUID. This should not normally happen, but could if someone is + // spoofing IPC messages. + if (NS_WARN_IF(!mService->AddSource(this))) { + KillInvalidChild(); + return; + } +} + +const ClientInfo& ClientSourceParent::Info() const { return mClientInfo; } + +bool ClientSourceParent::IsFrozen() const { return mFrozen; } + +bool ClientSourceParent::ExecutionReady() const { return mExecutionReady; } + +RefPtr ClientSourceParent::ExecutionReadyPromise() { + // Only call if ClientSourceParent::ExecutionReady() is false; otherwise, + // the promise will never resolve + MOZ_ASSERT(!mExecutionReady); + return mExecutionReadyPromise.Ensure(__func__); +} + +const Maybe& ClientSourceParent::GetController() + const { + return mController; +} + +void ClientSourceParent::ClearController() { mController.reset(); } + +void ClientSourceParent::AttachHandle(ClientHandleParent* aClientHandle) { + MOZ_DIAGNOSTIC_ASSERT(aClientHandle); + MOZ_DIAGNOSTIC_ASSERT(!mFrozen); + MOZ_ASSERT(!mHandleList.Contains(aClientHandle)); + mHandleList.AppendElement(aClientHandle); +} + +void ClientSourceParent::DetachHandle(ClientHandleParent* aClientHandle) { + MOZ_DIAGNOSTIC_ASSERT(aClientHandle); + MOZ_ASSERT(mHandleList.Contains(aClientHandle)); + mHandleList.RemoveElement(aClientHandle); +} + +RefPtr ClientSourceParent::StartOp( + ClientOpConstructorArgs&& aArgs) { + RefPtr promise = + new ClientOpPromise::Private(__func__); + + // If we are being controlled, remember that data before propagating + // on to the ClientSource. This must be set prior to triggering + // the controllerchange event from the ClientSource since some tests + // expect matchAll() to find the controlled client immediately after. + // If the control operation fails, then we reset the controller value + // to reflect the final state. + if (aArgs.type() == ClientOpConstructorArgs::TClientControlledArgs) { + mController.reset(); + mController.emplace(aArgs.get_ClientControlledArgs().serviceWorker()); + } + + // Constructor failure will reject the promise via ActorDestroy(). + ClientSourceOpParent* actor = + new ClientSourceOpParent(std::move(aArgs), promise); + Unused << SendPClientSourceOpConstructor(actor, actor->Args()); + + return promise; +} + +} // namespace mozilla::dom diff --git a/dom/clients/manager/ClientSourceParent.h b/dom/clients/manager/ClientSourceParent.h new file mode 100644 index 0000000000..b1fcdda561 --- /dev/null +++ b/dom/clients/manager/ClientSourceParent.h @@ -0,0 +1,91 @@ +/* -*- 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 { +namespace dom { + +class ClientHandleParent; +class ClientManagerService; + +class ClientSourceParent final : public PClientSourceParent { + ClientInfo mClientInfo; + Maybe mController; + const Maybe mContentParentId; + RefPtr mService; + nsTArray mHandleList; + MozPromiseHolder mExecutionReadyPromise; + bool mExecutionReady; + bool mFrozen; + + void KillInvalidChild(); + + // PClientSourceParent + mozilla::ipc::IPCResult RecvWorkerSyncPing() override; + + mozilla::ipc::IPCResult RecvTeardown() override; + + mozilla::ipc::IPCResult RecvExecutionReady( + const ClientSourceExecutionReadyArgs& aArgs) override; + + mozilla::ipc::IPCResult RecvFreeze() override; + + mozilla::ipc::IPCResult RecvThaw() override; + + mozilla::ipc::IPCResult RecvInheritController( + const ClientControlledArgs& aArgs) override; + + mozilla::ipc::IPCResult RecvNoteDOMContentLoaded() override; + + void ActorDestroy(ActorDestroyReason aReason) override; + + PClientSourceOpParent* AllocPClientSourceOpParent( + const ClientOpConstructorArgs& aArgs) override; + + bool DeallocPClientSourceOpParent(PClientSourceOpParent* aActor) override; + + public: + explicit ClientSourceParent(const ClientSourceConstructorArgs& aArgs, + const Maybe& aContentParentId); + ~ClientSourceParent(); + + void Init(); + + const ClientInfo& Info() const; + + bool IsFrozen() const; + + bool ExecutionReady() const; + + RefPtr ExecutionReadyPromise(); + + const Maybe& GetController() const; + + void ClearController(); + + bool IsOwnedByProcess(ContentParentId aContentParentId) const { + return mContentParentId && mContentParentId.value() == aContentParentId; + } + + void AttachHandle(ClientHandleParent* aClientSource); + + void DetachHandle(ClientHandleParent* aClientSource); + + RefPtr StartOp(ClientOpConstructorArgs&& aArgs); +}; + +} // namespace dom +} // namespace mozilla + +#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(aVisibilityState, aLastFocusTime, + aStorageAccess, aFocused)) {} + +ClientWindowState::ClientWindowState(const IPCClientWindowState& aData) + : mData(MakeUnique(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(*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(aStorageAccess)) {} + +ClientWorkerState::ClientWorkerState(const IPCClientWorkerState& aData) + : mData(MakeUnique(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(*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(); +} + +const ClientWindowState& ClientState::AsWindowState() const { + return mData.ref().as(); +} + +bool ClientState::IsWorkerState() const { + return mData.isSome() && mData.ref().is(); +} + +const ClientWorkerState& ClientState::AsWorkerState() const { + return mData.ref().as(); +} + +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..3e17d84591 --- /dev/null +++ b/dom/clients/manager/ClientState.h @@ -0,0 +1,127 @@ +/* -*- 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 { +namespace 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 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 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> 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 dom +} // namespace mozilla + +#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..69c8e63414 --- /dev/null +++ b/dom/clients/manager/ClientThing.h @@ -0,0 +1,140 @@ +/* -*- 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 { +namespace 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 +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 + void MaybeExecute( + const Callable& aSuccess, const std::function& 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 dom +} // namespace mozilla + +#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..e021da203e --- /dev/null +++ b/dom/clients/manager/ClientValidation.cpp @@ -0,0 +1,148 @@ +/* -*- 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 specURL; + nsresult rv = MozURL::Init(getter_AddRefs(specURL), content.spec()); + NS_ENSURE_SUCCESS(rv, false); + + // Verify the principal originNoSuffix parses. + RefPtr originURL; + rv = MozURL::Init(getter_AddRefs(originURL), content.originNoSuffix()); + NS_ENSURE_SUCCESS(rv, false); + + nsAutoCString originOrigin; + originURL->Origin(originOrigin); + + nsAutoCString specOrigin; + specURL->Origin(specOrigin); + + // 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 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 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; + } + + // 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..30e394f51c --- /dev/null +++ b/dom/clients/manager/PClientHandle.ipdl @@ -0,0 +1,36 @@ +/* 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 protocol PChildToParentStream; +include protocol PParentToChildStream; +include protocol PFileDescriptorSet; +include protocol PRemoteLazyInputStream; +include ClientIPCTypes; + +include "mozilla/ipc/ProtocolMessageUtils.h"; + +namespace mozilla { +namespace dom { + +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..ad79501804 --- /dev/null +++ b/dom/clients/manager/PClientHandleOp.ipdl @@ -0,0 +1,20 @@ +/* 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 { + +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..0ce05980be --- /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 protocol PChildToParentStream; +include protocol PParentToChildStream; +include protocol PFileDescriptorSet; +include protocol PRemoteLazyInputStream; +include ClientIPCTypes; + +namespace mozilla { +namespace dom { + +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); + +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..20f82c6314 --- /dev/null +++ b/dom/clients/manager/PClientManagerOp.ipdl @@ -0,0 +1,20 @@ +/* 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 { + +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..718aea2d3b --- /dev/null +++ b/dom/clients/manager/PClientNavigateOp.ipdl @@ -0,0 +1,20 @@ +/* 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 { + +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..84e3ae9bec --- /dev/null +++ b/dom/clients/manager/PClientSource.ipdl @@ -0,0 +1,38 @@ +/* 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 protocol PChildToParentStream; +include protocol PParentToChildStream; +include protocol PFileDescriptorSet; +include protocol PRemoteLazyInputStream; +include ClientIPCTypes; + +namespace mozilla { +namespace dom { + +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 __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..c37958a647 --- /dev/null +++ b/dom/clients/manager/PClientSourceOp.ipdl @@ -0,0 +1,20 @@ +/* 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 { + +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..2dfcbffd18 --- /dev/null +++ b/dom/clients/manager/moz.build @@ -0,0 +1,69 @@ +# -*- 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", + "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 += [] -- cgit v1.2.3