Bug 1225743 - Implement chrome.bookmarks.search

--HG--
extra : commitid : FoEz268aSjP
This commit is contained in:
Johann Hofmann 2015-11-24 17:40:08 +01:00
Родитель 91b63d605c
Коммит cb09799b68
6 изменённых файлов: 484 добавлений и 2 удалений

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

@ -111,7 +111,9 @@ extensions.registerSchemaAPI("bookmarks", "bookmarks", (extension, context) => {
return getTree(id, false);
},
// search
search: function(query) {
return Bookmarks.search(query).then(result => result.map(convert));
},
create: function(bookmark) {
let info = {

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

@ -234,7 +234,6 @@
},
{
"name": "search",
"unsupported": true,
"type": "function",
"description": "Searches for BookmarkTreeNodes matching the given query. Queries specified with an object produce BookmarkTreeNodes matching all specified properties.",
"async": "callback",
@ -258,6 +257,7 @@
},
"url": {
"type": "string",
"format": "url",
"optional": true,
"description": "The URL of the bookmark; matches verbatim. Note that folders have no URL."
},

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

@ -87,6 +87,158 @@ function backgroundScript() {
}).then(() => {
browser.test.assertEq(5, failures, "Expected failures");
// test bookmarks.search
}).then(() => {
return Promise.all([
browser.bookmarks.create({title: "MØzillä", url: "http://møzîllä.örg"}),
browser.bookmarks.create({title: "Example", url: "http://example.org"}),
browser.bookmarks.create({title: "Mozilla Folder"}),
browser.bookmarks.create({title: "EFF", url: "http://eff.org"}),
browser.bookmarks.create({title: "Menu Item", url: "http://menu.org", parentId: "menu________"}),
browser.bookmarks.create({title: "Toolbar Item", url: "http://toolbar.org", parentId: "toolbar_____"}),
]);
}).then(results => {
return Promise.all([
browser.bookmarks.create({title: "Mozilla", url: "http://allizom.org", parentId: results[1].id}),
browser.bookmarks.create({title: "Mozilla Corporation", url: "http://allizom.com", parentId: results[1].id}),
browser.bookmarks.create({title: "Firefox", url: "http://allizom.org/firefox", parentId: results[1].id}),
]);
}).then(() => {
// returns all items on empty object
return browser.bookmarks.search({});
}).then(results => {
browser.test.assertTrue(results.length >= 9);
// throws an error for invalid query objects
return browser.bookmarks.search();
}).catch(error => {
browser.test.assertTrue(error.message.includes("Incorrect argument types for bookmarks.search"));
return browser.bookmarks.search(null);
}).catch(error => {
browser.test.assertTrue(error.message.includes("Incorrect argument types for bookmarks.search"));
return browser.bookmarks.search(function(){});
}).catch(error => {
browser.test.assertTrue(error.message.includes("Incorrect argument types for bookmarks.search"));
return browser.bookmarks.search({banana: "banana"});
}).catch(error => {
browser.test.assertTrue(error.message.includes("banana"));
browser.test.assertTrue(error.message.includes("bookmarks.search"));
return browser.bookmarks.search({url: "spider-man vs. batman"});
}).catch(error => {
browser.test.assertTrue(error.message.includes("spider-man vs. batman"));
browser.test.assertTrue(error.message.includes("not a valid URL"));
browser.test.assertTrue(error.message.includes("bookmarks.search"));
// queries the url
return browser.bookmarks.search("example.org");
}).then(results => {
browser.test.assertEq(1, results.length);
browser.test.assertEq("Example", results[0].title);
browser.test.assertEq("http://example.org/", results[0].url);
browser.test.assertEq(2, results[0].index);
// queries the title
return browser.bookmarks.search("EFF");
}).then(results => {
browser.test.assertEq(1, results.length);
browser.test.assertEq("EFF", results[0].title);
browser.test.assertEq("http://eff.org/", results[0].url);
browser.test.assertEq("unfiled_____", results[0].parentId);
browser.test.assertEq(0, results[0].index);
// finds menu items
return browser.bookmarks.search("Menu Item");
}).then(results => {
browser.test.assertEq(1, results.length);
browser.test.assertEq("Menu Item", results[0].title);
browser.test.assertEq("http://menu.org/", results[0].url);
browser.test.assertEq("menu________", results[0].parentId);
// finds toolbar items
return browser.bookmarks.search("Toolbar Item");
}).then(results => {
browser.test.assertEq(1, results.length);
browser.test.assertEq("Toolbar Item", results[0].title);
browser.test.assertEq("http://toolbar.org/", results[0].url);
browser.test.assertEq("toolbar_____", results[0].parentId);
// finds folders
return browser.bookmarks.search("Mozilla Folder");
}).then(results => {
browser.test.assertEq(1, results.length);
browser.test.assertEq("Mozilla Folder", results[0].title);
// is case-insensitive
return browser.bookmarks.search("corporation");
}).then(results => {
browser.test.assertEq(1, results.length);
browser.test.assertEq("Mozilla Corporation", results[0].title);
// is case-insensitive for non-ascii
return browser.bookmarks.search("MøZILLÄ");
}).then(results => {
browser.test.assertEq(1, results.length);
browser.test.assertEq("MØzillä", results[0].title);
// returns multiple results
return browser.bookmarks.search("allizom");
}).then(results => {
browser.test.assertEq(3, results.length);
browser.test.assertEq("Mozilla", results[0].title);
browser.test.assertEq("Mozilla Corporation", results[1].title);
browser.test.assertEq("Firefox", results[2].title);
// accepts a url field
return browser.bookmarks.search({url: "http://allizom.com/"});
}).then(results => {
browser.test.assertEq(1, results.length);
browser.test.assertEq("Mozilla Corporation", results[0].title);
browser.test.assertEq("http://allizom.com/", results[0].url);
// normalizes urls
return browser.bookmarks.search({url: "http://allizom.com"});
}).then(results => {
browser.test.assertEq(results.length, 1);
browser.test.assertEq("Mozilla Corporation", results[0].title);
browser.test.assertEq("http://allizom.com/", results[0].url);
// normalizes urls even more
return browser.bookmarks.search({url: "http:allizom.com"});
}).then(results => {
browser.test.assertEq(results.length, 1);
browser.test.assertEq("Mozilla Corporation", results[0].title);
browser.test.assertEq("http://allizom.com/", results[0].url);
// accepts a title field
return browser.bookmarks.search({title: "Mozilla"});
}).then(results => {
browser.test.assertEq(results.length, 1);
browser.test.assertEq("Mozilla", results[0].title);
browser.test.assertEq("http://allizom.org/", results[0].url);
// can combine title and query
return browser.bookmarks.search({title: "Mozilla", query: "allizom"});
}).then(results => {
browser.test.assertEq(1, results.length);
browser.test.assertEq("Mozilla", results[0].title);
browser.test.assertEq("http://allizom.org/", results[0].url);
// uses AND conditions
return browser.bookmarks.search({title: "EFF", query: "allizom"});
}).then(results => {
browser.test.assertEq(0, results.length);
// returns an empty array on item not found
return browser.bookmarks.search("microsoft");
}).then(results => {
browser.test.assertEq(0, results.length);
browser.test.notifyPass("bookmarks");
}).catch(error => {
browser.test.fail(`Error: ${String(error)} :: ${error.stack}`);

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

@ -82,6 +82,9 @@ XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils",
const DB_URL_LENGTH_MAX = 65536;
const DB_TITLE_LENGTH_MAX = 4096;
const MATCH_BOUNDARY = Ci.mozIPlacesAutoComplete.MATCH_BOUNDARY;
const BEHAVIOR_BOOKMARK = Ci.mozIPlacesAutoComplete.BEHAVIOR_BOOKMARK;
var Bookmarks = Object.freeze({
/**
* Item's type constants.
@ -446,6 +449,57 @@ var Bookmarks = Object.freeze({
);
},
/**
* Searches a list of bookmark-items by a search term, url or title.
*
* @param query
* Either a string to use as search term, or an object
* containing any of these keys: query, title or url with the
* corresponding string to match as value.
* The url property can be either a string or an nsIURI.
*
* @return {Promise} resolved when the search is complete.
* @resolves to an array of found bookmark-items.
* @rejects if an error happens while searching.
* @throws if the arguments are invalid.
*
* @note Any unknown property in the query object is ignored.
* Known properties may be overwritten.
*/
search(query) {
if (!query) {
throw new Error("Query object is required");
}
if (typeof query === "string") {
query = { query: query };
}
if (typeof query !== "object") {
throw new Error("Query must be an object or a string");
}
if (query.query && typeof query.query !== "string") {
throw new Error("Query option must be a string");
}
if (query.title && typeof query.title !== "string") {
throw new Error("Title option must be a string");
}
if (query.url) {
if (typeof query.url === "string" || (query.url instanceof URL)) {
query.url = new URL(query.url).href;
} else if (query.url instanceof Ci.nsIURI) {
query.url = query.url.spec;
} else {
throw new Error("Url option must be a string or a URL object");
}
}
return Task.spawn(function* () {
let results = yield queryBookmarks(query);
return results;
});
},
/**
* Fetches information about a bookmark-item.
*
@ -823,6 +877,56 @@ function insertBookmark(item, parent) {
}));
}
////////////////////////////////////////////////////////////////////////////////
// Query implementation.
function queryBookmarks(info) {
let queryParams = {tags_folder: PlacesUtils.tagsFolderId};
// we're searching for bookmarks, so exclude tags
let queryString = "WHERE p.parent <> :tags_folder";
if (info.title) {
queryString += " AND b.title = :title";
queryParams.title = info.title;
}
if (info.url) {
queryString += " AND h.url = :url";
queryParams.url = info.url;
}
if (info.query) {
queryString += " AND AUTOCOMPLETE_MATCH(:query, h.url, b.title, NULL, NULL, 1, 1, NULL, :matchBehavior, :searchBehavior) ";
queryParams.query = info.query;
queryParams.matchBehavior = MATCH_BOUNDARY;
queryParams.searchBehavior = BEHAVIOR_BOOKMARK;
}
return PlacesUtils.withConnectionWrapper("Bookmarks.jsm: queryBookmarks",
Task.async(function*(db) {
// _id, _childCount, _grandParentId and _parentId fields
// are required to be in the result by the converting function
// hence setting them to NULL
let rows = yield db.executeCached(
`SELECT b.guid, IFNULL(p.guid, "") AS parentGuid, b.position AS 'index',
b.dateAdded, b.lastModified, b.type, b.title,
h.url AS url, b.parent, p.parent,
NULL AS _id,
NULL AS _childCount,
NULL AS _grandParentId,
NULL AS _parentId
FROM moz_bookmarks b
LEFT JOIN moz_bookmarks p ON p.id = b.parent
LEFT JOIN moz_places h ON h.id = b.fk
${queryString}
`, queryParams);
return rowsToItemsArray(rows);
}));
}
////////////////////////////////////////////////////////////////////////////////
// Fetch implementation.

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

@ -0,0 +1,223 @@
add_task(function* invalid_input_throws() {
Assert.throws(() => PlacesUtils.bookmarks.search(),
/Query object is required/);
Assert.throws(() => PlacesUtils.bookmarks.search(null),
/Query object is required/);
Assert.throws(() => PlacesUtils.bookmarks.search({title: 50}),
/Title option must be a string/);
Assert.throws(() => PlacesUtils.bookmarks.search({url: {url: "wombat"}}),
/Url option must be a string or a URL object/);
Assert.throws(() => PlacesUtils.bookmarks.search(50),
/Query must be an object or a string/);
Assert.throws(() => PlacesUtils.bookmarks.search(true),
/Query must be an object or a string/);
});
add_task(function* search_bookmark() {
yield PlacesUtils.bookmarks.eraseEverything();
let bm1 = yield PlacesUtils.bookmarks.insert({ parentGuid: PlacesUtils.bookmarks.unfiledGuid,
url: "http://example.com/",
title: "a bookmark" });
let bm2 = yield PlacesUtils.bookmarks.insert({ parentGuid: PlacesUtils.bookmarks.unfiledGuid,
url: "http://example.org/",
title: "another bookmark" });
let bm3 = yield PlacesUtils.bookmarks.insert({ parentGuid: PlacesUtils.bookmarks.menuGuid,
url: "http://menu.org/",
title: "a menu bookmark" });
let bm4 = yield PlacesUtils.bookmarks.insert({ parentGuid: PlacesUtils.bookmarks.toolbarGuid,
url: "http://toolbar.org/",
title: "a toolbar bookmark" });
checkBookmarkObject(bm1);
checkBookmarkObject(bm2);
checkBookmarkObject(bm3);
checkBookmarkObject(bm4);
// finds a result by query
let results = yield PlacesUtils.bookmarks.search("example.com");
Assert.equal(results.length, 1);
checkBookmarkObject(results[0]);
Assert.deepEqual(bm1, results[0]);
// finds multiple results
results = yield PlacesUtils.bookmarks.search("example");
Assert.equal(results.length, 2);
checkBookmarkObject(results[0]);
checkBookmarkObject(results[1]);
// finds menu bookmarks
results = yield PlacesUtils.bookmarks.search("a menu bookmark");
Assert.equal(results.length, 1);
checkBookmarkObject(results[0]);
Assert.deepEqual(bm3, results[0]);
// finds toolbar bookmarks
results = yield PlacesUtils.bookmarks.search("a toolbar bookmark");
Assert.equal(results.length, 1);
checkBookmarkObject(results[0]);
Assert.deepEqual(bm4, results[0]);
yield PlacesUtils.bookmarks.eraseEverything();
});
add_task(function* search_bookmark_by_query_object() {
let bm1 = yield PlacesUtils.bookmarks.insert({ parentGuid: PlacesUtils.bookmarks.unfiledGuid,
url: "http://example.com/",
title: "a bookmark" });
let bm2 = yield PlacesUtils.bookmarks.insert({ parentGuid: PlacesUtils.bookmarks.unfiledGuid,
url: "http://example.org/",
title: "another bookmark" });
checkBookmarkObject(bm1);
checkBookmarkObject(bm2);
let results = yield PlacesUtils.bookmarks.search({query: "example.com"});
Assert.equal(results.length, 1);
checkBookmarkObject(results[0]);
Assert.deepEqual(bm1, results[0]);
results = yield PlacesUtils.bookmarks.search({query: "example"});
Assert.equal(results.length, 2);
checkBookmarkObject(results[0]);
checkBookmarkObject(results[1]);
yield PlacesUtils.bookmarks.eraseEverything();
});
add_task(function* search_bookmark_by_url() {
let bm1 = yield PlacesUtils.bookmarks.insert({ parentGuid: PlacesUtils.bookmarks.unfiledGuid,
url: "http://example.com/",
title: "a bookmark" });
let bm2 = yield PlacesUtils.bookmarks.insert({ parentGuid: PlacesUtils.bookmarks.unfiledGuid,
url: "http://example.org/path",
title: "another bookmark" });
let bm3 = yield PlacesUtils.bookmarks.insert({ parentGuid: PlacesUtils.bookmarks.unfiledGuid,
url: "http://example.org/path",
title: "third bookmark" });
checkBookmarkObject(bm1);
checkBookmarkObject(bm2);
checkBookmarkObject(bm3);
// finds the correct result by url
let results = yield PlacesUtils.bookmarks.search({url: "http://example.com/"});
Assert.equal(results.length, 1);
checkBookmarkObject(results[0]);
Assert.deepEqual(bm1, results[0]);
// normalizes the url
results = yield PlacesUtils.bookmarks.search({url: "http:/example.com"});
Assert.equal(results.length, 1);
checkBookmarkObject(results[0]);
Assert.deepEqual(bm1, results[0]);
// returns multiple matches
results = yield PlacesUtils.bookmarks.search({url: "http://example.org/path"});
Assert.equal(results.length, 2);
// requires exact match
results = yield PlacesUtils.bookmarks.search({url: "http://example.org/"});
Assert.equal(results.length, 0);
yield PlacesUtils.bookmarks.eraseEverything();
});
add_task(function* search_bookmark_by_title() {
let bm1 = yield PlacesUtils.bookmarks.insert({ parentGuid: PlacesUtils.bookmarks.unfiledGuid,
url: "http://example.com/",
title: "a bookmark" });
let bm2 = yield PlacesUtils.bookmarks.insert({ parentGuid: PlacesUtils.bookmarks.unfiledGuid,
url: "http://example.org/path",
title: "another bookmark" });
let bm3 = yield PlacesUtils.bookmarks.insert({ parentGuid: PlacesUtils.bookmarks.unfiledGuid,
url: "http://example.net/",
title: "another bookmark" });
checkBookmarkObject(bm1);
checkBookmarkObject(bm2);
checkBookmarkObject(bm3);
// finds the correct result by title
let results = yield PlacesUtils.bookmarks.search({title: "a bookmark"});
Assert.equal(results.length, 1);
checkBookmarkObject(results[0]);
Assert.deepEqual(bm1, results[0]);
// returns multiple matches
results = yield PlacesUtils.bookmarks.search({title: "another bookmark"});
Assert.equal(results.length, 2);
// requires exact match
results = yield PlacesUtils.bookmarks.search({title: "bookmark"});
Assert.equal(results.length, 0);
yield PlacesUtils.bookmarks.eraseEverything();
});
add_task(function* search_bookmark_combinations() {
let bm1 = yield PlacesUtils.bookmarks.insert({ parentGuid: PlacesUtils.bookmarks.unfiledGuid,
url: "http://example.com/",
title: "a bookmark" });
let bm2 = yield PlacesUtils.bookmarks.insert({ parentGuid: PlacesUtils.bookmarks.unfiledGuid,
url: "http://example.org/path",
title: "another bookmark" });
let bm3 = yield PlacesUtils.bookmarks.insert({ parentGuid: PlacesUtils.bookmarks.unfiledGuid,
url: "http://example.net/",
title: "third bookmark" });
checkBookmarkObject(bm1);
checkBookmarkObject(bm2);
checkBookmarkObject(bm3);
// finds the correct result if title and url match
let results = yield PlacesUtils.bookmarks.search({url: "http://example.com/", title: "a bookmark"});
Assert.equal(results.length, 1);
checkBookmarkObject(results[0]);
Assert.deepEqual(bm1, results[0]);
// does not match if query is not matching but url and title match
results = yield PlacesUtils.bookmarks.search({url: "http://example.com/", title: "a bookmark", query: "nonexistent"});
Assert.equal(results.length, 0);
// does not match if one parameter is not matching
results = yield PlacesUtils.bookmarks.search({url: "http://what.ever", title: "a bookmark"});
Assert.equal(results.length, 0);
// query only matches if other fields match as well
results = yield PlacesUtils.bookmarks.search({query: "bookmark", url: "http://example.net/"});
Assert.equal(results.length, 1);
checkBookmarkObject(results[0]);
Assert.deepEqual(bm3, results[0]);
// non-matching query will also return no results
results = yield PlacesUtils.bookmarks.search({query: "nonexistent", url: "http://example.net/"});
Assert.equal(results.length, 0);
yield PlacesUtils.bookmarks.eraseEverything();
});
add_task(function* search_folder() {
let folder = yield PlacesUtils.bookmarks.insert({ parentGuid: PlacesUtils.bookmarks.menuGuid,
type: PlacesUtils.bookmarks.TYPE_FOLDER,
title: "a test folder" });
let bm = yield PlacesUtils.bookmarks.insert({ parentGuid: folder.guid,
url: "http://example.com/",
title: "a bookmark" });
checkBookmarkObject(folder);
checkBookmarkObject(bm);
// also finds folders
let results = yield PlacesUtils.bookmarks.search("a test folder");
Assert.equal(results.length, 1);
checkBookmarkObject(results[0]);
Assert.equal(folder.title, results[0].title);
Assert.equal(folder.type, results[0].type);
Assert.equal(folder.parentGuid, results[0].parentGuid);
// finds elements in folders
results = yield PlacesUtils.bookmarks.search("example.com");
Assert.equal(results.length, 1);
checkBookmarkObject(results[0]);
Assert.deepEqual(bm, results[0]);
Assert.equal(folder.guid, results[0].parentGuid);
yield PlacesUtils.bookmarks.eraseEverything();
});

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

@ -37,6 +37,7 @@ skip-if = toolkit == 'android' || toolkit == 'gonk'
[test_bookmarks_notifications.js]
[test_bookmarks_remove.js]
[test_bookmarks_reorder.js]
[test_bookmarks_search.js]
[test_bookmarks_update.js]
[test_changeBookmarkURI.js]
[test_getBookmarkedURIFor.js]