docshell/shistory/src/nsSHistory.cpp

Tue, 06 Jan 2015 21:39:09 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Tue, 06 Jan 2015 21:39:09 +0100
branch
TOR_BUG_9701
changeset 8
97036ab72558
permissions
-rw-r--r--

Conditionally force memory storage according to privacy.thirdparty.isolate;
This solves Tor bug #9701, complying with disk avoidance documented in
https://www.torproject.org/projects/torbrowser/design/#disk-avoidance.

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

mercurial