Source code

Revision control

Other Tools

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 "mozilla/dom/BrowserChild.h"
14
#include "nsXULAppAPI.h"
15
16
#include "History.h"
17
#include "nsNavHistory.h"
18
#include "nsNavBookmarks.h"
19
#include "Helpers.h"
20
#include "PlaceInfo.h"
21
#include "VisitInfo.h"
22
#include "nsPlacesMacros.h"
23
24
#include "mozilla/storage.h"
25
#include "mozilla/dom/Link.h"
26
#include "nsDocShellCID.h"
27
#include "mozilla/Services.h"
28
#include "nsThreadUtils.h"
29
#include "nsNetUtil.h"
30
#include "nsIFileURL.h"
31
#include "nsIWidget.h"
32
#include "nsIXPConnect.h"
33
#include "nsIXULRuntime.h"
34
#include "mozilla/Unused.h"
35
#include "nsContentUtils.h" // for nsAutoScriptBlocker
36
#include "nsJSUtils.h"
37
#include "mozilla/ipc/URIUtils.h"
38
#include "nsPrintfCString.h"
39
#include "nsTHashtable.h"
40
#include "jsapi.h"
41
#include "mozilla/StaticPrefs_layout.h"
42
#include "mozilla/dom/ContentProcessMessageManager.h"
43
#include "mozilla/dom/Element.h"
44
#include "mozilla/dom/PlacesObservers.h"
45
#include "mozilla/dom/PlacesVisit.h"
46
#include "mozilla/dom/ScriptSettings.h"
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
#define URI_VISITED "visited"
59
#define URI_NOT_VISITED "not visited"
60
#define URI_VISITED_RESOLUTION_TOPIC "visited-status-resolution"
61
// Observer event fired after a visit has been registered in the DB.
62
#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
guid.SetIsVoid(true);
88
title.SetIsVoid(true);
89
}
90
91
explicit VisitData(nsIURI* aURI, nsIURI* aReferrer = nullptr)
92
: placeId(0),
93
visitId(0),
94
hidden(true),
95
shouldUpdateHidden(true),
96
typed(false),
97
transitionType(UINT32_MAX),
98
visitTime(0),
99
frecency(-1),
100
lastVisitId(0),
101
lastVisitTime(0),
102
visitCount(0),
103
referrerVisitId(0),
104
titleChanged(false),
105
shouldUpdateFrecency(true),
106
useFrecencyRedirectBonus(false) {
107
MOZ_ASSERT(aURI);
108
if (aURI) {
109
(void)aURI->GetSpec(spec);
110
(void)GetReversedHostname(aURI, revHost);
111
}
112
if (aReferrer) {
113
(void)aReferrer->GetSpec(referrerSpec);
114
}
115
guid.SetIsVoid(true);
116
title.SetIsVoid(true);
117
}
118
119
/**
120
* Sets the transition type of the visit, as well as if it was typed.
121
*
122
* @param aTransitionType
123
* The transition type constant to set. Must be one of the
124
* TRANSITION_ constants on nsINavHistoryService.
125
*/
126
void SetTransitionType(uint32_t aTransitionType) {
127
typed = aTransitionType == nsINavHistoryService::TRANSITION_TYPED;
128
transitionType = aTransitionType;
129
}
130
131
int64_t placeId;
132
nsCString guid;
133
int64_t visitId;
134
nsCString spec;
135
nsString revHost;
136
bool hidden;
137
bool shouldUpdateHidden;
138
bool typed;
139
uint32_t transitionType;
140
PRTime visitTime;
141
int32_t frecency;
142
int64_t lastVisitId;
143
PRTime lastVisitTime;
144
uint32_t visitCount;
145
146
/**
147
* Stores the title. If this is empty (IsEmpty() returns true), then the
148
* title should be removed from the Place. If the title is void (IsVoid()
149
* returns true), then no title has been set on this object, and titleChanged
150
* should remain false.
151
*/
152
nsString title;
153
154
nsCString referrerSpec;
155
int64_t referrerVisitId;
156
157
// TODO bug 626836 hook up hidden and typed change tracking too!
158
bool titleChanged;
159
160
// Indicates whether frecency should be updated for this visit.
161
bool shouldUpdateFrecency;
162
163
// Whether to override the visit type bonus with a redirect bonus when
164
// calculating frecency on the most recent visit.
165
bool useFrecencyRedirectBonus;
166
};
167
168
////////////////////////////////////////////////////////////////////////////////
169
//// Anonymous Helpers
170
171
namespace {
172
173
/**
174
* Convert the given js value to a js array.
175
*
176
* @param [in] aValue
177
* the JS value to convert.
178
* @param [in] aCtx
179
* The JSContext for aValue.
180
* @param [out] _array
181
* the JS array.
182
* @param [out] _arrayLength
183
* _array's length.
184
*/
185
nsresult GetJSArrayFromJSValue(JS::Handle<JS::Value> aValue, JSContext* aCtx,
186
JS::MutableHandle<JSObject*> _array,
187
uint32_t* _arrayLength) {
188
if (aValue.isObjectOrNull()) {
189
JS::Rooted<JSObject*> val(aCtx, aValue.toObjectOrNull());
190
bool isArray;
191
if (!JS_IsArrayObject(aCtx, val, &isArray)) {
192
return NS_ERROR_UNEXPECTED;
193
}
194
if (isArray) {
195
_array.set(val);
196
(void)JS_GetArrayLength(aCtx, _array, _arrayLength);
197
NS_ENSURE_ARG(*_arrayLength > 0);
198
return NS_OK;
199
}
200
}
201
202
// Build a temporary array to store this one item so the code below can
203
// just loop.
204
*_arrayLength = 1;
205
_array.set(JS_NewArrayObject(aCtx, 0));
206
NS_ENSURE_TRUE(_array, NS_ERROR_OUT_OF_MEMORY);
207
208
bool rc = JS_DefineElement(aCtx, _array, 0, aValue, 0);
209
NS_ENSURE_TRUE(rc, NS_ERROR_UNEXPECTED);
210
return NS_OK;
211
}
212
213
/**
214
* Attemps to convert a given js value to a nsIURI object.
215
* @param aCtx
216
* The JSContext for aValue.
217
* @param aValue
218
* The JS value to convert.
219
* @return the nsIURI object, or null if aValue is not a nsIURI object.
220
*/
221
already_AddRefed<nsIURI> GetJSValueAsURI(JSContext* aCtx,
222
const JS::Value& aValue) {
223
if (!aValue.isPrimitive()) {
224
nsCOMPtr<nsIXPConnect> xpc = nsIXPConnect::XPConnect();
225
226
nsCOMPtr<nsIXPConnectWrappedNative> wrappedObj;
227
JS::Rooted<JSObject*> obj(aCtx, aValue.toObjectOrNull());
228
nsresult rv =
229
xpc->GetWrappedNativeOfJSObject(aCtx, obj, getter_AddRefs(wrappedObj));
230
NS_ENSURE_SUCCESS(rv, nullptr);
231
nsCOMPtr<nsIURI> uri = do_QueryInterface(wrappedObj->Native());
232
return uri.forget();
233
}
234
return nullptr;
235
}
236
237
/**
238
* Obtains an nsIURI from the "uri" property of a JSObject.
239
*
240
* @param aCtx
241
* The JSContext for aObject.
242
* @param aObject
243
* The JSObject to get the URI from.
244
* @param aProperty
245
* The name of the property to get the URI from.
246
* @return the URI if it exists.
247
*/
248
already_AddRefed<nsIURI> GetURIFromJSObject(JSContext* aCtx,
249
JS::Handle<JSObject*> aObject,
250
const char* aProperty) {
251
JS::Rooted<JS::Value> uriVal(aCtx);
252
bool rc = JS_GetProperty(aCtx, aObject, aProperty, &uriVal);
253
NS_ENSURE_TRUE(rc, nullptr);
254
return GetJSValueAsURI(aCtx, uriVal);
255
}
256
257
/**
258
* Attemps to convert a JS value to a string.
259
* @param aCtx
260
* The JSContext for aObject.
261
* @param aValue
262
* The JS value to convert.
263
* @param _string
264
* The string to populate with the value, or set it to void.
265
*/
266
void GetJSValueAsString(JSContext* aCtx, const JS::Value& aValue,
267
nsString& _string) {
268
if (aValue.isUndefined() || !(aValue.isNull() || aValue.isString())) {
269
_string.SetIsVoid(true);
270
return;
271
}
272
273
// |null| in JS maps to the empty string.
274
if (aValue.isNull()) {
275
_string.Truncate();
276
return;
277
}
278
279
if (!AssignJSString(aCtx, _string, aValue.toString())) {
280
_string.SetIsVoid(true);
281
}
282
}
283
284
/**
285
* Obtains the specified property of a JSObject.
286
*
287
* @param aCtx
288
* The JSContext for aObject.
289
* @param aObject
290
* The JSObject to get the string from.
291
* @param aProperty
292
* The property to get the value from.
293
* @param _string
294
* The string to populate with the value, or set it to void.
295
*/
296
void GetStringFromJSObject(JSContext* aCtx, JS::Handle<JSObject*> aObject,
297
const char* aProperty, nsString& _string) {
298
JS::Rooted<JS::Value> val(aCtx);
299
bool rc = JS_GetProperty(aCtx, aObject, aProperty, &val);
300
if (!rc) {
301
_string.SetIsVoid(true);
302
return;
303
} else {
304
GetJSValueAsString(aCtx, val, _string);
305
}
306
}
307
308
/**
309
* Obtains the specified property of a JSObject.
310
*
311
* @param aCtx
312
* The JSContext for aObject.
313
* @param aObject
314
* The JSObject to get the int from.
315
* @param aProperty
316
* The property to get the value from.
317
* @param _int
318
* The integer to populate with the value on success.
319
*/
320
template <typename IntType>
321
nsresult GetIntFromJSObject(JSContext* aCtx, JS::Handle<JSObject*> aObject,
322
const char* aProperty, IntType* _int) {
323
JS::Rooted<JS::Value> value(aCtx);
324
bool rc = JS_GetProperty(aCtx, aObject, aProperty, &value);
325
NS_ENSURE_TRUE(rc, NS_ERROR_UNEXPECTED);
326
if (value.isUndefined()) {
327
return NS_ERROR_INVALID_ARG;
328
}
329
NS_ENSURE_ARG(value.isPrimitive());
330
NS_ENSURE_ARG(value.isNumber());
331
332
double num;
333
rc = JS::ToNumber(aCtx, value, &num);
334
NS_ENSURE_TRUE(rc, NS_ERROR_UNEXPECTED);
335
NS_ENSURE_ARG(IntType(num) == num);
336
337
*_int = IntType(num);
338
return NS_OK;
339
}
340
341
/**
342
* Obtains the specified property of a JSObject.
343
*
344
* @pre aArray must be an Array object.
345
*
346
* @param aCtx
347
* The JSContext for aArray.
348
* @param aArray
349
* The JSObject to get the object from.
350
* @param aIndex
351
* The index to get the object from.
352
* @param objOut
353
* Set to the JSObject pointer on success.
354
*/
355
nsresult GetJSObjectFromArray(JSContext* aCtx, JS::Handle<JSObject*> aArray,
356
uint32_t aIndex,
357
JS::MutableHandle<JSObject*> objOut) {
358
JS::Rooted<JS::Value> value(aCtx);
359
bool rc = JS_GetElement(aCtx, aArray, aIndex, &value);
360
NS_ENSURE_TRUE(rc, NS_ERROR_UNEXPECTED);
361
NS_ENSURE_ARG(!value.isPrimitive());
362
objOut.set(&value.toObject());
363
return NS_OK;
364
}
365
366
} // namespace
367
368
class VisitedQuery final : public AsyncStatementCallback {
369
public:
370
NS_DECL_ISUPPORTS_INHERITED
371
372
static nsresult Start(nsIURI* aURI,
373
mozIVisitedStatusCallback* aCallback = nullptr) {
374
MOZ_ASSERT(aURI, "Null URI");
375
MOZ_ASSERT(XRE_IsParentProcess());
376
377
nsMainThreadPtrHandle<mozIVisitedStatusCallback> callback(
378
new nsMainThreadPtrHolder<mozIVisitedStatusCallback>(
379
"mozIVisitedStatusCallback", aCallback));
380
381
History* history = History::GetService();
382
NS_ENSURE_STATE(history);
383
RefPtr<VisitedQuery> query = new VisitedQuery(aURI, callback);
384
return history->QueueVisitedStatement(std::move(query));
385
}
386
387
void Execute(mozIStorageAsyncStatement& aStatement) {
388
// Bind by index for performance.
389
nsresult rv = URIBinder::Bind(&aStatement, 0, mURI);
390
if (NS_WARN_IF(NS_FAILED(rv))) {
391
return;
392
}
393
394
nsCOMPtr<mozIStoragePendingStatement> handle;
395
rv = aStatement.ExecuteAsync(this, getter_AddRefs(handle));
396
MOZ_ASSERT(NS_SUCCEEDED(rv));
397
Unused << rv;
398
}
399
400
NS_IMETHOD HandleResult(mozIStorageResultSet* aResults) override {
401
// If this method is called, we've gotten results, which means we have a
402
// visit.
403
mIsVisited = true;
404
return NS_OK;
405
}
406
407
NS_IMETHOD HandleError(mozIStorageError* aError) override {
408
// mIsVisited is already set to false, and that's the assumption we will
409
// make if an error occurred.
410
return NS_OK;
411
}
412
413
NS_IMETHOD HandleCompletion(uint16_t aReason) override {
414
if (aReason == mozIStorageStatementCallback::REASON_FINISHED) {
415
NotifyVisitedStatus();
416
}
417
return NS_OK;
418
}
419
420
void NotifyVisitedStatus() {
421
// If an external handling callback is provided, just notify through it.
422
if (mCallback) {
423
mCallback->IsVisited(mURI, mIsVisited);
424
return;
425
}
426
427
if (mIsVisited || StaticPrefs::layout_css_notify_of_unvisited()) {
428
History* history = History::GetService();
429
if (!history) {
430
return;
431
}
432
auto status = mIsVisited ? IHistory::VisitedStatus::Visited
433
: IHistory::VisitedStatus::Unvisited;
434
history->NotifyVisited(mURI, status);
435
if (BrowserTabsRemoteAutostart()) {
436
AutoTArray<VisitedQueryResult, 1> results;
437
VisitedQueryResult& result = *results.AppendElement();
438
result.visited() = mIsVisited;
439
SerializeURI(mURI, result.uri());
440
history->NotifyVisitedParent(results);
441
}
442
}
443
444
nsCOMPtr<nsIObserverService> observerService =
445
mozilla::services::GetObserverService();
446
if (observerService) {
447
static const char16_t visited[] = u"" URI_VISITED;
448
static const char16_t notVisited[] = u"" URI_NOT_VISITED;
449
const char16_t* status = mIsVisited ? visited : notVisited;
450
(void)observerService->NotifyObservers(mURI, URI_VISITED_RESOLUTION_TOPIC,
451
status);
452
}
453
}
454
455
private:
456
explicit VisitedQuery(
457
nsIURI* aURI,
458
const nsMainThreadPtrHandle<mozIVisitedStatusCallback>& aCallback)
459
: mURI(aURI), mCallback(aCallback), mIsVisited(false) {}
460
461
~VisitedQuery() {}
462
463
nsCOMPtr<nsIURI> mURI;
464
nsMainThreadPtrHandle<mozIVisitedStatusCallback> mCallback;
465
bool mIsVisited;
466
};
467
468
NS_IMPL_ISUPPORTS_INHERITED0(VisitedQuery, AsyncStatementCallback)
469
470
/**
471
* Notifies observers about a visit or an array of visits.
472
*/
473
class NotifyManyVisitsObservers : public Runnable {
474
public:
475
explicit NotifyManyVisitsObservers(const VisitData& aPlace)
476
: Runnable("places::NotifyManyVisitsObservers"),
477
mPlace(aPlace),
478
mHistory(History::GetService()) {}
479
480
explicit NotifyManyVisitsObservers(nsTArray<VisitData>& aPlaces)
481
: Runnable("places::NotifyManyVisitsObservers"),
482
mHistory(History::GetService()) {
483
aPlaces.SwapElements(mPlaces);
484
}
485
486
nsresult NotifyVisit(nsNavHistory* aNavHistory,
487
nsCOMPtr<nsIObserverService>& aObsService, PRTime aNow,
488
nsIURI* aURI, const VisitData& aPlace) {
489
if (aObsService) {
490
DebugOnly<nsresult> rv =
491
aObsService->NotifyObservers(aURI, URI_VISIT_SAVED, nullptr);
492
NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "Could not notify observers");
493
}
494
495
if (aNow - aPlace.visitTime < RECENTLY_VISITED_URIS_MAX_AGE) {
496
mHistory->AppendToRecentlyVisitedURIs(aURI, aPlace.hidden);
497
}
498
mHistory->NotifyVisited(aURI, IHistory::VisitedStatus::Visited);
499
500
if (aPlace.titleChanged) {
501
aNavHistory->NotifyTitleChange(aURI, aPlace.title, aPlace.guid);
502
}
503
504
aNavHistory->UpdateDaysOfHistory(aPlace.visitTime);
505
506
return NS_OK;
507
}
508
509
void AddPlaceForNotify(const VisitData& aPlace, nsIURI* aURI,
510
Sequence<OwningNonNull<PlacesEvent>>& aEvents) {
511
if (aPlace.transitionType != nsINavHistoryService::TRANSITION_EMBED) {
512
RefPtr<PlacesVisit> vd = new PlacesVisit();
513
vd->mVisitId = aPlace.visitId;
514
vd->mUrl.Assign(NS_ConvertUTF8toUTF16(aPlace.spec));
515
vd->mVisitTime = aPlace.visitTime / 1000;
516
vd->mReferringVisitId = aPlace.referrerVisitId;
517
vd->mTransitionType = aPlace.transitionType;
518
vd->mPageGuid.Assign(aPlace.guid);
519
vd->mHidden = aPlace.hidden;
520
vd->mVisitCount = aPlace.visitCount + 1; // Add current visit
521
vd->mTypedCount = static_cast<uint32_t>(aPlace.typed);
522
vd->mLastKnownTitle.Assign(aPlace.title);
523
bool success = !!aEvents.AppendElement(vd.forget(), fallible);
524
MOZ_RELEASE_ASSERT(success);
525
}
526
}
527
528
// MOZ_CAN_RUN_SCRIPT_BOUNDARY until Runnable::Run is marked
529
// MOZ_CAN_RUN_SCRIPT. See bug 1535398.
530
MOZ_CAN_RUN_SCRIPT_BOUNDARY
531
NS_IMETHOD Run() override {
532
MOZ_ASSERT(NS_IsMainThread(), "This should be called on the main thread");
533
534
// We are in the main thread, no need to lock.
535
if (mHistory->IsShuttingDown()) {
536
// If we are shutting down, we cannot notify the observers.
537
return NS_OK;
538
}
539
540
nsNavHistory* navHistory = nsNavHistory::GetHistoryService();
541
if (!navHistory) {
542
NS_WARNING(
543
"Trying to notify visits observers but cannot get the history "
544
"service!");
545
return NS_OK;
546
}
547
548
nsCOMPtr<nsIObserverService> obsService =
549
mozilla::services::GetObserverService();
550
551
Sequence<OwningNonNull<PlacesEvent>> events;
552
nsCOMArray<nsIURI> uris;
553
if (mPlaces.Length() > 0) {
554
for (uint32_t i = 0; i < mPlaces.Length(); ++i) {
555
nsCOMPtr<nsIURI> uri;
556
MOZ_ALWAYS_SUCCEEDS(NS_NewURI(getter_AddRefs(uri), mPlaces[i].spec));
557
if (!uri) {
558
return NS_ERROR_UNEXPECTED;
559
}
560
AddPlaceForNotify(mPlaces[i], uri, events);
561
uris.AppendElement(uri.forget());
562
}
563
} else {
564
nsCOMPtr<nsIURI> uri;
565
MOZ_ALWAYS_SUCCEEDS(NS_NewURI(getter_AddRefs(uri), mPlace.spec));
566
if (!uri) {
567
return NS_ERROR_UNEXPECTED;
568
}
569
AddPlaceForNotify(mPlace, uri, events);
570
uris.AppendElement(uri.forget());
571
}
572
573
if (events.Length() > 0) {
574
PlacesObservers::NotifyListeners(events);
575
}
576
577
PRTime now = PR_Now();
578
if (!mPlaces.IsEmpty()) {
579
nsTArray<VisitedQueryResult> results(mPlaces.Length());
580
for (uint32_t i = 0; i < mPlaces.Length(); ++i) {
581
nsresult rv =
582
NotifyVisit(navHistory, obsService, now, uris[i], mPlaces[i]);
583
NS_ENSURE_SUCCESS(rv, rv);
584
585
if (BrowserTabsRemoteAutostart()) {
586
VisitedQueryResult& result = *results.AppendElement();
587
SerializeURI(uris[i], result.uri());
588
result.visited() = true;
589
}
590
}
591
mHistory->NotifyVisitedParent(results);
592
} else {
593
AutoTArray<VisitedQueryResult, 1> results;
594
nsresult rv = NotifyVisit(navHistory, obsService, now, uris[0], mPlace);
595
NS_ENSURE_SUCCESS(rv, rv);
596
597
if (BrowserTabsRemoteAutostart()) {
598
VisitedQueryResult& result = *results.AppendElement();
599
SerializeURI(uris[0], result.uri());
600
result.visited() = true;
601
mHistory->NotifyVisitedParent(results);
602
}
603
}
604
605
return NS_OK;
606
}
607
608
private:
609
nsTArray<VisitData> mPlaces;
610
VisitData mPlace;
611
RefPtr<History> mHistory;
612
};
613
614
/**
615
* Notifies observers about a pages title changing.
616
*/
617
class NotifyTitleObservers : public Runnable {
618
public:
619
/**
620
* Notifies observers on the main thread.
621
*
622
* @param aSpec
623
* The spec of the URI to notify about.
624
* @param aTitle
625
* The new title to notify about.
626
*/
627
NotifyTitleObservers(const nsCString& aSpec, const nsString& aTitle,
628
const nsCString& aGUID)
629
: Runnable("places::NotifyTitleObservers"),
630
mSpec(aSpec),
631
mTitle(aTitle),
632
mGUID(aGUID) {}
633
634
NS_IMETHOD Run() override {
635
MOZ_ASSERT(NS_IsMainThread(), "This should be called on the main thread");
636
637
nsNavHistory* navHistory = nsNavHistory::GetHistoryService();
638
NS_ENSURE_TRUE(navHistory, NS_ERROR_OUT_OF_MEMORY);
639
nsCOMPtr<nsIURI> uri;
640
MOZ_ALWAYS_SUCCEEDS(NS_NewURI(getter_AddRefs(uri), mSpec));
641
if (!uri) {
642
return NS_ERROR_UNEXPECTED;
643
}
644
645
navHistory->NotifyTitleChange(uri, mTitle, mGUID);
646
647
return NS_OK;
648
}
649
650
private:
651
const nsCString mSpec;
652
const nsString mTitle;
653
const nsCString mGUID;
654
};
655
656
/**
657
* Helper class for methods which notify their callers through the
658
* mozIVisitInfoCallback interface.
659
*/
660
class NotifyPlaceInfoCallback : public Runnable {
661
public:
662
NotifyPlaceInfoCallback(
663
const nsMainThreadPtrHandle<mozIVisitInfoCallback>& aCallback,
664
const VisitData& aPlace, bool aIsSingleVisit, nsresult aResult)
665
: Runnable("places::NotifyPlaceInfoCallback"),
666
mCallback(aCallback),
667
mPlace(aPlace),
668
mResult(aResult),
669
mIsSingleVisit(aIsSingleVisit) {
670
MOZ_ASSERT(aCallback, "Must pass a non-null callback!");
671
}
672
673
NS_IMETHOD Run() override {
674
MOZ_ASSERT(NS_IsMainThread(), "This should be called on the main thread");
675
676
bool hasValidURIs = true;
677
nsCOMPtr<nsIURI> referrerURI;
678
if (!mPlace.referrerSpec.IsEmpty()) {
679
MOZ_ALWAYS_SUCCEEDS(
680
NS_NewURI(getter_AddRefs(referrerURI), mPlace.referrerSpec));
681
hasValidURIs = !!referrerURI;
682
}
683
684
nsCOMPtr<nsIURI> uri;
685
MOZ_ALWAYS_SUCCEEDS(NS_NewURI(getter_AddRefs(uri), mPlace.spec));
686
hasValidURIs = hasValidURIs && !!uri;
687
688
nsCOMPtr<mozIPlaceInfo> place;
689
if (mIsSingleVisit) {
690
nsCOMPtr<mozIVisitInfo> visit =
691
new VisitInfo(mPlace.visitId, mPlace.visitTime, mPlace.transitionType,
692
referrerURI.forget());
693
PlaceInfo::VisitsArray visits;
694
(void)visits.AppendElement(visit);
695
696
// The frecency isn't exposed because it may not reflect the updated value
697
// in the case of InsertVisitedURIs.
698
place = new PlaceInfo(mPlace.placeId, mPlace.guid, uri.forget(),
699
mPlace.title, -1, visits);
700
} else {
701
// Same as above.
702
place = new PlaceInfo(mPlace.placeId, mPlace.guid, uri.forget(),
703
mPlace.title, -1);
704
}
705
706
if (NS_SUCCEEDED(mResult) && hasValidURIs) {
707
(void)mCallback->HandleResult(place);
708
} else {
709
(void)mCallback->HandleError(mResult, place);
710
}
711
712
return NS_OK;
713
}
714
715
private:
716
nsMainThreadPtrHandle<mozIVisitInfoCallback> mCallback;
717
VisitData mPlace;
718
const nsresult mResult;
719
bool mIsSingleVisit;
720
};
721
722
/**
723
* Notifies a callback object when the operation is complete.
724
*/
725
class NotifyCompletion : public Runnable {
726
public:
727
explicit NotifyCompletion(
728
const nsMainThreadPtrHandle<mozIVisitInfoCallback>& aCallback,
729
uint32_t aUpdatedCount = 0)
730
: Runnable("places::NotifyCompletion"),
731
mCallback(aCallback),
732
mUpdatedCount(aUpdatedCount) {
733
MOZ_ASSERT(aCallback, "Must pass a non-null callback!");
734
}
735
736
NS_IMETHOD Run() override {
737
if (NS_IsMainThread()) {
738
(void)mCallback->HandleCompletion(mUpdatedCount);
739
} else {
740
(void)NS_DispatchToMainThread(this);
741
}
742
return NS_OK;
743
}
744
745
private:
746
nsMainThreadPtrHandle<mozIVisitInfoCallback> mCallback;
747
uint32_t mUpdatedCount;
748
};
749
750
/**
751
* Checks to see if we can add aURI to history, and dispatches an error to
752
* aCallback (if provided) if we cannot.
753
*
754
* @param aURI
755
* The URI to check.
756
* @param [optional] aGUID
757
* The guid of the URI to check. This is passed back to the callback.
758
* @param [optional] aCallback
759
* The callback to notify if the URI cannot be added to history.
760
* @return true if the URI can be added to history, false otherwise.
761
*/
762
bool CanAddURI(nsIURI* aURI, const nsCString& aGUID = EmptyCString(),
763
mozIVisitInfoCallback* aCallback = nullptr) {
764
MOZ_ASSERT(NS_IsMainThread());
765
nsNavHistory* navHistory = nsNavHistory::GetHistoryService();
766
NS_ENSURE_TRUE(navHistory, false);
767
768
bool canAdd;
769
nsresult rv = navHistory->CanAddURI(aURI, &canAdd);
770
if (NS_SUCCEEDED(rv) && canAdd) {
771
return true;
772
};
773
774
// We cannot add the URI. Notify the callback, if we were given one.
775
if (aCallback) {
776
VisitData place(aURI);
777
place.guid = aGUID;
778
nsMainThreadPtrHandle<mozIVisitInfoCallback> callback(
779
new nsMainThreadPtrHolder<mozIVisitInfoCallback>(
780
"mozIVisitInfoCallback", aCallback));
781
nsCOMPtr<nsIRunnable> event = new NotifyPlaceInfoCallback(
782
callback, place, true, NS_ERROR_INVALID_ARG);
783
(void)NS_DispatchToMainThread(event);
784
}
785
786
return false;
787
}
788
789
class NotifyManyFrecenciesChanged final : public Runnable {
790
public:
791
NotifyManyFrecenciesChanged()
792
: Runnable("places::NotifyManyFrecenciesChanged") {}
793
794
NS_IMETHOD Run() override {
795
MOZ_ASSERT(NS_IsMainThread(), "This should be called on the main thread");
796
nsNavHistory* navHistory = nsNavHistory::GetHistoryService();
797
NS_ENSURE_STATE(navHistory);
798
navHistory->NotifyManyFrecenciesChanged();
799
return NS_OK;
800
}
801
};
802
803
/**
804
* Adds a visit to the database.
805
*/
806
class InsertVisitedURIs final : public Runnable {
807
public:
808
/**
809
* Adds a visit to the database asynchronously.
810
*
811
* @param aConnection
812
* The database connection to use for these operations.
813
* @param aPlaces
814
* The locations to record visits.
815
* @param [optional] aCallback
816
* The callback to notify about the visit.
817
* @param [optional] aGroupNotifications
818
* Whether to group any observer notifications rather than
819
* sending them out individually.
820
*/
821
static nsresult Start(mozIStorageConnection* aConnection,
822
nsTArray<VisitData>& aPlaces,
823
mozIVisitInfoCallback* aCallback = nullptr,
824
bool aGroupNotifications = false,
825
uint32_t aInitialUpdatedCount = 0) {
826
MOZ_ASSERT(NS_IsMainThread(), "This should be called on the main thread");
827
MOZ_ASSERT(aPlaces.Length() > 0, "Must pass a non-empty array!");
828
829
// Make sure nsNavHistory service is up before proceeding:
830
nsNavHistory* navHistory = nsNavHistory::GetHistoryService();
831
MOZ_ASSERT(navHistory, "Could not get nsNavHistory?!");
832
if (!navHistory) {
833
return NS_ERROR_FAILURE;
834
}
835
836
nsMainThreadPtrHandle<mozIVisitInfoCallback> callback(
837
new nsMainThreadPtrHolder<mozIVisitInfoCallback>(
838
"mozIVisitInfoCallback", aCallback));
839
bool ignoreErrors = false, ignoreResults = false;
840
if (aCallback) {
841
// We ignore errors from either of these methods in case old JS consumers
842
// don't implement them (in which case they will get error/result
843
// notifications as normal).
844
Unused << aCallback->GetIgnoreErrors(&ignoreErrors);
845
Unused << aCallback->GetIgnoreResults(&ignoreResults);
846
}
847
RefPtr<InsertVisitedURIs> event = new InsertVisitedURIs(
848
aConnection, aPlaces, callback, aGroupNotifications, ignoreErrors,
849
ignoreResults, aInitialUpdatedCount);
850
851
// Get the target thread, and then start the work!
852
nsCOMPtr<nsIEventTarget> target = do_GetInterface(aConnection);
853
NS_ENSURE_TRUE(target, NS_ERROR_UNEXPECTED);
854
nsresult rv = target->Dispatch(event, NS_DISPATCH_NORMAL);
855
NS_ENSURE_SUCCESS(rv, rv);
856
857
return NS_OK;
858
}
859
860
NS_IMETHOD Run() override {
861
MOZ_ASSERT(!NS_IsMainThread(),
862
"This should not be called on the main thread");
863
864
// The inner run method may bail out at any point, so we ensure we do
865
// whatever we can and then notify the main thread we're done.
866
nsresult rv = InnerRun();
867
868
if (mSuccessfulUpdatedCount > 0 && mGroupNotifications) {
869
NS_DispatchToMainThread(new NotifyManyFrecenciesChanged());
870
}
871
if (!!mCallback) {
872
NS_DispatchToMainThread(
873
new NotifyCompletion(mCallback, mSuccessfulUpdatedCount));
874
}
875
return rv;
876
}
877
878
nsresult InnerRun() {
879
// Prevent the main thread from shutting down while this is running.
880
MutexAutoLock lockedScope(mHistory->GetShutdownMutex());
881
if (mHistory->IsShuttingDown()) {
882
// If we were already shutting down, we cannot insert the URIs.
883
return NS_OK;
884
}
885
886
mozStorageTransaction transaction(
887
mDBConn, false, mozIStorageConnection::TRANSACTION_IMMEDIATE);
888
889
const VisitData* lastFetchedPlace = nullptr;
890
uint32_t lastFetchedVisitCount = 0;
891
bool shouldChunkNotifications = mPlaces.Length() > NOTIFY_VISITS_CHUNK_SIZE;
892
nsTArray<VisitData> notificationChunk;
893
if (shouldChunkNotifications) {
894
notificationChunk.SetCapacity(NOTIFY_VISITS_CHUNK_SIZE);
895
}
896
for (nsTArray<VisitData>::size_type i = 0; i < mPlaces.Length(); i++) {
897
VisitData& place = mPlaces.ElementAt(i);
898
899
// Fetching from the database can overwrite this information, so save it
900
// apart.
901
bool typed = place.typed;
902
bool hidden = place.hidden;
903
904
// We can avoid a database lookup if it's the same place as the last
905
// visit we added.
906
bool known =
907
lastFetchedPlace && lastFetchedPlace->spec.Equals(place.spec);
908
if (!known) {
909
nsresult rv = mHistory->FetchPageInfo(place, &known);
910
if (NS_FAILED(rv)) {
911
if (!!mCallback && !mIgnoreErrors) {
912
nsCOMPtr<nsIRunnable> event =
913
new NotifyPlaceInfoCallback(mCallback, place, true, rv);
914
return NS_DispatchToMainThread(event);
915
}
916
return NS_OK;
917
}
918
lastFetchedPlace = &mPlaces.ElementAt(i);
919
lastFetchedVisitCount = lastFetchedPlace->visitCount;
920
} else {
921
// Copy over the data from the already known place.
922
place.placeId = lastFetchedPlace->placeId;
923
place.guid = lastFetchedPlace->guid;
924
place.lastVisitId = lastFetchedPlace->visitId;
925
place.lastVisitTime = lastFetchedPlace->visitTime;
926
if (!place.title.IsVoid()) {
927
place.titleChanged = !lastFetchedPlace->title.Equals(place.title);
928
}
929
place.frecency = lastFetchedPlace->frecency;
930
// Add one visit for the previous loop.
931
place.visitCount = ++lastFetchedVisitCount;
932
}
933
934
// If any transition is typed, ensure the page is marked as typed.
935
if (typed != lastFetchedPlace->typed) {
936
place.typed = true;
937
}
938
939
// If any transition is visible, ensure the page is marked as visible.
940
if (hidden != lastFetchedPlace->hidden) {
941
place.hidden = false;
942
}
943
944
// If this is a new page, or the existing page was already visible,
945
// there's no need to try to unhide it.
946
if (!known || !lastFetchedPlace->hidden) {
947
place.shouldUpdateHidden = false;
948
}
949
950
FetchReferrerInfo(place);
951
952
nsresult rv = DoDatabaseInserts(known, place);
953
if (!!mCallback) {
954
// Check if consumers wanted to be notified about success/failure,
955
// depending on whether this action succeeded or not.
956
if ((NS_SUCCEEDED(rv) && !mIgnoreResults) ||
957
(NS_FAILED(rv) && !mIgnoreErrors)) {
958
nsCOMPtr<nsIRunnable> event =
959
new NotifyPlaceInfoCallback(mCallback, place, true, rv);
960
nsresult rv2 = NS_DispatchToMainThread(event);
961
NS_ENSURE_SUCCESS(rv2, rv2);
962
}
963
}
964
NS_ENSURE_SUCCESS(rv, rv);
965
966
if (shouldChunkNotifications) {
967
int32_t numRemaining = mPlaces.Length() - (i + 1);
968
notificationChunk.AppendElement(place);
969
if (notificationChunk.Length() == NOTIFY_VISITS_CHUNK_SIZE ||
970
numRemaining == 0) {
971
// This will SwapElements on notificationChunk with an empty nsTArray
972
nsCOMPtr<nsIRunnable> event =
973
new NotifyManyVisitsObservers(notificationChunk);
974
rv = NS_DispatchToMainThread(event);
975
NS_ENSURE_SUCCESS(rv, rv);
976
977
int32_t nextCapacity =
978
std::min(NOTIFY_VISITS_CHUNK_SIZE, numRemaining);
979
notificationChunk.SetCapacity(nextCapacity);
980
}
981
}
982
983
// If we get here, we must have been successful adding/updating this
984
// visit/place, so update the count:
985
mSuccessfulUpdatedCount++;
986
}
987
988
{
989
// Trigger insertions for all the new origins of the places we inserted.
990
nsAutoCString query("DELETE FROM moz_updateoriginsinsert_temp");
991
nsCOMPtr<mozIStorageStatement> stmt = mHistory->GetStatement(query);
992
NS_ENSURE_STATE(stmt);
993
mozStorageStatementScoper scoper(stmt);
994
nsresult rv = stmt->Execute();
995
NS_ENSURE_SUCCESS(rv, rv);
996
}
997
998
{
999
// Trigger frecency updates for all those origins.
1000
nsAutoCString query("DELETE FROM moz_updateoriginsupdate_temp");
1001
nsCOMPtr<mozIStorageStatement> stmt = mHistory->GetStatement(query);
1002
NS_ENSURE_STATE(stmt);
1003
mozStorageStatementScoper scoper(stmt);
1004
nsresult rv = stmt->Execute();
1005
NS_ENSURE_SUCCESS(rv, rv);
1006
}
1007
1008
nsresult rv = transaction.Commit();
1009
NS_ENSURE_SUCCESS(rv, rv);
1010
1011
// If we don't need to chunk the notifications, just notify using the
1012
// original mPlaces array.
1013
if (!shouldChunkNotifications) {
1014
nsCOMPtr<nsIRunnable> event = new NotifyManyVisitsObservers(mPlaces);
1015
rv = NS_DispatchToMainThread(event);
1016
NS_ENSURE_SUCCESS(rv, rv);
1017
}
1018
1019
return NS_OK;
1020
}
1021
1022
private:
1023
InsertVisitedURIs(
1024
mozIStorageConnection* aConnection, nsTArray<VisitData>& aPlaces,
1025
const nsMainThreadPtrHandle<mozIVisitInfoCallback>& aCallback,
1026
bool aGroupNotifications, bool aIgnoreErrors, bool aIgnoreResults,
1027
uint32_t aInitialUpdatedCount)
1028
: Runnable("places::InsertVisitedURIs"),
1029
mDBConn(aConnection),
1030
mCallback(aCallback),
1031
mGroupNotifications(aGroupNotifications),
1032
mIgnoreErrors(aIgnoreErrors),
1033
mIgnoreResults(aIgnoreResults),
1034
mSuccessfulUpdatedCount(aInitialUpdatedCount),
1035
mHistory(History::GetService()) {
1036
MOZ_ASSERT(NS_IsMainThread(), "This should be called on the main thread");
1037
1038
mPlaces.SwapElements(aPlaces);
1039
1040
#ifdef DEBUG
1041
for (nsTArray<VisitData>::size_type i = 0; i < mPlaces.Length(); i++) {
1042
nsCOMPtr<nsIURI> uri;
1043
MOZ_ASSERT(NS_SUCCEEDED(NS_NewURI(getter_AddRefs(uri), mPlaces[i].spec)));
1044
MOZ_ASSERT(CanAddURI(uri),
1045
"Passed a VisitData with a URI we cannot add to history!");
1046
}
1047
#endif
1048
}
1049
1050
/**
1051
* Inserts or updates the entry in moz_places for this visit, adds the visit,
1052
* and updates the frecency of the place.
1053
*
1054
* @param aKnown
1055
* True if we already have an entry for this place in moz_places, false
1056
* otherwise.
1057
* @param aPlace
1058
* The place we are adding a visit for.
1059
*/
1060
nsresult DoDatabaseInserts(bool aKnown, VisitData& aPlace) {
1061
MOZ_ASSERT(!NS_IsMainThread(),
1062
"This should not be called on the main thread");
1063
1064
// If the page was in moz_places, we need to update the entry.
1065
nsresult rv;
1066
if (aKnown) {
1067
rv = mHistory->UpdatePlace(aPlace);
1068
NS_ENSURE_SUCCESS(rv, rv);
1069
}
1070
// Otherwise, the page was not in moz_places, so now we have to add it.
1071
else {
1072
rv = mHistory->InsertPlace(aPlace, !mGroupNotifications);
1073
NS_ENSURE_SUCCESS(rv, rv);
1074
aPlace.placeId = nsNavHistory::sLastInsertedPlaceId;
1075
}
1076
MOZ_ASSERT(aPlace.placeId > 0);
1077
1078
rv = AddVisit(aPlace);
1079
NS_ENSURE_SUCCESS(rv, rv);
1080
1081
// TODO (bug 623969) we shouldn't update this after each visit, but
1082
// rather only for each unique place to save disk I/O.
1083
1084
// Don't update frecency if the page should not appear in autocomplete.
1085
if (aPlace.shouldUpdateFrecency) {
1086
rv = UpdateFrecency(aPlace);
1087
NS_ENSURE_SUCCESS(rv, rv);
1088
}
1089
1090
return NS_OK;
1091
}
1092
1093
/**
1094
* Fetches information about a referrer for aPlace if it was a recent
1095
* visit or not.
1096
*
1097
* @param aPlace
1098
* The VisitData for the visit we will eventually add.
1099
*
1100
*/
1101
void FetchReferrerInfo(VisitData& aPlace) {
1102
if (aPlace.referrerSpec.IsEmpty()) {
1103
return;
1104
}
1105
1106
VisitData referrer;
1107
referrer.spec = aPlace.referrerSpec;
1108
// If the referrer is the same as the page, we don't need to fetch it.
1109
if (aPlace.referrerSpec.Equals(aPlace.spec)) {
1110
referrer = aPlace;
1111
// The page last visit id is also the referrer visit id.
1112
aPlace.referrerVisitId = aPlace.lastVisitId;
1113
} else {
1114
bool exists = false;
1115
if (NS_SUCCEEDED(mHistory->FetchPageInfo(referrer, &exists)) && exists) {
1116
// Copy the referrer last visit id.
1117
aPlace.referrerVisitId = referrer.lastVisitId;
1118
}
1119
}
1120
1121
// Check if the page has effectively been visited recently, otherwise
1122
// discard the referrer info.
1123
if (!aPlace.referrerVisitId || !referrer.lastVisitTime ||
1124
aPlace.visitTime - referrer.lastVisitTime > RECENT_EVENT_THRESHOLD) {
1125
// We will not be using the referrer data.
1126
aPlace.referrerSpec.Truncate();
1127
aPlace.referrerVisitId = 0;
1128
}
1129
}
1130
1131
/**
1132
* Adds a visit for _place and updates it with the right visit id.
1133
*
1134
* @param _place
1135
* The VisitData for the place we need to know visit information about.
1136
*/
1137
nsresult AddVisit(VisitData& _place) {
1138
MOZ_ASSERT(_place.placeId > 0);
1139
1140
nsresult rv;
1141
nsCOMPtr<mozIStorageStatement> stmt;
1142
stmt = mHistory->GetStatement(
1143
"INSERT INTO moz_historyvisits "
1144
"(from_visit, place_id, visit_date, visit_type, session) "
1145
"VALUES (:from_visit, :page_id, :visit_date, :visit_type, 0) ");
1146
NS_ENSURE_STATE(stmt);
1147
mozStorageStatementScoper scoper(stmt);
1148
1149
rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("page_id"), _place.placeId);
1150
NS_ENSURE_SUCCESS(rv, rv);
1151
rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("from_visit"),
1152
_place.referrerVisitId);
1153
NS_ENSURE_SUCCESS(rv, rv);
1154
rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("visit_date"),
1155
_place.visitTime);
1156
NS_ENSURE_SUCCESS(rv, rv);
1157
uint32_t transitionType = _place.transitionType;
1158
MOZ_ASSERT(transitionType >= nsINavHistoryService::TRANSITION_LINK &&
1159
transitionType <= nsINavHistoryService::TRANSITION_RELOAD,
1160
"Invalid transition type!");
1161
rv =
1162
stmt->BindInt32ByName(NS_LITERAL_CSTRING("visit_type"), transitionType);
1163
NS_ENSURE_SUCCESS(rv, rv);
1164
1165
rv = stmt->Execute();
1166
NS_ENSURE_SUCCESS(rv, rv);
1167
1168
_place.visitId = nsNavHistory::sLastInsertedVisitId;
1169
MOZ_ASSERT(_place.visitId > 0);
1170
1171
return NS_OK;
1172
}
1173
1174
/**
1175
* Updates the frecency, and possibly the hidden-ness of aPlace.
1176
*
1177
* @param aPlace
1178
* The VisitData for the place we want to update.
1179
*/
1180
nsresult UpdateFrecency(const VisitData& aPlace) {
1181
MOZ_ASSERT(aPlace.shouldUpdateFrecency);
1182
MOZ_ASSERT(aPlace.placeId > 0);
1183
1184
nsresult rv;
1185
{ // First, set our frecency to the proper value.
1186
nsCOMPtr<mozIStorageStatement> stmt;
1187
if (!mGroupNotifications) {
1188
// If we're notifying for individual frecency updates, use
1189
// the notify_frecency sql function which will call us back.
1190
stmt = mHistory->GetStatement(
1191
"UPDATE moz_places "
1192
"SET frecency = NOTIFY_FRECENCY("
1193
"CALCULATE_FRECENCY(:page_id, :redirect), "
1194
"url, guid, hidden, last_visit_date"
1195
") "
1196
"WHERE id = :page_id");
1197
} else {
1198
// otherwise, just update the frecency without notifying.
1199
stmt = mHistory->GetStatement(
1200
"UPDATE moz_places "
1201
"SET frecency = CALCULATE_FRECENCY(:page_id, :redirect) "
1202
"WHERE id = :page_id");
1203
}
1204
NS_ENSURE_STATE(stmt);
1205
mozStorageStatementScoper scoper(stmt);
1206
1207
rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("page_id"), aPlace.placeId);
1208
NS_ENSURE_SUCCESS(rv, rv);
1209
rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("redirect"),
1210
aPlace.useFrecencyRedirectBonus);
1211
NS_ENSURE_SUCCESS(rv, rv);
1212
1213
rv = stmt->Execute();
1214
NS_ENSURE_SUCCESS(rv, rv);
1215
}
1216
1217
if (!aPlace.hidden && aPlace.shouldUpdateHidden) {
1218
// Mark the page as not hidden if the frecency is now nonzero.
1219
nsCOMPtr<mozIStorageStatement> stmt;
1220
stmt = mHistory->GetStatement(
1221
"UPDATE moz_places "
1222
"SET hidden = 0 "
1223
"WHERE id = :page_id AND frecency <> 0");
1224
NS_ENSURE_STATE(stmt);
1225
mozStorageStatementScoper scoper(stmt);
1226
1227
rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("page_id"), aPlace.placeId);
1228
NS_ENSURE_SUCCESS(rv, rv);
1229
1230
rv = stmt->Execute();
1231
NS_ENSURE_SUCCESS(rv, rv);
1232
}
1233
1234
return NS_OK;
1235
}
1236
1237
mozIStorageConnection* mDBConn;
1238
1239
nsTArray<VisitData> mPlaces;
1240
1241
nsMainThreadPtrHandle<mozIVisitInfoCallback> mCallback;
1242
1243
bool mGroupNotifications;
1244
1245
bool mIgnoreErrors;
1246
1247
bool mIgnoreResults;
1248
1249
uint32_t mSuccessfulUpdatedCount;
1250
1251
/**
1252
* Strong reference to the History object because we do not want it to
1253
* disappear out from under us.
1254
*/
1255
RefPtr<History> mHistory;
1256
};
1257
1258
/**
1259
* Sets the page title for a page in moz_places (if necessary).
1260
*/
1261
class SetPageTitle : public Runnable {
1262
public:
1263
/**
1264
* Sets a pages title in the database asynchronously.
1265
*
1266
* @param aConnection
1267
* The database connection to use for this operation.
1268
* @param aURI
1269
* The URI to set the page title on.
1270
* @param aTitle
1271
* The title to set for the page, if the page exists.
1272
*/
1273
static nsresult Start(mozIStorageConnection* aConnection, nsIURI* aURI,
1274
const nsAString& aTitle) {
1275
MOZ_ASSERT(NS_IsMainThread(), "This should be called on the main thread");
1276
MOZ_ASSERT(aURI, "Must pass a non-null URI object!");
1277
1278
nsCString spec;
1279
nsresult rv = aURI->GetSpec(spec);
1280
NS_ENSURE_SUCCESS(rv, rv);
1281
1282
RefPtr<SetPageTitle> event = new SetPageTitle(spec, aTitle);
1283
1284
// Get the target thread, and then start the work!
1285
nsCOMPtr<nsIEventTarget> target = do_GetInterface(aConnection);
1286
NS_ENSURE_TRUE(target, NS_ERROR_UNEXPECTED);
1287
rv = target->Dispatch(event, NS_DISPATCH_NORMAL);
1288
NS_ENSURE_SUCCESS(rv, rv);
1289
1290
return NS_OK;
1291
}
1292
1293
NS_IMETHOD Run() override {
1294
MOZ_ASSERT(!NS_IsMainThread(),
1295
"This should not be called on the main thread");
1296
1297
// First, see if the page exists in the database (we'll need its id later).
1298
bool exists;
1299
nsresult rv = mHistory->FetchPageInfo(mPlace, &exists);
1300
NS_ENSURE_SUCCESS(rv, rv);
1301
1302
if (!exists || !mPlace.titleChanged) {
1303
// We have no record of this page, or we have no title change, so there
1304
// is no need to do any further work.
1305
return NS_OK;
1306
}
1307
1308
MOZ_ASSERT(mPlace.placeId > 0, "We somehow have an invalid place id here!");
1309
1310
// Now we can update our database record.
1311
nsCOMPtr<mozIStorageStatement> stmt = mHistory->GetStatement(
1312
"UPDATE moz_places "
1313
"SET title = :page_title "
1314
"WHERE id = :page_id ");
1315
NS_ENSURE_STATE(stmt);
1316
1317
{
1318
mozStorageStatementScoper scoper(stmt);
1319
rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("page_id"), mPlace.placeId);
1320
NS_ENSURE_SUCCESS(rv, rv);
1321
// Empty strings should clear the title, just like
1322
// nsNavHistory::SetPageTitle.
1323
if (mPlace.title.IsEmpty()) {
1324
rv = stmt->BindNullByName(NS_LITERAL_CSTRING("page_title"));
1325
} else {
1326
rv = stmt->BindStringByName(NS_LITERAL_CSTRING("page_title"),
1327
StringHead(mPlace.title, TITLE_LENGTH_MAX));
1328
}
1329
NS_ENSURE_SUCCESS(rv, rv);
1330
rv = stmt->Execute();
1331
NS_ENSURE_SUCCESS(rv, rv);
1332
}
1333
1334
nsCOMPtr<nsIRunnable> event =
1335
new NotifyTitleObservers(mPlace.spec, mPlace.title, mPlace.guid);
1336
rv = NS_DispatchToMainThread(event);
1337
NS_ENSURE_SUCCESS(rv, rv);
1338
1339
return NS_OK;
1340
}
1341
1342
private:
1343
SetPageTitle(const nsCString& aSpec, const nsAString& aTitle)
1344
: Runnable("places::SetPageTitle"), mHistory(History::GetService()) {
1345
mPlace.spec = aSpec;
1346
mPlace.title = aTitle;
1347
}
1348
1349
VisitData mPlace;
1350
1351
/**
1352
* Strong reference to the History object because we do not want it to
1353
* disappear out from under us.
1354
*/
1355
RefPtr<History> mHistory;
1356
};
1357
1358
/**
1359
* Stores an embed visit, and notifies observers.
1360
*
1361
* @param aPlace
1362
* The VisitData of the visit to store as an embed visit.
1363
* @param [optional] aCallback
1364
* The mozIVisitInfoCallback to notify, if provided.
1365
*
1366
* FIXME(emilio, bug 1595484): We should get rid of EMBED visits completely.
1367
*/
1368
void NotifyEmbedVisit(VisitData& aPlace,
1369
mozIVisitInfoCallback* aCallback = nullptr) {
1370
MOZ_ASSERT(aPlace.transitionType == nsINavHistoryService::TRANSITION_EMBED,
1371
"Must only pass TRANSITION_EMBED visits to this!");
1372
MOZ_ASSERT(NS_IsMainThread(), "Must be called on the main thread!");
1373
1374
nsCOMPtr<nsIURI> uri;
1375
MOZ_ALWAYS_SUCCEEDS(NS_NewURI(getter_AddRefs(uri), aPlace.spec));
1376
1377
if (!uri) {
1378
return;
1379
}
1380
1381
if (!!aCallback) {
1382
nsMainThreadPtrHandle<mozIVisitInfoCallback> callback(
1383
new nsMainThreadPtrHolder<mozIVisitInfoCallback>(
1384
"mozIVisitInfoCallback", aCallback));
1385
bool ignoreResults = false;
1386
Unused << aCallback->GetIgnoreResults(&ignoreResults);
1387
if (!ignoreResults) {
1388
nsCOMPtr<nsIRunnable> event =
1389
new NotifyPlaceInfoCallback(callback, aPlace, true, NS_OK);
1390
(void)NS_DispatchToMainThread(event);
1391
}
1392
}
1393
1394
nsCOMPtr<nsIRunnable> event = new NotifyManyVisitsObservers(aPlace);
1395
(void)NS_DispatchToMainThread(event);
1396
}
1397
1398
////////////////////////////////////////////////////////////////////////////////
1399
//// History
1400
1401
History* History::gService = nullptr;
1402
1403
History::History()
1404
: mShuttingDown(false),
1405
mShutdownMutex("History::mShutdownMutex"),
1406
mRecentlyVisitedURIs(RECENTLY_VISITED_URIS_SIZE) {
1407
NS_ASSERTION(!gService, "Ruh-roh! This service has already been created!");
1408
if (XRE_IsParentProcess()) {
1409
nsCOMPtr<nsIProperties> dirsvc = services::GetDirectoryService();
1410
bool haveProfile = false;
1411
MOZ_RELEASE_ASSERT(
1412
dirsvc &&
1413
NS_SUCCEEDED(
1414
dirsvc->Has(NS_APP_USER_PROFILE_50_DIR, &haveProfile)) &&
1415
haveProfile,
1416
"Can't construct history service if there is no profile.");
1417
}
1418
gService = this;
1419
1420
nsCOMPtr<nsIObserverService> os = services::GetObserverService();
1421
NS_WARNING_ASSERTION(os, "Observer service was not found!");
1422
if (os) {
1423
(void)os->AddObserver(this, TOPIC_PLACES_SHUTDOWN, false);
1424
}
1425
}
1426
1427
History::~History() {
1428
UnregisterWeakMemoryReporter(this);
1429
1430
MOZ_ASSERT(gService == this);
1431
gService = nullptr;
1432
}
1433
1434
void History::InitMemoryReporter() { RegisterWeakMemoryReporter(this); }
1435
1436
void History::NotifyVisitedParent(const nsTArray<VisitedQueryResult>& aURIs) {
1437
MOZ_ASSERT(XRE_IsParentProcess());
1438
nsTArray<ContentParent*> cplist;
1439
ContentParent::GetAll(cplist);
1440
1441
for (auto* cp : cplist) {
1442
Unused << cp->SendNotifyVisited(aURIs);
1443
}
1444
}
1445
1446
class ConcurrentStatementsHolder final : public mozIStorageCompletionCallback {
1447
public:
1448
NS_DECL_ISUPPORTS
1449
1450
explicit ConcurrentStatementsHolder(mozIStorageConnection* aDBConn)
1451
: mShutdownWasInvoked(false) {
1452
DebugOnly<nsresult> rv = aDBConn->AsyncClone(true, this);
1453
MOZ_ASSERT(NS_SUCCEEDED(rv));
1454
}
1455
1456
NS_IMETHOD Complete(nsresult aStatus, nsISupports* aConnection) override {
1457
if (NS_FAILED(aStatus)) {
1458
return NS_OK;
1459
}
1460
mReadOnlyDBConn = do_QueryInterface(aConnection);
1461
// It's possible Shutdown was invoked before we were handed back the
1462
// cloned connection handle.
1463
if (mShutdownWasInvoked) {
1464
Shutdown();
1465
return NS_OK;
1466
}
1467
1468
// Now we can create our cached statements.
1469
1470
if (!mIsVisitedStatement) {
1471
(void)mReadOnlyDBConn->CreateAsyncStatement(
1472
NS_LITERAL_CSTRING("SELECT 1 FROM moz_places h "
1473
"WHERE url_hash = hash(?1) AND url = ?1 AND "
1474
"last_visit_date NOTNULL "),
1475
getter_AddRefs(mIsVisitedStatement));
1476
MOZ_ASSERT(mIsVisitedStatement);
1477
auto queries = std::move(mVisitedQueries);
1478
if (mIsVisitedStatement) {
1479
for (auto& query : queries) {
1480
query->Execute(*mIsVisitedStatement);
1481
}
1482
}
1483
}
1484
1485
return NS_OK;
1486
}
1487
1488
void QueueVisitedStatement(RefPtr<VisitedQuery> aCallback) {
1489
if (mIsVisitedStatement) {
1490
aCallback->Execute(*mIsVisitedStatement);
1491
} else {
1492
mVisitedQueries.AppendElement(std::move(aCallback));
1493
}
1494
}
1495
1496
void Shutdown() {
1497
mShutdownWasInvoked = true;
1498
if (mReadOnlyDBConn) {
1499
mVisitedQueries.Clear();
1500
DebugOnly<nsresult> rv;
1501
if (mIsVisitedStatement) {
1502
rv = mIsVisitedStatement->Finalize();
1503
MOZ_ASSERT(NS_SUCCEEDED(rv));
1504
}
1505
rv = mReadOnlyDBConn->AsyncClose(nullptr);
1506
MOZ_ASSERT(NS_SUCCEEDED(rv));
1507
mReadOnlyDBConn = nullptr;
1508
}
1509
}
1510
1511
private:
1512
~ConcurrentStatementsHolder() {}
1513
1514
nsCOMPtr<mozIStorageAsyncConnection> mReadOnlyDBConn;
1515
nsCOMPtr<mozIStorageAsyncStatement> mIsVisitedStatement;
1516
nsTArray<RefPtr<VisitedQuery>> mVisitedQueries;
1517
bool mShutdownWasInvoked;
1518
};
1519
1520
NS_IMPL_ISUPPORTS(ConcurrentStatementsHolder, mozIStorageCompletionCallback)
1521
1522
nsresult History::QueueVisitedStatement(RefPtr<VisitedQuery> aQuery) {
1523
MOZ_ASSERT(NS_IsMainThread());
1524
if (mShuttingDown) {
1525
return NS_ERROR_NOT_AVAILABLE;
1526
}
1527
1528
if (!mConcurrentStatementsHolder) {
1529
mozIStorageConnection* dbConn = GetDBConn();
1530
NS_ENSURE_STATE(dbConn);
1531
mConcurrentStatementsHolder = new ConcurrentStatementsHolder(dbConn);
1532
}
1533
mConcurrentStatementsHolder->QueueVisitedStatement(std::move(aQuery));
1534
return NS_OK;
1535
}
1536
1537
nsresult History::InsertPlace(VisitData& aPlace,
1538
bool aShouldNotifyFrecencyChanged) {
1539
MOZ_ASSERT(aPlace.placeId == 0, "should not have a valid place id!");
1540
MOZ_ASSERT(!aPlace.shouldUpdateHidden, "We should not need to update hidden");
1541
MOZ_ASSERT(!NS_IsMainThread(), "must be called off of the main thread!");
1542
1543
nsCOMPtr<mozIStorageStatement> stmt = GetStatement(
1544
"INSERT INTO moz_places "
1545
"(url, url_hash, title, rev_host, hidden, typed, frecency, guid) "
1546
"VALUES (:url, hash(:url), :title, :rev_host, :hidden, :typed, "
1547
":frecency, :guid) ");
1548
NS_ENSURE_STATE(stmt);
1549
mozStorageStatementScoper scoper(stmt);
1550
1551
nsresult rv =
1552
stmt->BindStringByName(NS_LITERAL_CSTRING("rev_host"), aPlace.revHost);
1553
NS_ENSURE_SUCCESS(rv, rv);
1554
rv = URIBinder::Bind(stmt, NS_LITERAL_CSTRING("url"), aPlace.spec);
1555
NS_ENSURE_SUCCESS(rv, rv);
1556
nsString title = aPlace.title;
1557
// Empty strings should have no title, just like nsNavHistory::SetPageTitle.
1558
if (title.IsEmpty()) {
1559
rv = stmt->BindNullByName(NS_LITERAL_CSTRING("title"));
1560
} else {
1561
title.Assign(StringHead(aPlace.title, TITLE_LENGTH_MAX));
1562
rv = stmt->BindStringByName(NS_LITERAL_CSTRING("title"), title);
1563
}
1564
NS_ENSURE_SUCCESS(rv, rv);
1565
rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("typed"), aPlace.typed);
1566
NS_ENSURE_SUCCESS(rv, rv);
1567
// When inserting a page for a first visit that should not appear in
1568
// autocomplete, for example an error page, use a zero frecency.
1569
int32_t frecency = aPlace.shouldUpdateFrecency ? aPlace.frecency : 0;
1570
rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("frecency"), frecency);
1571
NS_ENSURE_SUCCESS(rv, rv);
1572
rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("hidden"), aPlace.hidden);
1573
NS_ENSURE_SUCCESS(rv, rv);
1574
if (aPlace.guid.IsVoid()) {
1575
rv = GenerateGUID(aPlace.guid);
1576
NS_ENSURE_SUCCESS(rv, rv);
1577
}
1578
rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("guid"), aPlace.guid);
1579
NS_ENSURE_SUCCESS(rv, rv);
1580
rv = stmt->Execute();
1581
NS_ENSURE_SUCCESS(rv, rv);
1582
1583
// Post an onFrecencyChanged observer notification.
1584
if (aShouldNotifyFrecencyChanged) {
1585
const nsNavHistory* navHistory = nsNavHistory::GetConstHistoryService();
1586
NS_ENSURE_STATE(navHistory);
1587
navHistory->DispatchFrecencyChangedNotification(
1588
aPlace.spec, frecency, aPlace.guid, aPlace.hidden, aPlace.visitTime);
1589
}
1590
1591
return NS_OK;
1592
}
1593
1594
nsresult History::UpdatePlace(const VisitData& aPlace) {
1595
MOZ_ASSERT(!NS_IsMainThread(), "must be called off of the main thread!");
1596
MOZ_ASSERT(aPlace.placeId > 0, "must have a valid place id!");
1597
MOZ_ASSERT(!aPlace.guid.IsVoid(), "must have a guid!");
1598
1599
nsCOMPtr<mozIStorageStatement> stmt;
1600
bool titleIsVoid = aPlace.title.IsVoid();
1601
if (titleIsVoid) {
1602
// Don't change the title.
1603
stmt = GetStatement(
1604
"UPDATE moz_places "
1605
"SET hidden = :hidden, "
1606
"typed = :typed, "
1607
"guid = :guid "
1608
"WHERE id = :page_id ");
1609
} else {
1610
stmt = GetStatement(
1611
"UPDATE moz_places "
1612
"SET title = :title, "
1613
"hidden = :hidden, "
1614
"typed = :typed, "
1615
"guid = :guid "
1616
"WHERE id = :page_id ");
1617
}
1618
NS_ENSURE_STATE(stmt);
1619
mozStorageStatementScoper scoper(stmt);
1620
1621
nsresult rv;
1622
if (!titleIsVoid) {
1623
// An empty string clears the title.
1624
if (aPlace.title.IsEmpty()) {
1625
rv = stmt->BindNullByName(NS_LITERAL_CSTRING("title"));
1626
} else {
1627
rv = stmt->BindStringByName(NS_LITERAL_CSTRING("title"),
1628
StringHead(aPlace.title, TITLE_LENGTH_MAX));
1629
}
1630
NS_ENSURE_SUCCESS(rv, rv);
1631
}
1632
rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("typed"), aPlace.typed);
1633
NS_ENSURE_SUCCESS(rv, rv);
1634
rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("hidden"), aPlace.hidden);
1635
NS_ENSURE_SUCCESS(rv, rv);
1636
rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("guid"), aPlace.guid);
1637
NS_ENSURE_SUCCESS(rv, rv);
1638
rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("page_id"), aPlace.placeId);
1639
NS_ENSURE_SUCCESS(rv, rv);
1640
rv = stmt->Execute();
1641
NS_ENSURE_SUCCESS(rv, rv);
1642
1643
return NS_OK;
1644
}
1645
1646
nsresult History::FetchPageInfo(VisitData& _place, bool* _exists) {
1647
MOZ_ASSERT(!_place.spec.IsEmpty() || !_place.guid.IsEmpty(),
1648
"must have either a non-empty spec or guid!");
1649
MOZ_ASSERT(!NS_IsMainThread(), "must be called off of the main thread!");
1650
1651
nsresult rv;
1652
1653
// URI takes precedence.
1654
nsCOMPtr<mozIStorageStatement> stmt;
1655
bool selectByURI = !_place.spec.IsEmpty();
1656
if (selectByURI) {
1657
stmt = GetStatement(
1658
"SELECT guid, id, title, hidden, typed, frecency, visit_count, "
1659
"last_visit_date, "
1660
"(SELECT id FROM moz_historyvisits "
1661
"WHERE place_id = h.id AND visit_date = h.last_visit_date) AS "
1662
"last_visit_id "
1663
"FROM moz_places h "
1664
"WHERE url_hash = hash(:page_url) AND url = :page_url ");
1665
NS_ENSURE_STATE(stmt);
1666
1667
rv = URIBinder::Bind(stmt, NS_LITERAL_CSTRING("page_url"), _place.spec);
1668
NS_ENSURE_SUCCESS(rv, rv);
1669
} else {
1670
stmt = GetStatement(
1671
"SELECT url, id, title, hidden, typed, frecency, visit_count, "
1672
"last_visit_date, "
1673
"(SELECT id FROM moz_historyvisits "
1674
"WHERE place_id = h.id AND visit_date = h.last_visit_date) AS "
1675
"last_visit_id "
1676
"FROM moz_places h "
1677
"WHERE guid = :guid ");
1678
NS_ENSURE_STATE(stmt);
1679
1680
rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("guid"), _place.guid);
1681
NS_ENSURE_SUCCESS(rv, rv);
1682
}
1683
1684
mozStorageStatementScoper scoper(stmt);
1685
1686
rv = stmt->ExecuteStep(_exists);
1687
NS_ENSURE_SUCCESS(rv, rv);
1688
1689
if (!*_exists) {
1690
return NS_OK;
1691
}
1692
1693
if (selectByURI) {
1694
if (_place.guid.IsEmpty()) {
1695
rv = stmt->GetUTF8String(0, _place.guid);
1696
NS_ENSURE_SUCCESS(rv, rv);
1697
}
1698
} else {
1699
nsAutoCString spec;
1700
rv = stmt->GetUTF8String(0, spec);
1701
NS_ENSURE_SUCCESS(rv, rv);
1702
_place.spec = spec;
1703
}
1704
1705
rv = stmt->GetInt64(1, &_place.placeId);
1706
NS_ENSURE_SUCCESS(rv, rv);
1707
1708
nsAutoString title;
1709
rv = stmt->GetString(2, title);
1710
NS_ENSURE_SUCCESS(rv, rv);
1711
1712
// If the title we were given was void, that means we did not bother to set
1713
// it to anything. As a result, ignore the fact that we may have changed the
1714
// title (because we don't want to, that would be empty), and set the title
1715
// to what is currently stored in the datbase.
1716
if (_place.title.IsVoid()) {
1717
_place.title = title;
1718
}
1719
// Otherwise, just indicate if the title has changed.
1720
else {
1721
_place.titleChanged = !(_place.title.Equals(title)) &&
1722
!(_place.title.IsEmpty() && title.IsVoid());
1723
}
1724
1725
int32_t hidden;
1726
rv = stmt->GetInt32(3, &hidden);
1727
NS_ENSURE_SUCCESS(rv, rv);
1728
_place.hidden = !!hidden;
1729
1730
int32_t typed;
1731
rv = stmt->GetInt32(4, &typed);
1732
NS_ENSURE_SUCCESS(rv, rv);
1733
_place.typed = !!typed;
1734
1735
rv = stmt->GetInt32(5, &_place.frecency);
1736
NS_ENSURE_SUCCESS(rv, rv);
1737
int32_t visitCount;
1738
rv = stmt->GetInt32(6, &visitCount);
1739
NS_ENSURE_SUCCESS(rv, rv);
1740
_place.visitCount = visitCount;
1741
rv = stmt->GetInt64(7, &_place.lastVisitTime);
1742
NS_ENSURE_SUCCESS(rv, rv);
1743
rv = stmt->GetInt64(8, &_place.lastVisitId);
1744
NS_ENSURE_SUCCESS(rv, rv);
1745
1746
return NS_OK;
1747
}
1748
1749
MOZ_DEFINE_MALLOC_SIZE_OF(HistoryMallocSizeOf)
1750
1751
NS_IMETHODIMP
1752
History::CollectReports(nsIHandleReportCallback* aHandleReport,
1753
nsISupports* aData, bool aAnonymize) {
1754
MOZ_COLLECT_REPORT(
1755
"explicit/history-links-hashtable", KIND_HEAP, UNITS_BYTES,
1756
SizeOfIncludingThis(HistoryMallocSizeOf),
1757
"Memory used by the hashtable that records changes to the visited state "
1758
"of links.");
1759
1760
return NS_OK;
1761
}
1762
1763
size_t History::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) {
1764
size_t size = aMallocSizeOf(this);
1765
size += mTrackedURIs.ShallowSizeOfExcludingThis(aMallocSizeOf);
1766
for (const auto& entry : mTrackedURIs) {
1767
size += entry.GetData().SizeOfExcludingThis(aMallocSizeOf);
1768
}
1769
return size;
1770
}
1771
1772
/* static */
1773
History* History::GetService() {
1774
if (gService) {
1775
return gService;
1776
}
1777
1778
nsCOMPtr<IHistory> service = services::GetHistoryService();
1779
if (service) {
1780
NS_ASSERTION(gService, "Our constructor was not run?!");
1781
}
1782
1783
return