Bug 1209027 - Reduce queries load on visits addition. r=adw
authorMarco Bonardo <mbonardo@mozilla.com>
Tue, 31 May 2016 15:19:16 +0200
changeset 301176 c33f555ec47fb5170d48ff014f337be1fb9c25d4
parent 301175 71d424246fe1149948c8054a4392feea87dfd0ad
child 301177 e69b8135f9e224e049c140dc6fad7e8aa9b18e1f
push id78241
push userkwierso@gmail.com
push dateThu, 09 Jun 2016 00:09:10 +0000
treeherdermozilla-inbound@2c66b75bbb7f [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersadw
bugs1209027
milestone50.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1209027 - Reduce queries load on visits addition. r=adw MozReview-Commit-ID: AvW7WB2LXZE
toolkit/components/places/Database.cpp
toolkit/components/places/History.cpp
toolkit/components/places/History.h
toolkit/components/places/SQLFunctions.cpp
toolkit/components/places/SQLFunctions.h
toolkit/components/places/nsNavHistory.cpp
toolkit/components/places/nsNavHistory.h
toolkit/components/places/nsPlacesTriggers.h
toolkit/components/places/tests/PlacesTestUtils.jsm
toolkit/components/places/tests/history/test_updatePlaces_sameUri_titleChanged.js
toolkit/components/places/tests/history/xpcshell.ini
--- a/toolkit/components/places/Database.cpp
+++ b/toolkit/components/places/Database.cpp
@@ -1032,16 +1032,18 @@ Database::InitFunctions()
   rv = CalculateFrecencyFunction::create(mMainConn);
   NS_ENSURE_SUCCESS(rv, rv);
   rv = GenerateGUIDFunction::create(mMainConn);
   NS_ENSURE_SUCCESS(rv, rv);
   rv = FixupURLFunction::create(mMainConn);
   NS_ENSURE_SUCCESS(rv, rv);
   rv = FrecencyNotificationFunction::create(mMainConn);
   NS_ENSURE_SUCCESS(rv, rv);
+  rv = StoreLastInsertedIdFunction::create(mMainConn);
+  NS_ENSURE_SUCCESS(rv, rv);
 
   return NS_OK;
 }
 
 nsresult
 Database::InitTempEntities()
 {
   MOZ_ASSERT(NS_IsMainThread());
--- a/toolkit/components/places/History.cpp
+++ b/toolkit/components/places/History.cpp
@@ -68,36 +68,44 @@ namespace places {
 ////////////////////////////////////////////////////////////////////////////////
 //// VisitData
 
 struct VisitData {
   VisitData()
   : placeId(0)
   , visitId(0)
   , hidden(true)
+  , shouldUpdateHidden(true)
   , typed(false)
   , transitionType(UINT32_MAX)
   , visitTime(0)
   , frecency(-1)
+  , lastVisitId(0)
+  , lastVisitTime(0)
+  , referrerVisitId(0)
   , titleChanged(false)
   , shouldUpdateFrecency(true)
   {
     guid.SetIsVoid(true);
     title.SetIsVoid(true);
   }
 
   explicit VisitData(nsIURI* aURI,
                      nsIURI* aReferrer = nullptr)
   : placeId(0)
   , visitId(0)
   , hidden(true)
+  , shouldUpdateHidden(true)
   , typed(false)
   , transitionType(UINT32_MAX)
   , visitTime(0)
   , frecency(-1)
+  , lastVisitId(0)
+  , lastVisitTime(0)
+  , referrerVisitId(0)
   , titleChanged(false)
   , shouldUpdateFrecency(true)
   {
     MOZ_ASSERT(aURI);
     if (aURI) {
       (void)aURI->GetSpec(spec);
       (void)GetReversedHostname(aURI, revHost);
     }
@@ -116,56 +124,40 @@ struct VisitData {
    *        TRANSITION_ constants on nsINavHistoryService.
    */
   void SetTransitionType(uint32_t aTransitionType)
   {
     typed = aTransitionType == nsINavHistoryService::TRANSITION_TYPED;
     transitionType = aTransitionType;
   }
 
-  /**
-   * Determines if this refers to the same url as aOther, and updates aOther
-   * with missing information if so.
-   *
-   * @param aOther
-   *        The other place to check against.
-   * @return true if this is a visit for the same place as aOther, false
-   *         otherwise.
-   */
-  bool IsSamePlaceAs(VisitData& aOther)
-  {
-    if (!spec.Equals(aOther.spec)) {
-      return false;
-    }
-
-    aOther.placeId = placeId;
-    aOther.guid = guid;
-    return true;
-  }
-
   int64_t placeId;
   nsCString guid;
   int64_t visitId;
   nsCString spec;
   nsString revHost;
   bool hidden;
+  bool shouldUpdateHidden;
   bool typed;
   uint32_t transitionType;
   PRTime visitTime;
   int32_t frecency;
+  int64_t lastVisitId;
+  PRTime lastVisitTime;
 
   /**
    * 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 frecency should be updated for this visit.
   bool shouldUpdateFrecency;
 };
 
@@ -618,20 +610,18 @@ NS_IMPL_ISUPPORTS_INHERITED(
 )
 
 /**
  * Notifies observers about a visit.
  */
 class NotifyVisitObservers : public Runnable
 {
 public:
-  NotifyVisitObservers(VisitData& aPlace,
-                       VisitData& aReferrer)
+  NotifyVisitObservers(VisitData& aPlace)
   : mPlace(aPlace)
-  , mReferrer(aReferrer)
   , mHistory(History::GetService())
   {
   }
 
   NS_IMETHOD Run()
   {
     MOZ_ASSERT(NS_IsMainThread(), "This should be called on the main thread");
 
@@ -652,17 +642,17 @@ public:
     if (!uri) {
       return NS_ERROR_UNEXPECTED;
     }
 
     // Notify the visit.  Note that TRANSITION_EMBED visits are never added
     // to the database, thus cannot be queried and we don't notify them.
     if (mPlace.transitionType != nsINavHistoryService::TRANSITION_EMBED) {
       navHistory->NotifyOnVisit(uri, mPlace.visitId, mPlace.visitTime,
-                                mReferrer.visitId, mPlace.transitionType,
+                                mPlace.referrerVisitId, mPlace.transitionType,
                                 mPlace.guid, mPlace.hidden);
     }
 
     nsCOMPtr<nsIObserverService> obsService =
       mozilla::services::GetObserverService();
     if (obsService) {
       DebugOnly<nsresult> rv =
         obsService->NotifyObservers(uri, URI_VISIT_SAVED, nullptr);
@@ -673,17 +663,16 @@ public:
     NS_ENSURE_STATE(history);
     history->AppendToRecentlyVisitedURIs(uri);
     history->NotifyVisited(uri);
 
     return NS_OK;
   }
 private:
   VisitData mPlace;
-  VisitData mReferrer;
   RefPtr<History> mHistory;
 };
 
 /**
  * Notifies observers about a pages title changing.
  */
 class NotifyTitleObservers : public Runnable
 {
@@ -922,61 +911,74 @@ public:
     }
 
     mozStorageTransaction transaction(mDBConn, false,
                                       mozIStorageConnection::TRANSACTION_IMMEDIATE);
 
     VisitData* lastFetchedPlace = nullptr;
     for (nsTArray<VisitData>::size_type i = 0; i < mPlaces.Length(); i++) {
       VisitData& place = mPlaces.ElementAt(i);
-      VisitData& referrer = mReferrers.ElementAt(i);
 
       // 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->IsSamePlaceAs(place);
+      bool known = lastFetchedPlace && lastFetchedPlace->spec.Equals(place.spec);
       if (!known) {
         nsresult rv = mHistory->FetchPageInfo(place, &known);
         if (NS_FAILED(rv)) {
           if (!!mCallback) {
             nsCOMPtr<nsIRunnable> event =
               new NotifyPlaceInfoCallback(mCallback, place, true, rv);
             return NS_DispatchToMainThread(event);
           }
           return NS_OK;
         }
         lastFetchedPlace = &mPlaces.ElementAt(i);
+      } 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;
+        place.titleChanged = !lastFetchedPlace->title.Equals(place.title);
+        place.frecency = lastFetchedPlace->frecency;
       }
 
       // 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(referrer, place);
-
-      nsresult rv = DoDatabaseInserts(known, place, referrer);
+      // If this is a new page, or the existing page was already visible,
+      // there's no need to try to unhide it.
+      if (!known || !lastFetchedPlace->hidden) {
+        place.shouldUpdateHidden = false;
+      }
+
+      FetchReferrerInfo(place);
+
+      nsresult rv = DoDatabaseInserts(known, place);
       if (!!mCallback) {
         nsCOMPtr<nsIRunnable> event =
           new NotifyPlaceInfoCallback(mCallback, place, true, rv);
         nsresult rv2 = NS_DispatchToMainThread(event);
         NS_ENSURE_SUCCESS(rv2, rv2);
       }
       NS_ENSURE_SUCCESS(rv, rv);
 
-      nsCOMPtr<nsIRunnable> event = new NotifyVisitObservers(place, referrer);
+      nsCOMPtr<nsIRunnable> event = new NotifyVisitObservers(place);
       rv = NS_DispatchToMainThread(event);
       NS_ENSURE_SUCCESS(rv, rv);
 
       // Notify about title change if needed.
       if ((!known && !place.title.IsVoid()) || place.titleChanged) {
         event = new NotifyTitleObservers(place.spec, place.title, place.guid);
         rv = NS_DispatchToMainThread(event);
         NS_ENSURE_SUCCESS(rv, rv);
@@ -994,325 +996,211 @@ private:
                     const nsMainThreadPtrHandle<mozIVisitInfoCallback>& aCallback)
   : mDBConn(aConnection)
   , mCallback(aCallback)
   , mHistory(History::GetService())
   {
     MOZ_ASSERT(NS_IsMainThread(), "This should be called on the main thread");
 
     mPlaces.SwapElements(aPlaces);
-    mReferrers.SetLength(mPlaces.Length());
-
-    for (nsTArray<VisitData>::size_type i = 0; i < mPlaces.Length(); i++) {
-      mReferrers[i].spec = mPlaces[i].referrerSpec;
 
 #ifdef DEBUG
+    for (nsTArray<VisitData>::size_type i = 0; i < mPlaces.Length(); i++) {
       nsCOMPtr<nsIURI> uri;
       MOZ_ASSERT(NS_SUCCEEDED(NS_NewURI(getter_AddRefs(uri), mPlaces[i].spec)));
-      NS_ASSERTION(CanAddURI(uri),
-                   "Passed a VisitData with a URI we cannot add to history!");
+      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 aKnown
    *        True if we already have an entry for this place in moz_places, false
    *        otherwise.
    * @param aPlace
    *        The place we are adding a visit for.
-   * @param aReferrer
-   *        The referrer for aPlace.
    */
   nsresult DoDatabaseInserts(bool aKnown,
-                             VisitData& aPlace,
-                             VisitData& aReferrer)
+                             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);
-
-      // We need the place id and guid of the page we just inserted when we
-      // have a callback or when the GUID isn't known.  No point in doing the
-      // disk I/O if we do not need it.
-      if (!!mCallback || aPlace.guid.IsEmpty()) {
-        bool exists;
-        rv = mHistory->FetchPageInfo(aPlace, &exists);
-        NS_ENSURE_SUCCESS(rv, rv);
-
-        if (!exists) {
-          NS_NOTREACHED("should have an entry in moz_places");
-        }
-      }
+      aPlace.placeId = nsNavHistory::sLastInsertedPlaceId;
     }
-
-    rv = AddVisit(aPlace, aReferrer);
+    MOZ_ASSERT(aPlace.placeId > 0);
+
+    rv = AddVisit(aPlace);
     NS_ENSURE_SUCCESS(rv, rv);
 
     // TODO (bug 623969) we shouldn't update this after each visit, but
     // rather only for each unique place to save disk I/O.
 
     // Don't update frecency if the page should not appear in autocomplete.
     if (aPlace.shouldUpdateFrecency) {
       rv = UpdateFrecency(aPlace);
       NS_ENSURE_SUCCESS(rv, rv);
     }
 
     return NS_OK;
   }
 
   /**
-   * Loads visit information about the page into _place.
-   *
-   * @param _place
-   *        The VisitData for the place we need to know visit information about.
-   * @param [optional] aThresholdStart
-   *        The timestamp of a new visit (not represented by _place) used to
-   *        determine if the page was recently visited or not.
-   * @return true if the page was recently (determined with aThresholdStart)
-   *         visited, false otherwise.
-   */
-  bool FetchVisitInfo(VisitData& _place,
-                      PRTime aThresholdStart = 0)
-  {
-    NS_PRECONDITION(!_place.spec.IsEmpty(), "must have a non-empty spec!");
-
-    nsCOMPtr<mozIStorageStatement> stmt;
-    // If we have a visitTime, we want information on that specific visit.
-    if (_place.visitTime) {
-      stmt = mHistory->GetStatement(
-        "SELECT id, visit_date "
-        "FROM moz_historyvisits "
-        "WHERE place_id = (SELECT id FROM moz_places WHERE url = :page_url) "
-        "AND visit_date = :visit_date "
-      );
-      NS_ENSURE_TRUE(stmt, false);
-
-      mozStorageStatementScoper scoper(stmt);
-      nsresult rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("visit_date"),
-                                          _place.visitTime);
-      NS_ENSURE_SUCCESS(rv, false);
-
-      scoper.Abandon();
-    }
-    // Otherwise, we want information about the most recent visit.
-    else {
-      stmt = mHistory->GetStatement(
-        "SELECT id, visit_date "
-        "FROM moz_historyvisits "
-        "WHERE place_id = (SELECT id FROM moz_places WHERE url = :page_url) "
-        "ORDER BY visit_date DESC "
-      );
-      NS_ENSURE_TRUE(stmt, false);
-    }
-    mozStorageStatementScoper scoper(stmt);
-
-    nsresult rv = URIBinder::Bind(stmt, NS_LITERAL_CSTRING("page_url"),
-                                  _place.spec);
-    NS_ENSURE_SUCCESS(rv, false);
-
-    bool hasResult;
-    rv = stmt->ExecuteStep(&hasResult);
-    NS_ENSURE_SUCCESS(rv, false);
-    if (!hasResult) {
-      return false;
-    }
-
-    rv = stmt->GetInt64(0, &_place.visitId);
-    NS_ENSURE_SUCCESS(rv, false);
-    rv = stmt->GetInt64(1, reinterpret_cast<int64_t*>(&_place.visitTime));
-    NS_ENSURE_SUCCESS(rv, false);
-
-    // If we have been given a visit threshold start time, go ahead and
-    // calculate if we have been recently visited.
-    if (aThresholdStart &&
-        aThresholdStart - _place.visitTime <= RECENT_EVENT_THRESHOLD) {
-      return true;
-    }
-
-    return false;
-  }
-
-  /**
    * Fetches information about a referrer for aPlace if it was a recent
    * visit or not.
    *
-   * @param aReferrer
-   *        The VisitData for the referrer.  This will be populated with
-   *        FetchVisitInfo.
    * @param aPlace
    *        The VisitData for the visit we will eventually add.
    *
    */
-  void FetchReferrerInfo(VisitData& aReferrer,
-                         VisitData& aPlace)
+  void FetchReferrerInfo(VisitData& aPlace)
   {
-    if (aReferrer.spec.IsEmpty()) {
+    if (aPlace.referrerSpec.IsEmpty()) {
       return;
     }
 
-    if (!FetchVisitInfo(aReferrer, aPlace.visitTime)) {
-      // We must change both the place and referrer to indicate that we will
-      // not be using the referrer's data. This behavior has test coverage, so
-      // if this invariant changes, we'll know.
+    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();
-      aReferrer.visitId = 0;
+      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.
-   * @param aReferrer
-   *        A reference to the referrer's visit data.
    */
-  nsresult AddVisit(VisitData& _place,
-                    const VisitData& aReferrer)
+  nsresult AddVisit(VisitData& _place)
   {
+    MOZ_ASSERT(_place.placeId > 0);
+
     nsresult rv;
     nsCOMPtr<mozIStorageStatement> stmt;
-    if (_place.placeId) {
-      stmt = mHistory->GetStatement(
-        "INSERT INTO moz_historyvisits "
-          "(from_visit, place_id, visit_date, visit_type, session) "
-        "VALUES (:from_visit, :page_id, :visit_date, :visit_type, 0) "
-      );
-      NS_ENSURE_STATE(stmt);
-      rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("page_id"), _place.placeId);
-      NS_ENSURE_SUCCESS(rv, rv);
-    }
-    else {
-      stmt = mHistory->GetStatement(
-        "INSERT INTO moz_historyvisits "
-          "(from_visit, place_id, visit_date, visit_type, session) "
-        "VALUES (:from_visit, (SELECT id FROM moz_places WHERE url = :page_url), :visit_date, :visit_type, 0) "
-      );
-      NS_ENSURE_STATE(stmt);
-      rv = URIBinder::Bind(stmt, NS_LITERAL_CSTRING("page_url"), _place.spec);
-      NS_ENSURE_SUCCESS(rv, rv);
-    }
+    stmt = mHistory->GetStatement(
+      "INSERT INTO moz_historyvisits "
+        "(from_visit, place_id, visit_date, visit_type, session) "
+      "VALUES (:from_visit, :page_id, :visit_date, :visit_type, 0) "
+    );
+    NS_ENSURE_STATE(stmt);
+    mozStorageStatementScoper scoper(stmt);
+
+    rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("page_id"), _place.placeId);
+    NS_ENSURE_SUCCESS(rv, rv);
     rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("from_visit"),
-                               aReferrer.visitId);
+                               _place.referrerVisitId);
     NS_ENSURE_SUCCESS(rv, rv);
     rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("visit_date"),
                                _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(NS_LITERAL_CSTRING("visit_type"),
                                transitionType);
     NS_ENSURE_SUCCESS(rv, rv);
 
-    mozStorageStatementScoper scoper(stmt);
     rv = stmt->Execute();
     NS_ENSURE_SUCCESS(rv, rv);
 
-    // Now that it should be in the database, we need to obtain the id of the
-    // visit we just added.
-    (void)FetchVisitInfo(_place);
+    _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 VisitData& aPlace)
   {
     MOZ_ASSERT(aPlace.shouldUpdateFrecency);
+    MOZ_ASSERT(aPlace.placeId > 0);
 
     nsresult rv;
     { // First, set our frecency to the proper value.
       nsCOMPtr<mozIStorageStatement> stmt;
-      if (aPlace.placeId) {
-        stmt = mHistory->GetStatement(
-          "UPDATE moz_places "
-          "SET frecency = NOTIFY_FRECENCY("
-            "CALCULATE_FRECENCY(:page_id), "
-            "url, guid, hidden, last_visit_date"
-          ") "
-          "WHERE id = :page_id"
-        );
-        NS_ENSURE_STATE(stmt);
-        rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("page_id"), aPlace.placeId);
-        NS_ENSURE_SUCCESS(rv, rv);
-      }
-      else {
-        stmt = mHistory->GetStatement(
-          "UPDATE moz_places "
-          "SET frecency = NOTIFY_FRECENCY("
-            "CALCULATE_FRECENCY(id), url, guid, hidden, last_visit_date"
-          ") "
-          "WHERE url = :page_url"
-        );
-        NS_ENSURE_STATE(stmt);
-        rv = URIBinder::Bind(stmt, NS_LITERAL_CSTRING("page_url"), aPlace.spec);
-        NS_ENSURE_SUCCESS(rv, rv);
-      }
+      stmt = mHistory->GetStatement(
+        "UPDATE moz_places "
+        "SET frecency = NOTIFY_FRECENCY("
+          "CALCULATE_FRECENCY(:page_id), "
+          "url, guid, hidden, last_visit_date"
+        ") "
+        "WHERE id = :page_id"
+      );
+      NS_ENSURE_STATE(stmt);
       mozStorageStatementScoper scoper(stmt);
 
+      rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("page_id"), aPlace.placeId);
+      NS_ENSURE_SUCCESS(rv, rv);
+
       rv = stmt->Execute();
       NS_ENSURE_SUCCESS(rv, rv);
     }
 
-    if (!aPlace.hidden) {
+    if (!aPlace.hidden && aPlace.shouldUpdateHidden) {
       // Mark the page as not hidden if the frecency is now nonzero.
       nsCOMPtr<mozIStorageStatement> stmt;
-      if (aPlace.placeId) {
-        stmt = mHistory->GetStatement(
-          "UPDATE moz_places "
-          "SET hidden = 0 "
-          "WHERE id = :page_id AND frecency <> 0"
-        );
-        NS_ENSURE_STATE(stmt);
-        rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("page_id"), aPlace.placeId);
-        NS_ENSURE_SUCCESS(rv, rv);
-      }
-      else {
-        stmt = mHistory->GetStatement(
-          "UPDATE moz_places "
-          "SET hidden = 0 "
-          "WHERE url = :page_url AND frecency <> 0"
-        );
-        NS_ENSURE_STATE(stmt);
-        rv = URIBinder::Bind(stmt, NS_LITERAL_CSTRING("page_url"), aPlace.spec);
-        NS_ENSURE_SUCCESS(rv, rv);
-      }
-
+      stmt = mHistory->GetStatement(
+        "UPDATE moz_places "
+        "SET hidden = 0 "
+        "WHERE id = :page_id AND frecency <> 0"
+      );
+      NS_ENSURE_STATE(stmt);
       mozStorageStatementScoper scoper(stmt);
+
+      rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("page_id"), aPlace.placeId);
+      NS_ENSURE_SUCCESS(rv, rv);
+
       rv = stmt->Execute();
       NS_ENSURE_SUCCESS(rv, rv);
     }
 
     return NS_OK;
   }
 
   mozIStorageConnection* mDBConn;
 
   nsTArray<VisitData> mPlaces;
-  nsTArray<VisitData> mReferrers;
 
   nsMainThreadPtrHandle<mozIVisitInfoCallback> mCallback;
 
   /**
    * Strong reference to the History object because we do not want it to
    * disappear out from under us.
    */
   RefPtr<History> mHistory;
@@ -1423,18 +1311,18 @@ public:
     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;
     }
 
