toolkit/components/places/History.cpp

Sat, 03 Jan 2015 20:18:00 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Sat, 03 Jan 2015 20:18:00 +0100
branch
TOR_BUG_3246
changeset 7
129ffea94266
permissions
-rw-r--r--

Conditionally enable double key logic according to:
private browsing mode or privacy.thirdparty.isolate preference and
implement in GetCookieStringCommon and FindCookie where it counts...
With some reservations of how to convince FindCookie users to test
condition and pass a nullptr when disabling double key logic.

     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/. */
     7 #include "mozilla/ArrayUtils.h"
     8 #include "mozilla/Attributes.h"
     9 #include "mozilla/DebugOnly.h"
    10 #include "mozilla/MemoryReporting.h"
    12 #include "mozilla/dom/ContentChild.h"
    13 #include "mozilla/dom/ContentParent.h"
    14 #include "nsXULAppAPI.h"
    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"
    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 "nsIXPConnect.h"
    32 #include "mozilla/unused.h"
    33 #include "nsContentUtils.h" // for nsAutoScriptBlocker
    34 #include "mozilla/ipc/URIUtils.h"
    35 #include "nsPrintfCString.h"
    36 #include "nsTHashtable.h"
    37 #include "jsapi.h"
    39 // Initial size for the cache holding visited status observers.
    40 #define VISIT_OBSERVERS_INITIAL_CACHE_SIZE 128
    42 // Initial size for the visits removal hash.
    43 #define VISITS_REMOVAL_INITIAL_HASH_SIZE 128
    45 using namespace mozilla::dom;
    46 using namespace mozilla::ipc;
    47 using mozilla::unused;
    49 namespace mozilla {
    50 namespace places {
    52 ////////////////////////////////////////////////////////////////////////////////
    53 //// Global Defines
    55 #define URI_VISITED "visited"
    56 #define URI_NOT_VISITED "not visited"
    57 #define URI_VISITED_RESOLUTION_TOPIC "visited-status-resolution"
    58 // Observer event fired after a visit has been registered in the DB.
    59 #define URI_VISIT_SAVED "uri-visit-saved"
    61 #define DESTINATIONFILEURI_ANNO \
    62         NS_LITERAL_CSTRING("downloads/destinationFileURI")
    63 #define DESTINATIONFILENAME_ANNO \
    64         NS_LITERAL_CSTRING("downloads/destinationFileName")
    66 ////////////////////////////////////////////////////////////////////////////////
    67 //// VisitData
    69 struct VisitData {
    70   VisitData()
    71   : placeId(0)
    72   , visitId(0)
    73   , hidden(true)
    74   , typed(false)
    75   , transitionType(UINT32_MAX)
    76   , visitTime(0)
    77   , frecency(-1)
    78   , titleChanged(false)
    79   , shouldUpdateFrecency(true)
    80   {
    81     guid.SetIsVoid(true);
    82     title.SetIsVoid(true);
    83   }
    85   VisitData(nsIURI* aURI,
    86             nsIURI* aReferrer = nullptr)
    87   : placeId(0)
    88   , visitId(0)
    89   , hidden(true)
    90   , typed(false)
    91   , transitionType(UINT32_MAX)
    92   , visitTime(0)
    93   , frecency(-1)
    94   , titleChanged(false)
    95   , shouldUpdateFrecency(true)
    96   {
    97     (void)aURI->GetSpec(spec);
    98     (void)GetReversedHostname(aURI, revHost);
    99     if (aReferrer) {
   100       (void)aReferrer->GetSpec(referrerSpec);
   101     }
   102     guid.SetIsVoid(true);
   103     title.SetIsVoid(true);
   104   }
   106   /**
   107    * Sets the transition type of the visit, as well as if it was typed.
   108    *
   109    * @param aTransitionType
   110    *        The transition type constant to set.  Must be one of the
   111    *        TRANSITION_ constants on nsINavHistoryService.
   112    */
   113   void SetTransitionType(uint32_t aTransitionType)
   114   {
   115     typed = aTransitionType == nsINavHistoryService::TRANSITION_TYPED;
   116     transitionType = aTransitionType;
   117   }
   119   /**
   120    * Determines if this refers to the same url as aOther, and updates aOther
   121    * with missing information if so.
   122    *
   123    * @param aOther
   124    *        The other place to check against.
   125    * @return true if this is a visit for the same place as aOther, false
   126    *         otherwise.
   127    */
   128   bool IsSamePlaceAs(VisitData& aOther)
   129   {
   130     if (!spec.Equals(aOther.spec)) {
   131       return false;
   132     }
   134     aOther.placeId = placeId;
   135     aOther.guid = guid;
   136     return true;
   137   }
   139   int64_t placeId;
   140   nsCString guid;
   141   int64_t visitId;
   142   nsCString spec;
   143   nsString revHost;
   144   bool hidden;
   145   bool typed;
   146   uint32_t transitionType;
   147   PRTime visitTime;
   148   int32_t frecency;
   150   /**
   151    * Stores the title.  If this is empty (IsEmpty() returns true), then the
   152    * title should be removed from the Place.  If the title is void (IsVoid()
   153    * returns true), then no title has been set on this object, and titleChanged
   154    * should remain false.
   155    */
   156   nsString title;
   158   nsCString referrerSpec;
   160   // TODO bug 626836 hook up hidden and typed change tracking too!
   161   bool titleChanged;
   163   // Indicates whether frecency should be updated for this visit.
   164   bool shouldUpdateFrecency;
   165 };
   167 ////////////////////////////////////////////////////////////////////////////////
   168 //// RemoveVisitsFilter
   170 /**
   171  * Used to store visit filters for RemoveVisits.
   172  */
   173 struct RemoveVisitsFilter {
   174   RemoveVisitsFilter()
   175   : transitionType(UINT32_MAX)
   176   {
   177   }
   179   uint32_t transitionType;
   180 };
   182 ////////////////////////////////////////////////////////////////////////////////
   183 //// PlaceHashKey
   185 class PlaceHashKey : public nsCStringHashKey
   186 {
   187   public:
   188     PlaceHashKey(const nsACString& aSpec)
   189     : nsCStringHashKey(&aSpec)
   190     , visitCount(-1)
   191     , bookmarked(-1)
   192     {
   193     }
   195     PlaceHashKey(const nsACString* aSpec)
   196     : nsCStringHashKey(aSpec)
   197     , visitCount(-1)
   198     , bookmarked(-1)
   199     {
   200     }
   202     PlaceHashKey(const PlaceHashKey& aOther)
   203     : nsCStringHashKey(&aOther.GetKey())
   204     {
   205       MOZ_ASSERT(false, "Do not call me!");
   206     }
   208     // Visit count for this place.
   209     int32_t visitCount;
   210     // Whether this place is bookmarked.
   211     int32_t bookmarked;
   212     // Array of VisitData objects.
   213     nsTArray<VisitData> visits;
   214 };
   216 ////////////////////////////////////////////////////////////////////////////////
   217 //// Anonymous Helpers
   219 namespace {
   221 /**
   222  * Convert the given js value to a js array.
   223  *
   224  * @param [in] aValue
   225  *        the JS value to convert.
   226  * @param [in] aCtx
   227  *        The JSContext for aValue.
   228  * @param [out] _array
   229  *        the JS array.
   230  * @param [out] _arrayLength
   231  *        _array's length.
   232  */
   233 nsresult
   234 GetJSArrayFromJSValue(JS::Handle<JS::Value> aValue,
   235                       JSContext* aCtx,
   236                       JS::MutableHandle<JSObject*> _array,
   237                       uint32_t* _arrayLength) {
   238   if (aValue.isObjectOrNull()) {
   239     JS::Rooted<JSObject*> val(aCtx, aValue.toObjectOrNull());
   240     if (JS_IsArrayObject(aCtx, val)) {
   241       _array.set(val);
   242       (void)JS_GetArrayLength(aCtx, _array, _arrayLength);
   243       NS_ENSURE_ARG(*_arrayLength > 0);
   244       return NS_OK;
   245     }
   246   }
   248   // Build a temporary array to store this one item so the code below can
   249   // just loop.
   250   *_arrayLength = 1;
   251   _array.set(JS_NewArrayObject(aCtx, 0));
   252   NS_ENSURE_TRUE(_array, NS_ERROR_OUT_OF_MEMORY);
   254   bool rc = JS_DefineElement(aCtx, _array, 0, aValue, nullptr, nullptr, 0);
   255   NS_ENSURE_TRUE(rc, NS_ERROR_UNEXPECTED);
   256   return NS_OK;
   257 }
   259 /**
   260  * Attemps to convert a given js value to a nsIURI object.
   261  * @param aCtx
   262  *        The JSContext for aValue.
   263  * @param aValue
   264  *        The JS value to convert.
   265  * @return the nsIURI object, or null if aValue is not a nsIURI object.
   266  */
   267 already_AddRefed<nsIURI>
   268 GetJSValueAsURI(JSContext* aCtx,
   269                 const JS::Value& aValue) {
   270   if (!JSVAL_IS_PRIMITIVE(aValue)) {
   271     nsCOMPtr<nsIXPConnect> xpc = mozilla::services::GetXPConnect();
   273     nsCOMPtr<nsIXPConnectWrappedNative> wrappedObj;
   274     nsresult rv = xpc->GetWrappedNativeOfJSObject(aCtx, JSVAL_TO_OBJECT(aValue),
   275                                                   getter_AddRefs(wrappedObj));
   276     NS_ENSURE_SUCCESS(rv, nullptr);
   277     nsCOMPtr<nsIURI> uri = do_QueryWrappedNative(wrappedObj);
   278     return uri.forget();
   279   }
   280   return nullptr;
   281 }
   283 /**
   284  * Obtains an nsIURI from the "uri" property of a JSObject.
   285  *
   286  * @param aCtx
   287  *        The JSContext for aObject.
   288  * @param aObject
   289  *        The JSObject to get the URI from.
   290  * @param aProperty
   291  *        The name of the property to get the URI from.
   292  * @return the URI if it exists.
   293  */
   294 already_AddRefed<nsIURI>
   295 GetURIFromJSObject(JSContext* aCtx,
   296                    JS::Handle<JSObject *> aObject,
   297                    const char* aProperty)
   298 {
   299   JS::Rooted<JS::Value> uriVal(aCtx);
   300   bool rc = JS_GetProperty(aCtx, aObject, aProperty, &uriVal);
   301   NS_ENSURE_TRUE(rc, nullptr);
   302   return GetJSValueAsURI(aCtx, uriVal);
   303 }
   305 /**
   306  * Attemps to convert a JS value to a string.
   307  * @param aCtx
   308  *        The JSContext for aObject.
   309  * @param aValue
   310  *        The JS value to convert.
   311  * @param _string
   312  *        The string to populate with the value, or set it to void.
   313  */
   314 void
   315 GetJSValueAsString(JSContext* aCtx,
   316                    const JS::Value& aValue,
   317                    nsString& _string) {
   318   if (JSVAL_IS_VOID(aValue) ||
   319       !(JSVAL_IS_NULL(aValue) || JSVAL_IS_STRING(aValue))) {
   320     _string.SetIsVoid(true);
   321     return;
   322   }
   324   // |null| in JS maps to the empty string.
   325   if (JSVAL_IS_NULL(aValue)) {
   326     _string.Truncate();
   327     return;
   328   }
   329   size_t length;
   330   const jschar* chars =
   331     JS_GetStringCharsZAndLength(aCtx, JSVAL_TO_STRING(aValue), &length);
   332   if (!chars) {
   333     _string.SetIsVoid(true);
   334     return;
   335   }
   336   _string.Assign(static_cast<const char16_t*>(chars), length);
   337 }
   339 /**
   340  * Obtains the specified property of a JSObject.
   341  *
   342  * @param aCtx
   343  *        The JSContext for aObject.
   344  * @param aObject
   345  *        The JSObject to get the string from.
   346  * @param aProperty
   347  *        The property to get the value from.
   348  * @param _string
   349  *        The string to populate with the value, or set it to void.
   350  */
   351 void
   352 GetStringFromJSObject(JSContext* aCtx,
   353                       JS::Handle<JSObject *> aObject,
   354                       const char* aProperty,
   355                       nsString& _string)
   356 {
   357   JS::Rooted<JS::Value> val(aCtx);
   358   bool rc = JS_GetProperty(aCtx, aObject, aProperty, &val);
   359   if (!rc) {
   360     _string.SetIsVoid(true);
   361     return;
   362   }
   363   else {
   364     GetJSValueAsString(aCtx, val, _string);
   365   }
   366 }
   368 /**
   369  * Obtains the specified property of a JSObject.
   370  *
   371  * @param aCtx
   372  *        The JSContext for aObject.
   373  * @param aObject
   374  *        The JSObject to get the int from.
   375  * @param aProperty
   376  *        The property to get the value from.
   377  * @param _int
   378  *        The integer to populate with the value on success.
   379  */
   380 template <typename IntType>
   381 nsresult
   382 GetIntFromJSObject(JSContext* aCtx,
   383                    JS::Handle<JSObject *> aObject,
   384                    const char* aProperty,
   385                    IntType* _int)
   386 {
   387   JS::Rooted<JS::Value> value(aCtx);
   388   bool rc = JS_GetProperty(aCtx, aObject, aProperty, &value);
   389   NS_ENSURE_TRUE(rc, NS_ERROR_UNEXPECTED);
   390   if (JSVAL_IS_VOID(value)) {
   391     return NS_ERROR_INVALID_ARG;
   392   }
   393   NS_ENSURE_ARG(JSVAL_IS_PRIMITIVE(value));
   394   NS_ENSURE_ARG(JSVAL_IS_NUMBER(value));
   396   double num;
   397   rc = JS::ToNumber(aCtx, value, &num);
   398   NS_ENSURE_TRUE(rc, NS_ERROR_UNEXPECTED);
   399   NS_ENSURE_ARG(IntType(num) == num);
   401   *_int = IntType(num);
   402   return NS_OK;
   403 }
   405 /**
   406  * Obtains the specified property of a JSObject.
   407  *
   408  * @pre aArray must be an Array object.
   409  *
   410  * @param aCtx
   411  *        The JSContext for aArray.
   412  * @param aArray
   413  *        The JSObject to get the object from.
   414  * @param aIndex
   415  *        The index to get the object from.
   416  * @param objOut
   417  *        Set to the JSObject pointer on success.
   418  */
   419 nsresult
   420 GetJSObjectFromArray(JSContext* aCtx,
   421                      JS::Handle<JSObject*> aArray,
   422                      uint32_t aIndex,
   423                      JS::MutableHandle<JSObject*> objOut)
   424 {
   425   NS_PRECONDITION(JS_IsArrayObject(aCtx, aArray),
   426                   "Must provide an object that is an array!");
   428   JS::Rooted<JS::Value> value(aCtx);
   429   bool rc = JS_GetElement(aCtx, aArray, aIndex, &value);
   430   NS_ENSURE_TRUE(rc, NS_ERROR_UNEXPECTED);
   431   NS_ENSURE_ARG(!value.isPrimitive());
   432   objOut.set(&value.toObject());
   433   return NS_OK;
   434 }
   436 class VisitedQuery : public AsyncStatementCallback
   437 {
   438 public:
   439   static nsresult Start(nsIURI* aURI,
   440                         mozIVisitedStatusCallback* aCallback=nullptr)
   441   {
   442     NS_PRECONDITION(aURI, "Null URI");
   444   // If we are a content process, always remote the request to the
   445   // parent process.
   446   if (XRE_GetProcessType() == GeckoProcessType_Content) {
   447     URIParams uri;
   448     SerializeURI(aURI, uri);
   450     mozilla::dom::ContentChild* cpc =
   451       mozilla::dom::ContentChild::GetSingleton();
   452     NS_ASSERTION(cpc, "Content Protocol is NULL!");
   453     (void)cpc->SendStartVisitedQuery(uri);
   454     return NS_OK;
   455   }
   457     nsNavHistory* navHistory = nsNavHistory::GetHistoryService();
   458     NS_ENSURE_STATE(navHistory);
   459     if (navHistory->hasEmbedVisit(aURI)) {
   460       nsRefPtr<VisitedQuery> callback = new VisitedQuery(aURI, aCallback, true);
   461       NS_ENSURE_TRUE(callback, NS_ERROR_OUT_OF_MEMORY);
   462       // As per IHistory contract, we must notify asynchronously.
   463       nsCOMPtr<nsIRunnable> event =
   464         NS_NewRunnableMethod(callback, &VisitedQuery::NotifyVisitedStatus);
   465       NS_DispatchToMainThread(event);
   467       return NS_OK;
   468     }
   470     History* history = History::GetService();
   471     NS_ENSURE_STATE(history);
   472     mozIStorageAsyncStatement* stmt = history->GetIsVisitedStatement();
   473     NS_ENSURE_STATE(stmt);
   475     // Bind by index for performance.
   476     nsresult rv = URIBinder::Bind(stmt, 0, aURI);
   477     NS_ENSURE_SUCCESS(rv, rv);
   479     nsRefPtr<VisitedQuery> callback = new VisitedQuery(aURI, aCallback);
   480     NS_ENSURE_TRUE(callback, NS_ERROR_OUT_OF_MEMORY);
   482     nsCOMPtr<mozIStoragePendingStatement> handle;
   483     return stmt->ExecuteAsync(callback, getter_AddRefs(handle));
   484   }
   486   NS_IMETHOD HandleResult(mozIStorageResultSet* aResults)
   487   {
   488     // If this method is called, we've gotten results, which means we have a
   489     // visit.
   490     mIsVisited = true;
   491     return NS_OK;
   492   }
   494   NS_IMETHOD HandleError(mozIStorageError* aError)
   495   {
   496     // mIsVisited is already set to false, and that's the assumption we will
   497     // make if an error occurred.
   498     return NS_OK;
   499   }
   501   NS_IMETHOD HandleCompletion(uint16_t aReason)
   502   {
   503     if (aReason != mozIStorageStatementCallback::REASON_FINISHED) {
   504       return NS_OK;
   505     }
   507     nsresult rv = NotifyVisitedStatus();
   508     NS_ENSURE_SUCCESS(rv, rv);
   509     return NS_OK;
   510   }
   512   nsresult NotifyVisitedStatus()
   513   {
   514     // If an external handling callback is provided, just notify through it.
   515     if (mCallback) {
   516       mCallback->IsVisited(mURI, mIsVisited);
   517       return NS_OK;
   518     }
   520     if (mIsVisited) {
   521       History* history = History::GetService();
   522       NS_ENSURE_STATE(history);
   523       history->NotifyVisited(mURI);
   524     }
   526     nsCOMPtr<nsIObserverService> observerService =
   527       mozilla::services::GetObserverService();
   528     if (observerService) {
   529       nsAutoString status;
   530       if (mIsVisited) {
   531         status.AssignLiteral(URI_VISITED);
   532       }
   533       else {
   534         status.AssignLiteral(URI_NOT_VISITED);
   535       }
   536       (void)observerService->NotifyObservers(mURI,
   537                                              URI_VISITED_RESOLUTION_TOPIC,
   538                                              status.get());
   539     }
   541     return NS_OK;
   542   }
   544 private:
   545   VisitedQuery(nsIURI* aURI,
   546                mozIVisitedStatusCallback *aCallback=nullptr,
   547                bool aIsVisited=false)
   548   : mURI(aURI)
   549   , mCallback(aCallback)
   550   , mIsVisited(aIsVisited)
   551   {
   552   }
   554   nsCOMPtr<nsIURI> mURI;
   555   nsCOMPtr<mozIVisitedStatusCallback> mCallback;
   556   bool mIsVisited;
   557 };
   559 /**
   560  * Notifies observers about a visit.
   561  */
   562 class NotifyVisitObservers : public nsRunnable
   563 {
   564 public:
   565   NotifyVisitObservers(VisitData& aPlace,
   566                        VisitData& aReferrer)
   567   : mPlace(aPlace)
   568   , mReferrer(aReferrer)
   569   , mHistory(History::GetService())
   570   {
   571   }
   573   NS_IMETHOD Run()
   574   {
   575     MOZ_ASSERT(NS_IsMainThread(), "This should be called on the main thread");
   577     // We are in the main thread, no need to lock.
   578     if (mHistory->IsShuttingDown()) {
   579       // If we are shutting down, we cannot notify the observers.
   580       return NS_OK;
   581     }
   583     nsNavHistory* navHistory = nsNavHistory::GetHistoryService();
   584     if (!navHistory) {
   585       NS_WARNING("Trying to notify about a visit but cannot get the history service!");
   586       return NS_OK;
   587     }
   589     nsCOMPtr<nsIURI> uri;
   590     (void)NS_NewURI(getter_AddRefs(uri), mPlace.spec);
   592     // Notify the visit.  Note that TRANSITION_EMBED visits are never added
   593     // to the database, thus cannot be queried and we don't notify them.
   594     if (mPlace.transitionType != nsINavHistoryService::TRANSITION_EMBED) {
   595       navHistory->NotifyOnVisit(uri, mPlace.visitId, mPlace.visitTime,
   596                                 mReferrer.visitId, mPlace.transitionType,
   597                                 mPlace.guid, mPlace.hidden);
   598     }
   600     nsCOMPtr<nsIObserverService> obsService =
   601       mozilla::services::GetObserverService();
   602     if (obsService) {
   603       DebugOnly<nsresult> rv =
   604         obsService->NotifyObservers(uri, URI_VISIT_SAVED, nullptr);
   605       NS_WARN_IF_FALSE(NS_SUCCEEDED(rv), "Could not notify observers");
   606     }
   608     History* history = History::GetService();
   609     NS_ENSURE_STATE(history);
   610     history->AppendToRecentlyVisitedURIs(uri);
   611     history->NotifyVisited(uri);
   613     return NS_OK;
   614   }
   615 private:
   616   VisitData mPlace;
   617   VisitData mReferrer;
   618   nsRefPtr<History> mHistory;
   619 };
   621 /**
   622  * Notifies observers about a pages title changing.
   623  */
   624 class NotifyTitleObservers : public nsRunnable
   625 {
   626 public:
   627   /**
   628    * Notifies observers on the main thread.
   629    *
   630    * @param aSpec
   631    *        The spec of the URI to notify about.
   632    * @param aTitle
   633    *        The new title to notify about.
   634    */
   635   NotifyTitleObservers(const nsCString& aSpec,
   636                        const nsString& aTitle,
   637                        const nsCString& aGUID)
   638   : mSpec(aSpec)
   639   , mTitle(aTitle)
   640   , mGUID(aGUID)
   641   {
   642   }
   644   NS_IMETHOD Run()
   645   {
   646     MOZ_ASSERT(NS_IsMainThread(), "This should be called on the main thread");
   648     nsNavHistory* navHistory = nsNavHistory::GetHistoryService();
   649     NS_ENSURE_TRUE(navHistory, NS_ERROR_OUT_OF_MEMORY);
   650     nsCOMPtr<nsIURI> uri;
   651     (void)NS_NewURI(getter_AddRefs(uri), mSpec);
   652     navHistory->NotifyTitleChange(uri, mTitle, mGUID);
   654     return NS_OK;
   655   }
   656 private:
   657   const nsCString mSpec;
   658   const nsString mTitle;
   659   const nsCString mGUID;
   660 };
   662 /**
   663  * Helper class for methods which notify their callers through the
   664  * mozIVisitInfoCallback interface.
   665  */
   666 class NotifyPlaceInfoCallback : public nsRunnable
   667 {
   668 public:
   669   NotifyPlaceInfoCallback(mozIVisitInfoCallback* aCallback,
   670                           const VisitData& aPlace,
   671                           bool aIsSingleVisit,
   672                           nsresult aResult)
   673   : mCallback(aCallback)
   674   , mPlace(aPlace)
   675   , mResult(aResult)
   676   , mIsSingleVisit(aIsSingleVisit)
   677   {
   678     MOZ_ASSERT(aCallback, "Must pass a non-null callback!");
   679   }
   681   NS_IMETHOD Run()
   682   {
   683     MOZ_ASSERT(NS_IsMainThread(), "This should be called on the main thread");
   685     nsCOMPtr<nsIURI> referrerURI;
   686     if (!mPlace.referrerSpec.IsEmpty()) {
   687       (void)NS_NewURI(getter_AddRefs(referrerURI), mPlace.referrerSpec);
   688     }
   690     nsCOMPtr<nsIURI> uri;
   691     (void)NS_NewURI(getter_AddRefs(uri), mPlace.spec);
   693     nsCOMPtr<mozIPlaceInfo> place;
   694     if (mIsSingleVisit) {
   695       nsCOMPtr<mozIVisitInfo> visit =
   696         new VisitInfo(mPlace.visitId, mPlace.visitTime, mPlace.transitionType,
   697                       referrerURI.forget());
   698       PlaceInfo::VisitsArray visits;
   699       (void)visits.AppendElement(visit);
   701       // The frecency isn't exposed because it may not reflect the updated value
   702       // in the case of InsertVisitedURIs.
   703       place =
   704         new PlaceInfo(mPlace.placeId, mPlace.guid, uri.forget(), mPlace.title,
   705                       -1, visits);
   706     }
   707     else {
   708       // Same as above.
   709       place =
   710         new PlaceInfo(mPlace.placeId, mPlace.guid, uri.forget(), mPlace.title,
   711                       -1);
   712     }
   714     if (NS_SUCCEEDED(mResult)) {
   715       (void)mCallback->HandleResult(place);
   716     }
   717     else {
   718       (void)mCallback->HandleError(mResult, place);
   719     }
   721     return NS_OK;
   722   }
   724 private:
   725   /**
   726    * Callers MUST hold a strong reference to this that outlives us because we
   727    * may be created off of the main thread, and therefore cannot call AddRef on
   728    * this object (and therefore cannot hold a strong reference to it).
   729    */
   730   mozIVisitInfoCallback* mCallback;
   731   VisitData mPlace;
   732   const nsresult mResult;
   733   bool mIsSingleVisit;
   734 };
   736 /**
   737  * Notifies a callback object when the operation is complete.
   738  */
   739 class NotifyCompletion : public nsRunnable
   740 {
   741 public:
   742   NotifyCompletion(mozIVisitInfoCallback* aCallback)
   743   : mCallback(aCallback)
   744   {
   745     MOZ_ASSERT(aCallback, "Must pass a non-null callback!");
   746   }
   748   NS_IMETHOD Run()
   749   {
   750     if (NS_IsMainThread()) {
   751       (void)mCallback->HandleCompletion();
   752     }
   753     else {
   754       (void)NS_DispatchToMainThread(this);
   756       // Also dispatch an event to release the reference to the callback after
   757       // we have run.
   758       nsCOMPtr<nsIThread> mainThread = do_GetMainThread();
   759       (void)NS_ProxyRelease(mainThread, mCallback, true);
   760     }
   761     return NS_OK;
   762   }
   764 private:
   765   /**
   766    * Callers MUST hold a strong reference to this because we may be created
   767    * off of the main thread, and therefore cannot call AddRef on this object
   768    * (and therefore cannot hold a strong reference to it). If invoked from a
   769    * background thread, NotifyCompletion will release the reference to this.
   770    */
   771   mozIVisitInfoCallback* mCallback;
   772 };
   774 /**
   775  * Checks to see if we can add aURI to history, and dispatches an error to
   776  * aCallback (if provided) if we cannot.
   777  *
   778  * @param aURI
   779  *        The URI to check.
   780  * @param [optional] aGUID
   781  *        The guid of the URI to check.  This is passed back to the callback.
   782  * @param [optional] aCallback
   783  *        The callback to notify if the URI cannot be added to history.
   784  * @return true if the URI can be added to history, false otherwise.
   785  */
   786 bool
   787 CanAddURI(nsIURI* aURI,
   788           const nsCString& aGUID = EmptyCString(),
   789           mozIVisitInfoCallback* aCallback = nullptr)
   790 {
   791   nsNavHistory* navHistory = nsNavHistory::GetHistoryService();
   792   NS_ENSURE_TRUE(navHistory, false);
   794   bool canAdd;
   795   nsresult rv = navHistory->CanAddURI(aURI, &canAdd);
   796   if (NS_SUCCEEDED(rv) && canAdd) {
   797     return true;
   798   };
   800   // We cannot add the URI.  Notify the callback, if we were given one.
   801   if (aCallback) {
   802     // NotifyPlaceInfoCallback does not hold a strong reference to the callback, so we
   803     // have to manage it by AddRefing now and then releasing it after the event
   804     // has run.
   805     NS_ADDREF(aCallback);
   807     VisitData place(aURI);
   808     place.guid = aGUID;
   809     nsCOMPtr<nsIRunnable> event =
   810       new NotifyPlaceInfoCallback(aCallback, place, true, NS_ERROR_INVALID_ARG);
   811     (void)NS_DispatchToMainThread(event);
   813     // Also dispatch an event to release our reference to the callback after
   814     // NotifyPlaceInfoCallback has run.
   815     nsCOMPtr<nsIThread> mainThread = do_GetMainThread();
   816     (void)NS_ProxyRelease(mainThread, aCallback, true);
   817   }
   819   return false;
   820 }
   822 /**
   823  * Adds a visit to the database.
   824  */
   825 class InsertVisitedURIs : public nsRunnable
   826 {
   827 public:
   828   /**
   829    * Adds a visit to the database asynchronously.
   830    *
   831    * @param aConnection
   832    *        The database connection to use for these operations.
   833    * @param aPlaces
   834    *        The locations to record visits.
   835    * @param [optional] aCallback
   836    *        The callback to notify about the visit.
   837    */
   838   static nsresult Start(mozIStorageConnection* aConnection,
   839                         nsTArray<VisitData>& aPlaces,
   840                         mozIVisitInfoCallback* aCallback = nullptr)
   841   {
   842     MOZ_ASSERT(NS_IsMainThread(), "This should be called on the main thread");
   843     MOZ_ASSERT(aPlaces.Length() > 0, "Must pass a non-empty array!");
   845     nsRefPtr<InsertVisitedURIs> event =
   846       new InsertVisitedURIs(aConnection, aPlaces, aCallback);
   848     // Get the target thread, and then start the work!
   849     nsCOMPtr<nsIEventTarget> target = do_GetInterface(aConnection);
   850     NS_ENSURE_TRUE(target, NS_ERROR_UNEXPECTED);
   851     nsresult rv = target->Dispatch(event, NS_DISPATCH_NORMAL);
   852     NS_ENSURE_SUCCESS(rv, rv);
   854     return NS_OK;
   855   }
   857   NS_IMETHOD Run()
   858   {
   859     MOZ_ASSERT(!NS_IsMainThread(), "This should not be called on the main thread");
   861     // Prevent the main thread from shutting down while this is running.
   862     MutexAutoLock lockedScope(mHistory->GetShutdownMutex());
   863     if (mHistory->IsShuttingDown()) {
   864       // If we were already shutting down, we cannot insert the URIs.
   865       return NS_OK;
   866     }
   868     mozStorageTransaction transaction(mDBConn, false,
   869                                       mozIStorageConnection::TRANSACTION_IMMEDIATE);
   871     VisitData* lastPlace = nullptr;
   872     for (nsTArray<VisitData>::size_type i = 0; i < mPlaces.Length(); i++) {
   873       VisitData& place = mPlaces.ElementAt(i);
   874       VisitData& referrer = mReferrers.ElementAt(i);
   876       // We can avoid a database lookup if it's the same place as the last
   877       // visit we added.
   878       bool known = lastPlace && lastPlace->IsSamePlaceAs(place);
   879       if (!known) {
   880         nsresult rv = mHistory->FetchPageInfo(place, &known);
   881         if (NS_FAILED(rv)) {
   882           if (mCallback) {
   883             nsCOMPtr<nsIRunnable> event =
   884               new NotifyPlaceInfoCallback(mCallback, place, true, rv);
   885             return NS_DispatchToMainThread(event);
   886           }
   887           return NS_OK;
   888         }
   889       }
   891       FetchReferrerInfo(referrer, place);
   893       nsresult rv = DoDatabaseInserts(known, place, referrer);
   894       if (mCallback) {
   895         nsCOMPtr<nsIRunnable> event =
   896           new NotifyPlaceInfoCallback(mCallback, place, true, rv);
   897         nsresult rv2 = NS_DispatchToMainThread(event);
   898         NS_ENSURE_SUCCESS(rv2, rv2);
   899       }
   900       NS_ENSURE_SUCCESS(rv, rv);
   902       nsCOMPtr<nsIRunnable> event = new NotifyVisitObservers(place, referrer);
   903       rv = NS_DispatchToMainThread(event);
   904       NS_ENSURE_SUCCESS(rv, rv);
   906       // Notify about title change if needed.
   907       if ((!known && !place.title.IsVoid()) || place.titleChanged) {
   908         event = new NotifyTitleObservers(place.spec, place.title, place.guid);
   909         rv = NS_DispatchToMainThread(event);
   910         NS_ENSURE_SUCCESS(rv, rv);
   911       }
   913       lastPlace = &mPlaces.ElementAt(i);
   914     }
   916     nsresult rv = transaction.Commit();
   917     NS_ENSURE_SUCCESS(rv, rv);
   919     return NS_OK;
   920   }
   921 private:
   922   InsertVisitedURIs(mozIStorageConnection* aConnection,
   923                     nsTArray<VisitData>& aPlaces,
   924                     mozIVisitInfoCallback* aCallback)
   925   : mDBConn(aConnection)
   926   , mCallback(aCallback)
   927   , mHistory(History::GetService())
   928   {
   929     MOZ_ASSERT(NS_IsMainThread(), "This should be called on the main thread");
   931     (void)mPlaces.SwapElements(aPlaces);
   932     (void)mReferrers.SetLength(mPlaces.Length());
   934     nsNavHistory* navHistory = nsNavHistory::GetHistoryService();
   935     NS_ABORT_IF_FALSE(navHistory, "Could not get nsNavHistory?!");
   937     for (nsTArray<VisitData>::size_type i = 0; i < mPlaces.Length(); i++) {
   938       mReferrers[i].spec = mPlaces[i].referrerSpec;
   940 #ifdef DEBUG
   941       nsCOMPtr<nsIURI> uri;
   942       (void)NS_NewURI(getter_AddRefs(uri), mPlaces[i].spec);
   943       NS_ASSERTION(CanAddURI(uri),
   944                    "Passed a VisitData with a URI we cannot add to history!");
   945 #endif
   946     }
   947   }
   949   virtual ~InsertVisitedURIs()
   950   {
   951     if (mCallback) {
   952       nsCOMPtr<nsIThread> mainThread = do_GetMainThread();
   953       (void)NS_ProxyRelease(mainThread, mCallback, true);
   954     }
   955   }
   957   /**
   958    * Inserts or updates the entry in moz_places for this visit, adds the visit,
   959    * and updates the frecency of the place.
   960    *
   961    * @param aKnown
   962    *        True if we already have an entry for this place in moz_places, false
   963    *        otherwise.
   964    * @param aPlace
   965    *        The place we are adding a visit for.
   966    * @param aReferrer
   967    *        The referrer for aPlace.
   968    */
   969   nsresult DoDatabaseInserts(bool aKnown,
   970                              VisitData& aPlace,
   971                              VisitData& aReferrer)
   972   {
   973     MOZ_ASSERT(!NS_IsMainThread(), "This should not be called on the main thread");
   975     // If the page was in moz_places, we need to update the entry.
   976     nsresult rv;
   977     if (aKnown) {
   978       rv = mHistory->UpdatePlace(aPlace);
   979       NS_ENSURE_SUCCESS(rv, rv);
   980     }
   981     // Otherwise, the page was not in moz_places, so now we have to add it.
   982     else {
   983       rv = mHistory->InsertPlace(aPlace);
   984       NS_ENSURE_SUCCESS(rv, rv);
   986       // We need the place id and guid of the page we just inserted when we
   987       // have a callback or when the GUID isn't known.  No point in doing the
   988       // disk I/O if we do not need it.
   989       if (mCallback || aPlace.guid.IsEmpty()) {
   990         bool exists;
   991         rv = mHistory->FetchPageInfo(aPlace, &exists);
   992         NS_ENSURE_SUCCESS(rv, rv);
   994         if (!exists) {
   995           NS_NOTREACHED("should have an entry in moz_places");
   996         }
   997       }
   998     }
  1000     rv = AddVisit(aPlace, aReferrer);
  1001     NS_ENSURE_SUCCESS(rv, rv);
  1003     // TODO (bug 623969) we shouldn't update this after each visit, but
  1004     // rather only for each unique place to save disk I/O.
  1006     // Don't update frecency if the page should not appear in autocomplete.
  1007     if (aPlace.shouldUpdateFrecency) {
  1008       rv = UpdateFrecency(aPlace);
  1009       NS_ENSURE_SUCCESS(rv, rv);
  1012     return NS_OK;
  1015   /**
  1016    * Loads visit information about the page into _place.
  1018    * @param _place
  1019    *        The VisitData for the place we need to know visit information about.
  1020    * @param [optional] aThresholdStart
  1021    *        The timestamp of a new visit (not represented by _place) used to
  1022    *        determine if the page was recently visited or not.
  1023    * @return true if the page was recently (determined with aThresholdStart)
  1024    *         visited, false otherwise.
  1025    */
  1026   bool FetchVisitInfo(VisitData& _place,
  1027                       PRTime aThresholdStart = 0)
  1029     NS_PRECONDITION(!_place.spec.IsEmpty(), "must have a non-empty spec!");
  1031     nsCOMPtr<mozIStorageStatement> stmt;
  1032     // If we have a visitTime, we want information on that specific visit.
  1033     if (_place.visitTime) {
  1034       stmt = mHistory->GetStatement(
  1035         "SELECT id, visit_date "
  1036         "FROM moz_historyvisits "
  1037         "WHERE place_id = (SELECT id FROM moz_places WHERE url = :page_url) "
  1038         "AND visit_date = :visit_date "
  1039       );
  1040       NS_ENSURE_TRUE(stmt, false);
  1042       mozStorageStatementScoper scoper(stmt);
  1043       nsresult rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("visit_date"),
  1044                                           _place.visitTime);
  1045       NS_ENSURE_SUCCESS(rv, false);
  1047       scoper.Abandon();
  1049     // Otherwise, we want information about the most recent visit.
  1050     else {
  1051       stmt = mHistory->GetStatement(
  1052         "SELECT id, visit_date "
  1053         "FROM moz_historyvisits "
  1054         "WHERE place_id = (SELECT id FROM moz_places WHERE url = :page_url) "
  1055         "ORDER BY visit_date DESC "
  1056       );
  1057       NS_ENSURE_TRUE(stmt, false);
  1059     mozStorageStatementScoper scoper(stmt);
  1061     nsresult rv = URIBinder::Bind(stmt, NS_LITERAL_CSTRING("page_url"),
  1062                                   _place.spec);
  1063     NS_ENSURE_SUCCESS(rv, false);
  1065     bool hasResult;
  1066     rv = stmt->ExecuteStep(&hasResult);
  1067     NS_ENSURE_SUCCESS(rv, false);
  1068     if (!hasResult) {
  1069       return false;
  1072     rv = stmt->GetInt64(0, &_place.visitId);
  1073     NS_ENSURE_SUCCESS(rv, false);
  1074     rv = stmt->GetInt64(1, reinterpret_cast<int64_t*>(&_place.visitTime));
  1075     NS_ENSURE_SUCCESS(rv, false);
  1077     // If we have been given a visit threshold start time, go ahead and
  1078     // calculate if we have been recently visited.
  1079     if (aThresholdStart &&
  1080         aThresholdStart - _place.visitTime <= RECENT_EVENT_THRESHOLD) {
  1081       return true;
  1084     return false;
  1087   /**
  1088    * Fetches information about a referrer for aPlace if it was a recent
  1089    * visit or not.
  1091    * @param aReferrer
  1092    *        The VisitData for the referrer.  This will be populated with
  1093    *        FetchVisitInfo.
  1094    * @param aPlace
  1095    *        The VisitData for the visit we will eventually add.
  1097    */
  1098   void FetchReferrerInfo(VisitData& aReferrer,
  1099                          VisitData& aPlace)
  1101     if (aReferrer.spec.IsEmpty()) {
  1102       return;
  1105     if (!FetchVisitInfo(aReferrer, aPlace.visitTime)) {
  1106       // We must change both the place and referrer to indicate that we will
  1107       // not be using the referrer's data. This behavior has test coverage, so
  1108       // if this invariant changes, we'll know.
  1109       aPlace.referrerSpec.Truncate();
  1110       aReferrer.visitId = 0;
  1114   /**
  1115    * Adds a visit for _place and updates it with the right visit id.
  1117    * @param _place
  1118    *        The VisitData for the place we need to know visit information about.
  1119    * @param aReferrer
  1120    *        A reference to the referrer's visit data.
  1121    */
  1122   nsresult AddVisit(VisitData& _place,
  1123                     const VisitData& aReferrer)
  1125     nsresult rv;
  1126     nsCOMPtr<mozIStorageStatement> stmt;
  1127     if (_place.placeId) {
  1128       stmt = mHistory->GetStatement(
  1129         "INSERT INTO moz_historyvisits "
  1130           "(from_visit, place_id, visit_date, visit_type, session) "
  1131         "VALUES (:from_visit, :page_id, :visit_date, :visit_type, 0) "
  1132       );
  1133       NS_ENSURE_STATE(stmt);
  1134       rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("page_id"), _place.placeId);
  1135       NS_ENSURE_SUCCESS(rv, rv);
  1137     else {
  1138       stmt = mHistory->GetStatement(
  1139         "INSERT INTO moz_historyvisits "
  1140           "(from_visit, place_id, visit_date, visit_type, session) "
  1141         "VALUES (:from_visit, (SELECT id FROM moz_places WHERE url = :page_url), :visit_date, :visit_type, 0) "
  1142       );
  1143       NS_ENSURE_STATE(stmt);
  1144       rv = URIBinder::Bind(stmt, NS_LITERAL_CSTRING("page_url"), _place.spec);
  1145       NS_ENSURE_SUCCESS(rv, rv);
  1147     rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("from_visit"),
  1148                                aReferrer.visitId);
  1149     NS_ENSURE_SUCCESS(rv, rv);
  1150     rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("visit_date"),
  1151                                _place.visitTime);
  1152     NS_ENSURE_SUCCESS(rv, rv);
  1153     uint32_t transitionType = _place.transitionType;
  1154     NS_ASSERTION(transitionType >= nsINavHistoryService::TRANSITION_LINK &&
  1155                  transitionType <= nsINavHistoryService::TRANSITION_FRAMED_LINK,
  1156                  "Invalid transition type!");
  1157     rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("visit_type"),
  1158                                transitionType);
  1159     NS_ENSURE_SUCCESS(rv, rv);
  1161     mozStorageStatementScoper scoper(stmt);
  1162     rv = stmt->Execute();
  1163     NS_ENSURE_SUCCESS(rv, rv);
  1165     // Now that it should be in the database, we need to obtain the id of the
  1166     // visit we just added.
  1167     (void)FetchVisitInfo(_place);
  1169     return NS_OK;
  1172   /**
  1173    * Updates the frecency, and possibly the hidden-ness of aPlace.
  1175    * @param aPlace
  1176    *        The VisitData for the place we want to update.
  1177    */
  1178   nsresult UpdateFrecency(const VisitData& aPlace)
  1180     MOZ_ASSERT(aPlace.shouldUpdateFrecency);
  1182     nsresult rv;
  1183     { // First, set our frecency to the proper value.
  1184       nsCOMPtr<mozIStorageStatement> stmt;
  1185       if (aPlace.placeId) {
  1186         stmt = mHistory->GetStatement(
  1187           "UPDATE moz_places "
  1188           "SET frecency = NOTIFY_FRECENCY("
  1189             "CALCULATE_FRECENCY(:page_id), "
  1190             "url, guid, hidden, last_visit_date"
  1191           ") "
  1192           "WHERE id = :page_id"
  1193         );
  1194         NS_ENSURE_STATE(stmt);
  1195         rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("page_id"), aPlace.placeId);
  1196         NS_ENSURE_SUCCESS(rv, rv);
  1198       else {
  1199         stmt = mHistory->GetStatement(
  1200           "UPDATE moz_places "
  1201           "SET frecency = NOTIFY_FRECENCY("
  1202             "CALCULATE_FRECENCY(id), url, guid, hidden, last_visit_date"
  1203           ") "
  1204           "WHERE url = :page_url"
  1205         );
  1206         NS_ENSURE_STATE(stmt);
  1207         rv = URIBinder::Bind(stmt, NS_LITERAL_CSTRING("page_url"), aPlace.spec);
  1208         NS_ENSURE_SUCCESS(rv, rv);
  1210       mozStorageStatementScoper scoper(stmt);
  1212       rv = stmt->Execute();
  1213       NS_ENSURE_SUCCESS(rv, rv);
  1216     if (!aPlace.hidden) {
  1217       // Mark the page as not hidden if the frecency is now nonzero.
  1218       nsCOMPtr<mozIStorageStatement> stmt;
  1219       if (aPlace.placeId) {
  1220         stmt = mHistory->GetStatement(
  1221           "UPDATE moz_places "
  1222           "SET hidden = 0 "
  1223           "WHERE id = :page_id AND frecency <> 0"
  1224         );
  1225         NS_ENSURE_STATE(stmt);
  1226         rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("page_id"), aPlace.placeId);
  1227         NS_ENSURE_SUCCESS(rv, rv);
  1229       else {
  1230         stmt = mHistory->GetStatement(
  1231           "UPDATE moz_places "
  1232           "SET hidden = 0 "
  1233           "WHERE url = :page_url AND frecency <> 0"
  1234         );
  1235         NS_ENSURE_STATE(stmt);
  1236         rv = URIBinder::Bind(stmt, NS_LITERAL_CSTRING("page_url"), aPlace.spec);
  1237         NS_ENSURE_SUCCESS(rv, rv);
  1240       mozStorageStatementScoper scoper(stmt);
  1241       rv = stmt->Execute();
  1242       NS_ENSURE_SUCCESS(rv, rv);
  1245     return NS_OK;
  1248   mozIStorageConnection* mDBConn;
  1250   nsTArray<VisitData> mPlaces;
  1251   nsTArray<VisitData> mReferrers;
  1253   nsCOMPtr<mozIVisitInfoCallback> mCallback;
  1255   /**
  1256    * Strong reference to the History object because we do not want it to
  1257    * disappear out from under us.
  1258    */
  1259   nsRefPtr<History> mHistory;
  1260 };
  1262 class GetPlaceInfo MOZ_FINAL : public nsRunnable {
  1263 public:
  1264   /**
  1265    * Get the place info for a given place (by GUID or URI)  asynchronously.
  1266    */
  1267   static nsresult Start(mozIStorageConnection* aConnection,
  1268                         VisitData& aPlace,
  1269                         mozIVisitInfoCallback* aCallback) {
  1270     MOZ_ASSERT(NS_IsMainThread(), "This should be called on the main thread");
  1272     nsRefPtr<GetPlaceInfo> event = new GetPlaceInfo(aPlace, aCallback);
  1274     // Get the target thread, and then start the work!
  1275     nsCOMPtr<nsIEventTarget> target = do_GetInterface(aConnection);
  1276     NS_ENSURE_TRUE(target, NS_ERROR_UNEXPECTED);
  1277     nsresult rv = target->Dispatch(event, NS_DISPATCH_NORMAL);
  1278     NS_ENSURE_SUCCESS(rv, rv);
  1280     return NS_OK;
  1283   NS_IMETHOD Run()
  1285     MOZ_ASSERT(!NS_IsMainThread(), "This should not be called on the main thread");
  1287     bool exists;
  1288     nsresult rv = mHistory->FetchPageInfo(mPlace, &exists);
  1289     NS_ENSURE_SUCCESS(rv, rv);
  1291     if (!exists)
  1292       rv = NS_ERROR_NOT_AVAILABLE;
  1294     nsCOMPtr<nsIRunnable> event =
  1295       new NotifyPlaceInfoCallback(mCallback, mPlace, false, rv);
  1297     rv = NS_DispatchToMainThread(event);
  1298     NS_ENSURE_SUCCESS(rv, rv);
  1300     return NS_OK;
  1302 private:
  1303   GetPlaceInfo(VisitData& aPlace,
  1304                mozIVisitInfoCallback* aCallback)
  1305   : mPlace(aPlace)
  1306   , mCallback(aCallback)
  1307   , mHistory(History::GetService())
  1309     MOZ_ASSERT(NS_IsMainThread(), "This should be called on the main thread");
  1312   virtual ~GetPlaceInfo()
  1314     if (mCallback) {
  1315       nsCOMPtr<nsIThread> mainThread = do_GetMainThread();
  1316       (void)NS_ProxyRelease(mainThread, mCallback, true);
  1320   VisitData mPlace;
  1321   nsCOMPtr<mozIVisitInfoCallback> mCallback;
  1322   nsRefPtr<History> mHistory;
  1323 };
  1325 /**
  1326  * Sets the page title for a page in moz_places (if necessary).
  1327  */
  1328 class SetPageTitle : public nsRunnable
  1330 public:
  1331   /**
  1332    * Sets a pages title in the database asynchronously.
  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   static nsresult Start(mozIStorageConnection* aConnection,
  1342                         nsIURI* aURI,
  1343                         const nsAString& aTitle)
  1345     MOZ_ASSERT(NS_IsMainThread(), "This should be called on the main thread");
  1346     MOZ_ASSERT(aURI, "Must pass a non-null URI object!");
  1348     nsCString spec;
  1349     nsresult rv = aURI->GetSpec(spec);
  1350     NS_ENSURE_SUCCESS(rv, rv);
  1352     nsRefPtr<SetPageTitle> event = new SetPageTitle(spec, aTitle);
  1354     // Get the target thread, and then start the work!
  1355     nsCOMPtr<nsIEventTarget> target = do_GetInterface(aConnection);
  1356     NS_ENSURE_TRUE(target, NS_ERROR_UNEXPECTED);
  1357     rv = target->Dispatch(event, NS_DISPATCH_NORMAL);
  1358     NS_ENSURE_SUCCESS(rv, rv);
  1360     return NS_OK;
  1363   NS_IMETHOD Run()
  1365     MOZ_ASSERT(!NS_IsMainThread(), "This should not be called on the main thread");
  1367     // First, see if the page exists in the database (we'll need its id later).
  1368     bool exists;
  1369     nsresult rv = mHistory->FetchPageInfo(mPlace, &exists);
  1370     NS_ENSURE_SUCCESS(rv, rv);
  1372     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       return NS_OK;
  1378     NS_ASSERTION(mPlace.placeId > 0,
  1379                  "We somehow have an invalid place id here!");
  1381     // Now we can update our database record.
  1382     nsCOMPtr<mozIStorageStatement> stmt =
  1383       mHistory->GetStatement(
  1384         "UPDATE moz_places "
  1385         "SET title = :page_title "
  1386         "WHERE id = :page_id "
  1387       );
  1388     NS_ENSURE_STATE(stmt);
  1391       mozStorageStatementScoper scoper(stmt);
  1392       rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("page_id"), mPlace.placeId);
  1393       NS_ENSURE_SUCCESS(rv, rv);
  1394       // Empty strings should clear the title, just like
  1395       // nsNavHistory::SetPageTitle.
  1396       if (mPlace.title.IsEmpty()) {
  1397         rv = stmt->BindNullByName(NS_LITERAL_CSTRING("page_title"));
  1399       else {
  1400         rv = stmt->BindStringByName(NS_LITERAL_CSTRING("page_title"),
  1401                                     StringHead(mPlace.title, TITLE_LENGTH_MAX));
  1403       NS_ENSURE_SUCCESS(rv, rv);
  1404       rv = stmt->Execute();
  1405       NS_ENSURE_SUCCESS(rv, rv);
  1408     nsCOMPtr<nsIRunnable> event =
  1409       new NotifyTitleObservers(mPlace.spec, mPlace.title, mPlace.guid);
  1410     rv = NS_DispatchToMainThread(event);
  1411     NS_ENSURE_SUCCESS(rv, rv);
  1413     return NS_OK;
  1416 private:
  1417   SetPageTitle(const nsCString& aSpec,
  1418                const nsAString& aTitle)
  1419   : mHistory(History::GetService())
  1421     mPlace.spec = aSpec;
  1422     mPlace.title = aTitle;
  1425   VisitData mPlace;
  1427   /**
  1428    * Strong reference to the History object because we do not want it to
  1429    * disappear out from under us.
  1430    */
  1431   nsRefPtr<History> mHistory;
  1432 };
  1434 /**
  1435  * Adds download-specific annotations to a download page.
  1436  */
  1437 class SetDownloadAnnotations MOZ_FINAL : public mozIVisitInfoCallback
  1439 public:
  1440   NS_DECL_ISUPPORTS
  1442   SetDownloadAnnotations(nsIURI* aDestination)
  1443   : mDestination(aDestination)
  1444   , mHistory(History::GetService())
  1446     MOZ_ASSERT(mDestination);
  1447     MOZ_ASSERT(NS_IsMainThread());
  1450   NS_IMETHOD HandleError(nsresult aResultCode, mozIPlaceInfo *aPlaceInfo)
  1452     // Just don't add the annotations in case the visit isn't added.
  1453     return NS_OK;
  1456   NS_IMETHOD HandleResult(mozIPlaceInfo *aPlaceInfo)
  1458     // Exit silently if the download destination is not a local file.
  1459     nsCOMPtr<nsIFileURL> destinationFileURL = do_QueryInterface(mDestination);
  1460     if (!destinationFileURL) {
  1461       return NS_OK;
  1464     nsCOMPtr<nsIURI> source;
  1465     nsresult rv = aPlaceInfo->GetUri(getter_AddRefs(source));
  1466     NS_ENSURE_SUCCESS(rv, rv);
  1468     nsCOMPtr<nsIFile> destinationFile;
  1469     rv = destinationFileURL->GetFile(getter_AddRefs(destinationFile));
  1470     NS_ENSURE_SUCCESS(rv, rv);
  1472     nsAutoString destinationFileName;
  1473     rv = destinationFile->GetLeafName(destinationFileName);
  1474     NS_ENSURE_SUCCESS(rv, rv);
  1476     nsAutoCString destinationURISpec;
  1477     rv = destinationFileURL->GetSpec(destinationURISpec);
  1478     NS_ENSURE_SUCCESS(rv, rv);
  1480     // Use annotations for storing the additional download metadata.
  1481     nsAnnotationService* annosvc = nsAnnotationService::GetAnnotationService();
  1482     NS_ENSURE_TRUE(annosvc, NS_ERROR_OUT_OF_MEMORY);
  1484     rv = annosvc->SetPageAnnotationString(
  1485       source,
  1486       DESTINATIONFILEURI_ANNO,
  1487       NS_ConvertUTF8toUTF16(destinationURISpec),
  1488       0,
  1489       nsIAnnotationService::EXPIRE_WITH_HISTORY
  1490     );
  1491     NS_ENSURE_SUCCESS(rv, rv);
  1493     rv = annosvc->SetPageAnnotationString(
  1494       source,
  1495       DESTINATIONFILENAME_ANNO,
  1496       destinationFileName,
  1497       0,
  1498       nsIAnnotationService::EXPIRE_WITH_HISTORY
  1499     );
  1500     NS_ENSURE_SUCCESS(rv, rv);
  1502     nsAutoString title;
  1503     rv = aPlaceInfo->GetTitle(title);
  1504     NS_ENSURE_SUCCESS(rv, rv);
  1506     // In case we are downloading a file that does not correspond to a web
  1507     // page for which the title is present, we populate the otherwise empty
  1508     // history title with the name of the destination file, to allow it to be
  1509     // visible and searchable in history results.
  1510     if (title.IsEmpty()) {
  1511       rv = mHistory->SetURITitle(source, destinationFileName);
  1512       NS_ENSURE_SUCCESS(rv, rv);
  1515     return NS_OK;
  1518   NS_IMETHOD HandleCompletion()
  1520     return NS_OK;
  1523 private:
  1524   nsCOMPtr<nsIURI> mDestination;
  1526   /**
  1527    * Strong reference to the History object because we do not want it to
  1528    * disappear out from under us.
  1529    */
  1530   nsRefPtr<History> mHistory;
  1531 };
  1532 NS_IMPL_ISUPPORTS(
  1533   SetDownloadAnnotations,
  1534   mozIVisitInfoCallback
  1537 /**
  1538  * Enumerator used by NotifyRemoveVisits to transfer the hash entries.
  1539  */
  1540 static PLDHashOperator TransferHashEntries(PlaceHashKey* aEntry,
  1541                                            void* aHash)
  1543   nsTHashtable<PlaceHashKey>* hash =
  1544     static_cast<nsTHashtable<PlaceHashKey> *>(aHash);
  1545   PlaceHashKey* copy = hash->PutEntry(aEntry->GetKey());
  1546   copy->visitCount = aEntry->visitCount;
  1547   copy->bookmarked = aEntry->bookmarked;
  1548   aEntry->visits.SwapElements(copy->visits);
  1549   return PL_DHASH_NEXT;
  1552 /**
  1553  * Enumerator used by NotifyRemoveVisits to notify removals.
  1554  */
  1555 static PLDHashOperator NotifyVisitRemoval(PlaceHashKey* aEntry,
  1556                                           void* aHistory)
  1558   nsNavHistory* history = static_cast<nsNavHistory *>(aHistory);
  1559   const nsTArray<VisitData>& visits = aEntry->visits;
  1560   nsCOMPtr<nsIURI> uri;
  1561   (void)NS_NewURI(getter_AddRefs(uri), visits[0].spec);
  1562   bool removingPage = visits.Length() == aEntry->visitCount &&
  1563                       !aEntry->bookmarked;
  1564   // FindRemovableVisits only sets the transition type on the VisitData objects
  1565   // it collects if the visits were filtered by transition type.
  1566   // RemoveVisitsFilter currently only supports filtering by transition type, so
  1567   // FindRemovableVisits will either find all visits, or all visits of a given
  1568   // type. Therefore, if transitionType is set on this visit, we pass the
  1569   // transition type to NotifyOnPageExpired which in turns passes it to
  1570   // OnDeleteVisits to indicate that all visits of a given type were removed.
  1571   uint32_t transition = visits[0].transitionType < UINT32_MAX ?
  1572                           visits[0].transitionType : 0;
  1573   history->NotifyOnPageExpired(uri, visits[0].visitTime, removingPage,
  1574                                visits[0].guid,
  1575                                nsINavHistoryObserver::REASON_DELETED,
  1576                                transition);
  1577   return PL_DHASH_NEXT;
  1580 /**
  1581  * Notify removed visits to observers.
  1582  */
  1583 class NotifyRemoveVisits : public nsRunnable
  1585 public:
  1587   NotifyRemoveVisits(nsTHashtable<PlaceHashKey>& aPlaces)
  1588     : mPlaces(VISITS_REMOVAL_INITIAL_HASH_SIZE)
  1589     , mHistory(History::GetService())
  1591     MOZ_ASSERT(!NS_IsMainThread(),
  1592                "This should not be called on the main thread");
  1593     aPlaces.EnumerateEntries(TransferHashEntries, &mPlaces);
  1596   NS_IMETHOD Run()
  1598     MOZ_ASSERT(NS_IsMainThread(), "This should be called on the main thread");
  1600     // We are in the main thread, no need to lock.
  1601     if (mHistory->IsShuttingDown()) {
  1602       // If we are shutting down, we cannot notify the observers.
  1603       return NS_OK;
  1606     nsNavHistory* navHistory = nsNavHistory::GetHistoryService();
  1607     if (!navHistory) {
  1608       NS_WARNING("Cannot notify without the history service!");
  1609       return NS_OK;
  1612     // Wrap all notifications in a batch, so the view can handle changes in a
  1613     // more performant way, by initiating a refresh after a limited number of
  1614     // single changes.
  1615     (void)navHistory->BeginUpdateBatch();
  1616     mPlaces.EnumerateEntries(NotifyVisitRemoval, navHistory);
  1617     (void)navHistory->EndUpdateBatch();
  1619     return NS_OK;
  1622 private:
  1623   nsTHashtable<PlaceHashKey> mPlaces;
  1625   /**
  1626    * Strong reference to the History object because we do not want it to
  1627    * disappear out from under us.
  1628    */
  1629   nsRefPtr<History> mHistory;
  1630 };
  1632 /**
  1633  * Enumerator used by RemoveVisits to populate list of removed place ids.
  1634  */
  1635 static PLDHashOperator ListToBeRemovedPlaceIds(PlaceHashKey* aEntry,
  1636                                                void* aIdsList)
  1638   const nsTArray<VisitData>& visits = aEntry->visits;
  1639   // Only orphan ids should be listed.
  1640   if (visits.Length() == aEntry->visitCount && !aEntry->bookmarked) {
  1641     nsCString* list = static_cast<nsCString*>(aIdsList);
  1642     if (!list->IsEmpty())
  1643       list->AppendLiteral(",");
  1644     list->AppendInt(visits[0].placeId);
  1646   return PL_DHASH_NEXT;
  1649 /**
  1650  * Remove visits from history.
  1651  */
  1652 class RemoveVisits : public nsRunnable
  1654 public:
  1655   /**
  1656    * Asynchronously removes visits from history.
  1658    * @param aConnection
  1659    *        The database connection to use for these operations.
  1660    * @param aFilter
  1661    *        Filter to remove visits.
  1662    */
  1663   static nsresult Start(mozIStorageConnection* aConnection,
  1664                         RemoveVisitsFilter& aFilter)
  1666     MOZ_ASSERT(NS_IsMainThread(), "This should be called on the main thread");
  1668     nsRefPtr<RemoveVisits> event = new RemoveVisits(aConnection, aFilter);
  1670     // Get the target thread, and then start the work!
  1671     nsCOMPtr<nsIEventTarget> target = do_GetInterface(aConnection);
  1672     NS_ENSURE_TRUE(target, NS_ERROR_UNEXPECTED);
  1673     nsresult rv = target->Dispatch(event, NS_DISPATCH_NORMAL);
  1674     NS_ENSURE_SUCCESS(rv, rv);
  1676     return NS_OK;
  1679   NS_IMETHOD Run()
  1681     MOZ_ASSERT(!NS_IsMainThread(),
  1682                "This should not be called on the main thread");
  1684     // Prevent the main thread from shutting down while this is running.
  1685     MutexAutoLock lockedScope(mHistory->GetShutdownMutex());
  1686     if (mHistory->IsShuttingDown()) {
  1687       // If we were already shutting down, we cannot remove the visits.
  1688       return NS_OK;
  1691     // Find all the visits relative to the current filters and whether their
  1692     // pages will be removed or not.
  1693     nsTHashtable<PlaceHashKey> places(VISITS_REMOVAL_INITIAL_HASH_SIZE);
  1694     nsresult rv = FindRemovableVisits(places);
  1695     NS_ENSURE_SUCCESS(rv, rv);
  1697     if (places.Count() == 0)
  1698       return NS_OK;
  1700     mozStorageTransaction transaction(mDBConn, false,
  1701                                       mozIStorageConnection::TRANSACTION_IMMEDIATE);
  1703     rv = RemoveVisitsFromDatabase();
  1704     NS_ENSURE_SUCCESS(rv, rv);
  1705     rv = RemovePagesFromDatabase(places);
  1706     NS_ENSURE_SUCCESS(rv, rv);
  1708     rv = transaction.Commit();
  1709     NS_ENSURE_SUCCESS(rv, rv);
  1711     nsCOMPtr<nsIRunnable> event = new NotifyRemoveVisits(places);
  1712     rv = NS_DispatchToMainThread(event);
  1713     NS_ENSURE_SUCCESS(rv, rv);
  1715     return NS_OK;
  1718 private:
  1719   RemoveVisits(mozIStorageConnection* aConnection,
  1720                RemoveVisitsFilter& aFilter)
  1721   : mDBConn(aConnection)
  1722   , mHasTransitionType(false)
  1723   , mHistory(History::GetService())
  1725     MOZ_ASSERT(NS_IsMainThread(), "This should be called on the main thread");
  1727     // Build query conditions.
  1728     nsTArray<nsCString> conditions;
  1729     // TODO: add support for binding params when adding further stuff here.
  1730     if (aFilter.transitionType < UINT32_MAX) {
  1731       conditions.AppendElement(nsPrintfCString("visit_type = %d", aFilter.transitionType));
  1732       mHasTransitionType = true;
  1734     if (conditions.Length() > 0) {
  1735       mWhereClause.AppendLiteral (" WHERE ");
  1736       for (uint32_t i = 0; i < conditions.Length(); ++i) {
  1737         if (i > 0)
  1738           mWhereClause.AppendLiteral(" AND ");
  1739         mWhereClause.Append(conditions[i]);
  1744   nsresult
  1745   FindRemovableVisits(nsTHashtable<PlaceHashKey>& aPlaces)
  1747     MOZ_ASSERT(!NS_IsMainThread(),
  1748                "This should not be called on the main thread");
  1750     nsCString query("SELECT h.id, url, guid, visit_date, visit_type, "
  1751                     "(SELECT count(*) FROM moz_historyvisits WHERE place_id = h.id) as full_visit_count, "
  1752                     "EXISTS(SELECT 1 FROM moz_bookmarks WHERE fk = h.id) as bookmarked "
  1753                     "FROM moz_historyvisits "
  1754                     "JOIN moz_places h ON place_id = h.id");
  1755     query.Append(mWhereClause);
  1757     nsCOMPtr<mozIStorageStatement> stmt = mHistory->GetStatement(query);
  1758     NS_ENSURE_STATE(stmt);
  1759     mozStorageStatementScoper scoper(stmt);
  1761     bool hasResult;
  1762     nsresult rv;
  1763     while (NS_SUCCEEDED((rv = stmt->ExecuteStep(&hasResult))) && hasResult) {
  1764       VisitData visit;
  1765       rv = stmt->GetInt64(0, &visit.placeId);
  1766       NS_ENSURE_SUCCESS(rv, rv);
  1767       rv = stmt->GetUTF8String(1, visit.spec);
  1768       NS_ENSURE_SUCCESS(rv, rv);
  1769       rv = stmt->GetUTF8String(2, visit.guid);
  1770       NS_ENSURE_SUCCESS(rv, rv);
  1771       rv = stmt->GetInt64(3, &visit.visitTime);
  1772       NS_ENSURE_SUCCESS(rv, rv);
  1773       if (mHasTransitionType) {
  1774         int32_t transition;
  1775         rv = stmt->GetInt32(4, &transition);
  1776         NS_ENSURE_SUCCESS(rv, rv);
  1777         visit.transitionType = static_cast<uint32_t>(transition);
  1779       int32_t visitCount, bookmarked;
  1780       rv = stmt->GetInt32(5, &visitCount);
  1781       NS_ENSURE_SUCCESS(rv, rv);
  1782       rv = stmt->GetInt32(6, &bookmarked);
  1783       NS_ENSURE_SUCCESS(rv, rv);
  1785       PlaceHashKey* entry = aPlaces.GetEntry(visit.spec);
  1786       if (!entry) {
  1787         entry = aPlaces.PutEntry(visit.spec);
  1789       entry->visitCount = visitCount;
  1790       entry->bookmarked = bookmarked;
  1791       entry->visits.AppendElement(visit);
  1793     NS_ENSURE_SUCCESS(rv, rv);
  1795     return NS_OK;
  1798   nsresult
  1799   RemoveVisitsFromDatabase()
  1801     MOZ_ASSERT(!NS_IsMainThread(),
  1802                "This should not be called on the main thread");
  1804     nsCString query("DELETE FROM moz_historyvisits");
  1805     query.Append(mWhereClause);
  1807     nsCOMPtr<mozIStorageStatement> stmt = mHistory->GetStatement(query);
  1808     NS_ENSURE_STATE(stmt);
  1809     mozStorageStatementScoper scoper(stmt);
  1810     nsresult rv = stmt->Execute();
  1811     NS_ENSURE_SUCCESS(rv, rv);
  1813     return NS_OK;
  1816   nsresult
  1817   RemovePagesFromDatabase(nsTHashtable<PlaceHashKey>& aPlaces)
  1819     MOZ_ASSERT(!NS_IsMainThread(),
  1820                "This should not be called on the main thread");
  1822     nsCString placeIdsToRemove;
  1823     aPlaces.EnumerateEntries(ListToBeRemovedPlaceIds, &placeIdsToRemove);
  1825 #ifdef DEBUG
  1827       // Ensure that we are not removing any problematic entry.
  1828       nsCString query("SELECT id FROM moz_places h WHERE id IN (");
  1829       query.Append(placeIdsToRemove);
  1830       query.AppendLiteral(") AND ("
  1831           "EXISTS(SELECT 1 FROM moz_bookmarks WHERE fk = h.id) OR "
  1832           "EXISTS(SELECT 1 FROM moz_historyvisits WHERE place_id = h.id) OR "
  1833           "SUBSTR(h.url, 1, 6) = 'place:' "
  1834         ")");
  1835       nsCOMPtr<mozIStorageStatement> stmt = mHistory->GetStatement(query);
  1836       NS_ENSURE_STATE(stmt);
  1837       mozStorageStatementScoper scoper(stmt);
  1838       bool hasResult;
  1839       MOZ_ASSERT(NS_SUCCEEDED(stmt->ExecuteStep(&hasResult)) && !hasResult,
  1840                  "Trying to remove a non-oprhan place from the database");
  1842 #endif
  1844     nsCString query("DELETE FROM moz_places "
  1845                     "WHERE id IN (");
  1846     query.Append(placeIdsToRemove);
  1847     query.AppendLiteral(")");
  1849     nsCOMPtr<mozIStorageStatement> stmt = mHistory->GetStatement(query);
  1850     NS_ENSURE_STATE(stmt);
  1851     mozStorageStatementScoper scoper(stmt);
  1852     nsresult rv = stmt->Execute();
  1853     NS_ENSURE_SUCCESS(rv, rv);
  1855     return NS_OK;
  1858   mozIStorageConnection* mDBConn;
  1859   bool mHasTransitionType;
  1860   nsCString mWhereClause;
  1862   /**
  1863    * Strong reference to the History object because we do not want it to
  1864    * disappear out from under us.
  1865    */
  1866   nsRefPtr<History> mHistory;
  1867 };
  1869 /**
  1870  * Stores an embed visit, and notifies observers.
  1872  * @param aPlace
  1873  *        The VisitData of the visit to store as an embed visit.
  1874  * @param [optional] aCallback
  1875  *        The mozIVisitInfoCallback to notify, if provided.
  1876  */
  1877 void
  1878 StoreAndNotifyEmbedVisit(VisitData& aPlace,
  1879                          mozIVisitInfoCallback* aCallback = nullptr)
  1881   MOZ_ASSERT(aPlace.transitionType == nsINavHistoryService::TRANSITION_EMBED,
  1882              "Must only pass TRANSITION_EMBED visits to this!");
  1883   MOZ_ASSERT(NS_IsMainThread(), "Must be called on the main thread!");
  1885   nsCOMPtr<nsIURI> uri;
  1886   (void)NS_NewURI(getter_AddRefs(uri), aPlace.spec);
  1888   nsNavHistory* navHistory = nsNavHistory::GetHistoryService();
  1889   if (!navHistory || !uri) {
  1890     return;
  1893   navHistory->registerEmbedVisit(uri, aPlace.visitTime);
  1895   if (aCallback) {
  1896     // NotifyPlaceInfoCallback does not hold a strong reference to the callback,
  1897     // so we have to manage it by AddRefing now and then releasing it after the
  1898     // event has run.
  1899     NS_ADDREF(aCallback);
  1900     nsCOMPtr<nsIRunnable> event =
  1901       new NotifyPlaceInfoCallback(aCallback, aPlace, true, NS_OK);
  1902     (void)NS_DispatchToMainThread(event);
  1904     // Also dispatch an event to release our reference to the callback after
  1905     // NotifyPlaceInfoCallback has run.
  1906     nsCOMPtr<nsIThread> mainThread = do_GetMainThread();
  1907     (void)NS_ProxyRelease(mainThread, aCallback, true);
  1910   VisitData noReferrer;
  1911   nsCOMPtr<nsIRunnable> event = new NotifyVisitObservers(aPlace, noReferrer);
  1912   (void)NS_DispatchToMainThread(event);
  1915 } // anonymous namespace
  1917 ////////////////////////////////////////////////////////////////////////////////
  1918 //// History
  1920 History* History::gService = nullptr;
  1922 History::History()
  1923   : mShuttingDown(false)
  1924   , mShutdownMutex("History::mShutdownMutex")
  1925   , mObservers(VISIT_OBSERVERS_INITIAL_CACHE_SIZE)
  1926   , mRecentlyVisitedURIsNextIndex(0)
  1928   NS_ASSERTION(!gService, "Ruh-roh!  This service has already been created!");
  1929   gService = this;
  1931   nsCOMPtr<nsIObserverService> os = services::GetObserverService();
  1932   NS_WARN_IF_FALSE(os, "Observer service was not found!");
  1933   if (os) {
  1934     (void)os->AddObserver(this, TOPIC_PLACES_SHUTDOWN, false);
  1938 History::~History()
  1940   UnregisterWeakMemoryReporter(this);
  1942   gService = nullptr;
  1944   NS_ASSERTION(mObservers.Count() == 0,
  1945                "Not all Links were removed before we disappear!");
  1948 void
  1949 History::InitMemoryReporter()
  1951   RegisterWeakMemoryReporter(this);
  1954 NS_IMETHODIMP
  1955 History::NotifyVisited(nsIURI* aURI)
  1957   NS_ENSURE_ARG(aURI);
  1959   nsAutoScriptBlocker scriptBlocker;
  1961   if (XRE_GetProcessType() == GeckoProcessType_Default) {
  1962     nsTArray<ContentParent*> cplist;
  1963     ContentParent::GetAll(cplist);
  1965     if (!cplist.IsEmpty()) {
  1966       URIParams uri;
  1967       SerializeURI(aURI, uri);
  1968       for (uint32_t i = 0; i < cplist.Length(); ++i) {
  1969         unused << cplist[i]->SendNotifyVisited(uri);
  1974   // If we have no observers for this URI, we have nothing to notify about.
  1975   KeyClass* key = mObservers.GetEntry(aURI);
  1976   if (!key) {
  1977     return NS_OK;
  1980   // Update status of each Link node.
  1982     // RemoveEntry will destroy the array, this iterator should not survive it.
  1983     ObserverArray::ForwardIterator iter(key->array);
  1984     while (iter.HasMore()) {
  1985       Link* link = iter.GetNext();
  1986       link->SetLinkState(eLinkState_Visited);
  1987       // Verify that the observers hash doesn't mutate while looping through
  1988       // the links associated with this URI.
  1989       NS_ABORT_IF_FALSE(key == mObservers.GetEntry(aURI),
  1990                         "The URIs hash mutated!");
  1994   // All the registered nodes can now be removed for this URI.
  1995   mObservers.RemoveEntry(aURI);
  1996   return NS_OK;
  1999 mozIStorageAsyncStatement*
  2000 History::GetIsVisitedStatement()
  2002   if (mIsVisitedStatement) {
  2003     return mIsVisitedStatement;
  2006   // If we don't yet have a database connection, go ahead and clone it now.
  2007   if (!mReadOnlyDBConn) {
  2008     mozIStorageConnection* dbConn = GetDBConn();
  2009     NS_ENSURE_TRUE(dbConn, nullptr);
  2011     (void)dbConn->Clone(true, getter_AddRefs(mReadOnlyDBConn));
  2012     NS_ENSURE_TRUE(mReadOnlyDBConn, nullptr);
  2015   // Now we can create our cached statement.
  2016   nsresult rv = mReadOnlyDBConn->CreateAsyncStatement(NS_LITERAL_CSTRING(
  2017     "SELECT 1 "
  2018     "FROM moz_places h "
  2019     "WHERE url = ?1 "
  2020       "AND last_visit_date NOTNULL "
  2021   ),  getter_AddRefs(mIsVisitedStatement));
  2022   NS_ENSURE_SUCCESS(rv, nullptr);
  2023   return mIsVisitedStatement;
  2026 nsresult
  2027 History::InsertPlace(const VisitData& aPlace)
  2029   NS_PRECONDITION(aPlace.placeId == 0, "should not have a valid place id!");
  2030   NS_PRECONDITION(!NS_IsMainThread(), "must be called off of the main thread!");
  2032   nsCOMPtr<mozIStorageStatement> stmt = GetStatement(
  2033       "INSERT INTO moz_places "
  2034         "(url, title, rev_host, hidden, typed, frecency, guid) "
  2035       "VALUES (:url, :title, :rev_host, :hidden, :typed, :frecency, :guid) "
  2036     );
  2037   NS_ENSURE_STATE(stmt);
  2038   mozStorageStatementScoper scoper(stmt);
  2040   nsresult rv = stmt->BindStringByName(NS_LITERAL_CSTRING("rev_host"),
  2041                                        aPlace.revHost);
  2042   NS_ENSURE_SUCCESS(rv, rv);
  2043   rv = URIBinder::Bind(stmt, NS_LITERAL_CSTRING("url"), aPlace.spec);
  2044   NS_ENSURE_SUCCESS(rv, rv);
  2045   nsString title = aPlace.title;
  2046   // Empty strings should have no title, just like nsNavHistory::SetPageTitle.
  2047   if (title.IsEmpty()) {
  2048     rv = stmt->BindNullByName(NS_LITERAL_CSTRING("title"));
  2050   else {
  2051     title.Assign(StringHead(aPlace.title, TITLE_LENGTH_MAX));
  2052     rv = stmt->BindStringByName(NS_LITERAL_CSTRING("title"), title);
  2054   NS_ENSURE_SUCCESS(rv, rv);
  2055   rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("typed"), aPlace.typed);
  2056   NS_ENSURE_SUCCESS(rv, rv);
  2057   // When inserting a page for a first visit that should not appear in
  2058   // autocomplete, for example an error page, use a zero frecency.
  2059   int32_t frecency = aPlace.shouldUpdateFrecency ? aPlace.frecency : 0;
  2060   rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("frecency"), frecency);
  2061   NS_ENSURE_SUCCESS(rv, rv);
  2062   rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("hidden"), aPlace.hidden);
  2063   NS_ENSURE_SUCCESS(rv, rv);
  2064   nsAutoCString guid(aPlace.guid);
  2065   if (aPlace.guid.IsVoid()) {
  2066     rv = GenerateGUID(guid);
  2067     NS_ENSURE_SUCCESS(rv, rv);
  2069   rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("guid"), guid);
  2070   NS_ENSURE_SUCCESS(rv, rv);
  2071   rv = stmt->Execute();
  2072   NS_ENSURE_SUCCESS(rv, rv);
  2074   // Post an onFrecencyChanged observer notification.
  2075   const nsNavHistory* navHistory = nsNavHistory::GetConstHistoryService();
  2076   NS_ENSURE_STATE(navHistory);
  2077   navHistory->DispatchFrecencyChangedNotification(aPlace.spec, frecency, guid,
  2078                                                   aPlace.hidden,
  2079                                                   aPlace.visitTime);
  2081   return NS_OK;
  2084 nsresult
  2085 History::UpdatePlace(const VisitData& aPlace)
  2087   NS_PRECONDITION(!NS_IsMainThread(), "must be called off of the main thread!");
  2088   NS_PRECONDITION(aPlace.placeId > 0, "must have a valid place id!");
  2089   NS_PRECONDITION(!aPlace.guid.IsVoid(), "must have a guid!");
  2091   nsCOMPtr<mozIStorageStatement> stmt = GetStatement(
  2092       "UPDATE moz_places "
  2093       "SET title = :title, "
  2094           "hidden = :hidden, "
  2095           "typed = :typed, "
  2096           "guid = :guid "
  2097       "WHERE id = :page_id "
  2098     );
  2099   NS_ENSURE_STATE(stmt);
  2100   mozStorageStatementScoper scoper(stmt);
  2102   nsresult rv;
  2103   // Empty strings should clear the title, just like nsNavHistory::SetPageTitle.
  2104   if (aPlace.title.IsEmpty()) {
  2105     rv = stmt->BindNullByName(NS_LITERAL_CSTRING("title"));
  2107   else {
  2108     rv = stmt->BindStringByName(NS_LITERAL_CSTRING("title"),
  2109                                 StringHead(aPlace.title, TITLE_LENGTH_MAX));
  2111   NS_ENSURE_SUCCESS(rv, rv);
  2112   rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("typed"), aPlace.typed);
  2113   NS_ENSURE_SUCCESS(rv, rv);
  2114   rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("hidden"), aPlace.hidden);
  2115   NS_ENSURE_SUCCESS(rv, rv);
  2116   rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("guid"), aPlace.guid);
  2117   NS_ENSURE_SUCCESS(rv, rv);
  2118   rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("page_id"),
  2119                              aPlace.placeId);
  2120   NS_ENSURE_SUCCESS(rv, rv);
  2121   rv = stmt->Execute();
  2122   NS_ENSURE_SUCCESS(rv, rv);
  2124   return NS_OK;
  2127 nsresult
  2128 History::FetchPageInfo(VisitData& _place, bool* _exists)
  2130   NS_PRECONDITION(!_place.spec.IsEmpty() || !_place.guid.IsEmpty(), "must have either a non-empty spec or guid!");
  2131   NS_PRECONDITION(!NS_IsMainThread(), "must be called off of the main thread!");
  2133   nsresult rv;
  2135   // URI takes precedence.
  2136   nsCOMPtr<mozIStorageStatement> stmt;
  2137   bool selectByURI = !_place.spec.IsEmpty();
  2138   if (selectByURI) {
  2139     stmt = GetStatement(
  2140       "SELECT guid, id, title, hidden, typed, frecency "
  2141       "FROM moz_places "
  2142       "WHERE url = :page_url "
  2143     );
  2144     NS_ENSURE_STATE(stmt);
  2146     rv = URIBinder::Bind(stmt, NS_LITERAL_CSTRING("page_url"), _place.spec);
  2147     NS_ENSURE_SUCCESS(rv, rv);
  2149   else {
  2150     stmt = GetStatement(
  2151       "SELECT url, id, title, hidden, typed, frecency "
  2152       "FROM moz_places "
  2153       "WHERE guid = :guid "
  2154     );
  2155     NS_ENSURE_STATE(stmt);
  2157     rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("guid"), _place.guid);
  2158     NS_ENSURE_SUCCESS(rv, rv);
  2161   mozStorageStatementScoper scoper(stmt);
  2163   rv = stmt->ExecuteStep(_exists);
  2164   NS_ENSURE_SUCCESS(rv, rv);
  2166   if (!*_exists) {
  2167     return NS_OK;
  2170   if (selectByURI) {
  2171     if (_place.guid.IsEmpty()) {
  2172       rv = stmt->GetUTF8String(0, _place.guid);
  2173       NS_ENSURE_SUCCESS(rv, rv);
  2176   else {
  2177     nsAutoCString spec;
  2178     rv = stmt->GetUTF8String(0, spec);
  2179     NS_ENSURE_SUCCESS(rv, rv);
  2180     _place.spec = spec;
  2183   rv = stmt->GetInt64(1, &_place.placeId);
  2184   NS_ENSURE_SUCCESS(rv, rv);
  2186   nsAutoString title;
  2187   rv = stmt->GetString(2, title);
  2188   NS_ENSURE_SUCCESS(rv, rv);
  2190   // If the title we were given was void, that means we did not bother to set
  2191   // it to anything.  As a result, ignore the fact that we may have changed the
  2192   // title (because we don't want to, that would be empty), and set the title
  2193   // to what is currently stored in the datbase.
  2194   if (_place.title.IsVoid()) {
  2195     _place.title = title;
  2197   // Otherwise, just indicate if the title has changed.
  2198   else {
  2199     _place.titleChanged = !(_place.title.Equals(title) ||
  2200                             (_place.title.IsEmpty() && title.IsVoid()));
  2203   if (_place.hidden) {
  2204     // If this transition was hidden, it is possible that others were not.
  2205     // Any one visible transition makes this location visible. If database
  2206     // has location as visible, reflect that in our data structure.
  2207     int32_t hidden;
  2208     rv = stmt->GetInt32(3, &hidden);
  2209     NS_ENSURE_SUCCESS(rv, rv);
  2210     _place.hidden = !!hidden;
  2213   if (!_place.typed) {
  2214     // If this transition wasn't typed, others might have been. If database
  2215     // has location as typed, reflect that in our data structure.
  2216     int32_t typed;
  2217     rv = stmt->GetInt32(4, &typed);
  2218     NS_ENSURE_SUCCESS(rv, rv);
  2219     _place.typed = !!typed;
  2222   rv = stmt->GetInt32(5, &_place.frecency);
  2223   NS_ENSURE_SUCCESS(rv, rv);
  2224   return NS_OK;
  2227 /* static */ size_t
  2228 History::SizeOfEntryExcludingThis(KeyClass* aEntry, mozilla::MallocSizeOf aMallocSizeOf, void *)
  2230   return aEntry->array.SizeOfExcludingThis(aMallocSizeOf);
  2233 MOZ_DEFINE_MALLOC_SIZE_OF(HistoryMallocSizeOf)
  2235 NS_IMETHODIMP
  2236 History::CollectReports(nsIHandleReportCallback* aHandleReport,
  2237                         nsISupports* aData)
  2239   return MOZ_COLLECT_REPORT(
  2240     "explicit/history-links-hashtable", KIND_HEAP, UNITS_BYTES,
  2241     SizeOfIncludingThis(HistoryMallocSizeOf),
  2242     "Memory used by the hashtable that records changes to the visited state "
  2243     "of links.");
  2246 size_t
  2247 History::SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOfThis)
  2249   return aMallocSizeOfThis(this) +
  2250          mObservers.SizeOfExcludingThis(SizeOfEntryExcludingThis, aMallocSizeOfThis);
  2253 /* static */
  2254 History*
  2255 History::GetService()
  2257   if (gService) {
  2258     return gService;
  2261   nsCOMPtr<IHistory> service(do_GetService(NS_IHISTORY_CONTRACTID));
  2262   NS_ABORT_IF_FALSE(service, "Cannot obtain IHistory service!");
  2263   NS_ASSERTION(gService, "Our constructor was not run?!");
  2265   return gService;
  2268 /* static */
  2269 History*
  2270 History::GetSingleton()
  2272   if (!gService) {
  2273     gService = new History();
  2274     NS_ENSURE_TRUE(gService, nullptr);
  2275     gService->InitMemoryReporter();
  2278   NS_ADDREF(gService);
  2279   return gService;
  2282 mozIStorageConnection*
  2283 History::GetDBConn()
  2285   if (!mDB) {
  2286     mDB = Database::GetDatabase();
  2287     NS_ENSURE_TRUE(mDB, nullptr);
  2289   return mDB->MainConn();
  2292 void
  2293 History::Shutdown()
  2295   MOZ_ASSERT(NS_IsMainThread());
  2297   // Prevent other threads from scheduling uses of the DB while we mark
  2298   // ourselves as shutting down.
  2299   MutexAutoLock lockedScope(mShutdownMutex);
  2300   MOZ_ASSERT(!mShuttingDown && "Shutdown was called more than once!");
  2302   mShuttingDown = true;
  2304   if (mReadOnlyDBConn) {
  2305     if (mIsVisitedStatement) {
  2306       (void)mIsVisitedStatement->Finalize();
  2308     (void)mReadOnlyDBConn->AsyncClose(nullptr);
  2312 void
  2313 History::AppendToRecentlyVisitedURIs(nsIURI* aURI) {
  2314   if (mRecentlyVisitedURIs.Length() < RECENTLY_VISITED_URI_SIZE) {
  2315     // Append a new element while the array is not full.
  2316     mRecentlyVisitedURIs.AppendElement(aURI);
  2317   } else {
  2318     // Otherwise, replace the oldest member.
  2319     mRecentlyVisitedURIsNextIndex %= RECENTLY_VISITED_URI_SIZE;
  2320     mRecentlyVisitedURIs.ElementAt(mRecentlyVisitedURIsNextIndex) = aURI;
  2321     mRecentlyVisitedURIsNextIndex++;
  2325 inline bool
  2326 History::IsRecentlyVisitedURI(nsIURI* aURI) {
  2327   bool equals = false;
  2328   RecentlyVisitedArray::index_type i;
  2329   for (i = 0; i < mRecentlyVisitedURIs.Length() && !equals; ++i) {
  2330     aURI->Equals(mRecentlyVisitedURIs.ElementAt(i), &equals);
  2332   return equals;
  2335 ////////////////////////////////////////////////////////////////////////////////
  2336 //// IHistory
  2338 NS_IMETHODIMP
  2339 History::VisitURI(nsIURI* aURI,
  2340                   nsIURI* aLastVisitedURI,
  2341                   uint32_t aFlags)
  2343   NS_PRECONDITION(aURI, "URI should not be NULL.");
  2344   if (mShuttingDown) {
  2345     return NS_OK;
  2348   if (XRE_GetProcessType() == GeckoProcessType_Content) {
  2349     URIParams uri;
  2350     SerializeURI(aURI, uri);
  2352     OptionalURIParams lastVisitedURI;
  2353     SerializeURI(aLastVisitedURI, lastVisitedURI);
  2355     mozilla::dom::ContentChild* cpc =
  2356       mozilla::dom::ContentChild::GetSingleton();
  2357     NS_ASSERTION(cpc, "Content Protocol is NULL!");
  2358     (void)cpc->SendVisitURI(uri, lastVisitedURI, aFlags);
  2359     return NS_OK;
  2362   nsNavHistory* navHistory = nsNavHistory::GetHistoryService();
  2363   NS_ENSURE_TRUE(navHistory, NS_ERROR_OUT_OF_MEMORY);
  2365   // Silently return if URI is something we shouldn't add to DB.
  2366   bool canAdd;
  2367   nsresult rv = navHistory->CanAddURI(aURI, &canAdd);
  2368   NS_ENSURE_SUCCESS(rv, rv);
  2369   if (!canAdd) {
  2370     return NS_OK;
  2373   if (aLastVisitedURI) {
  2374     bool same;
  2375     rv = aURI->Equals(aLastVisitedURI, &same);
  2376     NS_ENSURE_SUCCESS(rv, rv);
  2377     if (same && IsRecentlyVisitedURI(aURI)) {
  2378       // Do not save refresh visits if we have visited this URI recently.
  2379       return NS_OK;
  2383   nsTArray<VisitData> placeArray(1);
  2384   NS_ENSURE_TRUE(placeArray.AppendElement(VisitData(aURI, aLastVisitedURI)),
  2385                  NS_ERROR_OUT_OF_MEMORY);
  2386   VisitData& place = placeArray.ElementAt(0);
  2387   NS_ENSURE_FALSE(place.spec.IsEmpty(), NS_ERROR_INVALID_ARG);
  2389   place.visitTime = PR_Now();
  2391   // Assigns a type to the edge in the visit linked list. Each type will be
  2392   // considered differently when weighting the frecency of a location.
  2393   uint32_t recentFlags = navHistory->GetRecentFlags(aURI);
  2394   bool isFollowedLink = recentFlags & nsNavHistory::RECENT_ACTIVATED;
  2396   // Embed visits should never be added to the database, and the same is valid
  2397   // for redirects across frames.
  2398   // For the above reasoning non-toplevel transitions are handled at first.
  2399   // if the visit is toplevel or a non-toplevel followed link, then it can be
  2400   // handled as usual and stored on disk.
  2402   uint32_t transitionType = nsINavHistoryService::TRANSITION_LINK;
  2404   if (!(aFlags & IHistory::TOP_LEVEL) && !isFollowedLink) {
  2405     // A frame redirected to a new site without user interaction.
  2406     transitionType = nsINavHistoryService::TRANSITION_EMBED;
  2408   else if (aFlags & IHistory::REDIRECT_TEMPORARY) {
  2409     transitionType = nsINavHistoryService::TRANSITION_REDIRECT_TEMPORARY;
  2411   else if (aFlags & IHistory::REDIRECT_PERMANENT) {
  2412     transitionType = nsINavHistoryService::TRANSITION_REDIRECT_PERMANENT;
  2414   else if ((recentFlags & nsNavHistory::RECENT_TYPED) &&
  2415            !(aFlags & IHistory::UNRECOVERABLE_ERROR)) {
  2416     // Don't mark error pages as typed, even if they were actually typed by
  2417     // the user.  This is useful to limit their score in autocomplete.
  2418     transitionType = nsINavHistoryService::TRANSITION_TYPED;
  2420   else if (recentFlags & nsNavHistory::RECENT_BOOKMARKED) {
  2421     transitionType = nsINavHistoryService::TRANSITION_BOOKMARK;
  2423   else if (!(aFlags & IHistory::TOP_LEVEL) && isFollowedLink) {
  2424     // User activated a link in a frame.
  2425     transitionType = nsINavHistoryService::TRANSITION_FRAMED_LINK;
  2428   place.SetTransitionType(transitionType);
  2429   place.hidden = GetHiddenState(aFlags & IHistory::REDIRECT_SOURCE,
  2430                                 transitionType);
  2432   // Error pages should never be autocompleted.
  2433   if (aFlags & IHistory::UNRECOVERABLE_ERROR) {
  2434     place.shouldUpdateFrecency = false;
  2437   // EMBED visits are session-persistent and should not go through the database.
  2438   // They exist only to keep track of isVisited status during the session.
  2439   if (place.transitionType == nsINavHistoryService::TRANSITION_EMBED) {
  2440     StoreAndNotifyEmbedVisit(place);
  2442   else {
  2443     mozIStorageConnection* dbConn = GetDBConn();
  2444     NS_ENSURE_STATE(dbConn);
  2446     rv = InsertVisitedURIs::Start(dbConn, placeArray);
  2447     NS_ENSURE_SUCCESS(rv, rv);
  2450   // Finally, notify that we've been visited.
  2451   nsCOMPtr<nsIObserverService> obsService =
  2452     mozilla::services::GetObserverService();
  2453   if (obsService) {
  2454     obsService->NotifyObservers(aURI, NS_LINK_VISITED_EVENT_TOPIC, nullptr);
  2457   return NS_OK;
  2460 NS_IMETHODIMP
  2461 History::RegisterVisitedCallback(nsIURI* aURI,
  2462                                  Link* aLink)
  2464   NS_ASSERTION(aURI, "Must pass a non-null URI!");
  2465   if (XRE_GetProcessType() == GeckoProcessType_Content) {
  2466     NS_PRECONDITION(aLink, "Must pass a non-null Link!");
  2469   // Obtain our array of observers for this URI.
  2470 #ifdef DEBUG
  2471   bool keyAlreadyExists = !!mObservers.GetEntry(aURI);
  2472 #endif
  2473   KeyClass* key = mObservers.PutEntry(aURI);
  2474   NS_ENSURE_TRUE(key, NS_ERROR_OUT_OF_MEMORY);
  2475   ObserverArray& observers = key->array;
  2477   if (observers.IsEmpty()) {
  2478     NS_ASSERTION(!keyAlreadyExists,
  2479                  "An empty key was kept around in our hashtable!");
  2481     // We are the first Link node to ask about this URI, or there are no pending
  2482     // Links wanting to know about this URI.  Therefore, we should query the
  2483     // database now.
  2484     nsresult rv = VisitedQuery::Start(aURI);
  2486     // In IPC builds, we are passed a nullptr Link from
  2487     // ContentParent::RecvStartVisitedQuery.  Since we won't be adding a
  2488     // nullptr entry to our list of observers, and the code after this point
  2489     // assumes that aLink is non-nullptr, we will need to return now.
  2490     if (NS_FAILED(rv) || !aLink) {
  2491       // Remove our array from the hashtable so we don't keep it around.
  2492       mObservers.RemoveEntry(aURI);
  2493       return rv;
  2496   // In IPC builds, we are passed a nullptr Link from
  2497   // ContentParent::RecvStartVisitedQuery.  All of our code after this point
  2498   // assumes aLink is non-nullptr, so we have to return now.
  2499   else if (!aLink) {
  2500     NS_ASSERTION(XRE_GetProcessType() == GeckoProcessType_Default,
  2501                  "We should only ever get a null Link in the default process!");
  2502     return NS_OK;
  2505   // Sanity check that Links are not registered more than once for a given URI.
  2506   // This will not catch a case where it is registered for two different URIs.
  2507   NS_ASSERTION(!observers.Contains(aLink),
  2508                "Already tracking this Link object!");
  2510   // Start tracking our Link.
  2511   if (!observers.AppendElement(aLink)) {
  2512     // Curses - unregister and return failure.
  2513     (void)UnregisterVisitedCallback(aURI, aLink);
  2514     return NS_ERROR_OUT_OF_MEMORY;
  2517   return NS_OK;
  2520 NS_IMETHODIMP
  2521 History::UnregisterVisitedCallback(nsIURI* aURI,
  2522                                    Link* aLink)
  2524   NS_ASSERTION(aURI, "Must pass a non-null URI!");
  2525   NS_ASSERTION(aLink, "Must pass a non-null Link object!");
  2527   // Get the array, and remove the item from it.
  2528   KeyClass* key = mObservers.GetEntry(aURI);
  2529   if (!key) {
  2530     NS_ERROR("Trying to unregister for a URI that wasn't registered!");
  2531     return NS_ERROR_UNEXPECTED;
  2533   ObserverArray& observers = key->array;
  2534   if (!observers.RemoveElement(aLink)) {
  2535     NS_ERROR("Trying to unregister a node that wasn't registered!");
  2536     return NS_ERROR_UNEXPECTED;
  2539   // If the array is now empty, we should remove it from the hashtable.
  2540   if (observers.IsEmpty()) {
  2541     mObservers.RemoveEntry(aURI);
  2544   return NS_OK;
  2547 NS_IMETHODIMP
  2548 History::SetURITitle(nsIURI* aURI, const nsAString& aTitle)
  2550   NS_PRECONDITION(aURI, "Must pass a non-null URI!");
  2551   if (mShuttingDown) {
  2552     return NS_OK;
  2555   if (XRE_GetProcessType() == GeckoProcessType_Content) {
  2556     URIParams uri;
  2557     SerializeURI(aURI, uri);
  2559     mozilla::dom::ContentChild * cpc = 
  2560       mozilla::dom::ContentChild::GetSingleton();
  2561     NS_ASSERTION(cpc, "Content Protocol is NULL!");
  2562     (void)cpc->SendSetURITitle(uri, PromiseFlatString(aTitle));
  2563     return NS_OK;
  2566   nsNavHistory* navHistory = nsNavHistory::GetHistoryService();
  2568   // At first, it seems like nav history should always be available here, no
  2569   // matter what.
  2570   //
  2571   // nsNavHistory fails to register as a service if there is no profile in
  2572   // place (for instance, if user is choosing a profile).
  2573   //
  2574   // Maybe the correct thing to do is to not register this service if no
  2575   // profile has been selected?
  2576   //
  2577   NS_ENSURE_TRUE(navHistory, NS_ERROR_FAILURE);
  2579   bool canAdd;
  2580   nsresult rv = navHistory->CanAddURI(aURI, &canAdd);
  2581   NS_ENSURE_SUCCESS(rv, rv);
  2582   if (!canAdd) {
  2583     return NS_OK;
  2586   // Embed visits don't have a database entry, thus don't set a title on them.
  2587   if (navHistory->hasEmbedVisit(aURI)) {
  2588     return NS_OK;
  2591   mozIStorageConnection* dbConn = GetDBConn();
  2592   NS_ENSURE_STATE(dbConn);
  2594   rv = SetPageTitle::Start(dbConn, aURI, aTitle);
  2595   NS_ENSURE_SUCCESS(rv, rv);
  2597   return NS_OK;
  2600 ////////////////////////////////////////////////////////////////////////////////
  2601 //// nsIDownloadHistory
  2603 NS_IMETHODIMP
  2604 History::AddDownload(nsIURI* aSource, nsIURI* aReferrer,
  2605                      PRTime aStartTime, nsIURI* aDestination)
  2607   MOZ_ASSERT(NS_IsMainThread());
  2608   NS_ENSURE_ARG(aSource);
  2610   if (mShuttingDown) {
  2611     return NS_OK;
  2614   if (XRE_GetProcessType() == GeckoProcessType_Content) {
  2615     NS_ERROR("Cannot add downloads to history from content process!");
  2616     return NS_ERROR_NOT_AVAILABLE;
  2619   nsNavHistory* navHistory = nsNavHistory::GetHistoryService();
  2620   NS_ENSURE_TRUE(navHistory, NS_ERROR_OUT_OF_MEMORY);
  2622   // Silently return if URI is something we shouldn't add to DB.
  2623   bool canAdd;
  2624   nsresult rv = navHistory->CanAddURI(aSource, &canAdd);
  2625   NS_ENSURE_SUCCESS(rv, rv);
  2626   if (!canAdd) {
  2627     return NS_OK;
  2630   nsTArray<VisitData> placeArray(1);
  2631   NS_ENSURE_TRUE(placeArray.AppendElement(VisitData(aSource, aReferrer)),
  2632                  NS_ERROR_OUT_OF_MEMORY);
  2633   VisitData& place = placeArray.ElementAt(0);
  2634   NS_ENSURE_FALSE(place.spec.IsEmpty(), NS_ERROR_INVALID_ARG);
  2636   place.visitTime = aStartTime;
  2637   place.SetTransitionType(nsINavHistoryService::TRANSITION_DOWNLOAD);
  2638   place.hidden = false;
  2640   mozIStorageConnection* dbConn = GetDBConn();
  2641   NS_ENSURE_STATE(dbConn);
  2643   nsCOMPtr<mozIVisitInfoCallback> callback = aDestination
  2644                                   ? new SetDownloadAnnotations(aDestination)
  2645                                   : nullptr;
  2647   rv = InsertVisitedURIs::Start(dbConn, placeArray, callback);
  2648   NS_ENSURE_SUCCESS(rv, rv);
  2650   // Finally, notify that we've been visited.
  2651   nsCOMPtr<nsIObserverService> obsService =
  2652     mozilla::services::GetObserverService();
  2653   if (obsService) {
  2654     obsService->NotifyObservers(aSource, NS_LINK_VISITED_EVENT_TOPIC, nullptr);
  2657   return NS_OK;
  2660 NS_IMETHODIMP
  2661 History::RemoveAllDownloads()
  2663   MOZ_ASSERT(NS_IsMainThread());
  2665   if (mShuttingDown) {
  2666     return NS_OK;
  2669   if (XRE_GetProcessType() == GeckoProcessType_Content) {
  2670     NS_ERROR("Cannot remove downloads to history from content process!");
  2671     return NS_ERROR_NOT_AVAILABLE;
  2674   // Ensure navHistory is initialized.
  2675   nsNavHistory* navHistory = nsNavHistory::GetHistoryService();
  2676   NS_ENSURE_TRUE(navHistory, NS_ERROR_OUT_OF_MEMORY);
  2677   mozIStorageConnection* dbConn = GetDBConn();
  2678   NS_ENSURE_STATE(dbConn);
  2680   RemoveVisitsFilter filter;
  2681   filter.transitionType = nsINavHistoryService::TRANSITION_DOWNLOAD;
  2683   nsresult rv = RemoveVisits::Start(dbConn, filter);
  2684   NS_ENSURE_SUCCESS(rv, rv);
  2686   return NS_OK;
  2689 ////////////////////////////////////////////////////////////////////////////////
  2690 //// mozIAsyncHistory
  2692 NS_IMETHODIMP
  2693 History::GetPlacesInfo(JS::Handle<JS::Value> aPlaceIdentifiers,
  2694                        mozIVisitInfoCallback* aCallback,
  2695                        JSContext* aCtx) {
  2696   nsNavHistory* navHistory = nsNavHistory::GetHistoryService();
  2697   NS_ABORT_IF_FALSE(navHistory, "Could not get nsNavHistory?!");
  2699   uint32_t placesIndentifiersLength;
  2700   JS::Rooted<JSObject*> placesIndentifiers(aCtx);
  2701   nsresult rv = GetJSArrayFromJSValue(aPlaceIdentifiers, aCtx,
  2702                                       &placesIndentifiers,
  2703                                       &placesIndentifiersLength);
  2704   NS_ENSURE_SUCCESS(rv, rv);
  2706   nsTArray<VisitData> placesInfo;
  2707   placesInfo.SetCapacity(placesIndentifiersLength);
  2708   for (uint32_t i = 0; i < placesIndentifiersLength; i++) {
  2709     JS::Rooted<JS::Value> placeIdentifier(aCtx);
  2710     bool rc = JS_GetElement(aCtx, placesIndentifiers, i, &placeIdentifier);
  2711     NS_ENSURE_TRUE(rc, NS_ERROR_UNEXPECTED);
  2713     // GUID
  2714     nsAutoString fatGUID;
  2715     GetJSValueAsString(aCtx, placeIdentifier, fatGUID);
  2716     if (!fatGUID.IsVoid()) {
  2717       NS_ConvertUTF16toUTF8 guid(fatGUID);
  2718       if (!IsValidGUID(guid))
  2719         return NS_ERROR_INVALID_ARG;
  2721       VisitData& placeInfo = *placesInfo.AppendElement(VisitData());
  2722       placeInfo.guid = guid;
  2724     else {
  2725       nsCOMPtr<nsIURI> uri = GetJSValueAsURI(aCtx, placeIdentifier);
  2726       if (!uri)
  2727         return NS_ERROR_INVALID_ARG; // neither a guid, nor a uri.
  2728       placesInfo.AppendElement(VisitData(uri));
  2732   mozIStorageConnection* dbConn = GetDBConn();
  2733   NS_ENSURE_STATE(dbConn);
  2735   for (nsTArray<VisitData>::size_type i = 0; i < placesInfo.Length(); i++) {
  2736     nsresult rv = GetPlaceInfo::Start(dbConn, placesInfo.ElementAt(i), aCallback);
  2737     NS_ENSURE_SUCCESS(rv, rv);
  2740   // Be sure to notify that all of our operations are complete.  This
  2741   // is dispatched to the background thread first and redirected to the
  2742   // main thread from there to make sure that all database notifications
  2743   // and all embed or canAddURI notifications have finished.
  2744   if (aCallback) {
  2745     // NotifyCompletion does not hold a strong reference to the callback,
  2746     // so we have to manage it by AddRefing now. NotifyCompletion will
  2747     // release it for us once it has dispatched the callback to the main
  2748     // thread.
  2749     NS_ADDREF(aCallback);
  2751     nsCOMPtr<nsIEventTarget> backgroundThread = do_GetInterface(dbConn);
  2752     NS_ENSURE_TRUE(backgroundThread, NS_ERROR_UNEXPECTED);
  2753     nsCOMPtr<nsIRunnable> event = new NotifyCompletion(aCallback);
  2754     return backgroundThread->Dispatch(event, NS_DISPATCH_NORMAL);
  2757   return NS_OK;
  2760 NS_IMETHODIMP
  2761 History::UpdatePlaces(JS::Handle<JS::Value> aPlaceInfos,
  2762                       mozIVisitInfoCallback* aCallback,
  2763                       JSContext* aCtx)
  2765   NS_ENSURE_TRUE(NS_IsMainThread(), NS_ERROR_UNEXPECTED);
  2766   NS_ENSURE_TRUE(!JSVAL_IS_PRIMITIVE(aPlaceInfos), NS_ERROR_INVALID_ARG);
  2768   uint32_t infosLength;
  2769   JS::Rooted<JSObject*> infos(aCtx);
  2770   nsresult rv = GetJSArrayFromJSValue(aPlaceInfos, aCtx, &infos, &infosLength);
  2771   NS_ENSURE_SUCCESS(rv, rv);
  2773   nsTArray<VisitData> visitData;
  2774   for (uint32_t i = 0; i < infosLength; i++) {
  2775     JS::Rooted<JSObject*> info(aCtx);
  2776     nsresult rv = GetJSObjectFromArray(aCtx, infos, i, &info);
  2777     NS_ENSURE_SUCCESS(rv, rv);
  2779     nsCOMPtr<nsIURI> uri = GetURIFromJSObject(aCtx, info, "uri");
  2780     nsCString guid;
  2782       nsString fatGUID;
  2783       GetStringFromJSObject(aCtx, info, "guid", fatGUID);
  2784       if (fatGUID.IsVoid()) {
  2785         guid.SetIsVoid(true);
  2787       else {
  2788         guid = NS_ConvertUTF16toUTF8(fatGUID);
  2792     // Make sure that any uri we are given can be added to history, and if not,
  2793     // skip it (CanAddURI will notify our callback for us).
  2794     if (uri && !CanAddURI(uri, guid, aCallback)) {
  2795       continue;
  2798     // We must have at least one of uri or guid.
  2799     NS_ENSURE_ARG(uri || !guid.IsVoid());
  2801     // If we were given a guid, make sure it is valid.
  2802     bool isValidGUID = IsValidGUID(guid);
  2803     NS_ENSURE_ARG(guid.IsVoid() || isValidGUID);
  2805     nsString title;
  2806     GetStringFromJSObject(aCtx, info, "title", title);
  2808     JS::Rooted<JSObject*> visits(aCtx, nullptr);
  2810       JS::Rooted<JS::Value> visitsVal(aCtx);
  2811       bool rc = JS_GetProperty(aCtx, info, "visits", &visitsVal);
  2812       NS_ENSURE_TRUE(rc, NS_ERROR_UNEXPECTED);
  2813       if (!JSVAL_IS_PRIMITIVE(visitsVal)) {
  2814         visits = JSVAL_TO_OBJECT(visitsVal);
  2815         NS_ENSURE_ARG(JS_IsArrayObject(aCtx, visits));
  2818     NS_ENSURE_ARG(visits);
  2820     uint32_t visitsLength = 0;
  2821     if (visits) {
  2822       (void)JS_GetArrayLength(aCtx, visits, &visitsLength);
  2824     NS_ENSURE_ARG(visitsLength > 0);
  2826     // Check each visit, and build our array of VisitData objects.
  2827     visitData.SetCapacity(visitData.Length() + visitsLength);
  2828     for (uint32_t j = 0; j < visitsLength; j++) {
  2829       JS::Rooted<JSObject*> visit(aCtx);
  2830       rv = GetJSObjectFromArray(aCtx, visits, j, &visit);
  2831       NS_ENSURE_SUCCESS(rv, rv);
  2833       VisitData& data = *visitData.AppendElement(VisitData(uri));
  2834       data.title = title;
  2835       data.guid = guid;
  2837       // We must have a date and a transaction type!
  2838       rv = GetIntFromJSObject(aCtx, visit, "visitDate", &data.visitTime);
  2839       NS_ENSURE_SUCCESS(rv, rv);
  2840       uint32_t transitionType = 0;
  2841       rv = GetIntFromJSObject(aCtx, visit, "transitionType", &transitionType);
  2842       NS_ENSURE_SUCCESS(rv, rv);
  2843       NS_ENSURE_ARG_RANGE(transitionType,
  2844                           nsINavHistoryService::TRANSITION_LINK,
  2845                           nsINavHistoryService::TRANSITION_FRAMED_LINK);
  2846       data.SetTransitionType(transitionType);
  2847       data.hidden = GetHiddenState(false, transitionType);
  2849       // If the visit is an embed visit, we do not actually add it to the
  2850       // database.
  2851       if (transitionType == nsINavHistoryService::TRANSITION_EMBED) {
  2852         StoreAndNotifyEmbedVisit(data, aCallback);
  2853         visitData.RemoveElementAt(visitData.Length() - 1);
  2854         continue;
  2857       // The referrer is optional.
  2858       nsCOMPtr<nsIURI> referrer = GetURIFromJSObject(aCtx, visit,
  2859                                                      "referrerURI");
  2860       if (referrer) {
  2861         (void)referrer->GetSpec(data.referrerSpec);
  2866   mozIStorageConnection* dbConn = GetDBConn();
  2867   NS_ENSURE_STATE(dbConn);
  2869   // It is possible that all of the visits we were passed were dissallowed by
  2870   // CanAddURI, which isn't an error.  If we have no visits to add, however,
  2871   // we should not call InsertVisitedURIs::Start.
  2872   if (visitData.Length()) {
  2873     nsresult rv = InsertVisitedURIs::Start(dbConn, visitData, aCallback);
  2874     NS_ENSURE_SUCCESS(rv, rv);
  2877   // Be sure to notify that all of our operations are complete.  This
  2878   // is dispatched to the background thread first and redirected to the
  2879   // main thread from there to make sure that all database notifications
  2880   // and all embed or canAddURI notifications have finished.
  2881   if (aCallback) {
  2882     // NotifyCompletion does not hold a strong reference to the callback,
  2883     // so we have to manage it by AddRefing now. NotifyCompletion will
  2884     // release it for us once it has dispatched the callback to the main
  2885     // thread.
  2886     NS_ADDREF(aCallback);
  2888     nsCOMPtr<nsIEventTarget> backgroundThread = do_GetInterface(dbConn);
  2889     NS_ENSURE_TRUE(backgroundThread, NS_ERROR_UNEXPECTED);
  2890     nsCOMPtr<nsIRunnable> event = new NotifyCompletion(aCallback);
  2891     return backgroundThread->Dispatch(event, NS_DISPATCH_NORMAL);
  2894   return NS_OK;
  2897 NS_IMETHODIMP
  2898 History::IsURIVisited(nsIURI* aURI,
  2899                       mozIVisitedStatusCallback* aCallback)
  2901   NS_ENSURE_STATE(NS_IsMainThread());
  2902   NS_ENSURE_ARG(aURI);
  2903   NS_ENSURE_ARG(aCallback);
  2905   nsresult rv = VisitedQuery::Start(aURI, aCallback);
  2906   NS_ENSURE_SUCCESS(rv, rv);
  2908   return NS_OK;
  2911 ////////////////////////////////////////////////////////////////////////////////
  2912 //// nsIObserver
  2914 NS_IMETHODIMP
  2915 History::Observe(nsISupports* aSubject, const char* aTopic,
  2916                  const char16_t* aData)
  2918   if (strcmp(aTopic, TOPIC_PLACES_SHUTDOWN) == 0) {
  2919     Shutdown();
  2921     nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
  2922     if (os) {
  2923       (void)os->RemoveObserver(this, TOPIC_PLACES_SHUTDOWN);
  2927   return NS_OK;
  2930 ////////////////////////////////////////////////////////////////////////////////
  2931 //// nsISupports
  2933 NS_IMPL_ISUPPORTS(
  2934   History
  2935 , IHistory
  2936 , nsIDownloadHistory
  2937 , mozIAsyncHistory
  2938 , nsIObserver
  2939 , nsIMemoryReporter
  2942 } // namespace places
  2943 } // namespace mozilla

mercurial