Bug 1239708: Improve awesomebar autofill. Part 1: Core follow-ons. r?mak draft
authorDrew Willcoxon <adw@mozilla.com>
Mon, 14 May 2018 11:21:47 -0700
changeset 794913 131e2bad2f6b0480be96e0548a04ad8ba7e84992
parent 794912 c4b05f3d83a0581e3518b88bc778a119f8b1f7b9
child 794914 36196ca956e3fecc4afad4fd1f6ae29224fa13a1
push id109819
push userbmo:adw@mozilla.com
push dateMon, 14 May 2018 20:01:28 +0000
reviewersmak
bugs1239708
milestone62.0a1
Bug 1239708: Improve awesomebar autofill. Part 1: Core follow-ons. r?mak MozReview-Commit-ID: 1mfqU6mOyR9
netwerk/base/nsIBrowserSearchService.idl
toolkit/components/places/Database.cpp
toolkit/components/places/Database.h
toolkit/components/places/PlacesSearchAutocompleteProvider.jsm
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/search/nsSearchService.js
--- a/netwerk/base/nsIBrowserSearchService.idl
+++ b/netwerk/base/nsIBrowserSearchService.idl
@@ -150,19 +150,19 @@ interface nsISearchEngine : nsISupports
   /**
    * An optional unique identifier for this search engine within the context of
    * the distribution, as provided by the distributing entity.
    */
   readonly attribute AString identifier;
 
   /**
    * Gets a string representing the hostname from which search results for a
-   * given responseType are returned, minus the leading "www." (if present).
-   * This can be specified as an url attribute in the engine description file,
-   * but will default to host from the <Url>'s template otherwise.
+   * given responseType are returned.  This can be specified as an url attribute
+   * in the engine description file, but will default to host from the <Url>'s
+   * template otherwise.
    *
    * @param  responseType [optional]
    *         The MIME type to get resultDomain for.  Defaults to "text/html".
    *
    * @return the resultDomain for the given responseType.
    */
   AString getResultDomain([optional] in AString responseType);
 };
--- a/toolkit/components/places/Database.cpp
+++ b/toolkit/components/places/Database.cpp
@@ -73,16 +73,18 @@
 // * IE didn't support urls longer than 2083 chars
 // * Sitemaps protocol used to support a maximum of 2048 chars
 // * Various SEO guides suggest to not go over 2000 chars
 // * Various apps/services are known to have issues over 2000 chars
 // * RFC 2616 - HTTP/1.1 suggests being cautious about depending
 //   on URI lengths above 255 bytes
 #define PREF_HISTORY_MAXURLLEN_DEFAULT 2000
 
+#define PREF_MIGRATE_V48_FRECENCIES "places.database.migrateV48Frecencies"
+
 // Maximum size for the WAL file.
 // For performance reasons this should be as large as possible, so that more
 // transactions can fit into it, and the checkpoint cost is paid less often.
 // At the same time, since we use synchronous = NORMAL, an fsync happens only
 // at checkpoint time, so we don't want the WAL to grow too much and risk to
 // lose all the contained transactions on a crash.
 #define DATABASE_MAX_WAL_BYTES 2048000
 
@@ -1115,16 +1117,17 @@ Database::InitSchema(bool* aDatabaseMigr
       MOZ_ALWAYS_SUCCEEDS(mMainConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
         "VACUUM favicons"
       )));
     }
     if (mShouldConvertIconPayloads) {
       mShouldConvertIconPayloads = false;
       nsFaviconService::ConvertUnsupportedPayloads(mMainConn);
     }
