Bug 977177 - Move favicons to a separate store. r=adw draft
authorMarco Bonardo <mbonardo@mozilla.com>
Mon, 14 Nov 2016 16:22:46 +0100
changeset 561149 4d25966596dcdf63c9c872425c5bf147406d25ac
parent 560921 f40e24f40b4c4556944c762d4764eace261297f5
child 561150 fa49c4a81d6ab6b34a2f19ee4175e889a6e9d734
push id53651
push usermak77@bonardo.net
push dateWed, 12 Apr 2017 09:15:32 +0000
reviewersadw
bugs977177
milestone55.0a1
Bug 977177 - Move favicons to a separate store. r=adw This patch moves favicons blobs to a separate database names favicons.sqlite. The dabatase is then ATTACHED to the main Places connection, so that its tables can be used as if they were all part of the same database. The favicons.database contains 3 tables: 1. moz_pages_w_icons This is the way to join with moz_places, through page_url_hash and page_url. We are not using the place id to avoid possible mismatches between places.sqlite and favicons.sqlite. This way the database is "portable" and reusable even if places.sqlite changes. 2. moz_icons Contains icons payloads, each payload can either be an SVG or a PNG. These are the only stored formats, any other format is rescaled and converted to PNG. ICO files are split into single frames and stored as multiple PNGs. SVG are distinguishable through width == UINT16_MAX In future the table will also contain mask-icon color for SVG and average color for PNGs. The fixed_icon_url_hash is "fixed" to allow quickly fetch root icons, that means icons like "domain/favicon.ico" that can also be reused for any page under that domain. 3. moz_icons_to_pages This is the relation table between icons and pages. Each page can have multiple icons, each icon can be used by multiple pages. There is a FOREIGN_KEY constraint between this (child) table and icons or pages (parents), so that it's not possible to insert non-existing ids in this table, and if an entry is removed from a parent table, the relation will be automatically removed from here. Note though that removing from the relation table won't remove from the parent tables. Since the relations are now many-many, it's no more possible to simply join places with the icons table and obtain a single icon, thus it's suggested that consumers go through the "page-icon" protocol. The migration process from the old favicons table is async and interruptible, it will be restarted along with the favicons service until the temp preference places.favicons.convertPayloads is set to true. MozReview-Commit-ID: CUCoL9smRyt
dom/push/PushRecord.jsm
toolkit/components/places/Database.cpp
toolkit/components/places/Database.h
toolkit/components/places/FaviconHelpers.cpp
toolkit/components/places/FaviconHelpers.h
toolkit/components/places/SQLFunctions.cpp
toolkit/components/places/nsFaviconService.cpp
toolkit/components/places/nsFaviconService.h
toolkit/components/places/nsPlacesExpiration.js
toolkit/components/places/nsPlacesIndexes.h
toolkit/components/places/nsPlacesTables.h
toolkit/components/places/nsPlacesTriggers.h
toolkit/components/places/tests/head_common.js
toolkit/components/places/tests/migration/places_v36.sqlite
toolkit/components/places/tests/migration/places_v37.sqlite
toolkit/components/places/tests/migration/test_current_from_v36.js
toolkit/components/places/tests/migration/xpcshell.ini
--- a/dom/push/PushRecord.jsm
+++ b/dom/push/PushRecord.jsm
@@ -158,17 +158,17 @@ PushRecord.prototype = {
       Ci.nsINavHistoryService.TRANSITION_REDIRECT_PERMANENT,
       Ci.nsINavHistoryService.TRANSITION_REDIRECT_TEMPORARY
     ].join(",");
 
     let db =  yield PlacesUtils.promiseDBConnection();
     // We're using a custom query instead of `nsINavHistoryQueryOptions`
     // because the latter doesn't expose a way to filter by transition type:
     // `setTransitions` performs a logical "and," but we want an "or." We
