Quellcodebibliothek Statistik Leitseite products/Sources/formale Sprachen/C/Firefox/toolkit/components/places/   (Browser von der Mozilla Stiftung Version 136.0.1©)  Datei vom 10.2.2025 mit Größe 82 kB image not shown  

Quelle  History.cpp   Sprache: C

 
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */


#include "mozilla/Attributes.h"
#include "mozilla/DebugOnly.h"
#include "mozilla/MemoryReporting.h"

#include "mozilla/dom/ContentChild.h"
#include "mozilla/dom/ContentParent.h"
#include "mozilla/dom/BrowserChild.h"
#include "nsXULAppAPI.h"

#include "History.h"
#include "nsNavHistory.h"
#include "nsNavBookmarks.h"
#include "Helpers.h"
#include "PlaceInfo.h"
#include "VisitInfo.h"
#include "nsPlacesMacros.h"
#include "NotifyRankingChanged.h"

#include "mozilla/storage.h"
#include "mozIStorageResultSet.h"
#include "mozIStorageRow.h"
#include "mozilla/dom/Link.h"
#include "nsDocShellCID.h"
#include "mozilla/Components.h"
#include "nsAppDirectoryServiceDefs.h"
#include "nsThreadUtils.h"
#include "nsNetUtil.h"
#include "nsIWidget.h"
#include "nsIXPConnect.h"
#include "nsIXULRuntime.h"
#include "mozilla/Unused.h"
#include "nsContentUtils.h"  // for nsAutoScriptBlocker
#include "nsJSUtils.h"
#include "nsStandardURL.h"
#include "mozilla/ipc/URIUtils.h"
#include "nsPrintfCString.h"
#include "nsTHashtable.h"
#include "jsapi.h"
#include "js/Array.h"  // JS::GetArrayLength, JS::IsArrayObject, JS::NewArrayObject
#include "js/PropertyAndElement.h"  // JS_DefineElement, JS_GetElement, JS_GetProperty
#include "mozilla/StaticPrefs_layout.h"
#include "mozilla/StaticPrefs_places.h"
#include "mozilla/dom/ContentProcessMessageManager.h"
#include "mozilla/dom/Element.h"
#include "mozilla/dom/PlacesObservers.h"
#include "mozilla/dom/PlacesVisit.h"
#include "mozilla/dom/PlacesVisitTitle.h"
#include "mozilla/dom/ScriptSettings.h"
#include "mozilla/glean/PlacesMetrics.h"

#include "nsIBrowserWindowTracker.h"
#include "nsImportModule.h"
#include "mozilla/StaticPrefs_browser.h"

using namespace mozilla::dom;
using namespace mozilla::ipc;

namespace mozilla::places {

////////////////////////////////////////////////////////////////////////////////
//// Global Defines

// Observer event fired after a visit has been registered in the DB.
#define URI_VISIT_SAVED "uri-visit-saved"

#define DESTINATIONFILEURI_ANNO "downloads/destinationFileURI"_ns

////////////////////////////////////////////////////////////////////////////////
//// VisitData

struct VisitData {
  VisitData()
      : placeId(0),
        visitId(0),
        hidden(true),
        typed(false),
        transitionType(UINT32_MAX),
        visitTime(0),
        frecency(-1),
        lastVisitId(0),
        lastVisitTime(0),
        visitCount(0),
        referrerVisitId(0),
        titleChanged(false),
        isUnrecoverableError(false),
        useFrecencyRedirectBonus(false),
        source(nsINavHistoryService::VISIT_SOURCE_ORGANIC),
        triggeringPlaceId(0),
        triggeringSponsoredURLVisitTimeMS(0),
        bookmarked(false) {
    guid.SetIsVoid(true);
    title.SetIsVoid(true);
    baseDomain.SetIsVoid(true);
    triggeringSearchEngine.SetIsVoid(true);
    triggeringSponsoredURL.SetIsVoid(true);
    triggeringSponsoredURLBaseDomain.SetIsVoid(true);
  }

  explicit VisitData(nsIURI* aURI, nsIURI* aReferrer = nullptr)
      : placeId(0),
        visitId(0),
        hidden(true),
        typed(false),
        transitionType(UINT32_MAX),
        visitTime(0),
        frecency(-1),
        lastVisitId(0),
        lastVisitTime(0),
        visitCount(0),
        referrerVisitId(0),
        titleChanged(false),
        isUnrecoverableError(false),
        useFrecencyRedirectBonus(false),
        source(nsINavHistoryService::VISIT_SOURCE_ORGANIC),
        triggeringPlaceId(0),
        triggeringSponsoredURLVisitTimeMS(0),
        bookmarked(false) {
    MOZ_ASSERT(aURI);
    if (aURI) {
      (void)aURI->GetSpec(spec);
      (void)GetReversedHostname(aURI, revHost);
    }
    if (aReferrer) {
      (void)aReferrer->GetSpec(referrerSpec);
    }
    guid.SetIsVoid(true);
    title.SetIsVoid(true);
    baseDomain.SetIsVoid(true);
    triggeringSearchEngine.SetIsVoid(true);
    triggeringSponsoredURL.SetIsVoid(true);
    triggeringSponsoredURLBaseDomain.SetIsVoid(true);
  }

  /**
   * Sets the transition type of the visit, as well as if it was typed.
   *
   * @param aTransitionType
   *        The transition type constant to set.  Must be one of the
   *        TRANSITION_ constants on nsINavHistoryService.
   */

  void SetTransitionType(uint32_t aTransitionType) {
    typed = aTransitionType == nsINavHistoryService::TRANSITION_TYPED;
    transitionType = aTransitionType;
  }

  int64_t placeId;
  nsCString guid;
  int64_t visitId;
  nsCString spec;
  nsCString baseDomain;
  nsString revHost;
  bool hidden;
  bool typed;
  uint32_t transitionType;
  PRTime visitTime;
  int32_t frecency;
  int64_t lastVisitId;
  PRTime lastVisitTime;
  uint32_t visitCount;

  /**
   * Stores the title.  If this is empty (IsEmpty() returns true), then the
   * title should be removed from the Place.  If the title is void (IsVoid()
   * returns true), then no title has been set on this object, and titleChanged
   * should remain false.
   */

  nsString title;

  nsCString referrerSpec;
  int64_t referrerVisitId;

  // TODO bug 626836 hook up hidden and typed change tracking too!
  bool titleChanged;

  // Indicates whether the visit ended up in an unrecoverable error.
  bool isUnrecoverableError;

  // Whether to override the visit type bonus with a redirect bonus when
  // calculating frecency on the most recent visit.
  bool useFrecencyRedirectBonus;