+    MigrateV48Frecencies();
   });
 
   // We are going to update the database, so everything from now on should be in
   // a transaction for performances.
   mozStorageTransaction transaction(mMainConn, false);
 
   if (databaseInitialized) {
     // Migration How-to:
@@ -1254,61 +1257,70 @@ Database::InitSchema(bool* aDatabaseMigr
 
       if (currentSchemaVersion < 47) {
         rv = MigrateV47Up();
         NS_ENSURE_SUCCESS(rv, rv);
       }
 
       // Firefox 61 uses schema version 47.
 
+      if (currentSchemaVersion < 48) {
+        rv = MigrateV48Up();
+        NS_ENSURE_SUCCESS(rv, rv);
+      }
+
+      // Firefox 62 uses schema version 48.
+
       // Schema Upgrades must add migration code here.
       // >>> IMPORTANT! <<<
       // NEVER MIX UP SYNC AND ASYNC EXECUTION IN MIGRATORS, YOU MAY LOCK THE
       // CONNECTION AND CAUSE FURTHER STEPS TO FAIL.
       // In case, set a bool and do the async work in the ScopeExit guard just
       // before the migration steps.
     }
   }
   else {
     // This is a new database, so we have to create all the tables and indices.
 
+    // moz_origins.
+    rv = mMainConn->ExecuteSimpleSQL(CREATE_MOZ_ORIGINS);
+    NS_ENSURE_SUCCESS(rv, rv);
+
     // moz_places.
     rv = mMainConn->ExecuteSimpleSQL(CREATE_MOZ_PLACES);
     NS_ENSURE_SUCCESS(rv, rv);
     rv = mMainConn->ExecuteSimpleSQL(CREATE_IDX_MOZ_PLACES_URL_HASH);
     NS_ENSURE_SUCCESS(rv, rv);
     rv = mMainConn->ExecuteSimpleSQL(CREATE_IDX_MOZ_PLACES_REVHOST);
     NS_ENSURE_SUCCESS(rv, rv);
     rv = mMainConn->ExecuteSimpleSQL(CREATE_IDX_MOZ_PLACES_VISITCOUNT);
     NS_ENSURE_SUCCESS(rv, rv);
     rv = mMainConn->ExecuteSimpleSQL(CREATE_IDX_MOZ_PLACES_FRECENCY);
     NS_ENSURE_SUCCESS(rv, rv);
     rv = mMainConn->ExecuteSimpleSQL(CREATE_IDX_MOZ_PLACES_LASTVISITDATE);
     NS_ENSURE_SUCCESS(rv, rv);
     rv = mMainConn->ExecuteSimpleSQL(CREATE_IDX_MOZ_PLACES_GUID);
     NS_ENSURE_SUCCESS(rv, rv);
+    rv = mMainConn->ExecuteSimpleSQL(CREATE_IDX_MOZ_PLACES_ORIGIN_ID);
+    NS_ENSURE_SUCCESS(rv, rv);
 
     // moz_historyvisits.
     rv = mMainConn->ExecuteSimpleSQL(CREATE_MOZ_HISTORYVISITS);
     NS_ENSURE_SUCCESS(rv, rv);
     rv = mMainConn->ExecuteSimpleSQL(CREATE_IDX_MOZ_HISTORYVISITS_PLACEDATE);
     NS_ENSURE_SUCCESS(rv, rv);
     rv = mMainConn->ExecuteSimpleSQL(CREATE_IDX_MOZ_HISTORYVISITS_FROMVISIT);
     NS_ENSURE_SUCCESS(rv, rv);
     rv = mMainConn->ExecuteSimpleSQL(CREATE_IDX_MOZ_HISTORYVISITS_VISITDATE);
     NS_ENSURE_SUCCESS(rv, rv);
 
     // moz_inputhistory.
     rv = mMainConn->ExecuteSimpleSQL(CREATE_MOZ_INPUTHISTORY);
     NS_ENSURE_SUCCESS(rv, rv);
 
-    // moz_hosts.
-    rv = mMainConn->ExecuteSimpleSQL(CREATE_MOZ_HOSTS);
-    NS_ENSURE_SUCCESS(rv, rv);
-
     // moz_bookmarks.
     rv = mMainConn->ExecuteSimpleSQL(CREATE_MOZ_BOOKMARKS);
     NS_ENSURE_SUCCESS(rv, rv);
     rv = mMainConn->ExecuteSimpleSQL(CREATE_MOZ_BOOKMARKS_DELETED);
     NS_ENSURE_SUCCESS(rv, rv);
     rv = mMainConn->ExecuteSimpleSQL(CREATE_IDX_MOZ_BOOKMARKS_PLACETYPE);
     NS_ENSURE_SUCCESS(rv, rv);
     rv = mMainConn->ExecuteSimpleSQL(CREATE_IDX_MOZ_BOOKMARKS_PARENTPOSITION);
@@ -1528,47 +1540,53 @@ Database::InitFunctions()
   rv = FrecencyNotificationFunction::create(mMainConn);
   NS_ENSURE_SUCCESS(rv, rv);
   rv = StoreLastInsertedIdFunction::create(mMainConn);
   NS_ENSURE_SUCCESS(rv, rv);
   rv = HashFunction::create(mMainConn);
   NS_ENSURE_SUCCESS(rv, rv);
   rv = GetQueryParamFunction::create(mMainConn);
   NS_ENSURE_SUCCESS(rv, rv);
+  rv = GetPrefixFunction::create(mMainConn);
+  NS_ENSURE_SUCCESS(rv, rv);
+  rv = GetHostAndPortFunction::create(mMainConn);
+  NS_ENSURE_SUCCESS(rv, rv);
+  rv = StripPrefixAndUserinfoFunction::create(mMainConn);
+  NS_ENSURE_SUCCESS(rv, rv);
+  rv = IsFrecencyDecayingFunction::create(mMainConn);
+  NS_ENSURE_SUCCESS(rv, rv);
 
   return NS_OK;
 }
 
 nsresult
 Database::InitTempEntities()
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   nsresult rv = mMainConn->ExecuteSimpleSQL(CREATE_HISTORYVISITS_AFTERINSERT_TRIGGER);
   NS_ENSURE_SUCCESS(rv, rv);
   rv = mMainConn->ExecuteSimpleSQL(CREATE_HISTORYVISITS_AFTERDELETE_TRIGGER);
   NS_ENSURE_SUCCESS(rv, rv);
 
-  // Add the triggers that update the moz_hosts table as necessary.
-  rv = mMainConn->ExecuteSimpleSQL(CREATE_UPDATEHOSTSINSERT_TEMP);
+  // Add the triggers that update the moz_origins table as necessary.
+  rv = mMainConn->ExecuteSimpleSQL(CREATE_UPDATEORIGINSINSERT_TEMP);
   NS_ENSURE_SUCCESS(rv, rv);
-  rv = mMainConn->ExecuteSimpleSQL(CREATE_UPDATEHOSTSINSERT_AFTERDELETE_TRIGGER);
+  rv = mMainConn->ExecuteSimpleSQL(CREATE_UPDATEORIGINSINSERT_AFTERDELETE_TRIGGER);
   NS_ENSURE_SUCCESS(rv, rv);
   rv = mMainConn->ExecuteSimpleSQL(CREATE_PLACES_AFTERINSERT_TRIGGER);
   NS_ENSURE_SUCCESS(rv, rv);
-  rv = mMainConn->ExecuteSimpleSQL(CREATE_UPDATEHOSTSDELETE_TEMP);
+  rv = mMainConn->ExecuteSimpleSQL(CREATE_UPDATEORIGINSDELETE_TEMP);
   NS_ENSURE_SUCCESS(rv, rv);
-  rv = mMainConn->ExecuteSimpleSQL(CREATE_UPDATEHOSTSDELETE_AFTERDELETE_TRIGGER);
+  rv = mMainConn->ExecuteSimpleSQL(CREATE_UPDATEORIGINSDELETE_AFTERDELETE_TRIGGER);
   NS_ENSURE_SUCCESS(rv, rv);
   rv = mMainConn->ExecuteSimpleSQL(CREATE_PLACES_AFTERDELETE_TRIGGER);
   NS_ENSURE_SUCCESS(rv, rv);
   rv = mMainConn->ExecuteSimpleSQL(CREATE_PLACES_AFTERUPDATE_FRECENCY_TRIGGER);
   NS_ENSURE_SUCCESS(rv, rv);
-  rv = mMainConn->ExecuteSimpleSQL(CREATE_PLACES_AFTERUPDATE_TYPED_TRIGGER);
-  NS_ENSURE_SUCCESS(rv, rv);
 
   rv = mMainConn->ExecuteSimpleSQL(CREATE_BOOKMARKS_FOREIGNCOUNT_AFTERDELETE_TRIGGER);
   NS_ENSURE_SUCCESS(rv, rv);
   rv = mMainConn->ExecuteSimpleSQL(CREATE_BOOKMARKS_FOREIGNCOUNT_AFTERINSERT_TRIGGER);
   NS_ENSURE_SUCCESS(rv, rv);
   rv = mMainConn->ExecuteSimpleSQL(CREATE_BOOKMARKS_FOREIGNCOUNT_AFTERUPDATE_TRIGGER);
   NS_ENSURE_SUCCESS(rv, rv);
 
