michael@0: /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- michael@0: * michael@0: * This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: // Local Includes michael@0: #include "nsSHistory.h" michael@0: #include michael@0: michael@0: // Helper Classes michael@0: #include "mozilla/Preferences.h" michael@0: michael@0: // Interfaces Needed michael@0: #include "nsILayoutHistoryState.h" michael@0: #include "nsIDocShell.h" michael@0: #include "nsIDocShellLoadInfo.h" michael@0: #include "nsISHContainer.h" michael@0: #include "nsIDocShellTreeItem.h" michael@0: #include "nsIURI.h" michael@0: #include "nsIContentViewer.h" michael@0: #include "nsICacheService.h" michael@0: #include "nsIObserverService.h" michael@0: #include "prclist.h" michael@0: #include "mozilla/Services.h" michael@0: #include "nsTArray.h" michael@0: #include "nsCOMArray.h" michael@0: #include "nsDocShell.h" michael@0: #include "mozilla/Attributes.h" michael@0: #include "nsISHEntry.h" michael@0: #include "nsISHTransaction.h" michael@0: #include "nsISHistoryListener.h" michael@0: #include "nsComponentManagerUtils.h" michael@0: michael@0: // For calculating max history entries and max cachable contentviewers michael@0: #include "prsystem.h" michael@0: #include "mozilla/MathAlgorithms.h" michael@0: michael@0: using namespace mozilla; michael@0: michael@0: #define PREF_SHISTORY_SIZE "browser.sessionhistory.max_entries" michael@0: #define PREF_SHISTORY_MAX_TOTAL_VIEWERS "browser.sessionhistory.max_total_viewers" michael@0: michael@0: static const char* kObservedPrefs[] = { michael@0: PREF_SHISTORY_SIZE, michael@0: PREF_SHISTORY_MAX_TOTAL_VIEWERS, michael@0: nullptr michael@0: }; michael@0: michael@0: static int32_t gHistoryMaxSize = 50; michael@0: // Max viewers allowed per SHistory objects michael@0: static const int32_t gHistoryMaxViewers = 3; michael@0: // List of all SHistory objects, used for content viewer cache eviction michael@0: static PRCList gSHistoryList; michael@0: // Max viewers allowed total, across all SHistory objects - negative default michael@0: // means we will calculate how many viewers to cache based on total memory michael@0: int32_t nsSHistory::sHistoryMaxTotalViewers = -1; michael@0: michael@0: // A counter that is used to be able to know the order in which michael@0: // entries were touched, so that we can evict older entries first. michael@0: static uint32_t gTouchCounter = 0; michael@0: michael@0: #ifdef PR_LOGGING michael@0: michael@0: static PRLogModuleInfo* michael@0: GetSHistoryLog() michael@0: { michael@0: static PRLogModuleInfo *sLog; michael@0: if (!sLog) michael@0: sLog = PR_NewLogModule("nsSHistory"); michael@0: return sLog; michael@0: } michael@0: #define LOG(format) PR_LOG(GetSHistoryLog(), PR_LOG_DEBUG, format) michael@0: michael@0: // This macro makes it easier to print a log message which includes a URI's michael@0: // spec. Example use: michael@0: // michael@0: // nsIURI *uri = [...]; michael@0: // LOG_SPEC(("The URI is %s.", _spec), uri); michael@0: // michael@0: #define LOG_SPEC(format, uri) \ michael@0: PR_BEGIN_MACRO \ michael@0: if (PR_LOG_TEST(GetSHistoryLog(), PR_LOG_DEBUG)) { \ michael@0: nsAutoCString _specStr(NS_LITERAL_CSTRING("(null)"));\ michael@0: if (uri) { \ michael@0: uri->GetSpec(_specStr); \ michael@0: } \ michael@0: const char* _spec = _specStr.get(); \ michael@0: LOG(format); \ michael@0: } \ michael@0: PR_END_MACRO michael@0: michael@0: // This macro makes it easy to log a message including an SHEntry's URI. michael@0: // For example: michael@0: // michael@0: // nsCOMPtr shentry = [...]; michael@0: // LOG_SHENTRY_SPEC(("shentry %p has uri %s.", shentry.get(), _spec), shentry); michael@0: // michael@0: #define LOG_SHENTRY_SPEC(format, shentry) \ michael@0: PR_BEGIN_MACRO \ michael@0: if (PR_LOG_TEST(GetSHistoryLog(), PR_LOG_DEBUG)) { \ michael@0: nsCOMPtr uri; \ michael@0: shentry->GetURI(getter_AddRefs(uri)); \ michael@0: LOG_SPEC(format, uri); \ michael@0: } \ michael@0: PR_END_MACRO michael@0: michael@0: #else // !PR_LOGGING michael@0: michael@0: #define LOG(format) michael@0: #define LOG_SPEC(format, uri) michael@0: #define LOG_SHENTRY_SPEC(format, shentry) michael@0: michael@0: #endif // PR_LOGGING michael@0: michael@0: // Iterates over all registered session history listeners. michael@0: #define ITERATE_LISTENERS(body) \ michael@0: PR_BEGIN_MACRO \ michael@0: { \ michael@0: nsAutoTObserverArray::EndLimitedIterator \ michael@0: iter(mListeners); \ michael@0: while (iter.HasMore()) { \ michael@0: nsCOMPtr listener = \ michael@0: do_QueryReferent(iter.GetNext()); \ michael@0: if (listener) { \ michael@0: body \ michael@0: } \ michael@0: } \ michael@0: } \ michael@0: PR_END_MACRO michael@0: michael@0: // Calls a given method on all registered session history listeners. michael@0: #define NOTIFY_LISTENERS(method, args) \ michael@0: ITERATE_LISTENERS( \ michael@0: listener->method args; \ michael@0: ); michael@0: michael@0: // Calls a given method on all registered session history listeners. michael@0: // Listeners may return 'false' to cancel an action so make sure that we michael@0: // set the return value to 'false' if one of the listeners wants to cancel. michael@0: #define NOTIFY_LISTENERS_CANCELABLE(method, retval, args) \ michael@0: PR_BEGIN_MACRO \ michael@0: { \ michael@0: bool canceled = false; \ michael@0: retval = true; \ michael@0: ITERATE_LISTENERS( \ michael@0: listener->method args; \ michael@0: if (!retval) { \ michael@0: canceled = true; \ michael@0: } \ michael@0: ); \ michael@0: if (canceled) { \ michael@0: retval = false; \ michael@0: } \ michael@0: } \ michael@0: PR_END_MACRO michael@0: michael@0: enum HistCmd{ michael@0: HIST_CMD_BACK, michael@0: HIST_CMD_FORWARD, michael@0: HIST_CMD_GOTOINDEX, michael@0: HIST_CMD_RELOAD michael@0: } ; michael@0: michael@0: //***************************************************************************** michael@0: //*** nsSHistoryObserver michael@0: //***************************************************************************** michael@0: michael@0: class nsSHistoryObserver MOZ_FINAL : public nsIObserver michael@0: { michael@0: michael@0: public: michael@0: NS_DECL_ISUPPORTS michael@0: NS_DECL_NSIOBSERVER michael@0: michael@0: nsSHistoryObserver() {} michael@0: michael@0: protected: michael@0: ~nsSHistoryObserver() {} michael@0: }; michael@0: michael@0: static nsSHistoryObserver* gObserver = nullptr; michael@0: michael@0: NS_IMPL_ISUPPORTS(nsSHistoryObserver, nsIObserver) michael@0: michael@0: NS_IMETHODIMP michael@0: nsSHistoryObserver::Observe(nsISupports *aSubject, const char *aTopic, michael@0: const char16_t *aData) michael@0: { michael@0: if (!strcmp(aTopic, NS_PREFBRANCH_PREFCHANGE_TOPIC_ID)) { michael@0: nsSHistory::UpdatePrefs(); michael@0: nsSHistory::GloballyEvictContentViewers(); michael@0: } else if (!strcmp(aTopic, NS_CACHESERVICE_EMPTYCACHE_TOPIC_ID) || michael@0: !strcmp(aTopic, "memory-pressure")) { michael@0: nsSHistory::GloballyEvictAllContentViewers(); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: namespace { michael@0: michael@0: already_AddRefed michael@0: GetContentViewerForTransaction(nsISHTransaction *aTrans) michael@0: { michael@0: nsCOMPtr entry; michael@0: aTrans->GetSHEntry(getter_AddRefs(entry)); michael@0: if (!entry) { michael@0: return nullptr; michael@0: } michael@0: michael@0: nsCOMPtr ownerEntry; michael@0: nsCOMPtr viewer; michael@0: entry->GetAnyContentViewer(getter_AddRefs(ownerEntry), michael@0: getter_AddRefs(viewer)); michael@0: return viewer.forget(); michael@0: } michael@0: michael@0: void michael@0: EvictContentViewerForTransaction(nsISHTransaction *aTrans) michael@0: { michael@0: nsCOMPtr entry; michael@0: aTrans->GetSHEntry(getter_AddRefs(entry)); michael@0: nsCOMPtr viewer; michael@0: nsCOMPtr ownerEntry; michael@0: entry->GetAnyContentViewer(getter_AddRefs(ownerEntry), michael@0: getter_AddRefs(viewer)); michael@0: if (viewer) { michael@0: NS_ASSERTION(ownerEntry, michael@0: "Content viewer exists but its SHEntry is null"); michael@0: michael@0: LOG_SHENTRY_SPEC(("Evicting content viewer 0x%p for " michael@0: "owning SHEntry 0x%p at %s.", michael@0: viewer.get(), ownerEntry.get(), _spec), ownerEntry); michael@0: michael@0: // Drop the presentation state before destroying the viewer, so that michael@0: // document teardown is able to correctly persist the state. michael@0: ownerEntry->SetContentViewer(nullptr); michael@0: ownerEntry->SyncPresentationState(); michael@0: viewer->Destroy(); michael@0: } michael@0: } michael@0: michael@0: } // anonymous namespace michael@0: michael@0: //***************************************************************************** michael@0: //*** nsSHistory: Object Management michael@0: //***************************************************************************** michael@0: michael@0: nsSHistory::nsSHistory() : mListRoot(nullptr), mIndex(-1), mLength(0), mRequestedIndex(-1) michael@0: { michael@0: // Add this new SHistory object to the list michael@0: PR_APPEND_LINK(this, &gSHistoryList); michael@0: } michael@0: michael@0: michael@0: nsSHistory::~nsSHistory() michael@0: { michael@0: // Remove this SHistory object from the list michael@0: PR_REMOVE_LINK(this); michael@0: } michael@0: michael@0: //***************************************************************************** michael@0: // nsSHistory: nsISupports michael@0: //***************************************************************************** michael@0: michael@0: NS_IMPL_ADDREF(nsSHistory) michael@0: NS_IMPL_RELEASE(nsSHistory) michael@0: michael@0: NS_INTERFACE_MAP_BEGIN(nsSHistory) michael@0: NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsISHistory) michael@0: NS_INTERFACE_MAP_ENTRY(nsISHistory) michael@0: NS_INTERFACE_MAP_ENTRY(nsIWebNavigation) michael@0: NS_INTERFACE_MAP_ENTRY(nsISHistoryInternal) michael@0: NS_INTERFACE_MAP_END michael@0: michael@0: //***************************************************************************** michael@0: // nsSHistory: nsISHistory michael@0: //***************************************************************************** michael@0: michael@0: // static michael@0: uint32_t michael@0: nsSHistory::CalcMaxTotalViewers() michael@0: { michael@0: // Calculate an estimate of how many ContentViewers we should cache based michael@0: // on RAM. This assumes that the average ContentViewer is 4MB (conservative) michael@0: // and caps the max at 8 ContentViewers michael@0: // michael@0: // TODO: Should we split the cache memory betw. ContentViewer caching and michael@0: // nsCacheService? michael@0: // michael@0: // RAM ContentViewers michael@0: // ----------------------- michael@0: // 32 Mb 0 michael@0: // 64 Mb 1 michael@0: // 128 Mb 2 michael@0: // 256 Mb 3 michael@0: // 512 Mb 5 michael@0: // 1024 Mb 8 michael@0: // 2048 Mb 8 michael@0: // 4096 Mb 8 michael@0: uint64_t bytes = PR_GetPhysicalMemorySize(); michael@0: michael@0: if (bytes == 0) michael@0: return 0; michael@0: michael@0: // Conversion from unsigned int64_t to double doesn't work on all platforms. michael@0: // We need to truncate the value at INT64_MAX to make sure we don't michael@0: // overflow. michael@0: if (bytes > INT64_MAX) michael@0: bytes = INT64_MAX; michael@0: michael@0: double kBytesD = (double)(bytes >> 10); michael@0: michael@0: // This is essentially the same calculation as for nsCacheService, michael@0: // except that we divide the final memory calculation by 4, since michael@0: // we assume each ContentViewer takes on average 4MB michael@0: uint32_t viewers = 0; michael@0: double x = std::log(kBytesD)/std::log(2.0) - 14; michael@0: if (x > 0) { michael@0: viewers = (uint32_t)(x * x - x + 2.001); // add .001 for rounding michael@0: viewers /= 4; michael@0: } michael@0: michael@0: // Cap it off at 8 max michael@0: if (viewers > 8) { michael@0: viewers = 8; michael@0: } michael@0: return viewers; michael@0: } michael@0: michael@0: // static michael@0: void michael@0: nsSHistory::UpdatePrefs() michael@0: { michael@0: Preferences::GetInt(PREF_SHISTORY_SIZE, &gHistoryMaxSize); michael@0: Preferences::GetInt(PREF_SHISTORY_MAX_TOTAL_VIEWERS, michael@0: &sHistoryMaxTotalViewers); michael@0: // If the pref is negative, that means we calculate how many viewers michael@0: // we think we should cache, based on total memory michael@0: if (sHistoryMaxTotalViewers < 0) { michael@0: sHistoryMaxTotalViewers = CalcMaxTotalViewers(); michael@0: } michael@0: } michael@0: michael@0: // static michael@0: nsresult michael@0: nsSHistory::Startup() michael@0: { michael@0: UpdatePrefs(); michael@0: michael@0: // The goal of this is to unbreak users who have inadvertently set their michael@0: // session history size to less than the default value. michael@0: int32_t defaultHistoryMaxSize = michael@0: Preferences::GetDefaultInt(PREF_SHISTORY_SIZE, 50); michael@0: if (gHistoryMaxSize < defaultHistoryMaxSize) { michael@0: gHistoryMaxSize = defaultHistoryMaxSize; michael@0: } michael@0: michael@0: // Allow the user to override the max total number of cached viewers, michael@0: // but keep the per SHistory cached viewer limit constant michael@0: if (!gObserver) { michael@0: gObserver = new nsSHistoryObserver(); michael@0: NS_ADDREF(gObserver); michael@0: Preferences::AddStrongObservers(gObserver, kObservedPrefs); michael@0: michael@0: nsCOMPtr obsSvc = michael@0: mozilla::services::GetObserverService(); michael@0: if (obsSvc) { michael@0: // Observe empty-cache notifications so tahat clearing the disk/memory michael@0: // cache will also evict all content viewers. michael@0: obsSvc->AddObserver(gObserver, michael@0: NS_CACHESERVICE_EMPTYCACHE_TOPIC_ID, false); michael@0: michael@0: // Same for memory-pressure notifications michael@0: obsSvc->AddObserver(gObserver, "memory-pressure", false); michael@0: } michael@0: } michael@0: michael@0: // Initialize the global list of all SHistory objects michael@0: PR_INIT_CLIST(&gSHistoryList); michael@0: return NS_OK; michael@0: } michael@0: michael@0: // static michael@0: void michael@0: nsSHistory::Shutdown() michael@0: { michael@0: if (gObserver) { michael@0: Preferences::RemoveObservers(gObserver, kObservedPrefs); michael@0: nsCOMPtr obsSvc = michael@0: mozilla::services::GetObserverService(); michael@0: if (obsSvc) { michael@0: obsSvc->RemoveObserver(gObserver, NS_CACHESERVICE_EMPTYCACHE_TOPIC_ID); michael@0: obsSvc->RemoveObserver(gObserver, "memory-pressure"); michael@0: } michael@0: NS_RELEASE(gObserver); michael@0: } michael@0: } michael@0: michael@0: /* Add an entry to the History list at mIndex and michael@0: * increment the index to point to the new entry michael@0: */ michael@0: NS_IMETHODIMP michael@0: nsSHistory::AddEntry(nsISHEntry * aSHEntry, bool aPersist) michael@0: { michael@0: NS_ENSURE_ARG(aSHEntry); michael@0: michael@0: nsCOMPtr currentTxn; michael@0: michael@0: if(mListRoot) michael@0: GetTransactionAtIndex(mIndex, getter_AddRefs(currentTxn)); michael@0: michael@0: bool currentPersist = true; michael@0: if(currentTxn) michael@0: currentTxn->GetPersist(¤tPersist); michael@0: michael@0: int32_t currentIndex = mIndex; michael@0: michael@0: if(!currentPersist) michael@0: { michael@0: NOTIFY_LISTENERS(OnHistoryReplaceEntry, (currentIndex)); michael@0: NS_ENSURE_SUCCESS(currentTxn->SetSHEntry(aSHEntry),NS_ERROR_FAILURE); michael@0: currentTxn->SetPersist(aPersist); michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsCOMPtr txn(do_CreateInstance(NS_SHTRANSACTION_CONTRACTID)); michael@0: NS_ENSURE_TRUE(txn, NS_ERROR_FAILURE); michael@0: michael@0: nsCOMPtr uri; michael@0: aSHEntry->GetURI(getter_AddRefs(uri)); michael@0: NOTIFY_LISTENERS(OnHistoryNewEntry, (uri)); michael@0: michael@0: // If a listener has changed mIndex, we need to get currentTxn again, michael@0: // otherwise we'll be left at an inconsistent state (see bug 320742) michael@0: if (currentIndex != mIndex) { michael@0: GetTransactionAtIndex(mIndex, getter_AddRefs(currentTxn)); michael@0: } michael@0: michael@0: // Set the ShEntry and parent for the transaction. setting the michael@0: // parent will properly set the parent child relationship michael@0: txn->SetPersist(aPersist); michael@0: NS_ENSURE_SUCCESS(txn->Create(aSHEntry, currentTxn), NS_ERROR_FAILURE); michael@0: michael@0: // A little tricky math here... Basically when adding an object regardless of michael@0: // what the length was before, it should always be set back to the current and michael@0: // lop off the forward. michael@0: mLength = (++mIndex + 1); michael@0: michael@0: // If this is the very first transaction, initialize the list michael@0: if(!mListRoot) michael@0: mListRoot = txn; michael@0: michael@0: // Purge History list if it is too long michael@0: if ((gHistoryMaxSize >= 0) && (mLength > gHistoryMaxSize)) michael@0: PurgeHistory(mLength-gHistoryMaxSize); michael@0: michael@0: RemoveDynEntries(mIndex - 1, mIndex); michael@0: return NS_OK; michael@0: } michael@0: michael@0: /* Get size of the history list */ michael@0: NS_IMETHODIMP michael@0: nsSHistory::GetCount(int32_t * aResult) michael@0: { michael@0: NS_ENSURE_ARG_POINTER(aResult); michael@0: *aResult = mLength; michael@0: return NS_OK; michael@0: } michael@0: michael@0: /* Get index of the history list */ michael@0: NS_IMETHODIMP michael@0: nsSHistory::GetIndex(int32_t * aResult) michael@0: { michael@0: NS_PRECONDITION(aResult, "null out param?"); michael@0: *aResult = mIndex; michael@0: return NS_OK; michael@0: } michael@0: michael@0: /* Get the requestedIndex */ michael@0: NS_IMETHODIMP michael@0: nsSHistory::GetRequestedIndex(int32_t * aResult) michael@0: { michael@0: NS_PRECONDITION(aResult, "null out param?"); michael@0: *aResult = mRequestedIndex; michael@0: return NS_OK; michael@0: } michael@0: michael@0: /* Get the entry at a given index */ michael@0: NS_IMETHODIMP michael@0: nsSHistory::GetEntryAtIndex(int32_t aIndex, bool aModifyIndex, nsISHEntry** aResult) michael@0: { michael@0: nsresult rv; michael@0: nsCOMPtr txn; michael@0: michael@0: /* GetTransactionAtIndex ensures aResult is valid and validates aIndex */ michael@0: rv = GetTransactionAtIndex(aIndex, getter_AddRefs(txn)); michael@0: if (NS_SUCCEEDED(rv) && txn) { michael@0: //Get the Entry from the transaction michael@0: rv = txn->GetSHEntry(aResult); michael@0: if (NS_SUCCEEDED(rv) && (*aResult)) { michael@0: // Set mIndex to the requested index, if asked to do so.. michael@0: if (aModifyIndex) { michael@0: mIndex = aIndex; michael@0: } michael@0: } //entry michael@0: } //Transaction michael@0: return rv; michael@0: } michael@0: michael@0: /* Get the transaction at a given index */ michael@0: NS_IMETHODIMP michael@0: nsSHistory::GetTransactionAtIndex(int32_t aIndex, nsISHTransaction ** aResult) michael@0: { michael@0: nsresult rv; michael@0: NS_ENSURE_ARG_POINTER(aResult); michael@0: michael@0: if ((mLength <= 0) || (aIndex < 0) || (aIndex >= mLength)) michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: if (!mListRoot) michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: if (aIndex == 0) michael@0: { michael@0: *aResult = mListRoot; michael@0: NS_ADDREF(*aResult); michael@0: return NS_OK; michael@0: } michael@0: int32_t cnt=0; michael@0: nsCOMPtr tempPtr; michael@0: michael@0: rv = GetRootTransaction(getter_AddRefs(tempPtr)); michael@0: if (NS_FAILED(rv) || !tempPtr) michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: while(1) { michael@0: nsCOMPtr ptr; michael@0: rv = tempPtr->GetNext(getter_AddRefs(ptr)); michael@0: if (NS_SUCCEEDED(rv) && ptr) { michael@0: cnt++; michael@0: if (cnt == aIndex) { michael@0: *aResult = ptr; michael@0: NS_ADDREF(*aResult); michael@0: break; michael@0: } michael@0: else { michael@0: tempPtr = ptr; michael@0: continue; michael@0: } michael@0: } //NS_SUCCEEDED michael@0: else michael@0: return NS_ERROR_FAILURE; michael@0: } // while michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: michael@0: /* Get the index of a given entry */ michael@0: NS_IMETHODIMP michael@0: nsSHistory::GetIndexOfEntry(nsISHEntry* aSHEntry, int32_t* aResult) { michael@0: NS_ENSURE_ARG(aSHEntry); michael@0: NS_ENSURE_ARG_POINTER(aResult); michael@0: *aResult = -1; michael@0: michael@0: if (mLength <= 0) { michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: nsCOMPtr currentTxn; michael@0: int32_t cnt = 0; michael@0: michael@0: nsresult rv = GetRootTransaction(getter_AddRefs(currentTxn)); michael@0: if (NS_FAILED(rv) || !currentTxn) { michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: while (true) { michael@0: nsCOMPtr entry; michael@0: rv = currentTxn->GetSHEntry(getter_AddRefs(entry)); michael@0: if (NS_FAILED(rv) || !entry) { michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: if (aSHEntry == entry) { michael@0: *aResult = cnt; michael@0: break; michael@0: } michael@0: michael@0: rv = currentTxn->GetNext(getter_AddRefs(currentTxn)); michael@0: if (NS_FAILED(rv) || !currentTxn) { michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: cnt++; michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: michael@0: #ifdef DEBUG michael@0: nsresult michael@0: nsSHistory::PrintHistory() michael@0: { michael@0: michael@0: nsCOMPtr txn; michael@0: int32_t index = 0; michael@0: nsresult rv; michael@0: michael@0: if (!mListRoot) michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: txn = mListRoot; michael@0: michael@0: while (1) { michael@0: if (!txn) michael@0: break; michael@0: nsCOMPtr entry; michael@0: rv = txn->GetSHEntry(getter_AddRefs(entry)); michael@0: if (NS_FAILED(rv) && !entry) michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: nsCOMPtr layoutHistoryState; michael@0: nsCOMPtr uri; michael@0: nsXPIDLString title; michael@0: michael@0: entry->GetLayoutHistoryState(getter_AddRefs(layoutHistoryState)); michael@0: entry->GetURI(getter_AddRefs(uri)); michael@0: entry->GetTitle(getter_Copies(title)); michael@0: michael@0: #if 0 michael@0: nsAutoCString url; michael@0: if (uri) michael@0: uri->GetSpec(url); michael@0: michael@0: printf("**** SH Transaction #%d, Entry = %x\n", index, entry.get()); michael@0: printf("\t\t URL = %s\n", url.get()); michael@0: michael@0: printf("\t\t Title = %s\n", NS_LossyConvertUTF16toASCII(title).get()); michael@0: printf("\t\t layout History Data = %x\n", layoutHistoryState.get()); michael@0: #endif michael@0: michael@0: nsCOMPtr next; michael@0: rv = txn->GetNext(getter_AddRefs(next)); michael@0: if (NS_SUCCEEDED(rv) && next) { michael@0: txn = next; michael@0: index++; michael@0: continue; michael@0: } michael@0: else michael@0: break; michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: #endif michael@0: michael@0: michael@0: NS_IMETHODIMP michael@0: nsSHistory::GetRootTransaction(nsISHTransaction ** aResult) michael@0: { michael@0: NS_ENSURE_ARG_POINTER(aResult); michael@0: *aResult=mListRoot; michael@0: NS_IF_ADDREF(*aResult); michael@0: return NS_OK; michael@0: } michael@0: michael@0: /* Get the max size of the history list */ michael@0: NS_IMETHODIMP michael@0: nsSHistory::GetMaxLength(int32_t * aResult) michael@0: { michael@0: NS_ENSURE_ARG_POINTER(aResult); michael@0: *aResult = gHistoryMaxSize; michael@0: return NS_OK; michael@0: } michael@0: michael@0: /* Set the max size of the history list */ michael@0: NS_IMETHODIMP michael@0: nsSHistory::SetMaxLength(int32_t aMaxSize) michael@0: { michael@0: if (aMaxSize < 0) michael@0: return NS_ERROR_ILLEGAL_VALUE; michael@0: michael@0: gHistoryMaxSize = aMaxSize; michael@0: if (mLength > aMaxSize) michael@0: PurgeHistory(mLength-aMaxSize); michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsSHistory::PurgeHistory(int32_t aEntries) michael@0: { michael@0: if (mLength <= 0 || aEntries <= 0) michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: aEntries = std::min(aEntries, mLength); michael@0: michael@0: bool purgeHistory = true; michael@0: NOTIFY_LISTENERS_CANCELABLE(OnHistoryPurge, purgeHistory, michael@0: (aEntries, &purgeHistory)); michael@0: michael@0: if (!purgeHistory) { michael@0: // Listener asked us not to purge michael@0: return NS_SUCCESS_LOSS_OF_INSIGNIFICANT_DATA; michael@0: } michael@0: michael@0: int32_t cnt = 0; michael@0: while (cnt < aEntries) { michael@0: nsCOMPtr nextTxn; michael@0: if (mListRoot) { michael@0: mListRoot->GetNext(getter_AddRefs(nextTxn)); michael@0: mListRoot->SetNext(nullptr); michael@0: } michael@0: mListRoot = nextTxn; michael@0: if (mListRoot) { michael@0: mListRoot->SetPrev(nullptr); michael@0: } michael@0: cnt++; michael@0: } michael@0: mLength -= cnt; michael@0: mIndex -= cnt; michael@0: michael@0: // Now if we were not at the end of the history, mIndex could have michael@0: // become far too negative. If so, just set it to -1. michael@0: if (mIndex < -1) { michael@0: mIndex = -1; michael@0: } michael@0: michael@0: if (mRootDocShell) michael@0: mRootDocShell->HistoryPurged(cnt); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: michael@0: NS_IMETHODIMP michael@0: nsSHistory::AddSHistoryListener(nsISHistoryListener * aListener) michael@0: { michael@0: NS_ENSURE_ARG_POINTER(aListener); michael@0: michael@0: // Check if the listener supports Weak Reference. This is a must. michael@0: // This listener functionality is used by embedders and we want to michael@0: // have the right ownership with who ever listens to SHistory michael@0: nsWeakPtr listener = do_GetWeakReference(aListener); michael@0: if (!listener) return NS_ERROR_FAILURE; michael@0: michael@0: return mListeners.AppendElementUnlessExists(listener) ? michael@0: NS_OK : NS_ERROR_OUT_OF_MEMORY; michael@0: } michael@0: michael@0: michael@0: NS_IMETHODIMP michael@0: nsSHistory::RemoveSHistoryListener(nsISHistoryListener * aListener) michael@0: { michael@0: // Make sure the listener that wants to be removed is the michael@0: // one we have in store. michael@0: nsWeakPtr listener = do_GetWeakReference(aListener); michael@0: mListeners.RemoveElement(listener); michael@0: return NS_OK; michael@0: } michael@0: michael@0: michael@0: /* Replace an entry in the History list at a particular index. michael@0: * Do not update index or count. michael@0: */ michael@0: NS_IMETHODIMP michael@0: nsSHistory::ReplaceEntry(int32_t aIndex, nsISHEntry * aReplaceEntry) michael@0: { michael@0: NS_ENSURE_ARG(aReplaceEntry); michael@0: nsresult rv; michael@0: nsCOMPtr currentTxn; michael@0: michael@0: if (!mListRoot) // Session History is not initialised. michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: rv = GetTransactionAtIndex(aIndex, getter_AddRefs(currentTxn)); michael@0: michael@0: if(currentTxn) michael@0: { michael@0: NOTIFY_LISTENERS(OnHistoryReplaceEntry, (aIndex)); michael@0: michael@0: // Set the replacement entry in the transaction michael@0: rv = currentTxn->SetSHEntry(aReplaceEntry); michael@0: rv = currentTxn->SetPersist(true); michael@0: } michael@0: return rv; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsSHistory::NotifyOnHistoryReload(nsIURI* aReloadURI, uint32_t aReloadFlags, michael@0: bool* aCanReload) michael@0: { michael@0: NOTIFY_LISTENERS_CANCELABLE(OnHistoryReload, *aCanReload, michael@0: (aReloadURI, aReloadFlags, aCanReload)); michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsSHistory::EvictOutOfRangeContentViewers(int32_t aIndex) michael@0: { michael@0: // Check our per SHistory object limit in the currently navigated SHistory michael@0: EvictOutOfRangeWindowContentViewers(aIndex); michael@0: // Check our total limit across all SHistory objects michael@0: GloballyEvictContentViewers(); michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsSHistory::EvictAllContentViewers() michael@0: { michael@0: // XXXbz we don't actually do a good job of evicting things as we should, so michael@0: // we might have viewers quite far from mIndex. So just evict everything. michael@0: nsCOMPtr trans = mListRoot; michael@0: while (trans) { michael@0: EvictContentViewerForTransaction(trans); michael@0: michael@0: nsISHTransaction *temp = trans; michael@0: temp->GetNext(getter_AddRefs(trans)); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: michael@0: michael@0: //***************************************************************************** michael@0: // nsSHistory: nsIWebNavigation michael@0: //***************************************************************************** michael@0: michael@0: NS_IMETHODIMP michael@0: nsSHistory::GetCanGoBack(bool * aCanGoBack) michael@0: { michael@0: NS_ENSURE_ARG_POINTER(aCanGoBack); michael@0: *aCanGoBack = false; michael@0: michael@0: int32_t index = -1; michael@0: NS_ENSURE_SUCCESS(GetIndex(&index), NS_ERROR_FAILURE); michael@0: if(index > 0) michael@0: *aCanGoBack = true; michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsSHistory::GetCanGoForward(bool * aCanGoForward) michael@0: { michael@0: NS_ENSURE_ARG_POINTER(aCanGoForward); michael@0: *aCanGoForward = false; michael@0: michael@0: int32_t index = -1; michael@0: int32_t count = -1; michael@0: michael@0: NS_ENSURE_SUCCESS(GetIndex(&index), NS_ERROR_FAILURE); michael@0: NS_ENSURE_SUCCESS(GetCount(&count), NS_ERROR_FAILURE); michael@0: michael@0: if((index >= 0) && (index < (count - 1))) michael@0: *aCanGoForward = true; michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsSHistory::GoBack() michael@0: { michael@0: bool canGoBack = false; michael@0: michael@0: GetCanGoBack(&canGoBack); michael@0: if (!canGoBack) // Can't go back michael@0: return NS_ERROR_UNEXPECTED; michael@0: return LoadEntry(mIndex-1, nsIDocShellLoadInfo::loadHistory, HIST_CMD_BACK); michael@0: } michael@0: michael@0: michael@0: NS_IMETHODIMP michael@0: nsSHistory::GoForward() michael@0: { michael@0: bool canGoForward = false; michael@0: michael@0: GetCanGoForward(&canGoForward); michael@0: if (!canGoForward) // Can't go forward michael@0: return NS_ERROR_UNEXPECTED; michael@0: return LoadEntry(mIndex+1, nsIDocShellLoadInfo::loadHistory, HIST_CMD_FORWARD); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsSHistory::Reload(uint32_t aReloadFlags) michael@0: { michael@0: nsDocShellInfoLoadType loadType; michael@0: if (aReloadFlags & nsIWebNavigation::LOAD_FLAGS_BYPASS_PROXY && michael@0: aReloadFlags & nsIWebNavigation::LOAD_FLAGS_BYPASS_CACHE) michael@0: { michael@0: loadType = nsIDocShellLoadInfo::loadReloadBypassProxyAndCache; michael@0: } michael@0: else if (aReloadFlags & nsIWebNavigation::LOAD_FLAGS_BYPASS_PROXY) michael@0: { michael@0: loadType = nsIDocShellLoadInfo::loadReloadBypassProxy; michael@0: } michael@0: else if (aReloadFlags & nsIWebNavigation::LOAD_FLAGS_BYPASS_CACHE) michael@0: { michael@0: loadType = nsIDocShellLoadInfo::loadReloadBypassCache; michael@0: } michael@0: else if (aReloadFlags & nsIWebNavigation::LOAD_FLAGS_CHARSET_CHANGE) michael@0: { michael@0: loadType = nsIDocShellLoadInfo::loadReloadCharsetChange; michael@0: } michael@0: else if (aReloadFlags & nsIWebNavigation::LOAD_FLAGS_ALLOW_MIXED_CONTENT) michael@0: { michael@0: loadType = nsIDocShellLoadInfo::loadReloadMixedContent; michael@0: } michael@0: else michael@0: { michael@0: loadType = nsIDocShellLoadInfo::loadReloadNormal; michael@0: } michael@0: michael@0: // We are reloading. Send Reload notifications. michael@0: // nsDocShellLoadFlagType is not public, where as nsIWebNavigation michael@0: // is public. So send the reload notifications with the michael@0: // nsIWebNavigation flags. michael@0: bool canNavigate = true; michael@0: nsCOMPtr currentURI; michael@0: GetCurrentURI(getter_AddRefs(currentURI)); michael@0: NOTIFY_LISTENERS_CANCELABLE(OnHistoryReload, canNavigate, michael@0: (currentURI, aReloadFlags, &canNavigate)); michael@0: if (!canNavigate) michael@0: return NS_OK; michael@0: michael@0: return LoadEntry(mIndex, loadType, HIST_CMD_RELOAD); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsSHistory::ReloadCurrentEntry() michael@0: { michael@0: // Notify listeners michael@0: bool canNavigate = true; michael@0: nsCOMPtr currentURI; michael@0: GetCurrentURI(getter_AddRefs(currentURI)); michael@0: NOTIFY_LISTENERS_CANCELABLE(OnHistoryGotoIndex, canNavigate, michael@0: (mIndex, currentURI, &canNavigate)); michael@0: if (!canNavigate) michael@0: return NS_OK; michael@0: michael@0: return LoadEntry(mIndex, nsIDocShellLoadInfo::loadHistory, HIST_CMD_RELOAD); michael@0: } michael@0: michael@0: void michael@0: nsSHistory::EvictOutOfRangeWindowContentViewers(int32_t aIndex) michael@0: { michael@0: // XXX rename method to EvictContentViewersExceptAroundIndex, or something. michael@0: michael@0: // We need to release all content viewers that are no longer in the range michael@0: // michael@0: // aIndex - gHistoryMaxViewers to aIndex + gHistoryMaxViewers michael@0: // michael@0: // to ensure that this SHistory object isn't responsible for more than michael@0: // gHistoryMaxViewers content viewers. But our job is complicated by the michael@0: // fact that two transactions which are related by either hash navigations or michael@0: // history.pushState will have the same content viewer. michael@0: // michael@0: // To illustrate the issue, suppose gHistoryMaxViewers = 3 and we have four michael@0: // linked transactions in our history. Suppose we then add a new content michael@0: // viewer and call into this function. So the history looks like: michael@0: // michael@0: // A A A A B michael@0: // + * michael@0: // michael@0: // where the letters are content viewers and + and * denote the beginning and michael@0: // end of the range aIndex +/- gHistoryMaxViewers. michael@0: // michael@0: // Although one copy of the content viewer A exists outside the range, we michael@0: // don't want to evict A, because it has other copies in range! michael@0: // michael@0: // We therefore adjust our eviction strategy to read: michael@0: // michael@0: // Evict each content viewer outside the range aIndex -/+ michael@0: // gHistoryMaxViewers, unless that content viewer also appears within the michael@0: // range. michael@0: // michael@0: // (Note that it's entirely legal to have two copies of one content viewer michael@0: // separated by a different content viewer -- call pushState twice, go back michael@0: // once, and refresh -- so we can't rely on identical viewers only appearing michael@0: // adjacent to one another.) michael@0: michael@0: if (aIndex < 0) { michael@0: return; michael@0: } michael@0: NS_ENSURE_TRUE_VOID(aIndex < mLength); michael@0: michael@0: // Calculate the range that's safe from eviction. michael@0: int32_t startSafeIndex = std::max(0, aIndex - gHistoryMaxViewers); michael@0: int32_t endSafeIndex = std::min(mLength, aIndex + gHistoryMaxViewers); michael@0: michael@0: LOG(("EvictOutOfRangeWindowContentViewers(index=%d), " michael@0: "mLength=%d. Safe range [%d, %d]", michael@0: aIndex, mLength, startSafeIndex, endSafeIndex)); michael@0: michael@0: // The content viewers in range aIndex -/+ gHistoryMaxViewers will not be michael@0: // evicted. Collect a set of them so we don't accidentally evict one of them michael@0: // if it appears outside this range. michael@0: nsCOMArray safeViewers; michael@0: nsCOMPtr trans; michael@0: GetTransactionAtIndex(startSafeIndex, getter_AddRefs(trans)); michael@0: for (int32_t i = startSafeIndex; trans && i <= endSafeIndex; i++) { michael@0: nsCOMPtr viewer = GetContentViewerForTransaction(trans); michael@0: safeViewers.AppendObject(viewer); michael@0: nsISHTransaction *temp = trans; michael@0: temp->GetNext(getter_AddRefs(trans)); michael@0: } michael@0: michael@0: // Walk the SHistory list and evict any content viewers that aren't safe. michael@0: GetTransactionAtIndex(0, getter_AddRefs(trans)); michael@0: while (trans) { michael@0: nsCOMPtr viewer = GetContentViewerForTransaction(trans); michael@0: if (safeViewers.IndexOf(viewer) == -1) { michael@0: EvictContentViewerForTransaction(trans); michael@0: } michael@0: michael@0: nsISHTransaction *temp = trans; michael@0: temp->GetNext(getter_AddRefs(trans)); michael@0: } michael@0: } michael@0: michael@0: namespace { michael@0: michael@0: class TransactionAndDistance michael@0: { michael@0: public: michael@0: TransactionAndDistance(nsISHTransaction *aTrans, uint32_t aDist) michael@0: : mTransaction(aTrans) michael@0: , mDistance(aDist) michael@0: { michael@0: mViewer = GetContentViewerForTransaction(aTrans); michael@0: NS_ASSERTION(mViewer, "Transaction should have a content viewer"); michael@0: michael@0: nsCOMPtr shentry; michael@0: mTransaction->GetSHEntry(getter_AddRefs(shentry)); michael@0: michael@0: nsCOMPtr shentryInternal = do_QueryInterface(shentry); michael@0: if (shentryInternal) { michael@0: shentryInternal->GetLastTouched(&mLastTouched); michael@0: } else { michael@0: NS_WARNING("Can't cast to nsISHEntryInternal?"); michael@0: mLastTouched = 0; michael@0: } michael@0: } michael@0: michael@0: bool operator<(const TransactionAndDistance &aOther) const michael@0: { michael@0: // Compare distances first, and fall back to last-accessed times. michael@0: if (aOther.mDistance != this->mDistance) { michael@0: return this->mDistance < aOther.mDistance; michael@0: } michael@0: michael@0: return this->mLastTouched < aOther.mLastTouched; michael@0: } michael@0: michael@0: bool operator==(const TransactionAndDistance &aOther) const michael@0: { michael@0: // This is a little silly; we need == so the default comaprator can be michael@0: // instantiated, but this function is never actually called when we sort michael@0: // the list of TransactionAndDistance objects. michael@0: return aOther.mDistance == this->mDistance && michael@0: aOther.mLastTouched == this->mLastTouched; michael@0: } michael@0: michael@0: nsCOMPtr mTransaction; michael@0: nsCOMPtr mViewer; michael@0: uint32_t mLastTouched; michael@0: int32_t mDistance; michael@0: }; michael@0: michael@0: } // anonymous namespace michael@0: michael@0: //static michael@0: void michael@0: nsSHistory::GloballyEvictContentViewers() michael@0: { michael@0: // First, collect from each SHistory object the transactions which have a michael@0: // cached content viewer. Associate with each transaction its distance from michael@0: // its SHistory's current index. michael@0: michael@0: nsTArray transactions; michael@0: michael@0: nsSHistory *shist = static_cast(PR_LIST_HEAD(&gSHistoryList)); michael@0: while (shist != &gSHistoryList) { michael@0: michael@0: // Maintain a list of the transactions which have viewers and belong to michael@0: // this particular shist object. We'll add this list to the global list, michael@0: // |transactions|, eventually. michael@0: nsTArray shTransactions; michael@0: michael@0: // Content viewers are likely to exist only within shist->mIndex -/+ michael@0: // gHistoryMaxViewers, so only search within that range. michael@0: // michael@0: // A content viewer might exist outside that range due to either: michael@0: // michael@0: // * history.pushState or hash navigations, in which case a copy of the michael@0: // content viewer should exist within the range, or michael@0: // michael@0: // * bugs which cause us not to call nsSHistory::EvictContentViewers() michael@0: // often enough. Once we do call EvictContentViewers() for the michael@0: // SHistory object in question, we'll do a full search of its history michael@0: // and evict the out-of-range content viewers, so we don't bother here. michael@0: // michael@0: int32_t startIndex = std::max(0, shist->mIndex - gHistoryMaxViewers); michael@0: int32_t endIndex = std::min(shist->mLength - 1, michael@0: shist->mIndex + gHistoryMaxViewers); michael@0: nsCOMPtr trans; michael@0: shist->GetTransactionAtIndex(startIndex, getter_AddRefs(trans)); michael@0: for (int32_t i = startIndex; trans && i <= endIndex; i++) { michael@0: nsCOMPtr contentViewer = michael@0: GetContentViewerForTransaction(trans); michael@0: michael@0: if (contentViewer) { michael@0: // Because one content viewer might belong to multiple SHEntries, we michael@0: // have to search through shTransactions to see if we already know michael@0: // about this content viewer. If we find the viewer, update its michael@0: // distance from the SHistory's index and continue. michael@0: bool found = false; michael@0: for (uint32_t j = 0; j < shTransactions.Length(); j++) { michael@0: TransactionAndDistance &container = shTransactions[j]; michael@0: if (container.mViewer == contentViewer) { michael@0: container.mDistance = std::min(container.mDistance, DeprecatedAbs(i - shist->mIndex)); michael@0: found = true; michael@0: break; michael@0: } michael@0: } michael@0: michael@0: // If we didn't find a TransactionAndDistance for this content viewer, make a new michael@0: // one. michael@0: if (!found) { michael@0: TransactionAndDistance container(trans, DeprecatedAbs(i - shist->mIndex)); michael@0: shTransactions.AppendElement(container); michael@0: } michael@0: } michael@0: michael@0: nsISHTransaction *temp = trans; michael@0: temp->GetNext(getter_AddRefs(trans)); michael@0: } michael@0: michael@0: // We've found all the transactions belonging to shist which have viewers. michael@0: // Add those transactions to our global list and move on. michael@0: transactions.AppendElements(shTransactions); michael@0: shist = static_cast(PR_NEXT_LINK(shist)); michael@0: } michael@0: michael@0: // We now have collected all cached content viewers. First check that we michael@0: // have enough that we actually need to evict some. michael@0: if ((int32_t)transactions.Length() <= sHistoryMaxTotalViewers) { michael@0: return; michael@0: } michael@0: michael@0: // If we need to evict, sort our list of transactions and evict the largest michael@0: // ones. (We could of course get better algorithmic complexity here by using michael@0: // a heap or something more clever. But sHistoryMaxTotalViewers isn't large, michael@0: // so let's not worry about it.) michael@0: transactions.Sort(); michael@0: michael@0: for (int32_t i = transactions.Length() - 1; michael@0: i >= sHistoryMaxTotalViewers; --i) { michael@0: michael@0: EvictContentViewerForTransaction(transactions[i].mTransaction); michael@0: michael@0: } michael@0: } michael@0: michael@0: nsresult michael@0: nsSHistory::EvictExpiredContentViewerForEntry(nsIBFCacheEntry *aEntry) michael@0: { michael@0: int32_t startIndex = std::max(0, mIndex - gHistoryMaxViewers); michael@0: int32_t endIndex = std::min(mLength - 1, michael@0: mIndex + gHistoryMaxViewers); michael@0: nsCOMPtr trans; michael@0: GetTransactionAtIndex(startIndex, getter_AddRefs(trans)); michael@0: michael@0: int32_t i; michael@0: for (i = startIndex; trans && i <= endIndex; ++i) { michael@0: nsCOMPtr entry; michael@0: trans->GetSHEntry(getter_AddRefs(entry)); michael@0: michael@0: // Does entry have the same BFCacheEntry as the argument to this method? michael@0: if (entry->HasBFCacheEntry(aEntry)) { michael@0: break; michael@0: } michael@0: michael@0: nsISHTransaction *temp = trans; michael@0: temp->GetNext(getter_AddRefs(trans)); michael@0: } michael@0: if (i > endIndex) michael@0: return NS_OK; michael@0: michael@0: if (i == mIndex) { michael@0: NS_WARNING("How did the current SHEntry expire?"); michael@0: return NS_OK; michael@0: } michael@0: michael@0: EvictContentViewerForTransaction(trans); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: // Evicts all content viewers in all history objects. This is very michael@0: // inefficient, because it requires a linear search through all SHistory michael@0: // objects for each viewer to be evicted. However, this method is called michael@0: // infrequently -- only when the disk or memory cache is cleared. michael@0: michael@0: //static michael@0: void michael@0: nsSHistory::GloballyEvictAllContentViewers() michael@0: { michael@0: int32_t maxViewers = sHistoryMaxTotalViewers; michael@0: sHistoryMaxTotalViewers = 0; michael@0: GloballyEvictContentViewers(); michael@0: sHistoryMaxTotalViewers = maxViewers; michael@0: } michael@0: michael@0: void GetDynamicChildren(nsISHContainer* aContainer, michael@0: nsTArray& aDocshellIDs, michael@0: bool aOnlyTopLevelDynamic) michael@0: { michael@0: int32_t count = 0; michael@0: aContainer->GetChildCount(&count); michael@0: for (int32_t i = 0; i < count; ++i) { michael@0: nsCOMPtr child; michael@0: aContainer->GetChildAt(i, getter_AddRefs(child)); michael@0: if (child) { michael@0: bool dynAdded = false; michael@0: child->IsDynamicallyAdded(&dynAdded); michael@0: if (dynAdded) { michael@0: uint64_t docshellID = 0; michael@0: child->GetDocshellID(&docshellID); michael@0: aDocshellIDs.AppendElement(docshellID); michael@0: } michael@0: if (!dynAdded || !aOnlyTopLevelDynamic) { michael@0: nsCOMPtr childAsContainer = do_QueryInterface(child); michael@0: if (childAsContainer) { michael@0: GetDynamicChildren(childAsContainer, aDocshellIDs, michael@0: aOnlyTopLevelDynamic); michael@0: } michael@0: } michael@0: } michael@0: } michael@0: } michael@0: michael@0: bool michael@0: RemoveFromSessionHistoryContainer(nsISHContainer* aContainer, michael@0: nsTArray& aDocshellIDs) michael@0: { michael@0: nsCOMPtr root = do_QueryInterface(aContainer); michael@0: NS_ENSURE_TRUE(root, false); michael@0: michael@0: bool didRemove = false; michael@0: int32_t childCount = 0; michael@0: aContainer->GetChildCount(&childCount); michael@0: for (int32_t i = childCount - 1; i >= 0; --i) { michael@0: nsCOMPtr child; michael@0: aContainer->GetChildAt(i, getter_AddRefs(child)); michael@0: if (child) { michael@0: uint64_t docshelldID = 0; michael@0: child->GetDocshellID(&docshelldID); michael@0: if (aDocshellIDs.Contains(docshelldID)) { michael@0: didRemove = true; michael@0: aContainer->RemoveChild(child); michael@0: } else { michael@0: nsCOMPtr container = do_QueryInterface(child); michael@0: if (container) { michael@0: bool childRemoved = michael@0: RemoveFromSessionHistoryContainer(container, aDocshellIDs); michael@0: if (childRemoved) { michael@0: didRemove = true; michael@0: } michael@0: } michael@0: } michael@0: } michael@0: } michael@0: return didRemove; michael@0: } michael@0: michael@0: bool RemoveChildEntries(nsISHistory* aHistory, int32_t aIndex, michael@0: nsTArray& aEntryIDs) michael@0: { michael@0: nsCOMPtr rootHE; michael@0: aHistory->GetEntryAtIndex(aIndex, false, getter_AddRefs(rootHE)); michael@0: nsCOMPtr root = do_QueryInterface(rootHE); michael@0: return root ? RemoveFromSessionHistoryContainer(root, aEntryIDs) : false; michael@0: } michael@0: michael@0: bool IsSameTree(nsISHEntry* aEntry1, nsISHEntry* aEntry2) michael@0: { michael@0: if (!aEntry1 && !aEntry2) { michael@0: return true; michael@0: } michael@0: if ((!aEntry1 && aEntry2) || (aEntry1 && !aEntry2)) { michael@0: return false; michael@0: } michael@0: uint32_t id1, id2; michael@0: aEntry1->GetID(&id1); michael@0: aEntry2->GetID(&id2); michael@0: if (id1 != id2) { michael@0: return false; michael@0: } michael@0: michael@0: nsCOMPtr container1 = do_QueryInterface(aEntry1); michael@0: nsCOMPtr container2 = do_QueryInterface(aEntry2); michael@0: int32_t count1, count2; michael@0: container1->GetChildCount(&count1); michael@0: container2->GetChildCount(&count2); michael@0: // We allow null entries in the end of the child list. michael@0: int32_t count = std::max(count1, count2); michael@0: for (int32_t i = 0; i < count; ++i) { michael@0: nsCOMPtr child1, child2; michael@0: container1->GetChildAt(i, getter_AddRefs(child1)); michael@0: container2->GetChildAt(i, getter_AddRefs(child2)); michael@0: if (!IsSameTree(child1, child2)) { michael@0: return false; michael@0: } michael@0: } michael@0: michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: nsSHistory::RemoveDuplicate(int32_t aIndex, bool aKeepNext) michael@0: { michael@0: NS_ASSERTION(aIndex >= 0, "aIndex must be >= 0!"); michael@0: NS_ASSERTION(aIndex != 0 || aKeepNext, michael@0: "If we're removing index 0 we must be keeping the next"); michael@0: NS_ASSERTION(aIndex != mIndex, "Shouldn't remove mIndex!"); michael@0: int32_t compareIndex = aKeepNext ? aIndex + 1 : aIndex - 1; michael@0: nsCOMPtr root1, root2; michael@0: GetEntryAtIndex(aIndex, false, getter_AddRefs(root1)); michael@0: GetEntryAtIndex(compareIndex, false, getter_AddRefs(root2)); michael@0: if (IsSameTree(root1, root2)) { michael@0: nsCOMPtr txToRemove, txToKeep, txNext, txPrev; michael@0: GetTransactionAtIndex(aIndex, getter_AddRefs(txToRemove)); michael@0: GetTransactionAtIndex(compareIndex, getter_AddRefs(txToKeep)); michael@0: NS_ENSURE_TRUE(txToRemove, false); michael@0: NS_ENSURE_TRUE(txToKeep, false); michael@0: txToRemove->GetNext(getter_AddRefs(txNext)); michael@0: txToRemove->GetPrev(getter_AddRefs(txPrev)); michael@0: txToRemove->SetNext(nullptr); michael@0: txToRemove->SetPrev(nullptr); michael@0: if (aKeepNext) { michael@0: if (txPrev) { michael@0: txPrev->SetNext(txToKeep); michael@0: } else { michael@0: txToKeep->SetPrev(nullptr); michael@0: } michael@0: } else { michael@0: txToKeep->SetNext(txNext); michael@0: } michael@0: michael@0: if (aIndex == 0 && aKeepNext) { michael@0: NS_ASSERTION(txToRemove == mListRoot, michael@0: "Transaction at index 0 should be mListRoot!"); michael@0: // We're removing the very first session history transaction! michael@0: mListRoot = txToKeep; michael@0: } michael@0: if (mRootDocShell) { michael@0: static_cast(mRootDocShell)->HistoryTransactionRemoved(aIndex); michael@0: } michael@0: michael@0: // Adjust our indices to reflect the removed transaction michael@0: if (mIndex > aIndex) { michael@0: mIndex = mIndex - 1; michael@0: } michael@0: michael@0: // NB: If the transaction we are removing is the transaction currently michael@0: // being navigated to (mRequestedIndex) then we adjust the index michael@0: // only if we're not keeping the next entry (because if we are keeping michael@0: // the next entry (because the current is a duplicate of the next), then michael@0: // that entry slides into the spot that we're currently pointing to. michael@0: // We don't do this adjustment for mIndex because mIndex cannot equal michael@0: // aIndex. michael@0: michael@0: // NB: We don't need to guard on mRequestedIndex being nonzero here, michael@0: // because either they're strictly greater than aIndex which is at least michael@0: // zero, or they are equal to aIndex in which case aKeepNext must be true michael@0: // if aIndex is zero. michael@0: if (mRequestedIndex > aIndex || (mRequestedIndex == aIndex && !aKeepNext)) { michael@0: mRequestedIndex = mRequestedIndex - 1; michael@0: } michael@0: --mLength; michael@0: return true; michael@0: } michael@0: return false; michael@0: } michael@0: michael@0: NS_IMETHODIMP_(void) michael@0: nsSHistory::RemoveEntries(nsTArray& aIDs, int32_t aStartIndex) michael@0: { michael@0: int32_t index = aStartIndex; michael@0: while(index >= 0 && RemoveChildEntries(this, --index, aIDs)); michael@0: int32_t minIndex = index; michael@0: index = aStartIndex; michael@0: while(index >= 0 && RemoveChildEntries(this, index++, aIDs)); michael@0: michael@0: // We need to remove duplicate nsSHEntry trees. michael@0: bool didRemove = false; michael@0: while (index > minIndex) { michael@0: if (index != mIndex) { michael@0: didRemove = RemoveDuplicate(index, index < mIndex) || didRemove; michael@0: } michael@0: --index; michael@0: } michael@0: if (didRemove && mRootDocShell) { michael@0: nsRefPtr ev = michael@0: NS_NewRunnableMethod(static_cast(mRootDocShell), michael@0: &nsDocShell::FireDummyOnLocationChange); michael@0: NS_DispatchToCurrentThread(ev); michael@0: } michael@0: } michael@0: michael@0: void michael@0: nsSHistory::RemoveDynEntries(int32_t aOldIndex, int32_t aNewIndex) michael@0: { michael@0: // Search for the entries which are in the current index, michael@0: // but not in the new one. michael@0: nsCOMPtr originalSH; michael@0: GetEntryAtIndex(aOldIndex, false, getter_AddRefs(originalSH)); michael@0: nsCOMPtr originalContainer = do_QueryInterface(originalSH); michael@0: nsAutoTArray toBeRemovedEntries; michael@0: if (originalContainer) { michael@0: nsTArray originalDynDocShellIDs; michael@0: GetDynamicChildren(originalContainer, originalDynDocShellIDs, true); michael@0: if (originalDynDocShellIDs.Length()) { michael@0: nsCOMPtr currentSH; michael@0: GetEntryAtIndex(aNewIndex, false, getter_AddRefs(currentSH)); michael@0: nsCOMPtr newContainer = do_QueryInterface(currentSH); michael@0: if (newContainer) { michael@0: nsTArray newDynDocShellIDs; michael@0: GetDynamicChildren(newContainer, newDynDocShellIDs, false); michael@0: for (uint32_t i = 0; i < originalDynDocShellIDs.Length(); ++i) { michael@0: if (!newDynDocShellIDs.Contains(originalDynDocShellIDs[i])) { michael@0: toBeRemovedEntries.AppendElement(originalDynDocShellIDs[i]); michael@0: } michael@0: } michael@0: } michael@0: } michael@0: } michael@0: if (toBeRemovedEntries.Length()) { michael@0: RemoveEntries(toBeRemovedEntries, aOldIndex); michael@0: } michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsSHistory::UpdateIndex() michael@0: { michael@0: // Update the actual index with the right value. michael@0: if (mIndex != mRequestedIndex && mRequestedIndex != -1) { michael@0: RemoveDynEntries(mIndex, mRequestedIndex); michael@0: mIndex = mRequestedIndex; michael@0: } michael@0: michael@0: mRequestedIndex = -1; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsSHistory::Stop(uint32_t aStopFlags) michael@0: { michael@0: //Not implemented michael@0: return NS_OK; michael@0: } michael@0: michael@0: michael@0: NS_IMETHODIMP michael@0: nsSHistory::GetDocument(nsIDOMDocument** aDocument) michael@0: { michael@0: // Not implemented michael@0: return NS_OK; michael@0: } michael@0: michael@0: michael@0: NS_IMETHODIMP michael@0: nsSHistory::GetCurrentURI(nsIURI** aResultURI) michael@0: { michael@0: NS_ENSURE_ARG_POINTER(aResultURI); michael@0: nsresult rv; michael@0: michael@0: nsCOMPtr currentEntry; michael@0: rv = GetEntryAtIndex(mIndex, false, getter_AddRefs(currentEntry)); michael@0: if (NS_FAILED(rv) && !currentEntry) return rv; michael@0: rv = currentEntry->GetURI(aResultURI); michael@0: return rv; michael@0: } michael@0: michael@0: michael@0: NS_IMETHODIMP michael@0: nsSHistory::GetReferringURI(nsIURI** aURI) michael@0: { michael@0: *aURI = nullptr; michael@0: // Not implemented michael@0: return NS_OK; michael@0: } michael@0: michael@0: michael@0: NS_IMETHODIMP michael@0: nsSHistory::SetSessionHistory(nsISHistory* aSessionHistory) michael@0: { michael@0: // Not implemented michael@0: return NS_OK; michael@0: } michael@0: michael@0: michael@0: NS_IMETHODIMP michael@0: nsSHistory::GetSessionHistory(nsISHistory** aSessionHistory) michael@0: { michael@0: // Not implemented michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsSHistory::LoadURIWithBase(const char16_t* aURI, michael@0: uint32_t aLoadFlags, michael@0: nsIURI* aReferringURI, michael@0: nsIInputStream* aPostStream, michael@0: nsIInputStream* aExtraHeaderStream, michael@0: nsIURI* aBaseURI) michael@0: { michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsSHistory::LoadURI(const char16_t* aURI, michael@0: uint32_t aLoadFlags, michael@0: nsIURI* aReferringURI, michael@0: nsIInputStream* aPostStream, michael@0: nsIInputStream* aExtraHeaderStream) michael@0: { michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsSHistory::GotoIndex(int32_t aIndex) michael@0: { michael@0: return LoadEntry(aIndex, nsIDocShellLoadInfo::loadHistory, HIST_CMD_GOTOINDEX); michael@0: } michael@0: michael@0: nsresult michael@0: nsSHistory::LoadNextPossibleEntry(int32_t aNewIndex, long aLoadType, uint32_t aHistCmd) michael@0: { michael@0: mRequestedIndex = -1; michael@0: if (aNewIndex < mIndex) { michael@0: return LoadEntry(aNewIndex - 1, aLoadType, aHistCmd); michael@0: } michael@0: if (aNewIndex > mIndex) { michael@0: return LoadEntry(aNewIndex + 1, aLoadType, aHistCmd); michael@0: } michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsSHistory::LoadEntry(int32_t aIndex, long aLoadType, uint32_t aHistCmd) michael@0: { michael@0: nsCOMPtr docShell; michael@0: // Keep note of requested history index in mRequestedIndex. michael@0: mRequestedIndex = aIndex; michael@0: michael@0: nsCOMPtr prevEntry; michael@0: GetEntryAtIndex(mIndex, false, getter_AddRefs(prevEntry)); michael@0: michael@0: nsCOMPtr nextEntry; michael@0: GetEntryAtIndex(mRequestedIndex, false, getter_AddRefs(nextEntry)); michael@0: if (!nextEntry || !prevEntry) { michael@0: mRequestedIndex = -1; michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: // Remember that this entry is getting loaded at this point in the sequence michael@0: nsCOMPtr entryInternal = do_QueryInterface(nextEntry); michael@0: michael@0: if (entryInternal) { michael@0: entryInternal->SetLastTouched(++gTouchCounter); michael@0: } michael@0: michael@0: // Send appropriate listener notifications michael@0: bool canNavigate = true; michael@0: // Get the uri for the entry we are about to visit michael@0: nsCOMPtr nextURI; michael@0: nextEntry->GetURI(getter_AddRefs(nextURI)); michael@0: michael@0: if (aHistCmd == HIST_CMD_BACK) { michael@0: // We are going back one entry. Send GoBack notifications michael@0: NOTIFY_LISTENERS_CANCELABLE(OnHistoryGoBack, canNavigate, michael@0: (nextURI, &canNavigate)); michael@0: } else if (aHistCmd == HIST_CMD_FORWARD) { michael@0: // We are going forward. Send GoForward notification michael@0: NOTIFY_LISTENERS_CANCELABLE(OnHistoryGoForward, canNavigate, michael@0: (nextURI, &canNavigate)); michael@0: } else if (aHistCmd == HIST_CMD_GOTOINDEX) { michael@0: // We are going somewhere else. This is not reload either michael@0: NOTIFY_LISTENERS_CANCELABLE(OnHistoryGotoIndex, canNavigate, michael@0: (aIndex, nextURI, &canNavigate)); michael@0: } michael@0: michael@0: if (!canNavigate) { michael@0: // If the listener asked us not to proceed with michael@0: // the operation, simply return. michael@0: mRequestedIndex = -1; michael@0: return NS_OK; // XXX Maybe I can return some other error code? michael@0: } michael@0: michael@0: nsCOMPtr nexturi; michael@0: int32_t pCount=0, nCount=0; michael@0: nsCOMPtr prevAsContainer(do_QueryInterface(prevEntry)); michael@0: nsCOMPtr nextAsContainer(do_QueryInterface(nextEntry)); michael@0: if (prevAsContainer && nextAsContainer) { michael@0: prevAsContainer->GetChildCount(&pCount); michael@0: nextAsContainer->GetChildCount(&nCount); michael@0: } michael@0: michael@0: nsCOMPtr loadInfo; michael@0: if (mRequestedIndex == mIndex) { michael@0: // Possibly a reload case michael@0: docShell = mRootDocShell; michael@0: } michael@0: else { michael@0: // Going back or forward. michael@0: if ((pCount > 0) && (nCount > 0)) { michael@0: /* THis is a subframe navigation. Go find michael@0: * the docshell in which load should happen michael@0: */ michael@0: bool frameFound = false; michael@0: nsresult rv = CompareFrames(prevEntry, nextEntry, mRootDocShell, aLoadType, &frameFound); michael@0: if (!frameFound) { michael@0: // We did not successfully find the subframe in which michael@0: // the new url was to be loaded. Go further in the history. michael@0: return LoadNextPossibleEntry(aIndex, aLoadType, aHistCmd); michael@0: } michael@0: return rv; michael@0: } // (pCount >0) michael@0: else { michael@0: // Loading top level page. michael@0: uint32_t prevID = 0; michael@0: uint32_t nextID = 0; michael@0: prevEntry->GetID(&prevID); michael@0: nextEntry->GetID(&nextID); michael@0: if (prevID == nextID) { michael@0: // Try harder to find something new to load. michael@0: // This may happen for example if some page removed iframes dynamically. michael@0: return LoadNextPossibleEntry(aIndex, aLoadType, aHistCmd); michael@0: } michael@0: docShell = mRootDocShell; michael@0: } michael@0: } michael@0: michael@0: if (!docShell) { michael@0: // we did not successfully go to the proper index. michael@0: // return error. michael@0: mRequestedIndex = -1; michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: // Start the load on the appropriate docshell michael@0: return InitiateLoad(nextEntry, docShell, aLoadType); michael@0: } michael@0: michael@0: nsresult michael@0: nsSHistory::CompareFrames(nsISHEntry * aPrevEntry, nsISHEntry * aNextEntry, nsIDocShell * aParent, long aLoadType, bool * aIsFrameFound) michael@0: { michael@0: if (!aPrevEntry || !aNextEntry || !aParent) michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: // We should be comparing only entries which were created for the michael@0: // same docshell. This is here to just prevent anything strange happening. michael@0: // This check could be possibly an assertion. michael@0: uint64_t prevdID, nextdID; michael@0: aPrevEntry->GetDocshellID(&prevdID); michael@0: aNextEntry->GetDocshellID(&nextdID); michael@0: NS_ENSURE_STATE(prevdID == nextdID); michael@0: michael@0: nsresult result = NS_OK; michael@0: uint32_t prevID, nextID; michael@0: michael@0: aPrevEntry->GetID(&prevID); michael@0: aNextEntry->GetID(&nextID); michael@0: michael@0: // Check the IDs to verify if the pages are different. michael@0: if (prevID != nextID) { michael@0: if (aIsFrameFound) michael@0: *aIsFrameFound = true; michael@0: // Set the Subframe flag of the entry to indicate that michael@0: // it is subframe navigation michael@0: aNextEntry->SetIsSubFrame(true); michael@0: InitiateLoad(aNextEntry, aParent, aLoadType); michael@0: return NS_OK; michael@0: } michael@0: michael@0: /* The root entries are the same, so compare any child frames */ michael@0: int32_t pcnt=0, ncnt=0, dsCount=0; michael@0: nsCOMPtr prevContainer(do_QueryInterface(aPrevEntry)); michael@0: nsCOMPtr nextContainer(do_QueryInterface(aNextEntry)); michael@0: michael@0: if (!aParent) michael@0: return NS_ERROR_FAILURE; michael@0: if (!prevContainer || !nextContainer) michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: prevContainer->GetChildCount(&pcnt); michael@0: nextContainer->GetChildCount(&ncnt); michael@0: aParent->GetChildCount(&dsCount); michael@0: michael@0: // Create an array for child docshells. michael@0: nsCOMArray docshells; michael@0: for (int32_t i = 0; i < dsCount; ++i) { michael@0: nsCOMPtr treeItem; michael@0: aParent->GetChildAt(i, getter_AddRefs(treeItem)); michael@0: nsCOMPtr shell = do_QueryInterface(treeItem); michael@0: if (shell) { michael@0: docshells.AppendObject(shell); michael@0: } michael@0: } michael@0: michael@0: // Search for something to load next. michael@0: for (int32_t i = 0; i < ncnt; ++i) { michael@0: // First get an entry which may cause a new page to be loaded. michael@0: nsCOMPtr nChild; michael@0: nextContainer->GetChildAt(i, getter_AddRefs(nChild)); michael@0: if (!nChild) { michael@0: continue; michael@0: } michael@0: uint64_t docshellID = 0; michael@0: nChild->GetDocshellID(&docshellID); michael@0: michael@0: // Then find the associated docshell. michael@0: nsIDocShell* dsChild = nullptr; michael@0: int32_t count = docshells.Count(); michael@0: for (int32_t j = 0; j < count; ++j) { michael@0: uint64_t shellID = 0; michael@0: nsIDocShell* shell = docshells[j]; michael@0: shell->GetHistoryID(&shellID); michael@0: if (shellID == docshellID) { michael@0: dsChild = shell; michael@0: break; michael@0: } michael@0: } michael@0: if (!dsChild) { michael@0: continue; michael@0: } michael@0: michael@0: // Then look at the previous entries to see if there was michael@0: // an entry for the docshell. michael@0: nsCOMPtr pChild; michael@0: for (int32_t k = 0; k < pcnt; ++k) { michael@0: nsCOMPtr child; michael@0: prevContainer->GetChildAt(k, getter_AddRefs(child)); michael@0: if (child) { michael@0: uint64_t dID = 0; michael@0: child->GetDocshellID(&dID); michael@0: if (dID == docshellID) { michael@0: pChild = child; michael@0: break; michael@0: } michael@0: } michael@0: } michael@0: michael@0: // Finally recursively call this method. michael@0: // This will either load a new page to shell or some subshell or michael@0: // do nothing. michael@0: CompareFrames(pChild, nChild, dsChild, aLoadType, aIsFrameFound); michael@0: } michael@0: return result; michael@0: } michael@0: michael@0: michael@0: nsresult michael@0: nsSHistory::InitiateLoad(nsISHEntry * aFrameEntry, nsIDocShell * aFrameDS, long aLoadType) michael@0: { michael@0: NS_ENSURE_STATE(aFrameDS && aFrameEntry); michael@0: michael@0: nsCOMPtr loadInfo; michael@0: michael@0: /* Set the loadType in the SHEntry too to what was passed on. michael@0: * This will be passed on to child subframes later in nsDocShell, michael@0: * so that proper loadType is maintained through out a frameset michael@0: */ michael@0: aFrameEntry->SetLoadType(aLoadType); michael@0: aFrameDS->CreateLoadInfo (getter_AddRefs(loadInfo)); michael@0: michael@0: loadInfo->SetLoadType(aLoadType); michael@0: loadInfo->SetSHEntry(aFrameEntry); michael@0: michael@0: nsCOMPtr nextURI; michael@0: aFrameEntry->GetURI(getter_AddRefs(nextURI)); michael@0: // Time to initiate a document load michael@0: return aFrameDS->LoadURI(nextURI, loadInfo, nsIWebNavigation::LOAD_FLAGS_NONE, false); michael@0: michael@0: } michael@0: michael@0: michael@0: michael@0: NS_IMETHODIMP michael@0: nsSHistory::SetRootDocShell(nsIDocShell * aDocShell) michael@0: { michael@0: mRootDocShell = aDocShell; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsSHistory::GetRootDocShell(nsIDocShell ** aDocShell) michael@0: { michael@0: NS_ENSURE_ARG_POINTER(aDocShell); michael@0: michael@0: *aDocShell = mRootDocShell; michael@0: //Not refcounted. May this method should not be available for public michael@0: // NS_IF_ADDREF(*aDocShell); michael@0: return NS_OK; michael@0: } michael@0: michael@0: michael@0: NS_IMETHODIMP michael@0: nsSHistory::GetSHistoryEnumerator(nsISimpleEnumerator** aEnumerator) michael@0: { michael@0: nsresult status = NS_OK; michael@0: michael@0: NS_ENSURE_ARG_POINTER(aEnumerator); michael@0: nsSHEnumerator * iterator = new nsSHEnumerator(this); michael@0: if (iterator && NS_FAILED(status = CallQueryInterface(iterator, aEnumerator))) michael@0: delete iterator; michael@0: return status; michael@0: } michael@0: michael@0: michael@0: //***************************************************************************** michael@0: //*** nsSHEnumerator: Object Management michael@0: //***************************************************************************** michael@0: michael@0: nsSHEnumerator::nsSHEnumerator(nsSHistory * aSHistory):mIndex(-1) michael@0: { michael@0: mSHistory = aSHistory; michael@0: } michael@0: michael@0: nsSHEnumerator::~nsSHEnumerator() michael@0: { michael@0: mSHistory = nullptr; michael@0: } michael@0: michael@0: NS_IMPL_ISUPPORTS(nsSHEnumerator, nsISimpleEnumerator) michael@0: michael@0: NS_IMETHODIMP michael@0: nsSHEnumerator::HasMoreElements(bool * aReturn) michael@0: { michael@0: int32_t cnt; michael@0: *aReturn = false; michael@0: mSHistory->GetCount(&cnt); michael@0: if (mIndex >= -1 && mIndex < (cnt-1) ) { michael@0: *aReturn = true; michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: michael@0: NS_IMETHODIMP michael@0: nsSHEnumerator::GetNext(nsISupports **aItem) michael@0: { michael@0: NS_ENSURE_ARG_POINTER(aItem); michael@0: int32_t cnt= 0; michael@0: michael@0: nsresult result = NS_ERROR_FAILURE; michael@0: mSHistory->GetCount(&cnt); michael@0: if (mIndex < (cnt-1)) { michael@0: mIndex++; michael@0: nsCOMPtr hEntry; michael@0: result = mSHistory->GetEntryAtIndex(mIndex, false, getter_AddRefs(hEntry)); michael@0: if (hEntry) michael@0: result = CallQueryInterface(hEntry, aItem); michael@0: } michael@0: return result; michael@0: }