  uint16_t source;
  nsCString triggeringSearchEngine;
  int64_t triggeringPlaceId;
  nsCString triggeringSponsoredURL;
  nsCString triggeringSponsoredURLBaseDomain;
  int64_t triggeringSponsoredURLVisitTimeMS;
  bool bookmarked;
};

////////////////////////////////////////////////////////////////////////////////
//// Anonymous Helpers

namespace {

/**
 * Convert the given js value to a js array.
 *
 * @param [in] aValue
 *        the JS value to convert.
 * @param [in] aCtx
 *        The JSContext for aValue.
 * @param [out] _array
 *        the JS array.
 * @param [out] _arrayLength
 *        _array's length.
 */

nsresult GetJSArrayFromJSValue(JS::Handle<JS::Value> aValue, JSContext* aCtx,
                               JS::MutableHandle<JSObject*> _array,
                               uint32_t* _arrayLength) {
  if (aValue.isObjectOrNull()) {
    JS::Rooted<JSObject*> val(aCtx, aValue.toObjectOrNull());
    bool isArray;
    if (!JS::IsArrayObject(aCtx, val, &isArray)) {
      return NS_ERROR_UNEXPECTED;
    }
    if (isArray) {
      _array.set(val);
      (void)JS::GetArrayLength(aCtx, _array, _arrayLength);
      NS_ENSURE_ARG(*_arrayLength > 0);
      return NS_OK;
    }
  }

  // Build a temporary array to store this one item so the code below can
  // just loop.
  *_arrayLength = 1;
  _array.set(JS::NewArrayObject(aCtx, 0));
  NS_ENSURE_TRUE(_array, NS_ERROR_OUT_OF_MEMORY);

  bool rc = JS_DefineElement(aCtx, _array, 0, aValue, 0);
  NS_ENSURE_TRUE(rc, NS_ERROR_UNEXPECTED);
  return NS_OK;
}

/**
 * Attemps to convert a given js value to a nsIURI object.
 * @param aCtx
 *        The JSContext for aValue.
 * @param aValue
 *        The JS value to convert.
 * @return the nsIURI object, or null if aValue is not a nsIURI object.
 */

already_AddRefed<nsIURI> GetJSValueAsURI(JSContext* aCtx,
                                         const JS::Value& aValue) {
  if (!aValue.isPrimitive()) {
    nsCOMPtr<nsIXPConnect> xpc = nsIXPConnect::XPConnect();

    nsCOMPtr<nsIXPConnectWrappedNative> wrappedObj;
    JS::Rooted<JSObject*> obj(aCtx, aValue.toObjectOrNull());
    nsresult rv =
        xpc->GetWrappedNativeOfJSObject(aCtx, obj, getter_AddRefs(wrappedObj));
    NS_ENSURE_SUCCESS(rv, nullptr);
    nsCOMPtr<nsIURI> uri = do_QueryInterface(wrappedObj->Native());
    return uri.forget();
  }
  return nullptr;
}

/**
 * Obtains an nsIURI from the "uri" property of a JSObject.
 *
 * @param aCtx
 *        The JSContext for aObject.
 * @param aObject
 *        The JSObject to get the URI from.
 * @param aProperty
 *        The name of the property to get the URI from.
 * @return the URI if it exists.
 */

already_AddRefed<nsIURI> GetURIFromJSObject(JSContext* aCtx,
                                            JS::Handle<JSObject*> aObject,
                                            const char* aProperty) {
  JS::Rooted<JS::Value> uriVal(aCtx);
  bool rc = JS_GetProperty(aCtx, aObject, aProperty, &uriVal);
  NS_ENSURE_TRUE(rc, nullptr);
  return GetJSValueAsURI(aCtx, uriVal);
}

/**
 * Attemps to convert a JS value to a string.
 * @param aCtx
 *        The JSContext for aObject.
 * @param aValue
 *        The JS value to convert.
 * @param _string
 *        The string to populate with the value, or set it to void.
 */

void GetJSValueAsString(JSContext* aCtx, const JS::Value& aValue,
                        nsString& _string) {
  if (aValue.isUndefined() || !(aValue.isNull() || aValue.isString())) {
    _string.SetIsVoid(true);
    return;
  }

  // |null| in JS maps to the empty string.
  if (aValue.isNull()) {
    _string.Truncate();
    return;
  }

  if (!AssignJSString(aCtx, _string, aValue.toString())) {
    _string.SetIsVoid(true);
  }
}

/**
 * Obtains the specified property of a JSObject.
 *
 * @param aCtx
 *        The JSContext for aObject.
 * @param aObject
 *        The JSObject to get the string from.
 * @param aProperty
 *        The property to get the value from.
 * @param _string
 *        The string to populate with the value, or set it to void.
 */

void GetStringFromJSObject(JSContext* aCtx, JS::Handle<JSObject*> aObject,
                           const char* aProperty, nsString& _string) {
  JS::Rooted<JS::Value> val(aCtx);
  bool rc = JS_GetProperty(aCtx, aObject, aProperty, &val);
  if (!rc) {
    _string.SetIsVoid(true);
    return;
  }
  GetJSValueAsString(aCtx, val, _string);
}

/**
 * Obtains the specified property of a JSObject.
 *
 * @param aCtx
 *        The JSContext for aObject.
 * @param aObject
 *        The JSObject to get the int from.
 * @param aProperty
 *        The property to get the value from.
 * @param _int
 *        The integer to populate with the value on success.
 */

template <typename IntType>
nsresult GetIntFromJSObject(JSContext* aCtx, JS::Handle<JSObject*> aObject,
                            const char* aProperty, IntType* _int) {
  JS::Rooted<JS::Value> value(aCtx);
  bool rc = JS_GetProperty(aCtx, aObject, aProperty, &value);
  NS_ENSURE_TRUE(rc, NS_ERROR_UNEXPECTED);
  if (value.isUndefined()) {
    return NS_ERROR_INVALID_ARG;
  }
  NS_ENSURE_ARG(value.isPrimitive());
  NS_ENSURE_ARG(value.isNumber());

  double num;
  rc = JS::ToNumber(aCtx, value, &num);
  NS_ENSURE_TRUE(rc, NS_ERROR_UNEXPECTED);
  NS_ENSURE_ARG(IntType(num) == num);

  *_int = IntType(num);
  return NS_OK;
}

/**
 * Obtains the specified property of a JSObject.
 *
 * @pre aArray must be an Array object.
 *
 * @param aCtx
 *        The JSContext for aArray.
 * @param aArray
 *        The JSObject to get the object from.
 * @param aIndex
 *        The index to get the object from.
 * @param objOut
 *        Set to the JSObject pointer on success.
 */

nsresult GetJSObjectFromArray(JSContext* aCtx, JS::Handle<JSObject*> aArray,
                              uint32_t aIndex,
                              JS::MutableHandle<JSObject*> objOut) {
  JS::Rooted<JS::Value> value(aCtx);
  bool rc = JS_GetElement(aCtx, aArray, aIndex, &value);
  NS_ENSURE_TRUE(rc, NS_ERROR_UNEXPECTED);
  NS_ENSURE_ARG(!value.isPrimitive());
  objOut.set(&value.toObject());
  return NS_OK;
}

}  // namespace

class VisitedQuery final : public AsyncStatementCallback {
 public:
  NS_DECL_ISUPPORTS_INHERITED

  struct VisitedUrlInfo {
    VisitedUrlInfo(nsCOMPtr<nsIURI>&& aURI,
                   History::ContentParentSet&& aContentParentSet)
        : mURI(std::move(aURI)),
          mContentParentSet(std::move(aContentParentSet)) {}
    nsCOMPtr<nsIURI> mURI;
    bool mIsVisited{};
    History::ContentParentSet mContentParentSet;
  };

  using PendingVisitedQueries =
      nsTHashMap<nsURIHashKey, History::ContentParentSet>;

  using VisitedUrlsToContentParentSet =
      nsTHashMap<nsCStringHashKey, VisitedUrlInfo>;

  static nsresult Start(PendingVisitedQueries&& aURIsToContentParentSet) {
    MOZ_ASSERT(XRE_IsParentProcess());
    MOZ_ASSERT(NS_IsMainThread());

    History* history = History::GetService();
    NS_ENSURE_STATE(history);

    VisitedUrlsToContentParentSet urls(aURIsToContentParentSet.Count());
    for (auto& origEntry : aURIsToContentParentSet) {
      auto& data = *origEntry.GetModifiableData();
      nsCOMPtr<nsIURI> URI = origEntry.GetKey();
      nsAutoCString spec;
      if (NS_SUCCEEDED(URI->GetSpec(spec))) {
        VisitedUrlInfo info = VisitedUrlInfo(std::move(URI), std::move(data));
        urls.InsertOrUpdate(spec, std::move(info));
      }
    }
    RefPtr<VisitedQuery> query = new VisitedQuery(std::move(urls));
    return history->QueueVisitedStatement(std::move(query));
  }

  static nsresult Start(nsIURI* aURI,
                        mozIVisitedStatusCallback* aCallback = nullptr) {
    MOZ_ASSERT(aURI, "Null URI");
    MOZ_ASSERT(XRE_IsParentProcess());
    MOZ_ASSERT(NS_IsMainThread());

    nsMainThreadPtrHandle<mozIVisitedStatusCallback> callback(
        new nsMainThreadPtrHolder<mozIVisitedStatusCallback>(
            "mozIVisitedStatusCallback", aCallback));

    History* history = History::GetService();
    NS_ENSURE_STATE(history);
    VisitedUrlsToContentParentSet urls;
    nsAutoCString spec;
    if (NS_SUCCEEDED(aURI->GetSpec(spec))) {
      nsCOMPtr<nsIURI> uri = aURI;
      VisitedUrlInfo info =
          VisitedUrlInfo(std::move(uri), History::ContentParentSet());
      urls.InsertOrUpdate(spec, std::move(info));
    }
    RefPtr<VisitedQuery> query = new VisitedQuery(std::move(urls), callback);
    return history->QueueVisitedStatement(std::move(query));
  }

  void Execute(mozIStorageAsyncStatement& aStatement) {
    nsTArray<nsCString> urls =
        ToTArray<nsTArray<nsCString>>(mUrlsToContentParentSet.Keys());
    MOZ_ALWAYS_SUCCEEDS(aStatement.BindArrayOfUTF8StringsByIndex(0, urls));
    nsCOMPtr<mozIStoragePendingStatement> handle;
    MOZ_ALWAYS_SUCCEEDS(aStatement.ExecuteAsync(this, getter_AddRefs(handle)));
  }

  NS_IMETHOD HandleResult(mozIStorageResultSet* aResultSet) override {
    nsCOMPtr<mozIStorageRow> row;
    while (NS_SUCCEEDED(aResultSet->GetNextRow(getter_AddRefs(row))) && row) {
      nsAutoCString spec;
      nsresult rv = row->GetUTF8String(0, spec);
      if (NS_WARN_IF(NS_FAILED(rv))) {
        // Ignore this error and continue updating others.
        continue;
      }
      if (auto entry = mUrlsToContentParentSet.Lookup(spec)) {
        entry.Data().mIsVisited = !!row->AsInt64(1);
      }
    }
    return NS_OK;
  }

  NS_IMETHOD
  HandleError(mozIStorageError* aError) override {
    // We'll assume unvisited, that is the default value.
    return NS_OK;
  }

  NS_IMETHOD HandleCompletion(uint16_t aReason) override {
    if (aReason == mozIStorageStatementCallback::REASON_FINISHED) {
      NotifyVisitedStatus();
    }
    return NS_OK;
  }

  void NotifyVisitedStatus() {
    // If an external handling callback is provided, just notify through it.
    if (mCallback) {
      MOZ_DIAGNOSTIC_ASSERT(
          mUrlsToContentParentSet.Count() == 1,
          "Should only have 1 URI when a callback is provided");
      const auto iter = mUrlsToContentParentSet.ConstIter();
      if (!iter.Done()) {
        const VisitedUrlInfo& info = iter.Data();
        mCallback->IsVisited(info.mURI, info.mIsVisited);
      }
      return;
    }

    if (History* history = History::GetService()) {
      for (const auto& entry : mUrlsToContentParentSet) {
        const VisitedUrlInfo& info = entry.GetData();
        auto status = info.mIsVisited ? IHistory::VisitedStatus::Visited
                                      : IHistory::VisitedStatus::Unvisited;
        history->NotifyVisited(info.mURI, status, &info.mContentParentSet);
      }
    }
  }

 private:
  explicit VisitedQuery(VisitedUrlsToContentParentSet&& aUrlsToContentParentSet,
                        const nsMainThreadPtrHandle<mozIVisitedStatusCallback>&
                            aCallback = nullptr)
      : mUrlsToContentParentSet(std::move(aUrlsToContentParentSet)),
        mCallback(aCallback) {}

  ~VisitedQuery() = default;

