summaryrefslogtreecommitdiffstats
path: root/toolkit/components/places/tests/unit/test_keywords.js
diff options
context:
space:
mode:
Diffstat (limited to 'toolkit/components/places/tests/unit/test_keywords.js')
-rw-r--r--toolkit/components/places/tests/unit/test_keywords.js777
1 files changed, 777 insertions, 0 deletions
diff --git a/toolkit/components/places/tests/unit/test_keywords.js b/toolkit/components/places/tests/unit/test_keywords.js
new file mode 100644
index 0000000000..62c2b28d3d
--- /dev/null
+++ b/toolkit/components/places/tests/unit/test_keywords.js
@@ -0,0 +1,777 @@
+"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() {
+ let notifications = [];
+ let observer = new Proxy(NavBookmarkObserver, {
+ get(target, name) {
+ if (name == "check") {
+ PlacesUtils.bookmarks.removeObserver(observer);
+ return expectedNotifications =>
+ Assert.deepEqual(notifications, expectedNotifications);
+ }
+
+ if (name.startsWith("onItemChanged")) {
+ return function(itemId, property) {
+ if (property != "keyword") {
+ return;
+ }
+ let args = Array.from(arguments, arg => {
+ if (arg && arg instanceof Ci.nsIURI) {
+ return new URL(arg.spec);
+ }
+ if (arg && typeof arg == "number" && arg >= Date.now() * 1000) {
+ return new Date(parseInt(arg / 1000));
+ }
+ return arg;
+ });
+ notifications.push({ name, arguments: args });
+ };
+ }
+
+ if (name in target) {
+ return target[name];
+ }
+ return undefined;
+ },
+ });
+ PlacesUtils.bookmarks.addObserver(observer);
+ 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() {
+ await check_keyword(false, "http://example.com/", "keyword");
+ let fc = await foreign_count("http://example.com/");
+ let observer = expectBookmarkNotifications();
+
+ await PlacesUtils.keywords.insert({
+ keyword: "keyword",
+ url: "http://example.com/",
+ });
+ observer.check([]);
+
+ await check_keyword(true, "http://example.com/", "keyword");
+ Assert.equal(await foreign_count("http://example.com/"), fc + 1); // +1 keyword
+
+ // Now remove the keyword.
+ observer = expectBookmarkNotifications();
+ await PlacesUtils.keywords.remove("keyword");
+ observer.check([]);
+
+ await check_keyword(false, "http://example.com/", "keyword");
+ Assert.equal(await foreign_count("http://example.com/"), fc); // -1 keyword
+
+ // Check using URL.
+ await PlacesUtils.keywords.insert({
+ keyword: "keyword",
+ url: new URL("http://example.com/"),
+ });
+ await check_keyword(true, "http://example.com/", "keyword");
+ await PlacesUtils.keywords.remove("keyword");
+ await check_keyword(false, "http://example.com/", "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);
+ });
+
+ await check_keyword(false, "http://example.com/", "keyword");
+ let fc = await foreign_count("http://example.com/");
+ let bookmark = await PlacesUtils.bookmarks.insert({
+ url: "http://example.com/",
+ parentGuid: PlacesUtils.bookmarks.unfiledGuid,
+ });
+
+ let observer = expectBookmarkNotifications();
+ await PlacesUtils.keywords.insert({
+ keyword: "keyword",
+ url: "http://example.com/",
+ });
+
+ observer.check([
+ {
+ name: "onItemChanged",
+ arguments: [
+ await PlacesUtils.promiseItemId(bookmark.guid),
+ "keyword",
+ false,
+ "keyword",
+ bookmark.lastModified * 1000,
+ bookmark.type,
+ await PlacesUtils.promiseItemId(bookmark.parentGuid),
+ bookmark.guid,
+ bookmark.parentGuid,
+ "",
+ Ci.nsINavBookmarksService.SOURCE_DEFAULT,
+ ],
+ },
+ ]);
+
+ await check_keyword(true, "http://example.com/", "keyword");
+ Assert.equal(await foreign_count("http://example.com/"), fc + 2); // +1 bookmark +1 keyword
+
+ // Now remove the keyword.
+ observer = expectBookmarkNotifications();
+ await PlacesUtils.keywords.remove("keyword");
+
+ observer.check([
+ {
+ name: "onItemChanged",
+ arguments: [
+ await PlacesUtils.promiseItemId(bookmark.guid),
+ "keyword",
+ false,
+ "",
+ bookmark.lastModified * 1000,
+ bookmark.type,
+ await PlacesUtils.promiseItemId(bookmark.parentGuid),
+ bookmark.guid,
+ bookmark.parentGuid,
+ "",
+ Ci.nsINavBookmarksService.SOURCE_DEFAULT,
+ ],
+ },
+ ]);
+
+ await check_keyword(false, "http://example.com/", "keyword");
+ Assert.equal(await foreign_count("http://example.com/"), fc + 1); // -1 keyword
+
+ // Add again the keyword, then remove the bookmark.
+ await PlacesUtils.keywords.insert({
+ keyword: "keyword",
+ url: "http://example.com/",
+ });
+
+ 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
+ while (await foreign_count("http://example.com/")) {}
+ // We don't get any itemChanged notification since the bookmark has been
+ // removed already.
+ observer.check([]);
+
+ await check_keyword(false, "http://example.com/", "keyword");
+
+ await check_no_orphans();
+});
+
+add_task(async function test_addKeywordToURIHavingKeyword() {
+ await check_keyword(false, "http://example.com/", "keyword");
+ let fc = await foreign_count("http://example.com/");
+
+ let observer = expectBookmarkNotifications();
+ await PlacesUtils.keywords.insert({
+ keyword: "keyword",
+ url: "http://example.com/",
+ });
+ observer.check([]);
+
+ await check_keyword(true, "http://example.com/", "keyword");
+ Assert.equal(await foreign_count("http://example.com/"), fc + 1); // +1 keyword
+
+ await PlacesUtils.keywords.insert({
+ keyword: "keyword2",
+ url: "http://example.com/",
+ postData: "test=1",
+ });
+
+ await check_keyword(true, "http://example.com/", "keyword");
+ await check_keyword(true, "http://example.com/", "keyword2", "test=1");
+ Assert.equal(await foreign_count("http://example.com/"), fc + 2); // +1 keyword
+ let entries = [];
+ let entry = await PlacesUtils.keywords.fetch(
+ { url: "http://example.com/" },
+ 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_keyword(false, "http://example.com/", "keyword");
+ await check_keyword(false, "http://example.com/", "keyword2");
+ Assert.equal(await foreign_count("http://example.com/"), fc); // -1 keyword
+
+ await check_no_orphans();
+});
+
+add_task(async function test_addBookmarkToURIHavingKeyword() {
+ await check_keyword(false, "http://example.com/", "keyword");
+ let fc = await foreign_count("http://example.com/");
+ let observer = expectBookmarkNotifications();
+
+ await PlacesUtils.keywords.insert({
+ keyword: "keyword",
+ url: "http://example.com/",
+ });
+ observer.check([]);
+
+ await check_keyword(true, "http://example.com/", "keyword");
+ Assert.equal(await foreign_count("http://example.com/"), fc + 1); // +1 keyword
+
+ observer = expectBookmarkNotifications();
+ let bookmark = await PlacesUtils.bookmarks.insert({
+ url: "http://example.com/",
+ parentGuid: PlacesUtils.bookmarks.unfiledGuid,
+ });
+ Assert.equal(await foreign_count("http://example.com/"), fc + 2); // +1 bookmark
+ 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
+ while (await foreign_count("http://example.com/")) {}
+ // We don't get any itemChanged notification since the bookmark has been
+ // removed already.
+ observer.check([]);
+
+ await check_keyword(false, "http://example.com/", "keyword");
+
+ await check_no_orphans();
+});
+
+add_task(async function test_sameKeywordDifferentURL() {
+ let fc1 = await foreign_count("http://example1.com/");
+ let bookmark1 = await PlacesUtils.bookmarks.insert({
+ url: "http://example1.com/",
+ parentGuid: PlacesUtils.bookmarks.unfiledGuid,
+ });
+ let fc2 = await foreign_count("http://example2.com/");
+ let bookmark2 = await PlacesUtils.bookmarks.insert({
+ url: "http://example2.com/",
+ parentGuid: PlacesUtils.bookmarks.unfiledGuid,
+ });
+ await PlacesUtils.keywords.insert({
+ keyword: "keyword",
+ url: "http://example1.com/",
+ });
+
+ await check_keyword(true, "http://example1.com/", "keyword");
+ Assert.equal(await foreign_count("http://example1.com/"), fc1 + 2); // +1 bookmark +1 keyword
+ await check_keyword(false, "http://example2.com/", "keyword");
+ Assert.equal(await foreign_count("http://example2.com/"), fc2 + 1); // +1 bookmark
+
+ // Assign the same keyword to another url.
+ let observer = expectBookmarkNotifications();
+ await PlacesUtils.keywords.insert({
+ keyword: "keyword",
+ url: "http://example2.com/",
+ });
+
+ observer.check([
+ {
+ name: "onItemChanged",
+ arguments: [
+ await PlacesUtils.promiseItemId(bookmark1.guid),
+ "keyword",
+ false,
+ "",
+ bookmark1.lastModified * 1000,
+ bookmark1.type,
+ await PlacesUtils.promiseItemId(bookmark1.parentGuid),
+ bookmark1.guid,
+ bookmark1.parentGuid,
+ "",
+ Ci.nsINavBookmarksService.SOURCE_DEFAULT,
+ ],
+ },
+ {
+ name: "onItemChanged",
+ arguments: [
+ await PlacesUtils.promiseItemId(bookmark2.guid),
+ "keyword",
+ false,
+ "keyword",
+ bookmark2.lastModified * 1000,
+ bookmark2.type,
+ await PlacesUtils.promiseItemId(bookmark2.parentGuid),
+ bookmark2.guid,
+ bookmark2.parentGuid,
+ "",
+ Ci.nsINavBookmarksService.SOURCE_DEFAULT,
+ ],
+ },
+ ]);
+
+ await check_keyword(false, "http://example1.com/", "keyword");
+ Assert.equal(await foreign_count("http://example1.com/"), fc1 + 1); // -1 keyword
+ await check_keyword(true, "http://example2.com/", "keyword");
+ Assert.equal(await foreign_count("http://example2.com/"), fc2 + 2); // +1 keyword
+
+ // Now remove the keyword.
+ observer = expectBookmarkNotifications();
+ await PlacesUtils.keywords.remove("keyword");
+ observer.check([
+ {
+ name: "onItemChanged",
+ arguments: [
+ await PlacesUtils.promiseItemId(bookmark2.guid),
+ "keyword",
+ false,
+ "",
+ bookmark2.lastModified * 1000,
+ bookmark2.type,
+ await PlacesUtils.promiseItemId(bookmark2.parentGuid),
+ bookmark2.guid,
+ bookmark2.parentGuid,
+ "",
+ Ci.nsINavBookmarksService.SOURCE_DEFAULT,
+ ],
+ },
+ ]);
+
+ await check_keyword(false, "http://example1.com/", "keyword");
+ await check_keyword(false, "http://example2.com/", "keyword");
+ Assert.equal(await foreign_count("http://example1.com/"), fc1 + 1);
+ Assert.equal(await foreign_count("http://example2.com/"), fc2 + 1); // -1 keyword
+
+ await PlacesUtils.bookmarks.remove(bookmark1);
+ await PlacesUtils.bookmarks.remove(bookmark2);
+ Assert.equal(await foreign_count("http://example1.com/"), fc1); // -1 bookmark
+ // eslint-disable-next-line no-empty
+ while (await foreign_count("http://example2.com/")) {} // -1 keyword
+
+ await check_no_orphans();
+});
+
+add_task(async function test_sameURIDifferentKeyword() {
+ let fc = await foreign_count("http://example.com/");
+
+ let observer = expectBookmarkNotifications();
+ let bookmark = await PlacesUtils.bookmarks.insert({
+ url: "http://example.com/",
+ parentGuid: PlacesUtils.bookmarks.unfiledGuid,
+ });
+ await PlacesUtils.keywords.insert({
+ keyword: "keyword",
+ url: "http://example.com/",
+ });
+
+ await check_keyword(true, "http://example.com/", "keyword");
+ Assert.equal(await foreign_count("http://example.com/"), fc + 2); // +1 bookmark +1 keyword
+
+ observer.check([
+ {
+ name: "onItemChanged",
+ arguments: [
+ await PlacesUtils.promiseItemId(bookmark.guid),
+ "keyword",
+ false,
+ "keyword",
+ bookmark.lastModified * 1000,
+ bookmark.type,
+ await PlacesUtils.promiseItemId(bookmark.parentGuid),
+ bookmark.guid,
+ bookmark.parentGuid,
+ "",
+ Ci.nsINavBookmarksService.SOURCE_DEFAULT,
+ ],
+ },
+ ]);
+
+ observer = expectBookmarkNotifications();
+ await PlacesUtils.keywords.insert({
+ keyword: "keyword2",
+ url: "http://example.com/",
+ });
+ await check_keyword(false, "http://example.com/", "keyword");
+ await check_keyword(true, "http://example.com/", "keyword2");
+ Assert.equal(await foreign_count("http://example.com/"), fc + 2); // -1 keyword +1 keyword
+ observer.check([
+ {
+ name: "onItemChanged",
+ arguments: [
+ await PlacesUtils.promiseItemId(bookmark.guid),
+ "keyword",
+ false,
+ "keyword2",
+ bookmark.lastModified * 1000,
+ bookmark.type,
+ await PlacesUtils.promiseItemId(bookmark.parentGuid),
+ bookmark.guid,
+ bookmark.parentGuid,
+ "",
+ Ci.nsINavBookmarksService.SOURCE_DEFAULT,
+ ],
+ },
+ ]);
+
+ // Now remove the bookmark.
+ await PlacesUtils.bookmarks.remove(bookmark);
+ // eslint-disable-next-line no-empty
+ while (await foreign_count("http://example.com/")) {}
+ await check_keyword(false, "http://example.com/", "keyword");
+ await check_keyword(false, "http://example.com/", "keyword2");
+
+ await check_no_orphans();
+});
+
+add_task(async function test_deleteKeywordMultipleBookmarks() {
+ let fc = await foreign_count("http://example.com/");
+
+ let observer = expectBookmarkNotifications();
+ let bookmark1 = await PlacesUtils.bookmarks.insert({
+ url: "http://example.com/",
+ parentGuid: PlacesUtils.bookmarks.unfiledGuid,
+ });
+ let bookmark2 = await PlacesUtils.bookmarks.insert({
+ url: "http://example.com/",
+ parentGuid: PlacesUtils.bookmarks.unfiledGuid,
+ });
+ await PlacesUtils.keywords.insert({
+ keyword: "keyword",
+ url: "http://example.com/",
+ });
+
+ await check_keyword(true, "http://example.com/", "keyword");
+ Assert.equal(await foreign_count("http://example.com/"), fc + 3); // +2 bookmark +1 keyword
+ observer.check([
+ {
+ name: "onItemChanged",
+ arguments: [
+ await PlacesUtils.promiseItemId(bookmark2.guid),
+ "keyword",
+ false,
+ "keyword",
+ bookmark2.lastModified * 1000,
+ bookmark2.type,
+ await PlacesUtils.promiseItemId(bookmark2.parentGuid),
+ bookmark2.guid,
+ bookmark2.parentGuid,
+ "",
+ Ci.nsINavBookmarksService.SOURCE_DEFAULT,
+ ],
+ },
+ {
+ name: "onItemChanged",
+ arguments: [
+ await PlacesUtils.promiseItemId(bookmark1.guid),
+ "keyword",
+ false,
+ "keyword",
+ bookmark1.lastModified * 1000,
+ bookmark1.type,
+ await PlacesUtils.promiseItemId(bookmark1.parentGuid),
+ bookmark1.guid,
+ bookmark1.parentGuid,
+ "",
+ Ci.nsINavBookmarksService.SOURCE_DEFAULT,
+ ],
+ },
+ ]);
+
+ observer = expectBookmarkNotifications();
+ await PlacesUtils.keywords.remove("keyword");
+ await check_keyword(false, "http://example.com/", "keyword");
+ Assert.equal(await foreign_count("http://example.com/"), fc + 2); // -1 keyword
+ observer.check([
+ {
+ name: "onItemChanged",
+ arguments: [
+ await PlacesUtils.promiseItemId(bookmark2.guid),
+ "keyword",
+ false,
+ "",
+ bookmark2.lastModified * 1000,
+ bookmark2.type,
+ await PlacesUtils.promiseItemId(bookmark2.parentGuid),
+ bookmark2.guid,
+ bookmark2.parentGuid,
+ "",
+ Ci.nsINavBookmarksService.SOURCE_DEFAULT,
+ ],
+ },
+ {
+ name: "onItemChanged",
+ arguments: [
+ await PlacesUtils.promiseItemId(bookmark1.guid),
+ "keyword",
+ false,
+ "",
+ bookmark1.lastModified * 1000,
+ bookmark1.type,
+ await PlacesUtils.promiseItemId(bookmark1.parentGuid),
+ bookmark1.guid,
+ bookmark1.parentGuid,
+ "",
+ Ci.nsINavBookmarksService.SOURCE_DEFAULT,
+ ],
+ },
+ ]);
+
+ // Now remove the bookmarks.
+ await PlacesUtils.bookmarks.remove(bookmark1);
+ await PlacesUtils.bookmarks.remove(bookmark2);
+ Assert.equal(await foreign_count("http://example.com/"), fc); // -2 bookmarks
+
+ await check_no_orphans();
+});
+
+add_task(async function test_multipleKeywordsSamePostData() {
+ await PlacesUtils.keywords.insert({
+ keyword: "keyword",
+ url: "http://example.com/",
+ postData: "postData1",
+ });
+ await check_keyword(true, "http://example.com/", "keyword", "postData1");
+ // Add another keyword with same postData, should fail.
+ await PlacesUtils.keywords.insert({
+ keyword: "keyword2",
+ url: "http://example.com/",
+ postData: "postData1",
+ });
+ await check_keyword(false, "http://example.com/", "keyword", "postData1");
+ await check_keyword(true, "http://example.com/", "keyword2", "postData1");
+
+ await PlacesUtils.keywords.remove("keyword2");
+
+ await check_no_orphans();
+});
+
+add_task(async function test_bookmarkURLChange() {
+ let fc1 = await foreign_count("http://example1.com/");
+ let fc2 = await foreign_count("http://example2.com/");
+ let bookmark = await PlacesUtils.bookmarks.insert({
+ url: "http://example1.com/",
+ parentGuid: PlacesUtils.bookmarks.unfiledGuid,
+ });
+ await PlacesUtils.keywords.insert({
+ keyword: "keyword",
+ url: "http://example1.com/",
+ });
+
+ await check_keyword(true, "http://example1.com/", "keyword");
+ Assert.equal(await foreign_count("http://example1.com/"), fc1 + 2); // +1 bookmark +1 keyword
+
+ await PlacesUtils.bookmarks.update({
+ guid: bookmark.guid,
+ url: "http://example2.com/",
+ });
+ await promiseKeyword("keyword", "http://example2.com/");
+
+ await check_keyword(false, "http://example1.com/", "keyword");
+ await check_keyword(true, "http://example2.com/", "keyword");
+ Assert.equal(await foreign_count("http://example1.com/"), fc1); // -1 bookmark -1 keyword
+ Assert.equal(await foreign_count("http://example2.com/"), fc2 + 2); // +1 bookmark +1 keyword
+});
+
+add_task(async function test_tagDoesntPreventKeywordRemoval() {
+ await check_keyword(false, "http://example.com/", "example");
+ let fc = await foreign_count("http://example.com/");
+
+ let httpBookmark = await PlacesUtils.bookmarks.insert({
+ url: "http://example.com/",
+ parentGuid: PlacesUtils.bookmarks.unfiledGuid,
+ });
+ Assert.equal(await foreign_count("http://example.com/"), fc + 1); // +1 bookmark
+
+ PlacesUtils.tagging.tagURI(uri("http://example.com/"), ["example_tag"]);
+ Assert.equal(await foreign_count("http://example.com/"), fc + 2); // +1 bookmark +1 tag
+
+ await PlacesUtils.keywords.insert({
+ keyword: "example",
+ url: "http://example.com/",
+ });
+ Assert.equal(await foreign_count("http://example.com/"), fc + 3); // +1 bookmark +1 tag +1 keyword
+
+ await check_keyword(true, "http://example.com/", "example");
+
+ await PlacesUtils.bookmarks.remove(httpBookmark);
+
+ await TestUtils.waitForCondition(
+ async () =>
+ !(await PlacesUtils.bookmarks.fetch({ url: "http://example.com/" })),
+ "Wait for bookmark to be removed"
+ );
+
+ await check_keyword(false, "http://example.com/", "example");
+ Assert.equal(await foreign_count("http://example.com/"), fc); // bookmark, keyword, and tag should all have been removed
+
+ await check_no_orphans();
+});