/* -*- 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/WindowGlobalChild.h" #include "mozilla/AntiTrackingUtils.h" #include "mozilla/ClearOnShutdown.h" #include "mozilla/dom/WindowGlobalParent.h" #include "mozilla/dom/BrowsingContext.h" #include "mozilla/dom/BrowsingContextGroup.h" #include "mozilla/dom/ContentChild.h" #include "mozilla/dom/MozFrameLoaderOwnerBinding.h" #include "mozilla/dom/BrowserChild.h" #include "mozilla/dom/BrowserBridgeChild.h" #include "mozilla/dom/ContentParent.h" #include "mozilla/dom/SecurityPolicyViolationEvent.h" #include "mozilla/dom/SessionStoreRestoreData.h" #include "mozilla/dom/WindowGlobalActorsBinding.h" #include "mozilla/dom/WindowContext.h" #include "mozilla/dom/InProcessChild.h" #include "mozilla/dom/InProcessParent.h" #include "mozilla/ipc/Endpoint.h" #include "mozilla/PresShell.h" #include "mozilla/ScopeExit.h" #include "GeckoProfiler.h" #include "nsContentUtils.h" #include "nsDocShell.h" #include "nsFocusManager.h" #include "nsFrameLoaderOwner.h" #include "nsGlobalWindowInner.h" #include "nsNetUtil.h" #include "nsQueryObject.h" #include "nsSerializationHelper.h" #include "nsFrameLoader.h" #include "mozilla/dom/JSWindowActorBinding.h" #include "mozilla/dom/JSWindowActorChild.h" #include "mozilla/dom/JSActorService.h" #include "nsIHttpChannelInternal.h" #include "nsIURIMutator.h" using namespace mozilla::ipc; using namespace mozilla::dom::ipc; namespace mozilla::dom { WindowGlobalChild::WindowGlobalChild(dom::WindowContext* aWindowContext, nsIPrincipal* aPrincipal, nsIURI* aDocumentURI) : mWindowContext(aWindowContext), mDocumentPrincipal(aPrincipal), mDocumentURI(aDocumentURI) { MOZ_DIAGNOSTIC_ASSERT(mWindowContext); MOZ_DIAGNOSTIC_ASSERT(mDocumentPrincipal); 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(), aDocumentURI->GetSpecOrDefault(), 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()); 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); } 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); // 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()); SetDocumentPrincipal(aDocument->NodePrincipal(), aDocument->EffectiveStoragePrincipal()); nsCOMPtr securityInfo; if (nsCOMPtr channel = aDocument->GetChannel()) { channel->GetSecurityInfo(getter_AddRefs(securityInfo)); } SendUpdateDocumentSecurityInfo(securityInfo); 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()); if (auto policy = aDocument->GetEmbedderPolicy()) { txn.SetEmbedderPolicy(*policy); } if (nsCOMPtr channel = aDocument->GetChannel()) { nsCOMPtr loadInfo(channel->LoadInfo()); txn.SetIsOriginalFrameSource(loadInfo->GetOriginalFrameSrcLoad()); } 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)); } /* 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(); } 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(); } void WindowGlobalChild::BeforeUnloadAdded() { // Don't bother notifying the parent if we don't have an IPC link open. if (mBeforeUnloadListeners == 0 && CanSend()) { Unused << mWindowContext->SetHasBeforeUnload(true); } mBeforeUnloadListeners++; MOZ_ASSERT(mBeforeUnloadListeners > 0); } void WindowGlobalChild::BeforeUnloadRemoved() { mBeforeUnloadListeners--; MOZ_ASSERT(mBeforeUnloadListeners >= 0); if (mBeforeUnloadListeners == 0) { Unused << mWindowContext->SetHasBeforeUnload(false); } } void WindowGlobalChild::Destroy() { 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->GetOwnerGlobal() != 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 = NOT_REMOTE_TYPE; 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->GetOwnerGlobal() != 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 uint32_t& aFlags, DrawSnapshotResolver&& aResolve) { aResolve(gfx::PaintFragment::Record(BrowsingContext(), aRect, aScale, aBackgroundColor, (gfx::CrossProcessPaintFlags)aFlags)); 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) { nsGlobalWindowInner* window = GetWindowGlobal(); if (!window) { return IPC_OK(); } Document* doc = window->GetDocument(); if (!doc) { return IPC_OK(); } SecurityPolicyViolationEventInit violationEvent; if (!violationEvent.Init(aViolationEventJSON)) { return IPC_OK(); } RefPtr event = SecurityPolicyViolationEvent::Constructor( doc, u"securitypolicyviolation"_ns, violationEvent); event->SetTrusted(true); doc->DispatchEvent(*event, IgnoreErrors()); 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::RecvSetContainerFeaturePolicy( dom::FeaturePolicy* aContainerFeaturePolicy) { mContainerFeaturePolicy = aContainerFeaturePolicy; 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, const Maybe& aData, const Maybe& aStack) { Maybe data; if (aData) { data.emplace(); data->BorrowFromClonedMessageData(*aData); } Maybe stack; if (aStack) { stack.emplace(); stack->BorrowFromClonedMessageData(*aStack); } ReceiveRawMessage(aMeta, std::move(data), std::move(stack)); 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(), aDocumentURI->GetSpecOrDefault(), embedderInnerWindowID, BrowsingContext()->UsePrivateBrowsing()); mDocumentURI = aDocumentURI; SendUpdateDocumentURI(aDocumentURI); } void WindowGlobalChild::SetDocumentPrincipal( nsIPrincipal* aNewDocumentPrincipal, nsIPrincipal* aNewDocumentStoragePrincipal) { MOZ_ASSERT(mDocumentPrincipal->Equals(aNewDocumentPrincipal)); mDocumentPrincipal = aNewDocumentPrincipal; SendUpdateDocumentPrincipal(aNewDocumentPrincipal, aNewDocumentStoragePrincipal); } const nsACString& WindowGlobalChild::GetRemoteType() { if (XRE_IsContentProcess()) { return ContentChild::GetSingleton()->GetRemoteType(); } return NOT_REMOTE_TYPE; } 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"); // 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()); } 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_WEAK_PTR(WindowGlobalChild, mWindowGlobal, mContainerFeaturePolicy, mWindowContext) 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