Bug 1438577 - Add API to fetch bookmarks by GUID prefix. r=mak

MozReview-Commit-ID: GPEp5Vxrz5z

--HG--
extra : rebase_source : f8be9cfdad6db7f00366cef587751649538f74df
This commit is contained in:
Felipe Gomes 2018-02-20 13:31:41 -03:00
Родитель 4b5191dd1c
Коммит 35c0e50b23
3 изменённых файлов: 111 добавлений и 5 удалений

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

@ -968,6 +968,10 @@ var Bookmarks = Object.freeze({
* retrieves the most recent bookmark having the given URL.
* To retrieve ALL of the bookmarks for that URL, you must pass in an
* onResult callback, that will be invoked once for each found bookmark.
* - guidPrefix
* retrieves the most recent item with the specified guid prefix.
* To retrieve ALL of the bookmarks for that guid prefix, you must pass
* in an onResult callback, that will be invoked once for each bookmark.
*
* @param guidOrInfo
* The globally unique identifier of the item to fetch, or an
@ -1003,14 +1007,15 @@ var Bookmarks = Object.freeze({
info = { guid: guidOrInfo };
} else if (Object.keys(info).length == 1) {
// Just a faster code path.
if (!["url", "guid", "parentGuid", "index"].includes(Object.keys(info)[0]))
if (!["url", "guid", "parentGuid", "index", "guidPrefix"].includes(Object.keys(info)[0]))
throw new Error(`Unexpected number of conditions provided: 0`);
} else {
// Only one condition at a time can be provided.
let conditionsCount = [
v => v.hasOwnProperty("guid"),
v => v.hasOwnProperty("parentGuid") && v.hasOwnProperty("index"),
v => v.hasOwnProperty("url")
v => v.hasOwnProperty("url"),
v => v.hasOwnProperty("guidPrefix")
].reduce((old, fn) => old + fn(info) | 0, 0);
if (conditionsCount != 1)
throw new Error(`Unexpected number of conditions provided: ${conditionsCount}`);
@ -1039,6 +1044,8 @@ var Bookmarks = Object.freeze({
results = await fetchBookmark(fetchInfo, options && options.concurrent);
else if (fetchInfo.hasOwnProperty("parentGuid") && fetchInfo.hasOwnProperty("index"))
results = await fetchBookmarkByPosition(fetchInfo, options && options.concurrent);
else if (fetchInfo.hasOwnProperty("guidPrefix"))
results = await fetchBookmarksByGUIDPrefix(fetchInfo, options && options.concurrent);
if (!results)
return null;
@ -1781,6 +1788,32 @@ async function fetchBookmarkByPosition(info, concurrent) {
query);
}
async function fetchBookmarksByGUIDPrefix(info, concurrent) {
let query = async function(db) {
let rows = await db.executeCached(
`SELECT b.guid, IFNULL(p.guid, "") AS parentGuid, b.position AS 'index',
b.dateAdded, b.lastModified, b.type, IFNULL(b.title, "") AS title,
h.url AS url, b.id AS _id, b.parent AS _parentId,
NULL AS _childCount,
p.parent AS _grandParentId, b.syncStatus AS _syncStatus
FROM moz_bookmarks b
LEFT JOIN moz_bookmarks p ON p.id = b.parent
LEFT JOIN moz_places h ON h.id = b.fk
WHERE b.guid LIKE :guidPrefix
ORDER BY b.lastModified DESC
`, { guidPrefix: info.guidPrefix + "%" });
return rows.length ? rowsToItemsArray(rows) : null;
};
if (concurrent) {
let db = await PlacesUtils.promiseDBConnection();
return query(db);
}
return PlacesUtils.withConnectionWrapper("Bookmarks.jsm: fetchBookmarksByGUIDPrefix",
query);
}
async function fetchBookmarksByURL(info, concurrent) {
let query = async function(db) {
let tagsFolderId = await promiseTagsFolderId();

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

@ -197,8 +197,8 @@ const DB_DESCRIPTION_LENGTH_MAX = 256;
*/
const BOOKMARK_VALIDATORS = Object.freeze({
guid: simpleValidateFunc(v => PlacesUtils.isValidGuid(v)),
parentGuid: simpleValidateFunc(v => typeof(v) == "string" &&
/^[a-zA-Z0-9\-_]{12}$/.test(v)),
parentGuid: simpleValidateFunc(v => PlacesUtils.isValidGuid(v)),
guidPrefix: simpleValidateFunc(v => PlacesUtils.isValidGuidPrefix(v)),
index: simpleValidateFunc(v => Number.isInteger(v) &&
v >= PlacesUtils.bookmarks.DEFAULT_INDEX),
dateAdded: simpleValidateFunc(v => v.constructor.name == "Date"),
@ -340,6 +340,17 @@ this.PlacesUtils = {
(/^[a-zA-Z0-9\-_]{12}$/.test(guid));
},
/**
* Is a string a valid GUID prefix?
*
* @param guidPrefix: (String)
* @return (Boolean)
*/
isValidGuidPrefix(guidPrefix) {
return typeof guidPrefix == "string" && guidPrefix &&
(/^[a-zA-Z0-9\-_]{1,11}$/.test(guidPrefix));
},
/**
* Converts a string or n URL object to an nsIURI.
*
@ -2414,7 +2425,7 @@ var GuidHelper = {
updateCache(aItemId, aGuid) {
if (typeof(aItemId) != "number" || aItemId <= 0)
throw new Error("Trying to update the GUIDs cache with an invalid itemId");
if (typeof(aGuid) != "string" || !/^[a-zA-Z0-9\-_]{12}$/.test(aGuid))
if (!PlacesUtils.isValidGuid(aGuid))
throw new Error("Trying to update the GUIDs cache with an invalid GUID");
this.ensureObservingRemovedItems();
this.guidsForIds.set(aItemId, aGuid);

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

@ -45,6 +45,17 @@ add_task(async function invalid_input_throws() {
Assert.throws(() => PlacesUtils.bookmarks.fetch({ guid: 123 }),
/Invalid value for property 'guid'/);
Assert.throws(() => PlacesUtils.bookmarks.fetch({ guidPrefix: "" }),
/Invalid value for property 'guidPrefix'/);
Assert.throws(() => PlacesUtils.bookmarks.fetch({ guidPrefix: null }),
/Invalid value for property 'guidPrefix'/);
Assert.throws(() => PlacesUtils.bookmarks.fetch({ guidPrefix: 123 }),
/Invalid value for property 'guidPrefix'/);
Assert.throws(() => PlacesUtils.bookmarks.fetch({ guidPrefix: "123456789012" }),
/Invalid value for property 'guidPrefix'/);
Assert.throws(() => PlacesUtils.bookmarks.fetch({ guidPrefix: "@" }),
/Invalid value for property 'guidPrefix'/);
Assert.throws(() => PlacesUtils.bookmarks.fetch({ parentGuid: "test",
index: 0 }),
/Invalid value for property 'parentGuid'/);
@ -182,6 +193,57 @@ add_task(async function fetch_separator() {
await PlacesUtils.bookmarks.remove(bm1.guid);
});
add_task(async function fetch_byguid_prefix() {
function generateGuidWithPrefix(prefix) {
return prefix + PlacesUtils.history.makeGuid().substring(prefix.length);
}
const PREFIX = "PREFIX-";
let bm1 = await PlacesUtils.bookmarks.insert({ parentGuid: PlacesUtils.bookmarks.unfiledGuid,
guid: generateGuidWithPrefix(PREFIX),
url: "http://bm1.example.com/",
title: "bookmark 1" });
checkBookmarkObject(bm1);
Assert.ok(bm1.guid.startsWith(PREFIX));
let bm2 = await PlacesUtils.bookmarks.insert({ parentGuid: PlacesUtils.bookmarks.unfiledGuid,
guid: generateGuidWithPrefix(PREFIX),
url: "http://bm2.example.com/",
title: "bookmark 2" });
checkBookmarkObject(bm2);
Assert.ok(bm2.guid.startsWith(PREFIX));
let bm3 = await PlacesUtils.bookmarks.insert({ parentGuid: PlacesUtils.bookmarks.unfiledGuid,
type: PlacesUtils.bookmarks.TYPE_FOLDER,
guid: generateGuidWithPrefix(PREFIX),
title: "a folder" });
checkBookmarkObject(bm3);
Assert.ok(bm3.guid.startsWith(PREFIX));
// Bookmark 4 doesn't have the same guid prefix, so it shouldn't be returned in the results.
let bm4 = await PlacesUtils.bookmarks.insert({ parentGuid: PlacesUtils.bookmarks.unfiledGuid,
url: "http://bm3.example.com/",
title: "bookmark 4" });
checkBookmarkObject(bm4);
Assert.ok(!bm4.guid.startsWith(PREFIX));
await PlacesUtils.bookmarks.fetch({ guidPrefix: PREFIX }, gAccumulator.callback);
Assert.equal(gAccumulator.results.length, 3);
// The results are returned by most recent first, so the first bookmark
// inserted is the last one in the returned array.
Assert.deepEqual(bm1, gAccumulator.results[2]);
Assert.deepEqual(bm2, gAccumulator.results[1]);
Assert.deepEqual(bm3, gAccumulator.results[0]);
await PlacesUtils.bookmarks.remove(bm1);
await PlacesUtils.bookmarks.remove(bm2);
await PlacesUtils.bookmarks.remove(bm3);
await PlacesUtils.bookmarks.remove(bm4);
});
add_task(async function fetch_byposition_nonexisting_parentGuid() {
let bm = await PlacesUtils.bookmarks.fetch({ parentGuid: "123456789012",
index: 0 },