gecko-dev/docshell/shistory/nsSHistory.cpp

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

2371 lines
78 KiB
C++
Raw Normal View History

/* -*- 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
2012-05-21 11:12:37 +00:00
* 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/. */
#include "nsSHistory.h"
#include <algorithm>
#include "nsContentUtils.h"
#include "nsCOMArray.h"
#include "nsComponentManagerUtils.h"
#include "nsDocShell.h"
#include "nsFrameLoaderOwner.h"
#include "nsHashKeys.h"
#include "nsIContentViewer.h"
#include "nsIDocShell.h"
#include "nsDocShellLoadState.h"
#include "nsIDocShellTreeItem.h"
#include "nsILayoutHistoryState.h"
#include "nsIObserverService.h"
#include "nsISHEntry.h"
#include "nsISHistoryListener.h"
#include "nsIURI.h"
#include "nsIXULRuntime.h"
#include "nsNetUtil.h"
#include "nsTHashMap.h"
#include "nsSHEntry.h"
#include "SessionHistoryEntry.h"
#include "nsTArray.h"
#include "prsystem.h"
#include "mozilla/Attributes.h"
#include "mozilla/dom/BrowsingContextGroup.h"
#include "mozilla/dom/CanonicalBrowsingContext.h"
#include "mozilla/dom/ContentParent.h"
#include "mozilla/dom/Element.h"
#include "mozilla/dom/RemoteWebProgressRequest.h"
#include "mozilla/dom/WindowGlobalParent.h"
#include "mozilla/LinkedList.h"
#include "mozilla/MathAlgorithms.h"
#include "mozilla/Preferences.h"
#include "mozilla/ProcessPriorityManager.h"
#include "mozilla/Services.h"
#include "mozilla/StaticPrefs_fission.h"
#include "mozilla/StaticPtr.h"
#include "mozilla/dom/CanonicalBrowsingContext.h"
#include "nsIWebNavigation.h"
#include "nsDocShellLoadTypes.h"
#include "base/process.h"
using namespace mozilla;
using namespace mozilla::dom;
#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)
static const char* kObservedPrefs[] = {PREF_SHISTORY_SIZE,
PREF_SHISTORY_MAX_TOTAL_VIEWERS,
PREF_FISSION_BFCACHEINPARENT, nullptr};
static int32_t gHistoryMaxSize = 50;
// List of all SHistory objects, used for content viewer cache eviction
static LinkedList<nsSHistory> 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;
extern mozilla::LazyLogModule gSHLog;
LazyLogModule gSHistoryLog("nsSHistory");
#define LOG(format) MOZ_LOG(gSHistoryLog, mozilla::LogLevel::Debug, format)
extern mozilla::LazyLogModule gPageCacheLog;
extern mozilla::LazyLogModule gSHIPBFCacheLog;
// 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(); \
} \
const char* _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
// Iterates over all registered session history listeners.
#define ITERATE_LISTENERS(body) \
PR_BEGIN_MACRO { \
for (const nsWeakPtr& weakPtr : mListeners.EndLimitedRange()) { \
nsCOMPtr<nsISHistoryListener> listener = do_QueryReferent(weakPtr); \
if (listener) { \
body \
} \
} \
} \
PR_END_MACRO
// Calls a given method on all registered session history listeners.
#define NOTIFY_LISTENERS(method, args) \
ITERATE_LISTENERS(listener->method args;);
// Calls a given method 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.
#define NOTIFY_LISTENERS_CANCELABLE(method, retval, args) \
PR_BEGIN_MACRO { \
bool canceled = false; \
(retval) = true; \
ITERATE_LISTENERS(if (NS_SUCCEEDED(listener->method args) && !(retval)) { \
canceled = true; \
}); \
if (canceled) { \
(retval) = false; \
} \
} \
PR_END_MACRO
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);
if (mozilla::SessionHistoryInParent() &&
mSHistory->GetBrowsingContext()) {
mSHistory->GetBrowsingContext()
->Canonical()
->HistoryCommitIndexAndLength();
}
}
}
RefPtr<nsSHistory> mSHistory;
};
enum HistCmd { HIST_CMD_GOTOINDEX, HIST_CMD_RELOAD };
class nsSHistoryObserver final : public nsIObserver {
public:
NS_DECL_ISUPPORTS
NS_DECL_NSIOBSERVER
nsSHistoryObserver() {}
static void PrefChanged(const char* aPref, void* aSelf);
void PrefChanged(const char* aPref);
protected:
~nsSHistoryObserver() {}
};
StaticRefPtr<nsSHistoryObserver> gObserver;
NS_IMPL_ISUPPORTS(nsSHistoryObserver, nsIObserver)
// static
void nsSHistoryObserver::PrefChanged(const char* aPref, void* aSelf) {
static_cast<nsSHistoryObserver*>(aSelf)->PrefChanged(aPref);
}
void nsSHistoryObserver::PrefChanged(const char* aPref) {
nsSHistory::UpdatePrefs();
nsSHistory::GloballyEvictContentViewers();
}
NS_IMETHODIMP
nsSHistoryObserver::Observe(nsISupports* aSubject, const char* aTopic,
const char16_t* aData) {
if (!strcmp(aTopic, "cacheservice:empty-cache") ||
!strcmp(aTopic, "memory-pressure")) {
nsSHistory::GloballyEvictAllContentViewers();
}
return NS_OK;
}
void nsSHistory::EvictContentViewerForEntry(nsISHEntry* aEntry) {
nsCOMPtr<nsIContentViewer> viewer = aEntry->GetContentViewer();
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.
NotifyListenersContentViewerEvicted(1);
aEntry->SetContentViewer(nullptr);
aEntry->SyncPresentationState();
viewer->Destroy();
} else if (nsCOMPtr<SessionHistoryEntry> she = do_QueryInterface(aEntry)) {
if (RefPtr<nsFrameLoader> frameLoader = she->GetFrameLoader()) {
MOZ_LOG(gSHIPBFCacheLog, LogLevel::Debug,
("nsSHistory::EvictContentViewerForEntry "
"destroying an nsFrameLoader."));
NotifyListenersContentViewerEvicted(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);
}
}
nsSHistory::nsSHistory(BrowsingContext* aRootBC)
: mRootBC(aRootBC),
mHasOngoingUpdate(false),
mIndex(-1),
mRequestedIndex(-1),
mRootDocShellID(aRootBC->GetHistoryID()) {
static bool sCalledStartup = false;
if (!sCalledStartup) {
Startup();
sCalledStartup = true;
}
// Add this new SHistory object to the list
gSHistoryList.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();
}
NS_IMPL_ADDREF(nsSHistory)
NS_IMPL_RELEASE(nsSHistory)
NS_INTERFACE_MAP_BEGIN(nsSHistory)
NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsISHistory)
NS_INTERFACE_MAP_ENTRY(nsISHistory)
NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
NS_INTERFACE_MAP_END
// 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 ContentViewers we should cache based
// on RAM. This assumes that the average ContentViewer is 4MB (conservative)
// and caps the max at 8 ContentViewers
//
// TODO: Should we split the cache memory betw. ContentViewer caching and
// nsCacheService?
//
// RAM | ContentViewers | 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 ContentViewer 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;
}
// static
void nsSHistory::UpdatePrefs() {
Preferences::GetInt(PREF_SHISTORY_SIZE, &gHistoryMaxSize);
if (mozilla::SessionHistoryInParent() && !mozilla::BFCacheInParent()) {
sHistoryMaxTotalViewers = 0;
return;
}
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();
}
}
// static
nsresult nsSHistory::Startup() {
UpdatePrefs();
// 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);
}
}
return NS_OK;
}
// static
void nsSHistory::Shutdown() {
if (gObserver) {
Preferences::UnregisterCallbacks(nsSHistoryObserver::PrefChanged,
kObservedPrefs, gObserver.get());
nsCOMPtr<nsIObserverService> obsSvc =
mozilla::services::GetObserverService();
if (obsSvc) {
obsSvc->RemoveObserver(gObserver, "cacheservice:empty-cache");
obsSvc->RemoveObserver(gObserver, "memory-pressure");
}
gObserver = nullptr;
}
}
// static
already_AddRefed<nsISHEntry> nsSHistory::GetRootSHEntry(nsISHEntry* aEntry) {
nsCOMPtr<nsISHEntry> rootEntry = aEntry;
nsCOMPtr<nsISHEntry> result = nullptr;
while (rootEntry) {
result = rootEntry;
rootEntry = result->GetParent();
}
return result.forget();
}
// static
nsresult nsSHistory::WalkHistoryEntries(nsISHEntry* aRootEntry,
BrowsingContext* aBC,
WalkHistoryEntriesFunc aCallback,
void* aData) {
NS_ENSURE_TRUE(aRootEntry, NS_ERROR_FAILURE);
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;
}
}
nsDocShell* docshell = static_cast<nsDocShell*>(child->GetDocShell());
if (docshell && docshell->HasHistoryEntry(childEntry)) {
childBC = docshell->GetBrowsingContext();
foundChild = true;
}
// XXX Simplify this once the old and new session history
// implementations don't run at the same time.
if (foundChild) {
break;
}
}
}
nsresult rv = aCallback(childEntry, childBC, i, aData);
NS_ENSURE_SUCCESS(rv, rv);
}
return NS_OK;
}
// callback data for WalkHistoryEntries
struct MOZ_STACK_CLASS CloneAndReplaceData {
CloneAndReplaceData(uint32_t aCloneID, nsISHEntry* aReplaceEntry,
bool aCloneChildren, nsISHEntry* aDestTreeParent)
: cloneID(aCloneID),
cloneChildren(aCloneChildren),
replaceEntry(aReplaceEntry),
destTreeParent(aDestTreeParent) {}
uint32_t cloneID;
bool cloneChildren;
nsISHEntry* replaceEntry;
nsISHEntry* destTreeParent;
nsCOMPtr<nsISHEntry> resultEntry;
};
nsresult nsSHistory::CloneAndReplaceChild(nsISHEntry* aEntry,
BrowsingContext* aOwnerBC,
int32_t aChildIndex, void* aData) {
nsCOMPtr<nsISHEntry> dest;
CloneAndReplaceData* data = static_cast<CloneAndReplaceData*>(aData);
uint32_t cloneID = data->cloneID;
nsISHEntry* replaceEntry = data->replaceEntry;
if (!aEntry) {
if (data->destTreeParent) {
data->destTreeParent->AddChild(nullptr, aChildIndex);
}
return NS_OK;
}
uint32_t srcID = aEntry->GetID();
nsresult rv = NS_OK;
if (srcID == cloneID) {
// Replace the entry
dest = replaceEntry;
} else {
// Clone the SHEntry...
rv = aEntry->Clone(getter_AddRefs(dest));
NS_ENSURE_SUCCESS(rv, rv);
}
dest->SetIsSubFrame(true);
if (srcID != cloneID || data->cloneChildren) {
// Walk the children
CloneAndReplaceData childData(cloneID, replaceEntry, data->cloneChildren,
dest);
rv = nsSHistory::WalkHistoryEntries(aEntry, aOwnerBC, CloneAndReplaceChild,
&childData);
NS_ENSURE_SUCCESS(rv, rv);
}
if (srcID != cloneID && aOwnerBC) {
nsSHistory::HandleEntriesToSwapInDocShell(aOwnerBC, aEntry, dest);
}
if (data->destTreeParent) {
data->destTreeParent->AddChild(dest, aChildIndex);
}
data->resultEntry = dest;
return rv;
}
// static
nsresult nsSHistory::CloneAndReplace(
nsISHEntry* aSrcEntry, BrowsingContext* aOwnerBC, uint32_t aCloneID,
nsISHEntry* aReplaceEntry, bool aCloneChildren, nsISHEntry** aDestEntry) {
NS_ENSURE_ARG_POINTER(aDestEntry);
NS_ENSURE_TRUE(aReplaceEntry, NS_ERROR_FAILURE);
CloneAndReplaceData data(aCloneID, aReplaceEntry, aCloneChildren, nullptr);
nsresult rv = CloneAndReplaceChild(aSrcEntry, aOwnerBC, 0, &data);
data.resultEntry.swap(*aDestEntry);
return rv;
}
// static
void nsSHistory::WalkContiguousEntries(
nsISHEntry* aEntry, const std::function<void(nsISHEntry*)>& aCallback) {
MOZ_ASSERT(aEntry);
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;
}
aCallback(entry);
}
}
}
NS_IMETHODIMP
nsSHistory::AddChildSHEntryHelper(nsISHEntry* aCloneRef, nsISHEntry* aNewEntry,
BrowsingContext* aRootBC,
bool aCloneChildren) {
MOZ_ASSERT(aRootBC->IsTop());
/* 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;
}
GetEntryAtIndex(index, getter_AddRefs(currentHE));
NS_ENSURE_TRUE(currentHE, NS_ERROR_FAILURE);
nsresult rv = NS_OK;
uint32_t cloneID = aCloneRef->GetID();
rv = nsSHistory::CloneAndReplace(currentHE, aRootBC, cloneID, aNewEntry,
aCloneChildren, getter_AddRefs(child));
if (NS_SUCCEEDED(rv)) {
rv = AddEntry(child, true);
if (NS_SUCCEEDED(rv)) {
child->SetDocshellID(aRootBC->GetHistoryID());
}
}
return rv;
}
nsresult nsSHistory::SetChildHistoryEntry(nsISHEntry* aEntry,
BrowsingContext* aBC,
int32_t aEntryIndex, void* aData) {
SwapEntriesData* data = static_cast<SwapEntriesData*>(aData);
if (!aBC || aBC == data->ignoreBC) {
return NS_OK;
}
nsISHEntry* destTreeRoot = data->destTreeRoot;
nsCOMPtr<nsISHEntry> destEntry;
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;
}
if (entry->GetID() == targetID) {
destEntry.swap(entry);
break;
}
}
}
} else {
destEntry = destTreeRoot;
}
nsSHistory::HandleEntriesToSwapInDocShell(aBC, aEntry, destEntry);
// Now handle the children of aEntry.
SwapEntriesData childData = {data->ignoreBC, destTreeRoot, destEntry};
return nsSHistory::WalkHistoryEntries(aEntry, aBC, SetChildHistoryEntry,
&childData);
}
// static
void nsSHistory::HandleEntriesToSwapInDocShell(
mozilla::dom::BrowsingContext* aBC, nsISHEntry* aOldEntry,
nsISHEntry* aNewEntry) {
bool shPref = mozilla::SessionHistoryInParent();
if (aBC->IsInProcess() || !shPref) {
nsDocShell* docshell = static_cast<nsDocShell*>(aBC->GetDocShell());
if (docshell) {
docshell->SwapHistoryEntries(aOldEntry, aNewEntry);
}
} else {
// FIXME Bug 1633988: Need to update entries?
}
// 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() {
if (mRootBC && mRootBC->EverAttached()) {
bool sameDocument = IsEmptyOrHasEntriesForSingleTopLevelPage();
if (sameDocument != mRootBC->GetIsSingleToplevelInHistory()) {
// If the browsing context is discarded then its session history is
// invalid and will go away.
Unused << mRootBC->SetIsSingleToplevelInHistory(sameDocument);
}
}
}
NS_IMETHODIMP
nsSHistory::AddToRootSessionHistory(bool aCloneChildren, nsISHEntry* aOSHE,
BrowsingContext* aRootBC,
nsISHEntry* aEntry, uint32_t aLoadType,
bool aShouldPersist,
Maybe<int32_t>* aPreviousEntryIndex,
Maybe<int32_t>* aLoadedEntryIndex) {
MOZ_ASSERT(aRootBC->IsTop());
nsresult rv = NS_OK;
// 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
if (mRootBC) {
aSHEntry->SetDocshellID(mRootDocShellID);
}
if (mIndex >= 0) {
MOZ_ASSERT(mIndex < Length(), "Index out of range!");
if (mIndex >= Length()) {
return NS_ERROR_FAILURE;
}
if (mEntries[mIndex] && !mEntries[mIndex]->GetPersist()) {
NOTIFY_LISTENERS(OnHistoryReplaceEntry, ());
aSHEntry->SetPersist(aPersist);
mEntries[mIndex] = aSHEntry;
UpdateRootBrowsingContextState();
return NS_OK;
}
}
SHistoryChangeNotifier change(this);
int32_t truncating = Length() - 1 - mIndex;
if (truncating > 0) {
NOTIFY_LISTENERS(OnHistoryTruncate, (truncating));
}
nsCOMPtr<nsIURI> uri = aSHEntry->GetURI();
NOTIFY_LISTENERS(OnHistoryNewEntry, (uri, mIndex));
// 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);
}
UpdateRootBrowsingContextState();
return NS_OK;
}
void nsSHistory::NotifyOnHistoryReplaceEntry() {
NOTIFY_LISTENERS(OnHistoryReplaceEntry, ());
}
/* Get size of the history list */
NS_IMETHODIMP
nsSHistory::GetCount(int32_t* aResult) {
MOZ_ASSERT(aResult, "null out param?");
*aResult = Length();
return NS_OK;
}
NS_IMETHODIMP
nsSHistory::GetIndex(int32_t* aResult) {
MOZ_ASSERT(aResult, "null out param?");
*aResult = mIndex;
return NS_OK;
}
NS_IMETHODIMP
nsSHistory::SetIndex(int32_t aIndex) {
if (aIndex < 0 || aIndex >= Length()) {
return NS_ERROR_FAILURE;
}
mIndex = aIndex;
return NS_OK;
}
/* Get the requestedIndex */
NS_IMETHODIMP
nsSHistory::GetRequestedIndex(int32_t* aResult) {
MOZ_ASSERT(aResult, "null out param?");
*aResult = mRequestedIndex;
return NS_OK;
}
NS_IMETHODIMP_(void)
nsSHistory::InternalSetRequestedIndex(int32_t aRequestedIndex) {
MOZ_ASSERT(aRequestedIndex >= -1 && aRequestedIndex < Length());
mRequestedIndex = aRequestedIndex;
}
NS_IMETHODIMP
nsSHistory::GetEntryAtIndex(int32_t aIndex, nsISHEntry** aResult) {
NS_ENSURE_ARG_POINTER(aResult);
if (aIndex < 0 || aIndex >= Length()) {
return NS_ERROR_FAILURE;
}
*aResult = mEntries[aIndex];
NS_ADDREF(*aResult);
return NS_OK;
}
NS_IMETHODIMP_(int32_t)
nsSHistory::GetIndexOfEntry(nsISHEntry* aSHEntry) {
for (int32_t i = 0; i < Length(); i++) {
if (aSHEntry == mEntries[i]) {
return i;
}
}
return -1;
}
static void LogEntry(nsISHEntry* aEntry, int32_t aIndex, int32_t aTotal,
const nsCString& aPrefix, bool aIsCurrent) {
if (!aEntry) {
MOZ_LOG(gSHLog, LogLevel::Debug,
(" %s+- %i SH Entry null\n", aPrefix.get(), aIndex));
return;
}
nsCOMPtr<nsIURI> uri = aEntry->GetURI();
nsAutoString title, name;
aEntry->GetTitle(title);
aEntry->GetName(name);
SHEntrySharedParentState* shared;
if (mozilla::SessionHistoryInParent()) {
shared = static_cast<SessionHistoryEntry*>(aEntry)->SharedInfo();
} else {
shared = static_cast<nsSHEntry*>(aEntry)->GetState();
}
nsID docShellId;
aEntry->GetDocshellID(docShellId);
int32_t childCount = aEntry->GetChildCount();
MOZ_LOG(gSHLog, LogLevel::Debug,
("%s%s+- %i SH Entry %p %" PRIu64 " %s\n", aIsCurrent ? ">" : " ",
aPrefix.get(), aIndex, aEntry, shared->GetId(),
nsIDToCString(docShellId).get()));
nsCString prefix(aPrefix);
if (aIndex < aTotal - 1) {
prefix.AppendLiteral("| ");
} else {
prefix.AppendLiteral(" ");
}
MOZ_LOG(gSHLog, LogLevel::Debug,
(" %s%s URL = %s\n", prefix.get(), childCount > 0 ? "|" : " ",
uri->GetSpecOrDefault().get()));
MOZ_LOG(gSHLog, LogLevel::Debug,
(" %s%s Title = %s\n", prefix.get(), childCount > 0 ? "|" : " ",
NS_LossyConvertUTF16toASCII(title).get()));
MOZ_LOG(gSHLog, LogLevel::Debug,
(" %s%s Name = %s\n", prefix.get(), childCount > 0 ? "|" : " ",
NS_LossyConvertUTF16toASCII(name).get()));
MOZ_LOG(
gSHLog, LogLevel::Debug,
(" %s%s Is in BFCache = %s\n", prefix.get(), childCount > 0 ? "|" : " ",
aEntry->GetIsInBFCache() ? "true" : "false"));
nsCOMPtr<nsISHEntry> prevChild;
for (int32_t i = 0; i < childCount; ++i) {
nsCOMPtr<nsISHEntry> child;
aEntry->GetChildAt(i, getter_AddRefs(child));
LogEntry(child, i, childCount, prefix, false);
child.swap(prevChild);
}
}
void nsSHistory::LogHistory() {
if (!MOZ_LOG_TEST(gSHLog, LogLevel::Debug)) {
return;
}
MOZ_LOG(gSHLog, LogLevel::Debug, ("nsSHistory %p\n", this));
int32_t length = Length();
for (int32_t i = 0; i < length; i++) {
LogEntry(mEntries[i], i, length, EmptyCString(), i == mIndex);
}
}
void nsSHistory::WindowIndices(int32_t aIndex, int32_t* aOutStartIndex,
int32_t* aOutEndIndex) {
*aOutStartIndex = std::max(0, aIndex - nsSHistory::VIEWER_WINDOW);
*aOutEndIndex = std::min(Length() - 1, aIndex + nsSHistory::VIEWER_WINDOW);
}
static void MarkAsInitialEntry(
SessionHistoryEntry* aEntry,
nsTHashMap<nsIDHashKey, SessionHistoryEntry*>& aHashtable) {
if (!aEntry->BCHistoryLength().Modified()) {
++(aEntry->BCHistoryLength());
}
aHashtable.InsertOrUpdate(aEntry->DocshellID(), aEntry);
for (const RefPtr<SessionHistoryEntry>& entry : aEntry->Children()) {
if (entry) {
MarkAsInitialEntry(entry, aHashtable);
}
}
}
static void ClearEntries(SessionHistoryEntry* aEntry) {
aEntry->ClearBCHistoryLength();
for (const RefPtr<SessionHistoryEntry>& entry : aEntry->Children()) {
if (entry) {
ClearEntries(entry);
}
}
}
NS_IMETHODIMP
nsSHistory::PurgeHistory(int32_t aNumEntries) {
if (Length() <= 0 || aNumEntries <= 0) {
return NS_ERROR_FAILURE;
}
SHistoryChangeNotifier change(this);
aNumEntries = std::min(aNumEntries, Length());
NOTIFY_LISTENERS(OnHistoryPurge, (aNumEntries));
// 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);
}
}
if (mRootBC) {
mRootBC->PreOrderWalk([&docshellIDToEntry](BrowsingContext* aBC) {
SessionHistoryEntry* entry = docshellIDToEntry.Get(aBC->GetHistoryID());
Unused << aBC->SetHistoryEntryCount(
entry ? uint32_t(entry->BCHistoryLength()) : 0);
});
}
// 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 (mRootBC && mRootBC->GetDocShell()) {
mRootBC->GetDocShell()->HistoryPurged(aNumEntries);
}
2008-09-11 21:54:40 +00:00
UpdateRootBrowsingContextState();
return NS_OK;
}
NS_IMETHODIMP
nsSHistory::AddSHistoryListener(nsISHistoryListener* aListener) {
NS_ENSURE_ARG_POINTER(aListener);
// 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;
}
mListeners.AppendElementUnlessExists(listener);
return NS_OK;
}
void nsSHistory::NotifyListenersContentViewerEvicted(uint32_t aNumEvicted) {
NOTIFY_LISTENERS(OnContentViewerEvicted, (aNumEvicted));
}
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);
if (aIndex < 0 || aIndex >= Length()) {
return NS_ERROR_FAILURE;
}
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;
}
aReplaceEntry->SetShistory(this);
NOTIFY_LISTENERS(OnHistoryReplaceEntry, ());
aReplaceEntry->SetPersist(true);
mEntries[aIndex] = aReplaceEntry;
UpdateRootBrowsingContextState();
return NS_OK;
}
NS_IMETHODIMP
nsSHistory::NotifyOnHistoryReload(bool* aCanReload) {
NOTIFY_LISTENERS_CANCELABLE(OnHistoryReload, *aCanReload, (aCanReload));
return NS_OK;
}
NS_IMETHODIMP
nsSHistory::EvictOutOfRangeContentViewers(int32_t aIndex) {
MOZ_LOG(gSHIPBFCacheLog, LogLevel::Debug,
("nsSHistory::EvictOutOfRangeContentViewers %i", aIndex));
2008-09-11 21:54:40 +00:00
// Check our per SHistory object limit in the currently navigated SHistory
EvictOutOfRangeWindowContentViewers(aIndex);
// Check our total limit across all SHistory objects
GloballyEvictContentViewers();
return NS_OK;
}
NS_IMETHODIMP_(void)
nsSHistory::EvictContentViewersOrReplaceEntry(nsISHEntry* aNewSHEntry,
bool aReplace) {
if (!aReplace) {
int32_t curIndex;
GetIndex(&curIndex);
if (curIndex > -1) {
EvictOutOfRangeContentViewers(curIndex);
}
} else {
nsCOMPtr<nsISHEntry> rootSHEntry = nsSHistory::GetRootSHEntry(aNewSHEntry);
int32_t index = GetIndexOfEntry(rootSHEntry);
if (index > -1) {
ReplaceEntry(index, rootSHEntry);
}
}
}
NS_IMETHODIMP
nsSHistory::EvictAllContentViewers() {
// 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++) {
EvictContentViewerForEntry(mEntries[i]);
}
return NS_OK;
}
static void FinishRestore(CanonicalBrowsingContext* aBrowsingContext,
nsDocShellLoadState* aLoadState,
SessionHistoryEntry* aEntry,
nsFrameLoader* aFrameLoader, bool aCanSave) {
MOZ_ASSERT(aEntry);
MOZ_ASSERT(aFrameLoader);
aEntry->SetFrameLoader(nullptr);
nsCOMPtr<nsISHistory> shistory = aEntry->GetShistory();
int32_t indexOfHistoryLoad =
shistory ? shistory->GetIndexOfEntry(aEntry) : -1;
nsCOMPtr<nsFrameLoaderOwner> frameLoaderOwner =
do_QueryInterface(aBrowsingContext->GetEmbedderElement());
if (frameLoaderOwner && aFrameLoader->GetMaybePendingBrowsingContext() &&
indexOfHistoryLoad >= 0) {
// 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 */);
BrowsingContextWebProgress* webProgress =
aBrowsingContext->GetWebProgress();
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 (aCanSave && currentSHEntry) {
currentSHEntry->SetFrameLoader(currentFrameLoader);
Unused << aBrowsingContext->SetIsInBFCache(true);
}
if (aBrowsingContext->IsActive()) {
loadingBC->PreOrderWalk([&](BrowsingContext* aContext) {
if (BrowserParent* bp = aContext->Canonical()->GetBrowserParent()) {
ProcessPriorityManager::ActivityChanged(bp, true);
}
});
}
// ReplacedBy will swap the entry back.
aBrowsingContext->SetActiveSessionHistoryEntry(aEntry);
loadingBC->SetActiveSessionHistoryEntry(nullptr);
Bug 1650089 - Part 3: Rework DocumentChannel-triggered process switches to support null principals, r=annyG,kmag This is a large refactoring of the DocumentChannel process switch codepath, with the end goal of being better able to support future process switch requirements such as dynamic isolation on android, as well as the immediate requirement of null principal handling. The major changes include: 1. The logic is in C++ and has less failure cases, meaning it should be harder for us to error out unexpectedly and not process switch. 2. Process selection decisions are more explicit, and tend to rely less on state such as the current remoteType when possible. This makes reasoning about where a specific load will complete easier. 3. Additional checks are made after a "WebContent" behavior is selected to ensure that if an existing document in the same BCG is found, the load will finish in the required content process. This should make dynamic checks such as Android's logged-in site isolation easier to implement. 4. ProcessIsolation logging is split out from DocumentChannel so that it's easier to log just the information related to process selection when debugging. 5. Null result principal precursors are considered when performing process selection. Other uses of E10SUtils for process selection have not yet been migrated to the new design as they have slightly different requirements. This will be done in follow-up bugs. Differential Revision: https://phabricator.services.mozilla.com/D120673
2021-08-10 14:31:17 +00:00
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);
// The old page can't be stored in the bfcache,
// destroy the nsFrameLoader.
if (!aCanSave && currentFrameLoader) {
// Since destroying the browsing context may need to update layout
// history state, the browsing context needs to have still access to the
// correct entry.
aBrowsingContext->SetActiveSessionHistoryEntry(currentSHEntry);
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);
}
/* static */
void nsSHistory::LoadURIOrBFCache(LoadEntryResult& aLoadEntry) {
if (mozilla::BFCacheInParent() && aLoadEntry.mBrowsingContext->IsTop()) {
MOZ_ASSERT(XRE_IsParentProcess());
RefPtr<nsDocShellLoadState> loadState = aLoadEntry.mLoadState;
RefPtr<CanonicalBrowsingContext> canonicalBC =
aLoadEntry.mBrowsingContext->Canonical();
nsCOMPtr<SessionHistoryEntry> she = do_QueryInterface(loadState->SHEntry());
nsCOMPtr<SessionHistoryEntry> currentShe =
canonicalBC->GetActiveSessionHistoryEntry();
MOZ_ASSERT(she);
RefPtr<nsFrameLoader> frameLoader = she->GetFrameLoader();
if (frameLoader &&
(!currentShe || (she->SharedInfo() != currentShe->SharedInfo() &&
!currentShe->GetFrameLoader()))) {
bool canSave = (!currentShe || currentShe->GetSaveLayoutStateFlag()) &&
canonicalBC->AllowedInBFCache(Nothing());
MOZ_LOG(gSHIPBFCacheLog, LogLevel::Debug,
("nsSHistory::LoadURIOrBFCache "
"saving presentation=%i",
canSave));
if (!canSave) {
nsCOMPtr<nsFrameLoaderOwner> frameLoaderOwner =
do_QueryInterface(canonicalBC->GetEmbedderElement());
if (frameLoaderOwner) {
RefPtr<nsFrameLoader> currentFrameLoader =
frameLoaderOwner->GetFrameLoader();
if (currentFrameLoader &&
currentFrameLoader->GetMaybePendingBrowsingContext()) {
WindowGlobalParent* wgp =
currentFrameLoader->GetMaybePendingBrowsingContext()
->Canonical()
->GetCurrentWindowGlobal();
if (wgp) {
wgp->PermitUnload([canonicalBC, loadState, she, frameLoader,
currentFrameLoader](bool aAllow) {
if (aAllow) {
FinishRestore(canonicalBC, loadState, she, frameLoader,
false);
} else if (currentFrameLoader
->GetMaybePendingBrowsingContext()) {
nsISHistory* shistory =
currentFrameLoader->GetMaybePendingBrowsingContext()
->Canonical()
->GetSessionHistory();
if (shistory) {
shistory->InternalSetRequestedIndex(-1);
}
}
});
return;
}
}
}
}
FinishRestore(canonicalBC, loadState, she, frameLoader, canSave);
return;
}
if (frameLoader) {
she->SetFrameLoader(nullptr);
frameLoader->Destroy();
}
}
aLoadEntry.mBrowsingContext->LoadURI(aLoadEntry.mLoadState, false);
}
/* static */
void nsSHistory::LoadURIs(nsTArray<LoadEntryResult>& aLoadResults) {
for (LoadEntryResult& loadEntry : aLoadResults) {
LoadURIOrBFCache(loadEntry);
}
}
NS_IMETHODIMP
nsSHistory::Reload(uint32_t aReloadFlags) {
nsTArray<LoadEntryResult> loadResults;
nsresult rv = Reload(aReloadFlags, loadResults);
NS_ENSURE_SUCCESS(rv, rv);
if (loadResults.IsEmpty()) {
return NS_OK;
}
LoadURIs(loadResults);
return NS_OK;
}
nsresult nsSHistory::Reload(uint32_t aReloadFlags,
nsTArray<LoadEntryResult>& aLoadResults) {
MOZ_ASSERT(aLoadResults.IsEmpty());
uint32_t loadType;
if (aReloadFlags & nsIWebNavigation::LOAD_FLAGS_BYPASS_PROXY &&
aReloadFlags & nsIWebNavigation::LOAD_FLAGS_BYPASS_CACHE) {
loadType = LOAD_RELOAD_BYPASS_PROXY_AND_CACHE;
} else if (aReloadFlags & nsIWebNavigation::LOAD_FLAGS_BYPASS_PROXY) {
loadType = LOAD_RELOAD_BYPASS_PROXY;
} else if (aReloadFlags & nsIWebNavigation::LOAD_FLAGS_BYPASS_CACHE) {
loadType = LOAD_RELOAD_BYPASS_CACHE;
} else if (aReloadFlags & nsIWebNavigation::LOAD_FLAGS_CHARSET_CHANGE) {
loadType = LOAD_RELOAD_CHARSET_CHANGE;
} else {
loadType = LOAD_RELOAD_NORMAL;
}
// 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;
NOTIFY_LISTENERS_CANCELABLE(OnHistoryReload, canNavigate, (&canNavigate));
if (!canNavigate) {
return NS_OK;
}
nsresult rv = LoadEntry(
mIndex, loadType, HIST_CMD_RELOAD, aLoadResults, /* aSameEpoch */ false,
/* aLoadCurrentEntry */ true,
aReloadFlags & nsIWebNavigation::LOAD_FLAGS_USER_ACTIVATION);
if (NS_FAILED(rv)) {
aLoadResults.Clear();
return rv;
}
return NS_OK;
}
NS_IMETHODIMP
nsSHistory::ReloadCurrentEntry() {
nsTArray<LoadEntryResult> loadResults;
nsresult rv = ReloadCurrentEntry(loadResults);
NS_ENSURE_SUCCESS(rv, rv);
LoadURIs(loadResults);
return NS_OK;
}
nsresult nsSHistory::ReloadCurrentEntry(
nsTArray<LoadEntryResult>& aLoadResults) {
// Notify listeners
NOTIFY_LISTENERS(OnHistoryGotoIndex, ());
return LoadEntry(mIndex, LOAD_HISTORY, HIST_CMD_RELOAD, aLoadResults,
/* aSameEpoch */ false, /* aLoadCurrentEntry */ true,
/* aUserActivation */ false);
}
void nsSHistory::EvictOutOfRangeWindowContentViewers(int32_t aIndex) {
// XXX rename method to EvictContentViewersExceptAroundIndex, 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);
LOG(
("EvictOutOfRangeWindowContentViewers(index=%d), "
"Length()=%d. Safe range [%d, %d]",
aIndex, Length(), 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<nsIContentViewer> safeViewers;
nsTArray<RefPtr<nsFrameLoader>> safeFrameLoaders;
for (int32_t i = startSafeIndex; i <= endSafeIndex; i++) {
nsCOMPtr<nsIContentViewer> viewer = mEntries[i]->GetContentViewer();
if (viewer) {
safeViewers.AppendObject(viewer);
} else if (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<nsIContentViewer> viewer = entry->GetContentViewer();
if (viewer) {
if (safeViewers.IndexOf(viewer) == -1) {
EvictContentViewerForEntry(entry);
}
} else if (nsCOMPtr<SessionHistoryEntry> she =
do_QueryInterface(mEntries[i])) {
nsFrameLoader* frameLoader = she->GetFrameLoader();
if (frameLoader) {
if (!safeFrameLoaders.Contains(frameLoader)) {
EvictContentViewerForEntry(entry);
}
}
}
}
}
namespace {
class EntryAndDistance {
public:
EntryAndDistance(nsSHistory* aSHistory, nsISHEntry* aEntry, uint32_t aDist)
: mSHistory(aSHistory),
mEntry(aEntry),
mViewer(aEntry->GetContentViewer()),
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.");
}
bool operator<(const EntryAndDistance& aOther) const {
// Compare distances first, and fall back to last-accessed times.
if (aOther.mDistance != this->mDistance) {
return this->mDistance < aOther.mDistance;
}
return this->mLastTouched < aOther.mLastTouched;
}
bool operator==(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;
}
RefPtr<nsSHistory> mSHistory;
nsCOMPtr<nsISHEntry> mEntry;
nsCOMPtr<nsIContentViewer> mViewer;
RefPtr<nsFrameLoader> mFrameLoader;
uint32_t mLastTouched;
int32_t mDistance;
};
} // namespace
// static
void nsSHistory::GloballyEvictContentViewers() {
// 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) {
// 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::EvictContentViewers()
// often enough. Once we do call EvictContentViewers() 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<nsIContentViewer> contentViewer = entry->GetContentViewer();
bool found = false;
bool hasContentViewerOrFrameLoader = false;
if (contentViewer) {
hasContentViewerOrFrameLoader = 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 == contentViewer) {
container.mDistance =
std::min(container.mDistance, DeprecatedAbs(i - shist->mIndex));
found = true;
break;
}
}
} else if (nsCOMPtr<SessionHistoryEntry> she = do_QueryInterface(entry)) {
if (RefPtr<nsFrameLoader> frameLoader = she->GetFrameLoader()) {
hasContentViewerOrFrameLoader = 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 (hasContentViewerOrFrameLoader && !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)->EvictContentViewerForEntry(entries[i].mEntry);
}
}
nsresult nsSHistory::FindEntryForBFCache(SHEntrySharedParentState* aEntry,
nsISHEntry** aResult,
int32_t* aResultIndex) {
*aResult = nullptr;
*aResultIndex = -1;
int32_t startIndex, endIndex;
WindowIndices(mIndex, &startIndex, &endIndex);
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;
}
NS_IMETHODIMP_(void)
nsSHistory::EvictExpiredContentViewerForEntry(
SHEntrySharedParentState* aEntry) {
int32_t index;
nsCOMPtr<nsISHEntry> shEntry;
FindEntryForBFCache(aEntry, getter_AddRefs(shEntry), &index);
if (index == mIndex) {
NS_WARNING("How did the current SHEntry expire?");
}
if (shEntry) {
EvictContentViewerForEntry(shEntry);
}
}
NS_IMETHODIMP_(void)
nsSHistory::AddToExpirationTracker(SHEntrySharedParentState* aEntry) {
RefPtr<SHEntrySharedParentState> entry = aEntry;
if (!mHistoryTracker || !entry) {
return;
}
mHistoryTracker->AddObject(entry);
return;
}
NS_IMETHODIMP_(void)
nsSHistory::RemoveFromExpirationTracker(SHEntrySharedParentState* aEntry) {
RefPtr<SHEntrySharedParentState> entry = aEntry;
MOZ_ASSERT(mHistoryTracker && !mHistoryTracker->IsEmpty());
if (!mHistoryTracker || !entry) {
return;
}
mHistoryTracker->RemoveObject(entry);
}
// 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.
// static
void nsSHistory::GloballyEvictAllContentViewers() {
int32_t maxViewers = sHistoryMaxTotalViewers;
sHistoryMaxTotalViewers = 0;
GloballyEvictContentViewers();
sHistoryMaxTotalViewers = maxViewers;
}
void GetDynamicChildren(nsISHEntry* aEntry, nsTArray<nsID>& aDocshellIDs) {
int32_t count = aEntry->GetChildCount();
for (int32_t i = 0; i < count; ++i) {
nsCOMPtr<nsISHEntry> child;
aEntry->GetChildAt(i, getter_AddRefs(child));
if (child) {
if (child->IsDynamicallyAdded()) {
child->GetDocshellID(*aDocshellIDs.AppendElement());
} else {
GetDynamicChildren(child, aDocshellIDs);
}
}
}
}
bool RemoveFromSessionHistoryEntry(nsISHEntry* aRoot,
nsTArray<nsID>& aDocshellIDs) {
bool didRemove = false;
int32_t childCount = aRoot->GetChildCount();
for (int32_t i = childCount - 1; i >= 0; --i) {
nsCOMPtr<nsISHEntry> child;
aRoot->GetChildAt(i, getter_AddRefs(child));
if (child) {
nsID docshelldID;
child->GetDocshellID(docshelldID);
if (aDocshellIDs.Contains(docshelldID)) {
didRemove = true;
aRoot->RemoveChild(child);
} else if (RemoveFromSessionHistoryEntry(child, aDocshellIDs)) {
didRemove = true;
}
}
}
return didRemove;
}
bool RemoveChildEntries(nsISHistory* aHistory, int32_t aIndex,
nsTArray<nsID>& aEntryIDs) {
nsCOMPtr<nsISHEntry> root;
aHistory->GetEntryAtIndex(aIndex, getter_AddRefs(root));
return root ? RemoveFromSessionHistoryEntry(root, aEntryIDs) : false;
}
bool IsSameTree(nsISHEntry* aEntry1, nsISHEntry* aEntry2) {
if (!aEntry1 && !aEntry2) {
return true;
}
if ((!aEntry1 && aEntry2) || (aEntry1 && !aEntry2)) {
return false;
}
uint32_t id1 = aEntry1->GetID();
uint32_t id2 = aEntry2->GetID();
if (id1 != id2) {
return false;
}
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)) {
return false;
}
}
return true;
}
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!");
int32_t compareIndex = aKeepNext ? aIndex + 1 : aIndex - 1;
nsresult rv;
nsCOMPtr<nsISHEntry> root1, root2;
rv = GetEntryAtIndex(aIndex, getter_AddRefs(root1));
if (NS_FAILED(rv)) {
return false;
}
rv = GetEntryAtIndex(compareIndex, getter_AddRefs(root2));
if (NS_FAILED(rv)) {
return false;
}
SHistoryChangeNotifier change(this);
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;
}
return true;
}
return false;
}
NS_IMETHODIMP_(void)
nsSHistory::RemoveEntries(nsTArray<nsID>& aIDs, int32_t aStartIndex) {
bool didRemove;
RemoveEntries(aIDs, aStartIndex, &didRemove);
if (didRemove && mRootBC && mRootBC->GetDocShell()) {
mRootBC->GetDocShell()->DispatchLocationChangeEvent();
}
}
void nsSHistory::RemoveEntries(nsTArray<nsID>& aIDs, int32_t aStartIndex,
bool* aDidRemove) {
SHistoryChangeNotifier change(this);
int32_t index = aStartIndex;
while (index >= 0 && RemoveChildEntries(this, --index, aIDs)) {
}
int32_t minIndex = index;
index = aStartIndex;
while (index >= 0 && RemoveChildEntries(this, index++, aIDs)) {
}
// We need to remove duplicate nsSHEntry trees.
*aDidRemove = false;
while (index > minIndex) {
if (index != mIndex && RemoveDuplicate(index, index < mIndex)) {
*aDidRemove = true;
}
--index;
}
UpdateRootBrowsingContextState();
}
void nsSHistory::RemoveFrameEntries(nsISHEntry* aEntry) {
int32_t count = aEntry->GetChildCount();
AutoTArray<nsID, 16> ids;
for (int32_t i = 0; i < count; ++i) {
nsCOMPtr<nsISHEntry> child;
aEntry->GetChildAt(i, getter_AddRefs(child));
if (child) {
child->GetDocshellID(*ids.AppendElement());
}
}
RemoveEntries(ids, mIndex);
}
void nsSHistory::RemoveDynEntries(int32_t aIndex, nsISHEntry* aEntry) {
// Remove dynamic entries which are at the index and belongs to the container.
nsCOMPtr<nsISHEntry> entry(aEntry);
if (!entry) {
GetEntryAtIndex(aIndex, getter_AddRefs(entry));
}
if (entry) {
AutoTArray<nsID, 16> toBeRemovedEntries;
GetDynamicChildren(entry, toBeRemovedEntries);
if (toBeRemovedEntries.Length()) {
RemoveEntries(toBeRemovedEntries, aIndex);
}
}
}
void nsSHistory::RemoveDynEntriesForBFCacheEntry(nsIBFCacheEntry* aBFEntry) {
int32_t index;
nsCOMPtr<nsISHEntry> shEntry;
FindEntryForBFCache(static_cast<nsSHEntryShared*>(aBFEntry),
getter_AddRefs(shEntry), &index);
if (shEntry) {
RemoveDynEntries(index, shEntry);
}
}
NS_IMETHODIMP
nsSHistory::UpdateIndex() {
SHistoryChangeNotifier change(this);
// Update the actual index with the right value.
if (mIndex != mRequestedIndex && mRequestedIndex != -1) {
mIndex = mRequestedIndex;
}
mRequestedIndex = -1;
return NS_OK;
}
NS_IMETHODIMP
nsSHistory::GotoIndex(int32_t aIndex, bool aUserActivation) {
nsTArray<LoadEntryResult> loadResults;
nsresult rv = GotoIndex(aIndex, loadResults, /*aSameEpoch*/ false,
aIndex == mIndex, aUserActivation);
NS_ENSURE_SUCCESS(rv, rv);
LoadURIs(loadResults);
return NS_OK;
}
NS_IMETHODIMP_(void)
nsSHistory::EnsureCorrectEntryAtCurrIndex(nsISHEntry* aEntry) {
int index = mRequestedIndex == -1 ? mIndex : mRequestedIndex;
if (index > -1 && (mEntries[index] != aEntry)) {
ReplaceEntry(index, aEntry);
}
}
nsresult nsSHistory::GotoIndex(int32_t aIndex,
nsTArray<LoadEntryResult>& aLoadResults,
bool aSameEpoch, bool aLoadCurrentEntry,
bool aUserActivation) {
return LoadEntry(aIndex, LOAD_HISTORY, HIST_CMD_GOTOINDEX, aLoadResults,
aSameEpoch, aLoadCurrentEntry, aUserActivation);
}
NS_IMETHODIMP_(bool)
nsSHistory::HasUserInteractionAtIndex(int32_t aIndex) {
nsCOMPtr<nsISHEntry> entry;
GetEntryAtIndex(aIndex, getter_AddRefs(entry));
if (!entry) {
return false;
}
return entry->GetHasUserInteraction();
}
nsresult nsSHistory::LoadNextPossibleEntry(
int32_t aNewIndex, long aLoadType, uint32_t aHistCmd,
nsTArray<LoadEntryResult>& aLoadResults, bool aLoadCurrentEntry,
bool aUserActivation) {
mRequestedIndex = -1;
if (aNewIndex < mIndex) {
return LoadEntry(aNewIndex - 1, aLoadType, aHistCmd, aLoadResults,
/*aSameEpoch*/ false, aLoadCurrentEntry, aUserActivation);
}
if (aNewIndex > mIndex) {
return LoadEntry(aNewIndex + 1, aLoadType, aHistCmd, aLoadResults,
/*aSameEpoch*/ false, aLoadCurrentEntry, aUserActivation);
}
return NS_ERROR_FAILURE;
}
nsresult nsSHistory::LoadEntry(int32_t aIndex, long aLoadType,
uint32_t aHistCmd,
nsTArray<LoadEntryResult>& aLoadResults,
bool aSameEpoch, bool aLoadCurrentEntry,
bool aUserActivation) {
MOZ_LOG(gSHistoryLog, LogLevel::Debug,
("LoadEntry(%d, 0x%lx, %u)", aIndex, aLoadType, aHistCmd));
if (!mRootBC) {
return NS_ERROR_FAILURE;
}
if (aIndex < 0 || aIndex >= Length()) {
MOZ_LOG(gSHistoryLog, LogLevel::Debug, ("Index out of range"));
// The index is out of range
return NS_ERROR_FAILURE;
}
int32_t originalRequestedIndex = mRequestedIndex;
int32_t previousRequest = mRequestedIndex > -1 ? mRequestedIndex : mIndex;
int32_t requestedOffset = aIndex - previousRequest;
// This is a normal local history navigation.
nsCOMPtr<nsISHEntry> prevEntry;
nsCOMPtr<nsISHEntry> nextEntry;
GetEntryAtIndex(mIndex, getter_AddRefs(prevEntry));
GetEntryAtIndex(aIndex, getter_AddRefs(nextEntry));
if (!nextEntry || !prevEntry) {
mRequestedIndex = -1;
return NS_ERROR_FAILURE;
}
if (mozilla::SessionHistoryInParent()) {
if (aHistCmd == HIST_CMD_GOTOINDEX) {
// https://html.spec.whatwg.org/#history-traversal:
// To traverse the history
// "If entry has a different Document object than the current entry, then
// run the following substeps: Remove any tasks queued by the history
// traversal task source..."
//
// aSameEpoch is true only if the navigations would have been
// generated in the same spin of the event loop (i.e. history.go(-2);
// history.go(-1)) and from the same content process.
if (aSameEpoch) {
bool same_doc = false;
prevEntry->SharesDocumentWith(nextEntry, &same_doc);
if (!same_doc) {
MOZ_LOG(
gSHistoryLog, LogLevel::Debug,
("Aborting GotoIndex %d - same epoch and not same doc", aIndex));
// Ignore this load. Before SessionHistoryInParent, this would
// have been dropped in InternalLoad after we filter out SameDoc
// loads.
return NS_ERROR_FAILURE;
}
}
}
}
// Keep note of requested history index in mRequestedIndex; after all bailouts
mRequestedIndex = aIndex;
// Remember that this entry is getting loaded at this point in the sequence
nextEntry->SetLastTouched(++gTouchCounter);
// Get the uri for the entry we are about to visit
nsCOMPtr<nsIURI> nextURI = nextEntry->GetURI();
MOZ_ASSERT(nextURI, "nextURI can't be null");
// Send appropriate listener notifications.
if (aHistCmd == HIST_CMD_GOTOINDEX) {
// We are going somewhere else. This is not reload either
NOTIFY_LISTENERS(OnHistoryGotoIndex, ());
}
if (mRequestedIndex == mIndex) {
// Possibly a reload case
InitiateLoad(nextEntry, mRootBC, aLoadType, aLoadResults, aLoadCurrentEntry,
aUserActivation, requestedOffset);
return NS_OK;
}
// Going back or forward.
bool differenceFound = LoadDifferingEntries(
prevEntry, nextEntry, mRootBC, aLoadType, aLoadResults, aLoadCurrentEntry,
aUserActivation, requestedOffset);
if (!differenceFound) {
// LoadNextPossibleEntry will change the offset by one, and in order
// to keep track of the requestedOffset, need to reset mRequestedIndex to
// the value it had initially.
mRequestedIndex = originalRequestedIndex;
// We did not find any differences. Go further in the history.
return LoadNextPossibleEntry(aIndex, aLoadType, aHistCmd, aLoadResults,
aLoadCurrentEntry, aUserActivation);
}
return NS_OK;
}
bool nsSHistory::LoadDifferingEntries(nsISHEntry* aPrevEntry,
nsISHEntry* aNextEntry,
BrowsingContext* aParent, long aLoadType,
nsTArray<LoadEntryResult>& aLoadResults,
bool aLoadCurrentEntry,
bool aUserActivation, int32_t aOffset) {
MOZ_ASSERT(aPrevEntry && aNextEntry && aParent);
uint32_t prevID = aPrevEntry->GetID();
uint32_t nextID = aNextEntry->GetID();
// Check the IDs to verify if the pages are different.
if (prevID != nextID) {
// Set the Subframe flag if not navigating the root docshell.
aNextEntry->SetIsSubFrame(aParent != mRootBC);
InitiateLoad(aNextEntry, aParent, aLoadType, aLoadResults,
aLoadCurrentEntry, aUserActivation, aOffset);
return true;
}
// The entries are the same, so compare any child frames
int32_t pcnt = aPrevEntry->GetChildCount();
int32_t ncnt = aNextEntry->GetChildCount();
// Create an array for child browsing contexts.
nsTArray<RefPtr<BrowsingContext>> browsingContexts;
aParent->GetChildren(browsingContexts);
// Search for something to load next.
bool differenceFound = false;
for (int32_t i = 0; i < ncnt; ++i) {
// First get an entry which may cause a new page to be loaded.
nsCOMPtr<nsISHEntry> nChild;
aNextEntry->GetChildAt(i, getter_AddRefs(nChild));
if (!nChild) {
continue;
}
nsID docshellID;
nChild->GetDocshellID(docshellID);
// Then find the associated docshell.
RefPtr<BrowsingContext> bcChild;
for (const RefPtr<BrowsingContext>& bc : browsingContexts) {
if (bc->GetHistoryID() == docshellID) {
bcChild = bc;
break;
}
}
if (!bcChild) {
continue;
}
// Then look at the previous entries to see if there was
// an entry for the docshell.
nsCOMPtr<nsISHEntry> pChild;
for (int32_t k = 0; k < pcnt; ++k) {
nsCOMPtr<nsISHEntry> child;
aPrevEntry->GetChildAt(k, getter_AddRefs(child));
if (child) {
nsID dID;
child->GetDocshellID(dID);
if (dID == docshellID) {
pChild = child;
break;
}
}
}
if (!pChild) {
continue;
}
// Finally recursively call this method.
// This will either load a new page to shell or some subshell or
// do nothing.
if (LoadDifferingEntries(pChild, nChild, bcChild, aLoadType, aLoadResults,
aLoadCurrentEntry, aUserActivation, aOffset)) {
differenceFound = true;
}
}
return differenceFound;
}
void nsSHistory::InitiateLoad(nsISHEntry* aFrameEntry,
BrowsingContext* aFrameBC, long aLoadType,
nsTArray<LoadEntryResult>& aLoadResults,
bool aLoadCurrentEntry, bool aUserActivation,
int32_t aOffset) {
MOZ_ASSERT(aFrameBC && aFrameEntry);
LoadEntryResult* loadResult = aLoadResults.AppendElement();
loadResult->mBrowsingContext = aFrameBC;
nsCOMPtr<nsIURI> newURI = aFrameEntry->GetURI();
RefPtr<nsDocShellLoadState> loadState = new nsDocShellLoadState(newURI);
loadState->SetHasValidUserGestureActivation(aUserActivation);
// At the time we initiate a history entry load we already know if https-first
// was able to upgrade the request from http to https. There is no point in
// re-retrying to upgrade.
loadState->SetIsExemptFromHTTPSOnlyMode(true);
/* Set the loadType in the SHEntry too to what was passed on.
* This will be passed on to child subframes later in nsDocShell,
* so that proper loadType is maintained through out a frameset
*/
aFrameEntry->SetLoadType(aLoadType);
loadState->SetLoadType(aLoadType);
loadState->SetSHEntry(aFrameEntry);
// If we're loading the current entry we want to treat it as not a
// same-document navigation (see nsDocShell::IsSameDocumentNavigation), so
// record that here in the LoadingSessionHistoryEntry.
bool loadingCurrentEntry;
if (mozilla::SessionHistoryInParent()) {
loadingCurrentEntry = aLoadCurrentEntry;
} else {
loadingCurrentEntry =
aFrameBC->GetDocShell() &&
nsDocShell::Cast(aFrameBC->GetDocShell())->IsOSHE(aFrameEntry);
}
loadState->SetLoadIsFromSessionHistory(aOffset, loadingCurrentEntry);
if (mozilla::SessionHistoryInParent()) {
nsCOMPtr<SessionHistoryEntry> she = do_QueryInterface(aFrameEntry);
aFrameBC->Canonical()->AddLoadingSessionHistoryEntry(
loadState->GetLoadingSessionHistoryInfo()->mLoadId, she);
}
nsCOMPtr<nsIURI> originalURI = aFrameEntry->GetOriginalURI();
loadState->SetOriginalURI(originalURI);
loadState->SetLoadReplace(aFrameEntry->GetLoadReplace());
loadState->SetLoadFlags(nsIWebNavigation::LOAD_FLAGS_NONE);
nsCOMPtr<nsIPrincipal> triggeringPrincipal =
aFrameEntry->GetTriggeringPrincipal();
loadState->SetTriggeringPrincipal(triggeringPrincipal);
loadState->SetFirstParty(false);
nsCOMPtr<nsIContentSecurityPolicy> csp = aFrameEntry->GetCsp();
loadState->SetCsp(csp);
loadResult->mLoadState = std::move(loadState);
}
NS_IMETHODIMP
nsSHistory::CreateEntry(nsISHEntry** aEntry) {
nsCOMPtr<nsISHEntry> entry;
if (XRE_IsParentProcess() && mozilla::SessionHistoryInParent()) {
entry = new SessionHistoryEntry();
} else {
entry = new nsSHEntry();
}
entry.forget(aEntry);
return NS_OK;
}
NS_IMETHODIMP_(bool)
nsSHistory::IsEmptyOrHasEntriesForSingleTopLevelPage() {
if (mEntries.IsEmpty()) {
return true;
}
nsISHEntry* entry = mEntries[0];
size_t length = mEntries.Length();
for (size_t i = 1; i < length; ++i) {
bool sharesDocument = false;
mEntries[i]->SharesDocumentWith(entry, &sharesDocument);
if (!sharesDocument) {
return false;
}
}
return true;
}
static void CollectEntries(
nsTHashMap<nsIDHashKey, SessionHistoryEntry*>& aHashtable,
SessionHistoryEntry* aEntry) {
aHashtable.InsertOrUpdate(aEntry->DocshellID(), aEntry);
for (const RefPtr<SessionHistoryEntry>& entry : aEntry->Children()) {
if (entry) {
CollectEntries(aHashtable, entry);
}
}
}
static void UpdateEntryLength(
nsTHashMap<nsIDHashKey, SessionHistoryEntry*>& aHashtable,
SessionHistoryEntry* aNewEntry, bool aMove) {
SessionHistoryEntry* oldEntry = aHashtable.Get(aNewEntry->DocshellID());
if (oldEntry) {
MOZ_ASSERT(oldEntry->GetID() != aNewEntry->GetID() || !aMove ||
!aNewEntry->BCHistoryLength().Modified());
aNewEntry->SetBCHistoryLength(oldEntry->BCHistoryLength());
if (oldEntry->GetID() != aNewEntry->GetID()) {
MOZ_ASSERT(!aMove);
// If we have a new id then aNewEntry was created for a new load, so
// record that in BCHistoryLength.
++aNewEntry->BCHistoryLength();
} else if (aMove) {
// We're moving the BCHistoryLength from the old entry to the new entry,
// so we need to let the old entry know that it's not contributing to its
// BCHistoryLength, and the new one that it does if the old one was
// before.
aNewEntry->BCHistoryLength().SetModified(
oldEntry->BCHistoryLength().Modified());
oldEntry->BCHistoryLength().SetModified(false);
}
}
for (const RefPtr<SessionHistoryEntry>& entry : aNewEntry->Children()) {
if (entry) {
UpdateEntryLength(aHashtable, entry, aMove);
}
}
}
void nsSHistory::UpdateEntryLength(nsISHEntry* aOldEntry, nsISHEntry* aNewEntry,
bool aMove) {
nsCOMPtr<SessionHistoryEntry> oldSHE = do_QueryInterface(aOldEntry);
nsCOMPtr<SessionHistoryEntry> newSHE = do_QueryInterface(aNewEntry);
if (!oldSHE || !newSHE) {
return;
}
nsTHashMap<nsIDHashKey, SessionHistoryEntry*> docshellIDToEntry;
CollectEntries(docshellIDToEntry, oldSHE);
::UpdateEntryLength(docshellIDToEntry, newSHE, aMove);
}