Bug 1310295 - Make left pane queries virtual in the Places Library window. r=kitcambridge,mak

MozReview-Commit-ID: DcEMAlrXu8R

--HG--
extra : rebase_source : c98e6a50702ef0c238d036fa2a26f4a6ac9f15af
This commit is contained in:
Mark Banner 2018-02-16 20:30:04 +00:00
parent 4de3433bd6
commit 2155e56144
29 changed files with 273 additions and 742 deletions

View File

@ -1398,18 +1398,7 @@ var BookmarkingUI = {
MOBILE_BOOKMARKS_PREF: "browser.bookmarks.showMobileBookmarks",
_shouldShowMobileBookmarks() {
try {
return Services.prefs.getBoolPref(this.MOBILE_BOOKMARKS_PREF);
} catch (e) {}
// No pref set (or invalid pref set), look for a mobile bookmarks left pane query.
const organizerQueryAnno = "PlacesOrganizer/OrganizerQuery";
const mobileBookmarksAnno = "MobileBookmarks";
let shouldShow = PlacesUtils.annotations.getItemsWithAnnotation(organizerQueryAnno, {}).filter(
id => PlacesUtils.annotations.getItemAnnotation(id, organizerQueryAnno) == mobileBookmarksAnno
).length > 0;
// Sync will change this pref if/when it adds a mobile bookmarks query.
Services.prefs.setBoolPref(this.MOBILE_BOOKMARKS_PREF, shouldShow);
return shouldShow;
return Services.prefs.getBoolPref(this.MOBILE_BOOKMARKS_PREF, false);
},
_initMobileBookmarks(mobileMenuItem) {

View File

@ -206,10 +206,6 @@ let InternalFaviconLoader = {
};
var PlacesUIUtils = {
ORGANIZER_LEFTPANE_VERSION: 8,
ORGANIZER_FOLDER_ANNO: "PlacesOrganizer/OrganizerFolder",
ORGANIZER_QUERY_ANNO: "PlacesOrganizer/OrganizerQuery",
LOAD_IN_SIDEBAR_ANNO: "bookmarkProperties/loadInSidebar",
DESCRIPTION_ANNO: "bookmarkProperties/description",
@ -531,11 +527,16 @@ var PlacesUIUtils = {
}
// 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)) {
if (PlacesUtils.nodeIsQuery(parentNode)) {
if (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;
}
} else if (PlacesUtils.isVirtualLeftPaneItem(aNode.bookmarkGuid)) {
// If the item is a left-pane top-level item, it can't be removed.
return false;
}
}
@ -590,21 +591,7 @@ var PlacesUIUtils = {
view.controller.hasCachedLivemarkInfo(placesNode))
return true;
// 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;
return false;
},
/** aItemsToOpen needs to be an array of objects of the form:
@ -814,261 +801,6 @@ var PlacesUIUtils = {
return title || this.getString("noTitle");
},
get leftPaneQueries() {
// build the map
this.leftPaneFolderId;
return this.leftPaneQueries;
},
get leftPaneFolderId() {
delete this.leftPaneFolderId;
return this.leftPaneFolderId = this.maybeRebuildLeftPane();
},
// Get the folder id for the organizer left-pane folder.
maybeRebuildLeftPane() {
let leftPaneRoot = -1;
// 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") },
};
// All queries but PlacesRoot.
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.
return;
}
// removeItemAnnotation does not check if item exists, nor the anno,
// so this is safe to do.
as.removeItemAnnotation(aItemId, PlacesUIUtils.ORGANIZER_FOLDER_ANNO);
as.removeItemAnnotation(aItemId, PlacesUIUtils.ORGANIZER_QUERY_ANNO);
// This will throw if the annotation is an orphan.
bs.removeItem(aItemId);
} catch (e) { /* orphan anno */ }
}
// Returns true if item really exists, false otherwise.
function itemExists(aItemId) {
try {
bs.getFolderIdForItem(aItemId);
return true;
} catch (e) {
return false;
}
}
// Get all items marked as being the left pane folder.
let items = as.getItemsWithAnnotation(this.ORGANIZER_FOLDER_ANNO);
if (items.length > 1) {
// Something went wrong, we cannot have more than one left pane folder,
// remove all left pane folders and continue. We will create a new one.
items.forEach(safeRemoveItem);
} else if (items.length == 1 && items[0] != -1) {
leftPaneRoot = items[0];
// Check that organizer left pane root is valid.
let version = as.getItemAnnotation(leftPaneRoot, this.ORGANIZER_FOLDER_ANNO);
if (version != this.ORGANIZER_LEFTPANE_VERSION ||
!itemExists(leftPaneRoot)) {
// Invalid root, we must rebuild the left pane.
safeRemoveItem(leftPaneRoot);
leftPaneRoot = -1;
}
}
if (leftPaneRoot != -1) {
// A valid left pane folder has been found.
// Build the leftPaneQueries Map. This is used to quickly access them,
// associating a mnemonic name to the real item ids.
delete this.leftPaneQueries;
this.leftPaneQueries = {};
let queryItems = as.getItemsWithAnnotation(this.ORGANIZER_QUERY_ANNO);
// While looping through queries we will also check for their validity.
let queriesCount = 0;
let corrupt = false;
for (let i = 0; i < queryItems.length; i++) {
let queryName = as.getItemAnnotation(queryItems[i], this.ORGANIZER_QUERY_ANNO);
// Some extension did use our annotation to decorate their items
// with icons, so we should check only our elements, to avoid dataloss.
if (!(queryName in queries))
continue;
let query = queries[queryName];
query.itemId = queryItems[i];
if (!itemExists(query.itemId)) {
// Orphan annotation, bail out and create a new left pane root.
corrupt = true;
break;
}
// Check that all queries have valid parents.
let parentId = bs.getFolderIdForItem(query.itemId);
if (!queryItems.includes(parentId) && parentId != leftPaneRoot) {
// The parent is not part of the left pane, bail out and create a new
// left pane root.
corrupt = true;
break;
}
// Titles could have been corrupted or the user could have changed his
// locale. Check title and eventually fix it.
if (bs.getItemTitle(query.itemId) != query.title)
bs.setItemTitle(query.itemId, query.title);
if ("concreteId" in query) {
if (bs.getItemTitle(query.concreteId) != query.concreteTitle)
bs.setItemTitle(query.concreteId, query.concreteTitle);
}
// Add the query to our cache.
this.leftPaneQueries[queryName] = query.itemId;
queriesCount++;
}
// Note: it's not enough to just check for queriesCount, since we may
// find an invalid query just after accounting for a sufficient number of
// valid ones. As well as we can't just rely on corrupt since we may find
// less valid queries than expected.
if (corrupt || queriesCount != EXPECTED_QUERY_COUNT) {
// Queries number is wrong, so the left pane must be corrupt.
// Note: we can't just remove the leftPaneRoot, because some query could
// have a bad parent, so we have to remove all items one by one.
queryItems.forEach(safeRemoveItem);
safeRemoveItem(leftPaneRoot);
} else {
// Everything is fine, return the current left pane folder.
return leftPaneRoot;
}
}
// Create a new left pane folder.
var callback = {
// Helper to create an organizer special query.
create_query: function CB_create_query(aQueryName, aParentId, aQueryUrl) {
let itemId = bs.insertBookmark(aParentId,
Services.io.newURI(aQueryUrl),
bs.DEFAULT_INDEX,
queries[aQueryName].title);
// Mark as special organizer query.
as.setItemAnnotation(itemId, PlacesUIUtils.ORGANIZER_QUERY_ANNO, aQueryName,
0, as.EXPIRE_NEVER);
// We should never backup this, since it changes between profiles.
as.setItemAnnotation(itemId, PlacesUtils.EXCLUDE_FROM_BACKUP_ANNO, 1,
0, as.EXPIRE_NEVER);
// Add to the queries map.
PlacesUIUtils.leftPaneQueries[aQueryName] = itemId;
return itemId;
},
// Helper to create an organizer special folder.
create_folder: function CB_create_folder(aFolderName, aParentId, aIsRoot) {
// Left Pane Root Folder.
let folderId = bs.createFolder(aParentId,
queries[aFolderName].title,
bs.DEFAULT_INDEX);
// We should never backup this, since it changes between profiles.
as.setItemAnnotation(folderId, PlacesUtils.EXCLUDE_FROM_BACKUP_ANNO, 1,
0, as.EXPIRE_NEVER);
if (aIsRoot) {
// Mark as special left pane root.
as.setItemAnnotation(folderId, PlacesUIUtils.ORGANIZER_FOLDER_ANNO,
PlacesUIUtils.ORGANIZER_LEFTPANE_VERSION,
0, as.EXPIRE_NEVER);
} else {
// Mark as special organizer folder.
as.setItemAnnotation(folderId, PlacesUIUtils.ORGANIZER_QUERY_ANNO, aFolderName,
0, as.EXPIRE_NEVER);
PlacesUIUtils.leftPaneQueries[aFolderName] = folderId;
}
return folderId;
},
runBatched: function CB_runBatched(aUserData) {
delete PlacesUIUtils.leftPaneQueries;
PlacesUIUtils.leftPaneQueries = { };
// Left Pane Root Folder.
leftPaneRoot = this.create_folder("PlacesRoot", bs.placesRoot, true);
// History Query.
this.create_query("History", leftPaneRoot,
"place:type=" +
Ci.nsINavHistoryQueryOptions.RESULTS_AS_DATE_QUERY +
"&sort=" +
Ci.nsINavHistoryQueryOptions.SORT_BY_DATE_DESCENDING);
// Downloads.
this.create_query("Downloads", leftPaneRoot,
"place:transition=" +
Ci.nsINavHistoryService.TRANSITION_DOWNLOAD +
"&sort=" +
Ci.nsINavHistoryQueryOptions.SORT_BY_DATE_DESCENDING);
// 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.
this.create_query("AllBookmarks", leftPaneRoot,
"place:type=" +
Ci.nsINavHistoryQueryOptions.RESULTS_AS_ROOTS_QUERY);
}
};
bs.runInBatchMode(callback, null);
return leftPaneRoot;
},
/**
* 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 = "";
// If the let pane hasn't been built, use the annotation service
// directly, to avoid building the left pane too early.
if (Object.getOwnPropertyDescriptor(this, "leftPaneFolderId").value === undefined) {
try {
queryName = PlacesUtils.annotations.
getItemAnnotation(aItemId, this.ORGANIZER_QUERY_ANNO);
} catch (ex) {
// doesn't have the annotation
queryName = "";
}
} else {
// If the left pane has already been built, use the name->id map
// cached in PlacesUIUtils.
for (let [name, id] of Object.entries(this.leftPaneQueries)) {
if (aItemId == id)
queryName = name;
}
}
return queryName;
},
shouldShowTabsFromOtherComputersMenuitem() {
let weaveOK = Weave.Status.checkSetup() != Weave.CLIENT_NOT_CONFIGURED &&
Weave.Svc.Prefs.get("firstSync", "") != "notReady";
@ -1354,14 +1086,7 @@ function canMoveUnwrappedNode(unwrappedNode) {
parentGuid == PlacesUtils.bookmarks.rootGuid) {
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" &&
(unwrappedNode.parent == PlacesUIUtils.leftPaneFolderId)) {
return false;
}
return true;
}

View File

@ -56,12 +56,6 @@ var gEditItemOverlay = {
}
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));
}
parentId = parent.itemId;
parentGuid = parent.bookmarkGuid;
}

View File

@ -36,8 +36,7 @@ var PlacesOrganizer = {
],
_initFolderTree() {
var leftPaneRoot = PlacesUIUtils.leftPaneFolderId;
this._places.place = "place:excludeItems=1&expandQueries=0&folder=" + leftPaneRoot;
this._places.place = `place:type=${Ci.nsINavHistoryQueryOptions.RESULTS_AS_LEFT_PANE_QUERY}&excludeItems=1&expandQueries=0`;
},
/**
@ -50,31 +49,34 @@ var PlacesOrganizer = {
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;
this._places.selectItems([PlacesUtils.virtualAllBookmarksGuid]);
PlacesUtils.asContainer(this._places.selectedNode).containerOpen = true;
break;
case "History":
this._places.selectItems([PlacesUtils.virtualHistoryGuid]);
PlacesUtils.asContainer(this._places.selectedNode).containerOpen = true;
break;
case "Downloads":
this._places.selectItems([PlacesUtils.virtualDownloadsGuid]);
break;
case "Tags":
this._places.selectItems([PlacesUtils.virtualTagsGuid]);
break;
}
case "BookmarksMenu":
this.selectLeftPaneContainerByHierarchy([
PlacesUIUtils.leftPaneQueries.AllBookmarks,
PlacesUtils.virtualAllBookmarksGuid,
PlacesUtils.bookmarks.virtualMenuGuid
]);
break;
case "BookmarksToolbar":
this.selectLeftPaneContainerByHierarchy([
PlacesUIUtils.leftPaneQueries.AllBookmarks,
PlacesUtils.virtualAllBookmarksGuid,
PlacesUtils.bookmarks.virtualToolbarGuid
]);
break;
case "UnfiledBookmarks":
this.selectLeftPaneContainerByHierarchy([
PlacesUIUtils.leftPaneQueries.AllBookmarks,
PlacesUtils.virtualAllBookmarksGuid,
PlacesUtils.bookmarks.virtualUnfiledGuid
]);
break;
@ -92,7 +94,6 @@ var PlacesOrganizer = {
* container may be either an item id, a Places URI string,
* or a named query, like:
* "BookmarksMenu", "BookmarksToolbar", "UnfiledBookmarks", "AllBookmarks".
* @see PlacesUIUtils.leftPaneQueries for supported named queries.
*/
selectLeftPaneContainerByHierarchy(aHierarchy) {
if (!aHierarchy)
@ -312,12 +313,12 @@ var PlacesOrganizer = {
* the node to set up scope from
*/
_setSearchScopeForNode: function PO__setScopeForNode(aNode) {
let itemId = aNode.itemId;
let itemGuid = aNode.bookmarkGuid;
if (PlacesUtils.nodeIsHistoryContainer(aNode) ||
itemId == PlacesUIUtils.leftPaneQueries.History) {
itemGuid == PlacesUtils.virtualHistoryGuid) {
PlacesQueryBuilder.setScope("history");
} else if (itemId == PlacesUIUtils.leftPaneQueries.Downloads) {
} else if (itemGuid == PlacesUtils.virtualDownloadsGuid) {
PlacesQueryBuilder.setScope("downloads");
} else {
// Default to All Bookmarks for all other nodes, per bug 469437.

View File

@ -632,7 +632,7 @@
// 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.nodeIsQuery(node) && node.bookmarkGuid == PlacesUIUtils.virtualAllBookmarksGuid));
PlacesUtils.asContainer(node);
if (!node.containerOpen && !shouldOpen)

View File

@ -138,6 +138,7 @@ PlacesTreeView.prototype = {
case Ci.nsINavHistoryQueryOptions.RESULTS_AS_DATE_SITE_QUERY:
case Ci.nsINavHistoryQueryOptions.RESULTS_AS_TAG_QUERY:
case Ci.nsINavHistoryQueryOptions.RESULTS_AS_ROOTS_QUERY:
case Ci.nsINavHistoryQueryOptions.RESULTS_AS_LEFT_PANE_QUERY:
return false;
}
@ -1305,11 +1306,13 @@ PlacesTreeView.prototype = {
case PlacesUtils.bookmarks.virtualUnfiledGuid:
properties += ` queryFolder_${PlacesUtils.bookmarks.unfiledGuid}`;
break;
case PlacesUtils.virtualAllBookmarksGuid:
case PlacesUtils.virtualHistoryGuid:
case PlacesUtils.virtualDownloadsGuid:
case PlacesUtils.virtualTagsGuid:
properties += ` OrganizerQuery_${node.bookmarkGuid}`;
break;
}
} else {
let queryName = PlacesUIUtils.getLeftPaneQueryNameFromId(itemId);
if (queryName)
properties += " OrganizerQuery_" + queryName;
}
} else if (nodeType == Ci.nsINavHistoryResultNode.RESULT_TYPE_SEPARATOR)
properties += " separator";
@ -1792,15 +1795,6 @@ PlacesTreeView.prototype = {
PlacesUtils.isQueryGeneratedFolder(itemGuid))
return false;
let parentId = PlacesUtils.getConcreteItemId(node.parent);
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;
},

View File

@ -79,7 +79,6 @@ skip-if = (os == 'win' && ccov) # Bug 1423667
[browser_library_downloads.js]
skip-if = (os == 'win' && ccov) # Bug 1423667
[browser_library_infoBox.js]
[browser_library_left_pane_fixnames.js]
[browser_library_left_pane_middleclick.js]
skip-if = (os == 'win' && ccov) # Bug 1423667
[browser_library_left_pane_select_hierarchy.js]

View File

@ -50,7 +50,7 @@ add_task(async function copy_mobile_shortcut() {
});
library.PlacesOrganizer.selectLeftPaneContainerByHierarchy([
PlacesUIUtils.leftPaneQueries.AllBookmarks,
PlacesUtils.virtualAllBookmarksGuid,
PlacesUtils.bookmarks.virtualMobileGuid,
]);

View File

@ -1,71 +0,0 @@
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* vim:set ts=2 sw=2 sts=2 et: */
/* 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/. */
/**
* Test we correctly fix broken Library left pane queries names.
*/
// Array of left pane queries objects, each one has the following properties:
// name: query's identifier got from annotations,
// itemId: query's itemId,
// correctTitle: original and correct query's title.
var leftPaneQueries = [];
function onLibraryReady(organizer) {
// Check titles have been fixed.
for (var i = 0; i < leftPaneQueries.length; i++) {
var query = leftPaneQueries[i];
if ("concreteId" in query) {
is(PlacesUtils.bookmarks.getItemTitle(query.concreteId),
query.concreteTitle, "Concrete title is correct for query " + query.name);
}
}
// Close Library window.
organizer.close();
// No need to cleanup anything, we have a correct left pane now.
finish();
}
function test() {
waitForExplicitFinish();
// Ensure left pane is initialized.
ok(PlacesUIUtils.leftPaneFolderId > 0, "left pane folder is initialized");
// Get the left pane folder.
var leftPaneItems = PlacesUtils.annotations
.getItemsWithAnnotation(PlacesUIUtils.ORGANIZER_FOLDER_ANNO);
is(leftPaneItems.length, 1, "We correctly have only 1 left pane folder");
// Check version.
var version = PlacesUtils.annotations
.getItemAnnotation(leftPaneItems[0],
PlacesUIUtils.ORGANIZER_FOLDER_ANNO);
is(version, PlacesUIUtils.ORGANIZER_LEFTPANE_VERSION, "Left pane version is actual");
// Get all left pane queries.
var items = PlacesUtils.annotations
.getItemsWithAnnotation(PlacesUIUtils.ORGANIZER_QUERY_ANNO);
// Get current queries names.
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) };
leftPaneQueries.push(query);
// Rename to a bad title.
PlacesUtils.bookmarks.setItemTitle(query.itemId, "badName");
}
restoreLeftPaneGetters();
// Open Library, this will kick-off left pane code.
openLibrary(onLibraryReady);
}

