Bug 1423896 - Make the All Bookmarks folder for the left pane of the Library a virtual query. r=mak

MozReview-Commit-ID: HzJ9y1fiEz1

--HG--
extra : rebase_source : 27b0280e9dfef2626d187f9b8a81f23e4583302f
This commit is contained in:
Mark Banner 2017-12-07 14:15:39 +00:00
Родитель e23bde49fe
Коммит 362505f227
30 изменённых файлов: 436 добавлений и 429 удалений

Просмотреть файл

@ -584,20 +584,20 @@ var PlacesCommandHook = {
/**
* 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();
}
},

Просмотреть файл

@ -205,7 +205,7 @@ let InternalFaviconLoader = {
};
this.PlacesUIUtils = {
ORGANIZER_LEFTPANE_VERSION: 7,
ORGANIZER_LEFTPANE_VERSION: 8,
ORGANIZER_FOLDER_ANNO: "PlacesOrganizer/OrganizerFolder",
ORGANIZER_QUERY_ANNO: "PlacesOrganizer/OrganizerQuery",
@ -513,6 +513,16 @@ this.PlacesUIUtils = {
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) {
@ -563,7 +573,7 @@ this.PlacesUIUtils = {
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
@ -577,8 +587,7 @@ this.PlacesUIUtils = {
if (typeof Object.getOwnPropertyDescriptor(this, "leftPaneFolderId").get == "function") {
return false;
}
return itemId == this.leftPaneFolderId ||
itemId == this.allBookmarksFolderId;
return itemId == this.leftPaneFolderId;
},
/**
@ -839,7 +848,6 @@ this.PlacesUIUtils = {
// Get the folder id for the organizer left-pane folder.
maybeRebuildLeftPane() {
let leftPaneRoot = -1;
let allBookmarksId;
// Shortcuts to services.
let bs = PlacesUtils.bookmarks;
@ -852,21 +860,9 @@ this.PlacesUIUtils = {
"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) {
@ -1053,19 +1049,9 @@ this.PlacesUIUtils = {
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);
@ -1073,16 +1059,6 @@ this.PlacesUIUtils = {
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.

Просмотреть файл

@ -5,7 +5,7 @@
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) {

Просмотреть файл

@ -1377,7 +1377,8 @@ var PlacesControllerDragHelper = {
* @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;
@ -1392,8 +1393,7 @@ var PlacesControllerDragHelper = {
// 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;

Просмотреть файл

@ -60,8 +60,7 @@ var gEditItemOverlay = {
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;
@ -704,13 +703,13 @@ var gEditItemOverlay = {
// 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();
}
},
@ -929,7 +928,7 @@ var gEditItemOverlay = {
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

Просмотреть файл

@ -41,28 +41,63 @@ var PlacesOrganizer = {
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)
@ -74,12 +109,16 @@ var PlacesOrganizer = {
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);

Просмотреть файл

@ -76,7 +76,8 @@
// 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();
@ -621,8 +622,12 @@
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;

Просмотреть файл

@ -137,6 +137,7 @@ PlacesTreeView.prototype = {
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;
}
@ -191,12 +192,16 @@ PlacesTreeView.prototype = {
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);
}
@ -1276,21 +1281,35 @@ PlacesTreeView.prototype = {
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;
@ -1764,12 +1783,12 @@ PlacesTreeView.prototype = {
// 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

Просмотреть файл

@ -29,10 +29,6 @@ add_task(async function() {
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");
}
);
});

Просмотреть файл

@ -85,15 +85,16 @@ add_task(async function() {
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",
@ -107,14 +108,3 @@ add_task(async function() {
}
});
});
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");
}

Просмотреть файл

@ -88,7 +88,7 @@ add_task(async function test_query_on_toolbar() {
"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"),

Просмотреть файл

@ -60,25 +60,10 @@ function test() {
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();

Просмотреть файл

@ -20,16 +20,14 @@ add_task(async function() {
}],
});
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,

Просмотреть файл

@ -32,7 +32,7 @@ var gLibrary;
var testCases = [
function allBookmarksScope() {
let defScope = getDefaultScope(PlacesUIUtils.allBookmarksFolderId);
let defScope = getDefaultScope(PlacesUIUtils.leftPaneQueries.AllBookmarks);
search(PlacesUIUtils.allBookmarksFolderId, "dummy", defScope);
},

Просмотреть файл

@ -6,8 +6,7 @@ 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;

Просмотреть файл

@ -46,8 +46,7 @@
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;
@ -78,20 +77,34 @@
// 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;

Просмотреть файл

@ -10,7 +10,6 @@
// 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;
@ -25,8 +24,6 @@ add_task(async function() {
// 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());
});
@ -52,12 +49,11 @@ add_task(async function() {
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);
@ -89,13 +85,7 @@ var gTests = [
},
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",
@ -109,8 +99,8 @@ var gTests = [
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",
@ -124,25 +114,11 @@ var gTests = [
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);
}
];
/**

Просмотреть файл

@ -19,7 +19,7 @@
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();

Просмотреть файл

@ -37,23 +37,19 @@ treechildren::-moz-tree-image(title, separator) {
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");
}
@ -62,6 +58,10 @@ 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");
}

Просмотреть файл

@ -280,97 +280,6 @@ add_task(async function test_responder_missing_items() {
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();

Просмотреть файл

@ -135,22 +135,42 @@ var Bookmarks = Object.freeze({
* 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________",
// 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______"],
/**
* The GUIDs of the user content root folders that we support, for easy access
* as a set.
*/
userContentRoots: ["toolbar_____", "menu________", "unfiled_____", "mobile______"],
/**
* 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",
/**
* 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.

Просмотреть файл

@ -49,9 +49,6 @@ 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
@ -1061,70 +1058,15 @@ const BookmarkSyncUtils = PlacesSyncUtils.bookmarks = Object.freeze({
Services.prefs.setBoolPref(MOBILE_BOOKMARKS_PREF, hasMobileBookmarks);
if (hasMobileBookmarks) {
await this.upsertMobileQuery(db);
} else {
await this.removeMobileQuery(db);
}
},
let mobileTitle = PlacesUtils.getString("MobileBookmarksFolderTitle");
async upsertMobileQuery(db) {
let maybeAllBookmarksGuids = await fetchGuidsWithAnno(db,
ORGANIZER_QUERY_ANNO, ORGANIZER_ALL_BOOKMARKS_ANNO_VALUE);
if (!maybeAllBookmarksGuids.length) {
return;
}
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);
},
/**

Просмотреть файл

@ -113,7 +113,11 @@ function serializeNode(aNode, aIsLivemark) {
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;
@ -155,6 +159,7 @@ function serializeNode(aNode, aIsLivemark) {
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;
@ -432,6 +437,18 @@ this.PlacesUtils = {
});
},
/**
* 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

Просмотреть файл

@ -1073,6 +1073,14 @@ interface nsINavHistoryQueryOptions : nsISupports
*/
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_*

Просмотреть файл

@ -105,6 +105,10 @@ using namespace mozilla::places;
#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
@ -832,7 +836,9 @@ nsNavHistory::GetUpdateRequirements(const nsCOMArray<nsNavHistoryQuery>& aQuerie
}
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,
@ -1415,6 +1421,7 @@ private:
nsresult SelectAsDay();
nsresult SelectAsSite();
nsresult SelectAsTag();
nsresult SelectAsRoots();
nsresult Where();
nsresult GroupBy();
@ -1516,6 +1523,11 @@ PlacesSQLQueryBuilder::Select()
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");
}
@ -1924,6 +1936,49 @@ PlacesSQLQueryBuilder::SelectAsTag()
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()
{
@ -3570,7 +3625,7 @@ nsNavHistory::FilterResultSet(nsNavHistoryQueryResultNode* aQueryNode,
// 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;
}
@ -3791,6 +3846,11 @@ nsNavHistory::RowToResult(mozIStorageValueArray* aRow,
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));
@ -3891,8 +3951,11 @@ nsNavHistory::QueryRowToResult(int64_t itemId,
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;

Просмотреть файл

@ -1359,11 +1359,12 @@ nsNavHistoryQueryOptions::GetResultType(uint16_t* aType)
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;

Просмотреть файл

@ -1828,7 +1828,8 @@ nsNavHistoryQueryResultNode::IsContainersQuery()
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;
}
@ -1899,7 +1900,9 @@ nsNavHistoryQueryResultNode::GetHasChildren(bool* aHasChildren)
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;
}

Просмотреть файл

@ -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;
});

Просмотреть файл

@ -17,6 +17,7 @@ skip-if = (os == 'win' && ccov) # Bug 1423667
[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]

Просмотреть файл

@ -2888,16 +2888,9 @@ add_task(async function test_migrateOldTrackerEntries() {
});
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",
@ -2913,65 +2906,18 @@ add_task(async function test_ensureMobileQuery() {
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();