Coverage Report

Created: 2018-09-25 14:53

/src/mozilla-central/toolkit/components/places/History.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 "mozilla/Attributes.h"
8
#include "mozilla/DebugOnly.h"
9
#include "mozilla/MemoryReporting.h"
10
11
#include "mozilla/dom/ContentChild.h"
12
#include "mozilla/dom/ContentParent.h"
13
#include "nsXULAppAPI.h"
14
15
#include "History.h"
16
#include "nsNavHistory.h"
17
#include "nsNavBookmarks.h"
18
#include "Helpers.h"
19
#include "PlaceInfo.h"
20
#include "VisitInfo.h"
21
#include "nsPlacesMacros.h"
22
23
#include "mozilla/storage.h"
24
#include "mozilla/dom/Link.h"
25
#include "nsDocShellCID.h"
26
#include "mozilla/Services.h"
27
#include "nsThreadUtils.h"
28
#include "nsNetUtil.h"
29
#include "nsIFileURL.h"
30
#include "nsIXPConnect.h"
31
#include "nsIXULRuntime.h"
32
#include "mozilla/Unused.h"
33
#include "nsContentUtils.h" // for nsAutoScriptBlocker
34
#include "nsJSUtils.h"
35
#include "mozilla/ipc/URIUtils.h"
36
#include "nsPrintfCString.h"
37
#include "nsTHashtable.h"
38
#include "jsapi.h"
39
#include "mozilla/dom/ContentProcessMessageManager.h"
40
#include "mozilla/dom/Element.h"
41
#include "mozilla/dom/PlacesObservers.h"
42
#include "mozilla/dom/PlacesVisit.h"
43
#include "mozilla/dom/ScriptSettings.h"
44
45
// Initial size for the cache holding visited status observers.
46
#define VISIT_OBSERVERS_INITIAL_CACHE_LENGTH 64
47
48
using namespace mozilla::dom;
49
using namespace mozilla::ipc;
50
using mozilla::Unused;
51
52
namespace mozilla {
53
namespace places {
54
55
////////////////////////////////////////////////////////////////////////////////
56
//// Global Defines
57
58
0
#define URI_VISITED "visited"
59
0
#define URI_NOT_VISITED "not visited"
60
0
#define URI_VISITED_RESOLUTION_TOPIC "visited-status-resolution"
61
// Observer event fired after a visit has been registered in the DB.
62
0
#define URI_VISIT_SAVED "uri-visit-saved"
63
64
#define DESTINATIONFILEURI_ANNO \
65
        NS_LITERAL_CSTRING("downloads/destinationFileURI")
66
67
////////////////////////////////////////////////////////////////////////////////
68
//// VisitData
69
70
struct VisitData {
71
  VisitData()
72
  : placeId(0)
73
  , visitId(0)
74
  , hidden(true)
75
  , shouldUpdateHidden(true)
76
  , typed(false)
77
  , transitionType(UINT32_MAX)
78
  , visitTime(0)
79
  , frecency(-1)
80
  , lastVisitId(0)
81
  , lastVisitTime(0)
82
  , visitCount(0)
83
  , referrerVisitId(0)
84
  , titleChanged(false)
85
  , shouldUpdateFrecency(true)
86
  , useFrecencyRedirectBonus(false)
87
0
  {
88
0
    guid.SetIsVoid(true);
89
0
    title.SetIsVoid(true);
90
0
  }
91
92
  explicit VisitData(nsIURI* aURI,
93
                     nsIURI* aReferrer = nullptr)
94
  : placeId(0)
95
  , visitId(0)
96
  , hidden(true)
97
  , shouldUpdateHidden(true)
98
  , typed(false)
99
  , transitionType(UINT32_MAX)
100
  , visitTime(0)
101
  , frecency(-1)
102
  , lastVisitId(0)
103
  , lastVisitTime(0)
104
  , visitCount(0)
105
  , referrerVisitId(0)
106
  , titleChanged(false)
107
  , shouldUpdateFrecency(true)
108
  , useFrecencyRedirectBonus(false)
109
0
  {
110
0
    MOZ_ASSERT(aURI);
111
0
    if (aURI) {
112
0
      (void)aURI->GetSpec(spec);
113
0
      (void)GetReversedHostname(aURI, revHost);
114
0
    }
115
0
    if (aReferrer) {
116
0
      (void)aReferrer->GetSpec(referrerSpec);
117
0
    }
118
0
    guid.SetIsVoid(true);
119
0
    title.SetIsVoid(true);
120
0
  }
121
122
  /**
123
   * Sets the transition type of the visit, as well as if it was typed.
124
   *
125
   * @param aTransitionType
126
   *        The transition type constant to set.  Must be one of the
127
   *        TRANSITION_ constants on nsINavHistoryService.
128
   */
129
  void SetTransitionType(uint32_t aTransitionType)
130
0
  {
131
0
    typed = aTransitionType == nsINavHistoryService::TRANSITION_TYPED;
132
0
    transitionType = aTransitionType;
133
0
  }
134
135
  int64_t placeId;
136
  nsCString guid;
137
  int64_t visitId;
138
  nsCString spec;
139
  nsString revHost;
140
  bool hidden;
141
  bool shouldUpdateHidden;
142
  bool typed;
143
  uint32_t transitionType;
144
  PRTime visitTime;
145
  int32_t frecency;
146
  int64_t lastVisitId;
147
  PRTime lastVisitTime;
148
  uint32_t visitCount;
149
150
  /**
151
   * Stores the title.  If this is empty (IsEmpty() returns true), then the
152
   * title should be removed from the Place.  If the title is void (IsVoid()
153
   * returns true), then no title has been set on this object, and titleChanged
154
   * should remain false.
155
   */
156
  nsString title;
157
158
  nsCString referrerSpec;
159
  int64_t referrerVisitId;
160
161
  // TODO bug 626836 hook up hidden and typed change tracking too!
162
  bool titleChanged;
163
164
  // Indicates whether frecency should be updated for this visit.
165
  bool shouldUpdateFrecency;
166
167
  // Whether to override the visit type bonus with a redirect bonus when
168
  // calculating frecency on the most recent visit.
169
  bool useFrecencyRedirectBonus;
170
};
171
172
////////////////////////////////////////////////////////////////////////////////
173
//// Anonymous Helpers
174
175
namespace {
176
177
/**
178
 * Convert the given js value to a js array.
179
 *
180
 * @param [in] aValue
181
 *        the JS value to convert.
182
 * @param [in] aCtx
183
 *        The JSContext for aValue.
184
 * @param [out] _array
185
 *        the JS array.
186
 * @param [out] _arrayLength
187
 *        _array's length.
188
 */
189
nsresult
190
GetJSArrayFromJSValue(JS::Handle<JS::Value> aValue,
191
                      JSContext* aCtx,
192
                      JS::MutableHandle<JSObject*> _array,
193
0
                      uint32_t* _arrayLength) {
194
0
  if (aValue.isObjectOrNull()) {
195
0
    JS::Rooted<JSObject*> val(aCtx, aValue.toObjectOrNull());
196
0
    bool isArray;
197
0
    if (!JS_IsArrayObject(aCtx, val, &isArray)) {
198
0
      return NS_ERROR_UNEXPECTED;
199
0
    }
200
0
    if (isArray) {
201
0
      _array.set(val);
202
0
      (void)JS_GetArrayLength(aCtx, _array, _arrayLength);
203
0
      NS_ENSURE_ARG(*_arrayLength > 0);
204
0
      return NS_OK;
205
0
    }
206
0
  }
207
0
208
0
  // Build a temporary array to store this one item so the code below can
209
0
  // just loop.
210
0
  *_arrayLength = 1;
211
0
  _array.set(JS_NewArrayObject(aCtx, 0));
212
0
  NS_ENSURE_TRUE(_array, NS_ERROR_OUT_OF_MEMORY);
213
0
214
0
  bool rc = JS_DefineElement(aCtx, _array, 0, aValue, 0);
215
0
  NS_ENSURE_TRUE(rc, NS_ERROR_UNEXPECTED);
216
0
  return NS_OK;
217
0
}
218
219
/**
220
 * Attemps to convert a given js value to a nsIURI object.
221
 * @param aCtx
222
 *        The JSContext for aValue.
223
 * @param aValue
224
 *        The JS value to convert.
225
 * @return the nsIURI object, or null if aValue is not a nsIURI object.
226
 */
227
already_AddRefed<nsIURI>
228
GetJSValueAsURI(JSContext* aCtx,
229
0
                const JS::Value& aValue) {
230
0
  if (!aValue.isPrimitive()) {
231
0
    nsCOMPtr<nsIXPConnect> xpc = nsIXPConnect::XPConnect();
232
0
233
0
    nsCOMPtr<nsIXPConnectWrappedNative> wrappedObj;
234
0
    nsresult rv = xpc->GetWrappedNativeOfJSObject(aCtx, aValue.toObjectOrNull(),
235
0
                                                  getter_AddRefs(wrappedObj));
236
0
    NS_ENSURE_SUCCESS(rv, nullptr);
237
0
    nsCOMPtr<nsIURI> uri = do_QueryWrappedNative(wrappedObj);
238
0
    return uri.forget();
239
0
  }
240
0
  return nullptr;
241
0
}
242
243
/**
244
 * Obtains an nsIURI from the "uri" property of a JSObject.
245
 *
246
 * @param aCtx
247
 *        The JSContext for aObject.
248
 * @param aObject
249
 *        The JSObject to get the URI from.
250
 * @param aProperty
251
 *        The name of the property to get the URI from.
252
 * @return the URI if it exists.
253
 */
254
already_AddRefed<nsIURI>
255
GetURIFromJSObject(JSContext* aCtx,
256
                   JS::Handle<JSObject *> aObject,
257
                   const char* aProperty)
258
0
{
259
0
  JS::Rooted<JS::Value> uriVal(aCtx);
260
0
  bool rc = JS_GetProperty(aCtx, aObject, aProperty, &uriVal);
261
0
  NS_ENSURE_TRUE(rc, nullptr);
262
0
  return GetJSValueAsURI(aCtx, uriVal);
263
0
}
264
265
/**
266
 * Attemps to convert a JS value to a string.
267
 * @param aCtx
268
 *        The JSContext for aObject.
269
 * @param aValue
270
 *        The JS value to convert.
271
 * @param _string
272
 *        The string to populate with the value, or set it to void.
273
 */
274
void
275
GetJSValueAsString(JSContext* aCtx,
276
                   const JS::Value& aValue,
277
0
                   nsString& _string) {
278
0
  if (aValue.isUndefined() ||
279
0
      !(aValue.isNull() || aValue.isString())) {
280
0
    _string.SetIsVoid(true);
281
0
    return;
282
0
  }
283
0
284
0
  // |null| in JS maps to the empty string.
285
0
  if (aValue.isNull()) {
286
0
    _string.Truncate();
287
0
    return;
288
0
  }
289
0
290
0
  if (!AssignJSString(aCtx, _string, aValue.toString())) {
291
0
    _string.SetIsVoid(true);
292
0
  }
293
0
}
294
295
/**
296
 * Obtains the specified property of a JSObject.
297
 *
298
 * @param aCtx
299
 *        The JSContext for aObject.
300
 * @param aObject
301
 *        The JSObject to get the string from.
302
 * @param aProperty
303
 *        The property to get the value from.
304
 * @param _string
305
 *        The string to populate with the value, or set it to void.
306
 */
307
void
308
GetStringFromJSObject(JSContext* aCtx,
309
                      JS::Handle<JSObject *> aObject,
310
                      const char* aProperty,
311
                      nsString& _string)
312
0
{
313
0
  JS::Rooted<JS::Value> val(aCtx);
314
0
  bool rc = JS_GetProperty(aCtx, aObject, aProperty, &val);
315
0
  if (!rc) {
316
0
    _string.SetIsVoid(true);
317
0
    return;
318
0
  }
319
0
  else {
320
0
    GetJSValueAsString(aCtx, val, _string);
321
0
  }
322
0
}
323
324
/**
325
 * Obtains the specified property of a JSObject.
326
 *
327
 * @param aCtx
328
 *        The JSContext for aObject.
329
 * @param aObject
330
 *        The JSObject to get the int from.
331
 * @param aProperty
332
 *        The property to get the value from.
333
 * @param _int
334
 *        The integer to populate with the value on success.
335
 */
336
template <typename IntType>
337
nsresult
338
GetIntFromJSObject(JSContext* aCtx,
339
                   JS::Handle<JSObject *> aObject,
340
                   const char* aProperty,
341
                   IntType* _int)
342
0
{
343
0
  JS::Rooted<JS::Value> value(aCtx);
344
0
  bool rc = JS_GetProperty(aCtx, aObject, aProperty, &value);
345
0
  NS_ENSURE_TRUE(rc, NS_ERROR_UNEXPECTED);
346
0
  if (value.isUndefined()) {
347
0
    return NS_ERROR_INVALID_ARG;
348
0
  }
349
0
  NS_ENSURE_ARG(value.isPrimitive());
350
0
  NS_ENSURE_ARG(value.isNumber());
351
0
352
0
  double num;
353
0
  rc = JS::ToNumber(aCtx, value, &num);
354
0
  NS_ENSURE_TRUE(rc, NS_ERROR_UNEXPECTED);
355
0
  NS_ENSURE_ARG(IntType(num) == num);
356
0
357
0
  *_int = IntType(num);
358
0
  return NS_OK;
359
0
}
Unexecuted instantiation: Unified_cpp_components_places0.cpp:nsresult mozilla::places::(anonymous namespace)::GetIntFromJSObject<long>(JSContext*, JS::Handle<JSObject*>, char const*, long*)
Unexecuted instantiation: Unified_cpp_components_places0.cpp:nsresult mozilla::places::(anonymous namespace)::GetIntFromJSObject<unsigned int>(JSContext*, JS::Handle<JSObject*>, char const*, unsigned int*)
360
361
/**
362
 * Obtains the specified property of a JSObject.
363
 *
364
 * @pre aArray must be an Array object.
365
 *
366
 * @param aCtx
367
 *        The JSContext for aArray.
368
 * @param aArray
369
 *        The JSObject to get the object from.
370
 * @param aIndex
371
 *        The index to get the object from.
372
 * @param objOut
373
 *        Set to the JSObject pointer on success.
374
 */
375
nsresult
376
GetJSObjectFromArray(JSContext* aCtx,
377
                     JS::Handle<JSObject*> aArray,
378
                     uint32_t aIndex,
379
                     JS::MutableHandle<JSObject*> objOut)
380
0
{
381
0
  JS::Rooted<JS::Value> value(aCtx);
382
0
  bool rc = JS_GetElement(aCtx, aArray, aIndex, &value);
383
0
  NS_ENSURE_TRUE(rc, NS_ERROR_UNEXPECTED);
384
0
  NS_ENSURE_ARG(!value.isPrimitive());
385
0
  objOut.set(&value.toObject());
386
0
  return NS_OK;
387
0
}
388
389
class VisitedQuery final : public AsyncStatementCallback,
390
                           public mozIStorageCompletionCallback
391
{
392
public:
393
  NS_DECL_ISUPPORTS_INHERITED
394
395
  static nsresult Start(nsIURI* aURI,
396
                        mozIVisitedStatusCallback* aCallback=nullptr)
397
0
  {
398
0
    MOZ_ASSERT(aURI, "Null URI");
399
0
400
0
    // If we are a content process, always remote the request to the
401
0
    // parent process.
402
0
    if (XRE_IsContentProcess()) {
403
0
      URIParams uri;
404
0
      SerializeURI(aURI, uri);
405
0
406
0
      mozilla::dom::ContentChild* cpc =
407
0
        mozilla::dom::ContentChild::GetSingleton();
408
0
      NS_ASSERTION(cpc, "Content Protocol is NULL!");
409
0
      (void)cpc->SendStartVisitedQuery(uri);
410
0
      return NS_OK;
411
0
    }
412
0
413
0
    nsMainThreadPtrHandle<mozIVisitedStatusCallback>
414
0
      callback(new nsMainThreadPtrHolder<mozIVisitedStatusCallback>(
415
0
        "mozIVisitedStatusCallback", aCallback));
416
0
417
0
    nsNavHistory* navHistory = nsNavHistory::GetHistoryService();
418
0
    NS_ENSURE_STATE(navHistory);
419
0
    if (navHistory->hasEmbedVisit(aURI)) {
420
0
      RefPtr<VisitedQuery> cb = new VisitedQuery(aURI, callback, true);
421
0
      NS_ENSURE_TRUE(cb, NS_ERROR_OUT_OF_MEMORY);
422
0
      // As per IHistory contract, we must notify asynchronously.
423
0
      NS_DispatchToMainThread(
424
0
        NewRunnableMethod("places::VisitedQuery::NotifyVisitedStatus",
425
0
                          cb,
426
0
                          &VisitedQuery::NotifyVisitedStatus));
427
0
428
0
      return NS_OK;
429
0
    }
430
0
431
0
    History* history = History::GetService();
432
0
    NS_ENSURE_STATE(history);
433
0
    RefPtr<VisitedQuery> cb = new VisitedQuery(aURI, callback);
434
0
    NS_ENSURE_TRUE(cb, NS_ERROR_OUT_OF_MEMORY);
435
0
    nsresult rv = history->GetIsVisitedStatement(cb);
436
0
    NS_ENSURE_SUCCESS(rv, rv);
437
0
438
0
    return NS_OK;
439
0
  }
440
441
  // Note: the return value matters here.  We call into this method, it's not
442
  // just xpcom boilerplate.
443
  NS_IMETHOD Complete(nsresult aResult, nsISupports* aStatement) override
444
0
  {
445
0
    NS_ENSURE_SUCCESS(aResult, aResult);
446
0
    nsCOMPtr<mozIStorageAsyncStatement> stmt = do_QueryInterface(aStatement);
447
0
    NS_ENSURE_STATE(stmt);
448
0
    // Bind by index for performance.
449
0
    nsresult rv = URIBinder::Bind(stmt, 0, mURI);
450
0
    NS_ENSURE_SUCCESS(rv, rv);
451
0
452
0
    nsCOMPtr<mozIStoragePendingStatement> handle;
453
0
    return stmt->ExecuteAsync(this, getter_AddRefs(handle));
454
0
  }
455
456
  NS_IMETHOD HandleResult(mozIStorageResultSet* aResults) override
457
0
  {
458
0
    // If this method is called, we've gotten results, which means we have a
459
0
    // visit.
460
0
    mIsVisited = true;
461
0
    return NS_OK;
462
0
  }
463
464
  NS_IMETHOD HandleError(mozIStorageError* aError) override
465
0
  {
466
0
    // mIsVisited is already set to false, and that's the assumption we will
467
0
    // make if an error occurred.
468
0
    return NS_OK;
469
0
  }
470
471
  NS_IMETHOD HandleCompletion(uint16_t aReason) override
472
0
  {
473
0
    if (aReason != mozIStorageStatementCallback::REASON_FINISHED) {
474
0
      return NS_OK;
475
0
    }
476
0
477
0
    nsresult rv = NotifyVisitedStatus();
478
0
    NS_ENSURE_SUCCESS(rv, rv);
479
0
    return NS_OK;
480
0
  }
481
482
  nsresult NotifyVisitedStatus()
483
0
  {
484
0
    // If an external handling callback is provided, just notify through it.
485
0
    if (!!mCallback) {
486
0
      mCallback->IsVisited(mURI, mIsVisited);
487
0
      return NS_OK;
488
0
    }
489
0
490
0
    if (mIsVisited) {
491
0
      History* history = History::GetService();
492
0
      NS_ENSURE_STATE(history);
493
0
      history->NotifyVisited(mURI);
494
0
      if (BrowserTabsRemoteAutostart()) {
495
0
        AutoTArray<URIParams, 1> uris;
496
0
        URIParams uri;
497
0
        SerializeURI(mURI, uri);
498
0
        uris.AppendElement(std::move(uri));
499
0
        history->NotifyVisitedParent(uris);
500
0
      }
501
0
    }
502
0
503
0
    nsCOMPtr<nsIObserverService> observerService =
504
0
      mozilla::services::GetObserverService();
505
0
    if (observerService) {
506
0
      nsAutoString status;
507
0
      if (mIsVisited) {
508
0
        status.AssignLiteral(URI_VISITED);
509
0
      }
510
0
      else {
511
0
        status.AssignLiteral(URI_NOT_VISITED);
512
0
      }
513
0
      (void)observerService->NotifyObservers(mURI,
514
0
                                             URI_VISITED_RESOLUTION_TOPIC,
515
0
                                             status.get());
516
0
    }
517
0
518
0
    return NS_OK;
519
0
  }
520
521
private:
522
  explicit VisitedQuery(nsIURI* aURI,
523
                        const nsMainThreadPtrHandle<mozIVisitedStatusCallback>& aCallback,
524
                        bool aIsVisited=false)
525
  : mURI(aURI)
526
  , mCallback(aCallback)
527
  , mIsVisited(aIsVisited)
528
0
  {
529
0
  }
530
531
  ~VisitedQuery()
532
0
  {
533
0
  }
534
535
  nsCOMPtr<nsIURI> mURI;
536
  nsMainThreadPtrHandle<mozIVisitedStatusCallback> mCallback;
537
  bool mIsVisited;
538
};
539
540
NS_IMPL_ISUPPORTS_INHERITED(
541
  VisitedQuery
542
, AsyncStatementCallback
543
, mozIStorageCompletionCallback
544
)
545
546
/**
547
 * Notifies observers about a visit or an array of visits.
548
 */
549
class NotifyManyVisitsObservers : public Runnable
550
{
551
public:
552
  explicit NotifyManyVisitsObservers(const VisitData& aPlace)
553
    : Runnable("places::NotifyManyVisitsObservers")
554
    , mPlace(aPlace)
555
    , mHistory(History::GetService())
556
0
  {
557
0
  }
558
559
  explicit NotifyManyVisitsObservers(nsTArray<VisitData>& aPlaces)
560
    : Runnable("places::NotifyManyVisitsObservers")
561
    , mHistory(History::GetService())
562
0
  {
563
0
    aPlaces.SwapElements(mPlaces);
564
0
  }
565
566
  nsresult NotifyVisit(nsNavHistory* aNavHistory,
567
                       nsCOMPtr<nsIObserverService>& aObsService,
568
                       PRTime aNow,
569
                       nsIURI* aURI,
570
0
                       const VisitData& aPlace) {
571
0
    if (aObsService) {
572
0
      DebugOnly<nsresult> rv =
573
0
        aObsService->NotifyObservers(aURI, URI_VISIT_SAVED, nullptr);
574
0
      NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "Could not notify observers");
575
0
    }
576
0
577
0
    if (aNow - aPlace.visitTime < RECENTLY_VISITED_URIS_MAX_AGE) {
578
0
      mHistory->AppendToRecentlyVisitedURIs(aURI);
579
0
    }
580
0
    mHistory->NotifyVisited(aURI);
581
0
582
0
    if (aPlace.titleChanged) {
583
0
      aNavHistory->NotifyTitleChange(aURI, aPlace.title, aPlace.guid);
584
0
    }
585
0
586
0
    aNavHistory->UpdateDaysOfHistory(aPlace.visitTime);
587
0
588
0
    return NS_OK;
589
0
  }
590
591
  void AddPlaceForNotify(const VisitData& aPlace,
592
                         nsIURI* aURI,
593
0
                         Sequence<OwningNonNull<PlacesEvent>>& aEvents) {
594
0
    if (aPlace.transitionType != nsINavHistoryService::TRANSITION_EMBED) {
595
0
      RefPtr<PlacesVisit> vd = new PlacesVisit();
596
0
      vd->mVisitId = aPlace.visitId;
597
0
      vd->mUrl.Assign(NS_ConvertUTF8toUTF16(aPlace.spec));
598
0
      vd->mVisitTime = aPlace.visitTime / 1000;
599
0
      vd->mReferringVisitId = aPlace.referrerVisitId;
600
0
      vd->mTransitionType = aPlace.transitionType;
601
0
      vd->mPageGuid.Assign(aPlace.guid);
602
0
      vd->mHidden = aPlace.hidden;
603
0
      vd->mVisitCount = aPlace.visitCount + 1; // Add current visit
604
0
      vd->mTypedCount = static_cast<uint32_t>(aPlace.typed);
605
0
      vd->mLastKnownTitle.Assign(aPlace.title);
606
0
      bool success = !!aEvents.AppendElement(vd.forget(), fallible);
607
0
      MOZ_RELEASE_ASSERT(success);
608
0
    }
609
0
  }
610
611
  NS_IMETHOD Run() override
612
0
  {
613
0
    MOZ_ASSERT(NS_IsMainThread(), "This should be called on the main thread");
614
0
615
0
    // We are in the main thread, no need to lock.
616
0
    if (mHistory->IsShuttingDown()) {
617
0
      // If we are shutting down, we cannot notify the observers.
618
0
      return NS_OK;
619
0
    }
620
0
621
0
    nsNavHistory* navHistory = nsNavHistory::GetHistoryService();
622
0
    if (!navHistory) {
623
0
      NS_WARNING("Trying to notify visits observers but cannot get the history service!");
624
0
      return NS_OK;
625
0
    }
626
0
627
0
    nsCOMPtr<nsIObserverService> obsService =
628
0
      mozilla::services::GetObserverService();
629
0
630
0
    Sequence<OwningNonNull<PlacesEvent>> events;
631
0
    nsCOMArray<nsIURI> uris;
632
0
    if (mPlaces.Length() > 0) {
633
0
      for (uint32_t i = 0; i < mPlaces.Length(); ++i) {
634
0
        nsCOMPtr<nsIURI> uri;
635
0
        MOZ_ALWAYS_SUCCEEDS(NS_NewURI(getter_AddRefs(uri), mPlaces[i].spec));
636
0
        if (!uri) {
637
0
          return NS_ERROR_UNEXPECTED;
638
0
        }
639
0
        AddPlaceForNotify(mPlaces[i], uri, events);
640
0
        uris.AppendElement(uri.forget());
641
0
      }
642
0
    } else {
643
0
      nsCOMPtr<nsIURI> uri;
644
0
      MOZ_ALWAYS_SUCCEEDS(NS_NewURI(getter_AddRefs(uri), mPlace.spec));
645
0
      if (!uri) {
646
0
        return NS_ERROR_UNEXPECTED;
647
0
      }
648
0
      AddPlaceForNotify(mPlace, uri, events);
649
0
      uris.AppendElement(uri.forget());
650
0
    }
651
0
652
0
    if (events.Length() > 0) {
653
0
      PlacesObservers::NotifyListeners(events);
654
0
    }
655
0
656
0
    PRTime now = PR_Now();
657
0
    if (mPlaces.Length() > 0) {
658
0
      InfallibleTArray<URIParams> serializableUris(mPlaces.Length());
659
0
      for (uint32_t i = 0; i < mPlaces.Length(); ++i) {
660
0
        nsresult rv = NotifyVisit(navHistory, obsService, now, uris[i], mPlaces[i]);
661
0
        NS_ENSURE_SUCCESS(rv, rv);
662
0
663
0
        if (BrowserTabsRemoteAutostart()) {
664
0
          URIParams serializedUri;
665
0
          SerializeURI(uris[i], serializedUri);
666
0
          serializableUris.AppendElement(std::move(serializedUri));
667
0
        }
668
0
      }
669
0
      mHistory->NotifyVisitedParent(serializableUris);
670
0
    } else {
671
0
      AutoTArray<URIParams, 1> serializableUris;
672
0
      nsresult rv = NotifyVisit(navHistory, obsService, now, uris[0], mPlace);
673
0
      NS_ENSURE_SUCCESS(rv, rv);
674
0
675
0
      if (BrowserTabsRemoteAutostart()) {
676
0
        URIParams serializedUri;
677
0
        SerializeURI(uris[0], serializedUri);
678
0
        serializableUris.AppendElement(std::move(serializedUri));
679
0
        mHistory->NotifyVisitedParent(serializableUris);
680
0
      }
681
0
    }
682
0
683
0
    return NS_OK;
684
0
  }
685
private:
686
  nsTArray<VisitData> mPlaces;
687
  VisitData mPlace;
688
  RefPtr<History> mHistory;
689
};
690
691
/**
692
 * Notifies observers about a pages title changing.
693
 */
694
class NotifyTitleObservers : public Runnable
695
{
696
public:
697
  /**
698
   * Notifies observers on the main thread.
699
   *
700
   * @param aSpec
701
   *        The spec of the URI to notify about.
702
   * @param aTitle
703
   *        The new title to notify about.
704
   */
705
  NotifyTitleObservers(const nsCString& aSpec,
706
                       const nsString& aTitle,
707
                       const nsCString& aGUID)
708
    : Runnable("places::NotifyTitleObservers")
709
    , mSpec(aSpec)
710
    , mTitle(aTitle)
711
    , mGUID(aGUID)
712
0
  {
713
0
  }
714
715
  NS_IMETHOD Run() override
716
0
  {
717
0
    MOZ_ASSERT(NS_IsMainThread(), "This should be called on the main thread");
718
0
719
0
    nsNavHistory* navHistory = nsNavHistory::GetHistoryService();
720
0
    NS_ENSURE_TRUE(navHistory, NS_ERROR_OUT_OF_MEMORY);
721
0
    nsCOMPtr<nsIURI> uri;
722
0
    MOZ_ALWAYS_SUCCEEDS(NS_NewURI(getter_AddRefs(uri), mSpec));
723
0
    if (!uri) {
724
0
      return NS_ERROR_UNEXPECTED;
725
0
    }
726
0
727
0
    navHistory->NotifyTitleChange(uri, mTitle, mGUID);
728
0
729
0
    return NS_OK;
730
0
  }
731
private:
732
  const nsCString mSpec;
733
  const nsString mTitle;
734
  const nsCString mGUID;
735
};
736
737
/**
738
 * Helper class for methods which notify their callers through the
739
 * mozIVisitInfoCallback interface.
740
 */
741
class NotifyPlaceInfoCallback : public Runnable
742
{
743
public:
744
  NotifyPlaceInfoCallback(
745
    const nsMainThreadPtrHandle<mozIVisitInfoCallback>& aCallback,
746
    const VisitData& aPlace,
747
    bool aIsSingleVisit,
748
    nsresult aResult)
749
    : Runnable("places::NotifyPlaceInfoCallback")
750
    , mCallback(aCallback)
751
    , mPlace(aPlace)
752
    , mResult(aResult)
753
    , mIsSingleVisit(aIsSingleVisit)
754
0
  {
755
0
    MOZ_ASSERT(aCallback, "Must pass a non-null callback!");
756
0
  }
757
758
  NS_IMETHOD Run() override
759
0
  {
760
0
    MOZ_ASSERT(NS_IsMainThread(), "This should be called on the main thread");
761
0
762
0
    bool hasValidURIs = true;
763
0
    nsCOMPtr<nsIURI> referrerURI;
764
0
    if (!mPlace.referrerSpec.IsEmpty()) {
765
0
      MOZ_ALWAYS_SUCCEEDS(NS_NewURI(getter_AddRefs(referrerURI), mPlace.referrerSpec));
766
0
      hasValidURIs = !!referrerURI;
767
0
    }
768
0
769
0
    nsCOMPtr<nsIURI> uri;
770
0
    MOZ_ALWAYS_SUCCEEDS(NS_NewURI(getter_AddRefs(uri), mPlace.spec));
771
0
    hasValidURIs = hasValidURIs && !!uri;
772
0
773
0
    nsCOMPtr<mozIPlaceInfo> place;
774
0
    if (mIsSingleVisit) {
775
0
      nsCOMPtr<mozIVisitInfo> visit =
776
0
        new VisitInfo(mPlace.visitId, mPlace.visitTime, mPlace.transitionType,
777
0
                      referrerURI.forget());
778
0
      PlaceInfo::VisitsArray visits;
779
0
      (void)visits.AppendElement(visit);
780
0
781
0
      // The frecency isn't exposed because it may not reflect the updated value
782
0
      // in the case of InsertVisitedURIs.
783
0
      place =
784
0
        new PlaceInfo(mPlace.placeId, mPlace.guid, uri.forget(), mPlace.title,
785
0
                      -1, visits);
786
0
    }
787
0
    else {
788
0
      // Same as above.
789
0
      place =
790
0
        new PlaceInfo(mPlace.placeId, mPlace.guid, uri.forget(), mPlace.title,
791
0
                      -1);
792
0
    }
793
0
794
0
    if (NS_SUCCEEDED(mResult) && hasValidURIs) {
795
0
      (void)mCallback->HandleResult(place);
796
0
    } else {
797
0
      (void)mCallback->HandleError(mResult, place);
798
0
    }
799
0
800
0
    return NS_OK;
801
0
  }
802
803
private:
804
  nsMainThreadPtrHandle<mozIVisitInfoCallback> mCallback;
805
  VisitData mPlace;
806
  const nsresult mResult;
807
  bool mIsSingleVisit;
808
};
809
810
/**
811
 * Notifies a callback object when the operation is complete.
812
 */
813
class NotifyCompletion : public Runnable
814
{
815
public:
816
  explicit NotifyCompletion(
817
    const nsMainThreadPtrHandle<mozIVisitInfoCallback>& aCallback,
818
    uint32_t aUpdatedCount = 0)
819
    : Runnable("places::NotifyCompletion")
820
    , mCallback(aCallback)
821
    , mUpdatedCount(aUpdatedCount)
822
0
  {
823
0
    MOZ_ASSERT(aCallback, "Must pass a non-null callback!");
824
0
  }
825
826
  NS_IMETHOD Run() override
827
0
  {
828
0
    if (NS_IsMainThread()) {
829
0
      (void)mCallback->HandleCompletion(mUpdatedCount);
830
0
    }
831
0
    else {
832
0
      (void)NS_DispatchToMainThread(this);
833
0
    }
834
0
    return NS_OK;
835
0
  }
836
837
private:
838
  nsMainThreadPtrHandle<mozIVisitInfoCallback> mCallback;
839
  uint32_t mUpdatedCount;
840
};
841
842
/**
843
 * Checks to see if we can add aURI to history, and dispatches an error to
844
 * aCallback (if provided) if we cannot.
845
 *
846
 * @param aURI
847
 *        The URI to check.
848
 * @param [optional] aGUID
849
 *        The guid of the URI to check.  This is passed back to the callback.
850
 * @param [optional] aCallback
851
 *        The callback to notify if the URI cannot be added to history.
852
 * @return true if the URI can be added to history, false otherwise.
853
 */
854
bool
855
CanAddURI(nsIURI* aURI,
856
          const nsCString& aGUID = EmptyCString(),
857
          mozIVisitInfoCallback* aCallback = nullptr)
858
0
{
859
0
  MOZ_ASSERT(NS_IsMainThread());
860
0
  nsNavHistory* navHistory = nsNavHistory::GetHistoryService();
861
0
  NS_ENSURE_TRUE(navHistory, false);
862
0
863
0
  bool canAdd;
864
0
  nsresult rv = navHistory->CanAddURI(aURI, &canAdd);
865
0
  if (NS_SUCCEEDED(rv) && canAdd) {
866
0
    return true;
867
0
  };
868
0
869
0
  // We cannot add the URI.  Notify the callback, if we were given one.
870
0
  if (aCallback) {
871
0
    VisitData place(aURI);
872
0
    place.guid = aGUID;
873
0
    nsMainThreadPtrHandle<mozIVisitInfoCallback>
874
0
      callback(new nsMainThreadPtrHolder<mozIVisitInfoCallback>(
875
0
        "mozIVisitInfoCallback", aCallback));
876
0
    nsCOMPtr<nsIRunnable> event =
877
0
      new NotifyPlaceInfoCallback(callback, place, true, NS_ERROR_INVALID_ARG);
878
0
    (void)NS_DispatchToMainThread(event);
879
0
  }
880
0
881
0
  return false;
882
0
}
883
884
class NotifyManyFrecenciesChanged final : public Runnable
885
{
886
public:
887
  NotifyManyFrecenciesChanged()
888
    : Runnable("places::NotifyManyFrecenciesChanged")
889
0
  {
890
0
  }
891
892
  NS_IMETHOD Run() override
893
0
  {
894
0
    MOZ_ASSERT(NS_IsMainThread(), "This should be called on the main thread");
895
0
    nsNavHistory* navHistory = nsNavHistory::GetHistoryService();
896
0
    NS_ENSURE_STATE(navHistory);
897
0
    navHistory->NotifyManyFrecenciesChanged();
898
0
    return NS_OK;
899
0
  }
900
};
901
902
903
/**
904
 * Adds a visit to the database.
905
 */
906
class InsertVisitedURIs final: public Runnable
907
{
908
public:
909
  /**
910
   * Adds a visit to the database asynchronously.
911
   *
912
   * @param aConnection
913
   *        The database connection to use for these operations.
914
   * @param aPlaces
915
   *        The locations to record visits.
916
   * @param [optional] aCallback
917
   *        The callback to notify about the visit.
918
   * @param [optional] aGroupNotifications
919
   *        Whether to group any observer notifications rather than
920
   *        sending them out individually.
921
   */
922
  static nsresult Start(mozIStorageConnection* aConnection,
923
                        nsTArray<VisitData>& aPlaces,
924
                        mozIVisitInfoCallback* aCallback = nullptr,
925
                        bool aGroupNotifications = false,
926
                        uint32_t aInitialUpdatedCount = 0)
927
0
  {
928
0
    MOZ_ASSERT(NS_IsMainThread(), "This should be called on the main thread");
929
0
    MOZ_ASSERT(aPlaces.Length() > 0, "Must pass a non-empty array!");
930
0
931
0
    // Make sure nsNavHistory service is up before proceeding:
932
0
    nsNavHistory* navHistory = nsNavHistory::GetHistoryService();
933
0
    MOZ_ASSERT(navHistory, "Could not get nsNavHistory?!");
934
0
    if (!navHistory) {
935
0
      return NS_ERROR_FAILURE;
936
0
    }
937
0
938
0
    nsMainThreadPtrHandle<mozIVisitInfoCallback>
939
0
      callback(new nsMainThreadPtrHolder<mozIVisitInfoCallback>(
940
0
        "mozIVisitInfoCallback", aCallback));
941
0
    bool ignoreErrors = false, ignoreResults = false;
942
0
    if (aCallback) {
943
0
      // We ignore errors from either of these methods in case old JS consumers
944
0
      // don't implement them (in which case they will get error/result
945
0
      // notifications as normal).
946
0
      Unused << aCallback->GetIgnoreErrors(&ignoreErrors);
947
0
      Unused << aCallback->GetIgnoreResults(&ignoreResults);
948
0
    }
949
0
    RefPtr<InsertVisitedURIs> event =
950
0
      new InsertVisitedURIs(aConnection, aPlaces, callback, aGroupNotifications,
951
0
                            ignoreErrors, ignoreResults, aInitialUpdatedCount);
952
0
953
0
    // Get the target thread, and then start the work!
954
0
    nsCOMPtr<nsIEventTarget> target = do_GetInterface(aConnection);
955
0
    NS_ENSURE_TRUE(target, NS_ERROR_UNEXPECTED);
956
0
    nsresult rv = target->Dispatch(event, NS_DISPATCH_NORMAL);
957
0
    NS_ENSURE_SUCCESS(rv, rv);
958
0
959
0
    return NS_OK;
960
0
  }
961
962
  NS_IMETHOD Run() override
963
0
  {
964
0
    MOZ_ASSERT(!NS_IsMainThread(), "This should not be called on the main thread");
965
0
966
0
    // The inner run method may bail out at any point, so we ensure we do
967
0
    // whatever we can and then notify the main thread we're done.
968
0
    nsresult rv = InnerRun();
969
0
970
0
    if (mSuccessfulUpdatedCount > 0 && mGroupNotifications) {
971
0
      NS_DispatchToMainThread(new NotifyManyFrecenciesChanged());
972
0
    }
973
0
    if (!!mCallback) {
974
0
      NS_DispatchToMainThread(new NotifyCompletion(mCallback, mSuccessfulUpdatedCount));
975
0
    }
976
0
    return rv;
977
0
  }
978
979
0
  nsresult InnerRun() {
980
0
    // Prevent the main thread from shutting down while this is running.
981
0
    MutexAutoLock lockedScope(mHistory->GetShutdownMutex());
982
0
    if (mHistory->IsShuttingDown()) {
983
0
      // If we were already shutting down, we cannot insert the URIs.
984
0
      return NS_OK;
985
0
    }
986
0
987
0
    mozStorageTransaction transaction(mDBConn, false,
988
0
                                      mozIStorageConnection::TRANSACTION_IMMEDIATE);
989
0
990
0
    const VisitData* lastFetchedPlace = nullptr;
991
0
    uint32_t lastFetchedVisitCount = 0;
992
0
    bool shouldChunkNotifications = mPlaces.Length() > NOTIFY_VISITS_CHUNK_SIZE;
993
0
    InfallibleTArray<VisitData> notificationChunk;
994
0
    if (shouldChunkNotifications) {
995
0
      notificationChunk.SetCapacity(NOTIFY_VISITS_CHUNK_SIZE);
996
0
    }
997
0
    for (nsTArray<VisitData>::size_type i = 0; i < mPlaces.Length(); i++) {
998
0
      VisitData& place = mPlaces.ElementAt(i);
999
0
1000
0
      // Fetching from the database can overwrite this information, so save it
1001
0
      // apart.
1002
0
      bool typed = place.typed;
1003
0
      bool hidden = place.hidden;
1004
0
1005
0
      // We can avoid a database lookup if it's the same place as the last
1006
0
      // visit we added.
1007
0
      bool known = lastFetchedPlace && lastFetchedPlace->spec.Equals(place.spec);
1008
0
      if (!known) {
1009
0
        nsresult rv = mHistory->FetchPageInfo(place, &known);
1010
0
        if (NS_FAILED(rv)) {
1011
0
          if (!!mCallback && !mIgnoreErrors) {
1012
0
            nsCOMPtr<nsIRunnable> event =
1013
0
              new NotifyPlaceInfoCallback(mCallback, place, true, rv);
1014
0
            return NS_DispatchToMainThread(event);
1015
0
          }
1016
0
          return NS_OK;
1017
0
        }
1018
0
        lastFetchedPlace = &mPlaces.ElementAt(i);
1019
0
        lastFetchedVisitCount = lastFetchedPlace->visitCount;
1020
0
      } else {
1021
0
        // Copy over the data from the already known place.
1022
0
        place.placeId = lastFetchedPlace->placeId;
1023
0
        place.guid = lastFetchedPlace->guid;
1024
0
        place.lastVisitId = lastFetchedPlace->visitId;
1025
0
        place.lastVisitTime = lastFetchedPlace->visitTime;
1026
0
        if (!place.title.IsVoid()) {
1027
0
          place.titleChanged = !lastFetchedPlace->title.Equals(place.title);
1028
0
        }
1029
0
        place.frecency = lastFetchedPlace->frecency;
1030
0
        // Add one visit for the previous loop.
1031
0
        place.visitCount = ++lastFetchedVisitCount;
1032
0
      }
1033
0
1034
0
      // If any transition is typed, ensure the page is marked as typed.
1035
0
      if (typed != lastFetchedPlace->typed) {
1036
0
        place.typed = true;
1037
0
      }
1038
0
1039
0
      // If any transition is visible, ensure the page is marked as visible.
1040
0
      if (hidden != lastFetchedPlace->hidden) {
1041
0
        place.hidden = false;
1042
0
      }
1043
0
1044
0
      // If this is a new page, or the existing page was already visible,
1045
0
      // there's no need to try to unhide it.
1046
0
      if (!known || !lastFetchedPlace->hidden) {
1047
0
        place.shouldUpdateHidden = false;
1048
0
      }
1049
0
1050
0
      FetchReferrerInfo(place);
1051
0
1052
0
      nsresult rv = DoDatabaseInserts(known, place);
1053
0
      if (!!mCallback) {
1054
0
        // Check if consumers wanted to be notified about success/failure,
1055
0
        // depending on whether this action succeeded or not.
1056
0
        if ((NS_SUCCEEDED(rv) && !mIgnoreResults) ||
1057
0
            (NS_FAILED(rv) && !mIgnoreErrors)) {
1058
0
          nsCOMPtr<nsIRunnable> event =
1059
0
            new NotifyPlaceInfoCallback(mCallback, place, true, rv);
1060
0
          nsresult rv2 = NS_DispatchToMainThread(event);
1061
0
          NS_ENSURE_SUCCESS(rv2, rv2);
1062
0
        }
1063
0
      }
1064
0
      NS_ENSURE_SUCCESS(rv, rv);
1065
0
1066
0
      if (shouldChunkNotifications) {
1067
0
        int32_t numRemaining = mPlaces.Length() - (i + 1);
1068
0
        notificationChunk.AppendElement(place);
1069
0
        if (notificationChunk.Length() == NOTIFY_VISITS_CHUNK_SIZE ||
1070
0
            numRemaining == 0) {
1071
0
          // This will SwapElements on notificationChunk with an empty nsTArray
1072
0
          nsCOMPtr<nsIRunnable> event = new NotifyManyVisitsObservers(notificationChunk);
1073
0
          rv = NS_DispatchToMainThread(event);
1074
0
          NS_ENSURE_SUCCESS(rv, rv);
1075
0
1076
0
          int32_t nextCapacity = std::min(NOTIFY_VISITS_CHUNK_SIZE, numRemaining);
1077
0
          notificationChunk.SetCapacity(nextCapacity);
1078
0
        }
1079
0
      }
1080
0
1081
0
      // If we get here, we must have been successful adding/updating this
1082
0
      // visit/place, so update the count:
1083
0
      mSuccessfulUpdatedCount++;
1084
0
    }
1085
0
1086
0
    {
1087
0
      // Trigger insertions for all the new origins of the places we inserted.
1088
0
      nsAutoCString query("DELETE FROM moz_updateoriginsinsert_temp");
1089
0
      nsCOMPtr<mozIStorageStatement> stmt = mHistory->GetStatement(query);
1090
0
      NS_ENSURE_STATE(stmt);
1091
0
      mozStorageStatementScoper scoper(stmt);
1092
0
      nsresult rv = stmt->Execute();
1093
0
      NS_ENSURE_SUCCESS(rv, rv);
1094
0
    }
1095
0
1096
0
    {
1097
0
      // Trigger frecency updates for all those origins.
1098
0
      nsAutoCString query("DELETE FROM moz_updateoriginsupdate_temp");
1099
0
      nsCOMPtr<mozIStorageStatement> stmt = mHistory->GetStatement(query);
1100
0
      NS_ENSURE_STATE(stmt);
1101
0
      mozStorageStatementScoper scoper(stmt);
1102
0
      nsresult rv = stmt->Execute();
1103
0
      NS_ENSURE_SUCCESS(rv, rv);
1104
0
    }
1105
0
1106
0
    nsresult rv = transaction.Commit();
1107
0
    NS_ENSURE_SUCCESS(rv, rv);
1108
0
1109
0
    // If we don't need to chunk the notifications, just notify using the
1110
0
    // original mPlaces array.
1111
0
    if (!shouldChunkNotifications) {
1112
0
      nsCOMPtr<nsIRunnable> event = new NotifyManyVisitsObservers(mPlaces);
1113
0
      rv = NS_DispatchToMainThread(event);
1114
0
      NS_ENSURE_SUCCESS(rv, rv);
1115
0
    }
1116
0
1117
0
    return NS_OK;
1118
0
  }
1119
private:
1120
  InsertVisitedURIs(
1121
    mozIStorageConnection* aConnection,
1122
    nsTArray<VisitData>& aPlaces,
1123
    const nsMainThreadPtrHandle<mozIVisitInfoCallback>& aCallback,
1124
    bool aGroupNotifications,
1125
    bool aIgnoreErrors,
1126
    bool aIgnoreResults,
1127
    uint32_t aInitialUpdatedCount)
1128
    : Runnable("places::InsertVisitedURIs")
1129
    , mDBConn(aConnection)
1130
    , mCallback(aCallback)
1131
    , mGroupNotifications(aGroupNotifications)
1132
    , mIgnoreErrors(aIgnoreErrors)
1133
    , mIgnoreResults(aIgnoreResults)
1134
    , mSuccessfulUpdatedCount(aInitialUpdatedCount)
1135
    , mHistory(History::GetService())
1136
0
  {
1137
0
    MOZ_ASSERT(NS_IsMainThread(), "This should be called on the main thread");
1138
0
1139
0
    mPlaces.SwapElements(aPlaces);
1140
0
1141
#ifdef DEBUG
1142
    for (nsTArray<VisitData>::size_type i = 0; i < mPlaces.Length(); i++) {
1143
      nsCOMPtr<nsIURI> uri;
1144
      MOZ_ASSERT(NS_SUCCEEDED(NS_NewURI(getter_AddRefs(uri), mPlaces[i].spec)));
1145
      MOZ_ASSERT(CanAddURI(uri),
1146
                 "Passed a VisitData with a URI we cannot add to history!");
1147
    }
1148
#endif
1149
  }
1150
1151
  /**
1152
   * Inserts or updates the entry in moz_places for this visit, adds the visit,
1153
   * and updates the frecency of the place.
1154
   *
1155
   * @param aKnown
1156
   *        True if we already have an entry for this place in moz_places, false
1157
   *        otherwise.
1158
   * @param aPlace
1159
   *        The place we are adding a visit for.
1160
   */
1161
  nsresult DoDatabaseInserts(bool aKnown,
1162
                             VisitData& aPlace)
1163
0
  {
1164
0
    MOZ_ASSERT(!NS_IsMainThread(), "This should not be called on the main thread");
1165
0
1166
0
    // If the page was in moz_places, we need to update the entry.
1167
0
    nsresult rv;
1168
0
    if (aKnown) {
1169
0
      rv = mHistory->UpdatePlace(aPlace);
1170
0
      NS_ENSURE_SUCCESS(rv, rv);
1171
0
    }
1172
0
    // Otherwise, the page was not in moz_places, so now we have to add it.
1173
0
    else {
1174
0
      rv = mHistory->InsertPlace(aPlace, !mGroupNotifications);
1175
0
      NS_ENSURE_SUCCESS(rv, rv);
1176
0
      aPlace.placeId = nsNavHistory::sLastInsertedPlaceId;
1177
0
    }
1178
0
    MOZ_ASSERT(aPlace.placeId > 0);
1179
0
1180
0
    rv = AddVisit(aPlace);
1181
0
    NS_ENSURE_SUCCESS(rv, rv);
1182
0
1183
0
    // TODO (bug 623969) we shouldn't update this after each visit, but
1184
0
    // rather only for each unique place to save disk I/O.
1185
0
1186
0
    // Don't update frecency if the page should not appear in autocomplete.
1187
0
    if (aPlace.shouldUpdateFrecency) {
1188
0
      rv = UpdateFrecency(aPlace);
1189
0
      NS_ENSURE_SUCCESS(rv, rv);
1190
0
    }
1191
0
1192
0
    return NS_OK;
1193
0
  }
1194
1195
  /**
1196
   * Fetches information about a referrer for aPlace if it was a recent
1197
   * visit or not.
1198
   *
1199
   * @param aPlace
1200
   *        The VisitData for the visit we will eventually add.
1201
   *
1202
   */
1203
  void FetchReferrerInfo(VisitData& aPlace)
1204
0
  {
1205
0
    if (aPlace.referrerSpec.IsEmpty()) {
1206
0
      return;
1207
0
    }
1208
0
1209
0
    VisitData referrer;
1210
0
    referrer.spec = aPlace.referrerSpec;
1211
0
    // If the referrer is the same as the page, we don't need to fetch it.
1212
0
    if (aPlace.referrerSpec.Equals(aPlace.spec)) {
1213
0
      referrer = aPlace;
1214
0
      // The page last visit id is also the referrer visit id.
1215
0
      aPlace.referrerVisitId = aPlace.lastVisitId;
1216
0
    } else {
1217
0
      bool exists = false;
1218
0
      if (NS_SUCCEEDED(mHistory->FetchPageInfo(referrer, &exists)) && exists) {
1219
0
        // Copy the referrer last visit id.
1220
0
        aPlace.referrerVisitId = referrer.lastVisitId;
1221
0
      }
1222
0
    }
1223
0
1224
0
    // Check if the page has effectively been visited recently, otherwise
1225
0
    // discard the referrer info.
1226
0
    if (!aPlace.referrerVisitId || !referrer.lastVisitTime ||
1227
0
        aPlace.visitTime - referrer.lastVisitTime > RECENT_EVENT_THRESHOLD) {
1228
0
      // We will not be using the referrer data.
1229
0
      aPlace.referrerSpec.Truncate();
1230
0
      aPlace.referrerVisitId = 0;
1231
0
    }
1232
0
  }
1233
1234
  /**
1235
   * Adds a visit for _place and updates it with the right visit id.
1236
   *
1237
   * @param _place
1238
   *        The VisitData for the place we need to know visit information about.
1239
   */
1240
  nsresult AddVisit(VisitData& _place)
1241
0
  {
1242
0
    MOZ_ASSERT(_place.placeId > 0);
1243
0
1244
0
    nsresult rv;
1245
0
    nsCOMPtr<mozIStorageStatement> stmt;
1246
0
    stmt = mHistory->GetStatement(
1247
0
      "INSERT INTO moz_historyvisits "
1248
0
        "(from_visit, place_id, visit_date, visit_type, session) "
1249
0
      "VALUES (:from_visit, :page_id, :visit_date, :visit_type, 0) "
1250
0
    );
1251
0
    NS_ENSURE_STATE(stmt);
1252
0
    mozStorageStatementScoper scoper(stmt);
1253
0
1254
0
    rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("page_id"), _place.placeId);
1255
0
    NS_ENSURE_SUCCESS(rv, rv);
1256
0
    rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("from_visit"),
1257
0
                               _place.referrerVisitId);
1258
0
    NS_ENSURE_SUCCESS(rv, rv);
1259
0
    rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("visit_date"),
1260
0
                               _place.visitTime);
1261
0
    NS_ENSURE_SUCCESS(rv, rv);
1262
0
    uint32_t transitionType = _place.transitionType;
1263
0
    MOZ_ASSERT(transitionType >= nsINavHistoryService::TRANSITION_LINK &&
1264
0
               transitionType <= nsINavHistoryService::TRANSITION_RELOAD,
1265
0
                 "Invalid transition type!");
1266
0
    rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("visit_type"),
1267
0
                               transitionType);