@@ -1655,23 +1673,51 @@ Database::MigrateV32Up() {
     "WHERE host IN (SELECT host FROM moz_migrate_v32_temp) "
       "AND NOT EXISTS("
         "SELECT 1 FROM moz_places "
           "WHERE rev_host = get_unreversed_host(host || '.') || '.' "
              "OR rev_host = get_unreversed_host(host || '.') || '.www.' "
       "); "
   ), getter_AddRefs(deleteHostsStmt));
   NS_ENSURE_SUCCESS(rv, rv);
+
+#define HOST_TO_REVHOST_PREDICATE \
+  "rev_host = get_unreversed_host(host || '.') || '.' " \
+  "OR rev_host = get_unreversed_host(host || '.') || '.www.'"
+#define HOSTS_PREFIX_PRIORITY_FRAGMENT \
+  "SELECT CASE " \
+    "WHEN ( " \
+      "SELECT round(avg(substr(url,1,12) = 'https://www.')) FROM moz_places h " \
+      "WHERE (" HOST_TO_REVHOST_PREDICATE ") AND +h.typed = 1 " \
+    ") THEN 'https://www.' " \
+    "WHEN ( " \
+      "SELECT round(avg(substr(url,1,8) = 'https://')) FROM moz_places h " \
+      "WHERE (" HOST_TO_REVHOST_PREDICATE ") AND +h.typed = 1 " \
+    ") THEN 'https://' " \
+    "WHEN 1 = ( " \
+      "SELECT min(substr(url,1,4) = 'ftp:') FROM moz_places h " \
+      "WHERE (" HOST_TO_REVHOST_PREDICATE ") AND +h.typed = 1 " \
+    ") THEN 'ftp://' " \
+    "WHEN ( " \
+      "SELECT round(avg(substr(url,1,11) = 'http://www.')) FROM moz_places h " \
+      "WHERE (" HOST_TO_REVHOST_PREDICATE ") AND +h.typed = 1 " \
+    ") THEN 'www.' " \
+  "END "
+
   nsCOMPtr<mozIStorageAsyncStatement> updateHostsStmt;
   rv = mMainConn->CreateAsyncStatement(NS_LITERAL_CSTRING(
     "UPDATE moz_hosts "
     "SET prefix = (" HOSTS_PREFIX_PRIORITY_FRAGMENT ") "
     "WHERE host IN (SELECT host FROM moz_migrate_v32_temp) "
   ), getter_AddRefs(updateHostsStmt));
   NS_ENSURE_SUCCESS(rv, rv);
+
+#undef HOST_TO_REVHOST_PREDICATE
+#undef HOSTS_PREFIX_PRIORITY_FRAGMENT
+
   nsCOMPtr<mozIStorageAsyncStatement> dropTableStmt;
   rv = mMainConn->CreateAsyncStatement(NS_LITERAL_CSTRING(
     "DROP TABLE IF EXISTS moz_migrate_v32_temp"
   ), getter_AddRefs(dropTableStmt));
   NS_ENSURE_SUCCESS(rv, rv);
 
   mozIStorageBaseStatement *stmts[] = {
     expireOrphansStmt,
@@ -2245,16 +2291,162 @@ Database::MigrateV47Up() {
         "AND url = 'place:excludeItems=1' "
     ") "
   ));
   NS_ENSURE_SUCCESS(rv, rv);
   return NS_OK;
 }
 
 nsresult
