Backed out 2 changesets (bug 1310295) for Mochitest and TV failures on browser/components/places/tests/browser/browser_bookmark_folder_moveability.js

Backed out changeset a277657bfffa (bug 1310295)
Backed out changeset b1af75c617ea (bug 1310295)

--HG--
extra : rebase_source : cb7317f5afa7c828ec3b6d9b7679b474fa9cf5da
This commit is contained in:
Dorel Luca 2018-03-14 18:23:46 +02:00
parent 28e4c11d86
commit 7065341d2f
35 changed files with 745 additions and 613 deletions

View File

@ -1398,7 +1398,18 @@ var BookmarkingUI = {
MOBILE_BOOKMARKS_PREF: "browser.bookmarks.showMobileBookmarks",
_shouldShowMobileBookmarks() {
return Services.prefs.getBoolPref(this.MOBILE_BOOKMARKS_PREF, false);
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;
},
_initMobileBookmarks(mobileMenuItem) {

View File

@ -206,6 +206,10 @@ 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",
@ -527,16 +531,11 @@ var PlacesUIUtils = {
}
// Is it a query pointing to one of the special root folders?
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.
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;
}
}
@ -591,7 +590,21 @@ var PlacesUIUtils = {
view.controller.hasCachedLivemarkInfo(placesNode))
return true;
return false;
// 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;
},
/** aItemsToOpen needs to be an array of objects of the form:
@ -801,6 +814,261 @@ 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";
@ -1086,7 +1354,14 @@ 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,6 +56,12 @@ 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,7 +36,8 @@ var PlacesOrganizer = {
],
_initFolderTree() {
this._places.place = `place:type=${Ci.nsINavHistoryQueryOptions.RESULTS_AS_LEFT_PANE_QUERY}&excludeItems=1&expandQueries=0`;
var leftPaneRoot = PlacesUIUtils.leftPaneFolderId;
this._places.place = "place:excludeItems=1&expandQueries=0&folder=" + leftPaneRoot;
},
/**
@ -49,34 +50,31 @@ var PlacesOrganizer = {
selectLeftPaneBuiltIn(item) {
switch (item) {
case "AllBookmarks":
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]);
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([
PlacesUtils.virtualAllBookmarksGuid,
PlacesUIUtils.leftPaneQueries.AllBookmarks,
PlacesUtils.bookmarks.virtualMenuGuid
]);
break;
case "BookmarksToolbar":
this.selectLeftPaneContainerByHierarchy([
PlacesUtils.virtualAllBookmarksGuid,
PlacesUIUtils.leftPaneQueries.AllBookmarks,
PlacesUtils.bookmarks.virtualToolbarGuid
]);
break;
case "UnfiledBookmarks":
this.selectLeftPaneContainerByHierarchy([
PlacesUtils.virtualAllBookmarksGuid,
PlacesUIUtils.leftPaneQueries.AllBookmarks,
PlacesUtils.bookmarks.virtualUnfiledGuid
]);
break;
@ -94,6 +92,7 @@ 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)
@ -313,12 +312,12 @@ var PlacesOrganizer = {
* the node to set up scope from
*/
_setSearchScopeForNode: function PO__setScopeForNode(aNode) {
let itemGuid = aNode.bookmarkGuid;
let itemId = aNode.itemId;
if (PlacesUtils.nodeIsHistoryContainer(aNode) ||
itemGuid == PlacesUtils.virtualHistoryGuid) {
itemId == PlacesUIUtils.leftPaneQueries.History) {
PlacesQueryBuilder.setScope("history");
} else if (itemGuid == PlacesUtils.virtualDownloadsGuid) {
} else if (itemId == PlacesUIUtils.leftPaneQueries.Downloads) {
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.bookmarkGuid == PlacesUIUtils.virtualAllBookmarksGuid));
(PlacesUtils.nodeIsQuery(node) && node.itemId == PlacesUIUtils.leftPaneQueries.AllBookmarks));
PlacesUtils.asContainer(node);
if (!node.containerOpen && !shouldOpen)