1268
0
    NS_ENSURE_SUCCESS(rv, rv);
1269
0
1270
0
    rv = stmt->Execute();
1271
0
    NS_ENSURE_SUCCESS(rv, rv);
1272
0
1273
0
    _place.visitId = nsNavHistory::sLastInsertedVisitId;
1274
0
    MOZ_ASSERT(_place.visitId > 0);
1275
0
1276
0
    return NS_OK;
1277
0
  }
1278
1279
  /**
1280
   * Updates the frecency, and possibly the hidden-ness of aPlace.
1281
   *
1282
   * @param aPlace
1283
   *        The VisitData for the place we want to update.
1284
   */
1285
  nsresult UpdateFrecency(const VisitData& aPlace)
1286
0
  {
1287
0
    MOZ_ASSERT(aPlace.shouldUpdateFrecency);
1288
0
    MOZ_ASSERT(aPlace.placeId > 0);
1289
0
1290
0
    nsresult rv;
1291
0
    { // First, set our frecency to the proper value.
1292
0
      nsCOMPtr<mozIStorageStatement> stmt;
1293
0
      if (!mGroupNotifications) {
1294
0
        // If we're notifying for individual frecency updates, use
1295
0
        // the notify_frecency sql function which will call us back.
1296
0
        stmt = mHistory->GetStatement(
1297
0
          "UPDATE moz_places "
1298
0
          "SET frecency = NOTIFY_FRECENCY("
1299
0
            "CALCULATE_FRECENCY(:page_id, :redirect), "
1300
0
            "url, guid, hidden, last_visit_date"
1301
0
          ") "
1302
0
          "WHERE id = :page_id"
1303
0
        );
1304
0
      } else {
1305
0
        // otherwise, just update the frecency without notifying.
1306
0
        stmt = mHistory->GetStatement(
1307
0
          "UPDATE moz_places "
1308
0
          "SET frecency = CALCULATE_FRECENCY(:page_id, :redirect) "
1309
0
          "WHERE id = :page_id"
1310
0
        );
1311
0
      }
1312
0
      NS_ENSURE_STATE(stmt);
1313
0
      mozStorageStatementScoper scoper(stmt);
1314
0
1315
0
      rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("page_id"), aPlace.placeId);
1316
0
      NS_ENSURE_SUCCESS(rv, rv);
1317
0
      rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("redirect"), aPlace.useFrecencyRedirectBonus);
