commit bfe0525bedbac2db07d1a7f1adae45d1005ba66e
parent 0f4a3a9aac2fe2b03698c4c6a8b3451b813ed88b
Author: Adam Vandolder <avandolder@mozilla.com>
Date: Tue, 4 Nov 2025 01:44:39 +0000
Bug 1997823 - Allow active entry lists to be shared among browsing contexts. r=dom-core,smaug
Differential Revision: https://phabricator.services.mozilla.com/D270981
Diffstat:
8 files changed, 148 insertions(+), 55 deletions(-)
diff --git a/docshell/base/BrowsingContext.cpp b/docshell/base/BrowsingContext.cpp
@@ -1030,7 +1030,7 @@ void BrowsingContext::Detach(bool aFromIPC) {
if (XRE_IsParentProcess()) {
Canonical()->AddPendingDiscard();
- Canonical()->mActiveEntryList.clear();
+ Canonical()->mActiveEntryList = nullptr;
}
auto callListeners =
MakeScopeExit([&, listeners = std::move(mDiscardListeners), id = Id()] {
diff --git a/docshell/base/CanonicalBrowsingContext.cpp b/docshell/base/CanonicalBrowsingContext.cpp
@@ -161,7 +161,7 @@ CanonicalBrowsingContext::~CanonicalBrowsingContext() {
mSessionHistory->SetBrowsingContext(nullptr);
}
- mActiveEntryList.clear();
+ mActiveEntryList = nullptr;
}
/* static */
@@ -359,7 +359,7 @@ void CanonicalBrowsingContext::ReplacedBy(
MOZ_ASSERT(!aNewContext->mActiveEntry);
mActiveEntry.swap(aNewContext->mActiveEntry);
if (Navigation::IsAPIEnabled()) {
- MOZ_ASSERT(aNewContext->mActiveEntryList.isEmpty());
+ MOZ_ASSERT(!aNewContext->mActiveEntryList);
aNewContext->mActiveEntryList = std::move(mActiveEntryList);
}
@@ -486,13 +486,14 @@ SessionHistoryEntry* CanonicalBrowsingContext::GetActiveSessionHistoryEntry() {
void CanonicalBrowsingContext::SetActiveSessionHistoryEntryFromBFCache(
SessionHistoryEntry* aEntry) {
mActiveEntry = aEntry;
- if (Navigation::IsAPIEnabled()) {
+ auto* activeEntries = GetActiveEntries();
+ if (Navigation::IsAPIEnabled() && activeEntries) {
if (StaticPrefs::dom_navigation_api_strict_enabled()) {
- MOZ_DIAGNOSTIC_ASSERT(!aEntry || mActiveEntryList.contains(aEntry));
- MOZ_DIAGNOSTIC_ASSERT(aEntry || mActiveEntryList.isEmpty());
+ MOZ_DIAGNOSTIC_ASSERT(!aEntry || activeEntries->contains(aEntry));
+ MOZ_DIAGNOSTIC_ASSERT(aEntry || activeEntries->isEmpty());
} else {
- MOZ_ASSERT(!aEntry || mActiveEntryList.contains(aEntry));
- MOZ_ASSERT(aEntry || mActiveEntryList.isEmpty());
+ MOZ_ASSERT(!aEntry || activeEntries->contains(aEntry));
+ MOZ_ASSERT(aEntry || activeEntries->isEmpty());
}
}
}
@@ -510,6 +511,7 @@ void CanonicalBrowsingContext::SwapHistoryEntries(nsISHEntry* aOldEntry,
}
nsCOMPtr<SessionHistoryEntry> newEntry = do_QueryInterface(aNewEntry);
+ auto* activeEntries = GetActiveEntries();
MOZ_LOG(gSHLog, LogLevel::Verbose,
("Swapping History Entries: mActiveEntry=%p, aNewEntry=%p. "
"Is in list? mActiveEntry %s, aNewEntry %s. "
@@ -517,9 +519,9 @@ void CanonicalBrowsingContext::SwapHistoryEntries(nsISHEntry* aOldEntry,
mActiveEntry.get(), aNewEntry,
mActiveEntry && mActiveEntry->isInList() ? "yes" : "no",
newEntry && newEntry->isInList() ? "yes" : "no",
- mActiveEntryList.contains(newEntry) ? "yes" : "no"));
+ activeEntries->contains(newEntry) ? "yes" : "no"));
if (!newEntry) {
- mActiveEntryList.clear();
+ activeEntries->clear();
mActiveEntry = nullptr;
return;
}
@@ -534,7 +536,7 @@ void CanonicalBrowsingContext::SwapHistoryEntries(nsISHEntry* aOldEntry,
if (beforeOldEntry) {
beforeOldEntry->setNext(newEntry);
} else {
- mActiveEntryList.insertFront(newEntry);
+ activeEntries->insertFront(newEntry);
}
} else {
newEntry->setPrevious(mActiveEntry);
@@ -646,9 +648,12 @@ CanonicalBrowsingContext::CreateLoadingSessionHistoryEntryForLoad(
bool sessionHistoryLoad =
existingLoadingInfo && existingLoadingInfo->mLoadIsFromSessionHistory;
- if (sessionHistoryLoad && !mActiveEntry && mActiveEntryList.isEmpty()) {
- nsSHistory* shistory = static_cast<nsSHistory*>(GetSessionHistory());
- mActiveEntryList = shistory->ConstructContiguousEntryListFrom(entry);
+ if (sessionHistoryLoad && !mActiveEntry) {
+ auto* activeEntries = GetActiveEntries();
+ if (activeEntries->isEmpty()) {
+ nsSHistory* shistory = static_cast<nsSHistory*>(GetSessionHistory());
+ shistory->ReconstructContiguousEntryListFrom(entry);
+ }
}
MOZ_LOG_FMT(gNavigationAPILog, LogLevel::Debug,
@@ -1172,12 +1177,13 @@ void CanonicalBrowsingContext::SessionHistoryCommit(
[](nsISHEntry* aEntry) { aEntry->SetName(EmptyString()); });
}
+ auto* activeEntries = GetActiveEntries();
MOZ_LOG(gSHLog, LogLevel::Verbose,
("SessionHistoryCommit called with mActiveEntry=%p, "
"newActiveEntry=%p, "
"active entry list does%s contain the active entry.",
mActiveEntry.get(), newActiveEntry.get(),
- mActiveEntryList.contains(mActiveEntry) ? "" : "n't"));
+ activeEntries->contains(mActiveEntry) ? "" : "n't"));
bool addEntry = ShouldUpdateSessionHistory(aLoadType);
if (IsTop()) {
@@ -1211,7 +1217,7 @@ void CanonicalBrowsingContext::SessionHistoryCommit(
}
}
if (Navigation::IsAPIEnabled() && !newActiveEntry->isInList()) {
- mActiveEntryList.insertBack(newActiveEntry);
+ activeEntries->insertBack(newActiveEntry);
}
mActiveEntry = newActiveEntry;
} else if (LOAD_TYPE_HAS_FLAGS(
@@ -1231,7 +1237,7 @@ void CanonicalBrowsingContext::SessionHistoryCommit(
}
// TODO(avandolder): Can this check ever actually be false?
if (!newActiveEntry->isInList()) {
- mActiveEntryList.insertBack(newActiveEntry);
+ activeEntries->insertBack(newActiveEntry);
}
}
mActiveEntry = newActiveEntry;
@@ -1239,7 +1245,7 @@ void CanonicalBrowsingContext::SessionHistoryCommit(
MOZ_LOG_FMT(gSHLog, LogLevel::Verbose,
"IsTop: No active entry, adding new entry");
if (Navigation::IsAPIEnabled() && !newActiveEntry->isInList()) {
- mActiveEntryList.insertBack(newActiveEntry);
+ activeEntries->insertBack(newActiveEntry);
}
mActiveEntry = newActiveEntry;
} else {
@@ -1247,7 +1253,7 @@ void CanonicalBrowsingContext::SessionHistoryCommit(
"IsTop: Loading from session history");
mActiveEntry = newActiveEntry;
if (Navigation::IsAPIEnabled() && !mActiveEntry->isInList()) {
- mActiveEntryList.insertBack(mActiveEntry);
+ activeEntries->insertBack(mActiveEntry);
}
}
@@ -1279,11 +1285,8 @@ void CanonicalBrowsingContext::SessionHistoryCommit(
"NotTop: Loading from session history");
mActiveEntry = newActiveEntry;
if (Navigation::IsAPIEnabled() && !mActiveEntry->isInList()) {
- mActiveEntryList.clear();
- mActiveEntryList =
- shistory->ConstructContiguousEntryListFrom(mActiveEntry);
+ shistory->ReconstructContiguousEntryListFrom(mActiveEntry);
}
-
shistory->InternalSetRequestedIndex(indexOfHistoryLoad);
// FIXME UpdateIndex() here may update index too early (but even the
// old implementation seems to have similar issues).
@@ -1309,7 +1312,7 @@ void CanonicalBrowsingContext::SessionHistoryCommit(
aCloneEntryChildren);
if (Navigation::IsAPIEnabled()) {
if (!mActiveEntry->isInList()) {
- mActiveEntryList.insertBack(mActiveEntry);
+ activeEntries->insertBack(mActiveEntry);
}
mActiveEntry->setNext(newActiveEntry);
}
@@ -1324,7 +1327,7 @@ void CanonicalBrowsingContext::SessionHistoryCommit(
"NotTop: Adding entry without an active entry");
mActiveEntry = newActiveEntry;
if (Navigation::IsAPIEnabled() && !mActiveEntry->isInList()) {
- mActiveEntryList.insertBack(mActiveEntry);
+ activeEntries->insertBack(mActiveEntry);
}
// FIXME Using IsInProcess for aUseRemoteSubframes isn't quite
// right, but aUseRemoteSubframes should be going away.
@@ -1448,21 +1451,22 @@ void CanonicalBrowsingContext::SetActiveSessionHistoryEntry(
}
}
+ auto* activeEntries = GetActiveEntries();
MOZ_LOG(
gSHLog, LogLevel::Verbose,
("SetActiveSessionHistoryEntry called with oldActiveEntry=%p, "
"mActiveEntry=%p, active entry list does%s contain the active entry. ",
oldActiveEntry.get(), mActiveEntry.get(),
- mActiveEntryList.contains(mActiveEntry) ? "" : "n't"));
+ activeEntries->contains(mActiveEntry) ? "" : "n't"));
if (Navigation::IsAPIEnabled() &&
(!oldActiveEntry || oldActiveEntry->isInList())) {
- RefPtr toRemove = oldActiveEntry ? oldActiveEntry->getNext()
- : mActiveEntryList.getFirst();
+ RefPtr toRemove =
+ oldActiveEntry ? oldActiveEntry->getNext() : activeEntries->getFirst();
while (toRemove) {
toRemove = toRemove->removeAndGetNext();
}
- mActiveEntryList.insertBack(mActiveEntry);
+ activeEntries->insertBack(mActiveEntry);
}
ResetSHEntryHasUserInteractionCache();
@@ -3714,15 +3718,25 @@ void CanonicalBrowsingContext::MaybeReconstructActiveEntryList() {
auto* shistory = static_cast<nsSHistory*>(GetSessionHistory());
if (mActiveEntry && !shistory->ContainsEntry(mActiveEntry)) {
- mActiveEntryList.clear();
- mActiveEntryList = shistory->ConstructContiguousEntryList();
+ shistory->ReconstructContiguousEntryList();
+ }
+}
+
+EntryList* CanonicalBrowsingContext::GetActiveEntries() {
+ if (!mActiveEntryList) {
+ auto* shistory = static_cast<nsSHistory*>(GetSessionHistory());
+ if (shistory) {
+ mActiveEntryList = shistory->EntryListFor(GetHistoryID());
+ }
}
+ return mActiveEntryList;
}
NS_IMPL_CYCLE_COLLECTION_CLASS(CanonicalBrowsingContext)
NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(CanonicalBrowsingContext,
BrowsingContext)
+ tmp->mActiveEntryList = nullptr;
tmp->mPermanentKey.setNull();
if (tmp->mSessionHistory) {
tmp->mSessionHistory->SetBrowsingContext(nullptr);
diff --git a/docshell/base/CanonicalBrowsingContext.h b/docshell/base/CanonicalBrowsingContext.h
@@ -10,6 +10,7 @@
#include "mozilla/dom/BrowsingContext.h"
#include "mozilla/dom/MediaControlKeySource.h"
#include "mozilla/dom/BrowsingContextWebProgress.h"
+#include "mozilla/dom/EntryList.h"
#include "mozilla/dom/FeaturePolicy.h"
#include "mozilla/dom/ProcessIsolation.h"
#include "mozilla/dom/Promise.h"
@@ -589,6 +590,8 @@ class CanonicalBrowsingContext final : public BrowsingContext {
void GetContiguousEntriesForLoad(LoadingSessionHistoryInfo& aLoadingInfo,
const RefPtr<SessionHistoryEntry>& aEntry);
+ EntryList* GetActiveEntries();
+
// XXX(farre): Store a ContentParent pointer here rather than mProcessId?
// Indicates which process owns the docshell.
uint64_t mProcessId;
@@ -629,7 +632,7 @@ class CanonicalBrowsingContext final : public BrowsingContext {
RefPtr<SessionHistoryEntry> mEntry;
};
nsTArray<LoadingSessionHistoryEntry> mLoadingEntries;
- LinkedList<SessionHistoryEntry> mActiveEntryList;
+ RefPtr<EntryList> mActiveEntryList;
RefPtr<SessionHistoryEntry> mActiveEntry;
RefPtr<nsSecureBrowserUI> mSecureBrowserUI;
diff --git a/docshell/shistory/EntryList.cpp b/docshell/shistory/EntryList.cpp
@@ -0,0 +1,25 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 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 "EntryList.h"
+
+#include "nsIWeakReferenceUtils.h"
+#include "nsSHistory.h"
+
+namespace mozilla::dom {
+
+EntryList::EntryList(nsSHistory* aSessionHistory, const nsID& aHistoryID)
+ : mSessionHistory(do_GetWeakReference(aSessionHistory)),
+ mHistoryID(aHistoryID) {}
+
+EntryList::~EntryList() {
+ clear();
+ if (nsCOMPtr<nsISHistory> shistory = do_QueryReferent(mSessionHistory)) {
+ static_cast<nsSHistory*>(shistory.get())->RemoveEntryList(mHistoryID);
+ }
+}
+
+} // namespace mozilla::dom
diff --git a/docshell/shistory/EntryList.h b/docshell/shistory/EntryList.h
@@ -0,0 +1,37 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 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/. */
+
+#ifndef mozilla_dom_EntryList_h
+#define mozilla_dom_EntryList_h
+
+#include "mozilla/LinkedList.h"
+#include "mozilla/RefCounted.h"
+#include "mozilla/WeakPtr.h"
+#include "nsID.h"
+
+class nsSHistory;
+
+namespace mozilla::dom {
+
+class SessionHistoryEntry;
+
+class EntryList final : public LinkedList<SessionHistoryEntry>,
+ public RefCounted<EntryList>,
+ public SupportsWeakPtr {
+ public:
+ MOZ_DECLARE_REFCOUNTED_TYPENAME(EntryList)
+
+ explicit EntryList(nsSHistory* aSessionHistory, const nsID& aHistoryID);
+ ~EntryList();
+
+ private:
+ nsCOMPtr<nsIWeakReference> mSessionHistory;
+ nsID mHistoryID;
+};
+
+} // namespace mozilla::dom
+
+#endif // !defined(mozilla_dom_EntryList_h)
diff --git a/docshell/shistory/moz.build b/docshell/shistory/moz.build
@@ -21,11 +21,13 @@ EXPORTS += [
EXPORTS.mozilla.dom += [
"ChildSHistory.h",
+ "EntryList.h",
"SessionHistoryEntry.h",
]
UNIFIED_SOURCES += [
"ChildSHistory.cpp",
+ "EntryList.cpp",
"nsSHEntry.cpp",
"nsSHEntryShared.cpp",
"nsSHistory.cpp",
diff --git a/docshell/shistory/nsSHistory.cpp b/docshell/shistory/nsSHistory.cpp
@@ -37,6 +37,7 @@
#include "mozilla/dom/CanonicalBrowsingContext.h"
#include "mozilla/dom/ContentParent.h"
#include "mozilla/dom/Element.h"
+#include "mozilla/dom/EntryList.h"
#include "mozilla/dom/Navigation.h"
#include "mozilla/dom/RemoteWebProgressRequest.h"
#include "mozilla/dom/WindowGlobalParent.h"
@@ -2500,35 +2501,25 @@ mozilla::dom::SessionHistoryEntry* nsSHistory::FindAdjacentContiguousEntryFor(
return nullptr;
}
-LinkedList<SessionHistoryEntry> nsSHistory::ConstructContiguousEntryListFrom(
+void nsSHistory::ReconstructContiguousEntryListFrom(
SessionHistoryEntry* aEntry) {
- if (aEntry->isInList()) {
- aEntry->remove();
- }
-
- LinkedList<SessionHistoryEntry> entryList;
- entryList.insertBack(aEntry);
+ RefPtr entryList = EntryListFor(aEntry->DocshellID());
+ entryList->clear();
+ entryList->insertBack(aEntry);
for (auto* entry = aEntry;
(entry = FindAdjacentContiguousEntryFor(entry, -1));) {
- if (entry->isInList()) {
- entry->remove();
- }
- entryList.insertFront(entry);
+ entryList->insertFront(entry);
}
for (auto* entry = aEntry;
(entry = FindAdjacentContiguousEntryFor(entry, 1));) {
- if (entry->isInList()) {
- entry->remove();
- }
- entryList.insertBack(entry);
+ entryList->insertBack(entry);
}
- return entryList;
}
-LinkedList<SessionHistoryEntry> nsSHistory::ConstructContiguousEntryList() {
+void nsSHistory::ReconstructContiguousEntryList() {
MOZ_ASSERT(mIndex >= 0 && mIndex < Length());
nsCOMPtr currentEntry = mEntries[mIndex];
- return ConstructContiguousEntryListFrom(
+ ReconstructContiguousEntryListFrom(
static_cast<SessionHistoryEntry*>(currentEntry.get()));
}
@@ -2747,3 +2738,17 @@ bool nsSHistory::ContainsEntry(nsISHEntry* aEntry) {
nsCOMPtr rootEntry = GetRootSHEntry(aEntry);
return GetIndexOfEntry(rootEntry) != -1;
}
+
+already_AddRefed<EntryList> nsSHistory::EntryListFor(const nsID& aID) {
+ return mEntryLists.WithEntryHandle(
+ aID, [self = RefPtr{this}, aID](auto&& entry) {
+ if (entry && *entry) {
+ return do_AddRef(entry->get());
+ }
+ RefPtr entryList = MakeRefPtr<EntryList>(self, aID);
+ entry.InsertOrUpdate(entryList);
+ return entryList.forget();
+ });
+}
+
+void nsSHistory::RemoveEntryList(const nsID& aID) { mEntryLists.Remove(aID); }
diff --git a/docshell/shistory/nsSHistory.h b/docshell/shistory/nsSHistory.h
@@ -27,8 +27,9 @@ class nsISHEntry;
namespace mozilla {
namespace dom {
+class EntryList;
class LoadSHEntryResult;
-}
+} // namespace dom
} // namespace mozilla
class nsSHistory : public mozilla::LinkedListElement<nsSHistory>,
@@ -219,10 +220,11 @@ class nsSHistory : public mozilla::LinkedListElement<nsSHistory>,
mozilla::dom::SessionHistoryEntry* FindAdjacentContiguousEntryFor(
mozilla::dom::SessionHistoryEntry* aEntry, int32_t aSearchDirection);
- mozilla::LinkedList<mozilla::dom::SessionHistoryEntry>
- ConstructContiguousEntryListFrom(mozilla::dom::SessionHistoryEntry* aEntry);
- mozilla::LinkedList<mozilla::dom::SessionHistoryEntry>
- ConstructContiguousEntryList();
+ void ReconstructContiguousEntryListFrom(
+ mozilla::dom::SessionHistoryEntry* aEntry);
+ void ReconstructContiguousEntryList();
+ already_AddRefed<mozilla::dom::EntryList> EntryListFor(const nsID& aID);
+ void RemoveEntryList(const nsID& aID);
bool ContainsEntry(nsISHEntry* aEntry);
@@ -320,6 +322,11 @@ class nsSHistory : public mozilla::LinkedListElement<nsSHistory>,
// update the epoch via a runnable on each ::Go (including AsyncGo).
uint64_t mEpoch = 0;
mozilla::Maybe<mozilla::dom::ContentParentId> mEpochParentId;
+
+ // Session history entries grouped by DocshellID, which are deduplicated by
+ // SessionHistoryEntry ID.
+ nsTHashMap<nsIDHashKey, mozilla::WeakPtr<mozilla::dom::EntryList>>
+ mEntryLists;
};
// CallerWillNotifyHistoryIndexAndLengthChanges is used to prevent