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