diff options
Diffstat (limited to 'docshell/shistory/nsSHistory.h')
-rw-r--r-- | docshell/shistory/nsSHistory.h | 343 |
1 files changed, 343 insertions, 0 deletions
diff --git a/docshell/shistory/nsSHistory.h b/docshell/shistory/nsSHistory.h new file mode 100644 index 0000000000..d029a55c6c --- /dev/null +++ b/docshell/shistory/nsSHistory.h @@ -0,0 +1,343 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsSHistory_h +#define nsSHistory_h + +#include "nsCOMPtr.h" +#include "nsDocShellLoadState.h" +#include "nsExpirationTracker.h" +#include "nsISHistory.h" +#include "nsSHEntryShared.h" +#include "nsSimpleEnumerator.h" +#include "nsTObserverArray.h" +#include "nsWeakReference.h" + +#include "mozilla/dom/ipc/IdType.h" +#include "mozilla/LinkedList.h" +#include "mozilla/UniquePtr.h" + +class nsIDocShell; +class nsDocShell; +class nsSHistoryObserver; +class nsISHEntry; + +namespace mozilla { +namespace dom { +class LoadSHEntryResult; +} +} // namespace mozilla + +class nsSHistory : public mozilla::LinkedListElement<nsSHistory>, + public nsISHistory, + public nsSupportsWeakReference { + public: + // The timer based history tracker is used to evict bfcache on expiration. + class HistoryTracker final + : public nsExpirationTracker<mozilla::dom::SHEntrySharedParentState, 3> { + public: + explicit HistoryTracker(nsSHistory* aSHistory, uint32_t aTimeout, + nsIEventTarget* aEventTarget) + : nsExpirationTracker(1000 * aTimeout / 2, "HistoryTracker", + aEventTarget) { + MOZ_ASSERT(aSHistory); + mSHistory = aSHistory; + } + + protected: + virtual void NotifyExpired( + mozilla::dom::SHEntrySharedParentState* aObj) override { + RemoveObject(aObj); + mSHistory->EvictExpiredContentViewerForEntry(aObj); + } + + private: + // HistoryTracker is owned by nsSHistory; it always outlives HistoryTracker + // so it's safe to use raw pointer here. + nsSHistory* mSHistory; + }; + + // Structure used in SetChildHistoryEntry + struct SwapEntriesData { + mozilla::dom::BrowsingContext* + ignoreBC; // constant; the browsing context to ignore + nsISHEntry* destTreeRoot; // constant; the root of the dest tree + nsISHEntry* destTreeParent; // constant; the node under destTreeRoot + // whose children will correspond to aEntry + }; + + explicit nsSHistory(mozilla::dom::BrowsingContext* aRootBC); + NS_DECL_ISUPPORTS + NS_DECL_NSISHISTORY + + // One time initialization method + static nsresult Startup(); + static void Shutdown(); + static void UpdatePrefs(); + + // Max number of total cached content viewers. If the pref + // browser.sessionhistory.max_total_viewers is negative, then + // this value is calculated based on the total amount of memory. + // Otherwise, it comes straight from the pref. + static uint32_t GetMaxTotalViewers() { return sHistoryMaxTotalViewers; } + + // Get the root SHEntry from a given entry. + static already_AddRefed<nsISHEntry> GetRootSHEntry(nsISHEntry* aEntry); + + // Callback prototype for WalkHistoryEntries. + // `aEntry` is the child history entry, `aBC` is its corresponding browsing + // context, `aChildIndex` is the child's index in its parent entry, and + // `aData` is the opaque pointer passed to WalkHistoryEntries. Both structs + // that are passed as `aData` to this function have a field + // `aEntriesToUpdate`, which is an array of entries we need to update in + // docshell, if the 'SH in parent' pref is on (which implies that this method + // is executed in the parent) + typedef nsresult (*WalkHistoryEntriesFunc)(nsISHEntry* aEntry, + mozilla::dom::BrowsingContext* aBC, + int32_t aChildIndex, void* aData); + + // Clone a session history tree for subframe navigation. + // The tree rooted at |aSrcEntry| will be cloned into |aDestEntry|, except + // for the entry with id |aCloneID|, which will be replaced with + // |aReplaceEntry|. |aSrcShell| is a (possibly null) docshell which + // corresponds to |aSrcEntry| via its mLSHE or mOHE pointers, and will + // have that pointer updated to point to the cloned history entry. + // If aCloneChildren is true then the children of the entry with id + // |aCloneID| will be cloned into |aReplaceEntry|. + static nsresult CloneAndReplace(nsISHEntry* aSrcEntry, + mozilla::dom::BrowsingContext* aOwnerBC, + uint32_t aCloneID, nsISHEntry* aReplaceEntry, + bool aCloneChildren, nsISHEntry** aDestEntry); + + // Child-walking callback for CloneAndReplace + static nsresult CloneAndReplaceChild(nsISHEntry* aEntry, + mozilla::dom::BrowsingContext* aOwnerBC, + int32_t aChildIndex, void* aData); + + // Child-walking callback for SetHistoryEntry + static nsresult SetChildHistoryEntry(nsISHEntry* aEntry, + mozilla::dom::BrowsingContext* aBC, + int32_t aEntryIndex, void* aData); + + // For each child of aRootEntry, find the corresponding shell which is + // a child of aBC, and call aCallback. The opaque pointer aData + // is passed to the callback. + static nsresult WalkHistoryEntries(nsISHEntry* aRootEntry, + mozilla::dom::BrowsingContext* aBC, + WalkHistoryEntriesFunc aCallback, + void* aData); + + // This function finds all entries that are contiguous and same-origin with + // the aEntry. And call the aCallback on them, including the aEntry. This only + // works for the root entries. It will do nothing for non-root entries. + static void WalkContiguousEntries( + nsISHEntry* aEntry, const std::function<void(nsISHEntry*)>& aCallback); + + nsTArray<nsCOMPtr<nsISHEntry>>& Entries() { return mEntries; } + + void NotifyOnHistoryReplaceEntry(); + + void RemoveEntries(nsTArray<nsID>& aIDs, int32_t aStartIndex, + bool* aDidRemove); + + // The size of the window of SHEntries which can have alive viewers in the + // bfcache around the currently active SHEntry. + // + // We try to keep viewers for SHEntries between index - VIEWER_WINDOW and + // index + VIEWER_WINDOW alive. + static const int32_t VIEWER_WINDOW = 3; + + struct LoadEntryResult { + RefPtr<mozilla::dom::BrowsingContext> mBrowsingContext; + RefPtr<nsDocShellLoadState> mLoadState; + }; + + static void LoadURIs(nsTArray<LoadEntryResult>& aLoadResults); + static void LoadURIOrBFCache(LoadEntryResult& aLoadEntry); + + // If this doesn't return an error then either aLoadResult is set to nothing, + // in which case the caller should ignore the load, or it returns a valid + // LoadEntryResult in aLoadResult which the caller should use to do the load. + nsresult Reload(uint32_t aReloadFlags, + nsTArray<LoadEntryResult>& aLoadResults); + nsresult ReloadCurrentEntry(nsTArray<LoadEntryResult>& aLoadResults); + nsresult GotoIndex(int32_t aIndex, nsTArray<LoadEntryResult>& aLoadResults, + bool aSameEpoch, bool aLoadCurrentEntry, + bool aUserActivation); + + void WindowIndices(int32_t aIndex, int32_t* aOutStartIndex, + int32_t* aOutEndIndex); + void NotifyListenersContentViewerEvicted(uint32_t aNumEvicted); + + int32_t Length() { return int32_t(mEntries.Length()); } + int32_t Index() { return mIndex; } + already_AddRefed<mozilla::dom::BrowsingContext> GetBrowsingContext() { + return mozilla::dom::BrowsingContext::Get(mRootBC); + } + bool HasOngoingUpdate() { return mHasOngoingUpdate; } + void SetHasOngoingUpdate(bool aVal) { mHasOngoingUpdate = aVal; } + + void SetBrowsingContext(mozilla::dom::BrowsingContext* aRootBC) { + uint64_t newID = aRootBC ? aRootBC->Id() : 0; + if (mRootBC != newID) { + mRootBC = newID; + UpdateRootBrowsingContextState(aRootBC); + } + } + + int32_t GetIndexForReplace() { + // Replace current entry in session history; If the requested index is + // valid, it indicates the loading was triggered by a history load, and + // we should replace the entry at requested index instead. + return mRequestedIndex == -1 ? mIndex : mRequestedIndex; + } + + // Update the root browsing context state when adding, removing or + // replacing entries. + void UpdateRootBrowsingContextState() { + RefPtr<mozilla::dom::BrowsingContext> rootBC(GetBrowsingContext()); + UpdateRootBrowsingContextState(rootBC); + } + + void GetEpoch(uint64_t& aEpoch, + mozilla::Maybe<mozilla::dom::ContentParentId>& aId) const { + aEpoch = mEpoch; + aId = mEpochParentId; + } + void SetEpoch(uint64_t aEpoch, + mozilla::Maybe<mozilla::dom::ContentParentId> aId) { + mEpoch = aEpoch; + mEpochParentId = aId; + } + + void LogHistory(); + + protected: + virtual ~nsSHistory(); + + uint64_t mRootBC; + + private: + friend class nsSHistoryObserver; + + void UpdateRootBrowsingContextState( + mozilla::dom::BrowsingContext* aBrowsingContext); + + bool LoadDifferingEntries(nsISHEntry* aPrevEntry, nsISHEntry* aNextEntry, + mozilla::dom::BrowsingContext* aParent, + long aLoadType, + nsTArray<LoadEntryResult>& aLoadResults, + bool aLoadCurrentEntry, bool aUserActivation, + int32_t aOffset); + void InitiateLoad(nsISHEntry* aFrameEntry, + mozilla::dom::BrowsingContext* aFrameBC, long aLoadType, + nsTArray<LoadEntryResult>& aLoadResult, + bool aLoadCurrentEntry, bool aUserActivation, + int32_t aOffset); + + nsresult LoadEntry(int32_t aIndex, long aLoadType, uint32_t aHistCmd, + nsTArray<LoadEntryResult>& aLoadResults, bool aSameEpoch, + bool aLoadCurrentEntry, bool aUserActivation); + + // Find the history entry for a given bfcache entry. It only looks up between + // the range where alive viewers may exist (i.e nsSHistory::VIEWER_WINDOW). + nsresult FindEntryForBFCache(mozilla::dom::SHEntrySharedParentState* aEntry, + nsISHEntry** aResult, int32_t* aResultIndex); + + // Evict content viewers in this window which don't lie in the "safe" range + // around aIndex. + virtual void EvictOutOfRangeWindowContentViewers(int32_t aIndex); + void EvictContentViewerForEntry(nsISHEntry* aEntry); + static void GloballyEvictContentViewers(); + static void GloballyEvictAllContentViewers(); + + // Calculates a max number of total + // content viewers to cache, based on amount of total memory + static uint32_t CalcMaxTotalViewers(); + + nsresult LoadNextPossibleEntry(int32_t aNewIndex, long aLoadType, + uint32_t aHistCmd, + nsTArray<LoadEntryResult>& aLoadResults, + bool aLoadCurrentEntry, bool aUserActivation); + + // aIndex is the index of the entry which may be removed. + // If aKeepNext is true, aIndex is compared to aIndex + 1, + // otherwise comparison is done to aIndex - 1. + bool RemoveDuplicate(int32_t aIndex, bool aKeepNext); + + // We need to update entries in docshell and browsing context. + // If our docshell is located in parent or 'SH in parent' pref is off we can + // update it directly, Otherwise, we have two choices. If the browsing context + // that owns the docshell is in the same process as the process who called us + // over IPC, then we save entries that need to be updated in a list, and once + // we have returned from the IPC call, we update the docshell in the child + // process. Otherwise, if the browsing context is in a different process, we + // do a nested IPC call to that process to update the docshell in that + // process. + static void HandleEntriesToSwapInDocShell(mozilla::dom::BrowsingContext* aBC, + nsISHEntry* aOldEntry, + nsISHEntry* aNewEntry); + + void UpdateEntryLength(nsISHEntry* aOldEntry, nsISHEntry* aNewEntry, + bool aMove); + + protected: + bool mHasOngoingUpdate; + nsTArray<nsCOMPtr<nsISHEntry>> mEntries; // entries are never null + private: + // Track all bfcache entries and evict on expiration. + mozilla::UniquePtr<HistoryTracker> mHistoryTracker; + + int32_t mIndex; // -1 means "no index" + int32_t mRequestedIndex; // -1 means "no requested index" + + // Session History listeners + nsAutoTObserverArray<nsWeakPtr, 2> mListeners; + + nsID mRootDocShellID; + + // Max viewers allowed total, across all SHistory objects + static int32_t sHistoryMaxTotalViewers; + + // The epoch (and id) tell us what navigations occured within the same + // event-loop spin in the child. We need to know this in order to + // implement spec requirements for dropping pending navigations when we + // do a history navigation, if it's not same-document. Content processes + // update the epoch via a runnable on each ::Go (including AsyncGo). + uint64_t mEpoch = 0; + mozilla::Maybe<mozilla::dom::ContentParentId> mEpochParentId; +}; + +// CallerWillNotifyHistoryIndexAndLengthChanges is used to prevent +// SHistoryChangeNotifier to send automatic index and length updates. +// When that is done, it is up to the caller to explicitly send those updates. +// This is needed in cases when the update is a reaction to some change in a +// child process and child process passes a changeId to the parent side. +class MOZ_STACK_CLASS CallerWillNotifyHistoryIndexAndLengthChanges { + public: + explicit CallerWillNotifyHistoryIndexAndLengthChanges( + nsISHistory* aSHistory) { + nsSHistory* shistory = static_cast<nsSHistory*>(aSHistory); + if (shistory && !shistory->HasOngoingUpdate()) { + shistory->SetHasOngoingUpdate(true); + mSHistory = shistory; + } + } + + ~CallerWillNotifyHistoryIndexAndLengthChanges() { + if (mSHistory) { + mSHistory->SetHasOngoingUpdate(false); + } + } + + RefPtr<nsSHistory> mSHistory; +}; + +inline nsISupports* ToSupports(nsSHistory* aObj) { + return static_cast<nsISHistory*>(aObj); +} + +#endif /* nsSHistory */ |