Source code

Revision control

Copy as Markdown

Other Tools

Test Info: Warnings

/* Any copyright is dedicated to the Public Domain.
const UNVISITED_BOOKMARK_BONUS = 140;
function promiseRankingChanged() {
return PlacesTestUtils.waitForNotification("pages-rank-changed");
}
add_task(async function setup() {
Services.prefs.setIntPref(
"places.frecency.unvisitedBookmarkBonus",
UNVISITED_BOOKMARK_BONUS
);
});
add_task(async function invalid_input_throws() {
Assert.throws(
() => PlacesUtils.bookmarks.remove(),
/Input should be a valid object/
);
Assert.throws(
() => PlacesUtils.bookmarks.remove(null),
/Input should be a valid object/
);
Assert.throws(
() => PlacesUtils.bookmarks.remove("test"),
/Invalid value for property 'guid'/
);
Assert.throws(
() => PlacesUtils.bookmarks.remove(123),
/Invalid value for property 'guid'/
);
Assert.throws(
() => PlacesUtils.bookmarks.remove({ guid: "test" }),
/Invalid value for property 'guid'/
);
Assert.throws(
() => PlacesUtils.bookmarks.remove({ guid: null }),
/Invalid value for property 'guid'/
);
Assert.throws(
() => PlacesUtils.bookmarks.remove({ guid: 123 }),
/Invalid value for property 'guid'/
);
Assert.throws(
() => PlacesUtils.bookmarks.remove({ parentGuid: "test" }),
/Invalid value for property 'parentGuid'/
);
Assert.throws(
() => PlacesUtils.bookmarks.remove({ parentGuid: null }),
/Invalid value for property 'parentGuid'/
);
Assert.throws(
() => PlacesUtils.bookmarks.remove({ parentGuid: 123 }),
/Invalid value for property 'parentGuid'/
);
Assert.throws(
() => PlacesUtils.bookmarks.remove({ url: "http://te st/" }),
/Invalid value for property 'url'/
);
Assert.throws(
() => PlacesUtils.bookmarks.remove({ url: null }),
/Invalid value for property 'url'/
);
Assert.throws(
() => PlacesUtils.bookmarks.remove({ url: -10 }),
/Invalid value for property 'url'/
);
});
add_task(async function remove_nonexistent_guid() {
try {
await PlacesUtils.bookmarks.remove({ guid: "123456789012" });
Assert.ok(false, "Should have thrown");
} catch (ex) {
Assert.ok(/No bookmarks found for the provided GUID/.test(ex));
}
});
add_task(async function remove_roots_fail() {
let guids = [
PlacesUtils.bookmarks.rootGuid,
PlacesUtils.bookmarks.unfiledGuid,
PlacesUtils.bookmarks.menuGuid,
PlacesUtils.bookmarks.toolbarGuid,
PlacesUtils.bookmarks.tagsGuid,
PlacesUtils.bookmarks.mobileGuid,
];
for (let guid of guids) {
Assert.throws(
() => PlacesUtils.bookmarks.remove(guid),
/It's not possible to remove Places root folders\./
);
}
});
add_task(async function remove_bookmark() {
// When removing a bookmark we need to check the frecency. First we confirm
// that there is a normal update when it is inserted.
let promise = promiseRankingChanged();
let bm1 = await PlacesUtils.bookmarks.insert({
parentGuid: PlacesUtils.bookmarks.unfiledGuid,
type: PlacesUtils.bookmarks.TYPE_BOOKMARK,
title: "a bookmark",
});
checkBookmarkObject(bm1);
await PlacesFrecencyRecalculator.recalculateAnyOutdatedFrecencies();
await promise;
// This second one checks the frecency is changed when we remove the bookmark.
promise = promiseRankingChanged();
await PlacesUtils.bookmarks.remove(bm1.guid);
await PlacesFrecencyRecalculator.recalculateAnyOutdatedFrecencies();
await promise;
});
add_task(async function remove_multiple_bookmarks_simple() {
// When removing a bookmark we need to check the frecency. First we confirm
// that there is a normal update when it is inserted.
const promise1 = promiseRankingChanged();
let bm1 = await PlacesUtils.bookmarks.insert({
parentGuid: PlacesUtils.bookmarks.unfiledGuid,
type: PlacesUtils.bookmarks.TYPE_BOOKMARK,
title: "a bookmark",
});
checkBookmarkObject(bm1);
await PlacesFrecencyRecalculator.recalculateAnyOutdatedFrecencies();
const promise2 = promiseRankingChanged();
let bm2 = await PlacesUtils.bookmarks.insert({
parentGuid: PlacesUtils.bookmarks.unfiledGuid,
type: PlacesUtils.bookmarks.TYPE_BOOKMARK,
title: "a bookmark",
});
checkBookmarkObject(bm2);
await PlacesFrecencyRecalculator.recalculateAnyOutdatedFrecencies();
await Promise.all([promise1, promise2]);
// We should get a pages-rank-changed event with the removal of
// multiple bookmarks.
const promise3 = promiseRankingChanged();
await PlacesUtils.bookmarks.remove([bm1, bm2]);
await PlacesFrecencyRecalculator.recalculateAnyOutdatedFrecencies();
await promise3;
});
add_task(async function remove_multiple_bookmarks_complex() {
let bms = [];
for (let i = 0; i < 10; i++) {
bms.push(
await PlacesUtils.bookmarks.insert({
parentGuid: PlacesUtils.bookmarks.unfiledGuid,
title: `bookmark ${i}`,
})
);
}
// Remove bookmarks 2 and 3.
let bmsToRemove = bms.slice(2, 4);
let notifiedIndexes = [];
let notificationPromise = PlacesTestUtils.waitForNotification(
"bookmark-removed",
events => {
for (let event of events) {
notifiedIndexes.push({ guid: event.guid, index: event.index });
}
return notifiedIndexes.length == bmsToRemove.length;
}
);
await PlacesUtils.bookmarks.remove(bmsToRemove);
await notificationPromise;
let indexModifier = 0;
for (let i = 0; i < bmsToRemove.length; i++) {
Assert.equal(
notifiedIndexes[i].guid,
bmsToRemove[i].guid,
`Should have been notified of the correct guid for item ${i}`
);
Assert.equal(
notifiedIndexes[i].index,
bmsToRemove[i].index - indexModifier,
`Should have been notified of the correct index for the item ${i}`
);
indexModifier++;
}
let expectedIndex = 0;
for (let bm of [bms[0], bms[1], ...bms.slice(4)]) {
const fetched = await PlacesUtils.bookmarks.fetch(bm.guid);
Assert.equal(
fetched.index,
expectedIndex,
"Should have the correct index after consecutive item removal"
);
bm.index = fetched.index;
expectedIndex++;
}
// Remove some more including non-consecutive.
bmsToRemove = [bms[1], bms[5], bms[6], bms[8]];
notifiedIndexes = [];
notificationPromise = PlacesTestUtils.waitForNotification(
"bookmark-removed",
events => {
for (let event of events) {
notifiedIndexes.push({ guid: event.guid, index: event.index });
}
return notifiedIndexes.length == bmsToRemove.length;
}
);
await PlacesUtils.bookmarks.remove(bmsToRemove);
await notificationPromise;
indexModifier = 0;
for (let i = 0; i < bmsToRemove.length; i++) {
Assert.equal(
notifiedIndexes[i].guid,
bmsToRemove[i].guid,
`Should have been notified of the correct guid for item ${i}`
);
Assert.equal(
notifiedIndexes[i].index,
bmsToRemove[i].index - indexModifier,
`Should have been notified of the correct index for the item ${i}`
);
indexModifier++;
}
expectedIndex = 0;
const expectedRemaining = [bms[0], bms[4], bms[7], bms[9]];
for (let bm of expectedRemaining) {
const fetched = await PlacesUtils.bookmarks.fetch(bm.guid);
Assert.equal(
fetched.index,
expectedIndex,
"Should have the correct index after non-consecutive item removal"
);
expectedIndex++;
}
// Tidy up
await PlacesUtils.bookmarks.remove(expectedRemaining);
await PlacesTestUtils.promiseAsyncUpdates();
});
add_task(async function remove_bookmark_empty_title() {
let bm1 = await PlacesUtils.bookmarks.insert({
parentGuid: PlacesUtils.bookmarks.unfiledGuid,
type: PlacesUtils.bookmarks.TYPE_BOOKMARK,
title: "",
});
checkBookmarkObject(bm1);
await PlacesUtils.bookmarks.remove(bm1.guid);
Assert.strictEqual(await PlacesUtils.bookmarks.fetch(bm1.guid), null);
});
add_task(async function remove_folder() {
let bm1 = await PlacesUtils.bookmarks.insert({
parentGuid: PlacesUtils.bookmarks.unfiledGuid,
type: PlacesUtils.bookmarks.TYPE_FOLDER,
title: "a folder",
});
checkBookmarkObject(bm1);
await PlacesUtils.bookmarks.remove(bm1.guid);
Assert.strictEqual(await PlacesUtils.bookmarks.fetch(bm1.guid), null);
// No wait for pages-rank-changed event in this test as the folder doesn't have
// any children that would need updating.
});
add_task(async function test_contents_removed() {
let folder1 = await PlacesUtils.bookmarks.insert({
parentGuid: PlacesUtils.bookmarks.unfiledGuid,
type: PlacesUtils.bookmarks.TYPE_FOLDER,
title: "a folder",
});
let bm1 = await PlacesUtils.bookmarks.insert({
parentGuid: folder1.guid,
type: PlacesUtils.bookmarks.TYPE_BOOKMARK,
title: "",
});
let skipDescendantsObserver = expectPlacesObserverNotifications(
["bookmark-removed"],
false,
true
);
let receiveAllObserver = expectPlacesObserverNotifications(
["bookmark-removed"],
false,
false
);
const promise = promiseRankingChanged();
await PlacesUtils.bookmarks.remove(folder1);
Assert.strictEqual(await PlacesUtils.bookmarks.fetch(folder1.guid), null);
Assert.strictEqual(await PlacesUtils.bookmarks.fetch(bm1.guid), null);
await PlacesFrecencyRecalculator.recalculateAnyOutdatedFrecencies();
await promise;
let expectedNotifications = [
{
type: "bookmark-removed",
guid: folder1.guid,
},
];
// If we're skipping descendents, we'll only be notified of the folder.
skipDescendantsObserver.check(expectedNotifications);
// Note: Items of folders get notified first.
expectedNotifications.unshift({
type: "bookmark-removed",
guid: bm1.guid,
});
// If we don't skip descendents, we'll be notified of the folder and the
// bookmark.
receiveAllObserver.check(expectedNotifications);
});
add_task(async function test_nested_contents_removed() {
let folder1 = await PlacesUtils.bookmarks.insert({
parentGuid: PlacesUtils.bookmarks.unfiledGuid,
type: PlacesUtils.bookmarks.TYPE_FOLDER,
title: "a folder",
});
let folder2 = await PlacesUtils.bookmarks.insert({
parentGuid: folder1.guid,
type: PlacesUtils.bookmarks.TYPE_FOLDER,
title: "a folder",
});
let bm1 = await PlacesUtils.bookmarks.insert({
parentGuid: folder2.guid,
type: PlacesUtils.bookmarks.TYPE_BOOKMARK,
title: "",
});
const promise = promiseRankingChanged();
await PlacesUtils.bookmarks.remove(folder1);
Assert.strictEqual(await PlacesUtils.bookmarks.fetch(folder1.guid), null);
Assert.strictEqual(await PlacesUtils.bookmarks.fetch(folder2.guid), null);
Assert.strictEqual(await PlacesUtils.bookmarks.fetch(bm1.guid), null);
await PlacesFrecencyRecalculator.recalculateAnyOutdatedFrecencies();
await promise;
});
add_task(async function remove_folder_empty_title() {
let bm1 = await PlacesUtils.bookmarks.insert({
parentGuid: PlacesUtils.bookmarks.unfiledGuid,
type: PlacesUtils.bookmarks.TYPE_FOLDER,
title: "",
});
checkBookmarkObject(bm1);
await PlacesUtils.bookmarks.remove(bm1.guid);
Assert.strictEqual(await PlacesUtils.bookmarks.fetch(bm1.guid), null);
});
add_task(async function remove_separator() {
let bm1 = await PlacesUtils.bookmarks.insert({
parentGuid: PlacesUtils.bookmarks.unfiledGuid,
type: PlacesUtils.bookmarks.TYPE_SEPARATOR,
});
checkBookmarkObject(bm1);
await PlacesUtils.bookmarks.remove(bm1.guid);
Assert.strictEqual(await PlacesUtils.bookmarks.fetch(bm1.guid), null);
});
add_task(async function test_nested_content_fails_when_not_allowed() {
let folder1 = await PlacesUtils.bookmarks.insert({
parentGuid: PlacesUtils.bookmarks.unfiledGuid,
type: PlacesUtils.bookmarks.TYPE_FOLDER,
title: "a folder",
});
await PlacesUtils.bookmarks.insert({
parentGuid: folder1.guid,
type: PlacesUtils.bookmarks.TYPE_FOLDER,
title: "a folder",
});
await Assert.rejects(
PlacesUtils.bookmarks.remove(folder1, {
preventRemovalOfNonEmptyFolders: true,
}),
/Cannot remove a non-empty folder./
);
});
add_task(async function test_remove_bookmark_with_invalid_url() {
let folder = await PlacesUtils.bookmarks.insert({
type: PlacesUtils.bookmarks.TYPE_FOLDER,
parentGuid: PlacesUtils.bookmarks.unfiledGuid,
title: "folder",
});
let guid = "invalid_____";
let folderedGuid = "invalid____2";
let url = "invalid-uri";
await PlacesUtils.withConnectionWrapper("test_bookmarks_remove", async db => {
await db.execute(
`
INSERT INTO moz_places(url, url_hash, title, rev_host, guid)
VALUES (:url, hash(:url), 'Invalid URI', '.', GENERATE_GUID())
`,
{ url }
);
await db.execute(
`INSERT INTO moz_bookmarks (type, fk, parent, position, guid)
VALUES (:type,
(SELECT id FROM moz_places WHERE url = :url),
(SELECT id FROM moz_bookmarks WHERE guid = :parentGuid),
(SELECT MAX(position) + 1 FROM moz_bookmarks WHERE parent = (SELECT id FROM moz_bookmarks WHERE guid = :parentGuid)),
:guid)
`,
{
type: PlacesUtils.bookmarks.TYPE_BOOKMARK,
url,
parentGuid: PlacesUtils.bookmarks.unfiledGuid,
guid,
}
);
await db.execute(
`INSERT INTO moz_bookmarks (type, fk, parent, position, guid)
VALUES (:type,
(SELECT id FROM moz_places WHERE url = :url),
(SELECT id FROM moz_bookmarks WHERE guid = :parentGuid),
(SELECT MAX(position) + 1 FROM moz_bookmarks WHERE parent = (SELECT id FROM moz_bookmarks WHERE guid = :parentGuid)),
:guid)
`,
{
type: PlacesUtils.bookmarks.TYPE_BOOKMARK,
url,
parentGuid: folder.guid,
guid: folderedGuid,
}
);
});
await PlacesUtils.bookmarks.remove(guid);
Assert.strictEqual(
await PlacesUtils.bookmarks.fetch(guid),
null,
"Should not throw and not find the bookmark"
);
await PlacesUtils.bookmarks.remove(folder);
Assert.strictEqual(
await PlacesUtils.bookmarks.fetch(folderedGuid),
null,
"Should not throw and not find the bookmark"
);
});