Bug 478035 - Re-create the built-in root folders if they are missing in the places database. r=mak
authorMark Banner <standard8@mozilla.com>
Wed, 18 Apr 2018 18:29:19 +0100
changeset 468220 fe5121c793e4923e45bd622cf92c8687c001b0ff
parent 468219 db3d3eb4b46c52ee285a235c54dfc0041d092b27
child 468221 e726894fdd13ddb380001a627c3b8df2cefa283a
push id9165
push userasasaki@mozilla.com
push dateThu, 26 Apr 2018 21:04:54 +0000
treeherdermozilla-beta@064c3804de2e [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmak
bugs478035
milestone61.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
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 e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..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 e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..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]