+Database::MigrateV48Up() {
+  // Create and populate moz_origins.
+  nsCOMPtr<mozIStorageStatement> stmt;
+  nsresult rv = mMainConn->CreateStatement(NS_LITERAL_CSTRING(
+    "SELECT * FROM moz_origins; "
+  ), getter_AddRefs(stmt));
+  if (NS_FAILED(rv)) {
+    rv = mMainConn->ExecuteSimpleSQL(CREATE_MOZ_ORIGINS);
+    NS_ENSURE_SUCCESS(rv, rv);
+  }
+  rv = mMainConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+    "INSERT OR IGNORE INTO moz_origins (prefix, host, frecency) " \
+    "SELECT get_prefix(url), get_host_and_port(url), -1 " \
+    "FROM moz_places; "
+  ));
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  // Add and populate moz_places.origin_id.
+  rv = mMainConn->CreateStatement(NS_LITERAL_CSTRING(
+    "SELECT origin_id FROM moz_places; "
+  ), getter_AddRefs(stmt));
+  if (NS_FAILED(rv)) {
+    rv = mMainConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+      "ALTER TABLE moz_places " \
+      "ADD COLUMN origin_id INTEGER REFERENCES moz_origins(id); "
+    ));
+    NS_ENSURE_SUCCESS(rv, rv);
+  }
+  rv = mMainConn->ExecuteSimpleSQL(CREATE_IDX_MOZ_PLACES_ORIGIN_ID);
+  NS_ENSURE_SUCCESS(rv, rv);
+  rv = mMainConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+    "UPDATE moz_places " \
+    "SET origin_id = ( "
+      "SELECT id FROM moz_origins " \
+      "WHERE prefix = get_prefix(url) AND host = get_host_and_port(url) " \
+    "); "
+  ));
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  // Setting this pref will cause InitSchema to begin async migration of
+  // frecencies to moz_origins.  The reason we don't defer the other steps
+  // above, like we do this one here, is because we want to make sure that the
+  // main data in moz_origins, prefix and host, are coherent in relation to
+  // moz_places.
+  Unused << Preferences::SetBool(PREF_MIGRATE_V48_FRECENCIES, true);
+
+  // From this point on, nobody should use moz_hosts again.  Empty it so that we
+  // don't leak the user's history, but don't remove it yet so that the user can
+  // downgrade.
+  rv = mMainConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+    "DELETE FROM moz_hosts; "
+  ));
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  return NS_OK;
+}
+
+namespace {
+
+class MigrateV48FrecenciesRunnable final : public Runnable
+{
+public:
+  NS_DECL_NSIRUNNABLE
+  explicit MigrateV48FrecenciesRunnable(mozIStorageConnection* aDBConn);
+private:
+  nsCOMPtr<mozIStorageConnection> mDBConn;
+};
+
+MigrateV48FrecenciesRunnable::MigrateV48FrecenciesRunnable(mozIStorageConnection* aDBConn)
+  : Runnable("places::MigrateV48FrecenciesRunnable")
+  , mDBConn(aDBConn)
+{
+}
+
+NS_IMETHODIMP
+MigrateV48FrecenciesRunnable::Run()
+{
+  if (NS_IsMainThread()) {
+    // Migration done.  Clear the pref.
+    Unused << Preferences::ClearUser(PREF_MIGRATE_V48_FRECENCIES);
+    return NS_OK;
+  }
+
+  nsCOMPtr<mozIStorageStatement> selectStmt;
+  nsresult rv = mDBConn->CreateStatement(NS_LITERAL_CSTRING(
+    "SELECT id FROM moz_origins " \
+    "WHERE frecency = -1 " \
+    "ORDER BY id ASC " \
+    "LIMIT 200; "
+  ), getter_AddRefs(selectStmt));
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  nsCOMPtr<mozIStorageStatement> updateStmt;
+  rv = mDBConn->CreateStatement(NS_LITERAL_CSTRING(
+    "UPDATE moz_origins " \
+    "SET frecency = ( " \
+      "SELECT MAX(frecency) " \
+      "FROM moz_places " \
+      "WHERE moz_places.origin_id = moz_origins.id " \
+    ") " \
+    "WHERE id = :id; "
+  ), getter_AddRefs(updateStmt));
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  mozStorageStatementScoper updateScoper(updateStmt);
+
+  // We should do the work in chunks, or the wal journal may grow too much.
+  bool hasResult;
+  uint8_t count = 0;
+  for (; NS_SUCCEEDED(selectStmt->ExecuteStep(&hasResult)) && hasResult; ++count) {
+    int64_t id = selectStmt->AsInt64(0);
+    rv = updateStmt->BindInt64ByName(NS_LITERAL_CSTRING("id"), id);
+    NS_ENSURE_SUCCESS(rv, rv);
+    rv = updateStmt->Execute();
+    NS_ENSURE_SUCCESS(rv, rv);
+  }
+
+  if (count == 200) {
+    // There are more results to handle. Re-dispatch to the same thread for the
+    // next chunk.
+    return NS_DispatchToCurrentThread(this);
+  }
+
+  // Re-dispatch to the main-thread to flip the migration pref.
+  return NS_DispatchToMainThread(this);
+}
+
+} // namespace
+
+void
+Database::MigrateV48Frecencies()
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  if (!Preferences::GetBool(PREF_MIGRATE_V48_FRECENCIES)) {
+    return;
+  }
+
+  RefPtr<MigrateV48FrecenciesRunnable> runnable =
+    new MigrateV48FrecenciesRunnable(mMainConn);
+  nsCOMPtr<nsIEventTarget> target = do_GetInterface(mMainConn);
+  MOZ_ASSERT(target);
+  Unused << target->Dispatch(runnable, NS_DISPATCH_NORMAL);
+}
+
+nsresult
 Database::GetItemsWithAnno(const nsACString& aAnnoName, int32_t aItemType,
                            nsTArray<int64_t>& aItemIds)
 {
   nsCOMPtr<mozIStorageStatement> stmt;
   nsresult rv = mMainConn->CreateStatement(NS_LITERAL_CSTRING(
     "SELECT b.id FROM moz_items_annos a "
     "JOIN moz_anno_attributes n ON n.id = a.anno_attribute_id "
     "JOIN moz_bookmarks b ON b.id = a.item_id "
--- a/toolkit/components/places/Database.h
+++ b/toolkit/components/places/Database.h
@@ -14,17 +14,17 @@
 #include "mozilla/storage/StatementCache.h"
 #include "mozilla/Attributes.h"
 #include "nsIEventTarget.h"
 #include "Shutdown.h"
 #include "nsCategoryCache.h"
 
 // This is the schema version. Update it at any schema change and add a
 // corresponding migrateVxx method below.
-#define DATABASE_SCHEMA_VERSION 47
+#define DATABASE_SCHEMA_VERSION 48
 
 // Fired after Places inited.
 #define TOPIC_PLACES_INIT_COMPLETE "places-init-complete"
 // This topic is received when the profile is about to be lost.  Places does
 // initial shutdown work and notifies TOPIC_PLACES_SHUTDOWN to all listeners.
 // Any shutdown work that requires the Places APIs should happen here.
 #define TOPIC_PROFILE_CHANGE_TEARDOWN "profile-change-teardown"
 // Fired when Places is shutting down.  Any code should stop accessing Places
@@ -333,16 +333,19 @@ protected:
   nsresult MigrateV40Up();
   nsresult MigrateV41Up();
   nsresult MigrateV42Up();
   nsresult MigrateV43Up();
   nsresult MigrateV44Up();
   nsresult MigrateV45Up();
   nsresult MigrateV46Up();
   nsresult MigrateV47Up();
+  nsresult MigrateV48Up();
+
+  void MigrateV48Frecencies();
 
   nsresult UpdateBookmarkRootTitles();
 
   friend class ConnectionShutdownBlocker;
 
   int64_t CreateMobileRoot();
   nsresult GetItemsWithAnno(const nsACString& aAnnoName, int32_t aItemType,
                             nsTArray<int64_t>& aItemIds);
--- a/toolkit/components/places/PlacesSearchAutocompleteProvider.jsm
+++ b/toolkit/components/places/PlacesSearchAutocompleteProvider.jsm
@@ -226,18 +226,20 @@ var PlacesSearchAutocompleteProvider = O
    *           iconUrl: Icon associated to the match, or null if not available.
    *         }
    */
   async findMatchByToken(searchToken) {
     await this.ensureInitialized();
 
     // Match at the beginning for now.  In the future, an "options" argument may
     // allow the matching behavior to be tuned.
-    return SearchAutocompleteProviderInternal.priorityMatches
-                                             .find(m => m.token.startsWith(searchToken));
+    return SearchAutocompleteProviderInternal.priorityMatches.find(m => {
+      return m.token.startsWith(searchToken) ||
+             m.token.startsWith("www." + searchToken);
+    });
   },
 
   /**
    * Matches a given search string to an item that should be included by
    * components wishing to search using search engine aliases, like
    * autocomple.
    *
    * @param searchToken
--- a/toolkit/components/places/SQLFunctions.cpp
+++ b/toolkit/components/places/SQLFunctions.cpp
@@ -27,16 +27,18 @@
 using namespace mozilla::storage;
 
 ////////////////////////////////////////////////////////////////////////////////
 //// Anonymous Helpers
 
 namespace {
 
   typedef nsACString::const_char_iterator const_char_iterator;
+  typedef nsACString::size_type size_type;
+  typedef nsACString::char_type char_type;
 
   /**
    * Scan forward through UTF-8 text until the next potential character that
    * could match a given codepoint when lower-cased (false positives are okay).
    * This avoids having to actually parse the UTF-8 text, which is slow.
    *
    * @param aStart
    *        An iterator pointing to the first character position considered.
@@ -267,16 +269,121 @@ namespace {
       mResult->SetAsAString(aValue);
       return false;
     }
   private:
     const nsCString& mParamName;
     nsVariant* mResult;
   };
 
+  /**
+   * Gets the length of the prefix in a URI spec.  "Prefix" is defined to be the
+   * scheme, colon, and, if present, two slashes.
+   *
+   * Examples:
+   *
+   *   http://example.com
+   *   ~~~~~~~
+   *   => length == 7
+   *
+   *   foo:example
+   *   ~~~~
+   *   => length == 4
+   *
+   *   not a spec
+   *   => length == 0
+   *
+   * @param  aSpec
+   *         A URI spec, or a string that may be a URI spec.
+   * @return The length of the prefix in the spec.  If there isn't a prefix,
+   *         returns 0.
+   */
+  static
+  MOZ_ALWAYS_INLINE size_type
+  getPrefixLength(const nsACString &aSpec)
+  {
+    // To keep the search bounded, look at 64 characters at most.  The longest
+    // IANA schemes are ~30, so double that and round up to a nice number.
+    size_type length = std::min(static_cast<size_type>(64), aSpec.Length());
+    for (size_type i = 0; i < length; ++i) {
+      if (aSpec[i] == static_cast<char_type>(':')) {
+        // Found the ':'.  Now skip past "//", if present.
+        if (i + 2 < aSpec.Length() &&
+            aSpec[i + 1] == static_cast<char_type>('/') &&
+            aSpec[i + 2] == static_cast<char_type>('/')) {
+          i += 2;
+        }
+        return i + 1;
+      }
+    }
+    return 0;
+  }
+
+  /**
+   * Gets the index in a URI spec of the host and port substring and optionally
+   * its length.
+   *
+   * Examples:
+   *
+   *   http://example.com/
+   *          ~~~~~~~~~~~
+   *   => index == 7, length == 11
+   *
+   *   http://example.com:8888/
+   *          ~~~~~~~~~~~~~~~~
+   *   => index == 7, length == 16
+   *
+   *   http://user:pass@example.com/
+   *                    ~~~~~~~~~~~
+   *   => index == 17, length == 11
+   *
+   *   foo:example
+   *       ~~~~~~~
+   *   => index == 4, length == 7
+   *
+   *   not a spec
+   *   ~~~~~~~~~~
+   *   => index == 0, length == 10
+   *
+   * @param  aSpec
+   *         A URI spec, or a string that may be a URI spec.
+   * @param  _hostAndPortLength
+   *         The length of the host and port substring is returned through this
+   *         param.  Pass null if you don't care.
+   * @return The length of the host and port substring in the spec.  If aSpec
+   *         doesn't look like a URI, then the entire aSpec is assumed to be a
+   *         "host and port", and this returns 0, and _hostAndPortLength will be
+   *         the length of aSpec.
+   */
+  static
+  MOZ_ALWAYS_INLINE size_type
+  indexOfHostAndPort(const nsACString &aSpec,
+                     size_type *_hostAndPortLength)
+  {
+    size_type index = getPrefixLength(aSpec);
+    size_type i = index;
+    for (; i < aSpec.Length(); ++i) {
+      // RFC 3986 (URIs): The origin ("authority") is terminated by '/', '?', or
+      // '#' (or the end of the URI).
+      if (aSpec[i] == static_cast<char_type>('/') ||
+          aSpec[i] == static_cast<char_type>('?') ||
+          aSpec[i] == static_cast<char_type>('#')) {
+        break;
+      }
+      // RFC 3986: '@' marks the end of the userinfo component.
+      if (aSpec[i] == static_cast<char_type>('@')) {
+        index = i + 1;
+      }
+    }
+    if (_hostAndPortLength) {
+      *_hostAndPortLength = i - index;
+    }
+    return index;
+  }
+
 } // End anonymous namespace
 
 namespace mozilla {
 namespace places {
 
 ////////////////////////////////////////////////////////////////////////////////
 //// AutoComplete Matching Function
 
@@ -320,20 +427,16 @@ namespace places {
     if (StringBeginsWith(fixedSpec, NS_LITERAL_CSTRING("http://"))) {
       fixedSpec.Rebind(fixedSpec, 7);
     } else if (StringBeginsWith(fixedSpec, NS_LITERAL_CSTRING("https://"))) {
       fixedSpec.Rebind(fixedSpec, 8);
     } else if (StringBeginsWith(fixedSpec, NS_LITERAL_CSTRING("ftp://"))) {
       fixedSpec.Rebind(fixedSpec, 6);
     }
 
-    if (StringBeginsWith(fixedSpec, NS_LITERAL_CSTRING("www."))) {
-      fixedSpec.Rebind(fixedSpec, 4);
-    }
-
     return fixedSpec;
   }
 
   /* static */
   bool
   MatchAutoCompleteFunction::findAnywhere(const nsDependentCSubstring &aToken,
                                           const nsACString &aSourceString)
   {
@@ -1113,10 +1216,179 @@ namespace places {
     NS_ENSURE_SUCCESS(rv, rv);
     rv = result->SetAsInt64(hash);
     NS_ENSURE_SUCCESS(rv, rv);
 
     result.forget(_result);
     return NS_OK;
   }
 
+////////////////////////////////////////////////////////////////////////////////
+//// Get prefix function
+
+  /* static */
+  nsresult
+  GetPrefixFunction::create(mozIStorageConnection *aDBConn)
+  {
+    RefPtr<GetPrefixFunction> function = new GetPrefixFunction();
+    nsresult rv = aDBConn->CreateFunction(
+      NS_LITERAL_CSTRING("get_prefix"), 1, function
+    );
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    return NS_OK;
+  }
+
+  NS_IMPL_ISUPPORTS(
+    GetPrefixFunction,
+    mozIStorageFunction
+  )
+
+  NS_IMETHODIMP
+  GetPrefixFunction::OnFunctionCall(mozIStorageValueArray *aArgs,
+                                    nsIVariant **_result)
+  {
+    MOZ_ASSERT(aArgs);
+
+    uint32_t numArgs;
+    nsresult rv = aArgs->GetNumEntries(&numArgs);
+    NS_ENSURE_SUCCESS(rv, rv);
+    MOZ_ASSERT(numArgs == 1);
+
+    nsDependentCString spec(getSharedUTF8String(aArgs, 0));
+
+    RefPtr<nsVariant> result = new nsVariant();
+    result->SetAsACString(Substring(spec, 0, getPrefixLength(spec)));
+    result.forget(_result);
+    return NS_OK;
+  }
+
+////////////////////////////////////////////////////////////////////////////////
+//// Get host and port function
+
+  /* static */
+  nsresult
+  GetHostAndPortFunction::create(mozIStorageConnection *aDBConn)
+  {
+    RefPtr<GetHostAndPortFunction> function = new GetHostAndPortFunction();
+    nsresult rv = aDBConn->CreateFunction(
+      NS_LITERAL_CSTRING("get_host_and_port"), 1, function
+    );
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    return NS_OK;
+  }
+
+  NS_IMPL_ISUPPORTS(
+    GetHostAndPortFunction,
+    mozIStorageFunction
+  )
+
+  NS_IMETHODIMP
+  GetHostAndPortFunction::OnFunctionCall(mozIStorageValueArray *aArgs,
+                                         nsIVariant **_result)
+  {
+    MOZ_ASSERT(aArgs);
+
+    uint32_t numArgs;
+    nsresult rv = aArgs->GetNumEntries(&numArgs);
+    NS_ENSURE_SUCCESS(rv, rv);
+    MOZ_ASSERT(numArgs == 1);
+
+    nsDependentCString spec(getSharedUTF8String(aArgs, 0));
+
+    RefPtr<nsVariant> result = new nsVariant();
+
+    size_type length;
+    size_type index = indexOfHostAndPort(spec, &length);
+    result->SetAsACString(Substring(spec, index, length));
+    result.forget(_result);
+    return NS_OK;
+  }
+
+////////////////////////////////////////////////////////////////////////////////
+//// Strip prefix and userinfo function
+
+  /* static */
+  nsresult
+  StripPrefixAndUserinfoFunction::create(mozIStorageConnection *aDBConn)
+  {
+    RefPtr<StripPrefixAndUserinfoFunction> function =
+      new StripPrefixAndUserinfoFunction();
+    nsresult rv = aDBConn->CreateFunction(
+      NS_LITERAL_CSTRING("strip_prefix_and_userinfo"), 1, function
+    );
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    return NS_OK;
+  }
+
+  NS_IMPL_ISUPPORTS(
+    StripPrefixAndUserinfoFunction,
+    mozIStorageFunction
+  )
+
+  NS_IMETHODIMP
+  StripPrefixAndUserinfoFunction::OnFunctionCall(mozIStorageValueArray *aArgs,
+                                                 nsIVariant **_result)
+  {
+    MOZ_ASSERT(aArgs);
+
+    uint32_t numArgs;
+    nsresult rv = aArgs->GetNumEntries(&numArgs);
+    NS_ENSURE_SUCCESS(rv, rv);
+    MOZ_ASSERT(numArgs == 1);
+
+    nsDependentCString spec(getSharedUTF8String(aArgs, 0));
+
+    RefPtr<nsVariant> result = new nsVariant();
+
+    size_type index = indexOfHostAndPort(spec, NULL);
+    result->SetAsACString(Substring(spec, index, spec.Length() - index));
+    result.forget(_result);
+    return NS_OK;
+  }
+
+////////////////////////////////////////////////////////////////////////////////
+//// Is frecency decaying function
+
+  /* static */
+  nsresult
+  IsFrecencyDecayingFunction::create(mozIStorageConnection *aDBConn)
+  {
+    RefPtr<IsFrecencyDecayingFunction> function =
+      new IsFrecencyDecayingFunction();
+    nsresult rv = aDBConn->CreateFunction(
+      NS_LITERAL_CSTRING("is_frecency_decaying"), 0, function
+    );
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    return NS_OK;
+  }
+
+  NS_IMPL_ISUPPORTS(
+    IsFrecencyDecayingFunction,
+    mozIStorageFunction
+  )
+
+  NS_IMETHODIMP
+  IsFrecencyDecayingFunction::OnFunctionCall(mozIStorageValueArray *aArgs,
+                                             nsIVariant **_result)
+  {
+    MOZ_ASSERT(aArgs);
+
+    uint32_t numArgs;
+    nsresult rv = aArgs->GetNumEntries(&numArgs);
+    NS_ENSURE_SUCCESS(rv, rv);
+    MOZ_ASSERT(numArgs == 0);
+
+    const nsNavHistory *navHistory = nsNavHistory::GetConstHistoryService();
+    NS_ENSURE_STATE(navHistory);
+
+    RefPtr<nsVariant> result = new nsVariant();
+    rv = result->SetAsBool(navHistory->IsFrecencyDecaying());
+    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
@@ -440,12 +440,133 @@ public:
    *        The database connection to register with.
    */
   static nsresult create(mozIStorageConnection *aDBConn);
 private:
   ~GetQueryParamFunction() {}
 };
 
 
+////////////////////////////////////////////////////////////////////////////////
+//// Get prefix function
+
+/**
+ * Gets the length of the prefix in a URL.  "Prefix" is defined to be the
+ * scheme, colon, and, if present, two slashes.
+ *
+ * @param url
+ *        A URL, or a string that may be a URL.
+ * @return
+ *        If `url` is actually a URL and has a prefix, then this returns the
+ *        prefix.  Otherwise this returns an empty string.
+ */
+class GetPrefixFunction final : public mozIStorageFunction
+{
+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);
+private:
+  ~GetPrefixFunction() {}
+};
+
+
+////////////////////////////////////////////////////////////////////////////////
+//// Get host and port function
+
+/**
+ * Gets the host and port substring of a URL.
+ *
+ * @param url
+ *        A URL, or a string that may be a URL.
+ * @return
+ *        If `url` is actually a URL, or if it's a URL without the prefix, then
+ *        this returns the host and port substring of the URL.  Otherwise, this
+ *        returns `url` itself.
+ */
+class GetHostAndPortFunction final : public mozIStorageFunction
+{
+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);
+private:
+  ~GetHostAndPortFunction() {}
+};
+
+
+////////////////////////////////////////////////////////////////////////////////
+//// Strip prefix function
+
+/**
+ * Gets the part of a URL after its prefix and userinfo; i.e., the substring
+ * starting at the host.
+ *
+ * @param url
+ *        A URL, or a string that may be a URL.
+ * @return
+ *        If `url` is actually a URL, or if it's a URL without the prefix, then
+ *        this returns the substring starting at the host.  Otherwise, this
+ *        returns `url` itself.
+ */
+class StripPrefixAndUserinfoFunction final : public mozIStorageFunction
+{
+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);
+private:
+  ~StripPrefixAndUserinfoFunction() {}
+};
+
+
+////////////////////////////////////////////////////////////////////////////////
+//// Is frecency decaying function
+
+/**
+ * Returns nsNavHistory::IsFrecencyDecaying().
+ *
+ * @return
+ *        True if frecency is currently decaying and false otherwise.
+ */
+class IsFrecencyDecayingFunction final : public mozIStorageFunction
+{
+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);
+private:
+  ~IsFrecencyDecayingFunction() {}
+};
+
+
 } // namespace places
 } // namespace mozilla
 
 #endif // mozilla_places_SQLFunctions_h_
