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