Bug 478035 - Re-create the built-in root folders if they are missing in the places database. r?mak draft
authorMark Banner <standard8@mozilla.com>
Wed, 18 Apr 2018 18:29:19 +0100
changeset 785809 cae78c106b8cf2413a55ed0e60682df3ab8fedfa
parent 785808 0e60d3c4dc2bde877a963ecb37e1b246bf9a4825
child 785810 55d623fc9ff610f35b8de79a2ff0c80bdf6990c7
push id107311
push userbmo:standard8@mozilla.com
push dateFri, 20 Apr 2018 16:56:49 +0000
reviewersmak
bugs478035
milestone61.0a1
Bug 478035 - Re-create the built-in root folders if they are missing in the places database. r?mak MozReview-Commit-ID: FU7o6q5NpdR
toolkit/components/places/Database.cpp
toolkit/components/places/Database.h
toolkit/components/places/nsNavBookmarks.h
toolkit/components/places/tests/head_common.js
toolkit/components/places/tests/migration/head_migration.js
toolkit/components/places/tests/unit/missingBuiltIn.sqlite
toolkit/components/places/tests/unit/noRoot.sqlite
toolkit/components/places/tests/unit/test_missing_builtin_folders.js
toolkit/components/places/tests/unit/test_missing_root_folder.js
toolkit/components/places/tests/unit/xpcshell.ini
--- a/toolkit/components/places/Database.cpp
+++ b/toolkit/components/places/Database.cpp
@@ -5,21 +5,21 @@
 #include "mozilla/ArrayUtils.h"
 #include "mozilla/Attributes.h"
 #include "mozilla/DebugOnly.h"
 #include "mozilla/ScopeExit.h"
 
 #include "Database.h"
 
 #include "nsIAnnotationService.h"
-#include "nsINavBookmarksService.h"
 #include "nsIInterfaceRequestorUtils.h"
 #include "nsIFile.h"
 #include "nsIWritablePropertyBag2.h"
 
+#include "nsNavBookmarks.h"
 #include "nsNavHistory.h"
 #include "nsPlacesTables.h"
 #include "nsPlacesIndexes.h"
 #include "nsPlacesTriggers.h"
 #include "nsPlacesMacros.h"
 #include "nsVariant.h"
 #include "SQLFunctions.h"
 #include "Helpers.h"
@@ -228,23 +228,20 @@ SetJournalMode(nsCOMPtr<mozIStorageConne
   }
 
   return JOURNAL_DELETE;
 }
 
 nsresult
 CreateRoot(nsCOMPtr<mozIStorageConnection>& aDBConn,
            const nsCString& aRootName, const nsCString& aGuid,
-           const nsCString& titleString)
+           const nsCString& titleString, const int32_t position, int64_t& newId)
 {
   MOZ_ASSERT(NS_IsMainThread());
 
-  // The position of the new item in its folder.
-  static int32_t itemPosition = 0;
-
   // A single creation timestamp for all roots so that the root folder's
   // last modification time isn't earlier than its childrens' creation time.
   static PRTime timestamp = 0;
   if (!timestamp)
     timestamp = RoundedPRNow();
 
   // Create a new bookmark folder for the root.
   nsCOMPtr<mozIStorageStatement> stmt;
@@ -257,38 +254,34 @@ CreateRoot(nsCOMPtr<mozIStorageConnectio
             "IFNULL((SELECT id FROM moz_bookmarks WHERE parent = 0), 0), "
             "1, :sync_status)"
   ), getter_AddRefs(stmt));
   if (NS_FAILED(rv)) return rv;
 
   rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("item_type"),
                              nsINavBookmarksService::TYPE_FOLDER);
   if (NS_FAILED(rv)) return rv;
-  rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("item_position"), itemPosition);
+  rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("item_position"), position);
   if (NS_FAILED(rv)) return rv;
   rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("item_title"),
                                   titleString);
   if (NS_FAILED(rv)) return rv;
   rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("date_added"), timestamp);
   if (NS_FAILED(rv)) return rv;
   rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("last_modified"), timestamp);
   if (NS_FAILED(rv)) return rv;
   rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("guid"), aGuid);
   if (NS_FAILED(rv)) return rv;
   rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("sync_status"),
                              nsINavBookmarksService::SYNC_STATUS_NEW);
   if (NS_FAILED(rv)) return rv;
   rv = stmt->Execute();
   if (NS_FAILED(rv)) return rv;
 
