--- a/toolkit/components/places/Database.cpp
+++ b/toolkit/components/places/Database.cpp
@@ -37,24 +37,22 @@
#include "nsXULAppAPI.h"
// 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")
-#define DATABASE_RECOVER_FILENAME NS_LITERAL_STRING("places.sqlite.recover")
// 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 the database file name when it was found corrupt by a previous
+// maintenance run.
+#define PREF_FORCE_DATABASE_REPLACEMENT "places.database.replaceDatabaseOnStartup"
// Whether on corruption we should try to fix the database by cloning it.
#define PREF_DATABASE_CLONEONCORRUPTION "places.database.cloneOnCorruption"
// 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
@@ -129,50 +127,49 @@ using namespace mozilla;
namespace mozilla {
namespace places {
namespace {
////////////////////////////////////////////////////////////////////////////////
//// Helpers
+
/**
- * Checks whether exists a database backup created not longer than
+ * Get the filename for a corrupt database.
+ */
+nsString getCorruptFilename(const nsString& aDbFilename) {
+ return aDbFilename + NS_LITERAL_STRING(".corrupt");
+}
+/**
+ * Get the filename for a recover database.
+ */
+nsString getRecoverFilename(const nsString& aDbFilename) {
+ return aDbFilename + NS_LITERAL_STRING(".recover");
+}
+
+/**
+ * Checks whether exists a corrupt database file created not longer than
* RECENT_BACKUP_TIME_MICROSEC ago.
*/
bool
-hasRecentCorruptDB()
+isRecentCorruptFile(const nsCOMPtr<nsIFile>& aCorruptFile)
{
MOZ_ASSERT(NS_IsMainThread());
-
- nsCOMPtr<nsIFile> profDir;
- NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR, getter_AddRefs(profDir));
- NS_ENSURE_TRUE(profDir, false);
- nsCOMPtr<nsISimpleEnumerator> entries;
- profDir->GetDirectoryEntries(getter_AddRefs(entries));
- NS_ENSURE_TRUE(entries, false);
- bool hasMore;
- while (NS_SUCCEEDED(entries->HasMoreElements(&hasMore)) && hasMore) {
- nsCOMPtr<nsISupports> next;
- entries->GetNext(getter_AddRefs(next));
- NS_ENSURE_TRUE(next, false);
- nsCOMPtr<nsIFile> currFile = do_QueryInterface(next);
- NS_ENSURE_TRUE(currFile, false);
-
- nsAutoString leafName;
- if (NS_SUCCEEDED(currFile->GetLeafName(leafName)) &&
- leafName.Length() >= DATABASE_CORRUPT_FILENAME.Length() &&
- leafName.Find(".corrupt", DATABASE_FILENAME.Length()) != -1) {
- PRTime lastMod = 0;
- currFile->GetLastModifiedTime(&lastMod);
- NS_ENSURE_TRUE(lastMod > 0, false);
- return (PR_Now() - lastMod) > RECENT_BACKUP_TIME_MICROSEC;
- }
+ bool fileExists = false;
+ if (NS_FAILED(aCorruptFile->Exists(&fileExists)) || !fileExists) {
+ return false;
}
- return false;
+ PRTime lastMod = 0;
+ if (NS_FAILED(aCorruptFile->GetLastModifiedTime(&lastMod)) ||
+ lastMod <= 0 ||
+ (PR_Now() - lastMod) > RECENT_BACKUP_TIME_MICROSEC) {
+ return false;
+ }
+ return true;
}
/**
* Sets the connection journal mode to one of the JOURNAL_* types.
*
* @param aDBConn
* The database connection.
* @param aJournalMode
@@ -574,34 +571,64 @@ Database::EnsureConnection()
this, &Database::NotifyConnectionInitalized)
);
});
nsCOMPtr<mozIStorageService> storage =
do_GetService(MOZ_STORAGE_SERVICE_CONTRACTID);
NS_ENSURE_STATE(storage);
- // Init the database file and connect to it.
- bool databaseCreated = false;
- nsresult rv = InitDatabaseFile(storage, &databaseCreated);
- if (NS_SUCCEEDED(rv) && databaseCreated) {
+ nsCOMPtr<nsIFile> profileDir;
+ nsresult rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR,
+ getter_AddRefs(profileDir));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIFile> databaseFile;
+ rv = profileDir->Clone(getter_AddRefs(databaseFile));
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = databaseFile->Append(DATABASE_FILENAME);
+ NS_ENSURE_SUCCESS(rv, rv);
+ bool databaseExisted = false;
+ rv = databaseFile->Exists(&databaseExisted);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoString corruptDbName;
+ if (NS_SUCCEEDED(Preferences::GetString(PREF_FORCE_DATABASE_REPLACEMENT,
+ corruptDbName)) &&
+ !corruptDbName.IsEmpty()) {
+ // If this pref is set, maintenance required a database replacement, due to
+ // integrity corruption.
+ // Be sure to clear the pref to avoid handling it more than once.
+ (void)Preferences::ClearUser(PREF_FORCE_DATABASE_REPLACEMENT);
+
+ // The database is corrupt, backup and replace it with a new one.
+ nsCOMPtr<nsIFile> fileToBeReplaced;
+ bool fileExists = false;
+ if (NS_SUCCEEDED(profileDir->Clone(getter_AddRefs(fileToBeReplaced))) &&
+ NS_SUCCEEDED(fileToBeReplaced->Exists(&fileExists)) &&
+ fileExists) {
+ rv = BackupAndReplaceDatabaseFile(storage, corruptDbName, true, false);
+ 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.
+ rv = storage->OpenUnsharedDatabase(databaseFile, getter_AddRefs(mMainConn));
+ if (NS_SUCCEEDED(rv) && !databaseExisted) {
mDatabaseStatus = nsINavHistoryService::DATABASE_STATUS_CREATE;
}
else if (rv == NS_ERROR_FILE_CORRUPTED) {
// The database is corrupt, backup and replace it with a new one.
- mDatabaseStatus = nsINavHistoryService::DATABASE_STATUS_CORRUPT;
- rv = BackupAndReplaceDatabaseFile(storage, true);
+ rv = BackupAndReplaceDatabaseFile(storage, DATABASE_FILENAME, true, true);
// Fallback to catch-all handler.
}
NS_ENSURE_SUCCESS(rv, rv);
- // Ensure the icons database exists.
- rv = EnsureFaviconsDatabaseFile(storage);
- NS_ENSURE_SUCCESS(rv, 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 = SetupDatabaseConnection(storage);
bool shouldTryToCloneDb = true;
if (NS_SUCCEEDED(rv)) {
// Failing to initialize the schema may indicate a corruption.
rv = InitSchema(&databaseMigrated);
@@ -629,17 +656,20 @@ Database::EnsureConnection()
}
if (NS_WARN_IF(NS_FAILED(rv))) {
if (rv != NS_ERROR_FILE_IS_LOCKED) {
mDatabaseStatus = nsINavHistoryService::DATABASE_STATUS_CORRUPT;
}
// Some errors may not indicate a database corruption, for those cases we
// just bail out without throwing away a possibly valid places.sqlite.
if (rv == NS_ERROR_FILE_CORRUPTED) {
- rv = BackupAndReplaceDatabaseFile(storage, shouldTryToCloneDb);
+ // Since we don't know which database is corrupt, we must replace both.
+ rv = BackupAndReplaceDatabaseFile(storage, DATABASE_FAVICONS_FILENAME, false, false);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = BackupAndReplaceDatabaseFile(storage, DATABASE_FILENAME, shouldTryToCloneDb, true);
NS_ENSURE_SUCCESS(rv, rv);
// Try to initialize the new database again.
rv = SetupDatabaseConnection(storage);
NS_ENSURE_SUCCESS(rv, rv);
rv = InitSchema(&databaseMigrated);
}
// Bail out if we couldn't fix the database.
NS_ENSURE_SUCCESS(rv, rv);
@@ -662,47 +692,48 @@ Database::EnsureConnection()
initSucceeded = true;
}
return NS_OK;
}
nsresult
Database::NotifyConnectionInitalized()
{
+ MOZ_ASSERT(NS_IsMainThread());
// Notify about Places initialization.
nsCOMArray<nsIObserver> entries;
mCacheObservers.GetEntries(entries);
for (int32_t idx = 0; idx < entries.Count(); ++idx) {
MOZ_ALWAYS_SUCCEEDS(entries[idx]->Observe(nullptr, TOPIC_PLACES_INIT_COMPLETE, nullptr));
}
nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
if (obs) {
MOZ_ALWAYS_SUCCEEDS(obs->NotifyObservers(nullptr, TOPIC_PLACES_INIT_COMPLETE, nullptr));
}
return NS_OK;
}
nsresult
-Database::EnsureFaviconsDatabaseFile(nsCOMPtr<mozIStorageService>& aStorage)
+Database::EnsureFaviconsDatabaseAttached(const nsCOMPtr<mozIStorageService>& aStorage)
{
MOZ_ASSERT(NS_IsMainThread());
nsCOMPtr<nsIFile> databaseFile;
- nsresult rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR,
- getter_AddRefs(databaseFile));
+ NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR, getter_AddRefs(databaseFile));
+ NS_ENSURE_STATE(databaseFile);
+ nsresult rv = databaseFile->Append(DATABASE_FAVICONS_FILENAME);
NS_ENSURE_SUCCESS(rv, rv);
- rv = databaseFile->Append(DATABASE_FAVICONS_FILENAME);
+ nsString iconsPath;
+ rv = databaseFile->GetPath(iconsPath);
NS_ENSURE_SUCCESS(rv, rv);
- bool databaseFileExists = false;
- rv = databaseFile->Exists(&databaseFileExists);
- NS_ENSURE_SUCCESS(rv, rv);
-
- if (databaseFileExists) {
- return NS_OK;
+ bool fileExists = false;
+ if (NS_SUCCEEDED(databaseFile->Exists(&fileExists)) && fileExists) {
+ return AttachDatabase(mMainConn, NS_ConvertUTF16toUTF8(iconsPath),
+ NS_LITERAL_CSTRING("favicons"));
}
// Open the database file, this will also create it.
nsCOMPtr<mozIStorageConnection> conn;
rv = aStorage->OpenUnsharedDatabase(databaseFile, getter_AddRefs(conn));
NS_ENSURE_SUCCESS(rv, rv);
{
@@ -742,81 +773,77 @@ Database::EnsureFaviconsDatabaseFile(nsC
rv = conn->ExecuteSimpleSQL(CREATE_MOZ_ICONS_TO_PAGES);
NS_ENSURE_SUCCESS(rv, rv);
rv = transaction.Commit();
NS_ENSURE_SUCCESS(rv, rv);
// The scope exit will take care of closing the connection.
}
+ rv = AttachDatabase(mMainConn, NS_ConvertUTF16toUTF8(iconsPath),
+ NS_LITERAL_CSTRING("favicons"));
+ 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,
- getter_AddRefs(databaseFile));
- NS_ENSURE_SUCCESS(rv, rv);
- rv = databaseFile->Append(DATABASE_FILENAME);
- NS_ENSURE_SUCCESS(rv, rv);
-
- bool databaseFileExists = false;
- rv = databaseFile->Exists(&databaseFileExists);
- NS_ENSURE_SUCCESS(rv, rv);
-
- if (databaseFileExists &&
- Preferences::GetBool(PREF_FORCE_DATABASE_REPLACEMENT, false)) {
- // If this pref is set, Maintenance required a database replacement, due to
- // integrity corruption.
- // Be sure to clear the pref to avoid handling it more than once.
- (void)Preferences::ClearUser(PREF_FORCE_DATABASE_REPLACEMENT);
-
- return NS_ERROR_FILE_CORRUPTED;
- }
-
- // 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.
- rv = aStorage->OpenUnsharedDatabase(databaseFile, getter_AddRefs(mMainConn));
- NS_ENSURE_SUCCESS(rv, rv);
-
- *aNewDatabaseCreated = !databaseFileExists;
- return NS_OK;
-}
nsresult
Database::BackupAndReplaceDatabaseFile(nsCOMPtr<mozIStorageService>& aStorage,
- bool aTryToClone)
+ const nsString& aDbFilename,
+ bool aTryToClone,
+ bool aReopenConnection)
{
MOZ_ASSERT(NS_IsMainThread());
+
+ if (aDbFilename.Equals(DATABASE_FILENAME)) {
+ mDatabaseStatus = nsINavHistoryService::DATABASE_STATUS_CORRUPT;
+ } else {
+ // Due to OS file lockings, attached databases can't be cloned properly,
+ // otherwise trying to reattach them later would fail.
+ aTryToClone = false;
+ }
+
nsCOMPtr<nsIFile> profDir;
nsresult rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR,
getter_AddRefs(profDir));
NS_ENSURE_SUCCESS(rv, rv);
nsCOMPtr<nsIFile> databaseFile;
rv = profDir->Clone(getter_AddRefs(databaseFile));
NS_ENSURE_SUCCESS(rv, rv);
- rv = databaseFile->Append(DATABASE_FILENAME);
+ rv = databaseFile->Append(aDbFilename);
NS_ENSURE_SUCCESS(rv, rv);
- // If we have
- // already failed in the last 24 hours avoid to create another corrupt file,
+ // If we already failed in the last 24 hours avoid to create another corrupt file,
// since doing so, in some situation, could cause us to create a new corrupt
// file at every try to access any Places service. That is bad because it
// would quickly fill the user's disk space without any notice.
- if (!hasRecentCorruptDB()) {
+ nsCOMPtr<nsIFile> corruptFile;
+ rv = profDir->Clone(getter_AddRefs(corruptFile));
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsString corruptFilename = getCorruptFilename(aDbFilename);
+ rv = corruptFile->Append(corruptFilename);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (!isRecentCorruptFile(corruptFile)) {
+ // Ensure we never create more than one corrupt file.
+ nsCOMPtr<nsIFile> corruptFile;
+ rv = profDir->Clone(getter_AddRefs(corruptFile));
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsString corruptFilename = getCorruptFilename(aDbFilename);
+ rv = corruptFile->Append(corruptFilename);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = corruptFile->Remove(false);
+ if (NS_FAILED(rv) && rv != NS_ERROR_FILE_TARGET_DOES_NOT_EXIST &&
+ rv != NS_ERROR_FILE_NOT_FOUND) {
+ return rv;
+ }
+
nsCOMPtr<nsIFile> backup;
- (void)aStorage->BackupDatabaseFile(databaseFile, DATABASE_CORRUPT_FILENAME,
- profDir, getter_AddRefs(backup));
+ Unused << aStorage->BackupDatabaseFile(databaseFile, corruptFilename,
+ profDir, getter_AddRefs(backup));
}
// If anything fails from this point on, we have a stale connection or
// database file, and there's not much more we can do.
// The only thing we can try to do is to replace the database on the next
// startup, and report the problem through telemetry.
{
enum eCorruptDBReplaceStage : int8_t {
@@ -830,81 +857,87 @@ Database::BackupAndReplaceDatabaseFile(n
eCorruptDBReplaceStage stage = stage_closing;
auto guard = MakeScopeExit([&]() {
if (stage != stage_replaced) {
// Reaching this point means the database is corrupt and we failed to
// replace it. For this session part of the application related to
// bookmarks and history will misbehave. The frontend may show a
// "locked" notification to the user though.
// Set up a pref to try replacing the database at the next startup.
- Preferences::SetBool(PREF_FORCE_DATABASE_REPLACEMENT, true);
+ Preferences::SetString(PREF_FORCE_DATABASE_REPLACEMENT, aDbFilename);
}
// Report the corruption through telemetry.
Telemetry::Accumulate(Telemetry::PLACES_DATABASE_CORRUPTION_HANDLING_STAGE,
static_cast<int8_t>(stage));
});
// Close database connection if open.
if (mMainConn) {
rv = mMainConn->SpinningSynchronousClose();
NS_ENSURE_SUCCESS(rv, rv);
+ mMainConn = nullptr;
}
// Remove the broken database.
stage = stage_removing;
rv = databaseFile->Remove(false);
- if (NS_FAILED(rv) && rv != NS_ERROR_FILE_TARGET_DOES_NOT_EXIST) {
+ if (NS_FAILED(rv) && rv != NS_ERROR_FILE_TARGET_DOES_NOT_EXIST &&
+ rv != NS_ERROR_FILE_NOT_FOUND) {
return rv;
}
// Create a new database file and try to clone tables from the corrupt one.
bool cloned = false;
if (aTryToClone && Preferences::GetBool(PREF_DATABASE_CLONEONCORRUPTION, true)) {
stage = stage_cloning;
- rv = TryToCloneTablesFromCorruptDatabase(aStorage);
+ rv = TryToCloneTablesFromCorruptDatabase(aStorage, databaseFile);
if (NS_SUCCEEDED(rv)) {
+ // If we cloned successfully, we should not consider the database
+ // corrupt anymore, otherwise we could reimport default bookmarks.
mDatabaseStatus = nsINavHistoryService::DATABASE_STATUS_OK;
cloned = true;
}
}
- // Use an unshared connection, it will consume more memory but avoid shared
- // cache contentions across threads.
- stage = stage_reopening;
- rv = aStorage->OpenUnsharedDatabase(databaseFile, getter_AddRefs(mMainConn));
- NS_ENSURE_SUCCESS(rv, rv);
+ if (aReopenConnection) {
+ // Use an unshared connection, it will consume more memory but avoid shared
+ // cache contentions across threads.
+ stage = stage_reopening;
+ rv = aStorage->OpenUnsharedDatabase(databaseFile, getter_AddRefs(mMainConn));
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
stage = cloned ? stage_cloned : stage_replaced;
}
return NS_OK;
}
nsresult
-Database::TryToCloneTablesFromCorruptDatabase(nsCOMPtr<mozIStorageService>& aStorage)
+Database::TryToCloneTablesFromCorruptDatabase(const nsCOMPtr<mozIStorageService>& aStorage,
+ const nsCOMPtr<nsIFile>& aDatabaseFile)
{
MOZ_ASSERT(NS_IsMainThread());
- nsCOMPtr<nsIFile> profDir;
- nsresult rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR,
- getter_AddRefs(profDir));
- NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoString filename;
+ nsresult rv = aDatabaseFile->GetLeafName(filename);
nsCOMPtr<nsIFile> corruptFile;
- rv = profDir->Clone(getter_AddRefs(corruptFile));
+ rv = aDatabaseFile->Clone(getter_AddRefs(corruptFile));
NS_ENSURE_SUCCESS(rv, rv);
- rv = corruptFile->Append(DATABASE_CORRUPT_FILENAME);
+ rv = corruptFile->SetLeafName(getCorruptFilename(filename));
NS_ENSURE_SUCCESS(rv, rv);
nsAutoString path;
rv = corruptFile->GetPath(path);
NS_ENSURE_SUCCESS(rv, rv);
nsCOMPtr<nsIFile> recoverFile;
- rv = profDir->Clone(getter_AddRefs(recoverFile));
+ rv = aDatabaseFile->Clone(getter_AddRefs(recoverFile));
NS_ENSURE_SUCCESS(rv, rv);
- rv = recoverFile->Append(DATABASE_RECOVER_FILENAME);
+ rv = recoverFile->SetLeafName(getRecoverFilename(filename));
NS_ENSURE_SUCCESS(rv, rv);
// Ensure there's no previous recover file.
rv = recoverFile->Remove(false);
if (NS_FAILED(rv) && rv != NS_ERROR_FILE_TARGET_DOES_NOT_EXIST &&
rv != NS_ERROR_FILE_NOT_FOUND) {
return rv;
}
@@ -979,17 +1012,17 @@ Database::TryToCloneTablesFromCorruptDat
rv = stmt->Finalize();
NS_ENSURE_SUCCESS(rv, rv);
rv = transaction.Commit();
NS_ENSURE_SUCCESS(rv, rv);
Unused << conn->Close();
conn = nullptr;
- rv = recoverFile->RenameTo(profDir, DATABASE_FILENAME);
+ rv = recoverFile->RenameTo(nullptr, filename);
NS_ENSURE_SUCCESS(rv, rv);
Unused << corruptFile->Remove(false);
guard.release();
return NS_OK;
}
nsresult
@@ -1054,35 +1087,31 @@ Database::SetupDatabaseConnection(nsCOMP
if (stmt && NS_SUCCEEDED(stmt->ExecuteStep(&hasResult)) && hasResult) {
int32_t fkState = stmt->AsInt32(0);
MOZ_ASSERT(fkState, "Foreign keys should be enabled");
}
}
#endif
// Attach the favicons database to the main connection.
- nsCOMPtr<nsIFile> iconsFile;
- rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR,
- getter_AddRefs(iconsFile));
- NS_ENSURE_SUCCESS(rv, rv);
- rv = iconsFile->Append(DATABASE_FAVICONS_FILENAME);
- NS_ENSURE_SUCCESS(rv, rv);
- nsString iconsPath;
- rv = iconsFile->GetPath(iconsPath);
- NS_ENSURE_SUCCESS(rv, rv);
- rv = AttachDatabase(mMainConn, NS_ConvertUTF16toUTF8(iconsPath),
- NS_LITERAL_CSTRING("favicons"));
+ rv = EnsureFaviconsDatabaseAttached(aStorage);
if (NS_FAILED(rv)) {
// The favicons database may be corrupt. Try to replace and reattach it.
- rv = iconsFile->Remove(true);
+ nsCOMPtr<nsIFile> iconsFile;
+ rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR,
+ getter_AddRefs(iconsFile));
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = iconsFile->Append(DATABASE_FAVICONS_FILENAME);
NS_ENSURE_SUCCESS(rv, rv);
- rv = EnsureFaviconsDatabaseFile(aStorage);
- NS_ENSURE_SUCCESS(rv, rv);
- rv = AttachDatabase(mMainConn, NS_ConvertUTF16toUTF8(iconsPath),
- NS_LITERAL_CSTRING("favicons"));
+ rv = iconsFile->Remove(false);
+ if (NS_FAILED(rv) && rv != NS_ERROR_FILE_TARGET_DOES_NOT_EXIST &&
+ rv != NS_ERROR_FILE_NOT_FOUND) {
+ return rv;
+ }
+ rv = EnsureFaviconsDatabaseAttached(aStorage);
NS_ENSURE_SUCCESS(rv, rv);
}
// Create favicons temp entities.
rv = mMainConn->ExecuteSimpleSQL(CREATE_ICONS_AFTERINSERT_TRIGGER);
NS_ENSURE_SUCCESS(rv, rv);
// We use our functions during migration, so initialize them now.
--- a/toolkit/components/places/Database.h
+++ b/toolkit/components/places/Database.h
@@ -235,55 +235,51 @@ protected:
* Finalizes the cached statements and closes the database connection.
* A TOPIC_PLACES_CONNECTION_CLOSED notification is fired when done.
*/
void Shutdown();
bool IsShutdownStarted() const;
/**
- * Initializes the database file. If the database does not exist or is
- * corrupt, a new one is created. In case of corruption it also creates a
- * backup copy of the database.
- *
- * @param aStorage
- * mozStorage service instance.
- * @param aNewDatabaseCreated
- * whether a new database file has been created.
- */
- nsresult InitDatabaseFile(nsCOMPtr<mozIStorageService>& aStorage,
- bool* aNewDatabaseCreated);
-
- /**
* Ensure the favicons database file exists.
*
* @param aStorage
* mozStorage service instance.
*/
- nsresult EnsureFaviconsDatabaseFile(nsCOMPtr<mozIStorageService>& aStorage);
+ nsresult EnsureFaviconsDatabaseAttached(const nsCOMPtr<mozIStorageService>& aStorage);
/**
* Creates a database backup and replaces the original file with a new
* one.
*
* @param aStorage
* mozStorage service instance.
+ * @param aDbfilename
+ * the database file name to replace.
* @param aTryToClone
* whether we should try to clone a corrupt database.
+ * @param aReopenConnection
+ * whether we should open a new connection to the replaced database.
*/
nsresult BackupAndReplaceDatabaseFile(nsCOMPtr<mozIStorageService>& aStorage,
- bool aTryToClone);
+ const nsString& aDbFilename,
+ bool aTryToClone,
+ bool aReopenConnection);
/**
* Tries to recover tables and their contents from a corrupt database.
*
* @param aStorage
* mozStorage service instance.
+ * @param aDatabaseFile
+ * nsIFile pointing to the places.sqlite file considered corrupt.
*/
- nsresult TryToCloneTablesFromCorruptDatabase(nsCOMPtr<mozIStorageService>& aStorage);
+ nsresult TryToCloneTablesFromCorruptDatabase(const nsCOMPtr<mozIStorageService>& aStorage,
+ const nsCOMPtr<nsIFile>& aDatabaseFile);
/**
* Set up the connection environment through PRAGMAs.
* Will return NS_ERROR_FILE_CORRUPTED if any critical setting fails.
*
* @param aStorage
* mozStorage service instance.
*/
--- a/toolkit/components/places/PlacesDBUtils.jsm
+++ b/toolkit/components/places/PlacesDBUtils.jsm
@@ -6,16 +6,17 @@
const BYTES_PER_MEBIBYTE = 1048576;
ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
XPCOMUtils.defineLazyModuleGetters(this, {
Services: "resource://gre/modules/Services.jsm",
OS: "resource://gre/modules/osfile.jsm",
PlacesUtils: "resource://gre/modules/PlacesUtils.jsm",
+ Sqlite: "resource://gre/modules/Sqlite.jsm",
});
var EXPORTED_SYMBOLS = [ "PlacesDBUtils" ];
var PlacesDBUtils = {
_isShuttingDown: false,
shutdown() {
PlacesDBUtils._isShuttingDown = true;
@@ -100,54 +101,34 @@ var PlacesDBUtils = {
*
* @return {Promise} resolves if database is sane or is made sane.
* @resolves to an array of logs for this task.
* @rejects if we're unable to fix corruption or unable to check status.
*/
async checkIntegrity() {
let logs = [];
- async function integrity(db) {
- let row;
- await db.execute("PRAGMA integrity_check", null, (r, cancel) => {
- row = r;
- cancel();
- });
- return row.getResultByIndex(0) === "ok";
- }
- try {
- // Run a integrity check, but stop at the first error.
- await PlacesUtils.withConnectionWrapper("PlacesDBUtils: check the integrity", async db => {
- let isOk = await integrity(db);
- if (isOk) {
- logs.push("The database is sane");
- } else {
- // We stopped due to an integrity corruption, try to fix if possible.
- logs.push("The database is corrupt");
- // Try to reindex, this often fixes simple indices corruption.
- await db.execute("REINDEX");
- logs.push("The database has been REINDEXed");
- isOk = await integrity(db);
- if (isOk) {
- logs.push("The database is now sane");
- } else {
- logs.push("The database is still corrupt");
- Services.prefs.setBoolPref("places.database.replaceOnStartup", true);
- PlacesDBUtils.clearPendingTasks();
- throw new Error("Unable to fix corruption, database will be replaced on next startup");
- }
+ async function check(dbName) {
+ try {
+ await integrity(dbName);
+ logs.push(`The ${dbName} database is sane`);
+ } catch (ex) {
+ PlacesDBUtils.clearPendingTasks();
+ if (ex.status == Cr.NS_ERROR_FILE_CORRUPTED) {
+ logs.push(`The ${dbName} database is corrupt`);
+ Services.prefs.setCharPref("places.database.replaceDatabaseOnStartup", dbName);
+ throw new Error(`Unable to fix corruption, ${dbName} will be replaced on next startup`);
}
- });
- } catch (ex) {
- if (ex.message.indexOf("Unable to fix corruption") !== 0) {
- // There was some other error, so throw.
- PlacesDBUtils.clearPendingTasks();
- throw new Error("Unable to check database integrity");
+ throw new Error(`Unable to check ${dbName} integrity: ${ex}`);
}
}
+
+ await check("places.sqlite");
+ await check("favicons.sqlite");
+
return logs;
},
invalidateCaches() {
let logs = [];
return PlacesUtils.withConnectionWrapper("PlacesDBUtils: invalidate caches", async db => {
let idsWithInvalidGuidsRows = await db.execute(`
SELECT id FROM moz_bookmarks
@@ -1081,8 +1062,50 @@ var PlacesDBUtils = {
let result =
await task().then(logs => { return { succeeded: true, logs }; })
.catch(err => { return { succeeded: false, logs: [err.message] }; });
tasksMap.set(task.name, result);
}
return tasksMap;
}
};
+
+async function integrity(dbName) {
+ async function check(db) {
+ let row;
+ await db.execute("PRAGMA integrity_check", null, (r, cancel) => {
+ row = r;
+ cancel();
+ });
+ return row.getResultByIndex(0) === "ok";
+ }
+
+ // Create a new connection for this check, so we can operate independently
+ // from a broken Places service.
+ // openConnection returns an exception with .status == Cr.NS_ERROR_FILE_CORRUPTED,
+ // we should do the same everywhere we want maintenance to try replacing the
+ // database on next startup.
+ let path = OS.Path.join(OS.Constants.Path.profileDir, dbName);
+ let db = await Sqlite.openConnection({ path });
+ try {
+ if (await check(db))
+ return;
+
+ // We stopped due to an integrity corruption, try to fix it if possible.
+ // First, try to reindex, this often fixes simple indices problems.
+ try {
+ await db.execute("REINDEX");
+ } catch (ex) {
+ let error = new Error("Impossible to reindex database");
+ error.status = Cr.NS_ERROR_FILE_CORRUPTED;
+ throw error;
+ }
+
+ // Check again.
+ if (!await check(db)) {
+ let error = new Error("The database is still corrupt");
+ error.status = Cr.NS_ERROR_FILE_CORRUPTED;
+ throw error;
+ }
+ } finally {
+ await db.close();
+ }
+}
--- a/toolkit/components/places/tests/head_common.js
+++ b/toolkit/components/places/tests/head_common.js
@@ -918,23 +918,24 @@ function mapItemIdToInternalRootName(aIt
const DB_FILENAME = "places.sqlite";
/**
* Sets the database to use for the given test. This should be the very first
* thing in the test, otherwise this database will not be used!
*
* @param aFileName
* The filename of the database to use. This database must exist in
- * toolkit/components/places/tests/migration!
- * @return {Promise}
+ * the test folder.
+ * @return {Promise} the final path to the database
*/
-var setupPlacesDatabase = async function(aFileName, aDestFileName = DB_FILENAME) {
+async function setupPlacesDatabase(aFileName, aDestFileName = DB_FILENAME) {
let currentDir = await OS.File.getCurrentDirectory();
let src = OS.Path.join(currentDir, aFileName);
Assert.ok((await OS.File.exists(src)), "Database file found");
// Ensure that our database doesn't already exist.
let dest = OS.Path.join(OS.Constants.Path.profileDir, aDestFileName);
Assert.ok(!(await OS.File.exists(dest)), "Database file should not exist yet");
await OS.File.copy(src, dest);
-};
+ return dest;
+}
rename from toolkit/components/places/tests/unit/corruptDB.sqlite
rename to toolkit/components/places/tests/maintenance/corruptDB.sqlite
new file mode 100644
--- /dev/null
+++ b/toolkit/components/places/tests/maintenance/head.js
@@ -0,0 +1,92 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+ChromeUtils.import("resource://gre/modules/Services.jsm");
+
+// Import common head.
+{
+ /* import-globals-from ../head_common.js */
+ let commonFile = do_get_file("../head_common.js", false);
+ let uri = Services.io.newFileURI(commonFile);
+ Services.scriptloader.loadSubScript(uri.spec, this);
+}
+
+// Put any other stuff relative to this test folder below.
+
+XPCOMUtils.defineLazyModuleGetters(this, {
+ PlacesDBUtils: "resource://gre/modules/PlacesDBUtils.jsm",
+});
+
+async function createCorruptDb(filename) {
+ let path = OS.Path.join(OS.Constants.Path.profileDir, filename);
+ await OS.File.remove(path, {ignoreAbsent: true});
+ // Create a corrupt database.
+ let dir = await OS.File.getCurrentDirectory();
+ let src = OS.Path.join(dir, "corruptDB.sqlite");
+ await OS.File.copy(src, path);
+}
+
+/**
+ * Used in _replaceOnStartup_ tests as common test code. It checks whether we
+ * are properly cloning or replacing a corrupt database.
+ *
+ * @param {string} src
+ * Path to a test database, relative to this test folder
+ * @param {string} filename
+ * Database file name
+ * @param {boolean} shouldClone
+ * Whether we expect the database to be cloned
+ * @param {boolean} dbStatus
+ * The expected final database status
+ */
+async function test_database_replacement(src, filename, shouldClone, dbStatus) {
+ registerCleanupFunction(() => {
+ Services.prefs.clearUserPref("places.database.cloneOnCorruption");
+ });
+ Services.prefs.setBoolPref("places.database.cloneOnCorruption", shouldClone);
+
+ // Only the main database file (places.sqlite) will be cloned, because
+ // attached databased would break due to OS file lockings.
+ let willClone = shouldClone && filename == DB_FILENAME;
+
+ // Ensure that our databases don't exist yet.
+ let dest = OS.Path.join(OS.Constants.Path.profileDir, filename);
+ Assert.ok(!await OS.File.exists(dest), `"${filename} should not exist initially`);
+ let corrupt = OS.Path.join(OS.Constants.Path.profileDir, `${filename}.corrupt`);
+ Assert.ok(!await OS.File.exists(corrupt), `${filename}.corrupt should not exist initially`);
+
+ let dir = await OS.File.getCurrentDirectory();
+ src = OS.Path.join(dir, src);
+ await OS.File.copy(src, dest);
+
+ // Create some unique stuff to check later.
+ let db = await Sqlite.openConnection({ path: dest });
+ await db.execute(`CREATE TABLE moz_cloned (id INTEGER PRIMARY KEY)`);
+ await db.execute(`CREATE TABLE not_cloned (id INTEGER PRIMARY KEY)`);
+ await db.execute(`DELETE FROM moz_cloned`); // Shouldn't throw.
+ await db.close();
+
+ // Open the database with Places.
+ Services.prefs.setCharPref("places.database.replaceDatabaseOnStartup", filename);
+ Assert.equal(PlacesUtils.history.databaseStatus, dbStatus);
+
+ Assert.ok(await OS.File.exists(dest), "The database should exist");
+
+ // Check the new database still contains our special data.
+ db = await Sqlite.openConnection({ path: dest });
+ if (willClone) {
+ await db.execute(`DELETE FROM moz_cloned`); // Shouldn't throw.
+ }
+
+ // Check the new database is really a new one.
+ await Assert.rejects(db.execute(`DELETE FROM not_cloned`),
+ /no such table/,
+ "The database should have been replaced");
+ await db.close();
+
+ if (willClone) {
+ Assert.ok(!await OS.File.exists(corrupt), "The corrupt db should not exist");
+ } else {
+ Assert.ok(await OS.File.exists(corrupt), "The corrupt db should exist");
+ }
+}
new file mode 100644
--- /dev/null
+++ b/toolkit/components/places/tests/maintenance/test_corrupt_favicons.js
@@ -0,0 +1,14 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Tests that history initialization correctly handles a corrupt favicons file
+// that can't be opened.
+
+add_task(async function() {
+ await createCorruptDb("favicons.sqlite");
+
+ Assert.equal(PlacesUtils.history.databaseStatus,
+ PlacesUtils.history.DATABASE_STATUS_CREATE);
+ let db = await PlacesUtils.promiseDBConnection();
+ await db.execute("SELECT * FROM moz_icons"); // Should not fail.
+});
new file mode 100644
--- /dev/null
+++ b/toolkit/components/places/tests/maintenance/test_corrupt_favicons_schema.js
@@ -0,0 +1,21 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Tests that history initialization correctly handles a corrupt places schema.
+
+add_task(async function() {
+ let path = await setupPlacesDatabase("../migration/favicons_v41.sqlite");
+
+ // Ensure the database will go through a migration that depends on moz_places
+ // and break the schema by dropping that table.
+ let db = await Sqlite.openConnection({ path });
+ await db.setSchemaVersion(38);
+ await db.execute("DROP TABLE moz_icons");
+ await db.close();
+
+ Assert.equal(PlacesUtils.history.databaseStatus,
+ PlacesUtils.history.DATABASE_STATUS_CORRUPT);
+ db = await PlacesUtils.promiseDBConnection();
+ let rows = await db.execute("SELECT 1 FROM moz_icons");
+ Assert.equal(rows.length, 0, "Found no icons");
+});
new file mode 100644
--- /dev/null
+++ b/toolkit/components/places/tests/maintenance/test_corrupt_places_schema.js
@@ -0,0 +1,20 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Tests that history initialization correctly handles a corrupt places schema.
+
+add_task(async function() {
+ let path = await setupPlacesDatabase("../migration/places_v38.sqlite");
+
+ // Ensure the database will go through a migration that depends on moz_places
+ // and break the schema by dropping that table.
+ let db = await Sqlite.openConnection({ path });
+ await db.setSchemaVersion(38);
+ await db.execute("DROP TABLE moz_places");
+ await db.close();
+
+ Assert.equal(PlacesUtils.history.databaseStatus,
+ PlacesUtils.history.DATABASE_STATUS_CORRUPT);
+ db = await PlacesUtils.promiseDBConnection();
+ await db.execute("SELECT * FROM moz_places LIMIT 1"); // Should not fail.
+});
rename from toolkit/components/places/tests/unit/test_corrupt_telemetry.js
rename to toolkit/components/places/tests/maintenance/test_corrupt_telemetry.js
--- a/toolkit/components/places/tests/unit/test_corrupt_telemetry.js
+++ b/toolkit/components/places/tests/maintenance/test_corrupt_telemetry.js
@@ -1,23 +1,16 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
// Tests that history initialization correctly handles a request to forcibly
// replace the current database.
add_task(async function() {
- let profileDBPath = await OS.Path.join(OS.Constants.Path.profileDir, "places.sqlite");
- await OS.File.remove(profileDBPath, {ignoreAbsent: true});
- // Ensure that our database doesn't already exist.
- Assert.ok(!(await OS.File.exists(profileDBPath)), "places.sqlite shouldn't exist");
- let dir = await OS.File.getCurrentDirectory();
- let src = OS.Path.join(dir, "corruptDB.sqlite");
- await OS.File.copy(src, profileDBPath);
- Assert.ok(await OS.File.exists(profileDBPath), "places.sqlite should exist");
+ await createCorruptDb("places.sqlite");
let count = Services.telemetry
.getHistogramById("PLACES_DATABASE_CORRUPTION_HANDLING_STAGE")
.snapshot()
.counts[3];
Assert.equal(count, 0, "There should be no telemetry");
Assert.equal(PlacesUtils.history.databaseStatus,
rename from toolkit/components/places/tests/unit/test_database_replaceOnStartup.js
rename to toolkit/components/places/tests/maintenance/test_favicons_replaceOnStartup.js
--- a/toolkit/components/places/tests/unit/test_database_replaceOnStartup.js
+++ b/toolkit/components/places/tests/maintenance/test_favicons_replaceOnStartup.js
@@ -1,60 +1,14 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
// Tests that history initialization correctly handles a request to forcibly
// replace the current database.
add_task(async function() {
- registerCleanupFunction(function() {
- Services.prefs.clearUserPref("places.database.cloneOnCorruption");
- });
- test_replacement(true);
- test_replacement(false);
+ await test_database_replacement("../migration/favicons_v41.sqlite",
+ "favicons.sqlite",
+ false,
+ PlacesUtils.history.DATABASE_STATUS_CREATE);
});
-async function test_replacement(shouldClone) {
- Services.prefs.setBoolPref("places.database.cloneOnCorruption", shouldClone);
- // Ensure that our databases don't exist yet.
- let sane = OS.Path.join(OS.Constants.Path.profileDir, "places.sqlite");
- Assert.ok(!await OS.File.exists(sane), "The db should not exist initially");
- let corrupt = OS.Path.join(OS.Constants.Path.profileDir, "places.sqlite.corrupt");
- Assert.ok(!await OS.File.exists(corrupt), "The corrupt db should not exist initially");
- let file = do_get_file("../migration/places_v38.sqlite");
- file.copyToFollowingLinks(gProfD, "places.sqlite");
- file = gProfD.clone();
- file.append("places.sqlite");
-
- // Create some unique stuff to check later.
- let db = Services.storage.openUnsharedDatabase(file);
- db.executeSimpleSQL("CREATE TABLE moz_cloned(id INTEGER PRIMARY KEY)");
- db.executeSimpleSQL("CREATE TABLE not_cloned (id INTEGER PRIMARY KEY)");
- db.executeSimpleSQL("DELETE FROM moz_cloned"); // Shouldn't throw.
- db.close();
-
- // Open the database with Places.
- Services.prefs.setBoolPref("places.database.replaceOnStartup", true);
- let expectedStatus = shouldClone ? PlacesUtils.history.DATABASE_STATUS_UPGRADED
- : PlacesUtils.history.DATABASE_STATUS_CORRUPT;
- Assert.equal(PlacesUtils.history.databaseStatus, expectedStatus);
-
- Assert.ok(await OS.File.exists(sane), "The database should exist");
-
- // Check the new database still contains our special data.
- db = Services.storage.openUnsharedDatabase(file);
- if (shouldClone) {
- db.executeSimpleSQL("DELETE FROM moz_cloned"); // Shouldn't throw.
- }
-
- // Check the new database is really a new one.
- Assert.throws(() => {
- db.executeSimpleSQL("DELETE FROM not_cloned");
- }, "The database should have been replaced");
- db.close();
-
- if (shouldClone) {
- Assert.ok(!await OS.File.exists(corrupt), "The corrupt db should not exist");
- } else {
- Assert.ok(await OS.File.exists(corrupt), "The corrupt db should exist");
- }
-}
copy from toolkit/components/places/tests/unit/test_database_replaceOnStartup.js
copy to toolkit/components/places/tests/maintenance/test_favicons_replaceOnStartup_clone.js
--- a/toolkit/components/places/tests/unit/test_database_replaceOnStartup.js
+++ b/toolkit/components/places/tests/maintenance/test_favicons_replaceOnStartup_clone.js
@@ -1,60 +1,14 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
// Tests that history initialization correctly handles a request to forcibly
// replace the current database.
add_task(async function() {
- registerCleanupFunction(function() {
- Services.prefs.clearUserPref("places.database.cloneOnCorruption");
- });
- test_replacement(true);
- test_replacement(false);
+ // In reality, this won't try to clone the database, because attached
+ // databases cannot be supported when cloning. This test also verifies that.
+ await test_database_replacement("../migration/favicons_v41.sqlite",
+ "favicons.sqlite",
+ true,
+ PlacesUtils.history.DATABASE_STATUS_CREATE);
});
-
-async function test_replacement(shouldClone) {
- Services.prefs.setBoolPref("places.database.cloneOnCorruption", shouldClone);
- // Ensure that our databases don't exist yet.
- let sane = OS.Path.join(OS.Constants.Path.profileDir, "places.sqlite");
- Assert.ok(!await OS.File.exists(sane), "The db should not exist initially");
- let corrupt = OS.Path.join(OS.Constants.Path.profileDir, "places.sqlite.corrupt");
- Assert.ok(!await OS.File.exists(corrupt), "The corrupt db should not exist initially");
-
- let file = do_get_file("../migration/places_v38.sqlite");
- file.copyToFollowingLinks(gProfD, "places.sqlite");
- file = gProfD.clone();
- file.append("places.sqlite");
-
- // Create some unique stuff to check later.
- let db = Services.storage.openUnsharedDatabase(file);
- db.executeSimpleSQL("CREATE TABLE moz_cloned(id INTEGER PRIMARY KEY)");
- db.executeSimpleSQL("CREATE TABLE not_cloned (id INTEGER PRIMARY KEY)");
- db.executeSimpleSQL("DELETE FROM moz_cloned"); // Shouldn't throw.
- db.close();
-
- // Open the database with Places.
- Services.prefs.setBoolPref("places.database.replaceOnStartup", true);
- let expectedStatus = shouldClone ? PlacesUtils.history.DATABASE_STATUS_UPGRADED
- : PlacesUtils.history.DATABASE_STATUS_CORRUPT;
- Assert.equal(PlacesUtils.history.databaseStatus, expectedStatus);
-
- Assert.ok(await OS.File.exists(sane), "The database should exist");
-
- // Check the new database still contains our special data.
- db = Services.storage.openUnsharedDatabase(file);
- if (shouldClone) {
- db.executeSimpleSQL("DELETE FROM moz_cloned"); // Shouldn't throw.
- }
-
- // Check the new database is really a new one.
- Assert.throws(() => {
- db.executeSimpleSQL("DELETE FROM not_cloned");
- }, "The database should have been replaced");
- db.close();
-
- if (shouldClone) {
- Assert.ok(!await OS.File.exists(corrupt), "The corrupt db should not exist");
- } else {
- Assert.ok(await OS.File.exists(corrupt), "The corrupt db should exist");
- }
-}
copy from toolkit/components/places/tests/unit/test_database_replaceOnStartup.js
copy to toolkit/components/places/tests/maintenance/test_places_replaceOnStartup.js
--- a/toolkit/components/places/tests/unit/test_database_replaceOnStartup.js
+++ b/toolkit/components/places/tests/maintenance/test_places_replaceOnStartup.js
@@ -1,60 +1,12 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
// Tests that history initialization correctly handles a request to forcibly
// replace the current database.
add_task(async function() {
- registerCleanupFunction(function() {
- Services.prefs.clearUserPref("places.database.cloneOnCorruption");
- });
- test_replacement(true);
- test_replacement(false);
+ await test_database_replacement("../migration/places_v43.sqlite",
+ "places.sqlite",
+ false,
+ PlacesUtils.history.DATABASE_STATUS_CORRUPT);
});
-
-async function test_replacement(shouldClone) {
- Services.prefs.setBoolPref("places.database.cloneOnCorruption", shouldClone);
- // Ensure that our databases don't exist yet.
- let sane = OS.Path.join(OS.Constants.Path.profileDir, "places.sqlite");
- Assert.ok(!await OS.File.exists(sane), "The db should not exist initially");
- let corrupt = OS.Path.join(OS.Constants.Path.profileDir, "places.sqlite.corrupt");
- Assert.ok(!await OS.File.exists(corrupt), "The corrupt db should not exist initially");
-
- let file = do_get_file("../migration/places_v38.sqlite");
- file.copyToFollowingLinks(gProfD, "places.sqlite");
- file = gProfD.clone();
- file.append("places.sqlite");
-
- // Create some unique stuff to check later.
- let db = Services.storage.openUnsharedDatabase(file);
- db.executeSimpleSQL("CREATE TABLE moz_cloned(id INTEGER PRIMARY KEY)");
- db.executeSimpleSQL("CREATE TABLE not_cloned (id INTEGER PRIMARY KEY)");
- db.executeSimpleSQL("DELETE FROM moz_cloned"); // Shouldn't throw.
- db.close();
-
- // Open the database with Places.
- Services.prefs.setBoolPref("places.database.replaceOnStartup", true);
- let expectedStatus = shouldClone ? PlacesUtils.history.DATABASE_STATUS_UPGRADED
- : PlacesUtils.history.DATABASE_STATUS_CORRUPT;
- Assert.equal(PlacesUtils.history.databaseStatus, expectedStatus);
-
- Assert.ok(await OS.File.exists(sane), "The database should exist");
-
- // Check the new database still contains our special data.
- db = Services.storage.openUnsharedDatabase(file);
- if (shouldClone) {
- db.executeSimpleSQL("DELETE FROM moz_cloned"); // Shouldn't throw.
- }
-
- // Check the new database is really a new one.
- Assert.throws(() => {
- db.executeSimpleSQL("DELETE FROM not_cloned");
- }, "The database should have been replaced");
- db.close();
-
- if (shouldClone) {
- Assert.ok(!await OS.File.exists(corrupt), "The corrupt db should not exist");
- } else {
- Assert.ok(await OS.File.exists(corrupt), "The corrupt db should exist");
- }
-}
copy from toolkit/components/places/tests/unit/test_database_replaceOnStartup.js
copy to toolkit/components/places/tests/maintenance/test_places_replaceOnStartup_clone.js
--- a/toolkit/components/places/tests/unit/test_database_replaceOnStartup.js
+++ b/toolkit/components/places/tests/maintenance/test_places_replaceOnStartup_clone.js
@@ -1,60 +1,12 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
// Tests that history initialization correctly handles a request to forcibly
// replace the current database.
add_task(async function() {
- registerCleanupFunction(function() {
- Services.prefs.clearUserPref("places.database.cloneOnCorruption");
- });
- test_replacement(true);
- test_replacement(false);
+ await test_database_replacement("../migration/places_v43.sqlite",
+ "places.sqlite",
+ true,
+ PlacesUtils.history.DATABASE_STATUS_UPGRADED);
});
-
-async function test_replacement(shouldClone) {
- Services.prefs.setBoolPref("places.database.cloneOnCorruption", shouldClone);
- // Ensure that our databases don't exist yet.
- let sane = OS.Path.join(OS.Constants.Path.profileDir, "places.sqlite");
- Assert.ok(!await OS.File.exists(sane), "The db should not exist initially");
- let corrupt = OS.Path.join(OS.Constants.Path.profileDir, "places.sqlite.corrupt");
- Assert.ok(!await OS.File.exists(corrupt), "The corrupt db should not exist initially");
-
- let file = do_get_file("../migration/places_v38.sqlite");
- file.copyToFollowingLinks(gProfD, "places.sqlite");
- file = gProfD.clone();
- file.append("places.sqlite");
-
- // Create some unique stuff to check later.
- let db = Services.storage.openUnsharedDatabase(file);
- db.executeSimpleSQL("CREATE TABLE moz_cloned(id INTEGER PRIMARY KEY)");
- db.executeSimpleSQL("CREATE TABLE not_cloned (id INTEGER PRIMARY KEY)");
- db.executeSimpleSQL("DELETE FROM moz_cloned"); // Shouldn't throw.
- db.close();
-
- // Open the database with Places.
- Services.prefs.setBoolPref("places.database.replaceOnStartup", true);
- let expectedStatus = shouldClone ? PlacesUtils.history.DATABASE_STATUS_UPGRADED
- : PlacesUtils.history.DATABASE_STATUS_CORRUPT;
- Assert.equal(PlacesUtils.history.databaseStatus, expectedStatus);
-
- Assert.ok(await OS.File.exists(sane), "The database should exist");
-
- // Check the new database still contains our special data.
- db = Services.storage.openUnsharedDatabase(file);
- if (shouldClone) {
- db.executeSimpleSQL("DELETE FROM moz_cloned"); // Shouldn't throw.
- }
-
- // Check the new database is really a new one.
- Assert.throws(() => {
- db.executeSimpleSQL("DELETE FROM not_cloned");
- }, "The database should have been replaced");
- db.close();
-
- if (shouldClone) {
- Assert.ok(!await OS.File.exists(corrupt), "The corrupt db should not exist");
- } else {
- Assert.ok(await OS.File.exists(corrupt), "The corrupt db should exist");
- }
-}
rename from toolkit/components/places/tests/unit/test_preventive_maintenance.js
rename to toolkit/components/places/tests/maintenance/test_preventive_maintenance.js
--- a/toolkit/components/places/tests/unit/test_preventive_maintenance.js
+++ b/toolkit/components/places/tests/maintenance/test_preventive_maintenance.js
@@ -5,19 +5,16 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/**
* Test preventive maintenance
* For every maintenance query create an uncoherent db and check that we take
* correct fix steps, without polluting valid data.
*/
-// Include PlacesDBUtils module
-ChromeUtils.import("resource://gre/modules/PlacesDBUtils.jsm");
-
// Get services and database connection
var hs = PlacesUtils.history;
var bs = PlacesUtils.bookmarks;
var ts = PlacesUtils.tagging;
var as = PlacesUtils.annotations;
var fs = PlacesUtils.favicons;
var mDBConn = hs.DBConnection;
rename from toolkit/components/places/tests/unit/test_preventive_maintenance_checkAndFixDatabase.js
rename to toolkit/components/places/tests/maintenance/test_preventive_maintenance_checkAndFixDatabase.js
--- a/toolkit/components/places/tests/unit/test_preventive_maintenance_checkAndFixDatabase.js
+++ b/toolkit/components/places/tests/maintenance/test_preventive_maintenance_checkAndFixDatabase.js
@@ -3,20 +3,21 @@
/* 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/. */
/**
* Test preventive maintenance checkAndFixDatabase.
*/
-// Include PlacesDBUtils module.
-ChromeUtils.import("resource://gre/modules/PlacesDBUtils.jsm");
+add_task(async function() {
+ // We must initialize places first, or we won't have a db to check.
+ Assert.equal(PlacesUtils.history.databaseStatus,
+ PlacesUtils.history.DATABASE_STATUS_CREATE);
-add_task(async function() {
let tasksStatusMap = await PlacesDBUtils.checkAndFixDatabase();
let numberOfTasksRun = tasksStatusMap.size;
let successfulTasks = [];
let failedTasks = [];
tasksStatusMap.forEach(val => {
if (val.succeeded) {
successfulTasks.push(val);
} else {
rename from toolkit/components/places/tests/unit/test_preventive_maintenance_runTasks.js
rename to toolkit/components/places/tests/maintenance/test_preventive_maintenance_runTasks.js
--- a/toolkit/components/places/tests/unit/test_preventive_maintenance_runTasks.js
+++ b/toolkit/components/places/tests/maintenance/test_preventive_maintenance_runTasks.js
@@ -1,19 +1,16 @@
/* 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/. */
/**
* Test preventive maintenance runTasks.
*/
-// Include PlacesDBUtils module.
-ChromeUtils.import("resource://gre/modules/PlacesDBUtils.jsm");
-
add_task(async function() {
let tasksStatusMap = await PlacesDBUtils.runTasks([PlacesDBUtils.invalidateCaches]);
let numberOfTasksRun = tasksStatusMap.size;
let successfulTasks = [];
let failedTasks = [];
tasksStatusMap.forEach(val => {
if (val.succeeded) {
successfulTasks.push(val);
new file mode 100644
--- /dev/null
+++ b/toolkit/components/places/tests/maintenance/xpcshell.ini
@@ -0,0 +1,17 @@
+[DEFAULT]
+head = head.js
+firefox-appdir = browser
+support-files =
+ corruptDB.sqlite
+
+[test_corrupt_favicons.js]
+[test_corrupt_favicons_schema.js]
+[test_corrupt_places_schema.js]
+[test_corrupt_telemetry.js]
+[test_favicons_replaceOnStartup.js]
+[test_favicons_replaceOnStartup_clone.js]
+[test_places_replaceOnStartup.js]
+[test_places_replaceOnStartup_clone.js]
+[test_preventive_maintenance.js]
+[test_preventive_maintenance_checkAndFixDatabase.js]
+[test_preventive_maintenance_runTasks.js]
--- a/toolkit/components/places/tests/moz.build
+++ b/toolkit/components/places/tests/moz.build
@@ -11,16 +11,17 @@ TESTING_JS_MODULES += [
]
XPCSHELL_TESTS_MANIFESTS += [
'bookmarks/xpcshell.ini',
'expiration/xpcshell.ini',
'favicons/xpcshell.ini',
'history/xpcshell.ini',
'legacy/xpcshell.ini',
+ 'maintenance/xpcshell.ini',
'migration/xpcshell.ini',
'queries/xpcshell.ini',
'sync/xpcshell.ini',
'unifiedcomplete/xpcshell.ini',
'unit/xpcshell.ini',
]
BROWSER_CHROME_MANIFESTS += ['browser/browser.ini']
--- a/toolkit/components/places/tests/unit/xpcshell.ini
+++ b/toolkit/components/places/tests/unit/xpcshell.ini
@@ -2,17 +2,16 @@
head = head_bookmarks.js
firefox-appdir = browser
support-files =
bookmarks.corrupt.html
bookmarks.json
bookmarks_corrupt.json
bookmarks.preplaces.html
bookmarks_html_singleframe.html
- corruptDB.sqlite
livemark.xml
mobile_bookmarks_folder_import.json
mobile_bookmarks_folder_merge.json
mobile_bookmarks_multiple_folders.json
mobile_bookmarks_root_import.json
mobile_bookmarks_root_merge.json
places.sparse.sqlite
@@ -58,18 +57,16 @@ skip-if = (os == "win" && os_version ==
[test_bookmarks_html_escape_entities.js]
[test_bookmarks_html_import_tags.js]
[test_bookmarks_html_singleframe.js]
[test_bookmarks_restore_notification.js]
[test_broken_folderShortcut_result.js]
[test_browserhistory.js]
[test_bug636917_isLivemark.js]
[test_childlessTags.js]
-[test_corrupt_telemetry.js]
-[test_database_replaceOnStartup.js]
[test_download_history.js]
[test_frecency.js]
[test_frecency_decay.js]
[test_frecency_stats.js]
[test_frecency_zero_updated.js]
[test_getChildIndex.js]
[test_hash.js]
[test_history.js]
@@ -96,19 +93,16 @@ support-files = noRoot.sqlite
[test_onItemChanged_tags.js]
[test_origins.js]
[test_pageGuid_bookmarkGuid.js]
[test_frecency_observers.js]
[test_placeURIs.js]
[test_PlacesUtils_annotations.js]
[test_PlacesUtils_invalidateCachedGuidFor.js]
[test_PlacesUtils_isRootItem.js]
-[test_preventive_maintenance.js]
-[test_preventive_maintenance_checkAndFixDatabase.js]
-[test_preventive_maintenance_runTasks.js]
[test_promiseBookmarksTree.js]
[test_resolveNullBookmarkTitles.js]
[test_result_sort.js]
[test_resultsAsVisit_details.js]
[test_sql_function_origin.js]
[test_sql_guid_functions.js]
[test_tag_autocomplete_search.js]
[test_tagging.js]