View File

@ -30,98 +30,33 @@ const TEST_DOWNLOAD_URL = "http://dummy.mozilla.org/dummy.pdf";
var gLibrary;
var testCases = [
function allBookmarksScope() {
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);
},
function downloadsScope() {
let defScope = getDefaultScope(PlacesUIUtils.leftPaneQueries.Downloads);
search(PlacesUIUtils.leftPaneQueries.Downloads, "dummy", defScope);
},
];
/**
* Returns the default search scope for a given folder.
*
* @param aFolderId
* the item ID of a node in the left pane's tree
* @return the default scope when the folder is newly selected
*/
function getDefaultScope(aFolderId) {
switch (aFolderId) {
case PlacesUIUtils.leftPaneQueries.History:
return "scopeBarHistory";
case PlacesUIUtils.leftPaneQueries.Downloads:
return "scopeBarDownloads";
default:
return "scopeBarAll";
}
}
/**
* Returns the single nsINavHistoryQuery represented by a given place URI.
*
* @param aPlaceURI
* a URI that represents a single query
* @return an nsINavHistoryQuery object
*/
function queryStringToQuery(aPlaceURI) {
let queries = {};
PlacesUtils.history.queryStringToQueries(aPlaceURI, queries, {}, {});
return queries.value[0];
}
/**
* Resets the search by clearing the search box's text and ensures that the
* search scope remains as expected.
*
* @param aExpectedScopeButtonId
* this button should be selected after the reset
*/
function resetSearch(aExpectedScopeButtonId) {
search(null, "", aExpectedScopeButtonId);
}
/**
* Performs a search for a given folder and search string and ensures that the
* URI of the right pane's content tree is as expected for the folder and search
* string. Also ensures that the search scope button is as expected after the
* search.
*
* @param aFolderId
* the item ID of a node in the left pane's tree
* @param aFolderGuid
* the item guid of a node in the left pane's tree
* @param aSearchStr
* the search text; may be empty to reset the search
* @param aExpectedScopeButtonId
* after searching the selected scope button should be this
*/
function search(aFolderId, aSearchStr, aExpectedScopeButtonId) {
async function search(aFolderGuid, aSearchStr) {
let doc = gLibrary.document;
let folderTree = doc.getElementById("placesList");
let contentTree = doc.getElementById("placeContent");
// First, ensure that selecting the folder in the left pane updates the
// content tree properly.
if (aFolderId) {
folderTree.selectItems([aFolderId]);
isnot(folderTree.selectedNode, null,
if (aFolderGuid) {
folderTree.selectItems([aFolderGuid]);
Assert.notEqual(folderTree.selectedNode, null,
"Sanity check: left pane tree should have selection after selecting!");
// getFolders() on a History query returns an empty array, so no use
// comparing against aFolderId in that case.
if (aFolderId !== PlacesUIUtils.leftPaneQueries.History &&
aFolderId !== PlacesUIUtils.leftPaneQueries.Downloads) {
// contentTree.place should be equal to contentTree.result.root.uri,
// but it's not until bug 476952 is fixed.
let query = queryStringToQuery(contentTree.result.root.uri);
is(query.getFolders()[0], aFolderId,
// The downloads folder never quite matches the url of the contentTree,
// probably due to the way downloads are loaded.
if (aFolderGuid !== PlacesUtils.virtualDownloadsGuid) {
Assert.equal(folderTree.selectedNode.uri, contentTree.place,
"Content tree's folder should be what was selected in the left pane");
}
}
@ -131,13 +66,14 @@ function search(aFolderId, aSearchStr, aExpectedScopeButtonId) {
let searchBox = doc.getElementById("searchFilter");
searchBox.value = aSearchStr;
gLibrary.PlacesSearchBox.search(searchBox.value);
let query = queryStringToQuery(contentTree.result.root.uri);
let queries = {};
PlacesUtils.history.queryStringToQueries(contentTree.result.root.uri, queries, {}, {});
if (aSearchStr) {
is(query.searchTerms, aSearchStr,
"Content tree's searchTerms should be text in search box");
Assert.equal(queries.value[0].searchTerms, aSearchStr,
"Content tree's searchTerms should be text in search box");
} else {
is(query.hasSearchTerms, false,
"Content tree's searchTerms should not exist after search reset");
Assert.equal(queries.value[0].hasSearchTerms, false,
"Content tree's searchTerms should not exist after search reset");
}
}
@ -160,7 +96,15 @@ add_task(async function test() {
gLibrary = await promiseLibrary();
testCases.forEach(aTest => aTest());
const rootsToTest = [
PlacesUtils.virtualAllBookmarksGuid,
PlacesUtils.virtualHistoryGuid,
PlacesUtils.virtualDownloadsGuid,
];
for (let root of rootsToTest) {
await search(root, "dummy");
}
await promiseLibraryClosed(gLibrary);

View File

@ -5,27 +5,6 @@ ChromeUtils.defineModuleGetter(this, "PlacesTestUtils",
ChromeUtils.defineModuleGetter(this, "TestUtils",
"resource://testing-common/TestUtils.jsm");
// We need to cache these before test runs...
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);
}
}
}
// ...And restore them when test ends.
function restoreLeftPaneGetters() {
for (let [key, getter] of leftPaneGetters) {
Object.defineProperty(PlacesUIUtils, key, {
enumerable: true, configurable: true, get: getter
});
}
}
registerCleanupFunction(restoreLeftPaneGetters);
function openLibrary(callback, aLeftPaneRoot) {
let library = window.openDialog("chrome://browser/content/places/places.xul",
"", "chrome,toolbar=yes,dialog=no,resizable",

View File

@ -37,58 +37,41 @@
* Bug 510634 - Wrong icons on bookmarks sidebar
* https://bugzilla.mozilla.org/show_bug.cgi?id=510634
*
* Ensures that properties for special queries are set on their tree nodes,
* even if PlacesUIUtils.leftPaneFolderId was not initialized.
* Ensures that properties for special queries are set on their tree nodes.
*/
SimpleTest.waitForExplicitFinish();
function runTest() {
// We need to cache and restore the getters in order to simulate
// Bug 510634.
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);
}
}
}
function restoreLeftPaneGetters() {
for (let [key, getter] of leftPaneGetters) {
Object.defineProperty(PlacesUIUtils, key, {
enumerable: true, configurable: true, get: getter
});
}
}
let leftPaneFolderId = PlacesUIUtils.leftPaneFolderId;
restoreLeftPaneGetters();
// Setup the places tree contents.
let tree = document.getElementById("tree");
tree.place = "place:queryType=1&folder=" + leftPaneFolderId;
tree.place = `place:type=${Ci.nsINavHistoryQueryOptions.RESULTS_AS_LEFT_PANE_QUERY}&excludeItems=1&expandQueries=0`;
// 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"]]);
tree.selectItems([PlacesUtils.virtualAllBookmarksGuid]);
PlacesUtils.asContainer(tree.selectedNode).containerOpen = true;
is(tree.selectedNode.uri,
"place:type=" + Ci.nsINavHistoryQueryOptions.RESULTS_AS_ROOTS_QUERY +
"&queryType=" + Ci.nsINavHistoryQueryOptions.QUERY_TYPE_BOOKMARKS,
"Opened All Bookmarks");
for (let queryName of ["History", "Downloads", "Tags", "AllBookmarks"]) {
const topLevelGuids = [
PlacesUtils.virtualHistoryGuid,
PlacesUtils.virtualDownloadsGuid,
PlacesUtils.virtualTagsGuid,
PlacesUtils.virtualAllBookmarksGuid
];
for (let queryName of topLevelGuids) {
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");
ok(found, `OrganizerQuery_${queryName} is set`);
}
const folderGuids = [
@ -103,15 +86,12 @@
rowProperties = tree.view.getCellProperties(i, titleColumn).split(" ");
found = rowProperties.includes("queryFolder_" + guid);
}
ok(found, "queryFolder_" + guid + " is set");
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();
}

View File

@ -20,9 +20,6 @@ XPCOMUtils.defineLazyGetter(this, "PlacesUIUtils", function() {
return PlacesUIUtils;
});
const ORGANIZER_FOLDER_ANNO = "PlacesOrganizer/OrganizerFolder";
const ORGANIZER_QUERY_ANNO = "PlacesOrganizer/OrganizerQuery";
// Needed by some test that relies on having an app registered.
ChromeUtils.import("resource://testing-common/AppInfo.jsm", this);
updateAppInfo({

View File

@ -1,149 +0,0 @@
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* vim:set ts=2 sw=2 sts=2 et: */
/* 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/. */
/**
* Tests that we build a working leftpane in various corruption situations.
*/
// Used to store the original leftPaneFolderId getter.
var gLeftPaneFolderIdGetter;
// 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");
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,
title: "test",
index: PlacesUtils.bookmarks.DEFAULT_INDEX,
type: PlacesUtils.bookmarks.TYPE_FOLDER
});
let folderId = await PlacesUtils.promiseItemId(folder.guid);
PlacesUtils.annotations.setItemAnnotation(folderId, ORGANIZER_QUERY_ANNO,
"test", 0,
PlacesUtils.annotations.EXPIRE_NEVER);
// 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()();
// Regenerate getters.
Object.defineProperty(PlacesUIUtils, "leftPaneFolderId", gLeftPaneFolderIdGetter);
gLeftPaneFolderId = PlacesUIUtils.leftPaneFolderId;
// 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");
}
});
// Corruption cases.
var gTests = [
function test1() {
print("1. Do nothing, checks test calibration.");
},
async function test2() {
print("2. Delete the left pane folder.");
let guid = await PlacesUtils.promiseItemGuid(gLeftPaneFolderId);
await PlacesUtils.bookmarks.remove(guid);
},
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. 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 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 test6() {
print("6. Remove the left pane folder annotation.");
PlacesUtils.annotations.removeItemAnnotation(gLeftPaneFolderId,
ORGANIZER_FOLDER_ANNO);
},
];
/**
* 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));
root.containerOpen = false;
return hier;
}
function hierarchyToObj(aNode) {
let o = {};
o.title = aNode.title;
o.annos = PlacesUtils.getAnnotationsForItem(aNode.itemId);
if (PlacesUtils.nodeIsURI(aNode)) {
o.uri = aNode.uri;
} else if (PlacesUtils.nodeIsFolder(aNode)) {
o.children = [];
PlacesUtils.asContainer(aNode).containerOpen = true;
for (let i = 0; i < aNode.childCount; ++i) {
o.children.push(hierarchyToObj(aNode.getChild(i)));
}
aNode.containerOpen = false;
}
return o;
}

View File

@ -20,5 +20,4 @@ support-files =
[test_browserGlue_smartBookmarks.js]
[test_browserGlue_urlbar_defaultbehavior_migration.js]
[test_clearHistory_shutdown.js]
[test_leftpane_corruption_handling.js]
[test_PUIU_batchUpdatesForNode.js]

View File

@ -62,11 +62,6 @@ detailsPane.itemsCountLabel=One item;#1 items
mostVisitedTitle=Most Visited
recentTagsTitle=Recent Tags
OrganizerQueryHistory=History
OrganizerQueryDownloads=Downloads
OrganizerQueryAllBookmarks=All Bookmarks
OrganizerQueryTags=Tags
# LOCALIZATION NOTE (tagResultLabel, bookmarkResultLabel, switchtabResultLabel,
# keywordResultLabel, searchengineResultLabel)
# Noun used to describe the location bar autocomplete result type

View File

@ -58,16 +58,16 @@ treechildren::-moz-tree-image(query) {
list-style-image: url("chrome://browser/skin/places/folder-smart.svg");
}
treechildren::-moz-tree-image(query, OrganizerQuery_AllBookmarks) {
treechildren::-moz-tree-image(query, OrganizerQuery_allbms_____v) {
list-style-image: url("chrome://browser/skin/places/allBookmarks.png");
}
treechildren::-moz-tree-image(query, OrganizerQuery_Downloads) {
treechildren::-moz-tree-image(query, OrganizerQuery_downloads__v) {
list-style-image: url("chrome://browser/skin/places/downloads.png");
}
treechildren::-moz-tree-image(title, query, tagContainer),
treechildren::-moz-tree-image(query, OrganizerQuery_Tags) {
treechildren::-moz-tree-image(query, OrganizerQuery_tags_______v) {
list-style-image: url("chrome://browser/skin/places/tag.png");
}
@ -80,7 +80,7 @@ treechildren::-moz-tree-image(title, query, hostContainer) {
list-style-image: url("chrome://browser/skin/places/folder.svg");
}
treechildren::-moz-tree-image(query, OrganizerQuery_History) {
treechildren::-moz-tree-image(query, OrganizerQuery_history____v) {
list-style-image: url("chrome://browser/skin/places/history.svg");
}

View File

@ -22,16 +22,8 @@ Cu.importGlobalProperties(["URLSearchParams"]);
var EXPORTED_SYMBOLS = ["BookmarkValidator", "BookmarkProblemData"];
const LEFT_PANE_ROOT_ANNO = "PlacesOrganizer/OrganizerFolder";
const LEFT_PANE_QUERY_ANNO = "PlacesOrganizer/OrganizerQuery";
const QUERY_PROTOCOL = "place:";
// Indicates if a local bookmark tree node should be excluded from syncing.
function isNodeIgnored(treeNode) {
return treeNode.annos && treeNode.annos.some(anno => anno.name == LEFT_PANE_ROOT_ANNO ||
anno.name == LEFT_PANE_QUERY_ANNO);
}
function areURLsEqual(a, b) {
if (a === b) {
return true;
@ -653,8 +645,6 @@ class BookmarkValidator {
await this.maybeYield();
if (!synced) {
synced = syncedRoots.includes(treeNode.guid);
} else if (isNodeIgnored(treeNode)) {
synced = false;
}
let localId = treeNode.id;
let guid = PlacesSyncUtils.bookmarks.guidToRecordId(treeNode.guid);

View File

@ -285,11 +285,6 @@ add_task(async function test_cswc_serverUnexpected() {
"flags": 0,
"expires": 4,
"value": 1
}, {
"name": "PlacesOrganizer/OrganizerFolder",
"flags": 0,
"expires": 4,
"value": 7
}],
"type": "text/x-moz-place-container",
"children": [{
@ -300,11 +295,6 @@ add_task(async function test_cswc_serverUnexpected() {
"flags": 0,
"expires": 4,
"value": 1
}, {
"name": "PlacesOrganizer/OrganizerQuery",
"flags": 0,
"expires": 4,
"value": "History"
}],
"type": "text/x-moz-place",
"uri": "place:type=3&sort=4"

View File

@ -152,8 +152,8 @@ var Bookmarks = Object.freeze({
userContentRoots: ["toolbar_____", "menu________", "unfiled_____", "mobile______"],
/**
* GUIDs associated with virtual queries that are used for display in the left
* pane.
* GUIDs associated with virtual queries that are used for displaying bookmark
* folders in the left pane.
*/
virtualMenuGuid: "menu_______v",
virtualToolbarGuid: "toolbar____v",

View File

@ -118,7 +118,8 @@ function serializeNode(aNode, aIsLivemark) {
// 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)) {
if (guid && !PlacesUtils.bookmarks.isVirtualRootItem(guid) &&
!PlacesUtils.isVirtualLeftPaneItem(guid)) {
// TODO: Really guid should be set on everything, however currently this upsets
// the drag 'n' drop / cut/copy/paste operations.
data.itemGuid = guid;
@ -343,6 +344,29 @@ var PlacesUtils = {
ACTION_SCHEME: "moz-action:",
/**
* GUIDs associated with virtual queries that are used for displaying the
* top-level folders in the left pane.
*/
virtualAllBookmarksGuid: "allbms_____v",
virtualHistoryGuid: "history____v",
virtualDownloadsGuid: "downloads__v",
virtualTagsGuid: "tags_______v",
/**
* Checks if a guid is a virtual left-pane root.
*
* @param {String} guid The guid of the item to look for.
* @returns {Boolean} true if guid is a virtual root, false otherwise.
*/
isVirtualLeftPaneItem(guid) {
return guid == PlacesUtils.virtualAllBookmarksGuid ||
guid == PlacesUtils.virtualHistoryGuid ||
guid == PlacesUtils.virtualDownloadsGuid ||
guid == PlacesUtils.virtualTagsGuid;
},
asContainer: aNode => asContainer(aNode),
asQuery: aNode => asQuery(aNode),

View File

@ -1079,6 +1079,11 @@ interface nsINavHistoryQueryOptions : nsISupports
*/
const unsigned short RESULTS_AS_ROOTS_QUERY = 8;
/**
* This returns nsINavHistoryQueryResultNode for each left-pane root.
*/
const unsigned short RESULTS_AS_LEFT_PANE_QUERY = 9;
/**
* The sorting mode to be used for this query.
* mode is one of SORT_BY_*
@ -1139,7 +1144,7 @@ interface nsINavHistoryQueryOptions : nsISupports
attribute boolean includeHidden;
/**
* This is the maximum number of results that you want. The query is exeucted,
* This is the maximum number of results that you want. The query is executed,
* the results are sorted, and then the top 'maxResults' results are taken
* and returned. Set to 0 (the default) to get all results.
*

View File

@ -780,6 +780,10 @@ nsNavHistory::NormalizeTime(uint32_t aRelative, PRTime aOffset)
// QUERYUPDATE_COMPLEX_WITH_BOOKMARKS:
// A complex query that additionally has dependence on bookmarks. All
// bookmark-dependent queries fall under this category.
// QUERYUPDATE_MOBILEPREF:
// A complex query but only updates when the mobile preference changes.
// QUERYUPDATE_NONE:
// A query that never updates, e.g. the left-pane root query.
//
// aHasSearchTerms will be set to true if the query has any dependence on
// keywords. When there is no dependence on keywords, we can handle title
@ -835,6 +839,10 @@ nsNavHistory::GetUpdateRequirements(const nsCOMArray<nsNavHistoryQuery>& aQuerie
nsINavHistoryQueryOptions::RESULTS_AS_ROOTS_QUERY)
return QUERYUPDATE_MOBILEPREF;
if (aOptions->ResultType() ==
nsINavHistoryQueryOptions::RESULTS_AS_LEFT_PANE_QUERY)
return QUERYUPDATE_NONE;
// 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
@ -1416,6 +1424,7 @@ private:
nsresult SelectAsSite();
nsresult SelectAsTag();
nsresult SelectAsRoots();
nsresult SelectAsLeftPane();
nsresult Where();
nsresult GroupBy();
@ -1522,6 +1531,11 @@ PlacesSQLQueryBuilder::Select()
NS_ENSURE_SUCCESS(rv, rv);
break;
case nsINavHistoryQueryOptions::RESULTS_AS_LEFT_PANE_QUERY:
rv = SelectAsLeftPane();
NS_ENSURE_SUCCESS(rv, rv);
break;
default:
NS_NOTREACHED("Invalid result type");
}
@ -1975,6 +1989,48 @@ PlacesSQLQueryBuilder::SelectAsRoots()
return NS_OK;
}
nsresult
PlacesSQLQueryBuilder::SelectAsLeftPane()
{
nsNavHistory *history = nsNavHistory::GetHistoryService();
NS_ENSURE_STATE(history);
nsAutoCString historyTitle;
nsAutoCString downloadsTitle;
nsAutoCString tagsTitle;
nsAutoCString allBookmarksTitle;
history->GetStringFromName("OrganizerQueryHistory", historyTitle);
history->GetStringFromName("OrganizerQueryDownloads", downloadsTitle);
history->GetStringFromName("TagsFolderTitle", tagsTitle);
history->GetStringFromName("OrganizerQueryAllBookmarks", allBookmarksTitle);
mQueryString = nsPrintfCString(
"SELECT * FROM ("
"VALUES"
"(null, 'place:type=%d&sort=%d', '%s', null, null, null, "
"null, null, 0, 0, null, null, null, null, 'history____v', null), "
"(null, 'place:transition=%d&sort=%d', '%s', null, null, null, "
"null, null, 0, 0, null, null, null, null, 'downloads__v', null), "
"(null, 'place:type=%d&sort=%d', '%s', null, null, null, "
"null, null, 0, 0, null, null, null, null, 'tags_______v', null), "
"(null, 'place:type=%d', '%s', null, null, null, "
"null, null, 0, 0, null, null, null, null, 'allbms_____v', null) "
")",
nsINavHistoryQueryOptions::RESULTS_AS_DATE_QUERY,
nsINavHistoryQueryOptions::SORT_BY_DATE_DESCENDING,
historyTitle.get(),
nsINavHistoryService::TRANSITION_DOWNLOAD,
nsINavHistoryQueryOptions::SORT_BY_DATE_DESCENDING,
downloadsTitle.get(),
nsINavHistoryQueryOptions::RESULTS_AS_TAG_QUERY,
nsINavHistoryQueryOptions::SORT_BY_TITLE_ASCENDING,
tagsTitle.get(),
nsINavHistoryQueryOptions::RESULTS_AS_ROOTS_QUERY,
allBookmarksTitle.get());
return NS_OK;
}
nsresult
PlacesSQLQueryBuilder::Where()
{
@ -3834,7 +3890,8 @@ nsNavHistory::RowToResult(mozIStorageValueArray* aRow,
NS_ENSURE_SUCCESS(rv, rv);
}
if (aOptions->ResultType() == nsNavHistoryQueryOptions::RESULTS_AS_ROOTS_QUERY) {
if (aOptions->ResultType() == nsNavHistoryQueryOptions::RESULTS_AS_ROOTS_QUERY ||
aOptions->ResultType() == nsNavHistoryQueryOptions::RESULTS_AS_LEFT_PANE_QUERY) {
rv = aRow->GetUTF8String(kGetInfoIndex_Guid, guid);
NS_ENSURE_SUCCESS(rv, rv);
}

View File

@ -40,6 +40,7 @@
#define QUERYUPDATE_COMPLEX_WITH_BOOKMARKS 3
#define QUERYUPDATE_HOST 4
#define QUERYUPDATE_MOBILEPREF 5
#define QUERYUPDATE_NONE 6
// Clamp title and URL to generously large, but not too large, length.
// See bug 319004 for details.

View File

@ -1359,12 +1359,12 @@ nsNavHistoryQueryOptions::GetResultType(uint16_t* aType)
NS_IMETHODIMP
nsNavHistoryQueryOptions::SetResultType(uint16_t aType)
{
if (aType > RESULTS_AS_ROOTS_QUERY)
if (aType > RESULTS_AS_LEFT_PANE_QUERY)
return NS_ERROR_INVALID_ARG;
// 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)
aType == RESULTS_AS_ROOTS_QUERY || aType == RESULTS_AS_LEFT_PANE_QUERY)
mQueryType = QUERY_TYPE_BOOKMARKS;
mResultType = aType;
return NS_OK;
@ -1467,7 +1467,9 @@ nsNavHistoryQueryOptions::SetQueryType(uint16_t aQueryType)
// Tag query and containers are forced to QUERY_TYPE_BOOKMARKS when the
// resultType is set.
if (mResultType == RESULTS_AS_TAG_CONTENTS ||
mResultType == RESULTS_AS_TAG_QUERY)
mResultType == RESULTS_AS_TAG_QUERY ||
mResultType == RESULTS_AS_LEFT_PANE_QUERY ||
mResultType == RESULTS_AS_ROOTS_QUERY)
return NS_OK;
mQueryType = aQueryType;
return NS_OK;

View File

@ -1813,7 +1813,8 @@ nsNavHistoryQueryResultNode::IsContainersQuery()
resultType == nsINavHistoryQueryOptions::RESULTS_AS_DATE_SITE_QUERY ||
resultType == nsINavHistoryQueryOptions::RESULTS_AS_TAG_QUERY ||
resultType == nsINavHistoryQueryOptions::RESULTS_AS_SITE_QUERY ||
resultType == nsINavHistoryQueryOptions::RESULTS_AS_ROOTS_QUERY;
resultType == nsINavHistoryQueryOptions::RESULTS_AS_ROOTS_QUERY ||
resultType == nsINavHistoryQueryOptions::RESULTS_AS_LEFT_PANE_QUERY;
}
@ -1886,7 +1887,8 @@ nsNavHistoryQueryResultNode::GetHasChildren(bool* aHasChildren)
// Tags are always populated, otherwise they are removed.
if (resultType == nsINavHistoryQueryOptions::RESULTS_AS_TAG_CONTENTS ||
// AllBookmarks also always has children.
resultType == nsINavHistoryQueryOptions::RESULTS_AS_ROOTS_QUERY) {
resultType == nsINavHistoryQueryOptions::RESULTS_AS_ROOTS_QUERY ||
resultType == nsINavHistoryQueryOptions::RESULTS_AS_LEFT_PANE_QUERY) {
*aHasChildren = true;
return NS_OK;
}
@ -2112,6 +2114,13 @@ nsNavHistoryQueryResultNode::FillChildren()
mChildren.RemoveObjectAt(mChildren.Count() - 1);
}
// If we're not updating the query, we don't need to add listeners, so bail
// out early.
if (mLiveUpdate == QUERYUPDATE_NONE) {
mContentsValid = true;
return NS_OK;
}
nsNavHistoryResult* result = GetResult();
NS_ENSURE_STATE(result);
@ -2196,12 +2205,21 @@ nsNavHistoryQueryResultNode::Refresh()
// containing other queries. In this case calling Refresh for each child
// query could cause a major slowdown. We should not refresh nested
// queries, since we will already refresh the parent one.
if (!mExpanded ||
(mParent && mParent->IsQuery() &&
mParent->GetAsQuery()->IsContainersQuery())) {
// Don't update, just invalidate and unhook
// The only exception to this, is if the parent query is of QUERYUPDATE_NONE,
// this can be the case for the RESULTS_AS_TAG_QUERY
// under RESULTS_AS_LEFT_PANE_QUERY.
if (!mExpanded) {
ClearChildren(true);
return NS_OK; // no updates in tree state
return NS_OK;
}
if (mParent && mParent->IsQuery()) {
nsNavHistoryQueryResultNode* parent = mParent->GetAsQuery();
if (parent->IsContainersQuery() && parent->mLiveUpdate != QUERYUPDATE_NONE) {
// Don't update, just invalidate and unhook
ClearChildren(true);
return NS_OK; // no updates in tree state
}
}
if (mLiveUpdate == QUERYUPDATE_COMPLEX_WITH_BOOKMARKS)

View File

@ -0,0 +1,64 @@
"use strict";
const MOBILE_BOOKMARKS_PREF = "browser.bookmarks.showMobileBookmarks";
const expectedRoots = [{
title: "OrganizerQueryHistory",
uri: `place:sort=${Ci.nsINavHistoryQueryOptions.SORT_BY_DATE_DESCENDING}&type=${Ci.nsINavHistoryQueryOptions.RESULTS_AS_DATE_QUERY}`,
guid: "history____v",
}, {
title: "OrganizerQueryDownloads",
uri: `place:transition=${Ci.nsINavHistoryService.TRANSITION_DOWNLOAD}&sort=${Ci.nsINavHistoryQueryOptions.SORT_BY_DATE_DESCENDING}`,
guid: "downloads__v",
}, {
title: "TagsFolderTitle",
uri: `place:sort=${Ci.nsINavHistoryQueryOptions.SORT_BY_TITLE_ASCENDING}&type=${Ci.nsINavHistoryQueryOptions.RESULTS_AS_TAG_QUERY}&queryType=1`,
guid: "tags_______v",
}, {
title: "OrganizerQueryAllBookmarks",
uri: `place:type=${Ci.nsINavHistoryQueryOptions.RESULTS_AS_ROOTS_QUERY}&queryType=1`,
guid: "allbms_____v",
}];
const placesStrings = Services.strings.createBundle("chrome://places/locale/places.properties");
function getLeftPaneQuery() {
var query = PlacesUtils.history.getNewQuery();
// Options
var options = PlacesUtils.history.getNewQueryOptions();
options.resultType = options.RESULTS_AS_LEFT_PANE_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 = getLeftPaneQuery();
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;
});

View File

@ -17,6 +17,7 @@ skip-if = (os == 'win' && ccov) # Bug 1423667
[test_queryMultipleFolder.js]
[test_querySerialization.js]
[test_redirects.js]
[test_results-as-left-pane.js]
[test_results-as-roots.js]
[test_results-as-tag-contents-query.js]
[test_results-as-visit.js]

View File

@ -7,6 +7,9 @@ BookmarksToolbarFolderTitle=Bookmarks Toolbar
OtherBookmarksFolderTitle=Other Bookmarks
TagsFolderTitle=Tags
MobileBookmarksFolderTitle=Mobile Bookmarks
OrganizerQueryHistory=History
OrganizerQueryDownloads=Downloads
OrganizerQueryAllBookmarks=All Bookmarks
# LOCALIZATION NOTE (dateName):
# These are used to generate history containers when history is grouped by date