-    NS_ASSERTION(mPlace.placeId > 0,
-                 "We somehow have an invalid place id here!");
+    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 "
       );
@@ -1949,18 +1837,17 @@ StoreAndNotifyEmbedVisit(VisitData& aPla
   if (!!aCallback) {
     nsMainThreadPtrHandle<mozIVisitInfoCallback>
       callback(new nsMainThreadPtrHolder<mozIVisitInfoCallback>(aCallback));
     nsCOMPtr<nsIRunnable> event =
       new NotifyPlaceInfoCallback(callback, aPlace, true, NS_OK);
     (void)NS_DispatchToMainThread(event);
   }
 
-  VisitData noReferrer;
-  nsCOMPtr<nsIRunnable> event = new NotifyVisitObservers(aPlace, noReferrer);
+  nsCOMPtr<nsIRunnable> event = new NotifyVisitObservers(aPlace);
   (void)NS_DispatchToMainThread(event);
 }
 
 } // namespace
 
 ////////////////////////////////////////////////////////////////////////////////
 //// History
 
@@ -2130,20 +2017,21 @@ History::GetIsVisitedStatement(mozIStora
     NS_ENSURE_STATE(dbConn);
     mConcurrentStatementsHolder = new ConcurrentStatementsHolder(dbConn);
   }
   mConcurrentStatementsHolder->GetIsVisitedStatement(aCallback);
   return NS_OK;
 }
 
 nsresult
