/src/mozilla-central/docshell/shistory/nsSHistory.cpp
Line | Count | Source (jump to first uncovered line) |
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 "nsDocShellLoadInfo.h" |
17 | | #include "nsIDocShellTreeItem.h" |
18 | | #include "nsILayoutHistoryState.h" |
19 | | #include "nsIObserverService.h" |
20 | | #include "nsISHEntry.h" |
21 | | #include "nsISHistoryListener.h" |
22 | | #include "nsIURI.h" |
23 | | #include "nsNetUtil.h" |
24 | | #include "nsTArray.h" |
25 | | #include "prsystem.h" |
26 | | |
27 | | #include "mozilla/Attributes.h" |
28 | | #include "mozilla/LinkedList.h" |
29 | | #include "mozilla/MathAlgorithms.h" |
30 | | #include "mozilla/Preferences.h" |
31 | | #include "mozilla/Services.h" |
32 | | #include "mozilla/StaticPtr.h" |
33 | | #include "mozilla/dom/TabGroup.h" |
34 | | |
35 | | using namespace mozilla; |
36 | | |
37 | 6 | #define PREF_SHISTORY_SIZE "browser.sessionhistory.max_entries" |
38 | 3 | #define PREF_SHISTORY_MAX_TOTAL_VIEWERS "browser.sessionhistory.max_total_viewers" |
39 | 0 | #define CONTENT_VIEWER_TIMEOUT_SECONDS "browser.sessionhistory.contentViewerTimeout" |
40 | | |
41 | | // Default this to time out unused content viewers after 30 minutes |
42 | 0 | #define CONTENT_VIEWER_TIMEOUT_SECONDS_DEFAULT (30 * 60) |
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 | | // List of all SHistory objects, used for content viewer cache eviction |
52 | | static LinkedList<nsSHistory> gSHistoryList; |
53 | | // Max viewers allowed total, across all SHistory objects - negative default |
54 | | // means we will calculate how many viewers to cache based on total memory |
55 | | int32_t nsSHistory::sHistoryMaxTotalViewers = -1; |
56 | | |
57 | | // A counter that is used to be able to know the order in which |
58 | | // entries were touched, so that we can evict older entries first. |
59 | | static uint32_t gTouchCounter = 0; |
60 | | |
61 | | static LazyLogModule gSHistoryLog("nsSHistory"); |
62 | | |
63 | 0 | #define LOG(format) MOZ_LOG(gSHistoryLog, mozilla::LogLevel::Debug, format) |
64 | | |
65 | | // This macro makes it easier to print a log message which includes a URI's |
66 | | // spec. Example use: |
67 | | // |
68 | | // nsIURI *uri = [...]; |
69 | | // LOG_SPEC(("The URI is %s.", _spec), uri); |
70 | | // |
71 | | #define LOG_SPEC(format, uri) \ |
72 | 0 | PR_BEGIN_MACRO \ |
73 | 0 | if (MOZ_LOG_TEST(gSHistoryLog, LogLevel::Debug)) { \ |
74 | 0 | nsAutoCString _specStr(NS_LITERAL_CSTRING("(null)"));\ |
75 | 0 | if (uri) { \ |
76 | 0 | _specStr = uri->GetSpecOrDefault(); \ |
77 | 0 | } \ |
78 | 0 | const char* _spec = _specStr.get(); \ |
79 | 0 | LOG(format); \ |
80 | 0 | } \ |
81 | 0 | PR_END_MACRO |
82 | | |
83 | | // This macro makes it easy to log a message including an SHEntry's URI. |
84 | | // For example: |
85 | | // |
86 | | // nsCOMPtr<nsISHEntry> shentry = [...]; |
87 | | // LOG_SHENTRY_SPEC(("shentry %p has uri %s.", shentry.get(), _spec), shentry); |
88 | | // |
89 | | #define LOG_SHENTRY_SPEC(format, shentry) \ |
90 | 0 | PR_BEGIN_MACRO \ |
91 | 0 | if (MOZ_LOG_TEST(gSHistoryLog, LogLevel::Debug)) { \ |
92 | 0 | nsCOMPtr<nsIURI> uri = shentry->GetURI(); \ |
93 | 0 | LOG_SPEC(format, uri); \ |
94 | 0 | } \ |
95 | 0 | PR_END_MACRO |
96 | | |
97 | | // Iterates over all registered session history listeners. |
98 | | #define ITERATE_LISTENERS(body) \ |
99 | 0 | PR_BEGIN_MACRO \ |
100 | 0 | { \ |
101 | 0 | nsAutoTObserverArray<nsWeakPtr, 2>::EndLimitedIterator \ |
102 | 0 | iter(mListeners); \ |
103 | 0 | while (iter.HasMore()) { \ |
104 | 0 | nsCOMPtr<nsISHistoryListener> listener = \ |
105 | 0 | do_QueryReferent(iter.GetNext()); \ |
106 | 0 | if (listener) { \ |
107 | 0 | body \ |
108 | 0 | } \ |
109 | 0 | } \ |
110 | 0 | } \ |
111 | 0 | PR_END_MACRO |
112 | | |
113 | | // Calls a given method on all registered session history listeners. |
114 | | #define NOTIFY_LISTENERS(method, args) \ |
115 | 0 | ITERATE_LISTENERS( \ |
116 | 0 | listener->method args; \ |
117 | 0 | ); |
118 | | |
119 | | // Calls a given method on all registered session history listeners. |
120 | | // Listeners may return 'false' to cancel an action so make sure that we |
121 | | // set the return value to 'false' if one of the listeners wants to cancel. |
122 | | #define NOTIFY_LISTENERS_CANCELABLE(method, retval, args) \ |
123 | 0 | PR_BEGIN_MACRO \ |
124 | 0 | { \ |
125 | 0 | bool canceled = false; \ |
126 | 0 | retval = true; \ |
127 | 0 | ITERATE_LISTENERS( \ |
128 | 0 | listener->method args; \ |
129 | 0 | if (!retval) { \ |
130 | 0 | canceled = true; \ |
131 | 0 | } \ |
132 | 0 | ); \ |
133 | 0 | if (canceled) { \ |
134 | 0 | retval = false; \ |
135 | 0 | } \ |
136 | 0 | } \ |
137 | 0 | PR_END_MACRO |
138 | | |
139 | | enum HistCmd |
140 | | { |
141 | | HIST_CMD_GOTOINDEX, |
142 | | HIST_CMD_RELOAD |
143 | | }; |
144 | | |
145 | | class nsSHistoryObserver final : public nsIObserver |
146 | | { |
147 | | public: |
148 | | NS_DECL_ISUPPORTS |
149 | | NS_DECL_NSIOBSERVER |
150 | | |
151 | 3 | nsSHistoryObserver() {} |
152 | | |
153 | | void PrefChanged(const char* aPref); |
154 | | |
155 | | protected: |
156 | 0 | ~nsSHistoryObserver() {} |
157 | | }; |
158 | | |
159 | | StaticRefPtr<nsSHistoryObserver> gObserver; |
160 | | |
161 | | NS_IMPL_ISUPPORTS(nsSHistoryObserver, nsIObserver) |
162 | | |
163 | | void |
164 | | nsSHistoryObserver::PrefChanged(const char* aPref) |
165 | 0 | { |
166 | 0 | nsSHistory::UpdatePrefs(); |
167 | 0 | nsSHistory::GloballyEvictContentViewers(); |
168 | 0 |
|
169 | 0 | } |
170 | | |
171 | | NS_IMETHODIMP |
172 | | nsSHistoryObserver::Observe(nsISupports* aSubject, const char* aTopic, |
173 | | const char16_t* aData) |
174 | 0 | { |
175 | 0 | if (!strcmp(aTopic, "cacheservice:empty-cache") || |
176 | 0 | !strcmp(aTopic, "memory-pressure")) { |
177 | 0 | nsSHistory::GloballyEvictAllContentViewers(); |
178 | 0 | } |
179 | 0 |
|
180 | 0 | return NS_OK; |
181 | 0 | } |
182 | | |
183 | | namespace { |
184 | | |
185 | | already_AddRefed<nsIContentViewer> |
186 | | GetContentViewerForEntry(nsISHEntry* aEntry) |
187 | 0 | { |
188 | 0 | nsCOMPtr<nsISHEntry> ownerEntry; |
189 | 0 | nsCOMPtr<nsIContentViewer> viewer; |
190 | 0 | aEntry->GetAnyContentViewer(getter_AddRefs(ownerEntry), |
191 | 0 | getter_AddRefs(viewer)); |
192 | 0 | return viewer.forget(); |
193 | 0 | } |
194 | | |
195 | | } // namespace |
196 | | |
197 | | void |
198 | | nsSHistory::EvictContentViewerForEntry(nsISHEntry* aEntry) |
199 | 0 | { |
200 | 0 | nsCOMPtr<nsIContentViewer> viewer; |
201 | 0 | nsCOMPtr<nsISHEntry> ownerEntry; |
202 | 0 | aEntry->GetAnyContentViewer(getter_AddRefs(ownerEntry), |
203 | 0 | getter_AddRefs(viewer)); |
204 | 0 | if (viewer) { |
205 | 0 | NS_ASSERTION(ownerEntry, "Content viewer exists but its SHEntry is null"); |
206 | 0 |
|
207 | 0 | LOG_SHENTRY_SPEC(("Evicting content viewer 0x%p for " |
208 | 0 | "owning SHEntry 0x%p at %s.", |
209 | 0 | viewer.get(), ownerEntry.get(), _spec), |
210 | 0 | ownerEntry); |
211 | 0 |
|
212 | 0 | // Drop the presentation state before destroying the viewer, so that |
213 | 0 | // document teardown is able to correctly persist the state. |
214 | 0 | ownerEntry->SetContentViewer(nullptr); |
215 | 0 | ownerEntry->SyncPresentationState(); |
216 | 0 | viewer->Destroy(); |
217 | 0 | } |
218 | 0 |
|
219 | 0 | // When dropping bfcache, we have to remove associated dynamic entries as well. |
220 | 0 | int32_t index = GetIndexOfEntry(aEntry); |
221 | 0 | if (index != -1) { |
222 | 0 | RemoveDynEntries(index, aEntry); |
223 | 0 | } |
224 | 0 | } |
225 | | |
226 | | nsSHistory::nsSHistory() |
227 | | : mIndex(-1) |
228 | | , mRequestedIndex(-1) |
229 | | , mRootDocShell(nullptr) |
230 | 0 | { |
231 | 0 | // Add this new SHistory object to the list |
232 | 0 | gSHistoryList.insertBack(this); |
233 | 0 | } |
234 | | |
235 | | nsSHistory::~nsSHistory() |
236 | 0 | { |
237 | 0 | } |
238 | | |
239 | | NS_IMPL_ADDREF(nsSHistory) |
240 | | NS_IMPL_RELEASE(nsSHistory) |
241 | | |
242 | 0 | NS_INTERFACE_MAP_BEGIN(nsSHistory) |
243 | 0 | NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsISHistory) |
244 | 0 | NS_INTERFACE_MAP_ENTRY(nsISHistory) |
245 | 0 | NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference) |
246 | 0 | NS_INTERFACE_MAP_END |
247 | | |
248 | | // static |
249 | | uint32_t |
250 | | nsSHistory::CalcMaxTotalViewers() |
251 | 3 | { |
252 | 3 | // This value allows tweaking how fast the allowed amount of content viewers |
253 | 3 | // grows with increasing amounts of memory. Larger values mean slower growth. |
254 | | #ifdef ANDROID |
255 | | #define MAX_TOTAL_VIEWERS_BIAS 15.9 |
256 | | #else |
257 | 3 | #define MAX_TOTAL_VIEWERS_BIAS 14 |
258 | 3 | #endif |
259 | 3 | |
260 | 3 | // Calculate an estimate of how many ContentViewers we should cache based |
261 | 3 | // on RAM. This assumes that the average ContentViewer is 4MB (conservative) |
262 | 3 | // and caps the max at 8 ContentViewers |
263 | 3 | // |
264 | 3 | // TODO: Should we split the cache memory betw. ContentViewer caching and |
265 | 3 | // nsCacheService? |
266 | 3 | // |
267 | 3 | // RAM | ContentViewers | on Android |
268 | 3 | // ------------------------------------- |
269 | 3 | // 32 Mb 0 0 |
270 | 3 | // 64 Mb 1 0 |
271 | 3 | // 128 Mb 2 0 |
272 | 3 | // 256 Mb 3 1 |
273 | 3 | // 512 Mb 5 2 |
274 | 3 | // 768 Mb 6 2 |
275 | 3 | // 1024 Mb 8 3 |
276 | 3 | // 2048 Mb 8 5 |
277 | 3 | // 3072 Mb 8 7 |
278 | 3 | // 4096 Mb 8 8 |
279 | 3 | uint64_t bytes = PR_GetPhysicalMemorySize(); |
280 | 3 | |
281 | 3 | if (bytes == 0) { |
282 | 0 | return 0; |
283 | 0 | } |
284 | 3 | |
285 | 3 | // Conversion from unsigned int64_t to double doesn't work on all platforms. |
286 | 3 | // We need to truncate the value at INT64_MAX to make sure we don't |
287 | 3 | // overflow. |
288 | 3 | if (bytes > INT64_MAX) { |
289 | 0 | bytes = INT64_MAX; |
290 | 0 | } |
291 | 3 | |
292 | 3 | double kBytesD = (double)(bytes >> 10); |
293 | 3 | |
294 | 3 | // This is essentially the same calculation as for nsCacheService, |
295 | 3 | // except that we divide the final memory calculation by 4, since |
296 | 3 | // we assume each ContentViewer takes on average 4MB |
297 | 3 | uint32_t viewers = 0; |
298 | 3 | double x = std::log(kBytesD) / std::log(2.0) - MAX_TOTAL_VIEWERS_BIAS; |
299 | 3 | if (x > 0) { |
300 | 3 | viewers = (uint32_t)(x * x - x + 2.001); // add .001 for rounding |
301 | 3 | viewers /= 4; |
302 | 3 | } |
303 | 3 | |
304 | 3 | // Cap it off at 8 max |
305 | 3 | if (viewers > 8) { |
306 | 3 | viewers = 8; |
307 | 3 | } |
308 | 3 | return viewers; |
309 | 3 | } |
310 | | |
311 | | // static |
312 | | void |
313 | | nsSHistory::UpdatePrefs() |
314 | 3 | { |
315 | 3 | Preferences::GetInt(PREF_SHISTORY_SIZE, &gHistoryMaxSize); |
316 | 3 | Preferences::GetInt(PREF_SHISTORY_MAX_TOTAL_VIEWERS, |
317 | 3 | &sHistoryMaxTotalViewers); |
318 | 3 | // If the pref is negative, that means we calculate how many viewers |
319 | 3 | // we think we should cache, based on total memory |
320 | 3 | if (sHistoryMaxTotalViewers < 0) { |
321 | 3 | sHistoryMaxTotalViewers = CalcMaxTotalViewers(); |
322 | 3 | } |
323 | 3 | } |
324 | | |
325 | | // static |
326 | | nsresult |
327 | | nsSHistory::Startup() |
328 | 3 | { |
329 | 3 | UpdatePrefs(); |
330 | 3 | |
331 | 3 | // The goal of this is to unbreak users who have inadvertently set their |
332 | 3 | // session history size to less than the default value. |
333 | 3 | int32_t defaultHistoryMaxSize = |
334 | 3 | Preferences::GetInt(PREF_SHISTORY_SIZE, 50, PrefValueKind::Default); |
335 | 3 | if (gHistoryMaxSize < defaultHistoryMaxSize) { |
336 | 0 | gHistoryMaxSize = defaultHistoryMaxSize; |
337 | 0 | } |
338 | 3 | |
339 | 3 | // Allow the user to override the max total number of cached viewers, |
340 | 3 | // but keep the per SHistory cached viewer limit constant |
341 | 3 | if (!gObserver) { |
342 | 3 | gObserver = new nsSHistoryObserver(); |
343 | 3 | Preferences::RegisterCallbacks( |
344 | 3 | PREF_CHANGE_METHOD(nsSHistoryObserver::PrefChanged), |
345 | 3 | kObservedPrefs, gObserver.get()); |
346 | 3 | |
347 | 3 | nsCOMPtr<nsIObserverService> obsSvc = |
348 | 3 | mozilla::services::GetObserverService(); |
349 | 3 | if (obsSvc) { |
350 | 3 | // Observe empty-cache notifications so tahat clearing the disk/memory |
351 | 3 | // cache will also evict all content viewers. |
352 | 3 | obsSvc->AddObserver(gObserver, "cacheservice:empty-cache", false); |
353 | 3 | |
354 | 3 | // Same for memory-pressure notifications |
355 | 3 | obsSvc->AddObserver(gObserver, "memory-pressure", false); |
356 | 3 | } |
357 | 3 | } |
358 | 3 | |
359 | 3 | return NS_OK; |
360 | 3 | } |
361 | | |
362 | | // static |
363 | | void |
364 | | nsSHistory::Shutdown() |
365 | 0 | { |
366 | 0 | if (gObserver) { |
367 | 0 | Preferences::UnregisterCallbacks( |
368 | 0 | PREF_CHANGE_METHOD(nsSHistoryObserver::PrefChanged), |
369 | 0 | kObservedPrefs, gObserver.get()); |
370 | 0 |
|
371 | 0 | nsCOMPtr<nsIObserverService> obsSvc = |
372 | 0 | mozilla::services::GetObserverService(); |
373 | 0 | if (obsSvc) { |
374 | 0 | obsSvc->RemoveObserver(gObserver, "cacheservice:empty-cache"); |
375 | 0 | obsSvc->RemoveObserver(gObserver, "memory-pressure"); |
376 | 0 | } |
377 | 0 | gObserver = nullptr; |
378 | 0 | } |
379 | 0 | } |
380 | | |
381 | | // static |
382 | | nsISHEntry* |
383 | | nsSHistory::GetRootSHEntry(nsISHEntry* aEntry) |
384 | 0 | { |
385 | 0 | nsCOMPtr<nsISHEntry> rootEntry = aEntry; |
386 | 0 | nsISHEntry* result = nullptr; |
387 | 0 | while (rootEntry) { |
388 | 0 | result = rootEntry; |
389 | 0 | rootEntry = result->GetParent(); |
390 | 0 | } |
391 | 0 |
|
392 | 0 | return result; |
393 | 0 | } |
394 | | |
395 | | // static |
396 | | nsresult |
397 | | nsSHistory::WalkHistoryEntries(nsISHEntry* aRootEntry, |
398 | | nsDocShell* aRootShell, |
399 | | WalkHistoryEntriesFunc aCallback, |
400 | | void* aData) |
401 | 0 | { |
402 | 0 | NS_ENSURE_TRUE(aRootEntry, NS_ERROR_FAILURE); |
403 | 0 |
|
404 | 0 | int32_t childCount = aRootEntry->GetChildCount(); |
405 | 0 | for (int32_t i = 0; i < childCount; i++) { |
406 | 0 | nsCOMPtr<nsISHEntry> childEntry; |
407 | 0 | aRootEntry->GetChildAt(i, getter_AddRefs(childEntry)); |
408 | 0 | if (!childEntry) { |
409 | 0 | // childEntry can be null for valid reasons, for example if the |
410 | 0 | // docshell at index i never loaded anything useful. |
411 | 0 | // Remember to clone also nulls in the child array (bug 464064). |
412 | 0 | aCallback(nullptr, nullptr, i, aData); |
413 | 0 | continue; |
414 | 0 | } |
415 | 0 | |
416 | 0 | nsDocShell* childShell = nullptr; |
417 | 0 | if (aRootShell) { |
418 | 0 | // Walk the children of aRootShell and see if one of them |
419 | 0 | // has srcChild as a SHEntry. |
420 | 0 | int32_t length; |
421 | 0 | aRootShell->GetChildCount(&length); |
422 | 0 | for (int32_t i = 0; i < length; i++) { |
423 | 0 | nsCOMPtr<nsIDocShellTreeItem> item; |
424 | 0 | nsresult rv = aRootShell->GetChildAt(i, getter_AddRefs(item)); |
425 | 0 | NS_ENSURE_SUCCESS(rv, rv); |
426 | 0 | nsDocShell* child = static_cast<nsDocShell*>(item.get()); |
427 | 0 | if (child->HasHistoryEntry(childEntry)) { |
428 | 0 | childShell = child; |
429 | 0 | break; |
430 | 0 | } |
431 | 0 | } |
432 | 0 | } |
433 | 0 | nsresult rv = aCallback(childEntry, childShell, i, aData); |
434 | 0 | NS_ENSURE_SUCCESS(rv, rv); |
435 | 0 | } |
436 | 0 |
|
437 | 0 | return NS_OK; |
438 | 0 | } |
439 | | |
440 | | // callback data for WalkHistoryEntries |
441 | | struct MOZ_STACK_CLASS CloneAndReplaceData |
442 | | { |
443 | | CloneAndReplaceData(uint32_t aCloneID, nsISHEntry* aReplaceEntry, |
444 | | bool aCloneChildren, nsISHEntry* aDestTreeParent) |
445 | | : cloneID(aCloneID) |
446 | | , cloneChildren(aCloneChildren) |
447 | | , replaceEntry(aReplaceEntry) |
448 | | , destTreeParent(aDestTreeParent) |
449 | 0 | { |
450 | 0 | } |
451 | | |
452 | | uint32_t cloneID; |
453 | | bool cloneChildren; |
454 | | nsISHEntry* replaceEntry; |
455 | | nsISHEntry* destTreeParent; |
456 | | nsCOMPtr<nsISHEntry> resultEntry; |
457 | | }; |
458 | | |
459 | | // static |
460 | | nsresult |
461 | | nsSHistory::CloneAndReplaceChild(nsISHEntry* aEntry, |
462 | | nsDocShell* aShell, |
463 | | int32_t aEntryIndex, |
464 | | void* aData) |
465 | 0 | { |
466 | 0 | nsCOMPtr<nsISHEntry> dest; |
467 | 0 |
|
468 | 0 | CloneAndReplaceData* data = static_cast<CloneAndReplaceData*>(aData); |
469 | 0 | uint32_t cloneID = data->cloneID; |
470 | 0 | nsISHEntry* replaceEntry = data->replaceEntry; |
471 | 0 |
|
472 | 0 | if (!aEntry) { |
473 | 0 | if (data->destTreeParent) { |
474 | 0 | data->destTreeParent->AddChild(nullptr, aEntryIndex); |
475 | 0 | } |
476 | 0 | return NS_OK; |
477 | 0 | } |
478 | 0 |
|
479 | 0 | uint32_t srcID = aEntry->GetID(); |
480 | 0 |
|
481 | 0 | nsresult rv = NS_OK; |
482 | 0 | if (srcID == cloneID) { |
483 | 0 | // Replace the entry |
484 | 0 | dest = replaceEntry; |
485 | 0 | } else { |
486 | 0 | // Clone the SHEntry... |
487 | 0 | rv = aEntry->Clone(getter_AddRefs(dest)); |
488 | 0 | NS_ENSURE_SUCCESS(rv, rv); |
489 | 0 | } |
490 | 0 | dest->SetIsSubFrame(true); |
491 | 0 |
|
492 | 0 | if (srcID != cloneID || data->cloneChildren) { |
493 | 0 | // Walk the children |
494 | 0 | CloneAndReplaceData childData(cloneID, replaceEntry, |
495 | 0 | data->cloneChildren, dest); |
496 | 0 | rv = WalkHistoryEntries(aEntry, aShell, |
497 | 0 | CloneAndReplaceChild, &childData); |
498 | 0 | NS_ENSURE_SUCCESS(rv, rv); |
499 | 0 | } |
500 | 0 |
|
501 | 0 | if (srcID != cloneID && aShell) { |
502 | 0 | aShell->SwapHistoryEntries(aEntry, dest); |
503 | 0 | } |
504 | 0 |
|
505 | 0 | if (data->destTreeParent) { |
506 | 0 | data->destTreeParent->AddChild(dest, aEntryIndex); |
507 | 0 | } |
508 | 0 |
|
509 | 0 | data->resultEntry = dest; |
510 | 0 | return rv; |
511 | 0 | } |
512 | | |
513 | | // static |
514 | | nsresult |
515 | | nsSHistory::CloneAndReplace(nsISHEntry* aSrcEntry, |
516 | | nsDocShell* aSrcShell, |
517 | | uint32_t aCloneID, |
518 | | nsISHEntry* aReplaceEntry, |
519 | | bool aCloneChildren, |
520 | | nsISHEntry** aResultEntry) |
521 | 0 | { |
522 | 0 | NS_ENSURE_ARG_POINTER(aResultEntry); |
523 | 0 | NS_ENSURE_TRUE(aReplaceEntry, NS_ERROR_FAILURE); |
524 | 0 |
|
525 | 0 | CloneAndReplaceData data(aCloneID, aReplaceEntry, aCloneChildren, nullptr); |
526 | 0 | nsresult rv = CloneAndReplaceChild(aSrcEntry, aSrcShell, 0, &data); |
527 | 0 |
|
528 | 0 | data.resultEntry.swap(*aResultEntry); |
529 | 0 | return rv; |
530 | 0 | } |
531 | | |
532 | | // static |
533 | | nsresult |
534 | | nsSHistory::SetChildHistoryEntry(nsISHEntry* aEntry, nsDocShell* aShell, |
535 | | int32_t aEntryIndex, void* aData) |
536 | 0 | { |
537 | 0 | SwapEntriesData* data = static_cast<SwapEntriesData*>(aData); |
538 | 0 | nsDocShell* ignoreShell = data->ignoreShell; |
539 | 0 |
|
540 | 0 | if (!aShell || aShell == ignoreShell) { |
541 | 0 | return NS_OK; |
542 | 0 | } |
543 | 0 | |
544 | 0 | nsISHEntry* destTreeRoot = data->destTreeRoot; |
545 | 0 |
|
546 | 0 | nsCOMPtr<nsISHEntry> destEntry; |
547 | 0 |
|
548 | 0 | if (data->destTreeParent) { |
549 | 0 | // aEntry is a clone of some child of destTreeParent, but since the |
550 | 0 | // trees aren't necessarily in sync, we'll have to locate it. |
551 | 0 | // Note that we could set aShell's entry to null if we don't find a |
552 | 0 | // corresponding entry under destTreeParent. |
553 | 0 |
|
554 | 0 | uint32_t targetID = aEntry->GetID(); |
555 | 0 |
|
556 | 0 | // First look at the given index, since this is the common case. |
557 | 0 | nsCOMPtr<nsISHEntry> entry; |
558 | 0 | data->destTreeParent->GetChildAt(aEntryIndex, getter_AddRefs(entry)); |
559 | 0 | if (entry && entry->GetID() == targetID) { |
560 | 0 | destEntry.swap(entry); |
561 | 0 | } else { |
562 | 0 | int32_t childCount; |
563 | 0 | data->destTreeParent->GetChildCount(&childCount); |
564 | 0 | for (int32_t i = 0; i < childCount; ++i) { |
565 | 0 | data->destTreeParent->GetChildAt(i, getter_AddRefs(entry)); |
566 | 0 | if (!entry) { |
567 | 0 | continue; |
568 | 0 | } |
569 | 0 | |
570 | 0 | if (entry->GetID() == targetID) { |
571 | 0 | destEntry.swap(entry); |
572 | 0 | break; |
573 | 0 | } |
574 | 0 | } |
575 | 0 | } |
576 | 0 | } else { |
577 | 0 | destEntry = destTreeRoot; |
578 | 0 | } |
579 | 0 |
|
580 | 0 | aShell->SwapHistoryEntries(aEntry, destEntry); |
581 | 0 |
|
582 | 0 | // Now handle the children of aEntry. |
583 | 0 | SwapEntriesData childData = { ignoreShell, destTreeRoot, destEntry }; |
584 | 0 | return WalkHistoryEntries(aEntry, aShell, SetChildHistoryEntry, &childData); |
585 | 0 | } |
586 | | |
587 | | /* Add an entry to the History list at mIndex and |
588 | | * increment the index to point to the new entry |
589 | | */ |
590 | | NS_IMETHODIMP |
591 | | nsSHistory::AddEntry(nsISHEntry* aSHEntry, bool aPersist) |
592 | 0 | { |
593 | 0 | NS_ENSURE_ARG(aSHEntry); |
594 | 0 |
|
595 | 0 | nsCOMPtr<nsISHistory> shistoryOfEntry = aSHEntry->GetSHistory(); |
596 | 0 | if (shistoryOfEntry && shistoryOfEntry != this) { |
597 | 0 | NS_WARNING("The entry has been associated to another nsISHistory instance. " |
598 | 0 | "Try nsISHEntry.clone() and nsISHEntry.abandonBFCacheEntry() " |
599 | 0 | "first if you're copying an entry from another nsISHistory."); |
600 | 0 | return NS_ERROR_FAILURE; |
601 | 0 | } |
602 | 0 |
|
603 | 0 | nsCOMPtr<nsISHEntry> currentTxn; |
604 | 0 | if (mIndex >= 0) { |
605 | 0 | nsresult rv = GetEntryAtIndex(mIndex, getter_AddRefs(currentTxn)); |
606 | 0 | NS_ENSURE_SUCCESS(rv, rv); |
607 | 0 | } |
608 | 0 |
|
609 | 0 | aSHEntry->SetSHistory(this); |
610 | 0 |
|
611 | 0 | // If we have a root docshell, update the docshell id of the root shentry to |
612 | 0 | // match the id of that docshell |
613 | 0 | if (mRootDocShell) { |
614 | 0 | nsID docshellID = mRootDocShell->HistoryID(); |
615 | 0 | aSHEntry->SetDocshellID(&docshellID); |
616 | 0 | } |
617 | 0 |
|
618 | 0 | if (currentTxn && !currentTxn->GetPersist()) { |
619 | 0 | NOTIFY_LISTENERS(OnHistoryReplaceEntry, (mIndex)); |
620 | 0 | aSHEntry->SetPersist(aPersist); |
621 | 0 | mEntries[mIndex] = aSHEntry; |
622 | 0 | return NS_OK; |
623 | 0 | } |
624 | 0 |
|
625 | 0 | nsCOMPtr<nsIURI> uri = aSHEntry->GetURI(); |
626 | 0 | NOTIFY_LISTENERS(OnHistoryNewEntry, (uri, mIndex)); |
627 | 0 |
|
628 | 0 | // Remove all entries after the current one, add the new one, and set the new |
629 | 0 | // one as the current one. |
630 | 0 | MOZ_ASSERT(mIndex >= -1); |
631 | 0 | aSHEntry->SetPersist(aPersist); |
632 | 0 | mEntries.TruncateLength(mIndex + 1); |
633 | 0 | mEntries.AppendElement(aSHEntry); |
634 | 0 | mIndex++; |
635 | 0 |
|
636 | 0 | // Purge History list if it is too long |
637 | 0 | if (gHistoryMaxSize >= 0 && Length() > gHistoryMaxSize) { |
638 | 0 | PurgeHistory(Length() - gHistoryMaxSize); |
639 | 0 | } |
640 | 0 |
|
641 | 0 | return NS_OK; |
642 | 0 | } |
643 | | |
644 | | /* Get size of the history list */ |
645 | | NS_IMETHODIMP |
646 | | nsSHistory::GetCount(int32_t* aResult) |
647 | 0 | { |
648 | 0 | MOZ_ASSERT(aResult, "null out param?"); |
649 | 0 | *aResult = Length(); |
650 | 0 | return NS_OK; |
651 | 0 | } |
652 | | |
653 | | NS_IMETHODIMP |
654 | | nsSHistory::GetIndex(int32_t* aResult) |
655 | 0 | { |
656 | 0 | MOZ_ASSERT(aResult, "null out param?"); |
657 | 0 | *aResult = mIndex; |
658 | 0 | return NS_OK; |
659 | 0 | } |
660 | | |
661 | | NS_IMETHODIMP |
662 | | nsSHistory::SetIndex(int32_t aIndex) |
663 | 0 | { |
664 | 0 | if (aIndex < 0 || aIndex >= Length()) { |
665 | 0 | return NS_ERROR_FAILURE; |
666 | 0 | } |
667 | 0 | |
668 | 0 | mIndex = aIndex; |
669 | 0 | return NS_OK; |
670 | 0 | } |
671 | | |
672 | | /* Get the requestedIndex */ |
673 | | NS_IMETHODIMP |
674 | | nsSHistory::GetRequestedIndex(int32_t* aResult) |
675 | 0 | { |
676 | 0 | MOZ_ASSERT(aResult, "null out param?"); |
677 | 0 | *aResult = mRequestedIndex; |
678 | 0 | return NS_OK; |
679 | 0 | } |
680 | | |
681 | | NS_IMETHODIMP |
682 | | nsSHistory::GetEntryAtIndex(int32_t aIndex, nsISHEntry** aResult) |
683 | 0 | { |
684 | 0 | NS_ENSURE_ARG_POINTER(aResult); |
685 | 0 |
|
686 | 0 | if (aIndex < 0 || aIndex >= Length()) { |
687 | 0 | return NS_ERROR_FAILURE; |
688 | 0 | } |
689 | 0 | |
690 | 0 | *aResult = mEntries[aIndex]; |
691 | 0 | NS_ADDREF(*aResult); |
692 | 0 | return NS_OK; |
693 | 0 | } |
694 | | |
695 | | NS_IMETHODIMP_(int32_t) |
696 | | nsSHistory::GetIndexOfEntry(nsISHEntry* aSHEntry) |
697 | 0 | { |
698 | 0 | for (int32_t i = 0; i < Length(); i++) { |
699 | 0 | if (aSHEntry == mEntries[i]) { |
700 | 0 | return i; |
701 | 0 | } |
702 | 0 | } |
703 | 0 |
|
704 | 0 | return -1; |
705 | 0 | } |
706 | | |
707 | | #ifdef DEBUG |
708 | | nsresult |
709 | | nsSHistory::PrintHistory() |
710 | | { |
711 | | for (int32_t i = 0; i < Length(); i++) { |
712 | | nsCOMPtr<nsISHEntry> entry = mEntries[i]; |
713 | | nsCOMPtr<nsILayoutHistoryState> layoutHistoryState = |
714 | | entry->GetLayoutHistoryState(); |
715 | | nsCOMPtr<nsIURI> uri = entry->GetURI(); |
716 | | nsString title; |
717 | | entry->GetTitle(title); |
718 | | |
719 | | #if 0 |
720 | | nsAutoCString url; |
721 | | if (uri) { |
722 | | uri->GetSpec(url); |
723 | | } |
724 | | |
725 | | printf("**** SH Entry #%d: %x\n", i, entry.get()); |
726 | | printf("\t\t URL = %s\n", url.get()); |
727 | | |
728 | | printf("\t\t Title = %s\n", NS_LossyConvertUTF16toASCII(title).get()); |
729 | | printf("\t\t layout History Data = %x\n", layoutHistoryState.get()); |
730 | | #endif |
731 | | } |
732 | | |
733 | | return NS_OK; |
734 | | } |
735 | | #endif |
736 | | |
737 | | void |
738 | | nsSHistory::WindowIndices(int32_t aIndex, int32_t* aOutStartIndex, |
739 | | int32_t* aOutEndIndex) |
740 | 0 | { |
741 | 0 | *aOutStartIndex = std::max(0, aIndex - nsSHistory::VIEWER_WINDOW); |
742 | 0 | *aOutEndIndex = std::min(Length() - 1, aIndex + nsSHistory::VIEWER_WINDOW); |
743 | 0 | } |
744 | | |
745 | | NS_IMETHODIMP |
746 | | nsSHistory::PurgeHistory(int32_t aNumEntries) |
747 | 0 | { |
748 | 0 | if (Length() <= 0 || aNumEntries <= 0) { |
749 | 0 | return NS_ERROR_FAILURE; |
750 | 0 | } |
751 | 0 | |
752 | 0 | aNumEntries = std::min(aNumEntries, Length()); |
753 | 0 |
|
754 | 0 | NOTIFY_LISTENERS(OnHistoryPurge, (aNumEntries)); |
755 | 0 |
|
756 | 0 | // Remove the first `aNumEntries` entries. |
757 | 0 | mEntries.RemoveElementsAt(0, aNumEntries); |
758 | 0 |
|
759 | 0 | // Adjust the indices, but don't let them go below -1. |
760 | 0 | mIndex -= aNumEntries; |
761 | 0 | mIndex = std::max(mIndex, -1); |
762 | 0 | mRequestedIndex -= aNumEntries; |
763 | 0 | mRequestedIndex = std::max(mRequestedIndex, -1); |
764 | 0 |
|
765 | 0 | if (mRootDocShell) { |
766 | 0 | mRootDocShell->HistoryPurged(aNumEntries); |
767 | 0 | } |
768 | 0 |
|
769 | 0 | return NS_OK; |
770 | 0 | } |
771 | | |
772 | | NS_IMETHODIMP |
773 | | nsSHistory::AddSHistoryListener(nsISHistoryListener* aListener) |
774 | 0 | { |
775 | 0 | NS_ENSURE_ARG_POINTER(aListener); |
776 | 0 |
|
777 | 0 | // Check if the listener supports Weak Reference. This is a must. |
778 | 0 | // This listener functionality is used by embedders and we want to |
779 | 0 | // have the right ownership with who ever listens to SHistory |
780 | 0 | nsWeakPtr listener = do_GetWeakReference(aListener); |
781 | 0 | if (!listener) { |
782 | 0 | return NS_ERROR_FAILURE; |
783 | 0 | } |
784 | 0 | |
785 | 0 | return mListeners.AppendElementUnlessExists(listener) ? |
786 | 0 | NS_OK : NS_ERROR_OUT_OF_MEMORY; |
787 | 0 | } |
788 | | |
789 | | NS_IMETHODIMP |
790 | | nsSHistory::RemoveSHistoryListener(nsISHistoryListener* aListener) |
791 | 0 | { |
792 | 0 | // Make sure the listener that wants to be removed is the |
793 | 0 | // one we have in store. |
794 | 0 | nsWeakPtr listener = do_GetWeakReference(aListener); |
795 | 0 | mListeners.RemoveElement(listener); |
796 | 0 | return NS_OK; |
797 | 0 | } |
798 | | |
799 | | /* Replace an entry in the History list at a particular index. |
800 | | * Do not update index or count. |
801 | | */ |
802 | | NS_IMETHODIMP |
803 | | nsSHistory::ReplaceEntry(int32_t aIndex, nsISHEntry* aReplaceEntry) |
804 | 0 | { |
805 | 0 | NS_ENSURE_ARG(aReplaceEntry); |
806 | 0 |
|
807 | 0 | if (aIndex < 0 || aIndex >= Length()) { |
808 | 0 | return NS_ERROR_FAILURE; |
809 | 0 | } |
810 | 0 | |
811 | 0 | nsCOMPtr<nsISHistory> shistoryOfEntry = aReplaceEntry->GetSHistory(); |
812 | 0 | if (shistoryOfEntry && shistoryOfEntry != this) { |
813 | 0 | NS_WARNING("The entry has been associated to another nsISHistory instance. " |
814 | 0 | "Try nsISHEntry.clone() and nsISHEntry.abandonBFCacheEntry() " |
815 | 0 | "first if you're copying an entry from another nsISHistory."); |
816 | 0 | return NS_ERROR_FAILURE; |
817 | 0 | } |
818 | 0 |
|
819 | 0 | aReplaceEntry->SetSHistory(this); |
820 | 0 |
|
821 | 0 | NOTIFY_LISTENERS(OnHistoryReplaceEntry, (aIndex)); |
822 | 0 |
|
823 | 0 | aReplaceEntry->SetPersist(true); |
824 | 0 | mEntries[aIndex] = aReplaceEntry; |
825 | 0 |
|
826 | 0 | return NS_OK; |
827 | 0 | } |
828 | | |
829 | | NS_IMETHODIMP |
830 | | nsSHistory::NotifyOnHistoryReload(nsIURI* aReloadURI, uint32_t aReloadFlags, |
831 | | bool* aCanReload) |
832 | 0 | { |
833 | 0 | NOTIFY_LISTENERS_CANCELABLE(OnHistoryReload, *aCanReload, |
834 | 0 | (aReloadURI, aReloadFlags, aCanReload)); |
835 | 0 | return NS_OK; |
836 | 0 | } |
837 | | |
838 | | NS_IMETHODIMP |
839 | | nsSHistory::EvictOutOfRangeContentViewers(int32_t aIndex) |
840 | 0 | { |
841 | 0 | // Check our per SHistory object limit in the currently navigated SHistory |
842 | 0 | EvictOutOfRangeWindowContentViewers(aIndex); |
843 | 0 | // Check our total limit across all SHistory objects |
844 | 0 | GloballyEvictContentViewers(); |
845 | 0 | return NS_OK; |
846 | 0 | } |
847 | | |
848 | | NS_IMETHODIMP |
849 | | nsSHistory::EvictAllContentViewers() |
850 | 0 | { |
851 | 0 | // XXXbz we don't actually do a good job of evicting things as we should, so |
852 | 0 | // we might have viewers quite far from mIndex. So just evict everything. |
853 | 0 | for (int32_t i = 0; i < Length(); i++) { |
854 | 0 | EvictContentViewerForEntry(mEntries[i]); |
855 | 0 | } |
856 | 0 |
|
857 | 0 | return NS_OK; |
858 | 0 | } |
859 | | |
860 | | nsresult |
861 | | nsSHistory::Reload(uint32_t aReloadFlags) |
862 | 0 | { |
863 | 0 | uint32_t loadType; |
864 | 0 | if (aReloadFlags & nsIWebNavigation::LOAD_FLAGS_BYPASS_PROXY && |
865 | 0 | aReloadFlags & nsIWebNavigation::LOAD_FLAGS_BYPASS_CACHE) { |
866 | 0 | loadType = LOAD_RELOAD_BYPASS_PROXY_AND_CACHE; |
867 | 0 | } else if (aReloadFlags & nsIWebNavigation::LOAD_FLAGS_BYPASS_PROXY) { |
868 | 0 | loadType = LOAD_RELOAD_BYPASS_PROXY; |
869 | 0 | } else if (aReloadFlags & nsIWebNavigation::LOAD_FLAGS_BYPASS_CACHE) { |
870 | 0 | loadType = LOAD_RELOAD_BYPASS_CACHE; |
871 | 0 | } else if (aReloadFlags & nsIWebNavigation::LOAD_FLAGS_CHARSET_CHANGE) { |
872 | 0 | loadType = LOAD_RELOAD_CHARSET_CHANGE; |
873 | 0 | } else if (aReloadFlags & nsIWebNavigation::LOAD_FLAGS_ALLOW_MIXED_CONTENT) { |
874 | 0 | loadType = LOAD_RELOAD_ALLOW_MIXED_CONTENT; |
875 | 0 | } else { |
876 | 0 | loadType = LOAD_RELOAD_NORMAL; |
877 | 0 | } |
878 | 0 |
|
879 | 0 | // We are reloading. Send Reload notifications. |
880 | 0 | // nsDocShellLoadFlagType is not public, where as nsIWebNavigation |
881 | 0 | // is public. So send the reload notifications with the |
882 | 0 | // nsIWebNavigation flags. |
883 | 0 | bool canNavigate = true; |
884 | 0 | nsCOMPtr<nsIURI> currentURI; |
885 | 0 | GetCurrentURI(getter_AddRefs(currentURI)); |
886 | 0 | NOTIFY_LISTENERS_CANCELABLE(OnHistoryReload, canNavigate, |
887 | 0 | (currentURI, aReloadFlags, &canNavigate)); |
888 | 0 | if (!canNavigate) { |
889 | 0 | return NS_OK; |
890 | 0 | } |
891 | 0 | |
892 | 0 | return LoadEntry(mIndex, loadType, HIST_CMD_RELOAD); |
893 | 0 | } |
894 | | |
895 | | NS_IMETHODIMP |
896 | | nsSHistory::ReloadCurrentEntry() |
897 | 0 | { |
898 | 0 | // Notify listeners |
899 | 0 | nsCOMPtr<nsIURI> currentURI; |
900 | 0 | GetCurrentURI(getter_AddRefs(currentURI)); |
901 | 0 | NOTIFY_LISTENERS(OnHistoryGotoIndex, (mIndex, currentURI)); |
902 | 0 |
|
903 | 0 | return LoadEntry(mIndex, LOAD_HISTORY, HIST_CMD_RELOAD); |
904 | 0 | } |
905 | | |
906 | | void |
907 | | nsSHistory::EvictOutOfRangeWindowContentViewers(int32_t aIndex) |
908 | 0 | { |
909 | 0 | // XXX rename method to EvictContentViewersExceptAroundIndex, or something. |
910 | 0 |
|
911 | 0 | // We need to release all content viewers that are no longer in the range |
912 | 0 | // |
913 | 0 | // aIndex - VIEWER_WINDOW to aIndex + VIEWER_WINDOW |
914 | 0 | // |
915 | 0 | // to ensure that this SHistory object isn't responsible for more than |
916 | 0 | // VIEWER_WINDOW content viewers. But our job is complicated by the |
917 | 0 | // fact that two entries which are related by either hash navigations or |
918 | 0 | // history.pushState will have the same content viewer. |
919 | 0 | // |
920 | 0 | // To illustrate the issue, suppose VIEWER_WINDOW = 3 and we have four |
921 | 0 | // linked entries in our history. Suppose we then add a new content |
922 | 0 | // viewer and call into this function. So the history looks like: |
923 | 0 | // |
924 | 0 | // A A A A B |
925 | 0 | // + * |
926 | 0 | // |
927 | 0 | // where the letters are content viewers and + and * denote the beginning and |
928 | 0 | // end of the range aIndex +/- VIEWER_WINDOW. |
929 | 0 | // |
930 | 0 | // Although one copy of the content viewer A exists outside the range, we |
931 | 0 | // don't want to evict A, because it has other copies in range! |
932 | 0 | // |
933 | 0 | // We therefore adjust our eviction strategy to read: |
934 | 0 | // |
935 | 0 | // Evict each content viewer outside the range aIndex -/+ |
936 | 0 | // VIEWER_WINDOW, unless that content viewer also appears within the |
937 | 0 | // range. |
938 | 0 | // |
939 | 0 | // (Note that it's entirely legal to have two copies of one content viewer |
940 | 0 | // separated by a different content viewer -- call pushState twice, go back |
941 | 0 | // once, and refresh -- so we can't rely on identical viewers only appearing |
942 | 0 | // adjacent to one another.) |
943 | 0 |
|
944 | 0 | if (aIndex < 0) { |
945 | 0 | return; |
946 | 0 | } |
947 | 0 | NS_ENSURE_TRUE_VOID(aIndex < Length()); |
948 | 0 |
|
949 | 0 | // Calculate the range that's safe from eviction. |
950 | 0 | int32_t startSafeIndex, endSafeIndex; |
951 | 0 | WindowIndices(aIndex, &startSafeIndex, &endSafeIndex); |
952 | 0 |
|
953 | 0 | LOG(("EvictOutOfRangeWindowContentViewers(index=%d), " |
954 | 0 | "Length()=%d. Safe range [%d, %d]", |
955 | 0 | aIndex, Length(), startSafeIndex, endSafeIndex)); |
956 | 0 |
|
957 | 0 | // The content viewers in range aIndex -/+ VIEWER_WINDOW will not be |
958 | 0 | // evicted. Collect a set of them so we don't accidentally evict one of them |
959 | 0 | // if it appears outside this range. |
960 | 0 | nsCOMArray<nsIContentViewer> safeViewers; |
961 | 0 | for (int32_t i = startSafeIndex; i <= endSafeIndex; i++) { |
962 | 0 | nsCOMPtr<nsIContentViewer> viewer = GetContentViewerForEntry(mEntries[i]); |
963 | 0 | safeViewers.AppendObject(viewer); |
964 | 0 | } |
965 | 0 |
|
966 | 0 | // Walk the SHistory list and evict any content viewers that aren't safe. |
967 | 0 | // (It's important that the condition checks Length(), rather than a cached |
968 | 0 | // copy of Length(), because the length might change between iterations.) |
969 | 0 | for (int32_t i = 0; i < Length(); i++) { |
970 | 0 | nsCOMPtr<nsISHEntry> entry = mEntries[i]; |
971 | 0 | nsCOMPtr<nsIContentViewer> viewer = GetContentViewerForEntry(entry); |
972 | 0 | if (safeViewers.IndexOf(viewer) == -1) { |
973 | 0 | EvictContentViewerForEntry(entry); |
974 | 0 | } |
975 | 0 | } |
976 | 0 | } |
977 | | |
978 | | namespace { |
979 | | |
980 | | class EntryAndDistance |
981 | | { |
982 | | public: |
983 | | EntryAndDistance(nsSHistory* aSHistory, nsISHEntry* aEntry, uint32_t aDist) |
984 | | : mSHistory(aSHistory) |
985 | | , mEntry(aEntry) |
986 | | , mLastTouched(0) |
987 | | , mDistance(aDist) |
988 | 0 | { |
989 | 0 | mViewer = GetContentViewerForEntry(aEntry); |
990 | 0 | NS_ASSERTION(mViewer, "Entry should have a content viewer"); |
991 | 0 |
|
992 | 0 | mLastTouched = mEntry->GetLastTouched(); |
993 | 0 | } |
994 | | |
995 | | bool operator<(const EntryAndDistance& aOther) const |
996 | 0 | { |
997 | 0 | // Compare distances first, and fall back to last-accessed times. |
998 | 0 | if (aOther.mDistance != this->mDistance) { |
999 | 0 | return this->mDistance < aOther.mDistance; |
1000 | 0 | } |
1001 | 0 | |
1002 | 0 | return this->mLastTouched < aOther.mLastTouched; |
1003 | 0 | } |
1004 | | |
1005 | | bool operator==(const EntryAndDistance& aOther) const |
1006 | 0 | { |
1007 | 0 | // This is a little silly; we need == so the default comaprator can be |
1008 | 0 | // instantiated, but this function is never actually called when we sort |
1009 | 0 | // the list of EntryAndDistance objects. |
1010 | 0 | return aOther.mDistance == this->mDistance && |
1011 | 0 | aOther.mLastTouched == this->mLastTouched; |
1012 | 0 | } |
1013 | | |
1014 | | RefPtr<nsSHistory> mSHistory; |
1015 | | nsCOMPtr<nsISHEntry> mEntry; |
1016 | | nsCOMPtr<nsIContentViewer> mViewer; |
1017 | | uint32_t mLastTouched; |
1018 | | int32_t mDistance; |
1019 | | }; |
1020 | | |
1021 | | } // namespace |
1022 | | |
1023 | | // static |
1024 | | void |
1025 | | nsSHistory::GloballyEvictContentViewers() |
1026 | 0 | { |
1027 | 0 | // First, collect from each SHistory object the entries which have a cached |
1028 | 0 | // content viewer. Associate with each entry its distance from its SHistory's |
1029 | 0 | // current index. |
1030 | 0 |
|
1031 | 0 | nsTArray<EntryAndDistance> entries; |
1032 | 0 |
|
1033 | 0 | for (auto shist : gSHistoryList) { |
1034 | 0 |
|
1035 | 0 | // Maintain a list of the entries which have viewers and belong to |
1036 | 0 | // this particular shist object. We'll add this list to the global list, |
1037 | 0 | // |entries|, eventually. |
1038 | 0 | nsTArray<EntryAndDistance> shEntries; |
1039 | 0 |
|
1040 | 0 | // Content viewers are likely to exist only within shist->mIndex -/+ |
1041 | 0 | // VIEWER_WINDOW, so only search within that range. |
1042 | 0 | // |
1043 | 0 | // A content viewer might exist outside that range due to either: |
1044 | 0 | // |
1045 | 0 | // * history.pushState or hash navigations, in which case a copy of the |
1046 | 0 | // content viewer should exist within the range, or |
1047 | 0 | // |
1048 | 0 | // * bugs which cause us not to call nsSHistory::EvictContentViewers() |
1049 | 0 | // often enough. Once we do call EvictContentViewers() for the |
1050 | 0 | // SHistory object in question, we'll do a full search of its history |
1051 | 0 | // and evict the out-of-range content viewers, so we don't bother here. |
1052 | 0 | // |
1053 | 0 | int32_t startIndex, endIndex; |
1054 | 0 | shist->WindowIndices(shist->mIndex, &startIndex, &endIndex); |
1055 | 0 | for (int32_t i = startIndex; i <= endIndex; i++) { |
1056 | 0 | nsCOMPtr<nsISHEntry> entry = shist->mEntries[i]; |
1057 | 0 | nsCOMPtr<nsIContentViewer> contentViewer = |
1058 | 0 | GetContentViewerForEntry(entry); |
1059 | 0 |
|
1060 | 0 | if (contentViewer) { |
1061 | 0 | // Because one content viewer might belong to multiple SHEntries, we |
1062 | 0 | // have to search through shEntries to see if we already know |
1063 | 0 | // about this content viewer. If we find the viewer, update its |
1064 | 0 | // distance from the SHistory's index and continue. |
1065 | 0 | bool found = false; |
1066 | 0 | for (uint32_t j = 0; j < shEntries.Length(); j++) { |
1067 | 0 | EntryAndDistance& container = shEntries[j]; |
1068 | 0 | if (container.mViewer == contentViewer) { |
1069 | 0 | container.mDistance = std::min(container.mDistance, |
1070 | 0 | DeprecatedAbs(i - shist->mIndex)); |
1071 | 0 | found = true; |
1072 | 0 | break; |
1073 | 0 | } |
1074 | 0 | } |
1075 | 0 |
|
1076 | 0 | // If we didn't find a EntryAndDistance for this content viewer, make a |
1077 | 0 | // new one. |
1078 | 0 | if (!found) { |
1079 | 0 | EntryAndDistance container(shist, entry, |
1080 | 0 | DeprecatedAbs(i - shist->mIndex)); |
1081 | 0 | shEntries.AppendElement(container); |
1082 | 0 | } |
1083 | 0 | } |
1084 | 0 | } |
1085 | 0 |
|
1086 | 0 | // We've found all the entries belonging to shist which have viewers. |
1087 | 0 | // Add those entries to our global list and move on. |
1088 | 0 | entries.AppendElements(shEntries); |
1089 | 0 | } |
1090 | 0 |
|
1091 | 0 | // We now have collected all cached content viewers. First check that we |
1092 | 0 | // have enough that we actually need to evict some. |
1093 | 0 | if ((int32_t)entries.Length() <= sHistoryMaxTotalViewers) { |
1094 | 0 | return; |
1095 | 0 | } |
1096 | 0 | |
1097 | 0 | // If we need to evict, sort our list of entries and evict the largest |
1098 | 0 | // ones. (We could of course get better algorithmic complexity here by using |
1099 | 0 | // a heap or something more clever. But sHistoryMaxTotalViewers isn't large, |
1100 | 0 | // so let's not worry about it.) |
1101 | 0 | entries.Sort(); |
1102 | 0 |
|
1103 | 0 | for (int32_t i = entries.Length() - 1; i >= sHistoryMaxTotalViewers; |
1104 | 0 | --i) { |
1105 | 0 | (entries[i].mSHistory)->EvictContentViewerForEntry(entries[i].mEntry); |
1106 | 0 | } |
1107 | 0 | } |
1108 | | |
1109 | | nsresult |
1110 | | nsSHistory::FindEntryForBFCache(nsIBFCacheEntry* aBFEntry, |
1111 | | nsISHEntry** aResult, |
1112 | | int32_t* aResultIndex) |
1113 | 0 | { |
1114 | 0 | *aResult = nullptr; |
1115 | 0 | *aResultIndex = -1; |
1116 | 0 |
|
1117 | 0 | int32_t startIndex, endIndex; |
1118 | 0 | WindowIndices(mIndex, &startIndex, &endIndex); |
1119 | 0 |
|
1120 | 0 | for (int32_t i = startIndex; i <= endIndex; ++i) { |
1121 | 0 | nsCOMPtr<nsISHEntry> shEntry = mEntries[i]; |
1122 | 0 |
|
1123 | 0 | // Does shEntry have the same BFCacheEntry as the argument to this method? |
1124 | 0 | if (shEntry->HasBFCacheEntry(aBFEntry)) { |
1125 | 0 | shEntry.forget(aResult); |
1126 | 0 | *aResultIndex = i; |
1127 | 0 | return NS_OK; |
1128 | 0 | } |
1129 | 0 | } |
1130 | 0 | return NS_ERROR_FAILURE; |
1131 | 0 | } |
1132 | | |
1133 | | nsresult |
1134 | | nsSHistory::EvictExpiredContentViewerForEntry(nsIBFCacheEntry* aBFEntry) |
1135 | 0 | { |
1136 | 0 | int32_t index; |
1137 | 0 | nsCOMPtr<nsISHEntry> shEntry; |
1138 | 0 | FindEntryForBFCache(aBFEntry, getter_AddRefs(shEntry), &index); |
1139 | 0 |
|
1140 | 0 | if (index == mIndex) { |
1141 | 0 | NS_WARNING("How did the current SHEntry expire?"); |
1142 | 0 | return NS_OK; |
1143 | 0 | } |
1144 | 0 |
|
1145 | 0 | if (shEntry) { |
1146 | 0 | EvictContentViewerForEntry(shEntry); |
1147 | 0 | } |
1148 | 0 |
|
1149 | 0 | return NS_OK; |
1150 | 0 | } |
1151 | | |
1152 | | NS_IMETHODIMP_(void) |
1153 | | nsSHistory::AddToExpirationTracker(nsIBFCacheEntry* aBFEntry) |
1154 | 0 | { |
1155 | 0 | RefPtr<nsSHEntryShared> entry = static_cast<nsSHEntryShared*>(aBFEntry); |
1156 | 0 | if (!mHistoryTracker || !entry) { |
1157 | 0 | return; |
1158 | 0 | } |
1159 | 0 | |
1160 | 0 | mHistoryTracker->AddObject(entry); |
1161 | 0 | return; |
1162 | 0 | } |
1163 | | |
1164 | | NS_IMETHODIMP_(void) |
1165 | | nsSHistory::RemoveFromExpirationTracker(nsIBFCacheEntry* aBFEntry) |
1166 | 0 | { |
1167 | 0 | RefPtr<nsSHEntryShared> entry = static_cast<nsSHEntryShared*>(aBFEntry); |
1168 | 0 | MOZ_ASSERT(mHistoryTracker && !mHistoryTracker->IsEmpty()); |
1169 | 0 | if (!mHistoryTracker || !entry) { |
1170 | 0 | return; |
1171 | 0 | } |
1172 | 0 | |
1173 | 0 | mHistoryTracker->RemoveObject(entry); |
1174 | 0 | return; |
1175 | 0 | } |
1176 | | |
1177 | | // Evicts all content viewers in all history objects. This is very |
1178 | | // inefficient, because it requires a linear search through all SHistory |
1179 | | // objects for each viewer to be evicted. However, this method is called |
1180 | | // infrequently -- only when the disk or memory cache is cleared. |
1181 | | |
1182 | | // static |
1183 | | void |
1184 | | nsSHistory::GloballyEvictAllContentViewers() |
1185 | 0 | { |
1186 | 0 | int32_t maxViewers = sHistoryMaxTotalViewers; |
1187 | 0 | sHistoryMaxTotalViewers = 0; |
1188 | 0 | GloballyEvictContentViewers(); |
1189 | 0 | sHistoryMaxTotalViewers = maxViewers; |
1190 | 0 | } |
1191 | | |
1192 | | void |
1193 | | GetDynamicChildren(nsISHEntry* aEntry, |
1194 | | nsTArray<nsID>& aDocshellIDs, |
1195 | | bool aOnlyTopLevelDynamic) |
1196 | 0 | { |
1197 | 0 | int32_t count = aEntry->GetChildCount(); |
1198 | 0 | for (int32_t i = 0; i < count; ++i) { |
1199 | 0 | nsCOMPtr<nsISHEntry> child; |
1200 | 0 | aEntry->GetChildAt(i, getter_AddRefs(child)); |
1201 | 0 | if (child) { |
1202 | 0 | bool dynAdded = child->IsDynamicallyAdded(); |
1203 | 0 | if (dynAdded) { |
1204 | 0 | nsID docshellID = child->DocshellID(); |
1205 | 0 | aDocshellIDs.AppendElement(docshellID); |
1206 | 0 | } |
1207 | 0 | if (!dynAdded || !aOnlyTopLevelDynamic) { |
1208 | 0 | GetDynamicChildren(child, aDocshellIDs, aOnlyTopLevelDynamic); |
1209 | 0 | } |
1210 | 0 | } |
1211 | 0 | } |
1212 | 0 | } |
1213 | | |
1214 | | bool |
1215 | | RemoveFromSessionHistoryEntry(nsISHEntry* aRoot, nsTArray<nsID>& aDocshellIDs) |
1216 | 0 | { |
1217 | 0 | bool didRemove = false; |
1218 | 0 | int32_t childCount = aRoot->GetChildCount(); |
1219 | 0 | for (int32_t i = childCount - 1; i >= 0; --i) { |
1220 | 0 | nsCOMPtr<nsISHEntry> child; |
1221 | 0 | aRoot->GetChildAt(i, getter_AddRefs(child)); |
1222 | 0 | if (child) { |
1223 | 0 | nsID docshelldID = child->DocshellID(); |
1224 | 0 | if (aDocshellIDs.Contains(docshelldID)) { |
1225 | 0 | didRemove = true; |
1226 | 0 | aRoot->RemoveChild(child); |
1227 | 0 | } else { |
1228 | 0 | bool childRemoved = RemoveFromSessionHistoryEntry(child, aDocshellIDs); |
1229 | 0 | if (childRemoved) { |
1230 | 0 | didRemove = true; |
1231 | 0 | } |
1232 | 0 | } |
1233 | 0 | } |
1234 | 0 | } |
1235 | 0 | return didRemove; |
1236 | 0 | } |
1237 | | |
1238 | | bool |
1239 | | RemoveChildEntries(nsISHistory* aHistory, int32_t aIndex, |
1240 | | nsTArray<nsID>& aEntryIDs) |
1241 | 0 | { |
1242 | 0 | nsCOMPtr<nsISHEntry> root; |
1243 | 0 | aHistory->GetEntryAtIndex(aIndex, getter_AddRefs(root)); |
1244 | 0 | return root ? RemoveFromSessionHistoryEntry(root, aEntryIDs) : false; |
1245 | 0 | } |
1246 | | |
1247 | | bool |
1248 | | IsSameTree(nsISHEntry* aEntry1, nsISHEntry* aEntry2) |
1249 | 0 | { |
1250 | 0 | if (!aEntry1 && !aEntry2) { |
1251 | 0 | return true; |
1252 | 0 | } |
1253 | 0 | if ((!aEntry1 && aEntry2) || (aEntry1 && !aEntry2)) { |
1254 | 0 | return false; |
1255 | 0 | } |
1256 | 0 | uint32_t id1 = aEntry1->GetID(); |
1257 | 0 | uint32_t id2 = aEntry2->GetID(); |
1258 | 0 | if (id1 != id2) { |
1259 | 0 | return false; |
1260 | 0 | } |
1261 | 0 | |
1262 | 0 | int32_t count1 = aEntry1->GetChildCount(); |
1263 | 0 | int32_t count2 = aEntry2->GetChildCount(); |
1264 | 0 | // We allow null entries in the end of the child list. |
1265 | 0 | int32_t count = std::max(count1, count2); |
1266 | 0 | for (int32_t i = 0; i < count; ++i) { |
1267 | 0 | nsCOMPtr<nsISHEntry> child1, child2; |
1268 | 0 | aEntry1->GetChildAt(i, getter_AddRefs(child1)); |
1269 | 0 | aEntry2->GetChildAt(i, getter_AddRefs(child2)); |
1270 | 0 | if (!IsSameTree(child1, child2)) { |
1271 | 0 | return false; |
1272 | 0 | } |
1273 | 0 | } |
1274 | 0 |
|
1275 | 0 | return true; |
1276 | 0 | } |
1277 | | |
1278 | | bool |
1279 | | nsSHistory::RemoveDuplicate(int32_t aIndex, bool aKeepNext) |
1280 | 0 | { |
1281 | 0 | NS_ASSERTION(aIndex >= 0, "aIndex must be >= 0!"); |
1282 | 0 | NS_ASSERTION(aIndex != 0 || aKeepNext, |
1283 | 0 | "If we're removing index 0 we must be keeping the next"); |
1284 | 0 | NS_ASSERTION(aIndex != mIndex, "Shouldn't remove mIndex!"); |
1285 | 0 |
|
1286 | 0 | int32_t compareIndex = aKeepNext ? aIndex + 1 : aIndex - 1; |
1287 | 0 |
|
1288 | 0 | nsresult rv; |
1289 | 0 | nsCOMPtr<nsISHEntry> root1, root2; |
1290 | 0 | rv = GetEntryAtIndex(aIndex, getter_AddRefs(root1)); |
1291 | 0 | NS_ENSURE_SUCCESS(rv, false); |
1292 | 0 | rv = GetEntryAtIndex(compareIndex, getter_AddRefs(root2)); |
1293 | 0 | NS_ENSURE_SUCCESS(rv, false); |
1294 | 0 |
|
1295 | 0 | if (IsSameTree(root1, root2)) { |
1296 | 0 | mEntries.RemoveElementAt(aIndex); |
1297 | 0 |
|
1298 | 0 | if (mRootDocShell) { |
1299 | 0 | static_cast<nsDocShell*>(mRootDocShell)->HistoryEntryRemoved(aIndex); |
1300 | 0 | } |
1301 | 0 |
|
1302 | 0 | // Adjust our indices to reflect the removed entry. |
1303 | 0 | if (mIndex > aIndex) { |
1304 | 0 | mIndex = mIndex - 1; |
1305 | 0 | } |
1306 | 0 |
|
1307 | 0 | // NB: If the entry we are removing is the entry currently |
1308 | 0 | // being navigated to (mRequestedIndex) then we adjust the index |
1309 | 0 | // only if we're not keeping the next entry (because if we are keeping |
1310 | 0 | // the next entry (because the current is a duplicate of the next), then |
1311 | 0 | // that entry slides into the spot that we're currently pointing to. |
1312 | 0 | // We don't do this adjustment for mIndex because mIndex cannot equal |
1313 | 0 | // aIndex. |
1314 | 0 |
|
1315 | 0 | // NB: We don't need to guard on mRequestedIndex being nonzero here, |
1316 | 0 | // because either they're strictly greater than aIndex which is at least |
1317 | 0 | // zero, or they are equal to aIndex in which case aKeepNext must be true |
1318 | 0 | // if aIndex is zero. |
1319 | 0 | if (mRequestedIndex > aIndex || (mRequestedIndex == aIndex && !aKeepNext)) { |
1320 | 0 | mRequestedIndex = mRequestedIndex - 1; |
1321 | 0 | } |
1322 | 0 | return true; |
1323 | 0 | } |
1324 | 0 | return false; |
1325 | 0 | } |
1326 | | |
1327 | | NS_IMETHODIMP_(void) |
1328 | | nsSHistory::RemoveEntries(nsTArray<nsID>& aIDs, int32_t aStartIndex) |
1329 | 0 | { |
1330 | 0 | int32_t index = aStartIndex; |
1331 | 0 | while (index >= 0 && RemoveChildEntries(this, --index, aIDs)) { |
1332 | 0 | } |
1333 | 0 | int32_t minIndex = index; |
1334 | 0 | index = aStartIndex; |
1335 | 0 | while (index >= 0 && RemoveChildEntries(this, index++, aIDs)) { |
1336 | 0 | } |
1337 | 0 |
|
1338 | 0 | // We need to remove duplicate nsSHEntry trees. |
1339 | 0 | bool didRemove = false; |
1340 | 0 | while (index > minIndex) { |
1341 | 0 | if (index != mIndex) { |
1342 | 0 | didRemove = RemoveDuplicate(index, index < mIndex) || didRemove; |
1343 | 0 | } |
1344 | 0 | --index; |
1345 | 0 | } |
1346 | 0 | if (didRemove && mRootDocShell) { |
1347 | 0 | mRootDocShell->DispatchLocationChangeEvent(); |
1348 | 0 | } |
1349 | 0 | } |
1350 | | |
1351 | | void |
1352 | | nsSHistory::RemoveDynEntries(int32_t aIndex, nsISHEntry* aEntry) |
1353 | 0 | { |
1354 | 0 | // Remove dynamic entries which are at the index and belongs to the container. |
1355 | 0 | nsCOMPtr<nsISHEntry> entry(aEntry); |
1356 | 0 | if (!entry) { |
1357 | 0 | GetEntryAtIndex(aIndex, getter_AddRefs(entry)); |
1358 | 0 | } |
1359 | 0 |
|
1360 | 0 | if (entry) { |
1361 | 0 | AutoTArray<nsID, 16> toBeRemovedEntries; |
1362 | 0 | GetDynamicChildren(entry, toBeRemovedEntries, true); |
1363 | 0 | if (toBeRemovedEntries.Length()) { |
1364 | 0 | RemoveEntries(toBeRemovedEntries, aIndex); |
1365 | 0 | } |
1366 | 0 | } |
1367 | 0 | } |
1368 | | |
1369 | | void |
1370 | | nsSHistory::RemoveDynEntriesForBFCacheEntry(nsIBFCacheEntry* aBFEntry) |
1371 | 0 | { |
1372 | 0 | int32_t index; |
1373 | 0 | nsCOMPtr<nsISHEntry> shEntry; |
1374 | 0 | FindEntryForBFCache(aBFEntry, getter_AddRefs(shEntry), &index); |
1375 | 0 | if (shEntry) { |
1376 | 0 | RemoveDynEntries(index, shEntry); |
1377 | 0 | } |
1378 | 0 | } |
1379 | | |
1380 | | NS_IMETHODIMP |
1381 | | nsSHistory::UpdateIndex() |
1382 | 0 | { |
1383 | 0 | // Update the actual index with the right value. |
1384 | 0 | if (mIndex != mRequestedIndex && mRequestedIndex != -1) { |
1385 | 0 | mIndex = mRequestedIndex; |
1386 | 0 | } |
1387 | 0 |
|
1388 | 0 | mRequestedIndex = -1; |
1389 | 0 | return NS_OK; |
1390 | 0 | } |
1391 | | |
1392 | | nsresult |
1393 | | nsSHistory::GetCurrentURI(nsIURI** aResultURI) |
1394 | 0 | { |
1395 | 0 | NS_ENSURE_ARG_POINTER(aResultURI); |
1396 | 0 | nsresult rv; |
1397 | 0 |
|
1398 | 0 | nsCOMPtr<nsISHEntry> currentEntry; |
1399 | 0 | rv = GetEntryAtIndex(mIndex, getter_AddRefs(currentEntry)); |
1400 | 0 | if (NS_FAILED(rv) && !currentEntry) { |
1401 | 0 | return rv; |
1402 | 0 | } |
1403 | 0 | nsCOMPtr<nsIURI> uri = currentEntry->GetURI(); |
1404 | 0 | uri.forget(aResultURI); |
1405 | 0 | return rv; |
1406 | 0 | } |
1407 | | |
1408 | | NS_IMETHODIMP |
1409 | | nsSHistory::GotoIndex(int32_t aIndex) |
1410 | 0 | { |
1411 | 0 | return LoadEntry(aIndex, LOAD_HISTORY, HIST_CMD_GOTOINDEX); |
1412 | 0 | } |
1413 | | |
1414 | | nsresult |
1415 | | nsSHistory::LoadNextPossibleEntry(int32_t aNewIndex, long aLoadType, |
1416 | | uint32_t aHistCmd) |
1417 | 0 | { |
1418 | 0 | mRequestedIndex = -1; |
1419 | 0 | if (aNewIndex < mIndex) { |
1420 | 0 | return LoadEntry(aNewIndex - 1, aLoadType, aHistCmd); |
1421 | 0 | } |
1422 | 0 | if (aNewIndex > mIndex) { |
1423 | 0 | return LoadEntry(aNewIndex + 1, aLoadType, aHistCmd); |
1424 | 0 | } |
1425 | 0 | return NS_ERROR_FAILURE; |
1426 | 0 | } |
1427 | | |
1428 | | nsresult |
1429 | | nsSHistory::LoadEntry(int32_t aIndex, long aLoadType, uint32_t aHistCmd) |
1430 | 0 | { |
1431 | 0 | if (!mRootDocShell) { |
1432 | 0 | return NS_ERROR_FAILURE; |
1433 | 0 | } |
1434 | 0 | |
1435 | 0 | if (aIndex < 0 || aIndex >= Length()) { |
1436 | 0 | // The index is out of range |
1437 | 0 | return NS_ERROR_FAILURE; |
1438 | 0 | } |
1439 | 0 | |
1440 | 0 | // This is a normal local history navigation. |
1441 | 0 | // Keep note of requested history index in mRequestedIndex. |
1442 | 0 | mRequestedIndex = aIndex; |
1443 | 0 |
|
1444 | 0 | nsCOMPtr<nsISHEntry> prevEntry; |
1445 | 0 | nsCOMPtr<nsISHEntry> nextEntry; |
1446 | 0 | GetEntryAtIndex(mIndex, getter_AddRefs(prevEntry)); |
1447 | 0 | GetEntryAtIndex(mRequestedIndex, getter_AddRefs(nextEntry)); |
1448 | 0 | if (!nextEntry || !prevEntry) { |
1449 | 0 | mRequestedIndex = -1; |
1450 | 0 | return NS_ERROR_FAILURE; |
1451 | 0 | } |
1452 | 0 | |
1453 | 0 | // Remember that this entry is getting loaded at this point in the sequence |
1454 | 0 | |
1455 | 0 | nextEntry->SetLastTouched(++gTouchCounter); |
1456 | 0 |
|
1457 | 0 | // Get the uri for the entry we are about to visit |
1458 | 0 | nsCOMPtr<nsIURI> nextURI = nextEntry->GetURI(); |
1459 | 0 |
|
1460 | 0 | MOZ_ASSERT((prevEntry && nextEntry && nextURI), "prevEntry, nextEntry and nextURI can't be null"); |
1461 | 0 |
|
1462 | 0 | // Send appropriate listener notifications. |
1463 | 0 | if (aHistCmd == HIST_CMD_GOTOINDEX) { |
1464 | 0 | // We are going somewhere else. This is not reload either |
1465 | 0 | NOTIFY_LISTENERS(OnHistoryGotoIndex, (aIndex, nextURI)); |
1466 | 0 | } |
1467 | 0 |
|
1468 | 0 | if (mRequestedIndex == mIndex) { |
1469 | 0 | // Possibly a reload case |
1470 | 0 | return InitiateLoad(nextEntry, mRootDocShell, aLoadType); |
1471 | 0 | } |
1472 | 0 | |
1473 | 0 | // Going back or forward. |
1474 | 0 | bool differenceFound = false; |
1475 | 0 | nsresult rv = LoadDifferingEntries(prevEntry, nextEntry, mRootDocShell, |
1476 | 0 | aLoadType, differenceFound); |
1477 | 0 | if (!differenceFound) { |
1478 | 0 | // We did not find any differences. Go further in the history. |
1479 | 0 | return LoadNextPossibleEntry(aIndex, aLoadType, aHistCmd); |
1480 | 0 | } |
1481 | 0 | |
1482 | 0 | return rv; |
1483 | 0 | } |
1484 | | |
1485 | | nsresult |
1486 | | nsSHistory::LoadDifferingEntries(nsISHEntry* aPrevEntry, nsISHEntry* aNextEntry, |
1487 | | nsIDocShell* aParent, long aLoadType, |
1488 | | bool& aDifferenceFound) |
1489 | 0 | { |
1490 | 0 | if (!aPrevEntry || !aNextEntry || !aParent) { |
1491 | 0 | return NS_ERROR_FAILURE; |
1492 | 0 | } |
1493 | 0 | |
1494 | 0 | nsresult result = NS_OK; |
1495 | 0 | uint32_t prevID = aPrevEntry->GetID(); |
1496 | 0 | uint32_t nextID = aNextEntry->GetID(); |
1497 | 0 |
|
1498 | 0 | // Check the IDs to verify if the pages are different. |
1499 | 0 | if (prevID != nextID) { |
1500 | 0 | aDifferenceFound = true; |
1501 | 0 |
|
1502 | 0 | // Set the Subframe flag if not navigating the root docshell. |
1503 | 0 | aNextEntry->SetIsSubFrame(aParent != mRootDocShell); |
1504 | 0 | return InitiateLoad(aNextEntry, aParent, aLoadType); |
1505 | 0 | } |
1506 | 0 | |
1507 | 0 | // The entries are the same, so compare any child frames |
1508 | 0 | int32_t pcnt = aPrevEntry->GetChildCount(); |
1509 | 0 | int32_t ncnt = aNextEntry->GetChildCount(); |
1510 | 0 | int32_t dsCount = 0; |
1511 | 0 | aParent->GetChildCount(&dsCount); |
1512 | 0 |
|
1513 | 0 | // Create an array for child docshells. |
1514 | 0 | nsCOMArray<nsIDocShell> docshells; |
1515 | 0 | for (int32_t i = 0; i < dsCount; ++i) { |
1516 | 0 | nsCOMPtr<nsIDocShellTreeItem> treeItem; |
1517 | 0 | aParent->GetChildAt(i, getter_AddRefs(treeItem)); |
1518 | 0 | nsCOMPtr<nsIDocShell> shell = do_QueryInterface(treeItem); |
1519 | 0 | if (shell) { |
1520 | 0 | docshells.AppendElement(shell.forget()); |
1521 | 0 | } |
1522 | 0 | } |
1523 | 0 |
|
1524 | 0 | // Search for something to load next. |
1525 | 0 | for (int32_t i = 0; i < ncnt; ++i) { |
1526 | 0 | // First get an entry which may cause a new page to be loaded. |
1527 | 0 | nsCOMPtr<nsISHEntry> nChild; |
1528 | 0 | aNextEntry->GetChildAt(i, getter_AddRefs(nChild)); |
1529 | 0 | if (!nChild) { |
1530 | 0 | continue; |
1531 | 0 | } |
1532 | 0 | nsID docshellID = nChild->DocshellID(); |
1533 | 0 |
|
1534 | 0 | // Then find the associated docshell. |
1535 | 0 | nsIDocShell* dsChild = nullptr; |
1536 | 0 | int32_t count = docshells.Count(); |
1537 | 0 | for (int32_t j = 0; j < count; ++j) { |
1538 | 0 | nsIDocShell* shell = docshells[j]; |
1539 | 0 | nsID shellID = shell->HistoryID(); |
1540 | 0 | if (shellID == docshellID) { |
1541 | 0 | dsChild = shell; |
1542 | 0 | break; |
1543 | 0 | } |
1544 | 0 | } |
1545 | 0 | if (!dsChild) { |
1546 | 0 | continue; |
1547 | 0 | } |
1548 | 0 | |
1549 | 0 | // Then look at the previous entries to see if there was |
1550 | 0 | // an entry for the docshell. |
1551 | 0 | nsCOMPtr<nsISHEntry> pChild; |
1552 | 0 | for (int32_t k = 0; k < pcnt; ++k) { |
1553 | 0 | nsCOMPtr<nsISHEntry> child; |
1554 | 0 | aPrevEntry->GetChildAt(k, getter_AddRefs(child)); |
1555 | 0 | if (child) { |
1556 | 0 | nsID dID = child->DocshellID(); |
1557 | 0 | if (dID == docshellID) { |
1558 | 0 | pChild = child; |
1559 | 0 | break; |
1560 | 0 | } |
1561 | 0 | } |
1562 | 0 | } |
1563 | 0 |
|
1564 | 0 | // Finally recursively call this method. |
1565 | 0 | // This will either load a new page to shell or some subshell or |
1566 | 0 | // do nothing. |
1567 | 0 | LoadDifferingEntries(pChild, nChild, dsChild, aLoadType, aDifferenceFound); |
1568 | 0 | } |
1569 | 0 | return result; |
1570 | 0 | } |
1571 | | |
1572 | | nsresult |
1573 | | nsSHistory::InitiateLoad(nsISHEntry* aFrameEntry, nsIDocShell* aFrameDS, |
1574 | | long aLoadType) |
1575 | 0 | { |
1576 | 0 | NS_ENSURE_STATE(aFrameDS && aFrameEntry); |
1577 | 0 |
|
1578 | 0 | RefPtr<nsDocShellLoadInfo> loadInfo = new nsDocShellLoadInfo(); |
1579 | 0 |
|
1580 | 0 | /* Set the loadType in the SHEntry too to what was passed on. |
1581 | 0 | * This will be passed on to child subframes later in nsDocShell, |
1582 | 0 | * so that proper loadType is maintained through out a frameset |
1583 | 0 | */ |
1584 | 0 | aFrameEntry->SetLoadType(aLoadType); |
1585 | 0 |
|
1586 | 0 | loadInfo->SetLoadType(aLoadType); |
1587 | 0 | loadInfo->SetSHEntry(aFrameEntry); |
1588 | 0 |
|
1589 | 0 | nsCOMPtr<nsIURI> originalURI = aFrameEntry->GetOriginalURI(); |
1590 | 0 | loadInfo->SetOriginalURI(originalURI); |
1591 | 0 |
|
1592 | 0 | loadInfo->SetLoadReplace(aFrameEntry->GetLoadReplace()); |
1593 | 0 |
|
1594 | 0 | nsCOMPtr<nsIURI> nextURI = aFrameEntry->GetURI(); |
1595 | 0 | // Time to initiate a document load |
1596 | 0 | return aFrameDS->LoadURI(nextURI, loadInfo, |
1597 | 0 | nsIWebNavigation::LOAD_FLAGS_NONE, false); |
1598 | 0 |
|
1599 | 0 | } |
1600 | | |
1601 | | NS_IMETHODIMP_(void) |
1602 | | nsSHistory::SetRootDocShell(nsIDocShell* aDocShell) |
1603 | 0 | { |
1604 | 0 | mRootDocShell = aDocShell; |
1605 | 0 |
|
1606 | 0 | // Init mHistoryTracker on setting mRootDocShell so we can bind its event |
1607 | 0 | // target to the tabGroup. |
1608 | 0 | if (mRootDocShell) { |
1609 | 0 | nsCOMPtr<nsPIDOMWindowOuter> win = mRootDocShell->GetWindow(); |
1610 | 0 | if (!win) { |
1611 | 0 | return; |
1612 | 0 | } |
1613 | 0 | |
1614 | 0 | // Seamonkey moves shistory between <xul:browser>s when restoring a tab. |
1615 | 0 | // Let's try not to break our friend too badly... |
1616 | 0 | if (mHistoryTracker) { |
1617 | 0 | NS_WARNING("Change the root docshell of a shistory is unsafe and " |
1618 | 0 | "potentially problematic."); |
1619 | 0 | mHistoryTracker->AgeAllGenerations(); |
1620 | 0 | } |
1621 | 0 |
|
1622 | 0 | nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(win); |
1623 | 0 |
|
1624 | 0 | mHistoryTracker = mozilla::MakeUnique<HistoryTracker>( |
1625 | 0 | this, |
1626 | 0 | mozilla::Preferences::GetUint(CONTENT_VIEWER_TIMEOUT_SECONDS, |
1627 | 0 | CONTENT_VIEWER_TIMEOUT_SECONDS_DEFAULT), |
1628 | 0 | global->EventTargetFor(mozilla::TaskCategory::Other)); |
1629 | 0 | } |
1630 | 0 | } |