1318
0
      NS_ENSURE_SUCCESS(rv, rv);
1319
0
1320
0
      rv = stmt->Execute();
1321
0
      NS_ENSURE_SUCCESS(rv, rv);
1322
0
    }
1323
0
1324
0
    if (!aPlace.hidden && aPlace.shouldUpdateHidden) {
1325
0
      // Mark the page as not hidden if the frecency is now nonzero.
1326
0
      nsCOMPtr<mozIStorageStatement> stmt;
1327
0
      stmt = mHistory->GetStatement(
1328
0
        "UPDATE moz_places "
1329
0
        "SET hidden = 0 "
1330
0
        "WHERE id = :page_id AND frecency <> 0"
1331
0
      );
1332
0
      NS_ENSURE_STATE(stmt);
1333
0
      mozStorageStatementScoper scoper(stmt);
1334
0
1335
0
      rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("page_id"), aPlace.placeId);
1336
0
      NS_ENSURE_SUCCESS(rv, rv);
1337
0
1338
0
      rv = stmt->Execute();
1339
0
      NS_ENSURE_SUCCESS(rv, rv);
1340
0
    }
1341
0
1342
0
    return NS_OK;
1343
0
  }
1344
1345
  mozIStorageConnection* mDBConn;
1346
1347
  nsTArray<VisitData> mPlaces;
