Source code
Revision control
Copy as Markdown
Other Tools
Test Info:
"use strict";
ChromeUtils.defineESModuleGetters(this, {
Preferences: "resource://gre/modules/Preferences.sys.mjs",
});
async function check_keyword(aExpectExists, aHref, aKeyword, aPostData = null) {
// Check case-insensitivity.
aKeyword = aKeyword.toUpperCase();
let entry = await PlacesUtils.keywords.fetch(aKeyword);
Assert.deepEqual(
entry,
await PlacesUtils.keywords.fetch({ keyword: aKeyword })
);
if (aExpectExists) {
Assert.ok(!!entry, "A keyword should exist");
Assert.equal(entry.url.href, aHref);
Assert.equal(entry.postData, aPostData);
Assert.deepEqual(
entry,
await PlacesUtils.keywords.fetch({ keyword: aKeyword, url: aHref })
);
let entries = [];
await PlacesUtils.keywords.fetch({ url: aHref }, e => entries.push(e));
Assert.ok(
entries.some(
e => e.url.href == aHref && e.keyword == aKeyword.toLowerCase()
)
);
} else {
Assert.ok(
!entry || entry.url.href != aHref,
"The given keyword entry should not exist"
);
if (aHref) {
Assert.equal(
null,
await PlacesUtils.keywords.fetch({ keyword: aKeyword, url: aHref })
);
} else {
Assert.equal(
null,
await PlacesUtils.keywords.fetch({ keyword: aKeyword })
);
}
}
}
/**
* Polls the keywords cache waiting for the given keyword entry.
*/
async function promiseKeyword(keyword, expectedHref) {
let href = null;
do {
await new Promise(resolve => do_timeout(100, resolve));
let entry = await PlacesUtils.keywords.fetch(keyword);
if (entry) {
href = entry.url.href;
}
} while (href != expectedHref);
}
async function check_no_orphans() {
let db = await PlacesUtils.promiseDBConnection();
let rows = await db.executeCached(
`SELECT id FROM moz_keywords k
WHERE NOT EXISTS (SELECT 1 FROM moz_places WHERE id = k.place_id)
`
);
Assert.equal(rows.length, 0);
}
function expectBookmarkNotifications() {
const observer = {
notifications: [],
_start() {
this._handle = this._handle.bind(this);
PlacesUtils.observers.addListener(
["bookmark-keyword-changed"],
this._handle
);
},
_handle(events) {
for (const event of events) {
this.notifications.push({
type: event.type,
id: event.id,
itemType: event.itemType,
url: event.url,
guid: event.guid,
parentGuid: event.parentGuid,
keyword: event.keyword,
lastModified: new Date(event.lastModified),
source: event.source,
isTagging: event.isTagging,
});
}
},
check(expected) {
PlacesUtils.observers.removeListener(
["bookmark-keyword-changed"],
this._handle
);
Assert.deepEqual(this.notifications, expected);
},
};
observer._start();
return observer;
}
add_task(async function test_invalid_input() {
Assert.throws(() => PlacesUtils.keywords.fetch(null), /Invalid keyword/);
Assert.throws(() => PlacesUtils.keywords.fetch(5), /Invalid keyword/);
Assert.throws(() => PlacesUtils.keywords.fetch(undefined), /Invalid keyword/);
Assert.throws(
() => PlacesUtils.keywords.fetch({ keyword: null }),
/Invalid keyword/
);
Assert.throws(
() => PlacesUtils.keywords.fetch({ keyword: {} }),
/Invalid keyword/
);
Assert.throws(
() => PlacesUtils.keywords.fetch({ keyword: 5 }),
/Invalid keyword/
);
Assert.throws(
() => PlacesUtils.keywords.fetch({}),
/At least keyword or url must be provided/
);
Assert.throws(
() => PlacesUtils.keywords.fetch({ keyword: "test" }, "test"),
/onResult callback must be a valid function/
);
Assert.throws(
() => PlacesUtils.keywords.fetch({ url: "test" }),
/is not a valid URL/
);
Assert.throws(
() => PlacesUtils.keywords.fetch({ url: {} }),
/is not a valid URL/
);
Assert.throws(
() => PlacesUtils.keywords.fetch({ url: null }),
/is not a valid URL/
);
Assert.throws(
() => PlacesUtils.keywords.fetch({ url: "" }),
/is not a valid URL/
);
Assert.throws(
() => PlacesUtils.keywords.insert(null),
/Input should be a valid object/
);
Assert.throws(
() => PlacesUtils.keywords.insert("test"),
/Input should be a valid object/
);
Assert.throws(
() => PlacesUtils.keywords.insert(undefined),
/Input should be a valid object/
);
Assert.throws(() => PlacesUtils.keywords.insert({}), /Invalid keyword/);
Assert.throws(
() => PlacesUtils.keywords.insert({ keyword: null }),
/Invalid keyword/
);
Assert.throws(
() => PlacesUtils.keywords.insert({ keyword: 5 }),
/Invalid keyword/
);
Assert.throws(
() => PlacesUtils.keywords.insert({ keyword: "" }),
/Invalid keyword/
);
Assert.throws(
() => PlacesUtils.keywords.insert({ keyword: "test", postData: 5 }),
/Invalid POST data/
);
Assert.throws(
() => PlacesUtils.keywords.insert({ keyword: "test", postData: {} }),
/Invalid POST data/
);
Assert.throws(
() => PlacesUtils.keywords.insert({ keyword: "test" }),
/is not a valid URL/
);
Assert.throws(
() => PlacesUtils.keywords.insert({ keyword: "test", url: 5 }),
/is not a valid URL/
);
Assert.throws(
() => PlacesUtils.keywords.insert({ keyword: "test", url: "" }),
/is not a valid URL/
);
Assert.throws(
() => PlacesUtils.keywords.insert({ keyword: "test", url: null }),
/is not a valid URL/
);
Assert.throws(
() => PlacesUtils.keywords.insert({ keyword: "test", url: "mozilla" }),
/is not a valid URL/
);
Assert.throws(() => PlacesUtils.keywords.remove(null), /Invalid keyword/);
Assert.throws(() => PlacesUtils.keywords.remove(""), /Invalid keyword/);
Assert.throws(() => PlacesUtils.keywords.remove(5), /Invalid keyword/);
});
add_task(async function test_addKeyword() {
let observer = expectBookmarkNotifications();
await PlacesUtils.keywords.insert({
keyword: "keyword",
});
observer.check([]);
// Now remove the keyword.
observer = expectBookmarkNotifications();
await PlacesUtils.keywords.remove("keyword");
observer.check([]);
// Check using URL.
await PlacesUtils.keywords.insert({
keyword: "keyword",
});
await PlacesUtils.keywords.remove("keyword");
await check_no_orphans();
});
add_task(async function test_addBookmarkAndKeyword() {
let timerPrecision = Preferences.get("privacy.reduceTimerPrecision");
Preferences.set("privacy.reduceTimerPrecision", false);
registerCleanupFunction(function () {
Preferences.set("privacy.reduceTimerPrecision", timerPrecision);
});
let bookmark = await PlacesUtils.bookmarks.insert({
parentGuid: PlacesUtils.bookmarks.unfiledGuid,
});
let observer = expectBookmarkNotifications();
await PlacesUtils.keywords.insert({
keyword: "keyword",
});
observer.check([
{
type: "bookmark-keyword-changed",
id: await PlacesTestUtils.promiseItemId(bookmark.guid),
itemType: bookmark.type,
url: bookmark.url,
guid: bookmark.guid,
parentGuid: bookmark.parentGuid,
keyword: "keyword",
lastModified: new Date(bookmark.lastModified),
source: Ci.nsINavBookmarksService.SOURCE_DEFAULT,
isTagging: false,
},
]);
// Now remove the keyword.
observer = expectBookmarkNotifications();
await PlacesUtils.keywords.remove("keyword");
observer.check([
{
type: "bookmark-keyword-changed",
id: await PlacesTestUtils.promiseItemId(bookmark.guid),
itemType: bookmark.type,
url: bookmark.url,
guid: bookmark.guid,
parentGuid: bookmark.parentGuid,
keyword: "",
lastModified: new Date(bookmark.lastModified),
source: Ci.nsINavBookmarksService.SOURCE_DEFAULT,
isTagging: false,
},
]);
// Add again the keyword, then remove the bookmark.
await PlacesUtils.keywords.insert({
keyword: "keyword",
});
observer = expectBookmarkNotifications();
await PlacesUtils.bookmarks.remove(bookmark.guid);
// the notification is synchronous but the removal process is async.
// Unfortunately there's nothing explicit we can wait for.
// eslint-disable-next-line no-empty
// We don't get any itemChanged notification since the bookmark has been
// removed already.
observer.check([]);
await check_no_orphans();
});
add_task(async function test_addKeywordToURIHavingKeyword() {
let observer = expectBookmarkNotifications();
await PlacesUtils.keywords.insert({
keyword: "keyword",
});
observer.check([]);
await PlacesUtils.keywords.insert({
keyword: "keyword2",
postData: "test=1",
});
let entries = [];
let entry = await PlacesUtils.keywords.fetch(
e => entries.push(e)
);
Assert.equal(entries.length, 2);
Assert.deepEqual(entries[0], entry);
// Now remove the keywords.
observer = expectBookmarkNotifications();
await PlacesUtils.keywords.remove("keyword");
await PlacesUtils.keywords.remove("keyword2");
observer.check([]);
await check_no_orphans();
});
add_task(async function test_addBookmarkToURIHavingKeyword() {
let observer = expectBookmarkNotifications();
await PlacesUtils.keywords.insert({
keyword: "keyword",
});
observer.check([]);
observer = expectBookmarkNotifications();
let bookmark = await PlacesUtils.bookmarks.insert({
parentGuid: PlacesUtils.bookmarks.unfiledGuid,
});
observer.check([]);
observer = expectBookmarkNotifications();
await PlacesUtils.bookmarks.remove(bookmark.guid);
// the notification is synchronous but the removal process is async.
// Unfortunately there's nothing explicit we can wait for.
// eslint-disable-next-line no-empty
// We don't get any itemChanged notification since the bookmark has been
// removed already.
observer.check([]);
await check_no_orphans();
});
add_task(async function test_sameKeywordDifferentURL() {
let bookmark1 = await PlacesUtils.bookmarks.insert({
parentGuid: PlacesUtils.bookmarks.unfiledGuid,
});
let bookmark2 = await PlacesUtils.bookmarks.insert({
parentGuid: PlacesUtils.bookmarks.unfiledGuid,
});
await PlacesUtils.keywords.insert({
keyword: "keyword",
});
// Assign the same keyword to another url.
let observer = expectBookmarkNotifications();
await PlacesUtils.keywords.insert({
keyword: "keyword",
});
observer.check([
{
type: "bookmark-keyword-changed",
id: await PlacesTestUtils.promiseItemId(bookmark1.guid),
itemType: bookmark1.type,
url: bookmark1.url,
guid: bookmark1.guid,
parentGuid: bookmark1.parentGuid,
keyword: "",
lastModified: new Date(bookmark1.lastModified),
source: Ci.nsINavBookmarksService.SOURCE_DEFAULT,
isTagging: false,
},
{
type: "bookmark-keyword-changed",
id: await PlacesTestUtils.promiseItemId(bookmark2.guid),
itemType: bookmark2.type,
url: bookmark2.url,
guid: bookmark2.guid,
parentGuid: bookmark2.parentGuid,
keyword: "keyword",
lastModified: new Date(bookmark2.lastModified),
source: Ci.nsINavBookmarksService.SOURCE_DEFAULT,
isTagging: false,
},
]);
// Now remove the keyword.
observer = expectBookmarkNotifications();
await PlacesUtils.keywords.remove("keyword");
observer.check([
{
type: "bookmark-keyword-changed",
id: await PlacesTestUtils.promiseItemId(bookmark2.guid),
itemType: bookmark2.type,
url: bookmark2.url,
guid: bookmark2.guid,
parentGuid: bookmark2.parentGuid,
keyword: "",
lastModified: new Date(bookmark2.lastModified),
source: Ci.nsINavBookmarksService.SOURCE_DEFAULT,
isTagging: false,
},
]);
await PlacesUtils.bookmarks.remove(bookmark1);
await PlacesUtils.bookmarks.remove(bookmark2);
// eslint-disable-next-line no-empty
await check_no_orphans();
});
add_task(async function test_sameURIDifferentKeyword() {
let observer = expectBookmarkNotifications();
let bookmark = await PlacesUtils.bookmarks.insert({
parentGuid: PlacesUtils.bookmarks.unfiledGuid,
});
await PlacesUtils.keywords.insert({
keyword: "keyword",
});
observer.check([
{
type: "bookmark-keyword-changed",
id: await PlacesTestUtils.promiseItemId(bookmark.guid),
itemType: bookmark.type,
url: bookmark.url,
guid: bookmark.guid,
parentGuid: bookmark.parentGuid,
keyword: "keyword",
lastModified: new Date(bookmark.lastModified),
source: Ci.nsINavBookmarksService.SOURCE_DEFAULT,
isTagging: false,
},
]);
observer = expectBookmarkNotifications();
await PlacesUtils.keywords.insert({
keyword: "keyword2",
});
observer.check([
{
type: "bookmark-keyword-changed",
id: await PlacesTestUtils.promiseItemId(bookmark.guid),
itemType: bookmark.type,
url: bookmark.url,
guid: bookmark.guid,
parentGuid: bookmark.parentGuid,
keyword: "keyword2",
lastModified: new Date(bookmark.lastModified),
source: Ci.nsINavBookmarksService.SOURCE_DEFAULT,
isTagging: false,
},
]);
// Now remove the bookmark.
await PlacesUtils.bookmarks.remove(bookmark);
// eslint-disable-next-line no-empty
await check_no_orphans();
});
add_task(async function test_deleteKeywordMultipleBookmarks() {
let observer = expectBookmarkNotifications();
let bookmark1 = await PlacesUtils.bookmarks.insert({
parentGuid: PlacesUtils.bookmarks.unfiledGuid,
});
let bookmark2 = await PlacesUtils.bookmarks.insert({
parentGuid: PlacesUtils.bookmarks.unfiledGuid,
});
await PlacesUtils.keywords.insert({
keyword: "keyword",
});
observer.check([
{
type: "bookmark-keyword-changed",
id: await PlacesTestUtils.promiseItemId(bookmark2.guid),
itemType: bookmark2.type,
url: bookmark2.url,
guid: bookmark2.guid,
parentGuid: bookmark2.parentGuid,
keyword: "keyword",
lastModified: new Date(bookmark2.lastModified),
source: Ci.nsINavBookmarksService.SOURCE_DEFAULT,
isTagging: false,
},
{
type: "bookmark-keyword-changed",
id: await PlacesTestUtils.promiseItemId(bookmark1.guid),
itemType: bookmark1.type,
url: bookmark1.url,
guid: bookmark1.guid,
parentGuid: bookmark1.parentGuid,
keyword: "keyword",
lastModified: new Date(bookmark1.lastModified),
source: Ci.nsINavBookmarksService.SOURCE_DEFAULT,
isTagging: false,
},
]);
observer = expectBookmarkNotifications();
await PlacesUtils.keywords.remove("keyword");
observer.check([
{
type: "bookmark-keyword-changed",
id: await PlacesTestUtils.promiseItemId(bookmark2.guid),
itemType: bookmark2.type,
url: bookmark2.url,
guid: bookmark2.guid,
parentGuid: bookmark2.parentGuid,
keyword: "",
lastModified: new Date(bookmark2.lastModified),
source: Ci.nsINavBookmarksService.SOURCE_DEFAULT,
isTagging: false,
},
{
type: "bookmark-keyword-changed",
id: await PlacesTestUtils.promiseItemId(bookmark1.guid),
itemType: bookmark1.type,
url: bookmark1.url,
guid: bookmark1.guid,
parentGuid: bookmark1.parentGuid,
keyword: "",
lastModified: new Date(bookmark1.lastModified),
source: Ci.nsINavBookmarksService.SOURCE_DEFAULT,
isTagging: false,
},
]);
// Now remove the bookmarks.
await PlacesUtils.bookmarks.remove(bookmark1);
await PlacesUtils.bookmarks.remove(bookmark2);
await check_no_orphans();
});
add_task(async function test_multipleKeywordsSamePostData() {
await PlacesUtils.keywords.insert({
keyword: "keyword",
postData: "postData1",
});
// Add another keyword with same postData, should fail.
await PlacesUtils.keywords.insert({
keyword: "keyword2",
postData: "postData1",
});
await PlacesUtils.keywords.remove("keyword2");
await check_no_orphans();
});
add_task(async function test_bookmarkURLChange() {
let bookmark = await PlacesUtils.bookmarks.insert({
parentGuid: PlacesUtils.bookmarks.unfiledGuid,
});
await PlacesUtils.keywords.insert({
keyword: "keyword",
});
await PlacesUtils.bookmarks.update({
guid: bookmark.guid,
});
});
add_task(async function test_tagDoesntPreventKeywordRemoval() {
let httpBookmark = await PlacesUtils.bookmarks.insert({
parentGuid: PlacesUtils.bookmarks.unfiledGuid,
});
await PlacesUtils.keywords.insert({
keyword: "example",
});
await PlacesUtils.bookmarks.remove(httpBookmark);
await TestUtils.waitForCondition(
async () =>
"Wait for bookmark to be removed"
);
Assert.equal(await foreign_count("http://example.com/"), fc); // bookmark, keyword, and tag should all have been removed
await check_no_orphans();
});