-  // The 'places' root is a folder containing the other roots.
-  // The first bookmark in a folder has position 0.
-  if (!aRootName.EqualsLiteral("places"))
-    ++itemPosition;
-
+  newId = nsNavBookmarks::sLastInsertedItemId;
   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)) {
@@ -1348,19 +1341,17 @@ Database::InitSchema(bool* aDatabaseMigr
     NS_ENSURE_SUCCESS(rv, rv);
     rv = mMainConn->ExecuteSimpleSQL(CREATE_IDX_MOZ_ITEMSANNOS_PLACEATTRIBUTE);
     NS_ENSURE_SUCCESS(rv, rv);
 
     // moz_meta.
     rv = mMainConn->ExecuteSimpleSQL(CREATE_MOZ_META);
     NS_ENSURE_SUCCESS(rv, rv);
 
-    // Initialize the bookmark roots in the new DB.
-    rv = CreateBookmarkRoots();
-    NS_ENSURE_SUCCESS(rv, rv);
+    // The bookmarks roots get initialized in CheckRoots().
   }
 
   // Set the schema version to the current one.
   rv = mMainConn->SetSchemaVersion(DATABASE_SCHEMA_VERSION);
   NS_ENSURE_SUCCESS(rv, rv);
 
   rv = transaction.Commit();
   NS_ENSURE_SUCCESS(rv, rv);
@@ -1373,122 +1364,151 @@ Database::InitSchema(bool* aDatabaseMigr
   return NS_OK;
 }
 
 nsresult
 Database::CheckRoots()
 {
   MOZ_ASSERT(NS_IsMainThread());
 
+  // If the database has just been created, skip straight to the part where
+  // we create the roots.
+  if (mDatabaseStatus == nsINavHistoryService::DATABASE_STATUS_CREATE) {
+    return EnsureBookmarkRoots(0);
+  }
+
   nsCOMPtr<mozIStorageStatement> stmt;
   nsresult rv = mMainConn->CreateStatement(NS_LITERAL_CSTRING(
-    "SELECT guid, id FROM moz_bookmarks WHERE guid IN ( "
+    "SELECT guid, id, position FROM moz_bookmarks WHERE guid IN ( "
       "'" ROOT_GUID "', '" MENU_ROOT_GUID "', '" TOOLBAR_ROOT_GUID "', "
       "'" TAGS_ROOT_GUID "', '" UNFILED_ROOT_GUID "', '" MOBILE_ROOT_GUID "' )"
     ), getter_AddRefs(stmt));
   NS_ENSURE_SUCCESS(rv, rv);
 
   bool hasResult;
   nsAutoCString guid;
+  int32_t maxPosition = 0;
   while (NS_SUCCEEDED(stmt->ExecuteStep(&hasResult)) && hasResult) {
     rv = stmt->GetUTF8String(0, guid);
     NS_ENSURE_SUCCESS(rv, rv);
 
     if (guid.EqualsLiteral(ROOT_GUID)) {
       mRootId = stmt->AsInt64(1);
     }
-    else if (guid.EqualsLiteral(MENU_ROOT_GUID)) {
-      mMenuRootId = stmt->AsInt64(1);
-    }
-    else if (guid.EqualsLiteral(TOOLBAR_ROOT_GUID)) {
-      mToolbarRootId = stmt->AsInt64(1);
-    }
-    else if (guid.EqualsLiteral(TAGS_ROOT_GUID)) {
-      mTagsRootId = stmt->AsInt64(1);
-    }
-    else if (guid.EqualsLiteral(UNFILED_ROOT_GUID)) {
-      mUnfiledRootId = stmt->AsInt64(1);
-    }
-    else if (guid.EqualsLiteral(MOBILE_ROOT_GUID)) {
-      mMobileRootId = stmt->AsInt64(1);
+    else {
+      maxPosition = std::max(stmt->AsInt32(2), maxPosition);
+
+      if (guid.EqualsLiteral(MENU_ROOT_GUID)) {
+        mMenuRootId = stmt->AsInt64(1);
+      }
+      else if (guid.EqualsLiteral(TOOLBAR_ROOT_GUID)) {
+        mToolbarRootId = stmt->AsInt64(1);
+      }
+      else if (guid.EqualsLiteral(TAGS_ROOT_GUID)) {
+        mTagsRootId = stmt->AsInt64(1);
+      }
+      else if (guid.EqualsLiteral(UNFILED_ROOT_GUID)) {
+        mUnfiledRootId = stmt->AsInt64(1);
+      }
+      else if (guid.EqualsLiteral(MOBILE_ROOT_GUID)) {
+        mMobileRootId = stmt->AsInt64(1);
+      }
     }
   }
 
+  rv = EnsureBookmarkRoots(maxPosition + 1);
+  NS_ENSURE_SUCCESS(rv, rv);
+
   return NS_OK;
 }
 
 nsresult
-Database::CreateBookmarkRoots()
+Database::EnsureBookmarkRoots(const int32_t startPosition)
 {
   MOZ_ASSERT(NS_IsMainThread());
 
-  // The first root's title is an empty string.
-  nsresult rv = CreateRoot(mMainConn, NS_LITERAL_CSTRING("places"),
-                           NS_LITERAL_CSTRING("root________"), EmptyCString());
-  if (NS_FAILED(rv)) return rv;
+  nsresult rv;
+
+  // Note: If the root is missing, we recreate it but we don't fix any
+  // remaining built-in folder parent ids. We leave these to a maintenance task,
+  // so that we're not needing to do extra checks on startup.
+
+  if (mRootId < 1) {
+    // The first root's title is an empty string.
+    rv = CreateRoot(mMainConn, NS_LITERAL_CSTRING("places"),
+                    NS_LITERAL_CSTRING("root________"), EmptyCString(),
+                    0, mRootId);
+
+    if (NS_FAILED(rv)) return rv;
+  }
+
+  int32_t position = startPosition;
 
   // For the other roots, the UI doesn't rely on the value in the database, so
   // just set it to something simple to make it easier for humans to read.
-
-  rv = CreateRoot(mMainConn, NS_LITERAL_CSTRING("menu"),
-                  NS_LITERAL_CSTRING("menu________"), NS_LITERAL_CSTRING("menu"));
-  if (NS_FAILED(rv)) return rv;
-
-  rv = CreateRoot(mMainConn, NS_LITERAL_CSTRING("toolbar"),
-                  NS_LITERAL_CSTRING("toolbar_____"), NS_LITERAL_CSTRING("toolbar"));
-  if (NS_FAILED(rv)) return rv;
-
-  rv = CreateRoot(mMainConn, NS_LITERAL_CSTRING("tags"),
-                  NS_LITERAL_CSTRING("tags________"), NS_LITERAL_CSTRING("tags"));
-  if (NS_FAILED(rv)) return rv;
-
-  if (NS_FAILED(rv)) return rv;
-  rv = CreateRoot(mMainConn, NS_LITERAL_CSTRING("unfiled"),
-                  NS_LITERAL_CSTRING("unfiled_____"), NS_LITERAL_CSTRING("unfiled"));
-  if (NS_FAILED(rv)) return rv;
+  if (mMenuRootId < 1) {
+    rv = CreateRoot(mMainConn, NS_LITERAL_CSTRING("menu"),
+                    NS_LITERAL_CSTRING("menu________"), NS_LITERAL_CSTRING("menu"),
+                    position, mMenuRootId);
+    if (NS_FAILED(rv)) return rv;
+    position++;
+  }
 
-  int64_t mobileRootId = CreateMobileRoot();
-  if (mobileRootId <= 0) return NS_ERROR_FAILURE;
-  {
-    nsCOMPtr<mozIStorageStatement> mobileRootSyncStatusStmt;
-    rv = mMainConn->CreateStatement(NS_LITERAL_CSTRING(
-      "UPDATE moz_bookmarks SET syncStatus = :sync_status WHERE id = :id"
-    ), getter_AddRefs(mobileRootSyncStatusStmt));
+  if (mToolbarRootId < 1) {
+    rv = CreateRoot(mMainConn, NS_LITERAL_CSTRING("toolbar"),
+                    NS_LITERAL_CSTRING("toolbar_____"), NS_LITERAL_CSTRING("toolbar"),
+                    position, mToolbarRootId);
     if (NS_FAILED(rv)) return rv;
-    mozStorageStatementScoper mobileRootSyncStatusScoper(
-      mobileRootSyncStatusStmt);
+    position++;
+  }
 
-    rv = mobileRootSyncStatusStmt->BindInt32ByName(
-      NS_LITERAL_CSTRING("sync_status"),
-      nsINavBookmarksService::SYNC_STATUS_NEW
-    );
+  if (mTagsRootId < 1) {
+    rv = CreateRoot(mMainConn, NS_LITERAL_CSTRING("tags"),
+                    NS_LITERAL_CSTRING("tags________"), NS_LITERAL_CSTRING("tags"),
+                    position, mTagsRootId);
     if (NS_FAILED(rv)) return rv;
-    rv = mobileRootSyncStatusStmt->BindInt64ByName(NS_LITERAL_CSTRING("id"),
-                                                   mobileRootId);
-    if (NS_FAILED(rv)) return rv;
-
-    rv = mobileRootSyncStatusStmt->Execute();
-    NS_ENSURE_SUCCESS(rv, rv);
+    position++;
   }
 
-#if DEBUG
-  nsCOMPtr<mozIStorageStatement> stmt;
-  rv = mMainConn->CreateStatement(NS_LITERAL_CSTRING(
-    "SELECT count(*), sum(position) FROM moz_bookmarks"
-  ), getter_AddRefs(stmt));
-  if (NS_FAILED(rv)) return rv;
+  if (mUnfiledRootId < 1) {
+    if (NS_FAILED(rv)) return rv;
+    rv = CreateRoot(mMainConn, NS_LITERAL_CSTRING("unfiled"),
+                    NS_LITERAL_CSTRING("unfiled_____"), NS_LITERAL_CSTRING("unfiled"),
+                    position, mUnfiledRootId);
+    if (NS_FAILED(rv)) return rv;
+    position++;
+  }
 
-  bool hasResult;
-  rv = stmt->ExecuteStep(&hasResult);
-  if (NS_FAILED(rv)) return rv;
-  MOZ_ASSERT(hasResult);
-  int32_t bookmarkCount = stmt->AsInt32(0);
-  int32_t positionSum = stmt->AsInt32(1);
-  MOZ_ASSERT(bookmarkCount == 6 && positionSum == 10);
-#endif
+  if (mMobileRootId < 1) {
+    int64_t mobileRootId = CreateMobileRoot();
+    if (mobileRootId <= 0) return NS_ERROR_FAILURE;
+    {
+      nsCOMPtr<mozIStorageStatement> mobileRootSyncStatusStmt;
+      rv = mMainConn->CreateStatement(NS_LITERAL_CSTRING(
+        "UPDATE moz_bookmarks SET syncStatus = :sync_status WHERE id = :id"
+      ), getter_AddRefs(mobileRootSyncStatusStmt));
+      if (NS_FAILED(rv)) return rv;
+      mozStorageStatementScoper mobileRootSyncStatusScoper(
+        mobileRootSyncStatusStmt);
+
+      rv = mobileRootSyncStatusStmt->BindInt32ByName(
+        NS_LITERAL_CSTRING("sync_status"),
+        nsINavBookmarksService::SYNC_STATUS_NEW
+      );
+      if (NS_FAILED(rv)) return rv;
+      rv = mobileRootSyncStatusStmt->BindInt64ByName(NS_LITERAL_CSTRING("id"),
+                                                     mobileRootId);
+      if (NS_FAILED(rv)) return rv;
+
+      rv = mobileRootSyncStatusStmt->Execute();
+      NS_ENSURE_SUCCESS(rv, rv);
+
+      mMobileRootId = mobileRootId;
+    }
+  }
 
   return NS_OK;
 }
 
 nsresult
 Database::InitFunctions()
 {
   MOZ_ASSERT(NS_IsMainThread());
@@ -2303,17 +2323,17 @@ Database::CreateMobileRoot()
 
   // Create the mobile root, ignoring conflicts if one already exists (for
   // example, if the user downgraded to an earlier release channel).
   nsCOMPtr<mozIStorageStatement> createStmt;
   nsresult rv = mMainConn->CreateStatement(NS_LITERAL_CSTRING(
     "INSERT OR IGNORE INTO moz_bookmarks "
       "(type, title, dateAdded, lastModified, guid, position, parent) "
     "SELECT :item_type, :item_title, :timestamp, :timestamp, :guid, "
-      "(SELECT COUNT(*) FROM moz_bookmarks p WHERE p.parent = b.id), b.id "
+      "IFNULL((SELECT MAX(position) + 1 FROM moz_bookmarks p WHERE p.parent = b.id), 0), b.id "
     "FROM moz_bookmarks b WHERE b.parent = 0"
   ), getter_AddRefs(createStmt));
   if (NS_FAILED(rv)) return -1;
   mozStorageStatementScoper createScoper(createStmt);
 
   rv = createStmt->BindInt32ByName(NS_LITERAL_CSTRING("item_type"),
                                    nsINavBookmarksService::TYPE_FOLDER);
   if (NS_FAILED(rv)) return -1;
--- a/toolkit/components/places/Database.h
+++ b/toolkit/components/places/Database.h
@@ -301,17 +301,17 @@ protected:
   /**
    * Checks the root bookmark folders are present, and saves the IDs for them.
    */
   nsresult CheckRoots();
 
   /**
    * Creates bookmark roots in a new DB.
    */
-  nsresult CreateBookmarkRoots();
+  nsresult EnsureBookmarkRoots(const int32_t startPosition);
 
   /**
    * Initializes additionale SQLite functions, defined in SQLFunctions.h
    */
   nsresult InitFunctions();
 
   /**
    * Initializes temp entities, like triggers, tables, views...
--- a/toolkit/components/places/nsNavBookmarks.h
+++ b/toolkit/components/places/nsNavBookmarks.h
@@ -260,17 +260,17 @@ private:
   // is used to update tagged bookmarks when changing or removing a tag folder.
   nsresult AddSyncChangesForBookmarksInFolder(int64_t aFolderId,
                                               int64_t aSyncChangeDelta);
 
   // Inserts a tombstone for a removed synced item.
   nsresult InsertTombstone(const BookmarkData& aBookmark);
 
   // Inserts tombstones for removed synced items.
-  nsresult InsertTombstones(const nsTArray<TombstoneData>& aTombstones);
+  nsresult InsertTombstones(const nsTArray<mozilla::places::TombstoneData>& aTombstones);
 
   // Removes a stale synced bookmark tombstone.
   nsresult RemoveTombstone(const nsACString& aGUID);
 
   nsresult SetItemTitleInternal(BookmarkData& aBookmark,
                                 const nsACString& aTitle,
                                 int64_t aSyncChangeDelta);
 
--- a/toolkit/components/places/tests/head_common.js
+++ b/toolkit/components/places/tests/head_common.js
@@ -924,8 +924,32 @@ function mapItemIdToInternalRootName(aIt
       return "toolbarFolder";
     case PlacesUtils.unfiledBookmarksFolderId:
       return "unfiledBookmarksFolder";
     case PlacesUtils.mobileFolderId:
       return "mobileFolder";
   }
   return null;
 }
+
+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}
+ */
+var setupPlacesDatabase = async function(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);
+};
--- a/toolkit/components/places/tests/migration/head_migration.js
+++ b/toolkit/components/places/tests/migration/head_migration.js
@@ -9,34 +9,8 @@ ChromeUtils.import("resource://gre/modul
 {
   /* 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.
-
-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}
- */
-var setupPlacesDatabase = async function(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);
-};
-
-// This works provided all tests in this folder use add_task.
new file mode 100644
index 0000000000000000000000000000000000000000..72f217bf70f1672eb0c7c865a7929e860018833a
GIT binary patch
literal 5242880
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
z006$_bfmT>`nsCO+*?w;iRoIkuTU<Ru2pCI78VxzI(t(4S0pxyl|p&0FS|AMP-3<^
z(bp)AkC$fqGR-DBR+p|<s*{t2zVzEuhgYQ6igVR+@p5spFLht)(2A^PI6Xh!m%Kmq
zRIWYeYPm5pU8?j=SIgslUD4pX!p{fc>)}6!Ukm>#{A&1T;U9&+AAT`P1ONa400000
z00000000000000000000000000001hZ%ye`XQC@U<l;kTe8|R!Onm5w59#=jiVw+D
zsx#LWRq6_VITQYu@R!5i4qpww7XD`V)$mWlKL~$qvrh&900000000000000000000
z000000000000000007=lI@OuziVwL=Dwzmg>1+k*j#M%iyb={?#j~v-(+WD4(^IV=
z*%hU9g`W??*Ta7dzZU*g_|@>w!aoXsKm1~p2mk;80000000000000000000000000
z0000000000-<r~?Akh^ca`B-vK4jxVCO&k;hje^M#fM}n738|2N?qX#LHM`fkB5I8
zelDB|uZBMtz7T#U{Qk{882|tP00000000000000000000000000000000000zU6I8
z?MloYDI6I*IKS}h!9$I%IAQ9->4~|sbC*t@n0h7`C(InZP#k*V;OMi(GtYO%3D;`F
z2j(t~E)12+m$PxgbEhWGUp)Qdk@CW^a;8~jbhh`!v)4x7f91q<N1Sk}xBuGJb0hu5
z`uXS6al+W}iwDX>m-;VEkM^hHgmc5khcA~OKef<*?P79kYFBPxx;{2DJ#cKSK7PJC
z9_`Ido-F_X000000000000000000000000000000000000N7+*@!{qs4*~!H00000
z00000000000000000000000000000005(}ye7L#Eg8%>k00000000000000000000
z000000000000000fKApFA8u~)AOHXW00000000000000000000000000000000000
zV3T#nhnt%`2mk;800000000000000000000000000000000000*koPt;pQd}0ssI2
z00000000000000000000000000000000000Hd%N0-9a{dI2FDU9uEI&_#eZ+3}?fK
zH+xh700000000000000000000000000000000000008{|=$>pQcqrK!%vP_J%H={|
zwKg5Rl22xWC!@sia&@||QktG=l&|)!C=vd7uw!LIZ_i|cfvC=0xlk!K)|7sGqLp2k
zoUB&rE3+R+XM#gf-emD|v0R-i)>dZUpUMOW<I&7dw^CQ+MqOv4za1T!U}t<DrSb7n
zqi>=*8@$pL=Y^9&_`ky63jb61W8n{klVM{MCK&(#0000000000000000000000000
z0000000000fH%FZsfQA?)oZ14xzJawO=sVl8c58Q3zcG{uPr&VCABj#TN)oPHTov1
zvmN)P4kadwmy6}<T(Q<(F#WdF!NmCdbSt4PBXxi3$;5cMI^9<(P0uvSSKBj__ofDN
zg~`ckrQV+0ef@=C+e;%s_=WI4g#Y5k%u6HJ|K#QE*FSpwEiZrR<sW+a?DY@4^yQcS
z{f&3WKbt*|00000000000000000000000000000000000004YzI-cDUB!WcZsUXN?
zc6H{G$>i};tvFS^k{@n;F?aq<v0k08O%&@v$Ay8;Z+vWSDLg-PX0CGb^5b*IPZplo
zz9q;d5}gk<+sP%ml4pt&#Y!VTQkZU4{N_g=X@;*)^$(A{c;?D{@6f=(6OH8IZLONS
z7Hj5Gr>gZv{`pe9)F@6itGxE<nN|}YdGG0=!sy7r)k^=htA~^C%{8m$_O}L~NM=v9
zzAZfezWM3=NUbn&u~=I@>fzJn;iu0%K0aC*IG%jZmSziyJ&UKG&1OzFzwJD~*wX6S
z3&(nAdZ*?Ok1w3Ne6+h&`S4=pOt#}#sXj4ZuP@JCwUQq?b98khwdwQaLnlri8?2ms
z`pLUnjr1-y(veLcDVOtCqbbi%)C%>Pwew!7)COOCadfb8dF0%YyIR!`E>=%xQ%il1
z6zh#*Ek9DNme(|~@Z6aL1E;IS@qy8cH+BY@Ab4VHYc6*!Hq@Cq5zWF-<#M%rd2tzD
zef4ci;ekWLN6Wqa$I7)63n#i-Ls;BGiBvYZv<%0Vz7sw_Tx={<YZq6Kqi|&Z(S`l>
z-p7tT{X+7o&ZwbWqI2n7va5Gu{C)AOTR3<6@|i1z-l?Y_d!qlwPXza^yUX|PXzub{
z(As0URI;@dzwi7~SR8w%|KzpD4<EZQcj7=tTsoI{$KsAmrsgYCrSjq|eC4yh*XsMj
z!D8}QEE!gUpNMMZmTNVli61Z2R#b|1@?vtcQaY-1;GcTQ?$5M_ci&P+>DlUdG>+Ay
zYo(60M%SNiRl0X^29n9f(uKKHB?{xTu|;uD|HP5?C!3vg-n%ri*<xjWMIX^j$7xG_
zbUd1fYjk#YFAZyT=~}hgSSsBe_i^)|I$Zz&000000000000000000000000000000
z000000PyD06(4SH@*n^J000000000000000000000000000000000000AQ1Kg})nY
z3qKTuuZ3R^|2+Jo@V|#23V$>FweZv7FNL29e<u9N@JGXc8~#B2v)N+>0000000000
z0000000000000000000000000007|4Et3usiPVlvI+06sZEps-ZOx!_Yct4hX$G0@
zX3%kWGf3an3{qXqAerk(=b{SDwvyTKFN16%nQ6s3T0uJMDoRYXV#$su$R)eNKM1yk
zH-qq-;opZ}4gWO!SK$}Jo8i9>e<b{G;jf3E34ba4WcZ2jWAV>s&l3Ou0000000000
z00000000000000000000000000B>H|bRv;RCbOAzqBD`)-V9RPq9B(@ZEa>|Gh13=
zM|Ues-`xsRceTP~SF?CF*Q}iFY{rs1!oLZ!iF78D&LtA5j%JWdM?GeuSUMMVmQHte
z=DNa92HS4D9)!OU{^#)H;n%}IzwvtbZ^93T^>8vg7ak7}g^z^yg<bK_X3rG>00000
z0000000000000000000000000000000094Qbf<$%BIvj<(E0lJo!_w~oya7*TG6Tg
z?agSe8U5x*-n*?C?QBM0ef4cyo6&4D`i+mxZD~d`OVRFTv|}lHcQcw^ir&?Xrk0{z
z&1iBdn(I#I76%o@I&Tj#ie<y!4Yn-CJ{)A)@;)4N+>U+av%i;)&Tg^fYoDG;tvJ!-
z-BB#J6zd9qG1wON1mWL>UkQIJ{FU&h!ygS_3B#}!7Q^%5GvRlI1L4DAPyDml>jeM+
z00000000000000000000000000000000000z(2dYQ<+52abckI_3t~sV>$B8k3O<}
zIr7@4XSOXzUVZg#TbCo>_}JW*_DFYo<nH#!UG0&s_DHTh(%Bx#hJO%rHzQy9?C)ik
zbD~H`dn6s5#d5J!drtDMR3^6+=?Z^5*mmRdL6{6@Zv4rO{~cZpzdwB8#^=L74iAP8
zhkqM>Cj7bZp7415v)PLR00000000000000000000000000000000000006-MwQZ?g
ziQ?EZ{U@(Ie)!mhxf46$gyE4F&s>@B9U3@zqOm<rI6YJt9T~V<>A!aM@U}SNz@g!z
z<=*~d<=Tmbt#QJ_vEG^9srkd>3+FCxi4$tm=gWsqoIEyIIr(&VoKUIM248$}bg*%G
z<lNoOD$kucFmSqB93L3HcvqZIII{of!v1>iW5=F;p{qHL)8*l(&pkdqS{XQ=ixbYB
zzI^6Np?B)($DZi#j1$feotdkgy!`mw@sovY__<*FY<0X;E{-*2;u7<fsVHHxna~j@
zGz!!8u|?@<2DUe<)$(|u)=WsnC1#72`K1!ct*KqPTD96(O6ZQNB*VW5!heVk00000
z0000000000000000000000000000000002+=9f!$1otGmXD2JE&O}GBJ<&aWE!h=i
zB*U)-;cL+W000000000000000000000000000000000000002KZDdpTBxWZo$&S?a
z-1xQbC?yg8brAgm00000000000000000000000000000000000006+ZiEWA0uEcEh
z+StY7)rD$pvYzaUQj_7=gYdQJ0000000000000000000000000000000000000000
z-##+wM0X;U?1+L~vO7vhgkK4wUjP6A00000000000000000000000000000000000
zc(dzCq=JK|%Y}(zec##HLap(>YV~3iUhIl86X92b=obJ000000000000000000000
z0000000000000000N(U^QU`OV%Y}(zec##HLap(>YV~3iUhIxDZ*KD70000000000
z00000000000000000000000000001BlXb<1o0~ic0000000000000000000000000
z00000000000002kWZltUGW=u^emOb-00000000000000000000000000000000000
z006+Zg5AlkAd%bInMh<Z+s_myij~GlVY;491sxX#I$xjaUkV>d#04K}7Tk8KT5mjG
zs+StY$;Fc2{K$Kk!mg;<Wcc+Ud@VWv00000000000000000000000000000000000
z006+Zk4!p|OKeYeL_uy_SDbQllSc#q00000000000000000000000000000000000
z005h;JN~<VBM5&x{CHRohr*pV{{F_V-1xy8&)s<N`rlmtg(wRE000000000000000
z000000000000000000000N|g&W8LXp&)hRxy*75Scy*y#o2=I_l}nA{SYf_VEmbCq
zSH{{i#|9qD54`8@^sdpK_T&{s=E{YM;#{@fm@G63WAl~LrTJpBrKoCtpzp5q?)Tgm
zR~WBWFU}Tf7wan`V^Qa`&Ef-3<R8mF*58%h{nVYx&lPILN@J<Uf&9R`bLrh@->8Rj
zq28FSPL`%h#mTnb20GKb2k*T@-RPW(g~`d=mG<w?rgsnBsmk<xX|jD9qDlkr%A|Kb
z*{sx@t@`axWAnB0*i4~5(<<AaA9!a+dgsCQ%g#izyj1S7N7Ct?hkDj6cezwAH72U_
zl}6lP|GQEf_C8fBP82H>SC?8n*q==AJh6VO@f<c=jwfY#xc$9}^v<K}x4vd7qVk)4
z1poj5000000000000000000000000000000000000000000000000000000000000
z00000000000000000000000000000000000000000000000000000000000000000
z00000000000000000000000000000000000000000000000000000000000000000
z00000000000000000000000000000000000000000000000000000000000000000
z00000000000000000000000000000000000000000000000000000000000000000
z00000000000000000000000000000000000000000000000000000000000000000
z00000000000000000000000000000000000000000000000000000000000000000
z00000000000000000000000000000000000000000000000000000000000000000
z00000000000000000000000000000000000000000000000000000000000000000
z00000000000000000000000000000000000000000000000000000000000000000
z00000000000000000000000000000000000000000000000000000000000000000
z00000000000000000000000000000000000000000000000000000000000000000
z00000000000000000000000000000000000000000000000000000000000000000
z00000000000000000000000000000000000000000000000000000000000000000
z00000000000000000000000000000000000000000000000000000000000000000
z00000000000000000000000000000000000000000000000000000000000000000
z00000000000000000000000000000000000000000000000000000000000000000
z00000000000000000000000000000000000000000000000000000000000000000
z00000000000000000000000000000000000000000000000000000000000000000
z00000000000000000000000000000000000000000000000000000000000000000
z00000000000000000000000000000000000000000000000000000000000000000
z00000000000000000000000000000000000000000000000000000000000000000
z00000000000000000000000000000000000000000000000000000000000000000
z00000000000000000000000000000000000000000000000000000000000000000
z00000000000000000000000000000000000000000000000000000000000000000
z00000000000000000000000000000000000000000000000000000000000000000
z00000000000000000000000000000000000000000000000000000000000000000
z00000000000000000000000000000000000000000000000000000000000000000
z00000000000000000000000000000000000000000000000000000000000000000
z00000000000000000000000000000000000000000000000000000000000000000
z00000000000000000000000000000000000000000000000000000000000000000
z00000000000000000000000000000000000000000000000000000000000000000
z00000000000000000000000000000000000000000000000000000000000000000
z00000000000000000000000000000000000000000000000000000000000000000
z00000000000000000000000000000000000000000000000000000000000000000
z00000000000000000000000000002|pZ4I800000K+yl!&aeOg000000000000000
z00000000000000000000000000000000000000000000000000000000000000000
z00000000000000000000000000000000000000000000000000000000000000000
z00000000000000000000000000000000000000000000000000000000000000000
z00000000000000000000000000000000000000000000000000000000000000000
z00000000000000000000000000000000000000000000000000000000000000000
z00000000000000000000000000000000000000000000000000000000000000000
z00000000000000000000000000000000000000000000000000000000000000000
z00000000000000000000000000000000000000000000000000000000000000000
z00000000000000000000000000000000000000000000000000000000000000000
z00000000000000000000000000000000000000000000000000000000000000000
z00000000000000000000000000000000000000000000000000000000000000000
z00000000000000000000000000000000000000000000000000000000000000000
z00000000000000000000000000000000000000000000000000000000000000000
z00000000000000000000000000000000000000000000000000000000000000000
z00000000000000000000000000000000000000000000000000000000000000000
z00000000000000000000000000000000000000000000000000000000000000000
z00000000000000000000000000000000000000000000000000000000000000000
z00000000000000000000000000000000000000000000000000000000000000000
z00000000000000000000000000000000000000000000000000000000000000000
z00000000000000000000000000000000000000000000000000000000000000000
z00000000000000000000000000000000000000000000000000000000000000000
z00000000000000000000000000000000000000000000000000000000000000000
z00000000000000000000000000000000000000000000000000000000000000000
z00000000000000000000000000000000000000000000000000000000000000000
z00000000000000000000000000000000000000000000000000000000000000000
z00000000000000000000000000000000000000000000000000000000000000000
z00000000000000000000000000000000000000000000000000000000000000000
z00000000000000000000000000000000000000000000000000000000000000000
z00000000000000000000000000000000000000000000000000000000000000000
z00000000000000000000000000000000000000000000000000000000000000000
z00000000000000000000000000000000000000000000000000000000000000000
z00000000000000000000000000000000000000000000000000000000000000000
z00000000000000000000000000000000000000000000000000000000000000000
z00000000000000000000000000000000000000000000000000000000000000000
z00000000000000000000000000000000000000000000000000000000000000000
z00000000000000000000000000000000000000000000000000000000000000000
z00000000000000000000000000000000000000000000000000000000000000000
z00000000000000000000000000000000000000000000000000000000000000000
z00000000000000000000000000000000000000000000000000000000000000000
z00000000000000000000000000000000000000000000000000000000000000000
H005T&zXUya
new file mode 100644
index 0000000000000000000000000000000000000000..02bd9078915ffea2f8778b46f0e62276aaa20395
GIT binary patch
literal 1179648
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
z00000000000002rb$Sx(VguRsG4r;>_Si(dHjpouOV?_Zfh$+84D|LVb}kAwi`9I2
zdLX?n@o=nC8yjerMn_APfmFMTo~5-L)movDA4t9<@#La-y*OPf7cUnJ1Bv?+dlnV7
z(}|hUf%pT7r!(Cpv*qUGM5#J3Q7exQWTVMv!!HEkcfx-NzZw2@_<H!|@K3@&3}1_a
z000000000000000000000000000000000000002+-6@&qjb&ShOzY6wI;2~NRO`^w
zIwV_%MC%YwBziO1s8Kfj^;Gzu!(R`7FZ^HOH^bi!Ul0F0{G;$U*ZO1t0000000000
z0000000000000000000000000007`kB@?}|Z0nFoCE~H*<=#$`>`BBk!OKyFPCng9
zQk|q{A)e?Y@oW^yhF=K6?}Yylelz^*@b&P^;h%(m7`_$-0RR91000000000000000
z0000000000000000002syHhd|#ImhJrgi9T9n!5ss&(jT9g?j>qIHNT5<w;#HOhwP
zg7A04kB2`RekPm@|1_KpzY(4be`&2x1^@s6000000000000000000000000000000
z0001h?|SPKn`71Ky`^K@_a3P4Dxc1_0wZVkJTrJ{VsdQ%^V>76z~rTC`%ey^9j(4_
zqS@OD9J`vovg^Rf!$YS>M$)ao@Z{xFhpvrQE;eg3saD|X;Mu7wQzM7RD>D~+T7e74
zj$9spW@`7<(F-pmTY<snwjbHQuXwRGwfl6U6&OEJzj|c<bp8BvVJ5yVu{l$&3>_Rh
zF>?I5y`v}lTC=^m#;XMY00000000000000000000000000000000000003(&+dAA_
z<4FJj000000000000000000000000000000000000Kgi{whlMfcoF~r0000000000
z00000000000000000000000000I<fgt;5YVo&*2@0000000000000000000000000
z00000000000IadT*5T$FPXYh{000000000000000000000000000000000000M=Nx
zb-204lK=n!00000000000000000000000000000000000fHl?^{&0{EA4!BShfjw8
zCH#f(YhfjPWUXff00000000000000000000000000000000000006-MkM2#Uf`{Y1
zL8W%BR4(TSYW0cW<y<@!JQW2;%e9GtYH4D!S)LtOR3rRKuyJum??|PBp{UJtIbSU{
zm(_kptW%sX6l&GR;^GIBsbEi3Rw!OBmTS|+`r_gT5~*NUYc?|zo#>*{Xy|nGyRjz~
zY-*iHX>_#I92l!rf|s+cvak?@Uk!gJ{14$zg&zqEVRH=@82|tP000000000000000
z0000000000000000001hx4d<Uhhvr6wNkm9AE?zQ(r-@;#iq;oYOy)c6;9og*c7Xj
zMn_A{fw5Yp=l;Z=SfO~iSguVM>)jQT-<Q}G8=aZx1iA_m4<w$7jh1T@1J%;RWV1Zm
zT^PSFF_g&{3bksZJKT5uT(IHAGeP+8Z~S!l&u&bH=@-vj|Ff4iT>sehx4rbmmwx=E
z)7Rhs;+J0hcQ@X1<2`G=jsO4v0000000000000000000000000000000001dcRG~5
zCx`{H*waCfN^S1V#N+WprFwC^b~SgR^N+c6r;3f*Ont1_2zo9KzVR2c!F;;EP`h&M
z(&=X^#p15L8}11*u~_fJ?S3+`Z2VMltXOU4&g3UL4Zrrv-gf%+S3f*5Gc$g$Hh8MI
zd*WpL$@QI<o99|)631(eX6}8ZMyXjWw3~eO+tZycK7Mlc%-FMg3&Zt8qa)YiKa^=V
z&Ft(<J{C_O@BFv$-2Rz~+?jfQY^qpaI_uq6jvs#f@xjXY?xU6Xd+%xY5ZgL;`ssA)
zWc$CJ=jM7^+WOS9XKUxrjqSPiLcVsOuhaO+xyGq<&%sh-Y^KpzSi4#^cl6YOrJYP3
zI(4x+K73>-f3Eb@-JMRh&vnw1PVOt0bF<Ns=f>*!#^myKFXc~PJl+_uOzmq{_ubWL
zzH6>|GM$(o`%JOXEY@>pYPIsRE>7*69IRfPJi4p$!ZSBE1*ss|yRNgAo98;}O&pF^
z;b`@8t$cZI8(zQb{Cs-!+_9nK2cOxu{nW|9!`aRh<}RUFA|0RKhJ*9}6FzsM*t}A!
zPc5Ct==rNN$Di1_`}}O>QvB)OsH04*cm7<`ORvP%|BGMRL*ZnxwtJ><WVm{+bmP_F
z{uQtCZ5!KHc_!#wW0^#}-TWILJUO2}d+PAz!za$oRu2wNKhe{wor%43?uv{jW~$?*
z^4u!C_PNh?#=bN-7anPe?@Y8?WfodBqlF*M*B3R4uH?CJyHPT%22cEBZ`qxx&h+k|
zA1GO=jYjk64D|V5n4euII?|clV6xNbzPS~M$D8vv=KS2Ev=tkf6TTYk{724gFy0=d
z_rCd=Rf^S_MPo!O-HOeR(GxY=9t(PV`{t*$w06B#YtGm1YmIU9AG=%t0000000000
z0000000000000000000000000008jTl5HJsuJI%Q0000000000000000000000000
z0000000000003Z(Wy3du4dI7^@LS>6!mosX68=H>-@*@tza9Q&_@BaG3qKwHeE7-m
zC&PaoekA<JTF(^#000000000000000000000000000000000000D!l)R5FOg5*t&=
zSSFU;&`vVz+ez=bc9OoQouvBONzdKwBzadmNo3neJkyiRL=D<~#nYW6)k%6fNwSk9
zI!U}Wa3-D&|97w<ycvYw4!<709{zdwN8yLVSHqj(zYKpO{IB8X!q0@i7Je%H+3*wL
zC)RqM000000000000000000000000000000000000001Z>q;kMu~<BwP9<Z#vG|5|
zl2{)lnOI_7yD*))r<3;db<*VBoiuS*Cyi&@)zg`F<8*I37vGplW@52qs+}Zy+DSaw
zlgvaNN5^QeX!M>;HvCqw;l_7@@blq+3_lgV68=H>l^fp)|5f;bun`u*v*Dp|Pxxqf
zf0zxkYrR$g00000000000000000000000000000000000008{I(U%NTv7qPT;2Yog
z;K_~mBx9*qww?X@s~_Ib&Su)#uYLUF`gXRroqheT^XuB#bUXVOv%x*>Y-&E+*Ut9L
zXYX!jlk?fT+S$Z>Hrvj|=d+o<WM*zsQLgv)6r)_aD|cxywJ6tfJNMe>KAXIq`|7u+
z6N^qX-a7Nld@dXQU9chS55m6;|0w*O@U!8shCdhnWcYFzhV`%*o)3?OKNt>$kA(eE
zB>(^b00000000000000000000000000000000000zSrEHNX3Gli-T``<AWzRE@Zy;
z%H9nNnXi6(di_G?^}EimTgd#yY;aF^rms75cX#Hl?o75jlj+X%c4yKHnb$u53#r?g
z&waM1JCp3rB)T*4*7?n6vf(#^4L80Jgp=WqhVdI;2oHt#-uScdTsRv(67CBBH2iq@
znecbRU%K(PH~x05Hw6Fy00000000000000000000000000000000000fd6aj6Psht
zo;rN_@QHJ?)q{i68(V>qnVIo}wZT)x-4iD_v;wnd#-80<7_J{09l5r?6*zkC*wFEV
z&+OZN>g3?MR^ZgLXKUxrjqSPiLcVrSD=>NJ)Wz!f@R6bXxl&&%P|Baac)T%QncCN^
z-ra7pZ*s7Daq{S{$_vlj)e4NBzdCdLiJiO8&sHvFTY=qIjvs#f@xjXY?xU4VD^NID
ztnHpD92u^jEA_Sl`wO)z$1a_Ircx~KO1A=)+Gwd<9BE6n0yEX|C{SnzdRl>Iexfll
zC)p0vYUR;<y&Xuj`zTgt=4-@TYgDh*n)89aC=d_-DhU5RIsgCw000000000000000
z00000000000000000000z*}D?-V@v#>#G#1iQZUGup!nrdM%!f3gY26gYb>$00000
z00000000000000000000000000000000000-!syQdt;SCHQtlhkQu$!7e!*>-vrSQ
z000000000000000000000000000000000000001dk60f|Y>riG*G8s_vsY^MLL;7y
zqVe!sLHI^=000000000000000000000000000000000000001h?;WXRtS^>`_e4o1
z-WLU8;a>*P4*&oF00000000000000000000000000000000000yw&x`62Y#M<@{K&
zvEy_lUvKWO)uy6!DjOBX!q<c72LJ#700000000000000000000000000000000000
z-tzhryD}%s`LSYS$LUJG-rQfSO-1QcU#sxu8cz-Y0000000000000000000000000
z0000000000007olwsp9<#*+X5000000000000000000000000000000000000Dv{t
z7yZV=PX*zZq5}W`000000000000000000000000000000000000DL#t63+&)%%<L0
zES1`DsyJ4xHqYcI8p%Y^b8+yEufO`?`Sj6PtK!4$itCTp8qN2W8l`5jFjw<yA3r&t
zW}|N7;kSbDjpzUX00000000000000000000000000000000000007@RQps2*wjtgV
zC7JcvR^;Xy&j<hj000000000000000000000000000000000000M=Mv>-YMNApE`X
zm9P;W4L9BR_KnZp_^}($-+1Wy*RTIdR0IG3000000000000000000000000000000
z00000@K50JzU1a(_f~4xMy86hS8DY_qj9NRY8FTGGtFA5S}0x}=`I`@dOSDu-n)~V
zFZ6eZ7gd=q=f{fEwMMg$Z{|m4s-;Ua#dc3o)7;R&UCAx)y}#99v{sv{<m*$7MVXOk
z@JhS-(B9nRxyJ{y$t_Rcss41nUaU6fTRf2)dQT>~<@B44P|i1+m0F=RUMd#4MjPr)
zZW+Gs4sD}zD&`A?+l_YaOeeP-y;GBknNp#98KOo*?@lE*J=Jd1UaiLMPa`w+^2lVq
zG1;j*m>YUmPjb_)mFrGMt2|%t@kf)%O?&!RtarK8C^g4wGu39R!@+kaRvmr3UK}e{
o$7bhy-8C3bZaTbjudOv~_uN{Ph3O7%k0m!9Sh@FQOA*!oKROR?IsgCw
new file mode 100644
--- /dev/null
+++ b/toolkit/components/places/tests/unit/test_missing_builtin_folders.js
@@ -0,0 +1,92 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/**
+ * This file tests that a missing built-in folders (child of root) are correctly
+ * fixed when the database is loaded.
+ */
+
+const ALL_ROOT_GUIDS = [
+  PlacesUtils.bookmarks.menuGuid,
+  PlacesUtils.bookmarks.unfiledGuid,
+  PlacesUtils.bookmarks.toolbarGuid,
+  PlacesUtils.bookmarks.tagsGuid,
+  PlacesUtils.bookmarks.mobileGuid,
+];
+
+const INITIAL_ROOT_GUIDS = [
+  PlacesUtils.bookmarks.menuGuid,
+  PlacesUtils.bookmarks.tagsGuid,
+  PlacesUtils.bookmarks.unfiledGuid,
+];
+
+add_task(async function setup() {
+  // This file has the toolbar and mobile folders missing.
+  await setupPlacesDatabase("missingBuiltIn.sqlite");
+
+  // Check database contents to be migrated.
+  let path = OS.Path.join(OS.Constants.Path.profileDir, DB_FILENAME);
+  let db = await Sqlite.openConnection({ path });
+
+  let rows = await db.execute(`
+    SELECT guid FROM moz_bookmarks
+    WHERE parent = (SELECT id from moz_bookmarks WHERE guid = :guid)
+  `, {
+    guid: PlacesUtils.bookmarks.rootGuid
+  });
+
+  let guids = rows.map(row => row.getResultByName("guid"));
+  Assert.deepEqual(guids, INITIAL_ROOT_GUIDS,
+    "Initial database should have only the expected GUIDs");
+
+  await db.close();
+});
+
+add_task(async function test_database_recreates_roots() {
+  Assert.ok(PlacesUtils.history.databaseStatus == PlacesUtils.history.DATABASE_STATUS_OK ||
+    PlacesUtils.history.databaseStatus == PlacesUtils.history.DATABASE_STATUS_UPGRADED,
+    "Should successfully access the database for the first time");
+
+  let rootId = PlacesUtils.placesRootId;
+  Assert.greaterOrEqual(rootId, 0, "Should have a valid root Id");
+
+  let db = await PlacesUtils.promiseDBConnection();
+
+  for (let guid of ALL_ROOT_GUIDS) {
+    let rows = await db.execute(`
+      SELECT id, parent FROM moz_bookmarks
+      WHERE guid = :guid
+    `, {guid});
+
+    Assert.equal(rows.length, 1, "Should have exactly one row for the root");
+
+    Assert.equal(rows[0].getResultByName("parent"), rootId,
+      "Should have been created with the correct parent");
+
+    let root = await PlacesUtils.bookmarks.fetch(guid);
+
+    Assert.equal(root.guid, guid, "GUIDs should match");
+    Assert.equal(root.parentGuid, PlacesUtils.bookmarks.rootGuid,
+      "Should have the correct parent GUID");
+    Assert.equal(root.type, PlacesUtils.bookmarks.TYPE_FOLDER,
+      "Should have the correct type");
+
+    let id = rows[0].getResultByName("id");
+    Assert.equal(await PlacesUtils.promiseItemId(guid), id,
+      "Should return the correct id from promiseItemId");
+    Assert.equal(await PlacesUtils.promiseItemGuid(id), guid,
+      "Should return the correct guid from promiseItemGuid");
+  }
+
+  let rows = await db.execute(`
+    SELECT 1 FROM moz_bookmarks
+    WHERE parent = (SELECT id from moz_bookmarks WHERE guid = :guid)
+  `, {
+    guid: PlacesUtils.bookmarks.rootGuid
+  });
+
+  Assert.equal(rows.length, ALL_ROOT_GUIDS.length,
+    "Root folder should have the expected number of children");
+});
new file mode 100644
--- /dev/null
+++ b/toolkit/components/places/tests/unit/test_missing_root_folder.js
@@ -0,0 +1,81 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/**
+ * This file tests that a missing root folder is correctly fixed when the
+ * database is loaded.
+ */
+
+const ALL_ROOT_GUIDS = [
+  PlacesUtils.bookmarks.menuGuid,
+  PlacesUtils.bookmarks.unfiledGuid,
+  PlacesUtils.bookmarks.toolbarGuid,
+  PlacesUtils.bookmarks.tagsGuid,
+  PlacesUtils.bookmarks.mobileGuid,
+];
+
+add_task(async function setup() {
+  // This file has no root folder.
+  await setupPlacesDatabase("noRoot.sqlite");
+
+  // Check database contents to be migrated.
+  let path = OS.Path.join(OS.Constants.Path.profileDir, DB_FILENAME);
+  let db = await Sqlite.openConnection({ path });
+
+  let rows = await db.execute(`
+    SELECT guid FROM moz_bookmarks
+    WHERE guid = :guid
+  `, {
+    guid: PlacesUtils.bookmarks.rootGuid
+  });
+
+  Assert.equal(rows.length, 0, "Root folder should not exist");
+
+  await db.close();
+});
+
+add_task(async function test_database_recreates_roots() {
+  Assert.ok(PlacesUtils.history.databaseStatus == PlacesUtils.history.DATABASE_STATUS_OK ||
+    PlacesUtils.history.databaseStatus == PlacesUtils.history.DATABASE_STATUS_UPGRADED,
+    "Should successfully access the database for the first time");
+
+  let db = await PlacesUtils.promiseDBConnection();
+
+  let rows = await db.execute(`
+    SELECT id, parent, type FROM moz_bookmarks
+    WHERE guid = :guid
+  `, {guid: PlacesUtils.bookmarks.rootGuid});
+
+  Assert.equal(rows.length, 1, "Should have added exactly one root");
+  Assert.greaterOrEqual(rows[0].getResultByName("id"), 1,
+    "Should have a valid root Id");
+  Assert.equal(rows[0].getResultByName("parent"), 0,
+    "Should have a parent of id 0");
+  Assert.equal(rows[0].getResultByName("type"), PlacesUtils.bookmarks.TYPE_FOLDER,
+    "Should have a type of folder");
+
+  let id = rows[0].getResultByName("id");
+  Assert.equal(await PlacesUtils.promiseItemId(PlacesUtils.bookmarks.rootGuid), id,
+    "Should return the correct id from promiseItemId");
+  Assert.equal(await PlacesUtils.promiseItemGuid(id), PlacesUtils.bookmarks.rootGuid,
+    "Should return the correct guid from promiseItemGuid");
+
+  // Note: Currently we do not fix the parent of the folders on initial startup.
+  // There is a maintenance task that will do it, hence we don't check the parents
+  // here, just that the built-in folders correctly exist and haven't been
+  // duplicated.
+  for (let guid of ALL_ROOT_GUIDS) {
+    rows = await db.execute(`
+      SELECT id FROM moz_bookmarks
+      WHERE guid = :guid
+    `, {guid});
+
+    Assert.equal(rows.length, 1, "Should have exactly one row for the root");
+
+    let root = await PlacesUtils.bookmarks.fetch(guid);
+
+    Assert.equal(root.guid, guid, "GUIDs should match");
+  }
+});
--- a/toolkit/components/places/tests/unit/xpcshell.ini
+++ b/toolkit/components/places/tests/unit/xpcshell.ini
@@ -80,16 +80,20 @@ skip-if = (os == "win" && os_version == 
 [test_import_mobile_bookmarks.js]
 [test_isPageInDB.js]
 [test_isURIVisited.js]
 [test_isvisited.js]
 [test_keywords.js]
 [test_lastModified.js]
 [test_markpageas.js]
 [test_metadata.js]
+[test_missing_builtin_folders.js]
+support-files = missingBuiltIn.sqlite
+[test_missing_root_folder.js]
+support-files = noRoot.sqlite
 [test_mozIAsyncLivemarks.js]
 [test_multi_word_tags.js]
 [test_nsINavHistoryViewer.js]
 [test_null_interfaces.js]
 [test_onItemChanged_tags.js]
 [test_pageGuid_bookmarkGuid.js]
 [test_frecency_observers.js]
 [test_placeURIs.js]