1348
1349
  nsMainThreadPtrHandle<mozIVisitInfoCallback> mCallback;
1350
1351
  bool mGroupNotifications;
1352
1353
  bool mIgnoreErrors;
1354
1355
  bool mIgnoreResults;
1356
1357
  uint32_t mSuccessfulUpdatedCount;
1358
1359
  /**
1360
   * Strong reference to the History object because we do not want it to
1361
   * disappear out from under us.
1362
   */
1363
  RefPtr<History> mHistory;
1364
};
1365
1366
/**
1367
 * Sets the page title for a page in moz_places (if necessary).
1368
 */
1369
class SetPageTitle : public Runnable
1370
{
1371
public:
1372
  /**
1373
   * Sets a pages title in the database asynchronously.
1374
   *
1375
   * @param aConnection
1376
   *        The database connection to use for this operation.
1377
   * @param aURI
1378
   *        The URI to set the page title on.
1379
   * @param aTitle
1380
   *        The title to set for the page, if the page exists.
1381
   */
1382
  static nsresult Start(mozIStorageConnection* aConnection,
1383
                        nsIURI* aURI,
1384
                        const nsAString& aTitle)
1385
0
  {
1386
0
    MOZ_ASSERT(NS_IsMainThread(), "This should be called on the main thread");
1387
0
    MOZ_ASSERT(aURI, "Must pass a non-null URI object!");
1388
0
1389
0
    nsCString spec;
1390
0
    nsresult rv = aURI->GetSpec(spec);
1391
0
    NS_ENSURE_SUCCESS(rv, rv);
1392
0
1393
0
    RefPtr<SetPageTitle> event = new SetPageTitle(spec, aTitle);
1394
0
1395
0
    // Get the target thread, and then start the work!
1396
0
    nsCOMPtr<nsIEventTarget> target = do_GetInterface(aConnection);
1397
0
    NS_ENSURE_TRUE(target, NS_ERROR_UNEXPECTED);
1398
0
    rv = target->Dispatch(event, NS_DISPATCH_NORMAL);
1399
0
    NS_ENSURE_SUCCESS(rv, rv);
1400
0
1401
0
    return NS_OK;
1402
0
  }
1403
1404
  NS_IMETHOD Run() override
1405
0
  {
1406
0
    MOZ_ASSERT(!NS_IsMainThread(), "This should not be called on the main thread");
1407
0
1408
0
    // First, see if the page exists in the database (we'll need its id later).
1409
0
    bool exists;
1410
0
    nsresult rv = mHistory->FetchPageInfo(mPlace, &exists);
1411
0
    NS_ENSURE_SUCCESS(rv, rv);
1412
0
1413
0
    if (!exists || !mPlace.titleChanged) {
1414
0
      // We have no record of this page, or we have no title change, so there
1415
0
      // is no need to do any further work.
1416
0
      return NS_OK;
1417
0
    }
1418
0
1419
0
    MOZ_ASSERT(mPlace.placeId > 0,
1420
0
               "We somehow have an invalid place id here!");
1421
0
1422
0
    // Now we can update our database record.
1423
0
    nsCOMPtr<mozIStorageStatement> stmt =
1424
0
      mHistory->GetStatement(
1425
0
        "UPDATE moz_places "
1426
0
        "SET title = :page_title "
1427
0
        "WHERE id = :page_id "
1428
0
      );
1429
0
    NS_ENSURE_STATE(stmt);
1430
0
1431
0
    {
1432
0
      mozStorageStatementScoper scoper(stmt);
1433
0
      rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("page_id"), mPlace.placeId);
1434
0
      NS_ENSURE_SUCCESS(rv, rv);
1435
0
      // Empty strings should clear the title, just like
1436
0
      // nsNavHistory::SetPageTitle.
1437
0
      if (mPlace.title.IsEmpty()) {
1438
0
        rv = stmt->BindNullByName(NS_LITERAL_CSTRING("page_title"));
1439
0
      }
1440
0
      else {
1441
0
        rv = stmt->BindStringByName(NS_LITERAL_CSTRING("page_title"),
1442
0
                                    StringHead(mPlace.title, TITLE_LENGTH_MAX));
1443
0
      }
1444
0
      NS_ENSURE_SUCCESS(rv, rv);
1445
0
      rv = stmt->Execute();
1446
0
      NS_ENSURE_SUCCESS(rv, rv);
1447
0
    }
1448
0
1449
0
    nsCOMPtr<nsIRunnable> event =
1450
0
      new NotifyTitleObservers(mPlace.spec, mPlace.title, mPlace.guid);
1451
0
    rv = NS_DispatchToMainThread(event);
1452
0
    NS_ENSURE_SUCCESS(rv, rv);
1453
0
1454
0
    return NS_OK;
1455
0
  }
