/* -*- 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/. */
#define PREF_SHISTORY_SIZE "browser.sessionhistory.max_entries" #define PREF_SHISTORY_MAX_TOTAL_VIEWERS \ "browser.sessionhistory.max_total_viewers" #define CONTENT_VIEWER_TIMEOUT_SECONDS \ "browser.sessionhistory.contentViewerTimeout" // Observe fission.bfcacheInParent so that BFCache can be enabled/disabled when // the pref is changed. #define PREF_FISSION_BFCACHEINPARENT "fission.bfcacheInParent"
// Default this to time out unused content viewers after 30 minutes #define CONTENT_VIEWER_TIMEOUT_SECONDS_DEFAULT (30 * 60)
// List of all SHistory objects, used for content viewer cache eviction. // When being destroyed, this helper removes everything from the list to avoid // assertions when we leak. struct ListHelper { #ifdef DEBUG
~ListHelper() { mList.clear(); } #endif// DEBUG
LinkedList<nsSHistory> mList;
};
MOZ_RUNINIT static ListHelper gSHistoryList; // Max viewers allowed total, across all SHistory objects - negative default // means we will calculate how many viewers to cache based on total memory
int32_t nsSHistory::sHistoryMaxTotalViewers = -1;
// A counter that is used to be able to know the order in which // entries were touched, so that we can evict older entries first. static uint32_t gTouchCounter = 0;
// This macro makes it easier to print a log message which includes a URI's // spec. Example use: // // nsIURI *uri = [...]; // LOG_SPEC(("The URI is %s.", _spec), uri); // #define LOG_SPEC(format, uri) \
PR_BEGIN_MACRO \ if (MOZ_LOG_TEST(gSHistoryLog, LogLevel::Debug)) { \
nsAutoCString _specStr("(null)"_ns); \ if (uri) { \
_specStr = uri->GetSpecOrDefault(); \
} \ constchar* _spec = _specStr.get(); \
LOG(format); \
} \
PR_END_MACRO
// This macro makes it easy to log a message including an SHEntry's URI. // For example: // // nsCOMPtr<nsISHEntry> shentry = [...]; // LOG_SHENTRY_SPEC(("shentry %p has uri %s.", shentry.get(), _spec), shentry); // #define LOG_SHENTRY_SPEC(format, shentry) \
PR_BEGIN_MACRO \ if (MOZ_LOG_TEST(gSHistoryLog, LogLevel::Debug)) { \
nsCOMPtr<nsIURI> uri = shentry->GetURI(); \
LOG_SPEC(format, uri); \
} \
PR_END_MACRO
// Calls a F on all registered session history listeners. template <typename F> staticvoid NotifyListeners(nsAutoTObserverArray<nsWeakPtr, 2>& aListeners,
F&& f) { for (const nsWeakPtr& weakPtr : aListeners.EndLimitedRange()) {
nsCOMPtr<nsISHistoryListener> listener = do_QueryReferent(weakPtr); if (listener) {
f(listener);
}
}
}
class MOZ_STACK_CLASS SHistoryChangeNotifier { public: explicit SHistoryChangeNotifier(nsSHistory* aHistory) { // If we're already in an update, the outermost change notifier will // update browsing context in the destructor. if (!aHistory->HasOngoingUpdate()) {
aHistory->SetHasOngoingUpdate(true);
mSHistory = aHistory;
}
}
~SHistoryChangeNotifier() { if (mSHistory) {
MOZ_ASSERT(mSHistory->HasOngoingUpdate());
mSHistory->SetHasOngoingUpdate(false);
void nsSHistory::EvictDocumentViewerForEntry(nsISHEntry* aEntry) {
nsCOMPtr<nsIDocumentViewer> viewer = aEntry->GetDocumentViewer(); if (viewer) {
LOG_SHENTRY_SPEC(("Evicting content viewer 0x%p for " "owning SHEntry 0x%p at %s.",
viewer.get(), aEntry, _spec),
aEntry);
// Drop the presentation state before destroying the viewer, so that // document teardown is able to correctly persist the state.
NotifyListenersDocumentViewerEvicted(1);
aEntry->SetDocumentViewer(nullptr);
aEntry->SyncPresentationState();
viewer->Destroy();
} elseif (nsCOMPtr<SessionHistoryEntry> she = do_QueryInterface(aEntry)) { if (RefPtr<nsFrameLoader> frameLoader = she->GetFrameLoader()) {
nsCOMPtr<nsFrameLoaderOwner> owner =
do_QueryInterface(frameLoader->GetOwnerContent());
RefPtr<nsFrameLoader> currentFrameLoader; if (owner) {
currentFrameLoader = owner->GetFrameLoader();
}
// Only destroy non-current frameloader when evicting from the bfcache. if (currentFrameLoader != frameLoader) {
MOZ_LOG(gSHIPBFCacheLog, LogLevel::Debug,
("nsSHistory::EvictDocumentViewerForEntry " "destroying an nsFrameLoader."));
NotifyListenersDocumentViewerEvicted(1);
she->SetFrameLoader(nullptr);
frameLoader->Destroy();
}
}
}
// When dropping bfcache, we have to remove associated dynamic entries as // well.
int32_t index = GetIndexOfEntry(aEntry); if (index != -1) {
RemoveDynEntries(index, aEntry);
}
}
// Add this new SHistory object to the list
gSHistoryList.mList.insertBack(this);
// Init mHistoryTracker on setting mRootBC so we can bind its event // target to the tabGroup.
mHistoryTracker = mozilla::MakeUnique<HistoryTracker>( this,
mozilla::Preferences::GetUint(CONTENT_VIEWER_TIMEOUT_SECONDS,
CONTENT_VIEWER_TIMEOUT_SECONDS_DEFAULT),
GetCurrentSerialEventTarget());
}
nsSHistory::~nsSHistory() { // Clear mEntries explicitly here so that the destructor of the entries // can still access nsSHistory in a reasonable way.
mEntries.Clear();
}
// static
uint32_t nsSHistory::CalcMaxTotalViewers() { // This value allows tweaking how fast the allowed amount of content viewers // grows with increasing amounts of memory. Larger values mean slower growth. #ifdef ANDROID # define MAX_TOTAL_VIEWERS_BIAS 15.9 #else # define MAX_TOTAL_VIEWERS_BIAS 14 #endif
// Calculate an estimate of how many DocumentViewers we should cache based // on RAM. This assumes that the average DocumentViewer is 4MB (conservative) // and caps the max at 8 DocumentViewers // // TODO: Should we split the cache memory betw. DocumentViewer caching and // nsCacheService? // // RAM | DocumentViewers | on Android // ------------------------------------- // 32 Mb 0 0 // 64 Mb 1 0 // 128 Mb 2 0 // 256 Mb 3 1 // 512 Mb 5 2 // 768 Mb 6 2 // 1024 Mb 8 3 // 2048 Mb 8 5 // 3072 Mb 8 7 // 4096 Mb 8 8
uint64_t bytes = PR_GetPhysicalMemorySize();
if (bytes == 0) { return 0;
}
// Conversion from unsigned int64_t to double doesn't work on all platforms. // We need to truncate the value at INT64_MAX to make sure we don't // overflow. if (bytes > INT64_MAX) {
bytes = INT64_MAX;
}
double kBytesD = (double)(bytes >> 10);
// This is essentially the same calculation as for nsCacheService, // except that we divide the final memory calculation by 4, since // we assume each DocumentViewer takes on average 4MB
uint32_t viewers = 0; double x = std::log(kBytesD) / std::log(2.0) - MAX_TOTAL_VIEWERS_BIAS; if (x > 0) {
viewers = (uint32_t)(x * x - x + 2.001); // add .001 for rounding
viewers /= 4;
}
// Cap it off at 8 max if (viewers > 8) {
viewers = 8;
} return viewers;
}
Preferences::GetInt(PREF_SHISTORY_MAX_TOTAL_VIEWERS,
&sHistoryMaxTotalViewers); // If the pref is negative, that means we calculate how many viewers // we think we should cache, based on total memory if (sHistoryMaxTotalViewers < 0) {
sHistoryMaxTotalViewers = CalcMaxTotalViewers();
}
}
// The goal of this is to unbreak users who have inadvertently set their // session history size to less than the default value.
int32_t defaultHistoryMaxSize =
Preferences::GetInt(PREF_SHISTORY_SIZE, 50, PrefValueKind::Default); if (gHistoryMaxSize < defaultHistoryMaxSize) {
gHistoryMaxSize = defaultHistoryMaxSize;
}
// Allow the user to override the max total number of cached viewers, // but keep the per SHistory cached viewer limit constant if (!gObserver) {
gObserver = new nsSHistoryObserver();
Preferences::RegisterCallbacks(nsSHistoryObserver::PrefChanged,
kObservedPrefs, gObserver.get());
nsCOMPtr<nsIObserverService> obsSvc =
mozilla::services::GetObserverService(); if (obsSvc) { // Observe empty-cache notifications so tahat clearing the disk/memory // cache will also evict all content viewers.
obsSvc->AddObserver(gObserver, "cacheservice:empty-cache", false);
// Same for memory-pressure notifications
obsSvc->AddObserver(gObserver, "memory-pressure", false);
}
}
int32_t childCount = aRootEntry->GetChildCount(); for (int32_t i = 0; i < childCount; i++) {
nsCOMPtr<nsISHEntry> childEntry;
aRootEntry->GetChildAt(i, getter_AddRefs(childEntry)); if (!childEntry) { // childEntry can be null for valid reasons, for example if the // docshell at index i never loaded anything useful. // Remember to clone also nulls in the child array (bug 464064).
aCallback(nullptr, nullptr, i, aData); continue;
}
BrowsingContext* childBC = nullptr; if (aBC) { for (BrowsingContext* child : aBC->Children()) { // If the SH pref is on and we are in the parent process, update // canonical BC directly bool foundChild = false; if (mozilla::SessionHistoryInParent() && XRE_IsParentProcess()) { if (child->Canonical()->HasHistoryEntry(childEntry)) {
childBC = child;
foundChild = true;
}
}
nsCOMPtr<nsISHistory> shistory = aEntry->GetShistory(); if (!shistory) { // If there is no session history in the entry, it means this is not a root // entry. So, we can return from here. return;
}
int32_t index = shistory->GetIndexOfEntry(aEntry);
int32_t count = shistory->GetCount();
nsCOMPtr<nsIURI> targetURI = aEntry->GetURI();
// First, call the callback on the input entry.
aCallback(aEntry);
// Walk backward to find the entries that have the same origin as the // input entry. for (int32_t i = index - 1; i >= 0; i--) {
RefPtr<nsISHEntry> entry;
shistory->GetEntryAtIndex(i, getter_AddRefs(entry)); if (entry) {
nsCOMPtr<nsIURI> uri = entry->GetURI(); if (NS_FAILED(nsContentUtils::GetSecurityManager()->CheckSameOriginURI(
targetURI, uri, false, false))) { break;
}
aCallback(entry);
}
}
// Then, Walk forward. for (int32_t i = index + 1; i < count; i++) {
RefPtr<nsISHEntry> entry;
shistory->GetEntryAtIndex(i, getter_AddRefs(entry)); if (entry) {
nsCOMPtr<nsIURI> uri = entry->GetURI(); if (NS_FAILED(nsContentUtils::GetSecurityManager()->CheckSameOriginURI(
targetURI, uri, false, false))) { break;
}
/* You are currently in the rootDocShell. * You will get here when a subframe has a new url * to load and you have walked up the tree all the * way to the top to clone the current SHEntry hierarchy * and replace the subframe where a new url was loaded with * a new entry.
*/
nsCOMPtr<nsISHEntry> child;
nsCOMPtr<nsISHEntry> currentHE;
int32_t index = mIndex; if (index < 0) { return NS_ERROR_FAILURE;
}
if (data->destTreeParent) { // aEntry is a clone of some child of destTreeParent, but since the // trees aren't necessarily in sync, we'll have to locate it. // Note that we could set aShell's entry to null if we don't find a // corresponding entry under destTreeParent.
uint32_t targetID = aEntry->GetID();
// First look at the given index, since this is the common case.
nsCOMPtr<nsISHEntry> entry;
data->destTreeParent->GetChildAt(aEntryIndex, getter_AddRefs(entry)); if (entry && entry->GetID() == targetID) {
destEntry.swap(entry);
} else {
int32_t childCount;
data->destTreeParent->GetChildCount(&childCount); for (int32_t i = 0; i < childCount; ++i) {
data->destTreeParent->GetChildAt(i, getter_AddRefs(entry)); if (!entry) { continue;
}
// XXX Simplify this once the old and new session history implementations // don't run at the same time. if (shPref && XRE_IsParentProcess()) {
aBC->Canonical()->SwapHistoryEntries(aOldEntry, aNewEntry);
}
}
void nsSHistory::UpdateRootBrowsingContextState(BrowsingContext* aRootBC) { if (aRootBC && aRootBC->EverAttached()) { bool sameDocument = IsEmptyOrHasEntriesForSingleTopLevelPage(); if (sameDocument != aRootBC->GetIsSingleToplevelInHistory()) { // If the browsing context is discarded then its session history is // invalid and will go away.
Unused << aRootBC->SetIsSingleToplevelInHistory(sameDocument);
}
}
}
// If we need to clone our children onto the new session // history entry, do so now. if (aCloneChildren && aOSHE) {
uint32_t cloneID = aOSHE->GetID();
nsCOMPtr<nsISHEntry> newEntry;
nsSHistory::CloneAndReplace(aOSHE, aRootBC, cloneID, aEntry, true,
getter_AddRefs(newEntry));
NS_ASSERTION(aEntry == newEntry, "The new session history should be in the new entry");
} // This is the root docshell bool addToSHistory = !LOAD_TYPE_HAS_FLAGS(
aLoadType, nsIWebNavigation::LOAD_FLAGS_REPLACE_HISTORY); if (!addToSHistory) { // 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.
int32_t index = GetIndexForReplace();
// Replace the current entry with the new entry if (index >= 0) {
rv = ReplaceEntry(index, aEntry);
} else { // If we're trying to replace an inexistant shistory entry, append.
addToSHistory = true;
}
} if (addToSHistory) { // Add to session history
*aPreviousEntryIndex = Some(mIndex);
rv = AddEntry(aEntry, aShouldPersist);
*aLoadedEntryIndex = Some(mIndex);
MOZ_LOG(gPageCacheLog, LogLevel::Verbose,
("Previous index: %d, Loaded index: %d",
aPreviousEntryIndex->value(), aLoadedEntryIndex->value()));
} if (NS_SUCCEEDED(rv)) {
aEntry->SetDocshellID(aRootBC->GetHistoryID());
} return rv;
}
/* Add an entry to the History list at mIndex and * increment the index to point to the new entry
*/
NS_IMETHODIMP
nsSHistory::AddEntry(nsISHEntry* aSHEntry, bool aPersist) {
NS_ENSURE_ARG(aSHEntry);
nsCOMPtr<nsISHistory> shistoryOfEntry = aSHEntry->GetShistory(); if (shistoryOfEntry && shistoryOfEntry != this) {
NS_WARNING( "The entry has been associated to another nsISHistory instance. " "Try nsISHEntry.clone() and nsISHEntry.abandonBFCacheEntry() " "first if you're copying an entry from another nsISHistory."); return NS_ERROR_FAILURE;
}
aSHEntry->SetShistory(this);
// If we have a root docshell, update the docshell id of the root shentry to // match the id of that docshell
RefPtr<BrowsingContext> rootBC = GetBrowsingContext(); if (rootBC) {
aSHEntry->SetDocshellID(mRootDocShellID);
}
if (mIndex >= 0) {
MOZ_ASSERT(mIndex < Length(), "Index out of range!"); if (mIndex >= Length()) { return NS_ERROR_FAILURE;
}
// Remove all entries after the current one, add the new one, and set the // new one as the current one.
MOZ_ASSERT(mIndex >= -1);
aSHEntry->SetPersist(aPersist);
mEntries.TruncateLength(mIndex + 1);
mEntries.AppendElement(aSHEntry);
mIndex++; if (mIndex > 0) {
UpdateEntryLength(mEntries[mIndex - 1], mEntries[mIndex], false);
}
// Purge History list if it is too long if (gHistoryMaxSize >= 0 && Length() > gHistoryMaxSize) {
PurgeHistory(Length() - gHistoryMaxSize);
}
/* Get size of the history list */
NS_IMETHODIMP
nsSHistory::GetCount(int32_t* aResult) {
MOZ_ASSERT(aResult, "null out param?");
*aResult = Length(); return NS_OK;
}
// Set all the entries hanging of the first entry that we keep // (mEntries[aNumEntries]) as being created as the result of a load // (so contributing one to their BCHistoryLength).
nsTHashMap<nsIDHashKey, SessionHistoryEntry*> docshellIDToEntry; if (aNumEntries != Length()) {
nsCOMPtr<SessionHistoryEntry> she =
do_QueryInterface(mEntries[aNumEntries]); if (she) {
MarkAsInitialEntry(she, docshellIDToEntry);
}
}
// Reset the BCHistoryLength of all the entries that we're removing to a new // counter with value 0 while decreasing their contribution to a shared // BCHistoryLength. The end result is that they don't contribute to the // BCHistoryLength of any other entry anymore. for (int32_t i = 0; i < aNumEntries; ++i) {
nsCOMPtr<SessionHistoryEntry> she = do_QueryInterface(mEntries[i]); if (she) {
ClearEntries(she);
}
}
// Remove the first `aNumEntries` entries.
mEntries.RemoveElementsAt(0, aNumEntries);
// Adjust the indices, but don't let them go below -1.
mIndex -= aNumEntries;
mIndex = std::max(mIndex, -1);
mRequestedIndex -= aNumEntries;
mRequestedIndex = std::max(mRequestedIndex, -1);
if (rootBC && rootBC->GetDocShell()) {
rootBC->GetDocShell()->HistoryPurged(aNumEntries);
}
// Check if the listener supports Weak Reference. This is a must. // This listener functionality is used by embedders and we want to // have the right ownership with who ever listens to SHistory
nsWeakPtr listener = do_GetWeakReference(aListener); if (!listener) { return NS_ERROR_FAILURE;
}
NS_IMETHODIMP
nsSHistory::RemoveSHistoryListener(nsISHistoryListener* aListener) { // Make sure the listener that wants to be removed is the // one we have in store.
nsWeakPtr listener = do_GetWeakReference(aListener);
mListeners.RemoveElement(listener); return NS_OK;
}
/* Replace an entry in the History list at a particular index. * Do not update index or count.
*/
NS_IMETHODIMP
nsSHistory::ReplaceEntry(int32_t aIndex, nsISHEntry* aReplaceEntry) {
NS_ENSURE_ARG(aReplaceEntry);
nsCOMPtr<nsISHistory> shistoryOfEntry = aReplaceEntry->GetShistory(); if (shistoryOfEntry && shistoryOfEntry != this) {
NS_WARNING( "The entry has been associated to another nsISHistory instance. " "Try nsISHEntry.clone() and nsISHEntry.abandonBFCacheEntry() " "first if you're copying an entry from another nsISHistory."); return NS_ERROR_FAILURE;
}
// Calls OnHistoryReload on all registered session history listeners. // Listeners may return 'false' to cancel an action so make sure that we // set the return value to 'false' if one of the listeners wants to cancel.
NS_IMETHODIMP
nsSHistory::NotifyOnHistoryReload(bool* aCanReload) {
*aCanReload = true;
for (const nsWeakPtr& weakPtr : mListeners.EndLimitedRange()) {
nsCOMPtr<nsISHistoryListener> listener = do_QueryReferent(weakPtr); if (listener) { bool retval = true;
// Check our per SHistory object limit in the currently navigated SHistory
EvictOutOfRangeWindowDocumentViewers(aIndex); // Check our total limit across all SHistory objects
GloballyEvictDocumentViewers(); return NS_OK;
}
int32_t index = GetIndexOfEntry(rootSHEntry); if (index > -1) {
ReplaceEntry(index, rootSHEntry);
}
}
}
NS_IMETHODIMP
nsSHistory::EvictAllDocumentViewers() { // XXXbz we don't actually do a good job of evicting things as we should, so // we might have viewers quite far from mIndex. So just evict everything. for (int32_t i = 0; i < Length(); i++) {
EvictDocumentViewerForEntry(mEntries[i]);
}
nsCOMPtr<nsFrameLoaderOwner> frameLoaderOwner =
do_QueryInterface(aBrowsingContext->GetEmbedderElement()); if (frameLoaderOwner && aFrameLoader->GetMaybePendingBrowsingContext() &&
indexOfHistoryLoad >= 0) {
RefPtr<BrowsingContextWebProgress> webProgress =
aBrowsingContext->GetWebProgress(); if (webProgress) { // Synthesize a STATE_START WebProgress state change event from here // in order to ensure emitting it on the BrowsingContext we navigate // *from* instead of the BrowsingContext we navigate *to*. This will fire // before and the next one will be ignored by BrowsingContextWebProgress: // https://searchfox.org/mozilla-central/rev/77f0b36028b2368e342c982ea47609040b399d89/docshell/base/BrowsingContextWebProgress.cpp#196-203
nsCOMPtr<nsIURI> nextURI = aEntry->GetURI();
nsCOMPtr<nsIURI> nextOriginalURI = aEntry->GetOriginalURI();
nsCOMPtr<nsIRequest> request = MakeAndAddRef<RemoteWebProgressRequest>(
nextURI, nextOriginalURI ? nextOriginalURI : nextURI, ""_ns /* aMatchedList */);
webProgress->OnStateChange(webProgress, request,
nsIWebProgressListener::STATE_START |
nsIWebProgressListener::STATE_IS_DOCUMENT |
nsIWebProgressListener::STATE_IS_REQUEST |
nsIWebProgressListener::STATE_IS_WINDOW |
nsIWebProgressListener::STATE_IS_NETWORK,
NS_OK);
}
RefPtr<CanonicalBrowsingContext> loadingBC =
aFrameLoader->GetMaybePendingBrowsingContext()->Canonical();
RefPtr<nsFrameLoader> currentFrameLoader =
frameLoaderOwner->GetFrameLoader(); // The current page can be bfcached, store the // nsFrameLoader in the current SessionHistoryEntry.
RefPtr<SessionHistoryEntry> currentSHEntry =
aBrowsingContext->GetActiveSessionHistoryEntry(); if (currentSHEntry) { // Update layout history state now, before we change the IsInBFCache flag // and the active session history entry.
aBrowsingContext->SynchronizeLayoutHistoryState();
if (aCanSave) {
currentSHEntry->SetFrameLoader(currentFrameLoader);
Unused << aBrowsingContext->SetIsInBFCache(true);
}
}
// Ensure browser priority to matches `IsPriorityActive` after restoring. if (BrowserParent* bp = loadingBC->GetBrowserParent()) {
bp->VisitAll([&](BrowserParent* aBp) {
ProcessPriorityManager::BrowserPriorityChanged(
aBp, aBrowsingContext->IsPriorityActive());
});
}
if (aEntry) {
aEntry->SetWireframe(Nothing());
}
// ReplacedBy will swap the entry back.
aBrowsingContext->SetActiveSessionHistoryEntry(aEntry);
loadingBC->SetActiveSessionHistoryEntry(nullptr);
NavigationIsolationOptions options;
aBrowsingContext->ReplacedBy(loadingBC, options);
// Assuming we still have the session history, update the index. if (loadingBC->GetSessionHistory()) {
shistory->InternalSetRequestedIndex(indexOfHistoryLoad);
shistory->UpdateIndex();
}
loadingBC->HistoryCommitIndexAndLength();
// ResetSHEntryHasUserInteractionCache(); ? // browser.navigation.requireUserInteraction is still // disabled everywhere.
frameLoaderOwner->RestoreFrameLoaderFromBFCache(aFrameLoader); // EvictOutOfRangeDocumentViewers is called here explicitly to // possibly evict the now in the bfcache document. // HistoryCommitIndexAndLength might not have evicted that before the // FrameLoader swap.
shistory->EvictOutOfRangeDocumentViewers(indexOfHistoryLoad);
// The old page can't be stored in the bfcache, // destroy the nsFrameLoader. if (!aCanSave && currentFrameLoader) {
currentFrameLoader->Destroy();
}
Unused << loadingBC->SetIsInBFCache(false);
// We need to call this after we've restored the page from BFCache (see // SetIsInBFCache(false) above), so that the page is not frozen anymore and // the right focus events are fired.
frameLoaderOwner->UpdateFocusAndMouseEnterStateAfterFrameLoaderChange();
return;
}
aFrameLoader->Destroy();
// Fall back to do a normal load.
aBrowsingContext->LoadURI(aLoadState, false);
}
// We are reloading. Send Reload notifications. // nsDocShellLoadFlagType is not public, where as nsIWebNavigation // is public. So send the reload notifications with the // nsIWebNavigation flags. bool canNavigate = true;
MOZ_ALWAYS_SUCCEEDS(NotifyOnHistoryReload(&canNavigate)); if (!canNavigate) { return NS_OK;
}
void nsSHistory::EvictOutOfRangeWindowDocumentViewers(int32_t aIndex) { // XXX rename method to EvictDocumentViewersExceptAroundIndex, or something.
// We need to release all content viewers that are no longer in the range // // aIndex - VIEWER_WINDOW to aIndex + VIEWER_WINDOW // // to ensure that this SHistory object isn't responsible for more than // VIEWER_WINDOW content viewers. But our job is complicated by the // fact that two entries which are related by either hash navigations or // history.pushState will have the same content viewer. // // To illustrate the issue, suppose VIEWER_WINDOW = 3 and we have four // linked entries in our history. Suppose we then add a new content // viewer and call into this function. So the history looks like: // // A A A A B // + * // // where the letters are content viewers and + and * denote the beginning and // end of the range aIndex +/- VIEWER_WINDOW. // // Although one copy of the content viewer A exists outside the range, we // don't want to evict A, because it has other copies in range! // // We therefore adjust our eviction strategy to read: // // Evict each content viewer outside the range aIndex -/+ // VIEWER_WINDOW, unless that content viewer also appears within the // range. // // (Note that it's entirely legal to have two copies of one content viewer // separated by a different content viewer -- call pushState twice, go back // once, and refresh -- so we can't rely on identical viewers only appearing // adjacent to one another.)
if (aIndex < 0) { return;
}
NS_ENSURE_TRUE_VOID(aIndex < Length());
// Calculate the range that's safe from eviction.
int32_t startSafeIndex, endSafeIndex;
WindowIndices(aIndex, &startSafeIndex, &endSafeIndex);
// The content viewers in range aIndex -/+ VIEWER_WINDOW will not be // evicted. Collect a set of them so we don't accidentally evict one of them // if it appears outside this range.
nsCOMArray<nsIDocumentViewer> safeViewers;
nsTArray<RefPtr<nsFrameLoader>> safeFrameLoaders; for (int32_t i = startSafeIndex; i <= endSafeIndex; i++) {
nsCOMPtr<nsIDocumentViewer> viewer = mEntries[i]->GetDocumentViewer(); if (viewer) {
safeViewers.AppendObject(viewer);
} elseif (nsCOMPtr<SessionHistoryEntry> she =
do_QueryInterface(mEntries[i])) {
nsFrameLoader* frameLoader = she->GetFrameLoader(); if (frameLoader) {
safeFrameLoaders.AppendElement(frameLoader);
}
}
}
// Walk the SHistory list and evict any content viewers that aren't safe. // (It's important that the condition checks Length(), rather than a cached // copy of Length(), because the length might change between iterations.) for (int32_t i = 0; i < Length(); i++) {
nsCOMPtr<nsISHEntry> entry = mEntries[i];
nsCOMPtr<nsIDocumentViewer> viewer = entry->GetDocumentViewer(); if (viewer) { if (safeViewers.IndexOf(viewer) == -1) {
EvictDocumentViewerForEntry(entry);
}
} elseif (nsCOMPtr<SessionHistoryEntry> she =
do_QueryInterface(mEntries[i])) {
nsFrameLoader* frameLoader = she->GetFrameLoader(); if (frameLoader) { if (!safeFrameLoaders.Contains(frameLoader)) {
EvictDocumentViewerForEntry(entry);
}
}
}
}
}
namespace {
class EntryAndDistance { public:
EntryAndDistance(nsSHistory* aSHistory, nsISHEntry* aEntry, uint32_t aDist)
: mSHistory(aSHistory),
mEntry(aEntry),
mViewer(aEntry->GetDocumentViewer()),
mLastTouched(mEntry->GetLastTouched()),
mDistance(aDist) {
nsCOMPtr<SessionHistoryEntry> she = do_QueryInterface(aEntry); if (she) {
mFrameLoader = she->GetFrameLoader();
}
NS_ASSERTION(mViewer || mFrameLoader, "Entry should have a content viewer or frame loader.");
}
booloperator<(const EntryAndDistance& aOther) const { // Compare distances first, and fall back to last-accessed times. if (aOther.mDistance != this->mDistance) { return this->mDistance < aOther.mDistance;
}
booloperator==(const EntryAndDistance& aOther) const { // This is a little silly; we need == so the default comaprator can be // instantiated, but this function is never actually called when we sort // the list of EntryAndDistance objects. return aOther.mDistance == this->mDistance &&
aOther.mLastTouched == this->mLastTouched;
}
// static void nsSHistory::GloballyEvictDocumentViewers() { // First, collect from each SHistory object the entries which have a cached // content viewer. Associate with each entry its distance from its SHistory's // current index.
nsTArray<EntryAndDistance> entries;
for (auto shist : gSHistoryList.mList) { // Maintain a list of the entries which have viewers and belong to // this particular shist object. We'll add this list to the global list, // |entries|, eventually.
nsTArray<EntryAndDistance> shEntries;
// Content viewers are likely to exist only within shist->mIndex -/+ // VIEWER_WINDOW, so only search within that range. // // A content viewer might exist outside that range due to either: // // * history.pushState or hash navigations, in which case a copy of the // content viewer should exist within the range, or // // * bugs which cause us not to call nsSHistory::EvictDocumentViewers() // often enough. Once we do call EvictDocumentViewers() for the // SHistory object in question, we'll do a full search of its history // and evict the out-of-range content viewers, so we don't bother here. //
int32_t startIndex, endIndex;
shist->WindowIndices(shist->mIndex, &startIndex, &endIndex); for (int32_t i = startIndex; i <= endIndex; i++) {
nsCOMPtr<nsISHEntry> entry = shist->mEntries[i];
nsCOMPtr<nsIDocumentViewer> viewer = entry->GetDocumentViewer();
bool found = false; bool hasDocumentViewerOrFrameLoader = false; if (viewer) {
hasDocumentViewerOrFrameLoader = true; // Because one content viewer might belong to multiple SHEntries, we // have to search through shEntries to see if we already know // about this content viewer. If we find the viewer, update its // distance from the SHistory's index and continue. for (uint32_t j = 0; j < shEntries.Length(); j++) {
EntryAndDistance& container = shEntries[j]; if (container.mViewer == viewer) {
container.mDistance =
std::min(container.mDistance, DeprecatedAbs(i - shist->mIndex));
found = true; break;
}
}
} elseif (nsCOMPtr<SessionHistoryEntry> she = do_QueryInterface(entry)) { if (RefPtr<nsFrameLoader> frameLoader = she->GetFrameLoader()) {
hasDocumentViewerOrFrameLoader = true; // Similar search as above but using frameloader. for (uint32_t j = 0; j < shEntries.Length(); j++) {
EntryAndDistance& container = shEntries[j]; if (container.mFrameLoader == frameLoader) {
container.mDistance = std::min(container.mDistance,
DeprecatedAbs(i - shist->mIndex));
found = true; break;
}
}
}
}
// If we didn't find a EntryAndDistance for this content viewer / // frameloader, make a new one. if (hasDocumentViewerOrFrameLoader && !found) {
EntryAndDistance container(shist, entry,
DeprecatedAbs(i - shist->mIndex));
shEntries.AppendElement(container);
}
}
// We've found all the entries belonging to shist which have viewers. // Add those entries to our global list and move on.
entries.AppendElements(shEntries);
}
// We now have collected all cached content viewers. First check that we // have enough that we actually need to evict some. if ((int32_t)entries.Length() <= sHistoryMaxTotalViewers) { return;
}
// If we need to evict, sort our list of entries and evict the largest // ones. (We could of course get better algorithmic complexity here by using // a heap or something more clever. But sHistoryMaxTotalViewers isn't large, // so let's not worry about it.)
entries.Sort();
for (int32_t i = entries.Length() - 1; i >= sHistoryMaxTotalViewers; --i) {
(entries[i].mSHistory)->EvictDocumentViewerForEntry(entries[i].mEntry);
}
}
for (int32_t i = startIndex; i <= endIndex; ++i) {
nsCOMPtr<nsISHEntry> shEntry = mEntries[i];
// Does shEntry have the same BFCacheEntry as the argument to this method? if (shEntry->HasBFCacheEntry(aEntry)) {
shEntry.forget(aResult);
*aResultIndex = i; return NS_OK;
}
} return NS_ERROR_FAILURE;
}
// Evicts all content viewers in all history objects. This is very // inefficient, because it requires a linear search through all SHistory // objects for each viewer to be evicted. However, this method is called // infrequently -- only when the disk or memory cache is cleared.
int32_t count1 = aEntry1->GetChildCount();
int32_t count2 = aEntry2->GetChildCount(); // We allow null entries in the end of the child list.
int32_t count = std::max(count1, count2); for (int32_t i = 0; i < count; ++i) {
nsCOMPtr<nsISHEntry> child1, child2;
aEntry1->GetChildAt(i, getter_AddRefs(child1));
aEntry2->GetChildAt(i, getter_AddRefs(child2)); if (!IsSameTree(child1, child2)) { returnfalse;
}
}
returntrue;
}
bool nsSHistory::RemoveDuplicate(int32_t aIndex, bool aKeepNext) {
NS_ASSERTION(aIndex >= 0, "aIndex must be >= 0!");
NS_ASSERTION(aIndex != 0 || aKeepNext, "If we're removing index 0 we must be keeping the next");
NS_ASSERTION(aIndex != mIndex, "Shouldn't remove mIndex!");
if (IsSameTree(root1, root2)) { if (aIndex < compareIndex) { // If we're removing the entry with the lower index we need to move its // BCHistoryLength to the entry we're keeping. If we're removing the entry // with the higher index then it shouldn't have a modified // BCHistoryLength.
UpdateEntryLength(root1, root2, true);
}
nsCOMPtr<SessionHistoryEntry> she = do_QueryInterface(root1); if (she) {
ClearEntries(she);
}
mEntries.RemoveElementAt(aIndex);
// FIXME Bug 1546350: Reimplement history listeners. // if (mRootBC && mRootBC->GetDocShell()) { // static_cast<nsDocShell*>(mRootBC->GetDocShell()) // ->HistoryEntryRemoved(aIndex); //}
// Adjust our indices to reflect the removed entry. if (mIndex > aIndex) {
mIndex = mIndex - 1;
}
// NB: If the entry we are removing is the entry currently // being navigated to (mRequestedIndex) then we adjust the index // only if we're not keeping the next entry (because if we are keeping // the next entry (because the current is a duplicate of the next), then // that entry slides into the spot that we're currently pointing to. // We don't do this adjustment for mIndex because mIndex cannot equal // aIndex.
// NB: We don't need to guard on mRequestedIndex being nonzero here, // because either they're strictly greater than aIndex which is at least // zero, or they are equal to aIndex in which case aKeepNext must be true // if aIndex is zero. if (mRequestedIndex > aIndex || (mRequestedIndex == aIndex && !aKeepNext)) {
mRequestedIndex = mRequestedIndex - 1;
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung ist noch experimentell.