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/. */
11 #include "nsDocShellLoadState.h"
12 #include "nsExpirationTracker.h"
13 #include "nsISHistory.h"
14 #include "nsSHEntryShared.h"
15 #include "nsSimpleEnumerator.h"
16 #include "nsTObserverArray.h"
17 #include "nsWeakReference.h"
19 #include "mozilla/dom/ipc/IdType.h"
20 #include "mozilla/LinkedList.h"
21 #include "mozilla/UniquePtr.h"
25 class nsSHistoryObserver
;
30 class LoadSHEntryResult
;
32 } // namespace mozilla
34 class nsSHistory
: public mozilla::LinkedListElement
<nsSHistory
>,
36 public nsSupportsWeakReference
{
38 // The timer based history tracker is used to evict bfcache on expiration.
39 class HistoryTracker final
40 : public nsExpirationTracker
<mozilla::dom::SHEntrySharedParentState
, 3> {
42 explicit HistoryTracker(nsSHistory
* aSHistory
, uint32_t aTimeout
,
43 nsIEventTarget
* aEventTarget
)
44 : nsExpirationTracker(1000 * aTimeout
/ 2, "HistoryTracker",
46 MOZ_ASSERT(aSHistory
);
47 mSHistory
= aSHistory
;
51 virtual void NotifyExpired(
52 mozilla::dom::SHEntrySharedParentState
* aObj
) override
{
54 mSHistory
->EvictExpiredDocumentViewerForEntry(aObj
);
58 // HistoryTracker is owned by nsSHistory; it always outlives HistoryTracker
59 // so it's safe to use raw pointer here.
60 nsSHistory
* mSHistory
;
63 // Structure used in SetChildHistoryEntry
64 struct SwapEntriesData
{
65 mozilla::dom::BrowsingContext
*
66 ignoreBC
; // constant; the browsing context to ignore
67 nsISHEntry
* destTreeRoot
; // constant; the root of the dest tree
68 nsISHEntry
* destTreeParent
; // constant; the node under destTreeRoot
69 // whose children will correspond to aEntry
72 explicit nsSHistory(mozilla::dom::BrowsingContext
* aRootBC
);
76 // One time initialization method
77 static nsresult
Startup();
78 static void Shutdown();
79 static void UpdatePrefs();
81 // Max number of total cached content viewers. If the pref
82 // browser.sessionhistory.max_total_viewers is negative, then
83 // this value is calculated based on the total amount of memory.
84 // Otherwise, it comes straight from the pref.
85 static uint32_t GetMaxTotalViewers() { return sHistoryMaxTotalViewers
; }
87 // Get the root SHEntry from a given entry.
88 static already_AddRefed
<nsISHEntry
> GetRootSHEntry(nsISHEntry
* aEntry
);
90 // Callback prototype for WalkHistoryEntries.
91 // `aEntry` is the child history entry, `aBC` is its corresponding browsing
92 // context, `aChildIndex` is the child's index in its parent entry, and
93 // `aData` is the opaque pointer passed to WalkHistoryEntries. Both structs
94 // that are passed as `aData` to this function have a field
95 // `aEntriesToUpdate`, which is an array of entries we need to update in
96 // docshell, if the 'SH in parent' pref is on (which implies that this method
97 // is executed in the parent)
98 typedef nsresult (*WalkHistoryEntriesFunc
)(nsISHEntry
* aEntry
,
99 mozilla::dom::BrowsingContext
* aBC
,
100 int32_t aChildIndex
, void* aData
);
102 // Clone a session history tree for subframe navigation.
103 // The tree rooted at |aSrcEntry| will be cloned into |aDestEntry|, except
104 // for the entry with id |aCloneID|, which will be replaced with
105 // |aReplaceEntry|. |aSrcShell| is a (possibly null) docshell which
106 // corresponds to |aSrcEntry| via its mLSHE or mOHE pointers, and will
107 // have that pointer updated to point to the cloned history entry.
108 // If aCloneChildren is true then the children of the entry with id
109 // |aCloneID| will be cloned into |aReplaceEntry|.
110 static nsresult
CloneAndReplace(nsISHEntry
* aSrcEntry
,
111 mozilla::dom::BrowsingContext
* aOwnerBC
,
112 uint32_t aCloneID
, nsISHEntry
* aReplaceEntry
,
113 bool aCloneChildren
, nsISHEntry
** aDestEntry
);
115 // Child-walking callback for CloneAndReplace
116 static nsresult
CloneAndReplaceChild(nsISHEntry
* aEntry
,
117 mozilla::dom::BrowsingContext
* aOwnerBC
,
118 int32_t aChildIndex
, void* aData
);
120 // Child-walking callback for SetHistoryEntry
121 static nsresult
SetChildHistoryEntry(nsISHEntry
* aEntry
,
122 mozilla::dom::BrowsingContext
* aBC
,
123 int32_t aEntryIndex
, void* aData
);
125 // For each child of aRootEntry, find the corresponding shell which is
126 // a child of aBC, and call aCallback. The opaque pointer aData
127 // is passed to the callback.
128 static nsresult
WalkHistoryEntries(nsISHEntry
* aRootEntry
,
129 mozilla::dom::BrowsingContext
* aBC
,
130 WalkHistoryEntriesFunc aCallback
,
133 // This function finds all entries that are contiguous and same-origin with
134 // the aEntry. And call the aCallback on them, including the aEntry. This only
135 // works for the root entries. It will do nothing for non-root entries.
136 static void WalkContiguousEntries(
137 nsISHEntry
* aEntry
, const std::function
<void(nsISHEntry
*)>& aCallback
);
139 nsTArray
<nsCOMPtr
<nsISHEntry
>>& Entries() { return mEntries
; }
141 void NotifyOnHistoryReplaceEntry();
143 void RemoveEntries(nsTArray
<nsID
>& aIDs
, int32_t aStartIndex
,
146 // The size of the window of SHEntries which can have alive viewers in the
147 // bfcache around the currently active SHEntry.
149 // We try to keep viewers for SHEntries between index - VIEWER_WINDOW and
150 // index + VIEWER_WINDOW alive.
151 static const int32_t VIEWER_WINDOW
= 3;
153 struct LoadEntryResult
{
154 RefPtr
<mozilla::dom::BrowsingContext
> mBrowsingContext
;
155 RefPtr
<nsDocShellLoadState
> mLoadState
;
158 static void LoadURIs(nsTArray
<LoadEntryResult
>& aLoadResults
);
159 static void LoadURIOrBFCache(LoadEntryResult
& aLoadEntry
);
161 // If this doesn't return an error then either aLoadResult is set to nothing,
162 // in which case the caller should ignore the load, or it returns a valid
163 // LoadEntryResult in aLoadResult which the caller should use to do the load.
164 nsresult
Reload(uint32_t aReloadFlags
,
165 nsTArray
<LoadEntryResult
>& aLoadResults
);
166 nsresult
ReloadCurrentEntry(nsTArray
<LoadEntryResult
>& aLoadResults
);
167 nsresult
GotoIndex(int32_t aIndex
, nsTArray
<LoadEntryResult
>& aLoadResults
,
168 bool aSameEpoch
, bool aLoadCurrentEntry
,
169 bool aUserActivation
);
171 void WindowIndices(int32_t aIndex
, int32_t* aOutStartIndex
,
172 int32_t* aOutEndIndex
);
173 void NotifyListenersDocumentViewerEvicted(uint32_t aNumEvicted
);
175 int32_t Length() { return int32_t(mEntries
.Length()); }
176 int32_t Index() { return mIndex
; }
177 already_AddRefed
<mozilla::dom::BrowsingContext
> GetBrowsingContext() {
178 return mozilla::dom::BrowsingContext::Get(mRootBC
);
180 bool HasOngoingUpdate() { return mHasOngoingUpdate
; }
181 void SetHasOngoingUpdate(bool aVal
) { mHasOngoingUpdate
= aVal
; }
183 void SetBrowsingContext(mozilla::dom::BrowsingContext
* aRootBC
) {
184 uint64_t newID
= aRootBC
? aRootBC
->Id() : 0;
185 if (mRootBC
!= newID
) {
187 UpdateRootBrowsingContextState(aRootBC
);
191 int32_t GetIndexForReplace() {
192 // Replace current entry in session history; If the requested index is
193 // valid, it indicates the loading was triggered by a history load, and
194 // we should replace the entry at requested index instead.
195 return mRequestedIndex
== -1 ? mIndex
: mRequestedIndex
;
198 // Update the root browsing context state when adding, removing or
199 // replacing entries.
200 void UpdateRootBrowsingContextState() {
201 RefPtr
<mozilla::dom::BrowsingContext
> rootBC(GetBrowsingContext());
202 UpdateRootBrowsingContextState(rootBC
);
205 void GetEpoch(uint64_t& aEpoch
,
206 mozilla::Maybe
<mozilla::dom::ContentParentId
>& aId
) const {
208 aId
= mEpochParentId
;
210 void SetEpoch(uint64_t aEpoch
,
211 mozilla::Maybe
<mozilla::dom::ContentParentId
> aId
) {
213 mEpochParentId
= aId
;
219 virtual ~nsSHistory();
224 friend class nsSHistoryObserver
;
226 void UpdateRootBrowsingContextState(
227 mozilla::dom::BrowsingContext
* aBrowsingContext
);
229 bool LoadDifferingEntries(nsISHEntry
* aPrevEntry
, nsISHEntry
* aNextEntry
,
230 mozilla::dom::BrowsingContext
* aParent
,
232 nsTArray
<LoadEntryResult
>& aLoadResults
,
233 bool aLoadCurrentEntry
, bool aUserActivation
,
235 void InitiateLoad(nsISHEntry
* aFrameEntry
,
236 mozilla::dom::BrowsingContext
* aFrameBC
, long aLoadType
,
237 nsTArray
<LoadEntryResult
>& aLoadResult
,
238 bool aLoadCurrentEntry
, bool aUserActivation
,
241 nsresult
LoadEntry(int32_t aIndex
, long aLoadType
, uint32_t aHistCmd
,
242 nsTArray
<LoadEntryResult
>& aLoadResults
, bool aSameEpoch
,
243 bool aLoadCurrentEntry
, bool aUserActivation
);
245 // Find the history entry for a given bfcache entry. It only looks up between
246 // the range where alive viewers may exist (i.e nsSHistory::VIEWER_WINDOW).
247 nsresult
FindEntryForBFCache(mozilla::dom::SHEntrySharedParentState
* aEntry
,
248 nsISHEntry
** aResult
, int32_t* aResultIndex
);
250 // Evict content viewers in this window which don't lie in the "safe" range
252 virtual void EvictOutOfRangeWindowDocumentViewers(int32_t aIndex
);
253 void EvictDocumentViewerForEntry(nsISHEntry
* aEntry
);
254 static void GloballyEvictDocumentViewers();
255 static void GloballyEvictAllDocumentViewers();
257 // Calculates a max number of total
258 // content viewers to cache, based on amount of total memory
259 static uint32_t CalcMaxTotalViewers();
261 nsresult
LoadNextPossibleEntry(int32_t aNewIndex
, long aLoadType
,
263 nsTArray
<LoadEntryResult
>& aLoadResults
,
264 bool aLoadCurrentEntry
, bool aUserActivation
);
266 // aIndex is the index of the entry which may be removed.
267 // If aKeepNext is true, aIndex is compared to aIndex + 1,
268 // otherwise comparison is done to aIndex - 1.
269 bool RemoveDuplicate(int32_t aIndex
, bool aKeepNext
);
271 // We need to update entries in docshell and browsing context.
272 // If our docshell is located in parent or 'SH in parent' pref is off we can
273 // update it directly, Otherwise, we have two choices. If the browsing context
274 // that owns the docshell is in the same process as the process who called us
275 // over IPC, then we save entries that need to be updated in a list, and once
276 // we have returned from the IPC call, we update the docshell in the child
277 // process. Otherwise, if the browsing context is in a different process, we
278 // do a nested IPC call to that process to update the docshell in that
280 static void HandleEntriesToSwapInDocShell(mozilla::dom::BrowsingContext
* aBC
,
281 nsISHEntry
* aOldEntry
,
282 nsISHEntry
* aNewEntry
);
284 void UpdateEntryLength(nsISHEntry
* aOldEntry
, nsISHEntry
* aNewEntry
,
288 bool mHasOngoingUpdate
;
289 nsTArray
<nsCOMPtr
<nsISHEntry
>> mEntries
; // entries are never null
291 // Track all bfcache entries and evict on expiration.
292 mozilla::UniquePtr
<HistoryTracker
> mHistoryTracker
;
294 int32_t mIndex
; // -1 means "no index"
295 int32_t mRequestedIndex
; // -1 means "no requested index"
297 // Session History listeners
298 nsAutoTObserverArray
<nsWeakPtr
, 2> mListeners
;
300 nsID mRootDocShellID
;
302 // Max viewers allowed total, across all SHistory objects
303 static int32_t sHistoryMaxTotalViewers
;
305 // The epoch (and id) tell us what navigations occured within the same
306 // event-loop spin in the child. We need to know this in order to
307 // implement spec requirements for dropping pending navigations when we
308 // do a history navigation, if it's not same-document. Content processes
309 // update the epoch via a runnable on each ::Go (including AsyncGo).
311 mozilla::Maybe
<mozilla::dom::ContentParentId
> mEpochParentId
;
314 // CallerWillNotifyHistoryIndexAndLengthChanges is used to prevent
315 // SHistoryChangeNotifier to send automatic index and length updates.
316 // When that is done, it is up to the caller to explicitly send those updates.
317 // This is needed in cases when the update is a reaction to some change in a
318 // child process and child process passes a changeId to the parent side.
319 class MOZ_STACK_CLASS CallerWillNotifyHistoryIndexAndLengthChanges
{
321 explicit CallerWillNotifyHistoryIndexAndLengthChanges(
322 nsISHistory
* aSHistory
) {
323 nsSHistory
* shistory
= static_cast<nsSHistory
*>(aSHistory
);
324 if (shistory
&& !shistory
->HasOngoingUpdate()) {
325 shistory
->SetHasOngoingUpdate(true);
326 mSHistory
= shistory
;
330 ~CallerWillNotifyHistoryIndexAndLengthChanges() {
332 mSHistory
->SetHasOngoingUpdate(false);
336 RefPtr
<nsSHistory
> mSHistory
;
339 inline nsISupports
* ToSupports(nsSHistory
* aObj
) {
340 return static_cast<nsISHistory
*>(aObj
);
343 #endif /* nsSHistory */