1456
1457
private:
1458
  SetPageTitle(const nsCString& aSpec, const nsAString& aTitle)
1459
    : Runnable("places::SetPageTitle")
1460
    , mHistory(History::GetService())
1461
0
  {
1462
0
    mPlace.spec = aSpec;
1463
0
    mPlace.title = aTitle;
1464
0
  }
1465
1466
  VisitData mPlace;
1467
1468
  /**
1469
   * Strong reference to the History object because we do not want it to
1470
   * disappear out from under us.
1471
   */
1472
  RefPtr<History> mHistory;
1473
};
1474
1475
/**
1476
 * Stores an embed visit, and notifies observers.
1477
 *
1478
 * @param aPlace
1479
 *        The VisitData of the visit to store as an embed visit.
1480
 * @param [optional] aCallback
1481
 *        The mozIVisitInfoCallback to notify, if provided.
1482
 */
1483
void
1484
StoreAndNotifyEmbedVisit(VisitData& aPlace,
1485
                         mozIVisitInfoCallback* aCallback = nullptr)
1486
0
{
1487
0
  MOZ_ASSERT(aPlace.transitionType == nsINavHistoryService::TRANSITION_EMBED,
1488
0
             "Must only pass TRANSITION_EMBED visits to this!");
1489
0
  MOZ_ASSERT(NS_IsMainThread(), "Must be called on the main thread!");
1490
0
1491
0
  nsCOMPtr<nsIURI> uri;
1492
0
  MOZ_ALWAYS_SUCCEEDS(NS_NewURI(getter_AddRefs(uri), aPlace.spec));
1493
0
1494
0
  nsNavHistory* navHistory = nsNavHistory::GetHistoryService();
1495
0
  if (!navHistory || !uri) {
1496
0
    return;
1497
0
  }
1498
0
1499
0
  navHistory->registerEmbedVisit(uri, aPlace.visitTime);
1500
0
1501
0
  if (!!aCallback) {
1502
0
    nsMainThreadPtrHandle<mozIVisitInfoCallback>
1503
0
      callback(new nsMainThreadPtrHolder<mozIVisitInfoCallback>(
1504
0
        "mozIVisitInfoCallback", aCallback));
1505
0
    bool ignoreResults = false;
1506
0
    Unused << aCallback->GetIgnoreResults(&ignoreResults);
1507
0
    if (!ignoreResults) {
1508
0
      nsCOMPtr<nsIRunnable> event =
1509
0
        new NotifyPlaceInfoCallback(callback, aPlace, true, NS_OK);
1510
0
      (void)NS_DispatchToMainThread(event);
1511
0
    }
1512
0
  }
1513
0
1514
0
  nsCOMPtr<nsIRunnable> event = new NotifyManyVisitsObservers(aPlace);
1515
0
  (void)NS_DispatchToMainThread(event);
1516
0
}
1517
1518
} // namespace
1519
1520
////////////////////////////////////////////////////////////////////////////////
1521
//// History
1522
1523
History* History::gService = nullptr;
1524
1525
History::History()
1526
  : mShuttingDown(false)
1527
  , mShutdownMutex("History::mShutdownMutex")
1528
  , mObservers(VISIT_OBSERVERS_INITIAL_CACHE_LENGTH)
1529
  , mRecentlyVisitedURIs(RECENTLY_VISITED_URIS_SIZE)
1530
0
{
1531
0
  NS_ASSERTION(!gService, "Ruh-roh!  This service has already been created!");
1532
0
  gService = this;
1533
0
1534
0
  nsCOMPtr<nsIObserverService> os = services::GetObserverService();
1535
0
  NS_WARNING_ASSERTION(os, "Observer service was not found!");
1536
0
  if (os) {
1537
0
    (void)os->AddObserver(this, TOPIC_PLACES_SHUTDOWN, false);
1538
0
  }
1539
0
}
1540
1541
History::~History()
1542
0
{
1543
0
  UnregisterWeakMemoryReporter(this);
1544
0
1545
0
  MOZ_ASSERT(gService == this);
1546
0
  gService = nullptr;
1547
0
}
1548
1549
void
1550
History::InitMemoryReporter()
1551
0
{
1552
0
  RegisterWeakMemoryReporter(this);
1553
0
}
1554
1555
// Helper function which performs the checking required to fetch the document
1556
// object for the given link. May return null if the link does not have an owner
1557
// document.
1558
static nsIDocument*
1559
GetLinkDocument(Link* aLink)
1560
0
{
1561
0
  // NOTE: Theoretically GetElement should never return nullptr, but it does
1562
0
  // in GTests because they use a mock_Link which returns null from this
1563
0
  // method.
1564
0
  Element* element = aLink->GetElement();
1565
0
  return element ? element->OwnerDoc() : nullptr;
1566
0
}
1567
1568
void
1569
History::NotifyVisitedParent(const nsTArray<URIParams>& aURIs)
1570
0
{
1571
0
  MOZ_ASSERT(XRE_IsParentProcess());
1572
0
  nsTArray<ContentParent*> cplist;
1573
0
  ContentParent::GetAll(cplist);
1574
0
1575
0
  if (!cplist.IsEmpty()) {
1576
0
    for (uint32_t i = 0; i < cplist.Length(); ++i) {
1577
0
      Unused << cplist[i]->SendNotifyVisited(aURIs);
1578
0
    }
1579
0
  }
1580
0
}
1581
1582
NS_IMETHODIMP
1583
History::NotifyVisited(nsIURI* aURI)
1584
0
{
1585
0
  MOZ_ASSERT(NS_IsMainThread());
1586
0
  NS_ENSURE_ARG(aURI);
1587
0
  // NOTE: This can be run within the SystemGroup, and thus cannot directly
1588
0
  // interact with webpages.
1589
0
1590
0
  nsAutoScriptBlocker scriptBlocker;
1591
0
1592
0
  // If we have no observers for this URI, we have nothing to notify about.
1593
0
  KeyClass* key = mObservers.GetEntry(aURI);
1594
0
  if (!key) {
1595
0
    return NS_OK;
1596
0
  }
1597
0
  key->mVisited = true;
1598
0
1599
0
  // If we have a key, it should have at least one observer.
1600
0
  MOZ_ASSERT(!key->array.IsEmpty());
1601
0
1602
0
  // Dispatch an event to each document which has a Link observing this URL.
1603
0
  // These will fire asynchronously in the correct DocGroup.
1604
0
  {
1605
0
    nsTArray<nsIDocument*> seen; // Don't dispatch duplicate runnables.
1606
0
    ObserverArray::BackwardIterator iter(key->array);
1607
0
    while (iter.HasMore()) {
1608
0
      Link* link = iter.GetNext();
1609
0
      nsIDocument* doc = GetLinkDocument(link);
1610
0
      if (seen.Contains(doc)) {
1611
0
        continue;
1612
0
      }
1613
0
      seen.AppendElement(doc);
1614
0
      DispatchNotifyVisited(aURI, doc);
1615
0
    }
1616
0
  }
1617
0
1618
0
  return NS_OK;
1619
0
}
1620
1621
void
1622
History::NotifyVisitedForDocument(nsIURI* aURI, nsIDocument* aDocument)
1623
0
{
1624
0
  MOZ_ASSERT(NS_IsMainThread());
1625
0
  // Make sure that nothing invalidates our observer array while we're walking
1626
0
  // over it.
1627
0
  nsAutoScriptBlocker scriptBlocker;
1628
0
1629
0
  // If we have no observers for this URI, we have nothing to notify about.
1630
0
  KeyClass* key = mObservers.GetEntry(aURI);
1631
0
  if (!key) {
1632
0
    return;
1633
0
  }
1634
0
1635
0
  {
1636
0
    // Update status of each Link node. We iterate over the array backwards so
1637
0
    // we can remove the items as we encounter them.
1638
0
    ObserverArray::BackwardIterator iter(key->array);
1639
0
    while (iter.HasMore()) {
1640
0
      Link* link = iter.GetNext();
1641
0
      nsIDocument* doc = GetLinkDocument(link);
1642
0
      if (doc == aDocument) {
1643
0
        link->SetLinkState(eLinkState_Visited);
1644
0
        iter.Remove();
1645
0
      }
1646
0
1647
0
      // Verify that the observers hash doesn't mutate while looping through
1648
0
      // the links associated with this URI.
1649
0
      MOZ_ASSERT(key == mObservers.GetEntry(aURI),
1650
0
                 "The URIs hash mutated!");
1651
0
    }
1652
0
  }
1653
0
1654
0
  // If we don't have any links left, we can remove the array.
1655
0
  if (key->array.IsEmpty()) {
1656
0
    mObservers.RemoveEntry(key);
1657
0
  }
1658
0
}
1659
1660
void
1661
History::DispatchNotifyVisited(nsIURI* aURI, nsIDocument* aDocument)
1662
0
{
1663
0
  // Capture strong references to the arguments to capture in the closure.
1664
0
  nsCOMPtr<nsIDocument> doc = aDocument;
1665
0
  nsCOMPtr<nsIURI> uri = aURI;
1666
0
1667
0
  // Create and dispatch the runnable to call NotifyVisitedForDocument.
1668
0
  nsCOMPtr<nsIRunnable> runnable = NS_NewRunnableFunction("History::DispatchNotifyVisited", [uri, doc] {
1669
0
    nsCOMPtr<IHistory> history = services::GetHistoryService();
1670
0
    static_cast<History*>(history.get())->NotifyVisitedForDocument(uri, doc);
1671
0
  });
1672
0
1673
0
  if (doc) {
1674
0
    doc->Dispatch(TaskCategory::Other, runnable.forget());
1675
0
  } else {
1676
0
    NS_DispatchToMainThread(runnable.forget());
1677
0
  }
1678
0
}
1679
1680
class ConcurrentStatementsHolder final : public mozIStorageCompletionCallback {
1681
public:
1682
  NS_DECL_ISUPPORTS
1683
1684
  explicit ConcurrentStatementsHolder(mozIStorageConnection* aDBConn)
1685
  : mShutdownWasInvoked(false)
1686
0
  {
1687
0
    DebugOnly<nsresult> rv = aDBConn->AsyncClone(true, this);
1688
0
    MOZ_ASSERT(NS_SUCCEEDED(rv));
1689
0
  }
1690
1691
0
  NS_IMETHOD Complete(nsresult aStatus, nsISupports* aConnection) override {
1692
0
    if (NS_FAILED(aStatus)) {
1693
0
      return NS_OK;
1694
0
    }
1695
0
    mReadOnlyDBConn = do_QueryInterface(aConnection);
1696
0
    // It's possible Shutdown was invoked before we were handed back the
1697
0
    // cloned connection handle.
1698
0
    if (mShutdownWasInvoked) {
1699
0
      Shutdown();
1700
0
      return NS_OK;
1701
0
    }
1702
0
1703
0
    // Now we can create our cached statements.
1704
0
1705
0
    if (!mIsVisitedStatement) {
1706
0
      (void)mReadOnlyDBConn->CreateAsyncStatement(NS_LITERAL_CSTRING(
1707
0
        "SELECT 1 FROM moz_places h "
1708
0
        "WHERE url_hash = hash(?1) AND url = ?1 AND last_visit_date NOTNULL "
1709
0
      ),  getter_AddRefs(mIsVisitedStatement));
1710
0
      MOZ_ASSERT(mIsVisitedStatement);
1711
0
      nsresult result = mIsVisitedStatement ? NS_OK : NS_ERROR_NOT_AVAILABLE;
1712
0
      for (int32_t i = 0; i < mIsVisitedCallbacks.Count(); ++i) {
1713
0
        DebugOnly<nsresult> rv;
1714
0
        rv = mIsVisitedCallbacks[i]->Complete(result, mIsVisitedStatement);
1715
0
        MOZ_ASSERT(NS_SUCCEEDED(rv));
1716
0
      }
1717
0
      mIsVisitedCallbacks.Clear();
1718
0
    }
1719
0
1720
0
    return NS_OK;
1721
0
  }
1722
1723
  void GetIsVisitedStatement(mozIStorageCompletionCallback* aCallback)
1724
0
  {
1725
0
    if (mIsVisitedStatement) {
1726
0
      DebugOnly<nsresult> rv;
1727
0
      rv = aCallback->Complete(NS_OK, mIsVisitedStatement);
1728
0
      MOZ_ASSERT(NS_SUCCEEDED(rv));
1729
0
    } else {
1730
0
      DebugOnly<bool> added = mIsVisitedCallbacks.AppendObject(aCallback);
1731
0
      MOZ_ASSERT(added);
1732
0
    }
1733
0
  }
1734
1735
0
  void Shutdown() {
1736
0
    mShutdownWasInvoked = true;
1737
0
    if (mReadOnlyDBConn) {
1738
0
      mIsVisitedCallbacks.Clear();
1739
0
      DebugOnly<nsresult> rv;
1740
0
      if (mIsVisitedStatement) {
1741
0
        rv = mIsVisitedStatement->Finalize();
1742
0
        MOZ_ASSERT(NS_SUCCEEDED(rv));
1743
0
      }
1744
0
      rv = mReadOnlyDBConn->AsyncClose(nullptr);
1745
0
      MOZ_ASSERT(NS_SUCCEEDED(rv));
1746
0
      mReadOnlyDBConn = nullptr;
1747
0
    }
1748
0
  }
1749
1750
private:
1751
  ~ConcurrentStatementsHolder()
1752
0
  {
1753
0
  }
1754
1755
  nsCOMPtr<mozIStorageAsyncConnection> mReadOnlyDBConn;
1756
  nsCOMPtr<mozIStorageAsyncStatement> mIsVisitedStatement;
1757
  nsCOMArray<mozIStorageCompletionCallback> mIsVisitedCallbacks;
1758
  bool mShutdownWasInvoked;
1759
};
1760
1761
NS_IMPL_ISUPPORTS(
1762
  ConcurrentStatementsHolder
1763
, mozIStorageCompletionCallback
1764
)
1765
1766
nsresult
1767
History::GetIsVisitedStatement(mozIStorageCompletionCallback* aCallback)
1768
0
{
1769
0
  MOZ_ASSERT(NS_IsMainThread());
1770
0
  if (mShuttingDown)
1771
0
    return NS_ERROR_NOT_AVAILABLE;
1772
0
1773
0
  if (!mConcurrentStatementsHolder) {
1774
0
    mozIStorageConnection* dbConn = GetDBConn();
1775
0
    NS_ENSURE_STATE(dbConn);
1776
0
    mConcurrentStatementsHolder = new ConcurrentStatementsHolder(dbConn);
1777
0
  }
1778
0
  mConcurrentStatementsHolder->GetIsVisitedStatement(aCallback);
1779
0
  return NS_OK;
1780
0
}
1781
1782
nsresult
1783
History::InsertPlace(VisitData& aPlace, bool aShouldNotifyFrecencyChanged)
1784
0
{
1785
0
  MOZ_ASSERT(aPlace.placeId == 0, "should not have a valid place id!");
1786
0
  MOZ_ASSERT(!aPlace.shouldUpdateHidden, "We should not need to update hidden");
1787
0
  MOZ_ASSERT(!NS_IsMainThread(), "must be called off of the main thread!");
1788
0
1789
0
  nsCOMPtr<mozIStorageStatement> stmt = GetStatement(
1790
0
      "INSERT INTO moz_places "
1791
0
        "(url, url_hash, title, rev_host, hidden, typed, frecency, guid) "
1792
0
      "VALUES (:url, hash(:url), :title, :rev_host, :hidden, :typed, :frecency, :guid) "
1793
0
    );
1794
0
  NS_ENSURE_STATE(stmt);
1795
0
  mozStorageStatementScoper scoper(stmt);
1796
0
1797
0
  nsresult rv = stmt->BindStringByName(NS_LITERAL_CSTRING("rev_host"),
1798
0
                                       aPlace.revHost);
1799
0
  NS_ENSURE_SUCCESS(rv, rv);
1800
0
  rv = URIBinder::Bind(stmt, NS_LITERAL_CSTRING("url"), aPlace.spec);
1801
0
  NS_ENSURE_SUCCESS(rv, rv);
1802
0
  nsString title = aPlace.title;
1803
0
  // Empty strings should have no title, just like nsNavHistory::SetPageTitle.
1804
0
  if (title.IsEmpty()) {
1805
0
    rv = stmt->BindNullByName(NS_LITERAL_CSTRING("title"));
1806
0
  }
1807
0
  else {
1808
0
    title.Assign(StringHead(aPlace.title, TITLE_LENGTH_MAX));
1809
0
    rv = stmt->BindStringByName(NS_LITERAL_CSTRING("title"), title);
1810
0
  }
1811
0
  NS_ENSURE_SUCCESS(rv, rv);
1812
0
  rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("typed"), aPlace.typed);
