/* 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/WindowGlobalChild.h" #include "GeckoProfiler.h" #include "Navigator.h" #include "Units.h" #include "mozilla/AntiTrackingUtils.h" #include "mozilla/ClearOnShutdown.h" #include "mozilla/ErrorResult.h" #include "mozilla/PresShell.h" #include "mozilla/ScopeExit.h" #include "mozilla/StaticPrefs_dom.h" #include "mozilla/dom/AudioSession.h" #include "mozilla/dom/BrowserBridgeChild.h" #include "mozilla/dom/BrowserChild.h" #include "mozilla/dom/BrowsingContext.h" #include "mozilla/dom/BrowsingContextGroup.h" #include "mozilla/dom/CloseWatcherManager.h" #include "mozilla/dom/ContentChild.h" #include "mozilla/dom/ContentParent.h" #include "mozilla/dom/Document.h" #include "mozilla/dom/Element.h" #include "mozilla/dom/IdentityCredential.h" #include "mozilla/dom/InProcessChild.h" #include "mozilla/dom/InProcessParent.h" #include "mozilla/dom/JSActorService.h" #include "mozilla/dom/JSWindowActorBinding.h" #include "mozilla/dom/JSWindowActorChild.h" #include "mozilla/dom/ModelContext.h" #include "mozilla/dom/MozFrameLoaderOwnerBinding.h" #include "mozilla/dom/PolicyContainer.h" #include "mozilla/dom/Promise-inl.h" #include "mozilla/dom/Promise.h" #include "mozilla/dom/ReportingUtils.h" #include "mozilla/dom/ScriptSettings.h" #include "mozilla/dom/SecurityPolicyViolationEvent.h" #include "mozilla/dom/SessionStoreRestoreData.h" #include "mozilla/dom/WindowContext.h" #include "mozilla/dom/WindowGlobalActorsBinding.h" #include "mozilla/dom/WindowGlobalParent.h" #include "mozilla/ipc/Endpoint.h" #include "nsAtom.h" #include "nsContentUtils.h" #include "nsDocShell.h" #include "nsFocusManager.h" #include "nsFrameLoader.h" #include "nsFrameLoaderOwner.h" #include "nsGlobalWindowInner.h" #include "nsIDocumentEncoder.h" #include "nsIHttpChannelInternal.h" #include "nsITimer.h" #include "nsIURIMutator.h" #include "nsNetUtil.h" #include "nsQueryObject.h" #include "nsScriptSecurityManager.h" #include "nsSerializationHelper.h" #include "nsURLHelper.h" #include "xpcpublic.h" using namespace mozilla::ipc; using namespace mozilla::dom::ipc; namespace mozilla::dom { // Retrieves language metadata for the current document. // // The request waits for the document to load up to a configured limit. Once // collection begins, it may retry until the visible text sample meets the // requested minimum or the configured retry limit is reached. The request // completes without metadata if the current document is no longer eligible for // collection. class WindowGlobalChild::DocumentLanguageMetadataRequest final { public: NS_INLINE_DECL_REFCOUNTING(DocumentLanguageMetadataRequest) DocumentLanguageMetadataRequest( WindowGlobalChild* aWindowGlobalChild, uint32_t aTextSampleMinCodeUnits, uint32_t aTextSampleTargetCodeUnits, RequestDocumentLanguageMetadataResolver&& aResolver) : mWindowGlobalChild(aWindowGlobalChild), mResolver(std::move(aResolver)), mTextSampleMinCodeUnits(aTextSampleMinCodeUnits), mTextSampleTargetCodeUnits(aTextSampleTargetCodeUnits) { MOZ_ASSERT(aWindowGlobalChild); MOZ_ASSERT(aTextSampleMinCodeUnits <= aTextSampleTargetCodeUnits); } // Starts the process of collecting language metadata from the document // by waiting for the document to load until the configured timeout, or // by collecting immediately if the document has already loaded. void Start() { RefPtr windowGlobalChild = mWindowGlobalChild.get(); if (!windowGlobalChild || !windowGlobalChild->CanCollectDocumentLanguageMetadata()) { Cancel(); return; } if (windowGlobalChild->GetWindowGlobal()->IsDocumentLoaded()) { OnDocumentLoaded(); return; } uint32_t loadTimeoutMs = StaticPrefs::dom_document_language_metadata_load_timeout_ms(); if (!ScheduleTimer(loadTimeoutMs)) { mLoadTimedOut = true; CollectOrScheduleRetry(); } } // Reacts to the document loading by starting the collection process. void OnDocumentLoaded() { if (mCompleted) { return; } CancelTimer(); CollectOrScheduleRetry(); } // Resolves the request without language metadata. void Cancel() { CancelTimer(); Resolve(Nothing()); } // Returns whether the request has completed. bool IsCompleted() const { return mCompleted; } private: ~DocumentLanguageMetadataRequest() { CancelTimer(); } // Schedules another attempt to collect language metadata. bool ScheduleRetry() { mRetryCount++; uint32_t retryDelayMs = StaticPrefs::dom_document_language_metadata_retry_delay_base_ms() * mRetryCount; return ScheduleTimer(retryDelayMs); } // Schedules language metadata collection after the requested delay. bool ScheduleTimer(uint32_t aDelayMs) { CancelTimer(); WeakPtr weakWindowGlobalChild = mWindowGlobalChild; RefPtr request = this; nsresult rv = NS_NewTimerWithCallback( getter_AddRefs(mTimer), [weakWindowGlobalChild, request](nsITimer*) { RefPtr windowGlobalChild = weakWindowGlobalChild.get(); if (!windowGlobalChild) { return; } if (!request->mCompleted) { if (request->mRetryCount == 0) { request->mLoadTimedOut = true; } request->CancelTimer(); request->CollectOrScheduleRetry(); } windowGlobalChild->RemoveCompletedDocumentLanguageMetadataRequests(); }, aDelayMs, nsITimer::TYPE_ONE_SHOT, "WindowGlobalChild::DocumentLanguageMetadataRequest"_ns); return NS_SUCCEEDED(rv); } void CancelTimer() { if (mTimer) { mTimer->Cancel(); mTimer = nullptr; } } // Returns whether another collection attempt should be made. bool ShouldRetry(const DocumentLanguageMetadata& aMetadata) const { if (mLoadTimedOut) { // The page did not load in time. We will make only one attempt // to extract whatever text is available. return false; } uint32_t maxRetries = StaticPrefs::dom_document_language_metadata_max_retries(); return aMetadata.mTextSample.Length() < mTextSampleMinCodeUnits && mRetryCount < maxRetries; } // Collects language metadata. If the text sample is too short, the request // may retry before resolving with the most recently collected metadata. void CollectOrScheduleRetry() { if (mCompleted) { return; } RefPtr windowGlobalChild = mWindowGlobalChild.get(); if (!windowGlobalChild || !windowGlobalChild->CanCollectDocumentLanguageMetadata()) { Cancel(); return; } Maybe metadata = windowGlobalChild->GetDocumentLanguageMetadata( mTextSampleTargetCodeUnits); if (metadata.isNothing()) { Cancel(); return; } if (ShouldRetry(*metadata)) { if (ScheduleRetry()) { return; } } Resolve(std::move(metadata)); } // Resolves the request with the provided result. void Resolve(Maybe&& aMetadata) { if (mCompleted) { return; } mCompleted = true; mWindowGlobalChild = nullptr; CancelTimer(); auto resolver = std::move(mResolver); resolver(std::move(aMetadata)); } WeakPtr mWindowGlobalChild; RequestDocumentLanguageMetadataResolver mResolver; nsCOMPtr mTimer; uint32_t mTextSampleMinCodeUnits; uint32_t mTextSampleTargetCodeUnits; uint32_t mRetryCount = 0; bool mCompleted = false; bool mLoadTimedOut = false; }; WindowGlobalChild::WindowGlobalChild(dom::WindowContext* aWindowContext, nsIPrincipal* aPrincipal, nsIURI* aDocumentURI) : mWindowContext(aWindowContext), mDocumentPrincipal(aPrincipal), mDocumentURI(aDocumentURI) { MOZ_DIAGNOSTIC_ASSERT(mWindowContext); MOZ_DIAGNOSTIC_ASSERT(mDocumentPrincipal); MOZ_DIAGNOSTIC_ASSERT(mDocumentPrincipal->GetIsLocalIpAddress() == mWindowContext->IsLocalIP()); if (!mDocumentURI) { NS_NewURI(getter_AddRefs(mDocumentURI), "about:blank"); } // Registers a DOM Window with the profiler. It re-registers the same Inner // Window ID with different URIs because when a Browsing context is first // loaded, the first url loaded in it will be about:blank. This call keeps the // first non-about:blank registration of window and discards the previous one. uint64_t embedderInnerWindowID = 0; if (BrowsingContext()->GetParent()) { embedderInnerWindowID = BrowsingContext()->GetEmbedderInnerWindowId(); } profiler_register_page( BrowsingContext()->BrowserId(), InnerWindowId(), nsContentUtils::TruncatedURLForDisplay(aDocumentURI, 1024), embedderInnerWindowID, BrowsingContext()->UsePrivateBrowsing()); } already_AddRefed WindowGlobalChild::Create( nsGlobalWindowInner* aWindow) { #ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED // Opener policy is set when we start to load a document. Here, we ensure we // have set the correct Opener policy so that it will be available in the // parent process through window global child. nsCOMPtr chan = aWindow->GetDocument()->GetChannel(); nsCOMPtr loadInfo = chan ? chan->LoadInfo() : nullptr; nsCOMPtr httpChan = do_QueryInterface(chan); nsILoadInfo::CrossOriginOpenerPolicy policy; if (httpChan && loadInfo->GetExternalContentPolicyType() == ExtContentPolicy::TYPE_DOCUMENT && NS_SUCCEEDED(httpChan->GetCrossOriginOpenerPolicy(&policy))) { MOZ_DIAGNOSTIC_ASSERT(policy == aWindow->GetBrowsingContext()->GetOpenerPolicy()); } #endif WindowGlobalInit init = WindowGlobalActor::WindowInitializer(aWindow); RefPtr wgc = CreateDisconnected(init); // Send the link constructor over PBrowser, or link over PInProcess. if (XRE_IsParentProcess()) { InProcessChild* ipChild = InProcessChild::Singleton(); InProcessParent* ipParent = InProcessParent::Singleton(); if (!ipChild || !ipParent) { return nullptr; } ManagedEndpoint endpoint = ipChild->OpenPWindowGlobalEndpoint(wgc); ipParent->BindPWindowGlobalEndpoint(std::move(endpoint), wgc->WindowContext()->Canonical()); } else { RefPtr browserChild = BrowserChild::GetFrom(static_cast(aWindow)); MOZ_ASSERT(browserChild); #ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED dom::BrowsingContext* bc = aWindow->GetBrowsingContext(); #endif MOZ_DIAGNOSTIC_ASSERT(bc->AncestorsAreCurrent()); MOZ_DIAGNOSTIC_ASSERT(bc->IsInProcess()); MOZ_RELEASE_ASSERT(VerifyPartitionedPrincipalMatchesDocumentPrincipal( init.principal(), init.partitionedPrincipal())); ManagedEndpoint endpoint = browserChild->OpenPWindowGlobalEndpoint(wgc); browserChild->SendNewWindowGlobal(std::move(endpoint), init); } wgc->Init(); wgc->InitWindowGlobal(aWindow); return wgc.forget(); } already_AddRefed WindowGlobalChild::CreateDisconnected( const WindowGlobalInit& aInit) { RefPtr browsingContext = dom::BrowsingContext::Get(aInit.context().mBrowsingContextId); RefPtr windowContext = dom::WindowContext::GetById(aInit.context().mInnerWindowId); MOZ_RELEASE_ASSERT(!windowContext, "Creating duplicate WindowContext"); // Create our new WindowContext if (XRE_IsParentProcess()) { windowContext = WindowGlobalParent::CreateDisconnected(aInit, nullptr); } else { dom::WindowContext::FieldValues fields = aInit.context().mFields; windowContext = new dom::WindowContext( browsingContext, aInit.context().mInnerWindowId, aInit.context().mOuterWindowId, std::move(fields)); } RefPtr windowChild = new WindowGlobalChild( windowContext, aInit.principal(), aInit.documentURI()); windowContext->mIsInProcess = true; windowContext->mWindowGlobalChild = windowChild; return windowChild.forget(); } void WindowGlobalChild::Init() { MOZ_ASSERT(mWindowContext->mWindowGlobalChild == this); mWindowContext->Init(); } void WindowGlobalChild::InitWindowGlobal(nsGlobalWindowInner* aWindow) { mWindowGlobal = aWindow; } void WindowGlobalChild::OnNewDocument(Document* aDocument) { MOZ_RELEASE_ASSERT(mWindowGlobal); MOZ_RELEASE_ASSERT(aDocument); MOZ_RELEASE_ASSERT(mDocumentPrincipal->Equals(aDocument->NodePrincipal()), "Cannot change document principal origin"); MOZ_RELEASE_ASSERT( VerifyPartitionedPrincipalMatchesDocumentPrincipal( aDocument->NodePrincipal(), aDocument->PartitionedPrincipal()), "Invalid partitioned principal"); CancelDocumentLanguageMetadataRequests(); mDocumentPrincipal = aDocument->NodePrincipal(); // Send a series of messages to update document-specific state on // WindowGlobalParent, when we change documents on an existing WindowGlobal. // This data is also all sent when we construct a WindowGlobal, so anything // added here should also be added to WindowGlobalActor::WindowInitializer. // FIXME: Perhaps these should be combined into a smaller number of messages? SendSetIsInitialDocument(aDocument->IsInitialDocument()); SetDocumentURI(aDocument->GetDocumentURI()); SendUpdateDocumentPrincipal(WrapNotNull(aDocument->NodePrincipal()), WrapNotNull(aDocument->PartitionedPrincipal())); SendUpdatePrincipalPartitioning(!aDocument->UseRegularPrincipal()); RefPtr documentChannelHandle; if (nsIChannel* chan = aDocument->GetChannel()) { (void)chan->GetParentProcessChannelHandle( getter_AddRefs(documentChannelHandle)); } RefPtr failedChannelHandle; if (nsIChannel* chan = aDocument->GetFailedChannel()) { (void)chan->GetParentProcessChannelHandle( getter_AddRefs(failedChannelHandle)); } SendUpdateChannels(documentChannelHandle, failedChannelHandle); SendUpdateDocumentCspSettings(aDocument->GetBlockAllMixedContent(false), aDocument->GetUpgradeInsecureRequests(false)); SendUpdateSandboxFlags(aDocument->GetSandboxFlags()); net::CookieJarSettingsArgs csArgs; net::CookieJarSettings::Cast(aDocument->CookieJarSettings()) ->Serialize(csArgs); if (!SendUpdateCookieJarSettings(csArgs)) { NS_WARNING( "Failed to update document's cookie jar settings on the " "WindowGlobalParent"); } SendUpdateHttpsOnlyStatus(aDocument->HttpsOnlyStatus()); // Update window context fields for the newly loaded Document. WindowContext::Transaction txn; txn.SetCookieBehavior( Some(aDocument->CookieJarSettings()->GetCookieBehavior())); txn.SetIsOnContentBlockingAllowList( aDocument->CookieJarSettings()->GetIsOnContentBlockingAllowList()); txn.SetIsThirdPartyWindow(aDocument->HasThirdPartyChannel()); txn.SetIsThirdPartyTrackingResourceWindow( nsContentUtils::IsThirdPartyTrackingResourceWindow(mWindowGlobal)); txn.SetIsSecureContext(mWindowGlobal->IsSecureContext()); txn.SetIsFramebustingAllowed( mWindowGlobal->GetBrowsingContext()->ComputeIsFramebustingAllowed()); if (auto policy = aDocument->GetEmbedderPolicy()) { txn.SetEmbedderPolicy(*policy); } txn.SetShouldResistFingerprinting(aDocument->ShouldResistFingerprinting( RFPTarget::IsAlwaysEnabledForPrecompute)); txn.SetOverriddenFingerprintingSettings( aDocument->GetOverriddenFingerprintingSettings()); if (nsCOMPtr channel = aDocument->GetChannel()) { nsCOMPtr loadInfo(channel->LoadInfo()); txn.SetIsOriginalFrameSource(loadInfo->GetOriginalFrameSrcLoad()); nsILoadInfo::StoragePermissionState storageAccess = loadInfo->GetStoragePermission(); txn.SetUsingStorageAccess( storageAccess == nsILoadInfo::HasStoragePermission || storageAccess == nsILoadInfo::StoragePermissionAllowListed); } else { txn.SetIsOriginalFrameSource(false); } // Init Mixed Content Fields nsCOMPtr innerDocURI = NS_GetInnermostURI(aDocument->GetDocumentURI()); if (innerDocURI) { txn.SetIsSecure(innerDocURI->SchemeIs("https")); } MOZ_DIAGNOSTIC_ASSERT(mDocumentPrincipal->GetIsLocalIpAddress() == mWindowContext->IsLocalIP()); MOZ_ALWAYS_SUCCEEDS(txn.Commit(mWindowContext)); } void WindowGlobalChild::OnDocumentLoaded() { for (const RefPtr& request : mDocumentLanguageMetadataRequests) { request->OnDocumentLoaded(); } RemoveCompletedDocumentLanguageMetadataRequests(); } void WindowGlobalChild::OnDocumentUnloaded() { CancelDocumentLanguageMetadataRequests(); } bool WindowGlobalChild::CanCollectDocumentLanguageMetadata() { if (!mWindowGlobal || IsClosed() || !mWindowGlobal->IsCurrentInnerWindow()) { return false; } Document* document = mWindowGlobal->GetExtantDoc(); if (!document || !document->IsTopLevelContentDocument() || document->IsInitialDocument() || !document->IsCurrentActiveDocument() || document->GetWindowGlobalChild() != this) { return false; } nsIURI* uri = document->GetDocumentURI(); if (!uri) { return false; } return uri->SchemeIs("https") || uri->SchemeIs("http") || uri->SchemeIs("file") || uri->SchemeIs("moz-extension"); } Maybe WindowGlobalChild::GetDocumentLanguageMetadata( uint32_t aTextSampleTargetCodeUnits) { if (!CanCollectDocumentLanguageMetadata()) { return Nothing(); } Document* document = mWindowGlobal->GetExtantDoc(); DocumentLanguageMetadata metadata; if (Element* root = document->GetRootElement()) { if (nsAtom* langAtom = root->GetLang()) { langAtom->ToString(metadata.mHtmlLangAttribute); } } AUTO_PROFILER_MARKER_INNERWINDOWID("DocumentLanguageMetadata", DOM, InnerWindowId()); nsCOMPtr encoder = do_createDocumentEncoder("text/plain"); uint32_t flags = nsIDocumentEncoder::OutputBodyOnly | nsIDocumentEncoder::SkipInvisibleContent | nsIDocumentEncoder::AllowCrossShadowBoundary | nsIDocumentEncoder::OutputForPlainTextClipboardCopy | nsIDocumentEncoder::OutputDisallowLineBreaking | nsIDocumentEncoder::OutputDropInvisibleBreak | nsIDocumentEncoder::OutputLFLineBreak; nsresult rv = encoder->Init(document, u"text/plain"_ns, flags); if (NS_FAILED(rv)) { return Some(std::move(metadata)); } nsAutoString textSample; rv = encoder->EncodeToStringWithMaxLength(aTextSampleTargetCodeUnits, textSample); if (NS_SUCCEEDED(rv)) { metadata.mTextSample = textSample; } return Some(std::move(metadata)); } void WindowGlobalChild::RemoveCompletedDocumentLanguageMetadataRequests() { mDocumentLanguageMetadataRequests.RemoveElementsBy( [](const RefPtr& aRequest) { return aRequest->IsCompleted(); }); } void WindowGlobalChild::CancelDocumentLanguageMetadataRequests() { for (const RefPtr& request : mDocumentLanguageMetadataRequests) { request->Cancel(); } mDocumentLanguageMetadataRequests.Clear(); } /* static */ already_AddRefed WindowGlobalChild::GetByInnerWindowId( uint64_t aInnerWindowId) { if (RefPtr context = dom::WindowContext::GetById(aInnerWindowId)) { return do_AddRef(context->GetWindowGlobalChild()); } return nullptr; } dom::BrowsingContext* WindowGlobalChild::BrowsingContext() { return mWindowContext->GetBrowsingContext(); } Nullable WindowGlobalChild::GetContentWindow() { if (IsCurrentGlobal()) { return WindowProxyHolder(BrowsingContext()); } return nullptr; } uint64_t WindowGlobalChild::InnerWindowId() { return mWindowContext->InnerWindowId(); } uint64_t WindowGlobalChild::OuterWindowId() { return mWindowContext->OuterWindowId(); } bool WindowGlobalChild::IsCurrentGlobal() { return CanSend() && mWindowGlobal->IsCurrentInnerWindow(); } already_AddRefed WindowGlobalChild::GetParentActor() { if (!CanSend()) { return nullptr; } IProtocol* otherSide = InProcessChild::ParentActorFor(this); return do_AddRef(static_cast(otherSide)); } already_AddRefed WindowGlobalChild::GetBrowserChild() { if (IsInProcess() || !CanSend()) { return nullptr; } return do_AddRef(static_cast(Manager())); } uint64_t WindowGlobalChild::ContentParentId() { if (XRE_IsParentProcess()) { return 0; } return ContentChild::GetSingleton()->GetID(); } // A WindowGlobalChild is the root in its process if it has no parent, or its // embedder is in a different process. bool WindowGlobalChild::IsProcessRoot() { if (!BrowsingContext()->GetParent()) { return true; } return !BrowsingContext()->GetEmbedderElement(); } // When a "beforeunload" handler is added, it's recorded to be able to know when // dispatching "beforeunload" is needed. void WindowGlobalChild::BeforeUnloadAdded() { // Don't bother notifying the parent if we don't have an IPC link open. if (mBeforeUnloadListeners == 0 && CanSend()) { (void)mWindowContext->SetNeedsBeforeUnload(true); } mBeforeUnloadListeners++; MOZ_ASSERT(mBeforeUnloadListeners > 0); } // This is the inverse of `BeforeUnloadAdded`, making sure that "beforeunload" // isn't dispatched if all "beforeunload" handlers have been removed. void WindowGlobalChild::BeforeUnloadRemoved() { mBeforeUnloadListeners--; MOZ_ASSERT(mBeforeUnloadListeners >= 0); if (mBeforeUnloadListeners == 0) { (void)mWindowContext->SetNeedsBeforeUnload(false); } } // This is very similar to what is done for "beforeunload" and uses the same // state to keep track, but is only ever used for a top level window. It's used // to be able to track when a "navigate" event needs to be dispatched to the top // level window's navigation object, which needs to happen right after a // "beforeunload" event for that window would be dispatched, regardless of if it // is. void WindowGlobalChild::NavigateAdded() { if (!BrowsingContext()->IsTop()) { return; } BeforeUnloadAdded(); } // The inverse of `NavigateAdded`, again only ever used for a top level window. void WindowGlobalChild::NavigateRemoved() { if (!BrowsingContext()->IsTop()) { return; } BeforeUnloadRemoved(); } void WindowGlobalChild::Destroy() { CancelDocumentLanguageMetadataRequests(); JSActorWillDestroy(); mWindowContext->Discard(); // Perform async IPC shutdown unless we're not in-process, and our // BrowserChild is in the process of being destroyed, which will destroy // us as well. RefPtr browserChild = GetBrowserChild(); if (!browserChild || !browserChild->IsDestroyed()) { SendDestroy(); } } mozilla::ipc::IPCResult WindowGlobalChild::RecvMakeFrameLocal( const MaybeDiscarded& aFrameContext, uint64_t aPendingSwitchId) { MOZ_DIAGNOSTIC_ASSERT(XRE_IsContentProcess()); MOZ_LOG(BrowsingContext::GetLog(), LogLevel::Debug, ("RecvMakeFrameLocal ID=%" PRIx64, aFrameContext.ContextId())); if (NS_WARN_IF(aFrameContext.IsNullOrDiscarded())) { return IPC_OK(); } dom::BrowsingContext* frameContext = aFrameContext.get(); RefPtr embedderElt = frameContext->GetEmbedderElement(); if (NS_WARN_IF(!embedderElt)) { return IPC_OK(); } if (NS_WARN_IF(embedderElt->GetDocumentGlobal() != GetWindowGlobal())) { return IPC_OK(); } RefPtr flo = do_QueryObject(embedderElt); MOZ_DIAGNOSTIC_ASSERT(flo, "Embedder must be a nsFrameLoaderOwner"); // Trigger a process switch into the current process. RemotenessOptions options; options.mRemoteType = dom::RemoteType::NotRemote().Stringify(); options.mPendingSwitchID.Construct(aPendingSwitchId); options.mSwitchingInProgressLoad = true; flo->ChangeRemoteness(options, IgnoreErrors()); return IPC_OK(); } mozilla::ipc::IPCResult WindowGlobalChild::RecvMakeFrameRemote( const MaybeDiscarded& aFrameContext, ManagedEndpoint&& aEndpoint, const TabId& aTabId, const LayersId& aLayersId, MakeFrameRemoteResolver&& aResolve) { MOZ_DIAGNOSTIC_ASSERT(XRE_IsContentProcess()); MOZ_LOG(BrowsingContext::GetLog(), LogLevel::Debug, ("RecvMakeFrameRemote ID=%" PRIx64, aFrameContext.ContextId())); if (!aLayersId.IsValid()) { return IPC_FAIL(this, "Received an invalid LayersId"); } // Resolve the promise when this function exits, as we'll have fully unloaded // at that point. auto scopeExit = MakeScopeExit([&] { aResolve(true); }); // Get a BrowsingContext if we're not null or discarded. We don't want to // early-return before we connect the BrowserBridgeChild, as otherwise we'll // never break the channel in the parent. RefPtr frameContext; if (!aFrameContext.IsDiscarded()) { frameContext = aFrameContext.get(); } // Immediately construct the BrowserBridgeChild so we can destroy it cleanly // if the process switch fails. RefPtr bridge = new BrowserBridgeChild(frameContext, aTabId, aLayersId); RefPtr manager = GetBrowserChild(); if (NS_WARN_IF( !manager->BindPBrowserBridgeEndpoint(std::move(aEndpoint), bridge))) { return IPC_OK(); } // Synchronously delete de actor here rather than using SendBeginDestroy(), as // we haven't initialized it yet. auto deleteBridge = MakeScopeExit([&] { BrowserBridgeChild::Send__delete__(bridge); }); // Immediately tear down the actor if we don't have a valid FrameContext. if (NS_WARN_IF(aFrameContext.IsNullOrDiscarded())) { return IPC_OK(); } RefPtr embedderElt = frameContext->GetEmbedderElement(); if (NS_WARN_IF(!embedderElt)) { return IPC_OK(); } if (NS_WARN_IF(embedderElt->GetDocumentGlobal() != GetWindowGlobal())) { return IPC_OK(); } RefPtr flo = do_QueryObject(embedderElt); MOZ_DIAGNOSTIC_ASSERT(flo, "Embedder must be a nsFrameLoaderOwner"); // Trgger a process switch into the specified process. IgnoredErrorResult rv; flo->ChangeRemotenessWithBridge(bridge, rv); if (NS_WARN_IF(rv.Failed())) { return IPC_OK(); } // Everything succeeded, so don't delete the bridge. deleteBridge.release(); return IPC_OK(); } mozilla::ipc::IPCResult WindowGlobalChild::RecvDrawSnapshot( const Maybe& aRect, const float& aScale, const nscolor& aBackgroundColor, const gfx::CrossProcessPaintFlags& aFlags, DrawSnapshotResolver&& aResolve) { aResolve(gfx::PaintFragment::Record(BrowsingContext(), aRect, aScale, aBackgroundColor, aFlags)); return IPC_OK(); } mozilla::ipc::IPCResult WindowGlobalChild::RecvRequestDocumentLanguageMetadata( uint32_t aTextSampleMinCodeUnits, uint32_t aTextSampleTargetCodeUnits, RequestDocumentLanguageMetadataResolver&& aResolver) { MOZ_ASSERT(aTextSampleMinCodeUnits <= aTextSampleTargetCodeUnits); RefPtr request = MakeRefPtr( this, aTextSampleMinCodeUnits, aTextSampleTargetCodeUnits, std::move(aResolver)); mDocumentLanguageMetadataRequests.AppendElement(request); request->Start(); RemoveCompletedDocumentLanguageMetadataRequests(); return IPC_OK(); } mozilla::ipc::IPCResult WindowGlobalChild::RecvSaveStorageAccessPermissionGranted() { nsCOMPtr inner = GetWindowGlobal(); if (inner) { inner->SaveStorageAccessPermissionGranted(); } return IPC_OK(); } mozilla::ipc::IPCResult WindowGlobalChild::RecvDispatchSecurityPolicyViolation( const nsString& aViolationEventJSON, const nsString& aReportGroupName) { nsGlobalWindowInner* window = GetWindowGlobal(); if (!window) { return IPC_OK(); } Document* doc = window->GetDocument(); if (!doc) { return IPC_OK(); } ReportingUtils::DeserializeSecurityViolationEventAndReport( doc->GetTargetForDOMEvent(), window, aViolationEventJSON, aReportGroupName); return IPC_OK(); } mozilla::ipc::IPCResult WindowGlobalChild::RecvAddBlockedFrameNodeByClassifier( const MaybeDiscardedBrowsingContext& aNode) { if (aNode.IsNullOrDiscarded()) { return IPC_OK(); } nsGlobalWindowInner* window = GetWindowGlobal(); if (!window) { return IPC_OK(); } Document* doc = window->GetDocument(); if (!doc) { return IPC_OK(); } MOZ_ASSERT(aNode.get()->GetEmbedderElement()->OwnerDoc() == doc); doc->AddBlockedNodeByClassifier(aNode.get()->GetEmbedderElement()); return IPC_OK(); } mozilla::ipc::IPCResult WindowGlobalChild::RecvResetScalingZoom() { if (Document* doc = mWindowGlobal->GetExtantDoc()) { if (PresShell* ps = doc->GetPresShell()) { ps->SetResolutionAndScaleTo(1.0, ResolutionChangeOrigin::MainThreadAdjustment); } } return IPC_OK(); } mozilla::ipc::IPCResult WindowGlobalChild::RecvRestoreDocShellState( const dom::sessionstore::DocShellRestoreState& aState, RestoreDocShellStateResolver&& aResolve) { if (mWindowGlobal) { SessionStoreUtils::RestoreDocShellState(mWindowGlobal->GetDocShell(), aState); } aResolve(true); return IPC_OK(); } mozilla::ipc::IPCResult WindowGlobalChild::RecvRestoreTabContent( dom::SessionStoreRestoreData* aData, RestoreTabContentResolver&& aResolve) { aData->RestoreInto(BrowsingContext()); aResolve(true); return IPC_OK(); } IPCResult WindowGlobalChild::RecvRawMessage(const JSActorMessageMeta& aMeta, JSIPCValue&& aData, StructuredCloneData* aStack) { ReceiveRawMessage(aMeta, std::move(aData), aStack); return IPC_OK(); } IPCResult WindowGlobalChild::RecvNotifyAudioSessionStateChanged( const AudioSessionState& aState) { nsGlobalWindowInner* window = GetWindowGlobal(); if (!window) { return IPC_OK(); } if (RefPtr session = window->Navigator()->AudioSession()) { session->SetState(aState); } return IPC_OK(); } IPCResult WindowGlobalChild::RecvNotifyPermissionChange(const nsCString& aType, uint32_t aPermission) { nsCOMPtr observerService = services::GetObserverService(); NS_ENSURE_TRUE(observerService, IPC_FAIL(this, "Failed to get observer service")); nsPIDOMWindowInner* notifyTarget = static_cast(this->GetWindowGlobal()); observerService->NotifyObservers(notifyTarget, "perm-changed-notify-only", NS_ConvertUTF8toUTF16(aType).get()); // We only need to handle the revoked permission case here. The permission // grant case is handled via the Storage Access API code. if (this->GetWindowGlobal() && this->GetWindowGlobal()->UsingStorageAccess() && aPermission != nsIPermissionManager::ALLOW_ACTION) { this->GetWindowGlobal()->SaveStorageAccessPermissionRevoked(); } return IPC_OK(); } IPCResult WindowGlobalChild::RecvProcessCloseRequest( const MaybeDiscarded& aFocused) { RefPtr focusManager = nsFocusManager::GetFocusManager(); RefPtr focusedContext = focusManager ? focusManager->GetFocusedBrowsingContext() : nullptr; if (!focusedContext) { return IPC_OK(); } // Only the currently focused context's CloseWatcher should be processed. if (RefPtr doc = focusedContext->GetExtantDocument()) { RefPtr win = doc->GetInnerWindow(); if (win && win->IsFullyActive()) { RefPtr manager = win->EnsureCloseWatcherManager(); manager->ProcessCloseRequest(); } } return IPC_OK(); } IPCResult WindowGlobalChild::RecvGetModelContextTools( GetModelContextToolsResolver&& aResolver) { if (!IsCurrentGlobal()) { aResolver(std::make_tuple(NS_ERROR_DOM_INVALID_ACCESS_ERR, nsTArray())); return IPC_OK(); } if (!BrowsingContext()->IsTop()) { aResolver(std::make_tuple(NS_ERROR_DOM_INVALID_ACCESS_ERR, nsTArray())); return IPC_OK(); } nsTArray tools; if (nsGlobalWindowInner* win = GetWindowGlobal()) { if (Navigator* nav = win->Navigator()) { if (ModelContext* mc = nav->ModelContext()) { mc->GetIPCToolDefinitions(tools); } } } aResolver(std::make_tuple(NS_OK, std::move(tools))); return IPC_OK(); } IPCResult WindowGlobalChild::RecvGetContentMetrics( GetContentMetricsResolver&& aResolver) { CSSSize size; float devicePixelRatio = 1.0f; if (IsCurrentGlobal()) { if (RefPtr win = GetWindowGlobal()) { if (RefPtr doc = win->GetExtantDoc()) { if (RefPtr root = doc->GetDocumentElement()) { size = CSSPixel::FromAppUnits(root->GetScrollSize()); } } IgnoredErrorResult rv; double dpr = win->GetDevicePixelRatio(CallerType::System, rv); if (!rv.Failed() && dpr > 0.0) { devicePixelRatio = float(dpr); } } } aResolver(std::make_tuple(size, devicePixelRatio)); return IPC_OK(); } IPCResult WindowGlobalChild::RecvInvokeModelContextTool( const nsCString& aToolName, NotNull aInput, InvokeModelContextToolResolver&& aResolver) { if (!IsCurrentGlobal()) { aResolver(std::make_tuple( CopyableErrorResult(NS_ERROR_DOM_INVALID_ACCESS_ERR), nullptr)); return IPC_OK(); } if (!BrowsingContext()->IsTop()) { aResolver(std::make_tuple( CopyableErrorResult(NS_ERROR_DOM_INVALID_ACCESS_ERR), nullptr)); return IPC_OK(); } nsGlobalWindowInner* win = GetWindowGlobal(); Navigator* nav = win ? win->Navigator() : nullptr; RefPtr mc = nav ? nav->ModelContext() : nullptr; if (!mc) { aResolver(std::make_tuple( CopyableErrorResult(NS_ERROR_DOM_INVALID_ACCESS_ERR), nullptr)); return IPC_OK(); } AutoJSAPI jsapi; if (!jsapi.Init(win)) { aResolver(std::make_tuple(CopyableErrorResult(NS_ERROR_FAILURE), nullptr)); return IPC_OK(); } JSContext* cx = jsapi.cx(); JS::Rooted inputVal(cx); IgnoredErrorResult deserializeRv; aInput->Read(cx, &inputVal, deserializeRv); if (deserializeRv.Failed()) { aResolver(std::make_tuple(CopyableErrorResult(NS_ERROR_DOM_DATA_CLONE_ERR), nullptr)); return IPC_OK(); } ErrorResult rv; RefPtr domPromise = mc->InvokeToolInternal( cx, NS_ConvertUTF8toUTF16(aToolName), inputVal, rv); if (!domPromise) { aResolver(std::make_tuple(CopyableErrorResult(std::move(rv)), nullptr)); return IPC_OK(); } domPromise->AddCallbacksWithCycleCollectedArgs( [aResolver](JSContext* aCx, JS::Handle aValue, ErrorResult&) { RefPtr cloneData = MakeRefPtr(); IgnoredErrorResult rv; cloneData->Write(aCx, aValue, rv); if (rv.Failed()) { aResolver(std::make_tuple( CopyableErrorResult(NS_ERROR_DOM_DATA_CLONE_ERR), nullptr)); return; } aResolver(std::make_tuple(CopyableErrorResult(), std::move(cloneData))); }, [aResolver](JSContext* aCx, JS::Handle aValue, ErrorResult&) { RefPtr cloneData = MakeRefPtr(); IgnoredErrorResult rv; cloneData->Write(aCx, aValue, rv); if (rv.Failed()) { aResolver( std::make_tuple(CopyableErrorResult(NS_ERROR_FAILURE), nullptr)); return; } aResolver(std::make_tuple(CopyableErrorResult(NS_ERROR_FAILURE), std::move(cloneData))); }); return IPC_OK(); } void WindowGlobalChild::SetDocumentURI(nsIURI* aDocumentURI) { // Registers a DOM Window with the profiler. It re-registers the same Inner // Window ID with different URIs because when a Browsing context is first // loaded, the first url loaded in it will be about:blank. This call keeps the // first non-about:blank registration of window and discards the previous one. uint64_t embedderInnerWindowID = 0; if (BrowsingContext()->GetParent()) { embedderInnerWindowID = BrowsingContext()->GetEmbedderInnerWindowId(); } profiler_register_page( BrowsingContext()->BrowserId(), InnerWindowId(), nsContentUtils::TruncatedURLForDisplay(aDocumentURI, 1024), embedderInnerWindowID, BrowsingContext()->UsePrivateBrowsing()); nsCOMPtr principalURI = mDocumentPrincipal->GetURI(); if (mDocumentPrincipal->GetIsNullPrincipal()) { if (nsCOMPtr precursor = mDocumentPrincipal->GetPrecursorPrincipal()) { principalURI = precursor->GetURI(); } } MOZ_DIAGNOSTIC_ASSERT(!nsScriptSecurityManager::IsHttpOrHttpsAndCrossOrigin( principalURI, aDocumentURI), "Setting DocumentURI with a different origin " "than principal URI"); mDocumentURI = aDocumentURI; SendUpdateDocumentURI(WrapNotNull(aDocumentURI)); } const RemoteType& WindowGlobalChild::GetRemoteType() const { if (XRE_IsContentProcess()) { return ContentChild::GetSingleton()->GetRemoteType(); } return RemoteType::NotRemote(); } already_AddRefed WindowGlobalChild::GetActor( JSContext* aCx, const nsACString& aName, ErrorResult& aRv) { return JSActorManager::GetActor(aCx, aName, aRv) .downcast(); } already_AddRefed WindowGlobalChild::GetExistingActor( const nsACString& aName) { return JSActorManager::GetExistingActor(aName).downcast(); } already_AddRefed WindowGlobalChild::InitJSActor( JS::Handle aMaybeActor, const nsACString& aName, ErrorResult& aRv) { RefPtr actor; if (aMaybeActor.get()) { aRv = UNWRAP_OBJECT(JSWindowActorChild, aMaybeActor.get(), actor); if (aRv.Failed()) { return nullptr; } } else { actor = new JSWindowActorChild(); } MOZ_RELEASE_ASSERT(!actor->GetManager(), "mManager was already initialized once!"); actor->Init(aName, this); return actor.forget(); } void WindowGlobalChild::ActorDestroy(ActorDestroyReason aWhy) { MOZ_ASSERT(nsContentUtils::IsSafeToRunScript(), "Destroying WindowGlobalChild can run script"); CancelDocumentLanguageMetadataRequests(); // If our WindowContext hasn't been marked as discarded yet, ensure it's // marked as discarded at this point. mWindowContext->Discard(); profiler_unregister_page(InnerWindowId()); // Destroy our JSActors, and reject any pending queries. JSActorDidDestroy(); } bool WindowGlobalChild::IsSameOriginWith( const dom::WindowContext* aOther) const { if (aOther == WindowContext()) { return true; } MOZ_DIAGNOSTIC_ASSERT(WindowContext()->Group() == aOther->Group()); if (nsGlobalWindowInner* otherWin = aOther->GetInnerWindow()) { return mDocumentPrincipal->Equals(otherWin->GetPrincipal()); } return false; } bool WindowGlobalChild::SameOriginWithTop() { return IsSameOriginWith(WindowContext()->TopWindowContext()); } // For historical context, see: // // Bug 13871: Prevent frameset spoofing // Bug 103638: Targets with same name in different windows open in wrong // window with javascript // Bug 408052: Adopt "ancestor" frame navigation policy // Bug 1570207: Refactor logic to rely on BrowsingContextGroups to enforce // origin attribute isolation // Bug 1810619: Crash at null in nsDocShell::ValidateOrigin bool WindowGlobalChild::CanNavigate(dom::BrowsingContext* aTarget, bool aConsiderOpener) { return nsContentUtils::CanNavigate(BrowsingContext(), aTarget, DocumentPrincipal(), aConsiderOpener); } // FindWithName follows the rules for choosing a browsing context, // with the exception of sandboxing for iframes. The implementation // for arbitrarily choosing between two browsing contexts with the // same name is as follows: // // 1) The start browsing context, i.e. 'this' // 2) Descendants in insertion order // 3) The parent // 4) Siblings and their children, both in insertion order // 5) After this we iteratively follow the parent chain, repeating 3 // and 4 until // 6) If there is no parent, consider all other top level browsing // contexts and their children, both in insertion order // // See // https://html.spec.whatwg.org/multipage/browsers.html#the-rules-for-choosing-a-browsing-context-given-a-browsing-context-name dom::BrowsingContext* WindowGlobalChild::FindBrowsingContextWithName( const nsAString& aName, bool aUseEntryGlobalForAccessCheck) { RefPtr requestingContext = this; if (aUseEntryGlobalForAccessCheck) { if (nsGlobalWindowInner* caller = nsContentUtils::EntryInnerWindow()) { if (caller->GetBrowsingContextGroup() == WindowContext()->Group()) { requestingContext = caller->GetWindowGlobalChild(); } else { MOZ_RELEASE_ASSERT(caller->GetPrincipal()->IsSystemPrincipal(), "caller must be either same-group or system"); } } } MOZ_ASSERT(requestingContext, "must have a requestingContext"); dom::BrowsingContext* found = nullptr; if (aName.IsEmpty()) { // You can't find a browsing context with an empty name. found = nullptr; } else if (aName.LowerCaseEqualsLiteral("_blank")) { // Just return null. Caller must handle creating a new window with // a blank name. found = nullptr; } else if (nsContentUtils::IsSpecialName(aName)) { found = BrowsingContext()->FindWithSpecialName(aName, *requestingContext); } else if (dom::BrowsingContext* child = BrowsingContext()->FindWithNameInSubtree(aName, requestingContext)) { found = child; } else { dom::WindowContext* current = WindowContext(); do { Span> siblings; dom::WindowContext* parent = current->GetParentWindowContext(); if (!parent) { // We've reached the root of the tree, consider browsing // contexts in the same browsing context group. siblings = WindowContext()->Group()->Toplevels(); } else if (dom::BrowsingContext* bc = parent->GetBrowsingContext(); bc && bc->NameEquals(aName) && requestingContext->CanNavigate(bc) && bc->IsTargetable()) { found = bc; break; } else { siblings = parent->NonSyntheticChildren(); } for (dom::BrowsingContext* sibling : siblings) { if (sibling == current->GetBrowsingContext()) { continue; } if (dom::BrowsingContext* relative = sibling->FindWithNameInSubtree(aName, requestingContext)) { found = relative; // Breaks the outer loop parent = nullptr; break; } } current = parent; } while (current); } // Helpers should perform access control checks, which means that we // only need to assert that we can access found. MOZ_DIAGNOSTIC_ASSERT(!found || requestingContext->CanNavigate(found)); return found; } void WindowGlobalChild::UnblockBFCacheFor(BFCacheStatus aStatus) { SendUpdateBFCacheStatus(0, aStatus); } void WindowGlobalChild::BlockBFCacheFor(BFCacheStatus aStatus) { SendUpdateBFCacheStatus(aStatus, 0); } WindowGlobalChild::~WindowGlobalChild() = default; JSObject* WindowGlobalChild::WrapObject(JSContext* aCx, JS::Handle aGivenProto) { return WindowGlobalChild_Binding::Wrap(aCx, this, aGivenProto); } nsISupports* WindowGlobalChild::GetParentObject() { return xpc::NativeGlobal(xpc::PrivilegedJunkScope()); } NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS(WindowGlobalChild) NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(WindowGlobalChild) NS_IMPL_CYCLE_COLLECTION_UNLINK(mWindowGlobal) NS_IMPL_CYCLE_COLLECTION_UNLINK(mContainerFeaturePolicy) NS_IMPL_CYCLE_COLLECTION_UNLINK(mWindowContext) tmp->UnlinkManager(); NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER NS_IMPL_CYCLE_COLLECTION_UNLINK_WEAK_PTR NS_IMPL_CYCLE_COLLECTION_UNLINK_END NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(WindowGlobalChild) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mWindowGlobal) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mContainerFeaturePolicy) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mWindowContext) if (!tmp->IsInProcess()) { CycleCollectionNoteChild(cb, static_cast(tmp->Manager()), "Manager()"); } NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(WindowGlobalChild) NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY NS_INTERFACE_MAP_ENTRY(nsISupports) NS_INTERFACE_MAP_END NS_IMPL_CYCLE_COLLECTING_ADDREF(WindowGlobalChild) NS_IMPL_CYCLE_COLLECTING_RELEASE(WindowGlobalChild) } // namespace mozilla::dom