LCOV - code coverage report
Current view: top level - toolkit/components/places - History.cpp (source / functions) Hit Total Coverage
Test: output.info Lines: 415 1271 32.7 %
Date: 2017-07-14 16:53:18 Functions: 41 131 31.3 %
Legend: Lines: hit not hit

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

Generated by: LCOV version 1.13