--- 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(¤tSchemaVersion);
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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�j#ltg<t)Fh{b2a~&^zApc)LH-d!`JS6du8dp{wEvlzS3ZHzn<rNX!@@ia_XSr
zNYrC7hqkWxI(fzRi1BroZ4T_Xue)DV&yYmpqJ{Yrj_sIx@1b3N*0_JZNBh!>d7-w#
z75l<3neP3wxX~A@MmF%<@JB(w=Nq0saD0A))+<h(@o4p%*taoi_K)4x)~omIvh_!o
zm_vRqXk_f)^Z2raX}OnjHZR)LyzBX|bk}Ajbzjua@9kr8_1@ZfKwsZ$)|aX;Hq_r7
zUnhRVh8NC1+&}A?Ut)%iU9xmX@bhy5*M3&leMx+4SJjfbix+k6GO^>XUlY!byt47+
zCZFXC@~)|NAG-Sb<H={5)qUiA>ev}`lEOkde6r|>q1a{{v+fIztEaa;xuKnWXu-k<
z-}&IVui8eYYo4y1*)(?G(4Qxt*>pPbM##mh#mA0*DQO#vBl~U9zbABl_b+bw;9ZWp
zw9eGjIwk$4zAx>PB&l1;^^!NH4VyNz<eq6Arlw9iGi~?Ohe|5{77hRa0000000000
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]