-History::InsertPlace(const VisitData& aPlace)
+History::InsertPlace(VisitData& aPlace)
 {
-  NS_PRECONDITION(aPlace.placeId == 0, "should not have a valid place id!");
-  NS_PRECONDITION(!NS_IsMainThread(), "must be called off of the main thread!");
+  MOZ_ASSERT(aPlace.placeId == 0, "should not have a valid place id!");
+  MOZ_ASSERT(!aPlace.shouldUpdateHidden, "We should not need to update hidden");
+  MOZ_ASSERT(!NS_IsMainThread(), "must be called off of the main thread!");
 
   nsCOMPtr<mozIStorageStatement> stmt = GetStatement(
       "INSERT INTO moz_places "
         "(url, title, rev_host, hidden, typed, frecency, guid) "
       "VALUES (:url, :title, :rev_host, :hidden, :typed, :frecency, :guid) "
     );
   NS_ENSURE_STATE(stmt);
   mozStorageStatementScoper scoper(stmt);
@@ -2167,42 +2055,42 @@ History::InsertPlace(const VisitData& aP
   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.shouldUpdateFrecency ? aPlace.frecency : 0;
   rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("frecency"), frecency);
   NS_ENSURE_SUCCESS(rv, rv);
   rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("hidden"), aPlace.hidden);
   NS_ENSURE_SUCCESS(rv, rv);
