diff options
Diffstat (limited to '')
-rw-r--r-- | dom/ipc/WindowGlobalParent.cpp | 1724 |
1 files changed, 1724 insertions, 0 deletions
diff --git a/dom/ipc/WindowGlobalParent.cpp b/dom/ipc/WindowGlobalParent.cpp new file mode 100644 index 0000000000..13fbe2940c --- /dev/null +++ b/dom/ipc/WindowGlobalParent.cpp @@ -0,0 +1,1724 @@ +/* -*- Mode: C++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 8 -*- */ +/* vim: set sw=2 ts=8 et tw=80 ft=cpp : */ +/* 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 "mozilla/dom/WindowGlobalParent.h" + +#include <algorithm> + +#include "mozilla/AntiTrackingUtils.h" +#include "mozilla/AsyncEventDispatcher.h" +#include "mozilla/ClearOnShutdown.h" +#include "mozilla/ContentBlockingAllowList.h" +#include "mozilla/dom/InProcessParent.h" +#include "mozilla/dom/BrowserBridgeParent.h" +#include "mozilla/dom/BrowsingContextGroup.h" +#include "mozilla/dom/CanonicalBrowsingContext.h" +#include "mozilla/dom/ClientInfo.h" +#include "mozilla/dom/ClientIPCTypes.h" +#include "mozilla/dom/ContentChild.h" +#include "mozilla/dom/ContentParent.h" +#include "mozilla/dom/BrowserHost.h" +#include "mozilla/dom/BrowserParent.h" +#include "mozilla/dom/IdentityCredential.h" +#include "mozilla/dom/MediaController.h" +#include "mozilla/dom/WindowGlobalChild.h" +#include "mozilla/dom/ChromeUtils.h" +#include "mozilla/dom/UseCounterMetrics.h" +#include "mozilla/dom/ipc/IdType.h" +#include "mozilla/dom/ipc/StructuredCloneData.h" +#include "mozilla/glean/GleanMetrics.h" +#include "mozilla/Components.h" +#include "mozilla/ScopeExit.h" +#include "mozilla/ServoCSSParser.h" +#include "mozilla/ServoStyleSet.h" +#include "mozilla/StaticPrefs_dom.h" +#include "mozilla/StaticPrefs_network.h" +#include "mozilla/Telemetry.h" +#include "mozilla/Variant.h" +#include "mozilla/ipc/ProtocolUtils.h" +#include "nsContentUtils.h" +#include "nsDocShell.h" +#include "nsDocShellLoadState.h" +#include "nsError.h" +#include "nsFrameLoader.h" +#include "nsFrameLoaderOwner.h" +#include "nsICookieManager.h" +#include "nsICookieService.h" +#include "nsQueryObject.h" +#include "nsNetUtil.h" +#include "nsSandboxFlags.h" +#include "nsSerializationHelper.h" +#include "nsIBrowser.h" +#include "nsIEffectiveTLDService.h" +#include "nsIHttpsOnlyModePermission.h" +#include "nsIPromptCollection.h" +#include "nsITimer.h" +#include "nsITransportSecurityInfo.h" +#include "nsISharePicker.h" +#include "nsIURIMutator.h" +#include "nsIWebProgressListener.h" +#include "nsScriptSecurityManager.h" +#include "nsIOService.h" + +#include "mozilla/dom/DOMException.h" +#include "mozilla/dom/DOMExceptionBinding.h" + +#include "mozilla/dom/JSActorService.h" +#include "mozilla/dom/JSWindowActorBinding.h" +#include "mozilla/dom/JSWindowActorParent.h" + +#include "mozilla/net/NeckoParent.h" +#include "mozilla/net/PCookieServiceParent.h" +#include "mozilla/net/CookieServiceParent.h" + +#include "SessionStoreFunctions.h" +#include "nsIXPConnect.h" +#include "nsImportModule.h" +#include "nsIXULRuntime.h" + +#include "mozilla/dom/PBackgroundSessionStorageCache.h" + +using namespace mozilla::ipc; +using namespace mozilla::dom::ipc; + +extern mozilla::LazyLogModule gSHIPBFCacheLog; +extern mozilla::LazyLogModule gUseCountersLog; + +namespace mozilla::dom { + +WindowGlobalParent::WindowGlobalParent( + CanonicalBrowsingContext* aBrowsingContext, uint64_t aInnerWindowId, + uint64_t aOuterWindowId, FieldValues&& aInit) + : WindowContext(aBrowsingContext, aInnerWindowId, aOuterWindowId, + std::move(aInit)), + mSandboxFlags(0), + mDocumentHasLoaded(false), + mDocumentHasUserInteracted(false), + mBlockAllMixedContent(false), + mUpgradeInsecureRequests(false) { + MOZ_DIAGNOSTIC_ASSERT(XRE_IsParentProcess(), "Parent process only"); +} + +already_AddRefed<WindowGlobalParent> WindowGlobalParent::CreateDisconnected( + const WindowGlobalInit& aInit) { + RefPtr<CanonicalBrowsingContext> browsingContext = + CanonicalBrowsingContext::Get(aInit.context().mBrowsingContextId); + if (NS_WARN_IF(!browsingContext)) { + return nullptr; + } + + RefPtr<WindowGlobalParent> wgp = + GetByInnerWindowId(aInit.context().mInnerWindowId); + MOZ_RELEASE_ASSERT(!wgp, "Creating duplicate WindowGlobalParent"); + + FieldValues fields(aInit.context().mFields); + wgp = + new WindowGlobalParent(browsingContext, aInit.context().mInnerWindowId, + aInit.context().mOuterWindowId, std::move(fields)); + wgp->mDocumentPrincipal = aInit.principal(); + wgp->mDocumentURI = aInit.documentURI(); + wgp->mIsInitialDocument = Some(aInit.isInitialDocument()); + wgp->mBlockAllMixedContent = aInit.blockAllMixedContent(); + wgp->mUpgradeInsecureRequests = aInit.upgradeInsecureRequests(); + wgp->mSandboxFlags = aInit.sandboxFlags(); + wgp->mHttpsOnlyStatus = aInit.httpsOnlyStatus(); + wgp->mSecurityInfo = aInit.securityInfo(); + net::CookieJarSettings::Deserialize(aInit.cookieJarSettings(), + getter_AddRefs(wgp->mCookieJarSettings)); + MOZ_RELEASE_ASSERT(wgp->mDocumentPrincipal, "Must have a valid principal"); + + nsresult rv = wgp->SetDocumentStoragePrincipal(aInit.storagePrincipal()); + MOZ_RELEASE_ASSERT(NS_SUCCEEDED(rv), + "Must succeed in setting storage principal"); + + return wgp.forget(); +} + +void WindowGlobalParent::Init() { + MOZ_ASSERT(Manager(), "Should have a manager!"); + + // Invoke our base class' `Init` method. This will register us in + // `gWindowContexts`. + WindowContext::Init(); + + // Determine which content process the window global is coming from. + dom::ContentParentId processId(0); + ContentParent* cp = nullptr; + if (!IsInProcess()) { + cp = static_cast<ContentParent*>(Manager()->Manager()); + processId = cp->ChildID(); + + // Ensure the content process has permissions for this principal. + cp->TransmitPermissionsForPrincipal(mDocumentPrincipal); + } + + MOZ_DIAGNOSTIC_ASSERT( + !BrowsingContext()->GetParent() || + BrowsingContext()->GetEmbedderInnerWindowId(), + "When creating a non-root WindowGlobalParent, the WindowGlobalParent " + "for our embedder should've already been created."); + + // Ensure we have a document URI + if (!mDocumentURI) { + NS_NewURI(getter_AddRefs(mDocumentURI), "about:blank"); + } + + // NOTE: `cp` may be nullptr, but that's OK, we need to tell every other + // process in our group in that case. + IPCInitializer ipcinit = GetIPCInitializer(); + Group()->EachOtherParent(cp, [&](ContentParent* otherContent) { + Unused << otherContent->SendCreateWindowContext(ipcinit); + }); + + if (!BrowsingContext()->IsDiscarded()) { + MOZ_ALWAYS_SUCCEEDS( + BrowsingContext()->SetCurrentInnerWindowId(InnerWindowId())); + + Unused << SendSetContainerFeaturePolicy( + BrowsingContext()->GetContainerFeaturePolicy()); + } + + if (BrowsingContext()->IsTopContent()) { + // For top level sandboxed documents we need to create a new principal + // from URI + OriginAttributes, since the document principal will be a + // NullPrincipal. See Bug 1654546. + if (mSandboxFlags & SANDBOXED_ORIGIN) { + ContentBlockingAllowList::RecomputePrincipal( + mDocumentURI, mDocumentPrincipal->OriginAttributesRef(), + getter_AddRefs(mDocContentBlockingAllowListPrincipal)); + } else { + ContentBlockingAllowList::ComputePrincipal( + mDocumentPrincipal, + getter_AddRefs(mDocContentBlockingAllowListPrincipal)); + } + } + + nsCOMPtr<nsIObserverService> obs = services::GetObserverService(); + if (obs) { + obs->NotifyObservers(ToSupports(this), "window-global-created", nullptr); + } + + if (!BrowsingContext()->IsDiscarded() && ShouldTrackSiteOriginTelemetry()) { + mOriginCounter.emplace(); + mOriginCounter->UpdateSiteOriginsFrom(this, /* aIncrease = */ true); + } +} + +void WindowGlobalParent::OriginCounter::UpdateSiteOriginsFrom( + WindowGlobalParent* aParent, bool aIncrease) { + MOZ_RELEASE_ASSERT(aParent); + + if (aParent->DocumentPrincipal()->GetIsContentPrincipal()) { + nsAutoCString origin; + aParent->DocumentPrincipal()->GetSiteOrigin(origin); + + if (aIncrease) { + int32_t& count = mOriginMap.LookupOrInsert(origin); + count += 1; + mMaxOrigins = std::max(mMaxOrigins, mOriginMap.Count()); + } else if (auto entry = mOriginMap.Lookup(origin)) { + entry.Data() -= 1; + + if (entry.Data() == 0) { + entry.Remove(); + } + } + } +} + +void WindowGlobalParent::OriginCounter::Accumulate() { + mozilla::glean::geckoview::per_document_site_origins.AccumulateSamples( + {mMaxOrigins}); + + mMaxOrigins = 0; + mOriginMap.Clear(); +} + +/* static */ +already_AddRefed<WindowGlobalParent> WindowGlobalParent::GetByInnerWindowId( + uint64_t aInnerWindowId) { + if (!XRE_IsParentProcess()) { + return nullptr; + } + + return WindowContext::GetById(aInnerWindowId).downcast<WindowGlobalParent>(); +} + +already_AddRefed<WindowGlobalChild> WindowGlobalParent::GetChildActor() { + if (!CanSend()) { + return nullptr; + } + IProtocol* otherSide = InProcessParent::ChildActorFor(this); + return do_AddRef(static_cast<WindowGlobalChild*>(otherSide)); +} + +BrowserParent* WindowGlobalParent::GetBrowserParent() { + if (IsInProcess() || !CanSend()) { + return nullptr; + } + return static_cast<BrowserParent*>(Manager()); +} + +ContentParent* WindowGlobalParent::GetContentParent() { + if (IsInProcess() || !CanSend()) { + return nullptr; + } + return static_cast<ContentParent*>(Manager()->Manager()); +} + +already_AddRefed<nsFrameLoader> WindowGlobalParent::GetRootFrameLoader() { + dom::BrowsingContext* top = BrowsingContext()->Top(); + + RefPtr<nsFrameLoaderOwner> frameLoaderOwner = + do_QueryObject(top->GetEmbedderElement()); + if (frameLoaderOwner) { + return frameLoaderOwner->GetFrameLoader(); + } + return nullptr; +} + +uint64_t WindowGlobalParent::ContentParentId() { + RefPtr<BrowserParent> browserParent = GetBrowserParent(); + return browserParent ? browserParent->Manager()->ChildID() : 0; +} + +int32_t WindowGlobalParent::OsPid() { + RefPtr<BrowserParent> browserParent = GetBrowserParent(); + return browserParent ? browserParent->Manager()->Pid() : -1; +} + +// A WindowGlobalPaernt is the root in its process if it has no parent, or its +// embedder is in a different process. +bool WindowGlobalParent::IsProcessRoot() { + if (!BrowsingContext()->GetParent()) { + return true; + } + + RefPtr<WindowGlobalParent> embedder = + BrowsingContext()->GetEmbedderWindowGlobal(); + if (NS_WARN_IF(!embedder)) { + return false; + } + + return ContentParentId() != embedder->ContentParentId(); +} + +uint32_t WindowGlobalParent::ContentBlockingEvents() { + return GetContentBlockingLog()->GetContentBlockingEventsInLog(); +} + +void WindowGlobalParent::GetContentBlockingLog(nsAString& aLog) { + NS_ConvertUTF8toUTF16 log(GetContentBlockingLog()->Stringify()); + aLog.Assign(std::move(log)); +} + +mozilla::ipc::IPCResult WindowGlobalParent::RecvLoadURI( + const MaybeDiscarded<dom::BrowsingContext>& aTargetBC, + nsDocShellLoadState* aLoadState, bool aSetNavigating) { + if (aTargetBC.IsNullOrDiscarded()) { + MOZ_LOG( + BrowsingContext::GetLog(), LogLevel::Debug, + ("ParentIPC: Trying to send a message with dead or detached context")); + return IPC_OK(); + } + + if (net::SchemeIsJavascript(aLoadState->URI())) { + return IPC_FAIL(this, "Illegal cross-process javascript: load attempt"); + } + + RefPtr<CanonicalBrowsingContext> targetBC = aTargetBC.get_canonical(); + + // FIXME: For cross-process loads, we should double check CanAccess() for the + // source browsing context in the parent process. + + if (targetBC->Group() != BrowsingContext()->Group()) { + return IPC_FAIL(this, "Illegal cross-group BrowsingContext load"); + } + + // FIXME: We should really initiate the load in the parent before bouncing + // back down to the child. + + targetBC->LoadURI(aLoadState, aSetNavigating); + return IPC_OK(); +} + +mozilla::ipc::IPCResult WindowGlobalParent::RecvInternalLoad( + nsDocShellLoadState* aLoadState) { + if (!aLoadState->Target().IsEmpty() || + aLoadState->TargetBrowsingContext().IsNull()) { + return IPC_FAIL(this, "must already be retargeted"); + } + if (aLoadState->TargetBrowsingContext().IsDiscarded()) { + MOZ_LOG( + BrowsingContext::GetLog(), LogLevel::Debug, + ("ParentIPC: Trying to send a message with dead or detached context")); + return IPC_OK(); + } + + if (net::SchemeIsJavascript(aLoadState->URI())) { + return IPC_FAIL(this, "Illegal cross-process javascript: load attempt"); + } + + RefPtr<CanonicalBrowsingContext> targetBC = + aLoadState->TargetBrowsingContext().get_canonical(); + + // FIXME: For cross-process loads, we should double check CanAccess() for the + // source browsing context in the parent process. + + if (targetBC->Group() != BrowsingContext()->Group()) { + return IPC_FAIL(this, "Illegal cross-group BrowsingContext load"); + } + + // FIXME: We should really initiate the load in the parent before bouncing + // back down to the child. + + targetBC->InternalLoad(aLoadState); + return IPC_OK(); +} + +IPCResult WindowGlobalParent::RecvUpdateDocumentURI(NotNull<nsIURI*> aURI) { + // XXX(nika): Assert that the URI change was one which makes sense (either + // about:blank -> a real URI, or a legal push/popstate URI change): + if (StaticPrefs::dom_security_setdocumenturi()) { + nsAutoCString scheme; + if (NS_FAILED(aURI->GetScheme(scheme))) { + return IPC_FAIL(this, "Setting DocumentURI without scheme."); + } + + nsCOMPtr<nsIIOService> ios = do_GetIOService(); + if (!ios) { + return IPC_FAIL(this, "Cannot get IOService"); + } + nsCOMPtr<nsIProtocolHandler> handler; + ios->GetProtocolHandler(scheme.get(), getter_AddRefs(handler)); + if (!handler) { + return IPC_FAIL(this, "Setting DocumentURI with unknown protocol."); + } + + auto isLoadableViaInternet = [](nsIURI* uri) { + return (uri && (net::SchemeIsHTTP(uri) || net::SchemeIsHTTPS(uri))); + }; + + if (isLoadableViaInternet(aURI)) { + nsCOMPtr<nsIURI> principalURI = mDocumentPrincipal->GetURI(); + if (mDocumentPrincipal->GetIsNullPrincipal()) { + nsCOMPtr<nsIPrincipal> precursor = + mDocumentPrincipal->GetPrecursorPrincipal(); + if (precursor) { + principalURI = precursor->GetURI(); + } + } + + if (isLoadableViaInternet(principalURI) && + !nsScriptSecurityManager::SecurityCompareURIs(principalURI, aURI)) { + return IPC_FAIL(this, + "Setting DocumentURI with a different Origin than " + "principal URI"); + } + } + } + + mDocumentURI = aURI; + return IPC_OK(); +} + +nsresult WindowGlobalParent::SetDocumentStoragePrincipal( + nsIPrincipal* aNewDocumentStoragePrincipal) { + if (mDocumentPrincipal->Equals(aNewDocumentStoragePrincipal)) { + mDocumentStoragePrincipal = mDocumentPrincipal; + return NS_OK; + } + + // Compare originNoSuffix to ensure it's equal. + nsCString noSuffix; + nsresult rv = mDocumentPrincipal->GetOriginNoSuffix(noSuffix); + if (NS_FAILED(rv)) { + return rv; + } + + nsCString storageNoSuffix; + rv = aNewDocumentStoragePrincipal->GetOriginNoSuffix(storageNoSuffix); + if (NS_FAILED(rv)) { + return rv; + } + + if (noSuffix != storageNoSuffix) { + return NS_ERROR_FAILURE; + } + + if (!mDocumentPrincipal->OriginAttributesRef().EqualsIgnoringPartitionKey( + aNewDocumentStoragePrincipal->OriginAttributesRef())) { + return NS_ERROR_FAILURE; + } + + mDocumentStoragePrincipal = aNewDocumentStoragePrincipal; + return NS_OK; +} + +IPCResult WindowGlobalParent::RecvUpdateDocumentPrincipal( + nsIPrincipal* aNewDocumentPrincipal, + nsIPrincipal* aNewDocumentStoragePrincipal) { + if (!mDocumentPrincipal->Equals(aNewDocumentPrincipal)) { + return IPC_FAIL(this, + "Trying to reuse WindowGlobalParent but the principal of " + "the new document does not match the old one"); + } + mDocumentPrincipal = aNewDocumentPrincipal; + + if (NS_FAILED(SetDocumentStoragePrincipal(aNewDocumentStoragePrincipal))) { + return IPC_FAIL(this, + "Trying to reuse WindowGlobalParent but the principal of " + "the new document does not match the storage principal"); + } + + return IPC_OK(); +} +mozilla::ipc::IPCResult WindowGlobalParent::RecvUpdateDocumentTitle( + const nsString& aTitle) { + if (mDocumentTitle.isSome() && mDocumentTitle.value() == aTitle) { + return IPC_OK(); + } + + mDocumentTitle = Some(aTitle); + + // Send a pagetitlechanged event only for changes to the title + // for top-level frames. + if (!BrowsingContext()->IsTop()) { + return IPC_OK(); + } + + // Notify media controller in order to update its default metadata. + if (BrowsingContext()->HasCreatedMediaController()) { + BrowsingContext()->GetMediaController()->NotifyPageTitleChanged(); + } + + Element* frameElement = BrowsingContext()->GetEmbedderElement(); + if (!frameElement) { + return IPC_OK(); + } + + AsyncEventDispatcher::RunDOMEventWhenSafe( + *frameElement, u"pagetitlechanged"_ns, CanBubble::eYes, + ChromeOnlyDispatch::eYes); + + return IPC_OK(); +} + +mozilla::ipc::IPCResult WindowGlobalParent::RecvUpdateHttpsOnlyStatus( + uint32_t aHttpsOnlyStatus) { + mHttpsOnlyStatus = aHttpsOnlyStatus; + return IPC_OK(); +} + +IPCResult WindowGlobalParent::RecvUpdateDocumentHasLoaded( + bool aDocumentHasLoaded) { + mDocumentHasLoaded = aDocumentHasLoaded; + return IPC_OK(); +} + +IPCResult WindowGlobalParent::RecvUpdateDocumentHasUserInteracted( + bool aDocumentHasUserInteracted) { + mDocumentHasUserInteracted = aDocumentHasUserInteracted; + return IPC_OK(); +} + +IPCResult WindowGlobalParent::RecvUpdateSandboxFlags(uint32_t aSandboxFlags) { + mSandboxFlags = aSandboxFlags; + return IPC_OK(); +} + +IPCResult WindowGlobalParent::RecvUpdateDocumentCspSettings( + bool aBlockAllMixedContent, bool aUpgradeInsecureRequests) { + mBlockAllMixedContent = aBlockAllMixedContent; + mUpgradeInsecureRequests = aUpgradeInsecureRequests; + return IPC_OK(); +} + +mozilla::ipc::IPCResult WindowGlobalParent::RecvSetClientInfo( + const IPCClientInfo& aIPCClientInfo) { + mClientInfo = Some(ClientInfo(aIPCClientInfo)); + return IPC_OK(); +} + +IPCResult WindowGlobalParent::RecvDestroy() { + // Make a copy so that we can avoid potential iterator invalidation when + // calling the user-provided Destroy() methods. + JSActorWillDestroy(); + + if (CanSend()) { + RefPtr<BrowserParent> browserParent = GetBrowserParent(); + if (!browserParent || !browserParent->IsDestroyed()) { + Unused << Send__delete__(this); + } + } + return IPC_OK(); +} + +IPCResult WindowGlobalParent::RecvRawMessage( + const JSActorMessageMeta& aMeta, const Maybe<ClonedMessageData>& aData, + const Maybe<ClonedMessageData>& aStack) { + Maybe<StructuredCloneData> data; + if (aData) { + data.emplace(); + data->BorrowFromClonedMessageData(*aData); + } + Maybe<StructuredCloneData> stack; + if (aStack) { + stack.emplace(); + stack->BorrowFromClonedMessageData(*aStack); + } + ReceiveRawMessage(aMeta, std::move(data), std::move(stack)); + return IPC_OK(); +} + +const nsACString& WindowGlobalParent::GetRemoteType() { + if (RefPtr<BrowserParent> browserParent = GetBrowserParent()) { + return browserParent->Manager()->GetRemoteType(); + } + + return NOT_REMOTE_TYPE; +} + +void WindowGlobalParent::NotifyContentBlockingEvent( + uint32_t aEvent, nsIRequest* aRequest, bool aBlocked, + const nsACString& aTrackingOrigin, + const nsTArray<nsCString>& aTrackingFullHashes, + const Maybe<ContentBlockingNotifier::StorageAccessPermissionGrantedReason>& + aReason, + const Maybe<ContentBlockingNotifier::CanvasFingerprinter>& + aCanvasFingerprinter, + const Maybe<bool> aCanvasFingerprinterKnownText) { + MOZ_ASSERT(NS_IsMainThread()); + DebugOnly<bool> isCookiesBlocked = + aEvent == nsIWebProgressListener::STATE_COOKIES_BLOCKED_TRACKER || + aEvent == nsIWebProgressListener::STATE_COOKIES_BLOCKED_SOCIALTRACKER; + MOZ_ASSERT_IF(aBlocked, aReason.isNothing()); + MOZ_ASSERT_IF(!isCookiesBlocked, aReason.isNothing()); + MOZ_ASSERT_IF(isCookiesBlocked && !aBlocked, aReason.isSome()); + MOZ_DIAGNOSTIC_ASSERT(XRE_IsParentProcess()); + // TODO: temporarily remove this until we find the root case of Bug 1609144 + // MOZ_DIAGNOSTIC_ASSERT_IF(XRE_IsE10sParentProcess(), !IsInProcess()); + + // Return early if this WindowGlobalParent is in process. + if (IsInProcess()) { + return; + } + + Maybe<uint32_t> event = GetContentBlockingLog()->RecordLogParent( + aTrackingOrigin, aEvent, aBlocked, aReason, aTrackingFullHashes, + aCanvasFingerprinter, aCanvasFingerprinterKnownText); + + // Notify the OnContentBlockingEvent if necessary. + if (event) { + if (auto* webProgress = GetBrowsingContext()->GetWebProgress()) { + webProgress->OnContentBlockingEvent(webProgress, aRequest, event.value()); + } + } +} + +already_AddRefed<JSWindowActorParent> WindowGlobalParent::GetActor( + JSContext* aCx, const nsACString& aName, ErrorResult& aRv) { + return JSActorManager::GetActor(aCx, aName, aRv) + .downcast<JSWindowActorParent>(); +} + +already_AddRefed<JSWindowActorParent> WindowGlobalParent::GetExistingActor( + const nsACString& aName) { + return JSActorManager::GetExistingActor(aName) + .downcast<JSWindowActorParent>(); +} + +already_AddRefed<JSActor> WindowGlobalParent::InitJSActor( + JS::Handle<JSObject*> aMaybeActor, const nsACString& aName, + ErrorResult& aRv) { + RefPtr<JSWindowActorParent> actor; + if (aMaybeActor.get()) { + aRv = UNWRAP_OBJECT(JSWindowActorParent, aMaybeActor.get(), actor); + if (aRv.Failed()) { + return nullptr; + } + } else { + actor = new JSWindowActorParent(); + } + + MOZ_RELEASE_ASSERT(!actor->GetManager(), + "mManager was already initialized once!"); + actor->Init(aName, this); + return actor.forget(); +} + +bool WindowGlobalParent::IsCurrentGlobal() { + if (mozilla::SessionHistoryInParent() && BrowsingContext() && + BrowsingContext()->IsInBFCache()) { + return false; + } + + return CanSend() && BrowsingContext()->GetCurrentWindowGlobal() == this; +} + +bool WindowGlobalParent::IsActiveInTab() { + if (!CanSend()) { + return false; + } + + CanonicalBrowsingContext* bc = BrowsingContext(); + if (!bc || bc->GetCurrentWindowGlobal() != this) { + return false; + } + + // We check the top BC so we don't need to worry about getting a stale value. + // That may not be necessary. + MOZ_ASSERT(bc->Top()->IsInBFCache() == bc->IsInBFCache(), + "BFCache bit out of sync?"); + return bc->AncestorsAreCurrent() && !bc->Top()->IsInBFCache(); +} + +namespace { + +class ShareHandler final : public PromiseNativeHandler { + public: + explicit ShareHandler( + mozilla::dom::WindowGlobalParent::ShareResolver&& aResolver) + : mResolver(std::move(aResolver)) {} + + NS_DECL_ISUPPORTS + + public: + virtual void ResolvedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue, + ErrorResult& aRv) override { + mResolver(NS_OK); + } + + virtual void RejectedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue, + ErrorResult& aRv) override { + if (NS_WARN_IF(!aValue.isObject())) { + mResolver(NS_ERROR_FAILURE); + return; + } + + // nsresult is stored as Exception internally in Promise + JS::Rooted<JSObject*> obj(aCx, &aValue.toObject()); + RefPtr<DOMException> unwrapped; + nsresult rv = UNWRAP_OBJECT(DOMException, &obj, unwrapped); + if (NS_WARN_IF(NS_FAILED(rv))) { + mResolver(NS_ERROR_FAILURE); + return; + } + + mResolver(unwrapped->GetResult()); + } + + private: + ~ShareHandler() = default; + + mozilla::dom::WindowGlobalParent::ShareResolver mResolver; +}; + +NS_IMPL_ISUPPORTS0(ShareHandler) + +} // namespace + +mozilla::ipc::IPCResult WindowGlobalParent::RecvGetContentBlockingEvents( + WindowGlobalParent::GetContentBlockingEventsResolver&& aResolver) { + uint32_t events = GetContentBlockingLog()->GetContentBlockingEventsInLog(); + aResolver(events); + + return IPC_OK(); +} + +mozilla::ipc::IPCResult WindowGlobalParent::RecvUpdateCookieJarSettings( + const CookieJarSettingsArgs& aCookieJarSettingsArgs) { + net::CookieJarSettings::Deserialize(aCookieJarSettingsArgs, + getter_AddRefs(mCookieJarSettings)); + return IPC_OK(); +} + +mozilla::ipc::IPCResult WindowGlobalParent::RecvUpdateDocumentSecurityInfo( + nsITransportSecurityInfo* aSecurityInfo) { + mSecurityInfo = aSecurityInfo; + return IPC_OK(); +} + +mozilla::ipc::IPCResult WindowGlobalParent::RecvShare( + IPCWebShareData&& aData, WindowGlobalParent::ShareResolver&& aResolver) { + // Widget Layer handoff... + nsCOMPtr<nsISharePicker> sharePicker = + do_GetService("@mozilla.org/sharepicker;1"); + if (!sharePicker) { + aResolver(NS_ERROR_DOM_NOT_SUPPORTED_ERR); + return IPC_OK(); + } + + // Initialize the ShareWidget + RefPtr<BrowserParent> parent = GetBrowserParent(); + nsCOMPtr<mozIDOMWindowProxy> openerWindow; + if (parent) { + openerWindow = parent->GetParentWindowOuter(); + if (!openerWindow) { + aResolver(NS_ERROR_FAILURE); + return IPC_OK(); + } + } + sharePicker->Init(openerWindow); + + // And finally share the data... + RefPtr<Promise> promise; + nsresult rv = sharePicker->Share(aData.title(), aData.text(), aData.url(), + getter_AddRefs(promise)); + if (NS_FAILED(rv)) { + aResolver(rv); + return IPC_OK(); + } + + // Handler finally awaits response... + RefPtr<ShareHandler> handler = new ShareHandler(std::move(aResolver)); + promise->AppendNativeHandler(handler); + + return IPC_OK(); +} + +namespace { + +class CheckPermitUnloadRequest final : public PromiseNativeHandler, + public nsITimerCallback { + public: + CheckPermitUnloadRequest(WindowGlobalParent* aWGP, bool aHasInProcessBlocker, + nsIDocumentViewer::PermitUnloadAction aAction, + std::function<void(bool)>&& aResolver) + : mResolver(std::move(aResolver)), + mWGP(aWGP), + mAction(aAction), + mFoundBlocker(aHasInProcessBlocker) {} + + void Run(ContentParent* aIgnoreProcess = nullptr, uint32_t aTimeout = 0) { + MOZ_ASSERT(mState == State::UNINITIALIZED); + mState = State::WAITING; + + RefPtr<CheckPermitUnloadRequest> self(this); + + AutoTArray<ContentParent*, 8> seen; + if (aIgnoreProcess) { + seen.AppendElement(aIgnoreProcess); + } + + BrowsingContext* bc = mWGP->GetBrowsingContext(); + bc->PreOrderWalk([&](dom::BrowsingContext* aBC) { + if (WindowGlobalParent* wgp = + aBC->Canonical()->GetCurrentWindowGlobal()) { + ContentParent* cp = wgp->GetContentParent(); + if (wgp->HasBeforeUnload() && !seen.ContainsSorted(cp)) { + seen.InsertElementSorted(cp); + mPendingRequests++; + auto resolve = [self](bool blockNavigation) { + if (blockNavigation) { + self->mFoundBlocker = true; + } + self->ResolveRequest(); + }; + if (cp) { + cp->SendDispatchBeforeUnloadToSubtree( + bc, resolve, [self](auto) { self->ResolveRequest(); }); + } else { + ContentChild::DispatchBeforeUnloadToSubtree(bc, resolve); + } + } + } + }); + + if (mPendingRequests && aTimeout) { + Unused << NS_NewTimerWithCallback(getter_AddRefs(mTimer), this, aTimeout, + nsITimer::TYPE_ONE_SHOT); + } + + CheckDoneWaiting(); + } + + void ResolveRequest() { + mPendingRequests--; + CheckDoneWaiting(); + } + + NS_IMETHODIMP Notify(nsITimer* aTimer) override { + MOZ_ASSERT(aTimer == mTimer); + if (mState == State::WAITING) { + mState = State::TIMED_OUT; + CheckDoneWaiting(); + } + return NS_OK; + } + + void CheckDoneWaiting() { + // If we've found a blocker, we prompt immediately without waiting for + // further responses. The user's response applies to the entire navigation + // attempt, regardless of how many "beforeunload" listeners we call. + if (mState != State::TIMED_OUT && + (mState != State::WAITING || (mPendingRequests && !mFoundBlocker))) { + return; + } + + mState = State::PROMPTING; + + // Clearing our reference to the timer will automatically cancel it if it's + // still running. + mTimer = nullptr; + + if (!mFoundBlocker) { + SendReply(true); + return; + } + + auto action = mAction; + if (StaticPrefs::dom_disable_beforeunload()) { + action = nsIDocumentViewer::eDontPromptAndUnload; + } + if (action != nsIDocumentViewer::ePrompt) { + SendReply(action == nsIDocumentViewer::eDontPromptAndUnload); + return; + } + + // Handle any failure in prompting by aborting the navigation. See comment + // in nsContentViewer::PermitUnload for reasoning. + auto cleanup = MakeScopeExit([&]() { SendReply(false); }); + + if (nsCOMPtr<nsIPromptCollection> prompt = + do_GetService("@mozilla.org/embedcomp/prompt-collection;1")) { + RefPtr<Promise> promise; + prompt->AsyncBeforeUnloadCheck(mWGP->GetBrowsingContext(), + getter_AddRefs(promise)); + + if (!promise) { + return; + } + + promise->AppendNativeHandler(this); + cleanup.release(); + } + } + + void SendReply(bool aAllow) { + MOZ_ASSERT(mState != State::REPLIED); + mResolver(aAllow); + mState = State::REPLIED; + } + + void ResolvedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue, + ErrorResult& aRv) override { + MOZ_ASSERT(mState == State::PROMPTING); + + SendReply(JS::ToBoolean(aValue)); + } + + void RejectedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue, + ErrorResult& aRv) override { + MOZ_ASSERT(mState == State::PROMPTING); + + SendReply(false); + } + + NS_DECL_ISUPPORTS + + private: + ~CheckPermitUnloadRequest() { + // We may get here without having sent a reply if the promise we're waiting + // on is destroyed without being resolved or rejected. + if (mState != State::REPLIED) { + SendReply(false); + } + } + + enum class State : uint8_t { + UNINITIALIZED, + WAITING, + TIMED_OUT, + PROMPTING, + REPLIED, + }; + + std::function<void(bool)> mResolver; + + RefPtr<WindowGlobalParent> mWGP; + nsCOMPtr<nsITimer> mTimer; + + uint32_t mPendingRequests = 0; + + nsIDocumentViewer::PermitUnloadAction mAction; + + State mState = State::UNINITIALIZED; + + bool mFoundBlocker = false; +}; + +NS_IMPL_ISUPPORTS(CheckPermitUnloadRequest, nsITimerCallback) + +} // namespace + +mozilla::ipc::IPCResult WindowGlobalParent::RecvCheckPermitUnload( + bool aHasInProcessBlocker, XPCOMPermitUnloadAction aAction, + CheckPermitUnloadResolver&& aResolver) { + if (!IsCurrentGlobal()) { + aResolver(false); + return IPC_OK(); + } + + auto request = MakeRefPtr<CheckPermitUnloadRequest>( + this, aHasInProcessBlocker, aAction, std::move(aResolver)); + request->Run(/* aIgnoreProcess */ GetContentParent()); + + return IPC_OK(); +} + +already_AddRefed<Promise> WindowGlobalParent::PermitUnload( + PermitUnloadAction aAction, uint32_t aTimeout, mozilla::ErrorResult& aRv) { + nsIGlobalObject* global = GetParentObject(); + RefPtr<Promise> promise = Promise::Create(global, aRv); + if (NS_WARN_IF(aRv.Failed())) { + return nullptr; + } + + auto request = MakeRefPtr<CheckPermitUnloadRequest>( + this, /* aHasInProcessBlocker */ false, + nsIDocumentViewer::PermitUnloadAction(aAction), + [promise](bool aAllow) { promise->MaybeResolve(aAllow); }); + request->Run(/* aIgnoreProcess */ nullptr, aTimeout); + + return promise.forget(); +} + +void WindowGlobalParent::PermitUnload(std::function<void(bool)>&& aResolver) { + RefPtr<CheckPermitUnloadRequest> request = new CheckPermitUnloadRequest( + this, /* aHasInProcessBlocker */ false, + nsIDocumentViewer::PermitUnloadAction::ePrompt, std::move(aResolver)); + request->Run(); +} + +already_AddRefed<mozilla::dom::Promise> WindowGlobalParent::DrawSnapshot( + const DOMRect* aRect, double aScale, const nsACString& aBackgroundColor, + bool aResetScrollPosition, mozilla::ErrorResult& aRv) { + nsIGlobalObject* global = GetParentObject(); + RefPtr<Promise> promise = Promise::Create(global, aRv); + if (NS_WARN_IF(aRv.Failed())) { + return nullptr; + } + + nscolor color; + if (NS_WARN_IF(!ServoCSSParser::ComputeColor(nullptr, NS_RGB(0, 0, 0), + aBackgroundColor, &color, + nullptr, nullptr))) { + aRv = NS_ERROR_FAILURE; + return nullptr; + } + + gfx::CrossProcessPaintFlags flags = + gfx::CrossProcessPaintFlags::UseHighQualityScaling; + if (!aRect) { + // If no explicit Rect was passed, we want the currently visible viewport. + flags |= gfx::CrossProcessPaintFlags::DrawView; + } else if (aResetScrollPosition) { + flags |= gfx::CrossProcessPaintFlags::ResetScrollPosition; + } + + if (!gfx::CrossProcessPaint::Start(this, aRect, (float)aScale, color, flags, + promise)) { + aRv = NS_ERROR_FAILURE; + return nullptr; + } + return promise.forget(); +} + +void WindowGlobalParent::DrawSnapshotInternal(gfx::CrossProcessPaint* aPaint, + const Maybe<IntRect>& aRect, + float aScale, + nscolor aBackgroundColor, + uint32_t aFlags) { + auto promise = SendDrawSnapshot(aRect, aScale, aBackgroundColor, aFlags); + + RefPtr<gfx::CrossProcessPaint> paint(aPaint); + RefPtr<WindowGlobalParent> wgp(this); + promise->Then( + GetMainThreadSerialEventTarget(), __func__, + [paint, wgp](PaintFragment&& aFragment) { + paint->ReceiveFragment(wgp, std::move(aFragment)); + }, + [paint, wgp](ResponseRejectReason&& aReason) { + paint->LostFragment(wgp); + }); +} + +/** + * Accumulated page use counter data for a given top-level content document. + */ +struct PageUseCounters { + // The number of page use counter data messages we are still waiting for. + uint32_t mWaiting = 0; + + // Whether we have received any page use counter data. + bool mReceivedAny = false; + + // The accumulated page use counters. + UseCounters mUseCounters; +}; + +mozilla::ipc::IPCResult WindowGlobalParent::RecvExpectPageUseCounters( + const MaybeDiscarded<WindowContext>& aTop) { + if (aTop.IsNull()) { + return IPC_FAIL(this, "aTop must not be null"); + } + + MOZ_LOG(gUseCountersLog, LogLevel::Debug, + ("Expect page use counters: WindowContext %" PRIu64 " -> %" PRIu64, + InnerWindowId(), aTop.ContextId())); + + // We've been called to indicate that the document in our window intends + // to send use counter data to accumulate towards the top-level document's + // page use counters. This causes us to wait for this window to go away + // (in WindowGlobalParent::ActorDestroy) before reporting the page use + // counters via Telemetry. + RefPtr<WindowGlobalParent> page = + static_cast<WindowGlobalParent*>(aTop.GetMaybeDiscarded()); + if (!page || page->mSentPageUseCounters) { + MOZ_LOG(gUseCountersLog, LogLevel::Debug, + (" > too late, won't report page use counters for this straggler")); + return IPC_OK(); + } + + if (mPageUseCountersWindow) { + if (mPageUseCountersWindow != page) { + return IPC_FAIL(this, + "ExpectPageUseCounters called on the same " + "WindowContext with a different aTop value"); + } + + // We can get called with the same aTop value more than once, e.g. for + // initial about:blank documents and then subsequent "real" documents loaded + // into the same window. We must note each source window only once. + return IPC_OK(); + } + + // Note that the top-level document must wait for one more window's use + // counters before reporting via Telemetry. + mPageUseCountersWindow = page; + if (!page->mPageUseCounters) { + page->mPageUseCounters = MakeUnique<PageUseCounters>(); + } + ++page->mPageUseCounters->mWaiting; + + MOZ_LOG( + gUseCountersLog, LogLevel::Debug, + (" > top-level now waiting on %d\n", page->mPageUseCounters->mWaiting)); + + return IPC_OK(); +} + +mozilla::ipc::IPCResult WindowGlobalParent::RecvAccumulatePageUseCounters( + const UseCounters& aUseCounters) { + // We've been called to accumulate use counter data into the page use counters + // for the document in mPageUseCountersWindow. + + MOZ_LOG( + gUseCountersLog, LogLevel::Debug, + ("Accumulate page use counters: WindowContext %" PRIu64 " -> %" PRIu64, + InnerWindowId(), + mPageUseCountersWindow ? mPageUseCountersWindow->InnerWindowId() : 0)); + + if (!mPageUseCountersWindow || mPageUseCountersWindow->mSentPageUseCounters) { + MOZ_LOG(gUseCountersLog, LogLevel::Debug, + (" > too late, won't report page use counters for this straggler")); + return IPC_OK(); + } + + MOZ_ASSERT(mPageUseCountersWindow->mPageUseCounters); + MOZ_ASSERT(mPageUseCountersWindow->mPageUseCounters->mWaiting > 0); + + mPageUseCountersWindow->mPageUseCounters->mUseCounters |= aUseCounters; + mPageUseCountersWindow->mPageUseCounters->mReceivedAny = true; + return IPC_OK(); +} + +// This is called on the top-level WindowGlobal, i.e. the one that is +// accumulating the page use counters, not the (potentially descendant) window +// that has finished providing use counter data. +void WindowGlobalParent::FinishAccumulatingPageUseCounters() { + MOZ_LOG(gUseCountersLog, LogLevel::Debug, + ("Stop expecting page use counters: -> WindowContext %" PRIu64, + InnerWindowId())); + + if (!mPageUseCounters) { + MOZ_ASSERT_UNREACHABLE("Not expecting page use counter data"); + MOZ_LOG(gUseCountersLog, LogLevel::Debug, + (" > not expecting page use counter data")); + return; + } + + MOZ_ASSERT(mPageUseCounters->mWaiting > 0); + --mPageUseCounters->mWaiting; + + if (mPageUseCounters->mWaiting > 0) { + MOZ_LOG(gUseCountersLog, LogLevel::Debug, + (" > now waiting on %d", mPageUseCounters->mWaiting)); + return; + } + + if (mPageUseCounters->mReceivedAny) { + MOZ_LOG(gUseCountersLog, LogLevel::Debug, + (" > reporting [%s]", + nsContentUtils::TruncatedURLForDisplay(mDocumentURI).get())); + + Maybe<nsCString> urlForLogging; + const bool dumpCounters = StaticPrefs::dom_use_counters_dump_page(); + if (dumpCounters) { + urlForLogging.emplace( + nsContentUtils::TruncatedURLForDisplay(mDocumentURI)); + } + + glean::use_counter::top_level_content_documents_destroyed.Add(); + + bool any = false; + for (int32_t c = 0; c < eUseCounter_Count; ++c) { + auto uc = static_cast<UseCounter>(c); + if (!mPageUseCounters->mUseCounters[uc]) { + continue; + } + any = true; + const char* metricName = IncrementUseCounter(uc, /* aIsPage = */ true); + if (dumpCounters) { + printf_stderr("USE_COUNTER_PAGE: %s - %s\n", metricName, + urlForLogging->get()); + } + } + + if (!any) { + MOZ_LOG(gUseCountersLog, LogLevel::Debug, + (" > page use counter data was received, but was empty")); + } + } else { + MOZ_LOG(gUseCountersLog, LogLevel::Debug, + (" > no page use counter data was received")); + } + + mSentPageUseCounters = true; + mPageUseCounters = nullptr; +} + +Element* WindowGlobalParent::GetRootOwnerElement() { + WindowGlobalParent* top = TopWindowContext(); + if (!top) { + return nullptr; + } + + if (IsInProcess()) { + return top->BrowsingContext()->GetEmbedderElement(); + } + + if (BrowserParent* parent = top->GetBrowserParent()) { + return parent->GetOwnerElement(); + } + + return nullptr; +} + +void WindowGlobalParent::NotifySessionStoreUpdatesComplete(Element* aEmbedder) { + if (!aEmbedder) { + aEmbedder = GetRootOwnerElement(); + } + if (aEmbedder) { + if (nsCOMPtr<nsIObserverService> obs = services::GetObserverService()) { + obs->NotifyWhenScriptSafe(ToSupports(aEmbedder), + "browser-shutdown-tabstate-updated", nullptr); + } + } +} + +mozilla::ipc::IPCResult WindowGlobalParent::RecvRequestRestoreTabContent() { + CanonicalBrowsingContext* bc = BrowsingContext(); + if (bc && bc->AncestorsAreCurrent()) { + bc->Top()->RequestRestoreTabContent(this); + } + return IPC_OK(); +} + +nsCString BFCacheStatusToString(uint32_t aFlags) { + if (aFlags == 0) { + return "0"_ns; + } + + nsCString flags; +#define ADD_BFCACHESTATUS_TO_STRING(_flag) \ + if (aFlags & BFCacheStatus::_flag) { \ + if (!flags.IsEmpty()) { \ + flags.Append('|'); \ + } \ + flags.AppendLiteral(#_flag); \ + aFlags &= ~BFCacheStatus::_flag; \ + } + + ADD_BFCACHESTATUS_TO_STRING(NOT_ALLOWED); + ADD_BFCACHESTATUS_TO_STRING(EVENT_HANDLING_SUPPRESSED); + ADD_BFCACHESTATUS_TO_STRING(SUSPENDED); + ADD_BFCACHESTATUS_TO_STRING(UNLOAD_LISTENER); + ADD_BFCACHESTATUS_TO_STRING(REQUEST); + ADD_BFCACHESTATUS_TO_STRING(ACTIVE_GET_USER_MEDIA); + ADD_BFCACHESTATUS_TO_STRING(ACTIVE_PEER_CONNECTION); + ADD_BFCACHESTATUS_TO_STRING(CONTAINS_EME_CONTENT); + ADD_BFCACHESTATUS_TO_STRING(CONTAINS_MSE_CONTENT); + ADD_BFCACHESTATUS_TO_STRING(HAS_ACTIVE_SPEECH_SYNTHESIS); + ADD_BFCACHESTATUS_TO_STRING(HAS_USED_VR); + ADD_BFCACHESTATUS_TO_STRING(CONTAINS_REMOTE_SUBFRAMES); + ADD_BFCACHESTATUS_TO_STRING(NOT_ONLY_TOPLEVEL_IN_BCG); + ADD_BFCACHESTATUS_TO_STRING(BEFOREUNLOAD_LISTENER); + ADD_BFCACHESTATUS_TO_STRING(ACTIVE_LOCK); + +#undef ADD_BFCACHESTATUS_TO_STRING + + MOZ_ASSERT(aFlags == 0, + "Missing stringification for enum value in BFCacheStatus."); + return flags; +} + +mozilla::ipc::IPCResult WindowGlobalParent::RecvUpdateBFCacheStatus( + const uint32_t& aOnFlags, const uint32_t& aOffFlags) { + if (MOZ_UNLIKELY(MOZ_LOG_TEST(gSHIPBFCacheLog, LogLevel::Debug))) { + nsAutoCString uri("[no uri]"); + if (mDocumentURI) { + uri = mDocumentURI->GetSpecOrDefault(); + } + MOZ_LOG(gSHIPBFCacheLog, LogLevel::Debug, + ("Setting BFCache flags for %s +(%s) -(%s)", uri.get(), + BFCacheStatusToString(aOnFlags).get(), + BFCacheStatusToString(aOffFlags).get())); + } + mBFCacheStatus |= aOnFlags; + mBFCacheStatus &= ~aOffFlags; + return IPC_OK(); +} + +mozilla::ipc::IPCResult +WindowGlobalParent::RecvUpdateActivePeerConnectionStatus(bool aIsAdded) { + if (aIsAdded) { + RecvUpdateBFCacheStatus(BFCacheStatus::ACTIVE_PEER_CONNECTION, 0); + } else { + RecvUpdateBFCacheStatus(0, BFCacheStatus::ACTIVE_PEER_CONNECTION); + } + + if (WindowGlobalParent* top = TopWindowContext()) { + CheckedUint32 newValue(top->mNumOfProcessesWithActivePeerConnections); + if (aIsAdded) { + ++newValue; + } else { + --newValue; + } + if (!newValue.isValid()) { + return IPC_FAIL(this, + "mNumOfProcessesWithActivePeerConnections overflowed"); + } + + top->mNumOfProcessesWithActivePeerConnections = newValue.value(); + Unused << top->SetHasActivePeerConnections(newValue.value() > 0); + } + + return IPC_OK(); +} + +mozilla::ipc::IPCResult WindowGlobalParent::RecvSetSingleChannelId( + const Maybe<uint64_t>& aSingleChannelId) { + mSingleChannelId = aSingleChannelId; + return IPC_OK(); +} + +mozilla::ipc::IPCResult WindowGlobalParent::RecvSetDocumentDomain( + NotNull<nsIURI*> aDomain) { + if (mSandboxFlags & SANDBOXED_DOMAIN) { + // We're sandboxed; disallow setting domain + return IPC_FAIL(this, "Sandbox disallows domain setting."); + } + + // Might need to do a featurepolicy check here, like we currently do in the + // child process? + + nsCOMPtr<nsIURI> uri; + mDocumentPrincipal->GetDomain(getter_AddRefs(uri)); + if (!uri) { + uri = mDocumentPrincipal->GetURI(); + if (!uri) { + return IPC_OK(); + } + } + + if (!Document::IsValidDomain(uri, aDomain)) { + // Error: illegal domain + return IPC_FAIL( + this, "Setting domain that's not a suffix of existing domain value."); + } + + if (Group()->IsPotentiallyCrossOriginIsolated()) { + return IPC_FAIL(this, "Setting domain in a cross-origin isolated BC."); + } + + mDocumentPrincipal->SetDomain(aDomain); + return IPC_OK(); +} + +mozilla::ipc::IPCResult WindowGlobalParent::RecvReloadWithHttpsOnlyException() { + nsresult rv; + nsCOMPtr<nsIURI> currentUri = BrowsingContext()->Top()->GetCurrentURI(); + + if (!currentUri) { + return IPC_FAIL(this, "HTTPS-only mode: Failed to get current URI"); + } + + bool isViewSource = currentUri->SchemeIs("view-source"); + + nsCOMPtr<nsINestedURI> nestedURI = do_QueryInterface(currentUri); + nsCOMPtr<nsIURI> innerURI; + if (isViewSource) { + nestedURI->GetInnerURI(getter_AddRefs(innerURI)); + } else { + innerURI = currentUri; + } + + if (!innerURI->SchemeIs("https") && !innerURI->SchemeIs("http")) { + return IPC_FAIL(this, "HTTPS-only mode: Illegal state"); + } + + // If the error page is within an iFrame, we create an exception for whatever + // scheme the top-level site is currently on, because the user wants to + // unbreak the iFrame and not the top-level page. When the error page shows up + // on a top-level request, then we replace the scheme with http, because the + // user wants to unbreak the whole page. + nsCOMPtr<nsIURI> newURI; + if (!BrowsingContext()->IsTop()) { + newURI = innerURI; + } else { + Unused << NS_MutateURI(innerURI).SetScheme("http"_ns).Finalize( + getter_AddRefs(newURI)); + } + + OriginAttributes originAttributes = + TopWindowContext()->DocumentPrincipal()->OriginAttributesRef(); + + originAttributes.SetFirstPartyDomain(true, newURI); + + nsCOMPtr<nsIPermissionManager> permMgr = + components::PermissionManager::Service(); + if (!permMgr) { + return IPC_FAIL( + this, "HTTPS-only mode: Failed to get Permission Manager service"); + } + + nsCOMPtr<nsIPrincipal> principal = + BasePrincipal::CreateContentPrincipal(newURI, originAttributes); + + rv = permMgr->AddFromPrincipal( + principal, "https-only-load-insecure"_ns, + nsIHttpsOnlyModePermission::LOAD_INSECURE_ALLOW_SESSION, + nsIPermissionManager::EXPIRE_SESSION, 0); + + if (NS_FAILED(rv)) { + return IPC_FAIL( + this, "HTTPS-only mode: Failed to add permission to the principal"); + } + + nsCOMPtr<nsIURI> insecureURI = newURI; + if (isViewSource) { + nsAutoCString spec; + MOZ_ALWAYS_SUCCEEDS(newURI->GetSpec(spec)); + if (NS_FAILED( + NS_NewURI(getter_AddRefs(insecureURI), "view-source:"_ns + spec))) { + return IPC_FAIL( + this, "HTTPS-only mode: Failed to re-construct view-source URI"); + } + } + + RefPtr<nsDocShellLoadState> loadState = new nsDocShellLoadState(insecureURI); + loadState->SetTriggeringPrincipal(nsContentUtils::GetSystemPrincipal()); + loadState->SetLoadType(LOAD_NORMAL_REPLACE); + + RefPtr<CanonicalBrowsingContext> topBC = BrowsingContext()->Top(); + topBC->LoadURI(loadState, /* setNavigating */ true); + + return IPC_OK(); +} + +IPCResult WindowGlobalParent::RecvDiscoverIdentityCredentialFromExternalSource( + const IdentityCredentialRequestOptions& aOptions, + const DiscoverIdentityCredentialFromExternalSourceResolver& aResolver) { + IdentityCredential::DiscoverFromExternalSourceInMainProcess( + DocumentPrincipal(), this->BrowsingContext(), aOptions) + ->Then( + GetCurrentSerialEventTarget(), __func__, + [aResolver](const IPCIdentityCredential& aResult) { + return aResolver(Some(aResult)); + }, + [aResolver](nsresult aErr) { aResolver(Nothing()); }); + return IPC_OK(); +} + +IPCResult WindowGlobalParent::RecvGetStorageAccessPermission( + GetStorageAccessPermissionResolver&& aResolve) { + WindowGlobalParent* top = TopWindowContext(); + if (!top) { + return IPC_FAIL_NO_REASON(this); + } + nsIPrincipal* topPrincipal = top->DocumentPrincipal(); + nsIPrincipal* principal = DocumentPrincipal(); + uint32_t result; + nsresult rv = AntiTrackingUtils::TestStoragePermissionInParent( + topPrincipal, principal, &result); + if (NS_WARN_IF(NS_FAILED(rv))) { + aResolve(nsIPermissionManager::UNKNOWN_ACTION); + return IPC_OK(); + } + + aResolve(result); + return IPC_OK(); +} + +void WindowGlobalParent::ActorDestroy(ActorDestroyReason aWhy) { + if (GetBrowsingContext()->IsTopContent()) { + Telemetry::Accumulate(Telemetry::ORB_DID_EVER_BLOCK_RESPONSE, + mShouldReportHasBlockedOpaqueResponse); + } + + if (mPageUseCountersWindow) { + mPageUseCountersWindow->FinishAccumulatingPageUseCounters(); + mPageUseCountersWindow = nullptr; + } + + if (GetBrowsingContext()->IsTopContent() && + !mDocumentPrincipal->SchemeIs("about")) { + // Record the page load + uint32_t pageLoaded = 1; + Accumulate(Telemetry::MIXED_CONTENT_UNBLOCK_COUNTER, pageLoaded); + + // Record the mixed content status of the docshell in Telemetry + enum { + NO_MIXED_CONTENT = 0, // There is no Mixed Content on the page + MIXED_DISPLAY_CONTENT = + 1, // The page attempted to load Mixed Display Content + MIXED_ACTIVE_CONTENT = + 2, // The page attempted to load Mixed Active Content + MIXED_DISPLAY_AND_ACTIVE_CONTENT = 3 // The page attempted to load Mixed + // Display & Mixed Active Content + }; + + bool hasMixedDisplay = + mSecurityState & + (nsIWebProgressListener::STATE_LOADED_MIXED_DISPLAY_CONTENT | + nsIWebProgressListener::STATE_BLOCKED_MIXED_DISPLAY_CONTENT); + bool hasMixedActive = + mSecurityState & + (nsIWebProgressListener::STATE_LOADED_MIXED_ACTIVE_CONTENT | + nsIWebProgressListener::STATE_BLOCKED_MIXED_ACTIVE_CONTENT); + + uint32_t mixedContentLevel = NO_MIXED_CONTENT; + if (hasMixedDisplay && hasMixedActive) { + mixedContentLevel = MIXED_DISPLAY_AND_ACTIVE_CONTENT; + } else if (hasMixedActive) { + mixedContentLevel = MIXED_ACTIVE_CONTENT; + } else if (hasMixedDisplay) { + mixedContentLevel = MIXED_DISPLAY_CONTENT; + } + Accumulate(Telemetry::MIXED_CONTENT_PAGE_LOAD, mixedContentLevel); + + if (GetDocTreeHadMedia()) { + ScalarAdd(Telemetry::ScalarID::MEDIA_ELEMENT_IN_PAGE_COUNT, 1); + } + } + + ContentParent* cp = nullptr; + if (!IsInProcess()) { + cp = static_cast<ContentParent*>(Manager()->Manager()); + } + + Group()->EachOtherParent(cp, [&](ContentParent* otherContent) { + // Keep the WindowContext and our BrowsingContextGroup alive until other + // processes have acknowledged it has been discarded. + Group()->AddKeepAlive(); + auto callback = [self = RefPtr{this}](auto) { + self->Group()->RemoveKeepAlive(); + }; + otherContent->SendDiscardWindowContext(InnerWindowId(), callback, callback); + }); + + // Note that our WindowContext has become discarded. + WindowContext::Discard(); + + // Report content blocking log when destroyed. + // There shouldn't have any content blocking log when a document is loaded in + // the parent process(See NotifyContentBlockingEvent), so we could skip + // reporting log when it is in-process. + if (!IsInProcess()) { + RefPtr<BrowserParent> browserParent = + static_cast<BrowserParent*>(Manager()); + if (browserParent) { + nsCOMPtr<nsILoadContext> loadContext = browserParent->GetLoadContext(); + if (loadContext && !loadContext->UsePrivateBrowsing() && + BrowsingContext()->IsTopContent()) { + GetContentBlockingLog()->ReportLog(DocumentPrincipal()); + + if (mDocumentURI && (net::SchemeIsHTTP(mDocumentURI) || + net::SchemeIsHTTPS(mDocumentURI))) { + GetContentBlockingLog()->ReportCanvasFingerprintingLog( + DocumentPrincipal()); + GetContentBlockingLog()->ReportFontFingerprintingLog( + DocumentPrincipal()); + GetContentBlockingLog()->ReportEmailTrackingLog(DocumentPrincipal()); + } + } + } + } + + // Destroy our JSWindowActors, and reject any pending queries. + JSActorDidDestroy(); + + nsCOMPtr<nsIObserverService> obs = services::GetObserverService(); + if (obs) { + obs->NotifyObservers(ToSupports(this), "window-global-destroyed", nullptr); + } + + if (mOriginCounter) { + mOriginCounter->Accumulate(); + } +} + +WindowGlobalParent::~WindowGlobalParent() = default; + +JSObject* WindowGlobalParent::WrapObject(JSContext* aCx, + JS::Handle<JSObject*> aGivenProto) { + return WindowGlobalParent_Binding::Wrap(aCx, this, aGivenProto); +} + +nsIGlobalObject* WindowGlobalParent::GetParentObject() { + return xpc::NativeGlobal(xpc::PrivilegedJunkScope()); +} + +nsIDOMProcessParent* WindowGlobalParent::GetDomProcess() { + if (RefPtr<BrowserParent> browserParent = GetBrowserParent()) { + return browserParent->Manager(); + } + return InProcessParent::Singleton(); +} + +void WindowGlobalParent::DidBecomeCurrentWindowGlobal(bool aCurrent) { + WindowGlobalParent* top = BrowsingContext()->GetTopWindowContext(); + if (top && top->mOriginCounter) { + top->mOriginCounter->UpdateSiteOriginsFrom(this, + /* aIncrease = */ aCurrent); + } + + if (!aCurrent && Fullscreen()) { + ExitTopChromeDocumentFullscreen(); + } +} + +bool WindowGlobalParent::ShouldTrackSiteOriginTelemetry() { + CanonicalBrowsingContext* bc = BrowsingContext(); + + if (!bc->IsTopContent()) { + return false; + } + + RefPtr<BrowserParent> browserParent = GetBrowserParent(); + if (!browserParent || + !IsWebRemoteType(browserParent->Manager()->GetRemoteType())) { + return false; + } + + return DocumentPrincipal()->GetIsContentPrincipal(); +} + +void WindowGlobalParent::AddSecurityState(uint32_t aStateFlags) { + MOZ_ASSERT(TopWindowContext() == this); + MOZ_ASSERT((aStateFlags & + (nsIWebProgressListener::STATE_LOADED_MIXED_DISPLAY_CONTENT | + nsIWebProgressListener::STATE_LOADED_MIXED_ACTIVE_CONTENT | + nsIWebProgressListener::STATE_BLOCKED_MIXED_DISPLAY_CONTENT | + nsIWebProgressListener::STATE_BLOCKED_MIXED_ACTIVE_CONTENT | + nsIWebProgressListener::STATE_HTTPS_ONLY_MODE_UPGRADED | + nsIWebProgressListener::STATE_HTTPS_ONLY_MODE_UPGRADE_FAILED | + nsIWebProgressListener::STATE_HTTPS_ONLY_MODE_UPGRADED_FIRST)) == + aStateFlags, + "Invalid flags specified!"); + + if ((mSecurityState & aStateFlags) == aStateFlags) { + return; + } + + mSecurityState |= aStateFlags; + + if (GetBrowsingContext()->GetCurrentWindowGlobal() == this) { + GetBrowsingContext()->UpdateSecurityState(); + } +} + +bool WindowGlobalParent::HasActivePeerConnections() { + MOZ_ASSERT(TopWindowContext() == this, + "mNumOfProcessesWithActivePeerConnections is set only " + "in the top window context"); + return mNumOfProcessesWithActivePeerConnections > 0; +} + +void WindowGlobalParent::ExitTopChromeDocumentFullscreen() { + RefPtr<CanonicalBrowsingContext> chromeTop = + BrowsingContext()->TopCrossChromeBoundary(); + if (Document* chromeDoc = chromeTop->GetDocument()) { + Document::ClearPendingFullscreenRequests(chromeDoc); + if (chromeDoc->Fullscreen()) { + // This only clears the DOM fullscreen, will not exit from browser UI + // fullscreen mode. + Document::AsyncExitFullscreen(chromeDoc); + } + } +} + +void WindowGlobalParent::SetShouldReportHasBlockedOpaqueResponse( + nsContentPolicyType aContentPolicy) { + // It's always okay to block TYPE_BEACON, TYPE_PING and TYPE_CSP_REPORT in + // the parent process because content processes can do nothing to their + // responses. Hence excluding them from the telemetry as blocking + // them have no webcompat concerns. + if (aContentPolicy != nsIContentPolicy::TYPE_BEACON && + aContentPolicy != nsIContentPolicy::TYPE_PING && + aContentPolicy != nsIContentPolicy::TYPE_CSP_REPORT) { + if (IsTop()) { + mShouldReportHasBlockedOpaqueResponse = true; + } + } +} + +IPCResult WindowGlobalParent::RecvSetCookies( + const nsCString& aBaseDomain, const OriginAttributes& aOriginAttributes, + nsIURI* aHost, bool aFromHttp, const nsTArray<CookieStruct>& aCookies) { + // Get CookieServiceParent via + // ContentParent->NeckoParent->CookieServiceParent. + ContentParent* contentParent = GetContentParent(); + NS_ENSURE_TRUE(contentParent, IPC_OK()); + + net::PNeckoParent* neckoParent = + LoneManagedOrNullAsserts(contentParent->ManagedPNeckoParent()); + NS_ENSURE_TRUE(neckoParent, IPC_OK()); + net::PCookieServiceParent* csParent = + LoneManagedOrNullAsserts(neckoParent->ManagedPCookieServiceParent()); + NS_ENSURE_TRUE(csParent, IPC_OK()); + auto* cs = static_cast<net::CookieServiceParent*>(csParent); + + return cs->SetCookies(aBaseDomain, aOriginAttributes, aHost, aFromHttp, + aCookies, GetBrowsingContext()); +} + +NS_IMPL_CYCLE_COLLECTION_INHERITED(WindowGlobalParent, WindowContext, + mPageUseCountersWindow) + +NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN_INHERITED(WindowGlobalParent, + WindowContext) +NS_IMPL_CYCLE_COLLECTION_TRACE_END + +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(WindowGlobalParent) +NS_INTERFACE_MAP_END_INHERITING(WindowContext) + +NS_IMPL_ADDREF_INHERITED(WindowGlobalParent, WindowContext) +NS_IMPL_RELEASE_INHERITED(WindowGlobalParent, WindowContext) + +} // namespace mozilla::dom |