/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set ts=8 sts=2 et sw=2 tw=80: */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "mozilla/dom/BrowsingContext.h" #include "ipc/IPCMessageUtils.h" #ifdef ACCESSIBILITY # include "mozilla/a11y/DocAccessibleParent.h" # include "mozilla/a11y/Platform.h" # include "mozilla/a11y/ProxyAccessibleBase.h" # include "nsAccessibilityService.h" # if defined(XP_WIN) # include "mozilla/a11y/AccessibleWrap.h" # include "mozilla/a11y/Compatibility.h" # include "mozilla/a11y/nsWinUtils.h" # endif #endif #include "mozilla/dom/CanonicalBrowsingContext.h" #include "mozilla/dom/BrowserParent.h" #include "mozilla/dom/BrowsingContextGroup.h" #include "mozilla/dom/BrowsingContextBinding.h" #include "mozilla/dom/ContentChild.h" #include "mozilla/dom/ContentParent.h" #include "mozilla/dom/Document.h" #include "mozilla/dom/Element.h" #include "mozilla/dom/HTMLEmbedElement.h" #include "mozilla/dom/HTMLIFrameElement.h" #include "mozilla/dom/Location.h" #include "mozilla/dom/LocationBinding.h" #include "mozilla/dom/PopupBlocker.h" #include "mozilla/dom/ScriptSettings.h" #include "mozilla/dom/SessionStorageManager.h" #include "mozilla/dom/StructuredCloneTags.h" #include "mozilla/dom/UserActivationIPCUtils.h" #include "mozilla/dom/WindowBinding.h" #include "mozilla/dom/WindowGlobalChild.h" #include "mozilla/dom/WindowGlobalParent.h" #include "mozilla/dom/WindowProxyHolder.h" #include "mozilla/dom/SyncedContextInlines.h" #include "mozilla/dom/XULFrameElement.h" #include "mozilla/net/DocumentLoadListener.h" #include "mozilla/net/RequestContextService.h" #include "mozilla/Assertions.h" #include "mozilla/AsyncEventDispatcher.h" #include "mozilla/ClearOnShutdown.h" #include "mozilla/Components.h" #include "mozilla/HashTable.h" #include "mozilla/Logging.h" #include "mozilla/MediaFeatureChange.h" #include "mozilla/ResultExtensions.h" #include "mozilla/Services.h" #include "mozilla/StaticPrefs_fission.h" #include "mozilla/StaticPrefs_page_load.h" #include "mozilla/StaticPtr.h" #include "nsIURIFixup.h" #include "nsIXULRuntime.h" #include "nsDocShell.h" #include "nsDocShellLoadState.h" #include "nsFocusManager.h" #include "nsGlobalWindowOuter.h" #include "nsIObserverService.h" #include "nsISHistory.h" #include "nsContentUtils.h" #include "nsQueryObject.h" #include "nsSandboxFlags.h" #include "nsScriptError.h" #include "nsThreadUtils.h" #include "xpcprivate.h" #include "AutoplayPolicy.h" #include "GVAutoplayRequestStatusIPC.h" extern mozilla::LazyLogModule gAutoplayPermissionLog; extern mozilla::LazyLogModule gTimeoutDeferralLog; #define AUTOPLAY_LOG(msg, ...) \ MOZ_LOG(gAutoplayPermissionLog, LogLevel::Debug, (msg, ##__VA_ARGS__)) namespace IPC { // Allow serialization and deserialization of OrientationType over IPC template <> struct ParamTraits : public ContiguousEnumSerializer< mozilla::dom::OrientationType, mozilla::dom::OrientationType::Portrait_primary, mozilla::dom::OrientationType::EndGuard_> {}; template <> struct ParamTraits : public ContiguousEnumSerializer {}; template <> struct ParamTraits : public ContiguousEnumSerializer< mozilla::dom::PrefersColorSchemeOverride, mozilla::dom::PrefersColorSchemeOverride::None, mozilla::dom::PrefersColorSchemeOverride::EndGuard_> {}; template <> struct ParamTraits : public ContiguousEnumSerializer< mozilla::dom::ExplicitActiveStatus, mozilla::dom::ExplicitActiveStatus::None, mozilla::dom::ExplicitActiveStatus::EndGuard_> {}; // Allow serialization and deserialization of TouchEventsOverride over IPC template <> struct ParamTraits : public ContiguousEnumSerializer< mozilla::dom::TouchEventsOverride, mozilla::dom::TouchEventsOverride::Disabled, mozilla::dom::TouchEventsOverride::EndGuard_> {}; } // namespace IPC namespace mozilla { namespace dom { // Explicit specialization of the `Transaction` type. Required by the `extern // template class` declaration in the header. template class syncedcontext::Transaction; extern mozilla::LazyLogModule gUserInteractionPRLog; #define USER_ACTIVATION_LOG(msg, ...) \ MOZ_LOG(gUserInteractionPRLog, LogLevel::Debug, (msg, ##__VA_ARGS__)) static LazyLogModule gBrowsingContextLog("BrowsingContext"); typedef nsDataHashtable BrowsingContextMap; // All BrowsingContexts indexed by Id static StaticAutoPtr sBrowsingContexts; // Top-level Content BrowsingContexts only, indexed by BrowserId instead of Id static StaticAutoPtr sCurrentTopByBrowserId; static void UnregisterBrowserId(BrowsingContext* aBrowsingContext) { if (!aBrowsingContext->IsTopContent() || !sCurrentTopByBrowserId) { return; } // Avoids an extra lookup auto browserIdEntry = sCurrentTopByBrowserId->Lookup(aBrowsingContext->BrowserId()); if (browserIdEntry && browserIdEntry.Data() == aBrowsingContext) { browserIdEntry.Remove(); } } static void Register(BrowsingContext* aBrowsingContext) { sBrowsingContexts->Put(aBrowsingContext->Id(), aBrowsingContext); if (aBrowsingContext->IsTopContent()) { sCurrentTopByBrowserId->Put(aBrowsingContext->BrowserId(), aBrowsingContext); } aBrowsingContext->Group()->Register(aBrowsingContext); } BrowsingContext* BrowsingContext::GetParent() const { return mParentWindow ? mParentWindow->GetBrowsingContext() : nullptr; } bool BrowsingContext::IsInSubtreeOf(BrowsingContext* aContext) { BrowsingContext* bc = this; do { if (bc == aContext) { return true; } } while ((bc = bc->GetParent())); return false; } BrowsingContext* BrowsingContext::Top() { BrowsingContext* bc = this; while (bc->mParentWindow) { bc = bc->GetParent(); } return bc; } int32_t BrowsingContext::IndexOf(BrowsingContext* aChild) { int32_t index = -1; for (BrowsingContext* child : Children()) { ++index; if (child == aChild) { break; } } return index; } WindowContext* BrowsingContext::GetTopWindowContext() { if (mParentWindow) { return mParentWindow->TopWindowContext(); } return mCurrentWindowContext; } /* static */ void BrowsingContext::Init() { if (!sBrowsingContexts) { sBrowsingContexts = new BrowsingContextMap(); sCurrentTopByBrowserId = new BrowsingContextMap(); ClearOnShutdown(&sBrowsingContexts); ClearOnShutdown(&sCurrentTopByBrowserId); } } /* static */ LogModule* BrowsingContext::GetLog() { return gBrowsingContextLog; } /* static */ already_AddRefed BrowsingContext::Get(uint64_t aId) { return do_AddRef(sBrowsingContexts->Get(aId)); } /* static */ already_AddRefed BrowsingContext::GetCurrentTopByBrowserId( uint64_t aBrowserId) { return do_AddRef(sCurrentTopByBrowserId->Get(aBrowserId)); } /* static */ already_AddRefed BrowsingContext::GetFromWindow( WindowProxyHolder& aProxy) { return do_AddRef(aProxy.get()); } CanonicalBrowsingContext* BrowsingContext::Canonical() { return CanonicalBrowsingContext::Cast(this); } bool BrowsingContext::IsOwnedByProcess() const { return mIsInProcess && mDocShell && !nsDocShell::Cast(mDocShell)->WillChangeProcess(); } bool BrowsingContext::SameOriginWithTop() { MOZ_ASSERT(IsInProcess()); // If the top BrowsingContext is not same-process to us, it is cross-origin if (!Top()->IsInProcess()) { return false; } nsIDocShell* docShell = GetDocShell(); if (!docShell) { return false; } Document* doc = docShell->GetDocument(); if (!doc) { return false; } nsIPrincipal* principal = doc->NodePrincipal(); nsIDocShell* topDocShell = Top()->GetDocShell(); if (!topDocShell) { return false; } Document* topDoc = topDocShell->GetDocument(); if (!topDoc) { return false; } nsIPrincipal* topPrincipal = topDoc->NodePrincipal(); return principal->Equals(topPrincipal); } /* static */ already_AddRefed BrowsingContext::CreateDetached( nsGlobalWindowInner* aParent, BrowsingContext* aOpener, BrowsingContextGroup* aSpecificGroup, const nsAString& aName, Type aType, bool aCreatedDynamically) { if (aParent) { MOZ_DIAGNOSTIC_ASSERT(aParent->GetWindowContext()); MOZ_DIAGNOSTIC_ASSERT(aParent->GetBrowsingContext()->mType == aType); MOZ_DIAGNOSTIC_ASSERT(aParent->GetBrowsingContext()->GetBrowserId() != 0); } MOZ_DIAGNOSTIC_ASSERT(aType != Type::Chrome || XRE_IsParentProcess()); uint64_t id = nsContentUtils::GenerateBrowsingContextId(); MOZ_LOG(GetLog(), LogLevel::Debug, ("Creating 0x%08" PRIx64 " in %s", id, XRE_IsParentProcess() ? "Parent" : "Child")); RefPtr parentBC = aParent ? aParent->GetBrowsingContext() : nullptr; RefPtr parentWC = aParent ? aParent->GetWindowContext() : nullptr; BrowsingContext* inherit = parentBC ? parentBC.get() : aOpener; // Determine which BrowsingContextGroup this context should be created in. RefPtr group = aSpecificGroup; if (aType == Type::Chrome) { MOZ_DIAGNOSTIC_ASSERT(!group); group = BrowsingContextGroup::GetChromeGroup(); } else if (!group) { group = BrowsingContextGroup::Select(parentWC, aOpener); } // Configure initial values for synced fields. FieldValues fields; fields.mName = aName; if (aOpener) { MOZ_DIAGNOSTIC_ASSERT(!aParent, "new BC with both initial opener and parent"); MOZ_DIAGNOSTIC_ASSERT(aOpener->Group() == group); MOZ_DIAGNOSTIC_ASSERT(aOpener->mType == aType); fields.mOpenerId = aOpener->Id(); fields.mHadOriginalOpener = true; } if (aParent) { MOZ_DIAGNOSTIC_ASSERT(parentBC->Group() == group); MOZ_DIAGNOSTIC_ASSERT(parentBC->mType == aType); fields.mEmbedderInnerWindowId = aParent->WindowID(); // XXX(farre): Can/Should we check aParent->IsLoading() here? (Bug // 1608448) Check if the parent was itself loading already auto readystate = aParent->GetDocument()->GetReadyStateEnum(); fields.mAncestorLoading = parentBC->GetAncestorLoading() || readystate == Document::ReadyState::READYSTATE_LOADING || readystate == Document::ReadyState::READYSTATE_INTERACTIVE; } fields.mBrowserId = parentBC ? parentBC->GetBrowserId() : nsContentUtils::GenerateBrowserId(); fields.mOpenerPolicy = nsILoadInfo::OPENER_POLICY_UNSAFE_NONE; if (aOpener && aOpener->SameOriginWithTop()) { // We inherit the opener policy if there is a creator and if the creator's // origin is same origin with the creator's top-level origin. // If it is cross origin we should not inherit the CrossOriginOpenerPolicy fields.mOpenerPolicy = aOpener->Top()->GetOpenerPolicy(); } else if (aOpener) { // They are not same origin auto topPolicy = aOpener->Top()->GetOpenerPolicy(); MOZ_RELEASE_ASSERT(topPolicy == nsILoadInfo::OPENER_POLICY_UNSAFE_NONE || topPolicy == nsILoadInfo::OPENER_POLICY_SAME_ORIGIN_ALLOW_POPUPS); } nsContentUtils::GenerateUUIDInPlace(fields.mHistoryID); fields.mExplicitActive = [&] { if (parentBC) { // Non-root browsing-contexts inherit their status from its parent. return ExplicitActiveStatus::None; } if (aType == Type::Content) { // Content gets managed by the chrome front-end / embedder element and // starts as inactive. return ExplicitActiveStatus::Inactive; } // Chrome starts as active. return ExplicitActiveStatus::Active; }(); fields.mFullZoom = parentBC ? parentBC->FullZoom() : 1.0f; fields.mTextZoom = parentBC ? parentBC->TextZoom() : 1.0f; bool allowContentRetargeting = inherit ? inherit->GetAllowContentRetargetingOnChildren() : true; fields.mAllowContentRetargeting = allowContentRetargeting; fields.mAllowContentRetargetingOnChildren = allowContentRetargeting; // Assume top allows fullscreen for its children unless otherwise stated. // Subframes start with it false unless otherwise noted in SetEmbedderElement. fields.mFullscreenAllowedByOwner = !aParent; fields.mAllowPlugins = inherit ? inherit->GetAllowPlugins() : true; fields.mDefaultLoadFlags = inherit ? inherit->GetDefaultLoadFlags() : nsIRequest::LOAD_NORMAL; fields.mOrientationLock = mozilla::hal::eScreenOrientation_None; fields.mUseGlobalHistory = inherit ? inherit->GetUseGlobalHistory() : false; fields.mUseErrorPages = true; fields.mTouchEventsOverrideInternal = TouchEventsOverride::None; RefPtr context; if (XRE_IsParentProcess()) { context = new CanonicalBrowsingContext(parentWC, group, id, /* aOwnerProcessId */ 0, /* aEmbedderProcessId */ 0, aType, std::move(fields)); } else { context = new BrowsingContext(parentWC, group, id, aType, std::move(fields)); } context->mEmbeddedByThisProcess = XRE_IsParentProcess() || aParent; context->mCreatedDynamically = aCreatedDynamically; if (inherit) { context->mPrivateBrowsingId = inherit->mPrivateBrowsingId; context->mUseRemoteTabs = inherit->mUseRemoteTabs; context->mUseRemoteSubframes = inherit->mUseRemoteSubframes; context->mOriginAttributes = inherit->mOriginAttributes; } nsCOMPtr rcsvc = net::RequestContextService::GetOrCreate(); if (rcsvc) { nsCOMPtr requestContext; nsresult rv = rcsvc->NewRequestContext(getter_AddRefs(requestContext)); if (NS_SUCCEEDED(rv) && requestContext) { context->mRequestContextId = requestContext->GetID(); } } return context.forget(); } already_AddRefed BrowsingContext::CreateIndependent( Type aType) { RefPtr bc( CreateDetached(nullptr, nullptr, nullptr, u""_ns, aType)); bc->mWindowless = bc->IsContent(); bc->mEmbeddedByThisProcess = true; bc->EnsureAttached(); return bc.forget(); } void BrowsingContext::EnsureAttached() { if (!mEverAttached) { Register(this); // Attach the browsing context to the tree. Attach(/* aFromIPC */ false, /* aOriginProcess */ nullptr); } } /* static */ void BrowsingContext::CreateFromIPC(BrowsingContext::IPCInitializer&& aInit, BrowsingContextGroup* aGroup, ContentParent* aOriginProcess) { MOZ_DIAGNOSTIC_ASSERT(aOriginProcess || XRE_IsContentProcess()); MOZ_DIAGNOSTIC_ASSERT(aGroup); uint64_t originId = 0; if (aOriginProcess) { originId = aOriginProcess->ChildID(); aGroup->EnsureHostProcess(aOriginProcess); } MOZ_LOG(GetLog(), LogLevel::Debug, ("Creating 0x%08" PRIx64 " from IPC (origin=0x%08" PRIx64 ")", aInit.mId, originId)); RefPtr parent = aInit.GetParent(); RefPtr context; if (XRE_IsParentProcess()) { // If the new BrowsingContext has a parent, it is a sub-frame embedded in // whatever process sent the message. If it doesn't, and is not windowless, // it is a new window or tab, and will be embedded in the parent process. uint64_t embedderProcessId = (aInit.mWindowless || parent) ? originId : 0; context = new CanonicalBrowsingContext(parent, aGroup, aInit.mId, originId, embedderProcessId, Type::Content, std::move(aInit.mFields)); } else { context = new BrowsingContext(parent, aGroup, aInit.mId, Type::Content, std::move(aInit.mFields)); } context->mWindowless = aInit.mWindowless; context->mCreatedDynamically = aInit.mCreatedDynamically; if (context->GetHasSessionHistory()) { context->CreateChildSHistory(); if (mozilla::SessionHistoryInParent()) { context->GetChildSessionHistory()->SetIndexAndLength( aInit.mSessionHistoryIndex, aInit.mSessionHistoryCount, nsID()); } } // NOTE: Call through the `Set` methods for these values to ensure that any // relevant process-local state is also updated. context->SetOriginAttributes(aInit.mOriginAttributes); context->SetRemoteTabs(aInit.mUseRemoteTabs); context->SetRemoteSubframes(aInit.mUseRemoteSubframes); context->mRequestContextId = aInit.mRequestContextId; // NOTE: Private browsing ID is set by `SetOriginAttributes`. Register(context); context->Attach(/* aFromIPC */ true, aOriginProcess); } BrowsingContext::BrowsingContext(WindowContext* aParentWindow, BrowsingContextGroup* aGroup, uint64_t aBrowsingContextId, Type aType, FieldValues&& aInit) : mFields(std::move(aInit)), mType(aType), mBrowsingContextId(aBrowsingContextId), mGroup(aGroup), mParentWindow(aParentWindow), mPrivateBrowsingId(0), mEverAttached(false), mIsInProcess(false), mIsDiscarded(false), mWindowless(false), mDanglingRemoteOuterProxies(false), mEmbeddedByThisProcess(false), mUseRemoteTabs(false), mUseRemoteSubframes(false), mCreatedDynamically(false) { MOZ_RELEASE_ASSERT(!mParentWindow || mParentWindow->Group() == mGroup); MOZ_RELEASE_ASSERT(mBrowsingContextId != 0); MOZ_RELEASE_ASSERT(mGroup); } void BrowsingContext::SetDocShell(nsIDocShell* aDocShell) { // XXX(nika): We should communicate that we are now an active BrowsingContext // process to the parent & do other validation here. MOZ_DIAGNOSTIC_ASSERT(mEverAttached); MOZ_RELEASE_ASSERT(aDocShell->GetBrowsingContext() == this); mDocShell = aDocShell; mDanglingRemoteOuterProxies = !mIsInProcess; mIsInProcess = true; if (mChildSessionHistory) { mChildSessionHistory->SetIsInProcess(true); } } // This class implements a callback that will return the remote window proxy for // mBrowsingContext in that compartment, if it has one. It also removes the // proxy from the map, because the object will be transplanted into another kind // of object. class MOZ_STACK_CLASS CompartmentRemoteProxyTransplantCallback : public js::CompartmentTransplantCallback { public: explicit CompartmentRemoteProxyTransplantCallback( BrowsingContext* aBrowsingContext) : mBrowsingContext(aBrowsingContext) {} virtual JSObject* getObjectToTransplant( JS::Compartment* compartment) override { auto* priv = xpc::CompartmentPrivate::Get(compartment); if (!priv) { return nullptr; } auto& map = priv->GetRemoteProxyMap(); auto result = map.lookup(mBrowsingContext); if (!result) { return nullptr; } JSObject* resultObject = result->value(); map.remove(result); return resultObject; } private: BrowsingContext* mBrowsingContext; }; void BrowsingContext::CleanUpDanglingRemoteOuterWindowProxies( JSContext* aCx, JS::MutableHandle aOuter) { if (!mDanglingRemoteOuterProxies) { return; } mDanglingRemoteOuterProxies = false; CompartmentRemoteProxyTransplantCallback cb(this); js::RemapRemoteWindowProxies(aCx, &cb, aOuter); } bool BrowsingContext::IsActive() const { const BrowsingContext* current = this; do { auto explicit_ = current->GetExplicitActive(); if (explicit_ != ExplicitActiveStatus::None) { return explicit_ == ExplicitActiveStatus::Active; } if (current->IsCached()) { return false; } } while ((current = current->GetParent())); return false; } bool BrowsingContext::GetIsActiveBrowserWindow() { if (!XRE_IsParentProcess()) { return Top()->GetIsActiveBrowserWindowInternal(); } // chrome:// urls loaded in the parent won't receive // their own activation so we defer to the top chrome // Browsing Context when in the parent process. RefPtr chromeTop = Canonical()->TopCrossChromeBoundary(); return chromeTop->GetIsActiveBrowserWindowInternal(); } void BrowsingContext::SetIsActiveBrowserWindow(bool aActive) { Unused << SetIsActiveBrowserWindowInternal(aActive); } bool BrowsingContext::FullscreenAllowed() const { for (auto* current = this; current; current = current->GetParent()) { if (!current->GetFullscreenAllowedByOwner()) { return false; } } return true; } static bool OwnerAllowsFullscreen(const Element& aEmbedder) { if (aEmbedder.IsXULElement()) { return !aEmbedder.HasAttr(nsGkAtoms::disablefullscreen); } if (aEmbedder.IsHTMLElement(nsGkAtoms::iframe)) { // This is controlled by feature policy. return true; } if (const auto* embed = HTMLEmbedElement::FromNode(aEmbedder)) { return embed->AllowFullscreen(); } return false; } void BrowsingContext::SetEmbedderElement(Element* aEmbedder) { mEmbeddedByThisProcess = true; // Update embedder-element-specific fields in a shared transaction. // Don't do this when clearing our embedder, as we're being destroyed either // way. if (aEmbedder) { Transaction txn; txn.SetEmbedderElementType(Some(aEmbedder->LocalName())); if (nsCOMPtr inner = do_QueryInterface(aEmbedder->GetOwnerGlobal())) { txn.SetEmbedderInnerWindowId(inner->WindowID()); } txn.SetFullscreenAllowedByOwner(OwnerAllowsFullscreen(*aEmbedder)); if (XRE_IsParentProcess() && IsTopContent()) { nsAutoString messageManagerGroup; if (aEmbedder->IsXULElement()) { aEmbedder->GetAttr(nsGkAtoms::messagemanagergroup, messageManagerGroup); if (!aEmbedder->AttrValueIs(kNameSpaceID_None, nsGkAtoms::initiallyactive, nsGkAtoms::_false, eIgnoreCase)) { txn.SetExplicitActive(ExplicitActiveStatus::Active); } } txn.SetMessageManagerGroup(messageManagerGroup); bool useGlobalHistory = !aEmbedder->HasAttr( kNameSpaceID_None, nsGkAtoms::disableglobalhistory); txn.SetUseGlobalHistory(useGlobalHistory); } MOZ_ALWAYS_SUCCEEDS(txn.Commit(this)); } mEmbedderElement = aEmbedder; } void BrowsingContext::Embed() { if (auto* frame = HTMLIFrameElement::FromNode(mEmbedderElement)) { frame->BindToBrowsingContext(this); } } void BrowsingContext::Attach(bool aFromIPC, ContentParent* aOriginProcess) { MOZ_DIAGNOSTIC_ASSERT(!mEverAttached); mEverAttached = true; if (MOZ_LOG_TEST(GetLog(), LogLevel::Debug)) { nsAutoCString suffix; mOriginAttributes.CreateSuffix(suffix); MOZ_LOG(GetLog(), LogLevel::Debug, ("%s: Connecting 0x%08" PRIx64 " to 0x%08" PRIx64 " (private=%d, remote=%d, fission=%d, oa=%s)", XRE_IsParentProcess() ? "Parent" : "Child", Id(), GetParent() ? GetParent()->Id() : 0, (int)mPrivateBrowsingId, (int)mUseRemoteTabs, (int)mUseRemoteSubframes, suffix.get())); } MOZ_DIAGNOSTIC_ASSERT(mGroup); MOZ_DIAGNOSTIC_ASSERT(!mIsDiscarded); AssertCoherentLoadContext(); // Add ourselves either to our parent or BrowsingContextGroup's child list. if (mParentWindow) { if (!aFromIPC) { MOZ_DIAGNOSTIC_ASSERT(!mParentWindow->IsDiscarded(), "local attach in discarded window"); MOZ_DIAGNOSTIC_ASSERT(!GetParent()->IsDiscarded(), "local attach call in discarded bc"); } mParentWindow->AppendChildBrowsingContext(this); } else { mGroup->Toplevels().AppendElement(this); } if (GetIsPopupSpam()) { PopupBlocker::RegisterOpenPopupSpam(); } if (IsTop() && GetHasSessionHistory() && !mChildSessionHistory) { CreateChildSHistory(); } if (XRE_IsContentProcess() && !aFromIPC) { // Send attach to our parent if we need to. ContentChild::GetSingleton()->SendCreateBrowsingContext( mGroup->Id(), GetIPCInitializer()); } else if (XRE_IsParentProcess()) { mGroup->EachOtherParent(aOriginProcess, [&](ContentParent* aParent) { MOZ_DIAGNOSTIC_ASSERT(IsContent(), "chrome BCG cannot be synced to content process"); if (!Canonical()->IsEmbeddedInProcess(aParent->ChildID())) { Unused << aParent->SendCreateBrowsingContext(mGroup->Id(), GetIPCInitializer()); } }); if (IsTopContent() && !Canonical()->GetWebProgress()) { Canonical()->mWebProgress = new BrowsingContextWebProgress(); } } if (nsCOMPtr obs = services::GetObserverService()) { obs->NotifyWhenScriptSafe(ToSupports(this), "browsing-context-attached", nullptr); } } void BrowsingContext::Detach(bool aFromIPC) { MOZ_LOG(GetLog(), LogLevel::Debug, ("%s: Detaching 0x%08" PRIx64 " from 0x%08" PRIx64, XRE_IsParentProcess() ? "Parent" : "Child", Id(), GetParent() ? GetParent()->Id() : 0)); MOZ_DIAGNOSTIC_ASSERT(mEverAttached); MOZ_DIAGNOSTIC_ASSERT(!mIsDiscarded); nsCOMPtr rcsvc = net::RequestContextService::GetOrCreate(); if (rcsvc) { rcsvc->RemoveRequestContext(GetRequestContextId()); } // This will only ever be null if the cycle-collector has unlinked us. Don't // try to detach ourselves in that case. if (NS_WARN_IF(!mGroup)) { return; } if (mParentWindow) { mParentWindow->RemoveChildBrowsingContext(this); } else { mGroup->Toplevels().RemoveElement(this); } auto callSendDiscard = [&](auto* aActor) { // Hold a strong reference to ourself, and keep our BrowsingContextGroup // alive, until the responses comes back to ensure we don't die while // messages relating to this context are in-flight. // // When the callback is called, the keepalive on our group will be // destroyed, and the reference to the BrowsingContext will be dropped, // which may cause it to be fully destroyed. mGroup->AddKeepAlive(); auto callback = [self = RefPtr{this}](auto) { self->mGroup->RemoveKeepAlive(); }; aActor->SendDiscardBrowsingContext(this, callback, callback); }; if (XRE_IsParentProcess()) { Group()->EachParent([&](ContentParent* aParent) { // Only the embedder process is allowed to initiate a BrowsingContext // detach, so if we've gotten here, the host process already knows we've // been detached, and there's no need to tell it again. // // If the owner process is not the same as the embedder process, its // BrowsingContext will be detached when its nsWebBrowser instance is // destroyed. if (!Canonical()->IsEmbeddedInProcess(aParent->ChildID()) && !Canonical()->IsOwnedByProcess(aParent->ChildID())) { callSendDiscard(aParent); } }); } else if (!aFromIPC) { callSendDiscard(ContentChild::GetSingleton()); } mGroup->Unregister(this); UnregisterBrowserId(this); mIsDiscarded = true; if (XRE_IsParentProcess()) { nsFocusManager* fm = nsFocusManager::GetFocusManager(); if (fm) { fm->BrowsingContextDetached(this); } } if (nsCOMPtr obs = services::GetObserverService()) { obs->NotifyObservers(ToSupports(this), "browsing-context-discarded", nullptr); } // NOTE: Doesn't use SetClosed, as it will be set in all processes // automatically by calls to Detach() mFields.SetWithoutSyncing(true); if (GetIsPopupSpam()) { PopupBlocker::UnregisterOpenPopupSpam(); // NOTE: Doesn't use SetIsPopupSpam, as it will be set all processes // automatically. mFields.SetWithoutSyncing(false); } AssertOriginAttributesMatchPrivateBrowsing(); if (XRE_IsParentProcess()) { Canonical()->CanonicalDiscard(); } } void BrowsingContext::PrepareForProcessChange() { MOZ_LOG(GetLog(), LogLevel::Debug, ("%s: Preparing 0x%08" PRIx64 " for a process change", XRE_IsParentProcess() ? "Parent" : "Child", Id())); MOZ_ASSERT(mIsInProcess, "Must currently be an in-process frame"); MOZ_ASSERT(!mIsDiscarded, "We're already closed?"); mIsInProcess = false; mUserGestureStart = TimeStamp(); // NOTE: For now, clear our nsDocShell reference, as we're primarily in a // different process now. This may need to change in the future with // Cross-Process BFCache. mDocShell = nullptr; if (mChildSessionHistory) { // This can be removed once session history is stored exclusively in the // parent process. mChildSessionHistory->SetIsInProcess(false); } if (!mWindowProxy) { return; } // We have to go through mWindowProxy rather than calling GetDOMWindow() on // mDocShell because the mDocshell reference gets cleared immediately after // the window is closed. nsGlobalWindowOuter::PrepareForProcessChange(mWindowProxy); MOZ_ASSERT(!mWindowProxy); } bool BrowsingContext::IsCached() const { return mParentWindow && mParentWindow->IsCached(); } bool BrowsingContext::IsTargetable() const { return !GetClosed() && !mIsDiscarded && !IsCached(); } bool BrowsingContext::HasOpener() const { return sBrowsingContexts->Contains(GetOpenerId()); } bool BrowsingContext::AncestorsAreCurrent() const { const BrowsingContext* bc = this; while (true) { if (bc->IsDiscarded()) { return false; } if (WindowContext* wc = bc->GetParentWindowContext()) { if (wc->IsCached() || wc->IsDiscarded()) { return false; } bc = wc->GetBrowsingContext(); } else { return true; } } } Span> BrowsingContext::Children() const { if (WindowContext* current = mCurrentWindowContext) { return current->Children(); } return Span>(); } void BrowsingContext::GetChildren( nsTArray>& aChildren) { aChildren.AppendElements(Children()); } void BrowsingContext::GetWindowContexts( nsTArray>& aWindows) { aWindows.AppendElements(mWindowContexts); } void BrowsingContext::RegisterWindowContext(WindowContext* aWindow) { MOZ_ASSERT(!mWindowContexts.Contains(aWindow), "WindowContext already registered!"); MOZ_ASSERT(aWindow->GetBrowsingContext() == this); mWindowContexts.AppendElement(aWindow); // If the newly registered WindowContext is for our current inner window ID, // re-run the `DidSet` handler to re-establish the relationship. if (aWindow->InnerWindowId() == GetCurrentInnerWindowId()) { DidSet(FieldIndex()); MOZ_DIAGNOSTIC_ASSERT(mCurrentWindowContext == aWindow); } } void BrowsingContext::UnregisterWindowContext(WindowContext* aWindow) { MOZ_ASSERT(mWindowContexts.Contains(aWindow), "WindowContext not registered!"); mWindowContexts.RemoveElement(aWindow); // If our currently active window was unregistered, clear our reference to it. if (aWindow == mCurrentWindowContext) { // Re-read our `CurrentInnerWindowId` value and use it to set // `mCurrentWindowContext`. As `aWindow` is now unregistered and discarded, // we won't find it, and the value will be cleared back to `nullptr`. DidSet(FieldIndex()); MOZ_DIAGNOSTIC_ASSERT(mCurrentWindowContext == nullptr); } } void BrowsingContext::PreOrderWalk( const std::function& aCallback) { aCallback(this); AutoTArray, 8> children; children.AppendElements(Children()); for (auto& child : children) { child->PreOrderWalk(aCallback); } } void BrowsingContext::PostOrderWalk( const std::function& aCallback) { AutoTArray, 8> children; children.AppendElements(Children()); for (auto& child : children) { child->PostOrderWalk(aCallback); } aCallback(this); } void BrowsingContext::GetAllBrowsingContextsInSubtree( nsTArray>& aBrowsingContexts) { PreOrderWalk([&](BrowsingContext* aContext) { aBrowsingContexts.AppendElement(aContext); }); } // 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 BrowsingContext* BrowsingContext::FindWithName( const nsAString& aName, bool aUseEntryGlobalForAccessCheck) { RefPtr requestingContext = this; if (aUseEntryGlobalForAccessCheck) { if (nsCOMPtr caller = do_GetInterface(GetEntryGlobal())) { if (caller->GetBrowsingContext()) { requestingContext = caller->GetBrowsingContext(); } } } 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 = FindWithSpecialName(aName, *requestingContext); } else if (BrowsingContext* child = FindWithNameInSubtree(aName, *requestingContext)) { found = child; } else { BrowsingContext* current = this; do { Span> siblings; BrowsingContext* parent = current->GetParent(); if (!parent) { // We've reached the root of the tree, consider browsing // contexts in the same browsing context group. siblings = mGroup->Toplevels(); } else if (parent->NameEquals(aName) && requestingContext->CanAccess(parent) && parent->IsTargetable()) { found = parent; break; } else { siblings = parent->Children(); } for (BrowsingContext* sibling : siblings) { if (sibling == current) { continue; } if (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->CanAccess(found)); return found; } BrowsingContext* BrowsingContext::FindChildWithName( const nsAString& aName, BrowsingContext& aRequestingContext) { if (aName.IsEmpty()) { // You can't find a browsing context with the empty name. return nullptr; } for (BrowsingContext* child : Children()) { if (child->NameEquals(aName) && aRequestingContext.CanAccess(child) && child->IsTargetable()) { return child; } } return nullptr; } BrowsingContext* BrowsingContext::FindWithSpecialName( const nsAString& aName, BrowsingContext& aRequestingContext) { // TODO(farre): Neither BrowsingContext nor nsDocShell checks if the // browsing context pointed to by a special name is active. Should // it be? See Bug 1527913. if (aName.LowerCaseEqualsLiteral("_self")) { return this; } if (aName.LowerCaseEqualsLiteral("_parent")) { if (BrowsingContext* parent = GetParent()) { return aRequestingContext.CanAccess(parent) ? parent : nullptr; } return this; } if (aName.LowerCaseEqualsLiteral("_top")) { BrowsingContext* top = Top(); return aRequestingContext.CanAccess(top) ? top : nullptr; } return nullptr; } BrowsingContext* BrowsingContext::FindWithNameInSubtree( const nsAString& aName, BrowsingContext& aRequestingContext) { MOZ_DIAGNOSTIC_ASSERT(!aName.IsEmpty()); if (NameEquals(aName) && aRequestingContext.CanAccess(this) && IsTargetable()) { return this; } for (BrowsingContext* child : Children()) { if (BrowsingContext* found = child->FindWithNameInSubtree(aName, aRequestingContext)) { return found; } } return nullptr; } // 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. bool BrowsingContext::CanAccess(BrowsingContext* aTarget, bool aConsiderOpener) { MOZ_ASSERT( mDocShell, "CanAccess() may only be called in the process of the accessing window"); MOZ_ASSERT(aTarget, "Must have a target"); MOZ_DIAGNOSTIC_ASSERT( Group() == aTarget->Group(), "A BrowsingContext should never see a context from a different group"); // A frame can navigate itself and its own root. if (aTarget == this || aTarget == Top()) { return true; } // A frame can navigate any frame with a same-origin ancestor. for (BrowsingContext* bc = aTarget; bc; bc = bc->GetParent()) { if (bc->mDocShell && nsDocShell::ValidateOrigin(this, bc)) { return true; } } // If the target is a top-level document, a frame can navigate it if it can // navigate its opener. if (aConsiderOpener && !aTarget->GetParent()) { if (RefPtr opener = aTarget->GetOpener()) { return CanAccess(opener, false); } } return false; } bool BrowsingContext::IsSandboxedFrom(BrowsingContext* aTarget) { // If no target then not sandboxed. if (!aTarget) { return false; } // We cannot be sandboxed from ourselves. if (aTarget == this) { return false; } // Default the sandbox flags to our flags, so that if we can't retrieve the // active document, we will still enforce our own. uint32_t sandboxFlags = GetSandboxFlags(); if (mDocShell) { if (RefPtr doc = mDocShell->GetExtantDocument()) { sandboxFlags = doc->GetSandboxFlags(); } } // If no flags, we are not sandboxed at all. if (!sandboxFlags) { return false; } // If aTarget has an ancestor, it is not top level. if (RefPtr ancestorOfTarget = aTarget->GetParent()) { do { // We are not sandboxed if we are an ancestor of target. if (ancestorOfTarget == this) { return false; } ancestorOfTarget = ancestorOfTarget->GetParent(); } while (ancestorOfTarget); // Otherwise, we are sandboxed from aTarget. return true; } // aTarget is top level, are we the "one permitted sandboxed // navigator", i.e. did we open aTarget? if (aTarget->GetOnePermittedSandboxedNavigatorId() == Id()) { return false; } // If SANDBOXED_TOPLEVEL_NAVIGATION flag is not on, we are not sandboxed // from our top. if (!(sandboxFlags & SANDBOXED_TOPLEVEL_NAVIGATION) && aTarget == Top()) { return false; } // If SANDBOXED_TOPLEVEL_NAVIGATION_USER_ACTIVATION flag is not on, we are not // sandboxed from our top if we have user interaction. if (!(sandboxFlags & SANDBOXED_TOPLEVEL_NAVIGATION_USER_ACTIVATION) && mCurrentWindowContext && mCurrentWindowContext->HasValidTransientUserGestureActivation() && aTarget == Top()) { return false; } // Otherwise, we are sandboxed from aTarget. return true; } RefPtr BrowsingContext::GetSessionStorageManager() { RefPtr& manager = Top()->mSessionStorageManager; if (!manager) { manager = new SessionStorageManager(this); } return manager; } bool BrowsingContext::CrossOriginIsolated() { MOZ_ASSERT(NS_IsMainThread()); return StaticPrefs:: dom_postMessage_sharedArrayBuffer_withCOOP_COEP_AtStartup() && Top()->GetOpenerPolicy() == nsILoadInfo:: OPENER_POLICY_SAME_ORIGIN_EMBEDDER_POLICY_REQUIRE_CORP && XRE_IsContentProcess() && StringBeginsWith(ContentChild::GetSingleton()->GetRemoteType(), WITH_COOP_COEP_REMOTE_TYPE_PREFIX); } void BrowsingContext::SetTriggeringAndInheritPrincipals( nsIPrincipal* aTriggeringPrincipal, nsIPrincipal* aPrincipalToInherit, uint64_t aLoadIdentifier) { mTriggeringPrincipal = Some( PrincipalWithLoadIdentifierTuple(aTriggeringPrincipal, aLoadIdentifier)); if (aPrincipalToInherit) { mPrincipalToInherit = Some( PrincipalWithLoadIdentifierTuple(aPrincipalToInherit, aLoadIdentifier)); } } Tuple, nsCOMPtr> BrowsingContext::GetTriggeringAndInheritPrincipalsForCurrentLoad() { nsCOMPtr triggeringPrincipal = GetSavedPrincipal(mTriggeringPrincipal); nsCOMPtr principalToInherit = GetSavedPrincipal(mPrincipalToInherit); return MakeTuple(triggeringPrincipal, principalToInherit); } nsIPrincipal* BrowsingContext::GetSavedPrincipal( Maybe aPrincipalTuple) { if (aPrincipalTuple) { nsCOMPtr principal; uint64_t loadIdentifier; Tie(principal, loadIdentifier) = *aPrincipalTuple; // We want to return a principal only if the load identifier for it // matches the current one for this BC. if (auto current = GetCurrentLoadIdentifier(); current && *current == loadIdentifier) { return principal; } } return nullptr; } BrowsingContext::~BrowsingContext() { MOZ_DIAGNOSTIC_ASSERT(!mParentWindow || !mParentWindow->mChildren.Contains(this)); MOZ_DIAGNOSTIC_ASSERT(!mGroup || !mGroup->Toplevels().Contains(this)); mDeprioritizedLoadRunner.clear(); if (sBrowsingContexts) { sBrowsingContexts->Remove(Id()); } UnregisterBrowserId(this); } /* static */ void BrowsingContext::DiscardFromContentParent(ContentParent* aCP) { MOZ_ASSERT(XRE_IsParentProcess()); if (sBrowsingContexts) { AutoTArray, 8> toDiscard; for (const auto& entry : *sBrowsingContexts) { auto* bc = entry.GetData()->Canonical(); if (!bc->IsDiscarded() && bc->IsEmbeddedInProcess(aCP->ChildID())) { toDiscard.AppendElement(bc); } } for (BrowsingContext* bc : toDiscard) { bc->Detach(/* aFromIPC */ true); } } } nsISupports* BrowsingContext::GetParentObject() const { return xpc::NativeGlobal(xpc::PrivilegedJunkScope()); } JSObject* BrowsingContext::WrapObject(JSContext* aCx, JS::Handle aGivenProto) { return BrowsingContext_Binding::Wrap(aCx, this, aGivenProto); } bool BrowsingContext::WriteStructuredClone(JSContext* aCx, JSStructuredCloneWriter* aWriter, StructuredCloneHolder* aHolder) { MOZ_DIAGNOSTIC_ASSERT(mEverAttached); return (JS_WriteUint32Pair(aWriter, SCTAG_DOM_BROWSING_CONTEXT, 0) && JS_WriteUint32Pair(aWriter, uint32_t(Id()), uint32_t(Id() >> 32))); } /* static */ JSObject* BrowsingContext::ReadStructuredClone(JSContext* aCx, JSStructuredCloneReader* aReader, StructuredCloneHolder* aHolder) { uint32_t idLow = 0; uint32_t idHigh = 0; if (!JS_ReadUint32Pair(aReader, &idLow, &idHigh)) { return nullptr; } uint64_t id = uint64_t(idHigh) << 32 | idLow; // Note: Do this check after reading our ID data. Returning null will abort // the decode operation anyway, but we should at least be as safe as possible. if (NS_WARN_IF(!NS_IsMainThread())) { MOZ_DIAGNOSTIC_ASSERT(false, "We shouldn't be trying to decode a BrowsingContext " "on a background thread."); return nullptr; } JS::RootedValue val(aCx, JS::NullValue()); // We'll get rooting hazard errors from the RefPtr destructor if it isn't // destroyed before we try to return a raw JSObject*, so create it in its own // scope. if (RefPtr context = Get(id)) { if (!GetOrCreateDOMReflector(aCx, context, &val) || !val.isObject()) { return nullptr; } } return val.toObjectOrNull(); } bool BrowsingContext::CanSetOriginAttributes() { // A discarded BrowsingContext has already been destroyed, and cannot modify // its OriginAttributes. if (NS_WARN_IF(IsDiscarded())) { return false; } // Before attaching is the safest time to set OriginAttributes, and the only // allowed time for content BrowsingContexts. if (!EverAttached()) { return true; } // Attached content BrowsingContexts may have been synced to other processes. if (NS_WARN_IF(IsContent())) { MOZ_CRASH(); return false; } MOZ_DIAGNOSTIC_ASSERT(XRE_IsParentProcess()); // Cannot set OriginAttributes after we've created our child BrowsingContext. if (NS_WARN_IF(!Children().IsEmpty())) { return false; } // Only allow setting OriginAttributes if we have no associated document, or // the document is still `about:blank`. // TODO: Bug 1273058 - should have no document when setting origin attributes. if (WindowGlobalParent* window = Canonical()->GetCurrentWindowGlobal()) { if (nsIURI* uri = window->GetDocumentURI()) { MOZ_ASSERT(NS_IsAboutBlank(uri)); return NS_IsAboutBlank(uri); } } return true; } Nullable BrowsingContext::GetAssociatedWindow() { // nsILoadContext usually only returns same-process windows, // so we intentionally return nullptr if this BC is out of // process. if (IsInProcess()) { return WindowProxyHolder(this); } return nullptr; } Nullable BrowsingContext::GetTopWindow() { return Top()->GetAssociatedWindow(); } Element* BrowsingContext::GetTopFrameElement() { return Top()->GetEmbedderElement(); } void BrowsingContext::SetUsePrivateBrowsing(bool aUsePrivateBrowsing, ErrorResult& aError) { nsresult rv = SetUsePrivateBrowsing(aUsePrivateBrowsing); if (NS_FAILED(rv)) { aError.Throw(rv); } } void BrowsingContext::SetUseTrackingProtectionWebIDL( bool aUseTrackingProtection, ErrorResult& aRv) { SetForceEnableTrackingProtection(aUseTrackingProtection, aRv); } void BrowsingContext::GetOriginAttributes(JSContext* aCx, JS::MutableHandle aVal, ErrorResult& aError) { AssertOriginAttributesMatchPrivateBrowsing(); if (!ToJSValue(aCx, mOriginAttributes, aVal)) { aError.NoteJSContextException(aCx); } } NS_IMETHODIMP BrowsingContext::GetAssociatedWindow( mozIDOMWindowProxy** aAssociatedWindow) { nsCOMPtr win = GetDOMWindow(); win.forget(aAssociatedWindow); return NS_OK; } NS_IMETHODIMP BrowsingContext::GetTopWindow(mozIDOMWindowProxy** aTopWindow) { return Top()->GetAssociatedWindow(aTopWindow); } NS_IMETHODIMP BrowsingContext::GetTopFrameElement(Element** aTopFrameElement) { RefPtr topFrameElement = GetTopFrameElement(); topFrameElement.forget(aTopFrameElement); return NS_OK; } NS_IMETHODIMP BrowsingContext::GetIsContent(bool* aIsContent) { *aIsContent = IsContent(); return NS_OK; } NS_IMETHODIMP BrowsingContext::GetUsePrivateBrowsing( bool* aUsePrivateBrowsing) { *aUsePrivateBrowsing = mPrivateBrowsingId > 0; return NS_OK; } NS_IMETHODIMP BrowsingContext::SetUsePrivateBrowsing(bool aUsePrivateBrowsing) { if (!CanSetOriginAttributes()) { bool changed = aUsePrivateBrowsing != (mPrivateBrowsingId > 0); if (changed) { NS_WARNING("SetUsePrivateBrowsing when !CanSetOriginAttributes()"); } return changed ? NS_ERROR_FAILURE : NS_OK; } return SetPrivateBrowsing(aUsePrivateBrowsing); } NS_IMETHODIMP BrowsingContext::SetPrivateBrowsing(bool aPrivateBrowsing) { if (!CanSetOriginAttributes()) { NS_WARNING("Attempt to set PrivateBrowsing when !CanSetOriginAttributes"); return NS_ERROR_FAILURE; } bool changed = aPrivateBrowsing != (mPrivateBrowsingId > 0); if (changed) { mPrivateBrowsingId = aPrivateBrowsing ? 1 : 0; if (IsContent()) { mOriginAttributes.SyncAttributesWithPrivateBrowsing(aPrivateBrowsing); } } AssertOriginAttributesMatchPrivateBrowsing(); if (changed && mDocShell) { nsDocShell::Cast(mDocShell)->NotifyPrivateBrowsingChanged(); } return NS_OK; } NS_IMETHODIMP BrowsingContext::GetUseRemoteTabs(bool* aUseRemoteTabs) { *aUseRemoteTabs = mUseRemoteTabs; return NS_OK; } NS_IMETHODIMP BrowsingContext::SetRemoteTabs(bool aUseRemoteTabs) { if (!CanSetOriginAttributes()) { NS_WARNING("Attempt to set RemoteTabs when !CanSetOriginAttributes"); return NS_ERROR_FAILURE; } static bool annotated = false; if (aUseRemoteTabs && !annotated) { annotated = true; CrashReporter::AnnotateCrashReport(CrashReporter::Annotation::DOMIPCEnabled, true); } // Don't allow non-remote tabs with remote subframes. if (NS_WARN_IF(!aUseRemoteTabs && mUseRemoteSubframes)) { return NS_ERROR_UNEXPECTED; } mUseRemoteTabs = aUseRemoteTabs; return NS_OK; } NS_IMETHODIMP BrowsingContext::GetUseRemoteSubframes( bool* aUseRemoteSubframes) { *aUseRemoteSubframes = mUseRemoteSubframes; return NS_OK; } NS_IMETHODIMP BrowsingContext::SetRemoteSubframes(bool aUseRemoteSubframes) { if (!CanSetOriginAttributes()) { NS_WARNING("Attempt to set RemoteSubframes when !CanSetOriginAttributes"); return NS_ERROR_FAILURE; } static bool annotated = false; if (aUseRemoteSubframes && !annotated) { annotated = true; CrashReporter::AnnotateCrashReport( CrashReporter::Annotation::DOMFissionEnabled, true); } // Don't allow non-remote tabs with remote subframes. if (NS_WARN_IF(aUseRemoteSubframes && !mUseRemoteTabs)) { return NS_ERROR_UNEXPECTED; } mUseRemoteSubframes = aUseRemoteSubframes; return NS_OK; } NS_IMETHODIMP BrowsingContext::GetUseTrackingProtection( bool* aUseTrackingProtection) { *aUseTrackingProtection = false; if (GetForceEnableTrackingProtection() || StaticPrefs::privacy_trackingprotection_enabled() || (UsePrivateBrowsing() && StaticPrefs::privacy_trackingprotection_pbmode_enabled())) { *aUseTrackingProtection = true; return NS_OK; } if (GetParent()) { return GetParent()->GetUseTrackingProtection(aUseTrackingProtection); } return NS_OK; } NS_IMETHODIMP BrowsingContext::SetUseTrackingProtection( bool aUseTrackingProtection) { return SetForceEnableTrackingProtection(aUseTrackingProtection); } NS_IMETHODIMP BrowsingContext::GetScriptableOriginAttributes( JSContext* aCx, JS::MutableHandle aVal) { AssertOriginAttributesMatchPrivateBrowsing(); bool ok = ToJSValue(aCx, mOriginAttributes, aVal); NS_ENSURE_TRUE(ok, NS_ERROR_FAILURE); return NS_OK; } NS_IMETHODIMP_(void) BrowsingContext::GetOriginAttributes(OriginAttributes& aAttrs) { aAttrs = mOriginAttributes; AssertOriginAttributesMatchPrivateBrowsing(); } nsresult BrowsingContext::SetOriginAttributes(const OriginAttributes& aAttrs) { if (!CanSetOriginAttributes()) { NS_WARNING("Attempt to set OriginAttributes when !CanSetOriginAttributes"); return NS_ERROR_FAILURE; } AssertOriginAttributesMatchPrivateBrowsing(); mOriginAttributes = aAttrs; bool isPrivate = mOriginAttributes.mPrivateBrowsingId != nsIScriptSecurityManager::DEFAULT_PRIVATE_BROWSING_ID; // Chrome Browsing Context can not contain OriginAttributes.mPrivateBrowsingId if (IsChrome() && isPrivate) { mOriginAttributes.mPrivateBrowsingId = nsIScriptSecurityManager::DEFAULT_PRIVATE_BROWSING_ID; } SetPrivateBrowsing(isPrivate); AssertOriginAttributesMatchPrivateBrowsing(); return NS_OK; } void BrowsingContext::AssertCoherentLoadContext() { #ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED // LoadContext should generally match our opener or parent. if (IsContent()) { if (RefPtr opener = GetOpener()) { MOZ_DIAGNOSTIC_ASSERT(opener->mType == mType); MOZ_DIAGNOSTIC_ASSERT(opener->mGroup == mGroup); MOZ_DIAGNOSTIC_ASSERT(opener->mUseRemoteTabs == mUseRemoteTabs); MOZ_DIAGNOSTIC_ASSERT(opener->mUseRemoteSubframes == mUseRemoteSubframes); MOZ_DIAGNOSTIC_ASSERT(opener->mPrivateBrowsingId == mPrivateBrowsingId); MOZ_DIAGNOSTIC_ASSERT( opener->mOriginAttributes.EqualsIgnoringFPD(mOriginAttributes)); } } if (RefPtr parent = GetParent()) { MOZ_DIAGNOSTIC_ASSERT(parent->mType == mType); MOZ_DIAGNOSTIC_ASSERT(parent->mGroup == mGroup); MOZ_DIAGNOSTIC_ASSERT(parent->mUseRemoteTabs == mUseRemoteTabs); MOZ_DIAGNOSTIC_ASSERT(parent->mUseRemoteSubframes == mUseRemoteSubframes); MOZ_DIAGNOSTIC_ASSERT(parent->mPrivateBrowsingId == mPrivateBrowsingId); MOZ_DIAGNOSTIC_ASSERT( parent->mOriginAttributes.EqualsIgnoringFPD(mOriginAttributes)); } // UseRemoteSubframes and UseRemoteTabs must match. MOZ_DIAGNOSTIC_ASSERT( !mUseRemoteSubframes || mUseRemoteTabs, "Cannot set useRemoteSubframes without also setting useRemoteTabs"); // Double-check OriginAttributes/Private Browsing AssertOriginAttributesMatchPrivateBrowsing(); #endif } void BrowsingContext::AssertOriginAttributesMatchPrivateBrowsing() { // Chrome browsing contexts must not have a private browsing OriginAttribute // Content browsing contexts must maintain the equality: // mOriginAttributes.mPrivateBrowsingId == mPrivateBrowsingId if (IsChrome()) { MOZ_DIAGNOSTIC_ASSERT(mOriginAttributes.mPrivateBrowsingId == 0); } else { MOZ_DIAGNOSTIC_ASSERT(mOriginAttributes.mPrivateBrowsingId == mPrivateBrowsingId); } } NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(BrowsingContext) NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY NS_INTERFACE_MAP_ENTRY(nsILoadContext) NS_INTERFACE_MAP_ENTRY(nsISupports) NS_INTERFACE_MAP_END NS_IMPL_CYCLE_COLLECTION_CLASS(BrowsingContext) NS_IMPL_CYCLE_COLLECTING_ADDREF(BrowsingContext) NS_IMPL_CYCLE_COLLECTING_RELEASE(BrowsingContext) NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(BrowsingContext) NS_IMPL_CYCLE_COLLECTION_TRACE_PRESERVED_WRAPPER NS_IMPL_CYCLE_COLLECTION_TRACE_END NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(BrowsingContext) if (sBrowsingContexts) { sBrowsingContexts->Remove(tmp->Id()); } UnregisterBrowserId(tmp); if (tmp->GetIsPopupSpam()) { PopupBlocker::UnregisterOpenPopupSpam(); // NOTE: Doesn't use SetIsPopupSpam, as it will be set all processes // automatically. tmp->mFields.SetWithoutSyncing(false); } NS_IMPL_CYCLE_COLLECTION_UNLINK( mDocShell, mParentWindow, mGroup, mEmbedderElement, mWindowContexts, mCurrentWindowContext, mSessionStorageManager, mChildSessionHistory) NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER NS_IMPL_CYCLE_COLLECTION_UNLINK_END NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(BrowsingContext) NS_IMPL_CYCLE_COLLECTION_TRAVERSE( mDocShell, mParentWindow, mGroup, mEmbedderElement, mWindowContexts, mCurrentWindowContext, mSessionStorageManager, mChildSessionHistory) NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END class RemoteLocationProxy : public RemoteObjectProxy { public: typedef RemoteObjectProxy Base; constexpr RemoteLocationProxy() : RemoteObjectProxy(prototypes::id::Location) {} void NoteChildren(JSObject* aProxy, nsCycleCollectionTraversalCallback& aCb) const override { auto location = static_cast(GetNative(aProxy)); CycleCollectionNoteChild(aCb, location->GetBrowsingContext(), "JS::GetPrivate(obj)->GetBrowsingContext()"); } }; static const RemoteLocationProxy sSingleton; // Give RemoteLocationProxy 2 reserved slots, like the other wrappers, // so JSObject::swap can swap it with CrossCompartmentWrappers without requiring // malloc. template <> const JSClass RemoteLocationProxy::Base::sClass = PROXY_CLASS_DEF("Proxy", JSCLASS_HAS_RESERVED_SLOTS(2)); void BrowsingContext::Location(JSContext* aCx, JS::MutableHandle aLocation, ErrorResult& aError) { aError.MightThrowJSException(); sSingleton.GetProxyObject(aCx, &mLocation, /* aTransplantTo = */ nullptr, aLocation); if (!aLocation) { aError.StealExceptionFromJSContext(aCx); } } bool BrowsingContext::RemoveRootFromBFCacheSync() { if (WindowContext* wc = GetParentWindowContext()) { if (RefPtr doc = wc->TopWindowContext()->GetDocument()) { return doc->RemoveFromBFCacheSync(); } } return false; } nsresult BrowsingContext::CheckSandboxFlags(nsDocShellLoadState* aLoadState) { const auto& sourceBC = aLoadState->SourceBrowsingContext(); if (sourceBC.IsNull()) { return NS_OK; } // We might be called after the source BC has been discarded, but before we've // destroyed our in-process instance of the BrowsingContext object in some // situations (e.g. after creating a new pop-up with window.open while the // window is being closed). In these situations we want to still perform the // sandboxing check against our in-process copy. If we've forgotten about the // context already, assume it is sanboxed. (bug 1643450) BrowsingContext* bc = sourceBC.GetMaybeDiscarded(); if (!bc || bc->IsSandboxedFrom(this)) { return NS_ERROR_DOM_SECURITY_ERR; } return NS_OK; } nsresult BrowsingContext::LoadURI(nsDocShellLoadState* aLoadState, bool aSetNavigating) { // Per spec, most load attempts are silently ignored when a BrowsingContext is // null (which in our code corresponds to discarded), so we simply fail // silently in those cases. Regardless, we cannot trigger loads in/from // discarded BrowsingContexts via IPC, so we need to abort in any case. if (IsDiscarded()) { return NS_OK; } MOZ_DIAGNOSTIC_ASSERT(aLoadState->TargetBrowsingContext().IsNull(), "Targeting occurs in InternalLoad"); if (mDocShell) { return mDocShell->LoadURI(aLoadState, aSetNavigating); } // Note: We do this check both here and in `nsDocShell::InternalLoad`, since // document-specific sandbox flags are only available in the process // triggering the load, and we don't want the target process to have to trust // the triggering process to do the appropriate checks for the // BrowsingContext's sandbox flags. MOZ_TRY(CheckSandboxFlags(aLoadState)); SetTriggeringAndInheritPrincipals(aLoadState->TriggeringPrincipal(), aLoadState->PrincipalToInherit(), aLoadState->GetLoadIdentifier()); const auto& sourceBC = aLoadState->SourceBrowsingContext(); if (net::SchemeIsJavascript(aLoadState->URI())) { if (!XRE_IsParentProcess()) { // Web content should only be able to load javascript: URIs into documents // whose principals the caller principal subsumes, which by definition // excludes any document in a cross-process BrowsingContext. return NS_ERROR_DOM_BAD_CROSS_ORIGIN_URI; } MOZ_DIAGNOSTIC_ASSERT(!sourceBC, "Should never see a cross-process javascript: load " "triggered from content"); } MOZ_DIAGNOSTIC_ASSERT(!sourceBC || sourceBC->Group() == Group()); if (sourceBC && sourceBC->IsInProcess()) { if (!sourceBC->CanAccess(this)) { return NS_ERROR_DOM_PROP_ACCESS_DENIED; } nsCOMPtr win(sourceBC->GetDOMWindow()); if (WindowGlobalChild* wgc = win->GetCurrentInnerWindow()->GetWindowGlobalChild()) { wgc->SendLoadURI(this, aLoadState, aSetNavigating); } } else if (XRE_IsParentProcess()) { if (Canonical()->LoadInParent(aLoadState, aSetNavigating)) { return NS_OK; } if (ContentParent* cp = Canonical()->GetContentParent()) { // Attempt to initiate this load immediately in the parent, if it succeeds // it'll return a unique identifier so that we can find it later. uint64_t loadIdentifier = 0; if (Canonical()->AttemptSpeculativeLoadInParent(aLoadState)) { MOZ_DIAGNOSTIC_ASSERT(GetCurrentLoadIdentifier().isSome()); loadIdentifier = GetCurrentLoadIdentifier().value(); aLoadState->SetChannelInitialized(true); } cp->TransmitBlobDataIfBlobURL(aLoadState->URI(), aLoadState->TriggeringPrincipal()); // Setup a confirmation callback once the content process receives this // load. Normally we'd expect a PDocumentChannel actor to have been // created to claim the load identifier by that time. If not, then it // won't be coming, so make sure we clean up and deregister. cp->SendLoadURI(this, aLoadState, aSetNavigating) ->Then(GetMainThreadSerialEventTarget(), __func__, [loadIdentifier]( const PContentParent::LoadURIPromise::ResolveOrRejectValue& aValue) { if (loadIdentifier) { net::DocumentLoadListener::CleanupParentLoadAttempt( loadIdentifier); } }); } } else { MOZ_DIAGNOSTIC_ASSERT(sourceBC); if (!sourceBC) { return NS_ERROR_UNEXPECTED; } // If we're in a content process and the source BC is no longer in-process, // just fail silently. } return NS_OK; } nsresult BrowsingContext::InternalLoad(nsDocShellLoadState* aLoadState) { if (IsDiscarded()) { return NS_OK; } SetTriggeringAndInheritPrincipals(aLoadState->TriggeringPrincipal(), aLoadState->PrincipalToInherit(), aLoadState->GetLoadIdentifier()); MOZ_DIAGNOSTIC_ASSERT(aLoadState->Target().IsEmpty(), "should already have retargeted"); MOZ_DIAGNOSTIC_ASSERT(!aLoadState->TargetBrowsingContext().IsNull(), "should have target bc set"); MOZ_DIAGNOSTIC_ASSERT(aLoadState->TargetBrowsingContext() == this, "must be targeting this BrowsingContext"); if (mDocShell) { return nsDocShell::Cast(mDocShell)->InternalLoad(aLoadState); } // Note: We do this check both here and in `nsDocShell::InternalLoad`, since // document-specific sandbox flags are only available in the process // triggering the load, and we don't want the target process to have to trust // the triggering process to do the appropriate checks for the // BrowsingContext's sandbox flags. MOZ_TRY(CheckSandboxFlags(aLoadState)); const auto& sourceBC = aLoadState->SourceBrowsingContext(); if (net::SchemeIsJavascript(aLoadState->URI())) { if (!XRE_IsParentProcess()) { // Web content should only be able to load javascript: URIs into documents // whose principals the caller principal subsumes, which by definition // excludes any document in a cross-process BrowsingContext. return NS_ERROR_DOM_BAD_CROSS_ORIGIN_URI; } MOZ_DIAGNOSTIC_ASSERT(!sourceBC, "Should never see a cross-process javascript: load " "triggered from content"); } if (XRE_IsParentProcess()) { ContentParent* cp = Canonical()->GetContentParent(); if (!cp || !cp->CanSend()) { return NS_ERROR_FAILURE; } MOZ_ALWAYS_SUCCEEDS( SetCurrentLoadIdentifier(Some(aLoadState->GetLoadIdentifier()))); Unused << cp->SendInternalLoad(aLoadState); } else { MOZ_DIAGNOSTIC_ASSERT(sourceBC); MOZ_DIAGNOSTIC_ASSERT(sourceBC->Group() == Group()); if (!sourceBC->CanAccess(this)) { return NS_ERROR_DOM_PROP_ACCESS_DENIED; } nsCOMPtr win(sourceBC->GetDOMWindow()); WindowGlobalChild* wgc = win->GetCurrentInnerWindow()->GetWindowGlobalChild(); if (!wgc || !wgc->CanSend()) { return NS_ERROR_FAILURE; } MOZ_ALWAYS_SUCCEEDS( SetCurrentLoadIdentifier(Some(aLoadState->GetLoadIdentifier()))); wgc->SendInternalLoad(aLoadState); } return NS_OK; } void BrowsingContext::DisplayLoadError(const nsAString& aURI) { MOZ_LOG(GetLog(), LogLevel::Debug, ("DisplayLoadError")); MOZ_DIAGNOSTIC_ASSERT(!IsDiscarded()); MOZ_DIAGNOSTIC_ASSERT(mDocShell || XRE_IsParentProcess()); if (mDocShell) { bool didDisplayLoadError = false; mDocShell->DisplayLoadError(NS_ERROR_MALFORMED_URI, nullptr, PromiseFlatString(aURI).get(), nullptr, &didDisplayLoadError); } else { if (ContentParent* cp = Canonical()->GetContentParent()) { Unused << cp->SendDisplayLoadError(this, PromiseFlatString(aURI)); } } } WindowProxyHolder BrowsingContext::Window() { return WindowProxyHolder(Self()); } WindowProxyHolder BrowsingContext::GetFrames(ErrorResult& aError) { return Window(); } void BrowsingContext::Close(CallerType aCallerType, ErrorResult& aError) { if (mIsDiscarded) { return; } if (IsFrame()) { // .close() on frames is a no-op. return; } if (GetDOMWindow()) { nsGlobalWindowOuter::Cast(GetDOMWindow()) ->CloseOuter(aCallerType == CallerType::System); return; } // This is a bit of a hack for webcompat. Content needs to see an updated // |window.closed| value as early as possible, so we set this before we // actually send the DOMWindowClose event, which happens in the process where // the document for this browsing context is loaded. MOZ_ALWAYS_SUCCEEDS(SetClosed(true)); if (ContentChild* cc = ContentChild::GetSingleton()) { cc->SendWindowClose(this, aCallerType == CallerType::System); } else if (ContentParent* cp = Canonical()->GetContentParent()) { Unused << cp->SendWindowClose(this, aCallerType == CallerType::System); } } /* * Examine the current document state to see if we're in a way that is * typically abused by web designers. The window.open code uses this * routine to determine whether to allow the new window. * Returns a value from the PopupControlState enum. */ PopupBlocker::PopupControlState BrowsingContext::RevisePopupAbuseLevel( PopupBlocker::PopupControlState aControl) { if (!IsContent()) { return PopupBlocker::openAllowed; } PopupBlocker::PopupControlState abuse = aControl; switch (abuse) { case PopupBlocker::openControlled: case PopupBlocker::openBlocked: case PopupBlocker::openOverridden: if (IsPopupAllowed()) { abuse = PopupBlocker::PopupControlState(abuse - 1); } break; case PopupBlocker::openAbused: if (IsPopupAllowed()) { // Skip PopupBlocker::openBlocked abuse = PopupBlocker::openControlled; } break; case PopupBlocker::openAllowed: break; default: NS_WARNING("Strange PopupControlState!"); } // limit the number of simultaneously open popups if (abuse == PopupBlocker::openAbused || abuse == PopupBlocker::openBlocked || abuse == PopupBlocker::openControlled) { int32_t popupMax = StaticPrefs::dom_popup_maximum(); if (popupMax >= 0 && PopupBlocker::GetOpenPopupSpamCount() >= (uint32_t)popupMax) { abuse = PopupBlocker::openOverridden; } } // If we're currently in-process, attempt to consume transient user gesture // activations. if (RefPtr doc = GetExtantDocument()) { // HACK: Some pages using bogus library + UA sniffing call window.open() // from a blank iframe, only on Firefox, see bug 1685056. // // This is a hack-around to preserve behavior in that particular and // specific case, by consuming activation on the parent document, so we // don't care about the InProcessParent bits not being fission-safe or what // not. auto ConsumeTransientUserActivationForMultiplePopupBlocking = [&]() -> bool { if (doc->ConsumeTransientUserGestureActivation()) { return true; } if (!doc->IsInitialDocument()) { return false; } Document* parentDoc = doc->GetInProcessParentDocument(); if (!parentDoc || !parentDoc->NodePrincipal()->Equals(doc->NodePrincipal())) { return false; } return parentDoc->ConsumeTransientUserGestureActivation(); }; // If this popup is allowed, let's block any other for this event, forcing // PopupBlocker::openBlocked state. if ((abuse == PopupBlocker::openAllowed || abuse == PopupBlocker::openControlled) && StaticPrefs::dom_block_multiple_popups() && !IsPopupAllowed() && !ConsumeTransientUserActivationForMultiplePopupBlocking()) { nsContentUtils::ReportToConsole(nsIScriptError::warningFlag, "DOM"_ns, doc, nsContentUtils::eDOM_PROPERTIES, "MultiplePopupsBlockedNoUserActivation"); abuse = PopupBlocker::openBlocked; } } return abuse; } void BrowsingContext::IncrementHistoryEntryCountForBrowsingContext() { Unused << SetHistoryEntryCount(GetHistoryEntryCount() + 1); } std::tuple BrowsingContext::CanFocusCheck(CallerType aCallerType) { nsFocusManager* fm = nsFocusManager::GetFocusManager(); if (!fm) { return {false, false}; } nsCOMPtr caller = do_QueryInterface(GetEntryGlobal()); BrowsingContext* callerBC = caller ? caller->GetBrowsingContext() : nullptr; RefPtr openerBC = GetOpener(); MOZ_DIAGNOSTIC_ASSERT(!openerBC || openerBC->Group() == Group()); // Enforce dom.disable_window_flip (for non-chrome), but still allow the // window which opened us to raise us at times when popups are allowed // (bugs 355482 and 369306). bool canFocus = aCallerType == CallerType::System || !Preferences::GetBool("dom.disable_window_flip", true); if (!canFocus && openerBC == callerBC) { canFocus = (RevisePopupAbuseLevel(PopupBlocker::GetPopupControlState()) < PopupBlocker::openBlocked); } bool isActive = false; if (XRE_IsParentProcess()) { RefPtr chromeTop = Canonical()->TopCrossChromeBoundary(); nsCOMPtr activeWindow = fm->GetActiveWindow(); isActive = (activeWindow == chromeTop->GetDOMWindow()); } else { isActive = (fm->GetActiveBrowsingContext() == Top()); } return {canFocus, isActive}; } void BrowsingContext::Focus(CallerType aCallerType, ErrorResult& aError) { // These checks need to happen before the RequestFrameFocus call, which // is why they are done in an untrusted process. If we wanted to enforce // these in the parent, we'd need to do the checks there _also_. // These should be kept in sync with nsGlobalWindowOuter::FocusOuter. auto [canFocus, isActive] = CanFocusCheck(aCallerType); if (!(canFocus || isActive)) { return; } // Permission check passed if (mEmbedderElement) { // Make the activeElement in this process update synchronously. nsContentUtils::RequestFrameFocus(*mEmbedderElement, true, aCallerType); } uint64_t actionId = nsFocusManager::GenerateFocusActionId(); if (ContentChild* cc = ContentChild::GetSingleton()) { cc->SendWindowFocus(this, aCallerType, actionId); } else if (ContentParent* cp = Canonical()->GetContentParent()) { Unused << cp->SendWindowFocus(this, aCallerType, actionId); } } void BrowsingContext::Blur(ErrorResult& aError) { if (ContentChild* cc = ContentChild::GetSingleton()) { cc->SendWindowBlur(this); } else if (ContentParent* cp = Canonical()->GetContentParent()) { Unused << cp->SendWindowBlur(this); } } Nullable BrowsingContext::GetWindow() { if (XRE_IsParentProcess() && !IsInProcess()) { return nullptr; } return WindowProxyHolder(this); } Nullable BrowsingContext::GetTop(ErrorResult& aError) { if (mIsDiscarded) { return nullptr; } // We never return null or throw an error, but the implementation in // nsGlobalWindow does and we need to use the same signature. return WindowProxyHolder(Top()); } void BrowsingContext::GetOpener(JSContext* aCx, JS::MutableHandle aOpener, ErrorResult& aError) const { RefPtr opener = GetOpener(); if (!opener) { aOpener.setNull(); return; } if (!ToJSValue(aCx, WindowProxyHolder(opener), aOpener)) { aError.NoteJSContextException(aCx); } } // We never throw an error, but the implementation in nsGlobalWindow does and // we need to use the same signature. Nullable BrowsingContext::GetParent(ErrorResult& aError) { if (mIsDiscarded) { return nullptr; } if (GetParent()) { return WindowProxyHolder(GetParent()); } return WindowProxyHolder(this); } void BrowsingContext::PostMessageMoz(JSContext* aCx, JS::Handle aMessage, const nsAString& aTargetOrigin, const Sequence& aTransfer, nsIPrincipal& aSubjectPrincipal, ErrorResult& aError) { if (mIsDiscarded) { return; } RefPtr sourceBc; PostMessageData data; data.targetOrigin() = aTargetOrigin; data.subjectPrincipal() = &aSubjectPrincipal; RefPtr callerInnerWindow; nsAutoCString scriptLocation; // We don't need to get the caller's agentClusterId since that is used for // checking whether it's okay to sharing memory (and it's not allowed to share // memory cross processes) if (!nsGlobalWindowOuter::GatherPostMessageData( aCx, aTargetOrigin, getter_AddRefs(sourceBc), data.origin(), getter_AddRefs(data.targetOriginURI()), getter_AddRefs(data.callerPrincipal()), getter_AddRefs(callerInnerWindow), getter_AddRefs(data.callerURI()), /* aCallerAgentClusterId */ nullptr, &scriptLocation, aError)) { return; } if (sourceBc && sourceBc->IsDiscarded()) { return; } data.source() = sourceBc; data.isFromPrivateWindow() = callerInnerWindow && nsScriptErrorBase::ComputeIsFromPrivateWindow(callerInnerWindow); data.innerWindowId() = callerInnerWindow ? callerInnerWindow->WindowID() : 0; data.scriptLocation() = scriptLocation; JS::Rooted transferArray(aCx); aError = nsContentUtils::CreateJSValueFromSequenceOfObject(aCx, aTransfer, &transferArray); if (NS_WARN_IF(aError.Failed())) { return; } JS::CloneDataPolicy clonePolicy; if (callerInnerWindow && callerInnerWindow->IsSharedMemoryAllowed()) { clonePolicy.allowSharedMemoryObjects(); } // We will see if the message is required to be in the same process or it can // be in the different process after Write(). ipc::StructuredCloneData message = ipc::StructuredCloneData( StructuredCloneHolder::StructuredCloneScope::UnknownDestination, StructuredCloneHolder::TransferringSupported); message.Write(aCx, aMessage, transferArray, clonePolicy, aError); if (NS_WARN_IF(aError.Failed())) { return; } ClonedOrErrorMessageData messageData; if (ContentChild* cc = ContentChild::GetSingleton()) { // The clone scope gets set when we write the message data based on the // requirements of that data that we're writing. // If the message data contins a shared memory object, then CloneScope would // return SameProcess. Otherwise, it returns DifferentProcess. if (message.CloneScope() == StructuredCloneHolder::StructuredCloneScope::DifferentProcess) { ClonedMessageData clonedMessageData; if (!message.BuildClonedMessageDataForChild(cc, clonedMessageData)) { aError.Throw(NS_ERROR_FAILURE); return; } messageData = std::move(clonedMessageData); } else { MOZ_ASSERT(message.CloneScope() == StructuredCloneHolder::StructuredCloneScope::SameProcess); messageData = ErrorMessageData(); nsContentUtils::ReportToConsole( nsIScriptError::warningFlag, "DOM Window"_ns, callerInnerWindow ? callerInnerWindow->GetDocument() : nullptr, nsContentUtils::eDOM_PROPERTIES, "PostMessageSharedMemoryObjectToCrossOriginWarning"); } cc->SendWindowPostMessage(this, messageData, data); } else if (ContentParent* cp = Canonical()->GetContentParent()) { if (message.CloneScope() == StructuredCloneHolder::StructuredCloneScope::DifferentProcess) { ClonedMessageData clonedMessageData; if (!message.BuildClonedMessageDataForParent(cp, clonedMessageData)) { aError.Throw(NS_ERROR_FAILURE); return; } messageData = std::move(clonedMessageData); } else { MOZ_ASSERT(message.CloneScope() == StructuredCloneHolder::StructuredCloneScope::SameProcess); messageData = ErrorMessageData(); nsContentUtils::ReportToConsole( nsIScriptError::warningFlag, "DOM Window"_ns, callerInnerWindow ? callerInnerWindow->GetDocument() : nullptr, nsContentUtils::eDOM_PROPERTIES, "PostMessageSharedMemoryObjectToCrossOriginWarning"); } Unused << cp->SendWindowPostMessage(this, messageData, data); } } void BrowsingContext::PostMessageMoz(JSContext* aCx, JS::Handle aMessage, const WindowPostMessageOptions& aOptions, nsIPrincipal& aSubjectPrincipal, ErrorResult& aError) { PostMessageMoz(aCx, aMessage, aOptions.mTargetOrigin, aOptions.mTransfer, aSubjectPrincipal, aError); } void BrowsingContext::SendCommitTransaction(ContentParent* aParent, const BaseTransaction& aTxn, uint64_t aEpoch) { Unused << aParent->SendCommitBrowsingContextTransaction(this, aTxn, aEpoch); } void BrowsingContext::SendCommitTransaction(ContentChild* aChild, const BaseTransaction& aTxn, uint64_t aEpoch) { aChild->SendCommitBrowsingContextTransaction(this, aTxn, aEpoch); } BrowsingContext::IPCInitializer BrowsingContext::GetIPCInitializer() { MOZ_DIAGNOSTIC_ASSERT(mEverAttached); MOZ_DIAGNOSTIC_ASSERT(mType == Type::Content); IPCInitializer init; init.mId = Id(); init.mParentId = mParentWindow ? mParentWindow->Id() : 0; init.mWindowless = mWindowless; init.mUseRemoteTabs = mUseRemoteTabs; init.mUseRemoteSubframes = mUseRemoteSubframes; init.mCreatedDynamically = mCreatedDynamically; init.mOriginAttributes = mOriginAttributes; if (mChildSessionHistory && mozilla::SessionHistoryInParent()) { init.mSessionHistoryIndex = mChildSessionHistory->Index(); init.mSessionHistoryCount = mChildSessionHistory->Count(); } init.mRequestContextId = mRequestContextId; init.mFields = mFields.RawValues(); return init; } already_AddRefed BrowsingContext::IPCInitializer::GetParent() { RefPtr parent; if (mParentId != 0) { parent = WindowContext::GetById(mParentId); MOZ_RELEASE_ASSERT(parent); } return parent.forget(); } already_AddRefed BrowsingContext::IPCInitializer::GetOpener() { RefPtr opener; if (GetOpenerId() != 0) { opener = BrowsingContext::Get(GetOpenerId()); MOZ_RELEASE_ASSERT(opener); } return opener.forget(); } void BrowsingContext::StartDelayedAutoplayMediaComponents() { if (!mDocShell) { return; } AUTOPLAY_LOG("%s : StartDelayedAutoplayMediaComponents for bc 0x%08" PRIx64, XRE_IsParentProcess() ? "Parent" : "Child", Id()); mDocShell->StartDelayedAutoplayMediaComponents(); } nsresult BrowsingContext::ResetGVAutoplayRequestStatus() { MOZ_ASSERT(IsTop(), "Should only set GVAudibleAutoplayRequestStatus in the top-level " "browsing context"); Transaction txn; txn.SetGVAudibleAutoplayRequestStatus(GVAutoplayRequestStatus::eUNKNOWN); txn.SetGVInaudibleAutoplayRequestStatus(GVAutoplayRequestStatus::eUNKNOWN); return txn.Commit(this); } void BrowsingContext::DidSet(FieldIndex) { MOZ_ASSERT(IsTop(), "Should only set GVAudibleAutoplayRequestStatus in the top-level " "browsing context"); } void BrowsingContext::DidSet(FieldIndex) { MOZ_ASSERT(IsTop(), "Should only set GVAudibleAutoplayRequestStatus in the top-level " "browsing context"); } void BrowsingContext::DidSet(FieldIndex, ExplicitActiveStatus aOldValue) { const bool isActive = IsActive(); const bool wasActive = [&] { if (aOldValue != ExplicitActiveStatus::None) { return aOldValue == ExplicitActiveStatus::Active; } return GetParent() && GetParent()->IsActive(); }(); if (isActive == wasActive) { return; } if (IsTop()) { Group()->UpdateToplevelsSuspendedIfNeeded(); #if defined(XP_WIN) && defined(ACCESSIBILITY) if (XRE_IsParentProcess() && a11y::Compatibility::IsDolphin()) { // update active accessible documents on windows if (BrowserParent* bp = Canonical()->GetBrowserParent()) { if (a11y::DocAccessibleParent* tabDoc = bp->GetTopLevelDocAccessible()) { HWND window = tabDoc->GetEmulatedWindowHandle(); MOZ_ASSERT(window); if (window) { if (isActive) { a11y::nsWinUtils::ShowNativeWindow(window); } else { a11y::nsWinUtils::HideNativeWindow(window); } } } } } #endif } PreOrderWalk([&](BrowsingContext* aContext) { if (nsCOMPtr ds = aContext->GetDocShell()) { nsDocShell::Cast(ds)->ActivenessMaybeChanged(); } }); } bool BrowsingContext::CanSet(FieldIndex, bool aNewValue, ContentParent* aSource) { return IsTop() && LegacyCheckOnlyOwningProcessCanSet(aSource); } void BrowsingContext::DidSet(FieldIndex, bool aOldValue) { if (!IsTop() || aOldValue == GetHasMainMediaController()) { return; } Group()->UpdateToplevelsSuspendedIfNeeded(); } bool BrowsingContext::InactiveForSuspend() const { if (!StaticPrefs::dom_suspend_inactive_enabled()) { return false; } // We should suspend a page only when it's inactive and doesn't have a main // media controller. Having a main controller in context means it might be // playing media, or waiting media keys to control media (could be not playing // anything currently) return !IsActive() && !GetHasMainMediaController(); } bool BrowsingContext::CanSet( FieldIndex, const enum TouchEventsOverride& aTouchEventsOverride, ContentParent* aSource) { // TODO: Bug 1688948 - Should only be set in the parent process. return true; } void BrowsingContext::DidSet(FieldIndex, dom::PrefersColorSchemeOverride aOldValue) { MOZ_ASSERT(IsTop()); if (PrefersColorSchemeOverride() == aOldValue) { return; } PreOrderWalk([&](BrowsingContext* aContext) { if (nsIDocShell* shell = aContext->GetDocShell()) { if (nsPresContext* pc = shell->GetPresContext()) { // This is a bit of a lie, but it's the code-path that gets taken for // regular system metrics changes via ThemeChanged(). // TODO(emilio): The JustThisDocument is a bit suspect here, // prefers-color-scheme also applies to images or such, but the override // means that we could need to render the same image both with "light" // and "dark" appearance, so we just don't bother. pc->MediaFeatureValuesChanged( {MediaFeatureChangeReason::SystemMetricsChange}, MediaFeatureChangePropagation::JustThisDocument); } } }); } void BrowsingContext::DidSet(FieldIndex, nsString&& aOldValue) { MOZ_ASSERT(IsTop()); if (GetMediumOverride() == aOldValue) { return; } PreOrderWalk([&](BrowsingContext* aContext) { if (nsIDocShell* shell = aContext->GetDocShell()) { if (nsPresContext* pc = shell->GetPresContext()) { pc->RecomputeBrowsingContextDependentData(); } } }); } void BrowsingContext::DidSet(FieldIndex, enum DisplayMode aOldValue) { MOZ_ASSERT(IsTop()); if (GetDisplayMode() == aOldValue) { return; } PreOrderWalk([&](BrowsingContext* aContext) { if (nsIDocShell* shell = aContext->GetDocShell()) { if (nsPresContext* pc = shell->GetPresContext()) { pc->MediaFeatureValuesChanged( {MediaFeatureChangeReason::DisplayModeChange}, // We're already iterating through sub documents, so we don't need // to propagate the change again. // // Images and other resources don't change their display-mode // evaluation, display-mode is a property of the browsing context. MediaFeatureChangePropagation::JustThisDocument); } } }); } void BrowsingContext::DidSet(FieldIndex) { MOZ_ASSERT(IsTop(), "Set muted flag on non top-level context!"); USER_ACTIVATION_LOG("Set audio muted %d for %s browsing context 0x%08" PRIx64, GetMuted(), XRE_IsParentProcess() ? "Parent" : "Child", Id()); PreOrderWalk([&](BrowsingContext* aContext) { nsPIDOMWindowOuter* win = aContext->GetDOMWindow(); if (win) { win->RefreshMediaElementsVolume(); } }); } void BrowsingContext::SetCustomUserAgent(const nsAString& aUserAgent, ErrorResult& aRv) { Top()->SetUserAgentOverride(aUserAgent, aRv); } nsresult BrowsingContext::SetCustomUserAgent(const nsAString& aUserAgent) { return Top()->SetUserAgentOverride(aUserAgent); } void BrowsingContext::DidSet(FieldIndex) { MOZ_ASSERT(IsTop()); PreOrderWalk([&](BrowsingContext* aContext) { nsIDocShell* shell = aContext->GetDocShell(); if (shell) { shell->ClearCachedUserAgent(); } }); } void BrowsingContext::SetCustomPlatform(const nsAString& aPlatform, ErrorResult& aRv) { Top()->SetPlatformOverride(aPlatform, aRv); } void BrowsingContext::DidSet(FieldIndex) { MOZ_ASSERT(IsTop()); PreOrderWalk([&](BrowsingContext* aContext) { nsIDocShell* shell = aContext->GetDocShell(); if (shell) { shell->ClearCachedPlatform(); } }); } bool BrowsingContext::LegacyCheckOnlyOwningProcessCanSet( ContentParent* aSource) { if (aSource) { MOZ_ASSERT(XRE_IsParentProcess()); // Double-check ownership if we aren't the setter. if (!Canonical()->IsOwnedByProcess(aSource->ChildID()) && aSource->ChildID() != Canonical()->GetInFlightProcessId()) { return false; } } else if (!IsInProcess() && !XRE_IsParentProcess()) { // Don't allow this to be set from content processes that // don't own the BrowsingContext. return false; } return true; } bool BrowsingContext::CanSet(FieldIndex, const bool& aValue, ContentParent* aSource) { // Should only be set in the parent process. return XRE_IsParentProcess() && !aSource && IsTop(); } void BrowsingContext::DidSet(FieldIndex, bool aOldValue) { bool isActivateEvent = GetIsActiveBrowserWindowInternal(); // The browser window containing this context has changed // activation state so update window inactive document states // for all in-process documents. PreOrderWalk([isActivateEvent](BrowsingContext* aContext) { if (RefPtr doc = aContext->GetExtantDocument()) { doc->UpdateDocumentStates(NS_DOCUMENT_STATE_WINDOW_INACTIVE, true); if (XRE_IsContentProcess() && (!aContext->GetParent() || !aContext->GetParent()->IsInProcess())) { // Send the inner window an activate/deactivate event if // the context is the top of a sub-tree of in-process // contexts. nsContentUtils::DispatchEventOnlyToChrome( doc, doc->GetWindow()->GetCurrentInnerWindow(), isActivateEvent ? u"activate"_ns : u"deactivate"_ns, CanBubble::eYes, Cancelable::eYes, nullptr); } } }); } bool BrowsingContext::CanSet(FieldIndex, const bool& aAllowContentRetargeting, ContentParent* aSource) { return LegacyCheckOnlyOwningProcessCanSet(aSource); } bool BrowsingContext::CanSet(FieldIndex, const bool& aAllowContentRetargetingOnChildren, ContentParent* aSource) { return LegacyCheckOnlyOwningProcessCanSet(aSource); } bool BrowsingContext::CanSet(FieldIndex, const bool& aAllowPlugins, ContentParent* aSource) { return LegacyCheckOnlyOwningProcessCanSet(aSource); } bool BrowsingContext::CanSet(FieldIndex, const bool& aAllowed, ContentParent* aSource) { return CheckOnlyEmbedderCanSet(aSource); } bool BrowsingContext::CanSet(FieldIndex, const bool& aUseErrorPages, ContentParent* aSource) { return CheckOnlyEmbedderCanSet(aSource); } mozilla::dom::TouchEventsOverride BrowsingContext::TouchEventsOverride() const { const BrowsingContext* bc = this; while (bc) { mozilla::dom::TouchEventsOverride tev = bc->GetTouchEventsOverrideInternal(); if (tev != mozilla::dom::TouchEventsOverride::None) { return tev; } bc = bc->GetParent(); } return mozilla::dom::TouchEventsOverride::None; } void BrowsingContext::SetTouchEventsOverride( const enum TouchEventsOverride aTouchEventsOverride, ErrorResult& aRv) { SetTouchEventsOverrideInternal(aTouchEventsOverride, aRv); } nsresult BrowsingContext::SetTouchEventsOverride( const enum TouchEventsOverride aTouchEventsOverride) { ErrorResult rv; SetTouchEventsOverride(aTouchEventsOverride, rv); return rv.StealNSResult(); } // We map `watchedByDevTools` WebIDL attribute to `watchedByDevToolsInternal` // BC field. And we map it to the top level BrowsingContext. bool BrowsingContext::WatchedByDevTools() { return Top()->GetWatchedByDevToolsInternal(); } // Enforce that the watchedByDevTools BC field can only be set on the top level // Browsing Context. bool BrowsingContext::CanSet(FieldIndex, const bool& aWatchedByDevTools, ContentParent* aSource) { return IsTop(); } void BrowsingContext::SetWatchedByDevTools(bool aWatchedByDevTools, ErrorResult& aRv) { if (!IsTop()) { aRv.ThrowInvalidModificationError( "watchedByDevTools can only be set on top BrowsingContext"); return; } SetWatchedByDevToolsInternal(aWatchedByDevTools, aRv); } bool BrowsingContext::CanSet(FieldIndex, const uint32_t& aDefaultLoadFlags, ContentParent* aSource) { // Bug 1623565 - Are these flags only used by the debugger, which makes it // possible that this field can only be settable by the parent process? return LegacyCheckOnlyOwningProcessCanSet(aSource); } void BrowsingContext::DidSet(FieldIndex) { auto loadFlags = GetDefaultLoadFlags(); if (GetDocShell()) { nsDocShell::Cast(GetDocShell())->SetLoadGroupDefaultLoadFlags(loadFlags); } if (XRE_IsParentProcess()) { PreOrderWalk([&](BrowsingContext* aContext) { if (aContext != this) { // Setting load flags on a discarded context has no effect. Unused << aContext->SetDefaultLoadFlags(loadFlags); } }); } } bool BrowsingContext::CanSet(FieldIndex, const bool& aUseGlobalHistory, ContentParent* aSource) { // Should only be set in the parent process. // return XRE_IsParentProcess() && !aSource; return true; } bool BrowsingContext::CanSet(FieldIndex, const nsString& aUserAgent, ContentParent* aSource) { if (!IsTop()) { return false; } return LegacyCheckOnlyOwningProcessCanSet(aSource); } bool BrowsingContext::CanSet(FieldIndex, const nsString& aPlatform, ContentParent* aSource) { if (!IsTop()) { return false; } return LegacyCheckOnlyOwningProcessCanSet(aSource); } bool BrowsingContext::CheckOnlyEmbedderCanSet(ContentParent* aSource) { if (XRE_IsParentProcess()) { uint64_t childId = aSource ? aSource->ChildID() : 0; return Canonical()->IsEmbeddedInProcess(childId); } return mEmbeddedByThisProcess; } bool BrowsingContext::CanSet(FieldIndex, const uint64_t& aValue, ContentParent* aSource) { // If we have a parent window, our embedder inner window ID must match it. if (mParentWindow) { return mParentWindow->Id() == aValue; } // For toplevel BrowsingContext instances, this value may only be set by the // parent process, or initialized to `0`. return CheckOnlyEmbedderCanSet(aSource); } bool BrowsingContext::CanSet(FieldIndex, const Maybe&, ContentParent* aSource) { return CheckOnlyEmbedderCanSet(aSource); } bool BrowsingContext::CanSet(FieldIndex, const uint64_t& aValue, ContentParent* aSource) { // Generally allow clearing this. We may want to be more precise about this // check in the future. if (aValue == 0) { return true; } if (aSource) { MOZ_ASSERT(XRE_IsParentProcess()); // If in the parent process, double-check ownership and WindowGlobalParent // as well. RefPtr wgp = WindowGlobalParent::GetByInnerWindowId(aValue); if (NS_WARN_IF(!wgp) || NS_WARN_IF(wgp->BrowsingContext() != this)) { return false; } // Double-check ownership if we aren't the setter. if (!Canonical()->IsOwnedByProcess(aSource->ChildID()) && aSource->ChildID() != Canonical()->GetInFlightProcessId()) { return false; } } else if (XRE_IsContentProcess() && !IsOwnedByProcess()) { return false; } // We must have access to the specified context. RefPtr window = WindowContext::GetById(aValue); return window && window->GetBrowsingContext() == this; } void BrowsingContext::DidSet(FieldIndex) { RefPtr prevWindowContext = mCurrentWindowContext.forget(); mCurrentWindowContext = WindowContext::GetById(GetCurrentInnerWindowId()); MOZ_ASSERT( !mCurrentWindowContext || mWindowContexts.Contains(mCurrentWindowContext), "WindowContext not registered?"); // Clear our cached `children` value, to ensure that JS sees the up-to-date // value. BrowsingContext_Binding::ClearCachedChildrenValue(this); if (XRE_IsParentProcess()) { if (prevWindowContext != mCurrentWindowContext) { if (prevWindowContext) { prevWindowContext->Canonical()->DidBecomeCurrentWindowGlobal(false); } if (mCurrentWindowContext) { mCurrentWindowContext->Canonical()->DidBecomeCurrentWindowGlobal(true); } } BrowserParent::UpdateFocusFromBrowsingContext(); } } bool BrowsingContext::CanSet(FieldIndex, const bool& aValue, ContentParent* aSource) { // Ensure that we only mark a browsing context as popup spam once and never // unmark it. return aValue && !GetIsPopupSpam(); } void BrowsingContext::DidSet(FieldIndex) { if (GetIsPopupSpam()) { PopupBlocker::RegisterOpenPopupSpam(); } } bool BrowsingContext::CanSet(FieldIndex, const nsString& aMessageManagerGroup, ContentParent* aSource) { // Should only be set in the parent process on toplevel. return XRE_IsParentProcess() && !aSource && IsTopContent(); } bool BrowsingContext::CanSet( FieldIndex, const mozilla::hal::ScreenOrientation& aOrientationLock, ContentParent* aSource) { return IsTop(); } bool BrowsingContext::IsLoading() { if (GetLoading()) { return true; } // If we're in the same process as the page, we're possibly just // updating the flag. nsIDocShell* shell = GetDocShell(); if (shell) { Document* doc = shell->GetDocument(); return doc && doc->GetReadyStateEnum() < Document::READYSTATE_COMPLETE; } return false; } void BrowsingContext::DidSet(FieldIndex) { if (mFields.Get()) { return; } while (!mDeprioritizedLoadRunner.isEmpty()) { nsCOMPtr runner = mDeprioritizedLoadRunner.popFirst(); NS_DispatchToCurrentThread(runner.forget()); } if (StaticPrefs::dom_separate_event_queue_for_post_message_enabled() && Top() == this) { Group()->FlushPostMessageEvents(); } } // Inform the Document for this context of the (potential) change in // loading state void BrowsingContext::DidSet(FieldIndex) { nsPIDOMWindowOuter* outer = GetDOMWindow(); if (!outer) { MOZ_LOG(gTimeoutDeferralLog, mozilla::LogLevel::Debug, ("DidSetAncestorLoading BC: %p -- No outer window", (void*)this)); return; } Document* document = nsGlobalWindowOuter::Cast(outer)->GetExtantDoc(); if (document) { MOZ_LOG(gTimeoutDeferralLog, mozilla::LogLevel::Debug, ("DidSetAncestorLoading BC: %p -- NotifyLoading(%d, %d, %d)", (void*)this, GetAncestorLoading(), document->GetReadyStateEnum(), document->GetReadyStateEnum())); document->NotifyLoading(GetAncestorLoading(), document->GetReadyStateEnum(), document->GetReadyStateEnum()); } } void BrowsingContext::DidSet(FieldIndex) { MOZ_ASSERT(IsTop(), "Should only set AuthorStyleDisabledDefault in the top " "browsing context"); // We don't need to handle changes to this field, since PageStyleChild.jsm // will respond to the PageStyle:Disable message in all content processes. // // But we store the state here on the top BrowsingContext so that the // docshell has somewhere to look for the current author style disabling // state when new iframes are inserted. } void BrowsingContext::DidSet(FieldIndex, float aOldValue) { if (GetTextZoom() == aOldValue) { return; } if (IsInProcess()) { if (nsIDocShell* shell = GetDocShell()) { if (nsPresContext* pc = shell->GetPresContext()) { pc->RecomputeBrowsingContextDependentData(); } } for (BrowsingContext* child : Children()) { // Setting text zoom on a discarded context has no effect. Unused << child->SetTextZoom(GetTextZoom()); } } if (IsTop() && XRE_IsParentProcess()) { if (Element* element = GetEmbedderElement()) { auto dispatcher = MakeRefPtr( element, u"TextZoomChange"_ns, CanBubble::eYes, ChromeOnlyDispatch::eYes); dispatcher->RunDOMEventWhenSafe(); } } } // TODO(emilio): It'd be potentially nicer and cheaper to allow to set this only // on the Top() browsing context, but there are a lot of tests that rely on // zooming a subframe so... void BrowsingContext::DidSet(FieldIndex, float aOldValue) { if (GetFullZoom() == aOldValue) { return; } if (IsInProcess()) { if (nsIDocShell* shell = GetDocShell()) { if (nsPresContext* pc = shell->GetPresContext()) { pc->RecomputeBrowsingContextDependentData(); } } for (BrowsingContext* child : Children()) { // Setting full zoom on a discarded context has no effect. Unused << child->SetFullZoom(GetFullZoom()); } } if (IsTop() && XRE_IsParentProcess()) { if (Element* element = GetEmbedderElement()) { auto dispatcher = MakeRefPtr( element, u"FullZoomChange"_ns, CanBubble::eYes, ChromeOnlyDispatch::eYes); dispatcher->RunDOMEventWhenSafe(); } } } void BrowsingContext::AddDeprioritizedLoadRunner(nsIRunnable* aRunner) { MOZ_ASSERT(IsLoading()); MOZ_ASSERT(Top() == this); RefPtr runner = new DeprioritizedLoadRunner(aRunner); mDeprioritizedLoadRunner.insertBack(runner); NS_DispatchToCurrentThreadQueue( runner.forget(), StaticPrefs::page_load_deprioritization_period(), EventQueuePriority::Idle); } void BrowsingContext::GetHistoryID(JSContext* aCx, JS::MutableHandle aVal, ErrorResult& aError) { if (!xpc::ID2JSValue(aCx, GetHistoryID(), aVal)) { aError.Throw(NS_ERROR_OUT_OF_MEMORY); } } void BrowsingContext::InitSessionHistory() { MOZ_ASSERT(!IsDiscarded()); MOZ_ASSERT(IsTop()); MOZ_ASSERT(EverAttached()); if (!GetHasSessionHistory()) { MOZ_ALWAYS_SUCCEEDS(SetHasSessionHistory(true)); } } ChildSHistory* BrowsingContext::GetChildSessionHistory() { if (!mozilla::SessionHistoryInParent()) { // For now we're checking that the session history object for the child // process is available before returning the ChildSHistory object, because // it is the actual implementation that ChildSHistory forwards to. This can // be removed once session history is stored exclusively in the parent // process. return mChildSessionHistory && mChildSessionHistory->IsInProcess() ? mChildSessionHistory.get() : nullptr; } return mChildSessionHistory; } void BrowsingContext::CreateChildSHistory() { MOZ_ASSERT(IsTop()); MOZ_ASSERT(GetHasSessionHistory()); MOZ_DIAGNOSTIC_ASSERT(!mChildSessionHistory); // Because session history is global in a browsing context tree, every process // that has access to a browsing context tree needs access to its session // history. That is why we create the ChildSHistory object in every process // where we have access to this browsing context (which is the top one). mChildSessionHistory = new ChildSHistory(this); // If the top browsing context (this one) is loaded in this process then we // also create the session history implementation for the child process. // This can be removed once session history is stored exclusively in the // parent process. mChildSessionHistory->SetIsInProcess(IsInProcess()); } void BrowsingContext::DidSet(FieldIndex, bool aOldValue) { MOZ_ASSERT(GetHasSessionHistory() || !aOldValue, "We don't support turning off session history."); if (GetHasSessionHistory() && !aOldValue) { CreateChildSHistory(); } } bool BrowsingContext::CanSet(FieldIndex, const uint32_t& aValue, ContentParent* aSource) { // We should only be able to set this for toplevel contexts which don't have // an ID yet. return GetBrowserId() == 0 && IsTop() && Children().IsEmpty(); } bool BrowsingContext::CanSet(FieldIndex, bool aNewValue, ContentParent* aSource) { // Can only be cleared from `true` to `false`, and should only ever be set on // the toplevel BrowsingContext. return IsTop() && GetPendingInitialization() && !aNewValue; } void BrowsingContext::SessionHistoryChanged(int32_t aIndexDelta, int32_t aLengthDelta) { if (XRE_IsParentProcess() || mozilla::SessionHistoryInParent()) { // This method is used to test index and length for the session history // in child process only. return; } if (!IsTop()) { // Some tests have unexpected setup while Fission shistory is being // implemented. return; } RefPtr shistory = GetChildSessionHistory(); if (!shistory || !shistory->AsyncHistoryLength()) { return; } nsID changeID = shistory->AddPendingHistoryChange(aIndexDelta, aLengthDelta); uint32_t index = shistory->Index(); uint32_t length = shistory->Count(); // Do artificial history update through parent process to test asynchronous // history.length handling. ContentChild::GetSingleton()->SendSessionHistoryUpdate(this, index, length, changeID); } bool BrowsingContext::IsPopupAllowed() { for (auto* context = GetCurrentWindowContext(); context; context = context->GetParentWindowContext()) { if (context->CanShowPopup()) { return true; } } return false; } void BrowsingContext::SessionHistoryCommit( const LoadingSessionHistoryInfo& aInfo, uint32_t aLoadType, bool aHadActiveEntry, bool aPersist, bool aCloneEntryChildren) { nsID changeID = {}; if (XRE_IsContentProcess()) { RefPtr rootSH = Top()->GetChildSessionHistory(); if (rootSH) { if (!aInfo.mLoadIsFromSessionHistory) { // We try to mimic as closely as possible what will happen in // CanonicalBrowsingContext::SessionHistoryCommit. We'll be // incrementing the session history length if we're not replacing, // this is a top-level load or it's not the initial load in an iframe, // and ShouldUpdateSessionHistory(loadType) returns true. // It is possible that this leads to wrong length temporarily, but // so would not having the check for replace. if (!LOAD_TYPE_HAS_FLAGS( aLoadType, nsIWebNavigation::LOAD_FLAGS_REPLACE_HISTORY) && (IsTop() || aHadActiveEntry) && ShouldUpdateSessionHistory(aLoadType)) { changeID = rootSH->AddPendingHistoryChange(); } } else { // This is a load from session history, so we can update // index and length immediately. rootSH->SetIndexAndLength(aInfo.mRequestedIndex, aInfo.mSessionHistoryLength, changeID); } } ContentChild* cc = ContentChild::GetSingleton(); mozilla::Unused << cc->SendHistoryCommit(this, aInfo.mLoadId, changeID, aLoadType, aPersist, aCloneEntryChildren); } else { Canonical()->SessionHistoryCommit(aInfo.mLoadId, changeID, aLoadType, aPersist, aCloneEntryChildren); } } void BrowsingContext::SetActiveSessionHistoryEntry( const Maybe& aPreviousScrollPos, SessionHistoryInfo* aInfo, uint32_t aLoadType, uint32_t aUpdatedCacheKey) { if (XRE_IsContentProcess()) { // XXX Why we update cache key only in content process case? if (aUpdatedCacheKey != 0) { aInfo->SetCacheKey(aUpdatedCacheKey); } nsID changeID = {}; RefPtr shistory = Top()->GetChildSessionHistory(); if (shistory) { changeID = shistory->AddPendingHistoryChange(); } ContentChild::GetSingleton()->SendSetActiveSessionHistoryEntry( this, aPreviousScrollPos, *aInfo, aLoadType, aUpdatedCacheKey, changeID); } else { Canonical()->SetActiveSessionHistoryEntry( aPreviousScrollPos, aInfo, aLoadType, aUpdatedCacheKey, nsID()); } } void BrowsingContext::ReplaceActiveSessionHistoryEntry( SessionHistoryInfo* aInfo) { if (XRE_IsContentProcess()) { ContentChild::GetSingleton()->SendReplaceActiveSessionHistoryEntry(this, *aInfo); } else { Canonical()->ReplaceActiveSessionHistoryEntry(aInfo); } } void BrowsingContext::RemoveDynEntriesFromActiveSessionHistoryEntry() { if (XRE_IsContentProcess()) { ContentChild::GetSingleton() ->SendRemoveDynEntriesFromActiveSessionHistoryEntry(this); } else { Canonical()->RemoveDynEntriesFromActiveSessionHistoryEntry(); } } void BrowsingContext::RemoveFromSessionHistory(const nsID& aChangeID) { if (XRE_IsContentProcess()) { ContentChild::GetSingleton()->SendRemoveFromSessionHistory(this, aChangeID); } else { Canonical()->RemoveFromSessionHistory(aChangeID); } } void BrowsingContext::HistoryGo(int32_t aOffset, uint64_t aHistoryEpoch, bool aRequireUserInteraction, std::function&& aResolver) { if (XRE_IsContentProcess()) { ContentChild::GetSingleton()->SendHistoryGo( this, aOffset, aHistoryEpoch, aRequireUserInteraction, std::move(aResolver), [](mozilla::ipc:: ResponseRejectReason) { /* FIXME Is ignoring this fine? */ }); } else { Canonical()->HistoryGo( aOffset, aHistoryEpoch, aRequireUserInteraction, Canonical()->GetContentParent() ? Some(Canonical()->GetContentParent()->ChildID()) : Nothing(), std::move(aResolver)); } } void BrowsingContext::SetChildSHistory(ChildSHistory* aChildSHistory) { mChildSessionHistory = aChildSHistory; mChildSessionHistory->SetBrowsingContext(this); mFields.SetWithoutSyncing(true); } bool BrowsingContext::ShouldUpdateSessionHistory(uint32_t aLoadType) { // We don't update session history on reload unless we're loading // an iframe in shift-reload case. return nsDocShell::ShouldUpdateGlobalHistory(aLoadType) && (!(aLoadType & nsIDocShell::LOAD_CMD_RELOAD) || (IsForceReloadType(aLoadType) && IsFrame())); } nsresult BrowsingContext::CheckLocationChangeRateLimit(CallerType aCallerType) { // We only rate limit non system callers if (aCallerType == CallerType::System) { return NS_OK; } // Fetch rate limiting preferences uint32_t limitCount = StaticPrefs::dom_navigation_locationChangeRateLimit_count(); uint32_t timeSpanSeconds = StaticPrefs::dom_navigation_locationChangeRateLimit_timespan(); // Disable throttling if either of the preferences is set to 0. if (limitCount == 0 || timeSpanSeconds == 0) { return NS_OK; } TimeDuration throttleSpan = TimeDuration::FromSeconds(timeSpanSeconds); if (mLocationChangeRateLimitSpanStart.IsNull() || ((TimeStamp::Now() - mLocationChangeRateLimitSpanStart) > throttleSpan)) { // Initial call or timespan exceeded, reset counter and timespan. mLocationChangeRateLimitSpanStart = TimeStamp::Now(); mLocationChangeRateLimitCount = 1; return NS_OK; } if (mLocationChangeRateLimitCount >= limitCount) { // Rate limit reached Document* doc = GetDocument(); if (doc) { nsContentUtils::ReportToConsole(nsIScriptError::errorFlag, "DOM"_ns, doc, nsContentUtils::eDOM_PROPERTIES, "LocChangeFloodingPrevented"); } return NS_ERROR_DOM_SECURITY_ERR; } mLocationChangeRateLimitCount++; return NS_OK; } void BrowsingContext::ResetLocationChangeRateLimit() { // Resetting the timestamp object will cause the check function to // init again and reset the rate limit. mLocationChangeRateLimitSpanStart = TimeStamp(); } } // namespace dom namespace ipc { void IPDLParamTraits>::Write( IPC::Message* aMsg, IProtocol* aActor, const dom::MaybeDiscarded& aParam) { MOZ_DIAGNOSTIC_ASSERT(!aParam.GetMaybeDiscarded() || aParam.GetMaybeDiscarded()->EverAttached()); uint64_t id = aParam.ContextId(); WriteIPDLParam(aMsg, aActor, id); } bool IPDLParamTraits>::Read( const IPC::Message* aMsg, PickleIterator* aIter, IProtocol* aActor, dom::MaybeDiscarded* aResult) { uint64_t id = 0; if (!ReadIPDLParam(aMsg, aIter, aActor, &id)) { return false; } if (id == 0) { *aResult = nullptr; } else if (RefPtr bc = dom::BrowsingContext::Get(id)) { *aResult = std::move(bc); } else { aResult->SetDiscarded(id); } return true; } void IPDLParamTraits::Write( IPC::Message* aMessage, IProtocol* aActor, const dom::BrowsingContext::IPCInitializer& aInit) { // Write actor ID parameters. WriteIPDLParam(aMessage, aActor, aInit.mId); WriteIPDLParam(aMessage, aActor, aInit.mParentId); WriteIPDLParam(aMessage, aActor, aInit.mWindowless); WriteIPDLParam(aMessage, aActor, aInit.mUseRemoteTabs); WriteIPDLParam(aMessage, aActor, aInit.mUseRemoteSubframes); WriteIPDLParam(aMessage, aActor, aInit.mCreatedDynamically); WriteIPDLParam(aMessage, aActor, aInit.mOriginAttributes); WriteIPDLParam(aMessage, aActor, aInit.mRequestContextId); WriteIPDLParam(aMessage, aActor, aInit.mSessionHistoryIndex); WriteIPDLParam(aMessage, aActor, aInit.mSessionHistoryCount); WriteIPDLParam(aMessage, aActor, aInit.mFields); } bool IPDLParamTraits::Read( const IPC::Message* aMessage, PickleIterator* aIterator, IProtocol* aActor, dom::BrowsingContext::IPCInitializer* aInit) { // Read actor ID parameters. if (!ReadIPDLParam(aMessage, aIterator, aActor, &aInit->mId) || !ReadIPDLParam(aMessage, aIterator, aActor, &aInit->mParentId) || !ReadIPDLParam(aMessage, aIterator, aActor, &aInit->mWindowless) || !ReadIPDLParam(aMessage, aIterator, aActor, &aInit->mUseRemoteTabs) || !ReadIPDLParam(aMessage, aIterator, aActor, &aInit->mUseRemoteSubframes) || !ReadIPDLParam(aMessage, aIterator, aActor, &aInit->mCreatedDynamically) || !ReadIPDLParam(aMessage, aIterator, aActor, &aInit->mOriginAttributes) || !ReadIPDLParam(aMessage, aIterator, aActor, &aInit->mRequestContextId) || !ReadIPDLParam(aMessage, aIterator, aActor, &aInit->mSessionHistoryIndex) || !ReadIPDLParam(aMessage, aIterator, aActor, &aInit->mSessionHistoryCount) || !ReadIPDLParam(aMessage, aIterator, aActor, &aInit->mFields)) { return false; } return true; } template struct IPDLParamTraits; } // namespace ipc } // namespace mozilla