зеркало из https://github.com/mozilla/gecko-dev.git
2965 строки
102 KiB
C++
2965 строки
102 KiB
C++
/* -*- 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 "mozilla/CheckedInt.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/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"
|
|
#ifdef NS_PRINTING
|
|
# include "mozilla/layout/RemotePrintJobParent.h"
|
|
#endif
|
|
#include "mozilla/net/DocumentLoadListener.h"
|
|
#include "mozilla/NullPrincipal.h"
|
|
#include "mozilla/StaticPrefs_docshell.h"
|
|
#include "mozilla/StaticPrefs_fission.h"
|
|
#include "mozilla/Telemetry.h"
|
|
#include "nsILayoutHistoryState.h"
|
|
#include "nsIPrintSettings.h"
|
|
#include "nsIPrintSettingsService.h"
|
|
#include "nsISupports.h"
|
|
#include "nsIWebNavigation.h"
|
|
#include "mozilla/MozPromiseInlines.h"
|
|
#include "nsDocShell.h"
|
|
#include "nsFrameLoader.h"
|
|
#include "nsFrameLoaderOwner.h"
|
|
#include "nsGlobalWindowOuter.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 "SessionStoreFunctions.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::Telemetry::ScalarSet(
|
|
mozilla::Telemetry::ScalarID::DOM_PARENTPROCESS_PRIVATE_WINDOW_USED,
|
|
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::Preferences::GetBool("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 {
|
|
namespace 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.SetHistoryID(GetHistoryID());
|
|
txn.SetExplicitActive(GetExplicitActive());
|
|
txn.SetEmbedderColorScheme(GetEmbedderColorScheme());
|
|
txn.SetHasRestoreData(GetHasRestoreData());
|
|
txn.SetShouldDelayMediaFromStart(GetShouldDelayMediaFromStart());
|
|
|
|
// 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());
|
|
|
|
// 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);
|
|
}
|
|
|
|
if (mozilla::SessionHistoryInParent()) {
|
|
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();
|
|
if (nsCOMPtr<nsIDOMChromeWindow> chromeWin =
|
|
do_QueryInterface(chromeTop->GetDOMWindow())) {
|
|
nsCOMPtr<nsIBrowserDOMWindow> bdw;
|
|
if (NS_SUCCEEDED(chromeWin->GetBrowserDOMWindow(getter_AddRefs(bdw)))) {
|
|
return bdw.forget();
|
|
}
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
already_AddRefed<WindowGlobalParent>
|
|
CanonicalBrowsingContext::GetEmbedderWindowGlobal() const {
|
|
uint64_t windowId = GetEmbedderInnerWindowId();
|
|
if (windowId == 0) {
|
|
return nullptr;
|
|
}
|
|
|
|
return WindowGlobalParent::GetByInnerWindowId(windowId);
|
|
}
|
|
|
|
already_AddRefed<CanonicalBrowsingContext>
|
|
CanonicalBrowsingContext::GetParentCrossChromeBoundary() {
|
|
if (GetParent()) {
|
|
return do_AddRef(Cast(GetParent()));
|
|
}
|
|
if (GetEmbedderElement()) {
|
|
return do_AddRef(
|
|
Cast(GetEmbedderElement()->OwnerDoc()->GetBrowsingContext()));
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
already_AddRefed<CanonicalBrowsingContext>
|
|
CanonicalBrowsingContext::TopCrossChromeBoundary() {
|
|
RefPtr<CanonicalBrowsingContext> bc(this);
|
|
while (RefPtr<CanonicalBrowsingContext> parent =
|
|
bc->GetParentCrossChromeBoundary()) {
|
|
bc = parent.forget();
|
|
}
|
|
return bc.forget();
|
|
}
|
|
|
|
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, nsIChannel* aChannel) {
|
|
RefPtr<SessionHistoryEntry> entry;
|
|
const LoadingSessionHistoryInfo* existingLoadingInfo =
|
|
aLoadState->GetLoadingSessionHistoryInfo();
|
|
if (existingLoadingInfo) {
|
|
entry = SessionHistoryEntry::GetByLoadId(existingLoadingInfo->mLoadId);
|
|
MOZ_LOG(gSHLog, LogLevel::Verbose,
|
|
("SHEntry::GetByLoadId(%" PRIu64 ") -> %p",
|
|
existingLoadingInfo->mLoadId, entry.get()));
|
|
if (!entry) {
|
|
return nullptr;
|
|
}
|
|
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) == 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
|
|
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 {
|
|
if (aStateFlags & nsIWebProgressListener::STATE_STOP &&
|
|
aStateFlags & nsIWebProgressListener::STATE_IS_DOCUMENT && mPromise) {
|
|
mPromise->Resolve(true, __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;
|
|
}
|
|
|
|
private:
|
|
~PrintListenerAdapter() = default;
|
|
|
|
RefPtr<PrintPromise::Private> mPromise;
|
|
};
|
|
|
|
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](bool) { 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
|
|
|
|
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;
|
|
outerWindow->Print(aPrintSettings,
|
|
/* aRemotePrintJob = */ nullptr, listener,
|
|
/* aDocShellToCloneInto = */ nullptr,
|
|
nsGlobalWindowOuter::IsPreview::No,
|
|
nsGlobalWindowOuter::IsForWindowDotPrint::No,
|
|
/* aPrintPreviewCallback = */ 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.remotePrintJobParent() =
|
|
browserParent->Manager()->SendPRemotePrintJobConstructor(remotePrintJob);
|
|
|
|
if (listener) {
|
|
remotePrintJob->RegisterListener(listener);
|
|
}
|
|
|
|
if (NS_WARN_IF(!browserParent->SendPrint(this, printData))) {
|
|
promise->Reject(NS_ERROR_FAILURE, __func__);
|
|
}
|
|
return promise.forget();
|
|
#endif
|
|
}
|
|
|
|
void CanonicalBrowsingContext::CallOnAllTopDescendants(
|
|
const std::function<mozilla::CallState(CanonicalBrowsingContext*)>&
|
|
aCallback) {
|
|
#ifdef DEBUG
|
|
RefPtr<CanonicalBrowsingContext> parent = GetParentCrossChromeBoundary();
|
|
MOZ_ASSERT(!parent, "Should only call on top chrome BC");
|
|
#endif
|
|
|
|
nsTArray<RefPtr<BrowsingContextGroup>> 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;
|
|
}
|
|
|
|
RefPtr<CanonicalBrowsingContext> top =
|
|
bc->Canonical()->TopCrossChromeBoundary();
|
|
if (top == this) {
|
|
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?
|
|
}
|
|
|
|
static already_AddRefed<nsDocShellLoadState> 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);
|
|
loadState->SetLoadingSessionHistoryInfo(std::move(loadingInfo));
|
|
|
|
return loadState.forget();
|
|
}
|
|
|
|
void CanonicalBrowsingContext::NotifyOnHistoryReload(
|
|
bool aForceReload, bool& aCanReload,
|
|
Maybe<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(CreateLoadInfo(mActiveEntry));
|
|
aReloadActiveEntry.emplace(true);
|
|
if (aForceReload) {
|
|
shistory->RemoveFrameEntries(mActiveEntry);
|
|
}
|
|
} else if (!mLoadingEntries.IsEmpty()) {
|
|
const LoadingSessionHistoryEntry& loadingEntry =
|
|
mLoadingEntries.LastElement();
|
|
aLoadState.emplace(CreateLoadInfo(loadingEntry.mEntry));
|
|
aReloadActiveEntry.emplace(false);
|
|
if (aForceReload) {
|
|
SessionHistoryEntry* entry =
|
|
SessionHistoryEntry::GetByLoadId(loadingEntry.mLoadId);
|
|
if (entry) {
|
|
shistory->RemoveFrameEntries(entry);
|
|
}
|
|
}
|
|
}
|
|
|
|
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 EvictContentViewersOrReplaceEntry.
|
|
HistoryCommitIndexAndLength(aChangeID, caller);
|
|
|
|
static_cast<nsSHistory*>(shistory)->LogHistory();
|
|
}
|
|
|
|
void CanonicalBrowsingContext::ReplaceActiveSessionHistoryEntry(
|
|
SessionHistoryInfo* aInfo) {
|
|
if (!mActiveEntry) {
|
|
return;
|
|
}
|
|
|
|
mActiveEntry->SetInfo(aInfo);
|
|
// 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 EvictContentViewersOrReplaceEntry.
|
|
}
|
|
|
|
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);
|
|
}
|
|
}
|
|
|
|
void CanonicalBrowsingContext::HistoryGo(
|
|
int32_t aOffset, uint64_t aHistoryEpoch, bool aRequireUserInteraction,
|
|
bool aUserActivation, Maybe<ContentParentId> aContentId,
|
|
std::function<void(int32_t&&)>&& aResolver) {
|
|
if (aRequireUserInteraction && aOffset != -1 && aOffset != 1) {
|
|
NS_ERROR(
|
|
"aRequireUserInteraction may only be used with an offset of -1 or 1");
|
|
return;
|
|
}
|
|
|
|
nsSHistory* shistory = static_cast<nsSHistory*>(GetSessionHistory());
|
|
if (!shistory) {
|
|
return;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
// 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 (!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;
|
|
}
|
|
if (epoch < aHistoryEpoch || aContentId != id) {
|
|
MOZ_LOG(gSHLog, LogLevel::Debug, ("Set epoch"));
|
|
shistory->SetEpoch(aHistoryEpoch, aContentId);
|
|
}
|
|
aResolver(shistory->GetRequestedIndex());
|
|
nsSHistory::LoadURIs(loadResults);
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
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));
|
|
}
|
|
|
|
net::EarlyHintsService* CanonicalBrowsingContext::GetEarlyHintsService() {
|
|
return &mEarlyHintsService;
|
|
}
|
|
|
|
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();
|
|
}
|
|
|
|
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(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);
|
|
}
|
|
|
|
if (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);
|
|
}
|
|
|
|
if (auto* 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);
|
|
}
|
|
|
|
if (auto* 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);
|
|
}
|
|
|
|
if (auto* 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);
|
|
}
|
|
|
|
// 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 (mContentParent) {
|
|
// 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(mContentParent->ChildID());
|
|
if (found != mTarget->mUnloadingHosts.end()) {
|
|
found->mCallbacks.AppendElement(
|
|
[self = RefPtr{this}]() { 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 (mContentParent && NS_WARN_IF(mContentParent->IsDead())) {
|
|
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,
|
|
mContentParent ? u"true"_ns : u"false"_ns,
|
|
/* notify */ true);
|
|
|
|
// The process has been created, hand off to nsFrameLoaderOwner to finish
|
|
// the process switch.
|
|
ErrorResult error;
|
|
frameLoaderOwner->ChangeRemotenessToProcess(mContentParent, mOptions,
|
|
mSpecificGroup, error);
|
|
if (error.Failed()) {
|
|
return error.StealNSResult();
|
|
}
|
|
|
|
// Tell frontend the load is done.
|
|
bool loadResumed = false;
|
|
rv = browser->FinishChangeRemoteness(mPendingSwitchId, &loadResumed);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
// We did it! The process switch is complete.
|
|
RefPtr<nsFrameLoader> frameLoader = frameLoaderOwner->GetFrameLoader();
|
|
RefPtr<BrowserParent> newBrowser = frameLoader->GetBrowserParent();
|
|
if (!newBrowser) {
|
|
if (mContentParent) {
|
|
// Failed to create the BrowserParent somehow! Abort the process switch
|
|
// attempt.
|
|
return NS_ERROR_UNEXPECTED;
|
|
}
|
|
|
|
if (!loadResumed) {
|
|
RefPtr<nsDocShell> newDocShell = frameLoader->GetDocShell(error);
|
|
if (error.Failed()) {
|
|
return error.StealNSResult();
|
|
}
|
|
|
|
rv = newDocShell->ResumeRedirectedLoad(mPendingSwitchId,
|
|
/* aHistoryIndex */ -1);
|
|
if (NS_FAILED(rv)) {
|
|
return rv;
|
|
}
|
|
}
|
|
} else if (!loadResumed) {
|
|
newBrowser->ResumeLoad(mPendingSwitchId);
|
|
}
|
|
|
|
mPromise->Resolve(newBrowser, __func__);
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult CanonicalBrowsingContext::PendingRemotenessChange::FinishSubframe() {
|
|
MOZ_DIAGNOSTIC_ASSERT(!mOptions.mReplaceBrowsingContext,
|
|
"Cannot replace BC for subframe");
|
|
MOZ_DIAGNOSTIC_ASSERT(!mTarget->IsTop());
|
|
|
|
// 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;
|
|
}
|
|
|
|
if (NS_WARN_IF(!mContentParent)) {
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
|
|
RefPtr<WindowGlobalParent> embedderWindow = target->GetParentWindowContext();
|
|
if (NS_WARN_IF(!embedderWindow) || NS_WARN_IF(!embedderWindow->CanSend())) {
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
|
|
RefPtr<BrowserParent> embedderBrowser = embedderWindow->GetBrowserParent();
|
|
if (NS_WARN_IF(!embedderBrowser)) {
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
|
|
// If we're creating a new remote browser, and the host process is already
|
|
// dead, abort the process switch.
|
|
if (mContentParent != embedderBrowser->Manager() &&
|
|
NS_WARN_IF(mContentParent->IsDead())) {
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
|
|
RefPtr<BrowserParent> oldBrowser = target->GetBrowserParent();
|
|
target->SetCurrentBrowserParent(nullptr);
|
|
|
|
// If we were in a remote frame, trigger unloading of the remote window. The
|
|
// previous BrowserParent is registered in `mUnloadingHosts` and will only be
|
|
// cleared when the BrowserParent is fully destroyed.
|
|
bool wasRemote = oldBrowser && oldBrowser->GetBrowsingContext() == target;
|
|
if (wasRemote) {
|
|
MOZ_DIAGNOSTIC_ASSERT(oldBrowser != embedderBrowser);
|
|
MOZ_DIAGNOSTIC_ASSERT(oldBrowser->IsDestroyed() ||
|
|
oldBrowser->GetBrowserBridgeParent());
|
|
|
|
// `oldBrowser` will clear the `UnloadingHost` status once the actor has
|
|
// been destroyed.
|
|
if (oldBrowser->CanSend()) {
|
|
target->StartUnloadingHost(oldBrowser->Manager()->ChildID());
|
|
Unused << oldBrowser->SendWillChangeProcess();
|
|
oldBrowser->Destroy();
|
|
}
|
|
}
|
|
|
|
// Update which process is considered the current owner
|
|
target->SetOwnerProcessId(mContentParent->ChildID());
|
|
|
|
// If we're switching from remote to local, we don't need to create a
|
|
// BrowserBridge, and can instead perform the switch directly.
|
|
if (mContentParent == embedderBrowser->Manager()) {
|
|
MOZ_DIAGNOSTIC_ASSERT(
|
|
mPendingSwitchId,
|
|
"We always have a PendingSwitchId, except for print-preview loads, "
|
|
"which will never perform a process-switch to being in-process with "
|
|
"their embedder");
|
|
MOZ_DIAGNOSTIC_ASSERT(wasRemote,
|
|
"Attempt to process-switch from local to local?");
|
|
|
|
target->SetCurrentBrowserParent(embedderBrowser);
|
|
Unused << embedderWindow->SendMakeFrameLocal(target, mPendingSwitchId);
|
|
mPromise->Resolve(embedderBrowser, __func__);
|
|
return NS_OK;
|
|
}
|
|
|
|
// The BrowsingContext will be remote, either as an already-remote frame
|
|
// changing processes, or as a local frame becoming remote. Construct a new
|
|
// BrowserBridgeParent to host the remote content.
|
|
target->SetCurrentBrowserParent(nullptr);
|
|
|
|
MOZ_DIAGNOSTIC_ASSERT(target->UseRemoteTabs() && target->UseRemoteSubframes(),
|
|
"Not supported without fission");
|
|
uint32_t chromeFlags = nsIWebBrowserChrome::CHROME_REMOTE_WINDOW |
|
|
nsIWebBrowserChrome::CHROME_FISSION_WINDOW;
|
|
if (target->UsePrivateBrowsing()) {
|
|
chromeFlags |= nsIWebBrowserChrome::CHROME_PRIVATE_WINDOW;
|
|
}
|
|
|
|
nsCOMPtr<nsIPrincipal> initialPrincipal =
|
|
NullPrincipal::CreateWithInheritedAttributes(
|
|
target->OriginAttributesRef(),
|
|
/* isFirstParty */ false);
|
|
WindowGlobalInit windowInit =
|
|
WindowGlobalActor::AboutBlankInitializer(target, initialPrincipal);
|
|
|
|
// Create and initialize our new BrowserBridgeParent.
|
|
TabId tabId(nsContentUtils::GenerateTabId());
|
|
RefPtr<BrowserBridgeParent> bridge = new BrowserBridgeParent();
|
|
nsresult rv = bridge->InitWithProcess(embedderBrowser, mContentParent,
|
|
windowInit, chromeFlags, tabId);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
// If we've already destroyed our previous document, make a best-effort
|
|
// attempt to recover from this failure and show the crashed tab UI. We only
|
|
// do this in the previously-remote case, as previously in-process frames
|
|
// will have their navigation cancelled, and will remain visible.
|
|
if (wasRemote) {
|
|
target->ShowSubframeCrashedUI(oldBrowser->GetBrowserBridgeParent());
|
|
}
|
|
return rv;
|
|
}
|
|
|
|
// Tell the embedder process a remoteness change is in-process. When this is
|
|
// acknowledged, reset the in-flight ID if it used to be an in-process load.
|
|
RefPtr<BrowserParent> newBrowser = bridge->GetBrowserParent();
|
|
{
|
|
// If we weren't remote, mark our embedder window browser as unloading until
|
|
// our embedder process has acked our MakeFrameRemote message.
|
|
Maybe<uint64_t> clearChildID;
|
|
if (!wasRemote) {
|
|
clearChildID = Some(embedderBrowser->Manager()->ChildID());
|
|
target->StartUnloadingHost(*clearChildID);
|
|
}
|
|
auto callback = [target, clearChildID](auto&&) {
|
|
if (clearChildID) {
|
|
target->ClearUnloadingHost(*clearChildID);
|
|
}
|
|
};
|
|
|
|
ManagedEndpoint<PBrowserBridgeChild> endpoint =
|
|
embedderBrowser->OpenPBrowserBridgeEndpoint(bridge);
|
|
MOZ_DIAGNOSTIC_ASSERT(endpoint.IsValid());
|
|
embedderWindow->SendMakeFrameRemote(target, std::move(endpoint), tabId,
|
|
newBrowser->GetLayersId(), callback,
|
|
callback);
|
|
}
|
|
|
|
// Resume the pending load in our new process.
|
|
if (mPendingSwitchId) {
|
|
newBrowser->ResumeLoad(mPendingSwitchId);
|
|
}
|
|
|
|
// We did it! The process switch is complete.
|
|
mPromise->Resolve(newBrowser, __func__);
|
|
return NS_OK;
|
|
}
|
|
|
|
void CanonicalBrowsingContext::PendingRemotenessChange::Cancel(nsresult aRv) {
|
|
if (!mPromise) {
|
|
return;
|
|
}
|
|
|
|
mPromise->Reject(aRv, __func__);
|
|
Clear();
|
|
}
|
|
|
|
void CanonicalBrowsingContext::PendingRemotenessChange::Clear() {
|
|
// Make sure we don't die while we're doing cleanup.
|
|
RefPtr<PendingRemotenessChange> kungFuDeathGrip(this);
|
|
if (mTarget) {
|
|
MOZ_DIAGNOSTIC_ASSERT(mTarget->mPendingRemotenessChange == this);
|
|
mTarget->mPendingRemotenessChange = nullptr;
|
|
}
|
|
|
|
// When this PendingRemotenessChange was created, it was given a
|
|
// `mContentParent`.
|
|
if (mContentParent) {
|
|
mContentParent->RemoveKeepAlive();
|
|
mContentParent = nullptr;
|
|
}
|
|
|
|
// If we were given a specific group, stop keeping that group alive manually.
|
|
if (mSpecificGroup) {
|
|
mSpecificGroup->RemoveKeepAlive();
|
|
mSpecificGroup = nullptr;
|
|
}
|
|
|
|
mPromise = nullptr;
|
|
mTarget = nullptr;
|
|
}
|
|
|
|
CanonicalBrowsingContext::PendingRemotenessChange::PendingRemotenessChange(
|
|
CanonicalBrowsingContext* aTarget, RemotenessPromise::Private* aPromise,
|
|
uint64_t aPendingSwitchId, const NavigationIsolationOptions& aOptions)
|
|
: mTarget(aTarget),
|
|
mPromise(aPromise),
|
|
mPendingSwitchId(aPendingSwitchId),
|
|
mOptions(aOptions) {}
|
|
|
|
CanonicalBrowsingContext::PendingRemotenessChange::~PendingRemotenessChange() {
|
|
MOZ_ASSERT(!mPromise && !mTarget && !mContentParent && !mSpecificGroup,
|
|
"should've already been Cancel() or Complete()-ed");
|
|
}
|
|
|
|
BrowserParent* CanonicalBrowsingContext::GetBrowserParent() const {
|
|
return mCurrentBrowserParent;
|
|
}
|
|
|
|
void CanonicalBrowsingContext::SetCurrentBrowserParent(
|
|
BrowserParent* aBrowserParent) {
|
|
MOZ_DIAGNOSTIC_ASSERT(!mCurrentBrowserParent || !aBrowserParent,
|
|
"BrowsingContext already has a current BrowserParent!");
|
|
MOZ_DIAGNOSTIC_ASSERT_IF(aBrowserParent, aBrowserParent->CanSend());
|
|
MOZ_DIAGNOSTIC_ASSERT_IF(aBrowserParent,
|
|
aBrowserParent->Manager()->ChildID() == mProcessId);
|
|
|
|
// BrowserParent must either be directly for this BrowsingContext, or the
|
|
// manager out our embedder WindowGlobal.
|
|
MOZ_DIAGNOSTIC_ASSERT_IF(
|
|
aBrowserParent && aBrowserParent->GetBrowsingContext() != this,
|
|
GetParentWindowContext() &&
|
|
GetParentWindowContext()->Manager() == aBrowserParent);
|
|
|
|
mCurrentBrowserParent = aBrowserParent;
|
|
}
|
|
|
|
RefPtr<CanonicalBrowsingContext::RemotenessPromise>
|
|
CanonicalBrowsingContext::ChangeRemoteness(
|
|
const NavigationIsolationOptions& aOptions, uint64_t aPendingSwitchId) {
|
|
MOZ_DIAGNOSTIC_ASSERT(IsContent(),
|
|
"cannot change the process of chrome contexts");
|
|
MOZ_DIAGNOSTIC_ASSERT(
|
|
IsTop() == IsEmbeddedInProcess(0),
|
|
"toplevel content must be embedded in the parent process");
|
|
MOZ_DIAGNOSTIC_ASSERT(!aOptions.mReplaceBrowsingContext || IsTop(),
|
|
"Cannot replace BrowsingContext for subframes");
|
|
MOZ_DIAGNOSTIC_ASSERT(
|
|
aOptions.mSpecificGroupId == 0 || aOptions.mReplaceBrowsingContext,
|
|
"Cannot specify group ID unless replacing BC");
|
|
MOZ_DIAGNOSTIC_ASSERT(aPendingSwitchId || !IsTop(),
|
|
"Should always have aPendingSwitchId for top-level "
|
|
"frames");
|
|
|
|
if (!AncestorsAreCurrent()) {
|
|
NS_WARNING("An ancestor context is no longer current");
|
|
return RemotenessPromise::CreateAndReject(NS_ERROR_FAILURE, __func__);
|
|
}
|
|
|
|
// Ensure our embedder hasn't been destroyed already.
|
|
RefPtr<WindowGlobalParent> embedderWindowGlobal = GetEmbedderWindowGlobal();
|
|
if (!embedderWindowGlobal) {
|
|
NS_WARNING("Non-embedded BrowsingContext");
|
|
return RemotenessPromise::CreateAndReject(NS_ERROR_UNEXPECTED, __func__);
|
|
}
|
|
|
|
if (!embedderWindowGlobal->CanSend()) {
|
|
NS_WARNING("Embedder already been destroyed.");
|
|
return RemotenessPromise::CreateAndReject(NS_ERROR_NOT_AVAILABLE, __func__);
|
|
}
|
|
|
|
if (aOptions.mRemoteType.IsEmpty() && (!IsTop() || !GetEmbedderElement())) {
|
|
NS_WARNING("Cannot load non-remote subframes");
|
|
return RemotenessPromise::CreateAndReject(NS_ERROR_FAILURE, __func__);
|
|
}
|
|
|
|
// Cancel ongoing remoteness changes.
|
|
if (mPendingRemotenessChange) {
|
|
mPendingRemotenessChange->Cancel(NS_ERROR_ABORT);
|
|
MOZ_DIAGNOSTIC_ASSERT(!mPendingRemotenessChange, "Should have cleared");
|
|
}
|
|
|
|
auto promise = MakeRefPtr<RemotenessPromise::Private>(__func__);
|
|
promise->UseDirectTaskDispatch(__func__);
|
|
|
|
RefPtr<PendingRemotenessChange> change =
|
|
new PendingRemotenessChange(this, promise, aPendingSwitchId, aOptions);
|
|
mPendingRemotenessChange = change;
|
|
|
|
// If a specific BrowsingContextGroup ID was specified for this load, make
|
|
// sure to keep it alive until the process switch is completed.
|
|
if (aOptions.mSpecificGroupId) {
|
|
change->mSpecificGroup =
|
|
BrowsingContextGroup::GetOrCreate(aOptions.mSpecificGroupId);
|
|
change->mSpecificGroup->AddKeepAlive();
|
|
}
|
|
|
|
// Call `prepareToChangeRemoteness` in parallel with starting a new process
|
|
// for <browser> loads.
|
|
if (IsTop() && GetEmbedderElement()) {
|
|
nsCOMPtr<nsIBrowser> browser = GetEmbedderElement()->AsBrowser();
|
|
if (!browser) {
|
|
change->Cancel(NS_ERROR_FAILURE);
|
|
return promise.forget();
|
|
}
|
|
|
|
RefPtr<Promise> blocker;
|
|
nsresult rv = browser->PrepareToChangeRemoteness(getter_AddRefs(blocker));
|
|
if (NS_FAILED(rv)) {
|
|
change->Cancel(rv);
|
|
return promise.forget();
|
|
}
|
|
|
|
// Mark prepareToChange as unresolved, and wait for it to become resolved.
|
|
if (blocker && blocker->State() != Promise::PromiseState::Resolved) {
|
|
change->mWaitingForPrepareToChange = true;
|
|
RefPtr<DomPromiseListener> listener = new DomPromiseListener(
|
|
[change](JSContext* aCx, JS::Handle<JS::Value> aValue) {
|
|
change->mWaitingForPrepareToChange = false;
|
|
change->MaybeFinish();
|
|
},
|
|
[change](nsresult aRv) { change->Cancel(aRv); });
|
|
blocker->AppendNativeHandler(listener);
|
|
}
|
|
}
|
|
|
|
// Switching a subframe to be local within it's embedding process.
|
|
RefPtr<BrowserParent> embedderBrowser =
|
|
embedderWindowGlobal->GetBrowserParent();
|
|
if (embedderBrowser &&
|
|
aOptions.mRemoteType == embedderBrowser->Manager()->GetRemoteType()) {
|
|
MOZ_DIAGNOSTIC_ASSERT(
|
|
aPendingSwitchId,
|
|
"We always have a PendingSwitchId, except for print-preview loads, "
|
|
"which will never perform a process-switch to being in-process with "
|
|
"their embedder");
|
|
MOZ_DIAGNOSTIC_ASSERT(!aOptions.mReplaceBrowsingContext);
|
|
MOZ_DIAGNOSTIC_ASSERT(!aOptions.mRemoteType.IsEmpty());
|
|
MOZ_DIAGNOSTIC_ASSERT(!change->mWaitingForPrepareToChange);
|
|
MOZ_DIAGNOSTIC_ASSERT(!change->mSpecificGroup);
|
|
|
|
// Switching to local, so we don't need to create a new process, and will
|
|
// instead use our embedder process.
|
|
change->mContentParent = embedderBrowser->Manager();
|
|
change->mContentParent->AddKeepAlive();
|
|
change->ProcessLaunched();
|
|
return promise.forget();
|
|
}
|
|
|
|
// Switching to the parent process.
|
|
if (aOptions.mRemoteType.IsEmpty()) {
|
|
change->ProcessLaunched();
|
|
return promise.forget();
|
|
}
|
|
|
|
// If we're aiming to end up in a new process of the same type as our old
|
|
// process, and then putting our previous document in the BFCache, try to stay
|
|
// in the same process to avoid creating new processes unnecessarily.
|
|
RefPtr<ContentParent> existingProcess = GetContentParent();
|
|
if (existingProcess && existingProcess->IsAlive() &&
|
|
aOptions.mReplaceBrowsingContext &&
|
|
aOptions.mRemoteType == existingProcess->GetRemoteType()) {
|
|
change->mContentParent = existingProcess;
|
|
change->mContentParent->AddKeepAlive();
|
|
change->ProcessLaunched();
|
|
return promise.forget();
|
|
}
|
|
|
|
// Try to predict which BrowsingContextGroup will be used for the final load
|
|
// in this BrowsingContext. This has to be accurate if switching into an
|
|
// existing group, as it will control what pool of processes will be used
|
|
// for process selection.
|
|
//
|
|
// It's _technically_ OK to provide a group here if we're actually going to
|
|
// switch into a brand new group, though it's sub-optimal, as it can
|
|
// restrict the set of processes we're using.
|
|
BrowsingContextGroup* finalGroup =
|
|
aOptions.mReplaceBrowsingContext ? change->mSpecificGroup.get() : Group();
|
|
|
|
bool preferUsed =
|
|
StaticPrefs::browser_tabs_remote_subframesPreferUsed() && !IsTop();
|
|
|
|
change->mContentParent = ContentParent::GetNewOrUsedLaunchingBrowserProcess(
|
|
/* aRemoteType = */ aOptions.mRemoteType,
|
|
/* aGroup = */ finalGroup,
|
|
/* aPriority = */ hal::PROCESS_PRIORITY_FOREGROUND,
|
|
/* aPreferUsed = */ preferUsed);
|
|
if (!change->mContentParent) {
|
|
change->Cancel(NS_ERROR_FAILURE);
|
|
return promise.forget();
|
|
}
|
|
|
|
// Add a KeepAlive used by this ContentParent, which will be cleared when
|
|
// the change is complete. This should prevent the process dying before
|
|
// we're ready to use it.
|
|
change->mContentParent->AddKeepAlive();
|
|
if (change->mContentParent->IsLaunching()) {
|
|
change->mContentParent->WaitForLaunchAsync()->Then(
|
|
GetMainThreadSerialEventTarget(), __func__,
|
|
[change](ContentParent*) { change->ProcessLaunched(); },
|
|
[change](LaunchError) { change->Cancel(NS_ERROR_FAILURE); });
|
|
} else {
|
|
change->ProcessLaunched();
|
|
}
|
|
return promise.forget();
|
|
}
|
|
|
|
void CanonicalBrowsingContext::MaybeSetPermanentKey(Element* aEmbedder) {
|
|
MOZ_DIAGNOSTIC_ASSERT(IsTop());
|
|
|
|
if (aEmbedder) {
|
|
if (nsCOMPtr<nsIBrowser> browser = aEmbedder->AsBrowser()) {
|
|
JS::Rooted<JS::Value> key(RootingCx());
|
|
if (NS_SUCCEEDED(browser->GetPermanentKey(&key)) && key.isObject()) {
|
|
mPermanentKey = key;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
MediaController* CanonicalBrowsingContext::GetMediaController() {
|
|
// We would only create one media controller per tab, so accessing the
|
|
// controller via the top-level browsing context.
|
|
if (GetParent()) {
|
|
return Cast(Top())->GetMediaController();
|
|
}
|
|
|
|
MOZ_ASSERT(!GetParent(),
|
|
"Must access the controller from the top-level browsing context!");
|
|
// Only content browsing context can create media controller, we won't create
|
|
// controller for chrome document, such as the browser UI.
|
|
if (!mTabMediaController && !IsDiscarded() && IsContent()) {
|
|
mTabMediaController = new MediaController(Id());
|
|
}
|
|
return mTabMediaController;
|
|
}
|
|
|
|
bool CanonicalBrowsingContext::HasCreatedMediaController() const {
|
|
return !!mTabMediaController;
|
|
}
|
|
|
|
bool CanonicalBrowsingContext::SupportsLoadingInParent(
|
|
nsDocShellLoadState* aLoadState, uint64_t* aOuterWindowId) {
|
|
// We currently don't support initiating loads in the parent when they are
|
|
// watched by devtools. This is because devtools tracks loads using content
|
|
// process notifications, which happens after the load is initiated in this
|
|
// case. Devtools clears all prior requests when it detects a new navigation,
|
|
// so it drops the main document load that happened here.
|
|
if (WatchedByDevTools()) {
|
|
return false;
|
|
}
|
|
|
|
// Session-history-in-parent implementation relies currently on getting a
|
|
// round trip through a child process.
|
|
if (aLoadState->LoadIsFromSessionHistory()) {
|
|
return false;
|
|
}
|
|
|
|
// DocumentChannel currently only supports connecting channels into the
|
|
// content process, so we can only support schemes that will always be loaded
|
|
// there for now. Restrict to just http(s) for simplicity.
|
|
if (!net::SchemeIsHTTP(aLoadState->URI()) &&
|
|
!net::SchemeIsHTTPS(aLoadState->URI())) {
|
|
return false;
|
|
}
|
|
|
|
if (WindowGlobalParent* global = GetCurrentWindowGlobal()) {
|
|
nsCOMPtr<nsIURI> currentURI = global->GetDocumentURI();
|
|
if (currentURI) {
|
|
bool newURIHasRef = false;
|
|
aLoadState->URI()->GetHasRef(&newURIHasRef);
|
|
bool equalsExceptRef = false;
|
|
aLoadState->URI()->EqualsExceptRef(currentURI, &equalsExceptRef);
|
|
|
|
if (equalsExceptRef && newURIHasRef) {
|
|
// This navigation is same-doc WRT the current one, we should pass it
|
|
// down to the docshell to be handled.
|
|
return false;
|
|
}
|
|
}
|
|
// If the current document has a beforeunload listener, then we need to
|
|
// start the load in that process after we fire the event.
|
|
if (global->HasBeforeUnload()) {
|
|
return false;
|
|
}
|
|
|
|
*aOuterWindowId = global->OuterWindowId();
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool CanonicalBrowsingContext::LoadInParent(nsDocShellLoadState* aLoadState,
|
|
bool aSetNavigating) {
|
|
// We currently only support starting loads directly from the
|
|
// CanonicalBrowsingContext for top-level BCs.
|
|
// We currently only support starting loads directly from the
|
|
// CanonicalBrowsingContext for top-level BCs.
|
|
if (!IsTopContent() || !GetContentParent() ||
|
|
!StaticPrefs::browser_tabs_documentchannel_parent_controlled()) {
|
|
return false;
|
|
}
|
|
|
|
uint64_t outerWindowId = 0;
|
|
if (!SupportsLoadingInParent(aLoadState, &outerWindowId)) {
|
|
return false;
|
|
}
|
|
|
|
MOZ_ASSERT(!net::SchemeIsJavascript(aLoadState->URI()));
|
|
|
|
MOZ_ALWAYS_SUCCEEDS(
|
|
SetParentInitiatedNavigationEpoch(++gParentInitiatedNavigationEpoch));
|
|
// Note: If successful, this will recurse into StartDocumentLoad and
|
|
// set mCurrentLoad to the DocumentLoadListener instance created.
|
|
// Ideally in the future we will only start loads from here, and we can
|
|
// just set this directly instead.
|
|
return net::DocumentLoadListener::LoadInParent(this, aLoadState,
|
|
aSetNavigating);
|
|
}
|
|
|
|
bool CanonicalBrowsingContext::AttemptSpeculativeLoadInParent(
|
|
nsDocShellLoadState* aLoadState) {
|
|
// We currently only support starting loads directly from the
|
|
// CanonicalBrowsingContext for top-level BCs.
|
|
// We currently only support starting loads directly from the
|
|
// CanonicalBrowsingContext for top-level BCs.
|
|
if (!IsTopContent() || !GetContentParent() ||
|
|
(StaticPrefs::browser_tabs_documentchannel_parent_controlled())) {
|
|
return false;
|
|
}
|
|
|
|
uint64_t outerWindowId = 0;
|
|
if (!SupportsLoadingInParent(aLoadState, &outerWindowId)) {
|
|
return false;
|
|
}
|
|
|
|
// If we successfully open the DocumentChannel, then it'll register
|
|
// itself using aLoadIdentifier and be kept alive until it completes
|
|
// loading.
|
|
return net::DocumentLoadListener::SpeculativeLoadInParent(this, aLoadState);
|
|
}
|
|
|
|
bool CanonicalBrowsingContext::StartDocumentLoad(
|
|
net::DocumentLoadListener* aLoad) {
|
|
// If we're controlling loads from the parent, then starting a new load means
|
|
// that we need to cancel any existing ones.
|
|
if (StaticPrefs::browser_tabs_documentchannel_parent_controlled() &&
|
|
mCurrentLoad) {
|
|
// Make sure we are not loading a javascript URI.
|
|
MOZ_ASSERT(!aLoad->IsLoadingJSURI());
|
|
|
|
// If we want to do a download, don't cancel the current navigation.
|
|
if (!aLoad->IsDownload()) {
|
|
mCurrentLoad->Cancel(NS_BINDING_CANCELLED_OLD_LOAD);
|
|
}
|
|
}
|
|
mCurrentLoad = aLoad;
|
|
|
|
if (NS_FAILED(SetCurrentLoadIdentifier(Some(aLoad->GetLoadIdentifier())))) {
|
|
mCurrentLoad = nullptr;
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void CanonicalBrowsingContext::EndDocumentLoad(bool aContinueNavigating) {
|
|
mCurrentLoad = nullptr;
|
|
|
|
if (!aContinueNavigating) {
|
|
// Resetting the current load identifier on a discarded context
|
|
// has no effect when a document load has finished.
|
|
Unused << SetCurrentLoadIdentifier(Nothing());
|
|
}
|
|
}
|
|
|
|
already_AddRefed<nsIURI> CanonicalBrowsingContext::GetCurrentURI() const {
|
|
nsCOMPtr<nsIURI> currentURI;
|
|
if (nsIDocShell* docShell = GetDocShell()) {
|
|
MOZ_ALWAYS_SUCCEEDS(
|
|
nsDocShell::Cast(docShell)->GetCurrentURI(getter_AddRefs(currentURI)));
|
|
} else {
|
|
currentURI = mCurrentRemoteURI;
|
|
}
|
|
return currentURI.forget();
|
|
}
|
|
|
|
void CanonicalBrowsingContext::SetCurrentRemoteURI(nsIURI* aCurrentRemoteURI) {
|
|
MOZ_ASSERT(!GetDocShell());
|
|
mCurrentRemoteURI = aCurrentRemoteURI;
|
|
}
|
|
|
|
void CanonicalBrowsingContext::ResetSHEntryHasUserInteractionCache() {
|
|
WindowContext* topWc = GetTopWindowContext();
|
|
if (topWc && !topWc->IsDiscarded()) {
|
|
MOZ_ALWAYS_SUCCEEDS(topWc->SetSHEntryHasUserInteraction(false));
|
|
}
|
|
}
|
|
|
|
void CanonicalBrowsingContext::HistoryCommitIndexAndLength() {
|
|
nsID changeID = {};
|
|
CallerWillNotifyHistoryIndexAndLengthChanges caller(nullptr);
|
|
HistoryCommitIndexAndLength(changeID, caller);
|
|
}
|
|
void CanonicalBrowsingContext::HistoryCommitIndexAndLength(
|
|
const nsID& aChangeID,
|
|
const CallerWillNotifyHistoryIndexAndLengthChanges& aProofOfCaller) {
|
|
if (!IsTop()) {
|
|
Cast(Top())->HistoryCommitIndexAndLength(aChangeID, aProofOfCaller);
|
|
return;
|
|
}
|
|
|
|
nsISHistory* shistory = GetSessionHistory();
|
|
if (!shistory) {
|
|
return;
|
|
}
|
|
int32_t index = 0;
|
|
shistory->GetIndex(&index);
|
|
int32_t length = shistory->GetCount();
|
|
|
|
GetChildSessionHistory()->SetIndexAndLength(index, length, aChangeID);
|
|
|
|
shistory->EvictOutOfRangeContentViewers(index);
|
|
|
|
Group()->EachParent([&](ContentParent* aParent) {
|
|
Unused << aParent->SendHistoryCommitIndexAndLength(this, index, length,
|
|
aChangeID);
|
|
});
|
|
}
|
|
|
|
void CanonicalBrowsingContext::SynchronizeLayoutHistoryState() {
|
|
if (mActiveEntry) {
|
|
if (IsInProcess()) {
|
|
nsIDocShell* docShell = GetDocShell();
|
|
if (docShell) {
|
|
docShell->PersistLayoutHistoryState();
|
|
|
|
nsCOMPtr<nsILayoutHistoryState> state;
|
|
docShell->GetLayoutHistoryState(getter_AddRefs(state));
|
|
if (state) {
|
|
mActiveEntry->SetLayoutHistoryState(state);
|
|
}
|
|
}
|
|
} else if (ContentParent* cp = GetContentParent()) {
|
|
cp->SendGetLayoutHistoryState(this)->Then(
|
|
GetCurrentSerialEventTarget(), __func__,
|
|
[activeEntry = mActiveEntry](
|
|
const Tuple<RefPtr<nsILayoutHistoryState>, Maybe<Wireframe>>&
|
|
aResult) {
|
|
if (mozilla::Get<0>(aResult)) {
|
|
activeEntry->SetLayoutHistoryState(mozilla::Get<0>(aResult));
|
|
}
|
|
if (mozilla::Get<1>(aResult)) {
|
|
activeEntry->SetWireframe(mozilla::Get<1>(aResult));
|
|
}
|
|
},
|
|
[]() {});
|
|
}
|
|
}
|
|
}
|
|
|
|
void CanonicalBrowsingContext::ResetScalingZoom() {
|
|
// This currently only ever gets called in the parent process, and we
|
|
// pass the message on to the WindowGlobalChild for the rootmost browsing
|
|
// context.
|
|
if (WindowGlobalParent* topWindow = GetTopWindowContext()) {
|
|
Unused << topWindow->SendResetScalingZoom();
|
|
}
|
|
}
|
|
|
|
void CanonicalBrowsingContext::SetRestoreData(SessionStoreRestoreData* aData,
|
|
ErrorResult& aError) {
|
|
MOZ_DIAGNOSTIC_ASSERT(aData);
|
|
|
|
nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(GetParentObject());
|
|
RefPtr<Promise> promise = Promise::Create(global, aError);
|
|
if (aError.Failed()) {
|
|
return;
|
|
}
|
|
|
|
if (NS_WARN_IF(NS_FAILED(SetHasRestoreData(true)))) {
|
|
aError.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
|
|
return;
|
|
}
|
|
|
|
mRestoreState = new RestoreState();
|
|
mRestoreState->mData = aData;
|
|
mRestoreState->mPromise = promise;
|
|
}
|
|
|
|
already_AddRefed<Promise> CanonicalBrowsingContext::GetRestorePromise() {
|
|
if (mRestoreState) {
|
|
return do_AddRef(mRestoreState->mPromise);
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
void CanonicalBrowsingContext::ClearRestoreState() {
|
|
if (!mRestoreState) {
|
|
MOZ_DIAGNOSTIC_ASSERT(!GetHasRestoreData());
|
|
return;
|
|
}
|
|
if (mRestoreState->mPromise) {
|
|
mRestoreState->mPromise->MaybeRejectWithUndefined();
|
|
}
|
|
mRestoreState = nullptr;
|
|
MOZ_ALWAYS_SUCCEEDS(SetHasRestoreData(false));
|
|
}
|
|
|
|
void CanonicalBrowsingContext::RequestRestoreTabContent(
|
|
WindowGlobalParent* aWindow) {
|
|
MOZ_DIAGNOSTIC_ASSERT(IsTop());
|
|
|
|
if (IsDiscarded() || !mRestoreState || !mRestoreState->mData) {
|
|
return;
|
|
}
|
|
|
|
CanonicalBrowsingContext* context = aWindow->GetBrowsingContext();
|
|
MOZ_DIAGNOSTIC_ASSERT(!context->IsDiscarded());
|
|
|
|
RefPtr<SessionStoreRestoreData> data =
|
|
mRestoreState->mData->FindDataForChild(context);
|
|
|
|
if (context->IsTop()) {
|
|
MOZ_DIAGNOSTIC_ASSERT(context == this);
|
|
|
|
// We need to wait until the appropriate load event has fired before we
|
|
// can "complete" the restore process, so if we're holding an empty data
|
|
// object, just resolve the promise immediately.
|
|
if (mRestoreState->mData->IsEmpty()) {
|
|
MOZ_DIAGNOSTIC_ASSERT(!data || data->IsEmpty());
|
|
mRestoreState->Resolve();
|
|
ClearRestoreState();
|
|
return;
|
|
}
|
|
|
|
// Since we're following load event order, we'll only arrive here for a
|
|
// toplevel context after we've already sent down data for all child frames,
|
|
// so it's safe to clear this reference now. The completion callback below
|
|
// relies on the mData field being null to determine if all requests have
|
|
// been sent out.
|
|
mRestoreState->ClearData();
|
|
MOZ_ALWAYS_SUCCEEDS(SetHasRestoreData(false));
|
|
}
|
|
|
|
if (data && !data->IsEmpty()) {
|
|
auto onTabRestoreComplete = [self = RefPtr{this},
|
|
state = RefPtr{mRestoreState}](auto) {
|
|
state->mResolves++;
|
|
if (!state->mData && state->mRequests == state->mResolves) {
|
|
state->Resolve();
|
|
if (state == self->mRestoreState) {
|
|
self->ClearRestoreState();
|
|
}
|
|
}
|
|
};
|
|
|
|
mRestoreState->mRequests++;
|
|
|
|
if (data->CanRestoreInto(aWindow->GetDocumentURI())) {
|
|
if (!aWindow->IsInProcess()) {
|
|
aWindow->SendRestoreTabContent(data, onTabRestoreComplete,
|
|
onTabRestoreComplete);
|
|
return;
|
|
}
|
|
data->RestoreInto(context);
|
|
}
|
|
|
|
// This must be called both when we're doing an in-process restore, and when
|
|
// we didn't do a restore at all due to a URL mismatch.
|
|
onTabRestoreComplete(true);
|
|
}
|
|
}
|
|
|
|
void CanonicalBrowsingContext::RestoreState::Resolve() {
|
|
MOZ_DIAGNOSTIC_ASSERT(mPromise);
|
|
mPromise->MaybeResolveWithUndefined();
|
|
mPromise = nullptr;
|
|
}
|
|
|
|
nsresult CanonicalBrowsingContext::WriteSessionStorageToSessionStore(
|
|
const nsTArray<SSCacheCopy>& aSesssionStorage, uint32_t aEpoch) {
|
|
nsCOMPtr<nsISessionStoreFunctions> funcs = do_ImportModule(
|
|
"resource://gre/modules/SessionStoreFunctions.jsm", fallible);
|
|
if (!funcs) {
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
|
|
nsCOMPtr<nsIXPConnectWrappedJS> wrapped = do_QueryInterface(funcs);
|
|
AutoJSAPI jsapi;
|
|
if (!jsapi.Init(wrapped->GetJSObjectGlobal())) {
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
|
|
JS::Rooted<JS::Value> key(jsapi.cx(), Top()->PermanentKey());
|
|
|
|
Record<nsCString, Record<nsString, nsString>> storage;
|
|
JS::Rooted<JS::Value> update(jsapi.cx());
|
|
|
|
if (!aSesssionStorage.IsEmpty()) {
|
|
SessionStoreUtils::ConstructSessionStorageValues(this, aSesssionStorage,
|
|
storage);
|
|
if (!ToJSValue(jsapi.cx(), storage, &update)) {
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
} else {
|
|
update.setNull();
|
|
}
|
|
|
|
return funcs->UpdateSessionStoreForStorage(Top()->GetEmbedderElement(), this,
|
|
key, aEpoch, update);
|
|
}
|
|
|
|
void CanonicalBrowsingContext::UpdateSessionStoreSessionStorage(
|
|
const std::function<void()>& aDone) {
|
|
if (!StaticPrefs::browser_sessionstore_collect_session_storage_AtStartup()) {
|
|
aDone();
|
|
return;
|
|
}
|
|
|
|
using DataPromise = BackgroundSessionStorageManager::DataPromise;
|
|
BackgroundSessionStorageManager::GetData(
|
|
this, StaticPrefs::browser_sessionstore_dom_storage_limit(),
|
|
/* aClearSessionStoreTimer = */ true)
|
|
->Then(GetCurrentSerialEventTarget(), __func__,
|
|
[self = RefPtr{this}, aDone, epoch = GetSessionStoreEpoch()](
|
|
const DataPromise::ResolveOrRejectValue& valueList) {
|
|
if (valueList.IsResolve()) {
|
|
self->WriteSessionStorageToSessionStore(
|
|
valueList.ResolveValue(), epoch);
|
|
}
|
|
aDone();
|
|
});
|
|
}
|
|
|
|
/* static */
|
|
void CanonicalBrowsingContext::UpdateSessionStoreForStorage(
|
|
uint64_t aBrowsingContextId) {
|
|
RefPtr<CanonicalBrowsingContext> browsingContext = Get(aBrowsingContextId);
|
|
|
|
if (!browsingContext) {
|
|
return;
|
|
}
|
|
|
|
browsingContext->UpdateSessionStoreSessionStorage([]() {});
|
|
}
|
|
|
|
void CanonicalBrowsingContext::MaybeScheduleSessionStoreUpdate() {
|
|
if (!StaticPrefs::browser_sessionstore_platform_collection_AtStartup()) {
|
|
return;
|
|
}
|
|
|
|
if (!IsTop()) {
|
|
Top()->MaybeScheduleSessionStoreUpdate();
|
|
return;
|
|
}
|
|
|
|
if (IsInBFCache()) {
|
|
return;
|
|
}
|
|
|
|
if (mSessionStoreSessionStorageUpdateTimer) {
|
|
return;
|
|
}
|
|
|
|
if (!StaticPrefs::browser_sessionstore_debug_no_auto_updates()) {
|
|
auto result = NS_NewTimerWithFuncCallback(
|
|
[](nsITimer*, void* aClosure) {
|
|
auto* context = static_cast<CanonicalBrowsingContext*>(aClosure);
|
|
context->UpdateSessionStoreSessionStorage([]() {});
|
|
},
|
|
this, StaticPrefs::browser_sessionstore_interval(),
|
|
nsITimer::TYPE_ONE_SHOT,
|
|
"CanonicalBrowsingContext::MaybeScheduleSessionStoreUpdate");
|
|
|
|
if (result.isErr()) {
|
|
return;
|
|
}
|
|
|
|
mSessionStoreSessionStorageUpdateTimer = result.unwrap();
|
|
}
|
|
}
|
|
|
|
void CanonicalBrowsingContext::CancelSessionStoreUpdate() {
|
|
if (mSessionStoreSessionStorageUpdateTimer) {
|
|
mSessionStoreSessionStorageUpdateTimer->Cancel();
|
|
mSessionStoreSessionStorageUpdateTimer = nullptr;
|
|
}
|
|
}
|
|
|
|
void CanonicalBrowsingContext::SetContainerFeaturePolicy(
|
|
FeaturePolicy* aContainerFeaturePolicy) {
|
|
mContainerFeaturePolicy = aContainerFeaturePolicy;
|
|
|
|
if (WindowGlobalParent* current = GetCurrentWindowGlobal()) {
|
|
Unused << current->SendSetContainerFeaturePolicy(mContainerFeaturePolicy);
|
|
}
|
|
}
|
|
|
|
void CanonicalBrowsingContext::SetCrossGroupOpenerId(uint64_t aOpenerId) {
|
|
MOZ_DIAGNOSTIC_ASSERT(IsTopContent());
|
|
MOZ_DIAGNOSTIC_ASSERT(mCrossGroupOpenerId == 0,
|
|
"Can only set CrossGroupOpenerId once");
|
|
mCrossGroupOpenerId = aOpenerId;
|
|
}
|
|
|
|
void CanonicalBrowsingContext::SetCrossGroupOpener(
|
|
CanonicalBrowsingContext& aCrossGroupOpener, ErrorResult& aRv) {
|
|
if (!IsTopContent()) {
|
|
aRv.ThrowNotAllowedError(
|
|
"Can only set crossGroupOpener on toplevel content");
|
|
return;
|
|
}
|
|
if (mCrossGroupOpenerId != 0) {
|
|
aRv.ThrowNotAllowedError("Can only set crossGroupOpener once");
|
|
return;
|
|
}
|
|
|
|
SetCrossGroupOpenerId(aCrossGroupOpener.Id());
|
|
}
|
|
|
|
auto CanonicalBrowsingContext::FindUnloadingHost(uint64_t aChildID)
|
|
-> nsTArray<UnloadingHost>::iterator {
|
|
return std::find_if(
|
|
mUnloadingHosts.begin(), mUnloadingHosts.end(),
|
|
[&](const auto& host) { return host.mChildID == aChildID; });
|
|
}
|
|
|
|
void CanonicalBrowsingContext::ClearUnloadingHost(uint64_t aChildID) {
|
|
// Notify any callbacks which were waiting for the host to finish unloading
|
|
// that it has.
|
|
auto found = FindUnloadingHost(aChildID);
|
|
if (found != mUnloadingHosts.end()) {
|
|
auto callbacks = std::move(found->mCallbacks);
|
|
mUnloadingHosts.RemoveElementAt(found);
|
|
for (const auto& callback : callbacks) {
|
|
callback();
|
|
}
|
|
}
|
|
}
|
|
|
|
void CanonicalBrowsingContext::StartUnloadingHost(uint64_t aChildID) {
|
|
MOZ_DIAGNOSTIC_ASSERT(FindUnloadingHost(aChildID) == mUnloadingHosts.end());
|
|
mUnloadingHosts.AppendElement(UnloadingHost{aChildID, {}});
|
|
}
|
|
|
|
void CanonicalBrowsingContext::BrowserParentDestroyed(
|
|
BrowserParent* aBrowserParent, bool aAbnormalShutdown) {
|
|
ClearUnloadingHost(aBrowserParent->Manager()->ChildID());
|
|
|
|
// Handling specific to when the current BrowserParent has been destroyed.
|
|
if (mCurrentBrowserParent == aBrowserParent) {
|
|
mCurrentBrowserParent = nullptr;
|
|
|
|
// If this BrowserParent is for a subframe, attempt to recover from a
|
|
// subframe crash by rendering the subframe crashed page in the embedding
|
|
// content.
|
|
if (aAbnormalShutdown) {
|
|
ShowSubframeCrashedUI(aBrowserParent->GetBrowserBridgeParent());
|
|
}
|
|
}
|
|
}
|
|
|
|
void CanonicalBrowsingContext::ShowSubframeCrashedUI(
|
|
BrowserBridgeParent* aBridge) {
|
|
if (!aBridge || IsDiscarded() || !aBridge->CanSend()) {
|
|
return;
|
|
}
|
|
|
|
MOZ_DIAGNOSTIC_ASSERT(!aBridge->GetBrowsingContext() ||
|
|
aBridge->GetBrowsingContext() == this);
|
|
|
|
// There is no longer a current inner window within this
|
|
// BrowsingContext, update the `CurrentInnerWindowId` field to reflect
|
|
// this.
|
|
MOZ_ALWAYS_SUCCEEDS(SetCurrentInnerWindowId(0));
|
|
|
|
// The owning process will now be the embedder to render the subframe
|
|
// crashed page, switch ownership back over.
|
|
SetOwnerProcessId(aBridge->Manager()->Manager()->ChildID());
|
|
SetCurrentBrowserParent(aBridge->Manager());
|
|
|
|
Unused << aBridge->SendSubFrameCrashed();
|
|
}
|
|
|
|
static void LogBFCacheBlockingForDoc(BrowsingContext* aBrowsingContext,
|
|
uint32_t aBFCacheCombo, bool aIsSubDoc) {
|
|
if (aIsSubDoc) {
|
|
nsAutoCString uri("[no uri]");
|
|
nsCOMPtr<nsIURI> currentURI =
|
|
aBrowsingContext->Canonical()->GetCurrentURI();
|
|
if (currentURI) {
|
|
uri = currentURI->GetSpecOrDefault();
|
|
}
|
|
MOZ_LOG(gSHIPBFCacheLog, LogLevel::Debug,
|
|
(" ** Blocked for document %s", uri.get()));
|
|
}
|
|
if (aBFCacheCombo & BFCacheStatus::EVENT_HANDLING_SUPPRESSED) {
|
|
MOZ_LOG(gSHIPBFCacheLog, LogLevel::Debug,
|
|
(" * event handling suppression"));
|
|
}
|
|
if (aBFCacheCombo & BFCacheStatus::SUSPENDED) {
|
|
MOZ_LOG(gSHIPBFCacheLog, LogLevel::Debug, (" * suspended Window"));
|
|
}
|
|
if (aBFCacheCombo & BFCacheStatus::UNLOAD_LISTENER) {
|
|
MOZ_LOG(gSHIPBFCacheLog, LogLevel::Debug, (" * unload listener"));
|
|
}
|
|
if (aBFCacheCombo & BFCacheStatus::REQUEST) {
|
|
MOZ_LOG(gSHIPBFCacheLog, LogLevel::Debug, (" * requests in the loadgroup"));
|
|
}
|
|
if (aBFCacheCombo & BFCacheStatus::ACTIVE_GET_USER_MEDIA) {
|
|
MOZ_LOG(gSHIPBFCacheLog, LogLevel::Debug, (" * GetUserMedia"));
|
|
}
|
|
if (aBFCacheCombo & BFCacheStatus::ACTIVE_PEER_CONNECTION) {
|
|
MOZ_LOG(gSHIPBFCacheLog, LogLevel::Debug, (" * PeerConnection"));
|
|
}
|
|
if (aBFCacheCombo & BFCacheStatus::CONTAINS_EME_CONTENT) {
|
|
MOZ_LOG(gSHIPBFCacheLog, LogLevel::Debug, (" * EME content"));
|
|
}
|
|
if (aBFCacheCombo & BFCacheStatus::CONTAINS_MSE_CONTENT) {
|
|
MOZ_LOG(gSHIPBFCacheLog, LogLevel::Debug, (" * MSE use"));
|
|
}
|
|
if (aBFCacheCombo & BFCacheStatus::HAS_ACTIVE_SPEECH_SYNTHESIS) {
|
|
MOZ_LOG(gSHIPBFCacheLog, LogLevel::Debug, (" * Speech use"));
|
|
}
|
|
if (aBFCacheCombo & BFCacheStatus::HAS_USED_VR) {
|
|
MOZ_LOG(gSHIPBFCacheLog, LogLevel::Debug, (" * used VR"));
|
|
}
|
|
if (aBFCacheCombo & BFCacheStatus::BEFOREUNLOAD_LISTENER) {
|
|
MOZ_LOG(gSHIPBFCacheLog, LogLevel::Debug, (" * beforeunload listener"));
|
|
}
|
|
if (aBFCacheCombo & BFCacheStatus::ACTIVE_LOCK) {
|
|
MOZ_LOG(gSHIPBFCacheLog, LogLevel::Debug, (" * has active Web Locks"));
|
|
}
|
|
}
|
|
|
|
bool CanonicalBrowsingContext::AllowedInBFCache(
|
|
const Maybe<uint64_t>& aChannelId, nsIURI* aNewURI) {
|
|
if (MOZ_UNLIKELY(MOZ_LOG_TEST(gSHIPBFCacheLog, LogLevel::Debug))) {
|
|
nsAutoCString uri("[no uri]");
|
|
nsCOMPtr<nsIURI> currentURI = GetCurrentURI();
|
|
if (currentURI) {
|
|
uri = currentURI->GetSpecOrDefault();
|
|
}
|
|
MOZ_LOG(gSHIPBFCacheLog, LogLevel::Debug, ("Checking %s", uri.get()));
|
|
}
|
|
|
|
if (IsInProcess()) {
|
|
return false;
|
|
}
|
|
|
|
uint32_t bfcacheCombo = 0;
|
|
if (mRestoreState) {
|
|
bfcacheCombo |= BFCacheStatus::RESTORING;
|
|
MOZ_LOG(gSHIPBFCacheLog, LogLevel::Debug, (" * during session restore"));
|
|
}
|
|
|
|
if (Group()->Toplevels().Length() > 1) {
|
|
bfcacheCombo |= BFCacheStatus::NOT_ONLY_TOPLEVEL_IN_BCG;
|
|
MOZ_LOG(gSHIPBFCacheLog, LogLevel::Debug,
|
|
(" * auxiliary BrowsingContexts"));
|
|
}
|
|
|
|
// There are not a lot of about:* pages that are allowed to load in
|
|
// subframes, so it's OK to allow those few about:* pages enter BFCache.
|
|
MOZ_ASSERT(IsTop(), "Trying to put a non top level BC into BFCache");
|
|
|
|
WindowGlobalParent* wgp = GetCurrentWindowGlobal();
|
|
if (wgp && wgp->GetDocumentURI()) {
|
|
nsCOMPtr<nsIURI> currentURI = wgp->GetDocumentURI();
|
|
// Exempt about:* pages from bfcache, with the exception of about:blank
|
|
if (currentURI->SchemeIs("about") &&
|
|
!currentURI->GetSpecOrDefault().EqualsLiteral("about:blank")) {
|
|
bfcacheCombo |= BFCacheStatus::ABOUT_PAGE;
|
|
MOZ_LOG(gSHIPBFCacheLog, LogLevel::Debug, (" * about:* page"));
|
|
}
|
|
|
|
if (aNewURI) {
|
|
bool equalUri = false;
|
|
aNewURI->Equals(currentURI, &equalUri);
|
|
if (equalUri) {
|
|
// When loading the same uri, disable bfcache so that
|
|
// nsDocShell::OnNewURI transforms the load to LOAD_NORMAL_REPLACE.
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
// For telemetry we're collecting all the flags for all the BCs hanging
|
|
// from this top-level BC.
|
|
PreOrderWalk([&](BrowsingContext* aBrowsingContext) {
|
|
WindowGlobalParent* wgp =
|
|
aBrowsingContext->Canonical()->GetCurrentWindowGlobal();
|
|
uint32_t subDocBFCacheCombo = wgp ? wgp->GetBFCacheStatus() : 0;
|
|
if (wgp) {
|
|
const Maybe<uint64_t>& singleChannelId = wgp->GetSingleChannelId();
|
|
if (singleChannelId.isSome()) {
|
|
if (singleChannelId.value() == 0 || aChannelId.isNothing() ||
|
|
singleChannelId.value() != aChannelId.value()) {
|
|
subDocBFCacheCombo |= BFCacheStatus::REQUEST;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (MOZ_UNLIKELY(MOZ_LOG_TEST(gSHIPBFCacheLog, LogLevel::Debug))) {
|
|
LogBFCacheBlockingForDoc(aBrowsingContext, subDocBFCacheCombo,
|
|
aBrowsingContext != this);
|
|
}
|
|
|
|
bfcacheCombo |= subDocBFCacheCombo;
|
|
});
|
|
|
|
nsDocShell::ReportBFCacheComboTelemetry(bfcacheCombo);
|
|
if (MOZ_UNLIKELY(MOZ_LOG_TEST(gSHIPBFCacheLog, LogLevel::Debug))) {
|
|
nsAutoCString uri("[no uri]");
|
|
nsCOMPtr<nsIURI> currentURI = GetCurrentURI();
|
|
if (currentURI) {
|
|
uri = currentURI->GetSpecOrDefault();
|
|
}
|
|
MOZ_LOG(gSHIPBFCacheLog, LogLevel::Debug,
|
|
(" +> %s %s be blocked from going into the BFCache", uri.get(),
|
|
bfcacheCombo == 0 ? "shouldn't" : "should"));
|
|
}
|
|
|
|
if (StaticPrefs::docshell_shistory_bfcache_allow_unload_listeners()) {
|
|
bfcacheCombo &= ~BFCacheStatus::UNLOAD_LISTENER;
|
|
}
|
|
|
|
return bfcacheCombo == 0;
|
|
}
|
|
|
|
void CanonicalBrowsingContext::SetTouchEventsOverride(
|
|
dom::TouchEventsOverride aOverride, ErrorResult& aRv) {
|
|
SetTouchEventsOverrideInternal(aOverride, aRv);
|
|
}
|
|
|
|
void CanonicalBrowsingContext::SetTargetTopLevelLinkClicksToBlank(
|
|
bool aTargetTopLevelLinkClicksToBlank, ErrorResult& aRv) {
|
|
SetTargetTopLevelLinkClicksToBlankInternal(aTargetTopLevelLinkClicksToBlank,
|
|
aRv);
|
|
}
|
|
|
|
void CanonicalBrowsingContext::AddPageAwakeRequest() {
|
|
MOZ_ASSERT(IsTop());
|
|
auto count = GetPageAwakeRequestCount();
|
|
MOZ_ASSERT(count < UINT32_MAX);
|
|
Unused << SetPageAwakeRequestCount(++count);
|
|
}
|
|
|
|
void CanonicalBrowsingContext::RemovePageAwakeRequest() {
|
|
MOZ_ASSERT(IsTop());
|
|
auto count = GetPageAwakeRequestCount();
|
|
MOZ_ASSERT(count > 0);
|
|
Unused << SetPageAwakeRequestCount(--count);
|
|
}
|
|
|
|
void CanonicalBrowsingContext::CloneDocumentTreeInto(
|
|
CanonicalBrowsingContext* aSource, const nsACString& aRemoteType,
|
|
embedding::PrintData&& aPrintData) {
|
|
NavigationIsolationOptions options;
|
|
options.mRemoteType = aRemoteType;
|
|
|
|
mClonePromise =
|
|
ChangeRemoteness(options, /* aPendingSwitchId = */ 0)
|
|
->Then(
|
|
GetMainThreadSerialEventTarget(), __func__,
|
|
[source = MaybeDiscardedBrowsingContext{aSource},
|
|
data = std::move(aPrintData)](
|
|
BrowserParent* aBp) -> RefPtr<GenericNonExclusivePromise> {
|
|
RefPtr<BrowserBridgeParent> bridge =
|
|
aBp->GetBrowserBridgeParent();
|
|
return aBp->SendCloneDocumentTreeIntoSelf(source, data)
|
|
->Then(
|
|
GetMainThreadSerialEventTarget(), __func__,
|
|
[bridge](
|
|
BrowserParent::CloneDocumentTreeIntoSelfPromise::
|
|
ResolveOrRejectValue&& aValue) {
|
|
// We're cloning a remote iframe, so we created a
|
|
// BrowserBridge which makes us register an OOP load
|
|
// (see Document::OOPChildLoadStarted), even though
|
|
// this isn't a real load. We call
|
|
// SendMaybeFireEmbedderLoadEvents here so that we do
|
|
// register the end of the load (see
|
|
// Document::OOPChildLoadDone).
|
|
if (bridge) {
|
|
Unused << bridge->SendMaybeFireEmbedderLoadEvents(
|
|
EmbedderElementEventType::NoEvent);
|
|
}
|
|
if (aValue.IsResolve() && aValue.ResolveValue()) {
|
|
return GenericNonExclusivePromise::CreateAndResolve(
|
|
true, __func__);
|
|
}
|
|
return GenericNonExclusivePromise::CreateAndReject(
|
|
NS_ERROR_FAILURE, __func__);
|
|
});
|
|
},
|
|
[](nsresult aRv) -> RefPtr<GenericNonExclusivePromise> {
|
|
NS_WARNING(
|
|
nsPrintfCString("Remote clone failed: %x\n", unsigned(aRv))
|
|
.get());
|
|
return GenericNonExclusivePromise::CreateAndReject(
|
|
NS_ERROR_FAILURE, __func__);
|
|
});
|
|
|
|
mClonePromise->Then(
|
|
GetMainThreadSerialEventTarget(), __func__,
|
|
[self = RefPtr{this}]() { self->mClonePromise = nullptr; });
|
|
}
|
|
|
|
bool CanonicalBrowsingContext::StartApzAutoscroll(float aAnchorX,
|
|
float aAnchorY,
|
|
nsViewID aScrollId,
|
|
uint32_t aPresShellId) {
|
|
nsCOMPtr<nsIWidget> widget;
|
|
mozilla::layers::LayersId layersId{0};
|
|
|
|
if (IsInProcess()) {
|
|
nsCOMPtr<nsPIDOMWindowOuter> outer = GetDOMWindow();
|
|
if (!outer) {
|
|
return false;
|
|
}
|
|
|
|
widget = widget::WidgetUtils::DOMWindowToWidget(outer);
|
|
if (widget) {
|
|
layersId = widget->GetRootLayerTreeId();
|
|
}
|
|
} else {
|
|
RefPtr<BrowserParent> parent = GetBrowserParent();
|
|
if (!parent) {
|
|
return false;
|
|
}
|
|
|
|
widget = parent->GetWidget();
|
|
layersId = parent->GetLayersId();
|
|
}
|
|
|
|
if (!widget || !widget->AsyncPanZoomEnabled()) {
|
|
return false;
|
|
}
|
|
|
|
// The anchor coordinates that are passed in are relative to the origin of the
|
|
// screen, but we are sending them to APZ which only knows about coordinates
|
|
// relative to the widget, so convert them accordingly.
|
|
const LayoutDeviceIntPoint anchor =
|
|
RoundedToInt(LayoutDevicePoint(aAnchorX, aAnchorY)) -
|
|
widget->WidgetToScreenOffset();
|
|
|
|
mozilla::layers::ScrollableLayerGuid guid(layersId, aPresShellId, aScrollId);
|
|
|
|
return widget->StartAsyncAutoscroll(
|
|
ViewAs<ScreenPixel>(
|
|
anchor, PixelCastJustification::LayoutDeviceIsScreenForBounds),
|
|
guid);
|
|
}
|
|
|
|
void CanonicalBrowsingContext::StopApzAutoscroll(nsViewID aScrollId,
|
|
uint32_t aPresShellId) {
|
|
nsCOMPtr<nsIWidget> widget;
|
|
mozilla::layers::LayersId layersId{0};
|
|
|
|
if (IsInProcess()) {
|
|
nsCOMPtr<nsPIDOMWindowOuter> outer = GetDOMWindow();
|
|
if (!outer) {
|
|
return;
|
|
}
|
|
|
|
widget = widget::WidgetUtils::DOMWindowToWidget(outer);
|
|
if (widget) {
|
|
layersId = widget->GetRootLayerTreeId();
|
|
}
|
|
} else {
|
|
RefPtr<BrowserParent> parent = GetBrowserParent();
|
|
if (!parent) {
|
|
return;
|
|
}
|
|
|
|
widget = parent->GetWidget();
|
|
layersId = parent->GetLayersId();
|
|
}
|
|
|
|
if (!widget || !widget->AsyncPanZoomEnabled()) {
|
|
return;
|
|
}
|
|
|
|
mozilla::layers::ScrollableLayerGuid guid(layersId, aPresShellId, aScrollId);
|
|
widget->StopAsyncAutoscroll(guid);
|
|
}
|
|
|
|
NS_IMPL_CYCLE_COLLECTION_CLASS(CanonicalBrowsingContext)
|
|
|
|
NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(CanonicalBrowsingContext,
|
|
BrowsingContext)
|
|
tmp->mPermanentKey.setNull();
|
|
if (tmp->mSessionHistory) {
|
|
tmp->mSessionHistory->SetBrowsingContext(nullptr);
|
|
}
|
|
NS_IMPL_CYCLE_COLLECTION_UNLINK(mSessionHistory, mContainerFeaturePolicy,
|
|
mCurrentBrowserParent, mWebProgress,
|
|
mSessionStoreSessionStorageUpdateTimer)
|
|
NS_IMPL_CYCLE_COLLECTION_UNLINK_END
|
|
|
|
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(CanonicalBrowsingContext,
|
|
BrowsingContext)
|
|
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSessionHistory, mContainerFeaturePolicy,
|
|
mCurrentBrowserParent, mWebProgress,
|
|
mSessionStoreSessionStorageUpdateTimer)
|
|
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
|
|
|
|
NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN_INHERITED(CanonicalBrowsingContext,
|
|
BrowsingContext)
|
|
NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mPermanentKey)
|
|
NS_IMPL_CYCLE_COLLECTION_TRACE_END
|
|
|
|
NS_IMPL_ADDREF_INHERITED(CanonicalBrowsingContext, BrowsingContext)
|
|
NS_IMPL_RELEASE_INHERITED(CanonicalBrowsingContext, BrowsingContext)
|
|
|
|
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(CanonicalBrowsingContext)
|
|
NS_INTERFACE_MAP_END_INHERITING(BrowsingContext)
|
|
|
|
} // namespace dom
|
|
} // namespace mozilla
|