summaryrefslogtreecommitdiffstats
path: root/docshell/base/CanonicalBrowsingContext.cpp
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--docshell/base/CanonicalBrowsingContext.cpp3009
1 files changed, 3009 insertions, 0 deletions
diff --git a/docshell/base/CanonicalBrowsingContext.cpp b/docshell/base/CanonicalBrowsingContext.cpp
new file mode 100644
index 0000000000..542ee94542
--- /dev/null
+++ b/docshell/base/CanonicalBrowsingContext.cpp
@@ -0,0 +1,3009 @@
+/* -*- 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_browser.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::StaticPrefs::browser_privatebrowsing_autostart()) {
+ nsCOMPtr<nsIObserverService> observerService =
+ mozilla::services::GetObserverService();
+ if (observerService) {
+ MOZ_LOG(sPBContext, mozilla::LogLevel::Debug,
+ ("%s: last-pb-context-exited fired", __func__));
+ observerService->NotifyObservers(nullptr, "last-pb-context-exited",
+ nullptr);
+ }
+ }
+}
+
+namespace mozilla {
+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.SetEmbedderColorSchemes(GetEmbedderColorSchemes());
+ 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;
+ }
+
+ // If the entry was updated, update also the LoadingSessionHistoryInfo.
+ UniquePtr<LoadingSessionHistoryInfo> lshi =
+ MakeUnique<LoadingSessionHistoryInfo>(entry, existingLoadingInfo);
+ aLoadState->SetLoadingSessionHistoryInfo(std::move(lshi));
+ existingLoadingInfo = aLoadState->GetLoadingSessionHistoryInfo();
+ Unused << SetHistoryEntryCount(entry->BCHistoryLength());
+ } else if (aLoadState->LoadType() == LOAD_REFRESH &&
+ !ShouldAddEntryForRefresh(aLoadState->URI(),
+ aLoadState->PostDataStream()) &&
+ mActiveEntry) {
+ entry = mActiveEntry;
+ } else {
+ entry = new SessionHistoryEntry(aLoadState, aChannel);
+ if (IsTop()) {
+ // Only top level pages care about Get/SetPersist.
+ entry->SetPersist(
+ nsDocShell::ShouldAddToSessionHistory(aLoadState->URI(), aChannel));
+ } else if (mActiveEntry || !mLoadingEntries.IsEmpty()) {
+ entry->SetIsSubFrame(true);
+ }
+ entry->SetDocshellID(GetHistoryID());
+ entry->SetIsDynamicallyAdded(CreatedDynamically());
+ entry->SetForInitialLoad(true);
+ }
+ MOZ_DIAGNOSTIC_ASSERT(entry);
+
+ UniquePtr<LoadingSessionHistoryInfo> loadingInfo;
+ if (existingLoadingInfo) {
+ loadingInfo = MakeUnique<LoadingSessionHistoryInfo>(*existingLoadingInfo);
+ } else {
+ loadingInfo = MakeUnique<LoadingSessionHistoryInfo>(entry);
+ mLoadingEntries.AppendElement(
+ LoadingSessionHistoryEntry{loadingInfo->mLoadId, entry});
+ }
+
+ MOZ_ASSERT(SessionHistoryEntry::GetByLoadId(loadingInfo->mLoadId) == 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);
+ }
+}
+
+Maybe<int32_t> CanonicalBrowsingContext::HistoryGo(
+ int32_t aOffset, uint64_t aHistoryEpoch, bool aRequireUserInteraction,
+ bool aUserActivation, Maybe<ContentParentId> aContentId) {
+ if (aRequireUserInteraction && aOffset != -1 && aOffset != 1) {
+ NS_ERROR(
+ "aRequireUserInteraction may only be used with an offset of -1 or 1");
+ return Nothing();
+ }
+
+ nsSHistory* shistory = static_cast<nsSHistory*>(GetSessionHistory());
+ if (!shistory) {
+ return Nothing();
+ }
+
+ CheckedInt<int32_t> index = shistory->GetRequestedIndex() >= 0
+ ? shistory->GetRequestedIndex()
+ : shistory->Index();
+ MOZ_LOG(gSHLog, LogLevel::Debug,
+ ("HistoryGo(%d->%d) epoch %" PRIu64 "/id %" PRIu64, aOffset,
+ (index + aOffset).value(), aHistoryEpoch,
+ (uint64_t)(aContentId.isSome() ? aContentId.value() : 0)));
+
+ while (true) {
+ index += aOffset;
+ if (!index.isValid()) {
+ MOZ_LOG(gSHLog, LogLevel::Debug, ("Invalid index"));
+ return Nothing();
+ }
+
+ // Check for user interaction if desired, except for the first and last
+ // history entries. We compare with >= to account for the case where
+ // aOffset >= length.
+ if (!aRequireUserInteraction || index.value() >= shistory->Length() - 1 ||
+ index.value() <= 0) {
+ break;
+ }
+ if (shistory->HasUserInteractionAtIndex(index.value())) {
+ break;
+ }
+ }
+
+ // Implement aborting additional history navigations from within the same
+ // event spin of the content process.
+
+ uint64_t epoch;
+ bool sameEpoch = false;
+ Maybe<ContentParentId> id;
+ shistory->GetEpoch(epoch, id);
+
+ if (aContentId == id && epoch >= aHistoryEpoch) {
+ sameEpoch = true;
+ MOZ_LOG(gSHLog, LogLevel::Debug, ("Same epoch/id"));
+ }
+ // Don't update the epoch until we know if the target index is valid
+
+ // GoToIndex checks that index is >= 0 and < length.
+ nsTArray<nsSHistory::LoadEntryResult> loadResults;
+ nsresult rv = shistory->GotoIndex(index.value(), loadResults, sameEpoch,
+ aOffset == 0, aUserActivation);
+ if (NS_FAILED(rv)) {
+ MOZ_LOG(gSHLog, LogLevel::Debug,
+ ("Dropping HistoryGo - bad index or same epoch (not in same doc)"));
+ return Nothing();
+ }
+ if (epoch < aHistoryEpoch || aContentId != id) {
+ MOZ_LOG(gSHLog, LogLevel::Debug, ("Set epoch"));
+ shistory->SetEpoch(aHistoryEpoch, aContentId);
+ }
+ int32_t requestedIndex = shistory->GetRequestedIndex();
+ nsSHistory::LoadURIs(loadResults);
+ return Some(requestedIndex);
+}
+
+JSObject* CanonicalBrowsingContext::WrapObject(
+ JSContext* aCx, JS::Handle<JSObject*> aGivenProto) {
+ return CanonicalBrowsingContext_Binding::Wrap(aCx, this, aGivenProto);
+}
+
+void CanonicalBrowsingContext::DispatchWheelZoomChange(bool aIncrease) {
+ Element* element = Top()->GetEmbedderElement();
+ if (!element) {
+ return;
+ }
+
+ auto event = aIncrease ? u"DoZoomEnlargeBy10"_ns : u"DoZoomReduceBy10"_ns;
+ auto dispatcher = MakeRefPtr<AsyncEventDispatcher>(
+ element, event, CanBubble::eYes, ChromeOnlyDispatch::eYes);
+ dispatcher->PostDOMEvent();
+}
+
+void CanonicalBrowsingContext::CanonicalDiscard() {
+ if (mTabMediaController) {
+ mTabMediaController->Shutdown();
+ mTabMediaController = nullptr;
+ }
+
+ if (mCurrentLoad) {
+ mCurrentLoad->Cancel(NS_BINDING_ABORTED,
+ "CanonicalBrowsingContext::CanonicalDiscard"_ns);
+ }
+
+ if (mWebProgress) {
+ RefPtr<BrowsingContextWebProgress> progress = mWebProgress;
+ progress->ContextDiscarded();
+ }
+
+ if (IsTop()) {
+ BackgroundSessionStorageManager::RemoveManager(Id());
+ }
+
+ CancelSessionStoreUpdate();
+
+ if (UsePrivateBrowsing() && EverAttached() && IsContent()) {
+ DecreasePrivateCount();
+ }
+}
+
+void CanonicalBrowsingContext::CanonicalAttach() {
+ if (UsePrivateBrowsing() && IsContent()) {
+ IncreasePrivateCount();
+ }
+}
+
+void CanonicalBrowsingContext::AddPendingDiscard() {
+ MOZ_ASSERT(!mFullyDiscarded);
+ mPendingDiscards++;
+}
+
+void CanonicalBrowsingContext::RemovePendingDiscard() {
+ mPendingDiscards--;
+ if (!mPendingDiscards) {
+ mFullyDiscarded = true;
+ auto listeners = std::move(mFullyDiscardedListeners);
+ for (const auto& listener : listeners) {
+ listener(Id());
+ }
+ }
+}
+
+void CanonicalBrowsingContext::AddFinalDiscardListener(
+ std::function<void(uint64_t)>&& aListener) {
+ if (mFullyDiscarded) {
+ aListener(Id());
+ return;
+ }
+ mFullyDiscardedListeners.AppendElement(std::move(aListener));
+}
+
+void CanonicalBrowsingContext::SetForceAppWindowActive(bool aForceActive,
+ ErrorResult& aRv) {
+ MOZ_DIAGNOSTIC_ASSERT(IsChrome());
+ MOZ_DIAGNOSTIC_ASSERT(IsTop());
+ if (!IsChrome() || !IsTop()) {
+ return aRv.ThrowNotAllowedError(
+ "You shouldn't need to force this BrowsingContext to be active, use "
+ ".isActive instead");
+ }
+ if (mForceAppWindowActive == aForceActive) {
+ return;
+ }
+ mForceAppWindowActive = aForceActive;
+ RecomputeAppWindowVisibility();
+}
+
+void CanonicalBrowsingContext::RecomputeAppWindowVisibility() {
+ MOZ_RELEASE_ASSERT(IsChrome());
+ MOZ_RELEASE_ASSERT(IsTop());
+
+ const bool isActive = [&] {
+ if (ForceAppWindowActive()) {
+ return true;
+ }
+ auto* docShell = GetDocShell();
+ if (NS_WARN_IF(!docShell)) {
+ return false;
+ }
+ nsCOMPtr<nsIWidget> widget;
+ nsDocShell::Cast(docShell)->GetMainWidget(getter_AddRefs(widget));
+ if (NS_WARN_IF(!widget)) {
+ return false;
+ }
+ if (widget->IsFullyOccluded() ||
+ widget->SizeMode() == nsSizeMode_Minimized) {
+ return false;
+ }
+ return true;
+ }();
+ SetIsActive(isActive, IgnoreErrors());
+}
+
+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, ""_ns);
+ }
+
+ 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, ""_ns);
+ }
+
+ 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, ""_ns);
+ }
+
+ 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, ""_ns);
+ }
+
+ 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,
+ "CanonicalBrowsingContext::Stop"_ns);
+ }
+
+ // Ask the docshell to stop to handle loads that haven't
+ // yet reached here, as well as non-network activity.
+ if (auto* docShell = nsDocShell::Cast(GetDocShell())) {
+ docShell->Stop(aStopFlags);
+ } else if (ContentParent* cp = GetContentParent()) {
+ Unused << cp->SendStopLoad(this, aStopFlags);
+ }
+}
+
+void CanonicalBrowsingContext::PendingRemotenessChange::ProcessLaunched() {
+ if (!mPromise) {
+ return;
+ }
+
+ if (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::Create(target->OriginAttributesRef());
+ 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, ""_ns);
+ }
+ }
+ 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_ImportESModule(
+ "resource://gre/modules/SessionStoreFunctions.sys.mjs", 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