Bug 1411538 - Try to recover tables from a corrupt places.sqlite. r=standard8 draft
authorMarco Bonardo <mbonardo@mozilla.com>
Thu, 07 Dec 2017 14:05:29 +0100
changeset 718549 31051be94a313004fa9a6af1f162eba6386d4f75
parent 716325 81362f7306fe413b19fdba27cd0e9a5525d902e1
child 745515 3bb084c59b4f55beca1a2aa9eb39a2ea401d05c3
push id94960
push usermak77@bonardo.net
push dateWed, 10 Jan 2018 13:20:56 +0000
reviewersstandard8
bugs1411538
milestone59.0a1
Bug 1411538 - Try to recover tables from a corrupt places.sqlite. r=standard8 MozReview-Commit-ID: FvuKwIqSt7b
toolkit/components/places/Database.cpp
toolkit/components/places/Database.h
toolkit/components/places/tests/head_common.js
toolkit/components/places/tests/unit/default.sqlite
toolkit/components/places/tests/unit/test_database_replaceOnStartup.js
toolkit/components/places/tests/unit/xpcshell.ini
--- a/toolkit/components/places/Database.cpp
+++ b/toolkit/components/places/Database.cpp
@@ -39,22 +39,26 @@
 
 // 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"
 
+// 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
 // storage without robust transaction flushing guarantees. This makes
 // SQLite use much less I/O at the cost of losing data when things crash.
 // The pref is only honored if an environment variable is set. The env
 // variable is intentionally named something scary to help prevent someone
@@ -570,33 +574,37 @@ Database::EnsureConnection()
     bool databaseCreated = false;
     nsresult rv = InitDatabaseFile(storage, &databaseCreated);
     if (NS_SUCCEEDED(rv) && databaseCreated) {
       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);
+      rv = BackupAndReplaceDatabaseFile(storage, 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);
       if (NS_FAILED(rv)) {
+        // Cloning the db on a schema migration may not be a good idea, since we
+        // may end up cloning the schema problems.
+        shouldTryToCloneDb = false;
         if (rv == NS_ERROR_STORAGE_BUSY ||
             rv == NS_ERROR_FILE_IS_LOCKED ||
             rv == NS_ERROR_FILE_NO_DEVICE_SPACE ||
             rv == NS_ERROR_OUT_OF_MEMORY) {
           // The database is not corrupt, though some migration step failed.
           // This may be caused by concurrent use of sync and async Storage APIs
           // or by a system issue.
           // The best we can do is trying again. If it should still fail, Places
@@ -612,17 +620,17 @@ 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);
+        rv = BackupAndReplaceDatabaseFile(storage, shouldTryToCloneDb);
         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);
@@ -764,17 +772,18 @@ Database::InitDatabaseFile(nsCOMPtr<mozI
   rv = aStorage->OpenUnsharedDatabase(databaseFile, getter_AddRefs(mMainConn));
   NS_ENSURE_SUCCESS(rv, rv);
 
   *aNewDatabaseCreated = !databaseFileExists;
   return NS_OK;
 }
 
 nsresult
-Database::BackupAndReplaceDatabaseFile(nsCOMPtr<mozIStorageService>& aStorage)
+Database::BackupAndReplaceDatabaseFile(nsCOMPtr<mozIStorageService>& aStorage,
+                                       bool aTryToClone)
 {
   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);
   nsCOMPtr<nsIFile> databaseFile;
   rv = profDir->Clone(getter_AddRefs(databaseFile));
@@ -797,17 +806,19 @@ Database::BackupAndReplaceDatabaseFile(n
   // 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 {
       stage_closing = 0,
       stage_removing,
       stage_reopening,
-      stage_replaced
+      stage_replaced,
+      stage_cloning,
+      stage_cloned
     };
     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.
