|
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/. */ |
|
6 |
|
7 // Local Includes |
|
8 #include "nsSHistory.h" |
|
9 #include <algorithm> |
|
10 |
|
11 // Helper Classes |
|
12 #include "mozilla/Preferences.h" |
|
13 |
|
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" |
|
34 |
|
35 // For calculating max history entries and max cachable contentviewers |
|
36 #include "prsystem.h" |
|
37 #include "mozilla/MathAlgorithms.h" |
|
38 |
|
39 using namespace mozilla; |
|
40 |
|
41 #define PREF_SHISTORY_SIZE "browser.sessionhistory.max_entries" |
|
42 #define PREF_SHISTORY_MAX_TOTAL_VIEWERS "browser.sessionhistory.max_total_viewers" |
|
43 |
|
44 static const char* kObservedPrefs[] = { |
|
45 PREF_SHISTORY_SIZE, |
|
46 PREF_SHISTORY_MAX_TOTAL_VIEWERS, |
|
47 nullptr |
|
48 }; |
|
49 |
|
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; |
|
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 #ifdef PR_LOGGING |
|
64 |
|
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) |
|
74 |
|
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 |
|
92 |
|
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 |
|
107 |
|
108 #else // !PR_LOGGING |
|
109 |
|
110 #define LOG(format) |
|
111 #define LOG_SPEC(format, uri) |
|
112 #define LOG_SHENTRY_SPEC(format, shentry) |
|
113 |
|
114 #endif // PR_LOGGING |
|
115 |
|
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 |
|
131 |
|
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 ); |
|
137 |
|
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 |
|
157 |
|
158 enum HistCmd{ |
|
159 HIST_CMD_BACK, |
|
160 HIST_CMD_FORWARD, |
|
161 HIST_CMD_GOTOINDEX, |
|
162 HIST_CMD_RELOAD |
|
163 } ; |
|
164 |
|
165 //***************************************************************************** |
|
166 //*** nsSHistoryObserver |
|
167 //***************************************************************************** |
|
168 |
|
169 class nsSHistoryObserver MOZ_FINAL : public nsIObserver |
|
170 { |
|
171 |
|
172 public: |
|
173 NS_DECL_ISUPPORTS |
|
174 NS_DECL_NSIOBSERVER |
|
175 |
|
176 nsSHistoryObserver() {} |
|
177 |
|
178 protected: |
|
179 ~nsSHistoryObserver() {} |
|
180 }; |
|
181 |
|
182 static nsSHistoryObserver* gObserver = nullptr; |
|
183 |
|
184 NS_IMPL_ISUPPORTS(nsSHistoryObserver, nsIObserver) |
|
185 |
|
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 } |
|
197 |
|
198 return NS_OK; |
|
199 } |
|
200 |
|
201 namespace { |
|
202 |
|
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 } |
|
211 |
|
212 nsCOMPtr<nsISHEntry> ownerEntry; |
|
213 nsCOMPtr<nsIContentViewer> viewer; |
|
214 entry->GetAnyContentViewer(getter_AddRefs(ownerEntry), |
|
215 getter_AddRefs(viewer)); |
|
216 return viewer.forget(); |
|
217 } |
|
218 |
|
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"); |
|
231 |
|
232 LOG_SHENTRY_SPEC(("Evicting content viewer 0x%p for " |
|
233 "owning SHEntry 0x%p at %s.", |
|
234 viewer.get(), ownerEntry.get(), _spec), ownerEntry); |
|
235 |
|
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 } |
|
243 |
|
244 } // anonymous namespace |
|
245 |
|
246 //***************************************************************************** |
|
247 //*** nsSHistory: Object Management |
|
248 //***************************************************************************** |
|
249 |
|
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 } |
|
255 |
|
256 |
|
257 nsSHistory::~nsSHistory() |
|
258 { |
|
259 // Remove this SHistory object from the list |
|
260 PR_REMOVE_LINK(this); |
|
261 } |
|
262 |
|
263 //***************************************************************************** |
|
264 // nsSHistory: nsISupports |
|
265 //***************************************************************************** |
|
266 |
|
267 NS_IMPL_ADDREF(nsSHistory) |
|
268 NS_IMPL_RELEASE(nsSHistory) |
|
269 |
|
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 |
|
276 |
|
277 //***************************************************************************** |
|
278 // nsSHistory: nsISHistory |
|
279 //***************************************************************************** |
|
280 |
|
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(); |
|
303 |
|
304 if (bytes == 0) |
|
305 return 0; |
|
306 |
|
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; |
|
312 |
|
313 double kBytesD = (double)(bytes >> 10); |
|
314 |
|
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 } |
|
324 |
|
325 // Cap it off at 8 max |
|
326 if (viewers > 8) { |
|
327 viewers = 8; |
|
328 } |
|
329 return viewers; |
|
330 } |
|
331 |
|
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 } |
|
345 |
|
346 // static |
|
347 nsresult |
|
348 nsSHistory::Startup() |
|
349 { |
|
350 UpdatePrefs(); |
|
351 |
|
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 } |
|
359 |
|
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); |
|
366 |
|
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); |
|
374 |
|
375 // Same for memory-pressure notifications |
|
376 obsSvc->AddObserver(gObserver, "memory-pressure", false); |
|
377 } |
|
378 } |
|
379 |
|
380 // Initialize the global list of all SHistory objects |
|
381 PR_INIT_CLIST(&gSHistoryList); |
|
382 return NS_OK; |
|
383 } |
|
384 |
|
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 } |
|
400 |
|
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); |
|
408 |
|
409 nsCOMPtr<nsISHTransaction> currentTxn; |
|
410 |
|
411 if(mListRoot) |
|
412 GetTransactionAtIndex(mIndex, getter_AddRefs(currentTxn)); |
|
413 |
|
414 bool currentPersist = true; |
|
415 if(currentTxn) |
|
416 currentTxn->GetPersist(¤tPersist); |
|
417 |
|
418 int32_t currentIndex = mIndex; |
|
419 |
|
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 } |
|
427 |
|
428 nsCOMPtr<nsISHTransaction> txn(do_CreateInstance(NS_SHTRANSACTION_CONTRACTID)); |
|
429 NS_ENSURE_TRUE(txn, NS_ERROR_FAILURE); |
|
430 |
|
431 nsCOMPtr<nsIURI> uri; |
|
432 aSHEntry->GetURI(getter_AddRefs(uri)); |
|
433 NOTIFY_LISTENERS(OnHistoryNewEntry, (uri)); |
|
434 |
|
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 } |
|
440 |
|
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); |
|
445 |
|
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); |
|
450 |
|
451 // If this is the very first transaction, initialize the list |
|
452 if(!mListRoot) |
|
453 mListRoot = txn; |
|
454 |
|
455 // Purge History list if it is too long |
|
456 if ((gHistoryMaxSize >= 0) && (mLength > gHistoryMaxSize)) |
|
457 PurgeHistory(mLength-gHistoryMaxSize); |
|
458 |
|
459 RemoveDynEntries(mIndex - 1, mIndex); |
|
460 return NS_OK; |
|
461 } |
|
462 |
|
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 } |
|
471 |
|
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 } |
|
480 |
|
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 } |
|
489 |
|
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; |
|
496 |
|
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 } |
|
511 |
|
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); |
|
518 |
|
519 if ((mLength <= 0) || (aIndex < 0) || (aIndex >= mLength)) |
|
520 return NS_ERROR_FAILURE; |
|
521 |
|
522 if (!mListRoot) |
|
523 return NS_ERROR_FAILURE; |
|
524 |
|
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; |
|
533 |
|
534 rv = GetRootTransaction(getter_AddRefs(tempPtr)); |
|
535 if (NS_FAILED(rv) || !tempPtr) |
|
536 return NS_ERROR_FAILURE; |
|
537 |
|
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 |
|
556 |
|
557 return NS_OK; |
|
558 } |
|
559 |
|
560 |
|
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; |
|
567 |
|
568 if (mLength <= 0) { |
|
569 return NS_ERROR_FAILURE; |
|
570 } |
|
571 |
|
572 nsCOMPtr<nsISHTransaction> currentTxn; |
|
573 int32_t cnt = 0; |
|
574 |
|
575 nsresult rv = GetRootTransaction(getter_AddRefs(currentTxn)); |
|
576 if (NS_FAILED(rv) || !currentTxn) { |
|
577 return NS_ERROR_FAILURE; |
|
578 } |
|
579 |
|
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 } |
|
586 |
|
587 if (aSHEntry == entry) { |
|
588 *aResult = cnt; |
|
589 break; |
|
590 } |
|
591 |
|
592 rv = currentTxn->GetNext(getter_AddRefs(currentTxn)); |
|
593 if (NS_FAILED(rv) || !currentTxn) { |
|
594 return NS_ERROR_FAILURE; |
|
595 } |
|
596 |
|
597 cnt++; |
|
598 } |
|
599 |
|
600 return NS_OK; |
|
601 } |
|
602 |
|
603 |
|
604 #ifdef DEBUG |
|
605 nsresult |
|
606 nsSHistory::PrintHistory() |
|
607 { |
|
608 |
|
609 nsCOMPtr<nsISHTransaction> txn; |
|
610 int32_t index = 0; |
|
611 nsresult rv; |
|
612 |
|
613 if (!mListRoot) |
|
614 return NS_ERROR_FAILURE; |
|
615 |
|
616 txn = mListRoot; |
|
617 |
|
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; |
|
625 |
|
626 nsCOMPtr<nsILayoutHistoryState> layoutHistoryState; |
|
627 nsCOMPtr<nsIURI> uri; |
|
628 nsXPIDLString title; |
|
629 |
|
630 entry->GetLayoutHistoryState(getter_AddRefs(layoutHistoryState)); |
|
631 entry->GetURI(getter_AddRefs(uri)); |
|
632 entry->GetTitle(getter_Copies(title)); |
|
633 |
|
634 #if 0 |
|
635 nsAutoCString url; |
|
636 if (uri) |
|
637 uri->GetSpec(url); |
|
638 |
|
639 printf("**** SH Transaction #%d, Entry = %x\n", index, entry.get()); |
|
640 printf("\t\t URL = %s\n", url.get()); |
|
641 |
|
642 printf("\t\t Title = %s\n", NS_LossyConvertUTF16toASCII(title).get()); |
|
643 printf("\t\t layout History Data = %x\n", layoutHistoryState.get()); |
|
644 #endif |
|
645 |
|
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 } |
|
656 |
|
657 return NS_OK; |
|
658 } |
|
659 #endif |
|
660 |
|
661 |
|
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 } |
|
670 |
|
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 } |
|
679 |
|
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; |
|
686 |
|
687 gHistoryMaxSize = aMaxSize; |
|
688 if (mLength > aMaxSize) |
|
689 PurgeHistory(mLength-aMaxSize); |
|
690 return NS_OK; |
|
691 } |
|
692 |
|
693 NS_IMETHODIMP |
|
694 nsSHistory::PurgeHistory(int32_t aEntries) |
|
695 { |
|
696 if (mLength <= 0 || aEntries <= 0) |
|
697 return NS_ERROR_FAILURE; |
|
698 |
|
699 aEntries = std::min(aEntries, mLength); |
|
700 |
|
701 bool purgeHistory = true; |
|
702 NOTIFY_LISTENERS_CANCELABLE(OnHistoryPurge, purgeHistory, |
|
703 (aEntries, &purgeHistory)); |
|
704 |
|
705 if (!purgeHistory) { |
|
706 // Listener asked us not to purge |
|
707 return NS_SUCCESS_LOSS_OF_INSIGNIFICANT_DATA; |
|
708 } |
|
709 |
|
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; |
|
725 |
|
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 } |
|
731 |
|
732 if (mRootDocShell) |
|
733 mRootDocShell->HistoryPurged(cnt); |
|
734 |
|
735 return NS_OK; |
|
736 } |
|
737 |
|
738 |
|
739 NS_IMETHODIMP |
|
740 nsSHistory::AddSHistoryListener(nsISHistoryListener * aListener) |
|
741 { |
|
742 NS_ENSURE_ARG_POINTER(aListener); |
|
743 |
|
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; |
|
749 |
|
750 return mListeners.AppendElementUnlessExists(listener) ? |
|
751 NS_OK : NS_ERROR_OUT_OF_MEMORY; |
|
752 } |
|
753 |
|
754 |
|
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 } |
|
764 |
|
765 |
|
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; |
|
775 |
|
776 if (!mListRoot) // Session History is not initialised. |
|
777 return NS_ERROR_FAILURE; |
|
778 |
|
779 rv = GetTransactionAtIndex(aIndex, getter_AddRefs(currentTxn)); |
|
780 |
|
781 if(currentTxn) |
|
782 { |
|
783 NOTIFY_LISTENERS(OnHistoryReplaceEntry, (aIndex)); |
|
784 |
|
785 // Set the replacement entry in the transaction |
|
786 rv = currentTxn->SetSHEntry(aReplaceEntry); |
|
787 rv = currentTxn->SetPersist(true); |
|
788 } |
|
789 return rv; |
|
790 } |
|
791 |
|
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 } |
|
800 |
|
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 } |
|
810 |
|
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); |
|
819 |
|
820 nsISHTransaction *temp = trans; |
|
821 temp->GetNext(getter_AddRefs(trans)); |
|
822 } |
|
823 |
|
824 return NS_OK; |
|
825 } |
|
826 |
|
827 |
|
828 |
|
829 //***************************************************************************** |
|
830 // nsSHistory: nsIWebNavigation |
|
831 //***************************************************************************** |
|
832 |
|
833 NS_IMETHODIMP |
|
834 nsSHistory::GetCanGoBack(bool * aCanGoBack) |
|
835 { |
|
836 NS_ENSURE_ARG_POINTER(aCanGoBack); |
|
837 *aCanGoBack = false; |
|
838 |
|
839 int32_t index = -1; |
|
840 NS_ENSURE_SUCCESS(GetIndex(&index), NS_ERROR_FAILURE); |
|
841 if(index > 0) |
|
842 *aCanGoBack = true; |
|
843 |
|
844 return NS_OK; |
|
845 } |
|
846 |
|
847 NS_IMETHODIMP |
|
848 nsSHistory::GetCanGoForward(bool * aCanGoForward) |
|
849 { |
|
850 NS_ENSURE_ARG_POINTER(aCanGoForward); |
|
851 *aCanGoForward = false; |
|
852 |
|
853 int32_t index = -1; |
|
854 int32_t count = -1; |
|
855 |
|
856 NS_ENSURE_SUCCESS(GetIndex(&index), NS_ERROR_FAILURE); |
|
857 NS_ENSURE_SUCCESS(GetCount(&count), NS_ERROR_FAILURE); |
|
858 |
|
859 if((index >= 0) && (index < (count - 1))) |
|
860 *aCanGoForward = true; |
|
861 |
|
862 return NS_OK; |
|
863 } |
|
864 |
|
865 NS_IMETHODIMP |
|
866 nsSHistory::GoBack() |
|
867 { |
|
868 bool canGoBack = false; |
|
869 |
|
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 } |
|
875 |
|
876 |
|
877 NS_IMETHODIMP |
|
878 nsSHistory::GoForward() |
|
879 { |
|
880 bool canGoForward = false; |
|
881 |
|
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 } |
|
887 |
|
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 } |
|
917 |
|
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; |
|
929 |
|
930 return LoadEntry(mIndex, loadType, HIST_CMD_RELOAD); |
|
931 } |
|
932 |
|
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; |
|
944 |
|
945 return LoadEntry(mIndex, nsIDocShellLoadInfo::loadHistory, HIST_CMD_RELOAD); |
|
946 } |
|
947 |
|
948 void |
|
949 nsSHistory::EvictOutOfRangeWindowContentViewers(int32_t aIndex) |
|
950 { |
|
951 // XXX rename method to EvictContentViewersExceptAroundIndex, or something. |
|
952 |
|
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.) |
|
985 |
|
986 if (aIndex < 0) { |
|
987 return; |
|
988 } |
|
989 NS_ENSURE_TRUE_VOID(aIndex < mLength); |
|
990 |
|
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); |
|
994 |
|
995 LOG(("EvictOutOfRangeWindowContentViewers(index=%d), " |
|
996 "mLength=%d. Safe range [%d, %d]", |
|
997 aIndex, mLength, startSafeIndex, endSafeIndex)); |
|
998 |
|
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 } |
|
1011 |
|
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 } |
|
1019 |
|
1020 nsISHTransaction *temp = trans; |
|
1021 temp->GetNext(getter_AddRefs(trans)); |
|
1022 } |
|
1023 } |
|
1024 |
|
1025 namespace { |
|
1026 |
|
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"); |
|
1036 |
|
1037 nsCOMPtr<nsISHEntry> shentry; |
|
1038 mTransaction->GetSHEntry(getter_AddRefs(shentry)); |
|
1039 |
|
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 } |
|
1048 |
|
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 } |
|
1055 |
|
1056 return this->mLastTouched < aOther.mLastTouched; |
|
1057 } |
|
1058 |
|
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 } |
|
1067 |
|
1068 nsCOMPtr<nsISHTransaction> mTransaction; |
|
1069 nsCOMPtr<nsIContentViewer> mViewer; |
|
1070 uint32_t mLastTouched; |
|
1071 int32_t mDistance; |
|
1072 }; |
|
1073 |
|
1074 } // anonymous namespace |
|
1075 |
|
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. |
|
1083 |
|
1084 nsTArray<TransactionAndDistance> transactions; |
|
1085 |
|
1086 nsSHistory *shist = static_cast<nsSHistory*>(PR_LIST_HEAD(&gSHistoryList)); |
|
1087 while (shist != &gSHistoryList) { |
|
1088 |
|
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; |
|
1093 |
|
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); |
|
1115 |
|
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 } |
|
1130 |
|
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 } |
|
1138 |
|
1139 nsISHTransaction *temp = trans; |
|
1140 temp->GetNext(getter_AddRefs(trans)); |
|
1141 } |
|
1142 |
|
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 } |
|
1148 |
|
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 } |
|
1154 |
|
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(); |
|
1160 |
|
1161 for (int32_t i = transactions.Length() - 1; |
|
1162 i >= sHistoryMaxTotalViewers; --i) { |
|
1163 |
|
1164 EvictContentViewerForTransaction(transactions[i].mTransaction); |
|
1165 |
|
1166 } |
|
1167 } |
|
1168 |
|
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)); |
|
1177 |
|
1178 int32_t i; |
|
1179 for (i = startIndex; trans && i <= endIndex; ++i) { |
|
1180 nsCOMPtr<nsISHEntry> entry; |
|
1181 trans->GetSHEntry(getter_AddRefs(entry)); |
|
1182 |
|
1183 // Does entry have the same BFCacheEntry as the argument to this method? |
|
1184 if (entry->HasBFCacheEntry(aEntry)) { |
|
1185 break; |
|
1186 } |
|
1187 |
|
1188 nsISHTransaction *temp = trans; |
|
1189 temp->GetNext(getter_AddRefs(trans)); |
|
1190 } |
|
1191 if (i > endIndex) |
|
1192 return NS_OK; |
|
1193 |
|
1194 if (i == mIndex) { |
|
1195 NS_WARNING("How did the current SHEntry expire?"); |
|
1196 return NS_OK; |
|
1197 } |
|
1198 |
|
1199 EvictContentViewerForTransaction(trans); |
|
1200 |
|
1201 return NS_OK; |
|
1202 } |
|
1203 |
|
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. |
|
1208 |
|
1209 //static |
|
1210 void |
|
1211 nsSHistory::GloballyEvictAllContentViewers() |
|
1212 { |
|
1213 int32_t maxViewers = sHistoryMaxTotalViewers; |
|
1214 sHistoryMaxTotalViewers = 0; |
|
1215 GloballyEvictContentViewers(); |
|
1216 sHistoryMaxTotalViewers = maxViewers; |
|
1217 } |
|
1218 |
|
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 } |
|
1246 |
|
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); |
|
1253 |
|
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 } |
|
1280 |
|
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 } |
|
1289 |
|
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 } |
|
1304 |
|
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 } |
|
1320 |
|
1321 return true; |
|
1322 } |
|
1323 |
|
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 } |
|
1354 |
|
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 } |
|
1364 |
|
1365 // Adjust our indices to reflect the removed transaction |
|
1366 if (mIndex > aIndex) { |
|
1367 mIndex = mIndex - 1; |
|
1368 } |
|
1369 |
|
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. |
|
1377 |
|
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 } |
|
1390 |
|
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)); |
|
1399 |
|
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 } |
|
1415 |
|
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 } |
|
1447 |
|
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 } |
|
1456 |
|
1457 mRequestedIndex = -1; |
|
1458 return NS_OK; |
|
1459 } |
|
1460 |
|
1461 NS_IMETHODIMP |
|
1462 nsSHistory::Stop(uint32_t aStopFlags) |
|
1463 { |
|
1464 //Not implemented |
|
1465 return NS_OK; |
|
1466 } |
|
1467 |
|
1468 |
|
1469 NS_IMETHODIMP |
|
1470 nsSHistory::GetDocument(nsIDOMDocument** aDocument) |
|
1471 { |
|
1472 // Not implemented |
|
1473 return NS_OK; |
|
1474 } |
|
1475 |
|
1476 |
|
1477 NS_IMETHODIMP |
|
1478 nsSHistory::GetCurrentURI(nsIURI** aResultURI) |
|
1479 { |
|
1480 NS_ENSURE_ARG_POINTER(aResultURI); |
|
1481 nsresult rv; |
|
1482 |
|
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 } |
|
1489 |
|
1490 |
|
1491 NS_IMETHODIMP |
|
1492 nsSHistory::GetReferringURI(nsIURI** aURI) |
|
1493 { |
|
1494 *aURI = nullptr; |
|
1495 // Not implemented |
|
1496 return NS_OK; |
|
1497 } |
|
1498 |
|
1499 |
|
1500 NS_IMETHODIMP |
|
1501 nsSHistory::SetSessionHistory(nsISHistory* aSessionHistory) |
|
1502 { |
|
1503 // Not implemented |
|
1504 return NS_OK; |
|
1505 } |
|
1506 |
|
1507 |
|
1508 NS_IMETHODIMP |
|
1509 nsSHistory::GetSessionHistory(nsISHistory** aSessionHistory) |
|
1510 { |
|
1511 // Not implemented |
|
1512 return NS_OK; |
|
1513 } |
|
1514 |
|
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 } |
|
1525 |
|
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 } |
|
1535 |
|
1536 NS_IMETHODIMP |
|
1537 nsSHistory::GotoIndex(int32_t aIndex) |
|
1538 { |
|
1539 return LoadEntry(aIndex, nsIDocShellLoadInfo::loadHistory, HIST_CMD_GOTOINDEX); |
|
1540 } |
|
1541 |
|
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 } |
|
1554 |
|
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; |
|
1561 |
|
1562 nsCOMPtr<nsISHEntry> prevEntry; |
|
1563 GetEntryAtIndex(mIndex, false, getter_AddRefs(prevEntry)); |
|
1564 |
|
1565 nsCOMPtr<nsISHEntry> nextEntry; |
|
1566 GetEntryAtIndex(mRequestedIndex, false, getter_AddRefs(nextEntry)); |
|
1567 if (!nextEntry || !prevEntry) { |
|
1568 mRequestedIndex = -1; |
|
1569 return NS_ERROR_FAILURE; |
|
1570 } |
|
1571 |
|
1572 // Remember that this entry is getting loaded at this point in the sequence |
|
1573 nsCOMPtr<nsISHEntryInternal> entryInternal = do_QueryInterface(nextEntry); |
|
1574 |
|
1575 if (entryInternal) { |
|
1576 entryInternal->SetLastTouched(++gTouchCounter); |
|
1577 } |
|
1578 |
|
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)); |
|
1584 |
|
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 } |
|
1598 |
|
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 } |
|
1605 |
|
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 } |
|
1614 |
|
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 } |
|
1649 |
|
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 } |
|
1656 |
|
1657 // Start the load on the appropriate docshell |
|
1658 return InitiateLoad(nextEntry, docShell, aLoadType); |
|
1659 } |
|
1660 |
|
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; |
|
1666 |
|
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); |
|
1674 |
|
1675 nsresult result = NS_OK; |
|
1676 uint32_t prevID, nextID; |
|
1677 |
|
1678 aPrevEntry->GetID(&prevID); |
|
1679 aNextEntry->GetID(&nextID); |
|
1680 |
|
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 } |
|
1691 |
|
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)); |
|
1696 |
|
1697 if (!aParent) |
|
1698 return NS_ERROR_FAILURE; |
|
1699 if (!prevContainer || !nextContainer) |
|
1700 return NS_ERROR_FAILURE; |
|
1701 |
|
1702 prevContainer->GetChildCount(&pcnt); |
|
1703 nextContainer->GetChildCount(&ncnt); |
|
1704 aParent->GetChildCount(&dsCount); |
|
1705 |
|
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 } |
|
1716 |
|
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); |
|
1727 |
|
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 } |
|
1743 |
|
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 } |
|
1759 |
|
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 } |
|
1767 |
|
1768 |
|
1769 nsresult |
|
1770 nsSHistory::InitiateLoad(nsISHEntry * aFrameEntry, nsIDocShell * aFrameDS, long aLoadType) |
|
1771 { |
|
1772 NS_ENSURE_STATE(aFrameDS && aFrameEntry); |
|
1773 |
|
1774 nsCOMPtr<nsIDocShellLoadInfo> loadInfo; |
|
1775 |
|
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)); |
|
1782 |
|
1783 loadInfo->SetLoadType(aLoadType); |
|
1784 loadInfo->SetSHEntry(aFrameEntry); |
|
1785 |
|
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); |
|
1790 |
|
1791 } |
|
1792 |
|
1793 |
|
1794 |
|
1795 NS_IMETHODIMP |
|
1796 nsSHistory::SetRootDocShell(nsIDocShell * aDocShell) |
|
1797 { |
|
1798 mRootDocShell = aDocShell; |
|
1799 return NS_OK; |
|
1800 } |
|
1801 |
|
1802 NS_IMETHODIMP |
|
1803 nsSHistory::GetRootDocShell(nsIDocShell ** aDocShell) |
|
1804 { |
|
1805 NS_ENSURE_ARG_POINTER(aDocShell); |
|
1806 |
|
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 } |
|
1812 |
|
1813 |
|
1814 NS_IMETHODIMP |
|
1815 nsSHistory::GetSHistoryEnumerator(nsISimpleEnumerator** aEnumerator) |
|
1816 { |
|
1817 nsresult status = NS_OK; |
|
1818 |
|
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 } |
|
1825 |
|
1826 |
|
1827 //***************************************************************************** |
|
1828 //*** nsSHEnumerator: Object Management |
|
1829 //***************************************************************************** |
|
1830 |
|
1831 nsSHEnumerator::nsSHEnumerator(nsSHistory * aSHistory):mIndex(-1) |
|
1832 { |
|
1833 mSHistory = aSHistory; |
|
1834 } |
|
1835 |
|
1836 nsSHEnumerator::~nsSHEnumerator() |
|
1837 { |
|
1838 mSHistory = nullptr; |
|
1839 } |
|
1840 |
|
1841 NS_IMPL_ISUPPORTS(nsSHEnumerator, nsISimpleEnumerator) |
|
1842 |
|
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 } |
|
1854 |
|
1855 |
|
1856 NS_IMETHODIMP |
|
1857 nsSHEnumerator::GetNext(nsISupports **aItem) |
|
1858 { |
|
1859 NS_ENSURE_ARG_POINTER(aItem); |
|
1860 int32_t cnt= 0; |
|
1861 |
|
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 } |