1813
0
  NS_ENSURE_SUCCESS(rv, rv);
1814
0
  // When inserting a page for a first visit that should not appear in
1815
0
  // autocomplete, for example an error page, use a zero frecency.
1816
0
  int32_t frecency = aPlace.shouldUpdateFrecency ? aPlace.frecency : 0;
1817
0
  rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("frecency"), frecency);
1818
0
  NS_ENSURE_SUCCESS(rv, rv);
1819
0
  rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("hidden"), aPlace.hidden);
1820
0
  NS_ENSURE_SUCCESS(rv, rv);
1821
0
  if (aPlace.guid.IsVoid()) {
1822
0
    rv = GenerateGUID(aPlace.guid);
1823
0
    NS_ENSURE_SUCCESS(rv, rv);
1824
0
  }
1825
0
  rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("guid"), aPlace.guid);
1826
0
  NS_ENSURE_SUCCESS(rv, rv);
1827
0
  rv = stmt->Execute();
1828
0
  NS_ENSURE_SUCCESS(rv, rv);
1829
0
1830
0
  // Post an onFrecencyChanged observer notification.
1831
0
  if (aShouldNotifyFrecencyChanged) {
1832
0
    const nsNavHistory* navHistory = nsNavHistory::GetConstHistoryService();
1833
0
    NS_ENSURE_STATE(navHistory);
1834
0
    navHistory->DispatchFrecencyChangedNotification(aPlace.spec, frecency,
1835
0
                                                    aPlace.guid,
1836
0
                                                    aPlace.hidden,
1837
0
                                                    aPlace.visitTime);
1838
0
  }
1839
0
1840
0
  return NS_OK;
1841
0
}
1842
1843
nsresult
1844
History::UpdatePlace(const VisitData& aPlace)
1845
0
{
1846
0
  MOZ_ASSERT(!NS_IsMainThread(), "must be called off of the main thread!");
1847
0
  MOZ_ASSERT(aPlace.placeId > 0, "must have a valid place id!");
1848
0
  MOZ_ASSERT(!aPlace.guid.IsVoid(), "must have a guid!");
1849
0
1850
0
  nsCOMPtr<mozIStorageStatement> stmt;
1851
0
  bool titleIsVoid = aPlace.title.IsVoid();
1852
0
  if (titleIsVoid) {
1853
0
    // Don't change the title.
1854
0
    stmt = GetStatement(
1855
0
      "UPDATE moz_places "
1856
0
      "SET hidden = :hidden, "
1857
0
          "typed = :typed, "
1858
0
          "guid = :guid "
1859
0
      "WHERE id = :page_id "
1860
0
    );
1861
0
  } else {
1862
0
    stmt = GetStatement(
1863
0
      "UPDATE moz_places "
1864
0
      "SET title = :title, "
1865
0
          "hidden = :hidden, "
1866
0
          "typed = :typed, "
1867
0
          "guid = :guid "
1868
0
      "WHERE id = :page_id "
1869
0
    );
1870
0
  }
1871
0
  NS_ENSURE_STATE(stmt);
1872
0
  mozStorageStatementScoper scoper(stmt);
1873
0
1874
0
  nsresult rv;
1875
0
  if (!titleIsVoid) {
1876
0
    // An empty string clears the title.
1877
0
    if (aPlace.title.IsEmpty()) {
1878
0
      rv = stmt->BindNullByName(NS_LITERAL_CSTRING("title"));
1879
0
    } else {
1880
0
      rv = stmt->BindStringByName(NS_LITERAL_CSTRING("title"),
1881
0
                                  StringHead(aPlace.title, TITLE_LENGTH_MAX));
1882
0
    }
1883
0
    NS_ENSURE_SUCCESS(rv, rv);
1884
0
  }
1885
0
  rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("typed"), aPlace.typed);
1886
0
  NS_ENSURE_SUCCESS(rv, rv);
1887
0
  rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("hidden"), aPlace.hidden);
1888
0
  NS_ENSURE_SUCCESS(rv, rv);
1889
0
  rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("guid"), aPlace.guid);
1890
0
  NS_ENSURE_SUCCESS(rv, rv);
1891
0
  rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("page_id"),
1892
0
                             aPlace.placeId);
1893
0
  NS_ENSURE_SUCCESS(rv, rv);
1894
0
  rv = stmt->Execute();
1895
0
  NS_ENSURE_SUCCESS(rv, rv);
1896
0
1897
0
  return NS_OK;
1898
0
}
1899
1900
nsresult
1901
History::FetchPageInfo(VisitData& _place, bool* _exists)
1902
0
{
1903
0
  MOZ_ASSERT(!_place.spec.IsEmpty() || !_place.guid.IsEmpty(), "must have either a non-empty spec or guid!");
1904
0
  MOZ_ASSERT(!NS_IsMainThread(), "must be called off of the main thread!");
1905
0
1906
0
  nsresult rv;
1907
0
1908
0
  // URI takes precedence.
1909
0
  nsCOMPtr<mozIStorageStatement> stmt;
1910
0
  bool selectByURI = !_place.spec.IsEmpty();
1911
0
  if (selectByURI) {
1912
0
    stmt = GetStatement(
1913
0
      "SELECT guid, id, title, hidden, typed, frecency, visit_count, last_visit_date, "
1914
0
      "(SELECT id FROM moz_historyvisits "
1915
0
       "WHERE place_id = h.id AND visit_date = h.last_visit_date) AS last_visit_id "
1916
0
      "FROM moz_places h "
1917
0
      "WHERE url_hash = hash(:page_url) AND url = :page_url "
1918
0
    );
1919
0
    NS_ENSURE_STATE(stmt);
1920
0
1921
0
    rv = URIBinder::Bind(stmt, NS_LITERAL_CSTRING("page_url"), _place.spec);
1922
0
    NS_ENSURE_SUCCESS(rv, rv);
1923
0
  }
1924
0
  else {
1925
0
    stmt = GetStatement(
1926
0
      "SELECT url, id, title, hidden, typed, frecency, visit_count, last_visit_date, "
1927
0
      "(SELECT id FROM moz_historyvisits "
1928
0
       "WHERE place_id = h.id AND visit_date = h.last_visit_date) AS last_visit_id "
1929
0
      "FROM moz_places h "
1930
0
      "WHERE guid = :guid "
1931
0
    );
1932
0
    NS_ENSURE_STATE(stmt);
1933
0
1934
0
    rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("guid"), _place.guid);
1935
0
    NS_ENSURE_SUCCESS(rv, rv);
1936
0
  }
1937
0
1938
0
  mozStorageStatementScoper scoper(stmt);
1939
0
1940
0
  rv = stmt->ExecuteStep(_exists);
1941
0
  NS_ENSURE_SUCCESS(rv, rv);
1942
0
1943
0
  if (!*_exists) {
1944
0
    return NS_OK;
1945
0
  }
1946
0
1947
0
  if (selectByURI) {
1948
0
    if (_place.guid.IsEmpty()) {
1949
0
      rv = stmt->GetUTF8String(0, _place.guid);
1950
0
      NS_ENSURE_SUCCESS(rv, rv);
1951
0
    }
1952
0
  }
1953
0
  else {
1954
0
    nsAutoCString spec;
1955
0
    rv = stmt->GetUTF8String(0, spec);
1956
0
    NS_ENSURE_SUCCESS(rv, rv);
1957
0
    _place.spec = spec;
1958
0
  }
1959
0
1960
0
  rv = stmt->GetInt64(1, &_place.placeId);
1961
0
  NS_ENSURE_SUCCESS(rv, rv);
1962
0
1963
0
  nsAutoString title;
1964
0
  rv = stmt->GetString(2, title);
1965
0
  NS_ENSURE_SUCCESS(rv, rv);
1966
0
1967
0
  // If the title we were given was void, that means we did not bother to set
1968
0
  // it to anything.  As a result, ignore the fact that we may have changed the
1969
0
  // title (because we don't want to, that would be empty), and set the title
1970
0
  // to what is currently stored in the datbase.
1971
0
  if (_place.title.IsVoid()) {
1972
0
    _place.title = title;
1973
0
  }
1974
0
  // Otherwise, just indicate if the title has changed.
1975
0
  else {
1976
0
    _place.titleChanged = !(_place.title.Equals(title)) &&
1977
0
                          !(_place.title.IsEmpty() && title.IsVoid());
1978
0
  }
1979
0
1980
0
  int32_t hidden;
1981
0
  rv = stmt->GetInt32(3, &hidden);
1982
0
  NS_ENSURE_SUCCESS(rv, rv);
1983
0
  _place.hidden = !!hidden;
1984
0
1985
0
  int32_t typed;
1986
0
  rv = stmt->GetInt32(4, &typed);
1987
0
  NS_ENSURE_SUCCESS(rv, rv);
1988
0
  _place.typed = !!typed;
1989
0
1990
0
  rv = stmt->GetInt32(5, &_place.frecency);
1991
0
  NS_ENSURE_SUCCESS(rv, rv);
1992
0
  int32_t visitCount;
1993
0
  rv = stmt->GetInt32(6, &visitCount);
1994
0
  NS_ENSURE_SUCCESS(rv, rv);
1995
0
  _place.visitCount = visitCount;
1996
0
  rv = stmt->GetInt64(7, &_place.lastVisitTime);
1997
0
  NS_ENSURE_SUCCESS(rv, rv);
1998
0
  rv = stmt->GetInt64(8, &_place.lastVisitId);
1999
0
  NS_ENSURE_SUCCESS(rv, rv);
2000
0
2001
0
  return NS_OK;
2002
0
}
2003
2004
MOZ_DEFINE_MALLOC_SIZE_OF(HistoryMallocSizeOf)
2005
2006
NS_IMETHODIMP
2007
History::CollectReports(nsIHandleReportCallback* aHandleReport,
2008
                        nsISupports* aData, bool aAnonymize)
