docshell/shistory/src/nsSHistory.cpp

Fri, 16 Jan 2015 18:13:44 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Fri, 16 Jan 2015 18:13:44 +0100
branch
TOR_BUG_9701
changeset 14
925c144e1f1f
permissions
-rw-r--r--

Integrate suggestion from review to improve consistency with existing code.

     1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
     2  *
     3  * This Source Code Form is subject to the terms of the Mozilla Public
     4  * License, v. 2.0. If a copy of the MPL was not distributed with this
     5  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
     7 // Local Includes 
     8 #include "nsSHistory.h"
     9 #include <algorithm>
    11 // Helper Classes
    12 #include "mozilla/Preferences.h"
    14 // Interfaces Needed
    15 #include "nsILayoutHistoryState.h"
    16 #include "nsIDocShell.h"
    17 #include "nsIDocShellLoadInfo.h"
    18 #include "nsISHContainer.h"
    19 #include "nsIDocShellTreeItem.h"
    20 #include "nsIURI.h"
    21 #include "nsIContentViewer.h"
    22 #include "nsICacheService.h"
    23 #include "nsIObserverService.h"
    24 #include "prclist.h"
    25 #include "mozilla/Services.h"
    26 #include "nsTArray.h"
    27 #include "nsCOMArray.h"
    28 #include "nsDocShell.h"
    29 #include "mozilla/Attributes.h"
    30 #include "nsISHEntry.h"
    31 #include "nsISHTransaction.h"
    32 #include "nsISHistoryListener.h"
    33 #include "nsComponentManagerUtils.h"
    35 // For calculating max history entries and max cachable contentviewers
    36 #include "prsystem.h"
    37 #include "mozilla/MathAlgorithms.h"
    39 using namespace mozilla;
    41 #define PREF_SHISTORY_SIZE "browser.sessionhistory.max_entries"
    42 #define PREF_SHISTORY_MAX_TOTAL_VIEWERS "browser.sessionhistory.max_total_viewers"
    44 static const char* kObservedPrefs[] = {
    45   PREF_SHISTORY_SIZE,
    46   PREF_SHISTORY_MAX_TOTAL_VIEWERS,
    47   nullptr
    48 };
    50 static int32_t  gHistoryMaxSize = 50;
    51 // Max viewers allowed per SHistory objects
    52 static const int32_t  gHistoryMaxViewers = 3;
    53 // List of all SHistory objects, used for content viewer cache eviction
    54 static PRCList gSHistoryList;
    55 // Max viewers allowed total, across all SHistory objects - negative default
    56 // means we will calculate how many viewers to cache based on total memory
    57 int32_t nsSHistory::sHistoryMaxTotalViewers = -1;
    59 // A counter that is used to be able to know the order in which
    60 // entries were touched, so that we can evict older entries first.
    61 static uint32_t gTouchCounter = 0;
    63 #ifdef PR_LOGGING
    65 static PRLogModuleInfo*
    66 GetSHistoryLog()
    67 {
    68   static PRLogModuleInfo *sLog;
    69   if (!sLog)
    70     sLog = PR_NewLogModule("nsSHistory");
    71   return sLog;
    72 }
    73 #define LOG(format) PR_LOG(GetSHistoryLog(), PR_LOG_DEBUG, format)
    75 // This macro makes it easier to print a log message which includes a URI's
    76 // spec.  Example use:
    77 //
    78 //  nsIURI *uri = [...];
    79 //  LOG_SPEC(("The URI is %s.", _spec), uri);
    80 //
    81 #define LOG_SPEC(format, uri)                              \
    82   PR_BEGIN_MACRO                                           \
    83     if (PR_LOG_TEST(GetSHistoryLog(), PR_LOG_DEBUG)) {     \
    84       nsAutoCString _specStr(NS_LITERAL_CSTRING("(null)"));\
    85       if (uri) {                                           \
    86         uri->GetSpec(_specStr);                            \
    87       }                                                    \
    88       const char* _spec = _specStr.get();                  \
    89       LOG(format);                                         \
    90     }                                                      \
    91   PR_END_MACRO
    93 // This macro makes it easy to log a message including an SHEntry's URI.
    94 // For example:
    95 //
    96 //  nsCOMPtr<nsISHEntry> shentry = [...];
    97 //  LOG_SHENTRY_SPEC(("shentry %p has uri %s.", shentry.get(), _spec), shentry);
    98 //
    99 #define LOG_SHENTRY_SPEC(format, shentry)                  \
   100   PR_BEGIN_MACRO                                           \
   101     if (PR_LOG_TEST(GetSHistoryLog(), PR_LOG_DEBUG)) {     \
   102       nsCOMPtr<nsIURI> uri;                                \
   103       shentry->GetURI(getter_AddRefs(uri));                \
   104       LOG_SPEC(format, uri);                               \
   105     }                                                      \
   106   PR_END_MACRO
   108 #else // !PR_LOGGING
   110 #define LOG(format)
   111 #define LOG_SPEC(format, uri)
   112 #define LOG_SHENTRY_SPEC(format, shentry)
   114 #endif // PR_LOGGING
   116 // Iterates over all registered session history listeners.
   117 #define ITERATE_LISTENERS(body)                            \
   118   PR_BEGIN_MACRO                                           \
   119   {                                                        \
   120     nsAutoTObserverArray<nsWeakPtr, 2>::EndLimitedIterator \
   121       iter(mListeners);                                    \
   122     while (iter.HasMore()) {                               \
   123       nsCOMPtr<nsISHistoryListener> listener =             \
   124         do_QueryReferent(iter.GetNext());                  \
   125       if (listener) {                                      \
   126         body                                               \
   127       }                                                    \
   128     }                                                      \
   129   }                                                        \
   130   PR_END_MACRO
   132 // Calls a given method on all registered session history listeners.
   133 #define NOTIFY_LISTENERS(method, args)                     \
   134   ITERATE_LISTENERS(                                       \
   135     listener->method args;                                 \
   136   );
   138 // Calls a given method on all registered session history listeners.
   139 // Listeners may return 'false' to cancel an action so make sure that we
   140 // set the return value to 'false' if one of the listeners wants to cancel.
   141 #define NOTIFY_LISTENERS_CANCELABLE(method, retval, args)  \
   142   PR_BEGIN_MACRO                                           \
   143   {                                                        \
   144     bool canceled = false;                                 \
   145     retval = true;                                         \
   146     ITERATE_LISTENERS(                                     \
   147       listener->method args;                               \
   148       if (!retval) {                                       \
   149         canceled = true;                                   \
   150       }                                                    \
   151     );                                                     \
   152     if (canceled) {                                        \
   153       retval = false;                                      \
   154     }                                                      \
   155   }                                                        \
   156   PR_END_MACRO
   158 enum HistCmd{
   159   HIST_CMD_BACK,
   160   HIST_CMD_FORWARD,
   161   HIST_CMD_GOTOINDEX,
   162   HIST_CMD_RELOAD
   163 } ;
   165 //*****************************************************************************
   166 //***      nsSHistoryObserver
   167 //*****************************************************************************
   169 class nsSHistoryObserver MOZ_FINAL : public nsIObserver
   170 {
   172 public:
   173   NS_DECL_ISUPPORTS
   174   NS_DECL_NSIOBSERVER
   176   nsSHistoryObserver() {}
   178 protected:
   179   ~nsSHistoryObserver() {}
   180 };
   182 static nsSHistoryObserver* gObserver = nullptr;
   184 NS_IMPL_ISUPPORTS(nsSHistoryObserver, nsIObserver)
   186 NS_IMETHODIMP
   187 nsSHistoryObserver::Observe(nsISupports *aSubject, const char *aTopic,
   188                             const char16_t *aData)
   189 {
   190   if (!strcmp(aTopic, NS_PREFBRANCH_PREFCHANGE_TOPIC_ID)) {
   191     nsSHistory::UpdatePrefs();
   192     nsSHistory::GloballyEvictContentViewers();
   193   } else if (!strcmp(aTopic, NS_CACHESERVICE_EMPTYCACHE_TOPIC_ID) ||
   194              !strcmp(aTopic, "memory-pressure")) {
   195     nsSHistory::GloballyEvictAllContentViewers();
   196   }
   198   return NS_OK;
   199 }
   201 namespace {
   203 already_AddRefed<nsIContentViewer>
   204 GetContentViewerForTransaction(nsISHTransaction *aTrans)
   205 {
   206   nsCOMPtr<nsISHEntry> entry;
   207   aTrans->GetSHEntry(getter_AddRefs(entry));
   208   if (!entry) {
   209     return nullptr;
   210   }
   212   nsCOMPtr<nsISHEntry> ownerEntry;
   213   nsCOMPtr<nsIContentViewer> viewer;
   214   entry->GetAnyContentViewer(getter_AddRefs(ownerEntry),
   215                              getter_AddRefs(viewer));
   216   return viewer.forget();
   217 }
   219 void
   220 EvictContentViewerForTransaction(nsISHTransaction *aTrans)
   221 {
   222   nsCOMPtr<nsISHEntry> entry;
   223   aTrans->GetSHEntry(getter_AddRefs(entry));
   224   nsCOMPtr<nsIContentViewer> viewer;
   225   nsCOMPtr<nsISHEntry> ownerEntry;
   226   entry->GetAnyContentViewer(getter_AddRefs(ownerEntry),
   227                              getter_AddRefs(viewer));
   228   if (viewer) {
   229     NS_ASSERTION(ownerEntry,
   230                  "Content viewer exists but its SHEntry is null");
   232     LOG_SHENTRY_SPEC(("Evicting content viewer 0x%p for "
   233                       "owning SHEntry 0x%p at %s.",
   234                       viewer.get(), ownerEntry.get(), _spec), ownerEntry);
   236     // Drop the presentation state before destroying the viewer, so that
   237     // document teardown is able to correctly persist the state.
   238     ownerEntry->SetContentViewer(nullptr);
   239     ownerEntry->SyncPresentationState();
   240     viewer->Destroy();
   241   }
   242 }
   244 } // anonymous namespace
   246 //*****************************************************************************
   247 //***    nsSHistory: Object Management
   248 //*****************************************************************************
   250 nsSHistory::nsSHistory() : mListRoot(nullptr), mIndex(-1), mLength(0), mRequestedIndex(-1)
   251 {
   252   // Add this new SHistory object to the list
   253   PR_APPEND_LINK(this, &gSHistoryList);
   254 }
   257 nsSHistory::~nsSHistory()
   258 {
   259   // Remove this SHistory object from the list
   260   PR_REMOVE_LINK(this);
   261 }
   263 //*****************************************************************************
   264 //    nsSHistory: nsISupports
   265 //*****************************************************************************
   267 NS_IMPL_ADDREF(nsSHistory)
   268 NS_IMPL_RELEASE(nsSHistory)
   270 NS_INTERFACE_MAP_BEGIN(nsSHistory)
   271    NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsISHistory)
   272    NS_INTERFACE_MAP_ENTRY(nsISHistory)
   273    NS_INTERFACE_MAP_ENTRY(nsIWebNavigation)
   274    NS_INTERFACE_MAP_ENTRY(nsISHistoryInternal)
   275 NS_INTERFACE_MAP_END
   277 //*****************************************************************************
   278 //    nsSHistory: nsISHistory
   279 //*****************************************************************************
   281 // static
   282 uint32_t
   283 nsSHistory::CalcMaxTotalViewers()
   284 {
   285   // Calculate an estimate of how many ContentViewers we should cache based
   286   // on RAM.  This assumes that the average ContentViewer is 4MB (conservative)
   287   // and caps the max at 8 ContentViewers
   288   //
   289   // TODO: Should we split the cache memory betw. ContentViewer caching and
   290   // nsCacheService?
   291   //
   292   // RAM      ContentViewers
   293   // -----------------------
   294   // 32   Mb       0
   295   // 64   Mb       1
   296   // 128  Mb       2
   297   // 256  Mb       3
   298   // 512  Mb       5
   299   // 1024 Mb       8
   300   // 2048 Mb       8
   301   // 4096 Mb       8
   302   uint64_t bytes = PR_GetPhysicalMemorySize();
   304   if (bytes == 0)
   305     return 0;
   307   // Conversion from unsigned int64_t to double doesn't work on all platforms.
   308   // We need to truncate the value at INT64_MAX to make sure we don't
   309   // overflow.
   310   if (bytes > INT64_MAX)
   311     bytes = INT64_MAX;
   313   double kBytesD = (double)(bytes >> 10);
   315   // This is essentially the same calculation as for nsCacheService,
   316   // except that we divide the final memory calculation by 4, since
   317   // we assume each ContentViewer takes on average 4MB
   318   uint32_t viewers = 0;
   319   double x = std::log(kBytesD)/std::log(2.0) - 14;
   320   if (x > 0) {
   321     viewers    = (uint32_t)(x * x - x + 2.001); // add .001 for rounding
   322     viewers   /= 4;
   323   }
   325   // Cap it off at 8 max
   326   if (viewers > 8) {
   327     viewers = 8;
   328   }
   329   return viewers;
   330 }
   332 // static
   333 void
   334 nsSHistory::UpdatePrefs()
   335 {
   336   Preferences::GetInt(PREF_SHISTORY_SIZE, &gHistoryMaxSize);
   337   Preferences::GetInt(PREF_SHISTORY_MAX_TOTAL_VIEWERS,
   338                       &sHistoryMaxTotalViewers);
   339   // If the pref is negative, that means we calculate how many viewers
   340   // we think we should cache, based on total memory
   341   if (sHistoryMaxTotalViewers < 0) {
   342     sHistoryMaxTotalViewers = CalcMaxTotalViewers();
   343   }
   344 }
   346 // static
   347 nsresult
   348 nsSHistory::Startup()
   349 {
   350   UpdatePrefs();
   352   // The goal of this is to unbreak users who have inadvertently set their
   353   // session history size to less than the default value.
   354   int32_t defaultHistoryMaxSize =
   355     Preferences::GetDefaultInt(PREF_SHISTORY_SIZE, 50);
   356   if (gHistoryMaxSize < defaultHistoryMaxSize) {
   357     gHistoryMaxSize = defaultHistoryMaxSize;
   358   }
   360   // Allow the user to override the max total number of cached viewers,
   361   // but keep the per SHistory cached viewer limit constant
   362   if (!gObserver) {
   363     gObserver = new nsSHistoryObserver();
   364     NS_ADDREF(gObserver);
   365     Preferences::AddStrongObservers(gObserver, kObservedPrefs);
   367     nsCOMPtr<nsIObserverService> obsSvc =
   368       mozilla::services::GetObserverService();
   369     if (obsSvc) {
   370       // Observe empty-cache notifications so tahat clearing the disk/memory
   371       // cache will also evict all content viewers.
   372       obsSvc->AddObserver(gObserver,
   373                           NS_CACHESERVICE_EMPTYCACHE_TOPIC_ID, false);
   375       // Same for memory-pressure notifications
   376       obsSvc->AddObserver(gObserver, "memory-pressure", false);
   377     }
   378   }
   380   // Initialize the global list of all SHistory objects
   381   PR_INIT_CLIST(&gSHistoryList);
   382   return NS_OK;
   383 }
   385 // static
   386 void
   387 nsSHistory::Shutdown()
   388 {
   389   if (gObserver) {
   390     Preferences::RemoveObservers(gObserver, kObservedPrefs);
   391     nsCOMPtr<nsIObserverService> obsSvc =
   392       mozilla::services::GetObserverService();
   393     if (obsSvc) {
   394       obsSvc->RemoveObserver(gObserver, NS_CACHESERVICE_EMPTYCACHE_TOPIC_ID);
   395       obsSvc->RemoveObserver(gObserver, "memory-pressure");
   396     }
   397     NS_RELEASE(gObserver);
   398   }
   399 }
   401 /* Add an entry to the History list at mIndex and 
   402  * increment the index to point to the new entry
   403  */
   404 NS_IMETHODIMP
   405 nsSHistory::AddEntry(nsISHEntry * aSHEntry, bool aPersist)
   406 {
   407   NS_ENSURE_ARG(aSHEntry);
   409   nsCOMPtr<nsISHTransaction> currentTxn;
   411   if(mListRoot)
   412     GetTransactionAtIndex(mIndex, getter_AddRefs(currentTxn));
   414   bool currentPersist = true;
   415   if(currentTxn)
   416     currentTxn->GetPersist(&currentPersist);
   418   int32_t currentIndex = mIndex;
   420   if(!currentPersist)
   421   {
   422     NOTIFY_LISTENERS(OnHistoryReplaceEntry, (currentIndex));
   423     NS_ENSURE_SUCCESS(currentTxn->SetSHEntry(aSHEntry),NS_ERROR_FAILURE);
   424     currentTxn->SetPersist(aPersist);
   425     return NS_OK;
   426   }
   428   nsCOMPtr<nsISHTransaction> txn(do_CreateInstance(NS_SHTRANSACTION_CONTRACTID));
   429   NS_ENSURE_TRUE(txn, NS_ERROR_FAILURE);
   431   nsCOMPtr<nsIURI> uri;
   432   aSHEntry->GetURI(getter_AddRefs(uri));
   433   NOTIFY_LISTENERS(OnHistoryNewEntry, (uri));
   435   // If a listener has changed mIndex, we need to get currentTxn again,
   436   // otherwise we'll be left at an inconsistent state (see bug 320742)
   437   if (currentIndex != mIndex) {
   438     GetTransactionAtIndex(mIndex, getter_AddRefs(currentTxn));
   439   }
   441   // Set the ShEntry and parent for the transaction. setting the 
   442   // parent will properly set the parent child relationship
   443   txn->SetPersist(aPersist);
   444   NS_ENSURE_SUCCESS(txn->Create(aSHEntry, currentTxn), NS_ERROR_FAILURE);
   446   // A little tricky math here...  Basically when adding an object regardless of
   447   // what the length was before, it should always be set back to the current and
   448   // lop off the forward.
   449   mLength = (++mIndex + 1);
   451   // If this is the very first transaction, initialize the list
   452   if(!mListRoot)
   453     mListRoot = txn;
   455   // Purge History list if it is too long
   456   if ((gHistoryMaxSize >= 0) && (mLength > gHistoryMaxSize))
   457     PurgeHistory(mLength-gHistoryMaxSize);
   459   RemoveDynEntries(mIndex - 1, mIndex);
   460   return NS_OK;
   461 }
   463 /* Get size of the history list */
   464 NS_IMETHODIMP
   465 nsSHistory::GetCount(int32_t * aResult)
   466 {
   467   NS_ENSURE_ARG_POINTER(aResult);
   468   *aResult = mLength;
   469   return NS_OK;
   470 }
   472 /* Get index of the history list */
   473 NS_IMETHODIMP
   474 nsSHistory::GetIndex(int32_t * aResult)
   475 {
   476   NS_PRECONDITION(aResult, "null out param?");
   477   *aResult = mIndex;
   478   return NS_OK;
   479 }
   481 /* Get the requestedIndex */
   482 NS_IMETHODIMP
   483 nsSHistory::GetRequestedIndex(int32_t * aResult)
   484 {
   485   NS_PRECONDITION(aResult, "null out param?");
   486   *aResult = mRequestedIndex;
   487   return NS_OK;
   488 }
   490 /* Get the entry at a given index */
   491 NS_IMETHODIMP
   492 nsSHistory::GetEntryAtIndex(int32_t aIndex, bool aModifyIndex, nsISHEntry** aResult)
   493 {
   494   nsresult rv;
   495   nsCOMPtr<nsISHTransaction> txn;
   497   /* GetTransactionAtIndex ensures aResult is valid and validates aIndex */
   498   rv = GetTransactionAtIndex(aIndex, getter_AddRefs(txn));
   499   if (NS_SUCCEEDED(rv) && txn) {
   500     //Get the Entry from the transaction
   501     rv = txn->GetSHEntry(aResult);
   502     if (NS_SUCCEEDED(rv) && (*aResult)) {
   503       // Set mIndex to the requested index, if asked to do so..
   504       if (aModifyIndex) {
   505         mIndex = aIndex;
   506       }
   507     } //entry
   508   }  //Transaction
   509   return rv;
   510 }
   512 /* Get the transaction at a given index */
   513 NS_IMETHODIMP
   514 nsSHistory::GetTransactionAtIndex(int32_t aIndex, nsISHTransaction ** aResult)
   515 {
   516   nsresult rv;
   517   NS_ENSURE_ARG_POINTER(aResult);
   519   if ((mLength <= 0) || (aIndex < 0) || (aIndex >= mLength))
   520     return NS_ERROR_FAILURE;
   522   if (!mListRoot) 
   523     return NS_ERROR_FAILURE;
   525   if (aIndex == 0)
   526   {
   527     *aResult = mListRoot;
   528     NS_ADDREF(*aResult);
   529     return NS_OK;
   530   } 
   531   int32_t   cnt=0;
   532   nsCOMPtr<nsISHTransaction>  tempPtr;
   534   rv = GetRootTransaction(getter_AddRefs(tempPtr));
   535   if (NS_FAILED(rv) || !tempPtr)
   536     return NS_ERROR_FAILURE;
   538   while(1) {
   539     nsCOMPtr<nsISHTransaction> ptr;
   540     rv = tempPtr->GetNext(getter_AddRefs(ptr));
   541     if (NS_SUCCEEDED(rv) && ptr) {
   542       cnt++;
   543       if (cnt == aIndex) {
   544         *aResult = ptr;
   545         NS_ADDREF(*aResult);
   546         break;
   547       }
   548       else {
   549         tempPtr = ptr;
   550         continue;
   551       }
   552     }  //NS_SUCCEEDED
   553     else 
   554       return NS_ERROR_FAILURE;
   555   }  // while 
   557   return NS_OK;
   558 }
   561 /* Get the index of a given entry */
   562 NS_IMETHODIMP
   563 nsSHistory::GetIndexOfEntry(nsISHEntry* aSHEntry, int32_t* aResult) {
   564   NS_ENSURE_ARG(aSHEntry);
   565   NS_ENSURE_ARG_POINTER(aResult);
   566   *aResult = -1;
   568   if (mLength <= 0) {
   569     return NS_ERROR_FAILURE;
   570   }
   572   nsCOMPtr<nsISHTransaction> currentTxn;
   573   int32_t cnt = 0;
   575   nsresult rv = GetRootTransaction(getter_AddRefs(currentTxn));
   576   if (NS_FAILED(rv) || !currentTxn) {
   577     return NS_ERROR_FAILURE;
   578   }
   580   while (true) {
   581     nsCOMPtr<nsISHEntry> entry;
   582     rv = currentTxn->GetSHEntry(getter_AddRefs(entry));
   583     if (NS_FAILED(rv) || !entry) {
   584       return NS_ERROR_FAILURE;
   585     }
   587     if (aSHEntry == entry) {
   588       *aResult = cnt;
   589       break;
   590     }
   592     rv = currentTxn->GetNext(getter_AddRefs(currentTxn));
   593     if (NS_FAILED(rv) || !currentTxn) {
   594       return NS_ERROR_FAILURE;
   595     }
   597     cnt++;
   598   }
   600   return NS_OK;
   601 }
   604 #ifdef DEBUG
   605 nsresult
   606 nsSHistory::PrintHistory()
   607 {
   609   nsCOMPtr<nsISHTransaction>   txn;
   610   int32_t index = 0;
   611   nsresult rv;
   613   if (!mListRoot) 
   614     return NS_ERROR_FAILURE;
   616   txn = mListRoot;
   618   while (1) {
   619     if (!txn)
   620       break;
   621     nsCOMPtr<nsISHEntry>  entry;
   622     rv = txn->GetSHEntry(getter_AddRefs(entry));
   623     if (NS_FAILED(rv) && !entry)
   624       return NS_ERROR_FAILURE;
   626     nsCOMPtr<nsILayoutHistoryState> layoutHistoryState;
   627     nsCOMPtr<nsIURI>  uri;
   628     nsXPIDLString title;
   630     entry->GetLayoutHistoryState(getter_AddRefs(layoutHistoryState));
   631     entry->GetURI(getter_AddRefs(uri));
   632     entry->GetTitle(getter_Copies(title));
   634 #if 0
   635     nsAutoCString url;
   636     if (uri)
   637      uri->GetSpec(url);
   639     printf("**** SH Transaction #%d, Entry = %x\n", index, entry.get());
   640     printf("\t\t URL = %s\n", url.get());
   642     printf("\t\t Title = %s\n", NS_LossyConvertUTF16toASCII(title).get());
   643     printf("\t\t layout History Data = %x\n", layoutHistoryState.get());
   644 #endif
   646     nsCOMPtr<nsISHTransaction> next;
   647     rv = txn->GetNext(getter_AddRefs(next));
   648     if (NS_SUCCEEDED(rv) && next) {
   649       txn = next;
   650       index++;
   651       continue;
   652     }
   653     else
   654       break;
   655   }
   657   return NS_OK;
   658 }
   659 #endif
   662 NS_IMETHODIMP
   663 nsSHistory::GetRootTransaction(nsISHTransaction ** aResult)
   664 {
   665   NS_ENSURE_ARG_POINTER(aResult);
   666   *aResult=mListRoot;
   667   NS_IF_ADDREF(*aResult);
   668   return NS_OK;
   669 }
   671 /* Get the max size of the history list */
   672 NS_IMETHODIMP
   673 nsSHistory::GetMaxLength(int32_t * aResult)
   674 {
   675   NS_ENSURE_ARG_POINTER(aResult);
   676   *aResult = gHistoryMaxSize;
   677   return NS_OK;
   678 }
   680 /* Set the max size of the history list */
   681 NS_IMETHODIMP
   682 nsSHistory::SetMaxLength(int32_t aMaxSize)
   683 {
   684   if (aMaxSize < 0)
   685     return NS_ERROR_ILLEGAL_VALUE;
   687   gHistoryMaxSize = aMaxSize;
   688   if (mLength > aMaxSize)
   689     PurgeHistory(mLength-aMaxSize);
   690   return NS_OK;
   691 }
   693 NS_IMETHODIMP
   694 nsSHistory::PurgeHistory(int32_t aEntries)
   695 {
   696   if (mLength <= 0 || aEntries <= 0)
   697     return NS_ERROR_FAILURE;
   699   aEntries = std::min(aEntries, mLength);
   701   bool purgeHistory = true;
   702   NOTIFY_LISTENERS_CANCELABLE(OnHistoryPurge, purgeHistory,
   703                               (aEntries, &purgeHistory));
   705   if (!purgeHistory) {
   706     // Listener asked us not to purge
   707     return NS_SUCCESS_LOSS_OF_INSIGNIFICANT_DATA;
   708   }
   710   int32_t cnt = 0;
   711   while (cnt < aEntries) {
   712     nsCOMPtr<nsISHTransaction> nextTxn;
   713     if (mListRoot) {
   714       mListRoot->GetNext(getter_AddRefs(nextTxn));
   715       mListRoot->SetNext(nullptr);
   716     }
   717     mListRoot = nextTxn;
   718     if (mListRoot) {
   719       mListRoot->SetPrev(nullptr);
   720     }
   721     cnt++;        
   722   }
   723   mLength -= cnt;
   724   mIndex -= cnt;
   726   // Now if we were not at the end of the history, mIndex could have
   727   // become far too negative.  If so, just set it to -1.
   728   if (mIndex < -1) {
   729     mIndex = -1;
   730   }
   732   if (mRootDocShell)
   733     mRootDocShell->HistoryPurged(cnt);
   735   return NS_OK;
   736 }
   739 NS_IMETHODIMP
   740 nsSHistory::AddSHistoryListener(nsISHistoryListener * aListener)
   741 {
   742   NS_ENSURE_ARG_POINTER(aListener);
   744   // Check if the listener supports Weak Reference. This is a must.
   745   // This listener functionality is used by embedders and we want to 
   746   // have the right ownership with who ever listens to SHistory
   747   nsWeakPtr listener = do_GetWeakReference(aListener);
   748   if (!listener) return NS_ERROR_FAILURE;
   750   return mListeners.AppendElementUnlessExists(listener) ?
   751     NS_OK : NS_ERROR_OUT_OF_MEMORY;
   752 }
   755 NS_IMETHODIMP
   756 nsSHistory::RemoveSHistoryListener(nsISHistoryListener * aListener)
   757 {
   758   // Make sure the listener that wants to be removed is the
   759   // one we have in store. 
   760   nsWeakPtr listener = do_GetWeakReference(aListener);
   761   mListeners.RemoveElement(listener);
   762   return NS_OK;
   763 }
   766 /* Replace an entry in the History list at a particular index.
   767  * Do not update index or count.
   768  */
   769 NS_IMETHODIMP
   770 nsSHistory::ReplaceEntry(int32_t aIndex, nsISHEntry * aReplaceEntry)
   771 {
   772   NS_ENSURE_ARG(aReplaceEntry);
   773   nsresult rv;
   774   nsCOMPtr<nsISHTransaction> currentTxn;
   776   if (!mListRoot) // Session History is not initialised.
   777     return NS_ERROR_FAILURE;
   779   rv = GetTransactionAtIndex(aIndex, getter_AddRefs(currentTxn));
   781   if(currentTxn)
   782   {
   783     NOTIFY_LISTENERS(OnHistoryReplaceEntry, (aIndex));
   785     // Set the replacement entry in the transaction
   786     rv = currentTxn->SetSHEntry(aReplaceEntry);
   787     rv = currentTxn->SetPersist(true);
   788   }
   789   return rv;
   790 }
   792 NS_IMETHODIMP
   793 nsSHistory::NotifyOnHistoryReload(nsIURI* aReloadURI, uint32_t aReloadFlags,
   794                                   bool* aCanReload)
   795 {
   796   NOTIFY_LISTENERS_CANCELABLE(OnHistoryReload, *aCanReload,
   797                               (aReloadURI, aReloadFlags, aCanReload));
   798   return NS_OK;
   799 }
   801 NS_IMETHODIMP
   802 nsSHistory::EvictOutOfRangeContentViewers(int32_t aIndex)
   803 {
   804   // Check our per SHistory object limit in the currently navigated SHistory
   805   EvictOutOfRangeWindowContentViewers(aIndex);
   806   // Check our total limit across all SHistory objects
   807   GloballyEvictContentViewers();
   808   return NS_OK;
   809 }
   811 NS_IMETHODIMP
   812 nsSHistory::EvictAllContentViewers()
   813 {
   814   // XXXbz we don't actually do a good job of evicting things as we should, so
   815   // we might have viewers quite far from mIndex.  So just evict everything.
   816   nsCOMPtr<nsISHTransaction> trans = mListRoot;
   817   while (trans) {
   818     EvictContentViewerForTransaction(trans);
   820     nsISHTransaction *temp = trans;
   821     temp->GetNext(getter_AddRefs(trans));
   822   }
   824   return NS_OK;
   825 }
   829 //*****************************************************************************
   830 //    nsSHistory: nsIWebNavigation
   831 //*****************************************************************************
   833 NS_IMETHODIMP
   834 nsSHistory::GetCanGoBack(bool * aCanGoBack)
   835 {
   836   NS_ENSURE_ARG_POINTER(aCanGoBack);
   837   *aCanGoBack = false;
   839   int32_t index = -1;
   840   NS_ENSURE_SUCCESS(GetIndex(&index), NS_ERROR_FAILURE);
   841   if(index > 0)
   842      *aCanGoBack = true;
   844   return NS_OK;
   845 }
   847 NS_IMETHODIMP
   848 nsSHistory::GetCanGoForward(bool * aCanGoForward)
   849 {
   850   NS_ENSURE_ARG_POINTER(aCanGoForward);
   851   *aCanGoForward = false;
   853   int32_t index = -1;
   854   int32_t count = -1;
   856   NS_ENSURE_SUCCESS(GetIndex(&index), NS_ERROR_FAILURE);
   857   NS_ENSURE_SUCCESS(GetCount(&count), NS_ERROR_FAILURE);
   859   if((index >= 0) && (index < (count - 1)))
   860     *aCanGoForward = true;
   862   return NS_OK;
   863 }
   865 NS_IMETHODIMP
   866 nsSHistory::GoBack()
   867 {
   868   bool canGoBack = false;
   870   GetCanGoBack(&canGoBack);
   871   if (!canGoBack)  // Can't go back
   872     return NS_ERROR_UNEXPECTED;
   873   return LoadEntry(mIndex-1, nsIDocShellLoadInfo::loadHistory, HIST_CMD_BACK);
   874 }
   877 NS_IMETHODIMP
   878 nsSHistory::GoForward()
   879 {
   880   bool canGoForward = false;
   882   GetCanGoForward(&canGoForward);
   883   if (!canGoForward)  // Can't go forward
   884     return NS_ERROR_UNEXPECTED;
   885   return LoadEntry(mIndex+1, nsIDocShellLoadInfo::loadHistory, HIST_CMD_FORWARD);
   886 }
   888 NS_IMETHODIMP
   889 nsSHistory::Reload(uint32_t aReloadFlags)
   890 {
   891   nsDocShellInfoLoadType loadType;
   892   if (aReloadFlags & nsIWebNavigation::LOAD_FLAGS_BYPASS_PROXY && 
   893       aReloadFlags & nsIWebNavigation::LOAD_FLAGS_BYPASS_CACHE)
   894   {
   895     loadType = nsIDocShellLoadInfo::loadReloadBypassProxyAndCache;
   896   }
   897   else if (aReloadFlags & nsIWebNavigation::LOAD_FLAGS_BYPASS_PROXY)
   898   {
   899     loadType = nsIDocShellLoadInfo::loadReloadBypassProxy;
   900   }
   901   else if (aReloadFlags & nsIWebNavigation::LOAD_FLAGS_BYPASS_CACHE)
   902   {
   903     loadType = nsIDocShellLoadInfo::loadReloadBypassCache;
   904   }
   905   else if (aReloadFlags & nsIWebNavigation::LOAD_FLAGS_CHARSET_CHANGE)
   906   {
   907     loadType = nsIDocShellLoadInfo::loadReloadCharsetChange;
   908   }
   909   else if (aReloadFlags & nsIWebNavigation::LOAD_FLAGS_ALLOW_MIXED_CONTENT)
   910   {
   911     loadType = nsIDocShellLoadInfo::loadReloadMixedContent;
   912   }
   913   else
   914   {
   915     loadType = nsIDocShellLoadInfo::loadReloadNormal;
   916   }
   918   // We are reloading. Send Reload notifications.
   919   // nsDocShellLoadFlagType is not public, where as nsIWebNavigation
   920   // is public. So send the reload notifications with the
   921   // nsIWebNavigation flags.
   922   bool canNavigate = true;
   923   nsCOMPtr<nsIURI> currentURI;
   924   GetCurrentURI(getter_AddRefs(currentURI));
   925   NOTIFY_LISTENERS_CANCELABLE(OnHistoryReload, canNavigate,
   926                               (currentURI, aReloadFlags, &canNavigate));
   927   if (!canNavigate)
   928     return NS_OK;
   930   return LoadEntry(mIndex, loadType, HIST_CMD_RELOAD);
   931 }
   933 NS_IMETHODIMP
   934 nsSHistory::ReloadCurrentEntry()
   935 {
   936   // Notify listeners
   937   bool canNavigate = true;
   938   nsCOMPtr<nsIURI> currentURI;
   939   GetCurrentURI(getter_AddRefs(currentURI));
   940   NOTIFY_LISTENERS_CANCELABLE(OnHistoryGotoIndex, canNavigate,
   941                               (mIndex, currentURI, &canNavigate));
   942   if (!canNavigate)
   943     return NS_OK;
   945   return LoadEntry(mIndex, nsIDocShellLoadInfo::loadHistory, HIST_CMD_RELOAD);
   946 }
   948 void
   949 nsSHistory::EvictOutOfRangeWindowContentViewers(int32_t aIndex)
   950 {
   951   // XXX rename method to EvictContentViewersExceptAroundIndex, or something.
   953   // We need to release all content viewers that are no longer in the range
   954   //
   955   //  aIndex - gHistoryMaxViewers to aIndex + gHistoryMaxViewers
   956   //
   957   // to ensure that this SHistory object isn't responsible for more than
   958   // gHistoryMaxViewers content viewers.  But our job is complicated by the
   959   // fact that two transactions which are related by either hash navigations or
   960   // history.pushState will have the same content viewer.
   961   //
   962   // To illustrate the issue, suppose gHistoryMaxViewers = 3 and we have four
   963   // linked transactions in our history.  Suppose we then add a new content
   964   // viewer and call into this function.  So the history looks like:
   965   //
   966   //   A A A A B
   967   //     +     *
   968   //
   969   // where the letters are content viewers and + and * denote the beginning and
   970   // end of the range aIndex +/- gHistoryMaxViewers.
   971   //
   972   // Although one copy of the content viewer A exists outside the range, we
   973   // don't want to evict A, because it has other copies in range!
   974   //
   975   // We therefore adjust our eviction strategy to read:
   976   //
   977   //   Evict each content viewer outside the range aIndex -/+
   978   //   gHistoryMaxViewers, unless that content viewer also appears within the
   979   //   range.
   980   //
   981   // (Note that it's entirely legal to have two copies of one content viewer
   982   // separated by a different content viewer -- call pushState twice, go back
   983   // once, and refresh -- so we can't rely on identical viewers only appearing
   984   // adjacent to one another.)
   986   if (aIndex < 0) {
   987     return;
   988   }
   989   NS_ENSURE_TRUE_VOID(aIndex < mLength);
   991   // Calculate the range that's safe from eviction.
   992   int32_t startSafeIndex = std::max(0, aIndex - gHistoryMaxViewers);
   993   int32_t endSafeIndex = std::min(mLength, aIndex + gHistoryMaxViewers);
   995   LOG(("EvictOutOfRangeWindowContentViewers(index=%d), "
   996        "mLength=%d. Safe range [%d, %d]",
   997        aIndex, mLength, startSafeIndex, endSafeIndex)); 
   999   // The content viewers in range aIndex -/+ gHistoryMaxViewers will not be
  1000   // evicted.  Collect a set of them so we don't accidentally evict one of them
  1001   // if it appears outside this range.
  1002   nsCOMArray<nsIContentViewer> safeViewers;
  1003   nsCOMPtr<nsISHTransaction> trans;
  1004   GetTransactionAtIndex(startSafeIndex, getter_AddRefs(trans));
  1005   for (int32_t i = startSafeIndex; trans && i <= endSafeIndex; i++) {
  1006     nsCOMPtr<nsIContentViewer> viewer = GetContentViewerForTransaction(trans);
  1007     safeViewers.AppendObject(viewer);
  1008     nsISHTransaction *temp = trans;
  1009     temp->GetNext(getter_AddRefs(trans));
  1012   // Walk the SHistory list and evict any content viewers that aren't safe.
  1013   GetTransactionAtIndex(0, getter_AddRefs(trans));
  1014   while (trans) {
  1015     nsCOMPtr<nsIContentViewer> viewer = GetContentViewerForTransaction(trans);
  1016     if (safeViewers.IndexOf(viewer) == -1) {
  1017       EvictContentViewerForTransaction(trans);
  1020     nsISHTransaction *temp = trans;
  1021     temp->GetNext(getter_AddRefs(trans));
  1025 namespace {
  1027 class TransactionAndDistance
  1029 public:
  1030   TransactionAndDistance(nsISHTransaction *aTrans, uint32_t aDist)
  1031     : mTransaction(aTrans)
  1032     , mDistance(aDist)
  1034     mViewer = GetContentViewerForTransaction(aTrans);
  1035     NS_ASSERTION(mViewer, "Transaction should have a content viewer");
  1037     nsCOMPtr<nsISHEntry> shentry;
  1038     mTransaction->GetSHEntry(getter_AddRefs(shentry));
  1040     nsCOMPtr<nsISHEntryInternal> shentryInternal = do_QueryInterface(shentry);
  1041     if (shentryInternal) {
  1042       shentryInternal->GetLastTouched(&mLastTouched);
  1043     } else {
  1044       NS_WARNING("Can't cast to nsISHEntryInternal?");
  1045       mLastTouched = 0;
  1049   bool operator<(const TransactionAndDistance &aOther) const
  1051     // Compare distances first, and fall back to last-accessed times.
  1052     if (aOther.mDistance != this->mDistance) {
  1053       return this->mDistance < aOther.mDistance;
  1056     return this->mLastTouched < aOther.mLastTouched;
  1059   bool operator==(const TransactionAndDistance &aOther) const
  1061     // This is a little silly; we need == so the default comaprator can be
  1062     // instantiated, but this function is never actually called when we sort
  1063     // the list of TransactionAndDistance objects.
  1064     return aOther.mDistance == this->mDistance &&
  1065            aOther.mLastTouched == this->mLastTouched;
  1068   nsCOMPtr<nsISHTransaction> mTransaction;
  1069   nsCOMPtr<nsIContentViewer> mViewer;
  1070   uint32_t mLastTouched;
  1071   int32_t mDistance;
  1072 };
  1074 } // anonymous namespace
  1076 //static
  1077 void
  1078 nsSHistory::GloballyEvictContentViewers()
  1080   // First, collect from each SHistory object the transactions which have a
  1081   // cached content viewer.  Associate with each transaction its distance from
  1082   // its SHistory's current index.
  1084   nsTArray<TransactionAndDistance> transactions;
  1086   nsSHistory *shist = static_cast<nsSHistory*>(PR_LIST_HEAD(&gSHistoryList));
  1087   while (shist != &gSHistoryList) {
  1089     // Maintain a list of the transactions which have viewers and belong to
  1090     // this particular shist object.  We'll add this list to the global list,
  1091     // |transactions|, eventually.
  1092     nsTArray<TransactionAndDistance> shTransactions;
  1094     // Content viewers are likely to exist only within shist->mIndex -/+
  1095     // gHistoryMaxViewers, so only search within that range.
  1096     //
  1097     // A content viewer might exist outside that range due to either:
  1098     //
  1099     //   * history.pushState or hash navigations, in which case a copy of the
  1100     //     content viewer should exist within the range, or
  1101     //
  1102     //   * bugs which cause us not to call nsSHistory::EvictContentViewers()
  1103     //     often enough.  Once we do call EvictContentViewers() for the
  1104     //     SHistory object in question, we'll do a full search of its history
  1105     //     and evict the out-of-range content viewers, so we don't bother here.
  1106     //
  1107     int32_t startIndex = std::max(0, shist->mIndex - gHistoryMaxViewers);
  1108     int32_t endIndex = std::min(shist->mLength - 1,
  1109                               shist->mIndex + gHistoryMaxViewers);
  1110     nsCOMPtr<nsISHTransaction> trans;
  1111     shist->GetTransactionAtIndex(startIndex, getter_AddRefs(trans));
  1112     for (int32_t i = startIndex; trans && i <= endIndex; i++) {
  1113       nsCOMPtr<nsIContentViewer> contentViewer =
  1114         GetContentViewerForTransaction(trans);
  1116       if (contentViewer) {
  1117         // Because one content viewer might belong to multiple SHEntries, we
  1118         // have to search through shTransactions to see if we already know
  1119         // about this content viewer.  If we find the viewer, update its
  1120         // distance from the SHistory's index and continue.
  1121         bool found = false;
  1122         for (uint32_t j = 0; j < shTransactions.Length(); j++) {
  1123           TransactionAndDistance &container = shTransactions[j];
  1124           if (container.mViewer == contentViewer) {
  1125             container.mDistance = std::min(container.mDistance, DeprecatedAbs(i - shist->mIndex));
  1126             found = true;
  1127             break;
  1131         // If we didn't find a TransactionAndDistance for this content viewer, make a new
  1132         // one.
  1133         if (!found) {
  1134           TransactionAndDistance container(trans, DeprecatedAbs(i - shist->mIndex));
  1135           shTransactions.AppendElement(container);
  1139       nsISHTransaction *temp = trans;
  1140       temp->GetNext(getter_AddRefs(trans));
  1143     // We've found all the transactions belonging to shist which have viewers.
  1144     // Add those transactions to our global list and move on.
  1145     transactions.AppendElements(shTransactions);
  1146     shist = static_cast<nsSHistory*>(PR_NEXT_LINK(shist));
  1149   // We now have collected all cached content viewers.  First check that we
  1150   // have enough that we actually need to evict some.
  1151   if ((int32_t)transactions.Length() <= sHistoryMaxTotalViewers) {
  1152     return;
  1155   // If we need to evict, sort our list of transactions and evict the largest
  1156   // ones.  (We could of course get better algorithmic complexity here by using
  1157   // a heap or something more clever.  But sHistoryMaxTotalViewers isn't large,
  1158   // so let's not worry about it.)
  1159   transactions.Sort();
  1161   for (int32_t i = transactions.Length() - 1;
  1162        i >= sHistoryMaxTotalViewers; --i) {
  1164     EvictContentViewerForTransaction(transactions[i].mTransaction);
  1169 nsresult
  1170 nsSHistory::EvictExpiredContentViewerForEntry(nsIBFCacheEntry *aEntry)
  1172   int32_t startIndex = std::max(0, mIndex - gHistoryMaxViewers);
  1173   int32_t endIndex = std::min(mLength - 1,
  1174                             mIndex + gHistoryMaxViewers);
  1175   nsCOMPtr<nsISHTransaction> trans;
  1176   GetTransactionAtIndex(startIndex, getter_AddRefs(trans));
  1178   int32_t i;
  1179   for (i = startIndex; trans && i <= endIndex; ++i) {
  1180     nsCOMPtr<nsISHEntry> entry;
  1181     trans->GetSHEntry(getter_AddRefs(entry));
  1183     // Does entry have the same BFCacheEntry as the argument to this method?
  1184     if (entry->HasBFCacheEntry(aEntry)) {
  1185       break;
  1188     nsISHTransaction *temp = trans;
  1189     temp->GetNext(getter_AddRefs(trans));
  1191   if (i > endIndex)
  1192     return NS_OK;
  1194   if (i == mIndex) {
  1195     NS_WARNING("How did the current SHEntry expire?");
  1196     return NS_OK;
  1199   EvictContentViewerForTransaction(trans);
  1201   return NS_OK;
  1204 // Evicts all content viewers in all history objects.  This is very
  1205 // inefficient, because it requires a linear search through all SHistory
  1206 // objects for each viewer to be evicted.  However, this method is called
  1207 // infrequently -- only when the disk or memory cache is cleared.
  1209 //static
  1210 void
  1211 nsSHistory::GloballyEvictAllContentViewers()
  1213   int32_t maxViewers = sHistoryMaxTotalViewers;
  1214   sHistoryMaxTotalViewers = 0;
  1215   GloballyEvictContentViewers();
  1216   sHistoryMaxTotalViewers = maxViewers;
  1219 void GetDynamicChildren(nsISHContainer* aContainer,
  1220                         nsTArray<uint64_t>& aDocshellIDs,
  1221                         bool aOnlyTopLevelDynamic)
  1223   int32_t count = 0;
  1224   aContainer->GetChildCount(&count);
  1225   for (int32_t i = 0; i < count; ++i) {
  1226     nsCOMPtr<nsISHEntry> child;
  1227     aContainer->GetChildAt(i, getter_AddRefs(child));
  1228     if (child) {
  1229       bool dynAdded = false;
  1230       child->IsDynamicallyAdded(&dynAdded);
  1231       if (dynAdded) {
  1232         uint64_t docshellID = 0;
  1233         child->GetDocshellID(&docshellID);
  1234         aDocshellIDs.AppendElement(docshellID);
  1236       if (!dynAdded || !aOnlyTopLevelDynamic) {
  1237         nsCOMPtr<nsISHContainer> childAsContainer = do_QueryInterface(child);
  1238         if (childAsContainer) {
  1239           GetDynamicChildren(childAsContainer, aDocshellIDs,
  1240                              aOnlyTopLevelDynamic);
  1247 bool
  1248 RemoveFromSessionHistoryContainer(nsISHContainer* aContainer,
  1249                                   nsTArray<uint64_t>& aDocshellIDs)
  1251   nsCOMPtr<nsISHEntry> root = do_QueryInterface(aContainer);
  1252   NS_ENSURE_TRUE(root, false);
  1254   bool didRemove = false;
  1255   int32_t childCount = 0;
  1256   aContainer->GetChildCount(&childCount);
  1257   for (int32_t i = childCount - 1; i >= 0; --i) {
  1258     nsCOMPtr<nsISHEntry> child;
  1259     aContainer->GetChildAt(i, getter_AddRefs(child));
  1260     if (child) {
  1261       uint64_t docshelldID = 0;
  1262       child->GetDocshellID(&docshelldID);
  1263       if (aDocshellIDs.Contains(docshelldID)) {
  1264         didRemove = true;
  1265         aContainer->RemoveChild(child);
  1266       } else {
  1267         nsCOMPtr<nsISHContainer> container = do_QueryInterface(child);
  1268         if (container) {
  1269           bool childRemoved =
  1270             RemoveFromSessionHistoryContainer(container, aDocshellIDs);
  1271           if (childRemoved) {
  1272             didRemove = true;
  1278   return didRemove;
  1281 bool RemoveChildEntries(nsISHistory* aHistory, int32_t aIndex,
  1282                           nsTArray<uint64_t>& aEntryIDs)
  1284   nsCOMPtr<nsISHEntry> rootHE;
  1285   aHistory->GetEntryAtIndex(aIndex, false, getter_AddRefs(rootHE));
  1286   nsCOMPtr<nsISHContainer> root = do_QueryInterface(rootHE);
  1287   return root ? RemoveFromSessionHistoryContainer(root, aEntryIDs) : false;
  1290 bool IsSameTree(nsISHEntry* aEntry1, nsISHEntry* aEntry2)
  1292   if (!aEntry1 && !aEntry2) {
  1293     return true;
  1295   if ((!aEntry1 && aEntry2) || (aEntry1 && !aEntry2)) {
  1296     return false;
  1298   uint32_t id1, id2;
  1299   aEntry1->GetID(&id1);
  1300   aEntry2->GetID(&id2);
  1301   if (id1 != id2) {
  1302     return false;
  1305   nsCOMPtr<nsISHContainer> container1 = do_QueryInterface(aEntry1);
  1306   nsCOMPtr<nsISHContainer> container2 = do_QueryInterface(aEntry2);
  1307   int32_t count1, count2;
  1308   container1->GetChildCount(&count1);
  1309   container2->GetChildCount(&count2);
  1310   // We allow null entries in the end of the child list.
  1311   int32_t count = std::max(count1, count2);
  1312   for (int32_t i = 0; i < count; ++i) {
  1313     nsCOMPtr<nsISHEntry> child1, child2;
  1314     container1->GetChildAt(i, getter_AddRefs(child1));
  1315     container2->GetChildAt(i, getter_AddRefs(child2));
  1316     if (!IsSameTree(child1, child2)) {
  1317       return false;
  1321   return true;
  1324 bool
  1325 nsSHistory::RemoveDuplicate(int32_t aIndex, bool aKeepNext)
  1327   NS_ASSERTION(aIndex >= 0, "aIndex must be >= 0!");
  1328   NS_ASSERTION(aIndex != 0 || aKeepNext,
  1329                "If we're removing index 0 we must be keeping the next");
  1330   NS_ASSERTION(aIndex != mIndex, "Shouldn't remove mIndex!");
  1331   int32_t compareIndex = aKeepNext ? aIndex + 1 : aIndex - 1;
  1332   nsCOMPtr<nsISHEntry> root1, root2;
  1333   GetEntryAtIndex(aIndex, false, getter_AddRefs(root1));
  1334   GetEntryAtIndex(compareIndex, false, getter_AddRefs(root2));
  1335   if (IsSameTree(root1, root2)) {
  1336     nsCOMPtr<nsISHTransaction> txToRemove, txToKeep, txNext, txPrev;
  1337     GetTransactionAtIndex(aIndex, getter_AddRefs(txToRemove));
  1338     GetTransactionAtIndex(compareIndex, getter_AddRefs(txToKeep));
  1339     NS_ENSURE_TRUE(txToRemove, false);
  1340     NS_ENSURE_TRUE(txToKeep, false);
  1341     txToRemove->GetNext(getter_AddRefs(txNext));
  1342     txToRemove->GetPrev(getter_AddRefs(txPrev));
  1343     txToRemove->SetNext(nullptr);
  1344     txToRemove->SetPrev(nullptr);
  1345     if (aKeepNext) {
  1346       if (txPrev) {
  1347         txPrev->SetNext(txToKeep);
  1348       } else {
  1349         txToKeep->SetPrev(nullptr);
  1351     } else {
  1352       txToKeep->SetNext(txNext);
  1355     if (aIndex == 0 && aKeepNext) {
  1356       NS_ASSERTION(txToRemove == mListRoot,
  1357                    "Transaction at index 0 should be mListRoot!");
  1358       // We're removing the very first session history transaction!
  1359       mListRoot = txToKeep;
  1361     if (mRootDocShell) {
  1362       static_cast<nsDocShell*>(mRootDocShell)->HistoryTransactionRemoved(aIndex);
  1365     // Adjust our indices to reflect the removed transaction
  1366     if (mIndex > aIndex) {
  1367       mIndex = mIndex - 1;
  1370     // NB: If the transaction we are removing is the transaction currently
  1371     // being navigated to (mRequestedIndex) then we adjust the index
  1372     // only if we're not keeping the next entry (because if we are keeping
  1373     // the next entry (because the current is a duplicate of the next), then
  1374     // that entry slides into the spot that we're currently pointing to.
  1375     // We don't do this adjustment for mIndex because mIndex cannot equal
  1376     // aIndex.
  1378     // NB: We don't need to guard on mRequestedIndex being nonzero here,
  1379     // because either they're strictly greater than aIndex which is at least
  1380     // zero, or they are equal to aIndex in which case aKeepNext must be true
  1381     // if aIndex is zero.
  1382     if (mRequestedIndex > aIndex || (mRequestedIndex == aIndex && !aKeepNext)) {
  1383       mRequestedIndex = mRequestedIndex - 1;
  1385     --mLength;
  1386     return true;
  1388   return false;
  1391 NS_IMETHODIMP_(void)
  1392 nsSHistory::RemoveEntries(nsTArray<uint64_t>& aIDs, int32_t aStartIndex)
  1394   int32_t index = aStartIndex;
  1395   while(index >= 0 && RemoveChildEntries(this, --index, aIDs));
  1396   int32_t minIndex = index;
  1397   index = aStartIndex;
  1398   while(index >= 0 && RemoveChildEntries(this, index++, aIDs));
  1400   // We need to remove duplicate nsSHEntry trees.
  1401   bool didRemove = false;
  1402   while (index > minIndex) {
  1403     if (index != mIndex) {
  1404       didRemove = RemoveDuplicate(index, index < mIndex) || didRemove;
  1406     --index;
  1408   if (didRemove && mRootDocShell) {
  1409     nsRefPtr<nsIRunnable> ev =
  1410       NS_NewRunnableMethod(static_cast<nsDocShell*>(mRootDocShell),
  1411                            &nsDocShell::FireDummyOnLocationChange);
  1412     NS_DispatchToCurrentThread(ev);
  1416 void
  1417 nsSHistory::RemoveDynEntries(int32_t aOldIndex, int32_t aNewIndex)
  1419   // Search for the entries which are in the current index,
  1420   // but not in the new one.
  1421   nsCOMPtr<nsISHEntry> originalSH;
  1422   GetEntryAtIndex(aOldIndex, false, getter_AddRefs(originalSH));
  1423   nsCOMPtr<nsISHContainer> originalContainer = do_QueryInterface(originalSH);
  1424   nsAutoTArray<uint64_t, 16> toBeRemovedEntries;
  1425   if (originalContainer) {
  1426     nsTArray<uint64_t> originalDynDocShellIDs;
  1427     GetDynamicChildren(originalContainer, originalDynDocShellIDs, true);
  1428     if (originalDynDocShellIDs.Length()) {
  1429       nsCOMPtr<nsISHEntry> currentSH;
  1430       GetEntryAtIndex(aNewIndex, false, getter_AddRefs(currentSH));
  1431       nsCOMPtr<nsISHContainer> newContainer = do_QueryInterface(currentSH);
  1432       if (newContainer) {
  1433         nsTArray<uint64_t> newDynDocShellIDs;
  1434         GetDynamicChildren(newContainer, newDynDocShellIDs, false);
  1435         for (uint32_t i = 0; i < originalDynDocShellIDs.Length(); ++i) {
  1436           if (!newDynDocShellIDs.Contains(originalDynDocShellIDs[i])) {
  1437             toBeRemovedEntries.AppendElement(originalDynDocShellIDs[i]);
  1443   if (toBeRemovedEntries.Length()) {
  1444     RemoveEntries(toBeRemovedEntries, aOldIndex);
  1448 NS_IMETHODIMP
  1449 nsSHistory::UpdateIndex()
  1451   // Update the actual index with the right value. 
  1452   if (mIndex != mRequestedIndex && mRequestedIndex != -1) {
  1453     RemoveDynEntries(mIndex, mRequestedIndex);
  1454     mIndex = mRequestedIndex;
  1457   mRequestedIndex = -1;
  1458   return NS_OK;
  1461 NS_IMETHODIMP
  1462 nsSHistory::Stop(uint32_t aStopFlags)
  1464   //Not implemented
  1465   return NS_OK;
  1469 NS_IMETHODIMP
  1470 nsSHistory::GetDocument(nsIDOMDocument** aDocument)
  1472   // Not implemented
  1473   return NS_OK;
  1477 NS_IMETHODIMP
  1478 nsSHistory::GetCurrentURI(nsIURI** aResultURI)
  1480   NS_ENSURE_ARG_POINTER(aResultURI);
  1481   nsresult rv;
  1483   nsCOMPtr<nsISHEntry> currentEntry;
  1484   rv = GetEntryAtIndex(mIndex, false, getter_AddRefs(currentEntry));
  1485   if (NS_FAILED(rv) && !currentEntry) return rv;
  1486   rv = currentEntry->GetURI(aResultURI);
  1487   return rv;
  1491 NS_IMETHODIMP
  1492 nsSHistory::GetReferringURI(nsIURI** aURI)
  1494   *aURI = nullptr;
  1495   // Not implemented
  1496   return NS_OK;
  1500 NS_IMETHODIMP
  1501 nsSHistory::SetSessionHistory(nsISHistory* aSessionHistory)
  1503   // Not implemented
  1504   return NS_OK;
  1508 NS_IMETHODIMP
  1509 nsSHistory::GetSessionHistory(nsISHistory** aSessionHistory)
  1511   // Not implemented
  1512   return NS_OK;
  1515 NS_IMETHODIMP
  1516 nsSHistory::LoadURIWithBase(const char16_t* aURI,
  1517                             uint32_t aLoadFlags,
  1518                             nsIURI* aReferringURI,
  1519                             nsIInputStream* aPostStream,
  1520                             nsIInputStream* aExtraHeaderStream,
  1521                             nsIURI* aBaseURI)
  1523   return NS_OK;
  1526 NS_IMETHODIMP
  1527 nsSHistory::LoadURI(const char16_t* aURI,
  1528                     uint32_t aLoadFlags,
  1529                     nsIURI* aReferringURI,
  1530                     nsIInputStream* aPostStream,
  1531                     nsIInputStream* aExtraHeaderStream)
  1533   return NS_OK;
  1536 NS_IMETHODIMP
  1537 nsSHistory::GotoIndex(int32_t aIndex)
  1539   return LoadEntry(aIndex, nsIDocShellLoadInfo::loadHistory, HIST_CMD_GOTOINDEX);
  1542 nsresult
  1543 nsSHistory::LoadNextPossibleEntry(int32_t aNewIndex, long aLoadType, uint32_t aHistCmd)
  1545   mRequestedIndex = -1;
  1546   if (aNewIndex < mIndex) {
  1547     return LoadEntry(aNewIndex - 1, aLoadType, aHistCmd);
  1549   if (aNewIndex > mIndex) {
  1550     return LoadEntry(aNewIndex + 1, aLoadType, aHistCmd);
  1552   return NS_ERROR_FAILURE;
  1555 NS_IMETHODIMP
  1556 nsSHistory::LoadEntry(int32_t aIndex, long aLoadType, uint32_t aHistCmd)
  1558   nsCOMPtr<nsIDocShell> docShell;
  1559   // Keep note of requested history index in mRequestedIndex.
  1560   mRequestedIndex = aIndex;
  1562   nsCOMPtr<nsISHEntry> prevEntry;
  1563   GetEntryAtIndex(mIndex, false, getter_AddRefs(prevEntry));
  1565   nsCOMPtr<nsISHEntry> nextEntry;
  1566   GetEntryAtIndex(mRequestedIndex, false, getter_AddRefs(nextEntry));
  1567   if (!nextEntry || !prevEntry) {
  1568     mRequestedIndex = -1;
  1569     return NS_ERROR_FAILURE;
  1572   // Remember that this entry is getting loaded at this point in the sequence
  1573   nsCOMPtr<nsISHEntryInternal> entryInternal = do_QueryInterface(nextEntry);
  1575   if (entryInternal) {
  1576     entryInternal->SetLastTouched(++gTouchCounter);
  1579   // Send appropriate listener notifications
  1580   bool canNavigate = true;
  1581   // Get the uri for the entry we are about to visit
  1582   nsCOMPtr<nsIURI> nextURI;
  1583   nextEntry->GetURI(getter_AddRefs(nextURI));
  1585   if (aHistCmd == HIST_CMD_BACK) {
  1586     // We are going back one entry. Send GoBack notifications
  1587     NOTIFY_LISTENERS_CANCELABLE(OnHistoryGoBack, canNavigate,
  1588                                 (nextURI, &canNavigate));
  1589   } else if (aHistCmd == HIST_CMD_FORWARD) {
  1590     // We are going forward. Send GoForward notification
  1591     NOTIFY_LISTENERS_CANCELABLE(OnHistoryGoForward, canNavigate,
  1592                                 (nextURI, &canNavigate));
  1593   } else if (aHistCmd == HIST_CMD_GOTOINDEX) {
  1594     // We are going somewhere else. This is not reload either
  1595     NOTIFY_LISTENERS_CANCELABLE(OnHistoryGotoIndex, canNavigate,
  1596                                 (aIndex, nextURI, &canNavigate));
  1599   if (!canNavigate) {
  1600     // If the listener asked us not to proceed with 
  1601     // the operation, simply return.    
  1602     mRequestedIndex = -1;
  1603     return NS_OK;  // XXX Maybe I can return some other error code?
  1606   nsCOMPtr<nsIURI> nexturi;
  1607   int32_t pCount=0, nCount=0;
  1608   nsCOMPtr<nsISHContainer> prevAsContainer(do_QueryInterface(prevEntry));
  1609   nsCOMPtr<nsISHContainer> nextAsContainer(do_QueryInterface(nextEntry));
  1610   if (prevAsContainer && nextAsContainer) {
  1611     prevAsContainer->GetChildCount(&pCount);
  1612     nextAsContainer->GetChildCount(&nCount);
  1615   nsCOMPtr<nsIDocShellLoadInfo> loadInfo;
  1616   if (mRequestedIndex == mIndex) {
  1617     // Possibly a reload case 
  1618     docShell = mRootDocShell;
  1620   else {
  1621     // Going back or forward.
  1622     if ((pCount > 0) && (nCount > 0)) {
  1623       /* THis is a subframe navigation. Go find 
  1624        * the docshell in which load should happen
  1625        */
  1626       bool frameFound = false;
  1627       nsresult rv = CompareFrames(prevEntry, nextEntry, mRootDocShell, aLoadType, &frameFound);
  1628       if (!frameFound) {
  1629         // We did not successfully find the subframe in which
  1630         // the new url was to be loaded. Go further in the history.
  1631         return LoadNextPossibleEntry(aIndex, aLoadType, aHistCmd);
  1633       return rv;
  1634     }   // (pCount >0)
  1635     else {
  1636       // Loading top level page.
  1637       uint32_t prevID = 0;
  1638       uint32_t nextID = 0;
  1639       prevEntry->GetID(&prevID);
  1640       nextEntry->GetID(&nextID);
  1641       if (prevID == nextID) {
  1642         // Try harder to find something new to load.
  1643         // This may happen for example if some page removed iframes dynamically.
  1644         return LoadNextPossibleEntry(aIndex, aLoadType, aHistCmd);
  1646       docShell = mRootDocShell;
  1650   if (!docShell) {
  1651     // we did not successfully go to the proper index.
  1652     // return error.
  1653     mRequestedIndex = -1;
  1654     return NS_ERROR_FAILURE;
  1657   // Start the load on the appropriate docshell
  1658   return InitiateLoad(nextEntry, docShell, aLoadType);
  1661 nsresult
  1662 nsSHistory::CompareFrames(nsISHEntry * aPrevEntry, nsISHEntry * aNextEntry, nsIDocShell * aParent, long aLoadType, bool * aIsFrameFound)
  1664   if (!aPrevEntry || !aNextEntry || !aParent)
  1665     return NS_ERROR_FAILURE;
  1667   // We should be comparing only entries which were created for the
  1668   // same docshell. This is here to just prevent anything strange happening.
  1669   // This check could be possibly an assertion.
  1670   uint64_t prevdID, nextdID;
  1671   aPrevEntry->GetDocshellID(&prevdID);
  1672   aNextEntry->GetDocshellID(&nextdID);
  1673   NS_ENSURE_STATE(prevdID == nextdID);
  1675   nsresult result = NS_OK;
  1676   uint32_t prevID, nextID;
  1678   aPrevEntry->GetID(&prevID);
  1679   aNextEntry->GetID(&nextID);
  1681   // Check the IDs to verify if the pages are different.
  1682   if (prevID != nextID) {
  1683     if (aIsFrameFound)
  1684       *aIsFrameFound = true;
  1685     // Set the Subframe flag of the entry to indicate that
  1686     // it is subframe navigation        
  1687     aNextEntry->SetIsSubFrame(true);
  1688     InitiateLoad(aNextEntry, aParent, aLoadType);
  1689     return NS_OK;
  1692   /* The root entries are the same, so compare any child frames */
  1693   int32_t pcnt=0, ncnt=0, dsCount=0;
  1694   nsCOMPtr<nsISHContainer>  prevContainer(do_QueryInterface(aPrevEntry));
  1695   nsCOMPtr<nsISHContainer>  nextContainer(do_QueryInterface(aNextEntry));
  1697   if (!aParent)
  1698     return NS_ERROR_FAILURE;
  1699   if (!prevContainer || !nextContainer)
  1700     return NS_ERROR_FAILURE;
  1702   prevContainer->GetChildCount(&pcnt);
  1703   nextContainer->GetChildCount(&ncnt);
  1704   aParent->GetChildCount(&dsCount);
  1706   // Create an array for child docshells.
  1707   nsCOMArray<nsIDocShell> docshells;
  1708   for (int32_t i = 0; i < dsCount; ++i) {
  1709     nsCOMPtr<nsIDocShellTreeItem> treeItem;
  1710     aParent->GetChildAt(i, getter_AddRefs(treeItem));
  1711     nsCOMPtr<nsIDocShell> shell = do_QueryInterface(treeItem);
  1712     if (shell) {
  1713       docshells.AppendObject(shell);
  1717   // Search for something to load next.
  1718   for (int32_t i = 0; i < ncnt; ++i) {
  1719     // First get an entry which may cause a new page to be loaded.
  1720     nsCOMPtr<nsISHEntry> nChild;
  1721     nextContainer->GetChildAt(i, getter_AddRefs(nChild));
  1722     if (!nChild) {
  1723       continue;
  1725     uint64_t docshellID = 0;
  1726     nChild->GetDocshellID(&docshellID);
  1728     // Then find the associated docshell.
  1729     nsIDocShell* dsChild = nullptr;
  1730     int32_t count = docshells.Count();
  1731     for (int32_t j = 0; j < count; ++j) {
  1732       uint64_t shellID = 0;
  1733       nsIDocShell* shell = docshells[j];
  1734       shell->GetHistoryID(&shellID);
  1735       if (shellID == docshellID) {
  1736         dsChild = shell;
  1737         break;
  1740     if (!dsChild) {
  1741       continue;
  1744     // Then look at the previous entries to see if there was
  1745     // an entry for the docshell.
  1746     nsCOMPtr<nsISHEntry> pChild;
  1747     for (int32_t k = 0; k < pcnt; ++k) {
  1748       nsCOMPtr<nsISHEntry> child;
  1749       prevContainer->GetChildAt(k, getter_AddRefs(child));
  1750       if (child) {
  1751         uint64_t dID = 0;
  1752         child->GetDocshellID(&dID);
  1753         if (dID == docshellID) {
  1754           pChild = child;
  1755           break;
  1760     // Finally recursively call this method.
  1761     // This will either load a new page to shell or some subshell or
  1762     // do nothing.
  1763     CompareFrames(pChild, nChild, dsChild, aLoadType, aIsFrameFound);
  1765   return result;
  1769 nsresult 
  1770 nsSHistory::InitiateLoad(nsISHEntry * aFrameEntry, nsIDocShell * aFrameDS, long aLoadType)
  1772   NS_ENSURE_STATE(aFrameDS && aFrameEntry);
  1774   nsCOMPtr<nsIDocShellLoadInfo> loadInfo;
  1776   /* Set the loadType in the SHEntry too to  what was passed on.
  1777    * This will be passed on to child subframes later in nsDocShell,
  1778    * so that proper loadType is maintained through out a frameset
  1779    */
  1780   aFrameEntry->SetLoadType(aLoadType);    
  1781   aFrameDS->CreateLoadInfo (getter_AddRefs(loadInfo));
  1783   loadInfo->SetLoadType(aLoadType);
  1784   loadInfo->SetSHEntry(aFrameEntry);
  1786   nsCOMPtr<nsIURI> nextURI;
  1787   aFrameEntry->GetURI(getter_AddRefs(nextURI));
  1788   // Time   to initiate a document load
  1789   return aFrameDS->LoadURI(nextURI, loadInfo, nsIWebNavigation::LOAD_FLAGS_NONE, false);
  1795 NS_IMETHODIMP
  1796 nsSHistory::SetRootDocShell(nsIDocShell * aDocShell)
  1798   mRootDocShell = aDocShell;
  1799   return NS_OK;
  1802 NS_IMETHODIMP
  1803 nsSHistory::GetRootDocShell(nsIDocShell ** aDocShell)
  1805   NS_ENSURE_ARG_POINTER(aDocShell);
  1807   *aDocShell = mRootDocShell;
  1808   //Not refcounted. May this method should not be available for public
  1809   // NS_IF_ADDREF(*aDocShell);
  1810   return NS_OK;
  1814 NS_IMETHODIMP
  1815 nsSHistory::GetSHistoryEnumerator(nsISimpleEnumerator** aEnumerator)
  1817   nsresult status = NS_OK;
  1819   NS_ENSURE_ARG_POINTER(aEnumerator);
  1820   nsSHEnumerator * iterator = new nsSHEnumerator(this);
  1821   if (iterator && NS_FAILED(status = CallQueryInterface(iterator, aEnumerator)))
  1822     delete iterator;
  1823   return status;
  1827 //*****************************************************************************
  1828 //***    nsSHEnumerator: Object Management
  1829 //*****************************************************************************
  1831 nsSHEnumerator::nsSHEnumerator(nsSHistory * aSHistory):mIndex(-1)
  1833   mSHistory = aSHistory;
  1836 nsSHEnumerator::~nsSHEnumerator()
  1838   mSHistory = nullptr;
  1841 NS_IMPL_ISUPPORTS(nsSHEnumerator, nsISimpleEnumerator)
  1843 NS_IMETHODIMP
  1844 nsSHEnumerator::HasMoreElements(bool * aReturn)
  1846   int32_t cnt;
  1847   *aReturn = false;
  1848   mSHistory->GetCount(&cnt);
  1849   if (mIndex >= -1 && mIndex < (cnt-1) ) { 
  1850     *aReturn = true;
  1852   return NS_OK;
  1856 NS_IMETHODIMP 
  1857 nsSHEnumerator::GetNext(nsISupports **aItem)
  1859   NS_ENSURE_ARG_POINTER(aItem);
  1860   int32_t cnt= 0;
  1862   nsresult  result = NS_ERROR_FAILURE;
  1863   mSHistory->GetCount(&cnt);
  1864   if (mIndex < (cnt-1)) {
  1865     mIndex++;
  1866     nsCOMPtr<nsISHEntry> hEntry;
  1867     result = mSHistory->GetEntryAtIndex(mIndex, false, getter_AddRefs(hEntry));
  1868     if (hEntry)
  1869       result = CallQueryInterface(hEntry, aItem);
  1871   return result;

mercurial