-  nsAutoCString guid(aPlace.guid);
   if (aPlace.guid.IsVoid()) {
-    rv = GenerateGUID(guid);
+    rv = GenerateGUID(aPlace.guid);
     NS_ENSURE_SUCCESS(rv, rv);
   }
-  rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("guid"), guid);
+  rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("guid"), aPlace.guid);
   NS_ENSURE_SUCCESS(rv, rv);
   rv = stmt->Execute();
   NS_ENSURE_SUCCESS(rv, rv);
 
   // Post an onFrecencyChanged observer notification.
   const nsNavHistory* navHistory = nsNavHistory::GetConstHistoryService();
   NS_ENSURE_STATE(navHistory);
-  navHistory->DispatchFrecencyChangedNotification(aPlace.spec, frecency, guid,
+  navHistory->DispatchFrecencyChangedNotification(aPlace.spec, frecency,
+                                                  aPlace.guid,
                                                   aPlace.hidden,
                                                   aPlace.visitTime);
 
   return NS_OK;
 }
 
 nsresult
 History::UpdatePlace(const VisitData& aPlace)
 {
-  NS_PRECONDITION(!NS_IsMainThread(), "must be called off of the main thread!");
-  NS_PRECONDITION(aPlace.placeId > 0, "must have a valid place id!");
-  NS_PRECONDITION(!aPlace.guid.IsVoid(), "must have a guid!");
+  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 = GetStatement(
       "UPDATE moz_places "
       "SET title = :title, "
           "hidden = :hidden, "
           "typed = :typed, "
           "guid = :guid "
       "WHERE id = :page_id "
@@ -2233,39 +2121,43 @@ History::UpdatePlace(const VisitData& aP
   NS_ENSURE_SUCCESS(rv, rv);
 
   return NS_OK;
 }
 
 nsresult
 History::FetchPageInfo(VisitData& _place, bool* _exists)
 {
-  NS_PRECONDITION(!_place.spec.IsEmpty() || !_place.guid.IsEmpty(), "must have either a non-empty spec or guid!");
-  NS_PRECONDITION(!NS_IsMainThread(), "must be called off of the main thread!");
+  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 "
-      "FROM moz_places "
+      "SELECT guid, id, title, hidden, typed, frecency, last_visit_date, "
+      "(SELECT id FROM moz_historyvisits "
+       "WHERE place_id = h.id AND visit_date = h.last_visit_date) AS last_visit_id "
+      "FROM moz_places h "
       "WHERE url = :page_url "
     );
     NS_ENSURE_STATE(stmt);
 
     rv = URIBinder::Bind(stmt, NS_LITERAL_CSTRING("page_url"), _place.spec);
     NS_ENSURE_SUCCESS(rv, rv);
   }
   else {
     stmt = GetStatement(
-      "SELECT url, id, title, hidden, typed, frecency "
-      "FROM moz_places "
+      "SELECT url, id, title, hidden, typed, frecency, last_visit_date, "
+      "(SELECT id FROM moz_historyvisits "
+       "WHERE place_id = h.id AND visit_date = h.last_visit_date) AS last_visit_id "
+      "FROM moz_places h "
       "WHERE guid = :guid "
     );
     NS_ENSURE_STATE(stmt);
 
     rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("guid"), _place.guid);
     NS_ENSURE_SUCCESS(rv, rv);
   }
 