-    // also avoid an unneeded left join on `moz_favicons`, and an `ORDER BY`
+    // also avoid an unneeded left join with favicons, and an `ORDER BY`
     // clause that emits a suboptimal index warning.
     let rows = yield db.executeCached(
       `SELECT MAX(visit_date) AS lastVisit
        FROM moz_places p
        JOIN moz_historyvisits ON p.id = place_id
        WHERE rev_host = get_unreversed_host(:host || '.') || '.'
          AND url BETWEEN :prePath AND :prePath || X'FFFF'
          AND visit_type IN (${QUOTA_REFRESH_TRANSITIONS_SQL})
--- a/toolkit/components/places/Database.cpp
+++ b/toolkit/components/places/Database.cpp
@@ -18,16 +18,17 @@
 #include "nsNavHistory.h"
 #include "nsPlacesTables.h"
 #include "nsPlacesIndexes.h"
 #include "nsPlacesTriggers.h"
 #include "nsPlacesMacros.h"
 #include "nsVariant.h"
 #include "SQLFunctions.h"
 #include "Helpers.h"
+#include "nsFaviconService.h"
 
 #include "nsAppDirectoryServiceDefs.h"
 #include "nsDirectoryServiceUtils.h"
 #include "prenv.h"
 #include "prsystem.h"
 #include "nsPrintfCString.h"
 #include "mozilla/Preferences.h"
 #include "mozilla/Services.h"
@@ -38,16 +39,18 @@
 
 // Time between corrupt database backups.
 #define RECENT_BACKUP_TIME_MICROSEC (int64_t)86400 * PR_USEC_PER_SEC // 24H
 
 // Filename of the database.
 #define DATABASE_FILENAME NS_LITERAL_STRING("places.sqlite")
 // Filename used to backup corrupt databases.
 #define DATABASE_CORRUPT_FILENAME NS_LITERAL_STRING("places.sqlite.corrupt")
+// Filename of the icons database.
+#define DATABASE_FAVICONS_FILENAME NS_LITERAL_STRING("favicons.sqlite")
 
 // Set when the database file was found corrupt by a previous maintenance.
 #define PREF_FORCE_DATABASE_REPLACEMENT "places.database.replaceOnStartup"
 
 // Set to specify the size of the places database growth increments in kibibytes
 #define PREF_GROWTH_INCREMENT_KIB "places.database.growthIncrementKiB"
 
 // Set to disable the default robust storage and use volatile, in-memory
@@ -199,17 +202,17 @@ updateSQLiteStatistics(mozIStorageConnec
  * @param aJournalMode
  *        One of the JOURNAL_* types.
  * @returns the current journal mode.
  * @note this may return a different journal mode than the required one, since
  *       setting it may fail.
  */
 enum JournalMode
 SetJournalMode(nsCOMPtr<mozIStorageConnection>& aDBConn,
-                             enum JournalMode aJournalMode)
+               enum JournalMode aJournalMode)
 {
   MOZ_ASSERT(NS_IsMainThread());
   nsAutoCString journalMode;
   switch (aJournalMode) {
     default:
       MOZ_FALLTHROUGH_ASSERT("Trying to set an unknown journal mode.");
       // Fall through to the default DELETE journal.
     case JOURNAL_DELETE:
@@ -222,18 +225,17 @@ SetJournalMode(nsCOMPtr<mozIStorageConne
       journalMode.AssignLiteral("memory");
       break;
     case JOURNAL_WAL:
       journalMode.AssignLiteral("wal");
       break;
   }
 
   nsCOMPtr<mozIStorageStatement> statement;
-  nsAutoCString query(MOZ_STORAGE_UNIQUIFY_QUERY_STR
-		      "PRAGMA journal_mode = ");
+  nsAutoCString query(MOZ_STORAGE_UNIQUIFY_QUERY_STR "PRAGMA journal_mode = ");
   query.Append(journalMode);
   aDBConn->CreateStatement(query, getter_AddRefs(statement));
   NS_ENSURE_TRUE(statement, JOURNAL_DELETE);
 
   bool hasResult = false;
   if (NS_SUCCEEDED(statement->ExecuteStep(&hasResult)) && hasResult &&
       NS_SUCCEEDED(statement->GetUTF8String(0, journalMode))) {
     if (journalMode.EqualsLiteral("delete")) {
@@ -306,16 +308,97 @@ CreateRoot(nsCOMPtr<mozIStorageConnectio
   // The 'places' root is a folder containing the other roots.
   // The first bookmark in a folder has position 0.
   if (!aRootName.EqualsLiteral("places"))
     ++itemPosition;
 
   return NS_OK;
 }
 
+nsresult
+SetupDurability(nsCOMPtr<mozIStorageConnection>& aDBConn, int32_t aDBPageSize) {
+  nsresult rv;
+  if (PR_GetEnv(ENV_ALLOW_CORRUPTION) &&
+      Preferences::GetBool(PREF_DISABLE_DURABILITY, false)) {
+    // Volatile storage was requested. Use the in-memory journal (no
+    // filesystem I/O) and don't sync the filesystem after writing.
+    SetJournalMode(aDBConn, JOURNAL_MEMORY);
+    rv = aDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+      "PRAGMA synchronous = OFF"));
+    NS_ENSURE_SUCCESS(rv, rv);
+  } else {
+    // Be sure to set journal mode after page_size.  WAL would prevent the change
+    // otherwise.
+    if (JOURNAL_WAL == SetJournalMode(aDBConn, JOURNAL_WAL)) {
+      // Set the WAL journal size limit.  We want it to be small, since in
+      // synchronous = NORMAL mode a crash could cause loss of all the
+      // transactions in the journal.  For added safety we will also force
+      // checkpointing at strategic moments.
+      int32_t checkpointPages =
+        static_cast<int32_t>(DATABASE_MAX_WAL_SIZE_IN_KIBIBYTES * 1024 / aDBPageSize);
+      nsAutoCString checkpointPragma("PRAGMA wal_autocheckpoint = ");
+      checkpointPragma.AppendInt(checkpointPages);
+      rv = aDBConn->ExecuteSimpleSQL(checkpointPragma);
+      NS_ENSURE_SUCCESS(rv, rv);
+    }
+    else {
+      // Ignore errors, if we fail here the database could be considered corrupt
+      // and we won't be able to go on, even if it's just matter of a bogus file
+      // system.  The default mode (DELETE) will be fine in such a case.
+      (void)SetJournalMode(aDBConn, JOURNAL_TRUNCATE);
+
+      // Set synchronous to FULL to ensure maximum data integrity, even in
+      // case of crashes or unclean shutdowns.
+      rv = aDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+          "PRAGMA synchronous = FULL"));
+      NS_ENSURE_SUCCESS(rv, rv);
+    }
+  }
+
+  // The journal is usually free to grow for performance reasons, but it never
+  // shrinks back.  Since the space taken may be problematic, especially on
+  // mobile devices, limit its size.
+  // Since exceeding the limit will cause a truncate, allow a slightly
+  // larger limit than DATABASE_MAX_WAL_SIZE_IN_KIBIBYTES to reduce the number
+  // of times it is needed.
+  nsAutoCString journalSizePragma("PRAGMA journal_size_limit = ");
+  journalSizePragma.AppendInt(DATABASE_MAX_WAL_SIZE_IN_KIBIBYTES * 3);
+  (void)aDBConn->ExecuteSimpleSQL(journalSizePragma);
+
+  // Grow places in |growthIncrementKiB| increments to limit fragmentation on disk.
+  // By default, it's 5 MB.
+  int32_t growthIncrementKiB =
+    Preferences::GetInt(PREF_GROWTH_INCREMENT_KIB, 5 * BYTES_PER_KIBIBYTE);
+  if (growthIncrementKiB > 0) {
+    (void)aDBConn->SetGrowthIncrement(growthIncrementKiB * BYTES_PER_KIBIBYTE, EmptyCString());
+  }
+  return NS_OK;
+}
+
+nsresult
+AttachFaviconsDatabase(nsCOMPtr<mozIStorageConnection>& aDBConn) {
+  // Attach the favicons database to the main connection.
+  nsString path;
+  nsCOMPtr<nsIFile> file;
+  nsresult rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR, getter_AddRefs(file));
+  NS_ENSURE_SUCCESS(rv, rv);
+  rv = file->Append(DATABASE_FAVICONS_FILENAME);
+  NS_ENSURE_SUCCESS(rv, rv);
+  rv = file->GetPath(path);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  rv = aDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING("ATTACH DATABASE '") +
+    NS_ConvertUTF16toUTF8(path) + NS_LITERAL_CSTRING("' AS favicons"));
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  rv = aDBConn->ExecuteSimpleSQL(CREATE_ICONS_AFTERINSERT_TRIGGER);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  return NS_OK;
+}
 
 } // namespace
 
 ////////////////////////////////////////////////////////////////////////////////
 //// Database
 
 PLACES_FACTORY_SINGLETON_IMPLEMENTATION(Database, gDatabase)
 
@@ -464,16 +547,24 @@ Database::Init()
   // If the database connection still cannot be opened, it may just be locked
   // by third parties.  Send out a notification and interrupt initialization.
   if (NS_FAILED(rv)) {
     RefPtr<PlacesEvent> lockedEvent = new PlacesEvent(TOPIC_DATABASE_LOCKED);
     (void)NS_DispatchToMainThread(lockedEvent);
     return rv;
   }
 
+  // Initialize the icons database.
+  rv = InitFaviconsDatabaseFile(storage);
+  if (NS_FAILED(rv)) {
+    RefPtr<PlacesEvent> lockedEvent = new PlacesEvent(TOPIC_DATABASE_LOCKED);
+    (void)NS_DispatchToMainThread(lockedEvent);
+    return rv;
+  }
+
   // Initialize the database schema.  In case of failure the existing schema is
   // is corrupt or incoherent, thus the database should be replaced.
   bool databaseMigrated = false;
   rv = InitSchema(&databaseMigrated);
   if (NS_FAILED(rv)) {
     mDatabaseStatus = nsINavHistoryService::DATABASE_STATUS_CORRUPT;
     rv = BackupAndReplaceDatabaseFile(storage);
     NS_ENSURE_SUCCESS(rv, rv);
@@ -542,16 +633,98 @@ Database::Init()
   if (os) {
     (void)os->AddObserver(this, TOPIC_PROFILE_CHANGE_TEARDOWN, true);
   }
 
   return NS_OK;
 }
 
 nsresult
+Database::InitFaviconsDatabaseFile(nsCOMPtr<mozIStorageService>& aStorage)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  nsCOMPtr<nsIFile> databaseFile;
+  nsresult rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR,
+                                       getter_AddRefs(databaseFile));
+  NS_ENSURE_SUCCESS(rv, rv);
+  rv = databaseFile->Append(DATABASE_FAVICONS_FILENAME);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  bool databaseFileExists = false;
+  rv = databaseFile->Exists(&databaseFileExists);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  // Open the database file.  If it does not exist a new one will be created.
+  // Use an unshared connection, it will consume more memory but avoid shared
+  // cache contentions across threads.
+  // This also checks the database sanity, so we must do it regardless.
+  nsCOMPtr<mozIStorageConnection> conn;
+  rv = aStorage->OpenUnsharedDatabase(databaseFile, getter_AddRefs(conn));
+  if (rv == NS_ERROR_FILE_CORRUPTED) {
+    rv = databaseFile->Remove(true);
+    NS_ENSURE_SUCCESS(rv, rv);
+    databaseFileExists = false;
+    rv = aStorage->OpenUnsharedDatabase(databaseFile, getter_AddRefs(conn));
+  }
+  NS_ENSURE_SUCCESS(rv, rv);
+
+#define CLOSE_AND_BAILOUT_IF_FAILED(_rv)          \
+  PR_BEGIN_MACRO                                  \
+  if (NS_WARN_IF(NS_FAILED(_rv))) {               \
+    MOZ_ALWAYS_TRUE(NS_SUCCEEDED(conn->Close())); \
+    return _rv;                                   \
+  }                                               \
+  PR_END_MACRO
+
+  if (!databaseFileExists) {
+    int32_t defaultPageSize;
+    rv = conn->GetDefaultPageSize(&defaultPageSize);
+    CLOSE_AND_BAILOUT_IF_FAILED(rv);
+    rv = SetupDurability(conn, defaultPageSize);
+    CLOSE_AND_BAILOUT_IF_FAILED(rv);
+
+    // Enable incremental vacuum for this database. Since it will contain even
+    // large blobs and can be cleared with history, it's worth to have it.
+    // Note that it will be necessary to manually use PRAGMA incremental_vacuum.
+    rv = conn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+      "PRAGMA auto_vacuum = INCREMENTAL"
+    ));
+    CLOSE_AND_BAILOUT_IF_FAILED(rv);
+
+    // We are going to update the database, so everything from now on should be
+    // in a transaction for performances.
+    mozStorageTransaction transaction(conn, false);
+
+    rv = conn->ExecuteSimpleSQL(CREATE_MOZ_ICONS);
+    CLOSE_AND_BAILOUT_IF_FAILED(rv);
+    rv = conn->ExecuteSimpleSQL(CREATE_IDX_MOZ_ICONS_ICONURLHASH);
+    CLOSE_AND_BAILOUT_IF_FAILED(rv);
+
+    rv = conn->ExecuteSimpleSQL(CREATE_MOZ_PAGES_W_ICONS);
+    CLOSE_AND_BAILOUT_IF_FAILED(rv);
+    rv = conn->ExecuteSimpleSQL(CREATE_IDX_MOZ_PAGES_W_ICONS_ICONURLHASH);
+    CLOSE_AND_BAILOUT_IF_FAILED(rv);
+
+    rv = conn->ExecuteSimpleSQL(CREATE_MOZ_ICONS_TO_PAGES);
+    CLOSE_AND_BAILOUT_IF_FAILED(rv);
+
+    rv = transaction.Commit();
+    CLOSE_AND_BAILOUT_IF_FAILED(rv);
+  }
+
+#undef CLOSE_AND_BAILOUT_IF_FAILED
+
+  rv = conn->Close();
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  return NS_OK;
+}
+
+nsresult
 Database::InitDatabaseFile(nsCOMPtr<mozIStorageService>& aStorage,
                            bool* aNewDatabaseCreated)
 {
   MOZ_ASSERT(NS_IsMainThread());
   *aNewDatabaseCreated = false;
 
   nsCOMPtr<nsIFile> databaseFile;
   nsresult rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR,
@@ -652,78 +825,52 @@ Database::InitSchema(bool* aDatabaseMigr
     rv = statement->ExecuteStep(&hasResult);
     NS_ENSURE_TRUE(NS_SUCCEEDED(rv) && hasResult, NS_ERROR_FAILURE);
     rv = statement->GetInt32(0, &mDBPageSize);
     NS_ENSURE_TRUE(NS_SUCCEEDED(rv) && mDBPageSize > 0, NS_ERROR_UNEXPECTED);
   }
 
   // Ensure that temp tables are held in memory, not on disk.
   nsresult rv = mMainConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
-      MOZ_STORAGE_UNIQUIFY_QUERY_STR "PRAGMA temp_store = MEMORY"));
+    MOZ_STORAGE_UNIQUIFY_QUERY_STR "PRAGMA temp_store = MEMORY")
+  );
   NS_ENSURE_SUCCESS(rv, rv);
 
-  if (PR_GetEnv(ENV_ALLOW_CORRUPTION) && Preferences::GetBool(PREF_DISABLE_DURABILITY, false)) {
-    // Volatile storage was requested. Use the in-memory journal (no
-    // filesystem I/O) and don't sync the filesystem after writing.
-    SetJournalMode(mMainConn, JOURNAL_MEMORY);
-    rv = mMainConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
-        "PRAGMA synchronous = OFF"));
-    NS_ENSURE_SUCCESS(rv, rv);
-  }
-  else {
-    // Be sure to set journal mode after page_size.  WAL would prevent the change
-    // otherwise.
-    if (JOURNAL_WAL == SetJournalMode(mMainConn, JOURNAL_WAL)) {
-      // Set the WAL journal size limit.  We want it to be small, since in
-      // synchronous = NORMAL mode a crash could cause loss of all the
-      // transactions in the journal.  For added safety we will also force
-      // checkpointing at strategic moments.
-      int32_t checkpointPages =
-        static_cast<int32_t>(DATABASE_MAX_WAL_SIZE_IN_KIBIBYTES * 1024 / mDBPageSize);
-      nsAutoCString checkpointPragma("PRAGMA wal_autocheckpoint = ");
-      checkpointPragma.AppendInt(checkpointPages);
-      rv = mMainConn->ExecuteSimpleSQL(checkpointPragma);
-      NS_ENSURE_SUCCESS(rv, rv);
-    }
-    else {
-      // Ignore errors, if we fail here the database could be considered corrupt
-      // and we won't be able to go on, even if it's just matter of a bogus file
-      // system.  The default mode (DELETE) will be fine in such a case.
-      (void)SetJournalMode(mMainConn, JOURNAL_TRUNCATE);
-
-      // Set synchronous to FULL to ensure maximum data integrity, even in
-      // case of crashes or unclean shutdowns.
-      rv = mMainConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
-          "PRAGMA synchronous = FULL"));
-      NS_ENSURE_SUCCESS(rv, rv);
-    }
-  }
-
-  // The journal is usually free to grow for performance reasons, but it never
-  // shrinks back.  Since the space taken may be problematic, especially on
-  // mobile devices, limit its size.
-  // Since exceeding the limit will cause a truncate, allow a slightly
-  // larger limit than DATABASE_MAX_WAL_SIZE_IN_KIBIBYTES to reduce the number
-  // of times it is needed.
-  nsAutoCString journalSizePragma("PRAGMA journal_size_limit = ");
-  journalSizePragma.AppendInt(DATABASE_MAX_WAL_SIZE_IN_KIBIBYTES * 3);
-  (void)mMainConn->ExecuteSimpleSQL(journalSizePragma);
-
-  // Grow places in |growthIncrementKiB| increments to limit fragmentation on disk.
-  // By default, it's 10 MB.
-  int32_t growthIncrementKiB =
-    Preferences::GetInt(PREF_GROWTH_INCREMENT_KIB, 10 * BYTES_PER_KIBIBYTE);
-  if (growthIncrementKiB > 0) {
-    (void)mMainConn->SetGrowthIncrement(growthIncrementKiB * BYTES_PER_KIBIBYTE, EmptyCString());
-  }
+  rv = SetupDurability(mMainConn, mDBPageSize);
+  NS_ENSURE_SUCCESS(rv, rv);
 
   nsAutoCString busyTimeoutPragma("PRAGMA busy_timeout = ");
   busyTimeoutPragma.AppendInt(DATABASE_BUSY_TIMEOUT_MS);
   (void)mMainConn->ExecuteSimpleSQL(busyTimeoutPragma);
 
+  // Enable FOREIGN KEY support.
+  rv = mMainConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+    MOZ_STORAGE_UNIQUIFY_QUERY_STR "PRAGMA foreign_keys = ON")
+  );
+  NS_ENSURE_SUCCESS(rv, rv);
+#ifdef DEBUG
+  {
+    // There are a few cases where setting foreign_keys doesn't work:
+    //  * in the middle of a multi-statement transaction
+    //  * if the SQLite library in use doesn't support them
+    // Since we need foreign_keys, let's at least assert in debug mode.
+    nsCOMPtr<mozIStorageStatement> stmt;
+    mMainConn->CreateStatement(NS_LITERAL_CSTRING("PRAGMA foreign_keys"),
+                            getter_AddRefs(stmt));
+    bool hasResult = false;
+    if (stmt && NS_SUCCEEDED(stmt->ExecuteStep(&hasResult)) && hasResult) {
+      int32_t fkState = stmt->AsInt32(0);
+      MOZ_ASSERT(fkState, "Foreign keys should be enabled");
+    }
+  }
+#endif
+
+  rv = AttachFaviconsDatabase(mMainConn);
+  NS_ENSURE_SUCCESS(rv, rv);
+
   // We use our functions during migration, so initialize them now.
   rv = InitFunctions();
   NS_ENSURE_SUCCESS(rv, rv);
 
   // Get the database schema version.
   int32_t currentSchemaVersion;
   rv = mMainConn->GetSchemaVersion(&currentSchemaVersion);
   NS_ENSURE_SUCCESS(rv, rv);
@@ -893,17 +1040,22 @@ Database::InitSchema(bool* aDatabaseMigr
 
       // Firefox 52 uses schema version 35.
 
       if (currentSchemaVersion < 36) {
         rv = MigrateV36Up();
         NS_ENSURE_SUCCESS(rv, rv);
       }
 
-      // Firefox 53 uses schema version 36.
+      if (currentSchemaVersion < 37) {
+        rv = MigrateV37Up();
+        NS_ENSURE_SUCCESS(rv, rv);
+      }
+
+      // Firefox 55 uses schema version 37.
 
       // Schema Upgrades must add migration code here.
 
       rv = UpdateBookmarkRootTitles();
       // We don't want a broken localization to cause us to think
       // the database is corrupt and needs to be replaced.
       MOZ_ASSERT(NS_SUCCEEDED(rv));
     }
@@ -911,18 +1063,16 @@ Database::InitSchema(bool* aDatabaseMigr
   else {
     // This is a new database, so we have to create all the tables and indices.
 
     // 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_FAVICON);
-    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);
@@ -962,20 +1112,16 @@ Database::InitSchema(bool* aDatabaseMigr
     NS_ENSURE_SUCCESS(rv, rv);
 
     // moz_keywords.
     rv = mMainConn->ExecuteSimpleSQL(CREATE_MOZ_KEYWORDS);
     NS_ENSURE_SUCCESS(rv, rv);
     rv = mMainConn->ExecuteSimpleSQL(CREATE_IDX_MOZ_KEYWORDS_PLACEPOSTDATA);
     NS_ENSURE_SUCCESS(rv, rv);
 
-    // moz_favicons.
-    rv = mMainConn->ExecuteSimpleSQL(CREATE_MOZ_FAVICONS);
-    NS_ENSURE_SUCCESS(rv, rv);
-
     // moz_anno_attributes.
     rv = mMainConn->ExecuteSimpleSQL(CREATE_MOZ_ANNO_ATTRIBUTES);
     NS_ENSURE_SUCCESS(rv, rv);
 
     // moz_annos.
     rv = mMainConn->ExecuteSimpleSQL(CREATE_MOZ_ANNOS);
     NS_ENSURE_SUCCESS(rv, rv);
     rv = mMainConn->ExecuteSimpleSQL(CREATE_IDX_MOZ_ANNOS_PLACEATTRIBUTE);
@@ -1511,20 +1657,26 @@ Database::MigrateV21Up()
 
 nsresult
 Database::MigrateV22Up()
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   // Reset all session IDs to 0 since we don't support them anymore.
   // We don't set them to NULL to avoid breaking downgrades.
-  nsresult rv = mMainConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
-    "UPDATE moz_historyvisits SET session = 0"
-  ));
-  NS_ENSURE_SUCCESS(rv, rv);
+  nsCOMPtr<mozIStorageStatement> stmt;
+  nsresult rv = mMainConn->CreateStatement(NS_LITERAL_CSTRING(
+    "SELECT session FROM moz_historyvisits"
+  ), getter_AddRefs(stmt));
+  if (NS_SUCCEEDED(rv)) {
+    nsresult rv = mMainConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+      "UPDATE moz_historyvisits SET session = 0"
+    ));
+    NS_ENSURE_SUCCESS(rv, rv);
+  }
 
   return NS_OK;
 }
 
 
 nsresult
 Database::MigrateV23Up()
 {
@@ -1999,16 +2151,91 @@ Database::MigrateV36Up() {
     rv = mMainConn->ExecuteSimpleSQL(CREATE_MOZ_BOOKMARKS_DELETED);
     NS_ENSURE_SUCCESS(rv, rv);
   }
 
   return NS_OK;
 }
 
 nsresult
+Database::MigrateV37Up() {
+  MOZ_ASSERT(NS_IsMainThread());
+
+  // Move favicons to the new database.
+  // For now we retain the old moz_favicons table, but we empty it.
+  // This allows for a "safer" downgrade, even if icons will be lost in the
+  // process. In a couple versions we shall drop moz_favicons completely.
+
+  // First, check if the old favicons table still exists.
+  nsCOMPtr<mozIStorageStatement> stmt;
+  nsresult rv = mMainConn->CreateStatement(NS_LITERAL_CSTRING(
+    "SELECT url FROM moz_favicons"
+  ), getter_AddRefs(stmt));
+  if (NS_FAILED(rv)) {
+    // The table has already been removed, nothing to do.
+    return NS_OK;
+  }
+
+  // The new table accepts only png or svg payloads, so we set a valid width
+  // only for them, the mime-type for the others.  Later we will asynchronously
+  // try to convert the unsupported payloads, or remove them.
+
+  // Add pages.
+  rv = mMainConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+    "INSERT INTO moz_pages_w_icons (page_url, page_url_hash) "
+    "SELECT h.url, hash(h.url) "
+    "FROM moz_places h "
+    "JOIN moz_favicons f ON f.id = h.favicon_id"
+  ));
+  NS_ENSURE_SUCCESS(rv, rv);
+  // Set icons as expired, so we will replace them with proper versions at the
+  // first load.
+  // Note: we use a peculiarity of Sqlite here, where the column affinity
+  // is not enforced, thanks to that we can store a string in an integer column.
+  rv = mMainConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+    "INSERT INTO moz_icons (icon_url, fixed_icon_url_hash, width, data) "
+      "SELECT url, hash(fixup_url(url)), "
+             "(CASE WHEN mime_type = 'image/png' THEN 16 "
+                   "WHEN mime_type = 'image/svg+xml' THEN 65535 "
+                   "ELSE mime_type END), "
+             "data FROM moz_favicons "
+             "WHERE LENGTH(data) > 0 "
+  ));
+  NS_ENSURE_SUCCESS(rv, rv);
+  // Create relations.
+  rv = mMainConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+    "INSERT OR IGNORE INTO moz_icons_to_pages (page_id, icon_id) "
+      "SELECT (SELECT id FROM moz_pages_w_icons "
+              "WHERE page_url_hash = h.url_hash "
+                "AND page_url = h.url), "
+             "(SELECT id FROM moz_icons "
+              "WHERE fixed_icon_url_hash = hash(fixup_url(f.url)) "
+                "AND icon_url = f.url) "
+      "FROM moz_favicons f "
+      "JOIN moz_places h on f.id = h.favicon_id"
+  ));
+  NS_ENSURE_SUCCESS(rv, rv);
+  // Remove old favicons and relations.
+  rv = mMainConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+    "DELETE FROM moz_favicons"
+  ));
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  rv = mMainConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+    "UPDATE moz_places SET favicon_id = NULL"
+  ));
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  // Start the async conversion
+  nsFaviconService::ConvertUnsupportedPayloads(mMainConn);
+
+  return NS_OK;
+}
+
+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 "
@@ -2300,17 +2527,17 @@ Database::Observe(nsISupports *aSubject,
     // the connection does not exist anymore.  Removing those observers would
     // be less expensive but may cause their RemoveObserver calls to throw.
     // Thus notify the topic now, so they stop listening for it.
     nsCOMPtr<nsISimpleEnumerator> e;
     if (NS_SUCCEEDED(os->EnumerateObservers(TOPIC_PLACES_INIT_COMPLETE,
                      getter_AddRefs(e))) && e) {
       bool hasMore = false;
       while (NS_SUCCEEDED(e->HasMoreElements(&hasMore)) && hasMore) {
-	nsCOMPtr<nsISupports> supports;
+        nsCOMPtr<nsISupports> supports;
         if (NS_SUCCEEDED(e->GetNext(getter_AddRefs(supports)))) {
           nsCOMPtr<nsIObserver> observer = do_QueryInterface(supports);
           (void)observer->Observe(observer, TOPIC_PLACES_INIT_COMPLETE, nullptr);
         }
       }
     }
 
     // Notify all Places users that we are about to shutdown.
--- a/toolkit/components/places/Database.h
+++ b/toolkit/components/places/Database.h
@@ -13,17 +13,17 @@
 #include "mozilla/storage.h"
 #include "mozilla/storage/StatementCache.h"
 #include "mozilla/Attributes.h"
 #include "nsIEventTarget.h"
 #include "Shutdown.h"
 
 // This is the schema version. Update it at any schema change and add a
 // corresponding migrateVxx method below.
-#define DATABASE_SCHEMA_VERSION 36
+#define DATABASE_SCHEMA_VERSION 37
 
 // Fired after Places inited.
 #define TOPIC_PLACES_INIT_COMPLETE "places-init-complete"
 // Fired when initialization fails due to a locked database.
 #define TOPIC_DATABASE_LOCKED "places-database-locked"
 // 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.
@@ -211,16 +211,25 @@ protected:
    *        mozStorage service instance.
    * @param aNewDatabaseCreated
    *        whether a new database file has been created.
    */
   nsresult InitDatabaseFile(nsCOMPtr<mozIStorageService>& aStorage,
                             bool* aNewDatabaseCreated);
 
   /**
+   * Initializes the favicons database file.  If it does not exist or is
+   * corrupt, a new one is created.
+   *
+   * @param aStorage
+   *        mozStorage service instance.
+   */
+  nsresult InitFaviconsDatabaseFile(nsCOMPtr<mozIStorageService>& aStorage);
+
+  /**
    * Creates a database backup and replaces the original file with a new
    * one.
    *
    * @param aStorage
    *        mozStorage service instance.
    */
   nsresult BackupAndReplaceDatabaseFile(nsCOMPtr<mozIStorageService>& aStorage);
 
@@ -267,16 +276,17 @@ protected:
   nsresult MigrateV28Up();
   nsresult MigrateV30Up();
   nsresult MigrateV31Up();
   nsresult MigrateV32Up();
   nsresult MigrateV33Up();
   nsresult MigrateV34Up();
   nsresult MigrateV35Up();
   nsresult MigrateV36Up();
+  nsresult MigrateV37Up();
 
   nsresult UpdateBookmarkRootTitles();
 
   friend class ConnectionShutdownBlocker;
 
   int64_t CreateMobileRoot();
   nsresult GetItemsWithAnno(const nsACString& aAnnoName, int32_t aItemType,
                             nsTArray<int64_t>& aItemIds);
--- a/toolkit/components/places/FaviconHelpers.cpp
+++ b/toolkit/components/places/FaviconHelpers.cpp
@@ -13,20 +13,26 @@
 
 #include "nsNavHistory.h"
 #include "nsFaviconService.h"
 #include "mozilla/storage.h"
 #include "mozilla/Telemetry.h"
 #include "nsNetUtil.h"
 #include "nsPrintfCString.h"
 #include "nsStreamUtils.h"
+#include "nsStringStream.h"
 #include "nsIPrivateBrowsingChannel.h"
 #include "nsISupportsPriority.h"
 #include "nsContentUtils.h"
 #include <algorithm>
+#include "mozilla/gfx/2D.h"
+#include "imgIContainer.h"
+#include "ImageOps.h"
+#include "imgLoader.h"
+#include "imgIEncoder.h"
 
 using namespace mozilla::places;
 using namespace mozilla::storage;
 
 namespace mozilla {
 namespace places {
 
 namespace {
@@ -925,10 +931,245 @@ NotifyIconObservers::SendGlobalNotificat
     // This will be silent, so be sure to not pass in the current callback.
     nsMainThreadPtrHandle<nsIFaviconDataCallback> nullCallback;
     RefPtr<AsyncAssociateIconToPage> event =
         new AsyncAssociateIconToPage(mIcon, bookmarkedPage, nullCallback);
     DB->DispatchToAsyncThread(event);
   }
 }
 
+////////////////////////////////////////////////////////////////////////////////
+//// FetchAndConvertUnsupportedPayloads
+
+FetchAndConvertUnsupportedPayloads::FetchAndConvertUnsupportedPayloads (
+  mozIStorageConnection* aDBConn
+) : mDB(aDBConn)
+{
+
+}
+
+NS_IMETHODIMP
+FetchAndConvertUnsupportedPayloads::Run()
+{
+  if (NS_IsMainThread()) {
+    Preferences::ClearUser(PREF_CONVERT_PAYLOADS);
+    return NS_OK;
+  }
+
+  MOZ_ASSERT(!NS_IsMainThread());
+  NS_ENSURE_STATE(mDB);
+
+  nsCOMPtr<mozIStorageStatement> stmt;
+  nsresult rv = mDB->CreateStatement(NS_LITERAL_CSTRING(
+    "SELECT id, width, data FROM moz_icons WHERE typeof(width) = 'text' "
+    "ORDER BY id ASC "
+    "LIMIT 200 "
+  ), getter_AddRefs(stmt));
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  mozStorageTransaction transaction(mDB, false,
+                                    mozIStorageConnection::TRANSACTION_IMMEDIATE);
+
+  // We should do the work in chunks, or the wal journal may grow too much.
+  uint8_t count = 0;
+  bool hasResult;
+  while (NS_SUCCEEDED(stmt->ExecuteStep(&hasResult)) && hasResult) {
+    ++count;
+    int64_t id = stmt->AsInt64(0);
+    MOZ_ASSERT(id > 0);
+    nsAutoCString mimeType;
+    rv = stmt->GetUTF8String(1, mimeType);
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      continue;
+    }
+    uint8_t* data;
+    uint32_t dataLen = 0;
+    rv = stmt->GetBlob(2, &dataLen, &data);
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      continue;
+    }
+    nsCString buf;
+    buf.Adopt(TO_CHARBUFFER(data), dataLen);
+
+    int32_t width = 0;
+    rv = ConvertPayload(id, mimeType, buf, &width);
+    Unused << NS_WARN_IF(NS_FAILED(rv));
+    if (NS_SUCCEEDED(rv)) {
+      rv = StorePayload(id, width, buf);
+      if (NS_WARN_IF(NS_FAILED(rv))) {
+        continue;
+      }
+    }
+  }
+
+  rv = transaction.Commit();
+  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);
+  }
+
+  // We're done. Remove any leftovers and force a checkpoint for safety.
+  rv = mDB->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+    "DELETE FROM moz_icons WHERE typeof(width) = 'text'"
+  ));
+  NS_ENSURE_SUCCESS(rv, rv);
+  rv = mDB->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+    "PRAGMA wal_checkpoint"
+  ));
+  NS_ENSURE_SUCCESS(rv, rv);
+  // Run a one-time VACUUM of places.sqlite, since we removed a lot from it.
+  // It may cause jank, but not doing it could cause dataloss due to expiration.
+  rv = mDB->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+    "VACUUM"
+  ));
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  // Re-dispatch to the main-thread to flip the conversion pref.
+  return NS_DispatchToMainThread(this);
+}
+
+nsresult
+FetchAndConvertUnsupportedPayloads::ConvertPayload(int64_t aId,
+                                                   const nsACString& aMimeType,
+                                                   nsCString& aPayload,
+                                                   int32_t* aWidth)
+{
+  // TODO (bug 1346139): this should probably be unified with the function that
+  // will handle additions optimization off the main thread.
+  MOZ_ASSERT(!NS_IsMainThread());
+  *aWidth = 0;
+
+  // Exclude invalid mime types.
+  if (aPayload.Length() == 0 ||
+      !imgLoader::SupportImageWithMimeType(PromiseFlatCString(aMimeType).get(),
+                                           AcceptedMimeTypes::IMAGES)) {
+    return NS_ERROR_FAILURE;
+  }
+
+  // If it's an SVG, there's nothing to optimize or convert.
+  if (aMimeType.EqualsLiteral(SVG_MIME_TYPE)) {
+    *aWidth = UINT16_MAX;
+    return NS_OK;
+  }
+
+  // Convert the payload to an input stream.
+  nsCOMPtr<nsIInputStream> stream;
+  nsresult rv = NS_NewByteInputStream(getter_AddRefs(stream),
+                aPayload.get(), aPayload.Length(),
+                NS_ASSIGNMENT_DEPEND);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  // Decode the input stream to a surface.
+  RefPtr<gfx::SourceSurface> surface =
+      image::ImageOps::DecodeToSurface(stream,
+                                       aMimeType,
+                                       imgIContainer::DECODE_FLAGS_DEFAULT);
+  NS_ENSURE_STATE(surface);
+  RefPtr<gfx::DataSourceSurface> dataSurface = surface->GetDataSurface();
+  NS_ENSURE_STATE(dataSurface);
+
+  // Read the current size and set an appropriate final width.
+  int32_t width = dataSurface->GetSize().width;
+  int32_t height = dataSurface->GetSize().height;
+  // For non-square images, pick the largest side.
+  int32_t originalSize = std::max(width, height);
+  int32_t size = originalSize;
+  for (uint16_t supportedSize : sFaviconSizes) {
+    if (supportedSize <= originalSize) {
+      size = supportedSize;
+      break;
+    }
+  }
+  *aWidth = size;
+
+  // If the original payload is png and the size is the same, no reason to
+  // rescale the image.
+  if (aMimeType.EqualsLiteral(PNG_MIME_TYPE) && size == originalSize) {
+    return NS_OK;
+  }
+
+  // Rescale when needed.
+  RefPtr<gfx::DataSourceSurface> targetDataSurface =
+    gfx::Factory::CreateDataSourceSurface(gfx::IntSize(size, size),
+                                          gfx::SurfaceFormat::B8G8R8A8,
+                                          true);
+  NS_ENSURE_STATE(targetDataSurface);
+
+  { // Block scope for map.
+    gfx::DataSourceSurface::MappedSurface map;
+    if (!targetDataSurface->Map(gfx::DataSourceSurface::MapType::WRITE, &map)) {
+      return NS_ERROR_FAILURE;
+    }
+
+    RefPtr<gfx::DrawTarget> dt =
+      gfx::Factory::CreateDrawTargetForData(gfx::BackendType::CAIRO,
+                                            map.mData,
+                                            targetDataSurface->GetSize(),
+                                            map.mStride,
+                                            gfx::SurfaceFormat::B8G8R8A8);
+    NS_ENSURE_STATE(dt);
+
+    gfx::IntSize frameSize = dataSurface->GetSize();
+    dt->DrawSurface(dataSurface,
+                    gfx::Rect(0, 0, size, size),
+                    gfx::Rect(0, 0, frameSize.width, frameSize.height),
+                    gfx::DrawSurfaceOptions(),
+                    gfx::DrawOptions(1.0f, gfx::CompositionOp::OP_SOURCE));
+    targetDataSurface->Unmap();
+  }
+
+  // Finally Encode.
+  nsCOMPtr<imgIEncoder> encoder =
+    do_CreateInstance("@mozilla.org/image/encoder;2?type=image/png");
+  NS_ENSURE_STATE(encoder);
+
+  gfx::DataSourceSurface::MappedSurface map;
+  if (!targetDataSurface->Map(gfx::DataSourceSurface::MapType::READ, &map)) {
+    return NS_ERROR_FAILURE;
+  }
+  rv = encoder->InitFromData(map.mData, map.mStride * size, size, size,
+                             map.mStride, imgIEncoder::INPUT_FORMAT_HOSTARGB,
+                             EmptyString());
+  targetDataSurface->Unmap();
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  // Read the stream into a new buffer.
+  nsCOMPtr<nsIInputStream> iconStream = do_QueryInterface(encoder);
+  NS_ENSURE_STATE(iconStream);
+  rv = NS_ConsumeStream(iconStream, UINT32_MAX, aPayload);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  return NS_OK;
+}
+
+nsresult
+FetchAndConvertUnsupportedPayloads::StorePayload(int64_t aId,
+                                                 int32_t aWidth,
+                                                 const nsCString& aPayload)
+{
+  MOZ_ASSERT(!NS_IsMainThread());
+
+  NS_ENSURE_STATE(mDB);
+  nsCOMPtr<mozIStorageStatement> stmt;
+  nsresult rv = mDB->CreateStatement(NS_LITERAL_CSTRING(
+    "UPDATE moz_icons SET data = :data, width = :width WHERE id = :id"
+  ), getter_AddRefs(stmt));
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("id"), aId);
+  NS_ENSURE_SUCCESS(rv, rv);
+  rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("width"), aWidth);
+  NS_ENSURE_SUCCESS(rv, rv);
+  rv = stmt->BindBlobByName(NS_LITERAL_CSTRING("data"),
+                            TO_INTBUFFER(aPayload), aPayload.Length());
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  rv = stmt->Execute();
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  return NS_OK;
+}
+
 } // namespace places
 } // namespace mozilla
--- a/toolkit/components/places/FaviconHelpers.h
+++ b/toolkit/components/places/FaviconHelpers.h
@@ -8,16 +8,18 @@
 
 #include "nsIFaviconService.h"
 #include "nsIChannelEventSink.h"
 #include "nsIInterfaceRequestor.h"
 #include "nsIStreamListener.h"
 #include "mozIPlacesPendingOperation.h"
 #include "nsThreadUtils.h"
 #include "nsProxyRelease.h"
+#include "imgITools.h"
+#include "imgIContainer.h"
 
 class nsIPrincipal;
 
 #include "Database.h"
 #include "mozilla/storage.h"
 
 #define ICON_STATUS_UNKNOWN 0
 #define ICON_STATUS_CHANGED 1 << 0
@@ -25,24 +27,30 @@ class nsIPrincipal;
 #define ICON_STATUS_ASSOCIATED 1 << 2
 #define ICON_STATUS_CACHED 1 << 3
 
 #define TO_CHARBUFFER(_buffer) \
   reinterpret_cast<char*>(const_cast<uint8_t*>(_buffer))
 #define TO_INTBUFFER(_string) \
   reinterpret_cast<uint8_t*>(const_cast<char*>(_string.get()))
 
+#define PNG_MIME_TYPE "image/png"
+#define SVG_MIME_TYPE "image/svg+xml"
+
 /**
  * The maximum time we will keep a favicon around.  We always ask the cache, if
  * we can, but default to this value if we do not get a time back, or the time
  * is more in the future than this.
  * Currently set to one week from now.
  */
 #define MAX_FAVICON_EXPIRATION ((PRTime)7 * 24 * 60 * 60 * PR_USEC_PER_SEC)
 
+// Whether there are unsupported payloads to convert yet.
+#define PREF_CONVERT_PAYLOADS "places.favicons.convertPayloads"
+
 namespace mozilla {
 namespace places {
 
 /**
  * Indicates when a icon should be fetched from network.
  */
 enum AsyncFaviconFetchMode {
   FETCH_NEVER = 0
@@ -264,10 +272,35 @@ public:
 private:
   nsMainThreadPtrHandle<nsIFaviconDataCallback> mCallback;
   IconData mIcon;
   PageData mPage;
 
   void SendGlobalNotifications(nsIURI* aIconURI);
 };
 
+/**
+ * Fetches and converts unsupported payloads. This is used during the initial
+ * migration of icons from the old to the new store.
+ */
+class FetchAndConvertUnsupportedPayloads final : public Runnable
+{
+public:
+  NS_DECL_NSIRUNNABLE
+
+  /**
+   * Constructor.
+   *
+   * @param aDBConn
+   *        The database connection to use.
+   */
+  explicit FetchAndConvertUnsupportedPayloads(mozIStorageConnection* aDBConn);
+
+private:
+  nsresult ConvertPayload(int64_t aId, const nsACString& aMimeType,
+                          nsCString& aPayload, int32_t* aWidth);
+  nsresult StorePayload(int64_t aId, int32_t aWidth, const nsCString& aPayload);
+
+  nsCOMPtr<mozIStorageConnection> mDB;
+};
+
 } // namespace places
 } // namespace mozilla
--- a/toolkit/components/places/SQLFunctions.cpp
+++ b/toolkit/components/places/SQLFunctions.cpp
@@ -882,20 +882,23 @@ namespace places {
     nsAutoCString table;
     rv = aArgs->GetUTF8String(0, table);
     NS_ENSURE_SUCCESS(rv, rv);
 
     int64_t lastInsertedId = aArgs->AsInt64(1);
 
     MOZ_ASSERT(table.EqualsLiteral("moz_places") ||
                table.EqualsLiteral("moz_historyvisits") ||
-               table.EqualsLiteral("moz_bookmarks"));
+               table.EqualsLiteral("moz_bookmarks") ||
+               table.EqualsLiteral("moz_icons"));
 
     if (table.EqualsLiteral("moz_bookmarks")) {
       nsNavBookmarks::StoreLastInsertedId(table, lastInsertedId);
+    } else if (table.EqualsLiteral("moz_icons")) {
+      nsFaviconService::StoreLastInsertedId(table, lastInsertedId);
     } else {
       nsNavHistory::StoreLastInsertedId(table, lastInsertedId);
     }
 
     RefPtr<nsVariant> result = new nsVariant();
     rv = result->SetAsInt64(lastInsertedId);
     NS_ENSURE_SUCCESS(rv, rv);
     result.forget(_result);
--- a/toolkit/components/places/nsFaviconService.cpp
+++ b/toolkit/components/places/nsFaviconService.cpp
@@ -28,37 +28,26 @@
 #include "mozilla/ArrayUtils.h"
 #include "mozilla/LoadInfo.h"
 #include "mozilla/Preferences.h"
 #include "nsILoadInfo.h"
 #include "nsIContentPolicy.h"
 #include "nsContentUtils.h"
 #include "NullPrincipal.h"
 
-// For large favicons optimization.
-#include "imgITools.h"
-#include "imgIContainer.h"
-
-// The target dimension, in pixels, for favicons we optimize.
-#define OPTIMIZED_FAVICON_DIMENSION 32
-
 #define MAX_FAILED_FAVICONS 256
 #define FAVICON_CACHE_REDUCE_COUNT 64
 
 #define UNASSOCIATED_FAVICONS_LENGTH 32
 
 // When replaceFaviconData is called, we store the icons in an in-memory cache
 // instead of in storage. Icons in the cache are expired according to this
 // interval.
 #define UNASSOCIATED_ICON_EXPIRY_INTERVAL 60000
 
-// The MIME type of the default favicon and favicons created by
-// OptimizeFaviconImage.
-#define DEFAULT_MIME_TYPE "image/png"
-
 using namespace mozilla;
 using namespace mozilla::places;
 
 /**
  * Used to notify a topic to system observers on async execute completion.
  * Will throw on error.
  */
 class ExpireFaviconsStatementCallbackNotifier : public AsyncStatementCallback
@@ -93,26 +82,41 @@ nsFaviconService::nsFaviconService()
 nsFaviconService::~nsFaviconService()
 {
   NS_ASSERTION(gFaviconService == this,
                "Deleting a non-singleton instance of the service");
   if (gFaviconService == this)
     gFaviconService = nullptr;
 }
 
+Atomic<int64_t> nsFaviconService::sLastInsertedIconId(0);
+
+void // static
+nsFaviconService::StoreLastInsertedId(const nsACString& aTable,
+                                      const int64_t aLastInsertedId) {
+  MOZ_ASSERT(aTable.EqualsLiteral("moz_icons"));
+  sLastInsertedIconId = aLastInsertedId;
+}
 
 nsresult
 nsFaviconService::Init()
 {
   mDB = Database::GetDatabase();
   NS_ENSURE_STATE(mDB);
 
   mExpireUnassociatedIconsTimer = do_CreateInstance("@mozilla.org/timer;1");
   NS_ENSURE_STATE(mExpireUnassociatedIconsTimer);
 
+  // Check if there are still icon payloads to be converted.
+  bool shouldConvertPayloads =
+    Preferences::GetBool(PREF_CONVERT_PAYLOADS, false);
+  if (shouldConvertPayloads) {
+    ConvertUnsupportedPayloads(mDB->MainConn());
+  }
+
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsFaviconService::ExpireAllFavicons()
 {
   nsCOMPtr<mozIStorageAsyncStatement> unlinkIconsStmt = mDB->GetAsyncStatement(
     "UPDATE moz_places "
@@ -631,38 +635,36 @@ nsFaviconService::GetFaviconSpecForIconS
 nsresult
 nsFaviconService::OptimizeFaviconImage(const uint8_t* aData, uint32_t aDataLen,
                                        const nsACString& aMimeType,
                                        nsACString& aNewData,
                                        nsACString& aNewMimeType)
 {
   nsresult rv;
 
-  nsCOMPtr<imgITools> imgtool = do_CreateInstance("@mozilla.org/image/tools;1");
-
   nsCOMPtr<nsIInputStream> stream;
   rv = NS_NewByteInputStream(getter_AddRefs(stream),
                 reinterpret_cast<const char*>(aData), aDataLen,
                 NS_ASSIGNMENT_DEPEND);
   NS_ENSURE_SUCCESS(rv, rv);
 
   // decode image
   nsCOMPtr<imgIContainer> container;
-  rv = imgtool->DecodeImageData(stream, aMimeType, getter_AddRefs(container));
+  rv = GetImgTools()->DecodeImageData(stream, aMimeType, getter_AddRefs(container));
   NS_ENSURE_SUCCESS(rv, rv);
 
-  aNewMimeType.AssignLiteral(DEFAULT_MIME_TYPE);
+  aNewMimeType.AssignLiteral(PNG_MIME_TYPE);
 
   // scale and recompress
   nsCOMPtr<nsIInputStream> iconStream;
-  rv = imgtool->EncodeScaledImage(container, aNewMimeType,
-                                  OPTIMIZED_FAVICON_DIMENSION,
-                                  OPTIMIZED_FAVICON_DIMENSION,
-                                  EmptyString(),
-                                  getter_AddRefs(iconStream));
+  rv = GetImgTools()->EncodeScaledImage(container, aNewMimeType,
+                                        DEFAULT_FAVICON_SIZE,
+                                        DEFAULT_FAVICON_SIZE,
+                                        EmptyString(),
+                                        getter_AddRefs(iconStream));
   NS_ENSURE_SUCCESS(rv, rv);
 
   // Read the stream into a new buffer.
   rv = NS_ConsumeStream(iconStream, UINT32_MAX, aNewData);
   NS_ENSURE_SUCCESS(rv, rv);
 
   return NS_OK;
 }
@@ -684,16 +686,37 @@ nsFaviconService::GetFaviconDataAsync(ns
   aFaviconURI->GetSpecIgnoringRef(faviconURI);
   nsresult rv = URIBinder::Bind(stmt, NS_LITERAL_CSTRING("icon_url"), faviconURI);
   NS_ENSURE_SUCCESS(rv, rv);
 
   nsCOMPtr<mozIStoragePendingStatement> pendingStatement;
   return stmt->ExecuteAsync(aCallback, getter_AddRefs(pendingStatement));
 }
 
+void // static
+nsFaviconService::ConvertUnsupportedPayloads(mozIStorageConnection* aDBConn)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  // Ensure imgTools are initialized, so that the image decoders can be used
+  // off the main thread.
+  nsCOMPtr<imgITools> imgTools = do_CreateInstance("@mozilla.org/image/tools;1");
+
+  Preferences::SetBool(PREF_CONVERT_PAYLOADS, true);
+  MOZ_ASSERT(aDBConn);
+  if (aDBConn) {
+    RefPtr<FetchAndConvertUnsupportedPayloads> event =
+      new FetchAndConvertUnsupportedPayloads(aDBConn);
+    nsCOMPtr<nsIEventTarget> target = do_GetInterface(aDBConn);
+    MOZ_ASSERT(target);
+    if (target) {
+      (void)target->Dispatch(event, NS_DISPATCH_NORMAL);
+    }
+  }
+}
+
 ////////////////////////////////////////////////////////////////////////////////
 //// ExpireFaviconsStatementCallbackNotifier
 
 ExpireFaviconsStatementCallbackNotifier::ExpireFaviconsStatementCallbackNotifier()
 {
 }
 
 
--- a/toolkit/components/places/nsFaviconService.h
+++ b/toolkit/components/places/nsFaviconService.h
@@ -13,21 +13,29 @@
 #include "nsString.h"
 #include "nsDataHashtable.h"
 #include "nsServiceManagerUtils.h"
 #include "nsTHashtable.h"
 #include "nsToolkitCompsCID.h"
 #include "nsURIHashKey.h"
 #include "nsITimer.h"
 #include "Database.h"
+#include "imgITools.h"
 #include "mozilla/storage.h"
 #include "mozilla/Attributes.h"
 
 #include "FaviconHelpers.h"
 
+// The target dimension in pixels for favicons we store, in reverse order.
+static uint16_t sFaviconSizes[8] = {
+  256, 192, 144, 96, 64, 48, 32, 16
+};
+// Default size when preferred size is unknown, doubled for hi-dpi.
+#define DEFAULT_FAVICON_SIZE 32
+
 // Favicons bigger than this (in bytes) will not be stored in the database.  We
 // expect that most 32x32 PNG favicons will be no larger due to compression.
 #define MAX_FAVICON_FILESIZE 3072 /* 3 KiB */
 
 // forward class definitions
 class mozIStorageStatementCallback;
 
 class UnassociatedIconHashKey : public nsURIHashKey
@@ -73,16 +81,21 @@ public:
       nsCOMPtr<nsIFaviconService> serv =
         do_GetService(NS_FAVICONSERVICE_CONTRACTID);
       NS_ENSURE_TRUE(serv, nullptr);
       NS_ASSERTION(gFaviconService, "Should have static instance pointer now");
     }
     return gFaviconService;
   }
 
+  /**
+   * Fetch and migrate favicons from an unsupported payload to a supported one.
+   */
+  static void ConvertUnsupportedPayloads(mozIStorageConnection* aDBConn);
+
   // addition to API for strings to prevent excessive parsing of URIs
   nsresult GetFaviconLinkForIconString(const nsCString& aIcon, nsIURI** aOutput);
   void GetFaviconSpecForIconString(const nsCString& aIcon, nsACString& aOutput);
 
   nsresult OptimizeFaviconImage(const uint8_t* aData, uint32_t aDataLen,
                                 const nsACString& aMimeType,
                                 nsACString& aNewData, nsACString& aNewMimeType);
 
@@ -107,27 +120,39 @@ public:
    * @param aFaviconURI
    *        The moz-anno:favicon URI of the icon.
    * @param aGUID
    *        The unique ID associated with the page.
    */
   void SendFaviconNotifications(nsIURI* aPageURI, nsIURI* aFaviconURI,
                                 const nsACString& aGUID);
 
+  static mozilla::Atomic<int64_t> sLastInsertedIconId;
+  static void StoreLastInsertedId(const nsACString& aTable,
+                                  const int64_t aLastInsertedId);
+
   NS_DECL_ISUPPORTS
   NS_DECL_NSIFAVICONSERVICE
   NS_DECL_MOZIASYNCFAVICONS
   NS_DECL_NSITIMERCALLBACK
 
 private:
+  imgITools* GetImgTools() {
+    if (!mImgTools) {
+      mImgTools = do_CreateInstance("@mozilla.org/image/tools;1");
+    }
+    return mImgTools;
+  }
+
   ~nsFaviconService();
 
   RefPtr<mozilla::places::Database> mDB;
 
   nsCOMPtr<nsITimer> mExpireUnassociatedIconsTimer;
+  nsCOMPtr<imgITools> mImgTools;
 
   static nsFaviconService* gFaviconService;
 
   /**
    * A cached URI for the default icon. We return this a lot, and don't want to
    * re-parse and normalize our unchanging string many times.  Important: do
    * not return this directly; use Clone() since callers may change the object
    * they get back. May be null, in which case it needs initialization.
--- a/toolkit/components/places/nsPlacesExpiration.js
+++ b/toolkit/components/places/nsPlacesExpiration.js
@@ -69,17 +69,17 @@ const PREF_INTERVAL_SECONDS_NOTSET = 3 *
 // This percentage of memory size is used to protect against calculating a too
 // large database size on systems with small memory.
 const DATABASE_TO_MEMORY_PERC = 4;
 // This percentage of disk size is used to protect against calculating a too
 // large database size on disks with tiny quota or available space.
 const DATABASE_TO_DISK_PERC = 2;
 // Maximum size of the optimal database.  High-end hardware has plenty of
 // memory and disk space, but performances don't grow linearly.
-const DATABASE_MAX_SIZE = 73400320; // 70MiB
+const DATABASE_MAX_SIZE = 62914560; // 60MiB
 // If the physical memory size is bogus, fallback to this.
 const MEMSIZE_FALLBACK_BYTES = 268435456; // 256 MiB
 // If the disk available space is bogus, fallback to this.
 const DISKSIZE_FALLBACK_BYTES = 268435456; // 256 MiB
 
 // Max number of entries to expire at each expiration step.
 // This value is globally used for different kind of data we expire, can be
 // tweaked based on data type.  See below in getBoundStatement.
--- a/toolkit/components/places/nsPlacesIndexes.h
+++ b/toolkit/components/places/nsPlacesIndexes.h
@@ -116,9 +116,23 @@
  * moz_keywords
  */
 
 #define CREATE_IDX_MOZ_KEYWORDS_PLACEPOSTDATA \
   CREATE_PLACES_IDX( \
     "placepostdata_uniqueindex", "moz_keywords", "place_id, post_data", "UNIQUE" \
   )
 
+// moz_pages_w_icons
+
+#define CREATE_IDX_MOZ_PAGES_W_ICONS_ICONURLHASH \
+  CREATE_PLACES_IDX( \
+    "urlhashindex", "moz_pages_w_icons", "page_url_hash", "" \
+  )
+
+// moz_icons
+
+#define CREATE_IDX_MOZ_ICONS_ICONURLHASH \
+  CREATE_PLACES_IDX( \
+    "iconurlhashindex", "moz_icons", "fixed_icon_url_hash", "" \
+  )
+
 #endif // nsPlacesIndexes_h__
--- a/toolkit/components/places/nsPlacesTables.h
+++ b/toolkit/components/places/nsPlacesTables.h
@@ -12,17 +12,16 @@
     "CREATE TABLE moz_places ( " \
     "  id INTEGER PRIMARY KEY" \
     ", url LONGVARCHAR" \
     ", title LONGVARCHAR" \
     ", rev_host LONGVARCHAR" \
     ", visit_count INTEGER DEFAULT 0" \
     ", hidden INTEGER DEFAULT 0 NOT NULL" \
     ", typed INTEGER DEFAULT 0 NOT NULL" \
-    ", favicon_id INTEGER" \
     ", frecency INTEGER DEFAULT -1 NOT NULL" \
     ", last_visit_date INTEGER " \
     ", guid TEXT" \
     ", foreign_count INTEGER DEFAULT 0 NOT NULL" \
     ", url_hash INTEGER DEFAULT 0 NOT NULL " \
   ")" \
 )
 
@@ -47,17 +46,16 @@
   ")" \
 )
 
 #define CREATE_MOZ_ANNOS NS_LITERAL_CSTRING( \
   "CREATE TABLE moz_annos (" \
     "  id INTEGER PRIMARY KEY" \
     ", place_id INTEGER NOT NULL" \
     ", anno_attribute_id INTEGER" \
-    ", mime_type VARCHAR(32) DEFAULT NULL" \
     ", content LONGVARCHAR" \
     ", flags INTEGER DEFAULT 0" \
     ", expiration INTEGER DEFAULT 0" \
     ", type INTEGER DEFAULT 0" \
     ", dateAdded INTEGER DEFAULT 0" \
     ", lastModified INTEGER DEFAULT 0" \
   ")" \
 )
@@ -69,36 +67,25 @@
   ")" \
 )
 
 #define CREATE_MOZ_ITEMS_ANNOS NS_LITERAL_CSTRING( \
   "CREATE TABLE moz_items_annos (" \
     "  id INTEGER PRIMARY KEY" \
     ", item_id INTEGER NOT NULL" \
     ", anno_attribute_id INTEGER" \
-    ", mime_type VARCHAR(32) DEFAULT NULL" \
     ", content LONGVARCHAR" \
     ", flags INTEGER DEFAULT 0" \
     ", expiration INTEGER DEFAULT 0" \
     ", type INTEGER DEFAULT 0" \
     ", dateAdded INTEGER DEFAULT 0" \
     ", lastModified INTEGER DEFAULT 0" \
   ")" \
 )
 
-#define CREATE_MOZ_FAVICONS NS_LITERAL_CSTRING( \
-  "CREATE TABLE moz_favicons (" \
-    "  id INTEGER PRIMARY KEY" \
-    ", url LONGVARCHAR UNIQUE" \
-    ", data BLOB" \
-    ", mime_type VARCHAR(32)" \
-    ", expiration LONG" \
-  ")" \
-)
-
 #define CREATE_MOZ_BOOKMARKS NS_LITERAL_CSTRING( \
   "CREATE TABLE moz_bookmarks (" \
     "  id INTEGER PRIMARY KEY" \
     ", type INTEGER" \
     ", fk INTEGER DEFAULT NULL" /* place_id */ \
     ", parent INTEGER" \
     ", position INTEGER" \
     ", title LONGVARCHAR" \
@@ -174,9 +161,53 @@
 // take care of updating the moz_hosts table for every modified host.
 // See CREATE_PLACES_AFTERDELETE_TRIGGER in nsPlacestriggers.h for details.
 #define CREATE_UPDATEHOSTS_TEMP NS_LITERAL_CSTRING( \
   "CREATE TEMP TABLE moz_updatehosts_temp (" \
     "  host TEXT PRIMARY KEY " \
   ") WITHOUT ROWID " \
 )
 
+// This table would not be strictly needed for functionality since it's just
+// mimicking moz_places, though it's great for database portability.
+// With this we don't have to take care into account a bunch of database
+// mismatch cases, where places.sqlite could be mixed up with a favicons.sqlite
+// created with a different places.sqlite (not just in case of a user messing
+// up with the profile, but also in case of corruption).
+#define CREATE_MOZ_PAGES_W_ICONS NS_LITERAL_CSTRING( \
+  "CREATE TABLE moz_pages_w_icons ( " \
+    "id INTEGER PRIMARY KEY, " \
+    "page_url TEXT NOT NULL, " \
+    "page_url_hash INTEGER NOT NULL " \
+  ") " \
+)
+
+// This table retains the icons data. The hashes url is "fixed" (thus the scheme
+// and www are trimmed in most cases) so we can quickly query for root icon urls
+// like "domain/favicon.ico".
+// We are considering squared icons for simplicity, so storing only one size.
+// For svg payloads, width will be set to 65535 (UINT16_MAX).
+#define CREATE_MOZ_ICONS NS_LITERAL_CSTRING( \
+  "CREATE TABLE moz_icons ( " \
+    "id INTEGER PRIMARY KEY, " \
+    "icon_url TEXT NOT NULL, " \
+    "fixed_icon_url_hash INTEGER NOT NULL, " \
+    "width INTEGER NOT NULL DEFAULT 0, " \
+    "color INTEGER, " \
+    "expire_ms INTEGER NOT NULL DEFAULT 0, " \
+    "data BLOB " \
+  ") " \
+)
+
+// This table maintains relations between icons and pages.
+// Each page can have multiple icons, and each icon can be used by multiple
+// pages.
+#define CREATE_MOZ_ICONS_TO_PAGES NS_LITERAL_CSTRING( \
+  "CREATE TABLE moz_icons_to_pages ( " \
+    "page_id INTEGER NOT NULL, " \
+    "icon_id INTEGER NOT NULL, " \
+    "PRIMARY KEY (page_id, icon_id), " \
+    "FOREIGN KEY (page_id) REFERENCES moz_pages_w_icons ON DELETE CASCADE, " \
+    "FOREIGN KEY (icon_id) REFERENCES moz_icons ON DELETE CASCADE " \
+  ") WITHOUT ROWID " \
+)
+
 #endif // __nsPlacesTables_h__
--- a/toolkit/components/places/nsPlacesTriggers.h
+++ b/toolkit/components/places/nsPlacesTriggers.h
@@ -140,16 +140,19 @@
       "AND NOT EXISTS(" \
         "SELECT 1 FROM moz_places " \
           "WHERE rev_host = get_unreversed_host(host || '.') || '.' " \
              "OR rev_host = get_unreversed_host(host || '.') || '.www.' " \
       "); " \
     "UPDATE moz_hosts " \
     "SET prefix = (" HOSTS_PREFIX_PRIORITY_FRAGMENT ") " \
     "WHERE host = OLD.host; " \
+    "DELETE FROM moz_icons " \
+    "WHERE fixed_icon_url_hash = hash(OLD.host || '/favicon.ico') " \
+      "AND fixup_url(icon_url) = OLD.host || '/favicon.ico';" \
   "END" \
 )
 
 // For performance reasons the host frecency is updated only when the page
 // frecency changes by a meaningful percentage.  This is because the frecency
 // decay algorithm requires to update all the frecencies at once, causing a
 // too high overhead, while leaving the ordering unchanged.
 #define CREATE_PLACES_AFTERUPDATE_FRECENCY_TRIGGER NS_LITERAL_CSTRING( \
@@ -259,9 +262,17 @@
     "SET foreign_count = foreign_count + 1 " \
     "WHERE id = NEW.place_id; " \
     "UPDATE moz_places " \
     "SET foreign_count = foreign_count - 1 " \
     "WHERE id = OLD.place_id; " \
   "END" \
 )
 
+#define CREATE_ICONS_AFTERINSERT_TRIGGER NS_LITERAL_CSTRING( \
+  "CREATE TEMP TRIGGER moz_icons_afterinsert_v1_trigger " \
+  "AFTER INSERT ON moz_icons FOR EACH ROW " \
+  "BEGIN " \
+    "SELECT store_last_inserted_id('moz_icons', NEW.id); " \
+  "END" \
+)
+
 #endif // __nsPlacesTriggers_h__
--- a/toolkit/components/places/tests/head_common.js
+++ b/toolkit/components/places/tests/head_common.js
@@ -1,17 +1,17 @@
 /* -*- indent-tabs-mode: nil; js-indent-level: 2 -*-
  * 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/. */
 
 // It is expected that the test files importing this file define Cu etc.
 /* global Cu, Ci, Cc, Cr */
 
-const CURRENT_SCHEMA_VERSION = 36;
+const CURRENT_SCHEMA_VERSION = 37;
 const FIRST_UPGRADABLE_SCHEMA_VERSION = 11;
 
 const NS_APP_USER_PROFILE_50_DIR = "ProfD";
 const NS_APP_PROFILE_DIR_STARTUP = "ProfDS";
 
 // Shortcuts to transitions type.
 const TRANSITION_LINK = Ci.nsINavHistoryService.TRANSITION_LINK;
 const TRANSITION_TYPED = Ci.nsINavHistoryService.TRANSITION_TYPED;
index a2b180111fb3e3a06a872a125308525ba7972f70..d781c8eb284db8a19503ec7d70867f85db5c8801
GIT binary patch
literal 1212416
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
zchrw4Yeyd(WN<E>rc}Ff17(=(naXs~|1kZLiZi}-xZP5OONf8izUEuf`iCU;jxR1q
z&j`uwSg*m)%~tkuQ>M45t*oR%rwqDlo@1GacKmPiT_f7sl*`&RIIL}su_)1G%g?sk
zx+;y22p?vPi%TCoC}CoN5EsIR_4~rVtE;z&xvd&!`KVip4~|bywG9}Y5Zb>u)KNU*
zo97-M;o>cZ3FRi!snpfhvKj|oSxNHkfw!Kei<p}oJ2EabD52NnpqR-W>pk>GT=H10
zw@CiS6+6b>YEH?wn{KOJE9MRu9hn<7dh*a=;aNjE)=U3v#gtTyx5)X&wO7oS)O?%W
zl4CBuy%CL=n{ST^FsBYTrsu^C?pUvbyUV=sYHyMHA2*U|%Fes3ezllu?o^nXlbjHl
zY)dcbSWh$HwU;NTyoK3Wzx#i366I^_txbrz;eDe5CJvq4(^^nGT4`bZXVG5DLrW!{
z5y}q$00000000000000000000000000000000000000000000000000000000093I
zbyRLbr*d`I>4Zy&f7rg}Tf7|6)9-S(00000000000000000000000000000000000
z000000000000000000000002E%RC&r>36vk000000000000000000000000000000
z00000000000000000000000000002oWnRjrl71G-4*&oF00000000000000000000
z00000000000000000000000000000000000{|5C{uA-G%Cvq&s=Im^v!D7u2^F5RV
zmGqlXegFUf0000000000000000000000000000000000000000000000000000000
z`1kPEv~tg}6q~cNjRuP~!%K;(J@qjmeIYH8MoaBWPM2&f$t?+(_Up74rcIvKZJKuK
z7fLb!0000000000000000000000000000000000000000000000000000000fV-w^
zZEv9x?y3M$+tsy=PN&umw4|7`P5M}iB`e2h&9Vgxm##ZqzV|Mw2y>hnlAQv)9mQ4d
zqP)09tz-}J583kT$ciu~CVx~$NZ&s3iHW8tFK?wSm9ABJb~kmRDb<u~*C!b>Y-J@M
zdH8;3xbf#t?6&lz_@JPytRY2Nojl7*wka>^su^gp+4aNCHnZK7R#tRc&aSd5dSw<S
z47bHv%;tow%zhEIoJCdlmKSw#)5N4$^6mOz<xLiCUr`o*_5JYJG<!_SAZstv!1QR3
zvT_woYTeX*Om=;IZh<AcprXwpdqP?G<b=q+=E>Qk11uAhLOOMGcb0VztQZ%!p7}Pr
zCC6NB()Tu7P3e}R^75TtEel6p>Xd3UjTkYhcYI=GukO0C(+#e;EN*>G*?D?nZkj$R
z-)^y*joIa$FFd)WA`Hn3898WTRzz`HT1r<p=Y>&qtf)Y(9$5B&(N}N3@S95&V=&sD
z6qb}3H8Hy=GpDnw(!RUOy`s2AIdi)?H$y+vZnRboc;Vo(t6aFIudP#DM#50bL~BrX
zr!FpKbE>j!&0u?`$y#knh4=@r*;_X31rd$&bJLY6mFApMLE5s?4TUK0xnpuvE#V05
zN?dZet{P{F%E4AwHl$CoSh7=$*6Ix^^Tb{j?<^jzb{5z9-Clg4DL21*S!F`!D3KLS
z`>8yYvO1lYv$N`3vsx_{dqq|+$L%)#F5m6|000000000000000000000000000000
z0000000000000000000000000007)&9**7gyW9x?0000000000000000000000000
z000000000000000000000000000000008bXFJ)6nXNB?u00000000000000000000
z00000000000000000000000000000000000006+hMQxR)jmWVSCuf-^7h0@oHm^Yz
zQKx^sdQ-h7pD5g!|K#A&mTyg~Cmcy0IcDUrnMt*Wt{Hr7@aF#Y?smrk0000000000
z00000000000000000000008jcY4*t3BkQ_}ar3*haLu&a^K8+9fteYG980k|JKJcm
zSTh1`cB9>#s>GU8ExCrg+>E&igZg-RH1$x5d&T#SOH{%-j!mag_U!{|4;4Z+H(@|h
zuWQ$?-MDc>mgUP%%Hh?kSI?h6fAQi)<%1IQ-FM#|`{CFxzx<-aoH}*t&p-b>d-m+%
z!-tjlpMLu3^8=rsIdkT>-+ue*tFJ!#@S|_P{Z={Lw{PF^<HtYv;Dhbkw;wumNcsHI
zi!bfmx%2twpWpu4_8mKRDA^7kJg8)N;)y37ee}^)D_4E=(MPYo`s%yyzPowzW@Z28
zn{TdKwQ9lq1xuGMUB7<45;<YQgcn|TVQAt|i^cNB8*eCkCBNNnSDJY7#TT=(vXmk-
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
z00000000000000000000000000000000000000000002M|A0Z`uNkMxwA=G+(Sd=P
z8HOB7u{k^2Xs}o_0&RAq-JGh#no}*ghP>R2x<Z6%vMRINEjg)1yCKhLvzt=Pxz)<1
z8w;GJlwBPmlC`BV&SFZbJd-6a+f*%ci7nG&%{JzynXG{}lhK-*8DKNn?dIGJTY%MM
zvs<jDK<EFBZmXy_pgdb}guzyjQCo=inqigw%23ko)ERFfI%>vLW_46zE->9Gx0eu|
zHB%~cr&x_PWl&PHY?TF4EjfV}t1&mjlx|E_I$E`~rw|=9qbf_+aN<=m*Ak-YrTDu+
z^{AFRB|oDq*Bx@Z3qQ?>zqkNZvg-P1OY2rvTv%9GEn|);&1_Vzg;K<9HKkjMDvPG2
zMnr^$2ZaZvq=!4|aqFQi)l@WjYc(N3LE(Wp#xzqveqKO%4S`_+!C?_0;bB1`oq|H5
zf?fXO3PP(^5D~8Zk1GhNT0vNp=0C0=xM~HVq3Z5R1@8Yz1wmCR2<{Z2%s-)#HVElk
z>1`zd0000000000000000000000000000000000000000000000000000000007{B
zJ0Fckgy&dN%-N>E6pJM*$7s#6C0Z<YAGMN@kZnvg*#d_uvG&;V)RroZh_A?+V726#
ztah`KDa~X{wVLznW=pPzQn5yQUPvdUca#7C000000000000000000000000000000
z00000000000000000000000000D%AX8f(H;IhGW2wka^hV#&%eTC;447K>feQWNi9
z5tCrG<e98?v&j~iX0oMP&3SgSCD&0jA={X$#12(**<;IdtGyf*Pru721^@s600000
z0000000000000000000000000000000000000000000000002MUFM~1TB)^=9+5Jo
zuF3}h0000000000000000000000000000000000000000000000000000000008d5
zM_Ws%RHC*{<*wD%)&y%^zOOgIvH5A72`zOhm8+|lI?<GB%C#pMGi+Loa0&4b+4%I9
zim-#)nYEQOtEYOP#bzIFwwdjwwDP<Ujnh|z6IGr<<t|FgGHYVe()1}7OID82nq|}5
zEqZ&V*``moWT%;|jzgQtq(qr=^l4^WUbb<vDNS$5b;RgnD@!F=EZHeWYk6CRjn`F#
zUds7-Pa7?yFQjLsLTP~1y5xGvfs$n<DJ4NAV%olGk12@&00000000000000000000
z000000000000000000000000000000000000PsH&?X3+Q=#yh9PR=yj>=x_f0>}UN
zZo4faIWOCoYDzQOP3GJ*Q&F&9`4j4;4eVL}ACyhET5>9ig?j$OLRH%`)Y7)=+{BTm
z^1qm`c93kgo61`V(}(Cof;_bCy8l)FJfqc=Yp*B~st=BK*R~t_cP(TaZT1{XnmOHU
zN~_XZs7~81zTsb#&B!;WCFkdwC*_+eiUqfK)3)sF90%vf+HQVIPB#{qQ!Tm9tWo-q
zHm=&1;Wg*XwAk$B*}~eoXj?|soXt6+sh0d)yOPgOt8Ll!4h>sPsixf2$>mjs8Z_FL
zeQM6?7z$^#m6u7W+Fz}0*|X;Sw+=y+KG;un$ByJ%vy(H8w#<sGUXH0c{Vv}s00000
z000000000000000000000000000000000000000000000000000002oWgd>*^t;>%
z00000000000000000000000000000000000000000000000000000000002)G7sq?
zQCBK;ldejCO6R3tq!ZHj($~`G(nr!e(wovYX`{4SDwUQ>bMJOf0RR910000000000
z000000000000000000000000000000000000000000030)!Z~Hjkl`!kWQo4XyRS1
z4V5^ZCSKFhkx*OHvSV2mFLB&gxN1~t&qmvvVJ(-j&d|dp$r-x4^mT?h^+6YnO68_=
z>|I<Od#%>7*JvDjwbF{ayH+DqZjPO+htgc#k|%_8MY<qODA`i-M9B%sAgN2<m%f+w
zNiRz)rTJ2!)K6-Bw+9LU000000000000000000000000000000000000000000000
z00000000000REkPG=Z87tHof<&NdfYat!7ybD_c2+OWoWP0Nn=YC2boFEr%_*fR5s
z)(nHH_)s%VWVO^>bBf7sGv*n@ao>iT0Cm+gYf+A&(Y9M-3kwSkE@PW%I#thW%Clsf
z3QTDRm!uj?^lhXGRNvNL^}z<30M)ITbu~r0)D)?$DN<8Yq`JN)!2Py5HMLa+H|O5f
zLmBJ3(sChPlP*f<rC+5}(lP0XbU^w@dQaLdy)L~ZZIsqZ%cZaG_KW}k0000000000
z0000000000000000000000000000000000000000000000Q`%&YIGV`YePq%Y1z>c
zsEQ9c0&(2eMWa(U+UD51jCJmlocq3xz51Y5<F3+aH9D1>W9RDFxj1%O$4=wesWlp%
zyNA+9UFiWKU6uZn-jjAouSr{_XQU^khouLk4bnO3v~)~5EPWyEzuQ9r0000000000
z000000000000000000000000000000000000000000000008)xaMP$X4P31aouQ^>
zM`x%iKI9C=abH)BN<F&KHb>CFWo%iHR2KAg2I_;Z8nsHybtwz9Wr3zFP`hZ<?n;_Q
zrApChH9D1>W9RCjG+b9&D5PuBMd`fst8_{_CLNLXN$*HIr47<*>1pXPX`%Fj^y%Fm
z9smFU000000000000000000000000000000000000000000000000000002Mzlf_w
zXk4ug9f780M@OJ4KI90*abFjWP&eA<rEygYt$F>i*!II*#ya9$lAQa#j=lPzr^Zz!
zTtfWA_BG$4(`tmu&9QTJ>|7i>tz)Ng?9>{KaQ9FeRY~WC@&f<>000000000000000
z0000000000000000000000000000000000000000z`ux>R^{rh6D}eCVf&hI@law^
z(lw#{00000000000000000000000000000000000000000000000000000000002+
zucXoF+&z>~C0!884*&oF000000000000000000000000000000000000000000000
z0000000000|0=aKuI|Dm#6N6b^DQ3AacyadkgiC-OTS3Rr6bZI=@aP#=`HCEX`8f3
zdRAJYBm)2d0000000000000000000000000000000000000000000000000000000
zaCf+BRqiS`FK6KD=?q+IIRmYSGtjs@1GUaY>#k&R)e4nL<LwO8waWsxvcRP*(3S<7
zvOw*k1nz1NrK#G|6GFNw{UM!|PDw|lucZUhhtk{9o6^hDW@(+YLP-Vy0000000000
z0000000000000000000000000000000000000000000000N}20)2dV|H%+awQ0?KW
zRk^F&+?7D5a??4FG_KAgji)n@TJ7eHReL*Q)n2Yzca=(8+ZkwFoPk>FqIFjaDtje|
zR;$ywdnj$ymi&ZtUiwiwD7`PeE^U;SON*tMl3g-M!=*k_l#&bp000000000000000
z000000000000000000000000000000000000000006-aUt;$v9CR{@NLpJ{WiI?-x
z)p__xuS`$pp^Njd=&SE*IS;jEhfhxMa2{&P4o6;ccOI&phlMA%=)AP<uBx)5HGN&Z
zw8C-Jb*Ibs-bJ;`kCe16zmD{-cBJ-Hj@&DbZYE93*`>L8RJ47C+s&hbh{i6JN5O0M
zuF!fYr&d=A7t$Zn&r+=Py%a8eA?=lRN~Kc1v_u*#nWY8NSZS-Y=5AjA;QzEoKfg`Z
zaRA5HKI>p(I6&c`i2`vPoJ+cvu?<dx4mKEDVIAN?vTSVK){Par4Yrx^<q8)QH8Ih|
zsNvwxBPJ&DC+Gnp985GZ!2=#NMq}indsg`aJk5~TD`}t4_x*g6=jpcq0000000000
z00000000000000000000000000KorFW0Z<ocI?YO|ER9Av$|*~y#8LarzKug_Px0;
z^Jil9es%Hi_TM*a9_-x`rIM9>H&=Hb+I6cs&wcb^SL%Fqe)iz>p;VzdKivJvwN!6)
zzW!F<=gE7GQC*^@=J)->$G?4f)8oZ~?>>IJt|3Y#T6VngM{YD#9gBC~f33DUzEq#O
z5mm=$etzzQ&Gk{LzGX-B$(@7M81;YoMY3V-=WM@vx8~|!VK_8~U&B;55?%;DT>a_l
z-SADg9ooWY;oWc}tZek50{{R30000000000000000000000000000000002+AMJ>m
zqk$v2p<|<Mxhu<ErMk+_IGs#SpKQx77dt9Fm1s{(oYJjl3Ijd+^Vv%yi;30yaoWFF
zD7TMIof>ZMNY^~ryFF@7_O)e}GacQP?8@ZIu3K?AH$H!OzS5an8k#+xIv<yg)uY49
zvt#4gsmq<ILR_*-{n_%!mC}L!&hAw2`kuM5blb_&Kv!jaCV6jL)SOsa>~5X8aAvG{
zDK{9!Veas`TsEEWSxI-Vq~b8sb>+gv(R|0~Lh*7k4ihIbr;qlnUM@~$<~PUTZ2Ry+
zE>|2J$d_6-#bL0ocwwP`FrPiKnyrgN|47Gt=g7*?aQp13+NP+vzP-1zwKTMHq;h$6
zq+#vAnnJQB{1UzoUx%;4r{SG&JuHXma5fwb?cv4n^xACz00000000000000000000
z000000000000000005qN+oI;gmHe57!{wQo{Ot7c#yBj^7iZ=sCq`qbI4mv{3w`PI
z?Ch!K*{$oLT%H>rtE}&c*2CD;!sx$h<FL?`o5~NBOO^CoS6x%oTwf`d7a#A4H(V@_
zuRWx?ZdwnM^GlC6B;zo$SlC~f?kvqMPhZ>=hckyK7Q4p>Dm{}Yi(BH*-M#e6RHpY>
z-|5qnPsO3@!r0{5v#%cQJKfP!ABT?KnU%Sb)^e$k>1v2W=2GU|Kxy{C>cqtO=7#tP
zu5IvN00000000000000000000000000000000000000008>}Jzy0*bD0RR9100000
z0000000000000000000000000000008?0gNQyYG)36H{W;o;f<000000000000000
w000000000000000000000000_q)oMnM4~CGUB7NkM(fwcWHMEosIQOy0R@2cAOHXW
new file mode 100644
index 0000000000000000000000000000000000000000..5df92ec55c47a6c872d3899641d535bea7484c91
GIT binary patch
literal 1114112
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
z00000;M>*yzW(5Nus)v%HWhN6KMMbr_TOHA{E;^`qekoT;fLQ;D_uSjHfO4>=HtWL
zheqnp?AbpOK6hsO*y$kn+QX-x{^XV)F7yX`miN+W{^+vShxT6>JM+wuD-Rzyc&^?0
zkx&21&mYbA2m6AZN#k_Vx@W4@nr^;j+qR^ade|DOMYEM^wKNnpCbvyCqM=f?TA7V%
zL#?o0s!k6DFHK(DabVw>*65D2vu8JNJaYA(H=gTV7u@I#9$4OEtui)NX$_4>wQWx{
z!^Xw1@pz>%zIcQ;jZc;8^|1Q*rX4%$mC31A^-3)oA8NH5){?QpLkCaRn&q*PeOsU2
zyzz-M_r7_1u0MD%2<C>$mByl()?3CKrRG%F3|=~R@%Zqb;X~y^v&|Dh@V;OEwc<yA
z{Yp<VyL(oyw^S}i_2!cGhV`wdPi~v2G{TAKa<{^^xoK@}h0Rvj*xHJsYIDiPH0x2h
zTuLT;a(v{-_C3?5wmv<vck{-Vwm)^?*=&C>5<J!&IkhlyIlLHFqv^1*=4Lm->8P62
zDkt4;zqtR&{nHo1^T%6Jdh^C#{JE(QoyznF2ZP<q`yHE^>~^<Sx1DryW-RIT$n3$r
z)3X<L@0l8TB<b}Nubg|^Thsl)(cp>Y`!rUKCWq#BVa=YeR9jP%mHN<RRBiVj&YT&2
z@|jD~Gmkv~<mQbZ`qqt4olW%zqrukYonN|iX_aqaJgT=Em9d#txMYxa<=~|&=MEn~
z)7p3T;oAP;%^N5F@cnP^Z{JzhZ}XiE00000000000000000000000000000000000
z0002DS%3R*{Webm000000000000000000000000000000000000001Pv%dD>`fZ*B
z000000000000000000000000000000000000002oX8p;hH~!He{)_l`;t$3z#nbUW
zNwNR{000000000000000000000000000000000000Pqj;y53wcoX!W+)lxld4b`IA
zO0`-ViW-x_OB-{!;QpknRv8<sw1&o`TJX~Adb;&X<#JSSF0KFiY%bWHl$FDaVKtf#
z8%vAt&E$ezN%7dsWH-8`w3yBXkGD&!(d1CQGC9?%URhe>?o=*#pgo<%!M<b-y>U5+
zKOg^Y{O{uT#6KCoJ;?$9000000000000000000000000000000000000Koru-=7&y
zO;=0xur*YRW-HZdX((z;_O8o3P*{{-m$^SxtBj3RT0`SeE%(~Y?o>Iv7*?a{u(7yZ
z_BEMZsj->KZeUSC=HATXsj+G_IaIGqPPM9678j=5lPHzTQN6i1+|!@TZ{yp4D~SIn
z{`vU%_)XV7ckMT>&0ZV6`pVT`y*hsNz8C(-3-5X1tuGYc{=bt-000000000000000
z00000000000000000000008i9YRkrTK`KZUlG9vnQ=yPa2R$S2eCxYDFg2e}9v#2%
z?5^hST{{ksKYG`?ppZ)CAL!Oj^`%dQ<6*s3JXM-(b{b6G{WG2P9iMu+R66(ck<*8c
z&eop2lzwbOx8<g}mW9mWsM#t$TWMBWVY$=f@gMoEZWlp*^z@mh4pxqrAANTCX!?iO
zcbcXKJ5BScd_H%q^H<P|hr54(ywfW4%FCTJyXjq}=Z2qa9y>ByJJ33v-oLKf`mwpz
zz4@N~mFD<NvspaS{qyF<s9qd7v9H_9PdwF4Kl07`mC1|EeTPS<ckFAWf26<L%htJG
za{26@YPEPJnrRfr8>Qw{*j;|+zCY-uXQy_K?VTArarogYyT>0+@4d6ze%D<4o_uD0
z^iyH86*h{eqNv*KBmE1X>!x3OxwU(AV&c)6$DW%Rem;Hhj&2`M%=MAYr{^}HxPSf+
zycdsztxHkke5ZrdH@@8MsC?|nT}Pj8p1g4Ra2TG<{AgdNqr%R)jxzcAe)i5x7Ed)w
z<LARhw}*STchiq=DDN7n?%sax*l26|aAr@T`)$rIC!K$?(g-J_%L{wj{`>KrronH%
zzngyX*i?CB=Tlp2vuBSVt)vg;I~U%<YSQWS@;*+4&18Sa=e9OzJv#qIU%0&ek@51#
z#F2}~C-aZ?CUZ$Hs7>>$Ihb4-BlU|>^<wwJI{WwM-reot&B5W5d&YL0n>hB|!GkmD
z9l7=t3aQu5UHj>5EgGv-!*=B}U-;>F&!^q!XvYutblYs6yN=VDqphiA-}gq*`C6%Q
zzS(W`!iLVsfA{EXX6h43<8r5QdV99p`kwg_)AKjZe3N$CN@AmP9?x`I6t>New=huW
ziWw_47PoHSL#=MO)4Jy^=}z<f;L7HQ!}`pUw#mI-OJegQ_iRqJ+vfBA^OId(yAegL
z`P%*MhspKZ`z!+h000000000000000000000000000000000000D#-AuYI_Fn<oJP
z00000000000000000000000000000000000006gHfAUGke-XsrNDcr10000000000
z00000000000000000000000000Px+fknRcYP6f4cJ)2MU1b3x^vDr*tQjm_n8pMB-
z8~^|S00000000000000000000000000000000000;Cn`I=I&IjTu=98?kbGU_9u~4
z{7R7g3jhEB00000000000000000000000000000000000-y=4pGMiGhXm<2`c;!;m
zC^ysnNi-9GFo^#={#cR#00000000000000000000000000000000000006+ZyMat5
z*mbO08V{S>PS#3|*4`*OpQPvSPiKOWvAN8#Ml>BZT9u?yIc$zMD$}h>R3AtsZEMk3
zr5bLVuh~2iMXkQ1<4pXkLHy_O$C3m900000000000000000000000000000000000
z006%24P<r|YSCDw8g3hlqVu&<<9zc(6t#XZGm@GQ9BV|=VWU+Eo7>7^bG%WRZdIas
z`ZbwdsbkgBcoIIDRBP>>tDfm^cYOUeFAM+x000000000000000000000000000000
z000000B*DX<dcmb3gXYlSK|}O2><{900000000000000000000000000000000000
z+`>cIz95ylKc7nFa(87;gyUhob*ePk%w~d~k$1lJ9iMu6J{`=oYd+Aaxgm2nYPO!O
zG%KyJJXdup$j_(e(sv|PZ*=Qs(tFC~;#d@&uaz3-o5faCY)w^~#fhj|4jb)kGYpeZ
zSSyw*&FO0CN?0yN^>(DVccIm(D5{Q?8r`wZ{(b(-^XWaQWLkyw`BWjB%?)SIeBobQ
zZ-4rewPoYsApYI>XXAgGoB#j-00000000000000000000000000000000000!2gY{
z+3bTqG*F9XN5`V*e67?t-&~Rzt+c{grCts%KT=Ho*_O#Z_{6QMPnQ~Dy*1ZhXL0zk
zboRlMuhxIH)NIwFa%G|tmKVm_xg(W*aOCb=G@YEOlt*Xkl?yZBT&v-}WJ!JTKMUfo
z$6ts)8-FtXuklCX--tgDzc&d200000000000000000000000000000000000006-4
zaAzi$3WBdaeER86Zt0uPJo4#Z`T0UO^S)pHwc<yAJ=d4%Np0TviC50OEz>>y(6?@U
zD&0N(#h;t{P_}#e()Ong^b|5Zh0Pl${_y>8?*@-tz2}Yj?&%X}?tOD_Uovc8{8SMC
zZT$84OYtY;-;V!P{FCv;`1vFV0000000000000000000000000000000000000030
zK<hIPq-xRlP^&UFR;dm3zCQCrYO)axm8#XsY*ZV%Qf*C5R_a5OQFUx6eRpPSVR7lD
zOP7WQGCP;FXodAsb$TejJl?EF<#K7L=XIIKmc$$3bW{y5hUKB`Ycso-6m`<cnX#cv
zPiB2;sIPM@WHUi3*O$z_FaG@?{#N|g@mJ$7#-EG-EdEUV$MGkUAOHXW0000000000
z0000000000000000000000000d}qpL@~OV|q0m0$+lSuvA=f_iv=7<#A=5sjGnss$
zFKN^le<X<iHvUHZ<@odQpT?h#KN0_4{9lqF000000000000000000000000000000
z000000001dXUJvJso<r2H|gyrxo*<aO|so2(@oMnnRFouX7Z`N_My<1bkZ09eh_~v
z{_FUw@fYLI#eWumCjR626G;#N00000000000000000000000000000000000006!-
zWivsluYD-A5Bc_?w|&U94?XQewtdL759v%MDD<^kUBAt<1^@s6000000000000000
z0000000000000000001h+pMpBxPF@_0RR91000000000000000000000000000000
z00000w^?8NaQ!w<0ssI200000000000000000000000000000000000ZnM7RvoU^0
z5dTg5SMguQ|117X{NLg~h<_*k&+&)je;<D+{y_Zx_&xDIkAEtu1ONa40000000000
z0000000000000000000000000|A=zgR3X)OS0^cK=p^~|ouqeNC&~49lAb#|N%oFT
zlIiOt=|WGokkrg&gH$TBv6H0p-6Ypddb&xrn`F94x+h5r>AqzAjq&v${@eKL@t5My
z#eWk2_xO+E-;F;O|EKsj<9{3foA~|lyW^jU-<4DX0000000000000000000000000
z0000000000004l0B)!>GDwWTz@1{NLx@oq*n`Z9prs+F!*;FBw?@N+=Dqra2GQFK#
zW<#e=I-T!C(;GX{^j*1ZA(hJJI!UIdlccjf*+SAVIVLr-*?hjxm&{?~wQmLSSL6Q~
ze=7cX{M+%b#qW!MHvX}=6^HS&@xl1fcrbold`D6V0000000000000000000000000
z0000000000004mRO6#(zT&geV8F}Yh-|?xJ@9Ja=o$OSQ-_XhCJK5}}cdhSadpp_8
zN4~kPlg)LrXQ%o**`7`|{k50x>}0duZ28z7oouF)O?`YrUniUHWP>jrEA(dzxl}jz
z=AgGf8_eb2^?|94OLFNulU!jwceCj6ANj4!&0OY{m-7p`vw!#KYkC%PXTI>$@6IjC
zy|5wMm+V7d{9F)!DgI3Sd+~?k5616}e<HpdPbNVC0000000000000000000000000
z0000000000008)Zvp%z_aOK?L<7Zm?&OTh*KfEEcDK&X<$ANukTBAG8&YsP;1L4e>
z(I=m|6g~6E^G~KbfkOvR)|%z9k$qdA?rjH7j*lGKzGwQ>)~6@-_Ot`XE*>A=GkmCg
zXtsGG*A5(+J-Byz_QLKxQzMUL+JQs+FN~de=E#+Y4;(yKXa`2>&+OSh5<Yik``GEe
zc3}I({ZH<nz7U>2-ioq)$z(TP`~4vPQv8|t_u>!7AB^7{|3rK_o{XQ355y1055#xJ
z>1$uP_SvKo000000000000000000000000000000000000002rDcXPEex^QAsfOjz
zww`vN6-CvtQlk^dbONQx=IESkC$M{TV&c)6$DW%Re*Vt&nN5X86t(7Sv@2Y=y#0~!
z^2o%Ii^nJP?Lhs?<i+N`!=uwX_O;fv1GQ)@8MWIY*9nC6nfWf#?Z8xdWam>`YqMvM
zAFUMHfytxe7oOeK+`VhZ;qi^_KzY|lb@%pj$3|Pzhx^)rQt8~&M@}C)I$L}8(p{av
zbHmRyj~$t<9cZ0i-wuqPKJ(PU%JK4}&ki5m&<-3vxo2$0xrt-X9XvSG+YUUr>*&+X
zlNT-@4#Sgov;#ZG_Rfr*IQ;OH-Qy4U_a&Q_j{ho%|0X#A00000000000000000000
z000000000000000006-Ej$Aesq_XLrBq?P2+L7zGc}4&L00000000000000000000
z0000000000000000C1c2C7*2kr$PM9_^a_3k`n*`0000000000000000000000000
m00000000000Qj!dn@y!scV)7jbVDZ7lT8&0xlS&VPX8Z~1V4fR
new file mode 100644
--- /dev/null
+++ b/toolkit/components/places/tests/migration/test_current_from_v36.js
@@ -0,0 +1,82 @@
+const MAX_UNSIGNED_SHORT = 65535;
+
+var gTestcases = [];
+add_task(function* setup() {
+  // The database has been pre-populated, since we cannot generate the necessary
+  // hashes from a raw connection.
+  // To add further tests, the data must be copied over to this db from an
+  // existing one.
+  yield setupPlacesDatabase("places_v36.sqlite");
+  // Fetch database contents to be migrated.
+  let path = OS.Path.join(OS.Constants.Path.profileDir, DB_FILENAME);
+  let db = yield Sqlite.openConnection({ path });
+
+  let rows = yield db.execute(`
+    SELECT h.url AS page_url, f.url AS favicon_url, mime_type, expiration, LENGTH(data) AS datalen
+    FROM moz_favicons f
+    LEFT JOIN moz_places h ON h.favicon_id = f.id`);
+  for (let row of rows) {
+    let info = {
+      icon_url: row.getResultByName("favicon_url"),
+      page_url: row.getResultByName("page_url"),
+      mime_type: row.getResultByName("mime_type"),
+      expiration: row.getResultByName("expiration"),
+      has_data: row.getResultByName("mime_type") != "broken" &&
+                row.getResultByName("datalen") > 0,
+    };
+    gTestcases.push(info);
+  }
+  yield db.close();
+});
+
+add_task(function* database_is_valid() {
+  Assert.equal(PlacesUtils.history.databaseStatus,
+               PlacesUtils.history.DATABASE_STATUS_UPGRADED);
+
+  let db = yield PlacesUtils.promiseDBConnection();
+  Assert.equal((yield db.getSchemaVersion()), CURRENT_SCHEMA_VERSION);
+});
+
+add_task(function* test_icons() {
+  let db = yield PlacesUtils.promiseDBConnection();
+  let rows = yield db.execute(`SELECT url FROM moz_favicons`);
+  Assert.equal(rows.length, 0, "favicons table should be empty");
+  for (let entry of gTestcases) {
+    do_print("");
+    do_print("Checking " + entry.icon_url + " - " + entry.page_url);
+    rows = yield db.execute(`SELECT id, expire_ms, width FROM moz_icons
+                             WHERE fixed_icon_url_hash = hash(fixup_url(:icon_url))
+                               AND icon_url = :icon_url
+                            `, { icon_url: entry.icon_url });
+    Assert.equal(!!rows.length, entry.has_data, "icon exists");
+    if (!entry.has_data) {
+      // Icon not migrated.
+      continue;
+    }
+    Assert.equal(rows[0].getResultByName("expire_ms"), 0,
+                 "expiration is correct");
+
+    let width = rows[0].getResultByName("width");
+    if (entry.mime_type == "image/svg+xml") {
+      Assert.equal(width, MAX_UNSIGNED_SHORT, "width is correct");
+    } else {
+      Assert.ok(width > 0 && (width < 16 || width % 16 == 0), "width is correct");
+    }
+
+    let icon_id = rows[0].getResultByName("id");
+
+    rows = yield db.execute(`SELECT page_id FROM moz_icons_to_pages
+                             WHERE icon_id = :icon_id`, { icon_id });
+    let has_relation = !!entry.page_url;
+    Assert.equal(!!rows.length, has_relation, "page relations found");
+    if (has_relation) {
+      let page_ids = rows.map(r => r.getResultByName("page_id"));
+
+      rows = yield db.execute(`SELECT page_url FROM moz_pages_w_icons
+                               WHERE id IN(${page_ids.join(",")})`);
+      let urls = rows.map(r => r.getResultByName("page_url"));
+      Assert.ok(urls.some(url => url == entry.page_url),
+                "page_url found");
+    }
+  }
+});
--- a/toolkit/components/places/tests/migration/xpcshell.ini
+++ b/toolkit/components/places/tests/migration/xpcshell.ini
@@ -17,20 +17,22 @@ support-files =
   places_v28.sqlite
   places_v30.sqlite
   places_v31.sqlite
   places_v32.sqlite
   places_v33.sqlite
   places_v34.sqlite
   places_v35.sqlite
   places_v36.sqlite
+  places_v37.sqlite
 
 [test_current_from_downgraded.js]
 [test_current_from_v6.js]
 [test_current_from_v11.js]
 [test_current_from_v19.js]
 [test_current_from_v24.js]
 [test_current_from_v25.js]
 [test_current_from_v26.js]
 [test_current_from_v27.js]
 [test_current_from_v31.js]
 [test_current_from_v34.js]
 [test_current_from_v35.js]
+[test_current_from_v36.js]