Bug 647605 - Create a mobile bookmarks Places root and query. draft
authorKit Cambridge <kcambridge@mozilla.com>
Tue, 05 Jul 2016 14:54:46 -0700
changeset 384265 244ccab4cac91d3d28215dff5a2e714d611aee00
parent 384060 dbb31bcad5a1f60a35b5600ea1578d9b9fa55237
child 524671 dd90beaf7de30e0a57a1f6bbdfa78a67fb6dffd7
push id22234
push userkcambridge@mozilla.com
push dateTue, 05 Jul 2016 23:57:20 +0000
bugs647605
milestone50.0a1
Bug 647605 - Create a mobile bookmarks Places root and query. MozReview-Commit-ID: 5ed0Z4jHeqP
browser/components/places/PlacesUIUtils.jsm
toolkit/components/places/Database.cpp
toolkit/components/places/Database.h
toolkit/components/places/nsINavBookmarksService.idl
toolkit/components/places/nsNavBookmarks.cpp
toolkit/components/places/nsNavBookmarks.h
toolkit/components/places/nsNavHistoryQuery.cpp
--- a/browser/components/places/PlacesUIUtils.jsm
+++ b/browser/components/places/PlacesUIUtils.jsm
@@ -229,16 +229,17 @@ let InternalFaviconLoader = {
     loadDataForWindow.push(loadData);
   },
 };
 
 this.PlacesUIUtils = {
   ORGANIZER_LEFTPANE_VERSION: 7,
   ORGANIZER_FOLDER_ANNO: "PlacesOrganizer/OrganizerFolder",
   ORGANIZER_QUERY_ANNO: "PlacesOrganizer/OrganizerQuery",
+  ORGANIZER_HIDE_IF_EMPTY_ANNO: "PlacesOrganizer/HideIfEmpty"
 
   LOAD_IN_SIDEBAR_ANNO: "bookmarkProperties/loadInSidebar",
   DESCRIPTION_ANNO: "bookmarkProperties/description",
 
   /**
    * Makes a URI from a spec, and do fixup
    * @param   aSpec
    *          The string spec of the URI
@@ -1125,19 +1126,20 @@ this.PlacesUIUtils = {
       "BookmarksMenu":
         { title: null,
           concreteTitle: PlacesUtils.getString("BookmarksMenuFolderTitle"),
           concreteId: PlacesUtils.bookmarksMenuFolderId },
       "UnfiledBookmarks":
         { title: null,
           concreteTitle: PlacesUtils.getString("OtherBookmarksFolderTitle"),
           concreteId: PlacesUtils.unfiledBookmarksFolderId },
+      "MobileBookmarks": { title: this._getMobileQueryTitle() },
     };
     // All queries but PlacesRoot.
-    const EXPECTED_QUERY_COUNT = 7;
+    const EXPECTED_QUERY_COUNT = 8;
 
     // Removes an item and associated annotations, ignoring eventual errors.
     function safeRemoveItem(aItemId) {
       try {
         if (as.itemHasAnnotation(aItemId, PlacesUIUtils.ORGANIZER_QUERY_ANNO) &&
             !(as.getItemAnnotation(aItemId, PlacesUIUtils.ORGANIZER_QUERY_ANNO) in queries)) {
           // Some extension annotated their roots with our query annotation,
           // so we should not delete them.
@@ -1250,27 +1252,31 @@ this.PlacesUIUtils = {
         delete this.leftPaneFolderId;
         return this.leftPaneFolderId = leftPaneRoot;
       }
     }
 
     // Create a new left pane folder.
     var callback = {
       // Helper to create an organizer special query.
-      create_query: function CB_create_query(aQueryName, aParentId, aQueryUrl) {
+      create_query: function CB_create_query(aQueryName, aParentId, aQueryUrl, aOptions = {}) {
         let itemId = bs.insertBookmark(aParentId,
                                        PlacesUtils._uri(aQueryUrl),
                                        bs.DEFAULT_INDEX,
                                        queries[aQueryName].title);
         // Mark as special organizer query.
         as.setItemAnnotation(itemId, PlacesUIUtils.ORGANIZER_QUERY_ANNO, aQueryName,
                              0, as.EXPIRE_NEVER);
         // We should never backup this, since it changes between profiles.
         as.setItemAnnotation(itemId, PlacesUtils.EXCLUDE_FROM_BACKUP_ANNO, 1,
                              0, as.EXPIRE_NEVER);
+        if (aOptions.hideIfEmpty) {
+          as.setItemAnnotation(itemId, PlacesUIUtils.ORGANIZER_HIDE_IF_EMPTY_ANNO, 1,
+                               0, as.EXPIRE_NEVER);
+        }
         // Add to the queries map.
         PlacesUIUtils.leftPaneQueries[aQueryName] = itemId;
         return itemId;
       },
 
       // Helper to create an organizer special folder.
       create_folder: function CB_create_folder(aFolderName, aParentId, aIsRoot) {
               // Left Pane Root Folder.
@@ -1333,24 +1339,38 @@ this.PlacesUIUtils = {
 
         // All Bookmarks->Bookmarks Menu Query.
         this.create_query("BookmarksMenu", allBookmarksId,
                           "place:folder=BOOKMARKS_MENU");
 
         // All Bookmarks->Unfiled Bookmarks Query.
         this.create_query("UnfiledBookmarks", allBookmarksId,
                           "place:folder=UNFILED_BOOKMARKS");
+
+        // All Bookmarks->Mobile Bookmarks Query.
+        this.create_query("MobileBookmarks", allBookmarksId,
+                          "place:folder=MOBILE_BOOKMARKS", { hideIfEmpty: true });
       }
     };
     bs.runInBatchMode(callback, null);
 
     delete this.leftPaneFolderId;
     return this.leftPaneFolderId = leftPaneRoot;
   },
 
+  // TODO(kitcambridge): Move `mobile.label` into places.properties and remove
+  // this method.
+  _getMobileQueryTitle() {
+    try {
+      let syncBundle = Services.strings.createBundle("chrome://weave/locale/services/sync.properties");
+      return syncBundle.GetStringFromName("mobile.label");
+    } catch (ex) {}
+    return "";
+  },
+
   /**
    * Get the folder id for the organizer left-pane folder.
    */
   get allBookmarksFolderId() {
     // ensure the left-pane root is initialized;
     this.leftPaneFolderId;
     delete this.allBookmarksFolderId;
     return this.allBookmarksFolderId = this.leftPaneQueries["AllBookmarks"];
--- a/toolkit/components/places/Database.cpp
+++ b/toolkit/components/places/Database.cpp
@@ -849,16 +849,21 @@ Database::InitSchema(bool* aDatabaseMigr
 
       if (currentSchemaVersion < 33) {
         rv = MigrateV33Up();
         NS_ENSURE_SUCCESS(rv, rv);
       }
 
       // Firefox 50 uses schema version 33.
 
+      if (currentSchemaVersion < 34) {
+        rv = MigrateV34Up();
+        NS_ENSURE_SUCCESS(rv, rv);
+      }
+
       // Schema Upgrades must add migration code here.
 
       rv = UpdateBookmarkRootTitles();
       // We don't want a broken localization to cause us to think
       // the database is corrupt and needs to be replaced.
       MOZ_ASSERT(NS_SUCCEEDED(rv));
     }
   }
@@ -1003,30 +1008,33 @@ Database::CreateBookmarkRoots()
 
   rv = bundle->GetStringFromName(MOZ_UTF16("OtherBookmarksFolderTitle"),
                                  getter_Copies(rootTitle));
   if (NS_FAILED(rv)) return rv;
   rv = CreateRoot(mMainConn, NS_LITERAL_CSTRING("unfiled"),
                   NS_LITERAL_CSTRING("unfiled_____"), rootTitle);
   if (NS_FAILED(rv)) return rv;
 
+  rv = CreateMobileRoot();
+  if (NS_FAILED(rv)) return rv;
+
 #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;
 
   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 == 5 && positionSum == 6);
