2835 lines
95 KiB
C++
2835 lines
95 KiB
C++
/* 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/. */
|
|
|
|
#include "nsSHistory.h"
|
|
|
|
#include <algorithm>
|
|
#include <numbers>
|
|
|
|
#include "nsContentUtils.h"
|
|
#include "nsCOMArray.h"
|
|
#include "nsComponentManagerUtils.h"
|
|
#include "nsDocShell.h"
|
|
#include "nsFrameLoaderOwner.h"
|
|
#include "nsHashKeys.h"
|
|
#include "nsIDocShell.h"
|
|
#include "nsIDocumentViewer.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 "nsPIDOMWindowInlines.h"
|
|
#include "nsNetUtil.h"
|
|
#include "nsTHashMap.h"
|
|
#include "SessionHistoryEntry.h"
|
|
#include "nsTArray.h"
|
|
#include "prsystem.h"
|
|
|
|
#include "mozilla/Attributes.h"
|
|
#include "mozilla/dom/BrowsingContextGroup.h"
|
|
#include "mozilla/dom/BrowserParent.h"
|
|
#include "mozilla/dom/CanonicalBrowsingContext.h"
|
|
#include "mozilla/dom/ContentParent.h"
|
|
#include "mozilla/dom/Element.h"
|
|
#include "mozilla/dom/Navigation.h"
|
|
#include "mozilla/dom/RemoteWebProgressRequest.h"
|
|
#include "mozilla/dom/WindowGlobalParent.h"
|
|
#include "mozilla/glean/DocshellMetrics.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 constexpr 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.
|
|
// 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;
|
|
};
|
|
|
|
constinit 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;
|
|
|
|
extern mozilla::LazyLogModule gSHLog;
|
|
|
|
LazyLogModule gSHistoryLog("nsSHistory");
|
|
|
|
#define LOG(format) MOZ_LOG(gSHistoryLog, mozilla::LogLevel::Debug, format)
|
|
|
|
extern mozilla::LazyLogModule gPageCacheLog;
|
|
extern mozilla::LazyLogModule gNavigationAPILog;
|
|
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
|
|
|
|
// Calls a F on all registered session history listeners.
|
|
template <typename F>
|
|
static void 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;
|
|
}
|
|
}
|
|
|
|
MOZ_CAN_RUN_SCRIPT
|
|
~SHistoryChangeNotifier() {
|
|
if (mSHistory) {
|
|
MOZ_ASSERT(mSHistory->HasOngoingUpdate());
|
|
mSHistory->SetHasOngoingUpdate(false);
|
|
|
|
RefPtr<BrowsingContext> rootBC = mSHistory->GetBrowsingContext();
|
|
if (rootBC) {
|
|
RefPtr canonical = rootBC->Canonical();
|
|
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() = default;
|
|
|
|
static void PrefChanged(const char* aPref, void* aSelf);
|
|
void PrefChanged(const char* aPref);
|
|
|
|
protected:
|
|
~nsSHistoryObserver() = default;
|
|
};
|
|
|
|
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::GloballyEvictDocumentViewers();
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsSHistoryObserver::Observe(nsISupports* aSubject, const char* aTopic,
|
|
const char16_t* aData) {
|
|
if (!strcmp(aTopic, "cacheservice:empty-cache") ||
|
|
!strcmp(aTopic, "memory-pressure")) {
|
|
nsSHistory::GloballyEvictAllDocumentViewers();
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
void nsSHistory::EvictDocumentViewerForEntry(SessionHistoryEntry* aEntry) {
|
|
if (RefPtr<nsFrameLoader> frameLoader = aEntry->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);
|
|
aEntry->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->Id()),
|
|
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.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();
|
|
}
|
|
|
|
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 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::numbers::ln2 - 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::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<SessionHistoryEntry> nsSHistory::GetRootSHEntry(
|
|
SessionHistoryEntry* aEntry) {
|
|
RefPtr<SessionHistoryEntry> rootEntry = aEntry;
|
|
RefPtr<SessionHistoryEntry> result = nullptr;
|
|
while (rootEntry) {
|
|
result = rootEntry;
|
|
rootEntry = result->GetParent();
|
|
}
|
|
|
|
return result.forget();
|
|
}
|
|
|
|
// static
|
|
nsresult nsSHistory::WalkHistoryEntries(SessionHistoryEntry* 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++) {
|
|
RefPtr<SessionHistoryEntry> 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 we are in the parent process, update canonical BC directly.
|
|
if (XRE_IsParentProcess()) {
|
|
if (child->Canonical()->HasHistoryEntry(childEntry)) {
|
|
childBC = child;
|
|
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, SessionHistoryEntry* aReplaceEntry,
|
|
bool aCloneChildren, SessionHistoryEntry* aDestTreeParent)
|
|
: cloneID(aCloneID),
|
|
cloneChildren(aCloneChildren),
|
|
replaceEntry(aReplaceEntry),
|
|
destTreeParent(aDestTreeParent) {}
|
|
|
|
uint32_t cloneID;
|
|
bool cloneChildren;
|
|
SessionHistoryEntry* replaceEntry;
|
|
SessionHistoryEntry* destTreeParent;
|
|
RefPtr<SessionHistoryEntry> resultEntry;
|
|
};
|
|
|
|
nsresult nsSHistory::CloneAndReplaceChild(SessionHistoryEntry* aEntry,
|
|
BrowsingContext* aOwnerBC,
|
|
int32_t aChildIndex, void* aData) {
|
|
CloneAndReplaceData* data = static_cast<CloneAndReplaceData*>(aData);
|
|
uint32_t cloneID = data->cloneID;
|
|
SessionHistoryEntry* 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;
|
|
nsCOMPtr<nsISHEntry> dest;
|
|
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);
|
|
|
|
RefPtr<SessionHistoryEntry> shEntry(dest->GetAsSessionHistoryEntry());
|
|
if (srcID != cloneID || data->cloneChildren) {
|
|
// Walk the children
|
|
CloneAndReplaceData childData(cloneID, replaceEntry, data->cloneChildren,
|
|
shEntry);
|
|
rv = nsSHistory::WalkHistoryEntries(aEntry, aOwnerBC, CloneAndReplaceChild,
|
|
&childData);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
}
|
|
|
|
if (srcID != cloneID && aOwnerBC) {
|
|
nsSHistory::HandleEntriesToSwapInDocShell(aOwnerBC, aEntry, shEntry);
|
|
}
|
|
|
|
if (data->destTreeParent) {
|
|
data->destTreeParent->AddChild(shEntry, aChildIndex);
|
|
}
|
|
|
|
data->resultEntry = shEntry;
|
|
return rv;
|
|
}
|
|
|
|
// static
|
|
nsresult nsSHistory::CloneAndReplace(SessionHistoryEntry* aSrcEntry,
|
|
BrowsingContext* aOwnerBC,
|
|
uint32_t aCloneID,
|
|
SessionHistoryEntry* aReplaceEntry,
|
|
bool aCloneChildren,
|
|
SessionHistoryEntry** 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(
|
|
SessionHistoryEntry* aEntry,
|
|
const std::function<void(SessionHistoryEntry*)>& 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->GetAsSessionHistoryEntry());
|
|
}
|
|
}
|
|
|
|
// 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->GetAsSessionHistoryEntry());
|
|
}
|
|
}
|
|
}
|
|
|
|
// static
|
|
void nsSHistory::WalkContiguousEntriesInOrder(
|
|
SessionHistoryEntry* aEntry,
|
|
const std::function<bool(SessionHistoryEntry*)>& aCallback) {
|
|
MOZ_ASSERT(aEntry);
|
|
|
|
RefPtr<SessionHistoryEntry> entry = aEntry;
|
|
RefPtr<nsSHistory> shistory = entry->GetSessionHistory();
|
|
if (!shistory) {
|
|
// If there is no session history related to the entry there's not much to
|
|
// do, so just return.
|
|
return;
|
|
}
|
|
|
|
nsCOMPtr<nsIURI> targetURI = entry->GetURIOrInheritedForAboutBlank();
|
|
AutoTArray<SessionHistoryEntry*, 16> previousEntries;
|
|
|
|
// Walk backward to find the entries that have the same origin as the
|
|
// input entry.
|
|
RefPtr<SessionHistoryEntry> current = entry;
|
|
while (RefPtr previousEntry =
|
|
shistory->FindLeftmostAdjacentContiguousEntryFor(
|
|
current, SearchDirection::Left)) {
|
|
nsCOMPtr<nsIURI> uri = previousEntry->GetURIOrInheritedForAboutBlank();
|
|
if (NS_FAILED(nsContentUtils::GetSecurityManager()->CheckSameOriginURI(
|
|
targetURI, uri, false, false))) {
|
|
break;
|
|
}
|
|
previousEntries.AppendElement(previousEntry);
|
|
current = previousEntry;
|
|
}
|
|
|
|
for (auto* previousEntry : Reversed(previousEntries)) {
|
|
if (!aCallback(previousEntry)) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (!aCallback(entry)) {
|
|
return;
|
|
}
|
|
|
|
// Walk forward to find the entries that have the same origin as the
|
|
// input entry.
|
|
while ((entry = shistory->FindLeftmostAdjacentContiguousEntryFor(
|
|
entry, SearchDirection::Right))) {
|
|
nsCOMPtr<nsIURI> uri = entry->GetURIOrInheritedForAboutBlank();
|
|
if (NS_FAILED(nsContentUtils::GetSecurityManager()->CheckSameOriginURI(
|
|
targetURI, uri, false, false))) {
|
|
break;
|
|
}
|
|
if (!aCallback(entry)) {
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
// static
|
|
void nsSHistory::WalkClosestContiguousEntriesFrom(
|
|
SessionHistoryEntry* aEntry,
|
|
const std::function<bool(SessionHistoryEntry*)>& aCallback) {
|
|
MOZ_ASSERT(aEntry);
|
|
|
|
RefPtr<SessionHistoryEntry> entry = aEntry;
|
|
RefPtr<nsSHistory> shistory = entry->GetSessionHistory();
|
|
if (!shistory) {
|
|
// If there is no session history related to the entry there's not much to
|
|
// do, so just return.
|
|
return;
|
|
}
|
|
|
|
MOZ_ASSERT(entry);
|
|
if (!aCallback(entry)) {
|
|
return;
|
|
}
|
|
|
|
nsCOMPtr<nsIURI> targetURI = entry->GetURIOrInheritedForAboutBlank();
|
|
|
|
// Walk backward to find the entries that have the same origin as the
|
|
// input entry.
|
|
for (nsCOMPtr<SessionHistoryEntry> current =
|
|
shistory->FindClosestAdjacentContiguousEntryFor(
|
|
entry, SearchDirection::Left);
|
|
current; current = shistory->FindClosestAdjacentContiguousEntryFor(
|
|
current, SearchDirection::Left)) {
|
|
nsCOMPtr<nsIURI> uri = current->GetURIOrInheritedForAboutBlank();
|
|
if (NS_FAILED(nsContentUtils::GetSecurityManager()->CheckSameOriginURI(
|
|
targetURI, uri, false, false))) {
|
|
break;
|
|
}
|
|
if (!aCallback(current)) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Walk forward to find the entries that have the same origin as the
|
|
// input entry.
|
|
for (nsCOMPtr<SessionHistoryEntry> current =
|
|
shistory->FindClosestAdjacentContiguousEntryFor(
|
|
entry, SearchDirection::Right);
|
|
current; current = shistory->FindClosestAdjacentContiguousEntryFor(
|
|
current, SearchDirection::Right)) {
|
|
nsCOMPtr<nsIURI> uri = current->GetURIOrInheritedForAboutBlank();
|
|
if (NS_FAILED(nsContentUtils::GetSecurityManager()->CheckSameOriginURI(
|
|
targetURI, uri, false, false))) {
|
|
break;
|
|
}
|
|
if (!aCallback(current)) {
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsSHistory::AddNestedSHEntry(nsISHEntry* aOldEntry, 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> 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 = aOldEntry->GetID();
|
|
RefPtr<SessionHistoryEntry> child;
|
|
rv = nsSHistory::CloneAndReplace(currentHE->GetAsSessionHistoryEntry(),
|
|
aRootBC, cloneID,
|
|
aNewEntry->GetAsSessionHistoryEntry(),
|
|
aCloneChildren, getter_AddRefs(child));
|
|
|
|
if (NS_SUCCEEDED(rv)) {
|
|
if (aOldEntry->IsTransient()) {
|
|
rv = ReplaceEntry(mIndex, child);
|
|
} else {
|
|
rv = AddEntry(child);
|
|
}
|
|
|
|
if (NS_SUCCEEDED(rv)) {
|
|
child->SetDocshellID(aRootBC->GetHistoryID());
|
|
}
|
|
}
|
|
|
|
return rv;
|
|
}
|
|
|
|
nsresult nsSHistory::SetChildHistoryEntry(SessionHistoryEntry* aEntry,
|
|
BrowsingContext* aBC,
|
|
int32_t aEntryIndex, void* aData) {
|
|
SwapEntriesData* data = static_cast<SwapEntriesData*>(aData);
|
|
if (!aBC || aBC == data->ignoreBC) {
|
|
return NS_OK;
|
|
}
|
|
|
|
SessionHistoryEntry* destTreeRoot = data->destTreeRoot;
|
|
|
|
RefPtr<SessionHistoryEntry> 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.
|
|
RefPtr<SessionHistoryEntry> 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, SessionHistoryEntry* aOldEntry,
|
|
SessionHistoryEntry* aNewEntry) {
|
|
// 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 (XRE_IsParentProcess()) {
|
|
aBC->Canonical()->SwapHistoryEntries(aOldEntry, aNewEntry);
|
|
}
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsSHistory::AddToRootSessionHistory(bool aCloneChildren, nsISHEntry* aOSHE,
|
|
BrowsingContext* aRootBC,
|
|
nsISHEntry* aEntry, uint32_t aLoadType,
|
|
Maybe<int32_t>* aPreviousEntryIndex,
|
|
Maybe<int32_t>* aLoadedEntryIndex) {
|
|
MOZ_ASSERT(aRootBC->IsTop());
|
|
MOZ_ASSERT(aEntry);
|
|
|
|
nsresult rv = NS_OK;
|
|
auto* entry = aEntry->GetAsSessionHistoryEntry();
|
|
|
|
// If we need to clone our children onto the new session
|
|
// history entry, do so now.
|
|
if (aCloneChildren && aOSHE) {
|
|
uint32_t cloneID = aOSHE->GetID();
|
|
RefPtr<SessionHistoryEntry> newEntry;
|
|
nsSHistory::CloneAndReplace(aOSHE->GetAsSessionHistoryEntry(), aRootBC,
|
|
cloneID, entry, 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 = GetTargetIndexForHistoryOperation();
|
|
|
|
// Replace the current entry with the new entry
|
|
if (index >= 0) {
|
|
rv = ReplaceEntry(index, entry);
|
|
} 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(entry);
|
|
*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) {
|
|
NS_ENSURE_ARG(aSHEntry);
|
|
auto* shEntry = aSHEntry->GetAsSessionHistoryEntry();
|
|
|
|
nsCOMPtr<nsISHistory> shistoryOfEntry = shEntry->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;
|
|
}
|
|
|
|
shEntry->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) {
|
|
shEntry->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]->IsTransient()) {
|
|
NotifyListeners(mListeners, [](auto l) { l->OnHistoryReplaceEntry(); });
|
|
mEntries[mIndex] = shEntry;
|
|
return NS_OK;
|
|
}
|
|
}
|
|
SHistoryChangeNotifier change(this);
|
|
|
|
int32_t truncating = Length() - 1 - mIndex;
|
|
if (truncating > 0) {
|
|
NotifyListeners(mListeners,
|
|
[truncating](auto l) { l->OnHistoryTruncate(truncating); });
|
|
}
|
|
|
|
nsCOMPtr<nsIURI> uri = shEntry->GetURI();
|
|
NotifyListeners(mListeners,
|
|
[&uri, this](auto l) { l->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);
|
|
mEntries.TruncateLength(mIndex + 1);
|
|
mEntries.AppendElement(shEntry);
|
|
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);
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
void nsSHistory::NotifyOnHistoryReplaceEntry() {
|
|
NotifyListeners(mListeners, [](auto l) { l->OnHistoryReplaceEntry(); });
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsSHistory::NotifyOnEntryUpdated(nsISHEntry* aEntry) {
|
|
NotifyListeners(mListeners, [entry = nsCOMPtr{aEntry}](auto l) {
|
|
l->OnEntryUpdated(entry);
|
|
});
|
|
return NS_OK;
|
|
}
|
|
|
|
/* 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(SessionHistoryEntry* 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 =
|
|
aEntry->GetAsSessionHistoryEntry()->SharedInfo();
|
|
|
|
nsID docShellId;
|
|
aEntry->GetDocshellID(docShellId);
|
|
|
|
int32_t childCount = aEntry->GetChildCount();
|
|
|
|
MOZ_LOG(gSHLog, LogLevel::Debug,
|
|
("%s%s+- %i SH Entry %p shared:%" PRIu64 " %s %i\n",
|
|
aIsCurrent ? ">" : " ", aPrefix.get(), aIndex, aEntry,
|
|
shared->GetId(), nsIDToCString(docShellId).get(), aEntry->GetID()));
|
|
|
|
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_ConvertUTF16toUTF8(title).get()));
|
|
MOZ_LOG(gSHLog, LogLevel::Debug,
|
|
(" %s%s Name = %s\n", prefix.get(), childCount > 0 ? "|" : " ",
|
|
NS_ConvertUTF16toUTF8(name).get()));
|
|
MOZ_LOG(gSHLog, LogLevel::Debug,
|
|
(" %s%s Transient = %s\n", prefix.get(), childCount > 0 ? "|" : " ",
|
|
aEntry->IsTransient() ? "true" : "false"));
|
|
MOZ_LOG(
|
|
gSHLog, LogLevel::Debug,
|
|
(" %s%s Is in BFCache = %s\n", prefix.get(), childCount > 0 ? "|" : " ",
|
|
aEntry->GetIsInBFCache() ? "true" : "false"));
|
|
MOZ_LOG(gSHLog, LogLevel::Debug,
|
|
(" %s%s Has User Interaction = %s\n", prefix.get(),
|
|
childCount > 0 ? "|" : " ",
|
|
aEntry->GetHasUserInteraction() ? "true" : "false"));
|
|
MOZ_LOG(
|
|
gSHLog, LogLevel::Debug,
|
|
(" %s%s Navigation key = %s\n", prefix.get(), childCount > 0 ? "|" : " ",
|
|
aEntry->Info().NavigationKey().ToString().get()));
|
|
|
|
RefPtr<SessionHistoryEntry> prevChild;
|
|
for (int32_t i = 0; i < childCount; ++i) {
|
|
RefPtr<SessionHistoryEntry> 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());
|
|
|
|
NotifyListeners(mListeners,
|
|
[aNumEntries](auto l) { l->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()) {
|
|
MarkAsInitialEntry(mEntries[aNumEntries], 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) {
|
|
ClearEntries(mEntries[i]);
|
|
}
|
|
|
|
RefPtr<BrowsingContext> rootBC = GetBrowsingContext();
|
|
if (rootBC) {
|
|
rootBC->PreOrderWalk([&docshellIDToEntry](BrowsingContext* aBC) {
|
|
SessionHistoryEntry* entry = docshellIDToEntry.Get(aBC->GetHistoryID());
|
|
(void)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 (rootBC && rootBC->GetDocShell()) {
|
|
rootBC->GetDocShell()->HistoryPurged(aNumEntries);
|
|
}
|
|
|
|
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::NotifyListenersDocumentViewerEvicted(uint32_t aNumEvicted) {
|
|
NotifyListeners(mListeners, [aNumEvicted](auto l) {
|
|
l->OnDocumentViewerEvicted(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);
|
|
|
|
NotifyListeners(mListeners, [](auto l) { l->OnHistoryReplaceEntry(); });
|
|
|
|
mEntries[aIndex] = aReplaceEntry->GetAsSessionHistoryEntry();
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
// 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;
|
|
|
|
if (NS_SUCCEEDED(listener->OnHistoryReload(&retval)) && !retval) {
|
|
*aCanReload = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsSHistory::NotifyOnHistoryCommit() {
|
|
NotifyListeners(mListeners, [](auto l) { l->OnHistoryCommit(); });
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsSHistory::EvictOutOfRangeDocumentViewers(int32_t aIndex) {
|
|
MOZ_LOG(gSHIPBFCacheLog, LogLevel::Debug,
|
|
("nsSHistory::EvictOutOfRangeDocumentViewers %i", aIndex));
|
|
|
|
// 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;
|
|
}
|
|
|
|
NS_IMETHODIMP_(void)
|
|
nsSHistory::EvictDocumentViewersOrReplaceEntry(nsISHEntry* aNewSHEntry,
|
|
bool aReplace) {
|
|
if (!aReplace) {
|
|
int32_t curIndex;
|
|
GetIndex(&curIndex);
|
|
if (curIndex > -1) {
|
|
EvictOutOfRangeDocumentViewers(curIndex);
|
|
}
|
|
} else {
|
|
MOZ_ASSERT(aNewSHEntry);
|
|
RefPtr<SessionHistoryEntry> rootSHEntry =
|
|
nsSHistory::GetRootSHEntry(aNewSHEntry->GetAsSessionHistoryEntry());
|
|
|
|
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]);
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
MOZ_CAN_RUN_SCRIPT
|
|
static void FinishRestore(CanonicalBrowsingContext* aBrowsingContext,
|
|
nsDocShellLoadState* aLoadState,
|
|
SessionHistoryEntry* aEntry,
|
|
nsFrameLoader* aFrameLoader, bool aCanSave) {
|
|
MOZ_ASSERT(aEntry);
|
|
MOZ_ASSERT(aFrameLoader);
|
|
|
|
aEntry->SetFrameLoader(nullptr);
|
|
|
|
RefPtr<nsSHistory> shistory = aEntry->GetShistory().downcast<nsSHistory>();
|
|
|
|
int32_t indexOfHistoryLoad =
|
|
shistory ? shistory->GetIndexOfEntry(aEntry) : -1;
|
|
|
|
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);
|
|
aBrowsingContext->DeactivateDocuments();
|
|
}
|
|
}
|
|
|
|
// 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->SetActiveSessionHistoryEntryFromBFCache(aEntry);
|
|
loadingBC->SetActiveSessionHistoryEntryFromBFCache(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();
|
|
}
|
|
|
|
loadingBC->ReactivateDocuments(aEntry, currentSHEntry);
|
|
|
|
// We need to call this after we've restored the page from BFCache (see
|
|
// ReactivateDocuments above), so that the page is not frozen anymore and
|
|
// the right focus events are fired.
|
|
frameLoaderOwner->UpdateFocusAndMouseEnterStateAfterFrameLoaderChange();
|
|
|
|
glean::bfcache::page_restored
|
|
.EnumGet(glean::bfcache::PageRestoredLabel::eTrue)
|
|
.Add();
|
|
|
|
return;
|
|
}
|
|
|
|
aFrameLoader->Destroy();
|
|
|
|
// Fall back to do a normal load.
|
|
aBrowsingContext->LoadURI(aLoadState, false);
|
|
|
|
glean::bfcache::page_restored
|
|
.EnumGet(glean::bfcache::PageRestoredLabel::eFalse)
|
|
.Add();
|
|
}
|
|
|
|
MOZ_CAN_RUN_SCRIPT
|
|
static bool MaybeLoadBFCache(const nsSHistory::LoadEntryResult& aLoadEntry) {
|
|
MOZ_ASSERT(XRE_IsParentProcess());
|
|
RefPtr<nsDocShellLoadState> loadState = aLoadEntry.mLoadState;
|
|
RefPtr<CanonicalBrowsingContext> canonicalBC =
|
|
aLoadEntry.mBrowsingContext->Canonical();
|
|
RefPtr<SessionHistoryEntry> she = loadState->SHEntry();
|
|
RefPtr<SessionHistoryEntry> currentShe =
|
|
canonicalBC->GetActiveSessionHistoryEntry();
|
|
MOZ_ASSERT(she);
|
|
RefPtr<nsFrameLoader> frameLoader = she->GetFrameLoader();
|
|
if (frameLoader && canonicalBC->Group()->Toplevels().Length() == 1 &&
|
|
(!currentShe || (she->SharedInfo() != currentShe->SharedInfo() &&
|
|
!currentShe->GetFrameLoader()))) {
|
|
bool canSave = (!currentShe || currentShe->GetSaveLayoutStateFlag()) &&
|
|
canonicalBC->AllowedInBFCache(Nothing(), nullptr);
|
|
|
|
MOZ_LOG(gSHIPBFCacheLog, LogLevel::Debug,
|
|
("nsSHistory::LoadURIOrBFCache "
|
|
"saving presentation=%i",
|
|
canSave));
|
|
|
|
nsCOMPtr<nsFrameLoaderOwner> frameLoaderOwner =
|
|
do_QueryInterface(canonicalBC->GetEmbedderElement());
|
|
if (!loadState->NotifiedBeforeUnloadListeners() && frameLoaderOwner) {
|
|
RefPtr<nsFrameLoader> currentFrameLoader =
|
|
frameLoaderOwner->GetFrameLoader();
|
|
if (currentFrameLoader &&
|
|
currentFrameLoader->GetMaybePendingBrowsingContext()) {
|
|
if (WindowGlobalParent* wgp =
|
|
currentFrameLoader->GetMaybePendingBrowsingContext()
|
|
->Canonical()
|
|
->GetCurrentWindowGlobal()) {
|
|
wgp->PermitUnload(
|
|
[canonicalBC, loadState, she, frameLoader, currentFrameLoader,
|
|
canSave](nsIDocumentViewer::PermitUnloadResult aResult)
|
|
MOZ_CAN_RUN_SCRIPT_BOUNDARY_LAMBDA {
|
|
bool allow = aResult == nsIDocumentViewer::eContinue;
|
|
if (allow && !canonicalBC->IsReplaced()) {
|
|
FinishRestore(canonicalBC, loadState, she, frameLoader,
|
|
canSave && canonicalBC->AllowedInBFCache(
|
|
Nothing(), nullptr));
|
|
} else if (currentFrameLoader
|
|
->GetMaybePendingBrowsingContext()) {
|
|
nsISHistory* shistory =
|
|
currentFrameLoader->GetMaybePendingBrowsingContext()
|
|
->Canonical()
|
|
->GetSessionHistory();
|
|
if (shistory) {
|
|
shistory->InternalSetRequestedIndex(-1);
|
|
}
|
|
}
|
|
});
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!canonicalBC->IsReplaced()) {
|
|
FinishRestore(canonicalBC, loadState, she, frameLoader, canSave);
|
|
}
|
|
return true;
|
|
}
|
|
if (frameLoader) {
|
|
she->SetFrameLoader(nullptr);
|
|
frameLoader->Destroy();
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/* static */
|
|
void nsSHistory::LoadURIOrBFCache(const LoadEntryResult& aLoadEntry) {
|
|
if (mozilla::BFCacheInParent() && aLoadEntry.mBrowsingContext->IsTop()) {
|
|
if (MaybeLoadBFCache(aLoadEntry)) {
|
|
return;
|
|
}
|
|
|
|
glean::bfcache::page_restored
|
|
.EnumGet(glean::bfcache::PageRestoredLabel::eFalse)
|
|
.Add();
|
|
}
|
|
|
|
RefPtr<BrowsingContext> bc = aLoadEntry.mBrowsingContext;
|
|
RefPtr<nsDocShellLoadState> loadState = aLoadEntry.mLoadState;
|
|
bc->LoadURI(loadState, false);
|
|
}
|
|
|
|
// This implements step 4 of
|
|
// https://html.spec.whatwg.org/#checking-if-unloading-is-canceled which handles
|
|
// the case where we have a "navigate" handler for the top level window's
|
|
// navigation object and/or a "beforeunload" handler for that same window. The
|
|
// tricky part is that we need to check "beforeunload" for that window, then
|
|
// "navigate", and after that continue with "beforeunload" for the remaining
|
|
// tree.
|
|
bool nsSHistory::MaybeCheckUnloadingIsCanceled(
|
|
const nsTArray<nsSHistory::LoadEntryResult>& aLoadResults,
|
|
BrowsingContext* aTraversable,
|
|
std::function<void(nsTArray<nsSHistory::LoadEntryResult>&,
|
|
nsIDocumentViewer::PermitUnloadResult)>&& aResolver) {
|
|
// Step 4
|
|
if (!aTraversable || !aTraversable->IsTop() || !aLoadResults.Length() ||
|
|
!Navigation::IsAPIEnabled()) {
|
|
return false;
|
|
}
|
|
|
|
RefPtr<CanonicalBrowsingContext> traversable = aTraversable->Canonical();
|
|
|
|
RefPtr<WindowGlobalParent> windowGlobalParent =
|
|
traversable->GetCurrentWindowGlobal();
|
|
// An efficiency trick. We've set this flag on the window context if we've
|
|
// seen a "navigate" and/or a "beforeunload" handler set. If not we know we
|
|
// can skip this.
|
|
if (!windowGlobalParent || (!windowGlobalParent->NeedsBeforeUnload() &&
|
|
!windowGlobalParent->GetNeedsTraverse())) {
|
|
return false;
|
|
}
|
|
|
|
// Step 4.2
|
|
auto found =
|
|
std::find_if(aLoadResults.begin(), aLoadResults.end(),
|
|
[traversable](const auto& result) {
|
|
return result.mBrowsingContext->Id() == traversable->Id();
|
|
});
|
|
|
|
// Step 4.3, since current entry is always different to not finding one.
|
|
if (found == aLoadResults.end()) {
|
|
return false;
|
|
}
|
|
|
|
// Step 4.2
|
|
RefPtr<SessionHistoryEntry> targetEntry = found->mLoadState->SHEntry();
|
|
|
|
RefPtr<SessionHistoryEntry> currentEntry =
|
|
traversable->GetActiveSessionHistoryEntry();
|
|
|
|
// Step 4.3, but the actual checks in the spec.
|
|
if (!currentEntry || !targetEntry ||
|
|
currentEntry->GetID() == targetEntry->GetID()) {
|
|
return false;
|
|
}
|
|
|
|
nsCOMPtr<nsIURI> targetURI = targetEntry->GetURI();
|
|
if (!targetURI) {
|
|
return false;
|
|
}
|
|
|
|
nsCOMPtr<nsIPrincipal> targetPrincipal =
|
|
BasePrincipal::CreateContentPrincipal(targetURI,
|
|
traversable->OriginAttributesRef());
|
|
|
|
// More of step 4.3
|
|
if (!windowGlobalParent->DocumentPrincipal()->Equals(targetPrincipal)) {
|
|
return false;
|
|
}
|
|
|
|
// Step 4.3.2
|
|
// If we squint we can see spec here, insofar that for a traversable's
|
|
// beforeunload handler to fire, the target entry needs to be:
|
|
// * non-null, i.e. part of navigables being traversed
|
|
// * different from the current entry
|
|
// * cross document from the current entry
|
|
// * have beforeunload handlers
|
|
bool needsBeforeUnload =
|
|
windowGlobalParent->NeedsBeforeUnload() &&
|
|
currentEntry->SharedInfo() != targetEntry->SharedInfo();
|
|
|
|
// Step 4.3.3 isn't needed since that's what PermitUnloadChildNavigables
|
|
// achieves by skipping top level navigable.
|
|
|
|
// Step 4.3.4
|
|
// CheckIfUnloadingIsCanceledForTraversable only includes the process of the
|
|
// top level browsing context.
|
|
|
|
// If we're not going to run any beforeunload handlers, we still need to run
|
|
// navigate event handlers for the traversable.
|
|
nsIDocumentViewer::PermitUnloadAction action =
|
|
needsBeforeUnload
|
|
? nsIDocumentViewer::PermitUnloadAction::ePrompt
|
|
: nsIDocumentViewer::PermitUnloadAction::eDontPromptAndUnload;
|
|
|
|
RefPtr<nsDocShellLoadState> maybeInterceptedLoadState = found->mLoadState;
|
|
|
|
windowGlobalParent->CheckIfUnloadingIsCanceledForTraversable(
|
|
maybeInterceptedLoadState, action,
|
|
[action, loadResults = CopyableTArray(aLoadResults), windowGlobalParent,
|
|
aResolver = std::move(aResolver), maybeInterceptedLoadState,
|
|
traversableId = traversable->Id(),
|
|
contentParent = RefPtr{traversable->GetContentParent()}](
|
|
nsIDocumentViewer::PermitUnloadResult aResult) mutable {
|
|
if (aResult != nsIDocumentViewer::PermitUnloadResult::eContinue) {
|
|
loadResults.RemoveElementsBy(
|
|
[id = traversableId](const auto& result) {
|
|
return result.mBrowsingContext->Id() == id;
|
|
});
|
|
|
|
aResolver(loadResults, aResult);
|
|
return;
|
|
}
|
|
|
|
// If we didn't intercept the navigation, the load state wasn't used so
|
|
// we can take it out of pending.
|
|
if (contentParent) {
|
|
RefPtr clearedPendingState = contentParent->TakePendingLoadStateForId(
|
|
maybeInterceptedLoadState->GetLoadIdentifier());
|
|
MOZ_DIAGNOSTIC_ASSERT(!clearedPendingState ||
|
|
clearedPendingState ==
|
|
maybeInterceptedLoadState);
|
|
}
|
|
|
|
// If the traversable didn't have beforeunloadhandlers, we won't run
|
|
// other navigable's unload handlers either. That will be handled by
|
|
// regular navigation.
|
|
if (action ==
|
|
nsIDocumentViewer::PermitUnloadAction::eDontPromptAndUnload) {
|
|
aResolver(loadResults,
|
|
nsIDocumentViewer::PermitUnloadResult::eContinue);
|
|
return;
|
|
}
|
|
|
|
// CheckIfUnloadingIsCanceledForTraversable includes everything except
|
|
// the process of the top level browsing context.
|
|
windowGlobalParent->PermitUnloadChildNavigables(
|
|
action, [loadResults = std::move(loadResults),
|
|
aResolver = std::move(aResolver)](
|
|
nsIDocumentViewer::PermitUnloadResult aResult) mutable {
|
|
aResolver(loadResults, aResult);
|
|
});
|
|
});
|
|
|
|
return true;
|
|
}
|
|
|
|
// https://html.spec.whatwg.org/#apply-the-history-step
|
|
// nsSHistory::LoadURIs is also implementing #apply-the-history-step, maybe even
|
|
// more so than `CanonicalBrowsingContext::HistoryGo`.
|
|
/* static */
|
|
void nsSHistory::LoadURIs(const nsTArray<LoadEntryResult>& aLoadResults,
|
|
bool aCheckForCancelation,
|
|
const std::function<void(nsresult)>& aResolver,
|
|
BrowsingContext* aTraversable) {
|
|
// Step 5. We need to handle the case where we shouldn't check for
|
|
// cancelation, e.g when our caller is
|
|
// #resume-applying-the-traverse-history-step, and we're already ran
|
|
// #checking-if-unloading-is-canceled
|
|
if (aCheckForCancelation &&
|
|
MaybeCheckUnloadingIsCanceled(
|
|
aLoadResults, aTraversable,
|
|
[traversable = RefPtr{aTraversable}, aResolver](
|
|
nsTArray<LoadEntryResult>& aLoadResults,
|
|
nsIDocumentViewer::PermitUnloadResult aResult)
|
|
MOZ_CAN_RUN_SCRIPT_BOUNDARY_LAMBDA {
|
|
if (aResult != nsIDocumentViewer::eContinue) {
|
|
if (nsCOMPtr<nsFrameLoaderOwner> frameLoaderOwner =
|
|
do_QueryInterface(
|
|
traversable->GetEmbedderElement())) {
|
|
if (RefPtr<nsFrameLoader> currentFrameLoader =
|
|
frameLoaderOwner->GetFrameLoader()) {
|
|
nsISHistory* shistory =
|
|
currentFrameLoader->GetMaybePendingBrowsingContext()
|
|
->Canonical()
|
|
->GetSessionHistory();
|
|
if (shistory) {
|
|
shistory->InternalSetRequestedIndex(-1);
|
|
}
|
|
}
|
|
}
|
|
|
|
// https://html.spec.whatwg.org/#performing-a-navigation-api-traversal
|
|
// 12.5 If result is "canceled-by-beforeunload", then queue a
|
|
// global task on the navigation and traversal task
|
|
// source given navigation's relevant global object to
|
|
// reject the finished promise for apiMethodTracker with
|
|
// a new "AbortError" DOMException created in
|
|
// navigation's relevant realm.
|
|
if (aResult == nsIDocumentViewer::eCanceledByBeforeUnload) {
|
|
return aResolver(nsresult::NS_ERROR_DOM_ABORT_ERR);
|
|
}
|
|
|
|
aResolver(NS_OK);
|
|
|
|
return;
|
|
}
|
|
|
|
for (LoadEntryResult& loadEntry : aLoadResults) {
|
|
loadEntry.mLoadState->SetNotifiedBeforeUnloadListeners(true);
|
|
LoadURIOrBFCache(loadEntry);
|
|
}
|
|
})) {
|
|
return;
|
|
}
|
|
|
|
// There's no beforeunload handlers, resolve immediately.
|
|
aResolver(NS_OK);
|
|
|
|
// And we fall back to the simple case if we shouldn't fire a "traverse"
|
|
// navigate event.
|
|
for (const 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, /* aCheckForCancelation */ true);
|
|
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;
|
|
MOZ_ALWAYS_SUCCEEDS(NotifyOnHistoryReload(&canNavigate));
|
|
if (!canNavigate) {
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult rv =
|
|
LoadEntry(/* aSourceBrowsingContext */ nullptr, 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, /* aCheckForCancelation */ true);
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult nsSHistory::ReloadCurrentEntry(
|
|
nsTArray<LoadEntryResult>& aLoadResults) {
|
|
// Notify listeners
|
|
NotifyListeners(mListeners, [](auto l) { l->OnHistoryGotoIndex(); });
|
|
|
|
return LoadEntry(/* aSourceBrowsingContext */ nullptr, mIndex, LOAD_HISTORY,
|
|
HIST_CMD_RELOAD, aLoadResults,
|
|
/* aSameEpoch */ false, /* aLoadCurrentEntry */ true,
|
|
/* aUserActivation */ false);
|
|
}
|
|
|
|
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);
|
|
|
|
LOG(
|
|
("EvictOutOfRangeWindowDocumentViewers(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<nsIDocumentViewer> safeViewers;
|
|
nsTArray<RefPtr<nsFrameLoader>> safeFrameLoaders;
|
|
for (int32_t i = startSafeIndex; i <= endSafeIndex; i++) {
|
|
nsFrameLoader* frameLoader = mEntries[i]->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++) {
|
|
RefPtr<SessionHistoryEntry> entry = mEntries[i];
|
|
nsFrameLoader* frameLoader = entry->GetFrameLoader();
|
|
if (frameLoader) {
|
|
if (!safeFrameLoaders.Contains(frameLoader)) {
|
|
EvictDocumentViewerForEntry(entry);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
namespace {
|
|
|
|
class EntryAndDistance {
|
|
public:
|
|
EntryAndDistance(nsSHistory* aSHistory, SessionHistoryEntry* aEntry,
|
|
uint32_t aDist)
|
|
: mSHistory(aSHistory),
|
|
mEntry(aEntry),
|
|
mFrameLoader(aEntry->GetFrameLoader()),
|
|
mLastTouched(mEntry->GetLastTouched()),
|
|
mDistance(aDist) {
|
|
NS_ASSERTION(mFrameLoader, "Entry should have a 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;
|
|
RefPtr<SessionHistoryEntry> mEntry;
|
|
RefPtr<nsFrameLoader> mFrameLoader;
|
|
uint32_t mLastTouched;
|
|
uint32_t mDistance;
|
|
};
|
|
|
|
} // namespace
|
|
|
|
// 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++) {
|
|
RefPtr<SessionHistoryEntry> entry = shist->mEntries[i];
|
|
|
|
bool found = false;
|
|
bool hasFrameLoader = false;
|
|
if (RefPtr<nsFrameLoader> frameLoader = entry->GetFrameLoader()) {
|
|
hasFrameLoader = 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 (EntryAndDistance& container : shEntries) {
|
|
if (container.mFrameLoader == frameLoader) {
|
|
container.mDistance =
|
|
std::min(container.mDistance, Abs(i - shist->mIndex));
|
|
found = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// If we didn't find a EntryAndDistance for this content viewer /
|
|
// frameloader, make a new one.
|
|
if (hasFrameLoader && !found) {
|
|
EntryAndDistance container(shist, entry, Abs(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);
|
|
}
|
|
}
|
|
|
|
nsresult nsSHistory::FindEntryForBFCache(SHEntrySharedParentState* aEntry,
|
|
SessionHistoryEntry** aResult,
|
|
int32_t* aResultIndex) {
|
|
*aResult = nullptr;
|
|
*aResultIndex = -1;
|
|
|
|
int32_t startIndex, endIndex;
|
|
WindowIndices(mIndex, &startIndex, &endIndex);
|
|
|
|
for (int32_t i = startIndex; i <= endIndex; ++i) {
|
|
RefPtr<SessionHistoryEntry> 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::EvictExpiredDocumentViewerForEntry(
|
|
SHEntrySharedParentState* aEntry) {
|
|
int32_t index;
|
|
RefPtr<SessionHistoryEntry> shEntry;
|
|
FindEntryForBFCache(aEntry, getter_AddRefs(shEntry), &index);
|
|
|
|
if (index == mIndex) {
|
|
NS_WARNING("How did the current SHEntry expire?");
|
|
}
|
|
|
|
if (shEntry) {
|
|
EvictDocumentViewerForEntry(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::GloballyEvictAllDocumentViewers() {
|
|
int32_t maxViewers = sHistoryMaxTotalViewers;
|
|
sHistoryMaxTotalViewers = 0;
|
|
GloballyEvictDocumentViewers();
|
|
sHistoryMaxTotalViewers = maxViewers;
|
|
}
|
|
|
|
void GetDynamicChildren(SessionHistoryEntry* aEntry,
|
|
nsTArray<nsID>& aDocshellIDs) {
|
|
int32_t count = aEntry->GetChildCount();
|
|
for (int32_t i = 0; i < count; ++i) {
|
|
RefPtr<SessionHistoryEntry> child;
|
|
aEntry->GetChildAt(i, getter_AddRefs(child));
|
|
if (child) {
|
|
if (child->IsDynamicallyAdded()) {
|
|
child->GetDocshellID(*aDocshellIDs.AppendElement());
|
|
} else {
|
|
GetDynamicChildren(child, aDocshellIDs);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
bool RemoveFromSessionHistoryEntry(SessionHistoryEntry* aRoot,
|
|
nsTArray<nsID>& aDocshellIDs) {
|
|
bool didRemove = false;
|
|
int32_t childCount = aRoot->GetChildCount();
|
|
for (int32_t i = childCount - 1; i >= 0; --i) {
|
|
RefPtr<SessionHistoryEntry> 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->GetAsSessionHistoryEntry(),
|
|
aEntryIDs)
|
|
: false;
|
|
}
|
|
|
|
bool IsSameTree(SessionHistoryEntry* aEntry1, SessionHistoryEntry* 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) {
|
|
RefPtr<SessionHistoryEntry> 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;
|
|
|
|
RefPtr<SessionHistoryEntry> root1 = mEntries.SafeElementAt(aIndex, nullptr);
|
|
RefPtr<SessionHistoryEntry> root2 =
|
|
mEntries.SafeElementAt(compareIndex, nullptr);
|
|
if (!root1 || !root2) {
|
|
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);
|
|
}
|
|
ClearEntries(root1);
|
|
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) {
|
|
RefPtr<BrowsingContext> rootBC = GetBrowsingContext();
|
|
if (rootBC && rootBC->GetDocShell()) {
|
|
rootBC->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;
|
|
}
|
|
}
|
|
|
|
void nsSHistory::RemoveFrameEntries(nsISHEntry* aEntry) {
|
|
auto* entry = aEntry->GetAsSessionHistoryEntry();
|
|
int32_t count = entry->GetChildCount();
|
|
AutoTArray<nsID, 16> ids;
|
|
for (int32_t i = 0; i < count; ++i) {
|
|
RefPtr<SessionHistoryEntry> child;
|
|
entry->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.
|
|
RefPtr entry = static_cast<SessionHistoryEntry*>(
|
|
aEntry ? aEntry : mEntries.SafeElementAt(aIndex));
|
|
|
|
if (entry) {
|
|
AutoTArray<nsID, 16> toBeRemovedEntries;
|
|
GetDynamicChildren(entry, toBeRemovedEntries);
|
|
if (toBeRemovedEntries.Length()) {
|
|
RemoveEntries(toBeRemovedEntries, aIndex);
|
|
}
|
|
}
|
|
}
|
|
|
|
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(/* aSourceBrowsingContext */ nullptr, aIndex, loadResults,
|
|
/*aSameEpoch*/ false, aIndex == mIndex, aUserActivation);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
LoadURIs(loadResults, /* aCheckForCancelation */ true);
|
|
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->GetAsSessionHistoryEntry());
|
|
}
|
|
}
|
|
|
|
nsresult nsSHistory::GotoIndex(BrowsingContext* aSourceBrowsingContext,
|
|
int32_t aIndex,
|
|
nsTArray<LoadEntryResult>& aLoadResults,
|
|
bool aSameEpoch, bool aLoadCurrentEntry,
|
|
bool aUserActivation) {
|
|
return LoadEntry(aSourceBrowsingContext, aIndex, LOAD_HISTORY,
|
|
HIST_CMD_GOTOINDEX, aLoadResults, aSameEpoch,
|
|
aLoadCurrentEntry, aUserActivation);
|
|
}
|
|
|
|
NS_IMETHODIMP_(bool)
|
|
nsSHistory::HasUserInteractionAtIndex(int32_t aIndex) {
|
|
RefPtr<SessionHistoryEntry> entry = mEntries.SafeElementAt(aIndex);
|
|
if (!entry) {
|
|
return false;
|
|
}
|
|
return entry->GetHasUserInteraction();
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsSHistory::CanGoBackFromEntryAtIndex(int32_t aIndex, bool* aCanGoBack) {
|
|
*aCanGoBack = false;
|
|
if (!StaticPrefs::browser_navigation_requireUserInteraction()) {
|
|
*aCanGoBack = aIndex > 0;
|
|
return NS_OK;
|
|
}
|
|
|
|
for (int32_t i = aIndex - 1; i >= 0; i--) {
|
|
if (HasUserInteractionAtIndex(i)) {
|
|
*aCanGoBack = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult nsSHistory::LoadNextPossibleEntry(
|
|
BrowsingContext* aSourceBrowsingContext, int32_t aNewIndex, long aLoadType,
|
|
uint32_t aHistCmd, nsTArray<LoadEntryResult>& aLoadResults,
|
|
bool aLoadCurrentEntry, bool aUserActivation) {
|
|
mRequestedIndex = -1;
|
|
if (aNewIndex < mIndex) {
|
|
return LoadEntry(aSourceBrowsingContext, aNewIndex - 1, aLoadType, aHistCmd,
|
|
aLoadResults,
|
|
/*aSameEpoch*/ false, aLoadCurrentEntry, aUserActivation);
|
|
}
|
|
if (aNewIndex > mIndex) {
|
|
return LoadEntry(aSourceBrowsingContext, aNewIndex + 1, aLoadType, aHistCmd,
|
|
aLoadResults,
|
|
/*aSameEpoch*/ false, aLoadCurrentEntry, aUserActivation);
|
|
}
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
|
|
nsresult nsSHistory::LoadEntry(BrowsingContext* aSourceBrowsingContext,
|
|
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));
|
|
RefPtr<BrowsingContext> rootBC = GetBrowsingContext();
|
|
if (!rootBC) {
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
|
|
if (aIndex < 0 || aIndex >= Length()) {
|
|
MOZ_LOG(gSHistoryLog, LogLevel::Debug, ("Index out of range"));
|
|
// The index is out of range.
|
|
// Clear the requested index in case it had bogus value. This way the next
|
|
// load succeeds if the offset is reasonable.
|
|
mRequestedIndex = -1;
|
|
|
|
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.
|
|
|
|
RefPtr<SessionHistoryEntry> prevEntry = mEntries[mIndex];
|
|
RefPtr<SessionHistoryEntry> nextEntry = mEntries[aIndex];
|
|
if (!nextEntry || !prevEntry) {
|
|
mRequestedIndex = -1;
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
|
|
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
|
|
NotifyListeners(mListeners, [](auto l) { l->OnHistoryGotoIndex(); });
|
|
}
|
|
|
|
if (mRequestedIndex == mIndex) {
|
|
// Possibly a reload case
|
|
InitiateLoad(aSourceBrowsingContext, nextEntry, rootBC, aLoadType,
|
|
aLoadResults, aLoadCurrentEntry, aUserActivation,
|
|
requestedOffset, prevEntry);
|
|
return NS_OK;
|
|
}
|
|
|
|
// Going back or forward.
|
|
bool differenceFound = ForEachDifferingEntry(
|
|
prevEntry, nextEntry, rootBC,
|
|
[self = RefPtr{this},
|
|
sourceBrowsingContext = RefPtr{aSourceBrowsingContext}, aLoadType,
|
|
&aLoadResults, aLoadCurrentEntry, aUserActivation, requestedOffset,
|
|
prevEntry](SessionHistoryEntry* aEntry, BrowsingContext* aParent) {
|
|
// Set the Subframe flag if not navigating the root docshell.
|
|
aEntry->SetIsSubFrame(aParent->Id() != self->mRootBC);
|
|
self->InitiateLoad(sourceBrowsingContext, aEntry, aParent, aLoadType,
|
|
aLoadResults, aLoadCurrentEntry, aUserActivation,
|
|
requestedOffset, prevEntry);
|
|
});
|
|
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(aSourceBrowsingContext, aIndex, aLoadType,
|
|
aHistCmd, aLoadResults, aLoadCurrentEntry,
|
|
aUserActivation);
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
namespace {
|
|
|
|
// Walk the subtree downwards from aSubtreeRoot, finding the next entry in the
|
|
// list of ancestors, returning only if we are able to find the last ancestor -
|
|
// the parent we are looking for.
|
|
// aAncestors is ordered starting from the root down to the parent entry.
|
|
SessionHistoryEntry* FindParent(Span<SessionHistoryEntry*> aAncestors,
|
|
SessionHistoryEntry* aSubtreeRoot) {
|
|
if (!aSubtreeRoot || aAncestors.IsEmpty() ||
|
|
!aAncestors[0]->Info().SharesDocumentWith(aSubtreeRoot->Info())) {
|
|
return nullptr;
|
|
}
|
|
if (aAncestors.Length() == 1) {
|
|
return aSubtreeRoot;
|
|
}
|
|
for (const auto& child : aSubtreeRoot->Children()) {
|
|
if (auto* foundParent = FindParent(aAncestors.From(1), child)) {
|
|
return foundParent;
|
|
}
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
class SessionHistoryEntryIDComparator {
|
|
public:
|
|
static bool Equals(const RefPtr<SessionHistoryEntry>& aLhs,
|
|
const RefPtr<SessionHistoryEntry>& aRhs) {
|
|
return aLhs && aRhs && aLhs->DocshellID() == aRhs->DocshellID();
|
|
}
|
|
};
|
|
|
|
} // namespace
|
|
|
|
mozilla::dom::SessionHistoryEntry* nsSHistory::FindAdjacentEntryFor(
|
|
mozilla::dom::SessionHistoryEntry* aEntry,
|
|
SearchDirection aSearchDirection) {
|
|
MOZ_ASSERT(static_cast<int8_t>(aSearchDirection) == 1 ||
|
|
static_cast<int8_t>(aSearchDirection) == -1);
|
|
|
|
if (!aEntry) {
|
|
return nullptr;
|
|
}
|
|
|
|
// Construct the list of ancestors of aEntry, starting with the root entry
|
|
// down to it's immediate parent.
|
|
nsCOMPtr<nsISHEntry> ancestor = aEntry->GetParent();
|
|
AutoTArray<SessionHistoryEntry*, 8> ancestors;
|
|
while (ancestor) {
|
|
ancestors.AppendElement(static_cast<SessionHistoryEntry*>(ancestor.get()));
|
|
ancestor = ancestor->GetParent();
|
|
}
|
|
ancestors.Reverse();
|
|
|
|
nsCOMPtr<nsISHEntry> rootEntry = ancestors.IsEmpty() ? aEntry : ancestors[0];
|
|
nsCOMPtr<nsISHEntry> nextEntry;
|
|
SessionHistoryEntry* foundParent = nullptr;
|
|
int32_t i =
|
|
GetIndexOfEntry(rootEntry) + static_cast<int8_t>(aSearchDirection);
|
|
if (i < 0 || i >= Length()) {
|
|
return nullptr;
|
|
}
|
|
|
|
nextEntry = mEntries[i];
|
|
if (ancestors.IsEmpty()) {
|
|
// This can happen if we somehow have duplicates in mEntries. This should
|
|
// ideally never happen, but since it does we need to protect against it.
|
|
// See bug 2042897.
|
|
return nextEntry != aEntry
|
|
? static_cast<SessionHistoryEntry*>(nextEntry.get())
|
|
: nullptr;
|
|
}
|
|
|
|
foundParent =
|
|
FindParent(ancestors, static_cast<SessionHistoryEntry*>(nextEntry.get()));
|
|
if (foundParent) {
|
|
for (const auto& child : foundParent->Children()) {
|
|
// As in the ancestors-empty case above, if mEntries has duplicates
|
|
// (see bug 2042897) the adjacent entry can resolve back to aEntry itself;
|
|
// returning it would make callers loop forever, so skip it.
|
|
if (child && child != aEntry &&
|
|
child->DocshellID() == aEntry->DocshellID()) {
|
|
return child;
|
|
}
|
|
}
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
mozilla::dom::SessionHistoryEntry*
|
|
nsSHistory::FindClosestAdjacentContiguousEntryFor(
|
|
mozilla::dom::SessionHistoryEntry* aEntry,
|
|
SearchDirection aSearchDirection) {
|
|
for (SessionHistoryEntry* current =
|
|
FindAdjacentEntryFor(aEntry, aSearchDirection);
|
|
current; current = FindAdjacentEntryFor(current, aSearchDirection)) {
|
|
if (aEntry->GetID() != current->GetID()) {
|
|
return current;
|
|
}
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
mozilla::dom::SessionHistoryEntry*
|
|
nsSHistory::FindLeftmostAdjacentContiguousEntryFor(
|
|
mozilla::dom::SessionHistoryEntry* aEntry,
|
|
SearchDirection aSearchDirection) {
|
|
SessionHistoryEntry* current = nullptr;
|
|
for (current = FindAdjacentEntryFor(aEntry, aSearchDirection); current;
|
|
current = FindAdjacentEntryFor(current, aSearchDirection)) {
|
|
if (aEntry->GetID() != current->GetID()) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (aSearchDirection == SearchDirection::Right) {
|
|
return current;
|
|
}
|
|
|
|
while (SessionHistoryEntry* left =
|
|
FindAdjacentEntryFor(current, aSearchDirection)) {
|
|
if (left->GetID() != current->GetID()) {
|
|
break;
|
|
}
|
|
|
|
current = left;
|
|
}
|
|
|
|
return current;
|
|
}
|
|
|
|
bool nsSHistory::ForEachDifferingEntry(
|
|
SessionHistoryEntry* aPrevEntry, SessionHistoryEntry* aNextEntry,
|
|
BrowsingContext* aParent,
|
|
const std::function<void(SessionHistoryEntry*, BrowsingContext*)>&
|
|
aCallback) {
|
|
MOZ_ASSERT(aPrevEntry && aNextEntry && aParent);
|
|
|
|
uint32_t prevID = aPrevEntry->GetID();
|
|
uint32_t nextID = aNextEntry->GetID();
|
|
|
|
bool differenceFound = false;
|
|
// Check the IDs to verify if the pages are different.
|
|
if (prevID != nextID) {
|
|
aCallback(aNextEntry, aParent);
|
|
// if it's not same doc, any potential children will just be unloaded
|
|
// https://html.spec.whatwg.org/#get-all-navigables-whose-current-session-history-entry-will-change-or-reload
|
|
// step 3.3:
|
|
// If targetEntry's document is navigable's document, and targetEntry's
|
|
// document state's reload pending is false, then extend navigablesToCheck
|
|
// with the child navigables of navigable.
|
|
bool sameDoc = false;
|
|
aPrevEntry->SharesDocumentWith(aNextEntry, &sameDoc);
|
|
if (!sameDoc) {
|
|
return true;
|
|
}
|
|
differenceFound = 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.
|
|
for (int32_t i = 0; i < ncnt; ++i) {
|
|
// First get an entry which may cause a new page to be loaded.
|
|
RefPtr<SessionHistoryEntry> 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.
|
|
RefPtr<SessionHistoryEntry> pChild;
|
|
for (int32_t k = 0; k < pcnt; ++k) {
|
|
RefPtr<SessionHistoryEntry> child;
|
|
aPrevEntry->GetChildAt(k, getter_AddRefs(child));
|
|
if (child) {
|
|
nsID dID;
|
|
child->GetDocshellID(dID);
|
|
if (dID == docshellID) {
|
|
pChild = child;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if (!pChild) {
|
|
continue;
|
|
}
|
|
|
|
if (ForEachDifferingEntry(pChild, nChild, bcChild, aCallback)) {
|
|
differenceFound = true;
|
|
}
|
|
}
|
|
return differenceFound;
|
|
}
|
|
|
|
void nsSHistory::InitiateLoad(BrowsingContext* aSourceBrowsingContext,
|
|
SessionHistoryEntry* aFrameEntry,
|
|
BrowsingContext* aFrameBC, long aLoadType,
|
|
nsTArray<LoadEntryResult>& aLoadResults,
|
|
bool aLoadCurrentEntry, bool aUserActivation,
|
|
int32_t aOffset,
|
|
nsISHEntry* aPreviousEntryForActivation) {
|
|
MOZ_ASSERT(aFrameBC && aFrameEntry);
|
|
|
|
LoadEntryResult* loadResult = aLoadResults.AppendElement();
|
|
loadResult->mBrowsingContext = aFrameBC;
|
|
|
|
nsCOMPtr<nsIURI> newURI = aFrameEntry->GetURI();
|
|
RefPtr loadState = MakeRefPtr<nsDocShellLoadState>(newURI);
|
|
|
|
loadState->SetSourceBrowsingContext(aSourceBrowsingContext);
|
|
|
|
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->SetIsExemptFromHTTPSFirstMode(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);
|
|
|
|
loadState->SetPreviousEntryForActivation(aPreviousEntryForActivation);
|
|
|
|
// 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.
|
|
loadState->SetLoadIsFromSessionHistory(aOffset, aLoadCurrentEntry);
|
|
|
|
const LoadingSessionHistoryInfo* loadingInfo =
|
|
loadState->GetLoadingSessionHistoryInfo();
|
|
aFrameBC->Canonical()->AddLoadingSessionHistoryEntry(loadingInfo->mLoadId,
|
|
aFrameEntry);
|
|
|
|
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<nsIPolicyContainer> policyContainer =
|
|
aFrameEntry->GetPolicyContainer();
|
|
loadState->SetPolicyContainer(policyContainer);
|
|
|
|
loadResult->mLoadState = std::move(loadState);
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsSHistory::CreateEntry(nsISHEntry** aEntry) {
|
|
nsCOMPtr<nsISHEntry> entry;
|
|
if (XRE_IsParentProcess()) {
|
|
entry = MakeRefPtr<SessionHistoryEntry>();
|
|
}
|
|
entry.forget(aEntry);
|
|
return NS_OK;
|
|
}
|
|
|
|
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(SessionHistoryEntry* aOldEntry,
|
|
SessionHistoryEntry* aNewEntry, bool aMove) {
|
|
if (!aOldEntry || !aNewEntry) {
|
|
return;
|
|
}
|
|
|
|
nsTHashMap<nsIDHashKey, SessionHistoryEntry*> docshellIDToEntry;
|
|
CollectEntries(docshellIDToEntry, aOldEntry);
|
|
|
|
::UpdateEntryLength(docshellIDToEntry, aNewEntry, aMove);
|
|
}
|
|
|
|
bool nsSHistory::ContainsEntry(SessionHistoryEntry* aEntry) {
|
|
if (!aEntry) {
|
|
return false;
|
|
}
|
|
|
|
nsCOMPtr rootEntry = GetRootSHEntry(aEntry);
|
|
return GetIndexOfEntry(rootEntry) != -1;
|
|
}
|