/* -*- 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 #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 "MMPrinter.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::CreateDisconnected( const WindowGlobalInit& aInit) { RefPtr browsingContext = CanonicalBrowsingContext::Get(aInit.context().mBrowsingContextId); if (NS_WARN_IF(!browsingContext)) { return nullptr; } RefPtr 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(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 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.AccumulateSingleSample( mMaxOrigins); mMaxOrigins = 0; mOriginMap.Clear(); } /* static */ already_AddRefed WindowGlobalParent::GetByInnerWindowId( uint64_t aInnerWindowId) { if (!XRE_IsParentProcess()) { return nullptr; } return WindowContext::GetById(aInnerWindowId).downcast(); } already_AddRefed WindowGlobalParent::GetChildActor() { if (!CanSend()) { return nullptr; } IProtocol* otherSide = InProcessParent::ChildActorFor(this); return do_AddRef(static_cast(otherSide)); } BrowserParent* WindowGlobalParent::GetBrowserParent() { if (IsInProcess() || !CanSend()) { return nullptr; } return static_cast(Manager()); } ContentParent* WindowGlobalParent::GetContentParent() { if (IsInProcess() || !CanSend()) { return nullptr; } return static_cast(Manager()->Manager()); } already_AddRefed WindowGlobalParent::GetRootFrameLoader() { dom::BrowsingContext* top = BrowsingContext()->Top(); RefPtr frameLoaderOwner = do_QueryObject(top->GetEmbedderElement()); if (frameLoaderOwner) { return frameLoaderOwner->GetFrameLoader(); } return nullptr; } uint64_t WindowGlobalParent::ContentParentId() { RefPtr browserParent = GetBrowserParent(); return browserParent ? browserParent->Manager()->ChildID() : 0; } int32_t WindowGlobalParent::OsPid() { RefPtr 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 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& 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 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 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 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 ios = do_GetIOService(); if (!ios) { return IPC_FAIL(this, "Cannot get IOService"); } nsCOMPtr handler; ios->GetProtocolHandler(scheme.get(), getter_AddRefs(handler)); if (!handler) { return IPC_FAIL(this, "Setting DocumentURI with unknown protocol."); } nsCOMPtr principalURI = mDocumentPrincipal->GetURI(); if (mDocumentPrincipal->GetIsNullPrincipal()) { nsCOMPtr precursor = mDocumentPrincipal->GetPrecursorPrincipal(); if (precursor) { principalURI = precursor->GetURI(); } } if (nsScriptSecurityManager::IsHttpOrHttpsAndCrossOrigin(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 = GetBrowserParent(); if (!browserParent || !browserParent->IsDestroyed()) { Unused << Send__delete__(this); } } return IPC_OK(); } IPCResult WindowGlobalParent::RecvRawMessage( const JSActorMessageMeta& aMeta, const Maybe& aData, const Maybe& aStack) { Maybe data; if (aData) { data.emplace(); data->BorrowFromClonedMessageData(*aData); } Maybe stack; if (aStack) { stack.emplace(); stack->BorrowFromClonedMessageData(*aStack); } MMPrinter::Print("WindowGlobalParent::RecvRawMessage", aMeta.actorName(), aMeta.messageName(), aData); ReceiveRawMessage(aMeta, std::move(data), std::move(stack)); return IPC_OK(); } const nsACString& WindowGlobalParent::GetRemoteType() { if (RefPtr 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& aTrackingFullHashes, const Maybe& aReason, const Maybe& aCanvasFingerprinter, const Maybe aCanvasFingerprinterKnownText) { MOZ_ASSERT(NS_IsMainThread()); DebugOnly 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 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 WindowGlobalParent::GetActor( JSContext* aCx, const nsACString& aName, ErrorResult& aRv) { return JSActorManager::GetActor(aCx, aName, aRv) .downcast(); } already_AddRefed WindowGlobalParent::GetExistingActor( const nsACString& aName) { return JSActorManager::GetExistingActor(aName) .downcast(); } already_AddRefed WindowGlobalParent::InitJSActor( JS::Handle aMaybeActor, const nsACString& aName, ErrorResult& aRv) { RefPtr 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 aValue, ErrorResult& aRv) override { mResolver(NS_OK); } virtual void RejectedCallback(JSContext* aCx, JS::Handle 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 obj(aCx, &aValue.toObject()); RefPtr 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 sharePicker = do_GetService("@mozilla.org/sharepicker;1"); if (!sharePicker) { aResolver(NS_ERROR_DOM_NOT_SUPPORTED_ERR); return IPC_OK(); } // Initialize the ShareWidget RefPtr parent = GetBrowserParent(); nsCOMPtr 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; 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 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&& 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 self(this); AutoTArray 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 prompt = do_GetService("@mozilla.org/embedcomp/prompt-collection;1")) { RefPtr 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 aValue, ErrorResult& aRv) override { MOZ_ASSERT(mState == State::PROMPTING); SendReply(JS::ToBoolean(aValue)); } void RejectedCallback(JSContext* aCx, JS::Handle 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 mResolver; RefPtr mWGP; nsCOMPtr 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( this, aHasInProcessBlocker, aAction, std::move(aResolver)); request->Run(/* aIgnoreProcess */ GetContentParent()); return IPC_OK(); } already_AddRefed WindowGlobalParent::PermitUnload( PermitUnloadAction aAction, uint32_t aTimeout, mozilla::ErrorResult& aRv) { nsIGlobalObject* global = GetParentObject(); RefPtr promise = Promise::Create(global, aRv); if (NS_WARN_IF(aRv.Failed())) { return nullptr; } auto request = MakeRefPtr( 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&& aResolver) { RefPtr request = new CheckPermitUnloadRequest( this, /* aHasInProcessBlocker */ false, nsIDocumentViewer::PermitUnloadAction::ePrompt, std::move(aResolver)); request->Run(); } already_AddRefed WindowGlobalParent::DrawSnapshot( const DOMRect* aRect, double aScale, const nsACString& aBackgroundColor, bool aResetScrollPosition, mozilla::ErrorResult& aRv) { nsIGlobalObject* global = GetParentObject(); RefPtr 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& aRect, float aScale, nscolor aBackgroundColor, uint32_t aFlags) { auto promise = SendDrawSnapshot(aRect, aScale, aBackgroundColor, aFlags); RefPtr paint(aPaint); RefPtr 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& 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 page = static_cast(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(); } ++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 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(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 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& aSingleChannelId) { mSingleChannelId = aSingleChannelId; return IPC_OK(); } mozilla::ipc::IPCResult WindowGlobalParent::RecvSetDocumentDomain( NotNull 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 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 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 nestedURI = do_QueryInterface(currentUri); nsCOMPtr 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 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 permMgr = components::PermissionManager::Service(); if (!permMgr) { return IPC_FAIL( this, "HTTPS-only mode: Failed to get Permission Manager service"); } nsCOMPtr 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 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 loadState = new nsDocShellLoadState(insecureURI); loadState->SetTriggeringPrincipal(nsContentUtils::GetSystemPrincipal()); loadState->SetLoadType(LOAD_NORMAL_REPLACE); RefPtr 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(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 = static_cast(Manager()); if (browserParent) { nsCOMPtr 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 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 aGivenProto) { return WindowGlobalParent_Binding::Wrap(aCx, this, aGivenProto); } nsIGlobalObject* WindowGlobalParent::GetParentObject() { return xpc::NativeGlobal(xpc::PrivilegedJunkScope()); } nsIDOMProcessParent* WindowGlobalParent::GetDomProcess() { if (RefPtr 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 = 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 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& 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(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