+  MOZ_ASSERT(bookmarkCount == 6 && positionSum == 10);
 #endif
 
   return NS_OK;
 }
 
 nsresult
 Database::InitFunctions()
 {
@@ -1808,16 +1816,159 @@ Database::MigrateV33Up() {
 
   // Create an index on url_hash.
   rv = mMainConn->ExecuteSimpleSQL(CREATE_IDX_MOZ_PLACES_URL_HASH);
   NS_ENSURE_SUCCESS(rv, rv);
 
   return NS_OK;
 }
 
+nsresult
+Database::MigrateV34Up() {
+  MOZ_ASSERT(NS_IsMainThread());
+
+  nsresult rv = CreateMobileRoot();
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  {
+    nsCOMPtr<mozIStorageStatement> stmt;
+    nsresult rv = mMainConn->CreateStatement(NS_LITERAL_CSTRING(
+      "SELECT item_id FROM moz_anno_attributes"
+    ), getter_AddRefs(stmt));
+    if (NS_FAILED(rv)) {
+      return NS_OK;
+    }
+  }
+
+  // Find the old root ID.
+  int64_t oldRootId = -1;
+  {
+    nsCOMPtr<mozIStorageStatement> stmt;
+    rv = mMainConn->CreateStatement(NS_LITERAL_CSTRING(
+      "SELECT a.item_id "
+      "FROM moz_anno_attributes n "
+      "JOIN moz_items_annos a ON n.id = a.anno_attribute_id "
+      "WHERE n.name = :anno_name "
+      "LIMIT 1"
+    ), getter_AddRefs(stmt));
+    NS_ENSURE_SUCCESS(rv, rv);
+    mozStorageStatementScoper scoper(stmt);
+
+    rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("anno_name"),
+                                    NS_LITERAL_CSTRING("mobile/bookmarksRoot"));
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    bool hasResult = false;
+    rv = stmt->ExecuteStep(&hasResult);
+    NS_ENSURE_SUCCESS(rv, rv);
+    if (hasResult) {
+      rv = stmt->GetInt64(0, &oldRootId);
+      NS_ENSURE_TRUE(NS_SUCCEEDED(rv) && oldRootId > 0, NS_ERROR_UNEXPECTED);
+    }
+  }
+
+  if (oldRootId <= 0) {
+    return NS_OK;
+  }
+
+  int64_t newRootId;
+  rv = GetIdForGuid(mMainConn, NS_LITERAL_CSTRING("mobile______"), &newRootId);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  // Move existing mobile bookmarks to the new root.
+  {
+    nsCOMPtr<mozIStorageStatement> stmt;
+    rv = mMainConn->CreateStatement(NS_LITERAL_CSTRING(
+      "UPDATE moz_bookmarks SET "
+        "parent = :newId, "
+        "position = (SELECT count(*) FROM moz_bookmarks "
+          "WHERE parent = :newId) "
+      "WHERE parent = :oldId"
+    ), getter_AddRefs(stmt));
+    NS_ENSURE_SUCCESS(rv, rv);
+    mozStorageStatementScoper scoper(stmt);
+
+    rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("newId"), newRootId);
+    NS_ENSURE_SUCCESS(rv, rv);
+    rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("oldId"), oldRootId);
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    rv = stmt->Execute();
+    NS_ENSURE_SUCCESS(rv, rv);
+  }
+
+  // Remove the old root.
+  {
+    nsCOMPtr<mozIStorageStatement> stmt;
+    rv = mMainConn->CreateStatement(NS_LITERAL_CSTRING(
+      "DELETE FROM moz_bookmarks WHERE id = :id"
+    ), getter_AddRefs(stmt));
+    NS_ENSURE_SUCCESS(rv, rv);
+    mozStorageStatementScoper scoper(stmt);
+
+    rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("id"), oldRootId);
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    rv = stmt->Execute();
+    NS_ENSURE_SUCCESS(rv, rv);
+  }
+
+  return NS_OK;
+}
+
+nsresult
+Database::GetIdForGuid(nsCOMPtr<mozIStorageConnection>& aDBConn,
+                       const nsACString& aGuid, int64_t* aItemId)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  NS_ENSURE_ARG_POINTER(aItemId);
+  *aItemId = 0;
+
+  nsCOMPtr<mozIStorageStatement> stmt;
+  nsresult rv = mMainConn->CreateStatement(NS_LITERAL_CSTRING(
+    "SELECT id FROM moz_bookmarks WHERE guid = :guid LIMIT 1"
+  ), getter_AddRefs(stmt));
+  NS_ENSURE_SUCCESS(rv, rv);
+  mozStorageStatementScoper scoper(stmt);
+  rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("guid"), aGuid);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  bool hasResult = false;
+  rv = stmt->ExecuteStep(&hasResult);
+  NS_ENSURE_TRUE(NS_SUCCEEDED(rv) && hasResult, NS_ERROR_FAILURE);
+  rv = stmt->GetInt64(0, aItemId);
+  NS_ENSURE_TRUE(NS_SUCCEEDED(rv) && *aItemId > 0, NS_ERROR_UNEXPECTED);
+
+  return NS_OK;
+}
+
+nsresult
+Database::CreateMobileRoot()
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  // TODO(kitcambridge): Move `mobile.label` into places.properties.
+  nsXPIDLString rootTitle;
+  nsCOMPtr<nsIStringBundleService> bundleService =
+    services::GetStringBundleService();
+  NS_ENSURE_STATE(bundleService);
+
+  nsCOMPtr<nsIStringBundle> bundle;
+  nsresult rv = bundleService->CreateBundle(
+    "chrome://weave/locale/services/sync.properties", getter_AddRefs(bundle));
+  if (NS_SUCCEEDED(rv)) {
+    (void)bundle->GetStringFromName(MOZ_UTF16("mobile.label"),
+                                    getter_Copies(rootTitle));
+  }
+
+  return CreateRoot(mMainConn, NS_LITERAL_CSTRING("mobile"),
+                    NS_LITERAL_CSTRING("mobile______"), rootTitle);
+}
+
 void
 Database::Shutdown()
 {
   // As the last step in the shutdown path, finalize the database handle.
   MOZ_ASSERT(NS_IsMainThread());
   MOZ_ASSERT(!mClosed);
 
   // Break cycles with the shutdown blockers.
--- a/toolkit/components/places/Database.h
+++ b/toolkit/components/places/Database.h
@@ -264,21 +264,26 @@ protected:
   nsresult MigrateV25Up();
   nsresult MigrateV26Up();
   nsresult MigrateV27Up();
   nsresult MigrateV28Up();
   nsresult MigrateV30Up();
   nsresult MigrateV31Up();
   nsresult MigrateV32Up();
   nsresult MigrateV33Up();
+  nsresult MigrateV34Up();
 
   nsresult UpdateBookmarkRootTitles();
 
   friend class ConnectionShutdownBlocker;
 
+  nsresult GetIdForGuid(nsCOMPtr<mozIStorageConnection>& aDBConn,
+                        const nsACString& aGuid, int64_t* aItemId);
+  nsresult CreateMobileRoot();
+
 private:
   ~Database();
 
   /**
    * Singleton getter, invoked by class instantiation.
    */
   static already_AddRefed<Database> GetSingleton();
 
--- a/toolkit/components/places/nsINavBookmarksService.idl
+++ b/toolkit/components/places/nsINavBookmarksService.idl
@@ -260,16 +260,21 @@ interface nsINavBookmarksService : nsISu
   readonly attribute long long unfiledBookmarksFolder;
 
   /**
    * The item ID of the personal toolbar folder.
    */
   readonly attribute long long toolbarFolder;
 
   /**
+   * The item ID of the mobile bookmarks folder.
+   */
+  readonly attribute long long mobileFolder;
+
+  /**
    * This value should be used for APIs that allow passing in an index
    * where an index is not known, or not required to be specified.
    * e.g.: When appending an item to a folder.
    */
   const short DEFAULT_INDEX = -1;
 
   const unsigned short TYPE_BOOKMARK = 1;
   const unsigned short TYPE_FOLDER = 2;
--- a/toolkit/components/places/nsNavBookmarks.cpp
+++ b/toolkit/components/places/nsNavBookmarks.cpp
@@ -115,16 +115,17 @@ private:
 
 nsNavBookmarks::nsNavBookmarks()
   : mItemCount(0)
   , mRoot(0)
   , mMenuRoot(0)
   , mTagsRoot(0)
   , mUnfiledRoot(0)
   , mToolbarRoot(0)
+  , mMobileRoot(0)
   , mCanNotify(false)
   , mCacheObservers("bookmark-observers")
   , mBatching(false)
 {
   NS_ASSERTION(!gBookmarksService,
                "Attempting to create two instances of the service!");
   gBookmarksService = this;
 }
@@ -184,17 +185,17 @@ nsNavBookmarks::Init()
 
 nsresult
 nsNavBookmarks::ReadRoots()
 {
   nsCOMPtr<mozIStorageStatement> stmt;
   nsresult rv = mDB->MainConn()->CreateStatement(NS_LITERAL_CSTRING(
     "SELECT guid, id FROM moz_bookmarks WHERE guid IN ( "
       "'root________', 'menu________', 'toolbar_____', "
-      "'tags________', 'unfiled_____' )"
+      "'tags________', 'unfiled_____', 'mobile______' )"
   ), getter_AddRefs(stmt));
   NS_ENSURE_SUCCESS(rv, rv);
 
   bool hasResult;
   while (NS_SUCCEEDED(stmt->ExecuteStep(&hasResult)) && hasResult) {
     nsAutoCString guid;
     rv = stmt->GetUTF8String(0, guid);
     NS_ENSURE_SUCCESS(rv, rv);
@@ -212,19 +213,23 @@ nsNavBookmarks::ReadRoots()
       mToolbarRoot = id;
     }
     else if (guid.EqualsLiteral("tags________")) {
       mTagsRoot = id;
     }
     else if (guid.EqualsLiteral("unfiled_____")) {
       mUnfiledRoot = id;
     }
+    else if (guid.EqualsLiteral("mobile______")) {
+      mMobileRoot = id;
+    }
   }
 
-  if (!mRoot || !mMenuRoot || !mToolbarRoot || !mTagsRoot || !mUnfiledRoot)
+  if (!mRoot || !mMenuRoot || !mToolbarRoot || !mTagsRoot || !mUnfiledRoot ||
+      !mMobileRoot)
     return NS_ERROR_FAILURE;
 
   return NS_OK;
 }
 
 // nsNavBookmarks::IsBookmarkedInDatabase
 //
 //    This checks to see if the specified place_id is actually bookmarked.