--- a/toolkit/components/places/nsNavHistory.cpp
+++ b/toolkit/components/places/nsNavHistory.cpp
@@ -11,16 +11,17 @@
 
 #include "nsNavHistory.h"
 
 #include "mozIPlacesAutoComplete.h"
 #include "nsNavBookmarks.h"
 #include "nsAnnotationService.h"
 #include "nsFaviconService.h"
 #include "nsPlacesMacros.h"
+#include "nsPlacesTriggers.h"
 #include "DateTimeFormat.h"
 #include "History.h"
 #include "Helpers.h"
 
 #include "nsTArray.h"
 #include "nsCollationCID.h"
 #include "nsNetUtil.h"
 #include "nsPrintfCString.h"
@@ -103,17 +104,16 @@ using namespace mozilla::places;
 #define PREF_FREC_UNVISITED_BOOKMARK_BONUS_DEF  140
 #define PREF_FREC_UNVISITED_TYPED_BONUS         "places.frecency.unvisitedTypedBonus"
 #define PREF_FREC_UNVISITED_TYPED_BONUS_DEF     200
 #define PREF_FREC_RELOAD_VISIT_BONUS            "places.frecency.reloadVisitBonus"
 #define PREF_FREC_RELOAD_VISIT_BONUS_DEF        0
 
 // This is a 'hidden' pref for the purposes of unit tests.
 #define PREF_FREC_DECAY_RATE     "places.frecency.decayRate"