@@ -2318,16 +2210,21 @@ History::FetchPageInfo(VisitData& _place
 
   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);
+  rv = stmt->GetInt64(6, &_place.lastVisitTime);
+  NS_ENSURE_SUCCESS(rv, rv);
+  rv = stmt->GetInt64(7, &_place.lastVisitId);
+  NS_ENSURE_SUCCESS(rv, rv);
+
   return NS_OK;
 }
 
 MOZ_DEFINE_MALLOC_SIZE_OF(HistoryMallocSizeOf)
 
 NS_IMETHODIMP
 History::CollectReports(nsIHandleReportCallback* aHandleReport,
                         nsISupports* aData, bool aAnonymize)
--- a/toolkit/components/places/History.h
+++ b/toolkit/components/places/History.h
@@ -64,17 +64,17 @@ public:
   nsresult GetIsVisitedStatement(mozIStorageCompletionCallback* aCallback);
 
   /**
    * Adds an entry in moz_places with the data in aVisitData.
    *
    * @param aVisitData
    *        The visit data to use to populate a new row in moz_places.
    */
-  nsresult InsertPlace(const VisitData& aVisitData);
+  nsresult InsertPlace(VisitData& aVisitData);
 
   /**
    * Updates an entry in moz_places with the data in aVisitData.
    *
    * @param aVisitData
    *        The visit data to use to update the existing row in moz_places.
    */
   nsresult UpdatePlace(const VisitData& aVisitData);
