Source code

Revision control

Copy as Markdown

Other Tools

/* -*- 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 "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/AppShutdown.h"
#include "mozilla/dom/CanonicalBrowsingContext.h"
#include "mozilla/dom/BindingIPCUtils.h"
#include "mozilla/dom/BrowserHost.h"
#include "mozilla/dom/BrowserChild.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/MediaDevices.h"
#include "mozilla/dom/PopupBlocker.h"
#include "mozilla/dom/ScriptSettings.h"
#include "mozilla/dom/SessionStoreChild.h"
#include "mozilla/dom/SessionStorageManager.h"
#include "mozilla/dom/StructuredCloneTags.h"
#include "mozilla/dom/UserActivationIPCUtils.h"
#include "mozilla/dom/WindowBinding.h"
#include "mozilla/dom/WindowContext.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/ipc/ProtocolUtils.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_media.h"
#include "mozilla/StaticPrefs_page_load.h"
#include "mozilla/StaticPtr.h"
#include "mozilla/URLQueryStringStripper.h"
#include "mozilla/EventStateManager.h"
#include "nsIURIFixup.h"
#include "nsIXULRuntime.h"
#include "nsDocShell.h"
#include "nsDocShellLoadState.h"
#include "nsFocusManager.h"
#include "nsGlobalWindowInner.h"
#include "nsGlobalWindowOuter.h"
#include "PresShell.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<mozilla::dom::OrientationType>
: public mozilla::dom::WebIDLEnumSerializer<mozilla::dom::OrientationType> {
};
template <>
struct ParamTraits<mozilla::dom::DisplayMode>
: public mozilla::dom::WebIDLEnumSerializer<mozilla::dom::DisplayMode> {};
template <>
struct ParamTraits<mozilla::dom::PrefersColorSchemeOverride>
: public mozilla::dom::WebIDLEnumSerializer<
mozilla::dom::PrefersColorSchemeOverride> {};
template <>
struct ParamTraits<mozilla::dom::ExplicitActiveStatus>
: public ContiguousEnumSerializer<
mozilla::dom::ExplicitActiveStatus,
mozilla::dom::ExplicitActiveStatus::None,
mozilla::dom::ExplicitActiveStatus::EndGuard_> {};
// Allow serialization and deserialization of TouchEventsOverride over IPC
template <>
struct ParamTraits<mozilla::dom::TouchEventsOverride>
: public mozilla::dom::WebIDLEnumSerializer<
mozilla::dom::TouchEventsOverride> {};
template <>
struct ParamTraits<mozilla::dom::EmbedderColorSchemes> {
using paramType = mozilla::dom::EmbedderColorSchemes;
static void Write(MessageWriter* aWriter, const paramType& aParam) {
WriteParam(aWriter, aParam.mUsed);
WriteParam(aWriter, aParam.mPreferred);
}
static bool Read(MessageReader* aReader, paramType* aResult) {
return ReadParam(aReader, &aResult->mUsed) &&
ReadParam(aReader, &aResult->mPreferred);
}
};
} // 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<BrowsingContext>;
extern mozilla::LazyLogModule gUserInteractionPRLog;
#define USER_ACTIVATION_LOG(msg, ...) \
MOZ_LOG(gUserInteractionPRLog, LogLevel::Debug, (msg, ##__VA_ARGS__))
static LazyLogModule gBrowsingContextLog("BrowsingContext");
static LazyLogModule gBrowsingContextSyncLog("BrowsingContextSync");
typedef nsTHashMap<nsUint64HashKey, BrowsingContext*> BrowsingContextMap;
// All BrowsingContexts indexed by Id
static StaticAutoPtr<BrowsingContextMap> sBrowsingContexts;
// Top-level Content BrowsingContexts only, indexed by BrowserId instead of Id
static StaticAutoPtr<BrowsingContextMap> sCurrentTopByBrowserId;
static bool gIPCEnabledAnnotation = false;
static bool gFissionEnabledAnnotation = false;
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->InsertOrUpdate(aBrowsingContext->Id(), aBrowsingContext);
if (aBrowsingContext->IsTopContent()) {
sCurrentTopByBrowserId->InsertOrUpdate(aBrowsingContext->BrowserId(),
aBrowsingContext);
}
aBrowsingContext->Group()->Register(aBrowsingContext);
}
// static
void BrowsingContext::UpdateCurrentTopByBrowserId(
BrowsingContext* aNewBrowsingContext) {
if (aNewBrowsingContext->IsTopContent()) {
sCurrentTopByBrowserId->InsertOrUpdate(aNewBrowsingContext->BrowserId(),
aNewBrowsingContext);
}
}
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;
}
const BrowsingContext* BrowsingContext::Top() const {
const 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() const {
if (mParentWindow) {
return mParentWindow->TopWindowContext();
}
return mCurrentWindowContext;
}
/* static */
void BrowsingContext::Init() {
if (!sBrowsingContexts) {
sBrowsingContexts = new BrowsingContextMap();
sCurrentTopByBrowserId = new BrowsingContextMap();
ClearOnShutdown(&sBrowsingContexts);
ClearOnShutdown(&sCurrentTopByBrowserId);
CrashReporter::RegisterAnnotationBool(
CrashReporter::Annotation::DOMIPCEnabled, &gIPCEnabledAnnotation);
CrashReporter::RegisterAnnotationBool(
CrashReporter::Annotation::DOMFissionEnabled,
&gFissionEnabledAnnotation);
}
}
/* static */
LogModule* BrowsingContext::GetLog() { return gBrowsingContextLog; }
/* static */
LogModule* BrowsingContext::GetSyncLog() { return gBrowsingContextSyncLog; }
/* static */
already_AddRefed<BrowsingContext> BrowsingContext::Get(uint64_t aId) {
return do_AddRef(sBrowsingContexts->Get(aId));
}
/* static */
already_AddRefed<BrowsingContext> BrowsingContext::GetCurrentTopByBrowserId(
uint64_t aBrowserId) {
return do_AddRef(sCurrentTopByBrowserId->Get(aBrowserId));
}
/* static */
already_AddRefed<BrowsingContext> 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> BrowsingContext::CreateDetached(
nsGlobalWindowInner* aParent, BrowsingContext* aOpener,
BrowsingContextGroup* aSpecificGroup, const nsAString& aName, Type aType,
bool aIsPopupRequested, 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<BrowsingContext> parentBC =
aParent ? aParent->GetBrowsingContext() : nullptr;
RefPtr<WindowContext> parentWC =
aParent ? aParent->GetWindowContext() : nullptr;
BrowsingContext* inherit = parentBC ? parentBC.get() : aOpener;
// Determine which BrowsingContextGroup this context should be created in.
RefPtr<BrowsingContextGroup> 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.Get<IDX_Name>() = 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.Get<IDX_OpenerId>() = aOpener->Id();
fields.Get<IDX_HadOriginalOpener>() = true;
if (aType == Type::Chrome && !aParent) {
// See SetOpener for why we do this inheritance.
fields.Get<IDX_PrefersColorSchemeOverride>() =
aOpener->Top()->GetPrefersColorSchemeOverride();
}
}
if (aParent) {
MOZ_DIAGNOSTIC_ASSERT(parentBC->Group() == group);
MOZ_DIAGNOSTIC_ASSERT(parentBC->mType == aType);
fields.Get<IDX_EmbedderInnerWindowId>() = aParent->WindowID();
// Non-toplevel content documents are always embededed within content.
fields.Get<IDX_EmbeddedInContentDocument>() =
parentBC->mType == Type::Content;
// 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.Get<IDX_AncestorLoading>() =
parentBC->GetAncestorLoading() ||
readystate == Document::ReadyState::READYSTATE_LOADING ||
readystate == Document::ReadyState::READYSTATE_INTERACTIVE;
}
fields.Get<IDX_BrowserId>() =
parentBC ? parentBC->GetBrowserId() : nsContentUtils::GenerateBrowserId();
fields.Get<IDX_OpenerPolicy>() = 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.Get<IDX_OpenerPolicy>() = aOpener->Top()->GetOpenerPolicy();
// If we inherit a policy which is potentially cross-origin isolated, we
// must be in a potentially cross-origin isolated BCG.
bool isPotentiallyCrossOriginIsolated =
fields.Get<IDX_OpenerPolicy>() ==
nsILoadInfo::OPENER_POLICY_SAME_ORIGIN_EMBEDDER_POLICY_REQUIRE_CORP;
MOZ_RELEASE_ASSERT(isPotentiallyCrossOriginIsolated ==
group->IsPotentiallyCrossOriginIsolated());
} 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);
} else if (!aParent && group->IsPotentiallyCrossOriginIsolated()) {
// If we're creating a brand-new toplevel BC in a potentially cross-origin
// isolated group, it should start out with a strict opener policy.
fields.Get<IDX_OpenerPolicy>() =
nsILoadInfo::OPENER_POLICY_SAME_ORIGIN_EMBEDDER_POLICY_REQUIRE_CORP;
}
fields.Get<IDX_HistoryID>() = nsID::GenerateUUID();
fields.Get<IDX_ExplicitActive>() = [&] {
if (parentBC || aType == Type::Content) {
// Non-root browsing-contexts inherit their status from its parent.
// Top-content either gets managed by the top chrome, or gets manually
// managed by the front-end (see ManuallyManagesActiveness). In any case
// we want to start off as inactive.
return ExplicitActiveStatus::None;
}
// Chrome starts as active.
return ExplicitActiveStatus::Active;
}();
fields.Get<IDX_FullZoom>() = parentBC ? parentBC->FullZoom() : 1.0f;
fields.Get<IDX_TextZoom>() = parentBC ? parentBC->TextZoom() : 1.0f;
bool allowContentRetargeting =
inherit ? inherit->GetAllowContentRetargetingOnChildren() : true;
fields.Get<IDX_AllowContentRetargeting>() = allowContentRetargeting;
fields.Get<IDX_AllowContentRetargetingOnChildren>() = allowContentRetargeting;
// Assume top allows fullscreen for its children unless otherwise stated.
// Subframes start with it false unless otherwise noted in SetEmbedderElement.
fields.Get<IDX_FullscreenAllowedByOwner>() = !aParent;
fields.Get<IDX_DefaultLoadFlags>() =
inherit ? inherit->GetDefaultLoadFlags() : nsIRequest::LOAD_NORMAL;
fields.Get<IDX_OrientationLock>() = mozilla::hal::ScreenOrientation::None;
fields.Get<IDX_UseGlobalHistory>() =
inherit ? inherit->GetUseGlobalHistory() : false;
fields.Get<IDX_UseErrorPages>() = true;
fields.Get<IDX_TouchEventsOverrideInternal>() = TouchEventsOverride::None;
fields.Get<IDX_AllowJavascript>() =
inherit ? inherit->GetAllowJavascript() : true;
fields.Get<IDX_IsPopupRequested>() = aIsPopupRequested;
if (!parentBC) {
fields.Get<IDX_ShouldDelayMediaFromStart>() =
StaticPrefs::media_block_autoplay_until_in_foreground();
}
RefPtr<BrowsingContext> 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<nsIRequestContextService> rcsvc =
net::RequestContextService::GetOrCreate();
if (rcsvc) {
nsCOMPtr<nsIRequestContext> requestContext;
nsresult rv = rcsvc->NewRequestContext(getter_AddRefs(requestContext));
if (NS_SUCCEEDED(rv) && requestContext) {
context->mRequestContextId = requestContext->GetID();
}
}
return context.forget();
}
already_AddRefed<BrowsingContext> BrowsingContext::CreateIndependent(
Type aType) {
MOZ_DIAGNOSTIC_ASSERT(XRE_IsParentProcess(),
"BCs created in the content process must be related to "
"some BrowserChild");
RefPtr<BrowsingContext> bc(
CreateDetached(nullptr, nullptr, nullptr, u""_ns, aType, false));
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 */
mozilla::ipc::IPCResult 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<WindowContext> parent = aInit.GetParent();
RefPtr<BrowsingContext> 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;
context->mChildOffset = aInit.mChildOffset;
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);
return 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),
mIsInBFCache(false),
mCanExecuteScripts(true),
mChildOffset(0) {
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);
}
RecomputeCanExecuteScripts();
ClearCachedValuesOfLocations();
}
// 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<JSObject*> 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 (mParentWindow && !mParentWindow->IsCurrent()) {
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.
return Canonical()
->TopCrossChromeBoundary()
->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()));
txn.SetEmbeddedInContentDocument(
aEmbedder->OwnerDoc()->IsContentDocument());
if (nsCOMPtr<nsPIDOMWindowInner> inner =
do_QueryInterface(aEmbedder->GetOwnerGlobal())) {
txn.SetEmbedderInnerWindowId(inner->WindowID());
}
txn.SetFullscreenAllowedByOwner(OwnerAllowsFullscreen(*aEmbedder));
if (XRE_IsParentProcess() && aEmbedder->IsXULElement() && IsTopContent()) {
nsAutoString messageManagerGroup;
aEmbedder->GetAttr(nsGkAtoms::messagemanagergroup, messageManagerGroup);
txn.SetMessageManagerGroup(messageManagerGroup);
txn.SetUseGlobalHistory(
!aEmbedder->HasAttr(nsGkAtoms::disableglobalhistory));
if (!aEmbedder->HasAttr(nsGkAtoms::manualactiveness)) {
// We're active iff the parent cross-chrome-boundary is active. Note we
// can't just use this->Canonical()->GetParentCrossChromeBoundary here,
// since mEmbedderElement is still null at this point.
RefPtr bc = aEmbedder->OwnerDoc()->GetBrowsingContext();
const bool isActive = bc && bc->IsActive();
txn.SetExplicitActive(isActive ? ExplicitActiveStatus::Active
: ExplicitActiveStatus::Inactive);
if (auto* bp = Canonical()->GetBrowserParent()) {
bp->SetRenderLayers(isActive);
}
}
}
MOZ_ALWAYS_SUCCEEDS(txn.Commit(this));
}
if (XRE_IsParentProcess() && IsTopContent()) {
Canonical()->MaybeSetPermanentKey(aEmbedder);
}
mEmbedderElement = aEmbedder;
if (mEmbedderElement) {
if (nsCOMPtr<nsIObserverService> obs = services::GetObserverService()) {
obs->NotifyWhenScriptSafe(ToSupports(this),
"browsing-context-did-set-embedder", nullptr);
}
if (IsEmbedderTypeObjectOrEmbed()) {
Unused << SetSyntheticDocumentContainer(true);
}
}
}
bool BrowsingContext::IsEmbedderTypeObjectOrEmbed() {
if (const Maybe<nsString>& type = GetEmbedderElementType()) {
return nsGkAtoms::object->Equals(*type) || nsGkAtoms::embed->Equals(*type);
}
return false;
}
void BrowsingContext::Embed() {
if (auto* frame = HTMLIFrameElement::FromNode(mEmbedderElement)) {
frame->BindToBrowsingContext(this);
}
}
mozilla::ipc::IPCResult BrowsingContext::Attach(bool aFromIPC,
ContentParent* aOriginProcess) {
MOZ_DIAGNOSTIC_ASSERT(!mEverAttached);
MOZ_DIAGNOSTIC_ASSERT_IF(aFromIPC, aOriginProcess || XRE_IsContentProcess());
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);
if (mGroup->IsPotentiallyCrossOriginIsolated() !=
(Top()->GetOpenerPolicy() ==
nsILoadInfo::OPENER_POLICY_SAME_ORIGIN_EMBEDDER_POLICY_REQUIRE_CORP)) {
MOZ_DIAGNOSTIC_ASSERT(aFromIPC);
if (aFromIPC) {
auto* actor = aOriginProcess
? static_cast<mozilla::ipc::IProtocol*>(aOriginProcess)
: static_cast<mozilla::ipc::IProtocol*>(
ContentChild::GetSingleton());
return IPC_FAIL(
actor,
"Invalid CrossOriginIsolated state in BrowsingContext::Attach call");
} else {
MOZ_CRASH(
"Invalid CrossOriginIsolated state in BrowsingContext::Attach call");
}
}
AssertCoherentLoadContext();
// Add ourselves either to our parent or BrowsingContextGroup's child list.
// Important: We shouldn't return IPC_FAIL after this point, since the
// BrowsingContext will have already been added to the tree.
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");
MOZ_DIAGNOSTIC_ASSERT(mParentWindow->GetWindowGlobalChild(),
"local attach call with oop parent window");
MOZ_DIAGNOSTIC_ASSERT(mParentWindow->GetWindowGlobalChild()->CanSend(),
"local attach call with dead parent window");
}
mChildOffset =
mCreatedDynamically ? -1 : mParentWindow->Children().Length();
mParentWindow->AppendChildBrowsingContext(this);
RecomputeCanExecuteScripts();
} else {
mGroup->Toplevels().AppendElement(this);
}
if (GetIsPopupSpam()) {
PopupBlocker::RegisterOpenPopupSpam();
}
if (IsTop() && GetHasSessionHistory() && !mChildSessionHistory) {
CreateChildSHistory();
}
// Why the context is being attached. This will always be "attach" in the
// content process, but may be "replace" if it's known the context being
// replaced in the parent process.
const char16_t* why = u"attach";
if (XRE_IsContentProcess() && !aFromIPC) {
// Send attach to our parent if we need to.
ContentChild::GetSingleton()->SendCreateBrowsingContext(
mGroup->Id(), GetIPCInitializer());
} else if (XRE_IsParentProcess()) {
// If this window was created as a subframe by a content process, it must be
// being hosted within the same BrowserParent as its mParentWindow.
// Toplevel BrowsingContexts created by content have their BrowserParent
// configured during `RecvConstructPopupBrowser`.
if (mParentWindow && aOriginProcess) {
MOZ_DIAGNOSTIC_ASSERT(
mParentWindow->Canonical()->GetContentParent() == aOriginProcess,
"Creator process isn't the same as our embedder?");
Canonical()->SetCurrentBrowserParent(
mParentWindow->Canonical()->GetBrowserParent());
}
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 (IsTop() && IsContent() && Canonical()->GetWebProgress()) {
why = u"replace";
}
// We want to create a BrowsingContextWebProgress for all content
// BrowsingContexts.
if (IsContent() && !Canonical()->mWebProgress) {
Canonical()->mWebProgress = new BrowsingContextWebProgress(Canonical());
}
}
if (nsCOMPtr<nsIObserverService> obs = services::GetObserverService()) {
obs->NotifyWhenScriptSafe(ToSupports(this), "browsing-context-attached",
why);
}
if (XRE_IsParentProcess()) {
Canonical()->CanonicalAttach();
}
return IPC_OK();
}
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);
if (XRE_IsParentProcess()) {
Canonical()->AddPendingDiscard();
}
auto callListeners =
MakeScopeExit([&, listeners = std::move(mDiscardListeners), id = Id()] {
for (const auto& listener : listeners) {
listener(id);
}
if (XRE_IsParentProcess()) {
Canonical()->RemovePendingDiscard();
}
});
nsCOMPtr<nsIRequestContextService> 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)) {
MOZ_ASSERT_UNREACHABLE();
return;
}
if (mParentWindow) {
mParentWindow->RemoveChildBrowsingContext(this);
} else {
mGroup->Toplevels().RemoveElement(this);
}
if (XRE_IsParentProcess()) {
RefPtr<CanonicalBrowsingContext> self{Canonical()};
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.
bool doDiscard = !Canonical()->IsEmbeddedInProcess(aParent->ChildID()) &&
!Canonical()->IsOwnedByProcess(aParent->ChildID());
// 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();
self->AddPendingDiscard();
auto callback = [self](auto) {
self->mGroup->RemoveKeepAlive();
self->RemovePendingDiscard();
};
aParent->SendDiscardBrowsingContext(this, doDiscard, callback, callback);
});
} else {
// Hold a strong reference to ourself until the responses come back to
// ensure the BrowsingContext isn't cleaned up before the parent process
// acknowledges the discard request.
auto callback = [self = RefPtr{this}](auto) {};
ContentChild::GetSingleton()->SendDiscardBrowsingContext(
this, !aFromIPC, callback, callback);
}
mGroup->Unregister(this);
UnregisterBrowserId(this);
mIsDiscarded = true;
if (XRE_IsParentProcess()) {
nsFocusManager* fm = nsFocusManager::GetFocusManager();
if (fm) {
fm->BrowsingContextDetached(this);
}
}
if (nsCOMPtr<nsIObserverService> obs = services::GetObserverService()) {
// Why the context is being discarded. This will always be "discard" in the
// content process, but may be "replace" if it's known the context being
// replaced in the parent process.
const char16_t* why = u"discard";
if (XRE_IsParentProcess() && IsTop() && !Canonical()->GetWebProgress()) {
why = u"replace";
}
obs->NotifyObservers(ToSupports(this), "browsing-context-discarded", why);
}
// NOTE: Doesn't use SetClosed, as it will be set in all processes
// automatically by calls to Detach()
mFields.SetWithoutSyncing<IDX_Closed>(true);
if (GetIsPopupSpam()) {
PopupBlocker::UnregisterOpenPopupSpam();
// NOTE: Doesn't use SetIsPopupSpam, as it will be set all processes
// automatically.
mFields.SetWithoutSyncing<IDX_IsPopupSpam>(false);
}
AssertOriginAttributesMatchPrivateBrowsing();
if (XRE_IsParentProcess()) {
Canonical()->CanonicalDiscard();
}
}
void BrowsingContext::AddDiscardListener(
std::function<void(uint64_t)>&& aListener) {
if (mIsDiscarded) {
aListener(Id());
return;
}
mDiscardListeners.AppendElement(std::move(aListener));
}
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();
ClearCachedValuesOfLocations();
// 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::IsTargetable() const {
return !GetClosed() && AncestorsAreCurrent();
}
void BrowsingContext::SetOpener(BrowsingContext* aOpener) {
MOZ_DIAGNOSTIC_ASSERT(!aOpener || aOpener->Group() == Group());
MOZ_DIAGNOSTIC_ASSERT(!aOpener || aOpener->mType == mType);
MOZ_ALWAYS_SUCCEEDS(SetOpenerId(aOpener ? aOpener->Id() : 0));
if (IsChrome() && IsTop() && aOpener) {
// Inherit color scheme overrides from parent window. This is to inherit the
// color scheme of dark themed PBM windows in dialogs opened by such
// windows.
auto openerOverride = aOpener->Top()->PrefersColorSchemeOverride();
if (openerOverride != PrefersColorSchemeOverride()) {
MOZ_ALWAYS_SUCCEEDS(SetPrefersColorSchemeOverride(openerOverride));
}
}
}
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->IsCurrent() || wc->IsDiscarded()) {
return false;
}
bc = wc->GetBrowsingContext();
} else {
return true;
}
}
}
bool BrowsingContext::IsInBFCache() const {
if (mozilla::SessionHistoryInParent()) {
return mIsInBFCache;
}
return mParentWindow &&
mParentWindow->TopWindowContext()->GetWindowStateSaved();
}
Span<RefPtr<BrowsingContext>> BrowsingContext::Children() const {
if (WindowContext* current = mCurrentWindowContext) {
return current->Children();
}
return Span<RefPtr<BrowsingContext>>();
}
void BrowsingContext::GetChildren(
nsTArray<RefPtr<BrowsingContext>>& aChildren) {
aChildren.AppendElements(Children());
}
Span<RefPtr<BrowsingContext>> BrowsingContext::NonSyntheticChildren() const {
if (WindowContext* current = mCurrentWindowContext) {
return current->NonSyntheticChildren();
}
return Span<RefPtr<BrowsingContext>>();
}
void BrowsingContext::GetWindowContexts(
nsTArray<RefPtr<WindowContext>>& 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<IDX_CurrentInnerWindowId>());
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<IDX_CurrentInnerWindowId>());
MOZ_DIAGNOSTIC_ASSERT(mCurrentWindowContext == nullptr);
}
}
void BrowsingContext::PreOrderWalkVoid(
const std::function<void(BrowsingContext*)>& aCallback) {
aCallback(this);
AutoTArray<RefPtr<BrowsingContext>, 8> children;
children.AppendElements(Children());
for (auto& child : children) {
child->PreOrderWalkVoid(aCallback);
}
}
BrowsingContext::WalkFlag BrowsingContext::PreOrderWalkFlag(
const std::function<WalkFlag(BrowsingContext*)>& aCallback) {
switch (aCallback(this)) {
case WalkFlag::Skip:
return WalkFlag::Next;
case WalkFlag::Stop:
return WalkFlag::Stop;
case WalkFlag::Next:
default:
break;
}
AutoTArray<RefPtr<BrowsingContext>, 8> children;
children.AppendElements(Children());
for (auto& child : children) {
switch (child->PreOrderWalkFlag(aCallback)) {
case WalkFlag::Stop:
return WalkFlag::Stop;
default:
break;
}
}
return WalkFlag::Next;
}
void BrowsingContext::PostOrderWalk(
const std::function<void(BrowsingContext*)>& aCallback) {
AutoTArray<RefPtr<BrowsingContext>, 8> children;
children.AppendElements(Children());
for (auto& child : children) {
child->PostOrderWalk(aCallback);
}
aCallback(this);
}
void BrowsingContext::GetAllBrowsingContextsInSubtree(
nsTArray<RefPtr<BrowsingContext>>& aBrowsingContexts) {
PreOrderWalk([&](BrowsingContext* aContext) {
aBrowsingContexts.AppendElement(aContext);
});
}
BrowsingContext* BrowsingContext::FindChildWithName(
const nsAString& aName, WindowGlobalChild& aRequestingWindow) {
if (aName.IsEmpty()) {
// You can't find a browsing context with the empty name.
return nullptr;
}
for (BrowsingContext* child : NonSyntheticChildren()) {
if (child->NameEquals(aName) && aRequestingWindow.CanNavigate(child) &&
child->IsTargetable()) {
return child;
}
}
return nullptr;
}
BrowsingContext* BrowsingContext::FindWithSpecialName(
const nsAString& aName, WindowGlobalChild& aRequestingWindow) {
// 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 aRequestingWindow.CanNavigate(parent) ? parent : nullptr;
}
return this;
}
if (aName.LowerCaseEqualsLiteral("_top")) {
BrowsingContext* top = Top();
return aRequestingWindow.CanNavigate(top) ? top : nullptr;
}
return nullptr;
}
BrowsingContext* BrowsingContext::FindWithNameInSubtree(
const nsAString& aName, WindowGlobalChild* aRequestingWindow) {
MOZ_DIAGNOSTIC_ASSERT(!aName.IsEmpty());
if (NameEquals(aName) &&
(!aRequestingWindow || aRequestingWindow->CanNavigate(this)) &&
IsTargetable()) {
return this;
}
for (BrowsingContext* child : NonSyntheticChildren()) {
if (BrowsingContext* found =
child->FindWithNameInSubtree(aName, aRequestingWindow)) {
return found;
}
}
return nullptr;
}
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<Document> 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<BrowsingContext> 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. We assume there is a
// valid transient user gesture interaction if this check happens in the
// target process given that we have checked in the triggering process
// already.
if (!(sandboxFlags & SANDBOXED_TOPLEVEL_NAVIGATION_USER_ACTIVATION) &&
mCurrentWindowContext &&
(!mCurrentWindowContext->IsInProcess() ||
mCurrentWindowContext->HasValidTransientUserGestureActivation()) &&
aTarget == Top()) {
return false;
}
// Otherwise, we are sandboxed from aTarget.
return true;
}
RefPtr<SessionStorageManager> BrowsingContext::GetSessionStorageManager() {
RefPtr<SessionStorageManager>& 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));
}
}
std::tuple<nsCOMPtr<nsIPrincipal>, nsCOMPtr<nsIPrincipal>>
BrowsingContext::GetTriggeringAndInheritPrincipalsForCurrentLoad() {
nsCOMPtr<nsIPrincipal> triggeringPrincipal =
GetSavedPrincipal(mTriggeringPrincipal);
nsCOMPtr<nsIPrincipal> principalToInherit =
GetSavedPrincipal(mPrincipalToInherit);
return std::make_tuple(triggeringPrincipal, principalToInherit);
}
nsIPrincipal* BrowsingContext::GetSavedPrincipal(
Maybe<PrincipalWithLoadIdentifierTuple> aPrincipalTuple) {
if (aPrincipalTuple) {
nsCOMPtr<nsIPrincipal> principal;
uint64_t loadIdentifier;
std::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);
ClearCachedValuesOfLocations();
mLocations.clear();
}
/* static */
void BrowsingContext::DiscardFromContentParent(ContentParent* aCP) {
MOZ_ASSERT(XRE_IsParentProcess());
if (sBrowsingContexts) {
AutoTArray<RefPtr<BrowsingContext>, 8> toDiscard;
for (const auto& data : sBrowsingContexts->Values()) {
auto* bc = data->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<JSObject*> 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::Rooted<JS::Value> 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<BrowsingContext> 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<WindowProxyHolder> 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<WindowProxyHolder> 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<JS::Value> aVal,
ErrorResult& aError) {
AssertOriginAttributesMatchPrivateBrowsing();
if (!ToJSValue(aCx, mOriginAttributes, aVal)) {
aError.NoteJSContextException(aCx);
}
}
NS_IMETHODIMP BrowsingContext::GetAssociatedWindow(
mozIDOMWindowProxy** aAssociatedWindow) {
nsCOMPtr<mozIDOMWindowProxy> 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<Element> 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);
}
if (XRE_IsParentProcess()) {
Canonical()->AdjustPrivateBrowsingCount(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;
}
if (aUseRemoteTabs && !gIPCEnabledAnnotation) {
gIPCEnabledAnnotation = 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;
}
if (aUseRemoteSubframes && !gFissionEnabledAnnotation) {
gFissionEnabledAnnotation = 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<JS::Value> 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<BrowsingContext> 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<BrowsingContext> 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<IDX_IsPopupSpam>(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
static bool IsCertainlyAliveForCC(BrowsingContext* aContext) {
return aContext->HasKnownLiveWrapper() ||
(AppShutdown::GetCurrentShutdownPhase() ==
ShutdownPhase::NotInShutdown &&
aContext->EverAttached() && !aContext->IsDiscarded());
}
NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_BEGIN(BrowsingContext)
if (IsCertainlyAliveForCC(tmp)) {
if (tmp->PreservingWrapper()) {
tmp->MarkWrapperLive();
}
return true;
}
NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_END
NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_IN_CC_BEGIN(BrowsingContext)
return IsCertainlyAliveForCC(tmp) && tmp->HasNothingToTrace(tmp);
NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_IN_CC_END
NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_BEGIN(BrowsingContext)
return IsCertainlyAliveForCC(tmp);
NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_END
class RemoteLocationProxy
: public RemoteObjectProxy<BrowsingContext::LocationProxy,
Location_Binding::sCrossOriginProperties> {
public:
typedef RemoteObjectProxy Base;
constexpr RemoteLocationProxy()
: RemoteObjectProxy(prototypes::id::Location) {}
void NoteChildren(JSObject* aProxy,
nsCycleCollectionTraversalCallback& aCb) const override {
auto location =
static_cast<BrowsingContext::LocationProxy*>(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<JSObject*> 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<Document> 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");
aLoadState->AssertProcessCouldTriggerLoadIfSystem();
if (mDocShell) {
nsCOMPtr<nsIDocShell> docShell = mDocShell;
return docShell->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()) {
nsCOMPtr<nsPIDOMWindowOuter> win(sourceBC->GetDOMWindow());
if (WindowGlobalChild* wgc =
win->GetCurrentInnerWindow()->GetWindowGlobalChild()) {
if (!wgc->CanNavigate(this)) {
return NS_ERROR_DOM_PROP_ACCESS_DENIED;
}
wgc->SendLoadURI(this, mozilla::WrapNotNull(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());
// 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, mozilla::WrapNotNull(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");
aLoadState->AssertProcessCouldTriggerLoadIfSystem();
if (mDocShell) {
RefPtr<nsDocShell> docShell = nsDocShell::Cast(mDocShell);
return docShell->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(mozilla::WrapNotNull(aLoadState));
} else {
MOZ_DIAGNOSTIC_ASSERT(sourceBC);
MOZ_DIAGNOSTIC_ASSERT(sourceBC->Group() == Group());
nsCOMPtr<nsPIDOMWindowOuter> win(sourceBC->GetDOMWindow());
WindowGlobalChild* wgc =
win->GetCurrentInnerWindow()->GetWindowGlobalChild();
if (!wgc || !wgc->CanSend()) {
return NS_ERROR_FAILURE;
}
if (!wgc->CanNavigate(this)) {
return NS_ERROR_DOM_PROP_ACCESS_DENIED;
}
MOZ_ALWAYS_SUCCEEDS(
SetCurrentLoadIdentifier(Some(aLoadState->GetLoadIdentifier())));
wgc->SendInternalLoad(mozilla::WrapNotNull(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;
nsCOMPtr<nsIDocShell> docShell = mDocShell;
docShell->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 (IsSubframe()) {
// .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);
}
}
template <typename FuncT>
inline bool ApplyToDocumentsForPopup(Document* doc, FuncT func) {
// 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.
if (func(doc)) {
return true;
}
if (!doc->IsInitialDocument()) {
return false;
}
Document* parentDoc = doc->GetInProcessParentDocument();
if (!parentDoc || !parentDoc->NodePrincipal()->Equals(doc->NodePrincipal())) {
return false;
}
return func(parentDoc);
}
PopupBlocker::PopupControlState BrowsingContext::RevisePopupAbuseLevel(
PopupBlocker::PopupControlState aControl) {
if (!IsContent()) {
return PopupBlocker::openAllowed;
}
RefPtr<Document> doc = GetExtantDocument();
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() ||
(doc && doc->HasValidTransientUserGestureActivation())) {
// 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 (doc) {
auto ConsumeTransientUserActivationForMultiplePopupBlocking =
[&]() -> bool {
return ApplyToDocumentsForPopup(doc, [](Document* doc) {
return doc->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::GetUserActivationModifiersForPopup(
UserActivation::Modifiers* aModifiers) {
RefPtr<Document> doc = GetExtantDocument();
if (doc) {
// Unlike RevisePopupAbuseLevel, modifiers can always be used regardless
// of PopupControlState.
(void)ApplyToDocumentsForPopup(doc, [&](Document* doc) {
return doc->GetTransientUserGestureActivationModifiers(aModifiers);
});
}
}
void BrowsingContext::IncrementHistoryEntryCountForBrowsingContext() {
Unused << SetHistoryEntryCount(GetHistoryEntryCount() + 1);
}
std::tuple<bool, bool> BrowsingContext::CanFocusCheck(CallerType aCallerType) {
nsFocusManager* fm = nsFocusManager::GetFocusManager();
if (!fm) {
return {false, false};
}
nsCOMPtr<nsPIDOMWindowInner> caller = do_QueryInterface(GetEntryGlobal());
BrowsingContext* callerBC = caller ? caller->GetBrowsingContext() : nullptr;
RefPtr<BrowsingContext> 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 =
(callerBC ? callerBC : this)
->RevisePopupAbuseLevel(PopupBlocker::GetPopupControlState()) <
PopupBlocker::openBlocked;
}
bool isActive = false;
if (XRE_IsParentProcess()) {
CanonicalBrowsingContext* chromeTop = Canonical()->TopCrossChromeBoundary();
nsCOMPtr<nsPIDOMWindowOuter> 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);
}
}
bool BrowsingContext::CanBlurCheck(CallerType aCallerType) {
// If dom.disable_window_flip == true, then content should not be allowed
// to do blur (this would allow popunders, bug 369306)
return aCallerType == CallerType::System ||
!Preferences::GetBool("dom.disable_window_flip", true);
}
void BrowsingContext::Blur(CallerType aCallerType, ErrorResult& aError) {
if (!CanBlurCheck(aCallerType)) {
return;
}
if (ContentChild* cc = ContentChild::GetSingleton()) {
cc->SendWindowBlur(this, aCallerType);
} else if (ContentParent* cp = Canonical()->GetContentParent()) {
Unused << cp->SendWindowBlur(this, aCallerType);
}
}
Nullable<WindowProxyHolder> BrowsingContext::GetWindow() {
if (XRE_IsParentProcess() && !IsInProcess()) {
return nullptr;
}
return WindowProxyHolder(this);
}
Nullable<WindowProxyHolder> 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<JS::Value> aOpener,
ErrorResult& aError) const {
RefPtr<BrowsingContext> 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<WindowProxyHolder> BrowsingContext::GetParent(ErrorResult& aError) {
if (mIsDiscarded) {
return nullptr;
}
if (GetParent()) {
return WindowProxyHolder(GetParent());
}
return WindowProxyHolder(this);
}
void BrowsingContext::PostMessageMoz(JSContext* aCx,
JS::Handle<JS::Value> aMessage,
const nsAString& aTargetOrigin,
const Sequence<JSObject*>& aTransfer,
nsIPrincipal& aSubjectPrincipal,
ErrorResult& aError) {
if (mIsDiscarded) {
return;
}
RefPtr<BrowsingContext> sourceBc;
PostMessageData data;
data.targetOrigin() = aTargetOrigin;
data.subjectPrincipal() = &aSubjectPrincipal;
RefPtr<nsGlobalWindowInner> 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<JS::Value> 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 contains a shared memory object, then CloneScope
// would return SameProcess. Otherwise, it returns DifferentProcess.
if (message.CloneScope() ==
StructuredCloneHolder::StructuredCloneScope::DifferentProcess) {
ClonedMessageData clonedMessageData;
if (!message.BuildClonedMessageData(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.BuildClonedMessageData(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<JS::Value> 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.mChildOffset = mChildOffset;
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<WindowContext> BrowsingContext::IPCInitializer::GetParent() {
RefPtr<WindowContext> parent;
if (mParentId != 0) {
parent = WindowContext::GetById(mParentId);
MOZ_RELEASE_ASSERT(parent);
}
return parent.forget();
}
already_AddRefed<BrowsingContext> BrowsingContext::IPCInitializer::GetOpener() {
RefPtr<BrowsingContext> 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);
}
template <typename Callback>
void BrowsingContext::WalkPresContexts(Callback&& aCallback) {
PreOrderWalk([&](BrowsingContext* aContext) {
if (nsIDocShell* shell = aContext->GetDocShell()) {
if (RefPtr pc = shell->GetPresContext()) {
aCallback(pc.get());
}
}
});
}
void BrowsingContext::PresContextAffectingFieldChanged() {
WalkPresContexts([&](nsPresContext* aPc) {
aPc->RecomputeBrowsingContextDependentData();
});
}
void BrowsingContext::DidSet(FieldIndex<IDX_SessionStoreEpoch>,
uint32_t aOldValue) {
if (!mCurrentWindowContext) {
return;
}
SessionStoreChild* sessionStoreChild =
SessionStoreChild::From(mCurrentWindowContext->GetWindowGlobalChild());
if (!sessionStoreChild) {
return;
}
sessionStoreChild->SetEpoch(GetSessionStoreEpoch());
}
void BrowsingContext::DidSet(FieldIndex<IDX_GVAudibleAutoplayRequestStatus>) {
MOZ_ASSERT(IsTop(),
"Should only set GVAudibleAutoplayRequestStatus in the top-level "
"browsing context");
}
void BrowsingContext::DidSet(FieldIndex<IDX_GVInaudibleAutoplayRequestStatus>) {
MOZ_ASSERT(IsTop(),
"Should only set GVAudibleAutoplayRequestStatus in the top-level "
"browsing context");
}
bool BrowsingContext::CanSet(FieldIndex<IDX_ExplicitActive>,
const ExplicitActiveStatus&,
ContentParent* aSource) {
return XRE_IsParentProcess() && IsTop() && !aSource;
}
void BrowsingContext::DidSet(FieldIndex<IDX_ExplicitActive>,
ExplicitActiveStatus aOldValue) {
MOZ_ASSERT(IsTop());
const bool isActive = IsActive();
const bool wasActive = [&] {
if (aOldValue != ExplicitActiveStatus::None) {
return aOldValue == ExplicitActiveStatus::Active;
}
return GetParent() && GetParent()->IsActive();
}();
if (isActive == wasActive) {
return;
}
Group()->UpdateToplevelsSuspendedIfNeeded();
if (XRE_IsParentProcess()) {
if (BrowserParent* bp = Canonical()->GetBrowserParent()) {
bp->RecomputeProcessPriority();
#if defined(XP_WIN) && defined(ACCESSIBILITY)
if (a11y::Compatibility::IsDolphin()) {
// update active accessible documents on windows
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
}
// NOTE(emilio): Ideally we'd want to reuse the ExplicitActiveStatus::None
// set-up, but that's non-trivial to do because in content processes we
// can't access the top-cross-chrome-boundary bc.
auto manageTopDescendant = [&](auto* aChild) {
if (!aChild->ManuallyManagesActiveness()) {
aChild->SetIsActiveInternal(isActive, IgnoreErrors());
if (BrowserParent* bp = aChild->GetBrowserParent()) {
bp->SetRenderLayers(isActive);
}
}
return CallState::Continue;
};
Canonical()->CallOnAllTopDescendants(manageTopDescendant,
/* aIncludeNestedBrowsers = */ false);
}
PreOrderWalk([&](BrowsingContext* aContext) {
if (nsCOMPtr<nsIDocShell> ds = aContext->GetDocShell()) {
nsDocShell::Cast(ds)->ActivenessMaybeChanged();
}
});
}
void BrowsingContext::DidSet(FieldIndex<IDX_InRDMPane>, bool aOldValue) {
MOZ_ASSERT(IsTop(),
"Should only set InRDMPane in the top-level browsing context");
if (GetInRDMPane() == aOldValue) {
return;
}
PresContextAffectingFieldChanged();
}
bool BrowsingContext::CanSet(FieldIndex<IDX_PageAwakeRequestCount>,
uint32_t aNewValue, ContentParent* aSource) {
return IsTop() && XRE_IsParentProcess() && !aSource;
}
void BrowsingContext::DidSet(FieldIndex<IDX_PageAwakeRequestCount>,
uint32_t aOldValue) {
if (!IsTop() || aOldValue == GetPageAwakeRequestCount()) {
return;
}
Group()->UpdateToplevelsSuspendedIfNeeded();
}
auto BrowsingContext::CanSet(FieldIndex<IDX_AllowJavascript>, bool aValue,
ContentParent* aSource) -> CanSetResult {
if (mozilla::SessionHistoryInParent()) {
return XRE_IsParentProcess() && !aSource ? CanSetResult::Allow
: CanSetResult::Deny;
}
// Without Session History in Parent, session restore code still needs to set
// this from content processes.
return LegacyRevertIfNotOwningOrParentProcess(aSource);
}
void BrowsingContext::DidSet(FieldIndex<IDX_AllowJavascript>, bool aOldValue) {
RecomputeCanExecuteScripts();
}
void BrowsingContext::RecomputeCanExecuteScripts() {
const bool old = mCanExecuteScripts;
if (!AllowJavascript()) {
// Scripting has been explicitly disabled on our BrowsingContext.
mCanExecuteScripts = false;
} else if (GetParentWindowContext()) {
// Otherwise, inherit parent.
mCanExecuteScripts = GetParentWindowContext()->CanExecuteScripts();
} else {
// Otherwise, we're the root of the tree, and we haven't explicitly disabled
// script. Allow.
mCanExecuteScripts = true;
}
if (old != mCanExecuteScripts) {
for (WindowContext* wc : GetWindowContexts()) {
wc->RecomputeCanExecuteScripts();
}
}
}
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 any awake
// request that is used to prevent page from being suspended because web page
// might still need to run their script. Eg. waiting for media keys to resume
// media, playing web audio, waiting in a video call conference room.
return !IsActive() && GetPageAwakeRequestCount() == 0;
}
bool BrowsingContext::CanSet(FieldIndex<IDX_TouchEventsOverrideInternal>,
dom::TouchEventsOverride, ContentParent* aSource) {
return XRE_IsParentProcess() && !aSource;
}
void BrowsingContext::DidSet(FieldIndex<IDX_TouchEventsOverrideInternal>,
dom::TouchEventsOverride&& aOldValue) {
if (GetTouchEventsOverrideInternal() == aOldValue) {
return;
}
WalkPresContexts([&](nsPresContext* aPc) {
aPc->MediaFeatureValuesChanged(
{MediaFeatureChangeReason::SystemMetricsChange},
// We're already iterating through sub documents, so we don't need to
// propagate the change again.
MediaFeatureChangePropagation::JustThisDocument);
});
}
void BrowsingContext::DidSet(FieldIndex<IDX_EmbedderColorSchemes>,
EmbedderColorSchemes&& aOldValue) {
if (GetEmbedderColorSchemes() == aOldValue) {
return;
}
PresContextAffectingFieldChanged();
}
void BrowsingContext::DidSet(FieldIndex<IDX_PrefersColorSchemeOverride>,
dom::PrefersColorSchemeOverride aOldValue) {
MOZ_ASSERT(IsTop());
if (PrefersColorSchemeOverride() == aOldValue) {
return;
}
PresContextAffectingFieldChanged();
}
void BrowsingContext::DidSet(FieldIndex<IDX_MediumOverride>,
nsString&& aOldValue) {
MOZ_ASSERT(IsTop());
if (GetMediumOverride() == aOldValue) {
return;
}
PresContextAffectingFieldChanged();
}
void BrowsingContext::DidSet(FieldIndex<IDX_DisplayMode>,
enum DisplayMode aOldValue) {
MOZ_ASSERT(IsTop());
if (GetDisplayMode() == aOldValue) {
return;
}
WalkPresContexts([&](nsPresContext* aPc) {
aPc->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<IDX_Muted>) {
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();
}
});
}
bool BrowsingContext::CanSet(FieldIndex<IDX_IsAppTab>, const bool& aValue,
ContentParent* aSource) {
return XRE_IsParentProcess() && !aSource && IsTop();
}
bool BrowsingContext::CanSet(FieldIndex<IDX_HasSiblings>, const bool& aValue,
ContentParent* aSource) {
return XRE_IsParentProcess() && !aSource && IsTop();
}
bool BrowsingContext::CanSet(FieldIndex<IDX_ShouldDelayMediaFromStart>,
const bool& aValue, ContentParent* aSource) {
return IsTop();
}
void BrowsingContext::DidSet(FieldIndex<IDX_ShouldDelayMediaFromStart>,
bool aOldValue) {
MOZ_ASSERT(IsTop(), "Set attribute on non top-level context!");
if (aOldValue == GetShouldDelayMediaFromStart()) {
return;
}
if (!GetShouldDelayMediaFromStart()) {
PreOrderWalk([&](BrowsingContext* aContext) {
if (nsPIDOMWindowOuter* win = aContext->GetDOMWindow()) {
win->ActivateMediaComponents();
}
});
}
}
bool BrowsingContext::CanSet(FieldIndex<IDX_OverrideDPPX>, const float& aValue,
ContentParent* aSource) {
return XRE_IsParentProcess() && !aSource && IsTop();
}
void BrowsingContext::DidSet(FieldIndex<IDX_OverrideDPPX>, float aOldValue) {
MOZ_ASSERT(IsTop());
if (GetOverrideDPPX() == aOldValue) {
return;
}
PresContextAffectingFieldChanged();
}
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<IDX_UserAgentOverride>) {
MOZ_ASSERT(IsTop());
PreOrderWalk([&](BrowsingContext* aContext) {
nsIDocShell* shell = aContext->GetDocShell();
if (shell) {
shell->ClearCachedUserAgent();
}
});
}
bool BrowsingContext::CanSet(FieldIndex<IDX_IsInBFCache>, bool,
ContentParent* aSource) {
return IsTop() && !aSource && mozilla::BFCacheInParent();
}
void BrowsingContext::DidSet(FieldIndex<IDX_IsInBFCache>) {
MOZ_RELEASE_ASSERT(mozilla::BFCacheInParent());
MOZ_DIAGNOSTIC_ASSERT(IsTop());
const bool isInBFCache = GetIsInBFCache();
if (!isInBFCache) {
UpdateCurrentTopByBrowserId(this);
PreOrderWalk([&](BrowsingContext* aContext) {
aContext->mIsInBFCache = false;
nsCOMPtr<nsIDocShell> shell = aContext->GetDocShell();
if (shell) {
nsDocShell::Cast(shell)->ThawFreezeNonRecursive(true);
}
});
}
if (isInBFCache && XRE_IsContentProcess() && mDocShell) {
nsDocShell::Cast(mDocShell)->MaybeDisconnectChildListenersOnPageHide();
}
if (isInBFCache) {
PreOrderWalk([&](BrowsingContext* aContext) {
nsCOMPtr<nsIDocShell> shell = aContext->GetDocShell();
if (shell) {
nsDocShell::Cast(shell)->FirePageHideShowNonRecursive(false);
}
});
} else {
PostOrderWalk([&](BrowsingContext* aContext) {
nsCOMPtr<nsIDocShell> shell = aContext->GetDocShell();
if (shell) {
nsDocShell::Cast(shell)->FirePageHideShowNonRecursive(true);
}
});
}
if (isInBFCache) {
PreOrderWalk([&](BrowsingContext* aContext) {
nsCOMPtr<nsIDocShell> shell = aContext->GetDocShell();
if (shell) {
nsDocShell::Cast(shell)->ThawFreezeNonRecursive(false);
if (nsPresContext* pc = shell->GetPresContext()) {
pc->EventStateManager()->ResetHoverState();
}
}
aContext->mIsInBFCache = true;
Document* doc = aContext->GetDocument();
if (doc) {
// Notifying needs to happen after mIsInBFCache is set to true.
doc->NotifyActivityChanged();
}
});
if (XRE_IsParentProcess()) {
if (mCurrentWindowContext &&
mCurrentWindowContext->Canonical()->Fullscreen()) {
mCurrentWindowContext->Canonical()->ExitTopChromeDocumentFullscreen();
}
}
}
}
void BrowsingContext::DidSet(FieldIndex<IDX_SyntheticDocumentContainer>) {
if (WindowContext* parentWindowContext = GetParentWindowContext()) {
parentWindowContext->UpdateChildSynthetic(this,
GetSyntheticDocumentContainer());
}
}
void BrowsingContext::SetCustomPlatform(const nsAString& aPlatform,
ErrorResult& aRv) {
Top()->SetPlatformOverride(aPlatform, aRv);
}
void BrowsingContext::DidSet(FieldIndex<IDX_PlatformOverride>) {
MOZ_ASSERT(IsTop());
PreOrderWalk([&](BrowsingContext* aContext) {
nsIDocShell* shell = aContext->GetDocShell();
if (shell) {
shell->ClearCachedPlatform();
}
});
}
auto BrowsingContext::LegacyRevertIfNotOwningOrParentProcess(
ContentParent* aSource) -> CanSetResult {
if (aSource) {
MOZ_ASSERT(XRE_IsParentProcess());
if (!Canonical()->IsOwnedByProcess(aSource->ChildID())) {
return CanSetResult::Revert;
}
} else if (!IsInProcess() && !XRE_IsParentProcess()) {
// Don't allow this to be set from content processes that
// don't own the BrowsingContext.
return CanSetResult::Deny;
}
return CanSetResult::Allow;
}
bool BrowsingContext::CanSet(FieldIndex<IDX_IsActiveBrowserWindowInternal>,
const bool& aValue, ContentParent* aSource) {
// Should only be set in the parent process.
return XRE_IsParentProcess() && !aSource && IsTop();
}
void BrowsingContext::DidSet(FieldIndex<IDX_IsActiveBrowserWindowInternal>,
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<Document> doc = aContext->GetExtantDocument()) {
doc->UpdateDocumentStates(DocumentState::WINDOW_INACTIVE, true);
RefPtr<nsPIDOMWindowInner> win = doc->GetInnerWindow();
if (win) {
RefPtr<MediaDevices> devices;
if (isActivateEvent && (devices = win->GetExtantMediaDevices())) {
devices->BrowserWindowBecameActive();
}
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, nsGlobalWindowInner::Cast(win),
isActivateEvent ? u"activate"_ns : u"deactivate"_ns,
CanBubble::eYes, Cancelable::eYes, nullptr);
}
}
}
});
}
bool BrowsingContext::CanSet(FieldIndex<IDX_OpenerPolicy>,
nsILoadInfo::CrossOriginOpenerPolicy aPolicy,
ContentParent* aSource) {
// A potentially cross-origin isolated BC can't change opener policy, nor can
// a BC become potentially cross-origin isolated. An unchanged policy is
// always OK.
return GetOpenerPolicy() == aPolicy ||
(GetOpenerPolicy() !=
nsILoadInfo::
OPENER_POLICY_SAME_ORIGIN_EMBEDDER_POLICY_REQUIRE_CORP &&
aPolicy !=
nsILoadInfo::
OPENER_POLICY_SAME_ORIGIN_EMBEDDER_POLICY_REQUIRE_CORP);
}
auto BrowsingContext::CanSet(FieldIndex<IDX_AllowContentRetargeting>,
const bool& aAllowContentRetargeting,
ContentParent* aSource) -> CanSetResult {
return LegacyRevertIfNotOwningOrParentProcess(aSource);
}
auto BrowsingContext::CanSet(FieldIndex<IDX_AllowContentRetargetingOnChildren>,
const bool& aAllowContentRetargetingOnChildren,
ContentParent* aSource) -> CanSetResult {
return LegacyRevertIfNotOwningOrParentProcess(aSource);
}
bool BrowsingContext::CanSet(FieldIndex<IDX_FullscreenAllowedByOwner>,
const bool& aAllowed, ContentParent* aSource) {
return CheckOnlyEmbedderCanSet(aSource);
}
bool BrowsingContext::CanSet(FieldIndex<IDX_UseErrorPages>,
const bool& aUseErrorPages,
ContentParent* aSource) {
return CheckOnlyEmbedderCanSet(aSource);
}
TouchEventsOverride BrowsingContext::TouchEventsOverride() const {
for (const auto* bc = this; bc; bc = bc->GetParent()) {
auto tev = bc->GetTouchEventsOverrideInternal();
if (tev != dom::TouchEventsOverride::None) {
return tev;
}
}
return dom::TouchEventsOverride::None;
}
bool BrowsingContext::TargetTopLevelLinkClicksToBlank() const {
return Top()->GetTargetTopLevelLinkClicksToBlankInternal();
}
// 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<IDX_WatchedByDevToolsInternal>,
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);
}
auto BrowsingContext::CanSet(FieldIndex<IDX_DefaultLoadFlags>,
const uint32_t& aDefaultLoadFlags,
ContentParent* aSource) -> CanSetResult {
// 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 LegacyRevertIfNotOwningOrParentProcess(aSource);
}
void BrowsingContext::DidSet(FieldIndex<IDX_DefaultLoadFlags>) {
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<IDX_UseGlobalHistory>,
const bool& aUseGlobalHistory,
ContentParent* aSource) {
// Should only be set in the parent process.
// return XRE_IsParentProcess() && !aSource;
return true;
}
auto BrowsingContext::CanSet(FieldIndex<IDX_UserAgentOverride>,
const nsString& aUserAgent, ContentParent* aSource)
-> CanSetResult {
if (!IsTop()) {
return CanSetResult::Deny;
}
return LegacyRevertIfNotOwningOrParentProcess(aSource);
}
auto BrowsingContext::CanSet(FieldIndex<IDX_PlatformOverride>,
const nsString& aPlatform, ContentParent* aSource)
-> CanSetResult {
if (!IsTop()) {
return CanSetResult::Deny;
}
return LegacyRevertIfNotOwningOrParentProcess(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<IDX_EmbedderInnerWindowId>,
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<IDX_EmbedderElementType>,
const Maybe<nsString>&, ContentParent* aSource) {
return CheckOnlyEmbedderCanSet(aSource);
}
auto BrowsingContext::CanSet(FieldIndex<IDX_CurrentInnerWindowId>,
const uint64_t& aValue, ContentParent* aSource)
-> CanSetResult {
// Generally allow clearing this. We may want to be more precise about this
// check in the future.
if (aValue == 0) {
return CanSetResult::Allow;
}
// We must have access to the specified context.
RefPtr<WindowContext> window = WindowContext::GetById(aValue);
if (!window || window->GetBrowsingContext() != this) {
return CanSetResult::Deny;
}
if (aSource) {
// If the sending process is no longer the current owner, revert
MOZ_ASSERT(XRE_IsParentProcess());
if (!Canonical()->IsOwnedByProcess(aSource->ChildID())) {
return CanSetResult::Revert;
}
} else if (XRE_IsContentProcess() && !IsOwnedByProcess()) {
return CanSetResult::Deny;
}
return CanSetResult::Allow;
}
bool BrowsingContext::CanSet(FieldIndex<IDX_ParentInitiatedNavigationEpoch>,
const uint64_t& aValue, ContentParent* aSource) {
return XRE_IsParentProcess() && !aSource;
}
void BrowsingContext::DidSet(FieldIndex<IDX_CurrentInnerWindowId>) {
RefPtr<WindowContext> 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) {
// We set a timer when we set the current inner window. This
// will then flush the session storage to session store to
// make sure that we don't miss to store session storage to
// session store that is a result of navigation. This is due
// to Bug 1700623. We wish to fix this in Bug 1711886, where
// making sure to store everything would make this timer
// unnecessary.
Canonical()->MaybeScheduleSessionStoreUpdate();
mCurrentWindowContext->Canonical()->DidBecomeCurrentWindowGlobal(true);
}
}
BrowserParent::UpdateFocusFromBrowsingContext();
}
}
bool BrowsingContext::CanSet(FieldIndex<IDX_IsPopupSpam>, 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<IDX_IsPopupSpam>) {
if (GetIsPopupSpam()) {
PopupBlocker::RegisterOpenPopupSpam();
}
}
bool BrowsingContext::CanSet(FieldIndex<IDX_MessageManagerGroup>,
const nsString& aMessageManagerGroup,
ContentParent* aSource) {
// Should only be set in the parent process on toplevel.
return XRE_IsParentProcess() && !aSource && IsTopContent();
}
bool BrowsingContext::CanSet(
FieldIndex<IDX_OrientationLock>,
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<IDX_Loading>) {
if (mFields.Get<IDX_Loading>()) {
return;
}
while (!mDeprioritizedLoadRunner.isEmpty()) {
nsCOMPtr<nsIRunnable> runner = mDeprioritizedLoadRunner.popFirst();
NS_DispatchToCurrentThread(runner.forget());
}
if (IsTop()) {
Group()->FlushPostMessageEvents();
}
}
// Inform the Document for this context of the (potential) change in
// loading state
void BrowsingContext::DidSet(FieldIndex<IDX_AncestorLoading>) {
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<IDX_AuthorStyleDisabledDefault>) {
MOZ_ASSERT(IsTop(),
"Should only set AuthorStyleDisabledDefault in the top "
"browsing context");
// We don't need to handle changes to this field, since PageStyleChild.sys.mjs
// 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<IDX_TextZoom>, 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()) {
AsyncEventDispatcher::RunDOMEventWhenSafe(*element, u"TextZoomChange"_ns,
CanBubble::eYes,
ChromeOnlyDispatch::eYes);
}
}
}
// 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<IDX_FullZoom>, 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()) {
AsyncEventDispatcher::RunDOMEventWhenSafe(*element, u"FullZoomChange"_ns,
CanBubble::eYes,
ChromeOnlyDispatch::eYes);
}
}
}
void BrowsingContext::AddDeprioritizedLoadRunner(nsIRunnable* aRunner) {
MOZ_ASSERT(IsLoading());
MOZ_ASSERT(Top() == this);
RefPtr<DeprioritizedLoadRunner> runner = new DeprioritizedLoadRunner(aRunner);
mDeprioritizedLoadRunner.insertBack(runner);
NS_DispatchToCurrentThreadQueue(
runner.forget(), StaticPrefs::page_load_deprioritization_period(),
EventQueuePriority::Idle);
}
bool BrowsingContext::IsDynamic() const {
const BrowsingContext* current = this;
do {
if (current->CreatedDynamically()) {
return true;
}
} while ((current = current->GetParent()));
return false;
}
bool BrowsingContext::GetOffsetPath(nsTArray<uint32_t>& aPath) const {
for (const BrowsingContext* current = this; current && current->GetParent();
current = current->GetParent()) {
if (current->CreatedDynamically()) {
return false;
}
aPath.AppendElement(current->ChildOffset());
}
return true;
}
void BrowsingContext::GetHistoryID(JSContext* aCx,
JS::MutableHandle<JS::Value> 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_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<IDX_HasSessionHistory>,
bool aOldValue) {
MOZ_ASSERT(GetHasSessionHistory() || !aOldValue,
"We don't support turning off session history.");
if (GetHasSessionHistory() && !aOldValue) {
CreateChildSHistory();
}
}
bool BrowsingContext::CanSet(
FieldIndex<IDX_TargetTopLevelLinkClicksToBlankInternal>,
const bool& aTargetTopLevelLinkClicksToBlankInternal,
ContentParent* aSource) {
return XRE_IsParentProcess() && !aSource && IsTop();
}
bool BrowsingContext::CanSet(FieldIndex<IDX_BrowserId>, 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<IDX_PendingInitialization>,
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;
}
bool BrowsingContext::CanSet(FieldIndex<IDX_HasRestoreData>, bool aNewValue,
ContentParent* aSource) {
return IsTop();
}
bool BrowsingContext::CanSet(FieldIndex<IDX_IsUnderHiddenEmbedderElement>,
const bool& aIsUnderHiddenEmbedderElement,
ContentParent* aSource) {
return true;
}
bool BrowsingContext::CanSet(FieldIndex<IDX_ForceOffline>, bool aNewValue,
ContentParent* aSource) {
return XRE_IsParentProcess() && !aSource;
}
void BrowsingContext::DidSet(FieldIndex<IDX_IsUnderHiddenEmbedderElement>,
bool aOldValue) {
nsIDocShell* shell = GetDocShell();
if (!shell) {
return;
}
const bool newValue = IsUnderHiddenEmbedderElement();
if (NS_WARN_IF(aOldValue == newValue)) {
return;
}
if (auto* bc = BrowserChild::GetFrom(shell)) {
bc->UpdateVisibility();
}
if (PresShell* presShell = shell->GetPresShell()) {
presShell->SetIsUnderHiddenEmbedderElement(newValue);
}
// Propagate to children.
for (BrowsingContext* child : Children()) {
Element* embedderElement = child->GetEmbedderElement();
if (!embedderElement) {
// TODO: We shouldn't need to null check here since `child` and the
// element returned by `child->GetEmbedderElement()` are in our
// process (the actual browsing context represented by `child` may not
// be, but that doesn't matter). However, there are currently a very
// small number of crashes due to `embedderElement` being null, somehow
// - see bug 1551241. For now we wallpaper the crash.
continue;
}
bool embedderFrameIsHidden = true;
if (auto* embedderFrame = embedderElement->GetPrimaryFrame()) {
embedderFrameIsHidden = !embedderFrame->StyleVisibility()->IsVisible();
}
bool hidden = IsUnderHiddenEmbedderElement() || embedderFrameIsHidden;
if (child->IsUnderHiddenEmbedderElement() != hidden) {
Unused << child->SetIsUnderHiddenEmbedderElement(hidden);
}
}
}
bool BrowsingContext::IsPopupAllowed() {
for (auto* context = GetCurrentWindowContext(); context;
context = context->GetParentWindowContext()) {
if (context->CanShowPopup()) {
return true;
}
}
return false;
}
/* static */
bool BrowsingContext::ShouldAddEntryForRefresh(
nsIURI* aPreviousURI, const SessionHistoryInfo& aInfo) {
return ShouldAddEntryForRefresh(aPreviousURI, aInfo.GetURI(),
aInfo.HasPostData());
}
/* static */
bool BrowsingContext::ShouldAddEntryForRefresh(nsIURI* aPreviousURI,
nsIURI* aNewURI,
bool aHasPostData) {
if (aHasPostData) {
return true;
}
bool equalsURI = false;
if (aPreviousURI) {
aPreviousURI->Equals(aNewURI, &equalsURI);
}
return !equalsURI;
}
void BrowsingContext::SessionHistoryCommit(
const LoadingSessionHistoryInfo& aInfo, uint32_t aLoadType,
nsIURI* aPreviousURI, SessionHistoryInfo* aPreviousActiveEntry,
bool aPersist, bool aCloneEntryChildren, bool aChannelExpired,
uint32_t aCacheKey) {
nsID changeID = {};
if (XRE_IsContentProcess()) {
RefPtr<ChildSHistory> 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,
// ShouldUpdateSessionHistory(loadType) returns true and it's not a
// refresh for which ShouldAddEntryForRefresh returns false.
// It is possible that this leads to wrong length temporarily, but
// so would not having the check for replace.
// Note that nsSHistory::AddEntry does a replace load if the current
// entry is not marked as a persisted entry. The child process does
// not have access to the current entry, so we use the previous active
// entry as the best approximation. When that's not the current entry
// then the length might be wrong briefly, until the parent process
// commits the actual length.
if (!LOAD_TYPE_HAS_FLAGS(
aLoadType, nsIWebNavigation::LOAD_FLAGS_REPLACE_HISTORY) &&
(IsTop()
? (!aPreviousActiveEntry || aPreviousActiveEntry->GetPersist())
: !!aPreviousActiveEntry) &&
ShouldUpdateSessionHistory(aLoadType) &&
(!LOAD_TYPE_HAS_FLAGS(aLoadType,
nsIWebNavigation::LOAD_FLAGS_IS_REFRESH) ||
ShouldAddEntryForRefresh(aPreviousURI, aInfo.mInfo))) {
changeID = rootSH->AddPendingHistoryChange();
}
} else {
// History load doesn't change the length, only index.
changeID = rootSH->AddPendingHistoryChange(aInfo.mOffset, 0);
}
}
ContentChild* cc = ContentChild::GetSingleton();
mozilla::Unused << cc->SendHistoryCommit(
this, aInfo.mLoadId, changeID, aLoadType, aPersist, aCloneEntryChildren,
aChannelExpired, aCacheKey);
} else {
Canonical()->SessionHistoryCommit(aInfo.mLoadId, changeID, aLoadType,
aPersist, aCloneEntryChildren,
aChannelExpired, aCacheKey);
}
}
void BrowsingContext::SetActiveSessionHistoryEntry(
const Maybe<nsPoint>& aPreviousScrollPos, SessionHistoryInfo* aInfo,
uint32_t aLoadType, uint32_t aUpdatedCacheKey, bool aUpdateLength) {
if (XRE_IsContentProcess()) {
// XXX Why we update cache key only in content process case?
if (aUpdatedCacheKey != 0) {
aInfo->SetCacheKey(aUpdatedCacheKey);
}
nsID changeID = {};
if (aUpdateLength) {
RefPtr<ChildSHistory> 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,
bool aUserActivation, std::function<void(Maybe<int32_t>&&)>&& aResolver) {
if (XRE_IsContentProcess()) {
ContentChild::GetSingleton()->SendHistoryGo(
this, aOffset, aHistoryEpoch, aRequireUserInteraction, aUserActivation,
std::move(aResolver),
[](mozilla::ipc::
ResponseRejectReason) { /* FIXME Is ignoring this fine? */ });
} else {
RefPtr<CanonicalBrowsingContext> self = Canonical();
aResolver(self->HistoryGo(
aOffset, aHistoryEpoch, aRequireUserInteraction, aUserActivation,
Canonical()->GetContentParent()
? Some(Canonical()->GetContentParent()->ChildID())
: Nothing()));
}
}
void BrowsingContext::SetChildSHistory(ChildSHistory* aChildSHistory) {
mChildSessionHistory = aChildSHistory;
mChildSessionHistory->SetBrowsingContext(this);
mFields.SetWithoutSyncing<IDX_HasSessionHistory>(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) && IsSubframe()));
}
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();
}
void BrowsingContext::LocationCreated(dom::Location* aLocation) {
MOZ_ASSERT(!aLocation->isInList());
mLocations.insertBack(aLocation);
}
void BrowsingContext::ClearCachedValuesOfLocations() {
for (dom::Location* loc = mLocations.getFirst(); loc; loc = loc->getNext()) {
loc->ClearCachedValues();
}
}
} // namespace dom
namespace ipc {
void IPDLParamTraits<dom::MaybeDiscarded<dom::BrowsingContext>>::Write(
IPC::MessageWriter* aWriter, IProtocol* aActor,
const dom::MaybeDiscarded<dom::BrowsingContext>& aParam) {
MOZ_DIAGNOSTIC_ASSERT(!aParam.GetMaybeDiscarded() ||
aParam.GetMaybeDiscarded()->EverAttached());
uint64_t id = aParam.ContextId();
WriteIPDLParam(aWriter, aActor, id);
}
bool IPDLParamTraits<dom::MaybeDiscarded<dom::BrowsingContext>>::Read(
IPC::MessageReader* aReader, IProtocol* aActor,
dom::MaybeDiscarded<dom::BrowsingContext>* aResult) {
uint64_t id = 0;
if (!ReadIPDLParam(aReader, aActor, &id)) {
return false;
}
if (id == 0) {
*aResult = nullptr;
} else if (RefPtr<dom::BrowsingContext> bc = dom::BrowsingContext::Get(id)) {
*aResult = std::move(bc);
} else {
aResult->SetDiscarded(id);
}
return true;
}
void IPDLParamTraits<dom::BrowsingContext::IPCInitializer>::Write(
IPC::MessageWriter* aWriter, IProtocol* aActor,
const dom::BrowsingContext::IPCInitializer& aInit) {
// Write actor ID parameters.
WriteIPDLParam(aWriter, aActor, aInit.mId);
WriteIPDLParam(aWriter, aActor, aInit.mParentId);
WriteIPDLParam(aWriter, aActor, aInit.mWindowless);
WriteIPDLParam(aWriter, aActor, aInit.mUseRemoteTabs);
WriteIPDLParam(aWriter, aActor, aInit.mUseRemoteSubframes);
WriteIPDLParam(aWriter, aActor, aInit.mCreatedDynamically);
WriteIPDLParam(aWriter, aActor, aInit.mChildOffset);
WriteIPDLParam(aWriter, aActor, aInit.mOriginAttributes);
WriteIPDLParam(aWriter, aActor, aInit.mRequestContextId);
WriteIPDLParam(aWriter, aActor, aInit.mSessionHistoryIndex);
WriteIPDLParam(aWriter, aActor, aInit.mSessionHistoryCount);
WriteIPDLParam(aWriter, aActor, aInit.mFields);
}
bool IPDLParamTraits<dom::BrowsingContext::IPCInitializer>::Read(
IPC::MessageReader* aReader, IProtocol* aActor,
dom::BrowsingContext::IPCInitializer* aInit) {
// Read actor ID parameters.
if (!ReadIPDLParam(aReader, aActor, &aInit->mId) ||
!ReadIPDLParam(aReader, aActor, &aInit->mParentId) ||
!ReadIPDLParam(aReader, aActor, &aInit->mWindowless) ||
!ReadIPDLParam(aReader, aActor, &aInit->mUseRemoteTabs) ||
!ReadIPDLParam(aReader, aActor, &aInit->mUseRemoteSubframes) ||
!ReadIPDLParam(aReader, aActor, &aInit->mCreatedDynamically) ||
!ReadIPDLParam(aReader, aActor, &aInit->mChildOffset) ||
!ReadIPDLParam(aReader, aActor, &aInit->mOriginAttributes) ||
!ReadIPDLParam(aReader, aActor, &aInit->mRequestContextId) ||
!ReadIPDLParam(aReader, aActor, &aInit->mSessionHistoryIndex) ||
!ReadIPDLParam(aReader, aActor, &aInit->mSessionHistoryCount) ||
!ReadIPDLParam(aReader, aActor, &aInit->mFields)) {
return false;
}
return true;
}
template struct IPDLParamTraits<dom::BrowsingContext::BaseTransaction>;
} // namespace ipc
} // namespace mozilla