-#define PREF_FREC_DECAY_RATE_DEF 0.975f
 
 // In order to avoid calling PR_now() too often we use a cached "now" value
 // for repeating stuff.  These are milliseconds between "now" cache refreshes.
 #define RENEW_CACHED_NOW_TIMEOUT ((int32_t)3 * PR_MSEC_PER_SEC)
 
 // character-set annotation
 #define CHARSET_ANNO NS_LITERAL_CSTRING("URIProperties/characterSet")
 
@@ -273,16 +273,17 @@ nsNavHistory::nsNavHistory()
   , mBatchDBTransaction(nullptr)
   , mCachedNow(0)
   , mRecentTyped(RECENT_EVENTS_INITIAL_CACHE_LENGTH)
   , mRecentLink(RECENT_EVENTS_INITIAL_CACHE_LENGTH)
   , mRecentBookmark(RECENT_EVENTS_INITIAL_CACHE_LENGTH)
   , mEmbedVisits(EMBED_VISITS_INITIAL_CACHE_LENGTH)
   , mHistoryEnabled(true)
   , mNumVisitsForFrecency(10)
+  , mDecayFrecencyPendingCount(0)
   , mTagsFolder(-1)
   , mDaysOfHistory(-1)
   , mLastCachedStartOfDay(INT64_MAX)
   , mLastCachedEndOfDay(0)
   , mCanNotify(true)
 #ifdef XP_WIN
   , mCryptoProviderInitialized(false)
 #endif