@@ -315,16 +320,24 @@ nsNavBookmarks::GetTagsFolder(int64_t* a
 NS_IMETHODIMP
 nsNavBookmarks::GetUnfiledBookmarksFolder(int64_t* aRoot)
 {
   *aRoot = mUnfiledRoot;
   return NS_OK;
 }
 
 
+NS_IMETHODIMP
+nsNavBookmarks::GetMobileFolder(int64_t* aRoot)
+{
+  *aRoot = mMobileRoot;
+  return NS_OK;
+}
+
+
 nsresult
 nsNavBookmarks::InsertBookmarkInDB(int64_t aPlaceId,
                                    enum ItemType aItemType,
                                    int64_t aParentId,
                                    int32_t aIndex,
                                    const nsACString& aTitle,
                                    PRTime aDateAdded,
                                    PRTime aLastModified,
--- a/toolkit/components/places/nsNavBookmarks.h
+++ b/toolkit/components/places/nsNavBookmarks.h
@@ -270,21 +270,22 @@ private:
 
   nsMaybeWeakPtrArray<nsINavBookmarkObserver> mObservers;
 
   int64_t mRoot;
   int64_t mMenuRoot;
   int64_t mTagsRoot;
   int64_t mUnfiledRoot;
   int64_t mToolbarRoot;
+  int64_t mMobileRoot;
 
   inline bool IsRoot(int64_t aFolderId) {
     return aFolderId == mRoot || aFolderId == mMenuRoot ||
            aFolderId == mTagsRoot || aFolderId == mUnfiledRoot ||
-           aFolderId == mToolbarRoot;
+           aFolderId == mToolbarRoot || aFolderId == mMobileRoot;
   }
 
   nsresult IsBookmarkedInDatabase(int64_t aBookmarkID, bool* aIsBookmarked);
 
   nsresult SetItemDateInternal(enum mozilla::places::BookmarkDate aDateType,
                                int64_t aItemId,
                                PRTime aValue);
 
--- a/toolkit/components/places/nsNavHistoryQuery.cpp
+++ b/toolkit/components/places/nsNavHistoryQuery.cpp
@@ -169,17 +169,18 @@ inline void AppendInt64(nsACString& str,
 }
 
 namespace PlacesFolderConversion {
   #define PLACES_ROOT_FOLDER "PLACES_ROOT"
   #define BOOKMARKS_MENU_FOLDER "BOOKMARKS_MENU"
   #define TAGS_FOLDER "TAGS"
   #define UNFILED_BOOKMARKS_FOLDER "UNFILED_BOOKMARKS"
   #define TOOLBAR_FOLDER "TOOLBAR"
-  
+  #define MOBILE_BOOKMARKS_FOLDER "MOBILE_BOOKMARKS"
+
   /**
    * Converts a folder name to a folder id.
    *
    * @param aName
    *        The name of the folder to convert to a folder id.
    * @returns the folder id if aName is a recognizable name, -1 otherwise.
    */
   inline int64_t DecodeFolder(const nsCString &aName)
@@ -193,16 +194,18 @@ namespace PlacesFolderConversion {
     else if (aName.EqualsLiteral(BOOKMARKS_MENU_FOLDER))
       (void)bs->GetBookmarksMenuFolder(&folderID);
     else if (aName.EqualsLiteral(TAGS_FOLDER))
       (void)bs->GetTagsFolder(&folderID);
     else if (aName.EqualsLiteral(UNFILED_BOOKMARKS_FOLDER))
       (void)bs->GetUnfiledBookmarksFolder(&folderID);
     else if (aName.EqualsLiteral(TOOLBAR_FOLDER))
       (void)bs->GetToolbarFolder(&folderID);
+    else if (aName.EqualsLiteral(MOBILE_BOOKMARKS_FOLDER))
+      (void)bs->GetMobileFolder(&folderID);
 
     return folderID;
   }
 
   /**
    * Converts a folder id to a named constant, or a string representation of the
    * folder id if there is no named constant for the folder, and appends it to
    * aQuery.
@@ -234,16 +237,20 @@ namespace PlacesFolderConversion {
     else if (NS_SUCCEEDED(bs->GetUnfiledBookmarksFolder(&folderID)) &&
              aFolderID == folderID) {
       aQuery.AppendLiteral(UNFILED_BOOKMARKS_FOLDER);
     }
     else if (NS_SUCCEEDED(bs->GetToolbarFolder(&folderID)) &&
              aFolderID == folderID) {
       aQuery.AppendLiteral(TOOLBAR_FOLDER);
     }
+    else if (NS_SUCCEEDED(bs->GetMobileFolder(&folderID)) &&
+             aFolderID == folderID) {
+      aQuery.AppendLiteral(MOBILE_BOOKMARKS_FOLDER);
+    }
     else {
       // It wasn't one of our named constants, so just convert it to a string.
       aQuery.AppendInt(aFolderID);
     }
 
     return NS_OK;
   }
 } // namespace PlacesFolderConversion