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"
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"
26 #include "nsIXULRuntime.h"
27 #include "nsNetUtil.h"
28 #include "nsTHashMap.h"
29 #include "nsSHEntry.h"
30 #include "SessionHistoryEntry.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.
79 ~ListHelper() { mList
.clear(); }
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) \
111 if (MOZ_LOG_TEST(gSHistoryLog, LogLevel::Debug)) { \
112 nsAutoCString _specStr("(null)"_ns); \
114 _specStr = uri->GetSpecOrDefault(); \
116 const char* _spec = _specStr.get(); \
121 // This macro makes it easy to log a message including an SHEntry's URI.
124 // nsCOMPtr<nsISHEntry> shentry = [...];
125 // LOG_SHENTRY_SPEC(("shentry %p has uri %s.", shentry.get(), _spec), shentry);
127 #define LOG_SHENTRY_SPEC(format, shentry) \
129 if (MOZ_LOG_TEST(gSHistoryLog, LogLevel::Debug)) { \
130 nsCOMPtr<nsIURI> uri = shentry->GetURI(); \
131 LOG_SPEC(format, uri); \
135 // Calls a F on all registered session history listeners.
136 template <typename F
>
137 static void NotifyListeners(nsAutoTObserverArray
<nsWeakPtr
, 2>& aListeners
,
139 for (const nsWeakPtr
& weakPtr
: aListeners
.EndLimitedRange()) {
140 nsCOMPtr
<nsISHistoryListener
> listener
= do_QueryReferent(weakPtr
);
147 class MOZ_STACK_CLASS SHistoryChangeNotifier
{
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() {
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
{
180 nsSHistoryObserver() {}
182 static void PrefChanged(const char* aPref
, void* aSelf
);
183 void PrefChanged(const char* aPref
);
186 ~nsSHistoryObserver() {}
189 StaticRefPtr
<nsSHistoryObserver
> gObserver
;
191 NS_IMPL_ISUPPORTS(nsSHistoryObserver
, nsIObserver
)
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();
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();
214 void nsSHistory::EvictDocumentViewerForEntry(nsISHEntry
* aEntry
) {
215 nsCOMPtr
<nsIDocumentViewer
> viewer
= aEntry
->GetDocumentViewer();
217 LOG_SHENTRY_SPEC(("Evicting content viewer 0x%p for "
218 "owning SHEntry 0x%p at %s.",
219 viewer
.get(), aEntry
, _spec
),
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();
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
;
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
251 int32_t index
= GetIndexOfEntry(aEntry
);
253 RemoveDynEntries(index
, aEntry
);
257 nsSHistory::nsSHistory(BrowsingContext
* aRootBC
)
258 : mRootBC(aRootBC
->Id()),
259 mHasOngoingUpdate(false),
262 mRootDocShellID(aRootBC
->GetHistoryID()) {
263 static bool sCalledStartup
= false;
264 if (!sCalledStartup
) {
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
>(
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.
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
)
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.
301 # define MAX_TOTAL_VIEWERS_BIAS 15.9
303 # define MAX_TOTAL_VIEWERS_BIAS 14
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
313 // RAM | ContentViewers | on Android
314 // -------------------------------------
325 uint64_t bytes
= PR_GetPhysicalMemorySize();
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
334 if (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
;
346 viewers
= (uint32_t)(x
* x
- x
+ 2.001); // add .001 for rounding
350 // Cap it off at 8 max
358 void nsSHistory::UpdatePrefs() {
359 Preferences::GetInt(PREF_SHISTORY_SIZE
, &gHistoryMaxSize
);
360 if (mozilla::SessionHistoryInParent() && !mozilla::BFCacheInParent()) {
361 sHistoryMaxTotalViewers
= 0;
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();
375 nsresult
nsSHistory::Startup() {
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
389 gObserver
= new nsSHistoryObserver();
390 Preferences::RegisterCallbacks(nsSHistoryObserver::PrefChanged
,
391 kObservedPrefs
, gObserver
.get());
393 nsCOMPtr
<nsIObserverService
> obsSvc
=
394 mozilla::services::GetObserverService();
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);
409 void nsSHistory::Shutdown() {
411 Preferences::UnregisterCallbacks(nsSHistoryObserver::PrefChanged
,
412 kObservedPrefs
, gObserver
.get());
414 nsCOMPtr
<nsIObserverService
> obsSvc
=
415 mozilla::services::GetObserverService();
417 obsSvc
->RemoveObserver(gObserver
, "cacheservice:empty-cache");
418 obsSvc
->RemoveObserver(gObserver
, "memory-pressure");
425 already_AddRefed
<nsISHEntry
> nsSHistory::GetRootSHEntry(nsISHEntry
* aEntry
) {
426 nsCOMPtr
<nsISHEntry
> rootEntry
= aEntry
;
427 nsCOMPtr
<nsISHEntry
> result
= nullptr;
430 rootEntry
= result
->GetParent();
433 return result
.forget();
437 nsresult
nsSHistory::WalkHistoryEntries(nsISHEntry
* aRootEntry
,
438 BrowsingContext
* aBC
,
439 WalkHistoryEntriesFunc aCallback
,
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
));
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
);
455 BrowsingContext
* childBC
= nullptr;
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
)) {
468 nsDocShell
* docshell
= static_cast<nsDocShell
*>(child
->GetDocShell());
469 if (docshell
&& docshell
->HasHistoryEntry(childEntry
)) {
470 childBC
= docshell
->GetBrowsingContext();
474 // XXX Simplify this once the old and new session history
475 // implementations don't run at the same time.
482 nsresult rv
= aCallback(childEntry
, childBC
, i
, aData
);
483 NS_ENSURE_SUCCESS(rv
, rv
);
489 // callback data for WalkHistoryEntries
490 struct MOZ_STACK_CLASS CloneAndReplaceData
{
491 CloneAndReplaceData(uint32_t aCloneID
, nsISHEntry
* aReplaceEntry
,
492 bool aCloneChildren
, nsISHEntry
* aDestTreeParent
)
494 cloneChildren(aCloneChildren
),
495 replaceEntry(aReplaceEntry
),
496 destTreeParent(aDestTreeParent
) {}
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
;
515 if (data
->destTreeParent
) {
516 data
->destTreeParent
->AddChild(nullptr, aChildIndex
);
521 uint32_t srcID
= aEntry
->GetID();
524 if (srcID
== cloneID
) {
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
) {
536 CloneAndReplaceData
childData(cloneID
, replaceEntry
, data
->cloneChildren
,
538 rv
= nsSHistory::WalkHistoryEntries(aEntry
, aOwnerBC
, CloneAndReplaceChild
,
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
;
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
);
567 void nsSHistory::WalkContiguousEntries(
568 nsISHEntry
* aEntry
, const std::function
<void(nsISHEntry
*)>& aCallback
) {
571 nsCOMPtr
<nsISHistory
> shistory
= aEntry
->GetShistory();
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.
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.
586 // Walk backward to find the entries that have the same origin as the
588 for (int32_t i
= index
- 1; i
>= 0; i
--) {
589 RefPtr
<nsISHEntry
> entry
;
590 shistory
->GetEntryAtIndex(i
, getter_AddRefs(entry
));
592 nsCOMPtr
<nsIURI
> uri
= entry
->GetURI();
593 if (NS_FAILED(nsContentUtils::GetSecurityManager()->CheckSameOriginURI(
594 targetURI
, uri
, false, false))) {
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
));
607 nsCOMPtr
<nsIURI
> uri
= entry
->GetURI();
608 if (NS_FAILED(nsContentUtils::GetSecurityManager()->CheckSameOriginURI(
609 targetURI
, uri
, false, false))) {
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
631 nsCOMPtr
<nsISHEntry
> child
;
632 nsCOMPtr
<nsISHEntry
> currentHE
;
633 int32_t index
= mIndex
;
635 return NS_ERROR_FAILURE
;
638 GetEntryAtIndex(index
, getter_AddRefs(currentHE
));
639 NS_ENSURE_TRUE(currentHE
, NS_ERROR_FAILURE
);
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());
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
) {
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
);
683 data
->destTreeParent
->GetChildCount(&childCount
);
684 for (int32_t i
= 0; i
< childCount
; ++i
) {
685 data
->destTreeParent
->GetChildAt(i
, getter_AddRefs(entry
));
690 if (entry
->GetID() == targetID
) {
691 destEntry
.swap(entry
);
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
,
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());
715 docshell
->SwapHistoryEntries(aOldEntry
, aNewEntry
);
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
);
740 nsSHistory::AddToRootSessionHistory(bool aCloneChildren
, nsISHEntry
* aOSHE
,
741 BrowsingContext
* aRootBC
,
742 nsISHEntry
* aEntry
, uint32_t aLoadType
,
744 Maybe
<int32_t>* aPreviousEntryIndex
,
745 Maybe
<int32_t>* aLoadedEntryIndex
) {
746 MOZ_ASSERT(aRootBC
->IsTop());
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
771 rv
= ReplaceEntry(index
, aEntry
);
773 // If we're trying to replace an inexistant shistory entry, append.
774 addToSHistory
= true;
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());
792 /* Add an entry to the History list at mIndex and
793 * increment the index to point to the new entry
796 nsSHistory::AddEntry(nsISHEntry
* aSHEntry
, bool aPersist
) {
797 NS_ENSURE_ARG(aSHEntry
);
799 nsCOMPtr
<nsISHistory
> shistoryOfEntry
= aSHEntry
->GetShistory();
800 if (shistoryOfEntry
&& shistoryOfEntry
!= this) {
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();
814 aSHEntry
->SetDocshellID(mRootDocShellID
);
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();
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
);
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();
864 void nsSHistory::NotifyOnHistoryReplaceEntry() {
865 NotifyListeners(mListeners
, [](auto l
) { l
->OnHistoryReplaceEntry(); });
868 /* Get size of the history list */
870 nsSHistory::GetCount(int32_t* aResult
) {
871 MOZ_ASSERT(aResult
, "null out param?");
877 nsSHistory::GetIndex(int32_t* aResult
) {
878 MOZ_ASSERT(aResult
, "null out param?");
884 nsSHistory::SetIndex(int32_t aIndex
) {
885 if (aIndex
< 0 || aIndex
>= Length()) {
886 return NS_ERROR_FAILURE
;
893 /* Get the requestedIndex */
895 nsSHistory::GetRequestedIndex(int32_t* aResult
) {
896 MOZ_ASSERT(aResult
, "null out param?");
897 *aResult
= mRequestedIndex
;
902 nsSHistory::InternalSetRequestedIndex(int32_t aRequestedIndex
) {
903 MOZ_ASSERT(aRequestedIndex
>= -1 && aRequestedIndex
< Length());
904 mRequestedIndex
= aRequestedIndex
;
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
];
920 NS_IMETHODIMP_(int32_t)
921 nsSHistory::GetIndexOfEntry(nsISHEntry
* aSHEntry
) {
922 for (int32_t i
= 0; i
< Length(); i
++) {
923 if (aSHEntry
== mEntries
[i
]) {
931 static void LogEntry(nsISHEntry
* aEntry
, int32_t aIndex
, int32_t aTotal
,
932 const nsCString
& aPrefix
, bool aIsCurrent
) {
934 MOZ_LOG(gSHLog
, LogLevel::Debug
,
935 (" %s+- %i SH Entry null\n", aPrefix
.get(), aIndex
));
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();
948 shared
= static_cast<nsSHEntry
*>(aEntry
)->GetState();
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("| ");
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()));
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
)) {
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()) {
1018 MarkAsInitialEntry(entry
, aHashtable
);
1023 static void ClearEntries(SessionHistoryEntry
* aEntry
) {
1024 aEntry
->ClearBCHistoryLength();
1025 for (const RefPtr
<SessionHistoryEntry
>& entry
: aEntry
->Children()) {
1027 ClearEntries(entry
);
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
]);
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
]);
1068 RefPtr
<BrowsingContext
> rootBC
= GetBrowsingContext();
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
);
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
);
1104 return NS_ERROR_FAILURE
;
1107 mListeners
.AppendElementUnlessExists(listener
);
1111 void nsSHistory::NotifyListenersDocumentViewerEvicted(uint32_t aNumEvicted
) {
1112 NotifyListeners(mListeners
, [aNumEvicted
](auto l
) {
1113 l
->OnDocumentViewerEvicted(aNumEvicted
);
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
);
1126 /* Replace an entry in the History list at a particular index.
1127 * Do not update index or count.
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) {
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();
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.
1162 nsSHistory::NotifyOnHistoryReload(bool* aCanReload
) {
1165 for (const nsWeakPtr
& weakPtr
: mListeners
.EndLimitedRange()) {
1166 nsCOMPtr
<nsISHistoryListener
> listener
= do_QueryReferent(weakPtr
);
1170 if (NS_SUCCEEDED(listener
->OnHistoryReload(&retval
)) && !retval
) {
1171 *aCanReload
= false;
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();
1191 NS_IMETHODIMP_(void)
1192 nsSHistory::EvictDocumentViewersOrReplaceEntry(nsISHEntry
* aNewSHEntry
,
1196 GetIndex(&curIndex
);
1197 if (curIndex
> -1) {
1198 EvictOutOfRangeDocumentViewers(curIndex
);
1201 nsCOMPtr
<nsISHEntry
> rootSHEntry
= nsSHistory::GetRootSHEntry(aNewSHEntry
);
1203 int32_t index
= GetIndexOfEntry(rootSHEntry
);
1205 ReplaceEntry(index
, rootSHEntry
);
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
]);
1221 static void FinishRestore(CanonicalBrowsingContext
* aBrowsingContext
,
1222 nsDocShellLoadState
* aLoadState
,
1223 SessionHistoryEntry
* aEntry
,
1224 nsFrameLoader
* aFrameLoader
, bool aCanSave
) {
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();
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
,
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();
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);
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();
1331 aFrameLoader
->Destroy();
1333 // Fall back to do a normal load.
1334 aBrowsingContext
->LoadURI(aLoadState
, false);
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();
1348 RefPtr
<nsFrameLoader
> frameLoader
= she
->GetFrameLoader();
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",
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()
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()
1381 ->GetSessionHistory();
1383 shistory
->InternalSetRequestedIndex(-1);
1392 FinishRestore(canonicalBC
, loadState
, she
, frameLoader
, canSave
);
1396 she
->SetFrameLoader(nullptr);
1397 frameLoader
->Destroy();
1401 RefPtr
<BrowsingContext
> bc
= aLoadEntry
.mBrowsingContext
;
1402 RefPtr
<nsDocShellLoadState
> loadState
= aLoadEntry
.mLoadState
;
1403 bc
->LoadURI(loadState
, false);
1407 void nsSHistory::LoadURIs(nsTArray
<LoadEntryResult
>& aLoadResults
) {
1408 for (LoadEntryResult
& loadEntry
: aLoadResults
) {
1409 LoadURIOrBFCache(loadEntry
);
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()) {
1423 LoadURIs(loadResults
);
1427 nsresult
nsSHistory::Reload(uint32_t aReloadFlags
,
1428 nsTArray
<LoadEntryResult
>& aLoadResults
) {
1429 MOZ_ASSERT(aLoadResults
.IsEmpty());
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
;
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
));
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();
1468 nsSHistory::ReloadCurrentEntry() {
1469 nsTArray
<LoadEntryResult
> loadResults
;
1470 nsresult rv
= ReloadCurrentEntry(loadResults
);
1471 NS_ENSURE_SUCCESS(rv
, rv
);
1473 LoadURIs(loadResults
);
1477 nsresult
nsSHistory::ReloadCurrentEntry(
1478 nsTArray
<LoadEntryResult
>& aLoadResults
) {
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:
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
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.)
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
);
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();
1545 safeViewers
.AppendObject(viewer
);
1546 } else if (nsCOMPtr
<SessionHistoryEntry
> she
=
1547 do_QueryInterface(mEntries
[i
])) {
1548 nsFrameLoader
* frameLoader
= she
->GetFrameLoader();
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();
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();
1569 if (!safeFrameLoaders
.Contains(frameLoader
)) {
1570 EvictDocumentViewerForEntry(entry
);
1579 class EntryAndDistance
{
1581 EntryAndDistance(nsSHistory
* aSHistory
, nsISHEntry
* aEntry
, uint32_t aDist
)
1582 : mSHistory(aSHistory
),
1584 mViewer(aEntry
->GetDocumentViewer()),
1585 mLastTouched(mEntry
->GetLastTouched()),
1587 nsCOMPtr
<SessionHistoryEntry
> she
= do_QueryInterface(aEntry
);
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
;
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
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();
1656 bool hasDocumentViewerOrFrameLoader
= false;
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
));
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
));
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
) {
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.)
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
) {
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
);
1738 return NS_ERROR_FAILURE
;
1741 NS_IMETHODIMP_(void)
1742 nsSHistory::EvictExpiredDocumentViewerForEntry(
1743 SHEntrySharedParentState
* aEntry
) {
1745 nsCOMPtr
<nsISHEntry
> shEntry
;
1746 FindEntryForBFCache(aEntry
, getter_AddRefs(shEntry
), &index
);
1748 if (index
== mIndex
) {
1749 NS_WARNING("How did the current SHEntry expire?");
1753 EvictDocumentViewerForEntry(shEntry
);
1757 NS_IMETHODIMP_(void)
1758 nsSHistory::AddToExpirationTracker(SHEntrySharedParentState
* aEntry
) {
1759 RefPtr
<SHEntrySharedParentState
> entry
= aEntry
;
1760 if (!mHistoryTracker
|| !entry
) {
1764 mHistoryTracker
->AddObject(entry
);
1768 NS_IMETHODIMP_(void)
1769 nsSHistory::RemoveFromExpirationTracker(SHEntrySharedParentState
* aEntry
) {
1770 RefPtr
<SHEntrySharedParentState
> entry
= aEntry
;
1771 MOZ_ASSERT(mHistoryTracker
&& !mHistoryTracker
->IsEmpty());
1772 if (!mHistoryTracker
|| !entry
) {
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.
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
));
1798 if (child
->IsDynamicallyAdded()) {
1799 child
->GetDocshellID(*aDocshellIDs
.AppendElement());
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
));
1816 child
->GetDocshellID(docshelldID
);
1817 if (aDocshellIDs
.Contains(docshelldID
)) {
1819 aRoot
->RemoveChild(child
);
1820 } else if (RemoveFromSessionHistoryEntry(child
, aDocshellIDs
)) {
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
) {
1839 if ((!aEntry1
&& aEntry2
) || (aEntry1
&& !aEntry2
)) {
1842 uint32_t id1
= aEntry1
->GetID();
1843 uint32_t id2
= aEntry2
->GetID();
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
)) {
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;
1873 nsCOMPtr
<nsISHEntry
> root1
, root2
;
1874 rv
= GetEntryAtIndex(aIndex
, getter_AddRefs(root1
));
1875 if (NS_FAILED(rv
)) {
1878 rv
= GetEntryAtIndex(compareIndex
, getter_AddRefs(root2
));
1879 if (NS_FAILED(rv
)) {
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
1891 UpdateEntryLength(root1
, root2
, true);
1893 nsCOMPtr
<SessionHistoryEntry
> she
= do_QueryInterface(root1
);
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
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;
1931 NS_IMETHODIMP_(void)
1932 nsSHistory::RemoveEntries(nsTArray
<nsID
>& aIDs
, int32_t aStartIndex
) {
1934 RemoveEntries(aIDs
, aStartIndex
, &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
,
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
)) {
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
));
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
);
1984 GetEntryAtIndex(aIndex
, getter_AddRefs(entry
));
1988 AutoTArray
<nsID
, 16> toBeRemovedEntries
;
1989 GetDynamicChildren(entry
, toBeRemovedEntries
);
1990 if (toBeRemovedEntries
.Length()) {
1991 RemoveEntries(toBeRemovedEntries
, aIndex
);
1996 void nsSHistory::RemoveDynEntriesForBFCacheEntry(nsIBFCacheEntry
* aBFEntry
) {
1998 nsCOMPtr
<nsISHEntry
> shEntry
;
1999 FindEntryForBFCache(static_cast<nsSHEntryShared
*>(aBFEntry
),
2000 getter_AddRefs(shEntry
), &index
);
2002 RemoveDynEntries(index
, shEntry
);
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;
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
);
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
));
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
,
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();
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.
2121 bool same_doc
= false;
2122 prevEntry
->SharesDocumentWith(nextEntry
, &same_doc
);
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
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
);
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
);
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
);
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
));
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
) {
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
));
2237 child
->GetDocshellID(dID
);
2238 if (dID
== docshellID
) {
2248 // Finally recursively call this method.
2249 // This will either load a new page to shell or some subshell or
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
,
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
;
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
);
2325 nsSHistory::CreateEntry(nsISHEntry
** aEntry
) {
2326 nsCOMPtr
<nsISHEntry
> entry
;
2327 if (XRE_IsParentProcess() && mozilla::SessionHistoryInParent()) {
2328 entry
= new SessionHistoryEntry();
2330 entry
= new nsSHEntry();
2332 entry
.forget(aEntry
);
2336 NS_IMETHODIMP_(bool)
2337 nsSHistory::IsEmptyOrHasEntriesForSingleTopLevelPage() {
2338 if (mEntries
.IsEmpty()) {
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
) {
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()) {
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());
2371 MOZ_ASSERT(oldEntry
->GetID() != aNewEntry
->GetID() || !aMove
||
2372 !aNewEntry
->BCHistoryLength().Modified());
2373 aNewEntry
->SetBCHistoryLength(oldEntry
->BCHistoryLength());
2374 if (oldEntry
->GetID() != aNewEntry
->GetID()) {
2376 // If we have a new id then aNewEntry was created for a new load, so
2377 // record that in BCHistoryLength.
2378 ++aNewEntry
->BCHistoryLength();
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
2384 aNewEntry
->BCHistoryLength().SetModified(
2385 oldEntry
->BCHistoryLength().Modified());
2386 oldEntry
->BCHistoryLength().SetModified(false);
2390 for (const RefPtr
<SessionHistoryEntry
>& entry
: aNewEntry
->Children()) {
2392 UpdateEntryLength(aHashtable
, entry
, aMove
);
2397 void nsSHistory::UpdateEntryLength(nsISHEntry
* aOldEntry
, nsISHEntry
* aNewEntry
,
2399 nsCOMPtr
<SessionHistoryEntry
> oldSHE
= do_QueryInterface(aOldEntry
);
2400 nsCOMPtr
<SessionHistoryEntry
> newSHE
= do_QueryInterface(aNewEntry
);
2402 if (!oldSHE
|| !newSHE
) {
2406 nsTHashMap
<nsIDHashKey
, SessionHistoryEntry
*> docshellIDToEntry
;
2407 CollectEntries(docshellIDToEntry
, oldSHE
);
2409 ::UpdateEntryLength(docshellIDToEntry
, newSHE
, aMove
);