@@ -2460,47 +2461,41 @@ nsNavHistory::Observe(nsISupports *aSubj
   else if (strcmp(aTopic, TOPIC_IDLE_DAILY) == 0) {
     (void)DecayFrecency();
   }
 
   return NS_OK;
 }
 
 
-namespace {
-
-class DecayFrecencyCallback : public AsyncStatementTelemetryTimer
+class PlacesDecayFrecencyCallback : public AsyncStatementTelemetryTimer
 {
 public:
-  DecayFrecencyCallback()
+  PlacesDecayFrecencyCallback()
     : AsyncStatementTelemetryTimer(Telemetry::PLACES_IDLE_FRECENCY_DECAY_TIME_MS)
   {
   }
 
   NS_IMETHOD HandleCompletion(uint16_t aReason) override
   {
     (void)AsyncStatementTelemetryTimer::HandleCompletion(aReason);
-    if (aReason == REASON_FINISHED) {
-      nsNavHistory *navHistory = nsNavHistory::GetHistoryService();
-      NS_ENSURE_STATE(navHistory);
-      navHistory->NotifyManyFrecenciesChanged();
-    }
+    nsNavHistory *navHistory = nsNavHistory::GetHistoryService();
+    NS_ENSURE_STATE(navHistory);
+    navHistory->DecayFrecencyCompleted(aReason);
     return NS_OK;
   }
 };
 
-} // namespace
-
 nsresult
 nsNavHistory::DecayFrecency()
 {
   nsresult rv = FixInvalidFrecencies();
   NS_ENSURE_SUCCESS(rv, rv);
 
-  float decayRate = Preferences::GetFloat(PREF_FREC_DECAY_RATE, PREF_FREC_DECAY_RATE_DEF);
+  float decayRate = Preferences::GetFloat(PREF_FREC_DECAY_RATE, FRECENCY_DECAY_RATE);
 
   // Globally decay places frecency rankings to estimate reduced frecency
   // values of pages that haven't been visited for a while, i.e., they do
   // not get an updated frecency.  A scaling factor of .975 results in .5 the
   // original value after 28 days.
   // When changing the scaling factor, ensure that the barrier in
   // moz_places_afterupdate_frecency_trigger still ignores these changes.
   nsCOMPtr<mozIStorageAsyncStatement> decayFrecency = mDB->GetAsyncStatement(
@@ -2531,24 +2526,42 @@ nsNavHistory::DecayFrecency()
     return NS_ERROR_UNEXPECTED;
   }
   mozIStorageBaseStatement *stmts[] = {
     decayFrecency.get(),
     decayAdaptive.get(),
     deleteAdaptive.get()
   };
   nsCOMPtr<mozIStoragePendingStatement> ps;
-  RefPtr<DecayFrecencyCallback> cb = new DecayFrecencyCallback();
+  RefPtr<PlacesDecayFrecencyCallback> cb = new PlacesDecayFrecencyCallback();
   rv = conn->ExecuteAsync(stmts, ArrayLength(stmts), cb,
                                      getter_AddRefs(ps));
   NS_ENSURE_SUCCESS(rv, rv);
 
+  mDecayFrecencyPendingCount++;
+
   return NS_OK;
 }
 