  VisitedUrlsToContentParentSet mUrlsToContentParentSet;
  nsMainThreadPtrHandle<mozIVisitedStatusCallback> mCallback;
};

NS_IMPL_ISUPPORTS_INHERITED0(VisitedQuery, AsyncStatementCallback)

/**
 * Notifies observers about a visit or an array of visits.
 */

class NotifyManyVisitsObservers : public Runnable {
 public:
  explicit NotifyManyVisitsObservers(const VisitData& aPlace)
      : Runnable("places::NotifyManyVisitsObservers"),
        mPlaces({aPlace}),
        mHistory(History::GetService()) {}

  explicit NotifyManyVisitsObservers(nsTArray<VisitData>&& aPlaces)
      : Runnable("places::NotifyManyVisitsObservers"),
        mPlaces(std::move(aPlaces)),
        mHistory(History::GetService()) {}

  nsresult NotifyVisit(nsNavHistory* aNavHistory,
                       nsCOMPtr<nsIObserverService>& aObsService, PRTime aNow,
                       nsIURI* aURI, const VisitData& aPlace) {
    if (aObsService) {
      DebugOnly<nsresult> rv =
          aObsService->NotifyObservers(aURI, URI_VISIT_SAVED, nullptr);
      NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "Could not notify observers");
    }

    if (aNow - aPlace.visitTime < RECENTLY_VISITED_URIS_MAX_AGE) {
      mHistory->AppendToRecentlyVisitedURIs(aURI, aPlace.hidden);
    }
    mHistory->NotifyVisited(aURI, IHistory::VisitedStatus::Visited);

    aNavHistory->UpdateDaysOfHistory(aPlace.visitTime);

    return NS_OK;
  }

  void AddPlaceForNotify(const VisitData& aPlace,
                         Sequence<OwningNonNull<PlacesEvent>>& aEvents) {
    if (aPlace.transitionType == nsINavHistoryService::TRANSITION_EMBED) {
      return;
    }

    RefPtr<PlacesVisit> visitEvent = new PlacesVisit();
    visitEvent->mVisitId = aPlace.visitId;
    visitEvent->mUrl.Assign(NS_ConvertUTF8toUTF16(aPlace.spec));
    visitEvent->mVisitTime = aPlace.visitTime / 1000;
    visitEvent->mReferringVisitId = aPlace.referrerVisitId;
    visitEvent->mTransitionType = aPlace.transitionType;
    visitEvent->mPageGuid.Assign(aPlace.guid);
    visitEvent->mFrecency = aPlace.frecency;
    visitEvent->mHidden = aPlace.hidden;
    visitEvent->mVisitCount = aPlace.visitCount + 1;  // Add current visit
    visitEvent->mTypedCount = static_cast<uint32_t>(aPlace.typed);
    visitEvent->mLastKnownTitle.Assign(aPlace.title);

    bool success = !!aEvents.AppendElement(visitEvent.forget(), fallible);
    MOZ_RELEASE_ASSERT(success);

    if (aPlace.titleChanged) {
      RefPtr<PlacesVisitTitle> titleEvent = new PlacesVisitTitle();
      titleEvent->mUrl.Assign(NS_ConvertUTF8toUTF16(aPlace.spec));
      titleEvent->mPageGuid.Assign(aPlace.guid);
      titleEvent->mTitle.Assign(aPlace.title);
      bool success = !!aEvents.AppendElement(titleEvent.forget(), fallible);
      MOZ_RELEASE_ASSERT(success);
    }
  }

  // MOZ_CAN_RUN_SCRIPT_BOUNDARY until Runnable::Run is marked
  // MOZ_CAN_RUN_SCRIPT.  See bug 1535398.
  MOZ_CAN_RUN_SCRIPT_BOUNDARY
  NS_IMETHOD Run() override {
    MOZ_ASSERT(NS_IsMainThread(), "This should be called on the main thread");

    // We are in the main thread, no need to lock.
    if (mHistory->IsShuttingDown()) {
      // If we are shutting down, we cannot notify the observers.
      return NS_OK;
    }

    nsNavHistory* navHistory = nsNavHistory::GetHistoryService();
    if (!navHistory) {
      NS_WARNING(
          "Trying to notify visits observers but cannot get the history "
          "service!");
      return NS_OK;
    }

    nsCOMPtr<nsIObserverService> obsService =
        mozilla::services::GetObserverService();

    Sequence<OwningNonNull<PlacesEvent>> events;
    PRTime now = PR_Now();
    for (uint32_t i = 0; i < mPlaces.Length(); ++i) {
      nsCOMPtr<nsIURI> uri;
      if (NS_WARN_IF(
              NS_FAILED(NS_NewURI(getter_AddRefs(uri), mPlaces[i].spec)))) {
        return NS_ERROR_UNEXPECTED;
      }
      AddPlaceForNotify(mPlaces[i], events);

      nsresult rv = NotifyVisit(navHistory, obsService, now, uri, mPlaces[i]);
      NS_ENSURE_SUCCESS(rv, rv);
    }

    if (events.Length() > 0) {
      PlacesObservers::NotifyListeners(events);
    }

    return NS_OK;
  }

 private:
  AutoTArray<VisitData, 1> mPlaces;
  RefPtr<History> mHistory;
};

/**
 * Notifies observers about a pages title changing.
 */

class NotifyTitleObservers : public Runnable {
 public:
  /**
   * Notifies observers on the main thread.
   *
   * @param aSpec
   *        The spec of the URI to notify about.
   * @param aTitle
   *        The new title to notify about.
   */

  NotifyTitleObservers(const nsCString& aSpec, const nsString& aTitle,
                       const nsCString& aGUID)
      : Runnable("places::NotifyTitleObservers"),
        mSpec(aSpec),
        mTitle(aTitle),
        mGUID(aGUID) {}

  // MOZ_CAN_RUN_SCRIPT_BOUNDARY until Runnable::Run is marked
  // MOZ_CAN_RUN_SCRIPT.  See bug 1535398.
  MOZ_CAN_RUN_SCRIPT_BOUNDARY
  NS_IMETHOD Run() override {
    MOZ_ASSERT(NS_IsMainThread(), "This should be called on the main thread");

    RefPtr<PlacesVisitTitle> titleEvent = new PlacesVisitTitle();
    titleEvent->mUrl.Assign(NS_ConvertUTF8toUTF16(mSpec));
    titleEvent->mPageGuid.Assign(mGUID);
    titleEvent->mTitle.Assign(mTitle);

    Sequence<OwningNonNull<PlacesEvent>> events;
    bool success = !!events.AppendElement(titleEvent.forget(), fallible);
    MOZ_RELEASE_ASSERT(success);

    PlacesObservers::NotifyListeners(events);

    return NS_OK;
  }

 private:
  const nsCString mSpec;
  const nsString mTitle;
  const nsCString mGUID;
};

/**
 * Helper class for methods which notify their callers through the
 * mozIVisitInfoCallback interface.
 */

class NotifyPlaceInfoCallback : public Runnable {
 public:
  NotifyPlaceInfoCallback(
      const nsMainThreadPtrHandle<mozIVisitInfoCallback>& aCallback,
      const VisitData& aPlace, bool aIsSingleVisit, nsresult aResult)
      : Runnable("places::NotifyPlaceInfoCallback"),
        mCallback(aCallback),
        mPlace(aPlace),
        mResult(aResult),
        mIsSingleVisit(aIsSingleVisit) {
    MOZ_ASSERT(aCallback, "Must pass a non-null callback!");
  }

  NS_IMETHOD Run() override {
    MOZ_ASSERT(NS_IsMainThread(), "This should be called on the main thread");

    bool hasValidURIs = true;
    nsCOMPtr<nsIURI> referrerURI;
    if (!mPlace.referrerSpec.IsEmpty()) {
      hasValidURIs = !NS_WARN_IF(NS_FAILED(
          NS_NewURI(getter_AddRefs(referrerURI), mPlace.referrerSpec)));
    }

    nsCOMPtr<nsIURI> uri;
    hasValidURIs =
        hasValidURIs &&
        !NS_WARN_IF(NS_FAILED(NS_NewURI(getter_AddRefs(uri), mPlace.spec)));

    nsCOMPtr<mozIPlaceInfo> place;
    if (mIsSingleVisit) {
      nsCOMPtr<mozIVisitInfo> visit =
          new VisitInfo(mPlace.visitId, mPlace.visitTime, mPlace.transitionType,
                        referrerURI.forget());
      PlaceInfo::VisitsArray visits;
      (void)visits.AppendElement(visit);

      // The frecency isn't exposed because it may not reflect the updated value
      // in the case of InsertVisitedURIs.
      place = new PlaceInfo(mPlace.placeId, mPlace.guid, uri.forget(),
                            mPlace.title, -1, visits);
    } else {
      // Same as above.
      place = new PlaceInfo(mPlace.placeId, mPlace.guid, uri.forget(),
                            mPlace.title, -1);
    }

    if (NS_SUCCEEDED(mResult) && hasValidURIs) {
      (void)mCallback->HandleResult(place);
    } else {
      (void)mCallback->HandleError(mResult, place);
    }

    return NS_OK;
  }

 private:
  nsMainThreadPtrHandle<mozIVisitInfoCallback> mCallback;
  VisitData mPlace;
  const nsresult mResult;
  bool mIsSingleVisit;
};

/**
 * Notifies a callback object when the operation is complete.
 */

class NotifyCompletion : public Runnable {
 public:
  explicit NotifyCompletion(
      const nsMainThreadPtrHandle<mozIVisitInfoCallback>& aCallback,
      uint32_t aUpdatedCount = 0)
      : Runnable("places::NotifyCompletion"),
        mCallback(aCallback),
        mUpdatedCount(aUpdatedCount) {
    MOZ_ASSERT(aCallback, "Must pass a non-null callback!");
  }