@@ -827,26 +838,150 @@ Database::BackupAndReplaceDatabaseFile(n
 
     // Remove the broken database.
     stage = stage_removing;
     rv = databaseFile->Remove(false);
     if (NS_FAILED(rv) && rv != NS_ERROR_FILE_TARGET_DOES_NOT_EXIST) {
       return rv;
     }
 
-    // Create a new database file.
+    // 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);
+      if (NS_SUCCEEDED(rv)) {
+        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);
 
-    stage = stage_replaced;
+    stage = cloned ? stage_cloned : stage_replaced;
+  }
+
+  return NS_OK;
+}
+
+nsresult
+Database::TryToCloneTablesFromCorruptDatabase(nsCOMPtr<mozIStorageService>& aStorage)
+{
+  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);
+
+  nsCOMPtr<nsIFile> corruptFile;
+  rv = profDir->Clone(getter_AddRefs(corruptFile));
+  NS_ENSURE_SUCCESS(rv, rv);
+  rv = corruptFile->Append(DATABASE_CORRUPT_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));
+  NS_ENSURE_SUCCESS(rv, rv);
+  rv = recoverFile->Append(DATABASE_RECOVER_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;
   }
 
+  nsCOMPtr<mozIStorageConnection> conn;
+  auto guard = MakeScopeExit([&]() {
+    if (conn) {
+      Unused << conn->Close();
+    }
+    Unused << recoverFile->Remove(false);
+  });
+
+  rv = aStorage->OpenUnsharedDatabase(recoverFile, getter_AddRefs(conn));
+  NS_ENSURE_SUCCESS(rv, rv);
+  rv = AttachDatabase(conn, NS_ConvertUTF16toUTF8(path),
+                      NS_LITERAL_CSTRING("corrupt"));
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  mozStorageTransaction transaction(conn, false);
+
+  // Copy the schema version.
+  nsCOMPtr<mozIStorageStatement> stmt;
+  (void)conn->CreateStatement(NS_LITERAL_CSTRING("PRAGMA corrupt.user_version"),
+                              getter_AddRefs(stmt));
+  NS_ENSURE_TRUE(stmt, NS_ERROR_OUT_OF_MEMORY);
+  bool hasResult;
+  rv = stmt->ExecuteStep(&hasResult);
+  NS_ENSURE_SUCCESS(rv, rv);
+  int32_t schemaVersion = stmt->AsInt32(0);
+  rv = conn->SetSchemaVersion(schemaVersion);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  // Recreate the tables.
+  rv = conn->CreateStatement(NS_LITERAL_CSTRING(
+    "SELECT name, sql FROM corrupt.sqlite_master "
+    "WHERE type = 'table' AND name BETWEEN 'moz_' AND 'moza'"
+  ), getter_AddRefs(stmt));
+  NS_ENSURE_SUCCESS(rv, rv);
+  while (NS_SUCCEEDED(stmt->ExecuteStep(&hasResult)) && hasResult) {
+    nsAutoCString name;
+    rv = stmt->GetUTF8String(0, name);
+    NS_ENSURE_SUCCESS(rv, rv);
+    nsAutoCString query;
+    rv = stmt->GetUTF8String(1, query);
+    NS_ENSURE_SUCCESS(rv, rv);
+    rv = conn->ExecuteSimpleSQL(query);
+    NS_ENSURE_SUCCESS(rv, rv);
+    // Copy the table contents.
+    rv = conn->ExecuteSimpleSQL(NS_LITERAL_CSTRING("INSERT INTO main.") +
+     name + NS_LITERAL_CSTRING(" SELECT * FROM corrupt.") + name);
+    if (NS_FAILED(rv)) {
+      rv = conn->ExecuteSimpleSQL(NS_LITERAL_CSTRING("INSERT INTO main.") +
+            name + NS_LITERAL_CSTRING(" SELECT * FROM corrupt.") + name +
+            NS_LITERAL_CSTRING(" ORDER BY rowid DESC"));
+    }
+    NS_ENSURE_SUCCESS(rv, rv);
+  }
+
+  // Recreate the indices.  Doing this after data addition is faster.
+  rv = conn->CreateStatement(NS_LITERAL_CSTRING(
+    "SELECT sql FROM corrupt.sqlite_master "
+    "WHERE type <> 'table' AND name BETWEEN 'moz_' AND 'moza'"
+  ), getter_AddRefs(stmt));
+  NS_ENSURE_SUCCESS(rv, rv);
+  hasResult = false;
+  while (NS_SUCCEEDED(stmt->ExecuteStep(&hasResult)) && hasResult) {
+    nsAutoCString query;
+    rv = stmt->GetUTF8String(0, query);
+    NS_ENSURE_SUCCESS(rv, rv);
+    rv = conn->ExecuteSimpleSQL(query);
+    NS_ENSURE_SUCCESS(rv, rv);
+  }
+  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);
+  NS_ENSURE_SUCCESS(rv, rv);
+  Unused << corruptFile->Remove(false);
+
+  guard.release();
   return NS_OK;
 }
 
 nsresult
 Database::SetupDatabaseConnection(nsCOMPtr<mozIStorageService>& aStorage)
 {
   MOZ_ASSERT(NS_IsMainThread());
 
--- a/toolkit/components/places/Database.h
+++ b/toolkit/components/places/Database.h
@@ -236,18 +236,29 @@ protected:
   nsresult EnsureFaviconsDatabaseFile(nsCOMPtr<mozIStorageService>& aStorage);
 
   /**
    * Creates a database backup and replaces the original file with a new
    * one.
    *
    * @param aStorage
    *        mozStorage service instance.
+   * @param aTryToClone
+   *        whether we should try to clone a corrupt database.
    */
-  nsresult BackupAndReplaceDatabaseFile(nsCOMPtr<mozIStorageService>& aStorage);
+  nsresult BackupAndReplaceDatabaseFile(nsCOMPtr<mozIStorageService>& aStorage,
+                                        bool aTryToClone);
+
+  /**
+   * Tries to recover tables and their contents from a corrupt database.
+   *
+   * @param aStorage
+   *        mozStorage service instance.
+   */
+  nsresult TryToCloneTablesFromCorruptDatabase(nsCOMPtr<mozIStorageService>& aStorage);
 
   /**
    * 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/tests/head_common.js
+++ b/toolkit/components/places/tests/head_common.js
@@ -23,43 +23,32 @@ const TRANSITION_REDIRECT_TEMPORARY = Ci
 const TRANSITION_DOWNLOAD = Ci.nsINavHistoryService.TRANSITION_DOWNLOAD;
 const TRANSITION_RELOAD = Ci.nsINavHistoryService.TRANSITION_RELOAD;
 
 const TITLE_LENGTH_MAX = 4096;
 
 Cu.importGlobalProperties(["URL"]);
 
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "FileUtils",
-                                  "resource://gre/modules/FileUtils.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "NetUtil",
-                                  "resource://gre/modules/NetUtil.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "PromiseUtils",
-                                  "resource://gre/modules/PromiseUtils.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "Services",
-                                  "resource://gre/modules/Services.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "BookmarkJSONUtils",
-                                  "resource://gre/modules/BookmarkJSONUtils.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "BookmarkHTMLUtils",
-                                  "resource://gre/modules/BookmarkHTMLUtils.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "PlacesBackups",
-                                  "resource://gre/modules/PlacesBackups.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "PlacesSyncUtils",
-                                  "resource://gre/modules/PlacesSyncUtils.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "PlacesTestUtils",
-                                  "resource://testing-common/PlacesTestUtils.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "PlacesTransactions",
-                                  "resource://gre/modules/PlacesTransactions.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "OS",
-                                  "resource://gre/modules/osfile.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "Sqlite",
-                                  "resource://gre/modules/Sqlite.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "TestUtils",
-                                  "resource://testing-common/TestUtils.jsm");
-
+XPCOMUtils.defineLazyModuleGetters(this, {
+  FileUtils: "resource://gre/modules/FileUtils.jsm",
+  NetUtil: "resource://gre/modules/NetUtil.jsm",
+  PromiseUtils: "resource://gre/modules/PromiseUtils.jsm",
+  Services: "resource://gre/modules/Services.jsm",
+  BookmarkJSONUtils: "resource://gre/modules/BookmarkJSONUtils.jsm",
+  BookmarkHTMLUtils: "resource://gre/modules/BookmarkHTMLUtils.jsm",
+  PlacesBackups: "resource://gre/modules/PlacesBackups.jsm",
+  PlacesSyncUtils: "resource://gre/modules/PlacesSyncUtils.jsm",
+  PlacesTestUtils: "resource://testing-common/PlacesTestUtils.jsm",
+  PlacesTransactions: "resource://gre/modules/PlacesTransactions.jsm",
+  OS: "resource://gre/modules/osfile.jsm",
+  Sqlite: "resource://gre/modules/Sqlite.jsm",
+  TestUtils: "resource://testing-common/TestUtils.jsm",
+  AppConstants: "resource://gre/modules/AppConstants.jsm",
+});
 // This imports various other objects in addition to PlacesUtils.
 Cu.import("resource://gre/modules/PlacesUtils.jsm");
 
 XPCOMUtils.defineLazyGetter(this, "SMALLPNG_DATA_URI", function() {
   return NetUtil.newURI(
          "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAA" +
          "AAAA6fptVAAAACklEQVQI12NgAAAAAgAB4iG8MwAAAABJRU5ErkJggg==");
 });
deleted file mode 100644
index 8fbd3bc9acab0abff5305d493d9568a10b36d08b..0000000000000000000000000000000000000000
GIT binary patch
literal 0
Hc$@<O00001
--- a/toolkit/components/places/tests/unit/test_database_replaceOnStartup.js
+++ b/toolkit/components/places/tests/unit/test_database_replaceOnStartup.js
@@ -1,46 +1,60 @@
 /* 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.
 
-function run_test() {
-  // Ensure that our database doesn't already exist.
-  let dbFile = gProfD.clone();
-  dbFile.append("places.sqlite");
-  Assert.ok(!dbFile.exists());
+add_task(async function() {
+  registerCleanupFunction(function() {
+    Services.prefs.clearUserPref("places.database.cloneOnCorruption");
+  });
+  test_replacement(true);
+  test_replacement(false);
+});
 
-  dbFile = gProfD.clone();
-  dbFile.append("places.sqlite.corrupt");
-  Assert.ok(!dbFile.exists());
+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("default.sqlite");
+  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 test (id INTEGER PRIMARY KEY)");
+  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);
-  Assert.equal(PlacesUtils.history.databaseStatus,
-               PlacesUtils.history.DATABASE_STATUS_CORRUPT);
+  let expectedStatus = shouldClone ? PlacesUtils.history.DATABASE_STATUS_UPGRADED
+                                   : PlacesUtils.history.DATABASE_STATUS_CORRUPT;
+  Assert.equal(PlacesUtils.history.databaseStatus, expectedStatus);
 
-  dbFile = gProfD.clone();
-  dbFile.append("places.sqlite");
-  Assert.ok(dbFile.exists());
+  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.
-  db = Services.storage.openUnsharedDatabase(file);
-  try {
-    db.executeSimpleSQL("DELETE * FROM test");
-    do_throw("The new database should not have our unique content");
-  } catch (ex) {}
+  Assert.throws(() => {
+    db.executeSimpleSQL("DELETE FROM not_cloned");
+  }, "The database should have been replaced");
   db.close();
 
-  dbFile = gProfD.clone();
-  dbFile.append("places.sqlite.corrupt");
-  Assert.ok(dbFile.exists());
+  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");
+  }
 }
--- a/toolkit/components/places/tests/unit/xpcshell.ini
+++ b/toolkit/components/places/tests/unit/xpcshell.ini
@@ -4,17 +4,16 @@ firefox-appdir = browser
 support-files =
   bookmarks.corrupt.html
   bookmarks.json
   bookmarks_corrupt.json
   bookmarks.preplaces.html
   bookmarks_html_singleframe.html
   bug476292.sqlite
   corruptDB.sqlite
-  default.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
   nsDummyObserver.js
   nsDummyObserver.manifest