+void
+nsNavHistory::DecayFrecencyCompleted(uint16_t reason)
+{
+  MOZ_ASSERT(mDecayFrecencyPendingCount > 0);
+  mDecayFrecencyPendingCount--;
+  if (mozIStorageStatementCallback::REASON_FINISHED == reason) {
+    NotifyManyFrecenciesChanged();
+  }
+}
+
+bool
+nsNavHistory::IsFrecencyDecaying() const
+{
+  return mDecayFrecencyPendingCount > 0;
+}
+
 
 // Query stuff *****************************************************************
 
 // Helper class for QueryToSelectClause
 //
 // This class helps to build part of the WHERE clause.
 
 class ConditionBuilder
--- a/toolkit/components/places/nsNavHistory.h
+++ b/toolkit/components/places/nsNavHistory.h
@@ -60,30 +60,32 @@
 
 // The preference we watch to know when the mobile bookmarks folder is filled by
 // sync.
 #define MOBILE_BOOKMARKS_PREF "browser.bookmarks.showMobileBookmarks"
 
 // The guid of the mobile bookmarks virtual query.
 #define MOBILE_BOOKMARKS_VIRTUAL_GUID "mobile____v"
 
-class nsNavHistory;
-class QueryKeyValuePair;
+class nsIAutoCompleteController;
 class nsIEffectiveTLDService;
 class nsIIDNService;
+class nsNavHistory;
+class PlacesDecayFrecencyCallback;
 class PlacesSQLQueryBuilder;
-class nsIAutoCompleteController;
+class QueryKeyValuePair;
 
 // nsNavHistory
 
 class nsNavHistory final : public nsSupportsWeakReference
                          , public nsINavHistoryService
                          , public nsIObserver
                          , public mozIStorageVacuumParticipant
 {
+  friend class PlacesDecayFrecencyCallback;
   friend class PlacesSQLQueryBuilder;
 
 public:
   nsNavHistory();
 
   NS_DECL_THREADSAFE_ISUPPORTS
   NS_DECL_NSINAVHISTORYSERVICE
   NS_DECL_NSIOBSERVER
@@ -457,16 +459,23 @@ public:
    */
   void DispatchFrecencyChangedNotification(const nsACString& aSpec,
                                            int32_t aNewFrecency,
                                            const nsACString& aGUID,
                                            bool aHidden,
                                            PRTime aLastVisitDate) const;
 
   /**
+   * Returns true if frecency is currently being decayed.
+   *
+   * @return True if frecency is being decayed, false if not.
+   */
+  bool IsFrecencyDecaying() 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);
 
@@ -614,16 +623,19 @@ protected:
   int32_t mPermRedirectVisitBonus;
   int32_t mTempRedirectVisitBonus;
   int32_t mRedirectSourceVisitBonus;
   int32_t mDefaultVisitBonus;
   int32_t mUnvisitedBookmarkBonus;
   int32_t mUnvisitedTypedBonus;
   int32_t mReloadVisitBonus;
 
+  void DecayFrecencyCompleted(uint16_t reason);
+  uint32_t mDecayFrecencyPendingCount;
+
   // in nsNavHistoryQuery.cpp
   nsresult TokensToQuery(const nsTArray<QueryKeyValuePair>& aTokens,
                          nsNavHistoryQuery* aQuery,
                          nsNavHistoryQueryOptions* aOptions);
 
   int64_t mTagsFolder;
 
   int32_t mDaysOfHistory;
--- a/toolkit/components/places/nsPlacesTriggers.h
+++ b/toolkit/components/places/nsPlacesTriggers.h
@@ -116,30 +116,33 @@
     "DELETE FROM moz_icons " \
     "WHERE fixed_icon_url_hash = hash(fixup_url(OLD.host || '/favicon.ico')) " \
       "AND fixup_url(icon_url) = fixup_url(OLD.host || '/favicon.ico') "\
       "AND NOT EXISTS (SELECT 1 FROM moz_origins WHERE host = OLD.host " \
                                                    "OR host = fixup_url(OLD.host)); " \
   "END" \
 )
 
+#define FRECENCY_DECAY_RATE 0.975f
+#define FRECENCY_DECAY_RATE_STR "0.975"
+
 // This trigger keeps frecencies in the moz_origins table in sync with
 // frecencies in moz_places.  However, we skip this when frecency changes are
 // due to frecency decay since (1) decay updates all frecencies at once, so this
 // trigger would run for each moz_place, which would be expensive; and (2) decay
 // does not change the ordering of frecencies since all frecencies decay by the
 // same percentage.
 #define CREATE_PLACES_AFTERUPDATE_FRECENCY_TRIGGER NS_LITERAL_CSTRING( \
   "CREATE TEMP TRIGGER moz_places_afterupdate_frecency_trigger " \
   "AFTER UPDATE OF frecency ON moz_places FOR EACH ROW " \
   "WHEN NEW.frecency >= 0 AND NOT ( " \
     "OLD.frecency > 0 " \
     "AND is_frecency_decaying() " \
     "AND NEW.frecency < OLD.frecency " \
-    "AND (OLD.frecency - NEW.frecency) / OLD.frecency <= 0.975 " \
+    "AND (OLD.frecency - NEW.frecency) / OLD.frecency <= " FRECENCY_DECAY_RATE_STR \
   ") " \
   "BEGIN " \
     "UPDATE moz_origins " \
     "SET frecency = ( " \
       "SELECT IFNULL(MAX(frecency), 0) " \
       "FROM moz_places " \
       "WHERE moz_places.origin_id = moz_origins.id " \
     ") " \
--- a/toolkit/components/search/nsSearchService.js
+++ b/toolkit/components/search/nsSearchService.js
@@ -1071,20 +1071,16 @@ function EngineURL(aType, aMethod, aTemp
       break;
     default:
       FAIL("new EngineURL: template uses invalid scheme!", Cr.NS_ERROR_FAILURE);
   }
 
   // If no resultDomain was specified in the engine definition file, use the
   // host from the template.
   this.resultDomain = aResultDomain || templateURI.host;
-  // We never want to return a "www." prefix, so eventually strip it.
-  if (this.resultDomain.startsWith("www.")) {
-    this.resultDomain = this.resultDomain.substr(4);
-  }
 }
 EngineURL.prototype = {
 
   addParam: function SRCH_EURL_addParam(aName, aValue, aPurpose) {
     this.params.push(new QueryParameter(aName, aValue, aPurpose));
   },
 
   // Note: This method requires that aObj has a unique name or the previous MozParams entry with