--- a/toolkit/components/places/SQLFunctions.cpp
+++ b/toolkit/components/places/SQLFunctions.cpp
@@ -839,10 +839,60 @@ namespace places {
 
     RefPtr<nsVariant> result = new nsVariant();
     rv = result->SetAsInt32(newFrecency);
     NS_ENSURE_SUCCESS(rv, rv);
     result.forget(_result);
     return NS_OK;
   }
 
+////////////////////////////////////////////////////////////////////////////////
+//// Store Last Inserted Id Function
+
+  StoreLastInsertedIdFunction::~StoreLastInsertedIdFunction()
+  {
+  }
+
+  /* static */
+  nsresult
+  StoreLastInsertedIdFunction::create(mozIStorageConnection *aDBConn)
+  {
+    RefPtr<StoreLastInsertedIdFunction> function =
+      new StoreLastInsertedIdFunction();
+    nsresult rv = aDBConn->CreateFunction(
+      NS_LITERAL_CSTRING("store_last_inserted_id"), 2, function
+    );
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    return NS_OK;
+  }
+
+  NS_IMPL_ISUPPORTS(
+    StoreLastInsertedIdFunction,
+    mozIStorageFunction
+  )
+
+  NS_IMETHODIMP
+  StoreLastInsertedIdFunction::OnFunctionCall(mozIStorageValueArray *aArgs,
+                                              nsIVariant **_result)
+  {
+    uint32_t numArgs;
+    nsresult rv = aArgs->GetNumEntries(&numArgs);
+    NS_ENSURE_SUCCESS(rv, rv);
+    MOZ_ASSERT(numArgs == 2);
+
+    nsAutoCString table;
+    rv = aArgs->GetUTF8String(0, table);
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    int64_t lastInsertedId = aArgs->AsInt64(1);
+
+    nsNavHistory::StoreLastInsertedId(table, lastInsertedId);
+
+    RefPtr<nsVariant> result = new nsVariant();
+    rv = result->SetAsInt64(lastInsertedId);
+    NS_ENSURE_SUCCESS(rv, rv);
+    result.forget(_result);
+    return NS_OK;
+  }
+
 } // namespace places
 } // namespace mozilla
--- a/toolkit/components/places/SQLFunctions.h
+++ b/toolkit/components/places/SQLFunctions.h
@@ -319,12 +319,40 @@ public:
    *
    * @param aDBConn
    *        The database connection to register with.
    */
   static nsresult create(mozIStorageConnection *aDBConn);
 };
 
 
+////////////////////////////////////////////////////////////////////////////////
+//// Store Last Inserted Id Function
+
+/**
+ * Store the last inserted id for reference purpose.
+ *
+ * @param tableName
+ *        The table name.
+ * @param id
+ *        The last inserted id.
+ * @return null
+ */
+class StoreLastInsertedIdFunction final : public mozIStorageFunction
+{
+  ~StoreLastInsertedIdFunction();
+public:
+  NS_DECL_THREADSAFE_ISUPPORTS
+  NS_DECL_MOZISTORAGEFUNCTION
+
+  /**
+   * Registers the function with the specified database connection.
+   *
+   * @param aDBConn
+   *        The database connection to register with.
+   */
+  static nsresult create(mozIStorageConnection *aDBConn);
+};
+
 } // namespace places
 } // namespace mozilla
 
 #endif // mozilla_places_SQLFunctions_h_
--- a/toolkit/components/places/nsNavHistory.cpp
+++ b/toolkit/components/places/nsNavHistory.cpp
@@ -400,18 +400,18 @@ nsNavHistory::GetOrCreateIdForPage(nsIUR
   NS_ENSURE_SUCCESS(rv, rv);
 
   if (*_pageId != 0) {
     return NS_OK;
   }
 
   // Create a new hidden, untyped and unvisited entry.
   nsCOMPtr<mozIStorageStatement> stmt = mDB->GetStatement(
-    "INSERT OR IGNORE INTO moz_places (url, rev_host, hidden, frecency, guid) "
-    "VALUES (:page_url, :rev_host, :hidden, :frecency, GENERATE_GUID()) "
+    "INSERT INTO moz_places (url, rev_host, hidden, frecency, guid) "
+    "VALUES (:page_url, :rev_host, :hidden, :frecency, :guid) "
   );
   NS_ENSURE_STATE(stmt);
   mozStorageStatementScoper scoper(stmt);
 
   rv = URIBinder::Bind(stmt, NS_LITERAL_CSTRING("page_url"), aURI);
   NS_ENSURE_SUCCESS(rv, rv);
   // host (reversed with trailing period)
   nsAutoString revHost;
@@ -426,38 +426,26 @@ nsNavHistory::GetOrCreateIdForPage(nsIUR
   rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("hidden"), 1);
   NS_ENSURE_SUCCESS(rv, rv);
   nsAutoCString spec;
   rv = aURI->GetSpec(spec);
   NS_ENSURE_SUCCESS(rv, rv);
   rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("frecency"),
                              IsQueryURI(spec) ? 0 : -1);
   NS_ENSURE_SUCCESS(rv, rv);
+  nsAutoCString guid;
+  rv = GenerateGUID(_GUID);
+  NS_ENSURE_SUCCESS(rv, rv);
+  rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("guid"), _GUID);
+  NS_ENSURE_SUCCESS(rv, rv);
 
   rv = stmt->Execute();
   NS_ENSURE_SUCCESS(rv, rv);
 