  NS_IMETHOD Run() override {
    if (NS_IsMainThread()) {
      (void)mCallback->HandleCompletion(mUpdatedCount);
    } else {
      (void)NS_DispatchToMainThread(this);
    }
    return NS_OK;
  }

 private:
  nsMainThreadPtrHandle<mozIVisitInfoCallback> mCallback;
  uint32_t mUpdatedCount;
};

/**
 * Checks to see if we can add aURI to history, and dispatches an error to
 * aCallback (if provided) if we cannot.
 *
 * @param aURI
 *        The URI to check.
 * @param [optional] aGUID
 *        The guid of the URI to check.  This is passed back to the callback.
 * @param [optional] aCallback
 *        The callback to notify if the URI cannot be added to history.
 * @return true if the URI can be added to history, false otherwise.
 */

bool CanAddURI(nsIURI* aURI, const nsCString& aGUID = ""_ns,
               mozIVisitInfoCallback* aCallback = nullptr) {
  MOZ_ASSERT(NS_IsMainThread());
  nsNavHistory* navHistory = nsNavHistory::GetHistoryService();
  NS_ENSURE_TRUE(navHistory, false);

  bool canAdd;
  nsresult rv = navHistory->CanAddURI(aURI, &canAdd);
  if (NS_SUCCEEDED(rv) && canAdd) {
    return true;
  };

  // We cannot add the URI.  Notify the callback, if we were given one.
  if (aCallback) {
    VisitData place(aURI);
    place.guid = aGUID;
    nsMainThreadPtrHandle<mozIVisitInfoCallback> callback(
        new nsMainThreadPtrHolder<mozIVisitInfoCallback>(
            "mozIVisitInfoCallback", aCallback));
    nsCOMPtr<nsIRunnable> event = new NotifyPlaceInfoCallback(
        callback, place, true, NS_ERROR_INVALID_ARG);
    (void)NS_DispatchToMainThread(event);
  }

  return false;
}

/**
 * Adds a visit to the database.
 */

class InsertVisitedURIs final : public Runnable {
 public:
  /**
   * Adds a visit to the database asynchronously.
   *
   * @param aConnection
   *        The database connection to use for these operations.
   * @param aPlaces
   *        The locations to record visits.
   * @param [optional] aCallback
   *        The callback to notify about the visit.
   */

  static nsresult Start(mozIStorageConnection* aConnection,
                        nsTArray<VisitData>&& aPlaces,
                        mozIVisitInfoCallback* aCallback = nullptr,
                        uint32_t aInitialUpdatedCount = 0) {
    MOZ_ASSERT(NS_IsMainThread(), "This should be called on the main thread");
    MOZ_ASSERT(aPlaces.Length() > 0, "Must pass a non-empty array!");

    // Make sure nsNavHistory service is up before proceeding:
    nsNavHistory* navHistory = nsNavHistory::GetHistoryService();
    MOZ_ASSERT(navHistory, "Could not get nsNavHistory?!");
    if (!navHistory) {
      return NS_ERROR_FAILURE;
    }

    nsMainThreadPtrHandle<mozIVisitInfoCallback> callback(
        new nsMainThreadPtrHolder<mozIVisitInfoCallback>(
            "mozIVisitInfoCallback", aCallback));
    bool ignoreErrors = false, ignoreResults = false;
    if (aCallback) {
      // We ignore errors from either of these methods in case old JS consumers
      // don't implement them (in which case they will get error/result
      // notifications as normal).
      Unused << aCallback->GetIgnoreErrors(&ignoreErrors);
      Unused << aCallback->GetIgnoreResults(&ignoreResults);
    }
    RefPtr<InsertVisitedURIs> event = new InsertVisitedURIs(
        aConnection, std::move(aPlaces), callback, ignoreErrors, ignoreResults,
        aInitialUpdatedCount);

    // Get the target thread, and then start the work!
    nsCOMPtr<nsIEventTarget> target = do_GetInterface(aConnection);
    NS_ENSURE_TRUE(target, NS_ERROR_UNEXPECTED);
    nsresult rv = target->Dispatch(event, NS_DISPATCH_NORMAL);
    NS_ENSURE_SUCCESS(rv, rv);

    return NS_OK;
  }

  NS_IMETHOD Run() override {
    MOZ_ASSERT(!NS_IsMainThread(),
               "This should not be called on the main thread");

    // The inner run method may bail out at any point, so we ensure we do
    // whatever we can and then notify the main thread we're done.
    nsresult rv = InnerRun();

    if (!!mCallback) {
      NS_DispatchToMainThread(
          new NotifyCompletion(mCallback, mSuccessfulUpdatedCount));
    }
    return rv;
  }

  nsresult InnerRun() {
    MOZ_ASSERT(!NS_IsMainThread());
    // Prevent Shutdown() from proceeding while this is running.
    MutexAutoLock lockedScope(mHistory->mBlockShutdownMutex);
    // Check if we were already shutting down.
    if (mHistory->IsShuttingDown()) {
      return NS_OK;
    }

    mozStorageTransaction transaction(
        mDBConn, false, mozIStorageConnection::TRANSACTION_IMMEDIATE);

    // XXX Handle the error, bug 1696133.
    Unused << NS_WARN_IF(NS_FAILED(transaction.Start()));

    const VisitData* lastFetchedPlace = nullptr;
    uint32_t lastFetchedVisitCount = 0;
    bool shouldChunkNotifications = mPlaces.Length() > NOTIFY_VISITS_CHUNK_SIZE;
    nsTArray<VisitData> notificationChunk;
    if (shouldChunkNotifications) {
      notificationChunk.SetCapacity(NOTIFY_VISITS_CHUNK_SIZE);
    }

    // This is an optimization for frecency updating, if all the entries point
    // to the same URL (inserting multiple visits for the same url), then we can
    // update frecency once at the end of the loop. Otherwise, if there's
    // multiple pages, we'll delay frecency recalculation to a later time.
    bool shouldUpdateFrecency = false;

    for (nsTArray<VisitData>::size_type i = 0; i < mPlaces.Length(); i++) {
      VisitData& place = mPlaces.ElementAt(i);

      if (i == 0) {
        // isUnrecoverableError can only be defined when this is invoked by
        // VisitURI, to insert a single visit. When it's defined, the page
        // will be hidden, thus it's not worth updating.
        shouldUpdateFrecency = !place.isUnrecoverableError;
      } else if (shouldUpdateFrecency &&
                 (!place.spec.Equals(mPlaces.ElementAt(i - 1).spec))) {
        // We have multiple entries with different URLs, delay recalculation.
        // A SQL trigger will set recalc_frecency automatically when a visit
        // is added.
        shouldUpdateFrecency = false;
      }
      // Fetching from the database can overwrite this information, so save it
      // apart.
      bool typed = place.typed;
      bool hidden = place.hidden;

      // We can avoid a database lookup if it's the same place as the last
      // visit we added.
      bool known =
          lastFetchedPlace && lastFetchedPlace->spec.Equals(place.spec);
      if (!known) {
        nsresult rv = mHistory->FetchPageInfo(place, &known);
        if (NS_FAILED(rv)) {
          if (!!mCallback && !mIgnoreErrors) {
            nsCOMPtr<nsIRunnable> event =
                new NotifyPlaceInfoCallback(mCallback, place, true, rv);
            return NS_DispatchToMainThread(event);
          }
          return NS_OK;
        }
        lastFetchedPlace = &mPlaces.ElementAt(i);
        lastFetchedVisitCount = lastFetchedPlace->visitCount;
      } else {
        // Copy over the data from the already known place.
        place.placeId = lastFetchedPlace->placeId;
        place.guid = lastFetchedPlace->guid;
        place.lastVisitId = lastFetchedPlace->visitId;
        place.lastVisitTime = lastFetchedPlace->visitTime;
        if (!place.title.IsVoid()) {
          place.titleChanged = !lastFetchedPlace->title.Equals(place.title);
        }
        place.frecency = lastFetchedPlace->frecency;
        // Add one visit for the previous loop.
        place.visitCount = ++lastFetchedVisitCount;
      }

      // If any transition is typed, ensure the page is marked as typed.
      if (typed != lastFetchedPlace->typed) {
        place.typed = true;
      }

      // If any transition is visible, ensure the page is marked as visible.
      if (hidden != lastFetchedPlace->hidden) {
        place.hidden = false;
      }

      FetchReferrerInfo(place);
      UpdateVisitSource(place, mHistory);

      nsresult rv = DoDatabaseInserts(known, place);
      if (!!mCallback) {
        // Check if consumers wanted to be notified about success/failure,
        // depending on whether this action succeeded or not.
        if ((NS_SUCCEEDED(rv) && !mIgnoreResults) ||
            (NS_FAILED(rv) && !mIgnoreErrors)) {
          nsCOMPtr<nsIRunnable> event =
              new NotifyPlaceInfoCallback(mCallback, place, true, rv);
          nsresult rv2 = NS_DispatchToMainThread(event);
          NS_ENSURE_SUCCESS(rv2, rv2);
        }
      }
      NS_ENSURE_SUCCESS(rv, rv);

      if (shouldChunkNotifications) {
        int32_t numRemaining = (int32_t)(mPlaces.Length() - (i + 1));
        notificationChunk.AppendElement(place);
        if (notificationChunk.Length() == NOTIFY_VISITS_CHUNK_SIZE ||
            numRemaining == 0) {
          nsCOMPtr<nsIRunnable> event =
              new NotifyManyVisitsObservers(std::move(notificationChunk));
          rv = NS_DispatchToMainThread(event);
          NS_ENSURE_SUCCESS(rv, rv);

          int32_t nextCapacity =
              std::min(NOTIFY_VISITS_CHUNK_SIZE, numRemaining);
          notificationChunk.SetCapacity(nextCapacity);
        }
      }

      // If we get here, we must have been successful adding/updating this
      // visit/place, so update the count:
      mSuccessfulUpdatedCount++;
    }

    if (shouldUpdateFrecency) {
      VisitData& place = mPlaces.ElementAt(0);
      if (NS_SUCCEEDED(UpdateFrecency(
              place.placeId,
              place.useFrecencyRedirectBonus && mPlaces.Length() == 1))) {
        // Notifying a new visit should be sufficient to know that frecency
        // changed, but since historically we notified a frecency change, for
        // now we'll continue doing it, and re-evaluate in the future.
        NS_DispatchToMainThread(new NotifyRankingChanged());
      }
    }

    nsresult rv = transaction.Commit();
    NS_ENSURE_SUCCESS(rv, rv);

    // If we don't need to chunk the notifications, just notify using the
    // original mPlaces array.
    if (!shouldChunkNotifications) {
      nsCOMPtr<nsIRunnable> event =
          new NotifyManyVisitsObservers(std::move(mPlaces));
      rv = NS_DispatchToMainThread(event);
      NS_ENSURE_SUCCESS(rv, rv);
    }

    return NS_OK;
  }

