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); }