-  {
-    nsCOMPtr<mozIStorageStatement> getIdStmt = mDB->GetStatement(
-      "SELECT id, guid FROM moz_places WHERE url = :page_url "
-    );
-    NS_ENSURE_STATE(getIdStmt);
-    mozStorageStatementScoper getIdScoper(getIdStmt);
-
-    rv = URIBinder::Bind(getIdStmt, NS_LITERAL_CSTRING("page_url"), aURI);
-    NS_ENSURE_SUCCESS(rv, rv);
-
-    bool hasResult = false;
-    rv = getIdStmt->ExecuteStep(&hasResult);
-    NS_ENSURE_SUCCESS(rv, rv);
-    NS_ASSERTION(hasResult, "hasResult is false but the call succeeded?");
-    *_pageId = getIdStmt->AsInt64(0);
-    rv = getIdStmt->GetUTF8String(1, _GUID);
-    NS_ENSURE_SUCCESS(rv, rv);
-  }
+  *_pageId = sLastInsertedPlaceId;
 
   return NS_OK;
 }
 
 
 void
 nsNavHistory::LoadPrefs()
 {
@@ -608,16 +596,31 @@ nsNavHistory::DispatchFrecencyChangedNot
                                                   PRTime aLastVisitDate) const
 {
   nsCOMPtr<nsIRunnable> notif = new FrecencyNotification(aSpec, aNewFrecency,
                                                          aGUID, aHidden,
                                                          aLastVisitDate);
   (void)NS_DispatchToMainThread(notif);
 }
 
+Atomic<int64_t> nsNavHistory::sLastInsertedPlaceId(0);
+Atomic<int64_t> nsNavHistory::sLastInsertedVisitId(0);
+
+void // static
+nsNavHistory::StoreLastInsertedId(const nsACString& aTable,
+                                  const int64_t aLastInsertedId) {
+  if (aTable.Equals(NS_LITERAL_CSTRING("moz_places"))) {
+    nsNavHistory::sLastInsertedPlaceId = aLastInsertedId;
+  } else if (aTable.Equals(NS_LITERAL_CSTRING("moz_historyvisits"))) {
+    nsNavHistory::sLastInsertedVisitId = aLastInsertedId;
+  } else {
+    MOZ_ASSERT(false, "Trying to store the insert id for an unknown table?");
+  }
+}
+
 int32_t
 nsNavHistory::GetDaysOfHistory() {
   MOZ_ASSERT(NS_IsMainThread(), "This can only be called on the main thread");
 
   if (mDaysOfHistory != -1)
     return mDaysOfHistory;
 
   // SQLite doesn't have a CEIL() function, so we must do that later.
--- a/toolkit/components/places/nsNavHistory.h
+++ b/toolkit/components/places/nsNavHistory.h
@@ -22,16 +22,17 @@
 #include "nsToolkitCompsCID.h"
 #include "nsURIHashKey.h"
 #include "nsTHashtable.h"
 
 #include "nsNavHistoryResult.h"
 #include "nsNavHistoryQuery.h"
 #include "Database.h"
 #include "mozilla/Attributes.h"
+#include "mozilla/Atomics.h"
 
 #define QUERYUPDATE_TIME 0
 #define QUERYUPDATE_SIMPLE 1
 #define QUERYUPDATE_COMPLEX 2
 #define QUERYUPDATE_COMPLEX_WITH_BOOKMARKS 3
 #define QUERYUPDATE_HOST 4
 
 // Clamp title and URL to generously large, but not too large, length.
@@ -462,16 +463,25 @@ public:
    * Posts a runnable to the main thread that calls NotifyFrecencyChanged.
    */
   void DispatchFrecencyChangedNotification(const nsACString& aSpec,
                                            int32_t aNewFrecency,
                                            const nsACString& aGUID,
                                            bool aHidden,
                                            PRTime aLastVisitDate) const;
 
+  /**
+   * Store last insterted id for a table.
+   */
+  static mozilla::Atomic<int64_t> sLastInsertedPlaceId;
+  static mozilla::Atomic<int64_t> sLastInsertedVisitId;
+
+  static void StoreLastInsertedId(const nsACString& aTable,
+                                  const int64_t aLastInsertedId);
+
   bool isBatching() {
     return mBatchLevel > 0;
   }
 
 private:
   ~nsNavHistory();
 
   // used by GetHistoryService
--- a/toolkit/components/places/nsPlacesTriggers.h
+++ b/toolkit/components/places/nsPlacesTriggers.h
@@ -22,16 +22,17 @@
 /**
  * This triggers update visit_count and last_visit_date based on historyvisits
  * table changes.
  */
 #define CREATE_HISTORYVISITS_AFTERINSERT_TRIGGER NS_LITERAL_CSTRING( \
   "CREATE TEMP TRIGGER moz_historyvisits_afterinsert_v2_trigger " \
   "AFTER INSERT ON moz_historyvisits FOR EACH ROW " \
   "BEGIN " \
+    "SELECT store_last_inserted_id('moz_historyvisits', NEW.id); " \
     "UPDATE moz_places SET " \
       "visit_count = visit_count + (SELECT NEW.visit_type NOT IN (" EXCLUDED_VISIT_TYPES ")), "\
       "last_visit_date = MAX(IFNULL(last_visit_date, 0), NEW.visit_date) " \
     "WHERE id = NEW.place_id;" \
   "END" \
 )
 
 #define CREATE_HISTORYVISITS_AFTERDELETE_TRIGGER NS_LITERAL_CSTRING( \
@@ -89,30 +90,30 @@
   "END "
 
 /**
  * These triggers update the hostnames table whenever moz_places changes.
  */
 #define CREATE_PLACES_AFTERINSERT_TRIGGER NS_LITERAL_CSTRING( \
   "CREATE TEMP TRIGGER moz_places_afterinsert_trigger " \
   "AFTER INSERT ON moz_places FOR EACH ROW " \
-  "WHEN LENGTH(NEW.rev_host) > 1 " \
   "BEGIN " \
+    "SELECT store_last_inserted_id('moz_places', NEW.id); " \
     "INSERT OR REPLACE INTO moz_hosts (id, host, frecency, typed, prefix) " \
