Bug 647605 - Create a mobile bookmarks Places root and query.
MozReview-Commit-ID: 5ed0Z4jHeqP
--- 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