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