diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 19:33:14 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 19:33:14 +0000 |
commit | 36d22d82aa202bb199967e9512281e9a53db42c9 (patch) | |
tree | 105e8c98ddea1c1e4784a60a5a6410fa416be2de /dom/clients/manager/ClientManagerService.cpp | |
parent | Initial commit. (diff) | |
download | firefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.tar.xz firefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.zip |
Adding upstream version 115.7.0esr.upstream/115.7.0esrupstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'dom/clients/manager/ClientManagerService.cpp')
-rw-r--r-- | dom/clients/manager/ClientManagerService.cpp | 745 |
1 files changed, 745 insertions, 0 deletions
diff --git a/dom/clients/manager/ClientManagerService.cpp b/dom/clients/manager/ClientManagerService.cpp new file mode 100644 index 0000000000..b2d4cada95 --- /dev/null +++ b/dom/clients/manager/ClientManagerService.cpp @@ -0,0 +1,745 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "ClientManagerService.h" + +#include "ClientHandleParent.h" +#include "ClientManagerParent.h" +#include "ClientNavigateOpParent.h" +#include "ClientOpenWindowUtils.h" +#include "ClientPrincipalUtils.h" +#include "ClientSourceParent.h" +#include "mozilla/dom/ContentParent.h" +#include "mozilla/dom/ServiceWorkerManager.h" +#include "mozilla/dom/ServiceWorkerUtils.h" +#include "mozilla/ipc/BackgroundParent.h" +#include "mozilla/ipc/PBackgroundSharedTypes.h" +#include "mozilla/ClearOnShutdown.h" +#include "mozilla/MozPromise.h" +#include "mozilla/SchedulerGroup.h" +#include "mozilla/ScopeExit.h" +#include "jsfriendapi.h" +#include "nsIAsyncShutdown.h" +#include "nsIXULRuntime.h" +#include "nsProxyRelease.h" + +namespace mozilla::dom { + +using mozilla::ipc::AssertIsOnBackgroundThread; +using mozilla::ipc::PrincipalInfo; + +namespace { + +ClientManagerService* sClientManagerServiceInstance = nullptr; +bool sClientManagerServiceShutdownRegistered = false; + +class ClientShutdownBlocker final : public nsIAsyncShutdownBlocker { + RefPtr<GenericPromise::Private> mPromise; + + ~ClientShutdownBlocker() = default; + + public: + explicit ClientShutdownBlocker(GenericPromise::Private* aPromise) + : mPromise(aPromise) { + MOZ_DIAGNOSTIC_ASSERT(mPromise); + } + + NS_IMETHOD + GetName(nsAString& aNameOut) override { + aNameOut = nsLiteralString( + u"ClientManagerService: start destroying IPC actors early"); + return NS_OK; + } + + NS_IMETHOD + BlockShutdown(nsIAsyncShutdownClient* aClient) override { + mPromise->Resolve(true, __func__); + aClient->RemoveBlocker(this); + return NS_OK; + } + + NS_IMETHOD + GetState(nsIPropertyBag**) override { return NS_OK; } + + NS_DECL_ISUPPORTS +}; + +NS_IMPL_ISUPPORTS(ClientShutdownBlocker, nsIAsyncShutdownBlocker) + +// Helper function the resolves a MozPromise when we detect that the browser +// has begun to shutdown. +RefPtr<GenericPromise> OnShutdown() { + RefPtr<GenericPromise::Private> ref = new GenericPromise::Private(__func__); + + nsCOMPtr<nsIRunnable> r = + NS_NewRunnableFunction("ClientManagerServer::OnShutdown", [ref]() { + nsCOMPtr<nsIAsyncShutdownService> svc = + services::GetAsyncShutdownService(); + if (!svc) { + ref->Resolve(true, __func__); + return; + } + + nsCOMPtr<nsIAsyncShutdownClient> phase; + MOZ_ALWAYS_SUCCEEDS(svc->GetXpcomWillShutdown(getter_AddRefs(phase))); + if (!phase) { + ref->Resolve(true, __func__); + return; + } + + nsCOMPtr<nsIAsyncShutdownBlocker> blocker = + new ClientShutdownBlocker(ref); + nsresult rv = + phase->AddBlocker(blocker, NS_LITERAL_STRING_FROM_CSTRING(__FILE__), + __LINE__, u"ClientManagerService shutdown"_ns); + + if (NS_FAILED(rv)) { + ref->Resolve(true, __func__); + return; + } + }); + + MOZ_ALWAYS_SUCCEEDS( + SchedulerGroup::Dispatch(TaskCategory::Other, r.forget())); + + return ref; +} + +} // anonymous namespace + +ClientManagerService::FutureClientSourceParent::FutureClientSourceParent( + const IPCClientInfo& aClientInfo) + : mPrincipalInfo(aClientInfo.principalInfo()), mAssociated(false) {} + +ClientManagerService::ClientManagerService() : mShutdown(false) { + AssertIsOnBackgroundThread(); + + // Only register one shutdown handler at a time. If a previous service + // instance did this, but shutdown has not come, then we can avoid + // doing it again. + if (!sClientManagerServiceShutdownRegistered) { + sClientManagerServiceShutdownRegistered = true; + + // While the ClientManagerService will be gracefully terminated as windows + // and workers are naturally killed, this can cause us to do extra work + // relatively late in the shutdown process. To avoid this we eagerly begin + // shutdown at the first sign it has begun. Since we handle normal shutdown + // gracefully we don't really need to block anything here. We just begin + // destroying our IPC actors immediately. + OnShutdown()->Then(GetCurrentSerialEventTarget(), __func__, []() { + // Look up the latest service instance, if it exists. This may + // be different from the instance that registered the shutdown + // handler. + RefPtr<ClientManagerService> svc = ClientManagerService::GetInstance(); + if (svc) { + svc->Shutdown(); + } + }); + } +} + +ClientManagerService::~ClientManagerService() { + AssertIsOnBackgroundThread(); + MOZ_DIAGNOSTIC_ASSERT(mSourceTable.Count() == 0); + MOZ_DIAGNOSTIC_ASSERT(mManagerList.IsEmpty()); + + MOZ_DIAGNOSTIC_ASSERT(sClientManagerServiceInstance == this); + sClientManagerServiceInstance = nullptr; +} + +void ClientManagerService::Shutdown() { + AssertIsOnBackgroundThread(); + MOZ_DIAGNOSTIC_ASSERT(sClientManagerServiceShutdownRegistered); + + // If many ClientManagerService are created and destroyed quickly we can + // in theory get more than one shutdown listener calling us. + if (mShutdown) { + return; + } + mShutdown = true; + + // Begin destroying our various manager actors which will in turn destroy + // all source, handle, and operation actors. + for (auto actor : + CopyableAutoTArray<ClientManagerParent*, 16>(mManagerList)) { + Unused << PClientManagerParent::Send__delete__(actor); + } + + // Destroying manager actors should've also destroyed all source actors, so + // the only sources left will be future sources, which need to be aborted. + for (auto& entry : mSourceTable) { + MOZ_RELEASE_ASSERT(entry.GetData().is<FutureClientSourceParent>()); + CopyableErrorResult rv; + rv.ThrowInvalidStateError("Client creation aborted."); + entry.GetModifiableData() + ->as<FutureClientSourceParent>() + .RejectPromiseIfExists(rv); + } + mSourceTable.Clear(); +} + +ClientSourceParent* ClientManagerService::MaybeUnwrapAsExistingSource( + const SourceTableEntry& aEntry) const { + AssertIsOnBackgroundThread(); + + if (aEntry.is<FutureClientSourceParent>()) { + return nullptr; + } + + return aEntry.as<ClientSourceParent*>(); +} + +ClientSourceParent* ClientManagerService::FindExistingSource( + const nsID& aID, const PrincipalInfo& aPrincipalInfo) const { + AssertIsOnBackgroundThread(); + + auto entry = mSourceTable.Lookup(aID); + + if (!entry) { + return nullptr; + } + + ClientSourceParent* source = MaybeUnwrapAsExistingSource(entry.Data()); + + if (!source || NS_WARN_IF(!ClientMatchPrincipalInfo( + source->Info().PrincipalInfo(), aPrincipalInfo))) { + return nullptr; + } + return source; +} + +// static +already_AddRefed<ClientManagerService> +ClientManagerService::GetOrCreateInstance() { + AssertIsOnBackgroundThread(); + + if (!sClientManagerServiceInstance) { + sClientManagerServiceInstance = new ClientManagerService(); + } + + RefPtr<ClientManagerService> ref(sClientManagerServiceInstance); + return ref.forget(); +} + +// static +already_AddRefed<ClientManagerService> ClientManagerService::GetInstance() { + AssertIsOnBackgroundThread(); + + if (!sClientManagerServiceInstance) { + return nullptr; + } + + RefPtr<ClientManagerService> ref(sClientManagerServiceInstance); + return ref.forget(); +} + +namespace { + +bool IsNullPrincipalInfo(const PrincipalInfo& aPrincipalInfo) { + return aPrincipalInfo.type() == PrincipalInfo::TNullPrincipalInfo; +} + +bool AreBothNullPrincipals(const PrincipalInfo& aPrincipalInfo1, + const PrincipalInfo& aPrincipalInfo2) { + return IsNullPrincipalInfo(aPrincipalInfo1) && + IsNullPrincipalInfo(aPrincipalInfo2); +} + +} // anonymous namespace + +bool ClientManagerService::AddSource(ClientSourceParent* aSource) { + AssertIsOnBackgroundThread(); + MOZ_ASSERT(aSource); + + auto entry = mSourceTable.Lookup(aSource->Info().Id()); + if (entry) { + // Do not permit overwriting an existing ClientSource with the same + // UUID. This would allow a spoofed ClientParentSource actor to + // intercept postMessage() intended for the real actor. + if (entry.Data().is<ClientSourceParent*>()) { + return false; + } + FutureClientSourceParent& placeHolder = + entry.Data().as<FutureClientSourceParent>(); + + const PrincipalInfo& placeHolderPrincipalInfo = placeHolder.PrincipalInfo(); + const PrincipalInfo& sourcePrincipalInfo = aSource->Info().PrincipalInfo(); + + // The placeholder FutureClientSourceParent's PrincipalInfo must match the + // real ClientSourceParent's PrincipalInfo. The only exception is if both + // are null principals (two null principals are considered unequal). + if (!AreBothNullPrincipals(placeHolderPrincipalInfo, sourcePrincipalInfo) && + NS_WARN_IF(!ClientMatchPrincipalInfo(placeHolderPrincipalInfo, + sourcePrincipalInfo))) { + return false; + } + + placeHolder.ResolvePromiseIfExists(); + *entry = AsVariant(aSource); + return true; + } + if (!mSourceTable.WithEntryHandle(aSource->Info().Id(), + [aSource](auto&& entry) { + if (NS_WARN_IF(entry.HasEntry())) { + return false; + } + entry.Insert(AsVariant(aSource)); + return true; + })) { + return false; + } + return true; +} + +bool ClientManagerService::RemoveSource(ClientSourceParent* aSource) { + AssertIsOnBackgroundThread(); + MOZ_ASSERT(aSource); + auto entry = mSourceTable.Lookup(aSource->Info().Id()); + if (NS_WARN_IF(!entry)) { + return false; + } + entry.Remove(); + return true; +} + +bool ClientManagerService::ExpectFutureSource( + const IPCClientInfo& aClientInfo) { + AssertIsOnBackgroundThread(); + + if (!mSourceTable.WithEntryHandle( + aClientInfo.id(), [&aClientInfo](auto&& entry) { + // Prevent overwrites. + if (entry.HasEntry()) { + return false; + } + entry.Insert(SourceTableEntry( + VariantIndex<0>(), FutureClientSourceParent(aClientInfo))); + return true; + })) { + return false; + } + + return true; +} + +void ClientManagerService::ForgetFutureSource( + const IPCClientInfo& aClientInfo) { + AssertIsOnBackgroundThread(); + + auto entry = mSourceTable.Lookup(aClientInfo.id()); + + if (entry) { + if (entry.Data().is<ClientSourceParent*>()) { + return; + } + + // For non-e10s case, ClientChannelHelperParent will be freed before real + // ClientSourceParnet be created. In the end this methoed will be called to + // release the FutureClientSourceParent. That means a ClientHandle operation + // which waits for the FutureClientSourceParent will have no chance to + // connect to the ClientSourceParent. So the FutureClientSourceParent should + // be keep in this case. + // IsAssociated() makes sure there is a ClientHandle operation associated + // with it. + // More details please refer + // https://bugzilla.mozilla.org/show_bug.cgi?id=1730350#c2 + if (!XRE_IsE10sParentProcess() && + entry.Data().as<FutureClientSourceParent>().IsAssociated()) { + return; + } + + CopyableErrorResult rv; + rv.ThrowInvalidStateError("Client creation aborted."); + entry.Data().as<FutureClientSourceParent>().RejectPromiseIfExists(rv); + + entry.Remove(); + } +} + +RefPtr<SourcePromise> ClientManagerService::FindSource( + const nsID& aID, const PrincipalInfo& aPrincipalInfo) { + AssertIsOnBackgroundThread(); + + auto entry = mSourceTable.Lookup(aID); + if (!entry) { + CopyableErrorResult rv; + rv.ThrowInvalidStateError("Unknown client."); + return SourcePromise::CreateAndReject(rv, __func__); + } + + if (entry.Data().is<FutureClientSourceParent>()) { + entry.Data().as<FutureClientSourceParent>().SetAsAssociated(); + return entry.Data().as<FutureClientSourceParent>().Promise(); + } + + ClientSourceParent* source = entry.Data().as<ClientSourceParent*>(); + if (NS_WARN_IF(!ClientMatchPrincipalInfo(source->Info().PrincipalInfo(), + aPrincipalInfo))) { + CopyableErrorResult rv; + rv.ThrowInvalidStateError("Unknown client."); + return SourcePromise::CreateAndReject(rv, __func__); + } + + return SourcePromise::CreateAndResolve(true, __func__); +} + +void ClientManagerService::AddManager(ClientManagerParent* aManager) { + AssertIsOnBackgroundThread(); + MOZ_DIAGNOSTIC_ASSERT(aManager); + MOZ_ASSERT(!mManagerList.Contains(aManager)); + mManagerList.AppendElement(aManager); + + // If shutdown has already begun then immediately destroy the actor. + if (mShutdown) { + Unused << PClientManagerParent::Send__delete__(aManager); + } +} + +void ClientManagerService::RemoveManager(ClientManagerParent* aManager) { + AssertIsOnBackgroundThread(); + MOZ_DIAGNOSTIC_ASSERT(aManager); + DebugOnly<bool> removed = mManagerList.RemoveElement(aManager); + MOZ_ASSERT(removed); +} + +RefPtr<ClientOpPromise> ClientManagerService::Navigate( + ThreadsafeContentParentHandle* aOriginContent, + const ClientNavigateArgs& aArgs) { + ClientSourceParent* source = + FindExistingSource(aArgs.target().id(), aArgs.target().principalInfo()); + if (!source) { + CopyableErrorResult rv; + rv.ThrowInvalidStateError("Unknown client"); + return ClientOpPromise::CreateAndReject(rv, __func__); + } + + const IPCServiceWorkerDescriptor& serviceWorker = aArgs.serviceWorker(); + + // Per https://w3c.github.io/ServiceWorker/#dom-windowclient-navigate step 4, + // if the service worker does not control the client, reject with a TypeError. + const Maybe<ServiceWorkerDescriptor>& controller = source->GetController(); + if (controller.isNothing() || + controller.ref().Scope() != serviceWorker.scope() || + controller.ref().Id() != serviceWorker.id()) { + CopyableErrorResult rv; + rv.ThrowTypeError("Client is not controlled by this Service Worker"); + return ClientOpPromise::CreateAndReject(rv, __func__); + } + + PClientManagerParent* manager = source->Manager(); + MOZ_DIAGNOSTIC_ASSERT(manager); + + // This is safe to do because the ClientSourceChild cannot directly delete + // itself. Instead it sends a Teardown message to the parent which then + // calls delete. That means we can be sure that we are not racing with + // source destruction here. + ClientNavigateOpConstructorArgs args(WrapNotNull(source), aArgs.url(), + aArgs.baseURL()); + + RefPtr<ClientOpPromise::Private> promise = + new ClientOpPromise::Private(__func__); + + ClientNavigateOpParent* op = new ClientNavigateOpParent(args, promise); + PClientNavigateOpParent* result = + manager->SendPClientNavigateOpConstructor(op, args); + if (!result) { + CopyableErrorResult rv; + rv.ThrowInvalidStateError("Client is aborted"); + promise->Reject(rv, __func__); + } + + return promise; +} + +namespace { + +class PromiseListHolder final { + RefPtr<ClientOpPromise::Private> mResultPromise; + nsTArray<RefPtr<ClientOpPromise>> mPromiseList; + nsTArray<ClientInfoAndState> mResultList; + uint32_t mOutstandingPromiseCount; + + void ProcessSuccess(const ClientInfoAndState& aResult) { + mResultList.AppendElement(aResult); + ProcessCompletion(); + } + + void ProcessCompletion() { + MOZ_DIAGNOSTIC_ASSERT(mOutstandingPromiseCount > 0); + mOutstandingPromiseCount -= 1; + MaybeFinish(); + } + + ~PromiseListHolder() = default; + + public: + PromiseListHolder() + : mResultPromise(new ClientOpPromise::Private(__func__)), + mOutstandingPromiseCount(0) {} + + RefPtr<ClientOpPromise> GetResultPromise() { + RefPtr<PromiseListHolder> kungFuDeathGrip = this; + return mResultPromise->Then( + GetCurrentSerialEventTarget(), __func__, + [kungFuDeathGrip](const ClientOpPromise::ResolveOrRejectValue& aValue) { + return ClientOpPromise::CreateAndResolveOrReject(aValue, __func__); + }); + } + + void AddPromise(RefPtr<ClientOpPromise>&& aPromise) { + mPromiseList.AppendElement(std::move(aPromise)); + MOZ_DIAGNOSTIC_ASSERT(mPromiseList.LastElement()); + mOutstandingPromiseCount += 1; + + RefPtr<PromiseListHolder> self(this); + mPromiseList.LastElement()->Then( + GetCurrentSerialEventTarget(), __func__, + [self](const ClientOpResult& aResult) { + // TODO: This is pretty clunky. Try to figure out a better + // wait for MatchAll() and Claim() to share this code + // even though they expect different return values. + if (aResult.type() == ClientOpResult::TClientInfoAndState) { + self->ProcessSuccess(aResult.get_ClientInfoAndState()); + } else { + self->ProcessCompletion(); + } + }, + [self](const CopyableErrorResult& aResult) { + self->ProcessCompletion(); + }); + } + + void MaybeFinish() { + if (!mOutstandingPromiseCount) { + mResultPromise->Resolve(CopyableTArray(mResultList.Clone()), __func__); + } + } + + NS_INLINE_DECL_REFCOUNTING(PromiseListHolder) +}; + +} // anonymous namespace + +RefPtr<ClientOpPromise> ClientManagerService::MatchAll( + ThreadsafeContentParentHandle* aOriginContent, + const ClientMatchAllArgs& aArgs) { + AssertIsOnBackgroundThread(); + + ServiceWorkerDescriptor swd(aArgs.serviceWorker()); + const PrincipalInfo& principalInfo = swd.PrincipalInfo(); + + RefPtr<PromiseListHolder> promiseList = new PromiseListHolder(); + + for (const auto& entry : mSourceTable) { + ClientSourceParent* source = MaybeUnwrapAsExistingSource(entry.GetData()); + + if (!source || source->IsFrozen() || !source->ExecutionReady()) { + continue; + } + + if (aArgs.type() != ClientType::All && + source->Info().Type() != aArgs.type()) { + continue; + } + + if (!ClientMatchPrincipalInfo(source->Info().PrincipalInfo(), + principalInfo)) { + continue; + } + + if (!aArgs.includeUncontrolled()) { + const Maybe<ServiceWorkerDescriptor>& controller = + source->GetController(); + if (controller.isNothing()) { + continue; + } + + if (controller.ref().Id() != swd.Id() || + controller.ref().Scope() != swd.Scope()) { + continue; + } + } + + promiseList->AddPromise(source->StartOp(ClientGetInfoAndStateArgs( + source->Info().Id(), source->Info().PrincipalInfo()))); + } + + // Maybe finish the promise now in case we didn't find any matching clients. + promiseList->MaybeFinish(); + + return promiseList->GetResultPromise(); +} + +namespace { + +RefPtr<ClientOpPromise> ClaimOnMainThread( + const ClientInfo& aClientInfo, const ServiceWorkerDescriptor& aDescriptor) { + RefPtr<ClientOpPromise::Private> promise = + new ClientOpPromise::Private(__func__); + + nsCOMPtr<nsIRunnable> r = NS_NewRunnableFunction( + __func__, [promise, clientInfo = std::move(aClientInfo), + desc = std::move(aDescriptor)]() { + auto scopeExit = MakeScopeExit([&] { + // This will truncate the URLs if they have embedded nulls, if that + // can happen, but for // our purposes here that's OK. + nsPrintfCString err( + "Service worker at <%s> can't claim Client at <%s>", + desc.ScriptURL().get(), clientInfo.URL().get()); + CopyableErrorResult rv; + rv.ThrowInvalidStateError(err); + promise->Reject(rv, __func__); + }); + + RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance(); + NS_ENSURE_TRUE_VOID(swm); + + RefPtr<GenericErrorResultPromise> inner = + swm->MaybeClaimClient(clientInfo, desc); + inner->Then( + GetMainThreadSerialEventTarget(), __func__, + [promise](bool aResult) { + promise->Resolve(CopyableErrorResult(), __func__); + }, + [promise](const CopyableErrorResult& aRv) { + promise->Reject(aRv, __func__); + }); + + scopeExit.release(); + }); + + MOZ_ALWAYS_SUCCEEDS( + SchedulerGroup::Dispatch(TaskCategory::Other, r.forget())); + + return promise; +} + +} // anonymous namespace + +RefPtr<ClientOpPromise> ClientManagerService::Claim( + ThreadsafeContentParentHandle* aOriginContent, + const ClientClaimArgs& aArgs) { + AssertIsOnBackgroundThread(); + + const IPCServiceWorkerDescriptor& serviceWorker = aArgs.serviceWorker(); + const PrincipalInfo& principalInfo = serviceWorker.principalInfo(); + + RefPtr<PromiseListHolder> promiseList = new PromiseListHolder(); + + for (const auto& entry : mSourceTable) { + ClientSourceParent* source = MaybeUnwrapAsExistingSource(entry.GetData()); + + if (!source) { + continue; + } + + if (!ClientMatchPrincipalInfo(source->Info().PrincipalInfo(), + principalInfo)) { + continue; + } + + const Maybe<ServiceWorkerDescriptor>& controller = source->GetController(); + if (controller.isSome() && + controller.ref().Scope() == serviceWorker.scope() && + controller.ref().Id() == serviceWorker.id()) { + continue; + } + + // TODO: This logic to determine if a service worker should control + // a particular client should be moved to the ServiceWorkerManager. + // This can't happen until the SWM is moved to the parent process, + // though. + if (!source->ExecutionReady() || + source->Info().Type() == ClientType::Serviceworker || + source->Info().URL().Find(serviceWorker.scope()) != 0) { + continue; + } + + if (source->IsFrozen()) { + Unused << source->SendEvictFromBFCache(); + continue; + } + + promiseList->AddPromise(ClaimOnMainThread( + source->Info(), ServiceWorkerDescriptor(serviceWorker))); + } + + // Maybe finish the promise now in case we didn't find any matching clients. + promiseList->MaybeFinish(); + + return promiseList->GetResultPromise(); +} + +RefPtr<ClientOpPromise> ClientManagerService::GetInfoAndState( + ThreadsafeContentParentHandle* aOriginContent, + const ClientGetInfoAndStateArgs& aArgs) { + ClientSourceParent* source = + FindExistingSource(aArgs.id(), aArgs.principalInfo()); + + if (!source) { + CopyableErrorResult rv; + rv.ThrowInvalidStateError("Unknown client"); + return ClientOpPromise::CreateAndReject(rv, __func__); + } + + if (!source->ExecutionReady()) { + RefPtr<ClientManagerService> self = this; + + // rejection ultimately converted to `undefined` in Clients::Get + return source->ExecutionReadyPromise()->Then( + GetCurrentSerialEventTarget(), __func__, + [self = std::move(self), aArgs]() -> RefPtr<ClientOpPromise> { + ClientSourceParent* source = + self->FindExistingSource(aArgs.id(), aArgs.principalInfo()); + + if (!source) { + CopyableErrorResult rv; + rv.ThrowInvalidStateError("Unknown client"); + return ClientOpPromise::CreateAndReject(rv, __func__); + } + + return source->StartOp(aArgs); + }); + } + + return source->StartOp(aArgs); +} + +RefPtr<ClientOpPromise> ClientManagerService::OpenWindow( + ThreadsafeContentParentHandle* aOriginContent, + const ClientOpenWindowArgs& aArgs) { + return InvokeAsync(GetMainThreadSerialEventTarget(), __func__, + [originContent = RefPtr{aOriginContent}, aArgs]() { + return ClientOpenWindow(originContent, aArgs); + }); +} + +bool ClientManagerService::HasWindow( + const Maybe<ContentParentId>& aContentParentId, + const PrincipalInfo& aPrincipalInfo, const nsID& aClientId) { + AssertIsOnBackgroundThread(); + + ClientSourceParent* source = FindExistingSource(aClientId, aPrincipalInfo); + if (!source) { + return false; + } + + if (!source->ExecutionReady()) { + return false; + } + + if (source->Info().Type() != ClientType::Window) { + return false; + } + + if (aContentParentId && !source->IsOwnedByProcess(aContentParentId.value())) { + return false; + } + + return true; +} + +} // namespace mozilla::dom |