Source code
Revision control
Copy as Markdown
Other Tools
Test Info:
/* 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
async function check_has_child(aParentGuid, aChildGuid) {
let parentTree = await PlacesUtils.promiseBookmarksTree(aParentGuid);
Assert.ok("children" in parentTree);
Assert.notEqual(
parentTree.children.find(e => e.guid == aChildGuid),
null
);
}
async function compareToNode(aItem, aNode, aIsRootItem, aExcludedGuids = []) {
// itemId==-1 indicates a non-bookmark node, which is unexpected.
Assert.notEqual(aNode.itemId, -1);
function check_unset(...aProps) {
for (let p of aProps) {
if (p in aItem) {
Assert.ok(false, `Unexpected property ${p} with value ${aItem[p]}`);
}
}
}
function compare_prop(aItemProp, aNodeProp = aItemProp, aOptional = false) {
if (aOptional && aNode[aNodeProp] === null) {
check_unset(aItemProp);
} else {
Assert.strictEqual(aItem[aItemProp], aNode[aNodeProp]);
}
}
if (aNode.bookmarkIndex == -1) {
let bookmark = await PlacesUtils.bookmarks.fetch(aNode.bookmarkGuid);
Assert.strictEqual(aItem.index, bookmark.index);
} else {
compare_prop("index", "bookmarkIndex");
}
compare_prop("dateAdded");
compare_prop("lastModified");
if (aIsRootItem && aNode.bookmarkGuid != PlacesUtils.bookmarks.rootGuid) {
Assert.ok("parentGuid" in aItem);
await check_has_child(aItem.parentGuid, aItem.guid);
} else {
check_unset("parentGuid");
}
const BOOKMARK_ONLY_PROPS = ["uri", "iconUri", "tags", "charset", "keyword"];
const FOLDER_ONLY_PROPS = ["children", "root"];
let nodesCount = 1;
switch (aNode.type) {
case Ci.nsINavHistoryResultNode.RESULT_TYPE_FOLDER: {
Assert.equal(aItem.type, PlacesUtils.TYPE_X_MOZ_PLACE_CONTAINER);
Assert.equal(aItem.typeCode, PlacesUtils.bookmarks.TYPE_FOLDER);
compare_prop("title", "title", true);
check_unset(...BOOKMARK_ONLY_PROPS);
let expectedChildrenNodes = [];
PlacesUtils.asContainer(aNode);
if (!aNode.containerOpen) {
aNode.containerOpen = true;
}
for (let i = 0; i < aNode.childCount; i++) {
let childNode = aNode.getChild(i);
if (
childNode.itemId == PlacesUtils.tagsFolderId ||
aExcludedGuids.includes(childNode.bookmarkGuid)
) {
continue;
}
expectedChildrenNodes.push(childNode);
}
if (expectedChildrenNodes.length) {
Assert.ok(Array.isArray(aItem.children));
Assert.equal(aItem.children.length, expectedChildrenNodes.length);
for (let i = 0; i < aItem.children.length; i++) {
nodesCount += await compareToNode(
aItem.children[i],
expectedChildrenNodes[i],
false,
aExcludedGuids
);
}
} else {
check_unset("children");
}
let rootName = mapItemGuidToInternalRootName(aItem.guid);
if (rootName) {
Assert.equal(aItem.root, rootName);
} else {
check_unset("root");
}
break;
}
case Ci.nsINavHistoryResultNode.RESULT_TYPE_SEPARATOR:
Assert.equal(aItem.type, PlacesUtils.TYPE_X_MOZ_PLACE_SEPARATOR);
Assert.equal(aItem.typeCode, PlacesUtils.bookmarks.TYPE_SEPARATOR);
check_unset(...BOOKMARK_ONLY_PROPS, ...FOLDER_ONLY_PROPS);
break;
default: {
Assert.equal(aItem.type, PlacesUtils.TYPE_X_MOZ_PLACE);
Assert.equal(aItem.typeCode, PlacesUtils.bookmarks.TYPE_BOOKMARK);
compare_prop("uri");
// node.tags's format is "a, b" whilst promiseBoookmarksTree is "a,b"
if (aNode.tags === null) {
check_unset("tags");
} else {
Assert.equal(aItem.tags, aNode.tags.replace(/, /g, ","));
}
if (aNode.icon) {
try {
await compareFavicons(aNode.icon, aItem.iconUri);
} catch (ex) {
info(ex);
todo_check_true(false);
}
} else {
check_unset(aItem.iconUri);
}
check_unset(...FOLDER_ONLY_PROPS);
let pageInfo = await PlacesUtils.history.fetch(aNode.uri, {
includeAnnotations: true,
});
let expectedCharset = pageInfo.annotations.get(PlacesUtils.CHARSET_ANNO);
if (expectedCharset) {
Assert.equal(aItem.charset, expectedCharset);
} else {
check_unset("charset");
}
let entry = await PlacesUtils.keywords.fetch({ url: aNode.uri });
if (entry) {
Assert.equal(aItem.keyword, entry.keyword);
} else {
check_unset("keyword");
}
if ("title" in aItem) {
compare_prop("title");
} else {
Assert.equal(null, aNode.title);
}
}
}
if (aIsRootItem) {
Assert.strictEqual(aItem.itemsCount, nodesCount);
}
return nodesCount;
}
var itemsCount = 0;
async function new_bookmark(aInfo) {
++itemsCount;
if (!("url" in aInfo)) {
}
if (!("title" in aInfo)) {
aInfo.title = "Test Item (bookmark) " + itemsCount;
}
await PlacesTransactions.NewBookmark(aInfo).transact();
}
function new_folder(aInfo) {
if (!("title" in aInfo)) {
aInfo.title = "Test Item (folder) " + itemsCount;
}
return PlacesTransactions.NewFolder(aInfo).transact();
}
// Walks a result nodes tree and test promiseBookmarksTree for each node.
// DO NOT COPY THIS LOGIC: It is done here to accomplish a more comprehensive
// test of the API (the entire hierarchy data is available in the very test).
async function test_promiseBookmarksTreeForEachNode(
aNode,
aOptions,
aExcludedGuids
) {
Assert.ok(aNode.bookmarkGuid && !!aNode.bookmarkGuid.length);
let item = await PlacesUtils.promiseBookmarksTree(
aNode.bookmarkGuid,
aOptions
);
await compareToNode(item, aNode, true, aExcludedGuids);
if (!PlacesUtils.nodeIsContainer(aNode)) {
return item;
}
for (let i = 0; i < aNode.childCount; i++) {
let child = aNode.getChild(i);
if (child.itemId != PlacesUtils.tagsFolderId) {
await test_promiseBookmarksTreeForEachNode(
child,
{ includeItemIds: true },
aExcludedGuids
);
}
}
return item;
}
async function test_promiseBookmarksTreeAgainstResult(
aItemGuid = PlacesUtils.bookmarks.rootGuid,
aOptions = { includeItemIds: true },
aExcludedGuids
) {
let node = PlacesUtils.getFolderContents(aItemGuid).root;
return test_promiseBookmarksTreeForEachNode(node, aOptions, aExcludedGuids);
}
add_task(async function () {
// Add some bookmarks to cover various use cases.
await new_bookmark({ parentGuid: PlacesUtils.bookmarks.toolbarGuid });
await new_folder({
parentGuid: PlacesUtils.bookmarks.menuGuid,
annotations: [
{ name: "TestAnnoA", value: "TestVal" },
{ name: "TestAnnoB", value: 0 },
],
});
let sepInfo = { parentGuid: PlacesUtils.bookmarks.menuGuid };
await PlacesTransactions.NewSeparator(sepInfo).transact();
let folderGuid = await new_folder({
parentGuid: PlacesUtils.bookmarks.menuGuid,
});
await new_bookmark({
title: null,
parentGuid: folderGuid,
keyword: "test_keyword",
tags: ["TestTagA", "TestTagB"],
annotations: [{ name: "TestAnnoA", value: "TestVal2" }],
});
await new_bookmark({ parentGuid: folderGuid, url: urlWithCharsetAndFavicon });
await PlacesUtils.history.update({
url: urlWithCharsetAndFavicon,
annotations: new Map([[PlacesUtils.CHARSET_ANNO, "UTF-16"]]),
});
await setFaviconForPage(urlWithCharsetAndFavicon, SMALLPNG_DATA_URI);
// Test the default places root without specifying it.
await test_promiseBookmarksTreeAgainstResult();
// Do specify it
await test_promiseBookmarksTreeAgainstResult(PlacesUtils.bookmarks.rootGuid);
// Exclude the bookmarks menu.
// The calllback should be four times - once for the toolbar, once for
// the bookmark we inserted under, and once for the menu (and not
// at all for any of its descendants) and once for the unsorted bookmarks
// folder. However, promiseBookmarksTree is called multiple times, so
// rather than counting the calls, we count the number of unique items
// passed in.
let guidsPassedToExcludeCallback = new Set();
let placesRootWithoutTheMenu = await test_promiseBookmarksTreeAgainstResult(
PlacesUtils.bookmarks.rootGuid,
{
excludeItemsCallback: aItem => {
guidsPassedToExcludeCallback.add(aItem.guid);
return aItem.root == "bookmarksMenuFolder";
},
includeItemIds: true,
},
[PlacesUtils.bookmarks.menuGuid]
);
Assert.equal(guidsPassedToExcludeCallback.size, 5);
Assert.equal(placesRootWithoutTheMenu.children.length, 3);
});