Bug 1421664 - 3 - Fix the UI and tests. r=standard8

test_419731.js can be removed since it is already covered by browser_bookmarkProperties_editTagContainer.js

MozReview-Commit-ID: K0LFuTptWyW

--HG--
extra : rebase_source : 10066aa0bdb6a598fc6af638fed455d58422b7fb
This commit is contained in:
Marco Bonardo 2018-03-22 14:13:07 +01:00
Родитель 66117a6abe
Коммит d666e0fda1
15 изменённых файлов: 285 добавлений и 218 удалений

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

@ -205,10 +205,7 @@ PlacesViewBase.prototype = {
container = selectedNode.parent;
index = container.getChildIndex(selectedNode);
if (PlacesUtils.nodeIsTagQuery(container)) {
tagName = container.title;
// TODO (Bug 1160193): properly support dropping on a tag root.
if (!tagName)
return null;
tagName = PlacesUtils.asQuery(container).query.tags[0];
}
}
}

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

@ -175,7 +175,8 @@ PlacesController.prototype = {
Ci.nsINavHistoryQueryOptions.SORT_BY_NONE;
case "placesCmd_show:info": {
let selectedNode = this._view.selectedNode;
return selectedNode && PlacesUtils.getConcreteItemId(selectedNode) != -1;
return selectedNode && (PlacesUtils.nodeIsTagQuery(selectedNode) ||
PlacesUtils.getConcreteItemId(selectedNode) != -1);
}
case "placesCmd_reload": {
// Livemark containers
@ -807,18 +808,27 @@ PlacesController.prototype = {
if (PlacesUtils.nodeIsTagQuery(node.parent)) {
// This is a uri node inside a tag container. It needs a special
// untag transaction.
let tag = node.parent.title;
let tag = node.parent.title || "";
if (!tag) {
// TODO: Bug 1432405 Try using getConcreteItemGuid.
let tagItemId = PlacesUtils.getConcreteItemId(node.parent);
let tagGuid = await PlacesUtils.promiseItemGuid(tagItemId);
tag = (await PlacesUtils.bookmarks.fetch(tagGuid)).title;
// The parent may be the root node, that doesn't have a title.
// Until we fix bug 1293445, we have two ways to get tags:
if (node.parent.queryOptions.resultType ==
Ci.nsINavHistoryQueryOptions.RESULTS_AS_TAG_CONTENTS) {
// Get the tag using the bookmarks API.
let tagItemId = PlacesUtils.getConcreteItemId(node.parent);
let tagGuid = await PlacesUtils.promiseItemGuid(tagItemId);
tag = (await PlacesUtils.bookmarks.fetch(tagGuid)).title;
} else {
// Extract the tag from the query itself.
tag = node.parent.query.tags[0];
}
}
transactions.push(PlacesTransactions.Untag({ urls: [node.uri], tag }));
} else if (PlacesUtils.nodeIsTagQuery(node) && node.parent &&
PlacesUtils.nodeIsQuery(node.parent) &&
PlacesUtils.asQuery(node.parent).queryOptions.resultType ==
Ci.nsINavHistoryQueryOptions.RESULTS_AS_TAG_QUERY) {
} else if (PlacesUtils.nodeIsTagQuery(node) &&
node.parent &&
PlacesUtils.nodeIsQuery(node.parent) &&
PlacesUtils.asQuery(node.parent).queryOptions.resultType ==
Ci.nsINavHistoryQueryOptions.RESULTS_AS_TAG_QUERY) {
// This is a tag container.
// Untag all URIs tagged with this tag only if the tag container is
// child of the "Tags" query in the library, in all other places we
@ -827,16 +837,16 @@ PlacesController.prototype = {
let URIs = PlacesUtils.tagging.getURIsForTag(tag);
transactions.push(PlacesTransactions.Untag({ tag, urls: URIs }));
} else if (PlacesUtils.nodeIsURI(node) &&
PlacesUtils.nodeIsQuery(node.parent) &&
PlacesUtils.asQuery(node.parent).queryOptions.queryType ==
Ci.nsINavHistoryQueryOptions.QUERY_TYPE_HISTORY) {
PlacesUtils.nodeIsQuery(node.parent) &&
PlacesUtils.asQuery(node.parent).queryOptions.queryType ==
Ci.nsINavHistoryQueryOptions.QUERY_TYPE_HISTORY) {
// This is a uri node inside an history query.
PlacesUtils.history.remove(node.uri).catch(Cu.reportError);
// History deletes are not undoable, so we don't have a transaction.
} else if (node.itemId == -1 &&
PlacesUtils.nodeIsQuery(node) &&
PlacesUtils.asQuery(node).queryOptions.queryType ==
Ci.nsINavHistoryQueryOptions.QUERY_TYPE_HISTORY) {
PlacesUtils.nodeIsQuery(node) &&
PlacesUtils.asQuery(node).queryOptions.queryType ==
Ci.nsINavHistoryQueryOptions.QUERY_TYPE_HISTORY) {
// This is a dynamically generated history query, like queries
// grouped by site, time or both. Dynamically generated queries don't
// have an itemId even if they are descendants of a bookmark.

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

@ -33,13 +33,12 @@ var gEditItemOverlay = {
let isFolderShortcut = isItem &&
node.type == Ci.nsINavHistoryResultNode.RESULT_TYPE_FOLDER_SHORTCUT;
let isTag = node && PlacesUtils.nodeIsTagQuery(node);
let tag = null;
if (isTag) {
itemId = PlacesUtils.getConcreteItemId(node);
// For now we don't have access to the item guid synchronously for tags,
// so we'll need to fetch it later.
tag = PlacesUtils.asQuery(node).query.tags.length == 1 ? node.query.tags[0] : node.title;
}
let isURI = node && PlacesUtils.nodeIsURI(node);
let uri = isURI ? Services.io.newURI(node.uri) : null;
let uri = isURI || isTag ? Services.io.newURI(node.uri) : null;
let title = node ? node.title : null;
let isBookmark = isItem && isURI;
let bulkTagging = !node;
@ -68,7 +67,7 @@ var gEditItemOverlay = {
isBookmark, isFolderShortcut, isParentReadOnly,
bulkTagging, uris,
visibleRows, postData, isTag, focusedElement,
onPanelReady };
onPanelReady, tag };
},
get initialized() {
@ -77,7 +76,7 @@ var gEditItemOverlay = {
// Backwards-compatibility getters
get itemId() {
if (!this.initialized || this._paneInfo.bulkTagging)
if (!this.initialized || this._paneInfo.isTag || this._paneInfo.bulkTagging)
return -1;
return this._paneInfo.itemId;
},
@ -120,7 +119,7 @@ var gEditItemOverlay = {
throw new Error("_initNamePicker called unexpectedly");
// title may by null, which, for us, is the same as an empty string.
this._initTextField(this._namePicker, this._paneInfo.title || "");
this._initTextField(this._namePicker, this._paneInfo.title || this._paneInfo.tag || "");
},
_initLocationField() {
@ -607,18 +606,28 @@ var gEditItemOverlay = {
return;
// Here we update either the item title or its cached static title
let newTitle = this._namePicker.value;
if (!newTitle && this._paneInfo.isTag) {
// We don't allow setting an empty title for a tag, restore the old one.
this._initNamePicker();
} else {
this._mayUpdateFirstEditField("namePicker");
let guid = this._paneInfo.isTag
? (await PlacesUtils.promiseItemGuid(this._paneInfo.itemId))
: this._paneInfo.itemGuid;
await PlacesTransactions.EditTitle({ guid, title: newTitle }).transact();
if (this._paneInfo.isTag) {
let tag = this._namePicker.value;
if (!tag || tag.includes("&")) {
// We don't allow setting an empty title for a tag, restore the old one.
this._initNamePicker();
return;
}
// Get all the bookmarks for the old tag, tag them with the new tag, and
// untag them from the old tag.
let oldTag = this._paneInfo.tag;
this._paneInfo.tag = tag;
let title = this._paneInfo.title;
if (title == oldTag) {
this._paneInfo.title = tag;
}
await PlacesTransactions.RenameTag({ oldTag, tag }).transact();
return;
}
this._mayUpdateFirstEditField("namePicker");
await PlacesTransactions.EditTitle({ guid: this._paneInfo.itemGuid,
title: this._namePicker.value }).transact();
},
onDescriptionFieldChange() {
@ -1020,8 +1029,8 @@ var gEditItemOverlay = {
}
},
_onItemTitleChange(aItemId, aNewTitle) {
if (aItemId == this._paneInfo.itemId) {
_onItemTitleChange(aItemId, aNewTitle, aGuid) {
if (aItemId == this._paneInfo.itemId || aGuid == this._paneInfo.itemGuid) {
this._paneInfo.title = aNewTitle;
this._initTextField(this._namePicker, aNewTitle);
} else if (this._paneInfo.visibleRows.has("folderRow")) {
@ -1056,7 +1065,7 @@ var gEditItemOverlay = {
}
if (aProperty == "title" && (this._paneInfo.isItem || this._paneInfo.isTag)) {
// This also updates titles of folders in the folder menu list.
this._onItemTitleChange(aItemId, aValue);
this._onItemTitleChange(aItemId, aValue, aGuid);
return;
}
@ -1125,7 +1134,6 @@ var gEditItemOverlay = {
onItemVisited() { },
};
for (let elt of ["folderMenuList", "folderTree", "namePicker",
"locationField", "descriptionField", "keywordField",
"tagsField", "loadInSidebarCheckbox"]) {

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

@ -83,8 +83,8 @@
// We do not yet support searching into grouped queries or into
// tag containers, so we must fall to the default case.
if (PlacesUtils.nodeIsHistoryContainer(queryNode) ||
PlacesUtils.nodeIsTagQuery(queryNode) ||
options.resultType == options.RESULTS_AS_TAG_QUERY ||
options.resultType == options.RESULTS_AS_TAG_CONTENTS ||
options.resultType == options.RESULTS_AS_ROOTS_QUERY)
options.resultType = options.RESULTS_AS_URI;
@ -528,13 +528,8 @@
if (this.controller.disallowInsertion(container))
return null;
// TODO (Bug 1160193): properly support dropping on a tag root.
let tagName = null;
if (PlacesUtils.nodeIsTagQuery(container)) {
tagName = container.title;
if (!tagName)
return null;
}
let tagName = PlacesUtils.nodeIsTagQuery(container) ?
PlacesUtils.asQuery(container).query.tags[0] : null;
return new PlacesInsertionPoint({
parentId: PlacesUtils.getConcreteItemId(container),

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

@ -1351,8 +1351,8 @@ PlacesTreeView.prototype = {
// Treat non-expandable childless queries as non-containers, unless they
// are tags.
if (PlacesUtils.nodeIsQuery(node) && !PlacesUtils.nodeIsTagQuery(node)) {
PlacesUtils.asQuery(node);
return node.queryOptions.expandQueries || node.hasChildren;
return PlacesUtils.asQuery(node).queryOptions.expandQueries ||
node.hasChildren;
}
return true;
},
@ -1467,13 +1467,8 @@ PlacesTreeView.prototype = {
if (this._controller.disallowInsertion(container))
return null;
// TODO (Bug 1160193): properly support dropping on a tag root.
let tagName = null;
if (PlacesUtils.nodeIsTagQuery(container)) {
tagName = container.title;
if (!tagName)
return null;
}
let tagName = PlacesUtils.nodeIsTagQuery(container) ?
PlacesUtils.asQuery(container).query.tags[0] : null;
return new PlacesInsertionPoint({
parentId: PlacesUtils.getConcreteItemId(container),

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

@ -31,8 +31,11 @@ add_task(async function() {
Assert.ok(tree.controller.isCommandEnabled("placesCmd_show:info"),
"'placesCmd_show:info' on current selected node is enabled");
let promiseTitleResetNotification = PlacesTestUtils.waitForNotification(
"onItemChanged", (itemId, prop, isAnno, val) => prop == "title" && val == "tag1");
let promiseTagResetNotification = PlacesTestUtils.waitForNotification(
"onItemChanged", (itemId, prop) => {
let tags = PlacesUtils.tagging.getTagsForURI(uri);
return prop == "tags" && tags.length == 1 && tags[0] == "tag1";
});
await withBookmarksDialog(
true,
@ -47,22 +50,24 @@ add_task(async function() {
let namepicker = dialogWin.document.getElementById("editBMPanel_namePicker");
Assert.ok(!namepicker.readOnly, "Name field should not be read-only");
Assert.equal(namepicker.value, "tag1", "Node title is correct");
let promiseTitleChangeNotification = PlacesTestUtils.waitForNotification(
"onItemChanged", (itemId, prop, isAnno, val) => prop == "title" && val == "tag2");
let promiseTagChangeNotification = PlacesTestUtils.waitForNotification(
"onItemChanged", (itemId, prop) => {
let tags = PlacesUtils.tagging.getTagsForURI(uri);
return prop == "tags" && tags.length == 1 && tags[0] == "tag2";
});
fillBookmarkTextField("editBMPanel_namePicker", "tag2", dialogWin);
await promiseTitleChangeNotification;
await promiseTagChangeNotification;
Assert.equal(namepicker.value, "tag2", "Node title has been properly edited");
Assert.equal(namepicker.value, "tag2", "The title field has been changed");
// Check the shortcut's title.
Assert.equal(tree.selectedNode.title, "tag2", "The node has the correct title");
// Try to set an empty title, it should restore the previous one.
fillBookmarkTextField("editBMPanel_namePicker", "", dialogWin);
Assert.equal(namepicker.value, "tag2", "Title has not been changed");
Assert.equal(namepicker.value, "tag2", "The title field has been changed");
Assert.equal(tree.selectedNode.title, "tag2", "The node has the correct title");
// Check the tags have been edited.
@ -75,7 +80,7 @@ add_task(async function() {
}
);
await promiseTitleResetNotification;
await promiseTagResetNotification;
// Check the tag change has been reverted.
let tags = PlacesUtils.tagging.getTagsForURI(uri);

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

@ -116,14 +116,12 @@ function synthesizeDragWithDirection(aElement, aExpectedDragData, aDirection) {
function getToolbarNodeForItemId(itemGuid) {
var children = document.getElementById("PlacesToolbarItems").childNodes;
var node = null;
for (var i = 0; i < children.length; i++) {
if (itemGuid == children[i]._placesNode.bookmarkGuid) {
node = children[i];
break;
for (let child of children) {
if (itemGuid == child._placesNode.bookmarkGuid) {
return child;
}
}
return node;
return null;
}
function getExpectedDataForPlacesNode(aNode) {

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

@ -36,8 +36,8 @@ add_task(async function test_tags() {
children,
});
for (let i = 0; i < uris.length; i++) {
PlacesUtils.tagging.tagURI(uris[i], ["test"]);
for (let uri of uris) {
PlacesUtils.tagging.tagURI(uri, ["test"]);
}
let library = await promiseLibrary();

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

@ -908,7 +908,7 @@ DefineTransaction.defineInputProps(["guid", "parentGuid", "newParentGuid"],
DefineTransaction.guidValidate);
DefineTransaction.defineInputProps(["title", "postData"],
DefineTransaction.strOrNullValidate, null);
DefineTransaction.defineInputProps(["keyword", "oldKeyword", "tag",
DefineTransaction.defineInputProps(["keyword", "oldKeyword", "oldTag", "tag",
"excludingAnnotation"],
DefineTransaction.strValidate, "");
DefineTransaction.defineInputProps(["index", "newIndex"],
@ -1623,6 +1623,96 @@ PT.Untag.prototype = {
}
};
/**
* Transaction for renaming a tag.
*
* Required Input Properties: oldTag, tag.
*/
PT.RenameTag = DefineTransaction(["oldTag", "tag"]);
PT.RenameTag.prototype = {
async execute({ oldTag, tag }) {
// For now this is implemented by untagging and tagging all the bookmarks.
// We should create a specialized bookmarking API to just rename the tag.
let onUndo = [], onRedo = [];
let urls = PlacesUtils.tagging.getURIsForTag(oldTag);
if (urls.length > 0) {
let tagTxn = TransactionsHistory.getRawTransaction(
PT.Tag({ urls, tags: [tag] })
);
await tagTxn.execute();
onUndo.unshift(tagTxn.undo.bind(tagTxn));
onRedo.push(tagTxn.redo.bind(tagTxn));
let untagTxn = TransactionsHistory.getRawTransaction(
PT.Untag({ urls, tags: [oldTag] })
);
await untagTxn.execute();
onUndo.unshift(untagTxn.undo.bind(untagTxn));
onRedo.push(untagTxn.redo.bind(untagTxn));
// Update all the place: queries that refer to this tag.
let db = await PlacesUtils.promiseDBConnection();
let rows = await db.executeCached(`
SELECT h.url, b.guid, b.title
FROM moz_places h
JOIN moz_bookmarks b ON b.fk = h.id
WHERE url_hash BETWEEN hash("place", "prefix_lo")
AND hash("place", "prefix_hi")
AND url LIKE :tagQuery
`, { tagQuery: "%tag=%" });
for (let row of rows) {
let url = row.getResultByName("url");
try {
url = new URL(url);
let urlParams = new URLSearchParams(url.pathname);
let tags = urlParams.getAll("tag");
if (!tags.includes(oldTag))
continue;
if (tags.length > 1) {
// URLSearchParams cannot set more than 1 same-named param.
urlParams.delete("tag");
urlParams.set("tag", tag);
url = new URL(url.protocol + urlParams +
"&tag=" + tags.filter(t => t != oldTag).join("&tag="));
} else {
urlParams.set("tag", tag);
url = new URL(url.protocol + urlParams);
}
} catch (ex) {
Cu.reportError("Invalid bookmark url: " + row.getResultByName("url") + ": " + ex);
continue;
}
let guid = row.getResultByName("guid");
let title = row.getResultByName("title");
let editUrlTxn = TransactionsHistory.getRawTransaction(
PT.EditUrl({ guid, url })
);
await editUrlTxn.execute();
onUndo.unshift(editUrlTxn.undo.bind(editUrlTxn));
onRedo.push(editUrlTxn.redo.bind(editUrlTxn));
if (title == oldTag) {
let editTitleTxn = TransactionsHistory.getRawTransaction(
PT.EditTitle({ guid, title: tag })
);
await editTitleTxn.execute();
onUndo.unshift(editTitleTxn.undo.bind(editTitleTxn));
onRedo.push(editTitleTxn.redo.bind(editTitleTxn));
}
}
}
this.undo = async function() {
for (let f of onUndo) {
await f();
}
};
this.redo = async function() {
for (let f of onRedo) {
await f();
}
};
}
};
/**
* Transaction for copying an item.
*

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

@ -782,9 +782,20 @@ var PlacesUtils = {
* @returns true if the node is a tag container, false otherwise
*/
nodeIsTagQuery: function PU_nodeIsTagQuery(aNode) {
return aNode.type == Ci.nsINavHistoryResultNode.RESULT_TYPE_QUERY &&
asQuery(aNode).queryOptions.resultType ==
Ci.nsINavHistoryQueryOptions.RESULTS_AS_TAG_CONTENTS;
if (aNode.type != Ci.nsINavHistoryResultNode.RESULT_TYPE_QUERY)
return false;
// Direct child of RESULTS_AS_TAG_QUERY.
let parent = aNode.parent;
if (parent && PlacesUtils.asQuery(parent).queryOptions.resultType ==
Ci.nsINavHistoryQueryOptions.RESULTS_AS_TAG_QUERY)
return true;
// We must also support the right pane of the Library, when the tag query
// is the root node. Unfortunately this is also valid for any tag query
// selected in the left pane that is not a direct child of RESULTS_AS_TAG_QUERY.
if (!parent && aNode == aNode.parentResult.root &&
PlacesUtils.asQuery(aNode).query.tags.length == 1)
return true;
return false;
},
/**
@ -822,15 +833,8 @@ var PlacesUtils = {
* node.itemId, but for folder-shortcuts that's node.folderItemId.
*/
getConcreteItemId: function PU_getConcreteItemId(aNode) {
if (aNode.type == Ci.nsINavHistoryResultNode.RESULT_TYPE_FOLDER_SHORTCUT)
return asQuery(aNode).folderItemId;
else if (PlacesUtils.nodeIsTagQuery(aNode)) {
// RESULTS_AS_TAG_CONTENTS queries are similar to folder shortcuts
// so we can still get the concrete itemId for them.
let folders = aNode.query.getFolders();
return folders[0];
}
return aNode.itemId;
return aNode.type == Ci.nsINavHistoryResultNode.RESULT_TYPE_FOLDER_SHORTCUT ?
asQuery(aNode).folderItemId : aNode.itemId;
},
/**

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

@ -1130,7 +1130,8 @@ class PlacesSQLQueryBuilder
{
public:
PlacesSQLQueryBuilder(const nsCString& aConditions,
nsNavHistoryQueryOptions* aOptions,
const RefPtr<nsNavHistoryQuery>& aQuery,
const RefPtr<nsNavHistoryQueryOptions>& aOptions,
bool aUseLimit,
nsNavHistory::StringHash& aAddParams,
bool aHasSearchTerms);
@ -1173,12 +1174,14 @@ private:
nsCString mGroupBy;
bool mHasDateColumns;
bool mSkipOrderBy;
nsNavHistory::StringHash& mAddParams;
};
PlacesSQLQueryBuilder::PlacesSQLQueryBuilder(
const nsCString& aConditions,
nsNavHistoryQueryOptions* aOptions,
const RefPtr<nsNavHistoryQuery>& aQuery,
const RefPtr<nsNavHistoryQueryOptions>& aOptions,
bool aUseLimit,
nsNavHistory::StringHash& aAddParams,
bool aHasSearchTerms)
@ -1194,6 +1197,11 @@ PlacesSQLQueryBuilder::PlacesSQLQueryBuilder(
, mAddParams(aAddParams)
{
mHasDateColumns = (mQueryType == nsINavHistoryQueryOptions::QUERY_TYPE_BOOKMARKS);
// Force the default sorting mode for tag queries.
if (mSortingMode == nsINavHistoryQueryOptions::SORT_BY_NONE &&
aQuery->Tags().Length() > 0) {
mSortingMode = nsINavHistoryQueryOptions::SORT_BY_TITLE_ASCENDING;
}
}
nsresult
@ -1654,6 +1662,9 @@ PlacesSQLQueryBuilder::SelectAsTag()
// other history queries.
mHasDateColumns = true;
// TODO (Bug 1449939): This is likely wrong, since the tag name should
// probably be urlencoded, and we have no util for that in SQL, yet.
// We could encode the tag when the user sets it though.
mQueryString = nsPrintfCString(
"SELECT null, 'place:tag=' || title, "
"title, null, null, null, null, null, dateAdded, "
@ -2019,7 +2030,7 @@ nsNavHistory::ConstructQueryString(
// using FilterResultSet()
bool useLimitClause = !NeedToFilterResultSet(aQuery, aOptions);
PlacesSQLQueryBuilder queryStringBuilder(conditions, aOptions,
PlacesSQLQueryBuilder queryStringBuilder(conditions, aQuery, aOptions,
useLimitClause, aAddParams,
hasSearchTerms);
rv = queryStringBuilder.GetQueryString(queryString);
@ -3040,8 +3051,9 @@ nsNavHistory::QueryToSelectClause(const RefPtr<nsNavHistoryQuery>& aQuery,
clause.Str(",");
}
clause.Str(")");
if (!aQuery->TagsAreNot())
if (!aQuery->TagsAreNot()) {
clause.Str("GROUP BY bms.fk HAVING count(*) >=").Param(":tag_count");
}
clause.Str(")");
}

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

@ -1,123 +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/. */
add_task(async function test_tag_updates() {
const url = "http://foo.bar/";
let lastModified = new Date(Date.now() - 10000);
// create 2 bookmarks
let bm1 = await PlacesUtils.bookmarks.insert({
parentGuid: PlacesUtils.bookmarks.menuGuid,
dateAdded: lastModified,
lastModified: new Date(),
title: "title 1",
url,
});
let bm2 = await PlacesUtils.bookmarks.insert({
parentGuid: PlacesUtils.bookmarks.menuGuid,
dateAdded: lastModified,
lastModified,
title: "title 2",
url,
});
// add a new tag
PlacesUtils.tagging.tagURI(uri(url), ["foo"]);
// get tag folder id
let options = PlacesUtils.history.getNewQueryOptions();
let query = PlacesUtils.history.getNewQuery();
query.setFolders([PlacesUtils.tagsFolderId], 1);
let result = PlacesUtils.history.executeQuery(query, options);
let tagRoot = result.root;
tagRoot.containerOpen = true;
let tagNode = tagRoot.getChild(0)
.QueryInterface(Ci.nsINavHistoryContainerResultNode);
let tagItemGuid = tagNode.bookmarkGuid;
let tagItemId = tagNode.itemId;
tagRoot.containerOpen = false;
// change bookmark 1 title
await PlacesUtils.bookmarks.update({
guid: bm1.guid,
title: "new title 1",
});
// Workaround timers resolution and time skews.
bm2 = await PlacesUtils.bookmarks.fetch(bm2.guid);
lastModified = new Date(lastModified.getTime() + 1000);
await PlacesUtils.bookmarks.update({
guid: bm1.guid,
lastModified,
});
// Query the tag.
options = PlacesUtils.history.getNewQueryOptions();
options.queryType = Ci.nsINavHistoryQueryOptions.QUERY_TYPE_BOOKMARKS;
options.resultType = options.RESULTS_AS_TAG_QUERY;
query = PlacesUtils.history.getNewQuery();
result = PlacesUtils.history.executeQuery(query, options);
let root = result.root;
root.containerOpen = true;
Assert.equal(root.childCount, 1);
let theTag = root.getChild(0)
.QueryInterface(Ci.nsINavHistoryContainerResultNode);
// Bug 524219: Check that renaming the tag shows up in the result.
Assert.equal(theTag.title, "foo");
await PlacesUtils.bookmarks.update({
guid: tagItemGuid,
title: "bar",
});
// Check that the item has been replaced
Assert.notEqual(theTag, root.getChild(0));
theTag = root.getChild(0)
.QueryInterface(Ci.nsINavHistoryContainerResultNode);
Assert.equal(theTag.title, "bar");
// Check that tag container contains new title
theTag.containerOpen = true;
Assert.equal(theTag.childCount, 1);
let node = theTag.getChild(0);
Assert.equal(node.title, "new title 1");
theTag.containerOpen = false;
root.containerOpen = false;
// Change bookmark 2 title.
PlacesUtils.bookmarks.update({
guid: bm2.guid,
title: "new title 2"
});
// Workaround timers resolution and time skews.
lastModified = new Date(lastModified.getTime() + 1000);
await PlacesUtils.bookmarks.update({
guid: bm2.guid,
lastModified,
});
// Check that tag container contains new title
options = PlacesUtils.history.getNewQueryOptions();
options.queryType = Ci.nsINavHistoryQueryOptions.QUERY_TYPE_BOOKMARKS;
options.resultType = options.RESULTS_AS_TAG_CONTENTS;
query = PlacesUtils.history.getNewQuery();
query.setFolders([tagItemId], 1);
result = PlacesUtils.history.executeQuery(query, options);
root = result.root;
root.containerOpen = true;
Assert.equal(root.childCount, 1);
node = root.getChild(0);
Assert.equal(node.title, "new title 2");
root.containerOpen = false;
});

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

@ -1859,3 +1859,67 @@ add_task(async function test_remove_multiple() {
await PT.clearTransactionsHistory();
observer.reset();
});
add_task(async function test_renameTag() {
let url = "http://test.edit.keyword/";
await PT.Tag({ url, tags: ["t1", "t2"] }).transact();
ensureTagsForURI(url, ["t1", "t2"]);
// Create bookmark queries that point to the modified tag.
let bm1 = await PlacesUtils.bookmarks.insert({
url: "place:tag=t2",
parentGuid: PlacesUtils.bookmarks.unfiledGuid
});
let bm2 = await PlacesUtils.bookmarks.insert({
url: "place:tag=t2&sort=1",
parentGuid: PlacesUtils.bookmarks.unfiledGuid
});
// This points to 2 tags, and as such won't be touched.
let bm3 = await PlacesUtils.bookmarks.insert({
url: "place:tag=t2&tag=t1",
parentGuid: PlacesUtils.bookmarks.unfiledGuid
});
await PT.RenameTag({ oldTag: "t2", tag: "t3" }).transact();
ensureTagsForURI(url, ["t1", "t3"]);
Assert.equal((await PlacesUtils.bookmarks.fetch(bm1.guid)).url.href, "place:tag=t3",
"The fitst bookmark has been updated");
Assert.equal((await PlacesUtils.bookmarks.fetch(bm2.guid)).url.href, "place:tag=t3&sort=1",
"The second bookmark has been updated");
Assert.equal((await PlacesUtils.bookmarks.fetch(bm3.guid)).url.href, "place:tag=t3&tag=t1",
"The third bookmark has been updated");
await PT.undo();
ensureTagsForURI(url, ["t1", "t2"]);
Assert.equal((await PlacesUtils.bookmarks.fetch(bm1.guid)).url.href, "place:tag=t2",
"The fitst bookmark has been restored");
Assert.equal((await PlacesUtils.bookmarks.fetch(bm2.guid)).url.href, "place:tag=t2&sort=1",
"The second bookmark has been restored");
Assert.equal((await PlacesUtils.bookmarks.fetch(bm3.guid)).url.href, "place:tag=t2&tag=t1",
"The third bookmark has been restored");
await PT.redo();
ensureTagsForURI(url, ["t1", "t3"]);
Assert.equal((await PlacesUtils.bookmarks.fetch(bm1.guid)).url.href, "place:tag=t3",
"The fitst bookmark has been updated");
Assert.equal((await PlacesUtils.bookmarks.fetch(bm2.guid)).url.href, "place:tag=t3&sort=1",
"The second bookmark has been updated");
Assert.equal((await PlacesUtils.bookmarks.fetch(bm3.guid)).url.href, "place:tag=t3&tag=t1",
"The third bookmark has been updated");
await PT.undo();
ensureTagsForURI(url, ["t1", "t2"]);
Assert.equal((await PlacesUtils.bookmarks.fetch(bm1.guid)).url.href, "place:tag=t2",
"The fitst bookmark has been restored");
Assert.equal((await PlacesUtils.bookmarks.fetch(bm2.guid)).url.href, "place:tag=t2&sort=1",
"The second bookmark has been restored");
Assert.equal((await PlacesUtils.bookmarks.fetch(bm3.guid)).url.href, "place:tag=t2&tag=t1",
"The third bookmark has been restored");
await PT.undo();
ensureTagsForURI(url, []);
await PT.clearTransactionsHistory();
ensureUndoState();
await PlacesUtils.bookmarks.eraseEverything();
});

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

@ -4,7 +4,7 @@
* 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/. */
add_task(async function test_resolveNullBookmarkTitles() {
add_task(async function() {
let uri1 = uri("http://foo.tld/");
let uri2 = uri("https://bar.tld/");
@ -25,7 +25,9 @@ add_task(async function test_resolveNullBookmarkTitles() {
PlacesUtils.tagging.tagURI(uri1, ["tag 1"]);
PlacesUtils.tagging.tagURI(uri2, ["tag 2"]);
});
add_task(async function testAsTagQuery() {
let options = PlacesUtils.history.getNewQueryOptions();
options.queryType = Ci.nsINavHistoryQueryOptions.QUERY_TYPE_BOOKMARKS;
options.resultType = options.RESULTS_AS_TAG_CONTENTS;
@ -42,3 +44,14 @@ add_task(async function test_resolveNullBookmarkTitles() {
Assert.equal(root.getChild(1).title, "");
root.containerOpen = false;
});
add_task(async function testTagQuery() {
let options = PlacesUtils.history.getNewQueryOptions();
let query = PlacesUtils.history.getNewQuery();
query.tags = ["tag 1"];
let root = PlacesUtils.history.executeQuery(query, options).root;
root.containerOpen = true;
Assert.equal(root.childCount, 1);
Assert.equal(root.getChild(0).title, "");
root.containerOpen = false;
});

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

@ -33,7 +33,6 @@ skip-if = os == "linux"
[test_413784.js]
[test_415460.js]
[test_415757.js]
[test_419731.js]
[test_419792_node_tags_property.js]
[test_425563.js]
[test_429505_remove_shortcuts.js]