View File

@ -138,7 +138,6 @@ 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;
}
@ -1306,13 +1305,11 @@ 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";
@ -1795,6 +1792,15 @@ 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,6 +79,7 @@ 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([
PlacesUtils.virtualAllBookmarksGuid,
PlacesUIUtils.leftPaneQueries.AllBookmarks,
PlacesUtils.bookmarks.virtualMobileGuid,
]);

View File

@ -0,0 +1,71 @@
/* -*- 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,33 +30,98 @@ 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 aFolderGuid
* the item guid of a node in the left pane's tree
* @param aFolderId
* the item ID 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
*/
async function search(aFolderGuid, aSearchStr) {
function search(aFolderId, aSearchStr, aExpectedScopeButtonId) {
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 (aFolderGuid) {
folderTree.selectItems([aFolderGuid]);
Assert.notEqual(folderTree.selectedNode, null,
if (aFolderId) {
folderTree.selectItems([aFolderId]);
isnot(folderTree.selectedNode, null,
"Sanity check: left pane tree should have selection after selecting!");
// 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,
// 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,
"Content tree's folder should be what was selected in the left pane");
}
}
@ -66,14 +131,13 @@ async function search(aFolderGuid, aSearchStr) {
let searchBox = doc.getElementById("searchFilter");
searchBox.value = aSearchStr;
gLibrary.PlacesSearchBox.search(searchBox.value);
let queries = {};
PlacesUtils.history.queryStringToQueries(contentTree.result.root.uri, queries, {}, {});
let query = queryStringToQuery(contentTree.result.root.uri);
if (aSearchStr) {
Assert.equal(queries.value[0].searchTerms, aSearchStr,
"Content tree's searchTerms should be text in search box");
is(query.searchTerms, aSearchStr,
"Content tree's searchTerms should be text in search box");
} else {
Assert.equal(queries.value[0].hasSearchTerms, false,
"Content tree's searchTerms should not exist after search reset");
is(query.hasSearchTerms, false,
"Content tree's searchTerms should not exist after search reset");
}
}
@ -96,15 +160,7 @@ add_task(async function test() {
gLibrary = await promiseLibrary();
const rootsToTest = [
PlacesUtils.virtualAllBookmarksGuid,
PlacesUtils.virtualHistoryGuid,
PlacesUtils.virtualDownloadsGuid,
];
for (let root of rootsToTest) {
await search(root, "dummy");
}
testCases.forEach(aTest => aTest());
await promiseLibraryClosed(gLibrary);

View File

@ -5,6 +5,27 @@ 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,41 +37,58 @@
* 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.
* Ensures that properties for special queries are set on their tree nodes,
* even if PlacesUIUtils.leftPaneFolderId was not initialized.
*/
SimpleTest.waitForExplicitFinish();
function runTest() {
// We need to cache and restore the getters in order to simulate
// Bug 510634.
let leftPaneGetters = new Map([["leftPaneFolderId", null]]);
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:type=${Ci.nsINavHistoryQueryOptions.RESULTS_AS_LEFT_PANE_QUERY}&excludeItems=1&expandQueries=0`;
tree.place = "place:queryType=1&folder=" + leftPaneFolderId;
// The query-property is set on the title column for each row.
let titleColumn = tree.treeBoxObject.columns.getColumnAt(0);
// Open All Bookmarks
tree.selectItems([PlacesUtils.virtualAllBookmarksGuid]);
tree.selectItems([PlacesUIUtils.leftPaneQueries["AllBookmarks"]]);
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");
const topLevelGuids = [
PlacesUtils.virtualHistoryGuid,
PlacesUtils.virtualDownloadsGuid,
PlacesUtils.virtualTagsGuid,
PlacesUtils.virtualAllBookmarksGuid
];
for (let queryName of topLevelGuids) {
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`);
ok(found, "OrganizerQuery_" + queryName + " is set");
}
const folderGuids = [
@ -86,12 +103,15 @@
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,6 +20,9 @@ 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

@ -0,0 +1,149 @@
/* -*- 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,4 +20,5 @@ 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,6 +62,11 @@ 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_allbms_____v) {
treechildren::-moz-tree-image(query, OrganizerQuery_AllBookmarks) {
list-style-image: url("chrome://browser/skin/places/allBookmarks.png");
}
treechildren::-moz-tree-image(query, OrganizerQuery_downloads__v) {
treechildren::-moz-tree-image(query, OrganizerQuery_Downloads) {
list-style-image: url("chrome://browser/skin/places/downloads.png");
}
treechildren::-moz-tree-image(title, query, tagContainer),
treechildren::-moz-tree-image(query, OrganizerQuery_tags_______v) {
treechildren::-moz-tree-image(query, OrganizerQuery_Tags) {
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____v) {
treechildren::-moz-tree-image(query, OrganizerQuery_History) {
list-style-image: url("chrome://browser/skin/places/history.svg");
}

View File

@ -22,8 +22,16 @@ 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;
@ -645,6 +653,8 @@ 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,6 +285,11 @@ 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": [{
@ -295,6 +300,11 @@ 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 displaying bookmark
* folders in the left pane.
* GUIDs associated with virtual queries that are used for display in the left
* pane.
*/
virtualMenuGuid: "menu_______v",
virtualToolbarGuid: "toolbar____v",

View File

@ -1228,13 +1228,6 @@ Database::InitSchema(bool* aDatabaseMigrated)
// Firefox 60 uses schema version 43.
if (currentSchemaVersion < 44) {
rv = MigrateV44Up();
NS_ENSURE_SUCCESS(rv, rv);
}
// Firefox 61 uses schema version 44.
// Schema Upgrades must add migration code here.
// >>> IMPORTANT! <<<
// NEVER MIX UP SYNC AND ASYNC EXECUTION IN MIGRATORS, YOU MAY LOCK THE
@ -1949,130 +1942,6 @@ Database::MigrateV43Up() {
return NS_OK;
}
nsresult
Database::MigrateV44Up() {
// We need to remove any non-builtin roots and their descendants.
// Install a temp trigger to clean up linked tables when the main
// bookmarks are deleted.
nsresult rv = mMainConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
"CREATE TEMP TRIGGER moz_migrate_bookmarks_trigger "
"AFTER DELETE ON moz_bookmarks FOR EACH ROW "
"BEGIN "
// Insert tombstones.
"INSERT OR IGNORE INTO moz_bookmarks_deleted (guid, dateRemoved) "
"VALUES (OLD.guid, strftime('%s', 'now', 'localtime', 'utc') * 1000); "
// Remove old annotations for the bookmarks.
"DELETE FROM moz_items_annos "
"WHERE item_id = OLD.id; "
// Decrease the foreign_count in moz_places.
"UPDATE moz_places "
"SET foreign_count = foreign_count - 1 "
"WHERE id = OLD.fk; "
"END "
));
if (NS_FAILED(rv)) return rv;
// This trigger listens for moz_places deletes, and updates moz_annos and
// moz_keywords accordingly.
rv = mMainConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
"CREATE TEMP TRIGGER moz_migrate_annos_trigger "
"AFTER UPDATE ON moz_places FOR EACH ROW "
// Only remove from moz_places if we don't have any remaining keywords pointing to
// this place, and it hasn't been visited. Note: orphan keywords are tidied up below.
"WHEN NEW.visit_count = 0 AND "
" NEW.foreign_count = (SELECT COUNT(*) FROM moz_keywords WHERE place_id = NEW.id) "
"BEGIN "
// No more references to the place, so we can delete the place itself.
"DELETE FROM moz_places "
"WHERE id = NEW.id; "
// Delete annotations relating to the place.
"DELETE FROM moz_annos "
"WHERE place_id = NEW.id; "
// Delete keywords relating to the place.
"DELETE FROM moz_keywords "
"WHERE place_id = NEW.id; "
"END "
));
if (NS_FAILED(rv)) return rv;
// Listens to moz_keyword deletions, to ensure moz_places gets the
// foreign_count updated corrrectly.
rv = mMainConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
"CREATE TEMP TRIGGER moz_migrate_keyword_trigger "
"AFTER DELETE ON moz_keywords FOR EACH ROW "
"BEGIN "
// If we remove a keyword, then reduce the foreign_count.
"UPDATE moz_places "
"SET foreign_count = foreign_count - 1 "
"WHERE id = OLD.place_id; "
"END "
));
if (NS_FAILED(rv)) return rv;
// First of all, find the non-builtin roots.
nsCOMPtr<mozIStorageStatement> deleteStmt;
rv = mMainConn->CreateStatement(NS_LITERAL_CSTRING(
"WITH RECURSIVE "
"itemsToRemove(id, guid) AS ( "
"SELECT b.id, b.guid FROM moz_bookmarks b "
"JOIN moz_bookmarks p ON b.parent = p.id "
"WHERE p.guid = 'root________' AND "
"b.guid NOT IN ('menu________', 'toolbar_____', 'tags________', 'unfiled_____', 'mobile______') "
"UNION ALL "
"SELECT b.id, b.guid FROM moz_bookmarks b "
"JOIN itemsToRemove d ON d.id = b.parent "
") "
"DELETE FROM moz_bookmarks "
"WHERE id IN (SELECT id FROM itemsToRemove) "
), getter_AddRefs(deleteStmt));
if (NS_FAILED(rv)) return rv;
rv = deleteStmt->Execute();
if (NS_FAILED(rv)) return rv;
// Before we remove the triggers, check for keywords attached to places which
// no longer have a bookmark to them. We do this before removing the triggers,
// so that we can make use of the keyword trigger to update the counts in
// moz_places.
rv = mMainConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
"DELETE FROM moz_keywords WHERE place_id IN ( "
"SELECT h.id FROM moz_keywords k "
"JOIN moz_places h ON h.id = k.place_id "
"GROUP BY place_id HAVING h.foreign_count = count(*) "
")"
));
if (NS_FAILED(rv)) return rv;
// Now remove the temp triggers.
rv = mMainConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
"DROP TRIGGER moz_migrate_bookmarks_trigger "
));
if (NS_FAILED(rv)) return rv;
rv = mMainConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
"DROP TRIGGER moz_migrate_annos_trigger "
));
if (NS_FAILED(rv)) return rv;
rv = mMainConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
"DROP TRIGGER moz_migrate_keyword_trigger "
));
if (NS_FAILED(rv)) return rv;
// Cleanup any orphan annotation attributes.
rv = mMainConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
"DELETE FROM moz_anno_attributes WHERE id IN ( "
"SELECT id FROM moz_anno_attributes n "
"EXCEPT "
"SELECT DISTINCT anno_attribute_id FROM moz_annos "
"EXCEPT "
"SELECT DISTINCT anno_attribute_id FROM moz_items_annos "
")"
));
if (NS_FAILED(rv)) return rv;
return rv;
}
nsresult
Database::GetItemsWithAnno(const nsACString& aAnnoName, int32_t aItemType,
nsTArray<int64_t>& aItemIds)

