/* -*- 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/CanonicalBrowsingContext.h"
#include "ContentAnalysis.h"
#include "ErrorList.h"
#include "mozilla/CheckedInt.h"
#include "mozilla/Components.h"
#include "mozilla/ErrorResult.h"
#include "mozilla/EventForwards.h"
#include "mozilla/AsyncEventDispatcher.h"
#include "mozilla/dom/BrowserParent.h"
#include "mozilla/dom/BrowsingContextBinding.h"
#include "mozilla/dom/BrowsingContextGroup.h"
#include "mozilla/dom/ContentParent.h"
#include "mozilla/dom/EventTarget.h"
#include "mozilla/dom/PBrowserParent.h"
#include "mozilla/dom/PBackgroundSessionStorageCache.h"
#include "mozilla/dom/PWindowGlobalParent.h"
#include "mozilla/dom/Promise.h"
#include "mozilla/dom/Promise-inl.h"
#include "mozilla/dom/WindowGlobalParent.h"
#include "mozilla/dom/ContentProcessManager.h"
#include "mozilla/dom/MediaController.h"
#include "mozilla/dom/MediaControlService.h"
#include "mozilla/dom/ContentPlaybackController.h"
#include "mozilla/dom/SessionStorageManager.h"
#include "mozilla/ipc/ProtocolUtils.h"
#include "mozilla/layers/CompositorBridgeChild.h"
#ifdef NS_PRINTING
# include
"mozilla/layout/RemotePrintJobParent.h"
#endif
#include "mozilla/net/DocumentLoadListener.h"
#include "mozilla/NullPrincipal.h"
#include "mozilla/StaticPrefs_browser.h"
#include "mozilla/StaticPrefs_docshell.h"
#include "mozilla/StaticPrefs_fission.h"
#include "mozilla/glean/DomMetrics.h"
#include "nsILayoutHistoryState.h"
#include "nsIPrintSettings.h"
#include "nsIPrintSettingsService.h"
#include "nsISupports.h"
#include "nsIWebNavigation.h"
#include "nsDocShell.h"
#include "nsFrameLoader.h"
#include "nsFrameLoaderOwner.h"
#include "nsGlobalWindowOuter.h"
#include "nsIContentAnalysis.h"
#include "nsIWebBrowserChrome.h"
#include "nsIXULRuntime.h"
#include "nsNetUtil.h"
#include "nsSHistory.h"
#include "nsSecureBrowserUI.h"
#include "nsQueryObject.h"
#include "nsBrowserStatusFilter.h"
#include "nsIBrowser.h"
#include "nsTHashSet.h"
#include "nsISessionStoreFunctions.h"
#include "nsIXPConnect.h"
#include "nsImportModule.h"
#include "UnitTransforms.h"
using namespace mozilla::ipc;
extern mozilla::LazyLogModule gAutoplayPermissionLog;
extern mozilla::LazyLogModule gSHLog;
extern mozilla::LazyLogModule gSHIPBFCacheLog;
#define AUTOPLAY_LOG(msg, ...) \
MOZ_LOG(gAutoplayPermissionLog, LogLevel::Debug, (msg,
##__VA_ARGS__))
static mozilla::LazyLogModule sPBContext(
"PBContext");
// Global count of canonical browsing contexts with the private attribute set
static uint32_t gNumberOfPrivateContexts = 0;
// Current parent process epoch for parent initiated navigations
static uint64_t gParentInitiatedNavigationEpoch = 0;
static void IncreasePrivateCount() {
gNumberOfPrivateContexts++;
MOZ_LOG(sPBContext, mozilla::LogLevel::Debug,
(
"%s: Private browsing context count %d -> %d", __func__,
gNumberOfPrivateContexts - 1, gNumberOfPrivateContexts));
if (gNumberOfPrivateContexts > 1) {
return;
}
static bool sHasSeenPrivateContext =
false;
if (!sHasSeenPrivateContext) {
sHasSeenPrivateContext =
true;
mozilla::glean::dom_parentprocess::private_window_used.Set(
true);
}
}
static void DecreasePrivateCount() {
MOZ_ASSERT(gNumberOfPrivateContexts > 0);
gNumberOfPrivateContexts--;
MOZ_LOG(sPBContext, mozilla::LogLevel::Debug,
(
"%s: Private browsing context count %d -> %d", __func__,
gNumberOfPrivateContexts + 1, gNumberOfPrivateContexts));
if (!gNumberOfPrivateContexts &&
!mozilla::StaticPrefs::browser_privatebrowsing_autostart()) {
nsCOMPtr<nsIObserverService> observerService =
mozilla::services::GetObserverService();
if (observerService) {
MOZ_LOG(sPBContext, mozilla::LogLevel::Debug,
(
"%s: last-pb-context-exited fired", __func__));
observerService->NotifyObservers(nullptr,
"last-pb-context-exited",
nullptr);
}
}
}
namespace mozilla::dom {
extern mozilla::LazyLogModule gUserInteractionPRLog;
#define USER_ACTIVATION_LOG(msg, ...) \
MOZ_LOG(gUserInteractionPRLog, LogLevel::Debug, (msg,
##__VA_ARGS__))
CanonicalBrowsingContext::CanonicalBrowsingContext(WindowContext* aParentWindow,
BrowsingContextGroup* aGroup,
uint64_t aBrowsingContextId,
uint64_t aOwnerProcessId,
uint64_t aEmbedderProcessId,
BrowsingContext::Type aType,
FieldValues&& aInit)
: BrowsingContext(aParentWindow, aGroup, aBrowsingContextId, aType,
std::move(aInit)),
mProcessId(aOwnerProcessId),
mEmbedderProcessId(aEmbedderProcessId),
mPermanentKey(JS::NullValue()) {
// You are only ever allowed to create CanonicalBrowsingContexts in the
// parent process.
MOZ_RELEASE_ASSERT(XRE_IsParentProcess());
// The initial URI in a BrowsingContext is always "about:blank".
MOZ_ALWAYS_SUCCEEDS(
NS_NewURI(getter_AddRefs(mCurrentRemoteURI),
"about:blank"));
mozilla::HoldJSObjects(
this);
}
CanonicalBrowsingContext::~CanonicalBrowsingContext() {
mPermanentKey.setNull();
mozilla::DropJSObjects(
this);
if (mSessionHistory) {
mSessionHistory->SetBrowsingContext(nullptr);
}
}
/* static */
already_AddRefed<CanonicalBrowsingContext> CanonicalBrowsingContext::Get(
uint64_t aId) {
MOZ_RELEASE_ASSERT(XRE_IsParentProcess());
return BrowsingContext::Get(aId).downcast<CanonicalBrowsingContext>();
}
/* static */
CanonicalBrowsingContext* CanonicalBrowsingContext::Cast(
BrowsingContext* aContext) {
MOZ_RELEASE_ASSERT(XRE_IsParentProcess());
return static_cast<CanonicalBrowsingContext*>(aContext);
}
/* static */
const CanonicalBrowsingContext* CanonicalBrowsingContext::Cast(
const BrowsingContext* aContext) {
MOZ_RELEASE_ASSERT(XRE_IsParentProcess());
return static_cast<
const CanonicalBrowsingContext*>(aContext);
}
already_AddRefed<CanonicalBrowsingContext> CanonicalBrowsingContext::Cast(
already_AddRefed<BrowsingContext>&& aContext) {
MOZ_RELEASE_ASSERT(XRE_IsParentProcess());
return aContext.downcast<CanonicalBrowsingContext>();
}
ContentParent* CanonicalBrowsingContext::GetContentParent()
const {
if (mProcessId == 0) {
return nullptr;
}
ContentProcessManager* cpm = ContentProcessManager::GetSingleton();
if (!cpm) {
return nullptr;
}
return cpm->GetContentProcessById(ContentParentId(mProcessId));
}
void CanonicalBrowsingContext::GetCurrentRemoteType(nsACString& aRemoteType,
ErrorResult& aRv)
const {
// If we're in the parent process, dump out the void string.
if (mProcessId == 0) {
aRemoteType = NOT_REMOTE_TYPE;
return;
}
ContentParent* cp = GetContentParent();
if (!cp) {
aRv.
Throw(NS_ERROR_UNEXPECTED);
return;
}
aRemoteType = cp->GetRemoteType();
}
void CanonicalBrowsingContext::SetOwnerProcessId(uint64_t aProcessId) {
MOZ_LOG(GetLog(), LogLevel::Debug,
(
"SetOwnerProcessId for 0x%08" PRIx64
" (0x%08" PRIx64
" -> 0x%08" PRIx64
")",
Id(), mProcessId, aProcessId));
mProcessId = aProcessId;
}
nsISecureBrowserUI* CanonicalBrowsingContext::GetSecureBrowserUI() {
if (!IsTop()) {
return nullptr;
}
if (!mSecureBrowserUI) {
mSecureBrowserUI =
new nsSecureBrowserUI(
this);
}
return mSecureBrowserUI;
}
namespace {
// The DocShellProgressBridge is attached to a root content docshell loaded in
// the parent process. Notifications are paired up with the docshell which they
// came from, so that they can be fired to the correct
// BrowsingContextWebProgress and bubble through this tree separately.
//
// Notifications are filtered by a nsBrowserStatusFilter before being received
// by the DocShellProgressBridge.
class DocShellProgressBridge :
public nsIWebProgressListener {
public:
NS_DECL_ISUPPORTS
// NOTE: This relies in the expansion of `NS_FORWARD_SAFE` and all listener
// methods accepting an `aWebProgress` argument. If this changes in the
// future, this may need to be written manually.
NS_FORWARD_SAFE_NSIWEBPROGRESSLISTENER(GetTargetContext(aWebProgress))
explicit DocShellProgressBridge(uint64_t aTopContextId)
: mTopContextId(aTopContextId) {}
private:
virtual ~DocShellProgressBridge() =
default;
nsIWebProgressListener* GetTargetContext(nsIWebProgress* aWebProgress) {
RefPtr<CanonicalBrowsingContext> context;
if (nsCOMPtr<nsIDocShell> docShell = do_QueryInterface(aWebProgress)) {
context = docShell->GetBrowsingContext()->Canonical();
}
else {
context = CanonicalBrowsingContext::Get(mTopContextId);
}
return context && !context->IsDiscarded() ? context->GetWebProgress()
: nullptr;
}
uint64_t mTopContextId = 0;
};
NS_IMPL_ISUPPORTS(DocShellProgressBridge, nsIWebProgressListener)
}
// namespace
void CanonicalBrowsingContext::MaybeAddAsProgressListener(
nsIWebProgress* aWebProgress) {
// Only add as a listener if the created docshell is a toplevel content
// docshell. We'll get notifications for all of our subframes through a single
// listener.
if (!IsTopContent()) {
return;
}
if (!mDocShellProgressBridge) {
mDocShellProgressBridge =
new DocShellProgressBridge(Id());
mStatusFilter =
new nsBrowserStatusFilter();
mStatusFilter->AddProgressListener(mDocShellProgressBridge,
nsIWebProgress::NOTIFY_ALL);
}
aWebProgress->AddProgressListener(mStatusFilter, nsIWebProgress::NOTIFY_ALL);
}
void CanonicalBrowsingContext::ReplacedBy(
CanonicalBrowsingContext* aNewContext,
const NavigationIsolationOptions& aRemotenessOptions) {
MOZ_ASSERT(!aNewContext->mWebProgress);
MOZ_ASSERT(!aNewContext->mSessionHistory);
MOZ_ASSERT(IsTop() && aNewContext->IsTop());
mIsReplaced =
true;
aNewContext->mIsReplaced =
false;
if (mStatusFilter) {
mStatusFilter->RemoveProgressListener(mDocShellProgressBridge);
mStatusFilter = nullptr;
}
mWebProgress->ContextReplaced(aNewContext);
aNewContext->mWebProgress = std::move(mWebProgress);
// Use the Transaction for the fields which need to be updated whether or not
// the new context has been attached before.
// SetWithoutSyncing can be used if context hasn't been attached.
Transaction txn;
txn.SetBrowserId(GetBrowserId());
txn.SetIsAppTab(GetIsAppTab());
txn.SetHasSiblings(GetHasSiblings());
txn.SetTopLevelCreatedByWebContent(GetTopLevelCreatedByWebContent());
txn.SetHistoryID(GetHistoryID());
txn.SetExplicitActive(GetExplicitActive());
txn.SetEmbedderColorSchemes(GetEmbedderColorSchemes());
txn.SetHasRestoreData(GetHasRestoreData());
txn.SetShouldDelayMediaFromStart(GetShouldDelayMediaFromStart());
txn.SetForceOffline(GetForceOffline());
txn.SetTopInnerSizeForRFP(GetTopInnerSizeForRFP());
// Propagate some settings on BrowsingContext replacement so they're not lost
// on bfcached navigations. These are important for GeckoView (see bug
// 1781936).
txn.SetAllowJavascript(GetAllowJavascript());
txn.SetForceEnableTrackingProtection(GetForceEnableTrackingProtection());
txn.SetUserAgentOverride(GetUserAgentOverride());
txn.SetSuspendMediaWhenInactive(GetSuspendMediaWhenInactive());
txn.SetDisplayMode(GetDisplayMode());
txn.SetForceDesktopViewport(GetForceDesktopViewport());
txn.SetIsUnderHiddenEmbedderElement(GetIsUnderHiddenEmbedderElement());
// When using site-specific zoom, we let the frontend manage the zoom level
// of BFCache'd contexts. Overriding those zoom levels can cause weirdness
// like bug 1846141. We always copy to new contexts to avoid bug 1914149.
if (!aNewContext->EverAttached() ||
!StaticPrefs::browser_zoom_siteSpecific()) {
txn.SetFullZoom(GetFullZoom());
txn.SetTextZoom(GetTextZoom());
}
// Propagate the default load flags so that the TRR mode flags are forwarded
// to the new browsing context. See bug 1828643.
txn.SetDefaultLoadFlags(GetDefaultLoadFlags());
// As this is a different BrowsingContext, set InitialSandboxFlags to the
// current flags in the new context so that they also apply to any initial
// about:blank documents created in it.
txn.SetSandboxFlags(GetSandboxFlags());
txn.SetInitialSandboxFlags(GetSandboxFlags());
txn.SetTargetTopLevelLinkClicksToBlankInternal(
TargetTopLevelLinkClicksToBlank());
if (aNewContext->EverAttached()) {
MOZ_ALWAYS_SUCCEEDS(txn.Commit(aNewContext));
}
else {
txn.CommitWithoutSyncing(aNewContext);
}
aNewContext->mRestoreState = mRestoreState.forget();
MOZ_ALWAYS_SUCCEEDS(SetHasRestoreData(
false));
// XXXBFCache name handling is still a bit broken in Fission in general,
// at least in case name should be cleared.
if (aRemotenessOptions.mTryUseBFCache) {
MOZ_ASSERT(!aNewContext->EverAttached());
aNewContext->mFields.SetWithoutSyncing<IDX_Name>(GetName());
// We don't copy over HasLoadedNonInitialDocument here, we'll actually end
// up loading a new initial document at this point, before the real load.
// The real load will then end up setting HasLoadedNonInitialDocument to
// true.
}
if (mSessionHistory) {
mSessionHistory->SetBrowsingContext(aNewContext);
// At this point we will be creating a new ChildSHistory in the child.
// That means that the child's epoch will be reset, so it makes sense to
// reset the epoch in the parent too.
mSessionHistory->SetEpoch(0, Nothing());
mSessionHistory.swap(aNewContext->mSessionHistory);
RefPtr<ChildSHistory> childSHistory = ForgetChildSHistory();
aNewContext->SetChildSHistory(childSHistory);
}
BackgroundSessionStorageManager::PropagateManager(Id(), aNewContext->Id());
// Transfer the ownership of the priority active status from the old context
// to the new context.
aNewContext->mPriorityActive = mPriorityActive;
mPriorityActive =
false;
MOZ_ASSERT(aNewContext->mLoadingEntries.IsEmpty());
mLoadingEntries.SwapElements(aNewContext->mLoadingEntries);
MOZ_ASSERT(!aNewContext->mActiveEntry);
mActiveEntry.swap(aNewContext->mActiveEntry);
aNewContext->mPermanentKey = mPermanentKey;
mPermanentKey.setNull();
}
void CanonicalBrowsingContext::UpdateSecurityState() {
if (mSecureBrowserUI) {
mSecureBrowserUI->RecomputeSecurityFlags();
}
}
void CanonicalBrowsingContext::GetWindowGlobals(
nsTArray<RefPtr<WindowGlobalParent>>& aWindows) {
aWindows.SetCapacity(GetWindowContexts().Length());
for (
auto& window : GetWindowContexts()) {
aWindows.AppendElement(
static_cast<WindowGlobalParent*>(window.get()));
}
}
WindowGlobalParent* CanonicalBrowsingContext::GetCurrentWindowGlobal()
const {
return static_cast<WindowGlobalParent*>(GetCurrentWindowContext());
}
WindowGlobalParent* CanonicalBrowsingContext::GetParentWindowContext() {
return static_cast<WindowGlobalParent*>(
BrowsingContext::GetParentWindowContext());
}
WindowGlobalParent* CanonicalBrowsingContext::GetTopWindowContext() {
return static_cast<WindowGlobalParent*>(
BrowsingContext::GetTopWindowContext());
}
already_AddRefed<nsIWidget>
CanonicalBrowsingContext::GetParentProcessWidgetContaining() {
// If our document is loaded in-process, such as chrome documents, get the
// widget directly from our outer window. Otherwise, try to get the widget
// from the toplevel content's browser's element.
nsCOMPtr<nsIWidget> widget;
if (nsGlobalWindowOuter* window = nsGlobalWindowOuter::Cast(GetDOMWindow())) {
widget = window->GetNearestWidget();
}
else if (Element* topEmbedder = Top()->GetEmbedderElement()) {
widget = nsContentUtils::WidgetForContent(topEmbedder);
if (!widget) {
widget = nsContentUtils::WidgetForDocument(topEmbedder->OwnerDoc());
}
}
if (widget) {
widget = widget->GetTopLevelWidget();
}
return widget.forget();
}
already_AddRefed<nsIBrowserDOMWindow>
CanonicalBrowsingContext::GetBrowserDOMWindow() {
RefPtr<CanonicalBrowsingContext> chromeTop = TopCrossChromeBoundary();
nsGlobalWindowOuter* topWin;
if ((topWin = nsGlobalWindowOuter::Cast(chromeTop->GetDOMWindow())) &&
topWin->IsChromeWindow()) {
return do_AddRef(topWin->GetBrowserDOMWindow());
}
return nullptr;
}
already_AddRefed<WindowGlobalParent>
CanonicalBrowsingContext::GetEmbedderWindowGlobal()
const {
uint64_t windowId = GetEmbedderInnerWindowId();
if (windowId == 0) {
return nullptr;
}
return WindowGlobalParent::GetByInnerWindowId(windowId);
}
CanonicalBrowsingContext*
CanonicalBrowsingContext::GetParentCrossChromeBoundary() {
if (GetParent()) {
return Cast(GetParent());
}
if (
auto* embedder = GetEmbedderElement()) {
return Cast(embedder->OwnerDoc()->GetBrowsingContext());
}
return nullptr;
}
CanonicalBrowsingContext* CanonicalBrowsingContext::TopCrossChromeBoundary() {
CanonicalBrowsingContext* bc =
this;
while (
auto* parent = bc->GetParentCrossChromeBoundary()) {
bc = parent;
}
return bc;
}
Nullable<WindowProxyHolder> CanonicalBrowsingContext::GetTopChromeWindow() {
RefPtr<CanonicalBrowsingContext> bc = TopCrossChromeBoundary();
if (bc->IsChrome()) {
return WindowProxyHolder(bc.forget());
}
return nullptr;
}
nsISHistory* CanonicalBrowsingContext::GetSessionHistory() {
if (!IsTop()) {
return Cast(Top())->GetSessionHistory();
}
// Check GetChildSessionHistory() to make sure that this BrowsingContext has
// session history enabled.
if (!mSessionHistory && GetChildSessionHistory()) {
mSessionHistory =
new nsSHistory(
this);
}
return mSessionHistory;
}
SessionHistoryEntry* CanonicalBrowsingContext::GetActiveSessionHistoryEntry() {
return mActiveEntry;
}
void CanonicalBrowsingContext::SetActiveSessionHistoryEntry(
SessionHistoryEntry* aEntry) {
mActiveEntry = aEntry;
}
bool CanonicalBrowsingContext::HasHistoryEntry(nsISHEntry* aEntry) {
// XXX Should we check also loading entries?
return aEntry && mActiveEntry == aEntry;
}
void CanonicalBrowsingContext::SwapHistoryEntries(nsISHEntry* aOldEntry,
nsISHEntry* aNewEntry) {
// XXX Should we check also loading entries?
if (mActiveEntry == aOldEntry) {
nsCOMPtr<SessionHistoryEntry> newEntry = do_QueryInterface(aNewEntry);
mActiveEntry = newEntry.forget();
}
}
void CanonicalBrowsingContext::AddLoadingSessionHistoryEntry(
uint64_t aLoadId, SessionHistoryEntry* aEntry) {
Unused << SetHistoryID(aEntry->DocshellID());
mLoadingEntries.AppendElement(LoadingSessionHistoryEntry{aLoadId, aEntry});
}
void CanonicalBrowsingContext::GetLoadingSessionHistoryInfoFromParent(
Maybe<LoadingSessionHistoryInfo>& aLoadingInfo) {
nsISHistory* shistory = GetSessionHistory();
if (!shistory || !GetParent()) {
return;
}
SessionHistoryEntry* parentSHE =
GetParent()->Canonical()->GetActiveSessionHistoryEntry();
if (parentSHE) {
int32_t index = -1;
for (BrowsingContext* sibling : GetParent()->Children()) {
++index;
if (sibling ==
this) {
nsCOMPtr<nsISHEntry> shEntry;
parentSHE->GetChildSHEntryIfHasNoDynamicallyAddedChild(
index, getter_AddRefs(shEntry));
nsCOMPtr<SessionHistoryEntry> she = do_QueryInterface(shEntry);
if (she) {
aLoadingInfo.emplace(she);
mLoadingEntries.AppendElement(LoadingSessionHistoryEntry{
aLoadingInfo.value().mLoadId, she.get()});
Unused << SetHistoryID(she->DocshellID());
}
break;
}
}
}
}
UniquePtr<LoadingSessionHistoryInfo>
CanonicalBrowsingContext::CreateLoadingSessionHistoryEntryForLoad(
nsDocShellLoadState* aLoadState, SessionHistoryEntry* existingEntry,
nsIChannel* aChannel) {
RefPtr<SessionHistoryEntry> entry;
const LoadingSessionHistoryInfo* existingLoadingInfo =
aLoadState->GetLoadingSessionHistoryInfo();
MOZ_ASSERT_IF(!existingLoadingInfo, !existingEntry);
if (existingLoadingInfo) {
if (existingEntry) {
entry = existingEntry;
}
else {
MOZ_ASSERT(!existingLoadingInfo->mLoadIsFromSessionHistory);
SessionHistoryEntry::LoadingEntry* loadingEntry =
SessionHistoryEntry::GetByLoadId(existingLoadingInfo->mLoadId);
MOZ_LOG(gSHLog, LogLevel::Verbose,
(
"SHEntry::GetByLoadId(%" PRIu64
") -> %p",
existingLoadingInfo->mLoadId, entry.get()));
if (!loadingEntry) {
return nullptr;
}
entry = loadingEntry->mEntry;
}
// If the entry was updated, update also the LoadingSessionHistoryInfo.
UniquePtr<LoadingSessionHistoryInfo> lshi =
MakeUnique<LoadingSessionHistoryInfo>(entry, existingLoadingInfo);
aLoadState->SetLoadingSessionHistoryInfo(std::move(lshi));
existingLoadingInfo = aLoadState->GetLoadingSessionHistoryInfo();
Unused << SetHistoryEntryCount(entry->BCHistoryLength());
}
else if (aLoadState->LoadType() == LOAD_REFRESH &&
!ShouldAddEntryForRefresh(aLoadState->URI(),
aLoadState->PostDataStream()) &&
mActiveEntry) {
entry = mActiveEntry;
}
else {
entry =
new SessionHistoryEntry(aLoadState, aChannel);
if (IsTop()) {
// Only top level pages care about Get/SetPersist.
entry->SetPersist(
nsDocShell::ShouldAddToSessionHistory(aLoadState->URI(), aChannel));
}
else if (mActiveEntry || !mLoadingEntries.IsEmpty()) {
entry->SetIsSubFrame(
true);
}
entry->SetDocshellID(GetHistoryID());
entry->SetIsDynamicallyAdded(CreatedDynamically());
entry->SetForInitialLoad(
true);
}
MOZ_DIAGNOSTIC_ASSERT(entry);
UniquePtr<LoadingSessionHistoryInfo> loadingInfo;
if (existingLoadingInfo) {
loadingInfo = MakeUnique<LoadingSessionHistoryInfo>(*existingLoadingInfo);
}
else {
loadingInfo = MakeUnique<LoadingSessionHistoryInfo>(entry);
mLoadingEntries.AppendElement(
LoadingSessionHistoryEntry{loadingInfo->mLoadId, entry});
}
MOZ_ASSERT(SessionHistoryEntry::GetByLoadId(loadingInfo->mLoadId)->mEntry ==
entry);
return loadingInfo;
}
UniquePtr<LoadingSessionHistoryInfo>
CanonicalBrowsingContext::ReplaceLoadingSessionHistoryEntryForLoad(
LoadingSessionHistoryInfo* aInfo, nsIChannel* aNewChannel) {
MOZ_ASSERT(aInfo);
MOZ_ASSERT(aNewChannel);
SessionHistoryInfo newInfo = SessionHistoryInfo(
aNewChannel, aInfo->mInfo.LoadType(),
aInfo->mInfo.GetPartitionedPrincipalToInherit(), aInfo->mInfo.GetCsp());
for (size_t i = 0; i < mLoadingEntries.Length(); ++i) {
if (mLoadingEntries[i].mLoadId == aInfo->mLoadId) {
RefPtr<SessionHistoryEntry> loadingEntry = mLoadingEntries[i].mEntry;
loadingEntry->SetInfo(&newInfo);
if (IsTop()) {
// Only top level pages care about Get/SetPersist.
nsCOMPtr<nsIURI> uri;
aNewChannel->GetURI(getter_AddRefs(uri));
loadingEntry->SetPersist(
nsDocShell::ShouldAddToSessionHistory(uri, aNewChannel));
}
else {
loadingEntry->SetIsSubFrame(aInfo->mInfo.IsSubFrame());
}
loadingEntry->SetDocshellID(GetHistoryID());
loadingEntry->SetIsDynamicallyAdded(CreatedDynamically());
return MakeUnique<LoadingSessionHistoryInfo>(loadingEntry, aInfo);
}
}
return nullptr;
}
using PrintPromise = CanonicalBrowsingContext::PrintPromise;
#ifdef NS_PRINTING
// Clients must call StaticCloneForPrintingCreated or
// NoStaticCloneForPrintingWillBeCreated before the underlying promise can
// resolve.
class PrintListenerAdapter final :
public nsIWebProgressListener {
public:
explicit PrintListenerAdapter(PrintPromise::
Private* aPromise)
: mPromise(aPromise) {}
NS_DECL_ISUPPORTS
// NS_DECL_NSIWEBPROGRESSLISTENER
NS_IMETHOD OnStateChange(nsIWebProgress* aWebProgress, nsIRequest* aRequest,
uint32_t aStateFlags, nsresult aStatus) override {
MOZ_ASSERT(NS_IsMainThread());
if (aStateFlags & nsIWebProgressListener::STATE_STOP &&
aStateFlags & nsIWebProgressListener::STATE_IS_DOCUMENT && mPromise) {
mPrintJobFinished =
true;
if (mHaveSetBrowsingContext) {
mPromise->Resolve(mClonedStaticBrowsingContext, __func__);
mPromise = nullptr;
}
}
return NS_OK;
}
NS_IMETHOD OnStatusChange(nsIWebProgress* aWebProgress, nsIRequest* aRequest,
nsresult aStatus,
const char16_t* aMessage) override {
if (aStatus != NS_OK && mPromise) {
mPromise->Reject(aStatus, __func__);
mPromise = nullptr;
}
return NS_OK;
}
NS_IMETHOD OnProgressChange(nsIWebProgress* aWebProgress,
nsIRequest* aRequest, int32_t aCurSelfProgress,
int32_t aMaxSelfProgress,
int32_t aCurTotalProgress,
int32_t aMaxTotalProgress) override {
return NS_OK;
}
NS_IMETHOD OnLocationChange(nsIWebProgress* aWebProgress,
nsIRequest* aRequest, nsIURI* aLocation,
uint32_t aFlags) override {
return NS_OK;
}
NS_IMETHOD OnSecurityChange(nsIWebProgress* aWebProgress,
nsIRequest* aRequest, uint32_t aState) override {
return NS_OK;
}
NS_IMETHOD OnContentBlockingEvent(nsIWebProgress* aWebProgress,
nsIRequest* aRequest,
uint32_t aEvent) override {
return NS_OK;
}
void StaticCloneForPrintingCreated(
MaybeDiscardedBrowsingContext&& aClonedStaticBrowsingContext) {
MOZ_ASSERT(NS_IsMainThread());
mClonedStaticBrowsingContext = std::move(aClonedStaticBrowsingContext);
mHaveSetBrowsingContext =
true;
if (mPrintJobFinished && mPromise) {
mPromise->Resolve(mClonedStaticBrowsingContext, __func__);
mPromise = nullptr;
}
}
void NoStaticCloneForPrintingWillBeCreated() {
StaticCloneForPrintingCreated(nullptr);
}
private:
~PrintListenerAdapter() =
default;
RefPtr<PrintPromise::
Private> mPromise;
MaybeDiscardedBrowsingContext mClonedStaticBrowsingContext = nullptr;
bool mHaveSetBrowsingContext =
false;
bool mPrintJobFinished =
false;
};
NS_IMPL_ISUPPORTS(PrintListenerAdapter, nsIWebProgressListener)
#endif
already_AddRefed<Promise> CanonicalBrowsingContext::PrintJS(
nsIPrintSettings* aPrintSettings, ErrorResult& aRv) {
RefPtr<Promise> promise = Promise::Create(GetIncumbentGlobal(), aRv);
if (NS_WARN_IF(aRv.Failed())) {
return promise.forget();
}
Print(aPrintSettings)
->Then(
GetCurrentSerialEventTarget(), __func__,
[promise](MaybeDiscardedBrowsingContext) {
promise->MaybeResolveWithUndefined();
},
[promise](nsresult aResult) { promise->MaybeReject(aResult); });
return promise.forget();
}
RefPtr<PrintPromise> CanonicalBrowsingContext::Print(
nsIPrintSettings* aPrintSettings) {
#ifndef NS_PRINTING
return PrintPromise::CreateAndReject(NS_ERROR_NOT_AVAILABLE, __func__);
#else
// Content analysis is not supported on non-Windows platforms.
# if defined(XP_WIN)
bool needContentAnalysis =
false;
nsCOMPtr<nsIContentAnalysis> contentAnalysis =
mozilla::components::nsIContentAnalysis::Service();
Unused << NS_WARN_IF(!contentAnalysis);
if (contentAnalysis) {
nsresult rv = contentAnalysis->GetIsActive(&needContentAnalysis);
Unused << NS_WARN_IF(NS_FAILED(rv));
}
if (needContentAnalysis) {
auto done = MakeRefPtr<PrintPromise::
Private>(__func__);
contentanalysis::ContentAnalysis::PrintToPDFToDetermineIfPrintAllowed(
this, aPrintSettings)
->Then(
GetCurrentSerialEventTarget(), __func__,
[done, aPrintSettings = RefPtr{aPrintSettings},
self = RefPtr{
this}](
contentanalysis::ContentAnalysis::PrintAllowedResult aResponse)
MOZ_CAN_RUN_SCRIPT_BOUNDARY_LAMBDA
mutable {
if (aResponse.mAllowed) {
self->PrintWithNoContentAnalysis(
aPrintSettings,
false,
aResponse.mCachedStaticDocumentBrowsingContext)
->ChainTo(done.forget(), __func__);
}
else {
// Since we are not doing the second print in this case,
// release the clone that is no longer needed.
self->ReleaseClonedPrint(
aResponse.mCachedStaticDocumentBrowsingContext);
done->Reject(NS_ERROR_CONTENT_BLOCKED, __func__);
}
},
[done, self = RefPtr{
this}](
contentanalysis::ContentAnalysis::PrintAllowedError
aErrorResponse) MOZ_CAN_RUN_SCRIPT_BOUNDARY_LAMBDA {
// Since we are not doing the second print in this case, release
// the clone that is no longer needed.
self->ReleaseClonedPrint(
aErrorResponse.mCachedStaticDocumentBrowsingContext);
done->Reject(aErrorResponse.mError, __func__);
});
return done;
}
# endif
return PrintWithNoContentAnalysis(aPrintSettings,
false, nullptr);
#endif
}
void CanonicalBrowsingContext::ReleaseClonedPrint(
const MaybeDiscardedBrowsingContext& aClonedStaticBrowsingContext) {
#ifdef NS_PRINTING
auto* browserParent = GetBrowserParent();
if (NS_WARN_IF(!browserParent)) {
return;
}
Unused << browserParent->SendDestroyPrintClone(aClonedStaticBrowsingContext);
#endif
}
RefPtr<PrintPromise> CanonicalBrowsingContext::PrintWithNoContentAnalysis(
nsIPrintSettings* aPrintSettings,
bool aForceStaticDocument,
const MaybeDiscardedBrowsingContext& aCachedStaticDocument) {
#ifndef NS_PRINTING
return PrintPromise::CreateAndReject(NS_ERROR_NOT_AVAILABLE, __func__);
#else
auto promise = MakeRefPtr<PrintPromise::
Private>(__func__);
auto listener = MakeRefPtr<PrintListenerAdapter>(promise);
if (IsInProcess()) {
RefPtr<nsGlobalWindowOuter> outerWindow =
nsGlobalWindowOuter::Cast(GetDOMWindow());
if (NS_WARN_IF(!outerWindow)) {
promise->Reject(NS_ERROR_FAILURE, __func__);
return promise;
}
ErrorResult rv;
listener->NoStaticCloneForPrintingWillBeCreated();
outerWindow->Print(aPrintSettings,
/* aRemotePrintJob = */ nullptr, listener,
/* aDocShellToCloneInto = */ nullptr,
nsGlobalWindowOuter::IsPreview::No,
nsGlobalWindowOuter::IsForWindowDotPrint::No,
/* aPrintPreviewCallback = */ nullptr,
/* aCachedBrowsingContext = */ nullptr, rv);
if (rv.Failed()) {
promise->Reject(rv.StealNSResult(), __func__);
}
return promise;
}
auto* browserParent = GetBrowserParent();
if (NS_WARN_IF(!browserParent)) {
promise->Reject(NS_ERROR_FAILURE, __func__);
return promise;
}
nsCOMPtr<nsIPrintSettingsService> printSettingsSvc =
do_GetService(
"@mozilla.org/gfx/printsettings-service;1");
if (NS_WARN_IF(!printSettingsSvc)) {
promise->Reject(NS_ERROR_FAILURE, __func__);
return promise;
}
nsresult rv;
nsCOMPtr<nsIPrintSettings> printSettings = aPrintSettings;
if (!printSettings) {
rv =
printSettingsSvc->CreateNewPrintSettings(getter_AddRefs(printSettings));
if (NS_WARN_IF(NS_FAILED(rv))) {
promise->Reject(rv, __func__);
return promise;
}
}
embedding::PrintData printData;
rv = printSettingsSvc->SerializeToPrintData(printSettings, &printData);
if (NS_WARN_IF(NS_FAILED(rv))) {
promise->Reject(rv, __func__);
return promise;
}
layout::RemotePrintJobParent* remotePrintJob =
new layout::RemotePrintJobParent(printSettings);
printData.remotePrintJob() =
browserParent->Manager()->SendPRemotePrintJobConstructor(remotePrintJob);
remotePrintJob->RegisterListener(listener);
if (!aCachedStaticDocument.IsNullOrDiscarded()) {
// There is no cloned static browsing context that
// SendPrintClonedPage() will return, so indicate this
// so listener can resolve its promise.
listener->NoStaticCloneForPrintingWillBeCreated();
if (NS_WARN_IF(!browserParent->SendPrintClonedPage(
this, printData, aCachedStaticDocument))) {
promise->Reject(NS_ERROR_FAILURE, __func__);
}
}
else {
RefPtr<PBrowserParent::PrintPromise> printPromise =
browserParent->SendPrint(
this, printData, aForceStaticDocument);
printPromise->Then(
GetMainThreadSerialEventTarget(), __func__,
[listener](MaybeDiscardedBrowsingContext cachedStaticDocument) {
// promise will get resolved by the listener
listener->StaticCloneForPrintingCreated(
std::move(cachedStaticDocument));
},
[promise](ResponseRejectReason reason) {
NS_WARNING(
"SendPrint() failed");
promise->Reject(NS_ERROR_FAILURE, __func__);
});
}
return promise.forget();
#endif
}
void CanonicalBrowsingContext::CallOnAllTopDescendants(
const FunctionRef<CallState(CanonicalBrowsingContext*)>& aCallback,
bool aIncludeNestedBrowsers) {
MOZ_ASSERT(IsTop(),
"Should only call on top BC");
MOZ_ASSERT(
!aIncludeNestedBrowsers ||
(IsChrome() && !GetParentCrossChromeBoundary()),
"If aIncludeNestedBrowsers is set, should only call on top chrome BC");
if (!IsInProcess()) {
// We rely on top levels having to be embedded in the parent process, so
// we can only have top level descendants if embedded here..
return;
}
AutoTArray<RefPtr<BrowsingContextGroup>, 32> groups;
BrowsingContextGroup::GetAllGroups(groups);
for (
auto& browsingContextGroup : groups) {
for (
auto& bc : browsingContextGroup->Toplevels()) {
if (bc ==
this) {
// Cannot be a descendent of myself so skip.
continue;
}
if (aIncludeNestedBrowsers) {
if (
this != bc->Canonical()->TopCrossChromeBoundary()) {
continue;
}
}
else {
auto* parent = bc->Canonical()->GetParentCrossChromeBoundary();
if (!parent ||
this != parent->Top()) {
continue;
}
}
if (aCallback(bc->Canonical()) == CallState::Stop) {
return;
}
}
}
}
void CanonicalBrowsingContext::SessionHistoryCommit(
uint64_t aLoadId,
const nsID& aChangeID, uint32_t aLoadType,
bool aPersist,
bool aCloneEntryChildren,
bool aChannelExpired, uint32_t aCacheKey) {
MOZ_LOG(gSHLog, LogLevel::Verbose,
(
"CanonicalBrowsingContext::SessionHistoryCommit %p %" PRIu64,
this,
aLoadId));
MOZ_ASSERT(aLoadId != UINT64_MAX,
"Must not send special about:blank loadinfo to parent.");
for (size_t i = 0; i < mLoadingEntries.Length(); ++i) {
if (mLoadingEntries[i].mLoadId == aLoadId) {
nsSHistory* shistory =
static_cast<nsSHistory*>(GetSessionHistory());
if (!shistory) {
SessionHistoryEntry::RemoveLoadId(aLoadId);
mLoadingEntries.RemoveElementAt(i);
return;
}
RefPtr<SessionHistoryEntry> newActiveEntry = mLoadingEntries[i].mEntry;
if (aCacheKey != 0) {
newActiveEntry->SetCacheKey(aCacheKey);
}
if (aChannelExpired) {
newActiveEntry->SharedInfo()->mExpired =
true;
}
bool loadFromSessionHistory = !newActiveEntry->ForInitialLoad();
newActiveEntry->SetForInitialLoad(
false);
SessionHistoryEntry::RemoveLoadId(aLoadId);
mLoadingEntries.RemoveElementAt(i);
int32_t indexOfHistoryLoad = -1;
if (loadFromSessionHistory) {
nsCOMPtr<nsISHEntry> root = nsSHistory::GetRootSHEntry(newActiveEntry);
indexOfHistoryLoad = shistory->GetIndexOfEntry(root);
if (indexOfHistoryLoad < 0) {
// Entry has been removed from the session history.
return;
}
}
CallerWillNotifyHistoryIndexAndLengthChanges caller(shistory);
// If there is a name in the new entry, clear the name of all contiguous
// entries. This is for https://html.spec.whatwg.org/#history-traversal
// Step 4.4.2.
nsAutoString nameOfNewEntry;
newActiveEntry->GetName(nameOfNewEntry);
if (!nameOfNewEntry.IsEmpty()) {
nsSHistory::WalkContiguousEntries(
newActiveEntry,
[](nsISHEntry* aEntry) { aEntry->SetName(EmptyString()); });
}
bool addEntry = ShouldUpdateSessionHistory(aLoadType);
if (IsTop()) {
if (mActiveEntry && !mActiveEntry->GetFrameLoader()) {
bool sharesDocument =
true;
mActiveEntry->SharesDocumentWith(newActiveEntry, &sharesDocument);
if (!sharesDocument) {
// If the old page won't be in the bfcache,
// clear the dynamic entries.
RemoveDynEntriesFromActiveSessionHistoryEntry();
}
}
if (LOAD_TYPE_HAS_FLAGS(aLoadType,
nsIWebNavigation::LOAD_FLAGS_REPLACE_HISTORY)) {
// Replace the current entry with the new entry.
int32_t index = shistory->GetIndexForReplace();
// If we're trying to replace an inexistant shistory entry then we
// should append instead.
addEntry = index < 0;
if (!addEntry) {
shistory->ReplaceEntry(index, newActiveEntry);
}
mActiveEntry = newActiveEntry;
}
else if (LOAD_TYPE_HAS_FLAGS(
aLoadType, nsIWebNavigation::LOAD_FLAGS_IS_REFRESH) &&
!ShouldAddEntryForRefresh(newActiveEntry) && mActiveEntry) {
addEntry =
false;
mActiveEntry->ReplaceWith(*newActiveEntry);
}
else {
mActiveEntry = newActiveEntry;
}
if (loadFromSessionHistory) {
// XXX Synchronize browsing context tree and session history tree?
shistory->InternalSetRequestedIndex(indexOfHistoryLoad);
shistory->UpdateIndex();
if (IsTop()) {
mActiveEntry->SetWireframe(Nothing());
}
}
else if (addEntry) {
shistory->AddEntry(mActiveEntry, aPersist);
shistory->InternalSetRequestedIndex(-1);
}
}
else {
// FIXME The old implementations adds it to the parent's mLSHE if there
// is one, need to figure out if that makes sense here (peterv
// doesn't think it would).
if (loadFromSessionHistory) {
if (mActiveEntry) {
// mActiveEntry is null if we're loading iframes from session
// history while also parent page is loading from session history.
// In that case there isn't anything to sync.
mActiveEntry->SyncTreesForSubframeNavigation(newActiveEntry, Top(),
this);
}
mActiveEntry = newActiveEntry;
shistory->InternalSetRequestedIndex(indexOfHistoryLoad);
// FIXME UpdateIndex() here may update index too early (but even the
// old implementation seems to have similar issues).
shistory->UpdateIndex();
}
else if (addEntry) {
if (mActiveEntry) {
if (LOAD_TYPE_HAS_FLAGS(
aLoadType, nsIWebNavigation::LOAD_FLAGS_REPLACE_HISTORY) ||
(LOAD_TYPE_HAS_FLAGS(aLoadType,
nsIWebNavigation::LOAD_FLAGS_IS_REFRESH) &&
!ShouldAddEntryForRefresh(newActiveEntry))) {
// FIXME We need to make sure that when we create the info we
// make a copy of the shared state.
mActiveEntry->ReplaceWith(*newActiveEntry);
}
else {
// AddChildSHEntryHelper does update the index of the session
// history!
shistory->AddChildSHEntryHelper(mActiveEntry, newActiveEntry,
Top(), aCloneEntryChildren);
mActiveEntry = newActiveEntry;
}
}
else {
SessionHistoryEntry* parentEntry = GetParent()->mActiveEntry;
// XXX What should happen if parent doesn't have mActiveEntry?
// Or can that even happen ever?
if (parentEntry) {
mActiveEntry = newActiveEntry;
// FIXME Using IsInProcess for aUseRemoteSubframes isn't quite
// right, but aUseRemoteSubframes should be going away.
parentEntry->AddChild(
mActiveEntry,
CreatedDynamically() ? -1 : GetParent()->IndexOf(
this),
IsInProcess());
}
}
shistory->InternalSetRequestedIndex(-1);
}
}
ResetSHEntryHasUserInteractionCache();
HistoryCommitIndexAndLength(aChangeID, caller);
shistory->LogHistory();
return;
}
// XXX Should the loading entries before [i] be removed?
}
// FIXME Should we throw an error if we don't find an entry for
// aSessionHistoryEntryId?
}
already_AddRefed<nsDocShellLoadState> CanonicalBrowsingContext::CreateLoadInfo(
SessionHistoryEntry* aEntry) {
const SessionHistoryInfo& info = aEntry->Info();
RefPtr<nsDocShellLoadState> loadState(
new nsDocShellLoadState(info.GetURI()));
info.FillLoadInfo(*loadState);
UniquePtr<LoadingSessionHistoryInfo> loadingInfo;
loadingInfo = MakeUnique<LoadingSessionHistoryInfo>(aEntry);
mLoadingEntries.AppendElement(
LoadingSessionHistoryEntry{loadingInfo->mLoadId, aEntry});
loadState->SetLoadingSessionHistoryInfo(std::move(loadingInfo));
return loadState.forget();
}
void CanonicalBrowsingContext::NotifyOnHistoryReload(
bool aForceReload,
bool& aCanReload,
Maybe<NotNull<RefPtr<nsDocShellLoadState>>>& aLoadState,
Maybe<
bool>& aReloadActiveEntry) {
MOZ_DIAGNOSTIC_ASSERT(!aLoadState);
aCanReload =
true;
nsISHistory* shistory = GetSessionHistory();
NS_ENSURE_TRUE_VOID(shistory);
shistory->NotifyOnHistoryReload(&aCanReload);
if (!aCanReload) {
return;
}
if (mActiveEntry) {
aLoadState.emplace(WrapMovingNotNull(RefPtr{CreateLoadInfo(mActiveEntry)}));
aReloadActiveEntry.emplace(
true);
if (aForceReload) {
shistory->RemoveFrameEntries(mActiveEntry);
}
}
else if (!mLoadingEntries.IsEmpty()) {
const LoadingSessionHistoryEntry& loadingEntry =
mLoadingEntries.LastElement();
uint64_t loadId = loadingEntry.mLoadId;
aLoadState.emplace(
WrapMovingNotNull(RefPtr{CreateLoadInfo(loadingEntry.mEntry)}));
aReloadActiveEntry.emplace(
false);
if (aForceReload) {
SessionHistoryEntry::LoadingEntry* entry =
SessionHistoryEntry::GetByLoadId(loadId);
if (entry) {
shistory->RemoveFrameEntries(entry->mEntry);
}
}
}
if (aLoadState) {
// Use 0 as the offset, since aLoadState will be be used for reload.
aLoadState.ref()->SetLoadIsFromSessionHistory(0,
aReloadActiveEntry.value());
}
// If we don't have an active entry and we don't have a loading entry then
// the nsDocShell will create a load state based on its document.
}
void CanonicalBrowsingContext::SetActiveSessionHistoryEntry(
const Maybe<nsPoint>& aPreviousScrollPos, SessionHistoryInfo* aInfo,
uint32_t aLoadType, uint32_t aUpdatedCacheKey,
const nsID& aChangeID) {
nsISHistory* shistory = GetSessionHistory();
if (!shistory) {
return;
}
CallerWillNotifyHistoryIndexAndLengthChanges caller(shistory);
RefPtr<SessionHistoryEntry> oldActiveEntry = mActiveEntry;
if (aPreviousScrollPos.isSome() && oldActiveEntry) {
oldActiveEntry->SetScrollPosition(aPreviousScrollPos.ref().x,
aPreviousScrollPos.ref().y);
}
mActiveEntry =
new SessionHistoryEntry(aInfo);
mActiveEntry->SetDocshellID(GetHistoryID());
mActiveEntry->AdoptBFCacheEntry(oldActiveEntry);
if (aUpdatedCacheKey != 0) {
mActiveEntry->SharedInfo()->mCacheKey = aUpdatedCacheKey;
}
if (IsTop()) {
Maybe<int32_t> previousEntryIndex, loadedEntryIndex;
shistory->AddToRootSessionHistory(
true, oldActiveEntry,
this, mActiveEntry, aLoadType,
nsDocShell::ShouldAddToSessionHistory(aInfo->GetURI(), nullptr),
&previousEntryIndex, &loadedEntryIndex);
}
else {
if (oldActiveEntry) {
shistory->AddChildSHEntryHelper(oldActiveEntry, mActiveEntry, Top(),
true);
}
else if (GetParent() && GetParent()->mActiveEntry) {
GetParent()->mActiveEntry->AddChild(
mActiveEntry, CreatedDynamically() ? -1 : GetParent()->IndexOf(
this),
UseRemoteSubframes());
}
}
ResetSHEntryHasUserInteractionCache();
shistory->InternalSetRequestedIndex(-1);
// FIXME Need to do the equivalent of EvictDocumentViewersOrReplaceEntry.
HistoryCommitIndexAndLength(aChangeID, caller);
static_cast<nsSHistory*>(shistory)->LogHistory();
}
void CanonicalBrowsingContext::ReplaceActiveSessionHistoryEntry(
SessionHistoryInfo* aInfo) {
if (!mActiveEntry) {
return;
}
// aInfo comes from the entry stored in the current document's docshell, whose
// interaction state does not get updated. So we instead propagate state from
// the previous canonical entry. See bug 1917369.
const bool hasUserInteraction = mActiveEntry->GetHasUserInteraction();
mActiveEntry->SetInfo(aInfo);
mActiveEntry->SetHasUserInteraction(hasUserInteraction);
// Notify children of the update
nsSHistory* shistory =
static_cast<nsSHistory*>(GetSessionHistory());
if (shistory) {
shistory->NotifyOnHistoryReplaceEntry();
shistory->UpdateRootBrowsingContextState();
}
ResetSHEntryHasUserInteractionCache();
if (IsTop()) {
mActiveEntry->SetWireframe(Nothing());
}
// FIXME Need to do the equivalent of EvictDocumentViewersOrReplaceEntry.
}
void CanonicalBrowsingContext::RemoveDynEntriesFromActiveSessionHistoryEntry() {
nsISHistory* shistory = GetSessionHistory();
// In theory shistory can be null here if the method is called right after
// CanonicalBrowsingContext::ReplacedBy call.
NS_ENSURE_TRUE_VOID(shistory);
nsCOMPtr<nsISHEntry> root = nsSHistory::GetRootSHEntry(mActiveEntry);
shistory->RemoveDynEntries(shistory->GetIndexOfEntry(root), mActiveEntry);
}
void CanonicalBrowsingContext::RemoveFromSessionHistory(
const nsID& aChangeID) {
nsSHistory* shistory =
static_cast<nsSHistory*>(GetSessionHistory());
if (shistory) {
CallerWillNotifyHistoryIndexAndLengthChanges caller(shistory);
nsCOMPtr<nsISHEntry> root = nsSHistory::GetRootSHEntry(mActiveEntry);
bool didRemove;
AutoTArray<nsID, 16> ids({GetHistoryID()});
shistory->RemoveEntries(ids, shistory->GetIndexOfEntry(root), &didRemove);
if (didRemove) {
RefPtr<BrowsingContext> rootBC = shistory->GetBrowsingContext();
if (rootBC) {
if (!rootBC->IsInProcess()) {
if (ContentParent* cp = rootBC->Canonical()->GetContentParent()) {
Unused << cp->SendDispatchLocationChangeEvent(rootBC);
}
}
else if (rootBC->GetDocShell()) {
rootBC->GetDocShell()->DispatchLocationChangeEvent();
}
}
}
HistoryCommitIndexAndLength(aChangeID, caller);
}
}
Maybe<int32_t> CanonicalBrowsingContext::HistoryGo(
int32_t aOffset, uint64_t aHistoryEpoch,
bool aRequireUserInteraction,
bool aUserActivation, Maybe<ContentParentId> aContentId) {
if (aRequireUserInteraction && aOffset != -1 && aOffset != 1) {
NS_ERROR(
"aRequireUserInteraction may only be used with an offset of -1 or 1");
return Nothing();
}
nsSHistory* shistory =
static_cast<nsSHistory*>(GetSessionHistory());
if (!shistory) {
return Nothing();
}
CheckedInt<int32_t> index = shistory->GetRequestedIndex() >= 0
? shistory->GetRequestedIndex()
: shistory->Index();
MOZ_LOG(gSHLog, LogLevel::Debug,
(
"HistoryGo(%d->%d) epoch %" PRIu64
"/id %" PRIu64, aOffset,
(index + aOffset).value(), aHistoryEpoch,
(uint64_t)(aContentId.isSome() ? aContentId.value() : 0)));
while (
true) {
index += aOffset;
if (!index.isValid()) {
MOZ_LOG(gSHLog, LogLevel::Debug, (
"Invalid index"));
return Nothing();
}
// Check for user interaction if desired, except for the first and last
// history entries. We compare with >= to account for the case where
// aOffset >= length.
if (!StaticPrefs::browser_navigation_requireUserInteraction() ||
!aRequireUserInteraction || index.value() >= shistory->Length() - 1 ||
index.value() <= 0) {
break;
}
if (shistory->HasUserInteractionAtIndex(index.value())) {
break;
}
}
// Implement aborting additional history navigations from within the same
// event spin of the content process.
uint64_t epoch;
bool sameEpoch =
false;
Maybe<ContentParentId> id;
shistory->GetEpoch(epoch, id);
if (aContentId == id && epoch >= aHistoryEpoch) {
sameEpoch =
true;
MOZ_LOG(gSHLog, LogLevel::Debug, (
"Same epoch/id"));
}
// Don't update the epoch until we know if the target index is valid
// GoToIndex checks that index is >= 0 and < length.
nsTArray<nsSHistory::LoadEntryResult> loadResults;
nsresult rv = shistory->GotoIndex(index.value(), loadResults, sameEpoch,
aOffset == 0, aUserActivation);
if (NS_FAILED(rv)) {
MOZ_LOG(gSHLog, LogLevel::Debug,
(
"Dropping HistoryGo - bad index or same epoch (not in same doc)"));
return Nothing();
}
if (epoch < aHistoryEpoch || aContentId != id) {
MOZ_LOG(gSHLog, LogLevel::Debug, (
"Set epoch"));
shistory->SetEpoch(aHistoryEpoch, aContentId);
}
int32_t requestedIndex = shistory->GetRequestedIndex();
nsSHistory::LoadURIs(loadResults);
return Some(requestedIndex);
}
JSObject* CanonicalBrowsingContext::WrapObject(
JSContext* aCx, JS::Handle<JSObject*> aGivenProto) {
return CanonicalBrowsingContext_Binding::Wrap(aCx,
this, aGivenProto);
}
void CanonicalBrowsingContext::DispatchWheelZoomChange(
bool aIncrease) {
Element* element = Top()->GetEmbedderElement();
if (!element) {
return;
}
auto event = aIncrease ? u
"DoZoomEnlargeBy10"_ns : u
"DoZoomReduceBy10"_ns;
auto dispatcher = MakeRefPtr<AsyncEventDispatcher>(
element, event, CanBubble::eYes, ChromeOnlyDispatch::eYes);
dispatcher->PostDOMEvent();
}
void CanonicalBrowsingContext::CanonicalDiscard() {
if (mTabMediaController) {
mTabMediaController->Shutdown();
mTabMediaController = nullptr;
}
if (mCurrentLoad) {
mCurrentLoad->Cancel(NS_BINDING_ABORTED,
"CanonicalBrowsingContext::CanonicalDiscard"_ns);
}
if (mWebProgress) {
RefPtr<BrowsingContextWebProgress> progress = mWebProgress;
progress->ContextDiscarded();
}
if (IsTop()) {
BackgroundSessionStorageManager::RemoveManager(Id());
}
CancelSessionStoreUpdate();
if (UsePrivateBrowsing() && EverAttached() && IsContent()) {
DecreasePrivateCount();
}
}
void CanonicalBrowsingContext::CanonicalAttach() {
if (UsePrivateBrowsing() && IsContent()) {
IncreasePrivateCount();
}
}
void CanonicalBrowsingContext::AddPendingDiscard() {
MOZ_ASSERT(!mFullyDiscarded);
mPendingDiscards++;
}
void CanonicalBrowsingContext::RemovePendingDiscard() {
mPendingDiscards--;
if (!mPendingDiscards) {
mFullyDiscarded =
true;
auto listeners = std::move(mFullyDiscardedListeners);
for (
const auto& listener : listeners) {
listener(Id());
}
}
}
void CanonicalBrowsingContext::AddFinalDiscardListener(
std::function<
void(uint64_t)>&& aListener) {
if (mFullyDiscarded) {
aListener(Id());
return;
}
mFullyDiscardedListeners.AppendElement(std::move(aListener));
}
void CanonicalBrowsingContext::SetForceAppWindowActive(
bool aForceActive,
ErrorResult& aRv) {
MOZ_DIAGNOSTIC_ASSERT(IsChrome());
MOZ_DIAGNOSTIC_ASSERT(IsTop());
if (!IsChrome() || !IsTop()) {
return aRv.ThrowNotAllowedError(
"You shouldn't need to force this BrowsingContext to be active, use "
".isActive instead");
}
if (mForceAppWindowActive == aForceActive) {
return;
}
mForceAppWindowActive = aForceActive;
RecomputeAppWindowVisibility();
}
void CanonicalBrowsingContext::RecomputeAppWindowVisibility() {
MOZ_RELEASE_ASSERT(IsChrome());
MOZ_RELEASE_ASSERT(IsTop());
const bool wasAlreadyActive = IsActive();
nsCOMPtr<nsIWidget> widget;
if (
auto* docShell = GetDocShell()) {
nsDocShell::Cast(docShell)->GetMainWidget(getter_AddRefs(widget));
}
Unused << NS_WARN_IF(!widget);
const bool isNowActive =
ForceAppWindowActive() || (widget && !widget->IsFullyOccluded() &&
widget->SizeMode() != nsSizeMode_Minimized);
if (isNowActive == wasAlreadyActive) {
return;
}
SetIsActiveInternal(isNowActive, IgnoreErrors());
if (widget) {
// Pause if we are not active, resume if we are active.
widget->PauseOrResumeCompositor(!isNowActive);
}
}
void CanonicalBrowsingContext::AdjustPrivateBrowsingCount(
bool aPrivateBrowsing) {
if (IsDiscarded() || !EverAttached() || IsChrome()) {
return;
}
MOZ_DIAGNOSTIC_ASSERT(aPrivateBrowsing == UsePrivateBrowsing());
if (aPrivateBrowsing) {
IncreasePrivateCount();
}
else {
DecreasePrivateCount();
}
}
void CanonicalBrowsingContext::NotifyStartDelayedAutoplayMedia() {
WindowContext* windowContext = GetCurrentWindowContext();
if (!windowContext) {
return;
}
// As this function would only be called when user click the play icon on the
// tab bar. That's clear user intent to play, so gesture activate the window
// context so that the block-autoplay logic allows the media to autoplay.
windowContext->NotifyUserGestureActivation();
AUTOPLAY_LOG(
"NotifyStartDelayedAutoplayMedia for chrome bc 0x%08" PRIx64,
Id());
StartDelayedAutoplayMediaComponents();
// Notfiy all content browsing contexts which are related with the canonical
// browsing content tree to start delayed autoplay media.
Group()->EachParent([&](ContentParent* aParent) {
Unused << aParent->SendStartDelayedAutoplayMediaComponents(
this);
});
}
void CanonicalBrowsingContext::NotifyMediaMutedChanged(
bool aMuted,
ErrorResult& aRv) {
MOZ_ASSERT(!GetParent(),
"Notify media mute change on non top-level context!");
SetMuted(aMuted, aRv);
}
uint32_t CanonicalBrowsingContext::CountSiteOrigins(
GlobalObject& aGlobal,
const Sequence<OwningNonNull<BrowsingContext>>& aRoots) {
nsTHashSet<nsCString> uniqueSiteOrigins;
for (
const auto& root : aRoots) {
root->PreOrderWalk([&](BrowsingContext* aContext) {
WindowGlobalParent* windowGlobalParent =
aContext->Canonical()->GetCurrentWindowGlobal();
if (windowGlobalParent) {
nsIPrincipal* documentPrincipal =
windowGlobalParent->DocumentPrincipal();
bool isContentPrincipal = documentPrincipal->GetIsContentPrincipal();
if (isContentPrincipal) {
nsCString siteOrigin;
documentPrincipal->GetSiteOrigin(siteOrigin);
uniqueSiteOrigins.Insert(siteOrigin);
}
}
});
}
return uniqueSiteOrigins.Count();
}
/* static */
bool CanonicalBrowsingContext::IsPrivateBrowsingActive() {
return gNumberOfPrivateContexts > 0;
}
void CanonicalBrowsingContext::UpdateMediaControlAction(
const MediaControlAction& aAction) {
if (IsDiscarded()) {
return;
}
ContentMediaControlKeyHandler::HandleMediaControlAction(
this, aAction);
Group()->EachParent([&](ContentParent* aParent) {
Unused << aParent->SendUpdateMediaControlAction(
this, aAction);
});
}
void CanonicalBrowsingContext::LoadURI(nsIURI* aURI,
const LoadURIOptions& aOptions,
ErrorResult& aError) {
RefPtr<nsDocShellLoadState> loadState;
nsresult rv = nsDocShellLoadState::CreateFromLoadURIOptions(
this, aURI, aOptions, getter_AddRefs(loadState));
MOZ_ASSERT(rv != NS_ERROR_MALFORMED_URI);
if (NS_FAILED(rv)) {
aError.
Throw(rv);
return;
}
LoadURI(loadState,
true);
}
void CanonicalBrowsingContext::FixupAndLoadURIString(
const nsAString& aURI,
const LoadURIOptions& aOptions,
ErrorResult& aError) {
RefPtr<nsDocShellLoadState> loadState;
nsresult rv = nsDocShellLoadState::CreateFromLoadURIOptions(
this, aURI, aOptions, getter_AddRefs(loadState));
if (rv == NS_ERROR_MALFORMED_URI) {
DisplayLoadError(aURI);
return;
}
if (NS_FAILED(rv)) {
aError.
Throw(rv);
return;
}
LoadURI(loadState,
true);
}
void CanonicalBrowsingContext::GoBack(
const Optional<int32_t>& aCancelContentJSEpoch,
bool aRequireUserInteraction,
bool aUserActivation) {
if (IsDiscarded()) {
return;
}
// Stop any known network loads if necessary.
if (mCurrentLoad) {
mCurrentLoad->Cancel(NS_BINDING_CANCELLED_OLD_LOAD,
""_ns);
}
if (RefPtr<nsDocShell> docShell = nsDocShell::Cast(GetDocShell())) {
if (aCancelContentJSEpoch.WasPassed()) {
docShell->SetCancelContentJSEpoch(aCancelContentJSEpoch.Value());
}
docShell->GoBack(aRequireUserInteraction, aUserActivation);
}
else if (ContentParent* cp = GetContentParent()) {
Maybe<int32_t> cancelContentJSEpoch;
if (aCancelContentJSEpoch.WasPassed()) {
cancelContentJSEpoch = Some(aCancelContentJSEpoch.Value());
}
Unused << cp->SendGoBack(
this, cancelContentJSEpoch,
aRequireUserInteraction, aUserActivation);
}
}
void CanonicalBrowsingContext::GoForward(
const Optional<int32_t>& aCancelContentJSEpoch,
bool aRequireUserInteraction,
bool aUserActivation) {
if (IsDiscarded()) {
return;
}
// Stop any known network loads if necessary.
if (mCurrentLoad) {
mCurrentLoad->Cancel(NS_BINDING_CANCELLED_OLD_LOAD,
""_ns);
}
if (RefPtr<nsDocShell> docShell = nsDocShell::Cast(GetDocShell())) {
if (aCancelContentJSEpoch.WasPassed()) {
docShell->SetCancelContentJSEpoch(aCancelContentJSEpoch.Value());
}
docShell->GoForward(aRequireUserInteraction, aUserActivation);
}
else if (ContentParent* cp = GetContentParent()) {
Maybe<int32_t> cancelContentJSEpoch;
if (aCancelContentJSEpoch.WasPassed()) {
cancelContentJSEpoch.emplace(aCancelContentJSEpoch.Value());
}
Unused << cp->SendGoForward(
this, cancelContentJSEpoch,
aRequireUserInteraction, aUserActivation);
}
}
void CanonicalBrowsingContext::GoToIndex(
int32_t aIndex,
const Optional<int32_t>& aCancelContentJSEpoch,
bool aUserActivation) {
if (IsDiscarded()) {
return;
}
// Stop any known network loads if necessary.
if (mCurrentLoad) {
mCurrentLoad->Cancel(NS_BINDING_CANCELLED_OLD_LOAD,
""_ns);
}
if (RefPtr<nsDocShell> docShell = nsDocShell::Cast(GetDocShell())) {
if (aCancelContentJSEpoch.WasPassed()) {
docShell->SetCancelContentJSEpoch(aCancelContentJSEpoch.Value());
}
docShell->GotoIndex(aIndex, aUserActivation);
}
else if (ContentParent* cp = GetContentParent()) {
Maybe<int32_t> cancelContentJSEpoch;
if (aCancelContentJSEpoch.WasPassed()) {
cancelContentJSEpoch.emplace(aCancelContentJSEpoch.Value());
}
Unused << cp->SendGoToIndex(
this, aIndex, cancelContentJSEpoch,
aUserActivation);
}
}
void CanonicalBrowsingContext::Reload(uint32_t aReloadFlags) {
if (IsDiscarded()) {
return;
}
// Stop any known network loads if necessary.
if (mCurrentLoad) {
mCurrentLoad->Cancel(NS_BINDING_CANCELLED_OLD_LOAD,
""_ns);
}
if (RefPtr<nsDocShell> docShell = nsDocShell::Cast(GetDocShell())) {
docShell->Reload(aReloadFlags);
}
else if (ContentParent* cp = GetContentParent()) {
Unused << cp->SendReload(
this, aReloadFlags);
}
}
void CanonicalBrowsingContext::Stop(uint32_t aStopFlags) {
if (IsDiscarded()) {
return;
}
// Stop any known network loads if necessary.
if (mCurrentLoad && (aStopFlags & nsIWebNavigation::STOP_NETWORK)) {
mCurrentLoad->Cancel(NS_BINDING_ABORTED,
"CanonicalBrowsingContext::Stop"_ns);
}
// Ask the docshell to stop to handle loads that haven't
// yet reached here, as well as non-network activity.
if (
auto* docShell = nsDocShell::Cast(GetDocShell())) {
docShell->Stop(aStopFlags);
}
else if (ContentParent* cp = GetContentParent()) {
Unused << cp->SendStopLoad(
this, aStopFlags);
}
}
void CanonicalBrowsingContext::PendingRemotenessChange::ProcessLaunched() {
if (!mPromise) {
return;
}
if (mContentParentKeepAlive) {
// If our new content process is still unloading from a previous process
// switch, wait for that unload to complete before continuing.
auto found = mTarget->FindUnloadingHost(mContentParentKeepAlive->ChildID());
if (found != mTarget->mUnloadingHosts.end()) {
found->mCallbacks.AppendElement(
[self = RefPtr{
this}]()
MOZ_CAN_RUN_SCRIPT_BOUNDARY_LAMBDA { self->ProcessReady(); });
return;
}
}
ProcessReady();
}
void CanonicalBrowsingContext::PendingRemotenessChange::ProcessReady() {
if (!mPromise) {
return;
}
MOZ_ASSERT(!mProcessReady);
mProcessReady =
true;
MaybeFinish();
}
void CanonicalBrowsingContext::PendingRemotenessChange::MaybeFinish() {
if (!mPromise) {
return;
}
if (!mProcessReady || mWaitingForPrepareToChange) {
return;
}
// If this BrowsingContext is embedded within the parent process, perform the
// process switch directly.
nsresult rv = mTarget->IsTopContent() ? FinishTopContent() : FinishSubframe();
if (NS_FAILED(rv)) {
NS_WARNING(
"Error finishing PendingRemotenessChange!");
Cancel(rv);
}
else {
Clear();
}
}
// Logic for finishing a toplevel process change embedded within the parent
// process. Due to frontend integration the logic differs substantially from
// subframe process switches, and is handled separately.
nsresult CanonicalBrowsingContext::PendingRemotenessChange::FinishTopContent() {
MOZ_DIAGNOSTIC_ASSERT(mTarget->IsTop(),
"We shouldn't be trying to change the remoteness of "
"non-remote iframes");
// Abort if our ContentParent died while process switching.
if (mContentParentKeepAlive &&
NS_WARN_IF(mContentParentKeepAlive->IsShuttingDown())) {
return NS_ERROR_FAILURE;
}
// While process switching, we need to check if any of our ancestors are
// discarded or no longer current, in which case the process switch needs to
// be aborted.
RefPtr<CanonicalBrowsingContext> target(mTarget);
if (target->IsDiscarded() || !target->AncestorsAreCurrent()) {
return NS_ERROR_FAILURE;
}
Element* browserElement = target->GetEmbedderElement();
if (!browserElement) {
return NS_ERROR_FAILURE;
}
nsCOMPtr<nsIBrowser> browser = browserElement->AsBrowser();
if (!browser) {
return NS_ERROR_FAILURE;
}
RefPtr<nsFrameLoaderOwner> frameLoaderOwner = do_QueryObject(browserElement);
MOZ_RELEASE_ASSERT(frameLoaderOwner,
"embedder browser must be nsFrameLoaderOwner");
// If we're process switching a browsing context in private browsing
// mode we might decrease the private browsing count to '0', which
// would make us fire "last-pb-context-exited" and drop the private
// session. To prevent that we artificially increment the number of
// private browsing contexts with '1' until the process switch is done.
bool usePrivateBrowsing = mTarget->UsePrivateBrowsing();
if (usePrivateBrowsing) {
IncreasePrivateCount();
}
auto restorePrivateCount = MakeScopeExit([usePrivateBrowsing]() {
if (usePrivateBrowsing) {
DecreasePrivateCount();
}
});
// Tell frontend code that this browser element is about to change process.
nsresult rv = browser->BeforeChangeRemoteness();
if (NS_FAILED(rv)) {
return rv;
}
// Some frontend code checks the value of the `remote` attribute on the
// browser to determine if it is remote, so update the value.
browserElement->SetAttr(kNameSpaceID_None, nsGkAtoms::remote,
mContentParentKeepAlive ? u
"true"_ns : u
"false"_ns,
/* notify */ true);
// The process has been created, hand off to nsFrameLoaderOwner to finish
--> --------------------
--> maximum size reached
--> --------------------