 private:
  InsertVisitedURIs(
      mozIStorageConnection* aConnection, nsTArray<VisitData>&& aPlaces,
      const nsMainThreadPtrHandle<mozIVisitInfoCallback>& aCallback,
      bool aIgnoreErrors, bool aIgnoreResults, uint32_t aInitialUpdatedCount)
      : Runnable("places::InsertVisitedURIs"),
        mDBConn(aConnection),
        mPlaces(std::move(aPlaces)),
        mCallback(aCallback),
        mIgnoreErrors(aIgnoreErrors),
        mIgnoreResults(aIgnoreResults),
        mSuccessfulUpdatedCount(aInitialUpdatedCount),
        mHistory(History::GetService()) {
    MOZ_ASSERT(NS_IsMainThread(), "This should be called on the main thread");

#ifdef DEBUG
    for (nsTArray<VisitData>::size_type i = 0; i < mPlaces.Length(); i++) {
      nsCOMPtr<nsIURI> uri;
      MOZ_ALWAYS_SUCCEEDS((NS_NewURI(getter_AddRefs(uri), mPlaces[i].spec)));
      MOZ_ASSERT(CanAddURI(uri),
                 "Passed a VisitData with a URI we cannot add to history!");
    }
#endif
  }

  /**
   * Inserts or updates the entry in moz_places for this visit, adds the visit,
   * and updates the frecency of the place.
   *
   * @param {boolean} aKnown
   *        True if we already have an entry for this place in moz_places, false
   *        otherwise.
   * @param {VisitData} aPlace
   *        The place we are adding a visit for.
   */

  nsresult DoDatabaseInserts(bool aKnown, VisitData& aPlace) {
    MOZ_ASSERT(!NS_IsMainThread(),
               "This should not be called on the main thread");

    // If the page was in moz_places, we need to update the entry.
    nsresult rv;
    if (aKnown) {
      rv = mHistory->UpdatePlace(aPlace);
      NS_ENSURE_SUCCESS(rv, rv);
    }
    // Otherwise, the page was not in moz_places, so now we have to add it.
    else {
      rv = mHistory->InsertPlace(aPlace);
      NS_ENSURE_SUCCESS(rv, rv);
      aPlace.placeId = nsNavHistory::sLastInsertedPlaceId;
    }
    MOZ_ASSERT(aPlace.placeId > 0);

    rv = AddVisit(aPlace);
    NS_ENSURE_SUCCESS(rv, rv);

    // Adding a visit sets the recalculation flags through a trigger, but for
    // error pages we don't want that, because recalculation considers this a
    // normal successful visit. In the future we should store the error state
    // along with the visit, so that recalculation can do a better job and
    // we can remove this workaround update. See Bug 1842008.
    if (aPlace.isUnrecoverableError) {
      nsCOMPtr<mozIStorageStatement> stmt = mHistory->GetStatement(
          "UPDATE moz_places "
          "SET recalc_frecency = 0, recalc_alt_frecency = 0 "
          "WHERE id = :page_id");
      NS_ENSURE_STATE(stmt);
      mozStorageStatementScoper scoper(stmt);
      rv = stmt->BindInt64ByName("page_id"_ns, aPlace.placeId);
      NS_ENSURE_SUCCESS(rv, rv);
      rv = stmt->Execute();
      NS_ENSURE_SUCCESS(rv, rv);
    }

    return NS_OK;
  }

  /**
   * Fetches information about a referrer for aPlace if it was a recent
   * visit or not.
   *
   * @param aPlace
   *        The VisitData for the visit we will eventually add.
   *
   */

  void FetchReferrerInfo(VisitData& aPlace) {
    if (aPlace.referrerSpec.IsEmpty()) {
      return;
    }

    VisitData referrer;
    referrer.spec = aPlace.referrerSpec;
    // If the referrer is the same as the page, we don't need to fetch it.
    if (aPlace.referrerSpec.Equals(aPlace.spec)) {
      referrer = aPlace;
      // The page last visit id is also the referrer visit id.
      aPlace.referrerVisitId = aPlace.lastVisitId;
    } else {
      bool exists = false;
      if (NS_SUCCEEDED(mHistory->FetchPageInfo(referrer, &exists)) && exists) {
        // Copy the referrer last visit id.
        aPlace.referrerVisitId = referrer.lastVisitId;
      }
    }

    // Check if the page has effectively been visited recently, otherwise
    // discard the referrer info.
    if (!aPlace.referrerVisitId || !referrer.lastVisitTime ||
        aPlace.visitTime - referrer.lastVisitTime > RECENT_EVENT_THRESHOLD) {
      // We will not be using the referrer data.
      aPlace.referrerSpec.Truncate();
      aPlace.referrerVisitId = 0;
    }
  }

  /**
   * Adds a visit for _place and updates it with the right visit id.
   *
   * @param _place
   *        The VisitData for the place we need to know visit information about.
   */

  nsresult AddVisit(VisitData& _place) {
    MOZ_ASSERT(_place.placeId > 0);

    nsresult rv;
    nsCOMPtr<mozIStorageStatement> stmt;
    stmt = mHistory->GetStatement(
        "INSERT INTO moz_historyvisits "
        "(from_visit, place_id, visit_date, visit_type, session, source, "
        "triggeringPlaceId) "
        "VALUES (:from_visit, :page_id, :visit_date, :visit_type, 0, :source, "
        ":triggeringPlaceId) ");
    NS_ENSURE_STATE(stmt);
    mozStorageStatementScoper scoper(stmt);

    rv = stmt->BindInt64ByName("page_id"_ns, _place.placeId);
    NS_ENSURE_SUCCESS(rv, rv);
    rv = stmt->BindInt64ByName("from_visit"_ns, _place.referrerVisitId);
    NS_ENSURE_SUCCESS(rv, rv);
    rv = stmt->BindInt64ByName("visit_date"_ns, _place.visitTime);
    NS_ENSURE_SUCCESS(rv, rv);
    uint32_t transitionType = _place.transitionType;
    MOZ_ASSERT(transitionType >= nsINavHistoryService::TRANSITION_LINK &&
                   transitionType <= nsINavHistoryService::TRANSITION_RELOAD,
               "Invalid transition type!");
    rv = stmt->BindInt32ByName("visit_type"_ns, (int32_t)transitionType);
    NS_ENSURE_SUCCESS(rv, rv);
    rv = stmt->BindInt32ByName("source"_ns, _place.source);
    NS_ENSURE_SUCCESS(rv, rv);
    if (_place.triggeringPlaceId != 0) {
      rv = stmt->BindInt64ByName("triggeringPlaceId"_ns,
                                 _place.triggeringPlaceId);
    } else {
      rv = stmt->BindNullByName("triggeringPlaceId"_ns);
    }
    NS_ENSURE_SUCCESS(rv, rv);

    rv = stmt->Execute();
    NS_ENSURE_SUCCESS(rv, rv);

    _place.visitId = nsNavHistory::sLastInsertedVisitId;
    MOZ_ASSERT(_place.visitId > 0);

    return NS_OK;
  }

  /**
   * Updates the frecency, and possibly the hidden-ness of aPlace.
   *
   * @param aPlace
   *        The VisitData for the place we want to update.
   */

  nsresult UpdateFrecency(const int64_t aPlaceId, bool aIsRedirect) {
    nsresult rv;
    {  // First, set our frecency to the proper value.
      nsCOMPtr<mozIStorageStatement> stmt = mHistory->GetStatement(
          "UPDATE moz_places "
          "SET frecency = CALCULATE_FRECENCY(:page_id, :redirect) "
          "WHERE id = :page_id");
      NS_ENSURE_STATE(stmt);
      mozStorageStatementScoper scoper(stmt);

      rv = stmt->BindInt64ByName("page_id"_ns, aPlaceId);
      NS_ENSURE_SUCCESS(rv, rv);
      rv = stmt->BindInt32ByName("redirect"_ns, aIsRedirect);
      NS_ENSURE_SUCCESS(rv, rv);

      rv = stmt->Execute();
      NS_ENSURE_SUCCESS(rv, rv);
    }

    if (StaticPrefs::
            places_frecency_pages_alternative_featureGate_AtStartup()) {
      nsCOMPtr<mozIStorageStatement> stmt = mHistory->GetStatement(
          "UPDATE moz_places "
          "SET alt_frecency = CALCULATE_ALT_FRECENCY(id, :redirect), "
          "recalc_alt_frecency = 0 "
          "WHERE id = :page_id");
      NS_ENSURE_STATE(stmt);
      mozStorageStatementScoper scoper(stmt);
      rv = stmt->BindInt64ByName("page_id"_ns, aPlaceId);
      NS_ENSURE_SUCCESS(rv, rv);
      rv = stmt->BindInt32ByName("redirect"_ns, aIsRedirect);
      NS_ENSURE_SUCCESS(rv, rv);
      rv = stmt->Execute();
      NS_ENSURE_SUCCESS(rv, rv);
    }

    return NS_OK;
  }

