nsSHistory.h (15349B)
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/. */ 6 7 #ifndef nsSHistory_h 8 #define nsSHistory_h 9 10 #include "nsCOMPtr.h" 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" 18 19 #include "mozilla/dom/ipc/IdType.h" 20 #include "mozilla/LinkedList.h" 21 #include "mozilla/UniquePtr.h" 22 23 class nsIDocShell; 24 class nsDocShell; 25 class nsSHistoryObserver; 26 class nsISHEntry; 27 28 namespace mozilla { 29 namespace dom { 30 class EntryList; 31 class LoadSHEntryResult; 32 } // namespace dom 33 } // namespace mozilla 34 35 class nsSHistory : public mozilla::LinkedListElement<nsSHistory>, 36 public nsISHistory, 37 public nsSupportsWeakReference { 38 public: 39 // The timer based history tracker is used to evict bfcache on expiration. 40 class HistoryTracker final 41 : public nsExpirationTracker<mozilla::dom::SHEntrySharedParentState, 3> { 42 public: 43 explicit HistoryTracker(nsSHistory* aSHistory, uint32_t aTimeout, 44 nsIEventTarget* aEventTarget) 45 : nsExpirationTracker(1000 * aTimeout / 2, "HistoryTracker"_ns, 46 aEventTarget) { 47 MOZ_ASSERT(aSHistory); 48 mSHistory = aSHistory; 49 } 50 51 protected: 52 virtual void NotifyExpired( 53 mozilla::dom::SHEntrySharedParentState* aObj) override { 54 RemoveObject(aObj); 55 mSHistory->EvictExpiredDocumentViewerForEntry(aObj); 56 } 57 58 private: 59 // HistoryTracker is owned by nsSHistory; it always outlives HistoryTracker 60 // so it's safe to use raw pointer here. 61 nsSHistory* mSHistory; 62 }; 63 64 // Structure used in SetChildHistoryEntry 65 struct SwapEntriesData { 66 mozilla::dom::BrowsingContext* 67 ignoreBC; // constant; the browsing context to ignore 68 nsISHEntry* destTreeRoot; // constant; the root of the dest tree 69 nsISHEntry* destTreeParent; // constant; the node under destTreeRoot 70 // whose children will correspond to aEntry 71 }; 72 73 explicit nsSHistory(mozilla::dom::BrowsingContext* aRootBC); 74 NS_DECL_ISUPPORTS 75 NS_DECL_NSISHISTORY 76 77 // One time initialization method 78 static nsresult Startup(); 79 static void Shutdown(); 80 static void UpdatePrefs(); 81 82 // Max number of total cached content viewers. If the pref 83 // browser.sessionhistory.max_total_viewers is negative, then 84 // this value is calculated based on the total amount of memory. 85 // Otherwise, it comes straight from the pref. 86 static uint32_t GetMaxTotalViewers() { return sHistoryMaxTotalViewers; } 87 88 // Get the root SHEntry from a given entry. 89 static already_AddRefed<nsISHEntry> GetRootSHEntry(nsISHEntry* aEntry); 90 91 // Callback prototype for WalkHistoryEntries. 92 // `aEntry` is the child history entry, `aBC` is its corresponding browsing 93 // context, `aChildIndex` is the child's index in its parent entry, and 94 // `aData` is the opaque pointer passed to WalkHistoryEntries. Both structs 95 // that are passed as `aData` to this function have a field 96 // `aEntriesToUpdate`, which is an array of entries we need to update in 97 // docshell, if the 'SH in parent' pref is on (which implies that this method 98 // is executed in the parent) 99 typedef nsresult (*WalkHistoryEntriesFunc)(nsISHEntry* aEntry, 100 mozilla::dom::BrowsingContext* aBC, 101 int32_t aChildIndex, void* aData); 102 103 // Clone a session history tree for subframe navigation. 104 // The tree rooted at |aSrcEntry| will be cloned into |aDestEntry|, except 105 // for the entry with id |aCloneID|, which will be replaced with 106 // |aReplaceEntry|. |aSrcShell| is a (possibly null) docshell which 107 // corresponds to |aSrcEntry| via its mLSHE or mOHE pointers, and will 108 // have that pointer updated to point to the cloned history entry. 109 // If aCloneChildren is true then the children of the entry with id 110 // |aCloneID| will be cloned into |aReplaceEntry|. 111 static nsresult CloneAndReplace(nsISHEntry* aSrcEntry, 112 mozilla::dom::BrowsingContext* aOwnerBC, 113 uint32_t aCloneID, nsISHEntry* aReplaceEntry, 114 bool aCloneChildren, nsISHEntry** aDestEntry); 115 116 // Child-walking callback for CloneAndReplace 117 static nsresult CloneAndReplaceChild(nsISHEntry* aEntry, 118 mozilla::dom::BrowsingContext* aOwnerBC, 119 int32_t aChildIndex, void* aData); 120 121 // Child-walking callback for SetHistoryEntry 122 static nsresult SetChildHistoryEntry(nsISHEntry* aEntry, 123 mozilla::dom::BrowsingContext* aBC, 124 int32_t aEntryIndex, void* aData); 125 126 // For each child of aRootEntry, find the corresponding shell which is 127 // a child of aBC, and call aCallback. The opaque pointer aData 128 // is passed to the callback. 129 static nsresult WalkHistoryEntries(nsISHEntry* aRootEntry, 130 mozilla::dom::BrowsingContext* aBC, 131 WalkHistoryEntriesFunc aCallback, 132 void* aData); 133 134 // This function finds all entries that are contiguous and same-origin with 135 // the aEntry. And call the aCallback on them, including the aEntry. This only 136 // works for the root entries. It will do nothing for non-root entries. 137 static void WalkContiguousEntries( 138 nsISHEntry* aEntry, const std::function<void(nsISHEntry*)>& aCallback); 139 // Same as above, but calls aCallback on the entries in their history order. 140 // Will stop walking when `aCallback` returns false. 141 static void WalkContiguousEntriesInOrder( 142 nsISHEntry* aEntry, const std::function<bool(nsISHEntry*)>& aCallback); 143 144 nsTArray<nsCOMPtr<nsISHEntry>>& Entries() { return mEntries; } 145 146 void NotifyOnHistoryReplaceEntry(); 147 148 void RemoveEntries(nsTArray<nsID>& aIDs, int32_t aStartIndex, 149 bool* aDidRemove); 150 151 // The size of the window of SHEntries which can have alive viewers in the 152 // bfcache around the currently active SHEntry. 153 // 154 // We try to keep viewers for SHEntries between index - VIEWER_WINDOW and 155 // index + VIEWER_WINDOW alive. 156 static const int32_t VIEWER_WINDOW = 3; 157 158 struct LoadEntryResult { 159 RefPtr<mozilla::dom::BrowsingContext> mBrowsingContext; 160 RefPtr<nsDocShellLoadState> mLoadState; 161 }; 162 163 MOZ_CAN_RUN_SCRIPT 164 static void LoadURIs( 165 const nsTArray<LoadEntryResult>& aLoadResults, bool aCheckForCancelation, 166 const std::function<void(nsresult)>& aResolver = [](auto) {}, 167 mozilla::dom::BrowsingContext* aTraversable = nullptr); 168 169 MOZ_CAN_RUN_SCRIPT 170 static void LoadURIOrBFCache(const LoadEntryResult& aLoadEntry); 171 172 // If this doesn't return an error then either aLoadResult is set to nothing, 173 // in which case the caller should ignore the load, or it returns a valid 174 // LoadEntryResult in aLoadResult which the caller should use to do the load. 175 nsresult Reload(uint32_t aReloadFlags, 176 nsTArray<LoadEntryResult>& aLoadResults); 177 nsresult ReloadCurrentEntry(nsTArray<LoadEntryResult>& aLoadResults); 178 // Passing aSourceBrowsingContext should only be done by 179 // CanonicalBrowsingContext::HistoryGo, since that corresponds to a call to 180 // #apply-the-traverse-history-step 181 nsresult GotoIndex(mozilla::dom::BrowsingContext* aSourceBrowsingContext, 182 int32_t aIndex, nsTArray<LoadEntryResult>& aLoadResults, 183 bool aSameEpoch, bool aLoadCurrentEntry, 184 bool aUserActivation); 185 186 void WindowIndices(int32_t aIndex, int32_t* aOutStartIndex, 187 int32_t* aOutEndIndex); 188 void NotifyListenersDocumentViewerEvicted(uint32_t aNumEvicted); 189 190 int32_t Length() { return int32_t(mEntries.Length()); } 191 int32_t Index() { return mIndex; } 192 already_AddRefed<mozilla::dom::BrowsingContext> GetBrowsingContext() { 193 return mozilla::dom::BrowsingContext::Get(mRootBC); 194 } 195 bool HasOngoingUpdate() { return mHasOngoingUpdate; } 196 void SetHasOngoingUpdate(bool aVal) { mHasOngoingUpdate = aVal; } 197 198 void SetBrowsingContext(mozilla::dom::BrowsingContext* aRootBC) { 199 uint64_t newID = aRootBC ? aRootBC->Id() : 0; 200 if (mRootBC != newID) { 201 mRootBC = newID; 202 } 203 } 204 205 int32_t GetTargetIndexForHistoryOperation() { 206 // When performing a session history operation, such as replace or 207 // navigation by key that can happen during ongoing history traversals; If 208 // the requested index is valid, it indicates the loading was triggered by a 209 // history load, and we should target the entry at requested index instead. 210 return mRequestedIndex == -1 ? mIndex : mRequestedIndex; 211 } 212 213 void GetEpoch(uint64_t& aEpoch, 214 mozilla::Maybe<mozilla::dom::ContentParentId>& aId) const { 215 aEpoch = mEpoch; 216 aId = mEpochParentId; 217 } 218 void SetEpoch(uint64_t aEpoch, 219 mozilla::Maybe<mozilla::dom::ContentParentId> aId) { 220 mEpoch = aEpoch; 221 mEpochParentId = aId; 222 } 223 224 void LogHistory(); 225 226 mozilla::dom::SessionHistoryEntry* FindAdjacentContiguousEntryFor( 227 mozilla::dom::SessionHistoryEntry* aEntry, int32_t aSearchDirection); 228 void ReconstructContiguousEntryListFrom( 229 mozilla::dom::SessionHistoryEntry* aEntry); 230 void ReconstructContiguousEntryList(); 231 already_AddRefed<mozilla::dom::EntryList> EntryListFor(const nsID& aID); 232 void RemoveEntryList(const nsID& aID); 233 234 bool ContainsEntry(nsISHEntry* aEntry); 235 236 protected: 237 virtual ~nsSHistory(); 238 239 uint64_t mRootBC; 240 241 private: 242 friend class nsSHistoryObserver; 243 244 bool ForEachDifferingEntry( 245 nsISHEntry* aPrevEntry, nsISHEntry* aNextEntry, 246 mozilla::dom::BrowsingContext* aParent, 247 const std::function<void(nsISHEntry*, mozilla::dom::BrowsingContext*)>& 248 aCallback); 249 void InitiateLoad(mozilla::dom::BrowsingContext* aSourceBrowsingContext, 250 nsISHEntry* aFrameEntry, 251 mozilla::dom::BrowsingContext* aFrameBC, long aLoadType, 252 nsTArray<LoadEntryResult>& aLoadResult, 253 bool aLoadCurrentEntry, bool aUserActivation, 254 int32_t aOffset); 255 256 nsresult LoadEntry(mozilla::dom::BrowsingContext* aSourceBrowsingContext, 257 int32_t aIndex, long aLoadType, uint32_t aHistCmd, 258 nsTArray<LoadEntryResult>& aLoadResults, bool aSameEpoch, 259 bool aLoadCurrentEntry, bool aUserActivation); 260 261 // Find the history entry for a given bfcache entry. It only looks up between 262 // the range where alive viewers may exist (i.e nsSHistory::VIEWER_WINDOW). 263 nsresult FindEntryForBFCache(mozilla::dom::SHEntrySharedParentState* aEntry, 264 nsISHEntry** aResult, int32_t* aResultIndex); 265 266 // Evict content viewers in this window which don't lie in the "safe" range 267 // around aIndex. 268 virtual void EvictOutOfRangeWindowDocumentViewers(int32_t aIndex); 269 270 public: 271 void EvictDocumentViewerForEntry(nsISHEntry* aEntry); 272 273 private: 274 static void GloballyEvictDocumentViewers(); 275 static void GloballyEvictAllDocumentViewers(); 276 277 // Calculates a max number of total 278 // content viewers to cache, based on amount of total memory 279 static uint32_t CalcMaxTotalViewers(); 280 281 nsresult LoadNextPossibleEntry( 282 mozilla::dom::BrowsingContext* aSourceBrowsingContext, int32_t aNewIndex, 283 long aLoadType, uint32_t aHistCmd, 284 nsTArray<LoadEntryResult>& aLoadResults, bool aLoadCurrentEntry, 285 bool aUserActivation); 286 287 // aIndex is the index of the entry which may be removed. 288 // If aKeepNext is true, aIndex is compared to aIndex + 1, 289 // otherwise comparison is done to aIndex - 1. 290 bool RemoveDuplicate(int32_t aIndex, bool aKeepNext); 291 292 // We need to update entries in docshell and browsing context. 293 // If our docshell is located in parent or 'SH in parent' pref is off we can 294 // update it directly, Otherwise, we have two choices. If the browsing context 295 // that owns the docshell is in the same process as the process who called us 296 // over IPC, then we save entries that need to be updated in a list, and once 297 // we have returned from the IPC call, we update the docshell in the child 298 // process. Otherwise, if the browsing context is in a different process, we 299 // do a nested IPC call to that process to update the docshell in that 300 // process. 301 static void HandleEntriesToSwapInDocShell(mozilla::dom::BrowsingContext* aBC, 302 nsISHEntry* aOldEntry, 303 nsISHEntry* aNewEntry); 304 305 void UpdateEntryLength(nsISHEntry* aOldEntry, nsISHEntry* aNewEntry, 306 bool aMove); 307 308 protected: 309 bool mHasOngoingUpdate; 310 nsTArray<nsCOMPtr<nsISHEntry>> mEntries; // entries are never null 311 private: 312 // Track all bfcache entries and evict on expiration. 313 mozilla::UniquePtr<HistoryTracker> mHistoryTracker; 314 315 int32_t mIndex; // -1 means "no index" 316 int32_t mRequestedIndex; // -1 means "no requested index" 317 318 // Session History listeners 319 nsAutoTObserverArray<nsWeakPtr, 2> mListeners; 320 321 nsID mRootDocShellID; 322 323 // Max viewers allowed total, across all SHistory objects 324 static int32_t sHistoryMaxTotalViewers; 325 326 // The epoch (and id) tell us what navigations occured within the same 327 // event-loop spin in the child. We need to know this in order to 328 // implement spec requirements for dropping pending navigations when we 329 // do a history navigation, if it's not same-document. Content processes 330 // update the epoch via a runnable on each ::Go (including AsyncGo). 331 uint64_t mEpoch = 0; 332 mozilla::Maybe<mozilla::dom::ContentParentId> mEpochParentId; 333 334 // Session history entries grouped by DocshellID, which are deduplicated by 335 // SessionHistoryEntry ID. 336 nsTHashMap<nsIDHashKey, mozilla::WeakPtr<mozilla::dom::EntryList>> 337 mEntryLists; 338 }; 339 340 // CallerWillNotifyHistoryIndexAndLengthChanges is used to prevent 341 // SHistoryChangeNotifier to send automatic index and length updates. 342 // When that is done, it is up to the caller to explicitly send those updates. 343 // This is needed in cases when the update is a reaction to some change in a 344 // child process and child process passes a changeId to the parent side. 345 class MOZ_STACK_CLASS CallerWillNotifyHistoryIndexAndLengthChanges { 346 public: 347 explicit CallerWillNotifyHistoryIndexAndLengthChanges( 348 nsISHistory* aSHistory) { 349 nsSHistory* shistory = static_cast<nsSHistory*>(aSHistory); 350 if (shistory && !shistory->HasOngoingUpdate()) { 351 shistory->SetHasOngoingUpdate(true); 352 mSHistory = shistory; 353 } 354 } 355 356 ~CallerWillNotifyHistoryIndexAndLengthChanges() { 357 if (mSHistory) { 358 mSHistory->SetHasOngoingUpdate(false); 359 } 360 } 361 362 RefPtr<nsSHistory> mSHistory; 363 }; 364 365 inline nsISupports* ToSupports(nsSHistory* aObj) { 366 return static_cast<nsISHistory*>(aObj); 367 } 368 369 #endif /* nsSHistory */