2009
0
{
2010
0
  MOZ_COLLECT_REPORT(
2011
0
    "explicit/history-links-hashtable", KIND_HEAP, UNITS_BYTES,
2012
0
    SizeOfIncludingThis(HistoryMallocSizeOf),
2013
0
    "Memory used by the hashtable that records changes to the visited state "
2014
0
    "of links.");
2015
0
2016
0
  return NS_OK;
2017
0
}
2018
2019
size_t
2020
History::SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOfThis)
2021
0
{
2022
0
  return aMallocSizeOfThis(this) +
2023
0
         mObservers.SizeOfExcludingThis(aMallocSizeOfThis);
2024
0
}
2025
2026
/* static */
2027
History*
2028
History::GetService()
2029
0
{
2030
0
  if (gService) {
2031
0
    return gService;
2032
0
  }
2033
0
2034
0
  nsCOMPtr<IHistory> service(do_GetService(NS_IHISTORY_CONTRACTID));
2035
0
  if (service) {
2036
0
    NS_ASSERTION(gService, "Our constructor was not run?!");
2037
0
  }
2038
0
2039
0
  return gService;
2040
0
}
2041
2042
/* static */
2043
already_AddRefed<History>
2044
History::GetSingleton()
2045
0
{
2046
0
  if (!gService) {
2047
0
    RefPtr<History> svc = new History();
2048
0
    MOZ_ASSERT(gService == svc.get());
2049
0
    svc->InitMemoryReporter();
2050
0
    return svc.forget();
2051
0
  }
2052
0
2053
0
  return do_AddRef(gService);
2054
0
}
2055
2056
mozIStorageConnection*
2057
History::GetDBConn()
2058
0
{
2059
0
  MOZ_ASSERT(NS_IsMainThread());
2060
0
  if (mShuttingDown)
2061
0
    return nullptr;
2062
0
  if (!mDB) {
2063
0
    mDB = Database::GetDatabase();
2064
0
    NS_ENSURE_TRUE(mDB, nullptr);
2065
0
    // This must happen on the main-thread, so when we try to use the connection
2066
0
    // later it's initialized.
2067
0
    mDB->EnsureConnection();
2068
0
    NS_ENSURE_TRUE(mDB, nullptr);
2069
0
  }
2070
0
  return mDB->MainConn();
2071
0
}
2072
2073
const mozIStorageConnection*
2074
History::GetConstDBConn()
2075
0
{
2076
0
  MOZ_ASSERT(mDB || mShuttingDown);
2077
0
  if (mShuttingDown || !mDB) {
2078
0
    return nullptr;
2079
0
  }
2080
0
  return mDB->MainConn();
2081
0
}
2082
2083
void
2084
History::Shutdown()
2085
0
{
2086
0
  MOZ_ASSERT(NS_IsMainThread());
2087
0
2088
0
  // Prevent other threads from scheduling uses of the DB while we mark
2089
0
  // ourselves as shutting down.
2090
0
  MutexAutoLock lockedScope(mShutdownMutex);
2091
0
  MOZ_ASSERT(!mShuttingDown && "Shutdown was called more than once!");
2092
0
2093
0
  mShuttingDown = true;
2094
0
2095
0
  if (mConcurrentStatementsHolder) {
2096
0
    mConcurrentStatementsHolder->Shutdown();
2097
0
  }
2098
0
}
2099
2100
void
2101
0
History::AppendToRecentlyVisitedURIs(nsIURI* aURI) {
2102
0
  // Add a new entry, if necessary.
2103
0
  RecentURIKey* entry = mRecentlyVisitedURIs.GetEntry(aURI);
2104
0
  if (!entry) {
2105
0
    entry = mRecentlyVisitedURIs.PutEntry(aURI);
2106
0
  }
2107
0
  if (entry) {
2108
0
    entry->time = PR_Now();
2109
0
  }
2110
0
2111
0
  // Remove entries older than RECENTLY_VISITED_URIS_MAX_AGE.
2112
0
  for (auto iter = mRecentlyVisitedURIs.Iter(); !iter.Done(); iter.Next()) {
2113
0
    RecentURIKey* entry = iter.Get();
2114
0
    if ((PR_Now() - entry->time) > RECENTLY_VISITED_URIS_MAX_AGE) {
2115
0
      iter.Remove();
2116
0
    }
2117
0
  }
2118
0
}
2119
2120
inline bool
2121
0
History::IsRecentlyVisitedURI(nsIURI* aURI) {
2122
0
  RecentURIKey* entry = mRecentlyVisitedURIs.GetEntry(aURI);
2123
0
  // Check if the entry exists and is younger than RECENTLY_VISITED_URIS_MAX_AGE.
2124
0
  return entry && (PR_Now() - entry->time) < RECENTLY_VISITED_URIS_MAX_AGE;
2125
0
}
2126
2127
////////////////////////////////////////////////////////////////////////////////
2128
//// IHistory
2129
2130
NS_IMETHODIMP
2131
History::VisitURI(nsIURI* aURI,
2132
                  nsIURI* aLastVisitedURI,
2133
                  uint32_t aFlags)
2134
0
{
2135
0
  MOZ_ASSERT(NS_IsMainThread());
2136
0
  NS_ENSURE_ARG(aURI);
2137
0
2138
0
  if (mShuttingDown) {
2139
0
    return NS_OK;
2140
0
  }
2141
0
2142
0
  if (XRE_IsContentProcess()) {
2143
0
    URIParams uri;
2144
0
    SerializeURI(aURI, uri);
2145
0
2146
0
    OptionalURIParams lastVisitedURI;
2147
0
    SerializeURI(aLastVisitedURI, lastVisitedURI);
2148
0
2149
0
    mozilla::dom::ContentChild* cpc =
2150
0
      mozilla::dom::ContentChild::GetSingleton();
2151
0
    NS_ASSERTION(cpc, "Content Protocol is NULL!");
2152
0
    (void)cpc->SendVisitURI(uri, lastVisitedURI, aFlags);
2153
0
    return NS_OK;
2154
0
  }
2155
0
2156
0
  nsNavHistory* navHistory = nsNavHistory::GetHistoryService();
2157
0
  NS_ENSURE_TRUE(navHistory, NS_ERROR_OUT_OF_MEMORY);
2158
0
2159
0
  // Silently return if URI is something we shouldn't add to DB.
2160
0
  bool canAdd;
2161
0
  nsresult rv = navHistory->CanAddURI(aURI, &canAdd);
2162
0
  NS_ENSURE_SUCCESS(rv, rv);
2163
0
  if (!canAdd) {
2164
0
    return NS_OK;
2165
0
  }
2166
0
2167
0
  // Do not save a reloaded uri if we have visited the same URI recently.
2168
0
  bool reload = false;
2169
0
  if (aLastVisitedURI) {
2170
0
    rv = aURI->Equals(aLastVisitedURI, &reload);
2171
0
    NS_ENSURE_SUCCESS(rv, rv);
2172
0
    if (reload && IsRecentlyVisitedURI(aURI)) {
2173
0
      // Regardless we must update the stored visit time.
2174
0
      AppendToRecentlyVisitedURIs(aURI);
2175
0
      return NS_OK;
2176
0
    }
2177
0
  }
2178
0
2179
0
  nsTArray<VisitData> placeArray(1);
2180
0
  NS_ENSURE_TRUE(placeArray.AppendElement(VisitData(aURI, aLastVisitedURI)),
2181
0
                 NS_ERROR_OUT_OF_MEMORY);
2182
0
  VisitData& place = placeArray.ElementAt(0);
2183
0
  NS_ENSURE_FALSE(place.spec.IsEmpty(), NS_ERROR_INVALID_ARG);
2184
0
2185
0
  place.visitTime = PR_Now();
2186
0
2187
0
  // Assigns a type to the edge in the visit linked list. Each type will be
2188
0
  // considered differently when weighting the frecency of a location.
2189
0
  uint32_t recentFlags = navHistory->GetRecentFlags(aURI);
2190
0
  bool isFollowedLink = recentFlags & nsNavHistory::RECENT_ACTIVATED;
2191
0
2192
0
  // Embed visits should never be added to the database, and the same is valid
2193
0
  // for redirects across frames.
2194
0
  // For the above reasoning non-toplevel transitions are handled at first.
2195
0
  // if the visit is toplevel or a non-toplevel followed link, then it can be
2196
0
  // handled as usual and stored on disk.
2197
0
2198
0
  uint32_t transitionType = nsINavHistoryService::TRANSITION_LINK;
2199
0
2200
0
  if (!(aFlags & IHistory::TOP_LEVEL) && !isFollowedLink) {
2201
0
    // A frame redirected to a new site without user interaction.
2202
0
    transitionType = nsINavHistoryService::TRANSITION_EMBED;
2203
0
  }
2204
0
  else if (aFlags & IHistory::REDIRECT_TEMPORARY) {
2205
0
    transitionType = nsINavHistoryService::TRANSITION_REDIRECT_TEMPORARY;
2206
0
  }
2207
0
  else if (aFlags & IHistory::REDIRECT_PERMANENT) {
2208
0
    transitionType = nsINavHistoryService::TRANSITION_REDIRECT_PERMANENT;
2209
0
  }
2210
0
  else if (reload) {
2211
0
    transitionType = nsINavHistoryService::TRANSITION_RELOAD;
2212
0
  }
2213
0
  else if ((recentFlags & nsNavHistory::RECENT_TYPED) &&
2214
0
           !(aFlags & IHistory::UNRECOVERABLE_ERROR)) {
2215
0
    // Don't mark error pages as typed, even if they were actually typed by
2216
0
    // the user.  This is useful to limit their score in autocomplete.
2217
0
    transitionType = nsINavHistoryService::TRANSITION_TYPED;
2218
0
  }
2219
0
  else if (recentFlags & nsNavHistory::RECENT_BOOKMARKED) {
2220
0
    transitionType = nsINavHistoryService::TRANSITION_BOOKMARK;
2221
0
  }
2222
0
  else if (!(aFlags & IHistory::TOP_LEVEL) && isFollowedLink) {
2223
0
    // User activated a link in a frame.
2224
0
    transitionType = nsINavHistoryService::TRANSITION_FRAMED_LINK;
2225
0
  }
2226
0
2227
0
  place.SetTransitionType(transitionType);
2228
0
  bool isRedirect = aFlags & IHistory::REDIRECT_SOURCE;
2229
0
  if (isRedirect) {
2230
0
    place.useFrecencyRedirectBonus =
2231
0
      (aFlags & IHistory::REDIRECT_SOURCE_PERMANENT) ||
2232
0
      transitionType != nsINavHistoryService::TRANSITION_TYPED;
2233
0
  }
2234
0
  place.hidden = GetHiddenState(isRedirect, place.transitionType);
2235
0
2236
0
  // Error pages should never be autocompleted.
2237
0
  if (aFlags & IHistory::UNRECOVERABLE_ERROR) {
2238
0
    place.shouldUpdateFrecency = false;
2239
0
  }
2240
0
2241
0
  // EMBED visits are session-persistent and should not go through the database.
2242
0
  // They exist only to keep track of isVisited status during the session.
2243
0
  if (place.transitionType == nsINavHistoryService::TRANSITION_EMBED) {
2244
0
    StoreAndNotifyEmbedVisit(place);
2245
0
  }
2246
0
  else {
2247
0
    mozIStorageConnection* dbConn = GetDBConn();
2248
0
    NS_ENSURE_STATE(dbConn);
2249
0
2250
0
    rv = InsertVisitedURIs::Start(dbConn, placeArray);
2251
0
    NS_ENSURE_SUCCESS(rv, rv);
2252
0
  }
2253
0
2254
0
  return NS_OK;
2255
0
}
2256
2257
NS_IMETHODIMP
2258
History::RegisterVisitedCallback(nsIURI* aURI,
2259
                                 Link* aLink)
2260
0
{
2261
0
  MOZ_ASSERT(NS_IsMainThread());
2262
0
  NS_ASSERTION(aURI, "Must pass a non-null URI!");
2263
0
  if (XRE_IsContentProcess()) {
2264
0
    MOZ_ASSERT(aLink, "Must pass a non-null Link!");
2265
0
  }
2266
0
2267
0
  // Obtain our array of observers for this URI.
2268
#ifdef DEBUG
2269
  bool keyAlreadyExists = !!mObservers.GetEntry(aURI);
2270
#endif
2271
  KeyClass* key = mObservers.PutEntry(aURI);
2272
0
  NS_ENSURE_TRUE(key, NS_ERROR_OUT_OF_MEMORY);
2273
0
  ObserverArray& observers = key->array;
2274
0
2275
0
  if (observers.IsEmpty()) {
2276
0
    NS_ASSERTION(!keyAlreadyExists,
2277
0
                 "An empty key was kept around in our hashtable!");
2278
0
2279
0
    // We are the first Link node to ask about this URI, or there are no pending
2280
0
    // Links wanting to know about this URI.  Therefore, we should query the
2281
0
    // database now.
2282
0
    nsresult rv = VisitedQuery::Start(aURI);
2283
0
2284
0
    // In IPC builds, we are passed a nullptr Link from
2285
0
    // ContentParent::RecvStartVisitedQuery.  Since we won't be adding a
2286
0
    // nullptr entry to our list of observers, and the code after this point
2287
0
    // assumes that aLink is non-nullptr, we will need to return now.
2288
0
    if (NS_FAILED(rv) || !aLink) {
2289
0
      // Remove our array from the hashtable so we don't keep it around.
2290
0
      MOZ_ASSERT(key == mObservers.GetEntry(aURI), "The URIs hash mutated!");
2291
0
      // In some case calling RemoveEntry on the key obtained by PutEntry
2292
0
      // crashes for currently unknown reasons.  Our suspect is that something
2293
0
      // between PutEntry and this call causes a nested loop that either removes
2294
0
      // the entry or reallocs the hash.
2295
0
      // TODO (Bug 1412647): we must figure the root cause for these issues and
2296
0
      // remove this stop-gap crash fix.
2297
0
      key = mObservers.GetEntry(aURI);
2298
0
      if (key) {
2299
0
        mObservers.RemoveEntry(key);
2300
0
      }
2301
0
      return rv;
2302
0
    }
2303
0
  }
2304
0
  // In IPC builds, we are passed a nullptr Link from
2305
0
  // ContentParent::RecvStartVisitedQuery.  All of our code after this point
2306
0
  // assumes aLink is non-nullptr, so we have to return now.
2307
0
  else if (!aLink) {
2308
0
    NS_ASSERTION(XRE_IsParentProcess(),
2309
0
                 "We should only ever get a null Link in the default process!");
2310
0
    return NS_OK;
2311
0
  }
2312
0
2313
0
  // Sanity check that Links are not registered more than once for a given URI.
2314
0
  // This will not catch a case where it is registered for two different URIs.
2315
0
  NS_ASSERTION(!observers.Contains(aLink),
2316
0
               "Already tracking this Link object!");
2317
0
2318
0
  // Start tracking our Link.
2319
0
  if (!observers.AppendElement(aLink)) {
2320
0
    // Curses - unregister and return failure.
2321
0
    (void)UnregisterVisitedCallback(aURI, aLink);
2322
0
    return NS_ERROR_OUT_OF_MEMORY;
2323
0
  }
2324
0
2325
0
  // If this link has already been visited, we cannot synchronously mark
2326
0
  // ourselves as visited, so instead we fire a runnable into our docgroup,
2327
0
  // which will handle it for us.
2328
0
  if (key->mVisited) {
2329
0
    DispatchNotifyVisited(aURI, GetLinkDocument(aLink));
2330
0
  }
2331
0
2332
0
  return NS_OK;
2333
0
}
2334
2335
NS_IMETHODIMP
2336
History::UnregisterVisitedCallback(nsIURI* aURI,
2337
                                   Link* aLink)