  nsresult UpdateVisitSource(VisitData& aPlace, History* aHistory) {
    if (aPlace.bookmarked) {
      aPlace.source = nsINavHistoryService::VISIT_SOURCE_BOOKMARKED;
    } else if (!aPlace.triggeringSearchEngine.IsEmpty()) {
      aPlace.source = nsINavHistoryService::VISIT_SOURCE_SEARCHED;
    } else {
      aPlace.source = nsINavHistoryService::VISIT_SOURCE_ORGANIC;
    }

    if (aPlace.triggeringSponsoredURL.IsEmpty()) {
      // No triggeringSponsoredURL.
      return NS_OK;
    }

    if ((aPlace.visitTime -
         aPlace.triggeringSponsoredURLVisitTimeMS * PR_USEC_PER_MSEC) >
        StaticPrefs::browser_places_sponsoredSession_timeoutSecs() *
            PR_USEC_PER_SEC) {
      // Sponsored session timeout.
      return NS_OK;
    }

    if (aPlace.spec.Equals(aPlace.triggeringSponsoredURL)) {
      // This place is the triggeringSponsoredURL.
      aPlace.source = nsINavHistoryService::VISIT_SOURCE_SPONSORED;
      return NS_OK;
    }

    if (!aPlace.baseDomain.Equals(aPlace.triggeringSponsoredURLBaseDomain)) {
      // The base domain is not same.
      return NS_OK;
    }

    nsCOMPtr<mozIStorageStatement> stmt;
    stmt = aHistory->GetStatement(
        "SELECT id FROM moz_places h "
        "WHERE url_hash = hash(:url) AND url = :url");
    NS_ENSURE_STATE(stmt);
    nsresult rv =
        URIBinder::Bind(stmt, "url"_ns, aPlace.triggeringSponsoredURL);
    NS_ENSURE_SUCCESS(rv, rv);

    mozStorageStatementScoper scoper(stmt);

    bool exists;
    rv = stmt->ExecuteStep(&exists);
    NS_ENSURE_SUCCESS(rv, rv);

    if (exists) {
      rv = stmt->GetInt64(0, &aPlace.triggeringPlaceId);
      NS_ENSURE_SUCCESS(rv, rv);
    } else {
      glean::places::sponsored_visit_no_triggering_url.Add(1);
    }

    aPlace.source = nsINavHistoryService::VISIT_SOURCE_SPONSORED;

    return NS_OK;
  }

  mozIStorageConnection* mDBConn;

  nsTArray<VisitData> mPlaces;

  nsMainThreadPtrHandle<mozIVisitInfoCallback> mCallback;

  bool mIgnoreErrors;

  bool mIgnoreResults;

  uint32_t mSuccessfulUpdatedCount;

  /**
   * Strong reference to the History object because we do not want it to
   * disappear out from under us.
   */

  RefPtr<History> mHistory;
};

/**
 * Sets the page title for a page in moz_places (if necessary).
 */

class SetPageTitle : public Runnable {
 public:
  /**
   * Sets a pages title in the database asynchronously.
   *
   * @param aConnection
   *        The database connection to use for this operation.
   * @param aURI
   *        The URI to set the page title on.
   * @param aTitle
   *        The title to set for the page, if the page exists.
   */

  static nsresult Start(mozIStorageConnection* aConnection, nsIURI* aURI,
                        const nsAString& aTitle) {
    MOZ_ASSERT(NS_IsMainThread(), "This should be called on the main thread");
    MOZ_ASSERT(aURI, "Must pass a non-null URI object!");

    nsCString spec;
    nsresult rv = aURI->GetSpec(spec);
    NS_ENSURE_SUCCESS(rv, rv);

    RefPtr<SetPageTitle> event = new SetPageTitle(spec, aTitle);

    // Get the target thread, and then start the work!
    nsCOMPtr<nsIEventTarget> target = do_GetInterface(aConnection);
    NS_ENSURE_TRUE(target, NS_ERROR_UNEXPECTED);
    rv = target->Dispatch(event, NS_DISPATCH_NORMAL);
    NS_ENSURE_SUCCESS(rv, rv);

    return NS_OK;
  }

  NS_IMETHOD Run() override {
    MOZ_ASSERT(!NS_IsMainThread(),
               "This should not be called on the main thread");

    // First, see if the page exists in the database (we'll need its id later).
    bool exists;
    nsresult rv = mHistory->FetchPageInfo(mPlace, &exists);
    NS_ENSURE_SUCCESS(rv, rv);

    if (!exists || !mPlace.titleChanged) {
      // We have no record of this page, or we have no title change, so there
      // is no need to do any further work.
      return NS_OK;
    }

    MOZ_ASSERT(mPlace.placeId > 0, "We somehow have an invalid place id here!");

    // Now we can update our database record.
    nsCOMPtr<mozIStorageStatement> stmt = mHistory->GetStatement(
        "UPDATE moz_places "
        "SET title = :page_title "
        "WHERE id = :page_id ");
    NS_ENSURE_STATE(stmt);

    {
      mozStorageStatementScoper scoper(stmt);
      rv = stmt->BindInt64ByName("page_id"_ns, mPlace.placeId);
      NS_ENSURE_SUCCESS(rv, rv);
      // Empty strings should clear the title, just like
      // nsNavHistory::SetPageTitle.
      if (mPlace.title.IsEmpty()) {
        rv = stmt->BindNullByName("page_title"_ns);
      } else {
        rv = stmt->BindStringByName("page_title"_ns,
                                    StringHead(mPlace.title, TITLE_LENGTH_MAX));
      }
      NS_ENSURE_SUCCESS(rv, rv);
      rv = stmt->Execute();
      NS_ENSURE_SUCCESS(rv, rv);
    }

    nsCOMPtr<nsIRunnable> event =
        new NotifyTitleObservers(mPlace.spec, mPlace.title, mPlace.guid);
    rv = NS_DispatchToMainThread(event);
    NS_ENSURE_SUCCESS(rv, rv);

    return NS_OK;
  }

 private:
  SetPageTitle(const nsCString& aSpec, const nsAString& aTitle)
      : Runnable("places::SetPageTitle"), mHistory(History::GetService()) {
    mPlace.spec = aSpec;
    mPlace.title = aTitle;
  }

  VisitData mPlace;

  /**
   * Strong reference to the History object because we do not want it to
   * disappear out from under us.
   */

  RefPtr<History> mHistory;
};

/**
 * Stores an embed visit, and notifies observers.
 *
 * @param aPlace
 *        The VisitData of the visit to store as an embed visit.
 * @param [optional] aCallback
 *        The mozIVisitInfoCallback to notify, if provided.
 *
 * FIXME(emilio, bug 1595484): We should get rid of EMBED visits completely.
 */

void NotifyEmbedVisit(VisitData& aPlace,
                      mozIVisitInfoCallback* aCallback = nullptr) {
  MOZ_ASSERT(aPlace.transitionType == nsINavHistoryService::TRANSITION_EMBED,
             "Must only pass TRANSITION_EMBED visits to this!");
  MOZ_ASSERT(NS_IsMainThread(), "Must be called on the main thread!");

  nsCOMPtr<nsIURI> uri;
  if (NS_WARN_IF(NS_FAILED(NS_NewURI(getter_AddRefs(uri), aPlace.spec)))) {
    return;
  }

  if (!!aCallback) {
    nsMainThreadPtrHandle<mozIVisitInfoCallback> callback(
        new nsMainThreadPtrHolder<mozIVisitInfoCallback>(
            "mozIVisitInfoCallback", aCallback));
    bool ignoreResults = false;
    Unused << aCallback->GetIgnoreResults(&ignoreResults);
    if (!ignoreResults) {
      nsCOMPtr<nsIRunnable> event =
          new NotifyPlaceInfoCallback(callback, aPlace, true, NS_OK);
      (void)NS_DispatchToMainThread(event);
    }
  }

  nsCOMPtr<nsIRunnable> event = new NotifyManyVisitsObservers(aPlace);
  (void)NS_DispatchToMainThread(event);
}

void NotifyOriginRestrictedVisit(nsIURI* aURI) {
  MOZ_ASSERT(NS_IsMainThread(), "Must be called on the main thread!");

  nsCOMPtr<nsIRunnable> event = new NotifyManyVisitsObservers(VisitData(aURI));
  (void)NS_DispatchToMainThread(event);
}

void NotifyVisitIfHavingUserPass(nsIURI* aURI) {
  MOZ_ASSERT(NS_IsMainThread(), "Must be called on the main thread!");

  bool hasUserPass;
  if (NS_SUCCEEDED(aURI->GetHasUserPass(&hasUserPass)) && hasUserPass) {
    nsCOMPtr<nsIRunnable> event =
        new NotifyManyVisitsObservers(VisitData(aURI));
    (void)NS_DispatchToMainThread(event);
  }
}

////////////////////////////////////////////////////////////////////////////////
//// History

History* History::gService = nullptr;

