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