-    "VALUES (" \
-      "(SELECT id FROM moz_hosts WHERE host = fixup_url(get_unreversed_host(NEW.rev_host))), " \
-      "fixup_url(get_unreversed_host(NEW.rev_host)), " \
-      "MAX(IFNULL((SELECT frecency FROM moz_hosts WHERE host = fixup_url(get_unreversed_host(NEW.rev_host))), -1), NEW.frecency), " \
-      "MAX(IFNULL((SELECT typed FROM moz_hosts WHERE host = fixup_url(get_unreversed_host(NEW.rev_host))), 0), NEW.typed), " \
-      "(" HOSTS_PREFIX_PRIORITY_FRAGMENT \
-       "FROM ( " \
-          "SELECT fixup_url(get_unreversed_host(NEW.rev_host)) AS host " \
-        ") AS match " \
-      ") " \
-    "); " \
+    "SELECT " \
+        "(SELECT id FROM moz_hosts WHERE host = fixup_url(get_unreversed_host(NEW.rev_host))), " \
+        "fixup_url(get_unreversed_host(NEW.rev_host)), " \
+        "MAX(IFNULL((SELECT frecency FROM moz_hosts WHERE host = fixup_url(get_unreversed_host(NEW.rev_host))), -1), NEW.frecency), " \
+        "MAX(IFNULL((SELECT typed FROM moz_hosts WHERE host = fixup_url(get_unreversed_host(NEW.rev_host))), 0), NEW.typed), " \
+        "(" HOSTS_PREFIX_PRIORITY_FRAGMENT \
+         "FROM ( " \
+            "SELECT fixup_url(get_unreversed_host(NEW.rev_host)) AS host " \
+          ") AS match " \
+        ") " \
+    " WHERE LENGTH(NEW.rev_host) > 1; " \
   "END" \
 )
 
 // This is a hack to workaround the lack of FOR EACH STATEMENT in Sqlite, until
 // bug 871908 can be fixed properly.
 // We store the modified hosts in a temp table, and after every DELETE FROM
 // moz_places, we issue a DELETE FROM moz_updatehosts_temp.  The AFTER DELETE
 // trigger will then take care of updating the moz_hosts table.
--- a/toolkit/components/places/tests/PlacesTestUtils.jsm
+++ b/toolkit/components/places/tests/PlacesTestUtils.jsm
@@ -52,21 +52,26 @@ this.PlacesTestUtils = Object.freeze({
     }
 
     // Create mozIVisitInfo for each entry.
     let now = Date.now();
     for (let place of places) {
       if (typeof place.uri == "string") {
         place.uri = NetUtil.newURI(place.uri);
       } else if (place.uri instanceof URL) {
-        place.uri = NetUtil.newURI(place.href);
+        place.uri = NetUtil.newURI(place.uri.href);
       }
       if (typeof place.title != "string") {
         place.title = "test visit for " + place.uri.spec;
       }
+      if (typeof place.referrer == "string") {
+        place.referrer = NetUtil.newURI(place.referrer);
+      } else if (place.referrer instanceof URL) {
+        place.referrer = NetUtil.newURI(place.referrer.href);
+      }
       place.visits = [{
         transitionType: place.transition === undefined ? Ci.nsINavHistoryService.TRANSITION_LINK
                                                        : place.transition,
         visitDate: place.visitDate || (now++) * 1000,
         referrerURI: place.referrer
       }];
     }
 
new file mode 100644
--- /dev/null
+++ b/toolkit/components/places/tests/history/test_updatePlaces_sameUri_titleChanged.js
@@ -0,0 +1,52 @@
+// Test that repeated additions of the same URI through updatePlaces, properly
+// update from_visit and notify titleChanged.
+
+add_task(function* test() {
+  let uri = "http://test.com/";
+
+  let promiseTitleChangedNotifications = new Promise(resolve => {
+    let historyObserver = {
+      _count: 0,
+      __proto__: NavHistoryObserver.prototype,
+      onTitleChanged(aURI, aTitle, aGUID) {
+        Assert.equal(aURI.spec, uri, "Should notify the proper url");
+        if (++this._count == 2) {
+          PlacesUtils.history.removeObserver(historyObserver);
+          resolve();
+        }
+      }
+    };
+    PlacesUtils.history.addObserver(historyObserver, false);
+  });
+
+  // This repeats the url on purpose, don't merge it into a single place entry.
+  yield PlacesTestUtils.addVisits([
+    { uri, title: "test" },
+    { uri, referrer: uri, title: "test2" },
+  ]);
+
+  let options = PlacesUtils.history.getNewQueryOptions();
+  let query = PlacesUtils.history.getNewQuery();
+  query.uri = NetUtil.newURI(uri);
+  options.resultType = options.RESULTS_AS_VISIT;
+  let root = PlacesUtils.history.executeQuery(query, options).root;
+  root.containerOpen = true;
+
+  Assert.equal(root.childCount, 2);
+
+  let child = root.getChild(0);
+  Assert.equal(child.visitType, TRANSITION_LINK, "Visit type should be TRANSITION_LINK");
+  Assert.equal(child.visitId, 1, "Visit ID should be 1");
+  Assert.equal(child.fromVisitId, -1, "Should have no referrer visit ID");
+  Assert.equal(child.title, "test2", "Should have the correct title");
+
+  child = root.getChild(1);
+  Assert.equal(child.visitType, TRANSITION_LINK, "Visit type should be TRANSITION_LINK");
+  Assert.equal(child.visitId, 2, "Visit ID should be 2");
+  Assert.equal(child.fromVisitId, 1, "First visit should be the referring visit");
+  Assert.equal(child.title, "test2", "Should have the correct title");
+
+  root.containerOpen = false;
+
+  yield promiseTitleChangedNotifications;
+});
--- a/toolkit/components/places/tests/history/xpcshell.ini
+++ b/toolkit/components/places/tests/history/xpcshell.ini
@@ -1,8 +1,9 @@
 [DEFAULT]
 head = head_history.js
 tail =
 
 [test_insert.js]
 [test_remove.js]
 [test_removeVisits.js]
 [test_removeVisitsByFilter.js]
+[test_updatePlaces_sameUri_titleChanged.js]