--- a/browser/base/content/browser-places.js
+++ b/browser/base/content/browser-places.js
@@ -579,30 +579,30 @@ var PlacesCommandHook = {
hiddenRows: [ "feedLocation",
"siteLocation",
"description" ]
}, window);
},
/**
* Opens the Places Organizer.
- * @param aLeftPaneRoot
- * The query to select in the organizer window - options
- * are: History, AllBookmarks, BookmarksMenu, BookmarksToolbar,
- * UnfiledBookmarks, Tags and Downloads.
+ * @param {String} item The item to select in the organizer window,
+ * options are (case sensitive):
+ * BookmarksMenu, BookmarksToolbar, UnfiledBookmarks,
+ * AllBookmarks, History, Downloads.
*/
- showPlacesOrganizer: function PCH_showPlacesOrganizer(aLeftPaneRoot) {
+ showPlacesOrganizer(item) {
var organizer = Services.wm.getMostRecentWindow("Places:Organizer");
// Due to bug 528706, getMostRecentWindow can return closed windows.
if (!organizer || organizer.closed) {
// No currently open places window, so open one with the specified mode.
openDialog("chrome://browser/content/places/places.xul",
- "", "chrome,toolbar=yes,dialog=no,resizable", aLeftPaneRoot);
+ "", "chrome,toolbar=yes,dialog=no,resizable", item);
} else {
- organizer.PlacesOrganizer.selectLeftPaneContainerByHierarchy(aLeftPaneRoot);
+ organizer.PlacesOrganizer.selectLeftPaneContainerByHierarchy(item);
organizer.focus();
}
},
searchBookmarks() {
if (!focusAndSelectUrlBar()) {
return;
}
--- a/browser/components/places/PlacesUIUtils.jsm
+++ b/browser/components/places/PlacesUIUtils.jsm
@@ -200,17 +200,17 @@ let InternalFaviconLoader = {
this._removeLoadDataFromWindowMap(win, loadData);
}, FAVICON_REQUEST_TIMEOUT);
let loadDataForWindow = gFaviconLoadDataMap.get(win);
loadDataForWindow.push(loadData);
},
};
this.PlacesUIUtils = {
- ORGANIZER_LEFTPANE_VERSION: 7,
+ ORGANIZER_LEFTPANE_VERSION: 8,
ORGANIZER_FOLDER_ANNO: "PlacesOrganizer/OrganizerFolder",
ORGANIZER_QUERY_ANNO: "PlacesOrganizer/OrganizerQuery",
LOAD_IN_SIDEBAR_ANNO: "bookmarkProperties/loadInSidebar",
DESCRIPTION_ANNO: "bookmarkProperties/description",
/**
* Makes a URI from a spec, and do fixup
@@ -508,16 +508,26 @@ this.PlacesUIUtils = {
*/
canUserRemove(aNode, aView) {
let parentNode = aNode.parent;
if (!parentNode) {
// canUserRemove doesn't accept root nodes.
return false;
}
+ // Is it a query pointing to one of the special root folders?
+ if (PlacesUtils.nodeIsQuery(parentNode) && PlacesUtils.nodeIsFolder(aNode)) {
+ let guid = PlacesUtils.getConcreteItemGuid(aNode);
+ // If the parent folder is not a folder, it must be a query, and so this node
+ // cannot be removed.
+ if (PlacesUtils.isRootItem(guid)) {
+ return false;
+ }
+ }
+
// If it's not a bookmark, we can remove it unless it's a child of a
// livemark.
if (aNode.itemId == -1) {
// Rather than executing a db query, checking the existence of the feedURI
// annotation, detect livemark children by the fact that they are the only
// direct non-bookmark children of bookmark folders.
return !PlacesUtils.nodeIsFolder(parentNode);
}
@@ -558,32 +568,31 @@ this.PlacesUIUtils = {
if (!view || typeof view != "object") {
throw new Error("invalid value for aView");
}
let itemId = PlacesUtils.getConcreteItemId(placesNode);
if (itemId == PlacesUtils.placesRootId ||
view.controller.hasCachedLivemarkInfo(placesNode))
return true;
- // leftPaneFolderId, and as a result, allBookmarksFolderId, is a lazy getter
+ // leftPaneFolderId is a lazy getter
// performing at least a synchronous DB query (and on its very first call
// in a fresh profile, it also creates the entire structure).
// Therefore we don't want to this function, which is called very often by
// isCommandEnabled, to ever be the one that invokes it first, especially
// because isCommandEnabled may be called way before the left pane folder is
// even created (for example, if the user only uses the bookmarks menu or
// toolbar for managing bookmarks). To do so, we avoid comparing to those
// special folder if the lazy getter is still in place. This is safe merely
// because the only way to access the left pane contents goes through
// "resolving" the leftPaneFolderId getter.
if (typeof Object.getOwnPropertyDescriptor(this, "leftPaneFolderId").get == "function") {
return false;
}
- return itemId == this.leftPaneFolderId ||
- itemId == this.allBookmarksFolderId;
+ return itemId == this.leftPaneFolderId;
},
/**
* Gives the user a chance to cancel loading lots of tabs at once
*/
confirmOpenInTabs(numTabsToOpen, aWindow) {
const WARN_ON_OPEN_PREF = "browser.tabs.warnOnOpen";
var reallyOpen = true;
@@ -834,44 +843,31 @@ this.PlacesUIUtils = {
get leftPaneFolderId() {
delete this.leftPaneFolderId;
return this.leftPaneFolderId = this.maybeRebuildLeftPane();
},
// Get the folder id for the organizer left-pane folder.
maybeRebuildLeftPane() {
let leftPaneRoot = -1;
- let allBookmarksId;
// Shortcuts to services.
let bs = PlacesUtils.bookmarks;
let as = PlacesUtils.annotations;
// This is the list of the left pane queries.
let queries = {
"PlacesRoot": { title: "" },
"History": { title: this.getString("OrganizerQueryHistory") },
"Downloads": { title: this.getString("OrganizerQueryDownloads") },
"Tags": { title: this.getString("OrganizerQueryTags") },
"AllBookmarks": { title: this.getString("OrganizerQueryAllBookmarks") },
- "BookmarksToolbar":
- { title: "",
- concreteTitle: PlacesUtils.getString("BookmarksToolbarFolderTitle"),
- concreteId: PlacesUtils.toolbarFolderId },
- "BookmarksMenu":
- { title: "",
- concreteTitle: PlacesUtils.getString("BookmarksMenuFolderTitle"),
- concreteId: PlacesUtils.bookmarksMenuFolderId },
- "UnfiledBookmarks":
- { title: "",
- concreteTitle: PlacesUtils.getString("OtherBookmarksFolderTitle"),
- concreteId: PlacesUtils.unfiledBookmarksFolderId },
};
// All queries but PlacesRoot.
- const EXPECTED_QUERY_COUNT = 7;
+ const EXPECTED_QUERY_COUNT = 4;
// 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.
@@ -1048,47 +1044,27 @@ this.PlacesUIUtils = {
// Tags Query.
this.create_query("Tags", leftPaneRoot,
"place:type=" +
Ci.nsINavHistoryQueryOptions.RESULTS_AS_TAG_QUERY +
"&sort=" +
Ci.nsINavHistoryQueryOptions.SORT_BY_TITLE_ASCENDING);
// All Bookmarks Folder.
- allBookmarksId = this.create_folder("AllBookmarks", leftPaneRoot, false);
-
- // All Bookmarks->Bookmarks Toolbar Query.
- this.create_query("BookmarksToolbar", allBookmarksId,
- "place:folder=TOOLBAR");
-
- // 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");
+ this.create_query("AllBookmarks", leftPaneRoot,
+ "place:type=" +
+ Ci.nsINavHistoryQueryOptions.RESULTS_AS_ROOTS_QUERY);
}
};
bs.runInBatchMode(callback, null);
return leftPaneRoot;
},
/**
- * 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;
- },
-
- /**
* If an item is a left-pane query, returns the name of the query
* or an empty string if not.
*
* @param aItemId id of a container
* @return the name of the query, or empty string if not a left-pane query
*/
getLeftPaneQueryNameFromId: function PUIU_getLeftPaneQueryNameFromId(aItemId) {
var queryName = "";
--- a/browser/components/places/content/bookmarksPanel.js
+++ b/browser/components/places/content/bookmarksPanel.js
@@ -1,16 +1,16 @@
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
function init() {
document.getElementById("bookmarks-view").place =
- "place:queryType=1&folder=" + window.top.PlacesUIUtils.allBookmarksFolderId;
+ "place:type=" + Ci.nsINavHistoryQueryOptions.RESULTS_AS_ROOTS_QUERY;
}
function searchBookmarks(aSearchString) {
var tree = document.getElementById("bookmarks-view");
if (!aSearchString)
tree.place = tree.place;
else
tree.applyFilter(aSearchString,
--- a/browser/components/places/content/controller.js
+++ b/browser/components/places/content/controller.js
@@ -1372,33 +1372,33 @@ var PlacesControllerDragHelper = {
/**
* Determines if an unwrapped node can be moved.
*
* @param unwrappedNode
* A node unwrapped by PlacesUtils.unwrapNodes().
* @return True if the node can be moved, false otherwise.
*/
canMoveUnwrappedNode(unwrappedNode) {
- if (unwrappedNode.id <= 0 || PlacesUtils.isRootItem(unwrappedNode.id)) {
+ if ((unwrappedNode.concreteGuid && PlacesUtils.isRootItem(unwrappedNode.concreteGuid)) ||
+ unwrappedNode.id <= 0 || PlacesUtils.isRootItem(unwrappedNode.id)) {
return false;
}
let parentId = unwrappedNode.parent;
if (parentId <= 0 ||
parentId == PlacesUtils.placesRootId ||
parentId == PlacesUtils.tagsFolderId ||
unwrappedNode.grandParentId == PlacesUtils.tagsFolderId) {
return false;
}
// leftPaneFolderId and allBookmarksFolderId are lazy getters running
// at least a synchronous DB query. Therefore we don't want to invoke
// them first, especially because isCommandEnabled may be called way
// before the left pane folder is even necessary.
if (typeof Object.getOwnPropertyDescriptor(PlacesUIUtils, "leftPaneFolderId").get != "function" &&
- (parentId == PlacesUIUtils.leftPaneFolderId ||
- parentId == PlacesUIUtils.allBookmarksFolderId)) {
+ (parentId == PlacesUIUtils.leftPaneFolderId)) {
return false;
}
return true;
},
/**
* Determines if a node can be moved.
*
--- a/browser/components/places/content/editBookmarkOverlay.js
+++ b/browser/components/places/content/editBookmarkOverlay.js
@@ -55,18 +55,17 @@ var gEditItemOverlay = {
throw new Error("Cannot use an incomplete node to initialize the edit bookmark panel");
}
let parent = node.parent;
isParentReadOnly = !PlacesUtils.nodeIsFolder(parent);
if (!isParentReadOnly) {
let folderId = PlacesUtils.getConcreteItemId(parent);
isParentReadOnly = folderId == PlacesUtils.placesRootId ||
(!("get" in Object.getOwnPropertyDescriptor(PlacesUIUtils, "leftPaneFolderId")) &&
- (folderId == PlacesUIUtils.leftPaneFolderId ||
- folderId == PlacesUIUtils.allBookmarksFolderId));
+ (folderId == PlacesUIUtils.leftPaneFolderId));
}
parentId = parent.itemId;
parentGuid = parent.bookmarkGuid;
}
let focusedElement = aInitInfo.focusedElement;
let onPanelReady = aInitInfo.onPanelReady;
@@ -699,23 +698,23 @@ var gEditItemOverlay = {
expander.setAttribute("tooltiptext",
expander.getAttribute("tooltiptextup"));
folderTreeRow.collapsed = false;
// XXXmano: Ideally we would only do this once, but for some odd reason,
// the editable mode set on this tree, together with its collapsed state
// breaks the view.
const FOLDER_TREE_PLACE_URI =
- "place:excludeItems=1&excludeQueries=1&excludeReadOnlyFolders=1&folder=" +
- PlacesUIUtils.allBookmarksFolderId;
+ "place:excludeItems=1&excludeQueries=1&excludeReadOnlyFolders=1&type=" +
+ Ci.nsINavHistoryQueryOptions.RESULTS_AS_ROOTS_QUERY;
this._folderTree.place = FOLDER_TREE_PLACE_URI;
this._element("chooseFolderSeparator").hidden =
this._element("chooseFolderMenuItem").hidden = true;
- this._folderTree.selectItems([this._paneInfo.parentId]);
+ this._folderTree.selectItems([this._paneInfo.parentGuid]);
this._folderTree.focus();
}
},
/**
* Get the corresponding menu-item in the folder-menu-list for a bookmarks
* folder if such an item exists. Otherwise, this creates a menu-item for the
* folder. If the items-count limit (see MAX_FOLDERS_IN_MENU_LIST) is reached,
@@ -924,17 +923,17 @@ var gEditItemOverlay = {
.split(/\s*,\s*/) // Split on commas and remove spaces.
.filter(tag => tag.length > 0); // Kill empty tags.
},
async newFolder() {
let ip = this._folderTree.insertionPoint;
// default to the bookmarks menu folder
- if (!ip || ip.itemId == PlacesUIUtils.allBookmarksFolderId) {
+ if (!ip) {
ip = new InsertionPoint({
parentId: PlacesUtils.bookmarksMenuFolderId,
parentGuid: PlacesUtils.bookmarks.menuGuid
});
}
// XXXmano: add a separate "New Folder" string at some point...
let title = this._element("newFolderButton").label;
--- a/browser/components/places/content/places.js
+++ b/browser/components/places/content/places.js
@@ -36,55 +36,94 @@ var PlacesOrganizer = {
"editBMPanel_keywordRow",
],
_initFolderTree() {
var leftPaneRoot = PlacesUIUtils.leftPaneFolderId;
this._places.place = "place:excludeItems=1&expandQueries=0&folder=" + leftPaneRoot;
},
- selectLeftPaneBuiltIn(aQueryName) {
- var itemId = PlacesUIUtils.leftPaneQueries[aQueryName];
- this._places.selectItems([itemId]);
- // Forcefully expand all-bookmarks
- if (aQueryName == "AllBookmarks" || aQueryName == "History")
- PlacesUtils.asContainer(this._places.selectedNode).containerOpen = true;
+ /**
+ * Selects a left pane built-in item.
+ *
+ * @param {String} item The built-in item to select, may be one of (case sensitive):
+ * AllBookmarks, BookmarksMenu, BookmarksToolbar,
+ * History, Downloads, Tags, UnfiledBookmarks.
+ */
+ selectLeftPaneBuiltIn(item) {
+ switch (item) {
+ case "AllBookmarks":
+ case "History":
+ case "Downloads":
+ case "Tags": {
+ var itemId = PlacesUIUtils.leftPaneQueries[item];
+ this._places.selectItems([itemId]);
+ // Forcefully expand all-bookmarks
+ if (item == "AllBookmarks" || item == "History")
+ PlacesUtils.asContainer(this._places.selectedNode).containerOpen = true;
+ break;
+ }
+ case "BookmarksMenu":
+ this.selectLeftPaneContainerByHierarchy([
+ PlacesUIUtils.leftPaneQueries.AllBookmarks,
+ PlacesUtils.bookmarks.virtualMenuGuid
+ ]);
+ break;
+ case "BookmarksToolbar":
+ this.selectLeftPaneContainerByHierarchy([
+ PlacesUIUtils.leftPaneQueries.AllBookmarks,
+ PlacesUtils.bookmarks.virtualToolbarGuid
+ ]);
+ break;
+ case "UnfiledBookmarks":
+ this.selectLeftPaneContainerByHierarchy([
+ PlacesUIUtils.leftPaneQueries.AllBookmarks,
+ PlacesUtils.bookmarks.virtualUnfiledGuid
+ ]);
+ break;
+ default:
+ throw new Error(`Unrecognized item ${item} passed to selectLeftPaneRootItem`);
+ }
},
/**
* Opens a given hierarchy in the left pane, stopping at the last reachable
- * container.
+ * container. Note: item ids should be considered deprecated.
*
* @param aHierarchy A single container or an array of containers, sorted from
* the outmost to the innermost in the hierarchy. Each
* container may be either an item id, a Places URI string,
- * or a named query.
+ * or a named query, like:
+ * "BookmarksMenu", "BookmarksToolbar", "UnfiledBookmarks", "AllBookmarks".
* @see PlacesUIUtils.leftPaneQueries for supported named queries.
*/
- selectLeftPaneContainerByHierarchy:
- function PO_selectLeftPaneContainerByHierarchy(aHierarchy) {
+ selectLeftPaneContainerByHierarchy(aHierarchy) {
if (!aHierarchy)
- throw new Error("Invalid containers hierarchy");
+ throw new Error("Containers hierarchy not specified");
let hierarchy = [].concat(aHierarchy);
let selectWasSuppressed = this._places.view.selection.selectEventsSuppressed;
if (!selectWasSuppressed)
this._places.view.selection.selectEventsSuppressed = true;
try {
for (let container of hierarchy) {
switch (typeof container) {
case "number":
this._places.selectItems([container], false);
break;
case "string":
- if (container.substr(0, 6) == "place:")
- this._places.selectPlaceURI(container);
- else if (container in PlacesUIUtils.leftPaneQueries)
+ try {
this.selectLeftPaneBuiltIn(container);
- else
- throw new Error("Invalid container found: " + container);
+ } catch (ex) {
+ if (container.substr(0, 6) == "place:") {
+ this._places.selectPlaceURI(container);
+ } else {
+ // May be a guid.
+ this._places.selectItems([container], false);
+ }
+ }
break;
default:
throw new Error("Invalid container type found: " + container);
}
PlacesUtils.asContainer(this._places.selectedNode).containerOpen = true;
}
} finally {
if (!selectWasSuppressed)
--- a/browser/components/places/content/tree.xml
+++ b/browser/components/places/content/tree.xml
@@ -71,17 +71,18 @@
var queryNode = PlacesUtils.asQuery(this.result.root);
var options = queryNode.queryOptions.clone();
// Make sure we're getting uri results.
// We do not yet support searching into grouped queries or into
// tag containers, so we must fall to the default case.
if (PlacesUtils.nodeIsHistoryContainer(queryNode) ||
options.resultType == options.RESULTS_AS_TAG_QUERY ||
- options.resultType == options.RESULTS_AS_TAG_CONTENTS)
+ options.resultType == options.RESULTS_AS_TAG_CONTENTS ||
+ options.resultType == options.RESULTS_AS_ROOTS_QUERY)
options.resultType = options.RESULTS_AS_URI;
var query = PlacesUtils.history.getNewQuery();
query.searchTerms = filterString;
if (folderRestrict) {
query.setFolders(folderRestrict, folderRestrict.length);
options.queryType = options.QUERY_TYPE_BOOKMARKS;
@@ -616,18 +617,22 @@
ids.splice(index, 1);
}
var concreteGuid = PlacesUtils.getConcreteItemGuid(node);
if (ids.length == 0 || !PlacesUtils.nodeIsContainer(node) ||
checkedGuidsSet.has(concreteGuid))
return foundOne;
- // Only follow a query if it has been been explicitly opened by the caller.
- let shouldOpen = aOpenContainers && PlacesUtils.nodeIsFolder(node);
+ // Only follow a query if it has been been explicitly opened by the
+ // caller. We support the "AllBookmarks" case to allow callers to
+ // specify just the top-level bookmark folders.
+ let shouldOpen = aOpenContainers && (PlacesUtils.nodeIsFolder(node) ||
+ (PlacesUtils.nodeIsQuery(node) && node.itemId == PlacesUIUtils.leftPaneQueries.AllBookmarks));
+
PlacesUtils.asContainer(node);
if (!node.containerOpen && !shouldOpen)
return foundOne;
checkedGuidsSet.add(concreteGuid);
// Remember the beginning state so that we can re-close
// this node if we don't find any additional results here.
--- a/browser/components/places/content/treeView.js
+++ b/browser/components/places/content/treeView.js
@@ -132,16 +132,17 @@ PlacesTreeView.prototype = {
if (!(aContainer instanceof Ci.nsINavHistoryQueryResultNode))
return false;
switch (aContainer.queryOptions.resultType) {
case Ci.nsINavHistoryQueryOptions.RESULTS_AS_DATE_QUERY:
case Ci.nsINavHistoryQueryOptions.RESULTS_AS_SITE_QUERY:
case Ci.nsINavHistoryQueryOptions.RESULTS_AS_DATE_SITE_QUERY:
case Ci.nsINavHistoryQueryOptions.RESULTS_AS_TAG_QUERY:
+ case Ci.nsINavHistoryQueryOptions.RESULTS_AS_ROOTS_QUERY:
return false;
}
// If it's a folder, it's not a plain container.
let nodeType = aContainer.type;
return nodeType != Ci.nsINavHistoryResultNode.RESULT_TYPE_FOLDER &&
nodeType != Ci.nsINavHistoryResultNode.RESULT_TYPE_FOLDER_SHORTCUT;
},
@@ -186,22 +187,26 @@ PlacesTreeView.prototype = {
}
// Ensure that the entire chain is open, otherwise that node is invisible.
for (let ancestor of ancestors) {
if (!ancestor.containerOpen)
throw new Error("Invisible node passed to _getRowForNode");
}
- // Non-plain containers are initially built with their contents.
+ // Non-plain containers, and non-Roots queries are initially built with their
+ // contents.
let parent = aNode.parent;
let parentIsPlain = this._isPlainContainer(parent);
- if (!parentIsPlain) {
- if (parent == this._rootNode)
+ if (!parentIsPlain &&
+ parent.queryOptions.resultType !=
+ Ci.nsINavHistoryQueryOptions.RESULTS_AS_ROOTS_QUERY) {
+ if (parent == this._rootNode) {
return this._rows.indexOf(aNode);
+ }
return this._rows.indexOf(aNode, aParentRow);
}
let row = -1;
let useNodeIndex = typeof(aNodeIndex) == "number";
if (parent == this._rootNode) {
row = useNodeIndex ? aNodeIndex : this._rootNode.getChildIndex(aNode);
@@ -1271,31 +1276,45 @@ PlacesTreeView.prototype = {
if (PlacesUtils.nodeIsTagQuery(node))
properties += " tagContainer";
else if (PlacesUtils.nodeIsDay(node))
properties += " dayContainer";
else if (PlacesUtils.nodeIsHost(node))
properties += " hostContainer";
} else if (nodeType == Ci.nsINavHistoryResultNode.RESULT_TYPE_FOLDER ||
nodeType == Ci.nsINavHistoryResultNode.RESULT_TYPE_FOLDER_SHORTCUT) {
- if (this._controller.hasCachedLivemarkInfo(node)) {
- properties += " livemark";
- } else {
- PlacesUtils.livemarks.getLivemark({ id: node.itemId })
- .then(aLivemark => {
- this._controller.cacheLivemarkInfo(node, aLivemark);
- let livemarkProps = this._cellProperties.get(node);
- this._cellProperties.set(node, livemarkProps += " livemark");
- // The livemark attribute is set as a cell property on the title cell.
- this._invalidateCellValue(node, this.COLUMN_TYPE_TITLE);
- }, () => undefined);
+ if (itemId != -1) {
+ if (this._controller.hasCachedLivemarkInfo(node)) {
+ properties += " livemark";
+ } else {
+ PlacesUtils.livemarks.getLivemark({ id: itemId })
+ .then(aLivemark => {
+ this._controller.cacheLivemarkInfo(node, aLivemark);
+ let livemarkProps = this._cellProperties.get(node);
+ this._cellProperties.set(node, livemarkProps += " livemark");
+ // The livemark attribute is set as a cell property on the title cell.
+ this._invalidateCellValue(node, this.COLUMN_TYPE_TITLE);
+ }, () => undefined);
+ }
}
}
- if (itemId != -1) {
+ if (itemId == -1) {
+ switch (node.bookmarkGuid) {
+ case PlacesUtils.bookmarks.virtualToolbarGuid:
+ properties += ` queryFolder_${PlacesUtils.bookmarks.toolbarGuid}`;
+ break;
+ case PlacesUtils.bookmarks.virtualMenuGuid:
+ properties += ` queryFolder_${PlacesUtils.bookmarks.menuGuid}`;
+ break;
+ case PlacesUtils.bookmarks.virtualUnfiledGuid:
+ properties += ` queryFolder_${PlacesUtils.bookmarks.unfiledGuid}`;
+ break;
+ }
+ } else {
let queryName = PlacesUIUtils.getLeftPaneQueryNameFromId(itemId);
if (queryName)
properties += " OrganizerQuery_" + queryName;
}
} else if (nodeType == Ci.nsINavHistoryResultNode.RESULT_TYPE_SEPARATOR)
properties += " separator";
else if (PlacesUtils.nodeIsURI(node)) {
properties += " " + PlacesUIUtils.guessUrlSchemeForUI(node.uri);
@@ -1759,22 +1778,22 @@ PlacesTreeView.prototype = {
// * places-roots
// * the left pane special folders and queries (those are place: uri
// bookmarks)
// * separators
//
// Note that concrete itemIds aren't used intentionally. For example, we
// have no reason to disallow renaming a shortcut to the Bookmarks Toolbar,
// except for the one under All Bookmarks.
- if (PlacesUtils.nodeIsSeparator(node) || PlacesUtils.isRootItem(itemGuid))
+ if (PlacesUtils.nodeIsSeparator(node) || PlacesUtils.isRootItem(itemGuid) ||
+ PlacesUtils.isQueryGeneratedFolder(itemGuid))
return false;
let parentId = PlacesUtils.getConcreteItemId(node.parent);
- if (parentId == PlacesUIUtils.leftPaneFolderId ||
- parentId == PlacesUIUtils.allBookmarksFolderId) {
+ if (parentId == PlacesUIUtils.leftPaneFolderId) {
// Note that the for the time being this is the check that actually
// blocks renaming places "roots", and not the isRootItem check above.
// That's because places root are only exposed through folder shortcuts
// descendants of the left pane folder.
return false;
}
return true;
--- a/browser/components/places/tests/browser/browser_bookmarkProperties_readOnlyRoot.js
+++ b/browser/components/places/tests/browser/browser_bookmarkProperties_readOnlyRoot.js
@@ -24,16 +24,12 @@ add_task(async function() {
let namepicker = dialogWin.document.getElementById("editBMPanel_namePicker");
Assert.ok(namepicker.readOnly, "Name field is read-only");
let bookmark = await PlacesUtils.bookmarks.fetch(PlacesUtils.bookmarks.unfiledGuid);
Assert.equal(namepicker.value, bookmark.title, "Node title is correct");
// Blur the field and ensure root's name has not been changed.
namepicker.blur();
bookmark = await PlacesUtils.bookmarks.fetch(PlacesUtils.bookmarks.unfiledGuid);
Assert.equal(namepicker.value, bookmark.title, "Root title is correct");
- // Check the shortcut's title.
- info(tree.selectedNode.bookmarkGuid);
- bookmark = await PlacesUtils.bookmarks.fetch(tree.selectedNode.bookmarkGuid);
- Assert.equal(bookmark.title, "", "Shortcut title is null");
}
);
});
});
--- a/browser/components/places/tests/browser/browser_bookmark_folder_moveability.js
+++ b/browser/components/places/tests/browser/browser_bookmark_folder_moveability.js
@@ -80,41 +80,31 @@ add_task(async function() {
Assert.equal(tree.selectedNode.childCount, 1, "has tags");
let tagNode = tree.selectedNode.getChild(0);
Assert.ok(!PlacesControllerDragHelper.canMoveNode(tagNode, tree),
"should not be able to move tag container node");
tree.selectedNode.containerOpen = false;
info("Test that special folders and cannot be moved but other shortcuts can.");
let roots = [
- PlacesUtils.bookmarksMenuFolderId,
- PlacesUtils.unfiledBookmarksFolderId,
- PlacesUtils.toolbarFolderId,
+ PlacesUtils.bookmarks.menuGuid,
+ PlacesUtils.bookmarks.unfiledGuid,
+ PlacesUtils.bookmarks.toolbarGuid,
];
- for (let id of roots) {
- selectShortcutForRootId(tree, id);
+ for (let guid of roots) {
+ tree.selectItems([guid]);
Assert.ok(!PlacesControllerDragHelper.canMoveNode(tree.selectedNode, tree),
"shouldn't be able to move default shortcuts to roots");
+ let id = await PlacesUtils.promiseItemId(guid);
let s = await PlacesUtils.bookmarks.insert({
parentGuid: root.guid,
title: "bar",
url: `place:folder=${id}`,
});
tree.selectItems([s.guid]);
Assert.equal(tree.selectedNode.bookmarkGuid, s.guid,
"Selected the expected node");
Assert.ok(PlacesControllerDragHelper.canMoveNode(tree.selectedNode, tree),
"should be able to move user-created shortcuts to roots");
}
});
});
-
-function selectShortcutForRootId(tree, id) {
- for (let i = 0; i < tree.result.root.childCount; ++i) {
- let child = tree.result.root.getChild(i);
- if (PlacesUtils.getConcreteItemId(child) == id) {
- tree.selectItems([child.itemId]);
- return;
- }
- }
- Assert.ok(false, "Cannot find shortcut to root");
-}
--- a/browser/components/places/tests/browser/browser_library_commands.js
+++ b/browser/components/places/tests/browser/browser_library_commands.js
@@ -83,17 +83,17 @@ add_task(async function test_query_on_to
PO.selectLeftPaneBuiltIn("BookmarksToolbar");
isnot(PO._places.selectedNode, null, "We have a valid selection");
is(PlacesUtils.getConcreteItemId(PO._places.selectedNode),
PlacesUtils.toolbarFolderId,
"We have correctly selected bookmarks toolbar node.");
// Check that both cut and delete commands are disabled, cause this is a child
- // of AllBookmarksFolderId.
+ // of the All Bookmarks special query.
ok(PO._places.controller.isCommandEnabled("cmd_copy"),
"Copy command is enabled");
ok(!PO._places.controller.isCommandEnabled("cmd_cut"),
"Cut command is disabled");
ok(!PO._places.controller.isCommandEnabled("cmd_delete"),
"Delete command is disabled");
let toolbarNode = PlacesUtils.asContainer(PO._places.selectedNode);
--- a/browser/components/places/tests/browser/browser_library_left_pane_fixnames.js
+++ b/browser/components/places/tests/browser/browser_library_left_pane_fixnames.js
@@ -55,34 +55,19 @@ function test() {
for (var i = 0; i < items.length; i++) {
var itemId = items[i];
var queryName = PlacesUtils.annotations
.getItemAnnotation(items[i],
PlacesUIUtils.ORGANIZER_QUERY_ANNO);
var query = { name: queryName,
itemId,
correctTitle: PlacesUtils.bookmarks.getItemTitle(itemId) };
- switch (queryName) {
- case "BookmarksToolbar":
- query.concreteId = PlacesUtils.toolbarFolderId;
- query.concreteTitle = PlacesUtils.bookmarks.getItemTitle(query.concreteId);
- break;
- case "BookmarksMenu":
- query.concreteId = PlacesUtils.bookmarksMenuFolderId;
- query.concreteTitle = PlacesUtils.bookmarks.getItemTitle(query.concreteId);
- break;
- case "UnfiledBookmarks":
- query.concreteId = PlacesUtils.unfiledBookmarksFolderId;
- query.concreteTitle = PlacesUtils.bookmarks.getItemTitle(query.concreteId);
- break;
- }
+
leftPaneQueries.push(query);
// Rename to a bad title.
PlacesUtils.bookmarks.setItemTitle(query.itemId, "badName");
- if ("concreteId" in query)
- PlacesUtils.bookmarks.setItemTitle(query.concreteId, "badName");
}
restoreLeftPaneGetters();
// Open Library, this will kick-off left pane code.
openLibrary(onLibraryReady);
}
--- a/browser/components/places/tests/browser/browser_library_openFlatContainer.js
+++ b/browser/components/places/tests/browser/browser_library_openFlatContainer.js
@@ -15,26 +15,24 @@ add_task(async function() {
type: PlacesUtils.bookmarks.TYPE_FOLDER,
children: [{
title: "Bookmark",
url: "http://example.com",
}],
}],
});
- let library = await promiseLibrary("AllBookmarks");
+ let library = await promiseLibrary("UnfiledBookmarks");
registerCleanupFunction(async function() {
await promiseLibraryClosed(library);
await PlacesUtils.bookmarks.eraseEverything();
});
- // Select unfiled later, to ensure it's closed.
- library.PlacesOrganizer.selectLeftPaneBuiltIn("UnfiledBookmarks");
- ok(!library.PlacesOrganizer._places.selectedNode.containerOpen,
- "Unfiled container is closed");
+ // Ensure the container is closed.
+ library.PlacesOrganizer._places.selectedNode.containerOpen = false;
let folderNode = library.ContentTree.view.view.nodeForTreeIndex(0);
is(folderNode.bookmarkGuid, bookmarks[0].guid,
"Found the expected folder in the right pane");
// Select the folder node in the right pane.
library.ContentTree.view.selectNode(folderNode);
synthesizeClickOnSelectedTreeCell(library.ContentTree.view,
--- a/browser/components/places/tests/browser/browser_library_search.js
+++ b/browser/components/places/tests/browser/browser_library_search.js
@@ -27,17 +27,17 @@
const TEST_URL = "http://dummy.mozilla.org/";
const TEST_DOWNLOAD_URL = "http://dummy.mozilla.org/dummy.pdf";
var gLibrary;
var testCases = [
function allBookmarksScope() {
- let defScope = getDefaultScope(PlacesUIUtils.allBookmarksFolderId);
+ let defScope = getDefaultScope(PlacesUIUtils.leftPaneQueries.AllBookmarks);
search(PlacesUIUtils.allBookmarksFolderId, "dummy", defScope);
},
function historyScope() {
let defScope = getDefaultScope(PlacesUIUtils.leftPaneQueries.History);
search(PlacesUIUtils.leftPaneQueries.History, "dummy", defScope);
},
--- a/browser/components/places/tests/browser/head.js
+++ b/browser/components/places/tests/browser/head.js
@@ -1,18 +1,17 @@
ChromeUtils.defineModuleGetter(this, "NetUtil",
"resource://gre/modules/NetUtil.jsm");
ChromeUtils.defineModuleGetter(this, "PlacesTestUtils",
"resource://testing-common/PlacesTestUtils.jsm");
ChromeUtils.defineModuleGetter(this, "TestUtils",
"resource://testing-common/TestUtils.jsm");
// We need to cache these before test runs...
-let leftPaneGetters = new Map([["leftPaneFolderId", null],
- ["allBookmarksFolderId", null]]);
+let leftPaneGetters = new Map([["leftPaneFolderId", null]]);
for (let [key, val] of leftPaneGetters) {
if (!val) {
let getter = Object.getOwnPropertyDescriptor(PlacesUIUtils, key).get;
if (typeof getter == "function") {
leftPaneGetters.set(key, getter);
}
}
}
--- a/browser/components/places/tests/chrome/test_0_bug510634.xul
+++ b/browser/components/places/tests/chrome/test_0_bug510634.xul
@@ -41,18 +41,17 @@
* even if PlacesUIUtils.leftPaneFolderId was not initialized.
*/
SimpleTest.waitForExplicitFinish();
function runTest() {
// We need to cache and restore the getters in order to simulate
// Bug 510634.
- let leftPaneGetters = new Map([["leftPaneFolderId", null],
- ["allBookmarksFolderId", null]]);
+ let leftPaneGetters = new Map([["leftPaneFolderId", null]]);
for (let [key, val] of leftPaneGetters) {
if (!val) {
let getter = Object.getOwnPropertyDescriptor(PlacesUIUtils, key).get;
if (typeof getter == "function") {
leftPaneGetters.set(key, getter);
}
}
}
@@ -73,30 +72,44 @@
tree.place = "place:queryType=1&folder=" + leftPaneFolderId;
// The query-property is set on the title column for each row.
let titleColumn = tree.treeBoxObject.columns.getColumnAt(0);
// Open All Bookmarks
tree.selectItems([PlacesUIUtils.leftPaneQueries["AllBookmarks"]]);
PlacesUtils.asContainer(tree.selectedNode).containerOpen = true;
- is(PlacesUIUtils.allBookmarksFolderId, tree.selectedNode.itemId,
+ is(tree.selectedNode.uri,
+ "place:type=" + Ci.nsINavHistoryQueryOptions.RESULTS_AS_ROOTS_QUERY +
+ "&queryType=" + Ci.nsINavHistoryQueryOptions.QUERY_TYPE_BOOKMARKS,
"Opened All Bookmarks");
- ["History", "Downloads", "Tags", "AllBookmarks", "BookmarksToolbar",
- "BookmarksMenu", "UnfiledBookmarks"].forEach(
- function(aQueryName, aRow) {
- let found = false;
- for (let i = 0; i < tree.view.rowCount && !found; i++) {
- rowProperties = tree.view.getCellProperties(i, titleColumn).split(" ");
- found = rowProperties.includes("OrganizerQuery_" + aQueryName);
- }
- ok(found, "OrganizerQuery_" + aQueryName + " is set");
+ for (let queryName of ["History", "Downloads", "Tags", "AllBookmarks"]) {
+ let found = false;
+ for (let i = 0; i < tree.view.rowCount && !found; i++) {
+ rowProperties = tree.view.getCellProperties(i, titleColumn).split(" ");
+ found = rowProperties.includes("OrganizerQuery_" + queryName);
}
- );
+ ok(found, "OrganizerQuery_" + queryName + " is set");
+ }
+
+ const folderGuids = [
+ PlacesUtils.bookmarks.toolbarGuid,
+ PlacesUtils.bookmarks.menuGuid,
+ PlacesUtils.bookmarks.unfiledGuid,
+ ];
+
+ for (let guid of folderGuids) {
+ let found = false;
+ for (let i = 0; i < tree.view.rowCount && !found; i++) {
+ rowProperties = tree.view.getCellProperties(i, titleColumn).split(" ");
+ found = rowProperties.includes("queryFolder_" + guid);
+ }
+ ok(found, "queryFolder_" + guid + " is set");
+ }
// Close the root node
tree.result.root.containerOpen = false;
// Restore the getters for the next test.
restoreLeftPaneGetters();
SimpleTest.finish();
--- a/browser/components/places/tests/unit/test_leftpane_corruption_handling.js
+++ b/browser/components/places/tests/unit/test_leftpane_corruption_handling.js
@@ -5,33 +5,30 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/**
* Tests that we build a working leftpane in various corruption situations.
*/
// Used to store the original leftPaneFolderId getter.
var gLeftPaneFolderIdGetter;
-var gAllBookmarksFolderIdGetter;
// Used to store the original left Pane status as a JSON string.
var gReferenceHierarchy;
var gLeftPaneFolderId;
add_task(async function() {
// We want empty roots.
await PlacesUtils.bookmarks.eraseEverything();
// Sanity check.
Assert.ok(!!PlacesUIUtils);
// Check getters.
gLeftPaneFolderIdGetter = Object.getOwnPropertyDescriptor(PlacesUIUtils, "leftPaneFolderId");
Assert.equal(typeof(gLeftPaneFolderIdGetter.get), "function");
- gAllBookmarksFolderIdGetter = Object.getOwnPropertyDescriptor(PlacesUIUtils, "allBookmarksFolderId");
- Assert.equal(typeof(gAllBookmarksFolderIdGetter.get), "function");
registerCleanupFunction(() => PlacesUtils.bookmarks.eraseEverything());
});
add_task(async function() {
// Add a third party bogus annotated item. Should not be removed.
let folder = await PlacesUtils.bookmarks.insert({
parentGuid: PlacesUtils.bookmarks.unfiledGuid,
@@ -47,22 +44,21 @@ add_task(async function() {
// Create the left pane, and store its current status, it will be used
// as reference value.
gLeftPaneFolderId = PlacesUIUtils.leftPaneFolderId;
gReferenceHierarchy = folderIdToHierarchy(gLeftPaneFolderId);
while (gTests.length) {
// Run current test.
- await gTests.shift();
+ await gTests.shift()();
// Regenerate getters.
Object.defineProperty(PlacesUIUtils, "leftPaneFolderId", gLeftPaneFolderIdGetter);
gLeftPaneFolderId = PlacesUIUtils.leftPaneFolderId;
- Object.defineProperty(PlacesUIUtils, "allBookmarksFolderId", gAllBookmarksFolderIdGetter);
// Check the new left pane folder.
let leftPaneHierarchy = folderIdToHierarchy(gLeftPaneFolderId);
Assert.equal(gReferenceHierarchy, leftPaneHierarchy);
folder = await PlacesUtils.bookmarks.fetch({guid: folder.guid});
Assert.equal(folder.title, "test");
}
@@ -84,70 +80,50 @@ var gTests = [
async function test3() {
print("3. Delete a child of the left pane folder.");
let guid = await PlacesUtils.promiseItemGuid(gLeftPaneFolderId);
let bm = await PlacesUtils.bookmarks.fetch({parentGuid: guid, index: 0});
await PlacesUtils.bookmarks.remove(bm.guid);
},
async function test4() {
- print("4. Delete AllBookmarks.");
- let guid = await PlacesUtils.promiseItemGuid(PlacesUIUtils.allBookmarksFolderId);
- await PlacesUtils.bookmarks.remove(guid);
- },
-
- async function test5() {
- print("5. Create a duplicated left pane folder.");
+ print("4. Create a duplicated left pane folder.");
let folder = await PlacesUtils.bookmarks.insert({
parentGuid: PlacesUtils.bookmarks.unfiledGuid,
title: "PlacesRoot",
index: PlacesUtils.bookmarks.DEFAULT_INDEX,
type: PlacesUtils.bookmarks.TYPE_FOLDER
});
let folderId = await PlacesUtils.promiseItemId(folder.guid);
PlacesUtils.annotations.setItemAnnotation(folderId, ORGANIZER_FOLDER_ANNO,
"PlacesRoot", 0,
PlacesUtils.annotations.EXPIRE_NEVER);
},
- async function test6() {
- print("6. Create a duplicated left pane query.");
+ async function test5() {
+ print("5. Create a duplicated left pane query.");
let folder = await PlacesUtils.bookmarks.insert({
parentGuid: PlacesUtils.bookmarks.unfiledGuid,
title: "AllBookmarks",
index: PlacesUtils.bookmarks.DEFAULT_INDEX,
type: PlacesUtils.bookmarks.TYPE_FOLDER
});
let folderId = await PlacesUtils.promiseItemId(folder.guid);
PlacesUtils.annotations.setItemAnnotation(folderId, ORGANIZER_QUERY_ANNO,
"AllBookmarks", 0,
PlacesUtils.annotations.EXPIRE_NEVER);
},
- function test7() {
- print("7. Remove the left pane folder annotation.");
+ function test6() {
+ print("6. Remove the left pane folder annotation.");
PlacesUtils.annotations.removeItemAnnotation(gLeftPaneFolderId,
ORGANIZER_FOLDER_ANNO);
},
-
- function test8() {
- print("8. Remove a left pane query annotation.");
- PlacesUtils.annotations.removeItemAnnotation(PlacesUIUtils.allBookmarksFolderId,
- ORGANIZER_QUERY_ANNO);
- },
-
- async function test9() {
- print("9. Remove a child of AllBookmarks.");
- let guid = await PlacesUtils.promiseItemGuid(PlacesUIUtils.allBookmarksFolderId);
- let bm = await PlacesUtils.bookmarks.fetch({parentGuid: guid, index: 0});
- await PlacesUtils.bookmarks.remove(bm.guid);
- }
-
];
/**
* Convert a folder item id to a JSON representation of it and its contents.
*/
function folderIdToHierarchy(aFolderId) {
let root = PlacesUtils.getFolderContents(aFolderId).root;
let hier = JSON.stringify(hierarchyToObj(root));
--- a/browser/components/preferences/selectBookmark.js
+++ b/browser/components/preferences/selectBookmark.js
@@ -14,17 +14,17 @@
* a .names property. This dialog is responsible for updating the contents of
* the .urls property with an array of URLs to use as home pages and for
* updating the .names property with an array of names for those URLs before it
* closes.
*/
var SelectBookmarkDialog = {
init: function SBD_init() {
document.getElementById("bookmarks").place =
- "place:queryType=1&folder=" + PlacesUIUtils.allBookmarksFolderId;
+ "place:type=" + Ci.nsINavHistoryQueryOptions.RESULTS_AS_ROOTS_QUERY;
// Initial update of the OK button.
this.selectionChanged();
},
/**
* Update the disabled state of the OK button as the user changes the
* selection within the view.
--- a/browser/themes/shared/places/tree-icons.inc.css
+++ b/browser/themes/shared/places/tree-icons.inc.css
@@ -32,41 +32,41 @@ treechildren::-moz-tree-image(title, ope
treechildren::-moz-tree-image(title, separator) {
list-style-image: none;
width: 0 !important;
height: 0 !important;
margin: 0;
}
-treechildren::-moz-tree-image(container, OrganizerQuery_AllBookmarks) {
- list-style-image: url("chrome://browser/skin/places/allBookmarks.png");
-}
-
treechildren::-moz-tree-image(container, livemark) {
list-style-image: url("chrome://browser/skin/places/folder-live.svg");
}
-treechildren::-moz-tree-image(container, OrganizerQuery_BookmarksToolbar) {
+treechildren::-moz-tree-image(container, queryFolder_toolbar_____) {
list-style-image: url("chrome://browser/skin/places/bookmarksToolbar.svg");
}
-treechildren::-moz-tree-image(container, OrganizerQuery_BookmarksMenu) {
+treechildren::-moz-tree-image(container, queryFolder_menu________) {
list-style-image: url("chrome://browser/skin/places/bookmarksMenu.svg");
}
-treechildren::-moz-tree-image(container, OrganizerQuery_UnfiledBookmarks) {
+treechildren::-moz-tree-image(container, queryFolder_unfiled_____) {
list-style-image: url("chrome://browser/skin/places/unfiledBookmarks.svg");
}
/* query-nodes should be styled even if they're not expandable */
treechildren::-moz-tree-image(query) {
list-style-image: url("chrome://browser/skin/places/folder-smart.svg");
}
+treechildren::-moz-tree-image(query, OrganizerQuery_AllBookmarks) {
+ list-style-image: url("chrome://browser/skin/places/allBookmarks.png");
+}
+
treechildren::-moz-tree-image(query, OrganizerQuery_Downloads) {
list-style-image: url("chrome://browser/skin/places/downloads.png");
}
treechildren::-moz-tree-image(title, query, tagContainer),
treechildren::-moz-tree-image(query, OrganizerQuery_Tags) {
list-style-image: url("chrome://browser/skin/places/tag.png");
}
--- a/services/sync/tests/unit/test_bookmark_repair_responder.js
+++ b/services/sync/tests/unit/test_bookmark_repair_responder.js
@@ -275,107 +275,16 @@ add_task(async function test_responder_m
value: undefined,
extra: {flowID: request.flowID, numIDs: "2"},
},
]);
await cleanup(server);
});
-add_task(async function test_non_syncable() {
- let server = await makeServer();
-
- await Service.sync(); // to create the collections on the server.
-
- // Creates the left pane queries as a side effect.
- let leftPaneId = PlacesUIUtils.leftPaneFolderId;
- _(`Left pane root ID: ${leftPaneId}`);
- await PlacesTestUtils.promiseAsyncUpdates();
-
- // A child folder of the left pane root, containing queries for the menu,
- // toolbar, and unfiled queries.
- let allBookmarksId = PlacesUIUtils.leftPaneQueries.AllBookmarks;
- let allBookmarksGuid = await PlacesUtils.promiseItemGuid(allBookmarksId);
-
- let unfiledQueryId = PlacesUIUtils.leftPaneQueries.UnfiledBookmarks;
- let unfiledQueryGuid = await PlacesUtils.promiseItemGuid(unfiledQueryId);
-
- // Put the "Bookmarks Menu" on the server to simulate old bugs.
- let bookmarksMenuQueryId = PlacesUIUtils.leftPaneQueries.BookmarksMenu;
- let bookmarksMenuQueryGuid = await PlacesUtils.promiseItemGuid(bookmarksMenuQueryId);
- let collection = getServerBookmarks(server);
- collection.insert(bookmarksMenuQueryGuid, "doesn't matter");
-
- // Explicitly request the unfiled and allBookmarksGuid queries; these will
- // get tombstones. Because the BookmarksMenu is already on the server it
- // should be removed even though it wasn't requested. We should ignore the
- // toolbar query as it wasn't explicitly requested and isn't on the server.
- let request = {
- request: "upload",
- ids: [allBookmarksGuid, unfiledQueryGuid],
- flowID: Utils.makeGUID(),
- };
- let responder = new BookmarkRepairResponder();
- await responder.repair(request, null);
-
- checkRecordedEvents([
- { object: "repairResponse",
- method: "uploading",
- value: undefined,
- // Tombstones for the 2 items we requested and for bookmarksMenu
- extra: {flowID: request.flowID, numIDs: "3"},
- },
- ]);
-
- _("Sync to upload tombstones for items");
- await Service.sync();
-
- let toolbarQueryId = PlacesUIUtils.leftPaneQueries.BookmarksToolbar;
- let menuQueryId = PlacesUIUtils.leftPaneQueries.BookmarksMenu;
- let queryGuids = [
- allBookmarksGuid,
- await PlacesUtils.promiseItemGuid(toolbarQueryId),
- await PlacesUtils.promiseItemGuid(menuQueryId),
- unfiledQueryGuid,
- ];
-
- deepEqual(collection.keys().sort(), [
- // We always upload roots on the first sync.
- "menu",
- "mobile",
- "toolbar",
- "unfiled",
- ...request.ids,
- bookmarksMenuQueryGuid,
- ].sort(), "Should upload roots and queries on first sync");
-
- for (let guid of queryGuids) {
- let wbo = collection.wbo(guid);
- if (request.ids.includes(guid) || guid == bookmarksMenuQueryGuid) {
- // explicitly requested or already on the server, so should have a tombstone.
- let payload = JSON.parse(JSON.parse(wbo.payload).ciphertext);
- ok(payload.deleted, `Should upload tombstone for left pane query ${guid}`);
- } else {
- // not explicitly requested and not on the server at the start, so should
- // not be on the server now.
- ok(!wbo, `Should not upload anything for left pane query ${guid}`);
- }
- }
-
- checkRecordedEvents([
- { object: "repairResponse",
- method: "finished",
- value: undefined,
- extra: {flowID: request.flowID, numIDs: "3"},
- },
- ]);
-
- await cleanup(server);
-});
-
add_task(async function test_folder_descendants() {
let server = await makeServer();
let parentFolder = await PlacesUtils.bookmarks.insert({
type: PlacesUtils.bookmarks.TYPE_FOLDER,
parentGuid: PlacesUtils.bookmarks.menuGuid,
title: "Parent folder",
});
--- a/toolkit/components/places/Bookmarks.jsm
+++ b/toolkit/components/places/Bookmarks.jsm
@@ -130,32 +130,52 @@ var Bookmarks = Object.freeze({
IMPORT_REPLACE: Ci.nsINavBookmarksService.SOURCE_IMPORT_REPLACE,
SYNC_REPARENT_REMOVED_FOLDER_CHILDREN: Ci.nsINavBookmarksService.SOURCE_SYNC_REPARENT_REMOVED_FOLDER_CHILDREN,
},
/**
* Special GUIDs associated with bookmark roots.
* It's guaranteed that the roots will always have these guids.
*/
+ rootGuid: "root________",
+ menuGuid: "menu________",
+ toolbarGuid: "toolbar_____",
+ unfiledGuid: "unfiled_____",
+ mobileGuid: "mobile______",
- rootGuid: "root________",
- menuGuid: "menu________",
- toolbarGuid: "toolbar_____",
- unfiledGuid: "unfiled_____",
- mobileGuid: "mobile______",
+ // With bug 424160, tags will stop being bookmarks, thus this root will
+ // be removed. Do not rely on this, rather use the tagging service API.
+ tagsGuid: "tags________",
+
+ /**
+ * The GUIDs of the user content root folders that we support, for easy access
+ * as a set.
+ */
+ userContentRoots: ["toolbar_____", "menu________", "unfiled_____", "mobile______"],
- // With bug 424160, tags will stop being bookmarks, thus this root will
- // be removed. Do not rely on this, rather use the tagging service API.
- tagsGuid: "tags________",
+ /**
+ * GUIDs associated with virtual queries that are used for display in the left
+ * pane.
+ */
+ virtualMenuGuid: "menu_______v",
+ virtualToolbarGuid: "toolbar____v",
+ virtualUnfiledGuid: "unfiled___v",
+ virtualMobileGuid: "mobile____v",
- /**
- * The GUIDs of the user content root folders that we support, for easy access
- * as a set.
- */
- userContentRoots: ["toolbar_____", "menu________", "unfiled_____", "mobile______"],
+ /**
+ * Checks if a guid is a virtual root.
+ *
+ * @param {String} guid The guid of the item to look for.
+ * @returns {Boolean} true if guid is a virtual root, false otherwise.
+ */
+ isVirtualRootItem(guid) {
+ return guid == PlacesUtils.bookmarks.virtualMenuGuid ||
+ guid == PlacesUtils.bookmarks.virtualToolbarGuid ||
+ guid == PlacesUtils.bookmarks.virtualUnfiledGuid;
+ },
/**
* Inserts a bookmark-item into the bookmarks tree.
*
* For creating a bookmark, the following set of properties is required:
* - type
* - parentGuid
* - url, only for bookmarked URLs
--- a/toolkit/components/places/PlacesSyncUtils.jsm
+++ b/toolkit/components/places/PlacesSyncUtils.jsm
@@ -44,19 +44,16 @@ var PlacesSyncUtils = {
},
};
const { SOURCE_SYNC } = Ci.nsINavBookmarksService;
const MICROSECONDS_PER_SECOND = 1000000;
const SQLITE_MAX_VARIABLE_NUMBER = 999;
-const ORGANIZER_QUERY_ANNO = "PlacesOrganizer/OrganizerQuery";
-const ORGANIZER_ALL_BOOKMARKS_ANNO_VALUE = "AllBookmarks";
-const ORGANIZER_MOBILE_QUERY_ANNO_VALUE = "MobileBookmarks";
const MOBILE_BOOKMARKS_PREF = "browser.bookmarks.showMobileBookmarks";
// These are defined as lazy getters to defer initializing the bookmarks
// service until it's needed.
XPCOMUtils.defineLazyGetter(this, "ROOT_RECORD_ID_TO_GUID", () => ({
menu: PlacesUtils.bookmarks.menuGuid,
places: PlacesUtils.bookmarks.rootGuid,
tags: PlacesUtils.bookmarks.tagsGuid,
@@ -1056,80 +1053,25 @@ const BookmarkSyncUtils = PlacesSyncUtil
async ensureMobileQuery() {
let db = await PlacesUtils.promiseDBConnection();
let mobileChildGuids = await fetchChildGuids(db, PlacesUtils.bookmarks.mobileGuid);
let hasMobileBookmarks = mobileChildGuids.length > 0;
Services.prefs.setBoolPref(MOBILE_BOOKMARKS_PREF, hasMobileBookmarks);
if (hasMobileBookmarks) {
- await this.upsertMobileQuery(db);
- } else {
- await this.removeMobileQuery(db);
- }
- },
-
- async upsertMobileQuery(db) {
- let maybeAllBookmarksGuids = await fetchGuidsWithAnno(db,
- ORGANIZER_QUERY_ANNO, ORGANIZER_ALL_BOOKMARKS_ANNO_VALUE);
- if (!maybeAllBookmarksGuids.length) {
- return;
- }
+ let mobileTitle = PlacesUtils.getString("MobileBookmarksFolderTitle");
- let allBookmarksGuid = maybeAllBookmarksGuids[0];
- let mobileTitle = PlacesUtils.getString("MobileBookmarksFolderTitle");
-
- let maybeMobileQueryGuids = await fetchGuidsWithAnno(db,
- ORGANIZER_QUERY_ANNO, ORGANIZER_MOBILE_QUERY_ANNO_VALUE);
- if (maybeMobileQueryGuids.length) {
- let mobileQueryGuid = maybeMobileQueryGuids[0];
- // We have a left pane query for mobile bookmarks, make sure the
- // query title is correct.
+ // Make sure the mobile root title matches the query.
await PlacesUtils.bookmarks.update({
- guid: mobileQueryGuid,
- url: "place:folder=MOBILE_BOOKMARKS",
+ guid: PlacesUtils.bookmarks.mobileGuid,
title: mobileTitle,
source: SOURCE_SYNC,
});
- } else {
- // We have no left pane query. Create the query.
- let mobileQuery = await PlacesUtils.bookmarks.insert({
- parentGuid: allBookmarksGuid,
- url: "place:folder=MOBILE_BOOKMARKS",
- title: mobileTitle,
- source: SOURCE_SYNC,
- });
-
- let mobileQueryId = await PlacesUtils.promiseItemId(mobileQuery.guid);
-
- PlacesUtils.annotations.setItemAnnotation(mobileQueryId,
- ORGANIZER_QUERY_ANNO, ORGANIZER_MOBILE_QUERY_ANNO_VALUE, 0,
- PlacesUtils.annotations.EXPIRE_NEVER, SOURCE_SYNC);
- PlacesUtils.annotations.setItemAnnotation(mobileQueryId,
- PlacesUtils.EXCLUDE_FROM_BACKUP_ANNO, 1, 0,
- PlacesUtils.annotations.EXPIRE_NEVER, SOURCE_SYNC);
}
-
- // Make sure the mobile root title matches the query.
- await PlacesUtils.bookmarks.update({
- guid: PlacesUtils.bookmarks.mobileGuid,
- title: mobileTitle,
- source: SOURCE_SYNC,
- });
- },
-
- async removeMobileQuery(db) {
- let maybeMobileQueryGuids = await fetchGuidsWithAnno(db,
- ORGANIZER_QUERY_ANNO, ORGANIZER_MOBILE_QUERY_ANNO_VALUE);
- if (!maybeMobileQueryGuids.length) {
- BookmarkSyncLog.warn("Trying to remove non-existent mobile query");
- return;
- }
- let mobileQueryGuid = maybeMobileQueryGuids[0];
- await PlacesUtils.bookmarks.remove(mobileQueryGuid);
},
/**
* Fetches an array of GUIDs for items that have an annotation set with the
* given value.
*/
async fetchGuidsWithAnno(anno, val) {
let db = await PlacesUtils.promiseDBConnection();
--- a/toolkit/components/places/PlacesUtils.jsm
+++ b/toolkit/components/places/PlacesUtils.jsm
@@ -108,17 +108,21 @@ function serializeNode(aNode, aIsLivemar
data.title = aNode.title;
data.id = aNode.itemId;
data.livemark = aIsLivemark;
// Add an instanceId so we can tell which instance of an FF session the data
// is coming from.
data.instanceId = PlacesUtils.instanceId;
let guid = aNode.bookmarkGuid;
- if (guid) {
+ // Some nodes, e.g. the unfiled/menu/toolbar ones can have a virtual guid, so
+ // we ignore any that are a folder shortcut. These will be handled below.
+ if (guid && !PlacesUtils.bookmarks.isVirtualRootItem(guid)) {
+ // TODO: Really guid should be set on everything, however currently this upsets
+ // the drag 'n' drop / cut/copy/paste operations.
data.itemGuid = guid;
if (aNode.parent)
data.parent = aNode.parent.itemId;
let grandParent = aNode.parent && aNode.parent.parent;
if (grandParent)
data.grandParentId = grandParent.itemId;
data.dateAdded = aNode.dateAdded;
@@ -150,16 +154,17 @@ function serializeNode(aNode, aIsLivemar
let concreteId = PlacesUtils.getConcreteItemId(aNode);
if (concreteId != -1) {
// This is a bookmark or a tag container.
if (PlacesUtils.nodeIsQuery(aNode) || concreteId != aNode.itemId) {
// This is a folder shortcut.
data.type = PlacesUtils.TYPE_X_MOZ_PLACE;
data.uri = aNode.uri;
data.concreteId = concreteId;
+ data.concreteGuid = PlacesUtils.getConcreteItemGuid(aNode);
} else {
// This is a bookmark folder.
data.type = PlacesUtils.TYPE_X_MOZ_PLACE_CONTAINER;
}
} else {
// This is a grouped container query, dynamically generated.
data.type = PlacesUtils.TYPE_X_MOZ_PLACE;
data.uri = aNode.uri;
@@ -428,16 +433,28 @@ this.PlacesUtils = {
return str.split(",")
.map(v => {
let bucket = v.split(":");
return [ bucket[0].trim().toLowerCase(), Number(bucket[1]) ];
});
},
/**
+ * Determines if a folder is generated from a query.
+ * @param aNode a result true.
+ * @returns true if the node is a folder generated from a query.
+ */
+ isQueryGeneratedFolder(node) {
+ if (!node.parent) {
+ return false;
+ }
+ return this.nodeIsFolder(node) && this.nodeIsQuery(node.parent);
+ },
+
+ /**
* Determines whether or not a ResultNode is a Bookmark folder.
* @param aNode
* A result node
* @returns true if the node is a Bookmark folder, false otherwise
*/
nodeIsFolder: function PU_nodeIsFolder(aNode) {
return (aNode.type == Ci.nsINavHistoryResultNode.RESULT_TYPE_FOLDER ||
aNode.type == Ci.nsINavHistoryResultNode.RESULT_TYPE_FOLDER_SHORTCUT);
--- a/toolkit/components/places/nsINavHistoryService.idl
+++ b/toolkit/components/places/nsINavHistoryService.idl
@@ -1069,16 +1069,24 @@ interface nsINavHistoryQueryOptions : ns
* modified bookmarks for the given tag.
* Tag folder id must be defined in the query.
*
* @note Setting this resultType will force queryType to QUERY_TYPE_BOOKMARKS.
*/
const unsigned short RESULTS_AS_TAG_CONTENTS = 7;
/**
+ * This returns nsINavHistoryQueryResultNode nodes for each top-level bookmark
+ * root.
+ *
+ * @note Setting this resultType will force queryType to QUERY_TYPE_BOOKMARKS.
+ */
+ const unsigned short RESULTS_AS_ROOTS_QUERY = 8;
+
+ /**
* The sorting mode to be used for this query.
* mode is one of SORT_BY_*
*/
attribute unsigned short sortingMode;
/**
* The annotation to use in SORT_BY_ANNOTATION_* sorting modes.
*/
--- a/toolkit/components/places/nsNavHistory.cpp
+++ b/toolkit/components/places/nsNavHistory.cpp
@@ -100,16 +100,20 @@ using namespace mozilla::places;
#define PREF_FREC_DEFAULT_VISIT_BONUS_DEF 0
#define PREF_FREC_UNVISITED_BOOKMARK_BONUS "places.frecency.unvisitedBookmarkBonus"
#define PREF_FREC_UNVISITED_BOOKMARK_BONUS_DEF 140
#define PREF_FREC_UNVISITED_TYPED_BONUS "places.frecency.unvisitedTypedBonus"
#define PREF_FREC_UNVISITED_TYPED_BONUS_DEF 200
#define PREF_FREC_RELOAD_VISIT_BONUS "places.frecency.reloadVisitBonus"
#define PREF_FREC_RELOAD_VISIT_BONUS_DEF 0
+// This is a hidden pref to determine when to show the mobile bookmarks folder.
+// Note: the name here matches those used elsewhere in the code, for easier searching.
+#define MOBILE_BOOKMARKS_PREF "browser.bookmarks.showMobileBookmarks"
+
// This is a 'hidden' pref for the purposes of unit tests.
#define PREF_FREC_DECAY_RATE "places.frecency.decayRate"
#define PREF_FREC_DECAY_RATE_DEF 0.975f
// In order to avoid calling PR_now() too often we use a cached "now" value
// for repeating stuff. These are milliseconds between "now" cache refreshes.
#define RENEW_CACHED_NOW_TIMEOUT ((int32_t)3 * PR_MSEC_PER_SEC)
@@ -827,17 +831,19 @@ nsNavHistory::GetUpdateRequirements(cons
query->Uri() != nullptr)
nonTimeBasedItems = true;
if (!query->Domain().IsVoid())
domainBasedItems = true;
}
if (aOptions->ResultType() ==
- nsINavHistoryQueryOptions::RESULTS_AS_TAG_QUERY)
+ nsINavHistoryQueryOptions::RESULTS_AS_TAG_QUERY ||
+ aOptions->ResultType() ==
+ nsINavHistoryQueryOptions::RESULTS_AS_ROOTS_QUERY)
return QUERYUPDATE_COMPLEX_WITH_BOOKMARKS;
// Whenever there is a maximum number of results,
// and we are not a bookmark query we must requery. This
// is because we can't generally know if any given addition/change causes
// the item to be in the top N items in the database.
if (aOptions->MaxResults() > 0)
return QUERYUPDATE_COMPLEX;
@@ -1410,16 +1416,17 @@ public:
private:
nsresult Select();
nsresult SelectAsURI();
nsresult SelectAsVisit();
nsresult SelectAsDay();
nsresult SelectAsSite();
nsresult SelectAsTag();
+ nsresult SelectAsRoots();
nsresult Where();
nsresult GroupBy();
nsresult OrderBy();
nsresult Limit();
void OrderByColumnIndexAsc(int32_t aIndex);
void OrderByColumnIndexDesc(int32_t aIndex);
@@ -1511,16 +1518,21 @@ PlacesSQLQueryBuilder::Select()
NS_ENSURE_SUCCESS(rv, rv);
break;
case nsINavHistoryQueryOptions::RESULTS_AS_TAG_QUERY:
rv = SelectAsTag();
NS_ENSURE_SUCCESS(rv, rv);
break;
+ case nsINavHistoryQueryOptions::RESULTS_AS_ROOTS_QUERY:
+ rv = SelectAsRoots();
+ NS_ENSURE_SUCCESS(rv, rv);
+ break;
+
default:
NS_NOTREACHED("Invalid result type");
}
return NS_OK;
}
nsresult
PlacesSQLQueryBuilder::SelectAsURI()
@@ -1920,16 +1932,59 @@ PlacesSQLQueryBuilder::SelectAsTag()
nsINavHistoryQueryOptions::RESULTS_AS_TAG_CONTENTS,
history->GetTagsFolder()
);
return NS_OK;
}
nsresult
+PlacesSQLQueryBuilder::SelectAsRoots()
+{
+ nsNavHistory *history = nsNavHistory::GetHistoryService();
+ NS_ENSURE_STATE(history);
+
+ nsAutoCString toolbarTitle;
+ nsAutoCString menuTitle;
+ nsAutoCString unfiledTitle;
+
+ history->GetStringFromName("BookmarksToolbarFolderTitle", toolbarTitle);
+ history->GetStringFromName("BookmarksMenuFolderTitle", menuTitle);
+ history->GetStringFromName("OtherBookmarksFolderTitle", unfiledTitle);
+
+ nsAutoCString mobileString;
+
+ if (Preferences::GetBool(MOBILE_BOOKMARKS_PREF, false)) {
+ nsAutoCString mobileTitle;
+ history->GetStringFromName("MobileBookmarksFolderTitle", mobileTitle);
+
+ mobileString = nsPrintfCString(","
+ "(null, 'place:folder=MOBILE_BOOKMARKS', '%s', null, null, null, "
+ "null, null, 0, 0, null, null, null, null, 'mobile____v', null) ",
+ mobileTitle.get());
+ }
+
+ mQueryString = nsPrintfCString(
+ "SELECT * FROM ("
+ "VALUES(null, 'place:folder=TOOLBAR', '%s', null, null, null, "
+ "null, null, 0, 0, null, null, null, null, 'toolbar____v', null), "
+ "(null, 'place:folder=BOOKMARKS_MENU', '%s', null, null, null, "
+ "null, null, 0, 0, null, null, null, null, 'menu_______v', null), "
+ "(null, 'place:folder=UNFILED_BOOKMARKS', '%s', null, null, null, "
+ "null, null, 0, 0, null, null, null, null, 'unfiled___v', null) "
+ " %s "
+ ")",
+ toolbarTitle.get(),
+ menuTitle.get(),
+ unfiledTitle.get(),
+ mobileString.get());
+ return NS_OK;
+}
+
+nsresult
PlacesSQLQueryBuilder::Where()
{
// Set query options
nsAutoCString additionalVisitsConditions;
nsAutoCString additionalPlacesConditions;
if (!mIncludeHidden) {
@@ -3565,17 +3620,17 @@ nsNavHistory::FilterResultSet(nsNavHisto
continue;
}
// RESULTS_AS_TAG_CONTENTS returns a set ordered by place_id and
// lastModified. The set may contain duplicate, and to remove them we can
// just retain the first result.
if (resultType == nsINavHistoryQueryOptions::RESULTS_AS_TAG_CONTENTS &&
(!aSet[nodeIndex]->IsURI() ||
- nodeIndex > 0 && aSet[nodeIndex]->mURI == aSet[nodeIndex-1]->mURI)) {
+ (nodeIndex > 0 && aSet[nodeIndex]->mURI == aSet[nodeIndex-1]->mURI))) {
continue;
}
if (aSet[nodeIndex]->mItemId != -1 && aQueryNode &&
aQueryNode->mItemId == aSet[nodeIndex]->mItemId) {
continue;
}
@@ -3786,16 +3841,21 @@ nsNavHistory::RowToResult(mozIStorageVal
}
nsAutoCString guid;
if (itemId != -1) {
rv = aRow->GetUTF8String(nsNavBookmarks::kGetChildrenIndex_Guid, guid);
NS_ENSURE_SUCCESS(rv, rv);
}
+ if (aOptions->ResultType() == nsNavHistoryQueryOptions::RESULTS_AS_ROOTS_QUERY) {
+ rv = aRow->GetUTF8String(kGetInfoIndex_Guid, guid);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
RefPtr<nsNavHistoryResultNode> resultNode;
rv = QueryRowToResult(itemId, guid, url, title, accessCount, time,
getter_AddRefs(resultNode));
NS_ENSURE_SUCCESS(rv, rv);
if (itemId != -1 ||
aOptions->ResultType() == nsNavHistoryQueryOptions::RESULTS_AS_TAG_QUERY) {
// RESULTS_AS_TAG_QUERY has date columns
@@ -3886,18 +3946,21 @@ nsresult
nsNavHistory::QueryRowToResult(int64_t itemId,
const nsACString& aBookmarkGuid,
const nsACString& aURI,
const nsACString& aTitle,
uint32_t aAccessCount,
PRTime aTime,
nsNavHistoryResultNode** aNode)
{
- MOZ_ASSERT((itemId != -1 && !aBookmarkGuid.IsEmpty()) ||
- (itemId == -1 && aBookmarkGuid.IsEmpty()));
+ // Only assert if the itemId is set. In some cases (e.g. virtual queries), we
+ // have a guid, but not an itemId.
+ if (itemId != -1) {
+ MOZ_ASSERT(!aBookmarkGuid.IsEmpty());
+ }
nsCOMArray<nsNavHistoryQuery> queries;
nsCOMPtr<nsNavHistoryQueryOptions> options;
nsresult rv = QueryStringToQueryArray(aURI, &queries,
getter_AddRefs(options));
RefPtr<nsNavHistoryResultNode> resultNode;
// If this failed the query does not parse correctly, let the error pass and
--- a/toolkit/components/places/nsNavHistoryQuery.cpp
+++ b/toolkit/components/places/nsNavHistoryQuery.cpp
@@ -1354,21 +1354,22 @@ NS_IMETHODIMP
nsNavHistoryQueryOptions::GetResultType(uint16_t* aType)
{
*aType = mResultType;
return NS_OK;
}
NS_IMETHODIMP
nsNavHistoryQueryOptions::SetResultType(uint16_t aType)
{
- if (aType > RESULTS_AS_TAG_CONTENTS)
+ if (aType > RESULTS_AS_ROOTS_QUERY)
return NS_ERROR_INVALID_ARG;
- // Tag queries and containers are bookmarks related, so we set the QueryType
- // accordingly.
- if (aType == RESULTS_AS_TAG_QUERY || aType == RESULTS_AS_TAG_CONTENTS)
+ // Tag queries, containers and the roots query are bookmarks related, so we
+ // set the QueryType accordingly.
+ if (aType == RESULTS_AS_TAG_QUERY || aType == RESULTS_AS_TAG_CONTENTS ||
+ aType == RESULTS_AS_ROOTS_QUERY)
mQueryType = QUERY_TYPE_BOOKMARKS;
mResultType = aType;
return NS_OK;
}
// excludeItems
NS_IMETHODIMP
nsNavHistoryQueryOptions::GetExcludeItems(bool* aExclude)
--- a/toolkit/components/places/nsNavHistoryResult.cpp
+++ b/toolkit/components/places/nsNavHistoryResult.cpp
@@ -1823,17 +1823,18 @@ nsNavHistoryQueryResultNode::CanExpand()
*/
bool
nsNavHistoryQueryResultNode::IsContainersQuery()
{
uint16_t resultType = Options()->ResultType();
return resultType == nsINavHistoryQueryOptions::RESULTS_AS_DATE_QUERY ||
resultType == nsINavHistoryQueryOptions::RESULTS_AS_DATE_SITE_QUERY ||
resultType == nsINavHistoryQueryOptions::RESULTS_AS_TAG_QUERY ||
- resultType == nsINavHistoryQueryOptions::RESULTS_AS_SITE_QUERY;
+ resultType == nsINavHistoryQueryOptions::RESULTS_AS_SITE_QUERY ||
+ resultType == nsINavHistoryQueryOptions::RESULTS_AS_ROOTS_QUERY;
}
/**
* Here we do not want to call ContainerResultNode::OnRemoving since our own
* ClearChildren will do the same thing and more (unregister the observers).
* The base ResultNode::OnRemoving will clear some regular node stats, so it
* is OK.
@@ -1894,17 +1895,19 @@ nsNavHistoryQueryResultNode::GetHasChild
if (!CanExpand()) {
return NS_OK;
}
uint16_t resultType = mOptions->ResultType();
// Tags are always populated, otherwise they are removed.
- if (resultType == nsINavHistoryQueryOptions::RESULTS_AS_TAG_CONTENTS) {
+ if (resultType == nsINavHistoryQueryOptions::RESULTS_AS_TAG_CONTENTS ||
+ // AllBookmarks also always has children.
+ resultType == nsINavHistoryQueryOptions::RESULTS_AS_ROOTS_QUERY) {
*aHasChildren = true;
return NS_OK;
}
// For tag containers query we must check if we have any tag
if (resultType == nsINavHistoryQueryOptions::RESULTS_AS_TAG_QUERY) {
nsCOMPtr<nsITaggingService> tagging =
do_GetService(NS_TAGGINGSERVICE_CONTRACTID);
new file mode 100644
--- /dev/null
+++ b/toolkit/components/places/tests/queries/test_results-as-roots.js
@@ -0,0 +1,102 @@
+"use strict";
+
+const MOBILE_BOOKMARKS_PREF = "browser.bookmarks.showMobileBookmarks";
+
+const expectedRoots = [{
+ title: "BookmarksToolbarFolderTitle",
+ uri: "place:folder=TOOLBAR",
+ guid: PlacesUtils.bookmarks.virtualToolbarGuid,
+}, {
+ title: "BookmarksMenuFolderTitle",
+ uri: "place:folder=BOOKMARKS_MENU",
+ guid: PlacesUtils.bookmarks.virtualMenuGuid,
+}, {
+ title: "OtherBookmarksFolderTitle",
+ uri: "place:folder=UNFILED_BOOKMARKS",
+ guid: PlacesUtils.bookmarks.virtualUnfiledGuid,
+}];
+
+const expectedRootsWithMobile = [...expectedRoots, {
+ title: "MobileBookmarksFolderTitle",
+ uri: "place:folder=MOBILE_BOOKMARKS",
+ guid: PlacesUtils.bookmarks.virtualMobileGuid,
+}];
+
+const placesStrings = Services.strings.createBundle("chrome://places/locale/places.properties");
+
+function getAllBookmarksQuery() {
+ var query = PlacesUtils.history.getNewQuery();
+
+ // Options
+ var options = PlacesUtils.history.getNewQueryOptions();
+ options.sortingMode = options.SORT_BY_VISITCOUNT_ASCENDING;
+ options.resultType = options.RESULTS_AS_ROOTS_QUERY;
+
+ // Results
+ var result = PlacesUtils.history.executeQuery(query, options);
+ return result.root;
+}
+
+function assertExpectedChildren(root, expectedChildren) {
+ Assert.equal(root.childCount, expectedChildren.length, "Should have the expected number of children.");
+
+ for (let i = 0; i < root.childCount; i++) {
+ Assert.equal(root.getChild(i).uri, expectedChildren[i].uri,
+ "Should have the correct uri for root ${i}");
+ Assert.equal(root.getChild(i).title, placesStrings.GetStringFromName(expectedChildren[i].title),
+ "Should have the correct title for root ${i}");
+ Assert.equal(root.getChild(i).bookmarkGuid, expectedChildren[i].guid);
+ }
+}
+
+/**
+ * This test will test the basic RESULTS_AS_ROOTS_QUERY, that simply returns,
+ * the existing bookmark roots.
+ */
+add_task(async function test_results_as_root() {
+ let root = getAllBookmarksQuery();
+ root.containerOpen = true;
+
+ Assert.equal(PlacesUtils.asQuery(root).queryOptions.queryType,
+ Ci.nsINavHistoryQueryOptions.QUERY_TYPE_BOOKMARKS,
+ "Should have a query type of QUERY_TYPE_BOOKMARKS");
+
+ assertExpectedChildren(root, expectedRoots);
+
+ root.containerOpen = false;
+});
+
+add_task(async function test_results_as_root_with_mobile() {
+ Services.prefs.setBoolPref(MOBILE_BOOKMARKS_PREF, true);
+
+ let root = getAllBookmarksQuery();
+ root.containerOpen = true;
+
+ assertExpectedChildren(root, expectedRootsWithMobile);
+
+ root.containerOpen = false;
+ Services.prefs.clearUserPref(MOBILE_BOOKMARKS_PREF);
+});
+
+add_task(async function test_results_as_root_remove_mobile_dynamic() {
+ Services.prefs.setBoolPref(MOBILE_BOOKMARKS_PREF, true);
+
+ let root = getAllBookmarksQuery();
+ root.containerOpen = true;
+
+ // Now un-set the pref, and poke the database to update the query.
+ Services.prefs.clearUserPref(MOBILE_BOOKMARKS_PREF);
+
+ // We've un-set the pref, but we still expect the mobile folder to be there
+ // until the database is poked.
+ assertExpectedChildren(root, expectedRootsWithMobile);
+
+ await PlacesUtils.bookmarks.insert({
+ parentGuid: PlacesUtils.bookmarks.menuGuid,
+ url: "http://example.com",
+ });
+
+ assertExpectedChildren(root, expectedRoots);
+
+ root.containerOpen = false;
+});
--- a/toolkit/components/places/tests/queries/xpcshell.ini
+++ b/toolkit/components/places/tests/queries/xpcshell.ini
@@ -12,16 +12,17 @@ skip-if = (os == 'win' && ccov) # Bug 14
[test_excludeQueries.js]
[test_history_queries_tags_liveUpdate.js]
[test_history_queries_titles_liveUpdate.js]
[test_onlyBookmarked.js]
[test_options_inherit.js]
[test_queryMultipleFolder.js]
[test_querySerialization.js]
[test_redirects.js]
+[test_results-as-roots.js]
[test_results-as-tag-contents-query.js]
[test_results-as-visit.js]
[test_search_tags.js]
[test_searchterms-domain.js]
[test_searchterms-uri.js]
[test_searchterms-bookmarklets.js]
[test_sort-date-site-grouping.js]
[test_sorting.js]
--- a/toolkit/components/places/tests/unit/test_sync_utils.js
+++ b/toolkit/components/places/tests/unit/test_sync_utils.js
@@ -2883,100 +2883,46 @@ add_task(async function test_migrateOldT
dateRemoved: new Date(1479162463976),
}], "Should write tombstone for nonexistent migrated item");
await PlacesUtils.bookmarks.eraseEverything();
await PlacesSyncUtils.bookmarks.reset();
});
add_task(async function test_ensureMobileQuery() {
- info("Ensure we correctly create the mobile query");
-
- let PlacesUIUtils;
- try {
- PlacesUIUtils = ChromeUtils.import("resource:///modules/PlacesUIUtils.jsm", {}).PlacesUIUtils;
- PlacesUIUtils.maybeRebuildLeftPane();
- } catch (ex) {
- info("Can't build left pane roots; skipping test");
- return;
- }
+ info("Ensure we correctly set the showMobileBookmarks preference");
+ const mobilePref = "browser.bookmarks.showMobileBookmarks";
+ Services.prefs.clearUserPref(mobilePref);
await PlacesUtils.bookmarks.insert({
guid: "bookmarkAAAA",
parentGuid: PlacesUtils.bookmarks.mobileGuid,
url: "http://example.com/a",
title: "A",
});
await PlacesUtils.bookmarks.insert({
guid: "bookmarkBBBB",
parentGuid: PlacesUtils.bookmarks.mobileGuid,
url: "http://example.com/b",
title: "B",
});
- // Creates the organizer queries as a side effect.
- let leftPaneId = PlacesUIUtils.leftPaneFolderId;
- info(`Left pane root ID: ${leftPaneId}`);
-
- let allBookmarksGuids = await PlacesSyncUtils.bookmarks.fetchGuidsWithAnno(
- "PlacesOrganizer/OrganizerQuery", "AllBookmarks");
- equal(allBookmarksGuids.length, 1, "Should create folder with all bookmarks queries");
- let allBookmarkGuid = allBookmarksGuids[0];
-
- info("Try creating query after organizer is ready");
await PlacesSyncUtils.bookmarks.ensureMobileQuery();
- let queryGuids = await PlacesSyncUtils.bookmarks.fetchGuidsWithAnno(
- "PlacesOrganizer/OrganizerQuery", "MobileBookmarks");
- equal(queryGuids.length, 1, "Should create query because we have bookmarks A and B");
-
- let queryGuid = queryGuids[0];
-
- let queryInfo = await PlacesUtils.bookmarks.fetch(queryGuid);
- equal(queryInfo.url, `place:folder=MOBILE_BOOKMARKS`, "Query should point to mobile root");
- equal(queryInfo.title, "Mobile Bookmarks", "Query title should be localized");
- equal(queryInfo.parentGuid, allBookmarkGuid, "Should append mobile query to all bookmarks queries");
- info("Rename root and query, then recreate");
- await PlacesUtils.bookmarks.update({
- guid: PlacesUtils.bookmarks.mobileGuid,
- title: "renamed root",
- });
- await PlacesUtils.bookmarks.update({
- guid: queryGuid,
- title: "renamed query",
- });
- await PlacesSyncUtils.bookmarks.ensureMobileQuery();
- let rootInfo = await PlacesUtils.bookmarks.fetch(PlacesUtils.bookmarks.mobileGuid);
- equal(rootInfo.title, "Mobile Bookmarks", "Should fix root title");
- queryInfo = await PlacesUtils.bookmarks.fetch(queryGuid);
- equal(queryInfo.title, "Mobile Bookmarks", "Should fix query title");
-
- info("Point query to different folder");
- await PlacesUtils.bookmarks.update({
- guid: queryGuid,
- url: "place:folder=BOOKMARKS_MENU",
- });
- await PlacesSyncUtils.bookmarks.ensureMobileQuery();
- queryInfo = await PlacesUtils.bookmarks.fetch(queryGuid);
- equal(queryInfo.url.href, `place:folder=MOBILE_BOOKMARKS`,
- "Should fix query URL to point to mobile root");
-
- info("We shouldn't track the query or the left pane root");
-
- let changes = await PlacesSyncUtils.bookmarks.pullChanges();
- ok(!(queryGuid in changes), "Should not track mobile query");
+ Assert.ok(Services.prefs.getBoolPref(mobilePref),
+ "Pref should be true where there are bookmarks in the folder.");
await PlacesUtils.bookmarks.remove("bookmarkAAAA");
await PlacesUtils.bookmarks.remove("bookmarkBBBB");
+
await PlacesSyncUtils.bookmarks.ensureMobileQuery();
- queryGuids = await PlacesSyncUtils.bookmarks.fetchGuidsWithAnno(
- "PlacesOrganizer/OrganizerQuery", "MobileBookmarks");
- equal(queryGuids.length, 0, "Should delete query since there are no bookmarks");
+ Assert.ok(!Services.prefs.getBoolPref(mobilePref),
+ "Pref should be false where there are no bookmarks in the folder.");
await PlacesUtils.bookmarks.eraseEverything();
await PlacesSyncUtils.bookmarks.reset();
});
add_task(async function test_remove_stale_tombstones() {
info("Insert and delete synced bookmark");
{