Coverage Report

Created: 2018-09-25 14:53

/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
}