View File

@ -19,7 +19,7 @@
// This is the schema version. Update it at any schema change and add a
// corresponding migrateVxx method below.
#define DATABASE_SCHEMA_VERSION 44
#define DATABASE_SCHEMA_VERSION 43
// Fired after Places inited.
#define TOPIC_PLACES_INIT_COMPLETE "places-init-complete"
@ -304,7 +304,6 @@ protected:
nsresult MigrateV41Up();
nsresult MigrateV42Up();
nsresult MigrateV43Up();
nsresult MigrateV44Up();
nsresult UpdateBookmarkRootTitles();

View File

@ -118,8 +118,7 @@ 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) &&
!PlacesUtils.isVirtualLeftPaneItem(guid)) {
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;
@ -344,29 +343,6 @@ 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,11 +1079,6 @@ 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_*
@ -1144,7 +1139,7 @@ interface nsINavHistoryQueryOptions : nsISupports
attribute boolean includeHidden;
/**
* This is the maximum number of results that you want. The query is executed,
* This is the maximum number of results that you want. The query is exeucted,
* 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,10 +780,6 @@ 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
@ -839,10 +835,6 @@ 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
@ -1424,7 +1416,6 @@ private:
nsresult SelectAsSite();
nsresult SelectAsTag();
nsresult SelectAsRoots();
nsresult SelectAsLeftPane();
nsresult Where();
nsresult GroupBy();
@ -1531,11 +1522,6 @@ 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");
}
@ -1989,48 +1975,6 @@ 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()
{
@ -3890,8 +3834,7 @@ nsNavHistory::RowToResult(mozIStorageValueArray* aRow,
NS_ENSURE_SUCCESS(rv, rv);
}
if (aOptions->ResultType() == nsNavHistoryQueryOptions::RESULTS_AS_ROOTS_QUERY ||
aOptions->ResultType() == nsNavHistoryQueryOptions::RESULTS_AS_LEFT_PANE_QUERY) {
if (aOptions->ResultType() == nsNavHistoryQueryOptions::RESULTS_AS_ROOTS_QUERY) {
rv = aRow->GetUTF8String(kGetInfoIndex_Guid, guid);
NS_ENSURE_SUCCESS(rv, rv);
}

View File

@ -40,7 +40,6 @@
#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_LEFT_PANE_QUERY)
if (aType > RESULTS_AS_ROOTS_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_LEFT_PANE_QUERY)
aType == RESULTS_AS_ROOTS_QUERY)
mQueryType = QUERY_TYPE_BOOKMARKS;
mResultType = aType;
return NS_OK;
@ -1467,9 +1467,7 @@ 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_LEFT_PANE_QUERY ||
mResultType == RESULTS_AS_ROOTS_QUERY)
mResultType == RESULTS_AS_TAG_QUERY)
return NS_OK;
mQueryType = aQueryType;
return NS_OK;

View File

@ -1813,8 +1813,7 @@ 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_LEFT_PANE_QUERY;
resultType == nsINavHistoryQueryOptions::RESULTS_AS_ROOTS_QUERY;
}
@ -1887,8 +1886,7 @@ 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_LEFT_PANE_QUERY) {
resultType == nsINavHistoryQueryOptions::RESULTS_AS_ROOTS_QUERY) {
*aHasChildren = true;
return NS_OK;
}
@ -2114,13 +2112,6 @@ 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);
@ -2205,17 +2196,12 @@ 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.
// 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 ||
(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
}
(mParent && mParent->IsQuery() &&
mParent->GetAsQuery()->IsContainersQuery())) {
// 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

@ -3,7 +3,10 @@
* 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/. */
const CURRENT_SCHEMA_VERSION = 44;
// It is expected that the test files importing this file define Cu etc.
/* global Cu, Ci, Cc, Cr */
const CURRENT_SCHEMA_VERSION = 43;
const FIRST_UPGRADABLE_SCHEMA_VERSION = 30;
const NS_APP_USER_PROFILE_50_DIR = "ProfD";

View File

@ -1,210 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
const EXPECTED_REMAINING_ROOTS = [
...PlacesUtils.bookmarks.userContentRoots,
PlacesUtils.bookmarks.tagsGuid,
];
const EXPECTED_REMOVED_BOOKMARK_GUIDS = [
// These first ones are the old left-pane folder queries
"SNLmwJH6GtW9", // Root Query
"r0dY_2_y4mlx", // History
"xGGhZK3b6GnW", // Downloads
"EJG6I1nKkQFQ", // Tags
"gSyHo5oNSUJV", // All Bookmarks
// These are simulated add-on injections that we expect to be removed.
"exaddon_____",
"exaddon1____",
"exaddon2____",
"exaddon3____",
];
const EXPECTED_REMOVED_ANNOTATIONS = [
"PlacesOrganizer/OrganizerFolder",
"PlacesOrganizer/OrganizerQuery",
];
const EXPECTED_REMOVED_PLACES_ENTRIES = ["exaddonh____", "exaddonh3___"];
const EXPECTED_KEPT_PLACES_ENTRY = "exaddonh2___";
const EXPECTED_REMOVED_KEYWORDS = ["exaddon", "exaddon2"];
async function assertItemIn(db, table, field, expectedItems) {
let rows = await db.execute(`SELECT ${field} from ${table}`);
Assert.ok(rows.length >= expectedItems.length,
"Should be at least the number of annotations we expect to be removed.");
let fieldValues = rows.map(row => row.getResultByName(field));
for (let item of expectedItems) {
Assert.ok(fieldValues.includes(item),
`${table} should have ${expectedItems}`);
}
}
add_task(async function setup() {
await setupPlacesDatabase("places_v43.sqlite");
// Setup database contents to be migrated.
let path = OS.Path.join(OS.Constants.Path.profileDir, DB_FILENAME);
let db = await Sqlite.openConnection({ path });
let rows = await db.execute(`SELECT * FROM moz_bookmarks_deleted`);
Assert.equal(rows.length, 0, "Should be nothing in moz_bookmarks_deleted");
await assertItemIn(db, "moz_anno_attributes", "name", EXPECTED_REMOVED_ANNOTATIONS);
await assertItemIn(db, "moz_bookmarks", "guid", EXPECTED_REMOVED_BOOKMARK_GUIDS);
await assertItemIn(db, "moz_keywords", "keyword", EXPECTED_REMOVED_KEYWORDS);
await assertItemIn(db, "moz_places", "guid", EXPECTED_REMOVED_PLACES_ENTRIES);
await db.close();
});
add_task(async function database_is_valid() {
// Accessing the database for the first time triggers migration.
Assert.equal(PlacesUtils.history.databaseStatus,
PlacesUtils.history.DATABASE_STATUS_UPGRADED);
let db = await PlacesUtils.promiseDBConnection();
Assert.equal((await db.getSchemaVersion()), CURRENT_SCHEMA_VERSION);
});
add_task(async function test_roots_removed() {
let db = await PlacesUtils.promiseDBConnection();
let rows = await db.execute(`
SELECT id FROM moz_bookmarks
WHERE guid = :guid
`, {guid: PlacesUtils.bookmarks.rootGuid});
Assert.equal(rows.length, 1, "Should have exactly one root row.");
let rootId = rows[0].getResultByName("id");
rows = await db.execute(`
SELECT guid FROM moz_bookmarks
WHERE parent = :rootId`, {rootId});
Assert.equal(rows.length, EXPECTED_REMAINING_ROOTS.length,
"Should only have the built-in folder roots.");
for (let row of rows) {
let guid = row.getResultByName("guid");
Assert.ok(EXPECTED_REMAINING_ROOTS.includes(guid),
`Should have only the expected guids remaining, unexpected guid: ${guid}`);
}
});
add_task(async function test_tombstones_added() {
let db = await PlacesUtils.promiseDBConnection();
let rows = await db.execute(`
SELECT guid FROM moz_bookmarks_deleted
`);
for (let row of rows) {
let guid = row.getResultByName("guid");
Assert.ok(EXPECTED_REMOVED_BOOKMARK_GUIDS.includes(guid),
`Should have tombstoned the expected guids, unexpected guid: ${guid}`);
}
Assert.equal(rows.length, EXPECTED_REMOVED_BOOKMARK_GUIDS.length,
"Should have removed all the expected bookmarks.");
});
add_task(async function test_annotations_removed() {
let db = await PlacesUtils.promiseDBConnection();
for (let anno of EXPECTED_REMOVED_ANNOTATIONS) {
let rows = await db.execute(`
SELECT id FROM moz_anno_attributes
WHERE name = :anno
`, {anno});
Assert.equal(rows.length, 0, `${anno} should not exist in the database`);
}
});
add_task(async function test_check_history_entries() {
let db = await PlacesUtils.promiseDBConnection();
for (let entry of EXPECTED_REMOVED_PLACES_ENTRIES) {
let rows = await db.execute(`
SELECT id FROM moz_places
WHERE guid = '${entry}'`);
Assert.equal(rows.length, 0,
`Should have removed an orphaned history entry ${EXPECTED_REMOVED_PLACES_ENTRIES}.`);
}
let rows = await db.execute(`
SELECT foreign_count FROM moz_places
WHERE guid = :guid
`, {guid: EXPECTED_KEPT_PLACES_ENTRY});
Assert.equal(rows.length, 1,
`Should have kept visited history entry ${EXPECTED_KEPT_PLACES_ENTRY}`);
let foreignCount = rows[0].getResultByName("foreign_count");
Assert.equal(foreignCount, 0,
`Should have updated the foreign_count for ${EXPECTED_KEPT_PLACES_ENTRY}`);
});
add_task(async function test_check_keyword_removed() {
let db = await PlacesUtils.promiseDBConnection();
for (let keyword of EXPECTED_REMOVED_KEYWORDS) {
let rows = await db.execute(`
SELECT keyword FROM moz_keywords
WHERE keyword = :keyword
`, {keyword});
Assert.equal(rows.length, 0,
`Should have removed the expected keyword: ${keyword}.`);
}
});
add_task(async function test_no_orphan_annotations() {
let db = await PlacesUtils.promiseDBConnection();
let rows = await db.execute(`
SELECT item_id FROM moz_items_annos
WHERE item_id NOT IN (SELECT id from moz_bookmarks)
`);
Assert.equal(rows.length, 0,
`Should have no orphan annotations.`);
rows = await db.execute(`
SELECT id FROM moz_anno_attributes
WHERE id NOT IN (SELECT id from moz_items_annos)
`);
Assert.equal(rows.length, 0,
`Should have no orphan annotation attributes.`);
});
add_task(async function test_mobile_bookmarks_root_still_exists() {
let db = await PlacesUtils.promiseDBConnection();
let rows = await db.execute(`
SELECT id FROM moz_anno_attributes
WHERE name = 'mobile/bookmarksRoot'
`);
Assert.equal(rows.length, 1,
"Mobile bookmarks root annotation should still exist");
});
add_task(async function test_no_orphan_keywords() {
let db = await PlacesUtils.promiseDBConnection();
let rows = await db.execute(`
SELECT place_id FROM moz_keywords
WHERE place_id NOT IN (SELECT id from moz_places)
`);
Assert.equal(rows.length, 0,
`Should have no orphan keywords.`);
});

View File

@ -10,7 +10,6 @@ support-files =
places_v36.sqlite
places_v38.sqlite
places_v42.sqlite
places_v43.sqlite
[test_current_from_downgraded.js]
[test_current_from_outdated.js]
@ -22,4 +21,3 @@ support-files =
[test_current_from_v38.js]
[test_current_from_v41.js]
[test_current_from_v42.js]
[test_current_from_v43.js]

View File

@ -1,64 +0,0 @@
"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,7 +17,6 @@ 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,9 +7,6 @@ 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