tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

nsSHistory.cpp (95162B)


      1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
      2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
      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/. */
      6 
      7 #include "nsSHistory.h"
      8 
      9 #include <algorithm>
     10 
     11 #include "nsContentUtils.h"
     12 #include "nsCOMArray.h"
     13 #include "nsComponentManagerUtils.h"
     14 #include "nsDocShell.h"
     15 #include "nsFrameLoaderOwner.h"
     16 #include "nsHashKeys.h"
     17 #include "nsIDocShell.h"
     18 #include "nsIDocumentViewer.h"
     19 #include "nsDocShellLoadState.h"
     20 #include "nsIDocShellTreeItem.h"
     21 #include "nsILayoutHistoryState.h"
     22 #include "nsIObserverService.h"
     23 #include "nsISHEntry.h"
     24 #include "nsISHistoryListener.h"
     25 #include "nsIURI.h"
     26 #include "nsIXULRuntime.h"
     27 #include "nsNetUtil.h"
     28 #include "nsTHashMap.h"
     29 #include "nsSHEntry.h"
     30 #include "SessionHistoryEntry.h"
     31 #include "nsTArray.h"
     32 #include "prsystem.h"
     33 
     34 #include "mozilla/Attributes.h"
     35 #include "mozilla/dom/BrowsingContextGroup.h"
     36 #include "mozilla/dom/BrowserParent.h"
     37 #include "mozilla/dom/CanonicalBrowsingContext.h"
     38 #include "mozilla/dom/ContentParent.h"
     39 #include "mozilla/dom/Element.h"
     40 #include "mozilla/dom/EntryList.h"
     41 #include "mozilla/dom/Navigation.h"
     42 #include "mozilla/dom/RemoteWebProgressRequest.h"
     43 #include "mozilla/dom/WindowGlobalParent.h"
     44 #include "mozilla/LinkedList.h"
     45 #include "mozilla/MathAlgorithms.h"
     46 #include "mozilla/Preferences.h"
     47 #include "mozilla/ProcessPriorityManager.h"
     48 #include "mozilla/Services.h"
     49 #include "mozilla/StaticPrefs_fission.h"
     50 #include "mozilla/StaticPtr.h"
     51 #include "mozilla/dom/CanonicalBrowsingContext.h"
     52 #include "nsIWebNavigation.h"
     53 #include "nsDocShellLoadTypes.h"
     54 #include "base/process.h"
     55 
     56 using namespace mozilla;
     57 using namespace mozilla::dom;
     58 
     59 #define PREF_SHISTORY_SIZE "browser.sessionhistory.max_entries"
     60 #define PREF_SHISTORY_MAX_TOTAL_VIEWERS \
     61  "browser.sessionhistory.max_total_viewers"
     62 #define CONTENT_VIEWER_TIMEOUT_SECONDS \
     63  "browser.sessionhistory.contentViewerTimeout"
     64 // Observe fission.bfcacheInParent so that BFCache can be enabled/disabled when
     65 // the pref is changed.
     66 #define PREF_FISSION_BFCACHEINPARENT "fission.bfcacheInParent"
     67 
     68 // Default this to time out unused content viewers after 30 minutes
     69 #define CONTENT_VIEWER_TIMEOUT_SECONDS_DEFAULT (30 * 60)
     70 
     71 static constexpr const char* kObservedPrefs[] = {
     72    PREF_SHISTORY_SIZE, PREF_SHISTORY_MAX_TOTAL_VIEWERS,
     73    PREF_FISSION_BFCACHEINPARENT, nullptr};
     74 
     75 static int32_t gHistoryMaxSize = 50;
     76 
     77 // List of all SHistory objects, used for content viewer cache eviction.
     78 // When being destroyed, this helper removes everything from the list to avoid
     79 // assertions when we leak.
     80 struct ListHelper {
     81 #ifdef DEBUG
     82  ~ListHelper() { mList.clear(); }
     83 #endif  // DEBUG
     84 
     85  LinkedList<nsSHistory> mList;
     86 };
     87 
     88 MOZ_RUNINIT static ListHelper gSHistoryList;
     89 // Max viewers allowed total, across all SHistory objects - negative default
     90 // means we will calculate how many viewers to cache based on total memory
     91 int32_t nsSHistory::sHistoryMaxTotalViewers = -1;
     92 
     93 // A counter that is used to be able to know the order in which
     94 // entries were touched, so that we can evict older entries first.
     95 static uint32_t gTouchCounter = 0;
     96 
     97 extern mozilla::LazyLogModule gSHLog;
     98 
     99 LazyLogModule gSHistoryLog("nsSHistory");
    100 
    101 #define LOG(format) MOZ_LOG(gSHistoryLog, mozilla::LogLevel::Debug, format)
    102 
    103 extern mozilla::LazyLogModule gPageCacheLog;
    104 extern mozilla::LazyLogModule gNavigationAPILog;
    105 extern mozilla::LazyLogModule gSHIPBFCacheLog;
    106 
    107 // This macro makes it easier to print a log message which includes a URI's
    108 // spec.  Example use:
    109 //
    110 //  nsIURI *uri = [...];
    111 //  LOG_SPEC(("The URI is %s.", _spec), uri);
    112 //
    113 #define LOG_SPEC(format, uri)                        \
    114  PR_BEGIN_MACRO                                     \
    115  if (MOZ_LOG_TEST(gSHistoryLog, LogLevel::Debug)) { \
    116    nsAutoCString _specStr("(null)"_ns);             \
    117    if (uri) {                                       \
    118      _specStr = uri->GetSpecOrDefault();            \
    119    }                                                \
    120    const char* _spec = _specStr.get();              \
    121    LOG(format);                                     \
    122  }                                                  \
    123  PR_END_MACRO
    124 
    125 // This macro makes it easy to log a message including an SHEntry's URI.
    126 // For example:
    127 //
    128 //  nsCOMPtr<nsISHEntry> shentry = [...];
    129 //  LOG_SHENTRY_SPEC(("shentry %p has uri %s.", shentry.get(), _spec), shentry);
    130 //
    131 #define LOG_SHENTRY_SPEC(format, shentry)            \
    132  PR_BEGIN_MACRO                                     \
    133  if (MOZ_LOG_TEST(gSHistoryLog, LogLevel::Debug)) { \
    134    nsCOMPtr<nsIURI> uri = shentry->GetURI();        \
    135    LOG_SPEC(format, uri);                           \
    136  }                                                  \
    137  PR_END_MACRO
    138 
    139 // Calls a F on all registered session history listeners.
    140 template <typename F>
    141 static void NotifyListeners(nsAutoTObserverArray<nsWeakPtr, 2>& aListeners,
    142                            F&& f) {
    143  for (const nsWeakPtr& weakPtr : aListeners.EndLimitedRange()) {
    144    nsCOMPtr<nsISHistoryListener> listener = do_QueryReferent(weakPtr);
    145    if (listener) {
    146      f(listener);
    147    }
    148  }
    149 }
    150 
    151 class MOZ_STACK_CLASS SHistoryChangeNotifier {
    152 public:
    153  explicit SHistoryChangeNotifier(nsSHistory* aHistory) {
    154    // If we're already in an update, the outermost change notifier will
    155    // update browsing context in the destructor.
    156    if (!aHistory->HasOngoingUpdate()) {
    157      aHistory->SetHasOngoingUpdate(true);
    158      mSHistory = aHistory;
    159    }
    160  }
    161 
    162  ~SHistoryChangeNotifier() {
    163    if (mSHistory) {
    164      MOZ_ASSERT(mSHistory->HasOngoingUpdate());
    165      mSHistory->SetHasOngoingUpdate(false);
    166 
    167      RefPtr<BrowsingContext> rootBC = mSHistory->GetBrowsingContext();
    168      if (mozilla::SessionHistoryInParent() && rootBC) {
    169        rootBC->Canonical()->HistoryCommitIndexAndLength();
    170      }
    171    }
    172  }
    173 
    174  RefPtr<nsSHistory> mSHistory;
    175 };
    176 
    177 enum HistCmd { HIST_CMD_GOTOINDEX, HIST_CMD_RELOAD };
    178 
    179 class nsSHistoryObserver final : public nsIObserver {
    180 public:
    181  NS_DECL_ISUPPORTS
    182  NS_DECL_NSIOBSERVER
    183 
    184  nsSHistoryObserver() {}
    185 
    186  static void PrefChanged(const char* aPref, void* aSelf);
    187  void PrefChanged(const char* aPref);
    188 
    189 protected:
    190  ~nsSHistoryObserver() {}
    191 };
    192 
    193 StaticRefPtr<nsSHistoryObserver> gObserver;
    194 
    195 NS_IMPL_ISUPPORTS(nsSHistoryObserver, nsIObserver)
    196 
    197 // static
    198 void nsSHistoryObserver::PrefChanged(const char* aPref, void* aSelf) {
    199  static_cast<nsSHistoryObserver*>(aSelf)->PrefChanged(aPref);
    200 }
    201 
    202 void nsSHistoryObserver::PrefChanged(const char* aPref) {
    203  nsSHistory::UpdatePrefs();
    204  nsSHistory::GloballyEvictDocumentViewers();
    205 }
    206 
    207 NS_IMETHODIMP
    208 nsSHistoryObserver::Observe(nsISupports* aSubject, const char* aTopic,
    209                            const char16_t* aData) {
    210  if (!strcmp(aTopic, "cacheservice:empty-cache") ||
    211      !strcmp(aTopic, "memory-pressure")) {
    212    nsSHistory::GloballyEvictAllDocumentViewers();
    213  }
    214 
    215  return NS_OK;
    216 }
    217 
    218 void nsSHistory::EvictDocumentViewerForEntry(nsISHEntry* aEntry) {
    219  nsCOMPtr<nsIDocumentViewer> viewer = aEntry->GetDocumentViewer();
    220  if (viewer) {
    221    LOG_SHENTRY_SPEC(("Evicting content viewer 0x%p for "
    222                      "owning SHEntry 0x%p at %s.",
    223                      viewer.get(), aEntry, _spec),
    224                     aEntry);
    225 
    226    // Drop the presentation state before destroying the viewer, so that
    227    // document teardown is able to correctly persist the state.
    228    NotifyListenersDocumentViewerEvicted(1);
    229    aEntry->SetDocumentViewer(nullptr);
    230    aEntry->SyncPresentationState();
    231    viewer->Destroy();
    232  } else if (nsCOMPtr<SessionHistoryEntry> she = do_QueryInterface(aEntry)) {
    233    if (RefPtr<nsFrameLoader> frameLoader = she->GetFrameLoader()) {
    234      nsCOMPtr<nsFrameLoaderOwner> owner =
    235          do_QueryInterface(frameLoader->GetOwnerContent());
    236      RefPtr<nsFrameLoader> currentFrameLoader;
    237      if (owner) {
    238        currentFrameLoader = owner->GetFrameLoader();
    239      }
    240 
    241      // Only destroy non-current frameloader when evicting from the bfcache.
    242      if (currentFrameLoader != frameLoader) {
    243        MOZ_LOG(gSHIPBFCacheLog, LogLevel::Debug,
    244                ("nsSHistory::EvictDocumentViewerForEntry "
    245                 "destroying an nsFrameLoader."));
    246        NotifyListenersDocumentViewerEvicted(1);
    247        she->SetFrameLoader(nullptr);
    248        frameLoader->Destroy();
    249      }
    250    }
    251  }
    252 
    253  // When dropping bfcache, we have to remove associated dynamic entries as
    254  // well.
    255  int32_t index = GetIndexOfEntry(aEntry);
    256  if (index != -1) {
    257    RemoveDynEntries(index, aEntry);
    258  }
    259 }
    260 
    261 nsSHistory::nsSHistory(BrowsingContext* aRootBC)
    262    : mRootBC(aRootBC->Id()),
    263      mHasOngoingUpdate(false),
    264      mIndex(-1),
    265      mRequestedIndex(-1),
    266      mRootDocShellID(aRootBC->GetHistoryID()) {
    267  static bool sCalledStartup = false;
    268  if (!sCalledStartup) {
    269    Startup();
    270    sCalledStartup = true;
    271  }
    272 
    273  // Add this new SHistory object to the list
    274  gSHistoryList.mList.insertBack(this);
    275 
    276  // Init mHistoryTracker on setting mRootBC so we can bind its event
    277  // target to the tabGroup.
    278  mHistoryTracker = mozilla::MakeUnique<HistoryTracker>(
    279      this,
    280      mozilla::Preferences::GetUint(CONTENT_VIEWER_TIMEOUT_SECONDS,
    281                                    CONTENT_VIEWER_TIMEOUT_SECONDS_DEFAULT),
    282      GetCurrentSerialEventTarget());
    283 }
    284 
    285 nsSHistory::~nsSHistory() {
    286  // Clear mEntries explicitly here so that the destructor of the entries
    287  // can still access nsSHistory in a reasonable way.
    288  mEntries.Clear();
    289 }
    290 
    291 NS_IMPL_ADDREF(nsSHistory)
    292 NS_IMPL_RELEASE(nsSHistory)
    293 
    294 NS_INTERFACE_MAP_BEGIN(nsSHistory)
    295  NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsISHistory)
    296  NS_INTERFACE_MAP_ENTRY(nsISHistory)
    297  NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
    298 NS_INTERFACE_MAP_END
    299 
    300 // static
    301 uint32_t nsSHistory::CalcMaxTotalViewers() {
    302 // This value allows tweaking how fast the allowed amount of content viewers
    303 // grows with increasing amounts of memory. Larger values mean slower growth.
    304 #ifdef ANDROID
    305 #  define MAX_TOTAL_VIEWERS_BIAS 15.9
    306 #else
    307 #  define MAX_TOTAL_VIEWERS_BIAS 14
    308 #endif
    309 
    310  // Calculate an estimate of how many DocumentViewers we should cache based
    311  // on RAM.  This assumes that the average DocumentViewer is 4MB (conservative)
    312  // and caps the max at 8 DocumentViewers
    313  //
    314  // TODO: Should we split the cache memory betw. DocumentViewer caching and
    315  // nsCacheService?
    316  //
    317  // RAM    | DocumentViewers | on Android
    318  // -------------------------------------
    319  // 32   Mb       0                0
    320  // 64   Mb       1                0
    321  // 128  Mb       2                0
    322  // 256  Mb       3                1
    323  // 512  Mb       5                2
    324  // 768  Mb       6                2
    325  // 1024 Mb       8                3
    326  // 2048 Mb       8                5
    327  // 3072 Mb       8                7
    328  // 4096 Mb       8                8
    329  uint64_t bytes = PR_GetPhysicalMemorySize();
    330 
    331  if (bytes == 0) {
    332    return 0;
    333  }
    334 
    335  // Conversion from unsigned int64_t to double doesn't work on all platforms.
    336  // We need to truncate the value at INT64_MAX to make sure we don't
    337  // overflow.
    338  if (bytes > INT64_MAX) {
    339    bytes = INT64_MAX;
    340  }
    341 
    342  double kBytesD = (double)(bytes >> 10);
    343 
    344  // This is essentially the same calculation as for nsCacheService,
    345  // except that we divide the final memory calculation by 4, since
    346  // we assume each DocumentViewer takes on average 4MB
    347  uint32_t viewers = 0;
    348  double x = std::log(kBytesD) / std::log(2.0) - MAX_TOTAL_VIEWERS_BIAS;
    349  if (x > 0) {
    350    viewers = (uint32_t)(x * x - x + 2.001);  // add .001 for rounding
    351    viewers /= 4;
    352  }
    353 
    354  // Cap it off at 8 max
    355  if (viewers > 8) {
    356    viewers = 8;
    357  }
    358  return viewers;
    359 }
    360 
    361 // static
    362 void nsSHistory::UpdatePrefs() {
    363  Preferences::GetInt(PREF_SHISTORY_SIZE, &gHistoryMaxSize);
    364  if (mozilla::SessionHistoryInParent() && !mozilla::BFCacheInParent()) {
    365    sHistoryMaxTotalViewers = 0;
    366    return;
    367  }
    368 
    369  Preferences::GetInt(PREF_SHISTORY_MAX_TOTAL_VIEWERS,
    370                      &sHistoryMaxTotalViewers);
    371  // If the pref is negative, that means we calculate how many viewers
    372  // we think we should cache, based on total memory
    373  if (sHistoryMaxTotalViewers < 0) {
    374    sHistoryMaxTotalViewers = CalcMaxTotalViewers();
    375  }
    376 }
    377 
    378 // static
    379 nsresult nsSHistory::Startup() {
    380  UpdatePrefs();
    381 
    382  // The goal of this is to unbreak users who have inadvertently set their
    383  // session history size to less than the default value.
    384  int32_t defaultHistoryMaxSize =
    385      Preferences::GetInt(PREF_SHISTORY_SIZE, 50, PrefValueKind::Default);
    386  if (gHistoryMaxSize < defaultHistoryMaxSize) {
    387    gHistoryMaxSize = defaultHistoryMaxSize;
    388  }
    389 
    390  // Allow the user to override the max total number of cached viewers,
    391  // but keep the per SHistory cached viewer limit constant
    392  if (!gObserver) {
    393    gObserver = new nsSHistoryObserver();
    394    Preferences::RegisterCallbacks(nsSHistoryObserver::PrefChanged,
    395                                   kObservedPrefs, gObserver.get());
    396 
    397    nsCOMPtr<nsIObserverService> obsSvc =
    398        mozilla::services::GetObserverService();
    399    if (obsSvc) {
    400      // Observe empty-cache notifications so tahat clearing the disk/memory
    401      // cache will also evict all content viewers.
    402      obsSvc->AddObserver(gObserver, "cacheservice:empty-cache", false);
    403 
    404      // Same for memory-pressure notifications
    405      obsSvc->AddObserver(gObserver, "memory-pressure", false);
    406    }
    407  }
    408 
    409  return NS_OK;
    410 }
    411 
    412 // static
    413 void nsSHistory::Shutdown() {
    414  if (gObserver) {
    415    Preferences::UnregisterCallbacks(nsSHistoryObserver::PrefChanged,
    416                                     kObservedPrefs, gObserver.get());
    417 
    418    nsCOMPtr<nsIObserverService> obsSvc =
    419        mozilla::services::GetObserverService();
    420    if (obsSvc) {
    421      obsSvc->RemoveObserver(gObserver, "cacheservice:empty-cache");
    422      obsSvc->RemoveObserver(gObserver, "memory-pressure");
    423    }
    424    gObserver = nullptr;
    425  }
    426 }
    427 
    428 // static
    429 already_AddRefed<nsISHEntry> nsSHistory::GetRootSHEntry(nsISHEntry* aEntry) {
    430  nsCOMPtr<nsISHEntry> rootEntry = aEntry;
    431  nsCOMPtr<nsISHEntry> result = nullptr;
    432  while (rootEntry) {
    433    result = rootEntry;
    434    rootEntry = result->GetParent();
    435  }
    436 
    437  return result.forget();
    438 }
    439 
    440 // static
    441 nsresult nsSHistory::WalkHistoryEntries(nsISHEntry* aRootEntry,
    442                                        BrowsingContext* aBC,
    443                                        WalkHistoryEntriesFunc aCallback,
    444                                        void* aData) {
    445  NS_ENSURE_TRUE(aRootEntry, NS_ERROR_FAILURE);
    446 
    447  int32_t childCount = aRootEntry->GetChildCount();
    448  for (int32_t i = 0; i < childCount; i++) {
    449    nsCOMPtr<nsISHEntry> childEntry;
    450    aRootEntry->GetChildAt(i, getter_AddRefs(childEntry));
    451    if (!childEntry) {
    452      // childEntry can be null for valid reasons, for example if the
    453      // docshell at index i never loaded anything useful.
    454      // Remember to clone also nulls in the child array (bug 464064).
    455      aCallback(nullptr, nullptr, i, aData);
    456      continue;
    457    }
    458 
    459    BrowsingContext* childBC = nullptr;
    460    if (aBC) {
    461      for (BrowsingContext* child : aBC->Children()) {
    462        // If the SH pref is on and we are in the parent process, update
    463        // canonical BC directly
    464        bool foundChild = false;
    465        if (mozilla::SessionHistoryInParent() && XRE_IsParentProcess()) {
    466          if (child->Canonical()->HasHistoryEntry(childEntry)) {
    467            childBC = child;
    468            foundChild = true;
    469          }
    470        }
    471 
    472        nsDocShell* docshell = static_cast<nsDocShell*>(child->GetDocShell());
    473        if (docshell && docshell->HasHistoryEntry(childEntry)) {
    474          childBC = docshell->GetBrowsingContext();
    475          foundChild = true;
    476        }
    477 
    478        // XXX Simplify this once the old and new session history
    479        // implementations don't run at the same time.
    480        if (foundChild) {
    481          break;
    482        }
    483      }
    484    }
    485 
    486    nsresult rv = aCallback(childEntry, childBC, i, aData);
    487    NS_ENSURE_SUCCESS(rv, rv);
    488  }
    489 
    490  return NS_OK;
    491 }
    492 
    493 // callback data for WalkHistoryEntries
    494 struct MOZ_STACK_CLASS CloneAndReplaceData {
    495  CloneAndReplaceData(uint32_t aCloneID, nsISHEntry* aReplaceEntry,
    496                      bool aCloneChildren, nsISHEntry* aDestTreeParent)
    497      : cloneID(aCloneID),
    498        cloneChildren(aCloneChildren),
    499        replaceEntry(aReplaceEntry),
    500        destTreeParent(aDestTreeParent) {}
    501 
    502  uint32_t cloneID;
    503  bool cloneChildren;
    504  nsISHEntry* replaceEntry;
    505  nsISHEntry* destTreeParent;
    506  nsCOMPtr<nsISHEntry> resultEntry;
    507 };
    508 
    509 nsresult nsSHistory::CloneAndReplaceChild(nsISHEntry* aEntry,
    510                                          BrowsingContext* aOwnerBC,
    511                                          int32_t aChildIndex, void* aData) {
    512  nsCOMPtr<nsISHEntry> dest;
    513 
    514  CloneAndReplaceData* data = static_cast<CloneAndReplaceData*>(aData);
    515  uint32_t cloneID = data->cloneID;
    516  nsISHEntry* replaceEntry = data->replaceEntry;
    517 
    518  if (!aEntry) {
    519    if (data->destTreeParent) {
    520      data->destTreeParent->AddChild(nullptr, aChildIndex);
    521    }
    522    return NS_OK;
    523  }
    524 
    525  uint32_t srcID = aEntry->GetID();
    526 
    527  nsresult rv = NS_OK;
    528  if (srcID == cloneID) {
    529    // Replace the entry
    530    dest = replaceEntry;
    531  } else {
    532    // Clone the SHEntry...
    533    rv = aEntry->Clone(getter_AddRefs(dest));
    534    NS_ENSURE_SUCCESS(rv, rv);
    535  }
    536  dest->SetIsSubFrame(true);
    537 
    538  if (srcID != cloneID || data->cloneChildren) {
    539    // Walk the children
    540    CloneAndReplaceData childData(cloneID, replaceEntry, data->cloneChildren,
    541                                  dest);
    542    rv = nsSHistory::WalkHistoryEntries(aEntry, aOwnerBC, CloneAndReplaceChild,
    543                                        &childData);
    544    NS_ENSURE_SUCCESS(rv, rv);
    545  }
    546 
    547  if (srcID != cloneID && aOwnerBC) {
    548    nsSHistory::HandleEntriesToSwapInDocShell(aOwnerBC, aEntry, dest);
    549  }
    550 
    551  if (data->destTreeParent) {
    552    data->destTreeParent->AddChild(dest, aChildIndex);
    553  }
    554  data->resultEntry = dest;
    555  return rv;
    556 }
    557 
    558 // static
    559 nsresult nsSHistory::CloneAndReplace(
    560    nsISHEntry* aSrcEntry, BrowsingContext* aOwnerBC, uint32_t aCloneID,
    561    nsISHEntry* aReplaceEntry, bool aCloneChildren, nsISHEntry** aDestEntry) {
    562  NS_ENSURE_ARG_POINTER(aDestEntry);
    563  NS_ENSURE_TRUE(aReplaceEntry, NS_ERROR_FAILURE);
    564  CloneAndReplaceData data(aCloneID, aReplaceEntry, aCloneChildren, nullptr);
    565  nsresult rv = CloneAndReplaceChild(aSrcEntry, aOwnerBC, 0, &data);
    566  data.resultEntry.swap(*aDestEntry);
    567  return rv;
    568 }
    569 
    570 // static
    571 void nsSHistory::WalkContiguousEntries(
    572    nsISHEntry* aEntry, const std::function<void(nsISHEntry*)>& aCallback) {
    573  MOZ_ASSERT(aEntry);
    574 
    575  nsCOMPtr<nsISHistory> shistory = aEntry->GetShistory();
    576  if (!shistory) {
    577    // If there is no session history in the entry, it means this is not a root
    578    // entry. So, we can return from here.
    579    return;
    580  }
    581 
    582  int32_t index = shistory->GetIndexOfEntry(aEntry);
    583  int32_t count = shistory->GetCount();
    584 
    585  nsCOMPtr<nsIURI> targetURI = aEntry->GetURI();
    586 
    587  // First, call the callback on the input entry.
    588  aCallback(aEntry);
    589 
    590  // Walk backward to find the entries that have the same origin as the
    591  // input entry.
    592  for (int32_t i = index - 1; i >= 0; i--) {
    593    RefPtr<nsISHEntry> entry;
    594    shistory->GetEntryAtIndex(i, getter_AddRefs(entry));
    595    if (entry) {
    596      nsCOMPtr<nsIURI> uri = entry->GetURI();
    597      if (NS_FAILED(nsContentUtils::GetSecurityManager()->CheckSameOriginURI(
    598              targetURI, uri, false, false))) {
    599        break;
    600      }
    601 
    602      aCallback(entry);
    603    }
    604  }
    605 
    606  // Then, Walk forward.
    607  for (int32_t i = index + 1; i < count; i++) {
    608    RefPtr<nsISHEntry> entry;
    609    shistory->GetEntryAtIndex(i, getter_AddRefs(entry));
    610    if (entry) {
    611      nsCOMPtr<nsIURI> uri = entry->GetURI();
    612      if (NS_FAILED(nsContentUtils::GetSecurityManager()->CheckSameOriginURI(
    613              targetURI, uri, false, false))) {
    614        break;
    615      }
    616 
    617      aCallback(entry);
    618    }
    619  }
    620 }
    621 
    622 // static
    623 void nsSHistory::WalkContiguousEntriesInOrder(
    624    nsISHEntry* aEntry, const std::function<bool(nsISHEntry*)>& aCallback) {
    625  MOZ_ASSERT(aEntry);
    626  MOZ_ASSERT(SessionHistoryInParent());
    627 
    628  // Walk backward to find the entries that have the same origin as the
    629  // input entry.
    630  nsCOMPtr<SessionHistoryEntry> entry = do_QueryInterface(aEntry);
    631  MOZ_ASSERT(entry);
    632  nsCOMPtr<nsIURI> targetURI = entry->GetURIOrInheritedForAboutBlank();
    633  while (nsCOMPtr previousEntry = entry->getPrevious()) {
    634    nsCOMPtr<nsIURI> uri = previousEntry->GetURIOrInheritedForAboutBlank();
    635    if (NS_FAILED(nsContentUtils::GetSecurityManager()->CheckSameOriginURI(
    636            targetURI, uri, false, false))) {
    637      break;
    638    }
    639    entry = previousEntry;
    640  }
    641 
    642  bool shouldContinue = aCallback(entry);
    643  while (shouldContinue && (entry = entry->getNext())) {
    644    nsCOMPtr<nsIURI> uri = entry->GetURIOrInheritedForAboutBlank();
    645    if (NS_FAILED(nsContentUtils::GetSecurityManager()->CheckSameOriginURI(
    646            targetURI, uri, false, false))) {
    647      break;
    648    }
    649    shouldContinue = aCallback(entry);
    650  }
    651 }
    652 
    653 NS_IMETHODIMP
    654 nsSHistory::AddNestedSHEntry(nsISHEntry* aOldEntry, nsISHEntry* aNewEntry,
    655                             BrowsingContext* aRootBC, bool aCloneChildren) {
    656  MOZ_ASSERT(aRootBC->IsTop());
    657 
    658  /* You are currently in the rootDocShell.
    659   * You will get here when a subframe has a new url
    660   * to load and you have walked up the tree all the
    661   * way to the top to clone the current SHEntry hierarchy
    662   * and replace the subframe where a new url was loaded with
    663   * a new entry.
    664   */
    665  nsCOMPtr<nsISHEntry> child;
    666  nsCOMPtr<nsISHEntry> currentHE;
    667  int32_t index = mIndex;
    668  if (index < 0) {
    669    return NS_ERROR_FAILURE;
    670  }
    671 
    672  GetEntryAtIndex(index, getter_AddRefs(currentHE));
    673  NS_ENSURE_TRUE(currentHE, NS_ERROR_FAILURE);
    674 
    675  nsresult rv = NS_OK;
    676  uint32_t cloneID = aOldEntry->GetID();
    677  rv = nsSHistory::CloneAndReplace(currentHE, aRootBC, cloneID, aNewEntry,
    678                                   aCloneChildren, getter_AddRefs(child));
    679 
    680  if (NS_SUCCEEDED(rv)) {
    681    if (aOldEntry->IsTransient()) {
    682      rv = ReplaceEntry(mIndex, child);
    683    } else {
    684      rv = AddEntry(child);
    685    }
    686 
    687    if (NS_SUCCEEDED(rv)) {
    688      child->SetDocshellID(aRootBC->GetHistoryID());
    689    }
    690  }
    691 
    692  return rv;
    693 }
    694 
    695 nsresult nsSHistory::SetChildHistoryEntry(nsISHEntry* aEntry,
    696                                          BrowsingContext* aBC,
    697                                          int32_t aEntryIndex, void* aData) {
    698  SwapEntriesData* data = static_cast<SwapEntriesData*>(aData);
    699  if (!aBC || aBC == data->ignoreBC) {
    700    return NS_OK;
    701  }
    702 
    703  nsISHEntry* destTreeRoot = data->destTreeRoot;
    704 
    705  nsCOMPtr<nsISHEntry> destEntry;
    706 
    707  if (data->destTreeParent) {
    708    // aEntry is a clone of some child of destTreeParent, but since the
    709    // trees aren't necessarily in sync, we'll have to locate it.
    710    // Note that we could set aShell's entry to null if we don't find a
    711    // corresponding entry under destTreeParent.
    712 
    713    uint32_t targetID = aEntry->GetID();
    714 
    715    // First look at the given index, since this is the common case.
    716    nsCOMPtr<nsISHEntry> entry;
    717    data->destTreeParent->GetChildAt(aEntryIndex, getter_AddRefs(entry));
    718    if (entry && entry->GetID() == targetID) {
    719      destEntry.swap(entry);
    720    } else {
    721      int32_t childCount;
    722      data->destTreeParent->GetChildCount(&childCount);
    723      for (int32_t i = 0; i < childCount; ++i) {
    724        data->destTreeParent->GetChildAt(i, getter_AddRefs(entry));
    725        if (!entry) {
    726          continue;
    727        }
    728 
    729        if (entry->GetID() == targetID) {
    730          destEntry.swap(entry);
    731          break;
    732        }
    733      }
    734    }
    735  } else {
    736    destEntry = destTreeRoot;
    737  }
    738 
    739  nsSHistory::HandleEntriesToSwapInDocShell(aBC, aEntry, destEntry);
    740  // Now handle the children of aEntry.
    741  SwapEntriesData childData = {data->ignoreBC, destTreeRoot, destEntry};
    742  return nsSHistory::WalkHistoryEntries(aEntry, aBC, SetChildHistoryEntry,
    743                                        &childData);
    744 }
    745 
    746 // static
    747 void nsSHistory::HandleEntriesToSwapInDocShell(
    748    mozilla::dom::BrowsingContext* aBC, nsISHEntry* aOldEntry,
    749    nsISHEntry* aNewEntry) {
    750  bool shPref = mozilla::SessionHistoryInParent();
    751  if (aBC->IsInProcess() || !shPref) {
    752    nsDocShell* docshell = static_cast<nsDocShell*>(aBC->GetDocShell());
    753    if (docshell) {
    754      docshell->SwapHistoryEntries(aOldEntry, aNewEntry);
    755    }
    756  } else {
    757    // FIXME Bug 1633988: Need to update entries?
    758  }
    759 
    760  // XXX Simplify this once the old and new session history implementations
    761  // don't run at the same time.
    762  if (shPref && XRE_IsParentProcess()) {
    763    aBC->Canonical()->SwapHistoryEntries(aOldEntry, aNewEntry);
    764  }
    765 }
    766 
    767 NS_IMETHODIMP
    768 nsSHistory::AddToRootSessionHistory(bool aCloneChildren, nsISHEntry* aOSHE,
    769                                    BrowsingContext* aRootBC,
    770                                    nsISHEntry* aEntry, uint32_t aLoadType,
    771                                    Maybe<int32_t>* aPreviousEntryIndex,
    772                                    Maybe<int32_t>* aLoadedEntryIndex) {
    773  MOZ_ASSERT(aRootBC->IsTop());
    774 
    775  nsresult rv = NS_OK;
    776 
    777  // If we need to clone our children onto the new session
    778  // history entry, do so now.
    779  if (aCloneChildren && aOSHE) {
    780    uint32_t cloneID = aOSHE->GetID();
    781    nsCOMPtr<nsISHEntry> newEntry;
    782    nsSHistory::CloneAndReplace(aOSHE, aRootBC, cloneID, aEntry, true,
    783                                getter_AddRefs(newEntry));
    784    NS_ASSERTION(aEntry == newEntry,
    785                 "The new session history should be in the new entry");
    786  }
    787  // This is the root docshell
    788  bool addToSHistory = !LOAD_TYPE_HAS_FLAGS(
    789      aLoadType, nsIWebNavigation::LOAD_FLAGS_REPLACE_HISTORY);
    790  if (!addToSHistory) {
    791    // Replace current entry in session history; If the requested index is
    792    // valid, it indicates the loading was triggered by a history load, and
    793    // we should replace the entry at requested index instead.
    794    int32_t index = GetTargetIndexForHistoryOperation();
    795 
    796    // Replace the current entry with the new entry
    797    if (index >= 0) {
    798      rv = ReplaceEntry(index, aEntry);
    799    } else {
    800      // If we're trying to replace an inexistant shistory entry, append.
    801      addToSHistory = true;
    802    }
    803  }
    804  if (addToSHistory) {
    805    // Add to session history
    806    *aPreviousEntryIndex = Some(mIndex);
    807    rv = AddEntry(aEntry);
    808    *aLoadedEntryIndex = Some(mIndex);
    809    MOZ_LOG(gPageCacheLog, LogLevel::Verbose,
    810            ("Previous index: %d, Loaded index: %d",
    811             aPreviousEntryIndex->value(), aLoadedEntryIndex->value()));
    812  }
    813  if (NS_SUCCEEDED(rv)) {
    814    aEntry->SetDocshellID(aRootBC->GetHistoryID());
    815  }
    816  return rv;
    817 }
    818 
    819 /* Add an entry to the History list at mIndex and
    820 * increment the index to point to the new entry
    821 */
    822 NS_IMETHODIMP
    823 nsSHistory::AddEntry(nsISHEntry* aSHEntry) {
    824  NS_ENSURE_ARG(aSHEntry);
    825 
    826  nsCOMPtr<nsISHistory> shistoryOfEntry = aSHEntry->GetShistory();
    827  if (shistoryOfEntry && shistoryOfEntry != this) {
    828    NS_WARNING(
    829        "The entry has been associated to another nsISHistory instance. "
    830        "Try nsISHEntry.clone() and nsISHEntry.abandonBFCacheEntry() "
    831        "first if you're copying an entry from another nsISHistory.");
    832    return NS_ERROR_FAILURE;
    833  }
    834 
    835  aSHEntry->SetShistory(this);
    836 
    837  // If we have a root docshell, update the docshell id of the root shentry to
    838  // match the id of that docshell
    839  RefPtr<BrowsingContext> rootBC = GetBrowsingContext();
    840  if (rootBC) {
    841    aSHEntry->SetDocshellID(mRootDocShellID);
    842  }
    843 
    844  if (mIndex >= 0) {
    845    MOZ_ASSERT(mIndex < Length(), "Index out of range!");
    846    if (mIndex >= Length()) {
    847      return NS_ERROR_FAILURE;
    848    }
    849 
    850    if (mEntries[mIndex] && mEntries[mIndex]->IsTransient()) {
    851      NotifyListeners(mListeners, [](auto l) { l->OnHistoryReplaceEntry(); });
    852      mEntries[mIndex] = aSHEntry;
    853      return NS_OK;
    854    }
    855  }
    856  SHistoryChangeNotifier change(this);
    857 
    858  int32_t truncating = Length() - 1 - mIndex;
    859  if (truncating > 0) {
    860    NotifyListeners(mListeners,
    861                    [truncating](auto l) { l->OnHistoryTruncate(truncating); });
    862  }
    863 
    864  nsCOMPtr<nsIURI> uri = aSHEntry->GetURI();
    865  NotifyListeners(mListeners,
    866                  [&uri, this](auto l) { l->OnHistoryNewEntry(uri, mIndex); });
    867 
    868  // Remove all entries after the current one, add the new one, and set the
    869  // new one as the current one.
    870  MOZ_ASSERT(mIndex >= -1);
    871  mEntries.TruncateLength(mIndex + 1);
    872  mEntries.AppendElement(aSHEntry);
    873  mIndex++;
    874  if (mIndex > 0) {
    875    UpdateEntryLength(mEntries[mIndex - 1], mEntries[mIndex], false);
    876  }
    877 
    878  // Purge History list if it is too long
    879  if (gHistoryMaxSize >= 0 && Length() > gHistoryMaxSize) {
    880    PurgeHistory(Length() - gHistoryMaxSize);
    881  }
    882 
    883  return NS_OK;
    884 }
    885 
    886 void nsSHistory::NotifyOnHistoryReplaceEntry() {
    887  NotifyListeners(mListeners, [](auto l) { l->OnHistoryReplaceEntry(); });
    888 }
    889 
    890 /* Get size of the history list */
    891 NS_IMETHODIMP
    892 nsSHistory::GetCount(int32_t* aResult) {
    893  MOZ_ASSERT(aResult, "null out param?");
    894  *aResult = Length();
    895  return NS_OK;
    896 }
    897 
    898 NS_IMETHODIMP
    899 nsSHistory::GetIndex(int32_t* aResult) {
    900  MOZ_ASSERT(aResult, "null out param?");
    901  *aResult = mIndex;
    902  return NS_OK;
    903 }
    904 
    905 NS_IMETHODIMP
    906 nsSHistory::SetIndex(int32_t aIndex) {
    907  if (aIndex < 0 || aIndex >= Length()) {
    908    return NS_ERROR_FAILURE;
    909  }
    910 
    911  mIndex = aIndex;
    912  return NS_OK;
    913 }
    914 
    915 /* Get the requestedIndex */
    916 NS_IMETHODIMP
    917 nsSHistory::GetRequestedIndex(int32_t* aResult) {
    918  MOZ_ASSERT(aResult, "null out param?");
    919  *aResult = mRequestedIndex;
    920  return NS_OK;
    921 }
    922 
    923 NS_IMETHODIMP_(void)
    924 nsSHistory::InternalSetRequestedIndex(int32_t aRequestedIndex) {
    925  MOZ_ASSERT(aRequestedIndex >= -1 && aRequestedIndex < Length());
    926  mRequestedIndex = aRequestedIndex;
    927 }
    928 
    929 NS_IMETHODIMP
    930 nsSHistory::GetEntryAtIndex(int32_t aIndex, nsISHEntry** aResult) {
    931  NS_ENSURE_ARG_POINTER(aResult);
    932 
    933  if (aIndex < 0 || aIndex >= Length()) {
    934    return NS_ERROR_FAILURE;
    935  }
    936 
    937  *aResult = mEntries[aIndex];
    938  NS_ADDREF(*aResult);
    939  return NS_OK;
    940 }
    941 
    942 NS_IMETHODIMP_(int32_t)
    943 nsSHistory::GetIndexOfEntry(nsISHEntry* aSHEntry) {
    944  for (int32_t i = 0; i < Length(); i++) {
    945    if (aSHEntry == mEntries[i]) {
    946      return i;
    947    }
    948  }
    949 
    950  return -1;
    951 }
    952 
    953 static void LogEntry(nsISHEntry* aEntry, int32_t aIndex, int32_t aTotal,
    954                     const nsCString& aPrefix, bool aIsCurrent) {
    955  if (!aEntry) {
    956    MOZ_LOG(gSHLog, LogLevel::Debug,
    957            (" %s+- %i SH Entry null\n", aPrefix.get(), aIndex));
    958    return;
    959  }
    960 
    961  nsCOMPtr<nsIURI> uri = aEntry->GetURI();
    962  nsAutoString title, name;
    963  aEntry->GetTitle(title);
    964  aEntry->GetName(name);
    965 
    966  SHEntrySharedParentState* shared;
    967  if (mozilla::SessionHistoryInParent()) {
    968    shared = static_cast<SessionHistoryEntry*>(aEntry)->SharedInfo();
    969  } else {
    970    shared = static_cast<nsSHEntry*>(aEntry)->GetState();
    971  }
    972 
    973  nsID docShellId;
    974  aEntry->GetDocshellID(docShellId);
    975 
    976  int32_t childCount = aEntry->GetChildCount();
    977 
    978  MOZ_LOG(gSHLog, LogLevel::Debug,
    979          ("%s%s+- %i SH Entry %p %" PRIu64 " %s\n", aIsCurrent ? ">" : " ",
    980           aPrefix.get(), aIndex, aEntry, shared->GetId(),
    981           nsIDToCString(docShellId).get()));
    982 
    983  nsCString prefix(aPrefix);
    984  if (aIndex < aTotal - 1) {
    985    prefix.AppendLiteral("|   ");
    986  } else {
    987    prefix.AppendLiteral("    ");
    988  }
    989 
    990  MOZ_LOG(gSHLog, LogLevel::Debug,
    991          (" %s%s  URL = %s\n", prefix.get(), childCount > 0 ? "|" : " ",
    992           uri->GetSpecOrDefault().get()));
    993  MOZ_LOG(gSHLog, LogLevel::Debug,
    994          (" %s%s  Title = %s\n", prefix.get(), childCount > 0 ? "|" : " ",
    995           NS_LossyConvertUTF16toASCII(title).get()));
    996  MOZ_LOG(gSHLog, LogLevel::Debug,
    997          (" %s%s  Name = %s\n", prefix.get(), childCount > 0 ? "|" : " ",
    998           NS_LossyConvertUTF16toASCII(name).get()));
    999  MOZ_LOG(gSHLog, LogLevel::Debug,
   1000          (" %s%s  Transient = %s\n", prefix.get(), childCount > 0 ? "|" : " ",
   1001           aEntry->IsTransient() ? "true" : "false"));
   1002  MOZ_LOG(
   1003      gSHLog, LogLevel::Debug,
   1004      (" %s%s  Is in BFCache = %s\n", prefix.get(), childCount > 0 ? "|" : " ",
   1005       aEntry->GetIsInBFCache() ? "true" : "false"));
   1006  MOZ_LOG(gSHLog, LogLevel::Debug,
   1007          (" %s%s  Has User Interaction = %s\n", prefix.get(),
   1008           childCount > 0 ? "|" : " ",
   1009           aEntry->GetHasUserInteraction() ? "true" : "false"));
   1010  if (nsCOMPtr<SessionHistoryEntry> entry = do_QueryInterface(aEntry)) {
   1011    MOZ_LOG(gSHLog, LogLevel::Debug,
   1012            (" %s%s  Navigation key = %s\n", prefix.get(),
   1013             childCount > 0 ? "|" : " ",
   1014             entry->Info().NavigationKey().ToString().get()));
   1015  }
   1016 
   1017  nsCOMPtr<nsISHEntry> prevChild;
   1018  for (int32_t i = 0; i < childCount; ++i) {
   1019    nsCOMPtr<nsISHEntry> child;
   1020    aEntry->GetChildAt(i, getter_AddRefs(child));
   1021    LogEntry(child, i, childCount, prefix, false);
   1022    child.swap(prevChild);
   1023  }
   1024 }
   1025 
   1026 void nsSHistory::LogHistory() {
   1027  if (!MOZ_LOG_TEST(gSHLog, LogLevel::Debug)) {
   1028    return;
   1029  }
   1030 
   1031  MOZ_LOG(gSHLog, LogLevel::Debug, ("nsSHistory %p\n", this));
   1032  int32_t length = Length();
   1033  for (int32_t i = 0; i < length; i++) {
   1034    LogEntry(mEntries[i], i, length, EmptyCString(), i == mIndex);
   1035  }
   1036 }
   1037 
   1038 void nsSHistory::WindowIndices(int32_t aIndex, int32_t* aOutStartIndex,
   1039                               int32_t* aOutEndIndex) {
   1040  *aOutStartIndex = std::max(0, aIndex - nsSHistory::VIEWER_WINDOW);
   1041  *aOutEndIndex = std::min(Length() - 1, aIndex + nsSHistory::VIEWER_WINDOW);
   1042 }
   1043 
   1044 static void MarkAsInitialEntry(
   1045    SessionHistoryEntry* aEntry,
   1046    nsTHashMap<nsIDHashKey, SessionHistoryEntry*>& aHashtable) {
   1047  if (!aEntry->BCHistoryLength().Modified()) {
   1048    ++(aEntry->BCHistoryLength());
   1049  }
   1050  aHashtable.InsertOrUpdate(aEntry->DocshellID(), aEntry);
   1051  for (const RefPtr<SessionHistoryEntry>& entry : aEntry->Children()) {
   1052    if (entry) {
   1053      MarkAsInitialEntry(entry, aHashtable);
   1054    }
   1055  }
   1056 }
   1057 
   1058 static void ClearEntries(SessionHistoryEntry* aEntry) {
   1059  aEntry->ClearBCHistoryLength();
   1060  for (const RefPtr<SessionHistoryEntry>& entry : aEntry->Children()) {
   1061    if (entry) {
   1062      ClearEntries(entry);
   1063    }
   1064  }
   1065 }
   1066 
   1067 NS_IMETHODIMP
   1068 nsSHistory::PurgeHistory(int32_t aNumEntries) {
   1069  if (Length() <= 0 || aNumEntries <= 0) {
   1070    return NS_ERROR_FAILURE;
   1071  }
   1072 
   1073  SHistoryChangeNotifier change(this);
   1074 
   1075  aNumEntries = std::min(aNumEntries, Length());
   1076 
   1077  NotifyListeners(mListeners,
   1078                  [aNumEntries](auto l) { l->OnHistoryPurge(aNumEntries); });
   1079 
   1080  // Set all the entries hanging of the first entry that we keep
   1081  // (mEntries[aNumEntries]) as being created as the result of a load
   1082  // (so contributing one to their BCHistoryLength).
   1083  nsTHashMap<nsIDHashKey, SessionHistoryEntry*> docshellIDToEntry;
   1084  if (aNumEntries != Length()) {
   1085    nsCOMPtr<SessionHistoryEntry> she =
   1086        do_QueryInterface(mEntries[aNumEntries]);
   1087    if (she) {
   1088      MarkAsInitialEntry(she, docshellIDToEntry);
   1089    }
   1090  }
   1091 
   1092  // Reset the BCHistoryLength of all the entries that we're removing to a new
   1093  // counter with value 0 while decreasing their contribution to a shared
   1094  // BCHistoryLength. The end result is that they don't contribute to the
   1095  // BCHistoryLength of any other entry anymore.
   1096  for (int32_t i = 0; i < aNumEntries; ++i) {
   1097    nsCOMPtr<SessionHistoryEntry> she = do_QueryInterface(mEntries[i]);
   1098    if (she) {
   1099      ClearEntries(she);
   1100    }
   1101  }
   1102 
   1103  RefPtr<BrowsingContext> rootBC = GetBrowsingContext();
   1104  if (rootBC) {
   1105    rootBC->PreOrderWalk([&docshellIDToEntry](BrowsingContext* aBC) {
   1106      SessionHistoryEntry* entry = docshellIDToEntry.Get(aBC->GetHistoryID());
   1107      (void)aBC->SetHistoryEntryCount(entry ? uint32_t(entry->BCHistoryLength())
   1108                                            : 0);
   1109    });
   1110  }
   1111 
   1112  // Remove the first `aNumEntries` entries.
   1113  mEntries.RemoveElementsAt(0, aNumEntries);
   1114 
   1115  // Adjust the indices, but don't let them go below -1.
   1116  mIndex -= aNumEntries;
   1117  mIndex = std::max(mIndex, -1);
   1118  mRequestedIndex -= aNumEntries;
   1119  mRequestedIndex = std::max(mRequestedIndex, -1);
   1120 
   1121  if (rootBC && rootBC->GetDocShell()) {
   1122    rootBC->GetDocShell()->HistoryPurged(aNumEntries);
   1123  }
   1124 
   1125  return NS_OK;
   1126 }
   1127 
   1128 NS_IMETHODIMP
   1129 nsSHistory::AddSHistoryListener(nsISHistoryListener* aListener) {
   1130  NS_ENSURE_ARG_POINTER(aListener);
   1131 
   1132  // Check if the listener supports Weak Reference. This is a must.
   1133  // This listener functionality is used by embedders and we want to
   1134  // have the right ownership with who ever listens to SHistory
   1135  nsWeakPtr listener = do_GetWeakReference(aListener);
   1136  if (!listener) {
   1137    return NS_ERROR_FAILURE;
   1138  }
   1139 
   1140  mListeners.AppendElementUnlessExists(listener);
   1141  return NS_OK;
   1142 }
   1143 
   1144 void nsSHistory::NotifyListenersDocumentViewerEvicted(uint32_t aNumEvicted) {
   1145  NotifyListeners(mListeners, [aNumEvicted](auto l) {
   1146    l->OnDocumentViewerEvicted(aNumEvicted);
   1147  });
   1148 }
   1149 
   1150 NS_IMETHODIMP
   1151 nsSHistory::RemoveSHistoryListener(nsISHistoryListener* aListener) {
   1152  // Make sure the listener that wants to be removed is the
   1153  // one we have in store.
   1154  nsWeakPtr listener = do_GetWeakReference(aListener);
   1155  mListeners.RemoveElement(listener);
   1156  return NS_OK;
   1157 }
   1158 
   1159 /* Replace an entry in the History list at a particular index.
   1160 * Do not update index or count.
   1161 */
   1162 NS_IMETHODIMP
   1163 nsSHistory::ReplaceEntry(int32_t aIndex, nsISHEntry* aReplaceEntry) {
   1164  NS_ENSURE_ARG(aReplaceEntry);
   1165 
   1166  if (aIndex < 0 || aIndex >= Length()) {
   1167    return NS_ERROR_FAILURE;
   1168  }
   1169 
   1170  nsCOMPtr<nsISHistory> shistoryOfEntry = aReplaceEntry->GetShistory();
   1171  if (shistoryOfEntry && shistoryOfEntry != this) {
   1172    NS_WARNING(
   1173        "The entry has been associated to another nsISHistory instance. "
   1174        "Try nsISHEntry.clone() and nsISHEntry.abandonBFCacheEntry() "
   1175        "first if you're copying an entry from another nsISHistory.");
   1176    return NS_ERROR_FAILURE;
   1177  }
   1178 
   1179  aReplaceEntry->SetShistory(this);
   1180 
   1181  NotifyListeners(mListeners, [](auto l) { l->OnHistoryReplaceEntry(); });
   1182 
   1183  mEntries[aIndex] = aReplaceEntry;
   1184 
   1185  return NS_OK;
   1186 }
   1187 
   1188 // Calls OnHistoryReload on all registered session history listeners.
   1189 // Listeners may return 'false' to cancel an action so make sure that we
   1190 // set the return value to 'false' if one of the listeners wants to cancel.
   1191 NS_IMETHODIMP
   1192 nsSHistory::NotifyOnHistoryReload(bool* aCanReload) {
   1193  *aCanReload = true;
   1194 
   1195  for (const nsWeakPtr& weakPtr : mListeners.EndLimitedRange()) {
   1196    nsCOMPtr<nsISHistoryListener> listener = do_QueryReferent(weakPtr);
   1197    if (listener) {
   1198      bool retval = true;
   1199 
   1200      if (NS_SUCCEEDED(listener->OnHistoryReload(&retval)) && !retval) {
   1201        *aCanReload = false;
   1202      }
   1203    }
   1204  }
   1205 
   1206  return NS_OK;
   1207 }
   1208 
   1209 NS_IMETHODIMP
   1210 nsSHistory::EvictOutOfRangeDocumentViewers(int32_t aIndex) {
   1211  MOZ_LOG(gSHIPBFCacheLog, LogLevel::Debug,
   1212          ("nsSHistory::EvictOutOfRangeDocumentViewers %i", aIndex));
   1213 
   1214  // Check our per SHistory object limit in the currently navigated SHistory
   1215  EvictOutOfRangeWindowDocumentViewers(aIndex);
   1216  // Check our total limit across all SHistory objects
   1217  GloballyEvictDocumentViewers();
   1218  return NS_OK;
   1219 }
   1220 
   1221 NS_IMETHODIMP_(void)
   1222 nsSHistory::EvictDocumentViewersOrReplaceEntry(nsISHEntry* aNewSHEntry,
   1223                                               bool aReplace) {
   1224  if (!aReplace) {
   1225    int32_t curIndex;
   1226    GetIndex(&curIndex);
   1227    if (curIndex > -1) {
   1228      EvictOutOfRangeDocumentViewers(curIndex);
   1229    }
   1230  } else {
   1231    nsCOMPtr<nsISHEntry> rootSHEntry = nsSHistory::GetRootSHEntry(aNewSHEntry);
   1232 
   1233    int32_t index = GetIndexOfEntry(rootSHEntry);
   1234    if (index > -1) {
   1235      ReplaceEntry(index, rootSHEntry);
   1236    }
   1237  }
   1238 }
   1239 
   1240 NS_IMETHODIMP
   1241 nsSHistory::EvictAllDocumentViewers() {
   1242  // XXXbz we don't actually do a good job of evicting things as we should, so
   1243  // we might have viewers quite far from mIndex.  So just evict everything.
   1244  for (int32_t i = 0; i < Length(); i++) {
   1245    EvictDocumentViewerForEntry(mEntries[i]);
   1246  }
   1247 
   1248  return NS_OK;
   1249 }
   1250 
   1251 MOZ_CAN_RUN_SCRIPT
   1252 static void FinishRestore(CanonicalBrowsingContext* aBrowsingContext,
   1253                          nsDocShellLoadState* aLoadState,
   1254                          SessionHistoryEntry* aEntry,
   1255                          nsFrameLoader* aFrameLoader, bool aCanSave) {
   1256  MOZ_ASSERT(aEntry);
   1257  MOZ_ASSERT(aFrameLoader);
   1258 
   1259  aEntry->SetFrameLoader(nullptr);
   1260 
   1261  nsCOMPtr<nsISHistory> shistory = aEntry->GetShistory();
   1262  int32_t indexOfHistoryLoad =
   1263      shistory ? shistory->GetIndexOfEntry(aEntry) : -1;
   1264 
   1265  nsCOMPtr<nsFrameLoaderOwner> frameLoaderOwner =
   1266      do_QueryInterface(aBrowsingContext->GetEmbedderElement());
   1267  if (frameLoaderOwner && aFrameLoader->GetMaybePendingBrowsingContext() &&
   1268      indexOfHistoryLoad >= 0) {
   1269    RefPtr<BrowsingContextWebProgress> webProgress =
   1270        aBrowsingContext->GetWebProgress();
   1271    if (webProgress) {
   1272      // Synthesize a STATE_START WebProgress state change event from here
   1273      // in order to ensure emitting it on the BrowsingContext we navigate
   1274      // *from* instead of the BrowsingContext we navigate *to*. This will fire
   1275      // before and the next one will be ignored by BrowsingContextWebProgress:
   1276      // https://searchfox.org/mozilla-central/rev/77f0b36028b2368e342c982ea47609040b399d89/docshell/base/BrowsingContextWebProgress.cpp#196-203
   1277      nsCOMPtr<nsIURI> nextURI = aEntry->GetURI();
   1278      nsCOMPtr<nsIURI> nextOriginalURI = aEntry->GetOriginalURI();
   1279      nsCOMPtr<nsIRequest> request = MakeAndAddRef<RemoteWebProgressRequest>(
   1280          nextURI, nextOriginalURI ? nextOriginalURI : nextURI,
   1281          ""_ns /* aMatchedList */);
   1282      webProgress->OnStateChange(webProgress, request,
   1283                                 nsIWebProgressListener::STATE_START |
   1284                                     nsIWebProgressListener::STATE_IS_DOCUMENT |
   1285                                     nsIWebProgressListener::STATE_IS_REQUEST |
   1286                                     nsIWebProgressListener::STATE_IS_WINDOW |
   1287                                     nsIWebProgressListener::STATE_IS_NETWORK,
   1288                                 NS_OK);
   1289    }
   1290 
   1291    RefPtr<CanonicalBrowsingContext> loadingBC =
   1292        aFrameLoader->GetMaybePendingBrowsingContext()->Canonical();
   1293    RefPtr<nsFrameLoader> currentFrameLoader =
   1294        frameLoaderOwner->GetFrameLoader();
   1295    // The current page can be bfcached, store the
   1296    // nsFrameLoader in the current SessionHistoryEntry.
   1297    RefPtr<SessionHistoryEntry> currentSHEntry =
   1298        aBrowsingContext->GetActiveSessionHistoryEntry();
   1299    if (currentSHEntry) {
   1300      // Update layout history state now, before we change the IsInBFCache flag
   1301      // and the active session history entry.
   1302      aBrowsingContext->SynchronizeLayoutHistoryState();
   1303 
   1304      if (aCanSave) {
   1305        currentSHEntry->SetFrameLoader(currentFrameLoader);
   1306        (void)aBrowsingContext->SetIsInBFCache(true);
   1307      }
   1308    }
   1309 
   1310    // Ensure browser priority to matches `IsPriorityActive` after restoring.
   1311    if (BrowserParent* bp = loadingBC->GetBrowserParent()) {
   1312      bp->VisitAll([&](BrowserParent* aBp) {
   1313        ProcessPriorityManager::BrowserPriorityChanged(
   1314            aBp, aBrowsingContext->IsPriorityActive());
   1315      });
   1316    }
   1317 
   1318    if (aEntry) {
   1319      aEntry->SetWireframe(Nothing());
   1320    }
   1321 
   1322    // ReplacedBy will swap the entry back.
   1323    aBrowsingContext->SetActiveSessionHistoryEntryFromBFCache(aEntry);
   1324    loadingBC->SetActiveSessionHistoryEntryFromBFCache(nullptr);
   1325    NavigationIsolationOptions options;
   1326    aBrowsingContext->ReplacedBy(loadingBC, options);
   1327 
   1328    // Assuming we still have the session history, update the index.
   1329    if (loadingBC->GetSessionHistory()) {
   1330      shistory->InternalSetRequestedIndex(indexOfHistoryLoad);
   1331      shistory->UpdateIndex();
   1332    }
   1333    loadingBC->HistoryCommitIndexAndLength();
   1334 
   1335    // ResetSHEntryHasUserInteractionCache(); ?
   1336    // browser.navigation.requireUserInteraction is still
   1337    // disabled everywhere.
   1338 
   1339    frameLoaderOwner->RestoreFrameLoaderFromBFCache(aFrameLoader);
   1340    // EvictOutOfRangeDocumentViewers is called here explicitly to
   1341    // possibly evict the now in the bfcache document.
   1342    // HistoryCommitIndexAndLength might not have evicted that before the
   1343    // FrameLoader swap.
   1344    shistory->EvictOutOfRangeDocumentViewers(indexOfHistoryLoad);
   1345 
   1346    // The old page can't be stored in the bfcache,
   1347    // destroy the nsFrameLoader.
   1348    if (!aCanSave && currentFrameLoader) {
   1349      currentFrameLoader->Destroy();
   1350    }
   1351 
   1352    (void)loadingBC->SetIsInBFCache(false);
   1353 
   1354    // We need to call this after we've restored the page from BFCache (see
   1355    // SetIsInBFCache(false) above), so that the page is not frozen anymore and
   1356    // the right focus events are fired.
   1357    frameLoaderOwner->UpdateFocusAndMouseEnterStateAfterFrameLoaderChange();
   1358 
   1359    return;
   1360  }
   1361 
   1362  aFrameLoader->Destroy();
   1363 
   1364  // Fall back to do a normal load.
   1365  aBrowsingContext->LoadURI(aLoadState, false);
   1366 }
   1367 
   1368 MOZ_CAN_RUN_SCRIPT
   1369 static bool MaybeLoadBFCache(const nsSHistory::LoadEntryResult& aLoadEntry) {
   1370  MOZ_ASSERT(XRE_IsParentProcess());
   1371  RefPtr<nsDocShellLoadState> loadState = aLoadEntry.mLoadState;
   1372  RefPtr<CanonicalBrowsingContext> canonicalBC =
   1373      aLoadEntry.mBrowsingContext->Canonical();
   1374  nsCOMPtr<SessionHistoryEntry> she = do_QueryInterface(loadState->SHEntry());
   1375  nsCOMPtr<SessionHistoryEntry> currentShe =
   1376      canonicalBC->GetActiveSessionHistoryEntry();
   1377  MOZ_ASSERT(she);
   1378  RefPtr<nsFrameLoader> frameLoader = she->GetFrameLoader();
   1379  if (frameLoader && canonicalBC->Group()->Toplevels().Length() == 1 &&
   1380      (!currentShe || (she->SharedInfo() != currentShe->SharedInfo() &&
   1381                       !currentShe->GetFrameLoader()))) {
   1382    bool canSave = (!currentShe || currentShe->GetSaveLayoutStateFlag()) &&
   1383                   canonicalBC->AllowedInBFCache(Nothing(), nullptr);
   1384 
   1385    MOZ_LOG(gSHIPBFCacheLog, LogLevel::Debug,
   1386            ("nsSHistory::LoadURIOrBFCache "
   1387             "saving presentation=%i",
   1388             canSave));
   1389 
   1390    nsCOMPtr<nsFrameLoaderOwner> frameLoaderOwner =
   1391        do_QueryInterface(canonicalBC->GetEmbedderElement());
   1392    if (!loadState->NotifiedBeforeUnloadListeners() && frameLoaderOwner) {
   1393      RefPtr<nsFrameLoader> currentFrameLoader =
   1394          frameLoaderOwner->GetFrameLoader();
   1395      if (currentFrameLoader &&
   1396          currentFrameLoader->GetMaybePendingBrowsingContext()) {
   1397        if (WindowGlobalParent* wgp =
   1398                currentFrameLoader->GetMaybePendingBrowsingContext()
   1399                    ->Canonical()
   1400                    ->GetCurrentWindowGlobal()) {
   1401          wgp->PermitUnload(
   1402              [canonicalBC, loadState, she, frameLoader, currentFrameLoader,
   1403               canSave](nsIDocumentViewer::PermitUnloadResult aResult)
   1404                  MOZ_CAN_RUN_SCRIPT_BOUNDARY_LAMBDA {
   1405                    bool allow = aResult == nsIDocumentViewer::eContinue;
   1406                    if (allow && !canonicalBC->IsReplaced()) {
   1407                      FinishRestore(canonicalBC, loadState, she, frameLoader,
   1408                                    canSave && canonicalBC->AllowedInBFCache(
   1409                                                   Nothing(), nullptr));
   1410                    } else if (currentFrameLoader
   1411                                   ->GetMaybePendingBrowsingContext()) {
   1412                      nsISHistory* shistory =
   1413                          currentFrameLoader->GetMaybePendingBrowsingContext()
   1414                              ->Canonical()
   1415                              ->GetSessionHistory();
   1416                      if (shistory) {
   1417                        shistory->InternalSetRequestedIndex(-1);
   1418                      }
   1419                    }
   1420                  });
   1421          return true;
   1422        }
   1423      }
   1424    }
   1425 
   1426    FinishRestore(canonicalBC, loadState, she, frameLoader, canSave);
   1427    return true;
   1428  }
   1429  if (frameLoader) {
   1430    she->SetFrameLoader(nullptr);
   1431    frameLoader->Destroy();
   1432  }
   1433 
   1434  return false;
   1435 }
   1436 
   1437 /* static */
   1438 void nsSHistory::LoadURIOrBFCache(const LoadEntryResult& aLoadEntry) {
   1439  if (mozilla::BFCacheInParent() && aLoadEntry.mBrowsingContext->IsTop()) {
   1440    if (MaybeLoadBFCache(aLoadEntry)) {
   1441      return;
   1442    }
   1443  }
   1444 
   1445  RefPtr<BrowsingContext> bc = aLoadEntry.mBrowsingContext;
   1446  RefPtr<nsDocShellLoadState> loadState = aLoadEntry.mLoadState;
   1447  bc->LoadURI(loadState, false);
   1448 }
   1449 
   1450 // This implements step 4 of
   1451 // https://html.spec.whatwg.org/#checking-if-unloading-is-canceled which handles
   1452 // the case where we have a "navigate" handler for the top level window's
   1453 // navigation object and/or a "beforeunload" handler for that same window. The
   1454 // tricky part is that we need to check "beforeunload" for that window, then
   1455 // "navigate", and after that continue with "beforeunload" for the remaining
   1456 // tree.
   1457 MOZ_CAN_RUN_SCRIPT
   1458 static bool MaybeCheckUnloadingIsCanceled(
   1459    const nsTArray<nsSHistory::LoadEntryResult>& aLoadResults,
   1460    BrowsingContext* aTraversable,
   1461    std::function<void(nsTArray<nsSHistory::LoadEntryResult>&,
   1462                       nsIDocumentViewer::PermitUnloadResult)>&& aResolver) {
   1463  // Step 4
   1464  if (!aTraversable || !aTraversable->IsTop() || !SessionHistoryInParent() ||
   1465      !aLoadResults.Length() || !Navigation::IsAPIEnabled()) {
   1466    return false;
   1467  }
   1468 
   1469  RefPtr<CanonicalBrowsingContext> traversable = aTraversable->Canonical();
   1470 
   1471  RefPtr<WindowGlobalParent> windowGlobalParent =
   1472      traversable->GetCurrentWindowGlobal();
   1473  // An efficiency trick. We've set this flag on the window context if we've
   1474  // seen a "navigate" and/or a "beforeunload" handler set. If not we know we
   1475  // can skip this.
   1476  if (!windowGlobalParent || (!windowGlobalParent->NeedsBeforeUnload() &&
   1477                              !windowGlobalParent->GetNeedsTraverse())) {
   1478    return false;
   1479  }
   1480 
   1481  // Step 4.2
   1482  auto found =
   1483      std::find_if(aLoadResults.begin(), aLoadResults.end(),
   1484                   [traversable](const auto& result) {
   1485                     return result.mBrowsingContext->Id() == traversable->Id();
   1486                   });
   1487 
   1488  // Step 4.3, since current entry is always different to not finding one.
   1489  if (found == aLoadResults.end()) {
   1490    return false;
   1491  }
   1492 
   1493  // Step 4.2
   1494  nsCOMPtr<SessionHistoryEntry> targetEntry =
   1495      do_QueryInterface(found->mLoadState->SHEntry());
   1496 
   1497  nsCOMPtr<SessionHistoryEntry> currentEntry =
   1498      traversable->GetActiveSessionHistoryEntry();
   1499 
   1500  // Step 4.3, but the actual checks in the spec.
   1501  if (!currentEntry || !targetEntry ||
   1502      currentEntry->GetID() == targetEntry->GetID()) {
   1503    return false;
   1504  }
   1505 
   1506  nsCOMPtr<nsIURI> targetURI = targetEntry->GetURI();
   1507  if (!targetURI) {
   1508    return false;
   1509  }
   1510 
   1511  nsCOMPtr<nsIPrincipal> targetPrincipal =
   1512      BasePrincipal::CreateContentPrincipal(targetURI,
   1513                                            traversable->OriginAttributesRef());
   1514 
   1515  // More of step 4.3
   1516  if (!windowGlobalParent->DocumentPrincipal()->Equals(targetPrincipal)) {
   1517    return false;
   1518  }
   1519 
   1520  // Step 4.3.2
   1521  // If we squint we can see spec here, insofar that for a traversable's
   1522  // beforeunload handler to fire, the target entry needs to be:
   1523  // * non-null, i.e. part of navigables being traversed
   1524  // * different from the current entry
   1525  // * cross document from the current entry
   1526  // * have beforeunload handlers
   1527  bool needsBeforeUnload =
   1528      windowGlobalParent->NeedsBeforeUnload() &&
   1529      currentEntry->SharedInfo() != targetEntry->SharedInfo();
   1530 
   1531  // Step 4.3.3 isn't needed since that's what PermitUnloadChildNavigables
   1532  // achieves by skipping top level navigable.
   1533 
   1534  // Step 4.3.4
   1535  // PermitUnloadTraversable only includes the process of the top level browsing
   1536  // context.
   1537 
   1538  // If we're not going to run any beforeunload handlers, we still need to run
   1539  // navigate event handlers for the traversable.
   1540  nsIDocumentViewer::PermitUnloadAction action =
   1541      needsBeforeUnload
   1542          ? nsIDocumentViewer::PermitUnloadAction::ePrompt
   1543          : nsIDocumentViewer::PermitUnloadAction::eDontPromptAndUnload;
   1544  windowGlobalParent->PermitUnloadTraversable(
   1545      targetEntry->Info(), action,
   1546      [action, loadResults = CopyableTArray(std::move(aLoadResults)),
   1547       windowGlobalParent,
   1548       aResolver](nsIDocumentViewer::PermitUnloadResult aResult) mutable {
   1549        if (aResult != nsIDocumentViewer::PermitUnloadResult::eContinue) {
   1550          aResolver(loadResults, aResult);
   1551          return;
   1552        }
   1553 
   1554        // If the traversable didn't have beforeunloadun handlers, we won't run
   1555        // other navigable's unload handlers either. That will be handled by
   1556        // regular navigation.
   1557        if (action ==
   1558            nsIDocumentViewer::PermitUnloadAction::eDontPromptAndUnload) {
   1559          aResolver(loadResults,
   1560                    nsIDocumentViewer::PermitUnloadResult::eContinue);
   1561          return;
   1562        }
   1563 
   1564        // PermitUnloadTraversable includes everything except the process of the
   1565        // top level browsing context.
   1566        windowGlobalParent->PermitUnloadChildNavigables(
   1567            action, [loadResults = std::move(loadResults), aResolver](
   1568                        nsIDocumentViewer::PermitUnloadResult aResult) mutable {
   1569              aResolver(loadResults, aResult);
   1570            });
   1571      });
   1572 
   1573  return true;
   1574 }
   1575 
   1576 // https://html.spec.whatwg.org/#apply-the-history-step
   1577 // nsSHistory::LoadURIs is also implementing #apply-the-history-step, maybe even
   1578 // more so than `CanonicalBrowsingContext::HistoryGo`.
   1579 /* static */
   1580 void nsSHistory::LoadURIs(const nsTArray<LoadEntryResult>& aLoadResults,
   1581                          bool aCheckForCancelation,
   1582                          const std::function<void(nsresult)>& aResolver,
   1583                          BrowsingContext* aTraversable) {
   1584  // Step 5. We need to handle the case where we shouldn't check for
   1585  // cancelation, e.g when our caller is
   1586  // #resume-applying-the-traverse-history-step, and we're already ran
   1587  // #checking-if-unloading-is-canceled
   1588  if (aCheckForCancelation &&
   1589      MaybeCheckUnloadingIsCanceled(
   1590          aLoadResults, aTraversable,
   1591          [traversable = RefPtr{aTraversable}, aResolver](
   1592              nsTArray<LoadEntryResult>& aLoadResults,
   1593              nsIDocumentViewer::PermitUnloadResult aResult)
   1594              MOZ_CAN_RUN_SCRIPT_BOUNDARY_LAMBDA {
   1595                if (aResult != nsIDocumentViewer::eContinue) {
   1596                  if (nsCOMPtr<nsFrameLoaderOwner> frameLoaderOwner =
   1597                          do_QueryInterface(
   1598                              traversable->GetEmbedderElement())) {
   1599                    if (RefPtr<nsFrameLoader> currentFrameLoader =
   1600                            frameLoaderOwner->GetFrameLoader()) {
   1601                      nsISHistory* shistory =
   1602                          currentFrameLoader->GetMaybePendingBrowsingContext()
   1603                              ->Canonical()
   1604                              ->GetSessionHistory();
   1605                      if (shistory) {
   1606                        shistory->InternalSetRequestedIndex(-1);
   1607                      }
   1608                    }
   1609                  }
   1610 
   1611                  // https://html.spec.whatwg.org/#performing-a-navigation-api-traversal
   1612                  // 12.5 If result is "canceled-by-beforeunload", then queue a
   1613                  //      global task on the navigation and traversal task
   1614                  //      source given navigation's relevant global object to
   1615                  //      reject the finished promise for apiMethodTracker with
   1616                  //      a new "AbortError" DOMException created in
   1617                  //      navigation's relevant realm.
   1618                  if (aResult == nsIDocumentViewer::eCanceledByBeforeUnload) {
   1619                    return aResolver(nsresult::NS_ERROR_DOM_ABORT_ERR);
   1620                  }
   1621 
   1622                  return aResolver(NS_OK);
   1623                }
   1624 
   1625                for (LoadEntryResult& loadEntry : aLoadResults) {
   1626                  loadEntry.mLoadState->SetNotifiedBeforeUnloadListeners(true);
   1627                  LoadURIOrBFCache(loadEntry);
   1628                }
   1629              })) {
   1630    return;
   1631  }
   1632 
   1633  // There's no beforeunload handlers, resolve immediately.
   1634  aResolver(NS_OK);
   1635 
   1636  // And we fall back to the simple case if we shouldn't fire a "traverse"
   1637  // navigate event.
   1638  for (const LoadEntryResult& loadEntry : aLoadResults) {
   1639    LoadURIOrBFCache(loadEntry);
   1640  }
   1641 }
   1642 
   1643 NS_IMETHODIMP
   1644 nsSHistory::Reload(uint32_t aReloadFlags) {
   1645  nsTArray<LoadEntryResult> loadResults;
   1646  nsresult rv = Reload(aReloadFlags, loadResults);
   1647  NS_ENSURE_SUCCESS(rv, rv);
   1648 
   1649  if (loadResults.IsEmpty()) {
   1650    return NS_OK;
   1651  }
   1652 
   1653  LoadURIs(loadResults, /* aCheckForCancelation */ true);
   1654  return NS_OK;
   1655 }
   1656 
   1657 nsresult nsSHistory::Reload(uint32_t aReloadFlags,
   1658                            nsTArray<LoadEntryResult>& aLoadResults) {
   1659  MOZ_ASSERT(aLoadResults.IsEmpty());
   1660 
   1661  uint32_t loadType;
   1662  if (aReloadFlags & nsIWebNavigation::LOAD_FLAGS_BYPASS_PROXY &&
   1663      aReloadFlags & nsIWebNavigation::LOAD_FLAGS_BYPASS_CACHE) {
   1664    loadType = LOAD_RELOAD_BYPASS_PROXY_AND_CACHE;
   1665  } else if (aReloadFlags & nsIWebNavigation::LOAD_FLAGS_BYPASS_PROXY) {
   1666    loadType = LOAD_RELOAD_BYPASS_PROXY;
   1667  } else if (aReloadFlags & nsIWebNavigation::LOAD_FLAGS_BYPASS_CACHE) {
   1668    loadType = LOAD_RELOAD_BYPASS_CACHE;
   1669  } else if (aReloadFlags & nsIWebNavigation::LOAD_FLAGS_CHARSET_CHANGE) {
   1670    loadType = LOAD_RELOAD_CHARSET_CHANGE;
   1671  } else {
   1672    loadType = LOAD_RELOAD_NORMAL;
   1673  }
   1674 
   1675  // We are reloading. Send Reload notifications.
   1676  // nsDocShellLoadFlagType is not public, where as nsIWebNavigation
   1677  // is public. So send the reload notifications with the
   1678  // nsIWebNavigation flags.
   1679  bool canNavigate = true;
   1680  MOZ_ALWAYS_SUCCEEDS(NotifyOnHistoryReload(&canNavigate));
   1681  if (!canNavigate) {
   1682    return NS_OK;
   1683  }
   1684 
   1685  nsresult rv =
   1686      LoadEntry(/* aSourceBrowsingContext */ nullptr, mIndex, loadType,
   1687                HIST_CMD_RELOAD, aLoadResults, /* aSameEpoch */ false,
   1688                /* aLoadCurrentEntry */ true,
   1689                aReloadFlags & nsIWebNavigation::LOAD_FLAGS_USER_ACTIVATION);
   1690  if (NS_FAILED(rv)) {
   1691    aLoadResults.Clear();
   1692    return rv;
   1693  }
   1694 
   1695  return NS_OK;
   1696 }
   1697 
   1698 NS_IMETHODIMP
   1699 nsSHistory::ReloadCurrentEntry() {
   1700  nsTArray<LoadEntryResult> loadResults;
   1701  nsresult rv = ReloadCurrentEntry(loadResults);
   1702  NS_ENSURE_SUCCESS(rv, rv);
   1703 
   1704  LoadURIs(loadResults, /* aCheckForCancelation */ true);
   1705  return NS_OK;
   1706 }
   1707 
   1708 nsresult nsSHistory::ReloadCurrentEntry(
   1709    nsTArray<LoadEntryResult>& aLoadResults) {
   1710  // Notify listeners
   1711  NotifyListeners(mListeners, [](auto l) { l->OnHistoryGotoIndex(); });
   1712 
   1713  return LoadEntry(/* aSourceBrowsingContext */ nullptr, mIndex, LOAD_HISTORY,
   1714                   HIST_CMD_RELOAD, aLoadResults,
   1715                   /* aSameEpoch */ false, /* aLoadCurrentEntry */ true,
   1716                   /* aUserActivation */ false);
   1717 }
   1718 
   1719 void nsSHistory::EvictOutOfRangeWindowDocumentViewers(int32_t aIndex) {
   1720  // XXX rename method to EvictDocumentViewersExceptAroundIndex, or something.
   1721 
   1722  // We need to release all content viewers that are no longer in the range
   1723  //
   1724  //  aIndex - VIEWER_WINDOW to aIndex + VIEWER_WINDOW
   1725  //
   1726  // to ensure that this SHistory object isn't responsible for more than
   1727  // VIEWER_WINDOW content viewers.  But our job is complicated by the
   1728  // fact that two entries which are related by either hash navigations or
   1729  // history.pushState will have the same content viewer.
   1730  //
   1731  // To illustrate the issue, suppose VIEWER_WINDOW = 3 and we have four
   1732  // linked entries in our history.  Suppose we then add a new content
   1733  // viewer and call into this function.  So the history looks like:
   1734  //
   1735  //   A A A A B
   1736  //     +     *
   1737  //
   1738  // where the letters are content viewers and + and * denote the beginning and
   1739  // end of the range aIndex +/- VIEWER_WINDOW.
   1740  //
   1741  // Although one copy of the content viewer A exists outside the range, we
   1742  // don't want to evict A, because it has other copies in range!
   1743  //
   1744  // We therefore adjust our eviction strategy to read:
   1745  //
   1746  //   Evict each content viewer outside the range aIndex -/+
   1747  //   VIEWER_WINDOW, unless that content viewer also appears within the
   1748  //   range.
   1749  //
   1750  // (Note that it's entirely legal to have two copies of one content viewer
   1751  // separated by a different content viewer -- call pushState twice, go back
   1752  // once, and refresh -- so we can't rely on identical viewers only appearing
   1753  // adjacent to one another.)
   1754 
   1755  if (aIndex < 0) {
   1756    return;
   1757  }
   1758  NS_ENSURE_TRUE_VOID(aIndex < Length());
   1759 
   1760  // Calculate the range that's safe from eviction.
   1761  int32_t startSafeIndex, endSafeIndex;
   1762  WindowIndices(aIndex, &startSafeIndex, &endSafeIndex);
   1763 
   1764  LOG(
   1765      ("EvictOutOfRangeWindowDocumentViewers(index=%d), "
   1766       "Length()=%d. Safe range [%d, %d]",
   1767       aIndex, Length(), startSafeIndex, endSafeIndex));
   1768 
   1769  // The content viewers in range aIndex -/+ VIEWER_WINDOW will not be
   1770  // evicted.  Collect a set of them so we don't accidentally evict one of them
   1771  // if it appears outside this range.
   1772  nsCOMArray<nsIDocumentViewer> safeViewers;
   1773  nsTArray<RefPtr<nsFrameLoader>> safeFrameLoaders;
   1774  for (int32_t i = startSafeIndex; i <= endSafeIndex; i++) {
   1775    nsCOMPtr<nsIDocumentViewer> viewer = mEntries[i]->GetDocumentViewer();
   1776    if (viewer) {
   1777      safeViewers.AppendObject(viewer);
   1778    } else if (nsCOMPtr<SessionHistoryEntry> she =
   1779                   do_QueryInterface(mEntries[i])) {
   1780      nsFrameLoader* frameLoader = she->GetFrameLoader();
   1781      if (frameLoader) {
   1782        safeFrameLoaders.AppendElement(frameLoader);
   1783      }
   1784    }
   1785  }
   1786 
   1787  // Walk the SHistory list and evict any content viewers that aren't safe.
   1788  // (It's important that the condition checks Length(), rather than a cached
   1789  // copy of Length(), because the length might change between iterations.)
   1790  for (int32_t i = 0; i < Length(); i++) {
   1791    nsCOMPtr<nsISHEntry> entry = mEntries[i];
   1792    nsCOMPtr<nsIDocumentViewer> viewer = entry->GetDocumentViewer();
   1793    if (viewer) {
   1794      if (safeViewers.IndexOf(viewer) == -1) {
   1795        EvictDocumentViewerForEntry(entry);
   1796      }
   1797    } else if (nsCOMPtr<SessionHistoryEntry> she =
   1798                   do_QueryInterface(mEntries[i])) {
   1799      nsFrameLoader* frameLoader = she->GetFrameLoader();
   1800      if (frameLoader) {
   1801        if (!safeFrameLoaders.Contains(frameLoader)) {
   1802          EvictDocumentViewerForEntry(entry);
   1803        }
   1804      }
   1805    }
   1806  }
   1807 }
   1808 
   1809 namespace {
   1810 
   1811 class EntryAndDistance {
   1812 public:
   1813  EntryAndDistance(nsSHistory* aSHistory, nsISHEntry* aEntry, uint32_t aDist)
   1814      : mSHistory(aSHistory),
   1815        mEntry(aEntry),
   1816        mViewer(aEntry->GetDocumentViewer()),
   1817        mLastTouched(mEntry->GetLastTouched()),
   1818        mDistance(aDist) {
   1819    nsCOMPtr<SessionHistoryEntry> she = do_QueryInterface(aEntry);
   1820    if (she) {
   1821      mFrameLoader = she->GetFrameLoader();
   1822    }
   1823    NS_ASSERTION(mViewer || mFrameLoader,
   1824                 "Entry should have a content viewer or frame loader.");
   1825  }
   1826 
   1827  bool operator<(const EntryAndDistance& aOther) const {
   1828    // Compare distances first, and fall back to last-accessed times.
   1829    if (aOther.mDistance != this->mDistance) {
   1830      return this->mDistance < aOther.mDistance;
   1831    }
   1832 
   1833    return this->mLastTouched < aOther.mLastTouched;
   1834  }
   1835 
   1836  bool operator==(const EntryAndDistance& aOther) const {
   1837    // This is a little silly; we need == so the default comaprator can be
   1838    // instantiated, but this function is never actually called when we sort
   1839    // the list of EntryAndDistance objects.
   1840    return aOther.mDistance == this->mDistance &&
   1841           aOther.mLastTouched == this->mLastTouched;
   1842  }
   1843 
   1844  RefPtr<nsSHistory> mSHistory;
   1845  nsCOMPtr<nsISHEntry> mEntry;
   1846  nsCOMPtr<nsIDocumentViewer> mViewer;
   1847  RefPtr<nsFrameLoader> mFrameLoader;
   1848  uint32_t mLastTouched;
   1849  int32_t mDistance;
   1850 };
   1851 
   1852 }  // namespace
   1853 
   1854 // static
   1855 void nsSHistory::GloballyEvictDocumentViewers() {
   1856  // First, collect from each SHistory object the entries which have a cached
   1857  // content viewer. Associate with each entry its distance from its SHistory's
   1858  // current index.
   1859 
   1860  nsTArray<EntryAndDistance> entries;
   1861 
   1862  for (auto shist : gSHistoryList.mList) {
   1863    // Maintain a list of the entries which have viewers and belong to
   1864    // this particular shist object.  We'll add this list to the global list,
   1865    // |entries|, eventually.
   1866    nsTArray<EntryAndDistance> shEntries;
   1867 
   1868    // Content viewers are likely to exist only within shist->mIndex -/+
   1869    // VIEWER_WINDOW, so only search within that range.
   1870    //
   1871    // A content viewer might exist outside that range due to either:
   1872    //
   1873    //   * history.pushState or hash navigations, in which case a copy of the
   1874    //     content viewer should exist within the range, or
   1875    //
   1876    //   * bugs which cause us not to call nsSHistory::EvictDocumentViewers()
   1877    //     often enough.  Once we do call EvictDocumentViewers() for the
   1878    //     SHistory object in question, we'll do a full search of its history
   1879    //     and evict the out-of-range content viewers, so we don't bother here.
   1880    //
   1881    int32_t startIndex, endIndex;
   1882    shist->WindowIndices(shist->mIndex, &startIndex, &endIndex);
   1883    for (int32_t i = startIndex; i <= endIndex; i++) {
   1884      nsCOMPtr<nsISHEntry> entry = shist->mEntries[i];
   1885      nsCOMPtr<nsIDocumentViewer> viewer = entry->GetDocumentViewer();
   1886 
   1887      bool found = false;
   1888      bool hasDocumentViewerOrFrameLoader = false;
   1889      if (viewer) {
   1890        hasDocumentViewerOrFrameLoader = true;
   1891        // Because one content viewer might belong to multiple SHEntries, we
   1892        // have to search through shEntries to see if we already know
   1893        // about this content viewer.  If we find the viewer, update its
   1894        // distance from the SHistory's index and continue.
   1895        for (uint32_t j = 0; j < shEntries.Length(); j++) {
   1896          EntryAndDistance& container = shEntries[j];
   1897          if (container.mViewer == viewer) {
   1898            container.mDistance =
   1899                std::min(container.mDistance, DeprecatedAbs(i - shist->mIndex));
   1900            found = true;
   1901            break;
   1902          }
   1903        }
   1904      } else if (nsCOMPtr<SessionHistoryEntry> she = do_QueryInterface(entry)) {
   1905        if (RefPtr<nsFrameLoader> frameLoader = she->GetFrameLoader()) {
   1906          hasDocumentViewerOrFrameLoader = true;
   1907          // Similar search as above but using frameloader.
   1908          for (uint32_t j = 0; j < shEntries.Length(); j++) {
   1909            EntryAndDistance& container = shEntries[j];
   1910            if (container.mFrameLoader == frameLoader) {
   1911              container.mDistance = std::min(container.mDistance,
   1912                                             DeprecatedAbs(i - shist->mIndex));
   1913              found = true;
   1914              break;
   1915            }
   1916          }
   1917        }
   1918      }
   1919 
   1920      // If we didn't find a EntryAndDistance for this content viewer /
   1921      // frameloader, make a new one.
   1922      if (hasDocumentViewerOrFrameLoader && !found) {
   1923        EntryAndDistance container(shist, entry,
   1924                                   DeprecatedAbs(i - shist->mIndex));
   1925        shEntries.AppendElement(container);
   1926      }
   1927    }
   1928 
   1929    // We've found all the entries belonging to shist which have viewers.
   1930    // Add those entries to our global list and move on.
   1931    entries.AppendElements(shEntries);
   1932  }
   1933 
   1934  // We now have collected all cached content viewers.  First check that we
   1935  // have enough that we actually need to evict some.
   1936  if ((int32_t)entries.Length() <= sHistoryMaxTotalViewers) {
   1937    return;
   1938  }
   1939 
   1940  // If we need to evict, sort our list of entries and evict the largest
   1941  // ones.  (We could of course get better algorithmic complexity here by using
   1942  // a heap or something more clever.  But sHistoryMaxTotalViewers isn't large,
   1943  // so let's not worry about it.)
   1944  entries.Sort();
   1945 
   1946  for (int32_t i = entries.Length() - 1; i >= sHistoryMaxTotalViewers; --i) {
   1947    (entries[i].mSHistory)->EvictDocumentViewerForEntry(entries[i].mEntry);
   1948  }
   1949 }
   1950 
   1951 nsresult nsSHistory::FindEntryForBFCache(SHEntrySharedParentState* aEntry,
   1952                                         nsISHEntry** aResult,
   1953                                         int32_t* aResultIndex) {
   1954  *aResult = nullptr;
   1955  *aResultIndex = -1;
   1956 
   1957  int32_t startIndex, endIndex;
   1958  WindowIndices(mIndex, &startIndex, &endIndex);
   1959 
   1960  for (int32_t i = startIndex; i <= endIndex; ++i) {
   1961    nsCOMPtr<nsISHEntry> shEntry = mEntries[i];
   1962 
   1963    // Does shEntry have the same BFCacheEntry as the argument to this method?
   1964    if (shEntry->HasBFCacheEntry(aEntry)) {
   1965      shEntry.forget(aResult);
   1966      *aResultIndex = i;
   1967      return NS_OK;
   1968    }
   1969  }
   1970  return NS_ERROR_FAILURE;
   1971 }
   1972 
   1973 NS_IMETHODIMP_(void)
   1974 nsSHistory::EvictExpiredDocumentViewerForEntry(
   1975    SHEntrySharedParentState* aEntry) {
   1976  int32_t index;
   1977  nsCOMPtr<nsISHEntry> shEntry;
   1978  FindEntryForBFCache(aEntry, getter_AddRefs(shEntry), &index);
   1979 
   1980  if (index == mIndex) {
   1981    NS_WARNING("How did the current SHEntry expire?");
   1982  }
   1983 
   1984  if (shEntry) {
   1985    EvictDocumentViewerForEntry(shEntry);
   1986  }
   1987 }
   1988 
   1989 NS_IMETHODIMP_(void)
   1990 nsSHistory::AddToExpirationTracker(SHEntrySharedParentState* aEntry) {
   1991  RefPtr<SHEntrySharedParentState> entry = aEntry;
   1992  if (!mHistoryTracker || !entry) {
   1993    return;
   1994  }
   1995 
   1996  mHistoryTracker->AddObject(entry);
   1997  return;
   1998 }
   1999 
   2000 NS_IMETHODIMP_(void)
   2001 nsSHistory::RemoveFromExpirationTracker(SHEntrySharedParentState* aEntry) {
   2002  RefPtr<SHEntrySharedParentState> entry = aEntry;
   2003  MOZ_ASSERT(mHistoryTracker && !mHistoryTracker->IsEmpty());
   2004  if (!mHistoryTracker || !entry) {
   2005    return;
   2006  }
   2007 
   2008  mHistoryTracker->RemoveObject(entry);
   2009 }
   2010 
   2011 // Evicts all content viewers in all history objects.  This is very
   2012 // inefficient, because it requires a linear search through all SHistory
   2013 // objects for each viewer to be evicted.  However, this method is called
   2014 // infrequently -- only when the disk or memory cache is cleared.
   2015 
   2016 // static
   2017 void nsSHistory::GloballyEvictAllDocumentViewers() {
   2018  int32_t maxViewers = sHistoryMaxTotalViewers;
   2019  sHistoryMaxTotalViewers = 0;
   2020  GloballyEvictDocumentViewers();
   2021  sHistoryMaxTotalViewers = maxViewers;
   2022 }
   2023 
   2024 void GetDynamicChildren(nsISHEntry* aEntry, nsTArray<nsID>& aDocshellIDs) {
   2025  int32_t count = aEntry->GetChildCount();
   2026  for (int32_t i = 0; i < count; ++i) {
   2027    nsCOMPtr<nsISHEntry> child;
   2028    aEntry->GetChildAt(i, getter_AddRefs(child));
   2029    if (child) {
   2030      if (child->IsDynamicallyAdded()) {
   2031        child->GetDocshellID(*aDocshellIDs.AppendElement());
   2032      } else {
   2033        GetDynamicChildren(child, aDocshellIDs);
   2034      }
   2035    }
   2036  }
   2037 }
   2038 
   2039 bool RemoveFromSessionHistoryEntry(nsISHEntry* aRoot,
   2040                                   nsTArray<nsID>& aDocshellIDs) {
   2041  bool didRemove = false;
   2042  int32_t childCount = aRoot->GetChildCount();
   2043  for (int32_t i = childCount - 1; i >= 0; --i) {
   2044    nsCOMPtr<nsISHEntry> child;
   2045    aRoot->GetChildAt(i, getter_AddRefs(child));
   2046    if (child) {
   2047      nsID docshelldID;
   2048      child->GetDocshellID(docshelldID);
   2049      if (aDocshellIDs.Contains(docshelldID)) {
   2050        didRemove = true;
   2051        aRoot->RemoveChild(child);
   2052      } else if (RemoveFromSessionHistoryEntry(child, aDocshellIDs)) {
   2053        didRemove = true;
   2054      }
   2055    }
   2056  }
   2057  return didRemove;
   2058 }
   2059 
   2060 bool RemoveChildEntries(nsISHistory* aHistory, int32_t aIndex,
   2061                        nsTArray<nsID>& aEntryIDs) {
   2062  nsCOMPtr<nsISHEntry> root;
   2063  aHistory->GetEntryAtIndex(aIndex, getter_AddRefs(root));
   2064  return root ? RemoveFromSessionHistoryEntry(root, aEntryIDs) : false;
   2065 }
   2066 
   2067 bool IsSameTree(nsISHEntry* aEntry1, nsISHEntry* aEntry2) {
   2068  if (!aEntry1 && !aEntry2) {
   2069    return true;
   2070  }
   2071  if ((!aEntry1 && aEntry2) || (aEntry1 && !aEntry2)) {
   2072    return false;
   2073  }
   2074  uint32_t id1 = aEntry1->GetID();
   2075  uint32_t id2 = aEntry2->GetID();
   2076  if (id1 != id2) {
   2077    return false;
   2078  }
   2079 
   2080  int32_t count1 = aEntry1->GetChildCount();
   2081  int32_t count2 = aEntry2->GetChildCount();
   2082  // We allow null entries in the end of the child list.
   2083  int32_t count = std::max(count1, count2);
   2084  for (int32_t i = 0; i < count; ++i) {
   2085    nsCOMPtr<nsISHEntry> child1, child2;
   2086    aEntry1->GetChildAt(i, getter_AddRefs(child1));
   2087    aEntry2->GetChildAt(i, getter_AddRefs(child2));
   2088    if (!IsSameTree(child1, child2)) {
   2089      return false;
   2090    }
   2091  }
   2092 
   2093  return true;
   2094 }
   2095 
   2096 bool nsSHistory::RemoveDuplicate(int32_t aIndex, bool aKeepNext) {
   2097  NS_ASSERTION(aIndex >= 0, "aIndex must be >= 0!");
   2098  NS_ASSERTION(aIndex != 0 || aKeepNext,
   2099               "If we're removing index 0 we must be keeping the next");
   2100  NS_ASSERTION(aIndex != mIndex, "Shouldn't remove mIndex!");
   2101 
   2102  int32_t compareIndex = aKeepNext ? aIndex + 1 : aIndex - 1;
   2103 
   2104  nsresult rv;
   2105  nsCOMPtr<nsISHEntry> root1, root2;
   2106  rv = GetEntryAtIndex(aIndex, getter_AddRefs(root1));
   2107  if (NS_FAILED(rv)) {
   2108    return false;
   2109  }
   2110  rv = GetEntryAtIndex(compareIndex, getter_AddRefs(root2));
   2111  if (NS_FAILED(rv)) {
   2112    return false;
   2113  }
   2114 
   2115  SHistoryChangeNotifier change(this);
   2116 
   2117  if (IsSameTree(root1, root2)) {
   2118    if (aIndex < compareIndex) {
   2119      // If we're removing the entry with the lower index we need to move its
   2120      // BCHistoryLength to the entry we're keeping. If we're removing the entry
   2121      // with the higher index then it shouldn't have a modified
   2122      // BCHistoryLength.
   2123      UpdateEntryLength(root1, root2, true);
   2124    }
   2125    nsCOMPtr<SessionHistoryEntry> she = do_QueryInterface(root1);
   2126    if (she) {
   2127      ClearEntries(she);
   2128    }
   2129    mEntries.RemoveElementAt(aIndex);
   2130 
   2131    // FIXME Bug 1546350: Reimplement history listeners.
   2132    // if (mRootBC && mRootBC->GetDocShell()) {
   2133    //  static_cast<nsDocShell*>(mRootBC->GetDocShell())
   2134    //      ->HistoryEntryRemoved(aIndex);
   2135    //}
   2136 
   2137    // Adjust our indices to reflect the removed entry.
   2138    if (mIndex > aIndex) {
   2139      mIndex = mIndex - 1;
   2140    }
   2141 
   2142    // NB: If the entry we are removing is the entry currently
   2143    // being navigated to (mRequestedIndex) then we adjust the index
   2144    // only if we're not keeping the next entry (because if we are keeping
   2145    // the next entry (because the current is a duplicate of the next), then
   2146    // that entry slides into the spot that we're currently pointing to.
   2147    // We don't do this adjustment for mIndex because mIndex cannot equal
   2148    // aIndex.
   2149 
   2150    // NB: We don't need to guard on mRequestedIndex being nonzero here,
   2151    // because either they're strictly greater than aIndex which is at least
   2152    // zero, or they are equal to aIndex in which case aKeepNext must be true
   2153    // if aIndex is zero.
   2154    if (mRequestedIndex > aIndex || (mRequestedIndex == aIndex && !aKeepNext)) {
   2155      mRequestedIndex = mRequestedIndex - 1;
   2156    }
   2157 
   2158    return true;
   2159  }
   2160  return false;
   2161 }
   2162 
   2163 NS_IMETHODIMP_(void)
   2164 nsSHistory::RemoveEntries(nsTArray<nsID>& aIDs, int32_t aStartIndex) {
   2165  bool didRemove;
   2166  RemoveEntries(aIDs, aStartIndex, &didRemove);
   2167  if (didRemove) {
   2168    RefPtr<BrowsingContext> rootBC = GetBrowsingContext();
   2169    if (rootBC && rootBC->GetDocShell()) {
   2170      rootBC->GetDocShell()->DispatchLocationChangeEvent();
   2171    }
   2172  }
   2173 }
   2174 
   2175 void nsSHistory::RemoveEntries(nsTArray<nsID>& aIDs, int32_t aStartIndex,
   2176                               bool* aDidRemove) {
   2177  SHistoryChangeNotifier change(this);
   2178 
   2179  int32_t index = aStartIndex;
   2180  while (index >= 0 && RemoveChildEntries(this, --index, aIDs)) {
   2181  }
   2182  int32_t minIndex = index;
   2183  index = aStartIndex;
   2184  while (index >= 0 && RemoveChildEntries(this, index++, aIDs)) {
   2185  }
   2186 
   2187  // We need to remove duplicate nsSHEntry trees.
   2188  *aDidRemove = false;
   2189  while (index > minIndex) {
   2190    if (index != mIndex && RemoveDuplicate(index, index < mIndex)) {
   2191      *aDidRemove = true;
   2192    }
   2193    --index;
   2194  }
   2195 }
   2196 
   2197 void nsSHistory::RemoveFrameEntries(nsISHEntry* aEntry) {
   2198  int32_t count = aEntry->GetChildCount();
   2199  AutoTArray<nsID, 16> ids;
   2200  for (int32_t i = 0; i < count; ++i) {
   2201    nsCOMPtr<nsISHEntry> child;
   2202    aEntry->GetChildAt(i, getter_AddRefs(child));
   2203    if (child) {
   2204      child->GetDocshellID(*ids.AppendElement());
   2205    }
   2206  }
   2207  RemoveEntries(ids, mIndex);
   2208 }
   2209 
   2210 void nsSHistory::RemoveDynEntries(int32_t aIndex, nsISHEntry* aEntry) {
   2211  // Remove dynamic entries which are at the index and belongs to the container.
   2212  nsCOMPtr<nsISHEntry> entry(aEntry);
   2213  if (!entry) {
   2214    GetEntryAtIndex(aIndex, getter_AddRefs(entry));
   2215  }
   2216 
   2217  if (entry) {
   2218    AutoTArray<nsID, 16> toBeRemovedEntries;
   2219    GetDynamicChildren(entry, toBeRemovedEntries);
   2220    if (toBeRemovedEntries.Length()) {
   2221      RemoveEntries(toBeRemovedEntries, aIndex);
   2222    }
   2223  }
   2224 }
   2225 
   2226 void nsSHistory::RemoveDynEntriesForBFCacheEntry(nsIBFCacheEntry* aBFEntry) {
   2227  int32_t index;
   2228  nsCOMPtr<nsISHEntry> shEntry;
   2229  FindEntryForBFCache(static_cast<nsSHEntryShared*>(aBFEntry),
   2230                      getter_AddRefs(shEntry), &index);
   2231  if (shEntry) {
   2232    RemoveDynEntries(index, shEntry);
   2233  }
   2234 }
   2235 
   2236 NS_IMETHODIMP
   2237 nsSHistory::UpdateIndex() {
   2238  SHistoryChangeNotifier change(this);
   2239 
   2240  // Update the actual index with the right value.
   2241  if (mIndex != mRequestedIndex && mRequestedIndex != -1) {
   2242    mIndex = mRequestedIndex;
   2243  }
   2244 
   2245  mRequestedIndex = -1;
   2246  return NS_OK;
   2247 }
   2248 
   2249 NS_IMETHODIMP
   2250 nsSHistory::GotoIndex(int32_t aIndex, bool aUserActivation) {
   2251  nsTArray<LoadEntryResult> loadResults;
   2252  nsresult rv =
   2253      GotoIndex(/* aSourceBrowsingContext */ nullptr, aIndex, loadResults,
   2254                /*aSameEpoch*/ false, aIndex == mIndex, aUserActivation);
   2255  NS_ENSURE_SUCCESS(rv, rv);
   2256 
   2257  LoadURIs(loadResults, /* aCheckForCancelation */ true);
   2258  return NS_OK;
   2259 }
   2260 
   2261 NS_IMETHODIMP_(void)
   2262 nsSHistory::EnsureCorrectEntryAtCurrIndex(nsISHEntry* aEntry) {
   2263  int index = mRequestedIndex == -1 ? mIndex : mRequestedIndex;
   2264  if (index > -1 && (mEntries[index] != aEntry)) {
   2265    ReplaceEntry(index, aEntry);
   2266  }
   2267 }
   2268 
   2269 nsresult nsSHistory::GotoIndex(BrowsingContext* aSourceBrowsingContext,
   2270                               int32_t aIndex,
   2271                               nsTArray<LoadEntryResult>& aLoadResults,
   2272                               bool aSameEpoch, bool aLoadCurrentEntry,
   2273                               bool aUserActivation) {
   2274  return LoadEntry(aSourceBrowsingContext, aIndex, LOAD_HISTORY,
   2275                   HIST_CMD_GOTOINDEX, aLoadResults, aSameEpoch,
   2276                   aLoadCurrentEntry, aUserActivation);
   2277 }
   2278 
   2279 NS_IMETHODIMP_(bool)
   2280 nsSHistory::HasUserInteractionAtIndex(int32_t aIndex) {
   2281  nsCOMPtr<nsISHEntry> entry;
   2282  GetEntryAtIndex(aIndex, getter_AddRefs(entry));
   2283  if (!entry) {
   2284    return false;
   2285  }
   2286  return entry->GetHasUserInteraction();
   2287 }
   2288 
   2289 NS_IMETHODIMP
   2290 nsSHistory::CanGoBackFromEntryAtIndex(int32_t aIndex, bool* aCanGoBack) {
   2291  *aCanGoBack = false;
   2292  if (!StaticPrefs::browser_navigation_requireUserInteraction()) {
   2293    *aCanGoBack = aIndex > 0;
   2294    return NS_OK;
   2295  }
   2296 
   2297  for (int32_t i = aIndex - 1; i >= 0; i--) {
   2298    if (HasUserInteractionAtIndex(i)) {
   2299      *aCanGoBack = true;
   2300      break;
   2301    }
   2302  }
   2303 
   2304  return NS_OK;
   2305 }
   2306 
   2307 nsresult nsSHistory::LoadNextPossibleEntry(
   2308    BrowsingContext* aSourceBrowsingContext, int32_t aNewIndex, long aLoadType,
   2309    uint32_t aHistCmd, nsTArray<LoadEntryResult>& aLoadResults,
   2310    bool aLoadCurrentEntry, bool aUserActivation) {
   2311  mRequestedIndex = -1;
   2312  if (aNewIndex < mIndex) {
   2313    return LoadEntry(aSourceBrowsingContext, aNewIndex - 1, aLoadType, aHistCmd,
   2314                     aLoadResults,
   2315                     /*aSameEpoch*/ false, aLoadCurrentEntry, aUserActivation);
   2316  }
   2317  if (aNewIndex > mIndex) {
   2318    return LoadEntry(aSourceBrowsingContext, aNewIndex + 1, aLoadType, aHistCmd,
   2319                     aLoadResults,
   2320                     /*aSameEpoch*/ false, aLoadCurrentEntry, aUserActivation);
   2321  }
   2322  return NS_ERROR_FAILURE;
   2323 }
   2324 
   2325 nsresult nsSHistory::LoadEntry(BrowsingContext* aSourceBrowsingContext,
   2326                               int32_t aIndex, long aLoadType,
   2327                               uint32_t aHistCmd,
   2328                               nsTArray<LoadEntryResult>& aLoadResults,
   2329                               bool aSameEpoch, bool aLoadCurrentEntry,
   2330                               bool aUserActivation) {
   2331  MOZ_LOG(gSHistoryLog, LogLevel::Debug,
   2332          ("LoadEntry(%d, 0x%lx, %u)", aIndex, aLoadType, aHistCmd));
   2333  RefPtr<BrowsingContext> rootBC = GetBrowsingContext();
   2334  if (!rootBC) {
   2335    return NS_ERROR_FAILURE;
   2336  }
   2337 
   2338  if (aIndex < 0 || aIndex >= Length()) {
   2339    MOZ_LOG(gSHistoryLog, LogLevel::Debug, ("Index out of range"));
   2340    // The index is out of range.
   2341    // Clear the requested index in case it had bogus value. This way the next
   2342    // load succeeds if the offset is reasonable.
   2343    mRequestedIndex = -1;
   2344 
   2345    return NS_ERROR_FAILURE;
   2346  }
   2347 
   2348  int32_t originalRequestedIndex = mRequestedIndex;
   2349  int32_t previousRequest = mRequestedIndex > -1 ? mRequestedIndex : mIndex;
   2350  int32_t requestedOffset = aIndex - previousRequest;
   2351 
   2352  // This is a normal local history navigation.
   2353 
   2354  nsCOMPtr<nsISHEntry> prevEntry;
   2355  nsCOMPtr<nsISHEntry> nextEntry;
   2356  GetEntryAtIndex(mIndex, getter_AddRefs(prevEntry));
   2357  GetEntryAtIndex(aIndex, getter_AddRefs(nextEntry));
   2358  if (!nextEntry || !prevEntry) {
   2359    mRequestedIndex = -1;
   2360    return NS_ERROR_FAILURE;
   2361  }
   2362 
   2363  if (mozilla::SessionHistoryInParent()) {
   2364    if (aHistCmd == HIST_CMD_GOTOINDEX) {
   2365      // https://html.spec.whatwg.org/#history-traversal:
   2366      // To traverse the history
   2367      // "If entry has a different Document object than the current entry, then
   2368      // run the following substeps: Remove any tasks queued by the history
   2369      // traversal task source..."
   2370      //
   2371      // aSameEpoch is true only if the navigations would have been
   2372      // generated in the same spin of the event loop (i.e. history.go(-2);
   2373      // history.go(-1)) and from the same content process.
   2374      if (aSameEpoch) {
   2375        bool same_doc = false;
   2376        prevEntry->SharesDocumentWith(nextEntry, &same_doc);
   2377        if (!same_doc) {
   2378          MOZ_LOG(
   2379              gSHistoryLog, LogLevel::Debug,
   2380              ("Aborting GotoIndex %d - same epoch and not same doc", aIndex));
   2381          // Ignore this load. Before SessionHistoryInParent, this would
   2382          // have been dropped in InternalLoad after we filter out SameDoc
   2383          // loads.
   2384          return NS_ERROR_FAILURE;
   2385        }
   2386      }
   2387    }
   2388  }
   2389  // Keep note of requested history index in mRequestedIndex; after all bailouts
   2390  mRequestedIndex = aIndex;
   2391 
   2392  // Remember that this entry is getting loaded at this point in the sequence
   2393 
   2394  nextEntry->SetLastTouched(++gTouchCounter);
   2395 
   2396  // Get the uri for the entry we are about to visit
   2397  nsCOMPtr<nsIURI> nextURI = nextEntry->GetURI();
   2398 
   2399  MOZ_ASSERT(nextURI, "nextURI can't be null");
   2400 
   2401  // Send appropriate listener notifications.
   2402  if (aHistCmd == HIST_CMD_GOTOINDEX) {
   2403    // We are going somewhere else. This is not reload either
   2404    NotifyListeners(mListeners, [](auto l) { l->OnHistoryGotoIndex(); });
   2405  }
   2406 
   2407  if (mRequestedIndex == mIndex) {
   2408    // Possibly a reload case
   2409    InitiateLoad(aSourceBrowsingContext, nextEntry, rootBC, aLoadType,
   2410                 aLoadResults, aLoadCurrentEntry, aUserActivation,
   2411                 requestedOffset);
   2412    return NS_OK;
   2413  }
   2414 
   2415  // Going back or forward.
   2416  bool differenceFound = ForEachDifferingEntry(
   2417      prevEntry, nextEntry, rootBC,
   2418      [self = RefPtr{this},
   2419       sourceBrowsingContext = RefPtr{aSourceBrowsingContext}, aLoadType,
   2420       &aLoadResults, aLoadCurrentEntry, aUserActivation,
   2421       requestedOffset](nsISHEntry* aEntry, BrowsingContext* aParent) {
   2422        // Set the Subframe flag if not navigating the root docshell.
   2423        aEntry->SetIsSubFrame(aParent->Id() != self->mRootBC);
   2424        self->InitiateLoad(sourceBrowsingContext, aEntry, aParent, aLoadType,
   2425                           aLoadResults, aLoadCurrentEntry, aUserActivation,
   2426                           requestedOffset);
   2427      });
   2428  if (!differenceFound) {
   2429    // LoadNextPossibleEntry will change the offset by one, and in order
   2430    // to keep track of the requestedOffset, need to reset mRequestedIndex to
   2431    // the value it had initially.
   2432    mRequestedIndex = originalRequestedIndex;
   2433    // We did not find any differences. Go further in the history.
   2434    return LoadNextPossibleEntry(aSourceBrowsingContext, aIndex, aLoadType,
   2435                                 aHistCmd, aLoadResults, aLoadCurrentEntry,
   2436                                 aUserActivation);
   2437  }
   2438 
   2439  return NS_OK;
   2440 }
   2441 
   2442 namespace {
   2443 
   2444 // Walk the subtree downwards from aSubtreeRoot, finding the next entry in the
   2445 // list of ancestors, returning only if we are able to find the last ancestor -
   2446 // the parent we are looking for.
   2447 // aAncestors is ordered starting from the root down to the parent entry.
   2448 SessionHistoryEntry* FindParent(Span<SessionHistoryEntry*> aAncestors,
   2449                                SessionHistoryEntry* aSubtreeRoot) {
   2450  if (!aSubtreeRoot || aAncestors.IsEmpty() ||
   2451      !aAncestors[0]->Info().SharesDocumentWith(aSubtreeRoot->Info())) {
   2452    return nullptr;
   2453  }
   2454  if (aAncestors.Length() == 1) {
   2455    return aSubtreeRoot;
   2456  }
   2457  for (const auto& child : aSubtreeRoot->Children()) {
   2458    if (auto* foundParent = FindParent(aAncestors.From(1), child)) {
   2459      return foundParent;
   2460    }
   2461  }
   2462  return nullptr;
   2463 }
   2464 
   2465 class SessionHistoryEntryIDComparator {
   2466 public:
   2467  static bool Equals(const RefPtr<SessionHistoryEntry>& aLhs,
   2468                     const RefPtr<SessionHistoryEntry>& aRhs) {
   2469    return aLhs && aRhs && aLhs->GetID() == aRhs->GetID();
   2470  }
   2471 };
   2472 
   2473 }  // namespace
   2474 
   2475 mozilla::dom::SessionHistoryEntry* nsSHistory::FindAdjacentContiguousEntryFor(
   2476    mozilla::dom::SessionHistoryEntry* aEntry, int32_t aSearchDirection) {
   2477  MOZ_ASSERT(aSearchDirection == 1 || aSearchDirection == -1);
   2478 
   2479  // Construct the list of ancestors of aEntry, starting with the root entry
   2480  // down to it's immediate parent.
   2481  nsCOMPtr<nsISHEntry> ancestor = aEntry->GetParent();
   2482  nsTArray<SessionHistoryEntry*> ancestors;
   2483  while (ancestor) {
   2484    ancestors.AppendElement(static_cast<SessionHistoryEntry*>(ancestor.get()));
   2485    ancestor = ancestor->GetParent();
   2486  }
   2487  ancestors.Reverse();
   2488 
   2489  nsCOMPtr<nsISHEntry> rootEntry = ancestors.IsEmpty() ? aEntry : ancestors[0];
   2490  nsCOMPtr<nsISHEntry> nextEntry;
   2491  SessionHistoryEntry* foundParent = nullptr;
   2492  for (int32_t i = GetIndexOfEntry(rootEntry) + aSearchDirection;
   2493       i >= 0 && i < Length(); i += aSearchDirection) {
   2494    nextEntry = mEntries[i];
   2495    foundParent = FindParent(
   2496        ancestors, static_cast<SessionHistoryEntry*>(nextEntry.get()));
   2497    if ((!foundParent && nextEntry->GetID() != aEntry->GetID()) ||
   2498        (foundParent && !foundParent->Children().Contains(
   2499                            aEntry, SessionHistoryEntryIDComparator()))) {
   2500      break;
   2501    }
   2502  }
   2503 
   2504  if (foundParent) {
   2505    for (const auto& child : foundParent->Children()) {
   2506      if (child && child->DocshellID() == aEntry->DocshellID()) {
   2507        return child->GetID() != aEntry->GetID() ? child.get() : nullptr;
   2508      }
   2509    }
   2510  } else if (ancestors.IsEmpty() && nextEntry &&
   2511             nextEntry->GetID() != aEntry->GetID()) {
   2512    return static_cast<SessionHistoryEntry*>(nextEntry.get());
   2513  }
   2514  return nullptr;
   2515 }
   2516 
   2517 void nsSHistory::ReconstructContiguousEntryListFrom(
   2518    SessionHistoryEntry* aEntry) {
   2519  RefPtr entryList = EntryListFor(aEntry->DocshellID());
   2520  entryList->clear();
   2521 
   2522  if (aEntry->isInList()) {
   2523    aEntry->remove();
   2524  }
   2525  entryList->insertBack(aEntry);
   2526 
   2527  for (auto* entry = aEntry;
   2528       (entry = FindAdjacentContiguousEntryFor(entry, -1));) {
   2529    if (entry->isInList()) {
   2530      entry->remove();
   2531    }
   2532    entryList->insertFront(entry);
   2533  }
   2534 
   2535  for (auto* entry = aEntry;
   2536       (entry = FindAdjacentContiguousEntryFor(entry, 1));) {
   2537    if (entry->isInList()) {
   2538      entry->remove();
   2539    }
   2540    entryList->insertBack(entry);
   2541  }
   2542 }
   2543 
   2544 void nsSHistory::ReconstructContiguousEntryList() {
   2545  MOZ_ASSERT(mIndex >= 0 && mIndex < Length());
   2546  nsCOMPtr currentEntry = mEntries[mIndex];
   2547  ReconstructContiguousEntryListFrom(
   2548      static_cast<SessionHistoryEntry*>(currentEntry.get()));
   2549 }
   2550 
   2551 bool nsSHistory::ForEachDifferingEntry(
   2552    nsISHEntry* aPrevEntry, nsISHEntry* aNextEntry, BrowsingContext* aParent,
   2553    const std::function<void(nsISHEntry*, BrowsingContext*)>& aCallback) {
   2554  MOZ_ASSERT(aPrevEntry && aNextEntry && aParent);
   2555 
   2556  uint32_t prevID = aPrevEntry->GetID();
   2557  uint32_t nextID = aNextEntry->GetID();
   2558 
   2559  // Check the IDs to verify if the pages are different.
   2560  if (prevID != nextID) {
   2561    aCallback(aNextEntry, aParent);
   2562    return true;
   2563  }
   2564 
   2565  // The entries are the same, so compare any child frames
   2566  int32_t pcnt = aPrevEntry->GetChildCount();
   2567  int32_t ncnt = aNextEntry->GetChildCount();
   2568 
   2569  // Create an array for child browsing contexts.
   2570  nsTArray<RefPtr<BrowsingContext>> browsingContexts;
   2571  aParent->GetChildren(browsingContexts);
   2572 
   2573  // Search for something to load next.
   2574  bool differenceFound = false;
   2575  for (int32_t i = 0; i < ncnt; ++i) {
   2576    // First get an entry which may cause a new page to be loaded.
   2577    nsCOMPtr<nsISHEntry> nChild;
   2578    aNextEntry->GetChildAt(i, getter_AddRefs(nChild));
   2579    if (!nChild) {
   2580      continue;
   2581    }
   2582    nsID docshellID;
   2583    nChild->GetDocshellID(docshellID);
   2584 
   2585    // Then find the associated docshell.
   2586    RefPtr<BrowsingContext> bcChild;
   2587    for (const RefPtr<BrowsingContext>& bc : browsingContexts) {
   2588      if (bc->GetHistoryID() == docshellID) {
   2589        bcChild = bc;
   2590        break;
   2591      }
   2592    }
   2593    if (!bcChild) {
   2594      continue;
   2595    }
   2596 
   2597    // Then look at the previous entries to see if there was
   2598    // an entry for the docshell.
   2599    nsCOMPtr<nsISHEntry> pChild;
   2600    for (int32_t k = 0; k < pcnt; ++k) {
   2601      nsCOMPtr<nsISHEntry> child;
   2602      aPrevEntry->GetChildAt(k, getter_AddRefs(child));
   2603      if (child) {
   2604        nsID dID;
   2605        child->GetDocshellID(dID);
   2606        if (dID == docshellID) {
   2607          pChild = child;
   2608          break;
   2609        }
   2610      }
   2611    }
   2612    if (!pChild) {
   2613      continue;
   2614    }
   2615 
   2616    if (ForEachDifferingEntry(pChild, nChild, bcChild, aCallback)) {
   2617      differenceFound = true;
   2618    }
   2619  }
   2620  return differenceFound;
   2621 }
   2622 
   2623 void nsSHistory::InitiateLoad(BrowsingContext* aSourceBrowsingContext,
   2624                              nsISHEntry* aFrameEntry,
   2625                              BrowsingContext* aFrameBC, long aLoadType,
   2626                              nsTArray<LoadEntryResult>& aLoadResults,
   2627                              bool aLoadCurrentEntry, bool aUserActivation,
   2628                              int32_t aOffset) {
   2629  MOZ_ASSERT(aFrameBC && aFrameEntry);
   2630 
   2631  LoadEntryResult* loadResult = aLoadResults.AppendElement();
   2632  loadResult->mBrowsingContext = aFrameBC;
   2633 
   2634  nsCOMPtr<nsIURI> newURI = aFrameEntry->GetURI();
   2635  RefPtr<nsDocShellLoadState> loadState = new nsDocShellLoadState(newURI);
   2636 
   2637  loadState->SetSourceBrowsingContext(aSourceBrowsingContext);
   2638 
   2639  loadState->SetHasValidUserGestureActivation(aUserActivation);
   2640 
   2641  // At the time we initiate a history entry load we already know if https-first
   2642  // was able to upgrade the request from http to https. There is no point in
   2643  // re-retrying to upgrade.
   2644  loadState->SetIsExemptFromHTTPSFirstMode(true);
   2645 
   2646  /* Set the loadType in the SHEntry too to  what was passed on.
   2647   * This will be passed on to child subframes later in nsDocShell,
   2648   * so that proper loadType is maintained through out a frameset
   2649   */
   2650  aFrameEntry->SetLoadType(aLoadType);
   2651 
   2652  loadState->SetLoadType(aLoadType);
   2653 
   2654  loadState->SetSHEntry(aFrameEntry);
   2655 
   2656  // If we're loading the current entry we want to treat it as not a
   2657  // same-document navigation (see nsDocShell::IsSameDocumentNavigation), so
   2658  // record that here in the LoadingSessionHistoryEntry.
   2659  bool loadingCurrentEntry;
   2660  if (mozilla::SessionHistoryInParent()) {
   2661    loadingCurrentEntry = aLoadCurrentEntry;
   2662  } else {
   2663    loadingCurrentEntry =
   2664        aFrameBC->GetDocShell() &&
   2665        nsDocShell::Cast(aFrameBC->GetDocShell())->IsOSHE(aFrameEntry);
   2666  }
   2667  loadState->SetLoadIsFromSessionHistory(aOffset, loadingCurrentEntry);
   2668 
   2669  if (mozilla::SessionHistoryInParent()) {
   2670    nsCOMPtr<SessionHistoryEntry> she = do_QueryInterface(aFrameEntry);
   2671    aFrameBC->Canonical()->AddLoadingSessionHistoryEntry(
   2672        loadState->GetLoadingSessionHistoryInfo()->mLoadId, she);
   2673  }
   2674 
   2675  nsCOMPtr<nsIURI> originalURI = aFrameEntry->GetOriginalURI();
   2676  loadState->SetOriginalURI(originalURI);
   2677 
   2678  loadState->SetLoadReplace(aFrameEntry->GetLoadReplace());
   2679 
   2680  loadState->SetLoadFlags(nsIWebNavigation::LOAD_FLAGS_NONE);
   2681  nsCOMPtr<nsIPrincipal> triggeringPrincipal =
   2682      aFrameEntry->GetTriggeringPrincipal();
   2683  loadState->SetTriggeringPrincipal(triggeringPrincipal);
   2684  loadState->SetFirstParty(false);
   2685  nsCOMPtr<nsIPolicyContainer> policyContainer =
   2686      aFrameEntry->GetPolicyContainer();
   2687  loadState->SetPolicyContainer(policyContainer);
   2688 
   2689  loadResult->mLoadState = std::move(loadState);
   2690 }
   2691 
   2692 NS_IMETHODIMP
   2693 nsSHistory::CreateEntry(nsISHEntry** aEntry) {
   2694  nsCOMPtr<nsISHEntry> entry;
   2695  if (XRE_IsParentProcess() && mozilla::SessionHistoryInParent()) {
   2696    entry = new SessionHistoryEntry();
   2697  } else {
   2698    entry = new nsSHEntry();
   2699  }
   2700  entry.forget(aEntry);
   2701  return NS_OK;
   2702 }
   2703 
   2704 static void CollectEntries(
   2705    nsTHashMap<nsIDHashKey, SessionHistoryEntry*>& aHashtable,
   2706    SessionHistoryEntry* aEntry) {
   2707  aHashtable.InsertOrUpdate(aEntry->DocshellID(), aEntry);
   2708  for (const RefPtr<SessionHistoryEntry>& entry : aEntry->Children()) {
   2709    if (entry) {
   2710      CollectEntries(aHashtable, entry);
   2711    }
   2712  }
   2713 }
   2714 
   2715 static void UpdateEntryLength(
   2716    nsTHashMap<nsIDHashKey, SessionHistoryEntry*>& aHashtable,
   2717    SessionHistoryEntry* aNewEntry, bool aMove) {
   2718  SessionHistoryEntry* oldEntry = aHashtable.Get(aNewEntry->DocshellID());
   2719  if (oldEntry) {
   2720    MOZ_ASSERT(oldEntry->GetID() != aNewEntry->GetID() || !aMove ||
   2721               !aNewEntry->BCHistoryLength().Modified());
   2722    aNewEntry->SetBCHistoryLength(oldEntry->BCHistoryLength());
   2723    if (oldEntry->GetID() != aNewEntry->GetID()) {
   2724      MOZ_ASSERT(!aMove);
   2725      // If we have a new id then aNewEntry was created for a new load, so
   2726      // record that in BCHistoryLength.
   2727      ++aNewEntry->BCHistoryLength();
   2728    } else if (aMove) {
   2729      // We're moving the BCHistoryLength from the old entry to the new entry,
   2730      // so we need to let the old entry know that it's not contributing to its
   2731      // BCHistoryLength, and the new one that it does if the old one was
   2732      // before.
   2733      aNewEntry->BCHistoryLength().SetModified(
   2734          oldEntry->BCHistoryLength().Modified());
   2735      oldEntry->BCHistoryLength().SetModified(false);
   2736    }
   2737  }
   2738 
   2739  for (const RefPtr<SessionHistoryEntry>& entry : aNewEntry->Children()) {
   2740    if (entry) {
   2741      UpdateEntryLength(aHashtable, entry, aMove);
   2742    }
   2743  }
   2744 }
   2745 
   2746 void nsSHistory::UpdateEntryLength(nsISHEntry* aOldEntry, nsISHEntry* aNewEntry,
   2747                                   bool aMove) {
   2748  nsCOMPtr<SessionHistoryEntry> oldSHE = do_QueryInterface(aOldEntry);
   2749  nsCOMPtr<SessionHistoryEntry> newSHE = do_QueryInterface(aNewEntry);
   2750 
   2751  if (!oldSHE || !newSHE) {
   2752    return;
   2753  }
   2754 
   2755  nsTHashMap<nsIDHashKey, SessionHistoryEntry*> docshellIDToEntry;
   2756  CollectEntries(docshellIDToEntry, oldSHE);
   2757 
   2758  ::UpdateEntryLength(docshellIDToEntry, newSHE, aMove);
   2759 }
   2760 
   2761 bool nsSHistory::ContainsEntry(nsISHEntry* aEntry) {
   2762  if (!aEntry) {
   2763    return false;
   2764  }
   2765 
   2766  nsCOMPtr rootEntry = GetRootSHEntry(aEntry);
   2767  return GetIndexOfEntry(rootEntry) != -1;
   2768 }
   2769 
   2770 already_AddRefed<EntryList> nsSHistory::EntryListFor(const nsID& aID) {
   2771  return mEntryLists.WithEntryHandle(
   2772      aID, [self = RefPtr{this}, aID](auto&& entry) {
   2773        if (entry && *entry) {
   2774          return do_AddRef(entry->get());
   2775        }
   2776        RefPtr entryList = MakeRefPtr<EntryList>(self, aID);
   2777        entry.InsertOrUpdate(entryList);
   2778        return entryList.forget();
   2779      });
   2780 }
   2781 
   2782 void nsSHistory::RemoveEntryList(const nsID& aID) { mEntryLists.Remove(aID); }