History::History()
    : mShuttingDown(false),
      mShuttingDownMutex("History::mShuttingDownMutex"),
      mBlockShutdownMutex("History::mBlockShutdownMutex"),
      mRecentlyVisitedURIs(RECENTLY_VISITED_URIS_SIZE) {
  NS_ASSERTION(!gService, "Ruh-roh! This service has already been created!");
  if (XRE_IsParentProcess()) {
    nsCOMPtr<nsIProperties> dirsvc = components::Directory::Service();
    bool haveProfile = false;
    MOZ_RELEASE_ASSERT(
        dirsvc &&
            NS_SUCCEEDED(
                dirsvc->Has(NS_APP_USER_PROFILE_50_DIR, &haveProfile)) &&
            haveProfile,
        "Can't construct history service if there is no profile.");
  }
  gService = this;

  nsCOMPtr<nsIObserverService> os = services::GetObserverService();
  NS_WARNING_ASSERTION(os, "Observer service was not found!");
  if (os) {
    (void)os->AddObserver(this, TOPIC_PLACES_SHUTDOWN, false);
  }
}

History::~History() {
  UnregisterWeakMemoryReporter(this);

  MOZ_ASSERT(gService == this);
  gService = nullptr;
}

void History::InitMemoryReporter() { RegisterWeakMemoryReporter(this); }

class ConcurrentStatementsHolder final : public mozIStorageCompletionCallback {
 public:
  NS_DECL_ISUPPORTS

  ConcurrentStatementsHolder() : mShutdownWasInvoked(false) {}

  static RefPtr<ConcurrentStatementsHolder> Create(
      mozIStorageConnection* aDBConn) {
    RefPtr<ConcurrentStatementsHolder> holder =
        new ConcurrentStatementsHolder();
    nsresult rv = aDBConn->AsyncClone(true, holder);
    if (NS_FAILED(rv)) {
      return nullptr;
    }
    return holder;
  }

  NS_IMETHOD Complete(nsresult aStatus, nsISupports* aConnection) override {
    if (NS_FAILED(aStatus)) {
      return NS_OK;
    }
    mReadOnlyDBConn = do_QueryInterface(aConnection);
    // It's possible Shutdown was invoked before we were handed back the
    // cloned connection handle.
    if (mShutdownWasInvoked) {
      Shutdown();
      return NS_OK;
    }

    // Now we can create our cached statements.

    if (!mIsVisitedStatement) {
      // Bind by index to save a name lookup.
      (void)mReadOnlyDBConn->CreateAsyncStatement(
          nsLiteralCString("WITH urls (url, url_hash) AS ( "
                           " SELECT value, hash(value) FROM carray(?1) "
                           ") "
                           "SELECT url, last_visit_date NOTNULL "
                           "FROM moz_places "
                           "JOIN urls USING(url_hash, url) "),
          getter_AddRefs(mIsVisitedStatement));
      MOZ_ASSERT(mIsVisitedStatement);
      if (mIsVisitedStatement) {
        auto queries = std::move(mVisitedQueries);
        for (auto& query : queries) {
          query->Execute(*mIsVisitedStatement);
        }
      }
    }

    return NS_OK;
  }

  void QueueVisitedStatement(RefPtr<VisitedQuery>&& aVisitedQuery) {
    if (mIsVisitedStatement) {
      RefPtr<VisitedQuery> query = std::move(aVisitedQuery);
      query->Execute(*mIsVisitedStatement);
    } else {
      mVisitedQueries.AppendElement(aVisitedQuery);
    }
  }

  void Shutdown() {
    mShutdownWasInvoked = true;
    if (mReadOnlyDBConn) {
      mVisitedQueries.Clear();
      DebugOnly<nsresult> rv;
      if (mIsVisitedStatement) {
        MOZ_ALWAYS_SUCCEEDS(mIsVisitedStatement->Finalize());
      }
      MOZ_ALWAYS_SUCCEEDS(mReadOnlyDBConn->AsyncClose(nullptr));
      mReadOnlyDBConn = nullptr;
    }
  }

 private:
  ~ConcurrentStatementsHolder() = default;

  nsCOMPtr<mozIStorageAsyncConnection> mReadOnlyDBConn;
  nsCOMPtr<mozIStorageAsyncStatement> mIsVisitedStatement;
  nsTArray<RefPtr<VisitedQuery>> mVisitedQueries;
  bool mShutdownWasInvoked;
};

NS_IMPL_ISUPPORTS(ConcurrentStatementsHolder, mozIStorageCompletionCallback)

nsresult History::QueueVisitedStatement(RefPtr<VisitedQuery>&& aQuery) {
  MOZ_ASSERT(NS_IsMainThread());
  if (IsShuttingDown()) {
    return NS_ERROR_NOT_AVAILABLE;
  }

  if (!mConcurrentStatementsHolder) {
    mozIStorageConnection* dbConn = GetDBConn();
    NS_ENSURE_STATE(dbConn);
    mConcurrentStatementsHolder = ConcurrentStatementsHolder::Create(dbConn);
    if (!mConcurrentStatementsHolder) {
      return NS_ERROR_NOT_AVAILABLE;
    }
  }
  mConcurrentStatementsHolder->QueueVisitedStatement(std::move(aQuery));
  return NS_OK;
}

nsresult History::InsertPlace(VisitData& aPlace) {
  MOZ_ASSERT(aPlace.placeId == 0, "should not have a valid place id!");
  MOZ_ASSERT(!NS_IsMainThread(), "must be called off of the main thread!");

  nsCOMPtr<mozIStorageStatement> stmt = GetStatement(
      "INSERT INTO moz_places "
      "(url, url_hash, title, rev_host, hidden, typed, frecency, guid) "
      "VALUES (:url, hash(:url), :title, :rev_host, :hidden, :typed, "
      ":frecency, :guid) ");
  NS_ENSURE_STATE(stmt);
  mozStorageStatementScoper scoper(stmt);

  nsresult rv = stmt->BindStringByName("rev_host"_ns, aPlace.revHost);
  NS_ENSURE_SUCCESS(rv, rv);
  rv = URIBinder::Bind(stmt, "url"_ns, aPlace.spec);
  NS_ENSURE_SUCCESS(rv, rv);
  nsString title = aPlace.title;
  // Empty strings should have no title, just like nsNavHistory::SetPageTitle.
  if (title.IsEmpty()) {
    rv = stmt->BindNullByName("title"_ns);
  } else {
    title.Assign(StringHead(aPlace.title, TITLE_LENGTH_MAX));
    rv = stmt->BindStringByName("title"_ns, title);
  }
  NS_ENSURE_SUCCESS(rv, rv);
  rv = stmt->BindInt32ByName("typed"_ns, aPlace.typed);
  NS_ENSURE_SUCCESS(rv, rv);
  // When inserting a page for a first visit that should not appear in
  // autocomplete, for example an error page, use a zero frecency.
  int32_t frecency = aPlace.isUnrecoverableError ? 0 : aPlace.frecency;
  rv = stmt->BindInt32ByName("frecency"_ns, frecency);
  NS_ENSURE_SUCCESS(rv, rv);
  rv = stmt->BindInt32ByName("hidden"_ns, aPlace.hidden);
  NS_ENSURE_SUCCESS(rv, rv);
  if (aPlace.guid.IsVoid()) {
    rv = GenerateGUID(aPlace.guid);
    NS_ENSURE_SUCCESS(rv, rv);
  }
  rv = stmt->BindUTF8StringByName("guid"_ns, aPlace.guid);
  NS_ENSURE_SUCCESS(rv, rv);
  rv = stmt->Execute();
  NS_ENSURE_SUCCESS(rv, rv);

  return NS_OK;
}

nsresult History::UpdatePlace(const VisitData& aPlace) {
  MOZ_ASSERT(!NS_IsMainThread(), "must be called off of the main thread!");
  MOZ_ASSERT(aPlace.placeId > 0, "must have a valid place id!");
  MOZ_ASSERT(!aPlace.guid.IsVoid(), "must have a guid!");

  nsCOMPtr<mozIStorageStatement> stmt;
  bool titleIsVoid = aPlace.title.IsVoid();
  if (titleIsVoid) {
    // Don't change the title.
    stmt = GetStatement(
        "UPDATE moz_places "
        "SET hidden = :hidden, "
        "typed = :typed, "
        "guid = :guid "
        "WHERE id = :page_id ");
  } else {
    stmt = GetStatement(
        "UPDATE moz_places "
        "SET title = :title, "
        "hidden = :hidden, "
        "typed = :typed, "
        "guid = :guid "
        "WHERE id = :page_id ");
  }
  NS_ENSURE_STATE(stmt);
  mozStorageStatementScoper scoper(stmt);

  nsresult rv;
  if (!titleIsVoid) {
    // An empty string clears the title.
    if (aPlace.title.IsEmpty()) {
      rv = stmt->BindNullByName("title"_ns);
    } else {
      rv = stmt->BindStringByName("title"_ns,
                                  StringHead(aPlace.title, TITLE_LENGTH_MAX));
    }
    NS_ENSURE_SUCCESS(rv, rv);
  }
  rv = stmt->BindInt32ByName("typed"_ns, aPlace.typed);
  NS_ENSURE_SUCCESS(rv, rv);
  rv = stmt->BindInt32ByName("hidden"_ns, aPlace.hidden);
  NS_ENSURE_SUCCESS(rv, rv);
  rv = stmt->BindUTF8StringByName("guid"_ns, aPlace.guid);
  NS_ENSURE_SUCCESS(rv, rv);
  rv = stmt->BindInt64ByName("page_id"_ns, aPlace.placeId);
  NS_ENSURE_SUCCESS(rv, rv);
  rv = stmt->Execute();
  NS_ENSURE_SUCCESS(rv, rv);

  return NS_OK;
}

