toolkit/components/places/History.cpp

changeset 0
6474c204b198
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/toolkit/components/places/History.cpp	Wed Dec 31 06:09:35 2014 +0100
     1.3 @@ -0,0 +1,2943 @@
     1.4 +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
     1.5 +/* vim: set ts=8 sts=2 et sw=2 tw=80: */
     1.6 +/* This Source Code Form is subject to the terms of the Mozilla Public
     1.7 + * License, v. 2.0. If a copy of the MPL was not distributed with this
     1.8 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
     1.9 +
    1.10 +#include "mozilla/ArrayUtils.h"
    1.11 +#include "mozilla/Attributes.h"
    1.12 +#include "mozilla/DebugOnly.h"
    1.13 +#include "mozilla/MemoryReporting.h"
    1.14 +
    1.15 +#include "mozilla/dom/ContentChild.h"
    1.16 +#include "mozilla/dom/ContentParent.h"
    1.17 +#include "nsXULAppAPI.h"
    1.18 +
    1.19 +#include "History.h"
    1.20 +#include "nsNavHistory.h"
    1.21 +#include "nsNavBookmarks.h"
    1.22 +#include "nsAnnotationService.h"
    1.23 +#include "Helpers.h"
    1.24 +#include "PlaceInfo.h"
    1.25 +#include "VisitInfo.h"
    1.26 +#include "nsPlacesMacros.h"
    1.27 +
    1.28 +#include "mozilla/storage.h"
    1.29 +#include "mozilla/dom/Link.h"
    1.30 +#include "nsDocShellCID.h"
    1.31 +#include "mozilla/Services.h"
    1.32 +#include "nsThreadUtils.h"
    1.33 +#include "nsNetUtil.h"
    1.34 +#include "nsIXPConnect.h"
    1.35 +#include "mozilla/unused.h"
    1.36 +#include "nsContentUtils.h" // for nsAutoScriptBlocker
    1.37 +#include "mozilla/ipc/URIUtils.h"
    1.38 +#include "nsPrintfCString.h"
    1.39 +#include "nsTHashtable.h"
    1.40 +#include "jsapi.h"
    1.41 +
    1.42 +// Initial size for the cache holding visited status observers.
    1.43 +#define VISIT_OBSERVERS_INITIAL_CACHE_SIZE 128
    1.44 +
    1.45 +// Initial size for the visits removal hash.
    1.46 +#define VISITS_REMOVAL_INITIAL_HASH_SIZE 128
    1.47 +
    1.48 +using namespace mozilla::dom;
    1.49 +using namespace mozilla::ipc;
    1.50 +using mozilla::unused;
    1.51 +
    1.52 +namespace mozilla {
    1.53 +namespace places {
    1.54 +
    1.55 +////////////////////////////////////////////////////////////////////////////////
    1.56 +//// Global Defines
    1.57 +
    1.58 +#define URI_VISITED "visited"
    1.59 +#define URI_NOT_VISITED "not visited"
    1.60 +#define URI_VISITED_RESOLUTION_TOPIC "visited-status-resolution"
    1.61 +// Observer event fired after a visit has been registered in the DB.
    1.62 +#define URI_VISIT_SAVED "uri-visit-saved"
    1.63 +
    1.64 +#define DESTINATIONFILEURI_ANNO \
    1.65 +        NS_LITERAL_CSTRING("downloads/destinationFileURI")
    1.66 +#define DESTINATIONFILENAME_ANNO \
    1.67 +        NS_LITERAL_CSTRING("downloads/destinationFileName")
    1.68 +
    1.69 +////////////////////////////////////////////////////////////////////////////////
    1.70 +//// VisitData
    1.71 +
    1.72 +struct VisitData {
    1.73 +  VisitData()
    1.74 +  : placeId(0)
    1.75 +  , visitId(0)
    1.76 +  , hidden(true)
    1.77 +  , typed(false)
    1.78 +  , transitionType(UINT32_MAX)
    1.79 +  , visitTime(0)
    1.80 +  , frecency(-1)
    1.81 +  , titleChanged(false)
    1.82 +  , shouldUpdateFrecency(true)
    1.83 +  {
    1.84 +    guid.SetIsVoid(true);
    1.85 +    title.SetIsVoid(true);
    1.86 +  }
    1.87 +
    1.88 +  VisitData(nsIURI* aURI,
    1.89 +            nsIURI* aReferrer = nullptr)
    1.90 +  : placeId(0)
    1.91 +  , visitId(0)
    1.92 +  , hidden(true)
    1.93 +  , typed(false)
    1.94 +  , transitionType(UINT32_MAX)
    1.95 +  , visitTime(0)
    1.96 +  , frecency(-1)
    1.97 +  , titleChanged(false)
    1.98 +  , shouldUpdateFrecency(true)
    1.99 +  {
   1.100 +    (void)aURI->GetSpec(spec);
   1.101 +    (void)GetReversedHostname(aURI, revHost);
   1.102 +    if (aReferrer) {
   1.103 +      (void)aReferrer->GetSpec(referrerSpec);
   1.104 +    }
   1.105 +    guid.SetIsVoid(true);
   1.106 +    title.SetIsVoid(true);
   1.107 +  }
   1.108 +
   1.109 +  /**
   1.110 +   * Sets the transition type of the visit, as well as if it was typed.
   1.111 +   *
   1.112 +   * @param aTransitionType
   1.113 +   *        The transition type constant to set.  Must be one of the
   1.114 +   *        TRANSITION_ constants on nsINavHistoryService.
   1.115 +   */
   1.116 +  void SetTransitionType(uint32_t aTransitionType)
   1.117 +  {
   1.118 +    typed = aTransitionType == nsINavHistoryService::TRANSITION_TYPED;
   1.119 +    transitionType = aTransitionType;
   1.120 +  }
   1.121 +
   1.122 +  /**
   1.123 +   * Determines if this refers to the same url as aOther, and updates aOther
   1.124 +   * with missing information if so.
   1.125 +   *
   1.126 +   * @param aOther
   1.127 +   *        The other place to check against.
   1.128 +   * @return true if this is a visit for the same place as aOther, false
   1.129 +   *         otherwise.
   1.130 +   */
   1.131 +  bool IsSamePlaceAs(VisitData& aOther)
   1.132 +  {
   1.133 +    if (!spec.Equals(aOther.spec)) {
   1.134 +      return false;
   1.135 +    }
   1.136 +
   1.137 +    aOther.placeId = placeId;
   1.138 +    aOther.guid = guid;
   1.139 +    return true;
   1.140 +  }
   1.141 +
   1.142 +  int64_t placeId;
   1.143 +  nsCString guid;
   1.144 +  int64_t visitId;
   1.145 +  nsCString spec;
   1.146 +  nsString revHost;
   1.147 +  bool hidden;
   1.148 +  bool typed;
   1.149 +  uint32_t transitionType;
   1.150 +  PRTime visitTime;
   1.151 +  int32_t frecency;
   1.152 +
   1.153 +  /**
   1.154 +   * Stores the title.  If this is empty (IsEmpty() returns true), then the
   1.155 +   * title should be removed from the Place.  If the title is void (IsVoid()
   1.156 +   * returns true), then no title has been set on this object, and titleChanged
   1.157 +   * should remain false.
   1.158 +   */
   1.159 +  nsString title;
   1.160 +
   1.161 +  nsCString referrerSpec;
   1.162 +
   1.163 +  // TODO bug 626836 hook up hidden and typed change tracking too!
   1.164 +  bool titleChanged;
   1.165 +
   1.166 +  // Indicates whether frecency should be updated for this visit.
   1.167 +  bool shouldUpdateFrecency;
   1.168 +};
   1.169 +
   1.170 +////////////////////////////////////////////////////////////////////////////////
   1.171 +//// RemoveVisitsFilter
   1.172 +
   1.173 +/**
   1.174 + * Used to store visit filters for RemoveVisits.
   1.175 + */
   1.176 +struct RemoveVisitsFilter {
   1.177 +  RemoveVisitsFilter()
   1.178 +  : transitionType(UINT32_MAX)
   1.179 +  {
   1.180 +  }
   1.181 +
   1.182 +  uint32_t transitionType;
   1.183 +};
   1.184 +
   1.185 +////////////////////////////////////////////////////////////////////////////////
   1.186 +//// PlaceHashKey
   1.187 +
   1.188 +class PlaceHashKey : public nsCStringHashKey
   1.189 +{
   1.190 +  public:
   1.191 +    PlaceHashKey(const nsACString& aSpec)
   1.192 +    : nsCStringHashKey(&aSpec)
   1.193 +    , visitCount(-1)
   1.194 +    , bookmarked(-1)
   1.195 +    {
   1.196 +    }
   1.197 +
   1.198 +    PlaceHashKey(const nsACString* aSpec)
   1.199 +    : nsCStringHashKey(aSpec)
   1.200 +    , visitCount(-1)
   1.201 +    , bookmarked(-1)
   1.202 +    {
   1.203 +    }
   1.204 +
   1.205 +    PlaceHashKey(const PlaceHashKey& aOther)
   1.206 +    : nsCStringHashKey(&aOther.GetKey())
   1.207 +    {
   1.208 +      MOZ_ASSERT(false, "Do not call me!");
   1.209 +    }
   1.210 +
   1.211 +    // Visit count for this place.
   1.212 +    int32_t visitCount;
   1.213 +    // Whether this place is bookmarked.
   1.214 +    int32_t bookmarked;
   1.215 +    // Array of VisitData objects.
   1.216 +    nsTArray<VisitData> visits;
   1.217 +};
   1.218 +
   1.219 +////////////////////////////////////////////////////////////////////////////////
   1.220 +//// Anonymous Helpers
   1.221 +
   1.222 +namespace {
   1.223 +
   1.224 +/**
   1.225 + * Convert the given js value to a js array.
   1.226 + *
   1.227 + * @param [in] aValue
   1.228 + *        the JS value to convert.
   1.229 + * @param [in] aCtx
   1.230 + *        The JSContext for aValue.
   1.231 + * @param [out] _array
   1.232 + *        the JS array.
   1.233 + * @param [out] _arrayLength
   1.234 + *        _array's length.
   1.235 + */
   1.236 +nsresult
   1.237 +GetJSArrayFromJSValue(JS::Handle<JS::Value> aValue,
   1.238 +                      JSContext* aCtx,
   1.239 +                      JS::MutableHandle<JSObject*> _array,
   1.240 +                      uint32_t* _arrayLength) {
   1.241 +  if (aValue.isObjectOrNull()) {
   1.242 +    JS::Rooted<JSObject*> val(aCtx, aValue.toObjectOrNull());
   1.243 +    if (JS_IsArrayObject(aCtx, val)) {
   1.244 +      _array.set(val);
   1.245 +      (void)JS_GetArrayLength(aCtx, _array, _arrayLength);
   1.246 +      NS_ENSURE_ARG(*_arrayLength > 0);
   1.247 +      return NS_OK;
   1.248 +    }
   1.249 +  }
   1.250 +
   1.251 +  // Build a temporary array to store this one item so the code below can
   1.252 +  // just loop.
   1.253 +  *_arrayLength = 1;
   1.254 +  _array.set(JS_NewArrayObject(aCtx, 0));
   1.255 +  NS_ENSURE_TRUE(_array, NS_ERROR_OUT_OF_MEMORY);
   1.256 +
   1.257 +  bool rc = JS_DefineElement(aCtx, _array, 0, aValue, nullptr, nullptr, 0);
   1.258 +  NS_ENSURE_TRUE(rc, NS_ERROR_UNEXPECTED);
   1.259 +  return NS_OK;
   1.260 +}
   1.261 +
   1.262 +/**
   1.263 + * Attemps to convert a given js value to a nsIURI object.
   1.264 + * @param aCtx
   1.265 + *        The JSContext for aValue.
   1.266 + * @param aValue
   1.267 + *        The JS value to convert.
   1.268 + * @return the nsIURI object, or null if aValue is not a nsIURI object.
   1.269 + */
   1.270 +already_AddRefed<nsIURI>
   1.271 +GetJSValueAsURI(JSContext* aCtx,
   1.272 +                const JS::Value& aValue) {
   1.273 +  if (!JSVAL_IS_PRIMITIVE(aValue)) {
   1.274 +    nsCOMPtr<nsIXPConnect> xpc = mozilla::services::GetXPConnect();
   1.275 +
   1.276 +    nsCOMPtr<nsIXPConnectWrappedNative> wrappedObj;
   1.277 +    nsresult rv = xpc->GetWrappedNativeOfJSObject(aCtx, JSVAL_TO_OBJECT(aValue),
   1.278 +                                                  getter_AddRefs(wrappedObj));
   1.279 +    NS_ENSURE_SUCCESS(rv, nullptr);
   1.280 +    nsCOMPtr<nsIURI> uri = do_QueryWrappedNative(wrappedObj);
   1.281 +    return uri.forget();
   1.282 +  }
   1.283 +  return nullptr;
   1.284 +}
   1.285 +
   1.286 +/**
   1.287 + * Obtains an nsIURI from the "uri" property of a JSObject.
   1.288 + *
   1.289 + * @param aCtx
   1.290 + *        The JSContext for aObject.
   1.291 + * @param aObject
   1.292 + *        The JSObject to get the URI from.
   1.293 + * @param aProperty
   1.294 + *        The name of the property to get the URI from.
   1.295 + * @return the URI if it exists.
   1.296 + */
   1.297 +already_AddRefed<nsIURI>
   1.298 +GetURIFromJSObject(JSContext* aCtx,
   1.299 +                   JS::Handle<JSObject *> aObject,
   1.300 +                   const char* aProperty)
   1.301 +{
   1.302 +  JS::Rooted<JS::Value> uriVal(aCtx);
   1.303 +  bool rc = JS_GetProperty(aCtx, aObject, aProperty, &uriVal);
   1.304 +  NS_ENSURE_TRUE(rc, nullptr);
   1.305 +  return GetJSValueAsURI(aCtx, uriVal);
   1.306 +}
   1.307 +
   1.308 +/**
   1.309 + * Attemps to convert a JS value to a string.
   1.310 + * @param aCtx
   1.311 + *        The JSContext for aObject.
   1.312 + * @param aValue
   1.313 + *        The JS value to convert.
   1.314 + * @param _string
   1.315 + *        The string to populate with the value, or set it to void.
   1.316 + */
   1.317 +void
   1.318 +GetJSValueAsString(JSContext* aCtx,
   1.319 +                   const JS::Value& aValue,
   1.320 +                   nsString& _string) {
   1.321 +  if (JSVAL_IS_VOID(aValue) ||
   1.322 +      !(JSVAL_IS_NULL(aValue) || JSVAL_IS_STRING(aValue))) {
   1.323 +    _string.SetIsVoid(true);
   1.324 +    return;
   1.325 +  }
   1.326 +
   1.327 +  // |null| in JS maps to the empty string.
   1.328 +  if (JSVAL_IS_NULL(aValue)) {
   1.329 +    _string.Truncate();
   1.330 +    return;
   1.331 +  }
   1.332 +  size_t length;
   1.333 +  const jschar* chars =
   1.334 +    JS_GetStringCharsZAndLength(aCtx, JSVAL_TO_STRING(aValue), &length);
   1.335 +  if (!chars) {
   1.336 +    _string.SetIsVoid(true);
   1.337 +    return;
   1.338 +  }
   1.339 +  _string.Assign(static_cast<const char16_t*>(chars), length);
   1.340 +}
   1.341 +
   1.342 +/**
   1.343 + * Obtains the specified property of a JSObject.
   1.344 + *
   1.345 + * @param aCtx
   1.346 + *        The JSContext for aObject.
   1.347 + * @param aObject
   1.348 + *        The JSObject to get the string from.
   1.349 + * @param aProperty
   1.350 + *        The property to get the value from.
   1.351 + * @param _string
   1.352 + *        The string to populate with the value, or set it to void.
   1.353 + */
   1.354 +void
   1.355 +GetStringFromJSObject(JSContext* aCtx,
   1.356 +                      JS::Handle<JSObject *> aObject,
   1.357 +                      const char* aProperty,
   1.358 +                      nsString& _string)
   1.359 +{
   1.360 +  JS::Rooted<JS::Value> val(aCtx);
   1.361 +  bool rc = JS_GetProperty(aCtx, aObject, aProperty, &val);
   1.362 +  if (!rc) {
   1.363 +    _string.SetIsVoid(true);
   1.364 +    return;
   1.365 +  }
   1.366 +  else {
   1.367 +    GetJSValueAsString(aCtx, val, _string);
   1.368 +  }
   1.369 +}
   1.370 +
   1.371 +/**
   1.372 + * Obtains the specified property of a JSObject.
   1.373 + *
   1.374 + * @param aCtx
   1.375 + *        The JSContext for aObject.
   1.376 + * @param aObject
   1.377 + *        The JSObject to get the int from.
   1.378 + * @param aProperty
   1.379 + *        The property to get the value from.
   1.380 + * @param _int
   1.381 + *        The integer to populate with the value on success.
   1.382 + */
   1.383 +template <typename IntType>
   1.384 +nsresult
   1.385 +GetIntFromJSObject(JSContext* aCtx,
   1.386 +                   JS::Handle<JSObject *> aObject,
   1.387 +                   const char* aProperty,
   1.388 +                   IntType* _int)
   1.389 +{
   1.390 +  JS::Rooted<JS::Value> value(aCtx);
   1.391 +  bool rc = JS_GetProperty(aCtx, aObject, aProperty, &value);
   1.392 +  NS_ENSURE_TRUE(rc, NS_ERROR_UNEXPECTED);
   1.393 +  if (JSVAL_IS_VOID(value)) {
   1.394 +    return NS_ERROR_INVALID_ARG;
   1.395 +  }
   1.396 +  NS_ENSURE_ARG(JSVAL_IS_PRIMITIVE(value));
   1.397 +  NS_ENSURE_ARG(JSVAL_IS_NUMBER(value));
   1.398 +
   1.399 +  double num;
   1.400 +  rc = JS::ToNumber(aCtx, value, &num);
   1.401 +  NS_ENSURE_TRUE(rc, NS_ERROR_UNEXPECTED);
   1.402 +  NS_ENSURE_ARG(IntType(num) == num);
   1.403 +
   1.404 +  *_int = IntType(num);
   1.405 +  return NS_OK;
   1.406 +}
   1.407 +
   1.408 +/**
   1.409 + * Obtains the specified property of a JSObject.
   1.410 + *
   1.411 + * @pre aArray must be an Array object.
   1.412 + *
   1.413 + * @param aCtx
   1.414 + *        The JSContext for aArray.
   1.415 + * @param aArray
   1.416 + *        The JSObject to get the object from.
   1.417 + * @param aIndex
   1.418 + *        The index to get the object from.
   1.419 + * @param objOut
   1.420 + *        Set to the JSObject pointer on success.
   1.421 + */
   1.422 +nsresult
   1.423 +GetJSObjectFromArray(JSContext* aCtx,
   1.424 +                     JS::Handle<JSObject*> aArray,
   1.425 +                     uint32_t aIndex,
   1.426 +                     JS::MutableHandle<JSObject*> objOut)
   1.427 +{
   1.428 +  NS_PRECONDITION(JS_IsArrayObject(aCtx, aArray),
   1.429 +                  "Must provide an object that is an array!");
   1.430 +
   1.431 +  JS::Rooted<JS::Value> value(aCtx);
   1.432 +  bool rc = JS_GetElement(aCtx, aArray, aIndex, &value);
   1.433 +  NS_ENSURE_TRUE(rc, NS_ERROR_UNEXPECTED);
   1.434 +  NS_ENSURE_ARG(!value.isPrimitive());
   1.435 +  objOut.set(&value.toObject());
   1.436 +  return NS_OK;
   1.437 +}
   1.438 +
   1.439 +class VisitedQuery : public AsyncStatementCallback
   1.440 +{
   1.441 +public:
   1.442 +  static nsresult Start(nsIURI* aURI,
   1.443 +                        mozIVisitedStatusCallback* aCallback=nullptr)
   1.444 +  {
   1.445 +    NS_PRECONDITION(aURI, "Null URI");
   1.446 +
   1.447 +  // If we are a content process, always remote the request to the
   1.448 +  // parent process.
   1.449 +  if (XRE_GetProcessType() == GeckoProcessType_Content) {
   1.450 +    URIParams uri;
   1.451 +    SerializeURI(aURI, uri);
   1.452 +
   1.453 +    mozilla::dom::ContentChild* cpc =
   1.454 +      mozilla::dom::ContentChild::GetSingleton();
   1.455 +    NS_ASSERTION(cpc, "Content Protocol is NULL!");
   1.456 +    (void)cpc->SendStartVisitedQuery(uri);
   1.457 +    return NS_OK;
   1.458 +  }
   1.459 +
   1.460 +    nsNavHistory* navHistory = nsNavHistory::GetHistoryService();
   1.461 +    NS_ENSURE_STATE(navHistory);
   1.462 +    if (navHistory->hasEmbedVisit(aURI)) {
   1.463 +      nsRefPtr<VisitedQuery> callback = new VisitedQuery(aURI, aCallback, true);
   1.464 +      NS_ENSURE_TRUE(callback, NS_ERROR_OUT_OF_MEMORY);
   1.465 +      // As per IHistory contract, we must notify asynchronously.
   1.466 +      nsCOMPtr<nsIRunnable> event =
   1.467 +        NS_NewRunnableMethod(callback, &VisitedQuery::NotifyVisitedStatus);
   1.468 +      NS_DispatchToMainThread(event);
   1.469 +
   1.470 +      return NS_OK;
   1.471 +    }
   1.472 +
   1.473 +    History* history = History::GetService();
   1.474 +    NS_ENSURE_STATE(history);
   1.475 +    mozIStorageAsyncStatement* stmt = history->GetIsVisitedStatement();
   1.476 +    NS_ENSURE_STATE(stmt);
   1.477 +
   1.478 +    // Bind by index for performance.
   1.479 +    nsresult rv = URIBinder::Bind(stmt, 0, aURI);
   1.480 +    NS_ENSURE_SUCCESS(rv, rv);
   1.481 +
   1.482 +    nsRefPtr<VisitedQuery> callback = new VisitedQuery(aURI, aCallback);
   1.483 +    NS_ENSURE_TRUE(callback, NS_ERROR_OUT_OF_MEMORY);
   1.484 +
   1.485 +    nsCOMPtr<mozIStoragePendingStatement> handle;
   1.486 +    return stmt->ExecuteAsync(callback, getter_AddRefs(handle));
   1.487 +  }
   1.488 +
   1.489 +  NS_IMETHOD HandleResult(mozIStorageResultSet* aResults)
   1.490 +  {
   1.491 +    // If this method is called, we've gotten results, which means we have a
   1.492 +    // visit.
   1.493 +    mIsVisited = true;
   1.494 +    return NS_OK;
   1.495 +  }
   1.496 +
   1.497 +  NS_IMETHOD HandleError(mozIStorageError* aError)
   1.498 +  {
   1.499 +    // mIsVisited is already set to false, and that's the assumption we will
   1.500 +    // make if an error occurred.
   1.501 +    return NS_OK;
   1.502 +  }
   1.503 +
   1.504 +  NS_IMETHOD HandleCompletion(uint16_t aReason)
   1.505 +  {
   1.506 +    if (aReason != mozIStorageStatementCallback::REASON_FINISHED) {
   1.507 +      return NS_OK;
   1.508 +    }
   1.509 +
   1.510 +    nsresult rv = NotifyVisitedStatus();
   1.511 +    NS_ENSURE_SUCCESS(rv, rv);
   1.512 +    return NS_OK;
   1.513 +  }
   1.514 +
   1.515 +  nsresult NotifyVisitedStatus()
   1.516 +  {
   1.517 +    // If an external handling callback is provided, just notify through it.
   1.518 +    if (mCallback) {
   1.519 +      mCallback->IsVisited(mURI, mIsVisited);
   1.520 +      return NS_OK;
   1.521 +    }
   1.522 +
   1.523 +    if (mIsVisited) {
   1.524 +      History* history = History::GetService();
   1.525 +      NS_ENSURE_STATE(history);
   1.526 +      history->NotifyVisited(mURI);
   1.527 +    }
   1.528 +
   1.529 +    nsCOMPtr<nsIObserverService> observerService =
   1.530 +      mozilla::services::GetObserverService();
   1.531 +    if (observerService) {
   1.532 +      nsAutoString status;
   1.533 +      if (mIsVisited) {
   1.534 +        status.AssignLiteral(URI_VISITED);
   1.535 +      }
   1.536 +      else {
   1.537 +        status.AssignLiteral(URI_NOT_VISITED);
   1.538 +      }
   1.539 +      (void)observerService->NotifyObservers(mURI,
   1.540 +                                             URI_VISITED_RESOLUTION_TOPIC,
   1.541 +                                             status.get());
   1.542 +    }
   1.543 +
   1.544 +    return NS_OK;
   1.545 +  }
   1.546 +
   1.547 +private:
   1.548 +  VisitedQuery(nsIURI* aURI,
   1.549 +               mozIVisitedStatusCallback *aCallback=nullptr,
   1.550 +               bool aIsVisited=false)
   1.551 +  : mURI(aURI)
   1.552 +  , mCallback(aCallback)
   1.553 +  , mIsVisited(aIsVisited)
   1.554 +  {
   1.555 +  }
   1.556 +
   1.557 +  nsCOMPtr<nsIURI> mURI;
   1.558 +  nsCOMPtr<mozIVisitedStatusCallback> mCallback;
   1.559 +  bool mIsVisited;
   1.560 +};
   1.561 +
   1.562 +/**
   1.563 + * Notifies observers about a visit.
   1.564 + */
   1.565 +class NotifyVisitObservers : public nsRunnable
   1.566 +{
   1.567 +public:
   1.568 +  NotifyVisitObservers(VisitData& aPlace,
   1.569 +                       VisitData& aReferrer)
   1.570 +  : mPlace(aPlace)
   1.571 +  , mReferrer(aReferrer)
   1.572 +  , mHistory(History::GetService())
   1.573 +  {
   1.574 +  }
   1.575 +
   1.576 +  NS_IMETHOD Run()
   1.577 +  {
   1.578 +    MOZ_ASSERT(NS_IsMainThread(), "This should be called on the main thread");
   1.579 +
   1.580 +    // We are in the main thread, no need to lock.
   1.581 +    if (mHistory->IsShuttingDown()) {
   1.582 +      // If we are shutting down, we cannot notify the observers.
   1.583 +      return NS_OK;
   1.584 +    }
   1.585 +
   1.586 +    nsNavHistory* navHistory = nsNavHistory::GetHistoryService();
   1.587 +    if (!navHistory) {
   1.588 +      NS_WARNING("Trying to notify about a visit but cannot get the history service!");
   1.589 +      return NS_OK;
   1.590 +    }
   1.591 +
   1.592 +    nsCOMPtr<nsIURI> uri;
   1.593 +    (void)NS_NewURI(getter_AddRefs(uri), mPlace.spec);
   1.594 +
   1.595 +    // Notify the visit.  Note that TRANSITION_EMBED visits are never added
   1.596 +    // to the database, thus cannot be queried and we don't notify them.
   1.597 +    if (mPlace.transitionType != nsINavHistoryService::TRANSITION_EMBED) {
   1.598 +      navHistory->NotifyOnVisit(uri, mPlace.visitId, mPlace.visitTime,
   1.599 +                                mReferrer.visitId, mPlace.transitionType,
   1.600 +                                mPlace.guid, mPlace.hidden);
   1.601 +    }
   1.602 +
   1.603 +    nsCOMPtr<nsIObserverService> obsService =
   1.604 +      mozilla::services::GetObserverService();
   1.605 +    if (obsService) {
   1.606 +      DebugOnly<nsresult> rv =
   1.607 +        obsService->NotifyObservers(uri, URI_VISIT_SAVED, nullptr);
   1.608 +      NS_WARN_IF_FALSE(NS_SUCCEEDED(rv), "Could not notify observers");
   1.609 +    }
   1.610 +
   1.611 +    History* history = History::GetService();
   1.612 +    NS_ENSURE_STATE(history);
   1.613 +    history->AppendToRecentlyVisitedURIs(uri);
   1.614 +    history->NotifyVisited(uri);
   1.615 +
   1.616 +    return NS_OK;
   1.617 +  }
   1.618 +private:
   1.619 +  VisitData mPlace;
   1.620 +  VisitData mReferrer;
   1.621 +  nsRefPtr<History> mHistory;
   1.622 +};
   1.623 +
   1.624 +/**
   1.625 + * Notifies observers about a pages title changing.
   1.626 + */
   1.627 +class NotifyTitleObservers : public nsRunnable
   1.628 +{
   1.629 +public:
   1.630 +  /**
   1.631 +   * Notifies observers on the main thread.
   1.632 +   *
   1.633 +   * @param aSpec
   1.634 +   *        The spec of the URI to notify about.
   1.635 +   * @param aTitle
   1.636 +   *        The new title to notify about.
   1.637 +   */
   1.638 +  NotifyTitleObservers(const nsCString& aSpec,
   1.639 +                       const nsString& aTitle,
   1.640 +                       const nsCString& aGUID)
   1.641 +  : mSpec(aSpec)
   1.642 +  , mTitle(aTitle)
   1.643 +  , mGUID(aGUID)
   1.644 +  {
   1.645 +  }
   1.646 +
   1.647 +  NS_IMETHOD Run()
   1.648 +  {
   1.649 +    MOZ_ASSERT(NS_IsMainThread(), "This should be called on the main thread");
   1.650 +
   1.651 +    nsNavHistory* navHistory = nsNavHistory::GetHistoryService();
   1.652 +    NS_ENSURE_TRUE(navHistory, NS_ERROR_OUT_OF_MEMORY);
   1.653 +    nsCOMPtr<nsIURI> uri;
   1.654 +    (void)NS_NewURI(getter_AddRefs(uri), mSpec);
   1.655 +    navHistory->NotifyTitleChange(uri, mTitle, mGUID);
   1.656 +
   1.657 +    return NS_OK;
   1.658 +  }
   1.659 +private:
   1.660 +  const nsCString mSpec;
   1.661 +  const nsString mTitle;
   1.662 +  const nsCString mGUID;
   1.663 +};
   1.664 +
   1.665 +/**
   1.666 + * Helper class for methods which notify their callers through the
   1.667 + * mozIVisitInfoCallback interface.
   1.668 + */
   1.669 +class NotifyPlaceInfoCallback : public nsRunnable
   1.670 +{
   1.671 +public:
   1.672 +  NotifyPlaceInfoCallback(mozIVisitInfoCallback* aCallback,
   1.673 +                          const VisitData& aPlace,
   1.674 +                          bool aIsSingleVisit,
   1.675 +                          nsresult aResult)
   1.676 +  : mCallback(aCallback)
   1.677 +  , mPlace(aPlace)
   1.678 +  , mResult(aResult)
   1.679 +  , mIsSingleVisit(aIsSingleVisit)
   1.680 +  {
   1.681 +    MOZ_ASSERT(aCallback, "Must pass a non-null callback!");
   1.682 +  }
   1.683 +
   1.684 +  NS_IMETHOD Run()
   1.685 +  {
   1.686 +    MOZ_ASSERT(NS_IsMainThread(), "This should be called on the main thread");
   1.687 +
   1.688 +    nsCOMPtr<nsIURI> referrerURI;
   1.689 +    if (!mPlace.referrerSpec.IsEmpty()) {
   1.690 +      (void)NS_NewURI(getter_AddRefs(referrerURI), mPlace.referrerSpec);
   1.691 +    }
   1.692 +
   1.693 +    nsCOMPtr<nsIURI> uri;
   1.694 +    (void)NS_NewURI(getter_AddRefs(uri), mPlace.spec);
   1.695 +
   1.696 +    nsCOMPtr<mozIPlaceInfo> place;
   1.697 +    if (mIsSingleVisit) {
   1.698 +      nsCOMPtr<mozIVisitInfo> visit =
   1.699 +        new VisitInfo(mPlace.visitId, mPlace.visitTime, mPlace.transitionType,
   1.700 +                      referrerURI.forget());
   1.701 +      PlaceInfo::VisitsArray visits;
   1.702 +      (void)visits.AppendElement(visit);
   1.703 +
   1.704 +      // The frecency isn't exposed because it may not reflect the updated value
   1.705 +      // in the case of InsertVisitedURIs.
   1.706 +      place =
   1.707 +        new PlaceInfo(mPlace.placeId, mPlace.guid, uri.forget(), mPlace.title,
   1.708 +                      -1, visits);
   1.709 +    }
   1.710 +    else {
   1.711 +      // Same as above.
   1.712 +      place =
   1.713 +        new PlaceInfo(mPlace.placeId, mPlace.guid, uri.forget(), mPlace.title,
   1.714 +                      -1);
   1.715 +    }
   1.716 +
   1.717 +    if (NS_SUCCEEDED(mResult)) {
   1.718 +      (void)mCallback->HandleResult(place);
   1.719 +    }
   1.720 +    else {
   1.721 +      (void)mCallback->HandleError(mResult, place);
   1.722 +    }
   1.723 +
   1.724 +    return NS_OK;
   1.725 +  }
   1.726 +
   1.727 +private:
   1.728 +  /**
   1.729 +   * Callers MUST hold a strong reference to this that outlives us because we
   1.730 +   * may be created off of the main thread, and therefore cannot call AddRef on
   1.731 +   * this object (and therefore cannot hold a strong reference to it).
   1.732 +   */
   1.733 +  mozIVisitInfoCallback* mCallback;
   1.734 +  VisitData mPlace;
   1.735 +  const nsresult mResult;
   1.736 +  bool mIsSingleVisit;
   1.737 +};
   1.738 +
   1.739 +/**
   1.740 + * Notifies a callback object when the operation is complete.
   1.741 + */
   1.742 +class NotifyCompletion : public nsRunnable
   1.743 +{
   1.744 +public:
   1.745 +  NotifyCompletion(mozIVisitInfoCallback* aCallback)
   1.746 +  : mCallback(aCallback)
   1.747 +  {
   1.748 +    MOZ_ASSERT(aCallback, "Must pass a non-null callback!");
   1.749 +  }
   1.750 +
   1.751 +  NS_IMETHOD Run()
   1.752 +  {
   1.753 +    if (NS_IsMainThread()) {
   1.754 +      (void)mCallback->HandleCompletion();
   1.755 +    }
   1.756 +    else {
   1.757 +      (void)NS_DispatchToMainThread(this);
   1.758 +
   1.759 +      // Also dispatch an event to release the reference to the callback after
   1.760 +      // we have run.
   1.761 +      nsCOMPtr<nsIThread> mainThread = do_GetMainThread();
   1.762 +      (void)NS_ProxyRelease(mainThread, mCallback, true);
   1.763 +    }
   1.764 +    return NS_OK;
   1.765 +  }
   1.766 +
   1.767 +private:
   1.768 +  /**
   1.769 +   * Callers MUST hold a strong reference to this because we may be created
   1.770 +   * off of the main thread, and therefore cannot call AddRef on this object
   1.771 +   * (and therefore cannot hold a strong reference to it). If invoked from a
   1.772 +   * background thread, NotifyCompletion will release the reference to this.
   1.773 +   */
   1.774 +  mozIVisitInfoCallback* mCallback;
   1.775 +};
   1.776 +
   1.777 +/**
   1.778 + * Checks to see if we can add aURI to history, and dispatches an error to
   1.779 + * aCallback (if provided) if we cannot.
   1.780 + *
   1.781 + * @param aURI
   1.782 + *        The URI to check.
   1.783 + * @param [optional] aGUID
   1.784 + *        The guid of the URI to check.  This is passed back to the callback.
   1.785 + * @param [optional] aCallback
   1.786 + *        The callback to notify if the URI cannot be added to history.
   1.787 + * @return true if the URI can be added to history, false otherwise.
   1.788 + */
   1.789 +bool
   1.790 +CanAddURI(nsIURI* aURI,
   1.791 +          const nsCString& aGUID = EmptyCString(),
   1.792 +          mozIVisitInfoCallback* aCallback = nullptr)
   1.793 +{
   1.794 +  nsNavHistory* navHistory = nsNavHistory::GetHistoryService();
   1.795 +  NS_ENSURE_TRUE(navHistory, false);
   1.796 +
   1.797 +  bool canAdd;
   1.798 +  nsresult rv = navHistory->CanAddURI(aURI, &canAdd);
   1.799 +  if (NS_SUCCEEDED(rv) && canAdd) {
   1.800 +    return true;
   1.801 +  };
   1.802 +
   1.803 +  // We cannot add the URI.  Notify the callback, if we were given one.
   1.804 +  if (aCallback) {
   1.805 +    // NotifyPlaceInfoCallback does not hold a strong reference to the callback, so we
   1.806 +    // have to manage it by AddRefing now and then releasing it after the event
   1.807 +    // has run.
   1.808 +    NS_ADDREF(aCallback);
   1.809 +
   1.810 +    VisitData place(aURI);
   1.811 +    place.guid = aGUID;
   1.812 +    nsCOMPtr<nsIRunnable> event =
   1.813 +      new NotifyPlaceInfoCallback(aCallback, place, true, NS_ERROR_INVALID_ARG);
   1.814 +    (void)NS_DispatchToMainThread(event);
   1.815 +
   1.816 +    // Also dispatch an event to release our reference to the callback after
   1.817 +    // NotifyPlaceInfoCallback has run.
   1.818 +    nsCOMPtr<nsIThread> mainThread = do_GetMainThread();
   1.819 +    (void)NS_ProxyRelease(mainThread, aCallback, true);
   1.820 +  }
   1.821 +
   1.822 +  return false;
   1.823 +}
   1.824 +
   1.825 +/**
   1.826 + * Adds a visit to the database.
   1.827 + */
   1.828 +class InsertVisitedURIs : public nsRunnable
   1.829 +{
   1.830 +public:
   1.831 +  /**
   1.832 +   * Adds a visit to the database asynchronously.
   1.833 +   *
   1.834 +   * @param aConnection
   1.835 +   *        The database connection to use for these operations.
   1.836 +   * @param aPlaces
   1.837 +   *        The locations to record visits.
   1.838 +   * @param [optional] aCallback
   1.839 +   *        The callback to notify about the visit.
   1.840 +   */
   1.841 +  static nsresult Start(mozIStorageConnection* aConnection,
   1.842 +                        nsTArray<VisitData>& aPlaces,
   1.843 +                        mozIVisitInfoCallback* aCallback = nullptr)
   1.844 +  {
   1.845 +    MOZ_ASSERT(NS_IsMainThread(), "This should be called on the main thread");
   1.846 +    MOZ_ASSERT(aPlaces.Length() > 0, "Must pass a non-empty array!");
   1.847 +
   1.848 +    nsRefPtr<InsertVisitedURIs> event =
   1.849 +      new InsertVisitedURIs(aConnection, aPlaces, aCallback);
   1.850 +
   1.851 +    // Get the target thread, and then start the work!
   1.852 +    nsCOMPtr<nsIEventTarget> target = do_GetInterface(aConnection);
   1.853 +    NS_ENSURE_TRUE(target, NS_ERROR_UNEXPECTED);
   1.854 +    nsresult rv = target->Dispatch(event, NS_DISPATCH_NORMAL);
   1.855 +    NS_ENSURE_SUCCESS(rv, rv);
   1.856 +
   1.857 +    return NS_OK;
   1.858 +  }
   1.859 +
   1.860 +  NS_IMETHOD Run()
   1.861 +  {
   1.862 +    MOZ_ASSERT(!NS_IsMainThread(), "This should not be called on the main thread");
   1.863 +
   1.864 +    // Prevent the main thread from shutting down while this is running.
   1.865 +    MutexAutoLock lockedScope(mHistory->GetShutdownMutex());
   1.866 +    if (mHistory->IsShuttingDown()) {
   1.867 +      // If we were already shutting down, we cannot insert the URIs.
   1.868 +      return NS_OK;
   1.869 +    }
   1.870 +
   1.871 +    mozStorageTransaction transaction(mDBConn, false,
   1.872 +                                      mozIStorageConnection::TRANSACTION_IMMEDIATE);
   1.873 +
   1.874 +    VisitData* lastPlace = nullptr;
   1.875 +    for (nsTArray<VisitData>::size_type i = 0; i < mPlaces.Length(); i++) {
   1.876 +      VisitData& place = mPlaces.ElementAt(i);
   1.877 +      VisitData& referrer = mReferrers.ElementAt(i);
   1.878 +
   1.879 +      // We can avoid a database lookup if it's the same place as the last
   1.880 +      // visit we added.
   1.881 +      bool known = lastPlace && lastPlace->IsSamePlaceAs(place);
   1.882 +      if (!known) {
   1.883 +        nsresult rv = mHistory->FetchPageInfo(place, &known);
   1.884 +        if (NS_FAILED(rv)) {
   1.885 +          if (mCallback) {
   1.886 +            nsCOMPtr<nsIRunnable> event =
   1.887 +              new NotifyPlaceInfoCallback(mCallback, place, true, rv);
   1.888 +            return NS_DispatchToMainThread(event);
   1.889 +          }
   1.890 +          return NS_OK;
   1.891 +        }
   1.892 +      }
   1.893 +
   1.894 +      FetchReferrerInfo(referrer, place);
   1.895 +
   1.896 +      nsresult rv = DoDatabaseInserts(known, place, referrer);
   1.897 +      if (mCallback) {
   1.898 +        nsCOMPtr<nsIRunnable> event =
   1.899 +          new NotifyPlaceInfoCallback(mCallback, place, true, rv);
   1.900 +        nsresult rv2 = NS_DispatchToMainThread(event);
   1.901 +        NS_ENSURE_SUCCESS(rv2, rv2);
   1.902 +      }
   1.903 +      NS_ENSURE_SUCCESS(rv, rv);
   1.904 +
   1.905 +      nsCOMPtr<nsIRunnable> event = new NotifyVisitObservers(place, referrer);
   1.906 +      rv = NS_DispatchToMainThread(event);
   1.907 +      NS_ENSURE_SUCCESS(rv, rv);
   1.908 +
   1.909 +      // Notify about title change if needed.
   1.910 +      if ((!known && !place.title.IsVoid()) || place.titleChanged) {
   1.911 +        event = new NotifyTitleObservers(place.spec, place.title, place.guid);
   1.912 +        rv = NS_DispatchToMainThread(event);
   1.913 +        NS_ENSURE_SUCCESS(rv, rv);
   1.914 +      }
   1.915 +
   1.916 +      lastPlace = &mPlaces.ElementAt(i);
   1.917 +    }
   1.918 +
   1.919 +    nsresult rv = transaction.Commit();
   1.920 +    NS_ENSURE_SUCCESS(rv, rv);
   1.921 +
   1.922 +    return NS_OK;
   1.923 +  }
   1.924 +private:
   1.925 +  InsertVisitedURIs(mozIStorageConnection* aConnection,
   1.926 +                    nsTArray<VisitData>& aPlaces,
   1.927 +                    mozIVisitInfoCallback* aCallback)
   1.928 +  : mDBConn(aConnection)
   1.929 +  , mCallback(aCallback)
   1.930 +  , mHistory(History::GetService())
   1.931 +  {
   1.932 +    MOZ_ASSERT(NS_IsMainThread(), "This should be called on the main thread");
   1.933 +
   1.934 +    (void)mPlaces.SwapElements(aPlaces);
   1.935 +    (void)mReferrers.SetLength(mPlaces.Length());
   1.936 +
   1.937 +    nsNavHistory* navHistory = nsNavHistory::GetHistoryService();
   1.938 +    NS_ABORT_IF_FALSE(navHistory, "Could not get nsNavHistory?!");
   1.939 +
   1.940 +    for (nsTArray<VisitData>::size_type i = 0; i < mPlaces.Length(); i++) {
   1.941 +      mReferrers[i].spec = mPlaces[i].referrerSpec;
   1.942 +
   1.943 +#ifdef DEBUG
   1.944 +      nsCOMPtr<nsIURI> uri;
   1.945 +      (void)NS_NewURI(getter_AddRefs(uri), mPlaces[i].spec);
   1.946 +      NS_ASSERTION(CanAddURI(uri),
   1.947 +                   "Passed a VisitData with a URI we cannot add to history!");
   1.948 +#endif
   1.949 +    }
   1.950 +  }
   1.951 +
   1.952 +  virtual ~InsertVisitedURIs()
   1.953 +  {
   1.954 +    if (mCallback) {
   1.955 +      nsCOMPtr<nsIThread> mainThread = do_GetMainThread();
   1.956 +      (void)NS_ProxyRelease(mainThread, mCallback, true);
   1.957 +    }
   1.958 +  }
   1.959 +
   1.960 +  /**
   1.961 +   * Inserts or updates the entry in moz_places for this visit, adds the visit,
   1.962 +   * and updates the frecency of the place.
   1.963 +   *
   1.964 +   * @param aKnown
   1.965 +   *        True if we already have an entry for this place in moz_places, false
   1.966 +   *        otherwise.
   1.967 +   * @param aPlace
   1.968 +   *        The place we are adding a visit for.
   1.969 +   * @param aReferrer
   1.970 +   *        The referrer for aPlace.
   1.971 +   */
   1.972 +  nsresult DoDatabaseInserts(bool aKnown,
   1.973 +                             VisitData& aPlace,
   1.974 +                             VisitData& aReferrer)
   1.975 +  {
   1.976 +    MOZ_ASSERT(!NS_IsMainThread(), "This should not be called on the main thread");
   1.977 +
   1.978 +    // If the page was in moz_places, we need to update the entry.
   1.979 +    nsresult rv;
   1.980 +    if (aKnown) {
   1.981 +      rv = mHistory->UpdatePlace(aPlace);
   1.982 +      NS_ENSURE_SUCCESS(rv, rv);
   1.983 +    }
   1.984 +    // Otherwise, the page was not in moz_places, so now we have to add it.
   1.985 +    else {
   1.986 +      rv = mHistory->InsertPlace(aPlace);
   1.987 +      NS_ENSURE_SUCCESS(rv, rv);
   1.988 +
   1.989 +      // We need the place id and guid of the page we just inserted when we
   1.990 +      // have a callback or when the GUID isn't known.  No point in doing the
   1.991 +      // disk I/O if we do not need it.
   1.992 +      if (mCallback || aPlace.guid.IsEmpty()) {
   1.993 +        bool exists;
   1.994 +        rv = mHistory->FetchPageInfo(aPlace, &exists);
   1.995 +        NS_ENSURE_SUCCESS(rv, rv);
   1.996 +
   1.997 +        if (!exists) {
   1.998 +          NS_NOTREACHED("should have an entry in moz_places");
   1.999 +        }
  1.1000 +      }
  1.1001 +    }
  1.1002 +
  1.1003 +    rv = AddVisit(aPlace, aReferrer);
  1.1004 +    NS_ENSURE_SUCCESS(rv, rv);
  1.1005 +
  1.1006 +    // TODO (bug 623969) we shouldn't update this after each visit, but
  1.1007 +    // rather only for each unique place to save disk I/O.
  1.1008 +
  1.1009 +    // Don't update frecency if the page should not appear in autocomplete.
  1.1010 +    if (aPlace.shouldUpdateFrecency) {
  1.1011 +      rv = UpdateFrecency(aPlace);
  1.1012 +      NS_ENSURE_SUCCESS(rv, rv);
  1.1013 +    }
  1.1014 +
  1.1015 +    return NS_OK;
  1.1016 +  }
  1.1017 +
  1.1018 +  /**
  1.1019 +   * Loads visit information about the page into _place.
  1.1020 +   *
  1.1021 +   * @param _place
  1.1022 +   *        The VisitData for the place we need to know visit information about.
  1.1023 +   * @param [optional] aThresholdStart
  1.1024 +   *        The timestamp of a new visit (not represented by _place) used to
  1.1025 +   *        determine if the page was recently visited or not.
  1.1026 +   * @return true if the page was recently (determined with aThresholdStart)
  1.1027 +   *         visited, false otherwise.
  1.1028 +   */
  1.1029 +  bool FetchVisitInfo(VisitData& _place,
  1.1030 +                      PRTime aThresholdStart = 0)
  1.1031 +  {
  1.1032 +    NS_PRECONDITION(!_place.spec.IsEmpty(), "must have a non-empty spec!");
  1.1033 +
  1.1034 +    nsCOMPtr<mozIStorageStatement> stmt;
  1.1035 +    // If we have a visitTime, we want information on that specific visit.
  1.1036 +    if (_place.visitTime) {
  1.1037 +      stmt = mHistory->GetStatement(
  1.1038 +        "SELECT id, visit_date "
  1.1039 +        "FROM moz_historyvisits "
  1.1040 +        "WHERE place_id = (SELECT id FROM moz_places WHERE url = :page_url) "
  1.1041 +        "AND visit_date = :visit_date "
  1.1042 +      );
  1.1043 +      NS_ENSURE_TRUE(stmt, false);
  1.1044 +
  1.1045 +      mozStorageStatementScoper scoper(stmt);
  1.1046 +      nsresult rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("visit_date"),
  1.1047 +                                          _place.visitTime);
  1.1048 +      NS_ENSURE_SUCCESS(rv, false);
  1.1049 +
  1.1050 +      scoper.Abandon();
  1.1051 +    }
  1.1052 +    // Otherwise, we want information about the most recent visit.
  1.1053 +    else {
  1.1054 +      stmt = mHistory->GetStatement(
  1.1055 +        "SELECT id, visit_date "
  1.1056 +        "FROM moz_historyvisits "
  1.1057 +        "WHERE place_id = (SELECT id FROM moz_places WHERE url = :page_url) "
  1.1058 +        "ORDER BY visit_date DESC "
  1.1059 +      );
  1.1060 +      NS_ENSURE_TRUE(stmt, false);
  1.1061 +    }
  1.1062 +    mozStorageStatementScoper scoper(stmt);
  1.1063 +
  1.1064 +    nsresult rv = URIBinder::Bind(stmt, NS_LITERAL_CSTRING("page_url"),
  1.1065 +                                  _place.spec);
  1.1066 +    NS_ENSURE_SUCCESS(rv, false);
  1.1067 +
  1.1068 +    bool hasResult;
  1.1069 +    rv = stmt->ExecuteStep(&hasResult);
  1.1070 +    NS_ENSURE_SUCCESS(rv, false);
  1.1071 +    if (!hasResult) {
  1.1072 +      return false;
  1.1073 +    }
  1.1074 +
  1.1075 +    rv = stmt->GetInt64(0, &_place.visitId);
  1.1076 +    NS_ENSURE_SUCCESS(rv, false);
  1.1077 +    rv = stmt->GetInt64(1, reinterpret_cast<int64_t*>(&_place.visitTime));
  1.1078 +    NS_ENSURE_SUCCESS(rv, false);
  1.1079 +
  1.1080 +    // If we have been given a visit threshold start time, go ahead and
  1.1081 +    // calculate if we have been recently visited.
  1.1082 +    if (aThresholdStart &&
  1.1083 +        aThresholdStart - _place.visitTime <= RECENT_EVENT_THRESHOLD) {
  1.1084 +      return true;
  1.1085 +    }
  1.1086 +
  1.1087 +    return false;
  1.1088 +  }
  1.1089 +
  1.1090 +  /**
  1.1091 +   * Fetches information about a referrer for aPlace if it was a recent
  1.1092 +   * visit or not.
  1.1093 +   *
  1.1094 +   * @param aReferrer
  1.1095 +   *        The VisitData for the referrer.  This will be populated with
  1.1096 +   *        FetchVisitInfo.
  1.1097 +   * @param aPlace
  1.1098 +   *        The VisitData for the visit we will eventually add.
  1.1099 +   *
  1.1100 +   */
  1.1101 +  void FetchReferrerInfo(VisitData& aReferrer,
  1.1102 +                         VisitData& aPlace)
  1.1103 +  {
  1.1104 +    if (aReferrer.spec.IsEmpty()) {
  1.1105 +      return;
  1.1106 +    }
  1.1107 +
  1.1108 +    if (!FetchVisitInfo(aReferrer, aPlace.visitTime)) {
  1.1109 +      // We must change both the place and referrer to indicate that we will
  1.1110 +      // not be using the referrer's data. This behavior has test coverage, so
  1.1111 +      // if this invariant changes, we'll know.
  1.1112 +      aPlace.referrerSpec.Truncate();
  1.1113 +      aReferrer.visitId = 0;
  1.1114 +    }
  1.1115 +  }
  1.1116 +
  1.1117 +  /**
  1.1118 +   * Adds a visit for _place and updates it with the right visit id.
  1.1119 +   *
  1.1120 +   * @param _place
  1.1121 +   *        The VisitData for the place we need to know visit information about.
  1.1122 +   * @param aReferrer
  1.1123 +   *        A reference to the referrer's visit data.
  1.1124 +   */
  1.1125 +  nsresult AddVisit(VisitData& _place,
  1.1126 +                    const VisitData& aReferrer)
  1.1127 +  {
  1.1128 +    nsresult rv;
  1.1129 +    nsCOMPtr<mozIStorageStatement> stmt;
  1.1130 +    if (_place.placeId) {
  1.1131 +      stmt = mHistory->GetStatement(
  1.1132 +        "INSERT INTO moz_historyvisits "
  1.1133 +          "(from_visit, place_id, visit_date, visit_type, session) "
  1.1134 +        "VALUES (:from_visit, :page_id, :visit_date, :visit_type, 0) "
  1.1135 +      );
  1.1136 +      NS_ENSURE_STATE(stmt);
  1.1137 +      rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("page_id"), _place.placeId);
  1.1138 +      NS_ENSURE_SUCCESS(rv, rv);
  1.1139 +    }
  1.1140 +    else {
  1.1141 +      stmt = mHistory->GetStatement(
  1.1142 +        "INSERT INTO moz_historyvisits "
  1.1143 +          "(from_visit, place_id, visit_date, visit_type, session) "
  1.1144 +        "VALUES (:from_visit, (SELECT id FROM moz_places WHERE url = :page_url), :visit_date, :visit_type, 0) "
  1.1145 +      );
  1.1146 +      NS_ENSURE_STATE(stmt);
  1.1147 +      rv = URIBinder::Bind(stmt, NS_LITERAL_CSTRING("page_url"), _place.spec);
  1.1148 +      NS_ENSURE_SUCCESS(rv, rv);
  1.1149 +    }
  1.1150 +    rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("from_visit"),
  1.1151 +                               aReferrer.visitId);
  1.1152 +    NS_ENSURE_SUCCESS(rv, rv);
  1.1153 +    rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("visit_date"),
  1.1154 +                               _place.visitTime);
  1.1155 +    NS_ENSURE_SUCCESS(rv, rv);
  1.1156 +    uint32_t transitionType = _place.transitionType;
  1.1157 +    NS_ASSERTION(transitionType >= nsINavHistoryService::TRANSITION_LINK &&
  1.1158 +                 transitionType <= nsINavHistoryService::TRANSITION_FRAMED_LINK,
  1.1159 +                 "Invalid transition type!");
  1.1160 +    rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("visit_type"),
  1.1161 +                               transitionType);
  1.1162 +    NS_ENSURE_SUCCESS(rv, rv);
  1.1163 +
  1.1164 +    mozStorageStatementScoper scoper(stmt);
  1.1165 +    rv = stmt->Execute();
  1.1166 +    NS_ENSURE_SUCCESS(rv, rv);
  1.1167 +
  1.1168 +    // Now that it should be in the database, we need to obtain the id of the
  1.1169 +    // visit we just added.
  1.1170 +    (void)FetchVisitInfo(_place);
  1.1171 +
  1.1172 +    return NS_OK;
  1.1173 +  }
  1.1174 +
  1.1175 +  /**
  1.1176 +   * Updates the frecency, and possibly the hidden-ness of aPlace.
  1.1177 +   *
  1.1178 +   * @param aPlace
  1.1179 +   *        The VisitData for the place we want to update.
  1.1180 +   */
  1.1181 +  nsresult UpdateFrecency(const VisitData& aPlace)
  1.1182 +  {
  1.1183 +    MOZ_ASSERT(aPlace.shouldUpdateFrecency);
  1.1184 +
  1.1185 +    nsresult rv;
  1.1186 +    { // First, set our frecency to the proper value.
  1.1187 +      nsCOMPtr<mozIStorageStatement> stmt;
  1.1188 +      if (aPlace.placeId) {
  1.1189 +        stmt = mHistory->GetStatement(
  1.1190 +          "UPDATE moz_places "
  1.1191 +          "SET frecency = NOTIFY_FRECENCY("
  1.1192 +            "CALCULATE_FRECENCY(:page_id), "
  1.1193 +            "url, guid, hidden, last_visit_date"
  1.1194 +          ") "
  1.1195 +          "WHERE id = :page_id"
  1.1196 +        );
  1.1197 +        NS_ENSURE_STATE(stmt);
  1.1198 +        rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("page_id"), aPlace.placeId);
  1.1199 +        NS_ENSURE_SUCCESS(rv, rv);
  1.1200 +      }
  1.1201 +      else {
  1.1202 +        stmt = mHistory->GetStatement(
  1.1203 +          "UPDATE moz_places "
  1.1204 +          "SET frecency = NOTIFY_FRECENCY("
  1.1205 +            "CALCULATE_FRECENCY(id), url, guid, hidden, last_visit_date"
  1.1206 +          ") "
  1.1207 +          "WHERE url = :page_url"
  1.1208 +        );
  1.1209 +        NS_ENSURE_STATE(stmt);
  1.1210 +        rv = URIBinder::Bind(stmt, NS_LITERAL_CSTRING("page_url"), aPlace.spec);
  1.1211 +        NS_ENSURE_SUCCESS(rv, rv);
  1.1212 +      }
  1.1213 +      mozStorageStatementScoper scoper(stmt);
  1.1214 +
  1.1215 +      rv = stmt->Execute();
  1.1216 +      NS_ENSURE_SUCCESS(rv, rv);
  1.1217 +    }
  1.1218 +
  1.1219 +    if (!aPlace.hidden) {
  1.1220 +      // Mark the page as not hidden if the frecency is now nonzero.
  1.1221 +      nsCOMPtr<mozIStorageStatement> stmt;
  1.1222 +      if (aPlace.placeId) {
  1.1223 +        stmt = mHistory->GetStatement(
  1.1224 +          "UPDATE moz_places "
  1.1225 +          "SET hidden = 0 "
  1.1226 +          "WHERE id = :page_id AND frecency <> 0"
  1.1227 +        );
  1.1228 +        NS_ENSURE_STATE(stmt);
  1.1229 +        rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("page_id"), aPlace.placeId);
  1.1230 +        NS_ENSURE_SUCCESS(rv, rv);
  1.1231 +      }
  1.1232 +      else {
  1.1233 +        stmt = mHistory->GetStatement(
  1.1234 +          "UPDATE moz_places "
  1.1235 +          "SET hidden = 0 "
  1.1236 +          "WHERE url = :page_url AND frecency <> 0"
  1.1237 +        );
  1.1238 +        NS_ENSURE_STATE(stmt);
  1.1239 +        rv = URIBinder::Bind(stmt, NS_LITERAL_CSTRING("page_url"), aPlace.spec);
  1.1240 +        NS_ENSURE_SUCCESS(rv, rv);
  1.1241 +      }
  1.1242 +
  1.1243 +      mozStorageStatementScoper scoper(stmt);
  1.1244 +      rv = stmt->Execute();
  1.1245 +      NS_ENSURE_SUCCESS(rv, rv);
  1.1246 +    }
  1.1247 +
  1.1248 +    return NS_OK;
  1.1249 +  }
  1.1250 +
  1.1251 +  mozIStorageConnection* mDBConn;
  1.1252 +
  1.1253 +  nsTArray<VisitData> mPlaces;
  1.1254 +  nsTArray<VisitData> mReferrers;
  1.1255 +
  1.1256 +  nsCOMPtr<mozIVisitInfoCallback> mCallback;
  1.1257 +
  1.1258 +  /**
  1.1259 +   * Strong reference to the History object because we do not want it to
  1.1260 +   * disappear out from under us.
  1.1261 +   */
  1.1262 +  nsRefPtr<History> mHistory;
  1.1263 +};
  1.1264 +
  1.1265 +class GetPlaceInfo MOZ_FINAL : public nsRunnable {
  1.1266 +public:
  1.1267 +  /**
  1.1268 +   * Get the place info for a given place (by GUID or URI)  asynchronously.
  1.1269 +   */
  1.1270 +  static nsresult Start(mozIStorageConnection* aConnection,
  1.1271 +                        VisitData& aPlace,
  1.1272 +                        mozIVisitInfoCallback* aCallback) {
  1.1273 +    MOZ_ASSERT(NS_IsMainThread(), "This should be called on the main thread");
  1.1274 +
  1.1275 +    nsRefPtr<GetPlaceInfo> event = new GetPlaceInfo(aPlace, aCallback);
  1.1276 +
  1.1277 +    // Get the target thread, and then start the work!
  1.1278 +    nsCOMPtr<nsIEventTarget> target = do_GetInterface(aConnection);
  1.1279 +    NS_ENSURE_TRUE(target, NS_ERROR_UNEXPECTED);
  1.1280 +    nsresult rv = target->Dispatch(event, NS_DISPATCH_NORMAL);
  1.1281 +    NS_ENSURE_SUCCESS(rv, rv);
  1.1282 +
  1.1283 +    return NS_OK;
  1.1284 +  }
  1.1285 +
  1.1286 +  NS_IMETHOD Run()
  1.1287 +  {
  1.1288 +    MOZ_ASSERT(!NS_IsMainThread(), "This should not be called on the main thread");
  1.1289 +
  1.1290 +    bool exists;
  1.1291 +    nsresult rv = mHistory->FetchPageInfo(mPlace, &exists);
  1.1292 +    NS_ENSURE_SUCCESS(rv, rv);
  1.1293 +
  1.1294 +    if (!exists)
  1.1295 +      rv = NS_ERROR_NOT_AVAILABLE;
  1.1296 +
  1.1297 +    nsCOMPtr<nsIRunnable> event =
  1.1298 +      new NotifyPlaceInfoCallback(mCallback, mPlace, false, rv);
  1.1299 +
  1.1300 +    rv = NS_DispatchToMainThread(event);
  1.1301 +    NS_ENSURE_SUCCESS(rv, rv);
  1.1302 +
  1.1303 +    return NS_OK;
  1.1304 +  }
  1.1305 +private:
  1.1306 +  GetPlaceInfo(VisitData& aPlace,
  1.1307 +               mozIVisitInfoCallback* aCallback)
  1.1308 +  : mPlace(aPlace)
  1.1309 +  , mCallback(aCallback)
  1.1310 +  , mHistory(History::GetService())
  1.1311 +  {
  1.1312 +    MOZ_ASSERT(NS_IsMainThread(), "This should be called on the main thread");
  1.1313 +  }
  1.1314 +
  1.1315 +  virtual ~GetPlaceInfo()
  1.1316 +  {
  1.1317 +    if (mCallback) {
  1.1318 +      nsCOMPtr<nsIThread> mainThread = do_GetMainThread();
  1.1319 +      (void)NS_ProxyRelease(mainThread, mCallback, true);
  1.1320 +    }
  1.1321 +  }
  1.1322 +
  1.1323 +  VisitData mPlace;
  1.1324 +  nsCOMPtr<mozIVisitInfoCallback> mCallback;
  1.1325 +  nsRefPtr<History> mHistory;
  1.1326 +};
  1.1327 +
  1.1328 +/**
  1.1329 + * Sets the page title for a page in moz_places (if necessary).
  1.1330 + */
  1.1331 +class SetPageTitle : public nsRunnable
  1.1332 +{
  1.1333 +public:
  1.1334 +  /**
  1.1335 +   * Sets a pages title in the database asynchronously.
  1.1336 +   *
  1.1337 +   * @param aConnection
  1.1338 +   *        The database connection to use for this operation.
  1.1339 +   * @param aURI
  1.1340 +   *        The URI to set the page title on.
  1.1341 +   * @param aTitle
  1.1342 +   *        The title to set for the page, if the page exists.
  1.1343 +   */
  1.1344 +  static nsresult Start(mozIStorageConnection* aConnection,
  1.1345 +                        nsIURI* aURI,
  1.1346 +                        const nsAString& aTitle)
  1.1347 +  {
  1.1348 +    MOZ_ASSERT(NS_IsMainThread(), "This should be called on the main thread");
  1.1349 +    MOZ_ASSERT(aURI, "Must pass a non-null URI object!");
  1.1350 +
  1.1351 +    nsCString spec;
  1.1352 +    nsresult rv = aURI->GetSpec(spec);
  1.1353 +    NS_ENSURE_SUCCESS(rv, rv);
  1.1354 +
  1.1355 +    nsRefPtr<SetPageTitle> event = new SetPageTitle(spec, aTitle);
  1.1356 +
  1.1357 +    // Get the target thread, and then start the work!
  1.1358 +    nsCOMPtr<nsIEventTarget> target = do_GetInterface(aConnection);
  1.1359 +    NS_ENSURE_TRUE(target, NS_ERROR_UNEXPECTED);
  1.1360 +    rv = target->Dispatch(event, NS_DISPATCH_NORMAL);
  1.1361 +    NS_ENSURE_SUCCESS(rv, rv);
  1.1362 +
  1.1363 +    return NS_OK;
  1.1364 +  }
  1.1365 +
  1.1366 +  NS_IMETHOD Run()
  1.1367 +  {
  1.1368 +    MOZ_ASSERT(!NS_IsMainThread(), "This should not be called on the main thread");
  1.1369 +
  1.1370 +    // First, see if the page exists in the database (we'll need its id later).
  1.1371 +    bool exists;
  1.1372 +    nsresult rv = mHistory->FetchPageInfo(mPlace, &exists);
  1.1373 +    NS_ENSURE_SUCCESS(rv, rv);
  1.1374 +
  1.1375 +    if (!exists || !mPlace.titleChanged) {
  1.1376 +      // We have no record of this page, or we have no title change, so there
  1.1377 +      // is no need to do any further work.
  1.1378 +      return NS_OK;
  1.1379 +    }
  1.1380 +
  1.1381 +    NS_ASSERTION(mPlace.placeId > 0,
  1.1382 +                 "We somehow have an invalid place id here!");
  1.1383 +
  1.1384 +    // Now we can update our database record.
  1.1385 +    nsCOMPtr<mozIStorageStatement> stmt =
  1.1386 +      mHistory->GetStatement(
  1.1387 +        "UPDATE moz_places "
  1.1388 +        "SET title = :page_title "
  1.1389 +        "WHERE id = :page_id "
  1.1390 +      );
  1.1391 +    NS_ENSURE_STATE(stmt);
  1.1392 +
  1.1393 +    {
  1.1394 +      mozStorageStatementScoper scoper(stmt);
  1.1395 +      rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("page_id"), mPlace.placeId);
  1.1396 +      NS_ENSURE_SUCCESS(rv, rv);
  1.1397 +      // Empty strings should clear the title, just like
  1.1398 +      // nsNavHistory::SetPageTitle.
  1.1399 +      if (mPlace.title.IsEmpty()) {
  1.1400 +        rv = stmt->BindNullByName(NS_LITERAL_CSTRING("page_title"));
  1.1401 +      }
  1.1402 +      else {
  1.1403 +        rv = stmt->BindStringByName(NS_LITERAL_CSTRING("page_title"),
  1.1404 +                                    StringHead(mPlace.title, TITLE_LENGTH_MAX));
  1.1405 +      }
  1.1406 +      NS_ENSURE_SUCCESS(rv, rv);
  1.1407 +      rv = stmt->Execute();
  1.1408 +      NS_ENSURE_SUCCESS(rv, rv);
  1.1409 +    }
  1.1410 +
  1.1411 +    nsCOMPtr<nsIRunnable> event =
  1.1412 +      new NotifyTitleObservers(mPlace.spec, mPlace.title, mPlace.guid);
  1.1413 +    rv = NS_DispatchToMainThread(event);
  1.1414 +    NS_ENSURE_SUCCESS(rv, rv);
  1.1415 +
  1.1416 +    return NS_OK;
  1.1417 +  }
  1.1418 +
  1.1419 +private:
  1.1420 +  SetPageTitle(const nsCString& aSpec,
  1.1421 +               const nsAString& aTitle)
  1.1422 +  : mHistory(History::GetService())
  1.1423 +  {
  1.1424 +    mPlace.spec = aSpec;
  1.1425 +    mPlace.title = aTitle;
  1.1426 +  }
  1.1427 +
  1.1428 +  VisitData mPlace;
  1.1429 +
  1.1430 +  /**
  1.1431 +   * Strong reference to the History object because we do not want it to
  1.1432 +   * disappear out from under us.
  1.1433 +   */
  1.1434 +  nsRefPtr<History> mHistory;
  1.1435 +};
  1.1436 +
  1.1437 +/**
  1.1438 + * Adds download-specific annotations to a download page.
  1.1439 + */
  1.1440 +class SetDownloadAnnotations MOZ_FINAL : public mozIVisitInfoCallback
  1.1441 +{
  1.1442 +public:
  1.1443 +  NS_DECL_ISUPPORTS
  1.1444 +
  1.1445 +  SetDownloadAnnotations(nsIURI* aDestination)
  1.1446 +  : mDestination(aDestination)
  1.1447 +  , mHistory(History::GetService())
  1.1448 +  {
  1.1449 +    MOZ_ASSERT(mDestination);
  1.1450 +    MOZ_ASSERT(NS_IsMainThread());
  1.1451 +  }
  1.1452 +
  1.1453 +  NS_IMETHOD HandleError(nsresult aResultCode, mozIPlaceInfo *aPlaceInfo)
  1.1454 +  {
  1.1455 +    // Just don't add the annotations in case the visit isn't added.
  1.1456 +    return NS_OK;
  1.1457 +  }
  1.1458 +
  1.1459 +  NS_IMETHOD HandleResult(mozIPlaceInfo *aPlaceInfo)
  1.1460 +  {
  1.1461 +    // Exit silently if the download destination is not a local file.
  1.1462 +    nsCOMPtr<nsIFileURL> destinationFileURL = do_QueryInterface(mDestination);
  1.1463 +    if (!destinationFileURL) {
  1.1464 +      return NS_OK;
  1.1465 +    }
  1.1466 +
  1.1467 +    nsCOMPtr<nsIURI> source;
  1.1468 +    nsresult rv = aPlaceInfo->GetUri(getter_AddRefs(source));
  1.1469 +    NS_ENSURE_SUCCESS(rv, rv);
  1.1470 +
  1.1471 +    nsCOMPtr<nsIFile> destinationFile;
  1.1472 +    rv = destinationFileURL->GetFile(getter_AddRefs(destinationFile));
  1.1473 +    NS_ENSURE_SUCCESS(rv, rv);
  1.1474 +
  1.1475 +    nsAutoString destinationFileName;
  1.1476 +    rv = destinationFile->GetLeafName(destinationFileName);
  1.1477 +    NS_ENSURE_SUCCESS(rv, rv);
  1.1478 +
  1.1479 +    nsAutoCString destinationURISpec;
  1.1480 +    rv = destinationFileURL->GetSpec(destinationURISpec);
  1.1481 +    NS_ENSURE_SUCCESS(rv, rv);
  1.1482 +
  1.1483 +    // Use annotations for storing the additional download metadata.
  1.1484 +    nsAnnotationService* annosvc = nsAnnotationService::GetAnnotationService();
  1.1485 +    NS_ENSURE_TRUE(annosvc, NS_ERROR_OUT_OF_MEMORY);
  1.1486 +
  1.1487 +    rv = annosvc->SetPageAnnotationString(
  1.1488 +      source,
  1.1489 +      DESTINATIONFILEURI_ANNO,
  1.1490 +      NS_ConvertUTF8toUTF16(destinationURISpec),
  1.1491 +      0,
  1.1492 +      nsIAnnotationService::EXPIRE_WITH_HISTORY
  1.1493 +    );
  1.1494 +    NS_ENSURE_SUCCESS(rv, rv);
  1.1495 +
  1.1496 +    rv = annosvc->SetPageAnnotationString(
  1.1497 +      source,
  1.1498 +      DESTINATIONFILENAME_ANNO,
  1.1499 +      destinationFileName,
  1.1500 +      0,
  1.1501 +      nsIAnnotationService::EXPIRE_WITH_HISTORY
  1.1502 +    );
  1.1503 +    NS_ENSURE_SUCCESS(rv, rv);
  1.1504 +
  1.1505 +    nsAutoString title;
  1.1506 +    rv = aPlaceInfo->GetTitle(title);
  1.1507 +    NS_ENSURE_SUCCESS(rv, rv);
  1.1508 +
  1.1509 +    // In case we are downloading a file that does not correspond to a web
  1.1510 +    // page for which the title is present, we populate the otherwise empty
  1.1511 +    // history title with the name of the destination file, to allow it to be
  1.1512 +    // visible and searchable in history results.
  1.1513 +    if (title.IsEmpty()) {
  1.1514 +      rv = mHistory->SetURITitle(source, destinationFileName);
  1.1515 +      NS_ENSURE_SUCCESS(rv, rv);
  1.1516 +    }
  1.1517 +
  1.1518 +    return NS_OK;
  1.1519 +  }
  1.1520 +
  1.1521 +  NS_IMETHOD HandleCompletion()
  1.1522 +  {
  1.1523 +    return NS_OK;
  1.1524 +  }
  1.1525 +
  1.1526 +private:
  1.1527 +  nsCOMPtr<nsIURI> mDestination;
  1.1528 +
  1.1529 +  /**
  1.1530 +   * Strong reference to the History object because we do not want it to
  1.1531 +   * disappear out from under us.
  1.1532 +   */
  1.1533 +  nsRefPtr<History> mHistory;
  1.1534 +};
  1.1535 +NS_IMPL_ISUPPORTS(
  1.1536 +  SetDownloadAnnotations,
  1.1537 +  mozIVisitInfoCallback
  1.1538 +)
  1.1539 +
  1.1540 +/**
  1.1541 + * Enumerator used by NotifyRemoveVisits to transfer the hash entries.
  1.1542 + */
  1.1543 +static PLDHashOperator TransferHashEntries(PlaceHashKey* aEntry,
  1.1544 +                                           void* aHash)
  1.1545 +{
  1.1546 +  nsTHashtable<PlaceHashKey>* hash =
  1.1547 +    static_cast<nsTHashtable<PlaceHashKey> *>(aHash);
  1.1548 +  PlaceHashKey* copy = hash->PutEntry(aEntry->GetKey());
  1.1549 +  copy->visitCount = aEntry->visitCount;
  1.1550 +  copy->bookmarked = aEntry->bookmarked;
  1.1551 +  aEntry->visits.SwapElements(copy->visits);
  1.1552 +  return PL_DHASH_NEXT;
  1.1553 +}
  1.1554 +
  1.1555 +/**
  1.1556 + * Enumerator used by NotifyRemoveVisits to notify removals.
  1.1557 + */
  1.1558 +static PLDHashOperator NotifyVisitRemoval(PlaceHashKey* aEntry,
  1.1559 +                                          void* aHistory)
  1.1560 +{
  1.1561 +  nsNavHistory* history = static_cast<nsNavHistory *>(aHistory);
  1.1562 +  const nsTArray<VisitData>& visits = aEntry->visits;
  1.1563 +  nsCOMPtr<nsIURI> uri;
  1.1564 +  (void)NS_NewURI(getter_AddRefs(uri), visits[0].spec);
  1.1565 +  bool removingPage = visits.Length() == aEntry->visitCount &&
  1.1566 +                      !aEntry->bookmarked;
  1.1567 +  // FindRemovableVisits only sets the transition type on the VisitData objects
  1.1568 +  // it collects if the visits were filtered by transition type.
  1.1569 +  // RemoveVisitsFilter currently only supports filtering by transition type, so
  1.1570 +  // FindRemovableVisits will either find all visits, or all visits of a given
  1.1571 +  // type. Therefore, if transitionType is set on this visit, we pass the
  1.1572 +  // transition type to NotifyOnPageExpired which in turns passes it to
  1.1573 +  // OnDeleteVisits to indicate that all visits of a given type were removed.
  1.1574 +  uint32_t transition = visits[0].transitionType < UINT32_MAX ?
  1.1575 +                          visits[0].transitionType : 0;
  1.1576 +  history->NotifyOnPageExpired(uri, visits[0].visitTime, removingPage,
  1.1577 +                               visits[0].guid,
  1.1578 +                               nsINavHistoryObserver::REASON_DELETED,
  1.1579 +                               transition);
  1.1580 +  return PL_DHASH_NEXT;
  1.1581 +}
  1.1582 +
  1.1583 +/**
  1.1584 + * Notify removed visits to observers.
  1.1585 + */
  1.1586 +class NotifyRemoveVisits : public nsRunnable
  1.1587 +{
  1.1588 +public:
  1.1589 +
  1.1590 +  NotifyRemoveVisits(nsTHashtable<PlaceHashKey>& aPlaces)
  1.1591 +    : mPlaces(VISITS_REMOVAL_INITIAL_HASH_SIZE)
  1.1592 +    , mHistory(History::GetService())
  1.1593 +  {
  1.1594 +    MOZ_ASSERT(!NS_IsMainThread(),
  1.1595 +               "This should not be called on the main thread");
  1.1596 +    aPlaces.EnumerateEntries(TransferHashEntries, &mPlaces);
  1.1597 +  }
  1.1598 +
  1.1599 +  NS_IMETHOD Run()
  1.1600 +  {
  1.1601 +    MOZ_ASSERT(NS_IsMainThread(), "This should be called on the main thread");
  1.1602 +
  1.1603 +    // We are in the main thread, no need to lock.
  1.1604 +    if (mHistory->IsShuttingDown()) {
  1.1605 +      // If we are shutting down, we cannot notify the observers.
  1.1606 +      return NS_OK;
  1.1607 +    }
  1.1608 +
  1.1609 +    nsNavHistory* navHistory = nsNavHistory::GetHistoryService();
  1.1610 +    if (!navHistory) {
  1.1611 +      NS_WARNING("Cannot notify without the history service!");
  1.1612 +      return NS_OK;
  1.1613 +    }
  1.1614 +
  1.1615 +    // Wrap all notifications in a batch, so the view can handle changes in a
  1.1616 +    // more performant way, by initiating a refresh after a limited number of
  1.1617 +    // single changes.
  1.1618 +    (void)navHistory->BeginUpdateBatch();
  1.1619 +    mPlaces.EnumerateEntries(NotifyVisitRemoval, navHistory);
  1.1620 +    (void)navHistory->EndUpdateBatch();
  1.1621 +
  1.1622 +    return NS_OK;
  1.1623 +  }
  1.1624 +
  1.1625 +private:
  1.1626 +  nsTHashtable<PlaceHashKey> mPlaces;
  1.1627 +
  1.1628 +  /**
  1.1629 +   * Strong reference to the History object because we do not want it to
  1.1630 +   * disappear out from under us.
  1.1631 +   */
  1.1632 +  nsRefPtr<History> mHistory;
  1.1633 +};
  1.1634 +
  1.1635 +/**
  1.1636 + * Enumerator used by RemoveVisits to populate list of removed place ids.
  1.1637 + */
  1.1638 +static PLDHashOperator ListToBeRemovedPlaceIds(PlaceHashKey* aEntry,
  1.1639 +                                               void* aIdsList)
  1.1640 +{
  1.1641 +  const nsTArray<VisitData>& visits = aEntry->visits;
  1.1642 +  // Only orphan ids should be listed.
  1.1643 +  if (visits.Length() == aEntry->visitCount && !aEntry->bookmarked) {
  1.1644 +    nsCString* list = static_cast<nsCString*>(aIdsList);
  1.1645 +    if (!list->IsEmpty())
  1.1646 +      list->AppendLiteral(",");
  1.1647 +    list->AppendInt(visits[0].placeId);
  1.1648 +  }
  1.1649 +  return PL_DHASH_NEXT;
  1.1650 +}
  1.1651 +
  1.1652 +/**
  1.1653 + * Remove visits from history.
  1.1654 + */
  1.1655 +class RemoveVisits : public nsRunnable
  1.1656 +{
  1.1657 +public:
  1.1658 +  /**
  1.1659 +   * Asynchronously removes visits from history.
  1.1660 +   *
  1.1661 +   * @param aConnection
  1.1662 +   *        The database connection to use for these operations.
  1.1663 +   * @param aFilter
  1.1664 +   *        Filter to remove visits.
  1.1665 +   */
  1.1666 +  static nsresult Start(mozIStorageConnection* aConnection,
  1.1667 +                        RemoveVisitsFilter& aFilter)
  1.1668 +  {
  1.1669 +    MOZ_ASSERT(NS_IsMainThread(), "This should be called on the main thread");
  1.1670 +
  1.1671 +    nsRefPtr<RemoveVisits> event = new RemoveVisits(aConnection, aFilter);
  1.1672 +
  1.1673 +    // Get the target thread, and then start the work!
  1.1674 +    nsCOMPtr<nsIEventTarget> target = do_GetInterface(aConnection);
  1.1675 +    NS_ENSURE_TRUE(target, NS_ERROR_UNEXPECTED);
  1.1676 +    nsresult rv = target->Dispatch(event, NS_DISPATCH_NORMAL);
  1.1677 +    NS_ENSURE_SUCCESS(rv, rv);
  1.1678 +
  1.1679 +    return NS_OK;
  1.1680 +  }
  1.1681 +
  1.1682 +  NS_IMETHOD Run()
  1.1683 +  {
  1.1684 +    MOZ_ASSERT(!NS_IsMainThread(),
  1.1685 +               "This should not be called on the main thread");
  1.1686 +
  1.1687 +    // Prevent the main thread from shutting down while this is running.
  1.1688 +    MutexAutoLock lockedScope(mHistory->GetShutdownMutex());
  1.1689 +    if (mHistory->IsShuttingDown()) {
  1.1690 +      // If we were already shutting down, we cannot remove the visits.
  1.1691 +      return NS_OK;
  1.1692 +    }
  1.1693 +
  1.1694 +    // Find all the visits relative to the current filters and whether their
  1.1695 +    // pages will be removed or not.
  1.1696 +    nsTHashtable<PlaceHashKey> places(VISITS_REMOVAL_INITIAL_HASH_SIZE);
  1.1697 +    nsresult rv = FindRemovableVisits(places);
  1.1698 +    NS_ENSURE_SUCCESS(rv, rv);
  1.1699 +
  1.1700 +    if (places.Count() == 0)
  1.1701 +      return NS_OK;
  1.1702 +
  1.1703 +    mozStorageTransaction transaction(mDBConn, false,
  1.1704 +                                      mozIStorageConnection::TRANSACTION_IMMEDIATE);
  1.1705 +
  1.1706 +    rv = RemoveVisitsFromDatabase();
  1.1707 +    NS_ENSURE_SUCCESS(rv, rv);
  1.1708 +    rv = RemovePagesFromDatabase(places);
  1.1709 +    NS_ENSURE_SUCCESS(rv, rv);
  1.1710 +
  1.1711 +    rv = transaction.Commit();
  1.1712 +    NS_ENSURE_SUCCESS(rv, rv);
  1.1713 +
  1.1714 +    nsCOMPtr<nsIRunnable> event = new NotifyRemoveVisits(places);
  1.1715 +    rv = NS_DispatchToMainThread(event);
  1.1716 +    NS_ENSURE_SUCCESS(rv, rv);
  1.1717 +
  1.1718 +    return NS_OK;
  1.1719 +  }
  1.1720 +
  1.1721 +private:
  1.1722 +  RemoveVisits(mozIStorageConnection* aConnection,
  1.1723 +               RemoveVisitsFilter& aFilter)
  1.1724 +  : mDBConn(aConnection)
  1.1725 +  , mHasTransitionType(false)
  1.1726 +  , mHistory(History::GetService())
  1.1727 +  {
  1.1728 +    MOZ_ASSERT(NS_IsMainThread(), "This should be called on the main thread");
  1.1729 +
  1.1730 +    // Build query conditions.
  1.1731 +    nsTArray<nsCString> conditions;
  1.1732 +    // TODO: add support for binding params when adding further stuff here.
  1.1733 +    if (aFilter.transitionType < UINT32_MAX) {
  1.1734 +      conditions.AppendElement(nsPrintfCString("visit_type = %d", aFilter.transitionType));
  1.1735 +      mHasTransitionType = true;
  1.1736 +    }
  1.1737 +    if (conditions.Length() > 0) {
  1.1738 +      mWhereClause.AppendLiteral (" WHERE ");
  1.1739 +      for (uint32_t i = 0; i < conditions.Length(); ++i) {
  1.1740 +        if (i > 0)
  1.1741 +          mWhereClause.AppendLiteral(" AND ");
  1.1742 +        mWhereClause.Append(conditions[i]);
  1.1743 +      }
  1.1744 +    }
  1.1745 +  }
  1.1746 +
  1.1747 +  nsresult
  1.1748 +  FindRemovableVisits(nsTHashtable<PlaceHashKey>& aPlaces)
  1.1749 +  {
  1.1750 +    MOZ_ASSERT(!NS_IsMainThread(),
  1.1751 +               "This should not be called on the main thread");
  1.1752 +
  1.1753 +    nsCString query("SELECT h.id, url, guid, visit_date, visit_type, "
  1.1754 +                    "(SELECT count(*) FROM moz_historyvisits WHERE place_id = h.id) as full_visit_count, "
  1.1755 +                    "EXISTS(SELECT 1 FROM moz_bookmarks WHERE fk = h.id) as bookmarked "
  1.1756 +                    "FROM moz_historyvisits "
  1.1757 +                    "JOIN moz_places h ON place_id = h.id");
  1.1758 +    query.Append(mWhereClause);
  1.1759 +
  1.1760 +    nsCOMPtr<mozIStorageStatement> stmt = mHistory->GetStatement(query);
  1.1761 +    NS_ENSURE_STATE(stmt);
  1.1762 +    mozStorageStatementScoper scoper(stmt);
  1.1763 +
  1.1764 +    bool hasResult;
  1.1765 +    nsresult rv;
  1.1766 +    while (NS_SUCCEEDED((rv = stmt->ExecuteStep(&hasResult))) && hasResult) {
  1.1767 +      VisitData visit;
  1.1768 +      rv = stmt->GetInt64(0, &visit.placeId);
  1.1769 +      NS_ENSURE_SUCCESS(rv, rv);
  1.1770 +      rv = stmt->GetUTF8String(1, visit.spec);
  1.1771 +      NS_ENSURE_SUCCESS(rv, rv);
  1.1772 +      rv = stmt->GetUTF8String(2, visit.guid);
  1.1773 +      NS_ENSURE_SUCCESS(rv, rv);
  1.1774 +      rv = stmt->GetInt64(3, &visit.visitTime);
  1.1775 +      NS_ENSURE_SUCCESS(rv, rv);
  1.1776 +      if (mHasTransitionType) {
  1.1777 +        int32_t transition;
  1.1778 +        rv = stmt->GetInt32(4, &transition);
  1.1779 +        NS_ENSURE_SUCCESS(rv, rv);
  1.1780 +        visit.transitionType = static_cast<uint32_t>(transition);
  1.1781 +      }
  1.1782 +      int32_t visitCount, bookmarked;
  1.1783 +      rv = stmt->GetInt32(5, &visitCount);
  1.1784 +      NS_ENSURE_SUCCESS(rv, rv);
  1.1785 +      rv = stmt->GetInt32(6, &bookmarked);
  1.1786 +      NS_ENSURE_SUCCESS(rv, rv);
  1.1787 +
  1.1788 +      PlaceHashKey* entry = aPlaces.GetEntry(visit.spec);
  1.1789 +      if (!entry) {
  1.1790 +        entry = aPlaces.PutEntry(visit.spec);
  1.1791 +      }
  1.1792 +      entry->visitCount = visitCount;
  1.1793 +      entry->bookmarked = bookmarked;
  1.1794 +      entry->visits.AppendElement(visit);
  1.1795 +    }
  1.1796 +    NS_ENSURE_SUCCESS(rv, rv);
  1.1797 +
  1.1798 +    return NS_OK;
  1.1799 +  }
  1.1800 +
  1.1801 +  nsresult
  1.1802 +  RemoveVisitsFromDatabase()
  1.1803 +  {
  1.1804 +    MOZ_ASSERT(!NS_IsMainThread(),
  1.1805 +               "This should not be called on the main thread");
  1.1806 +
  1.1807 +    nsCString query("DELETE FROM moz_historyvisits");
  1.1808 +    query.Append(mWhereClause);
  1.1809 +
  1.1810 +    nsCOMPtr<mozIStorageStatement> stmt = mHistory->GetStatement(query);
  1.1811 +    NS_ENSURE_STATE(stmt);
  1.1812 +    mozStorageStatementScoper scoper(stmt);
  1.1813 +    nsresult rv = stmt->Execute();
  1.1814 +    NS_ENSURE_SUCCESS(rv, rv);
  1.1815 +
  1.1816 +    return NS_OK;
  1.1817 +  }
  1.1818 +
  1.1819 +  nsresult
  1.1820 +  RemovePagesFromDatabase(nsTHashtable<PlaceHashKey>& aPlaces)
  1.1821 +  {
  1.1822 +    MOZ_ASSERT(!NS_IsMainThread(),
  1.1823 +               "This should not be called on the main thread");
  1.1824 +
  1.1825 +    nsCString placeIdsToRemove;
  1.1826 +    aPlaces.EnumerateEntries(ListToBeRemovedPlaceIds, &placeIdsToRemove);
  1.1827 +
  1.1828 +#ifdef DEBUG
  1.1829 +    {
  1.1830 +      // Ensure that we are not removing any problematic entry.
  1.1831 +      nsCString query("SELECT id FROM moz_places h WHERE id IN (");
  1.1832 +      query.Append(placeIdsToRemove);
  1.1833 +      query.AppendLiteral(") AND ("
  1.1834 +          "EXISTS(SELECT 1 FROM moz_bookmarks WHERE fk = h.id) OR "
  1.1835 +          "EXISTS(SELECT 1 FROM moz_historyvisits WHERE place_id = h.id) OR "
  1.1836 +          "SUBSTR(h.url, 1, 6) = 'place:' "
  1.1837 +        ")");
  1.1838 +      nsCOMPtr<mozIStorageStatement> stmt = mHistory->GetStatement(query);
  1.1839 +      NS_ENSURE_STATE(stmt);
  1.1840 +      mozStorageStatementScoper scoper(stmt);
  1.1841 +      bool hasResult;
  1.1842 +      MOZ_ASSERT(NS_SUCCEEDED(stmt->ExecuteStep(&hasResult)) && !hasResult,
  1.1843 +                 "Trying to remove a non-oprhan place from the database");
  1.1844 +    }
  1.1845 +#endif
  1.1846 +
  1.1847 +    nsCString query("DELETE FROM moz_places "
  1.1848 +                    "WHERE id IN (");
  1.1849 +    query.Append(placeIdsToRemove);
  1.1850 +    query.AppendLiteral(")");
  1.1851 +
  1.1852 +    nsCOMPtr<mozIStorageStatement> stmt = mHistory->GetStatement(query);
  1.1853 +    NS_ENSURE_STATE(stmt);
  1.1854 +    mozStorageStatementScoper scoper(stmt);
  1.1855 +    nsresult rv = stmt->Execute();
  1.1856 +    NS_ENSURE_SUCCESS(rv, rv);
  1.1857 +
  1.1858 +    return NS_OK;
  1.1859 +  }
  1.1860 +
  1.1861 +  mozIStorageConnection* mDBConn;
  1.1862 +  bool mHasTransitionType;
  1.1863 +  nsCString mWhereClause;
  1.1864 +
  1.1865 +  /**
  1.1866 +   * Strong reference to the History object because we do not want it to
  1.1867 +   * disappear out from under us.
  1.1868 +   */
  1.1869 +  nsRefPtr<History> mHistory;
  1.1870 +};
  1.1871 +
  1.1872 +/**
  1.1873 + * Stores an embed visit, and notifies observers.
  1.1874 + *
  1.1875 + * @param aPlace
  1.1876 + *        The VisitData of the visit to store as an embed visit.
  1.1877 + * @param [optional] aCallback
  1.1878 + *        The mozIVisitInfoCallback to notify, if provided.
  1.1879 + */
  1.1880 +void
  1.1881 +StoreAndNotifyEmbedVisit(VisitData& aPlace,
  1.1882 +                         mozIVisitInfoCallback* aCallback = nullptr)
  1.1883 +{
  1.1884 +  MOZ_ASSERT(aPlace.transitionType == nsINavHistoryService::TRANSITION_EMBED,
  1.1885 +             "Must only pass TRANSITION_EMBED visits to this!");
  1.1886 +  MOZ_ASSERT(NS_IsMainThread(), "Must be called on the main thread!");
  1.1887 +
  1.1888 +  nsCOMPtr<nsIURI> uri;
  1.1889 +  (void)NS_NewURI(getter_AddRefs(uri), aPlace.spec);
  1.1890 +
  1.1891 +  nsNavHistory* navHistory = nsNavHistory::GetHistoryService();
  1.1892 +  if (!navHistory || !uri) {
  1.1893 +    return;
  1.1894 +  }
  1.1895 +
  1.1896 +  navHistory->registerEmbedVisit(uri, aPlace.visitTime);
  1.1897 +
  1.1898 +  if (aCallback) {
  1.1899 +    // NotifyPlaceInfoCallback does not hold a strong reference to the callback,
  1.1900 +    // so we have to manage it by AddRefing now and then releasing it after the
  1.1901 +    // event has run.
  1.1902 +    NS_ADDREF(aCallback);
  1.1903 +    nsCOMPtr<nsIRunnable> event =
  1.1904 +      new NotifyPlaceInfoCallback(aCallback, aPlace, true, NS_OK);
  1.1905 +    (void)NS_DispatchToMainThread(event);
  1.1906 +
  1.1907 +    // Also dispatch an event to release our reference to the callback after
  1.1908 +    // NotifyPlaceInfoCallback has run.
  1.1909 +    nsCOMPtr<nsIThread> mainThread = do_GetMainThread();
  1.1910 +    (void)NS_ProxyRelease(mainThread, aCallback, true);
  1.1911 +  }
  1.1912 +
  1.1913 +  VisitData noReferrer;
  1.1914 +  nsCOMPtr<nsIRunnable> event = new NotifyVisitObservers(aPlace, noReferrer);
  1.1915 +  (void)NS_DispatchToMainThread(event);
  1.1916 +}
  1.1917 +
  1.1918 +} // anonymous namespace
  1.1919 +
  1.1920 +////////////////////////////////////////////////////////////////////////////////
  1.1921 +//// History
  1.1922 +
  1.1923 +History* History::gService = nullptr;
  1.1924 +
  1.1925 +History::History()
  1.1926 +  : mShuttingDown(false)
  1.1927 +  , mShutdownMutex("History::mShutdownMutex")
  1.1928 +  , mObservers(VISIT_OBSERVERS_INITIAL_CACHE_SIZE)
  1.1929 +  , mRecentlyVisitedURIsNextIndex(0)
  1.1930 +{
  1.1931 +  NS_ASSERTION(!gService, "Ruh-roh!  This service has already been created!");
  1.1932 +  gService = this;
  1.1933 +
  1.1934 +  nsCOMPtr<nsIObserverService> os = services::GetObserverService();
  1.1935 +  NS_WARN_IF_FALSE(os, "Observer service was not found!");
  1.1936 +  if (os) {
  1.1937 +    (void)os->AddObserver(this, TOPIC_PLACES_SHUTDOWN, false);
  1.1938 +  }
  1.1939 +}
  1.1940 +
  1.1941 +History::~History()
  1.1942 +{
  1.1943 +  UnregisterWeakMemoryReporter(this);
  1.1944 +
  1.1945 +  gService = nullptr;
  1.1946 +
  1.1947 +  NS_ASSERTION(mObservers.Count() == 0,
  1.1948 +               "Not all Links were removed before we disappear!");
  1.1949 +}
  1.1950 +
  1.1951 +void
  1.1952 +History::InitMemoryReporter()
  1.1953 +{
  1.1954 +  RegisterWeakMemoryReporter(this);
  1.1955 +}
  1.1956 +
  1.1957 +NS_IMETHODIMP
  1.1958 +History::NotifyVisited(nsIURI* aURI)
  1.1959 +{
  1.1960 +  NS_ENSURE_ARG(aURI);
  1.1961 +
  1.1962 +  nsAutoScriptBlocker scriptBlocker;
  1.1963 +
  1.1964 +  if (XRE_GetProcessType() == GeckoProcessType_Default) {
  1.1965 +    nsTArray<ContentParent*> cplist;
  1.1966 +    ContentParent::GetAll(cplist);
  1.1967 +
  1.1968 +    if (!cplist.IsEmpty()) {
  1.1969 +      URIParams uri;
  1.1970 +      SerializeURI(aURI, uri);
  1.1971 +      for (uint32_t i = 0; i < cplist.Length(); ++i) {
  1.1972 +        unused << cplist[i]->SendNotifyVisited(uri);
  1.1973 +      }
  1.1974 +    }
  1.1975 +  }
  1.1976 +
  1.1977 +  // If we have no observers for this URI, we have nothing to notify about.
  1.1978 +  KeyClass* key = mObservers.GetEntry(aURI);
  1.1979 +  if (!key) {
  1.1980 +    return NS_OK;
  1.1981 +  }
  1.1982 +
  1.1983 +  // Update status of each Link node.
  1.1984 +  {
  1.1985 +    // RemoveEntry will destroy the array, this iterator should not survive it.
  1.1986 +    ObserverArray::ForwardIterator iter(key->array);
  1.1987 +    while (iter.HasMore()) {
  1.1988 +      Link* link = iter.GetNext();
  1.1989 +      link->SetLinkState(eLinkState_Visited);
  1.1990 +      // Verify that the observers hash doesn't mutate while looping through
  1.1991 +      // the links associated with this URI.
  1.1992 +      NS_ABORT_IF_FALSE(key == mObservers.GetEntry(aURI),
  1.1993 +                        "The URIs hash mutated!");
  1.1994 +    }
  1.1995 +  }
  1.1996 +
  1.1997 +  // All the registered nodes can now be removed for this URI.
  1.1998 +  mObservers.RemoveEntry(aURI);
  1.1999 +  return NS_OK;
  1.2000 +}
  1.2001 +
  1.2002 +mozIStorageAsyncStatement*
  1.2003 +History::GetIsVisitedStatement()
  1.2004 +{
  1.2005 +  if (mIsVisitedStatement) {
  1.2006 +    return mIsVisitedStatement;
  1.2007 +  }
  1.2008 +
  1.2009 +  // If we don't yet have a database connection, go ahead and clone it now.
  1.2010 +  if (!mReadOnlyDBConn) {
  1.2011 +    mozIStorageConnection* dbConn = GetDBConn();
  1.2012 +    NS_ENSURE_TRUE(dbConn, nullptr);
  1.2013 +
  1.2014 +    (void)dbConn->Clone(true, getter_AddRefs(mReadOnlyDBConn));
  1.2015 +    NS_ENSURE_TRUE(mReadOnlyDBConn, nullptr);
  1.2016 +  }
  1.2017 +
  1.2018 +  // Now we can create our cached statement.
  1.2019 +  nsresult rv = mReadOnlyDBConn->CreateAsyncStatement(NS_LITERAL_CSTRING(
  1.2020 +    "SELECT 1 "
  1.2021 +    "FROM moz_places h "
  1.2022 +    "WHERE url = ?1 "
  1.2023 +      "AND last_visit_date NOTNULL "
  1.2024 +  ),  getter_AddRefs(mIsVisitedStatement));
  1.2025 +  NS_ENSURE_SUCCESS(rv, nullptr);
  1.2026 +  return mIsVisitedStatement;
  1.2027 +}
  1.2028 +
  1.2029 +nsresult
  1.2030 +History::InsertPlace(const VisitData& aPlace)
  1.2031 +{
  1.2032 +  NS_PRECONDITION(aPlace.placeId == 0, "should not have a valid place id!");
  1.2033 +  NS_PRECONDITION(!NS_IsMainThread(), "must be called off of the main thread!");
  1.2034 +
  1.2035 +  nsCOMPtr<mozIStorageStatement> stmt = GetStatement(
  1.2036 +      "INSERT INTO moz_places "
  1.2037 +        "(url, title, rev_host, hidden, typed, frecency, guid) "
  1.2038 +      "VALUES (:url, :title, :rev_host, :hidden, :typed, :frecency, :guid) "
  1.2039 +    );
  1.2040 +  NS_ENSURE_STATE(stmt);
  1.2041 +  mozStorageStatementScoper scoper(stmt);
  1.2042 +
  1.2043 +  nsresult rv = stmt->BindStringByName(NS_LITERAL_CSTRING("rev_host"),
  1.2044 +                                       aPlace.revHost);
  1.2045 +  NS_ENSURE_SUCCESS(rv, rv);
  1.2046 +  rv = URIBinder::Bind(stmt, NS_LITERAL_CSTRING("url"), aPlace.spec);
  1.2047 +  NS_ENSURE_SUCCESS(rv, rv);
  1.2048 +  nsString title = aPlace.title;
  1.2049 +  // Empty strings should have no title, just like nsNavHistory::SetPageTitle.
  1.2050 +  if (title.IsEmpty()) {
  1.2051 +    rv = stmt->BindNullByName(NS_LITERAL_CSTRING("title"));
  1.2052 +  }
  1.2053 +  else {
  1.2054 +    title.Assign(StringHead(aPlace.title, TITLE_LENGTH_MAX));
  1.2055 +    rv = stmt->BindStringByName(NS_LITERAL_CSTRING("title"), title);
  1.2056 +  }
  1.2057 +  NS_ENSURE_SUCCESS(rv, rv);
  1.2058 +  rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("typed"), aPlace.typed);
  1.2059 +  NS_ENSURE_SUCCESS(rv, rv);
  1.2060 +  // When inserting a page for a first visit that should not appear in
  1.2061 +  // autocomplete, for example an error page, use a zero frecency.
  1.2062 +  int32_t frecency = aPlace.shouldUpdateFrecency ? aPlace.frecency : 0;
  1.2063 +  rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("frecency"), frecency);
  1.2064 +  NS_ENSURE_SUCCESS(rv, rv);
  1.2065 +  rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("hidden"), aPlace.hidden);
  1.2066 +  NS_ENSURE_SUCCESS(rv, rv);
  1.2067 +  nsAutoCString guid(aPlace.guid);
  1.2068 +  if (aPlace.guid.IsVoid()) {
  1.2069 +    rv = GenerateGUID(guid);
  1.2070 +    NS_ENSURE_SUCCESS(rv, rv);
  1.2071 +  }
  1.2072 +  rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("guid"), guid);
  1.2073 +  NS_ENSURE_SUCCESS(rv, rv);
  1.2074 +  rv = stmt->Execute();
  1.2075 +  NS_ENSURE_SUCCESS(rv, rv);
  1.2076 +
  1.2077 +  // Post an onFrecencyChanged observer notification.
  1.2078 +  const nsNavHistory* navHistory = nsNavHistory::GetConstHistoryService();
  1.2079 +  NS_ENSURE_STATE(navHistory);
  1.2080 +  navHistory->DispatchFrecencyChangedNotification(aPlace.spec, frecency, guid,
  1.2081 +                                                  aPlace.hidden,
  1.2082 +                                                  aPlace.visitTime);
  1.2083 +
  1.2084 +  return NS_OK;
  1.2085 +}
  1.2086 +
  1.2087 +nsresult
  1.2088 +History::UpdatePlace(const VisitData& aPlace)
  1.2089 +{
  1.2090 +  NS_PRECONDITION(!NS_IsMainThread(), "must be called off of the main thread!");
  1.2091 +  NS_PRECONDITION(aPlace.placeId > 0, "must have a valid place id!");
  1.2092 +  NS_PRECONDITION(!aPlace.guid.IsVoid(), "must have a guid!");
  1.2093 +
  1.2094 +  nsCOMPtr<mozIStorageStatement> stmt = GetStatement(
  1.2095 +      "UPDATE moz_places "
  1.2096 +      "SET title = :title, "
  1.2097 +          "hidden = :hidden, "
  1.2098 +          "typed = :typed, "
  1.2099 +          "guid = :guid "
  1.2100 +      "WHERE id = :page_id "
  1.2101 +    );
  1.2102 +  NS_ENSURE_STATE(stmt);
  1.2103 +  mozStorageStatementScoper scoper(stmt);
  1.2104 +
  1.2105 +  nsresult rv;
  1.2106 +  // Empty strings should clear the title, just like nsNavHistory::SetPageTitle.
  1.2107 +  if (aPlace.title.IsEmpty()) {
  1.2108 +    rv = stmt->BindNullByName(NS_LITERAL_CSTRING("title"));
  1.2109 +  }
  1.2110 +  else {
  1.2111 +    rv = stmt->BindStringByName(NS_LITERAL_CSTRING("title"),
  1.2112 +                                StringHead(aPlace.title, TITLE_LENGTH_MAX));
  1.2113 +  }
  1.2114 +  NS_ENSURE_SUCCESS(rv, rv);
  1.2115 +  rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("typed"), aPlace.typed);
  1.2116 +  NS_ENSURE_SUCCESS(rv, rv);
  1.2117 +  rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("hidden"), aPlace.hidden);
  1.2118 +  NS_ENSURE_SUCCESS(rv, rv);
  1.2119 +  rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("guid"), aPlace.guid);
  1.2120 +  NS_ENSURE_SUCCESS(rv, rv);
  1.2121 +  rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("page_id"),
  1.2122 +                             aPlace.placeId);
  1.2123 +  NS_ENSURE_SUCCESS(rv, rv);
  1.2124 +  rv = stmt->Execute();
  1.2125 +  NS_ENSURE_SUCCESS(rv, rv);
  1.2126 +
  1.2127 +  return NS_OK;
  1.2128 +}
  1.2129 +
  1.2130 +nsresult
  1.2131 +History::FetchPageInfo(VisitData& _place, bool* _exists)
  1.2132 +{
  1.2133 +  NS_PRECONDITION(!_place.spec.IsEmpty() || !_place.guid.IsEmpty(), "must have either a non-empty spec or guid!");
  1.2134 +  NS_PRECONDITION(!NS_IsMainThread(), "must be called off of the main thread!");
  1.2135 +
  1.2136 +  nsresult rv;
  1.2137 +
  1.2138 +  // URI takes precedence.
  1.2139 +  nsCOMPtr<mozIStorageStatement> stmt;
  1.2140 +  bool selectByURI = !_place.spec.IsEmpty();
  1.2141 +  if (selectByURI) {
  1.2142 +    stmt = GetStatement(
  1.2143 +      "SELECT guid, id, title, hidden, typed, frecency "
  1.2144 +      "FROM moz_places "
  1.2145 +      "WHERE url = :page_url "
  1.2146 +    );
  1.2147 +    NS_ENSURE_STATE(stmt);
  1.2148 +
  1.2149 +    rv = URIBinder::Bind(stmt, NS_LITERAL_CSTRING("page_url"), _place.spec);
  1.2150 +    NS_ENSURE_SUCCESS(rv, rv);
  1.2151 +  }
  1.2152 +  else {
  1.2153 +    stmt = GetStatement(
  1.2154 +      "SELECT url, id, title, hidden, typed, frecency "
  1.2155 +      "FROM moz_places "
  1.2156 +      "WHERE guid = :guid "
  1.2157 +    );
  1.2158 +    NS_ENSURE_STATE(stmt);
  1.2159 +
  1.2160 +    rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("guid"), _place.guid);
  1.2161 +    NS_ENSURE_SUCCESS(rv, rv);
  1.2162 +  }
  1.2163 +
  1.2164 +  mozStorageStatementScoper scoper(stmt);
  1.2165 +
  1.2166 +  rv = stmt->ExecuteStep(_exists);
  1.2167 +  NS_ENSURE_SUCCESS(rv, rv);
  1.2168 +
  1.2169 +  if (!*_exists) {
  1.2170 +    return NS_OK;
  1.2171 +  }
  1.2172 +
  1.2173 +  if (selectByURI) {
  1.2174 +    if (_place.guid.IsEmpty()) {
  1.2175 +      rv = stmt->GetUTF8String(0, _place.guid);
  1.2176 +      NS_ENSURE_SUCCESS(rv, rv);
  1.2177 +    }
  1.2178 +  }
  1.2179 +  else {
  1.2180 +    nsAutoCString spec;
  1.2181 +    rv = stmt->GetUTF8String(0, spec);
  1.2182 +    NS_ENSURE_SUCCESS(rv, rv);
  1.2183 +    _place.spec = spec;
  1.2184 +  }
  1.2185 +
  1.2186 +  rv = stmt->GetInt64(1, &_place.placeId);
  1.2187 +  NS_ENSURE_SUCCESS(rv, rv);
  1.2188 +
  1.2189 +  nsAutoString title;
  1.2190 +  rv = stmt->GetString(2, title);
  1.2191 +  NS_ENSURE_SUCCESS(rv, rv);
  1.2192 +
  1.2193 +  // If the title we were given was void, that means we did not bother to set
  1.2194 +  // it to anything.  As a result, ignore the fact that we may have changed the
  1.2195 +  // title (because we don't want to, that would be empty), and set the title
  1.2196 +  // to what is currently stored in the datbase.
  1.2197 +  if (_place.title.IsVoid()) {
  1.2198 +    _place.title = title;
  1.2199 +  }
  1.2200 +  // Otherwise, just indicate if the title has changed.
  1.2201 +  else {
  1.2202 +    _place.titleChanged = !(_place.title.Equals(title) ||
  1.2203 +                            (_place.title.IsEmpty() && title.IsVoid()));
  1.2204 +  }
  1.2205 +
  1.2206 +  if (_place.hidden) {
  1.2207 +    // If this transition was hidden, it is possible that others were not.
  1.2208 +    // Any one visible transition makes this location visible. If database
  1.2209 +    // has location as visible, reflect that in our data structure.
  1.2210 +    int32_t hidden;
  1.2211 +    rv = stmt->GetInt32(3, &hidden);
  1.2212 +    NS_ENSURE_SUCCESS(rv, rv);
  1.2213 +    _place.hidden = !!hidden;
  1.2214 +  }
  1.2215 +
  1.2216 +  if (!_place.typed) {
  1.2217 +    // If this transition wasn't typed, others might have been. If database
  1.2218 +    // has location as typed, reflect that in our data structure.
  1.2219 +    int32_t typed;
  1.2220 +    rv = stmt->GetInt32(4, &typed);
  1.2221 +    NS_ENSURE_SUCCESS(rv, rv);
  1.2222 +    _place.typed = !!typed;
  1.2223 +  }
  1.2224 +
  1.2225 +  rv = stmt->GetInt32(5, &_place.frecency);
  1.2226 +  NS_ENSURE_SUCCESS(rv, rv);
  1.2227 +  return NS_OK;
  1.2228 +}
  1.2229 +
  1.2230 +/* static */ size_t
  1.2231 +History::SizeOfEntryExcludingThis(KeyClass* aEntry, mozilla::MallocSizeOf aMallocSizeOf, void *)
  1.2232 +{
  1.2233 +  return aEntry->array.SizeOfExcludingThis(aMallocSizeOf);
  1.2234 +}
  1.2235 +
  1.2236 +MOZ_DEFINE_MALLOC_SIZE_OF(HistoryMallocSizeOf)
  1.2237 +
  1.2238 +NS_IMETHODIMP
  1.2239 +History::CollectReports(nsIHandleReportCallback* aHandleReport,
  1.2240 +                        nsISupports* aData)
  1.2241 +{
  1.2242 +  return MOZ_COLLECT_REPORT(
  1.2243 +    "explicit/history-links-hashtable", KIND_HEAP, UNITS_BYTES,
  1.2244 +    SizeOfIncludingThis(HistoryMallocSizeOf),
  1.2245 +    "Memory used by the hashtable that records changes to the visited state "
  1.2246 +    "of links.");
  1.2247 +}
  1.2248 +
  1.2249 +size_t
  1.2250 +History::SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOfThis)
  1.2251 +{
  1.2252 +  return aMallocSizeOfThis(this) +
  1.2253 +         mObservers.SizeOfExcludingThis(SizeOfEntryExcludingThis, aMallocSizeOfThis);
  1.2254 +}
  1.2255 +
  1.2256 +/* static */
  1.2257 +History*
  1.2258 +History::GetService()
  1.2259 +{
  1.2260 +  if (gService) {
  1.2261 +    return gService;
  1.2262 +  }
  1.2263 +
  1.2264 +  nsCOMPtr<IHistory> service(do_GetService(NS_IHISTORY_CONTRACTID));
  1.2265 +  NS_ABORT_IF_FALSE(service, "Cannot obtain IHistory service!");
  1.2266 +  NS_ASSERTION(gService, "Our constructor was not run?!");
  1.2267 +
  1.2268 +  return gService;
  1.2269 +}
  1.2270 +
  1.2271 +/* static */
  1.2272 +History*
  1.2273 +History::GetSingleton()
  1.2274 +{
  1.2275 +  if (!gService) {
  1.2276 +    gService = new History();
  1.2277 +    NS_ENSURE_TRUE(gService, nullptr);
  1.2278 +    gService->InitMemoryReporter();
  1.2279 +  }
  1.2280 +
  1.2281 +  NS_ADDREF(gService);
  1.2282 +  return gService;
  1.2283 +}
  1.2284 +
  1.2285 +mozIStorageConnection*
  1.2286 +History::GetDBConn()
  1.2287 +{
  1.2288 +  if (!mDB) {
  1.2289 +    mDB = Database::GetDatabase();
  1.2290 +    NS_ENSURE_TRUE(mDB, nullptr);
  1.2291 +  }
  1.2292 +  return mDB->MainConn();
  1.2293 +}
  1.2294 +
  1.2295 +void
  1.2296 +History::Shutdown()
  1.2297 +{
  1.2298 +  MOZ_ASSERT(NS_IsMainThread());
  1.2299 +
  1.2300 +  // Prevent other threads from scheduling uses of the DB while we mark
  1.2301 +  // ourselves as shutting down.
  1.2302 +  MutexAutoLock lockedScope(mShutdownMutex);
  1.2303 +  MOZ_ASSERT(!mShuttingDown && "Shutdown was called more than once!");
  1.2304 +
  1.2305 +  mShuttingDown = true;
  1.2306 +
  1.2307 +  if (mReadOnlyDBConn) {
  1.2308 +    if (mIsVisitedStatement) {
  1.2309 +      (void)mIsVisitedStatement->Finalize();
  1.2310 +    }
  1.2311 +    (void)mReadOnlyDBConn->AsyncClose(nullptr);
  1.2312 +  }
  1.2313 +}
  1.2314 +
  1.2315 +void
  1.2316 +History::AppendToRecentlyVisitedURIs(nsIURI* aURI) {
  1.2317 +  if (mRecentlyVisitedURIs.Length() < RECENTLY_VISITED_URI_SIZE) {
  1.2318 +    // Append a new element while the array is not full.
  1.2319 +    mRecentlyVisitedURIs.AppendElement(aURI);
  1.2320 +  } else {
  1.2321 +    // Otherwise, replace the oldest member.
  1.2322 +    mRecentlyVisitedURIsNextIndex %= RECENTLY_VISITED_URI_SIZE;
  1.2323 +    mRecentlyVisitedURIs.ElementAt(mRecentlyVisitedURIsNextIndex) = aURI;
  1.2324 +    mRecentlyVisitedURIsNextIndex++;
  1.2325 +  }
  1.2326 +}
  1.2327 +
  1.2328 +inline bool
  1.2329 +History::IsRecentlyVisitedURI(nsIURI* aURI) {
  1.2330 +  bool equals = false;
  1.2331 +  RecentlyVisitedArray::index_type i;
  1.2332 +  for (i = 0; i < mRecentlyVisitedURIs.Length() && !equals; ++i) {
  1.2333 +    aURI->Equals(mRecentlyVisitedURIs.ElementAt(i), &equals);
  1.2334 +  }
  1.2335 +  return equals;
  1.2336 +}
  1.2337 +
  1.2338 +////////////////////////////////////////////////////////////////////////////////
  1.2339 +//// IHistory
  1.2340 +
  1.2341 +NS_IMETHODIMP
  1.2342 +History::VisitURI(nsIURI* aURI,
  1.2343 +                  nsIURI* aLastVisitedURI,
  1.2344 +                  uint32_t aFlags)
  1.2345 +{
  1.2346 +  NS_PRECONDITION(aURI, "URI should not be NULL.");
  1.2347 +  if (mShuttingDown) {
  1.2348 +    return NS_OK;
  1.2349 +  }
  1.2350 +
  1.2351 +  if (XRE_GetProcessType() == GeckoProcessType_Content) {
  1.2352 +    URIParams uri;
  1.2353 +    SerializeURI(aURI, uri);
  1.2354 +
  1.2355 +    OptionalURIParams lastVisitedURI;
  1.2356 +    SerializeURI(aLastVisitedURI, lastVisitedURI);
  1.2357 +
  1.2358 +    mozilla::dom::ContentChild* cpc =
  1.2359 +      mozilla::dom::ContentChild::GetSingleton();
  1.2360 +    NS_ASSERTION(cpc, "Content Protocol is NULL!");
  1.2361 +    (void)cpc->SendVisitURI(uri, lastVisitedURI, aFlags);
  1.2362 +    return NS_OK;
  1.2363 +  } 
  1.2364 +
  1.2365 +  nsNavHistory* navHistory = nsNavHistory::GetHistoryService();
  1.2366 +  NS_ENSURE_TRUE(navHistory, NS_ERROR_OUT_OF_MEMORY);
  1.2367 +
  1.2368 +  // Silently return if URI is something we shouldn't add to DB.
  1.2369 +  bool canAdd;
  1.2370 +  nsresult rv = navHistory->CanAddURI(aURI, &canAdd);
  1.2371 +  NS_ENSURE_SUCCESS(rv, rv);
  1.2372 +  if (!canAdd) {
  1.2373 +    return NS_OK;
  1.2374 +  }
  1.2375 +
  1.2376 +  if (aLastVisitedURI) {
  1.2377 +    bool same;
  1.2378 +    rv = aURI->Equals(aLastVisitedURI, &same);
  1.2379 +    NS_ENSURE_SUCCESS(rv, rv);
  1.2380 +    if (same && IsRecentlyVisitedURI(aURI)) {
  1.2381 +      // Do not save refresh visits if we have visited this URI recently.
  1.2382 +      return NS_OK;
  1.2383 +    }
  1.2384 +  }
  1.2385 +
  1.2386 +  nsTArray<VisitData> placeArray(1);
  1.2387 +  NS_ENSURE_TRUE(placeArray.AppendElement(VisitData(aURI, aLastVisitedURI)),
  1.2388 +                 NS_ERROR_OUT_OF_MEMORY);
  1.2389 +  VisitData& place = placeArray.ElementAt(0);
  1.2390 +  NS_ENSURE_FALSE(place.spec.IsEmpty(), NS_ERROR_INVALID_ARG);
  1.2391 +
  1.2392 +  place.visitTime = PR_Now();
  1.2393 +
  1.2394 +  // Assigns a type to the edge in the visit linked list. Each type will be
  1.2395 +  // considered differently when weighting the frecency of a location.
  1.2396 +  uint32_t recentFlags = navHistory->GetRecentFlags(aURI);
  1.2397 +  bool isFollowedLink = recentFlags & nsNavHistory::RECENT_ACTIVATED;
  1.2398 +
  1.2399 +  // Embed visits should never be added to the database, and the same is valid
  1.2400 +  // for redirects across frames.
  1.2401 +  // For the above reasoning non-toplevel transitions are handled at first.
  1.2402 +  // if the visit is toplevel or a non-toplevel followed link, then it can be
  1.2403 +  // handled as usual and stored on disk.
  1.2404 +
  1.2405 +  uint32_t transitionType = nsINavHistoryService::TRANSITION_LINK;
  1.2406 +
  1.2407 +  if (!(aFlags & IHistory::TOP_LEVEL) && !isFollowedLink) {
  1.2408 +    // A frame redirected to a new site without user interaction.
  1.2409 +    transitionType = nsINavHistoryService::TRANSITION_EMBED;
  1.2410 +  }
  1.2411 +  else if (aFlags & IHistory::REDIRECT_TEMPORARY) {
  1.2412 +    transitionType = nsINavHistoryService::TRANSITION_REDIRECT_TEMPORARY;
  1.2413 +  }
  1.2414 +  else if (aFlags & IHistory::REDIRECT_PERMANENT) {
  1.2415 +    transitionType = nsINavHistoryService::TRANSITION_REDIRECT_PERMANENT;
  1.2416 +  }
  1.2417 +  else if ((recentFlags & nsNavHistory::RECENT_TYPED) &&
  1.2418 +           !(aFlags & IHistory::UNRECOVERABLE_ERROR)) {
  1.2419 +    // Don't mark error pages as typed, even if they were actually typed by
  1.2420 +    // the user.  This is useful to limit their score in autocomplete.
  1.2421 +    transitionType = nsINavHistoryService::TRANSITION_TYPED;
  1.2422 +  }
  1.2423 +  else if (recentFlags & nsNavHistory::RECENT_BOOKMARKED) {
  1.2424 +    transitionType = nsINavHistoryService::TRANSITION_BOOKMARK;
  1.2425 +  }
  1.2426 +  else if (!(aFlags & IHistory::TOP_LEVEL) && isFollowedLink) {
  1.2427 +    // User activated a link in a frame.
  1.2428 +    transitionType = nsINavHistoryService::TRANSITION_FRAMED_LINK;
  1.2429 +  }
  1.2430 +
  1.2431 +  place.SetTransitionType(transitionType);
  1.2432 +  place.hidden = GetHiddenState(aFlags & IHistory::REDIRECT_SOURCE,
  1.2433 +                                transitionType);
  1.2434 +
  1.2435 +  // Error pages should never be autocompleted.
  1.2436 +  if (aFlags & IHistory::UNRECOVERABLE_ERROR) {
  1.2437 +    place.shouldUpdateFrecency = false;
  1.2438 +  }
  1.2439 +
  1.2440 +  // EMBED visits are session-persistent and should not go through the database.
  1.2441 +  // They exist only to keep track of isVisited status during the session.
  1.2442 +  if (place.transitionType == nsINavHistoryService::TRANSITION_EMBED) {
  1.2443 +    StoreAndNotifyEmbedVisit(place);
  1.2444 +  }
  1.2445 +  else {
  1.2446 +    mozIStorageConnection* dbConn = GetDBConn();
  1.2447 +    NS_ENSURE_STATE(dbConn);
  1.2448 +
  1.2449 +    rv = InsertVisitedURIs::Start(dbConn, placeArray);
  1.2450 +    NS_ENSURE_SUCCESS(rv, rv);
  1.2451 +  }
  1.2452 +
  1.2453 +  // Finally, notify that we've been visited.
  1.2454 +  nsCOMPtr<nsIObserverService> obsService =
  1.2455 +    mozilla::services::GetObserverService();
  1.2456 +  if (obsService) {
  1.2457 +    obsService->NotifyObservers(aURI, NS_LINK_VISITED_EVENT_TOPIC, nullptr);
  1.2458 +  }
  1.2459 +
  1.2460 +  return NS_OK;
  1.2461 +}
  1.2462 +
  1.2463 +NS_IMETHODIMP
  1.2464 +History::RegisterVisitedCallback(nsIURI* aURI,
  1.2465 +                                 Link* aLink)
  1.2466 +{
  1.2467 +  NS_ASSERTION(aURI, "Must pass a non-null URI!");
  1.2468 +  if (XRE_GetProcessType() == GeckoProcessType_Content) {
  1.2469 +    NS_PRECONDITION(aLink, "Must pass a non-null Link!");
  1.2470 +  }
  1.2471 +
  1.2472 +  // Obtain our array of observers for this URI.
  1.2473 +#ifdef DEBUG
  1.2474 +  bool keyAlreadyExists = !!mObservers.GetEntry(aURI);
  1.2475 +#endif
  1.2476 +  KeyClass* key = mObservers.PutEntry(aURI);
  1.2477 +  NS_ENSURE_TRUE(key, NS_ERROR_OUT_OF_MEMORY);
  1.2478 +  ObserverArray& observers = key->array;
  1.2479 +
  1.2480 +  if (observers.IsEmpty()) {
  1.2481 +    NS_ASSERTION(!keyAlreadyExists,
  1.2482 +                 "An empty key was kept around in our hashtable!");
  1.2483 +
  1.2484 +    // We are the first Link node to ask about this URI, or there are no pending
  1.2485 +    // Links wanting to know about this URI.  Therefore, we should query the
  1.2486 +    // database now.
  1.2487 +    nsresult rv = VisitedQuery::Start(aURI);
  1.2488 +
  1.2489 +    // In IPC builds, we are passed a nullptr Link from
  1.2490 +    // ContentParent::RecvStartVisitedQuery.  Since we won't be adding a
  1.2491 +    // nullptr entry to our list of observers, and the code after this point
  1.2492 +    // assumes that aLink is non-nullptr, we will need to return now.
  1.2493 +    if (NS_FAILED(rv) || !aLink) {
  1.2494 +      // Remove our array from the hashtable so we don't keep it around.
  1.2495 +      mObservers.RemoveEntry(aURI);
  1.2496 +      return rv;
  1.2497 +    }
  1.2498 +  }
  1.2499 +  // In IPC builds, we are passed a nullptr Link from
  1.2500 +  // ContentParent::RecvStartVisitedQuery.  All of our code after this point
  1.2501 +  // assumes aLink is non-nullptr, so we have to return now.
  1.2502 +  else if (!aLink) {
  1.2503 +    NS_ASSERTION(XRE_GetProcessType() == GeckoProcessType_Default,
  1.2504 +                 "We should only ever get a null Link in the default process!");
  1.2505 +    return NS_OK;
  1.2506 +  }
  1.2507 +
  1.2508 +  // Sanity check that Links are not registered more than once for a given URI.
  1.2509 +  // This will not catch a case where it is registered for two different URIs.
  1.2510 +  NS_ASSERTION(!observers.Contains(aLink),
  1.2511 +               "Already tracking this Link object!");
  1.2512 +
  1.2513 +  // Start tracking our Link.
  1.2514 +  if (!observers.AppendElement(aLink)) {
  1.2515 +    // Curses - unregister and return failure.
  1.2516 +    (void)UnregisterVisitedCallback(aURI, aLink);
  1.2517 +    return NS_ERROR_OUT_OF_MEMORY;
  1.2518 +  }
  1.2519 +
  1.2520 +  return NS_OK;
  1.2521 +}
  1.2522 +
  1.2523 +NS_IMETHODIMP
  1.2524 +History::UnregisterVisitedCallback(nsIURI* aURI,
  1.2525 +                                   Link* aLink)
  1.2526 +{
  1.2527 +  NS_ASSERTION(aURI, "Must pass a non-null URI!");
  1.2528 +  NS_ASSERTION(aLink, "Must pass a non-null Link object!");
  1.2529 +
  1.2530 +  // Get the array, and remove the item from it.
  1.2531 +  KeyClass* key = mObservers.GetEntry(aURI);
  1.2532 +  if (!key) {
  1.2533 +    NS_ERROR("Trying to unregister for a URI that wasn't registered!");
  1.2534 +    return NS_ERROR_UNEXPECTED;
  1.2535 +  }
  1.2536 +  ObserverArray& observers = key->array;
  1.2537 +  if (!observers.RemoveElement(aLink)) {
  1.2538 +    NS_ERROR("Trying to unregister a node that wasn't registered!");
  1.2539 +    return NS_ERROR_UNEXPECTED;
  1.2540 +  }
  1.2541 +
  1.2542 +  // If the array is now empty, we should remove it from the hashtable.
  1.2543 +  if (observers.IsEmpty()) {
  1.2544 +    mObservers.RemoveEntry(aURI);
  1.2545 +  }
  1.2546 +
  1.2547 +  return NS_OK;
  1.2548 +}
  1.2549 +
  1.2550 +NS_IMETHODIMP
  1.2551 +History::SetURITitle(nsIURI* aURI, const nsAString& aTitle)
  1.2552 +{
  1.2553 +  NS_PRECONDITION(aURI, "Must pass a non-null URI!");
  1.2554 +  if (mShuttingDown) {
  1.2555 +    return NS_OK;
  1.2556 +  }
  1.2557 +
  1.2558 +  if (XRE_GetProcessType() == GeckoProcessType_Content) {
  1.2559 +    URIParams uri;
  1.2560 +    SerializeURI(aURI, uri);
  1.2561 +
  1.2562 +    mozilla::dom::ContentChild * cpc = 
  1.2563 +      mozilla::dom::ContentChild::GetSingleton();
  1.2564 +    NS_ASSERTION(cpc, "Content Protocol is NULL!");
  1.2565 +    (void)cpc->SendSetURITitle(uri, PromiseFlatString(aTitle));
  1.2566 +    return NS_OK;
  1.2567 +  } 
  1.2568 +
  1.2569 +  nsNavHistory* navHistory = nsNavHistory::GetHistoryService();
  1.2570 +
  1.2571 +  // At first, it seems like nav history should always be available here, no
  1.2572 +  // matter what.
  1.2573 +  //
  1.2574 +  // nsNavHistory fails to register as a service if there is no profile in
  1.2575 +  // place (for instance, if user is choosing a profile).
  1.2576 +  //
  1.2577 +  // Maybe the correct thing to do is to not register this service if no
  1.2578 +  // profile has been selected?
  1.2579 +  //
  1.2580 +  NS_ENSURE_TRUE(navHistory, NS_ERROR_FAILURE);
  1.2581 +
  1.2582 +  bool canAdd;
  1.2583 +  nsresult rv = navHistory->CanAddURI(aURI, &canAdd);
  1.2584 +  NS_ENSURE_SUCCESS(rv, rv);
  1.2585 +  if (!canAdd) {
  1.2586 +    return NS_OK;
  1.2587 +  }
  1.2588 +
  1.2589 +  // Embed visits don't have a database entry, thus don't set a title on them.
  1.2590 +  if (navHistory->hasEmbedVisit(aURI)) {
  1.2591 +    return NS_OK;
  1.2592 +  }
  1.2593 +
  1.2594 +  mozIStorageConnection* dbConn = GetDBConn();
  1.2595 +  NS_ENSURE_STATE(dbConn);
  1.2596 +
  1.2597 +  rv = SetPageTitle::Start(dbConn, aURI, aTitle);
  1.2598 +  NS_ENSURE_SUCCESS(rv, rv);
  1.2599 +
  1.2600 +  return NS_OK;
  1.2601 +}
  1.2602 +
  1.2603 +////////////////////////////////////////////////////////////////////////////////
  1.2604 +//// nsIDownloadHistory
  1.2605 +
  1.2606 +NS_IMETHODIMP
  1.2607 +History::AddDownload(nsIURI* aSource, nsIURI* aReferrer,
  1.2608 +                     PRTime aStartTime, nsIURI* aDestination)
  1.2609 +{
  1.2610 +  MOZ_ASSERT(NS_IsMainThread());
  1.2611 +  NS_ENSURE_ARG(aSource);
  1.2612 +
  1.2613 +  if (mShuttingDown) {
  1.2614 +    return NS_OK;
  1.2615 +  }
  1.2616 +
  1.2617 +  if (XRE_GetProcessType() == GeckoProcessType_Content) {
  1.2618 +    NS_ERROR("Cannot add downloads to history from content process!");
  1.2619 +    return NS_ERROR_NOT_AVAILABLE;
  1.2620 +  }
  1.2621 +
  1.2622 +  nsNavHistory* navHistory = nsNavHistory::GetHistoryService();
  1.2623 +  NS_ENSURE_TRUE(navHistory, NS_ERROR_OUT_OF_MEMORY);
  1.2624 +
  1.2625 +  // Silently return if URI is something we shouldn't add to DB.
  1.2626 +  bool canAdd;
  1.2627 +  nsresult rv = navHistory->CanAddURI(aSource, &canAdd);
  1.2628 +  NS_ENSURE_SUCCESS(rv, rv);
  1.2629 +  if (!canAdd) {
  1.2630 +    return NS_OK;
  1.2631 +  }
  1.2632 +
  1.2633 +  nsTArray<VisitData> placeArray(1);
  1.2634 +  NS_ENSURE_TRUE(placeArray.AppendElement(VisitData(aSource, aReferrer)),
  1.2635 +                 NS_ERROR_OUT_OF_MEMORY);
  1.2636 +  VisitData& place = placeArray.ElementAt(0);
  1.2637 +  NS_ENSURE_FALSE(place.spec.IsEmpty(), NS_ERROR_INVALID_ARG);
  1.2638 +
  1.2639 +  place.visitTime = aStartTime;
  1.2640 +  place.SetTransitionType(nsINavHistoryService::TRANSITION_DOWNLOAD);
  1.2641 +  place.hidden = false;
  1.2642 +
  1.2643 +  mozIStorageConnection* dbConn = GetDBConn();
  1.2644 +  NS_ENSURE_STATE(dbConn);
  1.2645 +
  1.2646 +  nsCOMPtr<mozIVisitInfoCallback> callback = aDestination
  1.2647 +                                  ? new SetDownloadAnnotations(aDestination)
  1.2648 +                                  : nullptr;
  1.2649 +
  1.2650 +  rv = InsertVisitedURIs::Start(dbConn, placeArray, callback);
  1.2651 +  NS_ENSURE_SUCCESS(rv, rv);
  1.2652 +
  1.2653 +  // Finally, notify that we've been visited.
  1.2654 +  nsCOMPtr<nsIObserverService> obsService =
  1.2655 +    mozilla::services::GetObserverService();
  1.2656 +  if (obsService) {
  1.2657 +    obsService->NotifyObservers(aSource, NS_LINK_VISITED_EVENT_TOPIC, nullptr);
  1.2658 +  }
  1.2659 +
  1.2660 +  return NS_OK;
  1.2661 +}
  1.2662 +
  1.2663 +NS_IMETHODIMP
  1.2664 +History::RemoveAllDownloads()
  1.2665 +{
  1.2666 +  MOZ_ASSERT(NS_IsMainThread());
  1.2667 +
  1.2668 +  if (mShuttingDown) {
  1.2669 +    return NS_OK;
  1.2670 +  }
  1.2671 +
  1.2672 +  if (XRE_GetProcessType() == GeckoProcessType_Content) {
  1.2673 +    NS_ERROR("Cannot remove downloads to history from content process!");
  1.2674 +    return NS_ERROR_NOT_AVAILABLE;
  1.2675 +  }
  1.2676 +
  1.2677 +  // Ensure navHistory is initialized.
  1.2678 +  nsNavHistory* navHistory = nsNavHistory::GetHistoryService();
  1.2679 +  NS_ENSURE_TRUE(navHistory, NS_ERROR_OUT_OF_MEMORY);
  1.2680 +  mozIStorageConnection* dbConn = GetDBConn();
  1.2681 +  NS_ENSURE_STATE(dbConn);
  1.2682 +
  1.2683 +  RemoveVisitsFilter filter;
  1.2684 +  filter.transitionType = nsINavHistoryService::TRANSITION_DOWNLOAD;
  1.2685 +
  1.2686 +  nsresult rv = RemoveVisits::Start(dbConn, filter);
  1.2687 +  NS_ENSURE_SUCCESS(rv, rv);
  1.2688 +
  1.2689 +  return NS_OK;
  1.2690 +}
  1.2691 +
  1.2692 +////////////////////////////////////////////////////////////////////////////////
  1.2693 +//// mozIAsyncHistory
  1.2694 +
  1.2695 +NS_IMETHODIMP
  1.2696 +History::GetPlacesInfo(JS::Handle<JS::Value> aPlaceIdentifiers,
  1.2697 +                       mozIVisitInfoCallback* aCallback,
  1.2698 +                       JSContext* aCtx) {
  1.2699 +  nsNavHistory* navHistory = nsNavHistory::GetHistoryService();
  1.2700 +  NS_ABORT_IF_FALSE(navHistory, "Could not get nsNavHistory?!");
  1.2701 +
  1.2702 +  uint32_t placesIndentifiersLength;
  1.2703 +  JS::Rooted<JSObject*> placesIndentifiers(aCtx);
  1.2704 +  nsresult rv = GetJSArrayFromJSValue(aPlaceIdentifiers, aCtx,
  1.2705 +                                      &placesIndentifiers,
  1.2706 +                                      &placesIndentifiersLength);
  1.2707 +  NS_ENSURE_SUCCESS(rv, rv);
  1.2708 +
  1.2709 +  nsTArray<VisitData> placesInfo;
  1.2710 +  placesInfo.SetCapacity(placesIndentifiersLength);
  1.2711 +  for (uint32_t i = 0; i < placesIndentifiersLength; i++) {
  1.2712 +    JS::Rooted<JS::Value> placeIdentifier(aCtx);
  1.2713 +    bool rc = JS_GetElement(aCtx, placesIndentifiers, i, &placeIdentifier);
  1.2714 +    NS_ENSURE_TRUE(rc, NS_ERROR_UNEXPECTED);
  1.2715 +
  1.2716 +    // GUID
  1.2717 +    nsAutoString fatGUID;
  1.2718 +    GetJSValueAsString(aCtx, placeIdentifier, fatGUID);
  1.2719 +    if (!fatGUID.IsVoid()) {
  1.2720 +      NS_ConvertUTF16toUTF8 guid(fatGUID);
  1.2721 +      if (!IsValidGUID(guid))
  1.2722 +        return NS_ERROR_INVALID_ARG;
  1.2723 +
  1.2724 +      VisitData& placeInfo = *placesInfo.AppendElement(VisitData());
  1.2725 +      placeInfo.guid = guid;
  1.2726 +    }
  1.2727 +    else {
  1.2728 +      nsCOMPtr<nsIURI> uri = GetJSValueAsURI(aCtx, placeIdentifier);
  1.2729 +      if (!uri)
  1.2730 +        return NS_ERROR_INVALID_ARG; // neither a guid, nor a uri.
  1.2731 +      placesInfo.AppendElement(VisitData(uri));
  1.2732 +    }
  1.2733 +  }
  1.2734 +
  1.2735 +  mozIStorageConnection* dbConn = GetDBConn();
  1.2736 +  NS_ENSURE_STATE(dbConn);
  1.2737 +
  1.2738 +  for (nsTArray<VisitData>::size_type i = 0; i < placesInfo.Length(); i++) {
  1.2739 +    nsresult rv = GetPlaceInfo::Start(dbConn, placesInfo.ElementAt(i), aCallback);
  1.2740 +    NS_ENSURE_SUCCESS(rv, rv);
  1.2741 +  }
  1.2742 +
  1.2743 +  // Be sure to notify that all of our operations are complete.  This
  1.2744 +  // is dispatched to the background thread first and redirected to the
  1.2745 +  // main thread from there to make sure that all database notifications
  1.2746 +  // and all embed or canAddURI notifications have finished.
  1.2747 +  if (aCallback) {
  1.2748 +    // NotifyCompletion does not hold a strong reference to the callback,
  1.2749 +    // so we have to manage it by AddRefing now. NotifyCompletion will
  1.2750 +    // release it for us once it has dispatched the callback to the main
  1.2751 +    // thread.
  1.2752 +    NS_ADDREF(aCallback);
  1.2753 +
  1.2754 +    nsCOMPtr<nsIEventTarget> backgroundThread = do_GetInterface(dbConn);
  1.2755 +    NS_ENSURE_TRUE(backgroundThread, NS_ERROR_UNEXPECTED);
  1.2756 +    nsCOMPtr<nsIRunnable> event = new NotifyCompletion(aCallback);
  1.2757 +    return backgroundThread->Dispatch(event, NS_DISPATCH_NORMAL);
  1.2758 +  }
  1.2759 +
  1.2760 +  return NS_OK;
  1.2761 +}
  1.2762 +
  1.2763 +NS_IMETHODIMP
  1.2764 +History::UpdatePlaces(JS::Handle<JS::Value> aPlaceInfos,
  1.2765 +                      mozIVisitInfoCallback* aCallback,
  1.2766 +                      JSContext* aCtx)
  1.2767 +{
  1.2768 +  NS_ENSURE_TRUE(NS_IsMainThread(), NS_ERROR_UNEXPECTED);
  1.2769 +  NS_ENSURE_TRUE(!JSVAL_IS_PRIMITIVE(aPlaceInfos), NS_ERROR_INVALID_ARG);
  1.2770 +
  1.2771 +  uint32_t infosLength;
  1.2772 +  JS::Rooted<JSObject*> infos(aCtx);
  1.2773 +  nsresult rv = GetJSArrayFromJSValue(aPlaceInfos, aCtx, &infos, &infosLength);
  1.2774 +  NS_ENSURE_SUCCESS(rv, rv);
  1.2775 +
  1.2776 +  nsTArray<VisitData> visitData;
  1.2777 +  for (uint32_t i = 0; i < infosLength; i++) {
  1.2778 +    JS::Rooted<JSObject*> info(aCtx);
  1.2779 +    nsresult rv = GetJSObjectFromArray(aCtx, infos, i, &info);
  1.2780 +    NS_ENSURE_SUCCESS(rv, rv);
  1.2781 +
  1.2782 +    nsCOMPtr<nsIURI> uri = GetURIFromJSObject(aCtx, info, "uri");
  1.2783 +    nsCString guid;
  1.2784 +    {
  1.2785 +      nsString fatGUID;
  1.2786 +      GetStringFromJSObject(aCtx, info, "guid", fatGUID);
  1.2787 +      if (fatGUID.IsVoid()) {
  1.2788 +        guid.SetIsVoid(true);
  1.2789 +      }
  1.2790 +      else {
  1.2791 +        guid = NS_ConvertUTF16toUTF8(fatGUID);
  1.2792 +      }
  1.2793 +    }
  1.2794 +
  1.2795 +    // Make sure that any uri we are given can be added to history, and if not,
  1.2796 +    // skip it (CanAddURI will notify our callback for us).
  1.2797 +    if (uri && !CanAddURI(uri, guid, aCallback)) {
  1.2798 +      continue;
  1.2799 +    }
  1.2800 +
  1.2801 +    // We must have at least one of uri or guid.
  1.2802 +    NS_ENSURE_ARG(uri || !guid.IsVoid());
  1.2803 +
  1.2804 +    // If we were given a guid, make sure it is valid.
  1.2805 +    bool isValidGUID = IsValidGUID(guid);
  1.2806 +    NS_ENSURE_ARG(guid.IsVoid() || isValidGUID);
  1.2807 +
  1.2808 +    nsString title;
  1.2809 +    GetStringFromJSObject(aCtx, info, "title", title);
  1.2810 +
  1.2811 +    JS::Rooted<JSObject*> visits(aCtx, nullptr);
  1.2812 +    {
  1.2813 +      JS::Rooted<JS::Value> visitsVal(aCtx);
  1.2814 +      bool rc = JS_GetProperty(aCtx, info, "visits", &visitsVal);
  1.2815 +      NS_ENSURE_TRUE(rc, NS_ERROR_UNEXPECTED);
  1.2816 +      if (!JSVAL_IS_PRIMITIVE(visitsVal)) {
  1.2817 +        visits = JSVAL_TO_OBJECT(visitsVal);
  1.2818 +        NS_ENSURE_ARG(JS_IsArrayObject(aCtx, visits));
  1.2819 +      }
  1.2820 +    }
  1.2821 +    NS_ENSURE_ARG(visits);
  1.2822 +
  1.2823 +    uint32_t visitsLength = 0;
  1.2824 +    if (visits) {
  1.2825 +      (void)JS_GetArrayLength(aCtx, visits, &visitsLength);
  1.2826 +    }
  1.2827 +    NS_ENSURE_ARG(visitsLength > 0);
  1.2828 +
  1.2829 +    // Check each visit, and build our array of VisitData objects.
  1.2830 +    visitData.SetCapacity(visitData.Length() + visitsLength);
  1.2831 +    for (uint32_t j = 0; j < visitsLength; j++) {
  1.2832 +      JS::Rooted<JSObject*> visit(aCtx);
  1.2833 +      rv = GetJSObjectFromArray(aCtx, visits, j, &visit);
  1.2834 +      NS_ENSURE_SUCCESS(rv, rv);
  1.2835 +
  1.2836 +      VisitData& data = *visitData.AppendElement(VisitData(uri));
  1.2837 +      data.title = title;
  1.2838 +      data.guid = guid;
  1.2839 +
  1.2840 +      // We must have a date and a transaction type!
  1.2841 +      rv = GetIntFromJSObject(aCtx, visit, "visitDate", &data.visitTime);
  1.2842 +      NS_ENSURE_SUCCESS(rv, rv);
  1.2843 +      uint32_t transitionType = 0;
  1.2844 +      rv = GetIntFromJSObject(aCtx, visit, "transitionType", &transitionType);
  1.2845 +      NS_ENSURE_SUCCESS(rv, rv);
  1.2846 +      NS_ENSURE_ARG_RANGE(transitionType,
  1.2847 +                          nsINavHistoryService::TRANSITION_LINK,
  1.2848 +                          nsINavHistoryService::TRANSITION_FRAMED_LINK);
  1.2849 +      data.SetTransitionType(transitionType);
  1.2850 +      data.hidden = GetHiddenState(false, transitionType);
  1.2851 +
  1.2852 +      // If the visit is an embed visit, we do not actually add it to the
  1.2853 +      // database.
  1.2854 +      if (transitionType == nsINavHistoryService::TRANSITION_EMBED) {
  1.2855 +        StoreAndNotifyEmbedVisit(data, aCallback);
  1.2856 +        visitData.RemoveElementAt(visitData.Length() - 1);
  1.2857 +        continue;
  1.2858 +      }
  1.2859 +
  1.2860 +      // The referrer is optional.
  1.2861 +      nsCOMPtr<nsIURI> referrer = GetURIFromJSObject(aCtx, visit,
  1.2862 +                                                     "referrerURI");
  1.2863 +      if (referrer) {
  1.2864 +        (void)referrer->GetSpec(data.referrerSpec);
  1.2865 +      }
  1.2866 +    }
  1.2867 +  }
  1.2868 +
  1.2869 +  mozIStorageConnection* dbConn = GetDBConn();
  1.2870 +  NS_ENSURE_STATE(dbConn);
  1.2871 +
  1.2872 +  // It is possible that all of the visits we were passed were dissallowed by
  1.2873 +  // CanAddURI, which isn't an error.  If we have no visits to add, however,
  1.2874 +  // we should not call InsertVisitedURIs::Start.
  1.2875 +  if (visitData.Length()) {
  1.2876 +    nsresult rv = InsertVisitedURIs::Start(dbConn, visitData, aCallback);
  1.2877 +    NS_ENSURE_SUCCESS(rv, rv);
  1.2878 +  }
  1.2879 +
  1.2880 +  // Be sure to notify that all of our operations are complete.  This
  1.2881 +  // is dispatched to the background thread first and redirected to the
  1.2882 +  // main thread from there to make sure that all database notifications
  1.2883 +  // and all embed or canAddURI notifications have finished.
  1.2884 +  if (aCallback) {
  1.2885 +    // NotifyCompletion does not hold a strong reference to the callback,
  1.2886 +    // so we have to manage it by AddRefing now. NotifyCompletion will
  1.2887 +    // release it for us once it has dispatched the callback to the main
  1.2888 +    // thread.
  1.2889 +    NS_ADDREF(aCallback);
  1.2890 +
  1.2891 +    nsCOMPtr<nsIEventTarget> backgroundThread = do_GetInterface(dbConn);
  1.2892 +    NS_ENSURE_TRUE(backgroundThread, NS_ERROR_UNEXPECTED);
  1.2893 +    nsCOMPtr<nsIRunnable> event = new NotifyCompletion(aCallback);
  1.2894 +    return backgroundThread->Dispatch(event, NS_DISPATCH_NORMAL);
  1.2895 +  }
  1.2896 +
  1.2897 +  return NS_OK;
  1.2898 +}
  1.2899 +
  1.2900 +NS_IMETHODIMP
  1.2901 +History::IsURIVisited(nsIURI* aURI,
  1.2902 +                      mozIVisitedStatusCallback* aCallback)
  1.2903 +{
  1.2904 +  NS_ENSURE_STATE(NS_IsMainThread());
  1.2905 +  NS_ENSURE_ARG(aURI);
  1.2906 +  NS_ENSURE_ARG(aCallback);
  1.2907 +
  1.2908 +  nsresult rv = VisitedQuery::Start(aURI, aCallback);
  1.2909 +  NS_ENSURE_SUCCESS(rv, rv);
  1.2910 +
  1.2911 +  return NS_OK;
  1.2912 +}
  1.2913 +
  1.2914 +////////////////////////////////////////////////////////////////////////////////
  1.2915 +//// nsIObserver
  1.2916 +
  1.2917 +NS_IMETHODIMP
  1.2918 +History::Observe(nsISupports* aSubject, const char* aTopic,
  1.2919 +                 const char16_t* aData)
  1.2920 +{
  1.2921 +  if (strcmp(aTopic, TOPIC_PLACES_SHUTDOWN) == 0) {
  1.2922 +    Shutdown();
  1.2923 +
  1.2924 +    nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
  1.2925 +    if (os) {
  1.2926 +      (void)os->RemoveObserver(this, TOPIC_PLACES_SHUTDOWN);
  1.2927 +    }
  1.2928 +  }
  1.2929 +
  1.2930 +  return NS_OK;
  1.2931 +}
  1.2932 +
  1.2933 +////////////////////////////////////////////////////////////////////////////////
  1.2934 +//// nsISupports
  1.2935 +
  1.2936 +NS_IMPL_ISUPPORTS(
  1.2937 +  History
  1.2938 +, IHistory
  1.2939 +, nsIDownloadHistory
  1.2940 +, mozIAsyncHistory
  1.2941 +, nsIObserver
  1.2942 +, nsIMemoryReporter
  1.2943 +)
  1.2944 +
  1.2945 +} // namespace places
  1.2946 +} // namespace mozilla

mercurial