Bug 1888590 - Mark some subtests on trusted-types-event-handlers.html as failing...
[gecko.git] / docshell / shistory / nsSHistory.cpp
blobd69d78a9ec2d7985a6bd108b7535ce27d5b2f38f
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "nsSHistory.h"
9 #include <algorithm>
11 #include "nsContentUtils.h"
12 #include "nsCOMArray.h"
13 #include "nsComponentManagerUtils.h"
14 #include "nsDocShell.h"
15 #include "nsFrameLoaderOwner.h"
16 #include "nsHashKeys.h"
17 #include "nsIDocShell.h"
18 #include "nsIDocumentViewer.h"
19 #include "nsDocShellLoadState.h"
20 #include "nsIDocShellTreeItem.h"
21 #include "nsILayoutHistoryState.h"
22 #include "nsIObserverService.h"
23 #include "nsISHEntry.h"
24 #include "nsISHistoryListener.h"
25 #include "nsIURI.h"
26 #include "nsIXULRuntime.h"
27 #include "nsNetUtil.h"
28 #include "nsTHashMap.h"
29 #include "nsSHEntry.h"
30 #include "SessionHistoryEntry.h"
31 #include "nsTArray.h"
32 #include "prsystem.h"
34 #include "mozilla/Attributes.h"
35 #include "mozilla/dom/BrowsingContextGroup.h"
36 #include "mozilla/dom/CanonicalBrowsingContext.h"
37 #include "mozilla/dom/ContentParent.h"
38 #include "mozilla/dom/Element.h"
39 #include "mozilla/dom/RemoteWebProgressRequest.h"
40 #include "mozilla/dom/WindowGlobalParent.h"
41 #include "mozilla/LinkedList.h"
42 #include "mozilla/MathAlgorithms.h"
43 #include "mozilla/Preferences.h"
44 #include "mozilla/ProcessPriorityManager.h"
45 #include "mozilla/Services.h"
46 #include "mozilla/StaticPrefs_fission.h"
47 #include "mozilla/StaticPtr.h"
48 #include "mozilla/dom/CanonicalBrowsingContext.h"
49 #include "nsIWebNavigation.h"
50 #include "nsDocShellLoadTypes.h"
51 #include "base/process.h"
53 using namespace mozilla;
54 using namespace mozilla::dom;
56 #define PREF_SHISTORY_SIZE "browser.sessionhistory.max_entries"
57 #define PREF_SHISTORY_MAX_TOTAL_VIEWERS \
58 "browser.sessionhistory.max_total_viewers"
59 #define CONTENT_VIEWER_TIMEOUT_SECONDS \
60 "browser.sessionhistory.contentViewerTimeout"
61 // Observe fission.bfcacheInParent so that BFCache can be enabled/disabled when
62 // the pref is changed.
63 #define PREF_FISSION_BFCACHEINPARENT "fission.bfcacheInParent"
65 // Default this to time out unused content viewers after 30 minutes
66 #define CONTENT_VIEWER_TIMEOUT_SECONDS_DEFAULT (30 * 60)
68 static constexpr const char* kObservedPrefs[] = {
69 PREF_SHISTORY_SIZE, PREF_SHISTORY_MAX_TOTAL_VIEWERS,
70 PREF_FISSION_BFCACHEINPARENT, nullptr};
72 static int32_t gHistoryMaxSize = 50;
74 // List of all SHistory objects, used for content viewer cache eviction.
75 // When being destroyed, this helper removes everything from the list to avoid
76 // assertions when we leak.
77 struct ListHelper {
78 #ifdef DEBUG
79 ~ListHelper() { mList.clear(); }
80 #endif // DEBUG
82 LinkedList<nsSHistory> mList;
85 static ListHelper gSHistoryList;
86 // Max viewers allowed total, across all SHistory objects - negative default
87 // means we will calculate how many viewers to cache based on total memory
88 int32_t nsSHistory::sHistoryMaxTotalViewers = -1;
90 // A counter that is used to be able to know the order in which
91 // entries were touched, so that we can evict older entries first.
92 static uint32_t gTouchCounter = 0;
94 extern mozilla::LazyLogModule gSHLog;
96 LazyLogModule gSHistoryLog("nsSHistory");
98 #define LOG(format) MOZ_LOG(gSHistoryLog, mozilla::LogLevel::Debug, format)
100 extern mozilla::LazyLogModule gPageCacheLog;
101 extern mozilla::LazyLogModule gSHIPBFCacheLog;
103 // This macro makes it easier to print a log message which includes a URI's
104 // spec. Example use:
106 // nsIURI *uri = [...];
107 // LOG_SPEC(("The URI is %s.", _spec), uri);
109 #define LOG_SPEC(format, uri) \
110 PR_BEGIN_MACRO \
111 if (MOZ_LOG_TEST(gSHistoryLog, LogLevel::Debug)) { \
112 nsAutoCString _specStr("(null)"_ns); \
113 if (uri) { \
114 _specStr = uri->GetSpecOrDefault(); \
116 const char* _spec = _specStr.get(); \
117 LOG(format); \
119 PR_END_MACRO
121 // This macro makes it easy to log a message including an SHEntry's URI.
122 // For example:
124 // nsCOMPtr<nsISHEntry> shentry = [...];
125 // LOG_SHENTRY_SPEC(("shentry %p has uri %s.", shentry.get(), _spec), shentry);
127 #define LOG_SHENTRY_SPEC(format, shentry) \
128 PR_BEGIN_MACRO \
129 if (MOZ_LOG_TEST(gSHistoryLog, LogLevel::Debug)) { \
130 nsCOMPtr<nsIURI> uri = shentry->GetURI(); \
131 LOG_SPEC(format, uri); \
133 PR_END_MACRO
135 // Calls a F on all registered session history listeners.
136 template <typename F>
137 static void NotifyListeners(nsAutoTObserverArray<nsWeakPtr, 2>& aListeners,
138 F&& f) {
139 for (const nsWeakPtr& weakPtr : aListeners.EndLimitedRange()) {
140 nsCOMPtr<nsISHistoryListener> listener = do_QueryReferent(weakPtr);
141 if (listener) {
142 f(listener);
147 class MOZ_STACK_CLASS SHistoryChangeNotifier {
148 public:
149 explicit SHistoryChangeNotifier(nsSHistory* aHistory) {
150 // If we're already in an update, the outermost change notifier will
151 // update browsing context in the destructor.
152 if (!aHistory->HasOngoingUpdate()) {
153 aHistory->SetHasOngoingUpdate(true);
154 mSHistory = aHistory;
158 ~SHistoryChangeNotifier() {
159 if (mSHistory) {
160 MOZ_ASSERT(mSHistory->HasOngoingUpdate());
161 mSHistory->SetHasOngoingUpdate(false);
163 RefPtr<BrowsingContext> rootBC = mSHistory->GetBrowsingContext();
164 if (mozilla::SessionHistoryInParent() && rootBC) {
165 rootBC->Canonical()->HistoryCommitIndexAndLength();
170 RefPtr<nsSHistory> mSHistory;
173 enum HistCmd { HIST_CMD_GOTOINDEX, HIST_CMD_RELOAD };
175 class nsSHistoryObserver final : public nsIObserver {
176 public:
177 NS_DECL_ISUPPORTS
178 NS_DECL_NSIOBSERVER
180 nsSHistoryObserver() {}
182 static void PrefChanged(const char* aPref, void* aSelf);
183 void PrefChanged(const char* aPref);
185 protected:
186 ~nsSHistoryObserver() {}
189 StaticRefPtr<nsSHistoryObserver> gObserver;
191 NS_IMPL_ISUPPORTS(nsSHistoryObserver, nsIObserver)
193 // static
194 void nsSHistoryObserver::PrefChanged(const char* aPref, void* aSelf) {
195 static_cast<nsSHistoryObserver*>(aSelf)->PrefChanged(aPref);
198 void nsSHistoryObserver::PrefChanged(const char* aPref) {
199 nsSHistory::UpdatePrefs();
200 nsSHistory::GloballyEvictDocumentViewers();
203 NS_IMETHODIMP
204 nsSHistoryObserver::Observe(nsISupports* aSubject, const char* aTopic,
205 const char16_t* aData) {
206 if (!strcmp(aTopic, "cacheservice:empty-cache") ||
207 !strcmp(aTopic, "memory-pressure")) {
208 nsSHistory::GloballyEvictAllDocumentViewers();
211 return NS_OK;
214 void nsSHistory::EvictDocumentViewerForEntry(nsISHEntry* aEntry) {
215 nsCOMPtr<nsIDocumentViewer> viewer = aEntry->GetDocumentViewer();
216 if (viewer) {
217 LOG_SHENTRY_SPEC(("Evicting content viewer 0x%p for "
218 "owning SHEntry 0x%p at %s.",
219 viewer.get(), aEntry, _spec),
220 aEntry);
222 // Drop the presentation state before destroying the viewer, so that
223 // document teardown is able to correctly persist the state.
224 NotifyListenersDocumentViewerEvicted(1);
225 aEntry->SetDocumentViewer(nullptr);
226 aEntry->SyncPresentationState();
227 viewer->Destroy();
228 } else if (nsCOMPtr<SessionHistoryEntry> she = do_QueryInterface(aEntry)) {
229 if (RefPtr<nsFrameLoader> frameLoader = she->GetFrameLoader()) {
230 nsCOMPtr<nsFrameLoaderOwner> owner =
231 do_QueryInterface(frameLoader->GetOwnerContent());
232 RefPtr<nsFrameLoader> currentFrameLoader;
233 if (owner) {
234 currentFrameLoader = owner->GetFrameLoader();
237 // Only destroy non-current frameloader when evicting from the bfcache.
238 if (currentFrameLoader != frameLoader) {
239 MOZ_LOG(gSHIPBFCacheLog, LogLevel::Debug,
240 ("nsSHistory::EvictDocumentViewerForEntry "
241 "destroying an nsFrameLoader."));
242 NotifyListenersDocumentViewerEvicted(1);
243 she->SetFrameLoader(nullptr);
244 frameLoader->Destroy();
249 // When dropping bfcache, we have to remove associated dynamic entries as
250 // well.
251 int32_t index = GetIndexOfEntry(aEntry);
252 if (index != -1) {
253 RemoveDynEntries(index, aEntry);
257 nsSHistory::nsSHistory(BrowsingContext* aRootBC)
258 : mRootBC(aRootBC->Id()),
259 mHasOngoingUpdate(false),
260 mIndex(-1),
261 mRequestedIndex(-1),
262 mRootDocShellID(aRootBC->GetHistoryID()) {
263 static bool sCalledStartup = false;
264 if (!sCalledStartup) {
265 Startup();
266 sCalledStartup = true;
269 // Add this new SHistory object to the list
270 gSHistoryList.mList.insertBack(this);
272 // Init mHistoryTracker on setting mRootBC so we can bind its event
273 // target to the tabGroup.
274 mHistoryTracker = mozilla::MakeUnique<HistoryTracker>(
275 this,
276 mozilla::Preferences::GetUint(CONTENT_VIEWER_TIMEOUT_SECONDS,
277 CONTENT_VIEWER_TIMEOUT_SECONDS_DEFAULT),
278 GetCurrentSerialEventTarget());
281 nsSHistory::~nsSHistory() {
282 // Clear mEntries explicitly here so that the destructor of the entries
283 // can still access nsSHistory in a reasonable way.
284 mEntries.Clear();
287 NS_IMPL_ADDREF(nsSHistory)
288 NS_IMPL_RELEASE(nsSHistory)
290 NS_INTERFACE_MAP_BEGIN(nsSHistory)
291 NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsISHistory)
292 NS_INTERFACE_MAP_ENTRY(nsISHistory)
293 NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
294 NS_INTERFACE_MAP_END
296 // static
297 uint32_t nsSHistory::CalcMaxTotalViewers() {
298 // This value allows tweaking how fast the allowed amount of content viewers
299 // grows with increasing amounts of memory. Larger values mean slower growth.
300 #ifdef ANDROID
301 # define MAX_TOTAL_VIEWERS_BIAS 15.9
302 #else
303 # define MAX_TOTAL_VIEWERS_BIAS 14
304 #endif
306 // Calculate an estimate of how many ContentViewers we should cache based
307 // on RAM. This assumes that the average ContentViewer is 4MB (conservative)
308 // and caps the max at 8 ContentViewers
310 // TODO: Should we split the cache memory betw. ContentViewer caching and
311 // nsCacheService?
313 // RAM | ContentViewers | on Android
314 // -------------------------------------
315 // 32 Mb 0 0
316 // 64 Mb 1 0
317 // 128 Mb 2 0
318 // 256 Mb 3 1
319 // 512 Mb 5 2
320 // 768 Mb 6 2
321 // 1024 Mb 8 3
322 // 2048 Mb 8 5
323 // 3072 Mb 8 7
324 // 4096 Mb 8 8
325 uint64_t bytes = PR_GetPhysicalMemorySize();
327 if (bytes == 0) {
328 return 0;
331 // Conversion from unsigned int64_t to double doesn't work on all platforms.
332 // We need to truncate the value at INT64_MAX to make sure we don't
333 // overflow.
334 if (bytes > INT64_MAX) {
335 bytes = INT64_MAX;
338 double kBytesD = (double)(bytes >> 10);
340 // This is essentially the same calculation as for nsCacheService,
341 // except that we divide the final memory calculation by 4, since
342 // we assume each ContentViewer takes on average 4MB
343 uint32_t viewers = 0;
344 double x = std::log(kBytesD) / std::log(2.0) - MAX_TOTAL_VIEWERS_BIAS;
345 if (x > 0) {
346 viewers = (uint32_t)(x * x - x + 2.001); // add .001 for rounding
347 viewers /= 4;
350 // Cap it off at 8 max
351 if (viewers > 8) {
352 viewers = 8;
354 return viewers;
357 // static
358 void nsSHistory::UpdatePrefs() {
359 Preferences::GetInt(PREF_SHISTORY_SIZE, &gHistoryMaxSize);
360 if (mozilla::SessionHistoryInParent() && !mozilla::BFCacheInParent()) {
361 sHistoryMaxTotalViewers = 0;
362 return;
365 Preferences::GetInt(PREF_SHISTORY_MAX_TOTAL_VIEWERS,
366 &sHistoryMaxTotalViewers);
367 // If the pref is negative, that means we calculate how many viewers
368 // we think we should cache, based on total memory
369 if (sHistoryMaxTotalViewers < 0) {
370 sHistoryMaxTotalViewers = CalcMaxTotalViewers();
374 // static
375 nsresult nsSHistory::Startup() {
376 UpdatePrefs();
378 // The goal of this is to unbreak users who have inadvertently set their
379 // session history size to less than the default value.
380 int32_t defaultHistoryMaxSize =
381 Preferences::GetInt(PREF_SHISTORY_SIZE, 50, PrefValueKind::Default);
382 if (gHistoryMaxSize < defaultHistoryMaxSize) {
383 gHistoryMaxSize = defaultHistoryMaxSize;
386 // Allow the user to override the max total number of cached viewers,
387 // but keep the per SHistory cached viewer limit constant
388 if (!gObserver) {
389 gObserver = new nsSHistoryObserver();
390 Preferences::RegisterCallbacks(nsSHistoryObserver::PrefChanged,
391 kObservedPrefs, gObserver.get());
393 nsCOMPtr<nsIObserverService> obsSvc =
394 mozilla::services::GetObserverService();
395 if (obsSvc) {
396 // Observe empty-cache notifications so tahat clearing the disk/memory
397 // cache will also evict all content viewers.
398 obsSvc->AddObserver(gObserver, "cacheservice:empty-cache", false);
400 // Same for memory-pressure notifications
401 obsSvc->AddObserver(gObserver, "memory-pressure", false);
405 return NS_OK;
408 // static
409 void nsSHistory::Shutdown() {
410 if (gObserver) {
411 Preferences::UnregisterCallbacks(nsSHistoryObserver::PrefChanged,
412 kObservedPrefs, gObserver.get());
414 nsCOMPtr<nsIObserverService> obsSvc =
415 mozilla::services::GetObserverService();
416 if (obsSvc) {
417 obsSvc->RemoveObserver(gObserver, "cacheservice:empty-cache");
418 obsSvc->RemoveObserver(gObserver, "memory-pressure");
420 gObserver = nullptr;
424 // static
425 already_AddRefed<nsISHEntry> nsSHistory::GetRootSHEntry(nsISHEntry* aEntry) {
426 nsCOMPtr<nsISHEntry> rootEntry = aEntry;
427 nsCOMPtr<nsISHEntry> result = nullptr;
428 while (rootEntry) {
429 result = rootEntry;
430 rootEntry = result->GetParent();
433 return result.forget();
436 // static
437 nsresult nsSHistory::WalkHistoryEntries(nsISHEntry* aRootEntry,
438 BrowsingContext* aBC,
439 WalkHistoryEntriesFunc aCallback,
440 void* aData) {
441 NS_ENSURE_TRUE(aRootEntry, NS_ERROR_FAILURE);
443 int32_t childCount = aRootEntry->GetChildCount();
444 for (int32_t i = 0; i < childCount; i++) {
445 nsCOMPtr<nsISHEntry> childEntry;
446 aRootEntry->GetChildAt(i, getter_AddRefs(childEntry));
447 if (!childEntry) {
448 // childEntry can be null for valid reasons, for example if the
449 // docshell at index i never loaded anything useful.
450 // Remember to clone also nulls in the child array (bug 464064).
451 aCallback(nullptr, nullptr, i, aData);
452 continue;
455 BrowsingContext* childBC = nullptr;
456 if (aBC) {
457 for (BrowsingContext* child : aBC->Children()) {
458 // If the SH pref is on and we are in the parent process, update
459 // canonical BC directly
460 bool foundChild = false;
461 if (mozilla::SessionHistoryInParent() && XRE_IsParentProcess()) {
462 if (child->Canonical()->HasHistoryEntry(childEntry)) {
463 childBC = child;
464 foundChild = true;
468 nsDocShell* docshell = static_cast<nsDocShell*>(child->GetDocShell());
469 if (docshell && docshell->HasHistoryEntry(childEntry)) {
470 childBC = docshell->GetBrowsingContext();
471 foundChild = true;
474 // XXX Simplify this once the old and new session history
475 // implementations don't run at the same time.
476 if (foundChild) {
477 break;
482 nsresult rv = aCallback(childEntry, childBC, i, aData);
483 NS_ENSURE_SUCCESS(rv, rv);
486 return NS_OK;
489 // callback data for WalkHistoryEntries
490 struct MOZ_STACK_CLASS CloneAndReplaceData {
491 CloneAndReplaceData(uint32_t aCloneID, nsISHEntry* aReplaceEntry,
492 bool aCloneChildren, nsISHEntry* aDestTreeParent)
493 : cloneID(aCloneID),
494 cloneChildren(aCloneChildren),
495 replaceEntry(aReplaceEntry),
496 destTreeParent(aDestTreeParent) {}
498 uint32_t cloneID;
499 bool cloneChildren;
500 nsISHEntry* replaceEntry;
501 nsISHEntry* destTreeParent;
502 nsCOMPtr<nsISHEntry> resultEntry;
505 nsresult nsSHistory::CloneAndReplaceChild(nsISHEntry* aEntry,
506 BrowsingContext* aOwnerBC,
507 int32_t aChildIndex, void* aData) {
508 nsCOMPtr<nsISHEntry> dest;
510 CloneAndReplaceData* data = static_cast<CloneAndReplaceData*>(aData);
511 uint32_t cloneID = data->cloneID;
512 nsISHEntry* replaceEntry = data->replaceEntry;
514 if (!aEntry) {
515 if (data->destTreeParent) {
516 data->destTreeParent->AddChild(nullptr, aChildIndex);
518 return NS_OK;
521 uint32_t srcID = aEntry->GetID();
523 nsresult rv = NS_OK;
524 if (srcID == cloneID) {
525 // Replace the entry
526 dest = replaceEntry;
527 } else {
528 // Clone the SHEntry...
529 rv = aEntry->Clone(getter_AddRefs(dest));
530 NS_ENSURE_SUCCESS(rv, rv);
532 dest->SetIsSubFrame(true);
534 if (srcID != cloneID || data->cloneChildren) {
535 // Walk the children
536 CloneAndReplaceData childData(cloneID, replaceEntry, data->cloneChildren,
537 dest);
538 rv = nsSHistory::WalkHistoryEntries(aEntry, aOwnerBC, CloneAndReplaceChild,
539 &childData);
540 NS_ENSURE_SUCCESS(rv, rv);
543 if (srcID != cloneID && aOwnerBC) {
544 nsSHistory::HandleEntriesToSwapInDocShell(aOwnerBC, aEntry, dest);
547 if (data->destTreeParent) {
548 data->destTreeParent->AddChild(dest, aChildIndex);
550 data->resultEntry = dest;
551 return rv;
554 // static
555 nsresult nsSHistory::CloneAndReplace(
556 nsISHEntry* aSrcEntry, BrowsingContext* aOwnerBC, uint32_t aCloneID,
557 nsISHEntry* aReplaceEntry, bool aCloneChildren, nsISHEntry** aDestEntry) {
558 NS_ENSURE_ARG_POINTER(aDestEntry);
559 NS_ENSURE_TRUE(aReplaceEntry, NS_ERROR_FAILURE);
560 CloneAndReplaceData data(aCloneID, aReplaceEntry, aCloneChildren, nullptr);
561 nsresult rv = CloneAndReplaceChild(aSrcEntry, aOwnerBC, 0, &data);
562 data.resultEntry.swap(*aDestEntry);
563 return rv;
566 // static
567 void nsSHistory::WalkContiguousEntries(
568 nsISHEntry* aEntry, const std::function<void(nsISHEntry*)>& aCallback) {
569 MOZ_ASSERT(aEntry);
571 nsCOMPtr<nsISHistory> shistory = aEntry->GetShistory();
572 if (!shistory) {
573 // If there is no session history in the entry, it means this is not a root
574 // entry. So, we can return from here.
575 return;
578 int32_t index = shistory->GetIndexOfEntry(aEntry);
579 int32_t count = shistory->GetCount();
581 nsCOMPtr<nsIURI> targetURI = aEntry->GetURI();
583 // First, call the callback on the input entry.
584 aCallback(aEntry);
586 // Walk backward to find the entries that have the same origin as the
587 // input entry.
588 for (int32_t i = index - 1; i >= 0; i--) {
589 RefPtr<nsISHEntry> entry;
590 shistory->GetEntryAtIndex(i, getter_AddRefs(entry));
591 if (entry) {
592 nsCOMPtr<nsIURI> uri = entry->GetURI();
593 if (NS_FAILED(nsContentUtils::GetSecurityManager()->CheckSameOriginURI(
594 targetURI, uri, false, false))) {
595 break;
598 aCallback(entry);
602 // Then, Walk forward.
603 for (int32_t i = index + 1; i < count; i++) {
604 RefPtr<nsISHEntry> entry;
605 shistory->GetEntryAtIndex(i, getter_AddRefs(entry));
606 if (entry) {
607 nsCOMPtr<nsIURI> uri = entry->GetURI();
608 if (NS_FAILED(nsContentUtils::GetSecurityManager()->CheckSameOriginURI(
609 targetURI, uri, false, false))) {
610 break;
613 aCallback(entry);
618 NS_IMETHODIMP
619 nsSHistory::AddChildSHEntryHelper(nsISHEntry* aCloneRef, nsISHEntry* aNewEntry,
620 BrowsingContext* aRootBC,
621 bool aCloneChildren) {
622 MOZ_ASSERT(aRootBC->IsTop());
624 /* You are currently in the rootDocShell.
625 * You will get here when a subframe has a new url
626 * to load and you have walked up the tree all the
627 * way to the top to clone the current SHEntry hierarchy
628 * and replace the subframe where a new url was loaded with
629 * a new entry.
631 nsCOMPtr<nsISHEntry> child;
632 nsCOMPtr<nsISHEntry> currentHE;
633 int32_t index = mIndex;
634 if (index < 0) {
635 return NS_ERROR_FAILURE;
638 GetEntryAtIndex(index, getter_AddRefs(currentHE));
639 NS_ENSURE_TRUE(currentHE, NS_ERROR_FAILURE);
641 nsresult rv = NS_OK;
642 uint32_t cloneID = aCloneRef->GetID();
643 rv = nsSHistory::CloneAndReplace(currentHE, aRootBC, cloneID, aNewEntry,
644 aCloneChildren, getter_AddRefs(child));
646 if (NS_SUCCEEDED(rv)) {
647 rv = AddEntry(child, true);
648 if (NS_SUCCEEDED(rv)) {
649 child->SetDocshellID(aRootBC->GetHistoryID());
653 return rv;
656 nsresult nsSHistory::SetChildHistoryEntry(nsISHEntry* aEntry,
657 BrowsingContext* aBC,
658 int32_t aEntryIndex, void* aData) {
659 SwapEntriesData* data = static_cast<SwapEntriesData*>(aData);
660 if (!aBC || aBC == data->ignoreBC) {
661 return NS_OK;
664 nsISHEntry* destTreeRoot = data->destTreeRoot;
666 nsCOMPtr<nsISHEntry> destEntry;
668 if (data->destTreeParent) {
669 // aEntry is a clone of some child of destTreeParent, but since the
670 // trees aren't necessarily in sync, we'll have to locate it.
671 // Note that we could set aShell's entry to null if we don't find a
672 // corresponding entry under destTreeParent.
674 uint32_t targetID = aEntry->GetID();
676 // First look at the given index, since this is the common case.
677 nsCOMPtr<nsISHEntry> entry;
678 data->destTreeParent->GetChildAt(aEntryIndex, getter_AddRefs(entry));
679 if (entry && entry->GetID() == targetID) {
680 destEntry.swap(entry);
681 } else {
682 int32_t childCount;
683 data->destTreeParent->GetChildCount(&childCount);
684 for (int32_t i = 0; i < childCount; ++i) {
685 data->destTreeParent->GetChildAt(i, getter_AddRefs(entry));
686 if (!entry) {
687 continue;
690 if (entry->GetID() == targetID) {
691 destEntry.swap(entry);
692 break;
696 } else {
697 destEntry = destTreeRoot;
700 nsSHistory::HandleEntriesToSwapInDocShell(aBC, aEntry, destEntry);
701 // Now handle the children of aEntry.
702 SwapEntriesData childData = {data->ignoreBC, destTreeRoot, destEntry};
703 return nsSHistory::WalkHistoryEntries(aEntry, aBC, SetChildHistoryEntry,
704 &childData);
707 // static
708 void nsSHistory::HandleEntriesToSwapInDocShell(
709 mozilla::dom::BrowsingContext* aBC, nsISHEntry* aOldEntry,
710 nsISHEntry* aNewEntry) {
711 bool shPref = mozilla::SessionHistoryInParent();
712 if (aBC->IsInProcess() || !shPref) {
713 nsDocShell* docshell = static_cast<nsDocShell*>(aBC->GetDocShell());
714 if (docshell) {
715 docshell->SwapHistoryEntries(aOldEntry, aNewEntry);
717 } else {
718 // FIXME Bug 1633988: Need to update entries?
721 // XXX Simplify this once the old and new session history implementations
722 // don't run at the same time.
723 if (shPref && XRE_IsParentProcess()) {
724 aBC->Canonical()->SwapHistoryEntries(aOldEntry, aNewEntry);
728 void nsSHistory::UpdateRootBrowsingContextState(BrowsingContext* aRootBC) {
729 if (aRootBC && aRootBC->EverAttached()) {
730 bool sameDocument = IsEmptyOrHasEntriesForSingleTopLevelPage();
731 if (sameDocument != aRootBC->GetIsSingleToplevelInHistory()) {
732 // If the browsing context is discarded then its session history is
733 // invalid and will go away.
734 Unused << aRootBC->SetIsSingleToplevelInHistory(sameDocument);
739 NS_IMETHODIMP
740 nsSHistory::AddToRootSessionHistory(bool aCloneChildren, nsISHEntry* aOSHE,
741 BrowsingContext* aRootBC,
742 nsISHEntry* aEntry, uint32_t aLoadType,
743 bool aShouldPersist,
744 Maybe<int32_t>* aPreviousEntryIndex,
745 Maybe<int32_t>* aLoadedEntryIndex) {
746 MOZ_ASSERT(aRootBC->IsTop());
748 nsresult rv = NS_OK;
750 // If we need to clone our children onto the new session
751 // history entry, do so now.
752 if (aCloneChildren && aOSHE) {
753 uint32_t cloneID = aOSHE->GetID();
754 nsCOMPtr<nsISHEntry> newEntry;
755 nsSHistory::CloneAndReplace(aOSHE, aRootBC, cloneID, aEntry, true,
756 getter_AddRefs(newEntry));
757 NS_ASSERTION(aEntry == newEntry,
758 "The new session history should be in the new entry");
760 // This is the root docshell
761 bool addToSHistory = !LOAD_TYPE_HAS_FLAGS(
762 aLoadType, nsIWebNavigation::LOAD_FLAGS_REPLACE_HISTORY);
763 if (!addToSHistory) {
764 // Replace current entry in session history; If the requested index is
765 // valid, it indicates the loading was triggered by a history load, and
766 // we should replace the entry at requested index instead.
767 int32_t index = GetIndexForReplace();
769 // Replace the current entry with the new entry
770 if (index >= 0) {
771 rv = ReplaceEntry(index, aEntry);
772 } else {
773 // If we're trying to replace an inexistant shistory entry, append.
774 addToSHistory = true;
777 if (addToSHistory) {
778 // Add to session history
779 *aPreviousEntryIndex = Some(mIndex);
780 rv = AddEntry(aEntry, aShouldPersist);
781 *aLoadedEntryIndex = Some(mIndex);
782 MOZ_LOG(gPageCacheLog, LogLevel::Verbose,
783 ("Previous index: %d, Loaded index: %d",
784 aPreviousEntryIndex->value(), aLoadedEntryIndex->value()));
786 if (NS_SUCCEEDED(rv)) {
787 aEntry->SetDocshellID(aRootBC->GetHistoryID());
789 return rv;
792 /* Add an entry to the History list at mIndex and
793 * increment the index to point to the new entry
795 NS_IMETHODIMP
796 nsSHistory::AddEntry(nsISHEntry* aSHEntry, bool aPersist) {
797 NS_ENSURE_ARG(aSHEntry);
799 nsCOMPtr<nsISHistory> shistoryOfEntry = aSHEntry->GetShistory();
800 if (shistoryOfEntry && shistoryOfEntry != this) {
801 NS_WARNING(
802 "The entry has been associated to another nsISHistory instance. "
803 "Try nsISHEntry.clone() and nsISHEntry.abandonBFCacheEntry() "
804 "first if you're copying an entry from another nsISHistory.");
805 return NS_ERROR_FAILURE;
808 aSHEntry->SetShistory(this);
810 // If we have a root docshell, update the docshell id of the root shentry to
811 // match the id of that docshell
812 RefPtr<BrowsingContext> rootBC = GetBrowsingContext();
813 if (rootBC) {
814 aSHEntry->SetDocshellID(mRootDocShellID);
817 if (mIndex >= 0) {
818 MOZ_ASSERT(mIndex < Length(), "Index out of range!");
819 if (mIndex >= Length()) {
820 return NS_ERROR_FAILURE;
823 if (mEntries[mIndex] && !mEntries[mIndex]->GetPersist()) {
824 NotifyListeners(mListeners, [](auto l) { l->OnHistoryReplaceEntry(); });
825 aSHEntry->SetPersist(aPersist);
826 mEntries[mIndex] = aSHEntry;
827 UpdateRootBrowsingContextState();
828 return NS_OK;
831 SHistoryChangeNotifier change(this);
833 int32_t truncating = Length() - 1 - mIndex;
834 if (truncating > 0) {
835 NotifyListeners(mListeners,
836 [truncating](auto l) { l->OnHistoryTruncate(truncating); });
839 nsCOMPtr<nsIURI> uri = aSHEntry->GetURI();
840 NotifyListeners(mListeners,
841 [&uri, this](auto l) { l->OnHistoryNewEntry(uri, mIndex); });
843 // Remove all entries after the current one, add the new one, and set the
844 // new one as the current one.
845 MOZ_ASSERT(mIndex >= -1);
846 aSHEntry->SetPersist(aPersist);
847 mEntries.TruncateLength(mIndex + 1);
848 mEntries.AppendElement(aSHEntry);
849 mIndex++;
850 if (mIndex > 0) {
851 UpdateEntryLength(mEntries[mIndex - 1], mEntries[mIndex], false);
854 // Purge History list if it is too long
855 if (gHistoryMaxSize >= 0 && Length() > gHistoryMaxSize) {
856 PurgeHistory(Length() - gHistoryMaxSize);
859 UpdateRootBrowsingContextState();
861 return NS_OK;
864 void nsSHistory::NotifyOnHistoryReplaceEntry() {
865 NotifyListeners(mListeners, [](auto l) { l->OnHistoryReplaceEntry(); });
868 /* Get size of the history list */
869 NS_IMETHODIMP
870 nsSHistory::GetCount(int32_t* aResult) {
871 MOZ_ASSERT(aResult, "null out param?");
872 *aResult = Length();
873 return NS_OK;
876 NS_IMETHODIMP
877 nsSHistory::GetIndex(int32_t* aResult) {
878 MOZ_ASSERT(aResult, "null out param?");
879 *aResult = mIndex;
880 return NS_OK;
883 NS_IMETHODIMP
884 nsSHistory::SetIndex(int32_t aIndex) {
885 if (aIndex < 0 || aIndex >= Length()) {
886 return NS_ERROR_FAILURE;
889 mIndex = aIndex;
890 return NS_OK;
893 /* Get the requestedIndex */
894 NS_IMETHODIMP
895 nsSHistory::GetRequestedIndex(int32_t* aResult) {
896 MOZ_ASSERT(aResult, "null out param?");
897 *aResult = mRequestedIndex;
898 return NS_OK;
901 NS_IMETHODIMP_(void)
902 nsSHistory::InternalSetRequestedIndex(int32_t aRequestedIndex) {
903 MOZ_ASSERT(aRequestedIndex >= -1 && aRequestedIndex < Length());
904 mRequestedIndex = aRequestedIndex;
907 NS_IMETHODIMP
908 nsSHistory::GetEntryAtIndex(int32_t aIndex, nsISHEntry** aResult) {
909 NS_ENSURE_ARG_POINTER(aResult);
911 if (aIndex < 0 || aIndex >= Length()) {
912 return NS_ERROR_FAILURE;
915 *aResult = mEntries[aIndex];
916 NS_ADDREF(*aResult);
917 return NS_OK;
920 NS_IMETHODIMP_(int32_t)
921 nsSHistory::GetIndexOfEntry(nsISHEntry* aSHEntry) {
922 for (int32_t i = 0; i < Length(); i++) {
923 if (aSHEntry == mEntries[i]) {
924 return i;
928 return -1;
931 static void LogEntry(nsISHEntry* aEntry, int32_t aIndex, int32_t aTotal,
932 const nsCString& aPrefix, bool aIsCurrent) {
933 if (!aEntry) {
934 MOZ_LOG(gSHLog, LogLevel::Debug,
935 (" %s+- %i SH Entry null\n", aPrefix.get(), aIndex));
936 return;
939 nsCOMPtr<nsIURI> uri = aEntry->GetURI();
940 nsAutoString title, name;
941 aEntry->GetTitle(title);
942 aEntry->GetName(name);
944 SHEntrySharedParentState* shared;
945 if (mozilla::SessionHistoryInParent()) {
946 shared = static_cast<SessionHistoryEntry*>(aEntry)->SharedInfo();
947 } else {
948 shared = static_cast<nsSHEntry*>(aEntry)->GetState();
951 nsID docShellId;
952 aEntry->GetDocshellID(docShellId);
954 int32_t childCount = aEntry->GetChildCount();
956 MOZ_LOG(gSHLog, LogLevel::Debug,
957 ("%s%s+- %i SH Entry %p %" PRIu64 " %s\n", aIsCurrent ? ">" : " ",
958 aPrefix.get(), aIndex, aEntry, shared->GetId(),
959 nsIDToCString(docShellId).get()));
961 nsCString prefix(aPrefix);
962 if (aIndex < aTotal - 1) {
963 prefix.AppendLiteral("| ");
964 } else {
965 prefix.AppendLiteral(" ");
968 MOZ_LOG(gSHLog, LogLevel::Debug,
969 (" %s%s URL = %s\n", prefix.get(), childCount > 0 ? "|" : " ",
970 uri->GetSpecOrDefault().get()));
971 MOZ_LOG(gSHLog, LogLevel::Debug,
972 (" %s%s Title = %s\n", prefix.get(), childCount > 0 ? "|" : " ",
973 NS_LossyConvertUTF16toASCII(title).get()));
974 MOZ_LOG(gSHLog, LogLevel::Debug,
975 (" %s%s Name = %s\n", prefix.get(), childCount > 0 ? "|" : " ",
976 NS_LossyConvertUTF16toASCII(name).get()));
977 MOZ_LOG(
978 gSHLog, LogLevel::Debug,
979 (" %s%s Is in BFCache = %s\n", prefix.get(), childCount > 0 ? "|" : " ",
980 aEntry->GetIsInBFCache() ? "true" : "false"));
982 nsCOMPtr<nsISHEntry> prevChild;
983 for (int32_t i = 0; i < childCount; ++i) {
984 nsCOMPtr<nsISHEntry> child;
985 aEntry->GetChildAt(i, getter_AddRefs(child));
986 LogEntry(child, i, childCount, prefix, false);
987 child.swap(prevChild);
991 void nsSHistory::LogHistory() {
992 if (!MOZ_LOG_TEST(gSHLog, LogLevel::Debug)) {
993 return;
996 MOZ_LOG(gSHLog, LogLevel::Debug, ("nsSHistory %p\n", this));
997 int32_t length = Length();
998 for (int32_t i = 0; i < length; i++) {
999 LogEntry(mEntries[i], i, length, EmptyCString(), i == mIndex);
1003 void nsSHistory::WindowIndices(int32_t aIndex, int32_t* aOutStartIndex,
1004 int32_t* aOutEndIndex) {
1005 *aOutStartIndex = std::max(0, aIndex - nsSHistory::VIEWER_WINDOW);
1006 *aOutEndIndex = std::min(Length() - 1, aIndex + nsSHistory::VIEWER_WINDOW);
1009 static void MarkAsInitialEntry(
1010 SessionHistoryEntry* aEntry,
1011 nsTHashMap<nsIDHashKey, SessionHistoryEntry*>& aHashtable) {
1012 if (!aEntry->BCHistoryLength().Modified()) {
1013 ++(aEntry->BCHistoryLength());
1015 aHashtable.InsertOrUpdate(aEntry->DocshellID(), aEntry);
1016 for (const RefPtr<SessionHistoryEntry>& entry : aEntry->Children()) {
1017 if (entry) {
1018 MarkAsInitialEntry(entry, aHashtable);
1023 static void ClearEntries(SessionHistoryEntry* aEntry) {
1024 aEntry->ClearBCHistoryLength();
1025 for (const RefPtr<SessionHistoryEntry>& entry : aEntry->Children()) {
1026 if (entry) {
1027 ClearEntries(entry);
1032 NS_IMETHODIMP
1033 nsSHistory::PurgeHistory(int32_t aNumEntries) {
1034 if (Length() <= 0 || aNumEntries <= 0) {
1035 return NS_ERROR_FAILURE;
1038 SHistoryChangeNotifier change(this);
1040 aNumEntries = std::min(aNumEntries, Length());
1042 NotifyListeners(mListeners,
1043 [aNumEntries](auto l) { l->OnHistoryPurge(aNumEntries); });
1045 // Set all the entries hanging of the first entry that we keep
1046 // (mEntries[aNumEntries]) as being created as the result of a load
1047 // (so contributing one to their BCHistoryLength).
1048 nsTHashMap<nsIDHashKey, SessionHistoryEntry*> docshellIDToEntry;
1049 if (aNumEntries != Length()) {
1050 nsCOMPtr<SessionHistoryEntry> she =
1051 do_QueryInterface(mEntries[aNumEntries]);
1052 if (she) {
1053 MarkAsInitialEntry(she, docshellIDToEntry);
1057 // Reset the BCHistoryLength of all the entries that we're removing to a new
1058 // counter with value 0 while decreasing their contribution to a shared
1059 // BCHistoryLength. The end result is that they don't contribute to the
1060 // BCHistoryLength of any other entry anymore.
1061 for (int32_t i = 0; i < aNumEntries; ++i) {
1062 nsCOMPtr<SessionHistoryEntry> she = do_QueryInterface(mEntries[i]);
1063 if (she) {
1064 ClearEntries(she);
1068 RefPtr<BrowsingContext> rootBC = GetBrowsingContext();
1069 if (rootBC) {
1070 rootBC->PreOrderWalk([&docshellIDToEntry](BrowsingContext* aBC) {
1071 SessionHistoryEntry* entry = docshellIDToEntry.Get(aBC->GetHistoryID());
1072 Unused << aBC->SetHistoryEntryCount(
1073 entry ? uint32_t(entry->BCHistoryLength()) : 0);
1077 // Remove the first `aNumEntries` entries.
1078 mEntries.RemoveElementsAt(0, aNumEntries);
1080 // Adjust the indices, but don't let them go below -1.
1081 mIndex -= aNumEntries;
1082 mIndex = std::max(mIndex, -1);
1083 mRequestedIndex -= aNumEntries;
1084 mRequestedIndex = std::max(mRequestedIndex, -1);
1086 if (rootBC && rootBC->GetDocShell()) {
1087 rootBC->GetDocShell()->HistoryPurged(aNumEntries);
1090 UpdateRootBrowsingContextState(rootBC);
1092 return NS_OK;
1095 NS_IMETHODIMP
1096 nsSHistory::AddSHistoryListener(nsISHistoryListener* aListener) {
1097 NS_ENSURE_ARG_POINTER(aListener);
1099 // Check if the listener supports Weak Reference. This is a must.
1100 // This listener functionality is used by embedders and we want to
1101 // have the right ownership with who ever listens to SHistory
1102 nsWeakPtr listener = do_GetWeakReference(aListener);
1103 if (!listener) {
1104 return NS_ERROR_FAILURE;
1107 mListeners.AppendElementUnlessExists(listener);
1108 return NS_OK;
1111 void nsSHistory::NotifyListenersDocumentViewerEvicted(uint32_t aNumEvicted) {
1112 NotifyListeners(mListeners, [aNumEvicted](auto l) {
1113 l->OnDocumentViewerEvicted(aNumEvicted);
1117 NS_IMETHODIMP
1118 nsSHistory::RemoveSHistoryListener(nsISHistoryListener* aListener) {
1119 // Make sure the listener that wants to be removed is the
1120 // one we have in store.
1121 nsWeakPtr listener = do_GetWeakReference(aListener);
1122 mListeners.RemoveElement(listener);
1123 return NS_OK;
1126 /* Replace an entry in the History list at a particular index.
1127 * Do not update index or count.
1129 NS_IMETHODIMP
1130 nsSHistory::ReplaceEntry(int32_t aIndex, nsISHEntry* aReplaceEntry) {
1131 NS_ENSURE_ARG(aReplaceEntry);
1133 if (aIndex < 0 || aIndex >= Length()) {
1134 return NS_ERROR_FAILURE;
1137 nsCOMPtr<nsISHistory> shistoryOfEntry = aReplaceEntry->GetShistory();
1138 if (shistoryOfEntry && shistoryOfEntry != this) {
1139 NS_WARNING(
1140 "The entry has been associated to another nsISHistory instance. "
1141 "Try nsISHEntry.clone() and nsISHEntry.abandonBFCacheEntry() "
1142 "first if you're copying an entry from another nsISHistory.");
1143 return NS_ERROR_FAILURE;
1146 aReplaceEntry->SetShistory(this);
1148 NotifyListeners(mListeners, [](auto l) { l->OnHistoryReplaceEntry(); });
1150 aReplaceEntry->SetPersist(true);
1151 mEntries[aIndex] = aReplaceEntry;
1153 UpdateRootBrowsingContextState();
1155 return NS_OK;
1158 // Calls OnHistoryReload on all registered session history listeners.
1159 // Listeners may return 'false' to cancel an action so make sure that we
1160 // set the return value to 'false' if one of the listeners wants to cancel.
1161 NS_IMETHODIMP
1162 nsSHistory::NotifyOnHistoryReload(bool* aCanReload) {
1163 *aCanReload = true;
1165 for (const nsWeakPtr& weakPtr : mListeners.EndLimitedRange()) {
1166 nsCOMPtr<nsISHistoryListener> listener = do_QueryReferent(weakPtr);
1167 if (listener) {
1168 bool retval = true;
1170 if (NS_SUCCEEDED(listener->OnHistoryReload(&retval)) && !retval) {
1171 *aCanReload = false;
1176 return NS_OK;
1179 NS_IMETHODIMP
1180 nsSHistory::EvictOutOfRangeDocumentViewers(int32_t aIndex) {
1181 MOZ_LOG(gSHIPBFCacheLog, LogLevel::Debug,
1182 ("nsSHistory::EvictOutOfRangeDocumentViewers %i", aIndex));
1184 // Check our per SHistory object limit in the currently navigated SHistory
1185 EvictOutOfRangeWindowDocumentViewers(aIndex);
1186 // Check our total limit across all SHistory objects
1187 GloballyEvictDocumentViewers();
1188 return NS_OK;
1191 NS_IMETHODIMP_(void)
1192 nsSHistory::EvictDocumentViewersOrReplaceEntry(nsISHEntry* aNewSHEntry,
1193 bool aReplace) {
1194 if (!aReplace) {
1195 int32_t curIndex;
1196 GetIndex(&curIndex);
1197 if (curIndex > -1) {
1198 EvictOutOfRangeDocumentViewers(curIndex);
1200 } else {
1201 nsCOMPtr<nsISHEntry> rootSHEntry = nsSHistory::GetRootSHEntry(aNewSHEntry);
1203 int32_t index = GetIndexOfEntry(rootSHEntry);
1204 if (index > -1) {
1205 ReplaceEntry(index, rootSHEntry);
1210 NS_IMETHODIMP
1211 nsSHistory::EvictAllDocumentViewers() {
1212 // XXXbz we don't actually do a good job of evicting things as we should, so
1213 // we might have viewers quite far from mIndex. So just evict everything.
1214 for (int32_t i = 0; i < Length(); i++) {
1215 EvictDocumentViewerForEntry(mEntries[i]);
1218 return NS_OK;
1221 static void FinishRestore(CanonicalBrowsingContext* aBrowsingContext,
1222 nsDocShellLoadState* aLoadState,
1223 SessionHistoryEntry* aEntry,
1224 nsFrameLoader* aFrameLoader, bool aCanSave) {
1225 MOZ_ASSERT(aEntry);
1226 MOZ_ASSERT(aFrameLoader);
1228 aEntry->SetFrameLoader(nullptr);
1230 nsCOMPtr<nsISHistory> shistory = aEntry->GetShistory();
1231 int32_t indexOfHistoryLoad =
1232 shistory ? shistory->GetIndexOfEntry(aEntry) : -1;
1234 nsCOMPtr<nsFrameLoaderOwner> frameLoaderOwner =
1235 do_QueryInterface(aBrowsingContext->GetEmbedderElement());
1236 if (frameLoaderOwner && aFrameLoader->GetMaybePendingBrowsingContext() &&
1237 indexOfHistoryLoad >= 0) {
1238 RefPtr<BrowsingContextWebProgress> webProgress =
1239 aBrowsingContext->GetWebProgress();
1240 if (webProgress) {
1241 // Synthesize a STATE_START WebProgress state change event from here
1242 // in order to ensure emitting it on the BrowsingContext we navigate
1243 // *from* instead of the BrowsingContext we navigate *to*. This will fire
1244 // before and the next one will be ignored by BrowsingContextWebProgress:
1245 // https://searchfox.org/mozilla-central/rev/77f0b36028b2368e342c982ea47609040b399d89/docshell/base/BrowsingContextWebProgress.cpp#196-203
1246 nsCOMPtr<nsIURI> nextURI = aEntry->GetURI();
1247 nsCOMPtr<nsIURI> nextOriginalURI = aEntry->GetOriginalURI();
1248 nsCOMPtr<nsIRequest> request = MakeAndAddRef<RemoteWebProgressRequest>(
1249 nextURI, nextOriginalURI ? nextOriginalURI : nextURI,
1250 ""_ns /* aMatchedList */);
1251 webProgress->OnStateChange(webProgress, request,
1252 nsIWebProgressListener::STATE_START |
1253 nsIWebProgressListener::STATE_IS_DOCUMENT |
1254 nsIWebProgressListener::STATE_IS_REQUEST |
1255 nsIWebProgressListener::STATE_IS_WINDOW |
1256 nsIWebProgressListener::STATE_IS_NETWORK,
1257 NS_OK);
1260 RefPtr<CanonicalBrowsingContext> loadingBC =
1261 aFrameLoader->GetMaybePendingBrowsingContext()->Canonical();
1262 RefPtr<nsFrameLoader> currentFrameLoader =
1263 frameLoaderOwner->GetFrameLoader();
1264 // The current page can be bfcached, store the
1265 // nsFrameLoader in the current SessionHistoryEntry.
1266 RefPtr<SessionHistoryEntry> currentSHEntry =
1267 aBrowsingContext->GetActiveSessionHistoryEntry();
1268 if (currentSHEntry) {
1269 // Update layout history state now, before we change the IsInBFCache flag
1270 // and the active session history entry.
1271 aBrowsingContext->SynchronizeLayoutHistoryState();
1273 if (aCanSave) {
1274 currentSHEntry->SetFrameLoader(currentFrameLoader);
1275 Unused << aBrowsingContext->SetIsInBFCache(true);
1279 if (aBrowsingContext->IsActive()) {
1280 loadingBC->PreOrderWalk([&](BrowsingContext* aContext) {
1281 if (BrowserParent* bp = aContext->Canonical()->GetBrowserParent()) {
1282 ProcessPriorityManager::BrowserPriorityChanged(bp, true);
1287 if (aEntry) {
1288 aEntry->SetWireframe(Nothing());
1291 // ReplacedBy will swap the entry back.
1292 aBrowsingContext->SetActiveSessionHistoryEntry(aEntry);
1293 loadingBC->SetActiveSessionHistoryEntry(nullptr);
1294 NavigationIsolationOptions options;
1295 aBrowsingContext->ReplacedBy(loadingBC, options);
1297 // Assuming we still have the session history, update the index.
1298 if (loadingBC->GetSessionHistory()) {
1299 shistory->InternalSetRequestedIndex(indexOfHistoryLoad);
1300 shistory->UpdateIndex();
1302 loadingBC->HistoryCommitIndexAndLength();
1304 // ResetSHEntryHasUserInteractionCache(); ?
1305 // browser.navigation.requireUserInteraction is still
1306 // disabled everywhere.
1308 frameLoaderOwner->RestoreFrameLoaderFromBFCache(aFrameLoader);
1309 // EvictOutOfRangeDocumentViewers is called here explicitly to
1310 // possibly evict the now in the bfcache document.
1311 // HistoryCommitIndexAndLength might not have evicted that before the
1312 // FrameLoader swap.
1313 shistory->EvictOutOfRangeDocumentViewers(indexOfHistoryLoad);
1315 // The old page can't be stored in the bfcache,
1316 // destroy the nsFrameLoader.
1317 if (!aCanSave && currentFrameLoader) {
1318 currentFrameLoader->Destroy();
1321 Unused << loadingBC->SetIsInBFCache(false);
1323 // We need to call this after we've restored the page from BFCache (see
1324 // SetIsInBFCache(false) above), so that the page is not frozen anymore and
1325 // the right focus events are fired.
1326 frameLoaderOwner->UpdateFocusAndMouseEnterStateAfterFrameLoaderChange();
1328 return;
1331 aFrameLoader->Destroy();
1333 // Fall back to do a normal load.
1334 aBrowsingContext->LoadURI(aLoadState, false);
1337 /* static */
1338 void nsSHistory::LoadURIOrBFCache(LoadEntryResult& aLoadEntry) {
1339 if (mozilla::BFCacheInParent() && aLoadEntry.mBrowsingContext->IsTop()) {
1340 MOZ_ASSERT(XRE_IsParentProcess());
1341 RefPtr<nsDocShellLoadState> loadState = aLoadEntry.mLoadState;
1342 RefPtr<CanonicalBrowsingContext> canonicalBC =
1343 aLoadEntry.mBrowsingContext->Canonical();
1344 nsCOMPtr<SessionHistoryEntry> she = do_QueryInterface(loadState->SHEntry());
1345 nsCOMPtr<SessionHistoryEntry> currentShe =
1346 canonicalBC->GetActiveSessionHistoryEntry();
1347 MOZ_ASSERT(she);
1348 RefPtr<nsFrameLoader> frameLoader = she->GetFrameLoader();
1349 if (frameLoader &&
1350 (!currentShe || (she->SharedInfo() != currentShe->SharedInfo() &&
1351 !currentShe->GetFrameLoader()))) {
1352 bool canSave = (!currentShe || currentShe->GetSaveLayoutStateFlag()) &&
1353 canonicalBC->AllowedInBFCache(Nothing(), nullptr);
1355 MOZ_LOG(gSHIPBFCacheLog, LogLevel::Debug,
1356 ("nsSHistory::LoadURIOrBFCache "
1357 "saving presentation=%i",
1358 canSave));
1360 nsCOMPtr<nsFrameLoaderOwner> frameLoaderOwner =
1361 do_QueryInterface(canonicalBC->GetEmbedderElement());
1362 if (frameLoaderOwner) {
1363 RefPtr<nsFrameLoader> currentFrameLoader =
1364 frameLoaderOwner->GetFrameLoader();
1365 if (currentFrameLoader &&
1366 currentFrameLoader->GetMaybePendingBrowsingContext()) {
1367 if (WindowGlobalParent* wgp =
1368 currentFrameLoader->GetMaybePendingBrowsingContext()
1369 ->Canonical()
1370 ->GetCurrentWindowGlobal()) {
1371 wgp->PermitUnload([canonicalBC, loadState, she, frameLoader,
1372 currentFrameLoader, canSave](bool aAllow) {
1373 if (aAllow && !canonicalBC->IsReplaced()) {
1374 FinishRestore(canonicalBC, loadState, she, frameLoader,
1375 canSave && canonicalBC->AllowedInBFCache(
1376 Nothing(), nullptr));
1377 } else if (currentFrameLoader->GetMaybePendingBrowsingContext()) {
1378 nsISHistory* shistory =
1379 currentFrameLoader->GetMaybePendingBrowsingContext()
1380 ->Canonical()
1381 ->GetSessionHistory();
1382 if (shistory) {
1383 shistory->InternalSetRequestedIndex(-1);
1387 return;
1392 FinishRestore(canonicalBC, loadState, she, frameLoader, canSave);
1393 return;
1395 if (frameLoader) {
1396 she->SetFrameLoader(nullptr);
1397 frameLoader->Destroy();
1401 RefPtr<BrowsingContext> bc = aLoadEntry.mBrowsingContext;
1402 RefPtr<nsDocShellLoadState> loadState = aLoadEntry.mLoadState;
1403 bc->LoadURI(loadState, false);
1406 /* static */
1407 void nsSHistory::LoadURIs(nsTArray<LoadEntryResult>& aLoadResults) {
1408 for (LoadEntryResult& loadEntry : aLoadResults) {
1409 LoadURIOrBFCache(loadEntry);
1413 NS_IMETHODIMP
1414 nsSHistory::Reload(uint32_t aReloadFlags) {
1415 nsTArray<LoadEntryResult> loadResults;
1416 nsresult rv = Reload(aReloadFlags, loadResults);
1417 NS_ENSURE_SUCCESS(rv, rv);
1419 if (loadResults.IsEmpty()) {
1420 return NS_OK;
1423 LoadURIs(loadResults);
1424 return NS_OK;
1427 nsresult nsSHistory::Reload(uint32_t aReloadFlags,
1428 nsTArray<LoadEntryResult>& aLoadResults) {
1429 MOZ_ASSERT(aLoadResults.IsEmpty());
1431 uint32_t loadType;
1432 if (aReloadFlags & nsIWebNavigation::LOAD_FLAGS_BYPASS_PROXY &&
1433 aReloadFlags & nsIWebNavigation::LOAD_FLAGS_BYPASS_CACHE) {
1434 loadType = LOAD_RELOAD_BYPASS_PROXY_AND_CACHE;
1435 } else if (aReloadFlags & nsIWebNavigation::LOAD_FLAGS_BYPASS_PROXY) {
1436 loadType = LOAD_RELOAD_BYPASS_PROXY;
1437 } else if (aReloadFlags & nsIWebNavigation::LOAD_FLAGS_BYPASS_CACHE) {
1438 loadType = LOAD_RELOAD_BYPASS_CACHE;
1439 } else if (aReloadFlags & nsIWebNavigation::LOAD_FLAGS_CHARSET_CHANGE) {
1440 loadType = LOAD_RELOAD_CHARSET_CHANGE;
1441 } else {
1442 loadType = LOAD_RELOAD_NORMAL;
1445 // We are reloading. Send Reload notifications.
1446 // nsDocShellLoadFlagType is not public, where as nsIWebNavigation
1447 // is public. So send the reload notifications with the
1448 // nsIWebNavigation flags.
1449 bool canNavigate = true;
1450 MOZ_ALWAYS_SUCCEEDS(NotifyOnHistoryReload(&canNavigate));
1451 if (!canNavigate) {
1452 return NS_OK;
1455 nsresult rv = LoadEntry(
1456 mIndex, loadType, HIST_CMD_RELOAD, aLoadResults, /* aSameEpoch */ false,
1457 /* aLoadCurrentEntry */ true,
1458 aReloadFlags & nsIWebNavigation::LOAD_FLAGS_USER_ACTIVATION);
1459 if (NS_FAILED(rv)) {
1460 aLoadResults.Clear();
1461 return rv;
1464 return NS_OK;
1467 NS_IMETHODIMP
1468 nsSHistory::ReloadCurrentEntry() {
1469 nsTArray<LoadEntryResult> loadResults;
1470 nsresult rv = ReloadCurrentEntry(loadResults);
1471 NS_ENSURE_SUCCESS(rv, rv);
1473 LoadURIs(loadResults);
1474 return NS_OK;
1477 nsresult nsSHistory::ReloadCurrentEntry(
1478 nsTArray<LoadEntryResult>& aLoadResults) {
1479 // Notify listeners
1480 NotifyListeners(mListeners, [](auto l) { l->OnHistoryGotoIndex(); });
1482 return LoadEntry(mIndex, LOAD_HISTORY, HIST_CMD_RELOAD, aLoadResults,
1483 /* aSameEpoch */ false, /* aLoadCurrentEntry */ true,
1484 /* aUserActivation */ false);
1487 void nsSHistory::EvictOutOfRangeWindowDocumentViewers(int32_t aIndex) {
1488 // XXX rename method to EvictDocumentViewersExceptAroundIndex, or something.
1490 // We need to release all content viewers that are no longer in the range
1492 // aIndex - VIEWER_WINDOW to aIndex + VIEWER_WINDOW
1494 // to ensure that this SHistory object isn't responsible for more than
1495 // VIEWER_WINDOW content viewers. But our job is complicated by the
1496 // fact that two entries which are related by either hash navigations or
1497 // history.pushState will have the same content viewer.
1499 // To illustrate the issue, suppose VIEWER_WINDOW = 3 and we have four
1500 // linked entries in our history. Suppose we then add a new content
1501 // viewer and call into this function. So the history looks like:
1503 // A A A A B
1504 // + *
1506 // where the letters are content viewers and + and * denote the beginning and
1507 // end of the range aIndex +/- VIEWER_WINDOW.
1509 // Although one copy of the content viewer A exists outside the range, we
1510 // don't want to evict A, because it has other copies in range!
1512 // We therefore adjust our eviction strategy to read:
1514 // Evict each content viewer outside the range aIndex -/+
1515 // VIEWER_WINDOW, unless that content viewer also appears within the
1516 // range.
1518 // (Note that it's entirely legal to have two copies of one content viewer
1519 // separated by a different content viewer -- call pushState twice, go back
1520 // once, and refresh -- so we can't rely on identical viewers only appearing
1521 // adjacent to one another.)
1523 if (aIndex < 0) {
1524 return;
1526 NS_ENSURE_TRUE_VOID(aIndex < Length());
1528 // Calculate the range that's safe from eviction.
1529 int32_t startSafeIndex, endSafeIndex;
1530 WindowIndices(aIndex, &startSafeIndex, &endSafeIndex);
1532 LOG(
1533 ("EvictOutOfRangeWindowDocumentViewers(index=%d), "
1534 "Length()=%d. Safe range [%d, %d]",
1535 aIndex, Length(), startSafeIndex, endSafeIndex));
1537 // The content viewers in range aIndex -/+ VIEWER_WINDOW will not be
1538 // evicted. Collect a set of them so we don't accidentally evict one of them
1539 // if it appears outside this range.
1540 nsCOMArray<nsIDocumentViewer> safeViewers;
1541 nsTArray<RefPtr<nsFrameLoader>> safeFrameLoaders;
1542 for (int32_t i = startSafeIndex; i <= endSafeIndex; i++) {
1543 nsCOMPtr<nsIDocumentViewer> viewer = mEntries[i]->GetDocumentViewer();
1544 if (viewer) {
1545 safeViewers.AppendObject(viewer);
1546 } else if (nsCOMPtr<SessionHistoryEntry> she =
1547 do_QueryInterface(mEntries[i])) {
1548 nsFrameLoader* frameLoader = she->GetFrameLoader();
1549 if (frameLoader) {
1550 safeFrameLoaders.AppendElement(frameLoader);
1555 // Walk the SHistory list and evict any content viewers that aren't safe.
1556 // (It's important that the condition checks Length(), rather than a cached
1557 // copy of Length(), because the length might change between iterations.)
1558 for (int32_t i = 0; i < Length(); i++) {
1559 nsCOMPtr<nsISHEntry> entry = mEntries[i];
1560 nsCOMPtr<nsIDocumentViewer> viewer = entry->GetDocumentViewer();
1561 if (viewer) {
1562 if (safeViewers.IndexOf(viewer) == -1) {
1563 EvictDocumentViewerForEntry(entry);
1565 } else if (nsCOMPtr<SessionHistoryEntry> she =
1566 do_QueryInterface(mEntries[i])) {
1567 nsFrameLoader* frameLoader = she->GetFrameLoader();
1568 if (frameLoader) {
1569 if (!safeFrameLoaders.Contains(frameLoader)) {
1570 EvictDocumentViewerForEntry(entry);
1577 namespace {
1579 class EntryAndDistance {
1580 public:
1581 EntryAndDistance(nsSHistory* aSHistory, nsISHEntry* aEntry, uint32_t aDist)
1582 : mSHistory(aSHistory),
1583 mEntry(aEntry),
1584 mViewer(aEntry->GetDocumentViewer()),
1585 mLastTouched(mEntry->GetLastTouched()),
1586 mDistance(aDist) {
1587 nsCOMPtr<SessionHistoryEntry> she = do_QueryInterface(aEntry);
1588 if (she) {
1589 mFrameLoader = she->GetFrameLoader();
1591 NS_ASSERTION(mViewer || mFrameLoader,
1592 "Entry should have a content viewer or frame loader.");
1595 bool operator<(const EntryAndDistance& aOther) const {
1596 // Compare distances first, and fall back to last-accessed times.
1597 if (aOther.mDistance != this->mDistance) {
1598 return this->mDistance < aOther.mDistance;
1601 return this->mLastTouched < aOther.mLastTouched;
1604 bool operator==(const EntryAndDistance& aOther) const {
1605 // This is a little silly; we need == so the default comaprator can be
1606 // instantiated, but this function is never actually called when we sort
1607 // the list of EntryAndDistance objects.
1608 return aOther.mDistance == this->mDistance &&
1609 aOther.mLastTouched == this->mLastTouched;
1612 RefPtr<nsSHistory> mSHistory;
1613 nsCOMPtr<nsISHEntry> mEntry;
1614 nsCOMPtr<nsIDocumentViewer> mViewer;
1615 RefPtr<nsFrameLoader> mFrameLoader;
1616 uint32_t mLastTouched;
1617 int32_t mDistance;
1620 } // namespace
1622 // static
1623 void nsSHistory::GloballyEvictDocumentViewers() {
1624 // First, collect from each SHistory object the entries which have a cached
1625 // content viewer. Associate with each entry its distance from its SHistory's
1626 // current index.
1628 nsTArray<EntryAndDistance> entries;
1630 for (auto shist : gSHistoryList.mList) {
1631 // Maintain a list of the entries which have viewers and belong to
1632 // this particular shist object. We'll add this list to the global list,
1633 // |entries|, eventually.
1634 nsTArray<EntryAndDistance> shEntries;
1636 // Content viewers are likely to exist only within shist->mIndex -/+
1637 // VIEWER_WINDOW, so only search within that range.
1639 // A content viewer might exist outside that range due to either:
1641 // * history.pushState or hash navigations, in which case a copy of the
1642 // content viewer should exist within the range, or
1644 // * bugs which cause us not to call nsSHistory::EvictDocumentViewers()
1645 // often enough. Once we do call EvictDocumentViewers() for the
1646 // SHistory object in question, we'll do a full search of its history
1647 // and evict the out-of-range content viewers, so we don't bother here.
1649 int32_t startIndex, endIndex;
1650 shist->WindowIndices(shist->mIndex, &startIndex, &endIndex);
1651 for (int32_t i = startIndex; i <= endIndex; i++) {
1652 nsCOMPtr<nsISHEntry> entry = shist->mEntries[i];
1653 nsCOMPtr<nsIDocumentViewer> viewer = entry->GetDocumentViewer();
1655 bool found = false;
1656 bool hasDocumentViewerOrFrameLoader = false;
1657 if (viewer) {
1658 hasDocumentViewerOrFrameLoader = true;
1659 // Because one content viewer might belong to multiple SHEntries, we
1660 // have to search through shEntries to see if we already know
1661 // about this content viewer. If we find the viewer, update its
1662 // distance from the SHistory's index and continue.
1663 for (uint32_t j = 0; j < shEntries.Length(); j++) {
1664 EntryAndDistance& container = shEntries[j];
1665 if (container.mViewer == viewer) {
1666 container.mDistance =
1667 std::min(container.mDistance, DeprecatedAbs(i - shist->mIndex));
1668 found = true;
1669 break;
1672 } else if (nsCOMPtr<SessionHistoryEntry> she = do_QueryInterface(entry)) {
1673 if (RefPtr<nsFrameLoader> frameLoader = she->GetFrameLoader()) {
1674 hasDocumentViewerOrFrameLoader = true;
1675 // Similar search as above but using frameloader.
1676 for (uint32_t j = 0; j < shEntries.Length(); j++) {
1677 EntryAndDistance& container = shEntries[j];
1678 if (container.mFrameLoader == frameLoader) {
1679 container.mDistance = std::min(container.mDistance,
1680 DeprecatedAbs(i - shist->mIndex));
1681 found = true;
1682 break;
1688 // If we didn't find a EntryAndDistance for this content viewer /
1689 // frameloader, make a new one.
1690 if (hasDocumentViewerOrFrameLoader && !found) {
1691 EntryAndDistance container(shist, entry,
1692 DeprecatedAbs(i - shist->mIndex));
1693 shEntries.AppendElement(container);
1697 // We've found all the entries belonging to shist which have viewers.
1698 // Add those entries to our global list and move on.
1699 entries.AppendElements(shEntries);
1702 // We now have collected all cached content viewers. First check that we
1703 // have enough that we actually need to evict some.
1704 if ((int32_t)entries.Length() <= sHistoryMaxTotalViewers) {
1705 return;
1708 // If we need to evict, sort our list of entries and evict the largest
1709 // ones. (We could of course get better algorithmic complexity here by using
1710 // a heap or something more clever. But sHistoryMaxTotalViewers isn't large,
1711 // so let's not worry about it.)
1712 entries.Sort();
1714 for (int32_t i = entries.Length() - 1; i >= sHistoryMaxTotalViewers; --i) {
1715 (entries[i].mSHistory)->EvictDocumentViewerForEntry(entries[i].mEntry);
1719 nsresult nsSHistory::FindEntryForBFCache(SHEntrySharedParentState* aEntry,
1720 nsISHEntry** aResult,
1721 int32_t* aResultIndex) {
1722 *aResult = nullptr;
1723 *aResultIndex = -1;
1725 int32_t startIndex, endIndex;
1726 WindowIndices(mIndex, &startIndex, &endIndex);
1728 for (int32_t i = startIndex; i <= endIndex; ++i) {
1729 nsCOMPtr<nsISHEntry> shEntry = mEntries[i];
1731 // Does shEntry have the same BFCacheEntry as the argument to this method?
1732 if (shEntry->HasBFCacheEntry(aEntry)) {
1733 shEntry.forget(aResult);
1734 *aResultIndex = i;
1735 return NS_OK;
1738 return NS_ERROR_FAILURE;
1741 NS_IMETHODIMP_(void)
1742 nsSHistory::EvictExpiredDocumentViewerForEntry(
1743 SHEntrySharedParentState* aEntry) {
1744 int32_t index;
1745 nsCOMPtr<nsISHEntry> shEntry;
1746 FindEntryForBFCache(aEntry, getter_AddRefs(shEntry), &index);
1748 if (index == mIndex) {
1749 NS_WARNING("How did the current SHEntry expire?");
1752 if (shEntry) {
1753 EvictDocumentViewerForEntry(shEntry);
1757 NS_IMETHODIMP_(void)
1758 nsSHistory::AddToExpirationTracker(SHEntrySharedParentState* aEntry) {
1759 RefPtr<SHEntrySharedParentState> entry = aEntry;
1760 if (!mHistoryTracker || !entry) {
1761 return;
1764 mHistoryTracker->AddObject(entry);
1765 return;
1768 NS_IMETHODIMP_(void)
1769 nsSHistory::RemoveFromExpirationTracker(SHEntrySharedParentState* aEntry) {
1770 RefPtr<SHEntrySharedParentState> entry = aEntry;
1771 MOZ_ASSERT(mHistoryTracker && !mHistoryTracker->IsEmpty());
1772 if (!mHistoryTracker || !entry) {
1773 return;
1776 mHistoryTracker->RemoveObject(entry);
1779 // Evicts all content viewers in all history objects. This is very
1780 // inefficient, because it requires a linear search through all SHistory
1781 // objects for each viewer to be evicted. However, this method is called
1782 // infrequently -- only when the disk or memory cache is cleared.
1784 // static
1785 void nsSHistory::GloballyEvictAllDocumentViewers() {
1786 int32_t maxViewers = sHistoryMaxTotalViewers;
1787 sHistoryMaxTotalViewers = 0;
1788 GloballyEvictDocumentViewers();
1789 sHistoryMaxTotalViewers = maxViewers;
1792 void GetDynamicChildren(nsISHEntry* aEntry, nsTArray<nsID>& aDocshellIDs) {
1793 int32_t count = aEntry->GetChildCount();
1794 for (int32_t i = 0; i < count; ++i) {
1795 nsCOMPtr<nsISHEntry> child;
1796 aEntry->GetChildAt(i, getter_AddRefs(child));
1797 if (child) {
1798 if (child->IsDynamicallyAdded()) {
1799 child->GetDocshellID(*aDocshellIDs.AppendElement());
1800 } else {
1801 GetDynamicChildren(child, aDocshellIDs);
1807 bool RemoveFromSessionHistoryEntry(nsISHEntry* aRoot,
1808 nsTArray<nsID>& aDocshellIDs) {
1809 bool didRemove = false;
1810 int32_t childCount = aRoot->GetChildCount();
1811 for (int32_t i = childCount - 1; i >= 0; --i) {
1812 nsCOMPtr<nsISHEntry> child;
1813 aRoot->GetChildAt(i, getter_AddRefs(child));
1814 if (child) {
1815 nsID docshelldID;
1816 child->GetDocshellID(docshelldID);
1817 if (aDocshellIDs.Contains(docshelldID)) {
1818 didRemove = true;
1819 aRoot->RemoveChild(child);
1820 } else if (RemoveFromSessionHistoryEntry(child, aDocshellIDs)) {
1821 didRemove = true;
1825 return didRemove;
1828 bool RemoveChildEntries(nsISHistory* aHistory, int32_t aIndex,
1829 nsTArray<nsID>& aEntryIDs) {
1830 nsCOMPtr<nsISHEntry> root;
1831 aHistory->GetEntryAtIndex(aIndex, getter_AddRefs(root));
1832 return root ? RemoveFromSessionHistoryEntry(root, aEntryIDs) : false;
1835 bool IsSameTree(nsISHEntry* aEntry1, nsISHEntry* aEntry2) {
1836 if (!aEntry1 && !aEntry2) {
1837 return true;
1839 if ((!aEntry1 && aEntry2) || (aEntry1 && !aEntry2)) {
1840 return false;
1842 uint32_t id1 = aEntry1->GetID();
1843 uint32_t id2 = aEntry2->GetID();
1844 if (id1 != id2) {
1845 return false;
1848 int32_t count1 = aEntry1->GetChildCount();
1849 int32_t count2 = aEntry2->GetChildCount();
1850 // We allow null entries in the end of the child list.
1851 int32_t count = std::max(count1, count2);
1852 for (int32_t i = 0; i < count; ++i) {
1853 nsCOMPtr<nsISHEntry> child1, child2;
1854 aEntry1->GetChildAt(i, getter_AddRefs(child1));
1855 aEntry2->GetChildAt(i, getter_AddRefs(child2));
1856 if (!IsSameTree(child1, child2)) {
1857 return false;
1861 return true;
1864 bool nsSHistory::RemoveDuplicate(int32_t aIndex, bool aKeepNext) {
1865 NS_ASSERTION(aIndex >= 0, "aIndex must be >= 0!");
1866 NS_ASSERTION(aIndex != 0 || aKeepNext,
1867 "If we're removing index 0 we must be keeping the next");
1868 NS_ASSERTION(aIndex != mIndex, "Shouldn't remove mIndex!");
1870 int32_t compareIndex = aKeepNext ? aIndex + 1 : aIndex - 1;
1872 nsresult rv;
1873 nsCOMPtr<nsISHEntry> root1, root2;
1874 rv = GetEntryAtIndex(aIndex, getter_AddRefs(root1));
1875 if (NS_FAILED(rv)) {
1876 return false;
1878 rv = GetEntryAtIndex(compareIndex, getter_AddRefs(root2));
1879 if (NS_FAILED(rv)) {
1880 return false;
1883 SHistoryChangeNotifier change(this);
1885 if (IsSameTree(root1, root2)) {
1886 if (aIndex < compareIndex) {
1887 // If we're removing the entry with the lower index we need to move its
1888 // BCHistoryLength to the entry we're keeping. If we're removing the entry
1889 // with the higher index then it shouldn't have a modified
1890 // BCHistoryLength.
1891 UpdateEntryLength(root1, root2, true);
1893 nsCOMPtr<SessionHistoryEntry> she = do_QueryInterface(root1);
1894 if (she) {
1895 ClearEntries(she);
1897 mEntries.RemoveElementAt(aIndex);
1899 // FIXME Bug 1546350: Reimplement history listeners.
1900 // if (mRootBC && mRootBC->GetDocShell()) {
1901 // static_cast<nsDocShell*>(mRootBC->GetDocShell())
1902 // ->HistoryEntryRemoved(aIndex);
1905 // Adjust our indices to reflect the removed entry.
1906 if (mIndex > aIndex) {
1907 mIndex = mIndex - 1;
1910 // NB: If the entry we are removing is the entry currently
1911 // being navigated to (mRequestedIndex) then we adjust the index
1912 // only if we're not keeping the next entry (because if we are keeping
1913 // the next entry (because the current is a duplicate of the next), then
1914 // that entry slides into the spot that we're currently pointing to.
1915 // We don't do this adjustment for mIndex because mIndex cannot equal
1916 // aIndex.
1918 // NB: We don't need to guard on mRequestedIndex being nonzero here,
1919 // because either they're strictly greater than aIndex which is at least
1920 // zero, or they are equal to aIndex in which case aKeepNext must be true
1921 // if aIndex is zero.
1922 if (mRequestedIndex > aIndex || (mRequestedIndex == aIndex && !aKeepNext)) {
1923 mRequestedIndex = mRequestedIndex - 1;
1926 return true;
1928 return false;
1931 NS_IMETHODIMP_(void)
1932 nsSHistory::RemoveEntries(nsTArray<nsID>& aIDs, int32_t aStartIndex) {
1933 bool didRemove;
1934 RemoveEntries(aIDs, aStartIndex, &didRemove);
1935 if (didRemove) {
1936 RefPtr<BrowsingContext> rootBC = GetBrowsingContext();
1937 if (rootBC && rootBC->GetDocShell()) {
1938 rootBC->GetDocShell()->DispatchLocationChangeEvent();
1943 void nsSHistory::RemoveEntries(nsTArray<nsID>& aIDs, int32_t aStartIndex,
1944 bool* aDidRemove) {
1945 SHistoryChangeNotifier change(this);
1947 int32_t index = aStartIndex;
1948 while (index >= 0 && RemoveChildEntries(this, --index, aIDs)) {
1950 int32_t minIndex = index;
1951 index = aStartIndex;
1952 while (index >= 0 && RemoveChildEntries(this, index++, aIDs)) {
1955 // We need to remove duplicate nsSHEntry trees.
1956 *aDidRemove = false;
1957 while (index > minIndex) {
1958 if (index != mIndex && RemoveDuplicate(index, index < mIndex)) {
1959 *aDidRemove = true;
1961 --index;
1964 UpdateRootBrowsingContextState();
1967 void nsSHistory::RemoveFrameEntries(nsISHEntry* aEntry) {
1968 int32_t count = aEntry->GetChildCount();
1969 AutoTArray<nsID, 16> ids;
1970 for (int32_t i = 0; i < count; ++i) {
1971 nsCOMPtr<nsISHEntry> child;
1972 aEntry->GetChildAt(i, getter_AddRefs(child));
1973 if (child) {
1974 child->GetDocshellID(*ids.AppendElement());
1977 RemoveEntries(ids, mIndex);
1980 void nsSHistory::RemoveDynEntries(int32_t aIndex, nsISHEntry* aEntry) {
1981 // Remove dynamic entries which are at the index and belongs to the container.
1982 nsCOMPtr<nsISHEntry> entry(aEntry);
1983 if (!entry) {
1984 GetEntryAtIndex(aIndex, getter_AddRefs(entry));
1987 if (entry) {
1988 AutoTArray<nsID, 16> toBeRemovedEntries;
1989 GetDynamicChildren(entry, toBeRemovedEntries);
1990 if (toBeRemovedEntries.Length()) {
1991 RemoveEntries(toBeRemovedEntries, aIndex);
1996 void nsSHistory::RemoveDynEntriesForBFCacheEntry(nsIBFCacheEntry* aBFEntry) {
1997 int32_t index;
1998 nsCOMPtr<nsISHEntry> shEntry;
1999 FindEntryForBFCache(static_cast<nsSHEntryShared*>(aBFEntry),
2000 getter_AddRefs(shEntry), &index);
2001 if (shEntry) {
2002 RemoveDynEntries(index, shEntry);
2006 NS_IMETHODIMP
2007 nsSHistory::UpdateIndex() {
2008 SHistoryChangeNotifier change(this);
2010 // Update the actual index with the right value.
2011 if (mIndex != mRequestedIndex && mRequestedIndex != -1) {
2012 mIndex = mRequestedIndex;
2015 mRequestedIndex = -1;
2016 return NS_OK;
2019 NS_IMETHODIMP
2020 nsSHistory::GotoIndex(int32_t aIndex, bool aUserActivation) {
2021 nsTArray<LoadEntryResult> loadResults;
2022 nsresult rv = GotoIndex(aIndex, loadResults, /*aSameEpoch*/ false,
2023 aIndex == mIndex, aUserActivation);
2024 NS_ENSURE_SUCCESS(rv, rv);
2026 LoadURIs(loadResults);
2027 return NS_OK;
2030 NS_IMETHODIMP_(void)
2031 nsSHistory::EnsureCorrectEntryAtCurrIndex(nsISHEntry* aEntry) {
2032 int index = mRequestedIndex == -1 ? mIndex : mRequestedIndex;
2033 if (index > -1 && (mEntries[index] != aEntry)) {
2034 ReplaceEntry(index, aEntry);
2038 nsresult nsSHistory::GotoIndex(int32_t aIndex,
2039 nsTArray<LoadEntryResult>& aLoadResults,
2040 bool aSameEpoch, bool aLoadCurrentEntry,
2041 bool aUserActivation) {
2042 return LoadEntry(aIndex, LOAD_HISTORY, HIST_CMD_GOTOINDEX, aLoadResults,
2043 aSameEpoch, aLoadCurrentEntry, aUserActivation);
2046 NS_IMETHODIMP_(bool)
2047 nsSHistory::HasUserInteractionAtIndex(int32_t aIndex) {
2048 nsCOMPtr<nsISHEntry> entry;
2049 GetEntryAtIndex(aIndex, getter_AddRefs(entry));
2050 if (!entry) {
2051 return false;
2053 return entry->GetHasUserInteraction();
2056 nsresult nsSHistory::LoadNextPossibleEntry(
2057 int32_t aNewIndex, long aLoadType, uint32_t aHistCmd,
2058 nsTArray<LoadEntryResult>& aLoadResults, bool aLoadCurrentEntry,
2059 bool aUserActivation) {
2060 mRequestedIndex = -1;
2061 if (aNewIndex < mIndex) {
2062 return LoadEntry(aNewIndex - 1, aLoadType, aHistCmd, aLoadResults,
2063 /*aSameEpoch*/ false, aLoadCurrentEntry, aUserActivation);
2065 if (aNewIndex > mIndex) {
2066 return LoadEntry(aNewIndex + 1, aLoadType, aHistCmd, aLoadResults,
2067 /*aSameEpoch*/ false, aLoadCurrentEntry, aUserActivation);
2069 return NS_ERROR_FAILURE;
2072 nsresult nsSHistory::LoadEntry(int32_t aIndex, long aLoadType,
2073 uint32_t aHistCmd,
2074 nsTArray<LoadEntryResult>& aLoadResults,
2075 bool aSameEpoch, bool aLoadCurrentEntry,
2076 bool aUserActivation) {
2077 MOZ_LOG(gSHistoryLog, LogLevel::Debug,
2078 ("LoadEntry(%d, 0x%lx, %u)", aIndex, aLoadType, aHistCmd));
2079 RefPtr<BrowsingContext> rootBC = GetBrowsingContext();
2080 if (!rootBC) {
2081 return NS_ERROR_FAILURE;
2084 if (aIndex < 0 || aIndex >= Length()) {
2085 MOZ_LOG(gSHistoryLog, LogLevel::Debug, ("Index out of range"));
2086 // The index is out of range.
2087 // Clear the requested index in case it had bogus value. This way the next
2088 // load succeeds if the offset is reasonable.
2089 mRequestedIndex = -1;
2091 return NS_ERROR_FAILURE;
2094 int32_t originalRequestedIndex = mRequestedIndex;
2095 int32_t previousRequest = mRequestedIndex > -1 ? mRequestedIndex : mIndex;
2096 int32_t requestedOffset = aIndex - previousRequest;
2098 // This is a normal local history navigation.
2100 nsCOMPtr<nsISHEntry> prevEntry;
2101 nsCOMPtr<nsISHEntry> nextEntry;
2102 GetEntryAtIndex(mIndex, getter_AddRefs(prevEntry));
2103 GetEntryAtIndex(aIndex, getter_AddRefs(nextEntry));
2104 if (!nextEntry || !prevEntry) {
2105 mRequestedIndex = -1;
2106 return NS_ERROR_FAILURE;
2109 if (mozilla::SessionHistoryInParent()) {
2110 if (aHistCmd == HIST_CMD_GOTOINDEX) {
2111 // https://html.spec.whatwg.org/#history-traversal:
2112 // To traverse the history
2113 // "If entry has a different Document object than the current entry, then
2114 // run the following substeps: Remove any tasks queued by the history
2115 // traversal task source..."
2117 // aSameEpoch is true only if the navigations would have been
2118 // generated in the same spin of the event loop (i.e. history.go(-2);
2119 // history.go(-1)) and from the same content process.
2120 if (aSameEpoch) {
2121 bool same_doc = false;
2122 prevEntry->SharesDocumentWith(nextEntry, &same_doc);
2123 if (!same_doc) {
2124 MOZ_LOG(
2125 gSHistoryLog, LogLevel::Debug,
2126 ("Aborting GotoIndex %d - same epoch and not same doc", aIndex));
2127 // Ignore this load. Before SessionHistoryInParent, this would
2128 // have been dropped in InternalLoad after we filter out SameDoc
2129 // loads.
2130 return NS_ERROR_FAILURE;
2135 // Keep note of requested history index in mRequestedIndex; after all bailouts
2136 mRequestedIndex = aIndex;
2138 // Remember that this entry is getting loaded at this point in the sequence
2140 nextEntry->SetLastTouched(++gTouchCounter);
2142 // Get the uri for the entry we are about to visit
2143 nsCOMPtr<nsIURI> nextURI = nextEntry->GetURI();
2145 MOZ_ASSERT(nextURI, "nextURI can't be null");
2147 // Send appropriate listener notifications.
2148 if (aHistCmd == HIST_CMD_GOTOINDEX) {
2149 // We are going somewhere else. This is not reload either
2150 NotifyListeners(mListeners, [](auto l) { l->OnHistoryGotoIndex(); });
2153 if (mRequestedIndex == mIndex) {
2154 // Possibly a reload case
2155 InitiateLoad(nextEntry, rootBC, aLoadType, aLoadResults, aLoadCurrentEntry,
2156 aUserActivation, requestedOffset);
2157 return NS_OK;
2160 // Going back or forward.
2161 bool differenceFound = LoadDifferingEntries(
2162 prevEntry, nextEntry, rootBC, aLoadType, aLoadResults, aLoadCurrentEntry,
2163 aUserActivation, requestedOffset);
2164 if (!differenceFound) {
2165 // LoadNextPossibleEntry will change the offset by one, and in order
2166 // to keep track of the requestedOffset, need to reset mRequestedIndex to
2167 // the value it had initially.
2168 mRequestedIndex = originalRequestedIndex;
2169 // We did not find any differences. Go further in the history.
2170 return LoadNextPossibleEntry(aIndex, aLoadType, aHistCmd, aLoadResults,
2171 aLoadCurrentEntry, aUserActivation);
2174 return NS_OK;
2177 bool nsSHistory::LoadDifferingEntries(nsISHEntry* aPrevEntry,
2178 nsISHEntry* aNextEntry,
2179 BrowsingContext* aParent, long aLoadType,
2180 nsTArray<LoadEntryResult>& aLoadResults,
2181 bool aLoadCurrentEntry,
2182 bool aUserActivation, int32_t aOffset) {
2183 MOZ_ASSERT(aPrevEntry && aNextEntry && aParent);
2185 uint32_t prevID = aPrevEntry->GetID();
2186 uint32_t nextID = aNextEntry->GetID();
2188 // Check the IDs to verify if the pages are different.
2189 if (prevID != nextID) {
2190 // Set the Subframe flag if not navigating the root docshell.
2191 aNextEntry->SetIsSubFrame(aParent->Id() != mRootBC);
2192 InitiateLoad(aNextEntry, aParent, aLoadType, aLoadResults,
2193 aLoadCurrentEntry, aUserActivation, aOffset);
2194 return true;
2197 // The entries are the same, so compare any child frames
2198 int32_t pcnt = aPrevEntry->GetChildCount();
2199 int32_t ncnt = aNextEntry->GetChildCount();
2201 // Create an array for child browsing contexts.
2202 nsTArray<RefPtr<BrowsingContext>> browsingContexts;
2203 aParent->GetChildren(browsingContexts);
2205 // Search for something to load next.
2206 bool differenceFound = false;
2207 for (int32_t i = 0; i < ncnt; ++i) {
2208 // First get an entry which may cause a new page to be loaded.
2209 nsCOMPtr<nsISHEntry> nChild;
2210 aNextEntry->GetChildAt(i, getter_AddRefs(nChild));
2211 if (!nChild) {
2212 continue;
2214 nsID docshellID;
2215 nChild->GetDocshellID(docshellID);
2217 // Then find the associated docshell.
2218 RefPtr<BrowsingContext> bcChild;
2219 for (const RefPtr<BrowsingContext>& bc : browsingContexts) {
2220 if (bc->GetHistoryID() == docshellID) {
2221 bcChild = bc;
2222 break;
2225 if (!bcChild) {
2226 continue;
2229 // Then look at the previous entries to see if there was
2230 // an entry for the docshell.
2231 nsCOMPtr<nsISHEntry> pChild;
2232 for (int32_t k = 0; k < pcnt; ++k) {
2233 nsCOMPtr<nsISHEntry> child;
2234 aPrevEntry->GetChildAt(k, getter_AddRefs(child));
2235 if (child) {
2236 nsID dID;
2237 child->GetDocshellID(dID);
2238 if (dID == docshellID) {
2239 pChild = child;
2240 break;
2244 if (!pChild) {
2245 continue;
2248 // Finally recursively call this method.
2249 // This will either load a new page to shell or some subshell or
2250 // do nothing.
2251 if (LoadDifferingEntries(pChild, nChild, bcChild, aLoadType, aLoadResults,
2252 aLoadCurrentEntry, aUserActivation, aOffset)) {
2253 differenceFound = true;
2256 return differenceFound;
2259 void nsSHistory::InitiateLoad(nsISHEntry* aFrameEntry,
2260 BrowsingContext* aFrameBC, long aLoadType,
2261 nsTArray<LoadEntryResult>& aLoadResults,
2262 bool aLoadCurrentEntry, bool aUserActivation,
2263 int32_t aOffset) {
2264 MOZ_ASSERT(aFrameBC && aFrameEntry);
2266 LoadEntryResult* loadResult = aLoadResults.AppendElement();
2267 loadResult->mBrowsingContext = aFrameBC;
2269 nsCOMPtr<nsIURI> newURI = aFrameEntry->GetURI();
2270 RefPtr<nsDocShellLoadState> loadState = new nsDocShellLoadState(newURI);
2272 loadState->SetHasValidUserGestureActivation(aUserActivation);
2274 // At the time we initiate a history entry load we already know if https-first
2275 // was able to upgrade the request from http to https. There is no point in
2276 // re-retrying to upgrade.
2277 loadState->SetIsExemptFromHTTPSFirstMode(true);
2279 /* Set the loadType in the SHEntry too to what was passed on.
2280 * This will be passed on to child subframes later in nsDocShell,
2281 * so that proper loadType is maintained through out a frameset
2283 aFrameEntry->SetLoadType(aLoadType);
2285 loadState->SetLoadType(aLoadType);
2287 loadState->SetSHEntry(aFrameEntry);
2289 // If we're loading the current entry we want to treat it as not a
2290 // same-document navigation (see nsDocShell::IsSameDocumentNavigation), so
2291 // record that here in the LoadingSessionHistoryEntry.
2292 bool loadingCurrentEntry;
2293 if (mozilla::SessionHistoryInParent()) {
2294 loadingCurrentEntry = aLoadCurrentEntry;
2295 } else {
2296 loadingCurrentEntry =
2297 aFrameBC->GetDocShell() &&
2298 nsDocShell::Cast(aFrameBC->GetDocShell())->IsOSHE(aFrameEntry);
2300 loadState->SetLoadIsFromSessionHistory(aOffset, loadingCurrentEntry);
2302 if (mozilla::SessionHistoryInParent()) {
2303 nsCOMPtr<SessionHistoryEntry> she = do_QueryInterface(aFrameEntry);
2304 aFrameBC->Canonical()->AddLoadingSessionHistoryEntry(
2305 loadState->GetLoadingSessionHistoryInfo()->mLoadId, she);
2308 nsCOMPtr<nsIURI> originalURI = aFrameEntry->GetOriginalURI();
2309 loadState->SetOriginalURI(originalURI);
2311 loadState->SetLoadReplace(aFrameEntry->GetLoadReplace());
2313 loadState->SetLoadFlags(nsIWebNavigation::LOAD_FLAGS_NONE);
2314 nsCOMPtr<nsIPrincipal> triggeringPrincipal =
2315 aFrameEntry->GetTriggeringPrincipal();
2316 loadState->SetTriggeringPrincipal(triggeringPrincipal);
2317 loadState->SetFirstParty(false);
2318 nsCOMPtr<nsIContentSecurityPolicy> csp = aFrameEntry->GetCsp();
2319 loadState->SetCsp(csp);
2321 loadResult->mLoadState = std::move(loadState);
2324 NS_IMETHODIMP
2325 nsSHistory::CreateEntry(nsISHEntry** aEntry) {
2326 nsCOMPtr<nsISHEntry> entry;
2327 if (XRE_IsParentProcess() && mozilla::SessionHistoryInParent()) {
2328 entry = new SessionHistoryEntry();
2329 } else {
2330 entry = new nsSHEntry();
2332 entry.forget(aEntry);
2333 return NS_OK;
2336 NS_IMETHODIMP_(bool)
2337 nsSHistory::IsEmptyOrHasEntriesForSingleTopLevelPage() {
2338 if (mEntries.IsEmpty()) {
2339 return true;
2342 nsISHEntry* entry = mEntries[0];
2343 size_t length = mEntries.Length();
2344 for (size_t i = 1; i < length; ++i) {
2345 bool sharesDocument = false;
2346 mEntries[i]->SharesDocumentWith(entry, &sharesDocument);
2347 if (!sharesDocument) {
2348 return false;
2352 return true;
2355 static void CollectEntries(
2356 nsTHashMap<nsIDHashKey, SessionHistoryEntry*>& aHashtable,
2357 SessionHistoryEntry* aEntry) {
2358 aHashtable.InsertOrUpdate(aEntry->DocshellID(), aEntry);
2359 for (const RefPtr<SessionHistoryEntry>& entry : aEntry->Children()) {
2360 if (entry) {
2361 CollectEntries(aHashtable, entry);
2366 static void UpdateEntryLength(
2367 nsTHashMap<nsIDHashKey, SessionHistoryEntry*>& aHashtable,
2368 SessionHistoryEntry* aNewEntry, bool aMove) {
2369 SessionHistoryEntry* oldEntry = aHashtable.Get(aNewEntry->DocshellID());
2370 if (oldEntry) {
2371 MOZ_ASSERT(oldEntry->GetID() != aNewEntry->GetID() || !aMove ||
2372 !aNewEntry->BCHistoryLength().Modified());
2373 aNewEntry->SetBCHistoryLength(oldEntry->BCHistoryLength());
2374 if (oldEntry->GetID() != aNewEntry->GetID()) {
2375 MOZ_ASSERT(!aMove);
2376 // If we have a new id then aNewEntry was created for a new load, so
2377 // record that in BCHistoryLength.
2378 ++aNewEntry->BCHistoryLength();
2379 } else if (aMove) {
2380 // We're moving the BCHistoryLength from the old entry to the new entry,
2381 // so we need to let the old entry know that it's not contributing to its
2382 // BCHistoryLength, and the new one that it does if the old one was
2383 // before.
2384 aNewEntry->BCHistoryLength().SetModified(
2385 oldEntry->BCHistoryLength().Modified());
2386 oldEntry->BCHistoryLength().SetModified(false);
2390 for (const RefPtr<SessionHistoryEntry>& entry : aNewEntry->Children()) {
2391 if (entry) {
2392 UpdateEntryLength(aHashtable, entry, aMove);
2397 void nsSHistory::UpdateEntryLength(nsISHEntry* aOldEntry, nsISHEntry* aNewEntry,
2398 bool aMove) {
2399 nsCOMPtr<SessionHistoryEntry> oldSHE = do_QueryInterface(aOldEntry);
2400 nsCOMPtr<SessionHistoryEntry> newSHE = do_QueryInterface(aNewEntry);
2402 if (!oldSHE || !newSHE) {
2403 return;
2406 nsTHashMap<nsIDHashKey, SessionHistoryEntry*> docshellIDToEntry;
2407 CollectEntries(docshellIDToEntry, oldSHE);
2409 ::UpdateEntryLength(docshellIDToEntry, newSHE, aMove);