nsresult History::FetchPageInfo(VisitData& _place, bool* _exists) {
  MOZ_ASSERT(!_place.spec.IsEmpty() || !_place.guid.IsEmpty(),
             "must have either a non-empty spec or guid!");
  MOZ_ASSERT(!NS_IsMainThread(), "must be called off of the main thread!");

  nsresult rv;

  // URI takes precedence.
  nsCOMPtr<mozIStorageStatement> stmt;
  bool selectByURI = !_place.spec.IsEmpty();
  if (selectByURI) {
    stmt = GetStatement(
        "SELECT guid, id, title, hidden, typed, frecency, visit_count, "
        "last_visit_date, "
        "(SELECT id FROM moz_historyvisits "
        "WHERE place_id = h.id AND visit_date = h.last_visit_date) AS "
        "last_visit_id, "
        "EXISTS(SELECT 1 FROM moz_bookmarks WHERE fk = h.id) AS bookmarked "
        "FROM moz_places h "
        "WHERE url_hash = hash(:page_url) AND url = :page_url ");
    NS_ENSURE_STATE(stmt);

    rv = URIBinder::Bind(stmt, "page_url"_ns, _place.spec);
    NS_ENSURE_SUCCESS(rv, rv);
  } else {
    stmt = GetStatement(
        "SELECT url, id, title, hidden, typed, frecency, visit_count, "
        "last_visit_date, "
        "(SELECT id FROM moz_historyvisits "
        "WHERE place_id = h.id AND visit_date = h.last_visit_date) AS "
        "last_visit_id, "
        "EXISTS(SELECT 1 FROM moz_bookmarks WHERE fk = h.id) AS bookmarked "
        "FROM moz_places h "
        "WHERE guid = :guid ");
    NS_ENSURE_STATE(stmt);

    rv = stmt->BindUTF8StringByName("guid"_ns, _place.guid);
    NS_ENSURE_SUCCESS(rv, rv);
  }

  mozStorageStatementScoper scoper(stmt);

  rv = stmt->ExecuteStep(_exists);
  NS_ENSURE_SUCCESS(rv, rv);

  if (!*_exists) {
    return NS_OK;
  }

  if (selectByURI) {
    if (_place.guid.IsEmpty()) {
      rv = stmt->GetUTF8String(0, _place.guid);
      NS_ENSURE_SUCCESS(rv, rv);
    }
  } else {
    nsAutoCString spec;
    rv = stmt->GetUTF8String(0, spec);
    NS_ENSURE_SUCCESS(rv, rv);
    _place.spec = spec;
  }

  rv = stmt->GetInt64(1, &_place.placeId);
  NS_ENSURE_SUCCESS(rv, rv);

  nsAutoString title;
  rv = stmt->GetString(2, title);
  NS_ENSURE_SUCCESS(rv, rv);

  // If the title we were given was void, that means we did not bother to set
  // it to anything.  As a result, ignore the fact that we may have changed the
  // title (because we don't want to, that would be empty), and set the title
  // to what is currently stored in the datbase.
  if (_place.title.IsVoid()) {
    _place.title = title;
  }
  // Otherwise, just indicate if the title has changed.
  else {
    _place.titleChanged = !(_place.title.Equals(title)) &&
                          !(_place.title.IsEmpty() && title.IsVoid());
  }

  int32_t hidden;
  rv = stmt->GetInt32(3, &hidden);
  NS_ENSURE_SUCCESS(rv, rv);
  _place.hidden = !!hidden;

  int32_t typed;
  rv = stmt->GetInt32(4, &typed);
  NS_ENSURE_SUCCESS(rv, rv);
  _place.typed = !!typed;

  rv = stmt->GetInt32(5, &_place.frecency);
  NS_ENSURE_SUCCESS(rv, rv);
  int32_t visitCount;
  rv = stmt->GetInt32(6, &visitCount);
  NS_ENSURE_SUCCESS(rv, rv);
  _place.visitCount = visitCount;
  rv = stmt->GetInt64(7, &_place.lastVisitTime);
  NS_ENSURE_SUCCESS(rv, rv);
  rv = stmt->GetInt64(8, &_place.lastVisitId);
  NS_ENSURE_SUCCESS(rv, rv);
  int32_t bookmarked;
  rv = stmt->GetInt32(9, &bookmarked);
  NS_ENSURE_SUCCESS(rv, rv);
  _place.bookmarked = bookmarked == 1;

  return NS_OK;
}

MOZ_DEFINE_MALLOC_SIZE_OF(HistoryMallocSizeOf)

NS_IMETHODIMP
History::CollectReports(nsIHandleReportCallback* aHandleReport,
                        nsISupports* aData, bool aAnonymize) {
  MOZ_COLLECT_REPORT(
      "explicit/history-links-hashtable", KIND_HEAP, UNITS_BYTES,
      SizeOfIncludingThis(HistoryMallocSizeOf),
      "Memory used by the hashtable that records changes to the visited state "
      "of links.");

  return NS_OK;
}

size_t History::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) {
  size_t size = aMallocSizeOf(this);
  size += mTrackedURIs.ShallowSizeOfExcludingThis(aMallocSizeOf);
  for (const auto& entry : mTrackedURIs.Values()) {
    size += entry.SizeOfExcludingThis(aMallocSizeOf);
  }
  return size;
}

/* static */
History* History::GetService() {
  if (gService) {
    return gService;
  }

  nsCOMPtr<IHistory> service = components::History::Service();
  if (service) {
    NS_ASSERTION(gService, "Our constructor was not run?!");
  }

  return gService;
}

/* static */
already_AddRefed<History> History::GetSingleton() {
  if (!gService) {
    RefPtr<History> svc = new History();
    MOZ_ASSERT(gService == svc.get());
    svc->InitMemoryReporter();
    return svc.forget();
  }

  return do_AddRef(gService);
}

mozIStorageConnection* History::GetDBConn() {
  MOZ_ASSERT(NS_IsMainThread());
  if (IsShuttingDown()) {
    return nullptr;
  }
  if (!mDB) {
    mDB = Database::GetDatabase();
    NS_ENSURE_TRUE(mDB, nullptr);
    // This must happen on the main-thread, so when we try to use the connection
    // later it's initialized.
    mDB->EnsureConnection();
    NS_ENSURE_TRUE(mDB, nullptr);
  }
  return mDB->MainConn();
}

const mozIStorageConnection* History::GetConstDBConn() {
  MOZ_ASSERT(!NS_IsMainThread());
  {
    MOZ_ASSERT(mDB || IsShuttingDown());
    if (IsShuttingDown() || !mDB) {
      return nullptr;
    }
  }
  return mDB->MainConn();
}

void History::UpdateOriginFloodingRestriction(nsACString& aOrigin) {
  MOZ_ASSERT(NS_IsMainThread());

  TimeStamp latestInputTimeStamp = UserActivation::LatestUserInputStart();
  if (latestInputTimeStamp.IsNull()) {
    // Probably just after browser initialization.
    return;
  }

  TimeStamp now = TimeStamp::Now();

  // Remove expired flooding resrictions.
  for (auto iter = mOriginFloodingRestrictions.Iter(); !iter.Done();
       iter.Next()) {
    if ((now - iter.Data().mLastVisitTimeStamp).ToSeconds() >
        iter.Data().mExpireIntervalSeconds) {
      iter.Remove();
    }
  }

  // If not long enough time elapsed sincer last user interaction, this visit
  // can be stored.
  if ((now - latestInputTimeStamp).ToSeconds() <
      StaticPrefs::
          places_history_floodingPrevention_maxSecondsFromLastUserInteraction()) {
    mOriginFloodingRestrictions.Remove(aOrigin);
    return;
  }

  // Otherwise, update the flooding restriction for the origin.
  auto restriction = mOriginFloodingRestrictions.Lookup(aOrigin);
  if (restriction) {
    if (restriction->mAllowedVisitCount) {
      // Count down to 0. If 0, the origin should be restricted.
      restriction->mAllowedVisitCount -= 1;
    } else {
      // Since the origin is marked as restricted make its expiration time
      // longer.
      restriction->mExpireIntervalSeconds *= 2;
    }
  } else {
    // Initialize OriginFloodingRestriction to store the origin restriction.
    mOriginFloodingRestrictions.InsertOrUpdate(
        aOrigin,
        OriginFloodingRestriction{
            now,
            StaticPrefs::
                places_history_floodingPrevention_restrictionExpireSeconds(),
            StaticPrefs::places_history_floodingPrevention_restrictionCount()});
  }
}

bool History::IsRestrictedOrigin(nsACString& aOrigin) {
  auto restriction = mOriginFloodingRestrictions.Lookup(aOrigin);
  // If the count is 0, need to restrict the origin.
  return restriction && !restriction->mAllowedVisitCount;
}

void History::Shutdown() {
  MOZ_ASSERT(NS_IsMainThread());
  MutexAutoLock lockedScope(mBlockShutdownMutex);
  {
    MutexAutoLock lockedScope(mShuttingDownMutex);
    MOZ_ASSERT(!mShuttingDown && "Shutdown was called more than once!");
    mShuttingDown = true;
  }
  if (mConcurrentStatementsHolder) {
    mConcurrentStatementsHolder->Shutdown();
  }
}

void History::AppendToRecentlyVisitedURIs(nsIURI* aURI, bool aHidden) {
  PRTime now = PR_Now();

  mRecentlyVisitedURIs.InsertOrUpdate(aURI, RecentURIVisit{now, aHidden});

  // Remove entries older than RECENTLY_VISITED_URIS_MAX_AGE.
  for (auto iter = mRecentlyVisitedURIs.Iter(); !iter.Done(); iter.Next()) {
    if ((now - iter.Data().mTime) > RECENTLY_VISITED_URIS_MAX_AGE) {
      iter.Remove();
    }
  }
}

--> --------------------

--> maximum size reached

--> --------------------

Messung V0.5
C=93 H=96 G=94

¤ Dauer der Verarbeitung: 0.18 Sekunden  ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

Beweissystem der NASA

Beweissystem Isabelle

NIST Cobol Testsuite

Cephes Mathematical Library

Wiener Entwicklungsmethode

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.