2338
0
{
2339
0
  MOZ_ASSERT(NS_IsMainThread());
2340
0
  // TODO: aURI is sometimes null - see bug 548685
2341
0
  NS_ASSERTION(aURI, "Must pass a non-null URI!");
2342
0
  NS_ASSERTION(aLink, "Must pass a non-null Link object!");
2343
0
2344
0
  // Get the array, and remove the item from it.
2345
0
  KeyClass* key = mObservers.GetEntry(aURI);
2346
0
  if (!key) {
2347
0
    NS_ERROR("Trying to unregister for a URI that wasn't registered!");
2348
0
    return NS_ERROR_UNEXPECTED;
2349
0
  }
2350
0
  ObserverArray& observers = key->array;
2351
0
  if (!observers.RemoveElement(aLink)) {
2352
0
    NS_ERROR("Trying to unregister a node that wasn't registered!");
2353
0
    return NS_ERROR_UNEXPECTED;
2354
0
  }
2355
0
2356
0
  // If the array is now empty, we should remove it from the hashtable.
2357
0
  if (observers.IsEmpty()) {
2358
0
    MOZ_ASSERT(key == mObservers.GetEntry(aURI), "The URIs hash mutated!");
2359
0
    mObservers.RemoveEntry(key);
2360
0
  }
2361
0
2362
0
  return NS_OK;
2363
0
}
2364
2365
NS_IMETHODIMP
2366
History::SetURITitle(nsIURI* aURI, const nsAString& aTitle)
2367
0
{
2368
0
  MOZ_ASSERT(NS_IsMainThread());
2369
0
  NS_ENSURE_ARG(aURI);
2370
0
2371
0
  if (mShuttingDown) {
2372
0
    return NS_OK;
2373
0
  }
2374
0
2375
0
  if (XRE_IsContentProcess()) {
2376
0
    URIParams uri;
2377
0
    SerializeURI(aURI, uri);
2378
0
2379
0
    mozilla::dom::ContentChild * cpc =
2380
0
      mozilla::dom::ContentChild::GetSingleton();
2381
0
    NS_ASSERTION(cpc, "Content Protocol is NULL!");
2382
0
    (void)cpc->SendSetURITitle(uri, PromiseFlatString(aTitle));
2383
0
    return NS_OK;
2384
0
  }
2385
0
2386
0
  nsNavHistory* navHistory = nsNavHistory::GetHistoryService();
2387
0
2388
0
  // At first, it seems like nav history should always be available here, no
2389
0
  // matter what.
2390
0
  //
2391
0
  // nsNavHistory fails to register as a service if there is no profile in
2392
0
  // place (for instance, if user is choosing a profile).
2393
0
  //
2394
0
  // Maybe the correct thing to do is to not register this service if no
2395
0
  // profile has been selected?
2396
0
  //
2397
0
  NS_ENSURE_TRUE(navHistory, NS_ERROR_FAILURE);
2398
0
2399
0
  bool canAdd;
2400
0
  nsresult rv = navHistory->CanAddURI(aURI, &canAdd);
2401
0
  NS_ENSURE_SUCCESS(rv, rv);
2402
0
  if (!canAdd) {
2403
0
    return NS_OK;
2404
0
  }
2405
0
2406
0
  // Embed visits don't have a database entry, thus don't set a title on them.
2407
0
  if (navHistory->hasEmbedVisit(aURI)) {
2408
0
    return NS_OK;
2409
0
  }
2410
0
2411
0
  mozIStorageConnection* dbConn = GetDBConn();
2412
0
  NS_ENSURE_STATE(dbConn);
2413
0
2414
0
  rv = SetPageTitle::Start(dbConn, aURI, aTitle);
2415
0
  NS_ENSURE_SUCCESS(rv, rv);
2416
0
2417
0
  return NS_OK;
2418
0
}
2419
2420
////////////////////////////////////////////////////////////////////////////////
2421
//// mozIAsyncHistory
2422
2423
NS_IMETHODIMP
2424
History::UpdatePlaces(JS::Handle<JS::Value> aPlaceInfos,
2425
                      mozIVisitInfoCallback* aCallback,
2426
                      bool aGroupNotifications,
2427
                      JSContext* aCtx)
2428
0
{
2429
0
  NS_ENSURE_TRUE(NS_IsMainThread(), NS_ERROR_UNEXPECTED);
2430
0
  NS_ENSURE_TRUE(!aPlaceInfos.isPrimitive(), NS_ERROR_INVALID_ARG);
2431
0
2432
0
  uint32_t infosLength;
2433
0
  JS::Rooted<JSObject*> infos(aCtx);
2434
0
  nsresult rv = GetJSArrayFromJSValue(aPlaceInfos, aCtx, &infos, &infosLength);
2435
0
  NS_ENSURE_SUCCESS(rv, rv);
2436
0
2437
0
  uint32_t initialUpdatedCount = 0;
2438
0
2439
0
  nsTArray<VisitData> visitData;
2440
0
  for (uint32_t i = 0; i < infosLength; i++) {
2441
0
    JS::Rooted<JSObject*> info(aCtx);
2442
0
    nsresult rv = GetJSObjectFromArray(aCtx, infos, i, &info);
2443
0
    NS_ENSURE_SUCCESS(rv, rv);
2444
0
2445
0
    nsCOMPtr<nsIURI> uri = GetURIFromJSObject(aCtx, info, "uri");
2446
0
    nsCString guid;
2447
0
    {
2448
0
      nsString fatGUID;
2449
0
      GetStringFromJSObject(aCtx, info, "guid", fatGUID);
2450
0
      if (fatGUID.IsVoid()) {
2451
0
        guid.SetIsVoid(true);
2452
0
      }
2453
0
      else {
2454
0
        guid = NS_ConvertUTF16toUTF8(fatGUID);
2455
0
      }
2456
0
    }
2457
0
2458
0
    // Make sure that any uri we are given can be added to history, and if not,
2459
0
    // skip it (CanAddURI will notify our callback for us).
2460
0
    if (uri && !CanAddURI(uri, guid, aCallback)) {
2461
0
      continue;
2462
0
    }
2463
0
2464
0
    // We must have at least one of uri or guid.
2465
0
    NS_ENSURE_ARG(uri || !guid.IsVoid());
2466
0
2467
0
    // If we were given a guid, make sure it is valid.
2468
0
    bool isValidGUID = IsValidGUID(guid);
2469
0
    NS_ENSURE_ARG(guid.IsVoid() || isValidGUID);
2470
0
2471
0
    nsString title;
2472
0
    GetStringFromJSObject(aCtx, info, "title", title);
2473
0
2474
0
    JS::Rooted<JSObject*> visits(aCtx, nullptr);
2475
0
    {
2476
0
      JS::Rooted<JS::Value> visitsVal(aCtx);
2477
0
      bool rc = JS_GetProperty(aCtx, info, "visits", &visitsVal);
2478
0
      NS_ENSURE_TRUE(rc, NS_ERROR_UNEXPECTED);
2479
0
      if (!visitsVal.isPrimitive()) {
2480
0
        visits = visitsVal.toObjectOrNull();
2481
0
        bool isArray;
2482
0
        if (!JS_IsArrayObject(aCtx, visits, &isArray)) {
2483
0
          return NS_ERROR_UNEXPECTED;
2484
0
        }
2485
0
        if (!isArray) {
2486
0
          return NS_ERROR_INVALID_ARG;
2487
0
        }
2488
0
      }
2489
0
    }
2490
0
    NS_ENSURE_ARG(visits);
2491
0
2492
0
    uint32_t visitsLength = 0;
2493
0
    if (visits) {
2494
0
      (void)JS_GetArrayLength(aCtx, visits, &visitsLength);
2495
0
    }
2496
0
    NS_ENSURE_ARG(visitsLength > 0);
2497
0
2498
0
    // Check each visit, and build our array of VisitData objects.
2499
0
    visitData.SetCapacity(visitData.Length() + visitsLength);
2500
0
    for (uint32_t j = 0; j < visitsLength; j++) {
2501
0
      JS::Rooted<JSObject*> visit(aCtx);
2502
0
      rv = GetJSObjectFromArray(aCtx, visits, j, &visit);
2503
0
      NS_ENSURE_SUCCESS(rv, rv);
2504
0
2505
0
      VisitData& data = *visitData.AppendElement(VisitData(uri));
2506
0
      if (!title.IsEmpty()) {
2507
0
        data.title = title;
2508
0
      } else if (!title.IsVoid()) {
2509
0
        // Setting data.title to an empty string wouldn't make it non-void.
2510
0
        data.title.SetIsVoid(false);
2511
0
      }
2512
0
      data.guid = guid;
2513
0
2514
0
      // We must have a date and a transaction type!
2515
0
      rv = GetIntFromJSObject(aCtx, visit, "visitDate", &data.visitTime);
2516
0
      NS_ENSURE_SUCCESS(rv, rv);
2517
0
      // visitDate should be in microseconds. It's easy to do the wrong thing
2518
0
      // and pass milliseconds to updatePlaces, so we lazily check for that.
2519
0
      // While it's not easily distinguishable, since both are integers, we can
2520
0
      // check if the value is very far in the past, and assume it's probably
2521
0
      // a mistake.
2522
0
      if (data.visitTime < (PR_Now() / 1000)) {
2523
#ifdef DEBUG
2524
        nsCOMPtr<nsIXPConnect> xpc = nsIXPConnect::XPConnect();
2525
        Unused << xpc->DebugDumpJSStack(false, false, false);
2526
        MOZ_CRASH("invalid time format passed to updatePlaces");
2527
#endif
2528
        return NS_ERROR_INVALID_ARG;
2529
0
      }
2530
0
      uint32_t transitionType = 0;
2531
0
      rv = GetIntFromJSObject(aCtx, visit, "transitionType", &transitionType);
2532
0
      NS_ENSURE_SUCCESS(rv, rv);
2533
0
      NS_ENSURE_ARG_RANGE(transitionType,
2534
0
                          nsINavHistoryService::TRANSITION_LINK,
2535
0
                          nsINavHistoryService::TRANSITION_RELOAD);
2536
0
      data.SetTransitionType(transitionType);
2537
0
      data.hidden = GetHiddenState(false, transitionType);
2538
0
2539
0
      // If the visit is an embed visit, we do not actually add it to the
2540
0
      // database.
2541
0
      if (transitionType == nsINavHistoryService::TRANSITION_EMBED) {
2542
0
        StoreAndNotifyEmbedVisit(data, aCallback);
2543
0
        visitData.RemoveLastElement();
2544
0
        initialUpdatedCount++;
2545
0
        continue;
2546
0
      }
2547
0
2548
0
      // The referrer is optional.
2549
0
      nsCOMPtr<nsIURI> referrer = GetURIFromJSObject(aCtx, visit,
2550
0
                                                     "referrerURI");
2551
0
      if (referrer) {
2552
0
        (void)referrer->GetSpec(data.referrerSpec);
2553
0
      }
2554
0
    }
2555
0
  }
2556
0
2557
0
  mozIStorageConnection* dbConn = GetDBConn();
2558
0
  NS_ENSURE_STATE(dbConn);
2559
0
2560
0
  nsMainThreadPtrHandle<mozIVisitInfoCallback>
2561
0
    callback(new nsMainThreadPtrHolder<mozIVisitInfoCallback>(
2562
0
      "mozIVisitInfoCallback", aCallback));
2563
0
2564
0
  // It is possible that all of the visits we were passed were dissallowed by
2565
0
  // CanAddURI, which isn't an error.  If we have no visits to add, however,
2566
0
  // we should not call InsertVisitedURIs::Start.
2567
0
  if (visitData.Length()) {
2568
0
    nsresult rv = InsertVisitedURIs::Start(dbConn, visitData,
2569
0
                                           callback, aGroupNotifications,
2570
0
                                           initialUpdatedCount);
2571
0
    NS_ENSURE_SUCCESS(rv, rv);
2572
0
  } else if (aCallback) {
2573
0
    // Be sure to notify that all of our operations are complete.  This
2574
0
    // is dispatched to the background thread first and redirected to the
2575
0
    // main thread from there to make sure that all database notifications
2576
0
    // and all embed or canAddURI notifications have finished.
2577
0
2578
0
    // Note: if we're inserting anything, it's the responsibility of
2579
0
    // InsertVisitedURIs to call the completion callback, as here we won't
2580
0
    // know how yet many items we will successfully insert/update.
2581
0
    nsCOMPtr<nsIEventTarget> backgroundThread = do_GetInterface(dbConn);
2582
0
    NS_ENSURE_TRUE(backgroundThread, NS_ERROR_UNEXPECTED);
2583
0
    nsCOMPtr<nsIRunnable> event = new NotifyCompletion(callback,
2584
0
                                                       initialUpdatedCount);
2585
0
    return backgroundThread->Dispatch(event, NS_DISPATCH_NORMAL);
2586
0
  }
2587
0
2588
0
  return NS_OK;
2589
0
}
2590
2591
NS_IMETHODIMP
2592
History::IsURIVisited(nsIURI* aURI,
2593
                      mozIVisitedStatusCallback* aCallback)
2594
0
{
2595
0
  NS_ENSURE_STATE(NS_IsMainThread());
2596
0
  NS_ENSURE_ARG(aURI);
2597
0
  NS_ENSURE_ARG(aCallback);
2598
0
2599
0
  nsresult rv = VisitedQuery::Start(aURI, aCallback);
2600
0
  NS_ENSURE_SUCCESS(rv, rv);
2601
0
2602
0
  return NS_OK;
2603
0
}
2604
2605
////////////////////////////////////////////////////////////////////////////////
2606
//// nsIObserver
2607
2608
NS_IMETHODIMP
2609
History::Observe(nsISupports* aSubject, const char* aTopic,
2610
                 const char16_t* aData)
2611
0
{
2612
0
  if (strcmp(aTopic, TOPIC_PLACES_SHUTDOWN) == 0) {
2613
0
    Shutdown();
2614
0
2615
0
    nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
2616
0
    if (os) {
2617
0
      (void)os->RemoveObserver(this, TOPIC_PLACES_SHUTDOWN);
2618
0
    }
2619
0
  }
2620
0
2621
0
  return NS_OK;
2622
0
}
2623
2624
////////////////////////////////////////////////////////////////////////////////
2625
//// nsISupports
2626
2627
NS_IMPL_ISUPPORTS(
2628
  History
2629
, IHistory
2630
, mozIAsyncHistory
2631
, nsIObserver
2632
, nsIMemoryReporter
2633
)
2634
2635
} // namespace places
2636
} // namespace mozilla