summaryrefslogtreecommitdiffstats
path: root/toolkit/components/places/tests/bookmarks
diff options
context:
space:
mode:
authorMatt A. Tobin <mattatobin@localhost.localdomain>2018-02-02 04:16:08 -0500
committerMatt A. Tobin <mattatobin@localhost.localdomain>2018-02-02 04:16:08 -0500
commit5f8de423f190bbb79a62f804151bc24824fa32d8 (patch)
tree10027f336435511475e392454359edea8e25895d /toolkit/components/places/tests/bookmarks
parent49ee0794b5d912db1f95dce6eb52d781dc210db5 (diff)
downloadUXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar
UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.gz
UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.lz
UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.xz
UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.zip
Add m-esr52 at 52.6.0
Diffstat (limited to 'toolkit/components/places/tests/bookmarks')
-rw-r--r--toolkit/components/places/tests/bookmarks/.eslintrc.js7
-rw-r--r--toolkit/components/places/tests/bookmarks/head_bookmarks.js20
-rw-r--r--toolkit/components/places/tests/bookmarks/test_1016953-renaming-uncompressed.js103
-rw-r--r--toolkit/components/places/tests/bookmarks/test_1017502-bookmarks_foreign_count.js112
-rw-r--r--toolkit/components/places/tests/bookmarks/test_1129529.js76
-rw-r--r--toolkit/components/places/tests/bookmarks/test_384228.js98
-rw-r--r--toolkit/components/places/tests/bookmarks/test_385829.js182
-rw-r--r--toolkit/components/places/tests/bookmarks/test_388695.js52
-rw-r--r--toolkit/components/places/tests/bookmarks/test_393498.js102
-rw-r--r--toolkit/components/places/tests/bookmarks/test_395101.js87
-rw-r--r--toolkit/components/places/tests/bookmarks/test_395593.js69
-rw-r--r--toolkit/components/places/tests/bookmarks/test_405938_restore_queries.js221
-rw-r--r--toolkit/components/places/tests/bookmarks/test_417228-exclude-from-backup.js141
-rw-r--r--toolkit/components/places/tests/bookmarks/test_417228-other-roots.js158
-rw-r--r--toolkit/components/places/tests/bookmarks/test_424958-json-quoted-folders.js91
-rw-r--r--toolkit/components/places/tests/bookmarks/test_448584.js113
-rw-r--r--toolkit/components/places/tests/bookmarks/test_458683.js131
-rw-r--r--toolkit/components/places/tests/bookmarks/test_466303-json-remove-backups.js124
-rw-r--r--toolkit/components/places/tests/bookmarks/test_477583_json-backup-in-future.js56
-rw-r--r--toolkit/components/places/tests/bookmarks/test_675416.js56
-rw-r--r--toolkit/components/places/tests/bookmarks/test_711914.js56
-rw-r--r--toolkit/components/places/tests/bookmarks/test_818584-discard-duplicate-backups.js59
-rw-r--r--toolkit/components/places/tests/bookmarks/test_818587_compress-bookmarks-backups.js57
-rw-r--r--toolkit/components/places/tests/bookmarks/test_818593-store-backup-metadata.js57
-rw-r--r--toolkit/components/places/tests/bookmarks/test_992901-backup-unsorted-hierarchy.js48
-rw-r--r--toolkit/components/places/tests/bookmarks/test_997030-bookmarks-html-encode.js37
-rw-r--r--toolkit/components/places/tests/bookmarks/test_async_observers.js177
-rw-r--r--toolkit/components/places/tests/bookmarks/test_bmindex.js124
-rw-r--r--toolkit/components/places/tests/bookmarks/test_bookmarks.js718
-rw-r--r--toolkit/components/places/tests/bookmarks/test_bookmarks_eraseEverything.js116
-rw-r--r--toolkit/components/places/tests/bookmarks/test_bookmarks_fetch.js310
-rw-r--r--toolkit/components/places/tests/bookmarks/test_bookmarks_getRecent.js44
-rw-r--r--toolkit/components/places/tests/bookmarks/test_bookmarks_insert.js264
-rw-r--r--toolkit/components/places/tests/bookmarks/test_bookmarks_notifications.js527
-rw-r--r--toolkit/components/places/tests/bookmarks/test_bookmarks_remove.js204
-rw-r--r--toolkit/components/places/tests/bookmarks/test_bookmarks_reorder.js177
-rw-r--r--toolkit/components/places/tests/bookmarks/test_bookmarks_search.js223
-rw-r--r--toolkit/components/places/tests/bookmarks/test_bookmarks_update.js414
-rw-r--r--toolkit/components/places/tests/bookmarks/test_bookmarkstree_cache.js18
-rw-r--r--toolkit/components/places/tests/bookmarks/test_changeBookmarkURI.js68
-rw-r--r--toolkit/components/places/tests/bookmarks/test_getBookmarkedURIFor.js84
-rw-r--r--toolkit/components/places/tests/bookmarks/test_keywords.js310
-rw-r--r--toolkit/components/places/tests/bookmarks/test_nsINavBookmarkObserver.js640
-rw-r--r--toolkit/components/places/tests/bookmarks/test_protectRoots.js37
-rw-r--r--toolkit/components/places/tests/bookmarks/test_removeFolderTransaction_reinsert.js70
-rw-r--r--toolkit/components/places/tests/bookmarks/test_removeItem.js30
-rw-r--r--toolkit/components/places/tests/bookmarks/test_savedsearches.js209
-rw-r--r--toolkit/components/places/tests/bookmarks/xpcshell.ini50
48 files changed, 7127 insertions, 0 deletions
diff --git a/toolkit/components/places/tests/bookmarks/.eslintrc.js b/toolkit/components/places/tests/bookmarks/.eslintrc.js
new file mode 100644
index 000000000..d35787cd2
--- /dev/null
+++ b/toolkit/components/places/tests/bookmarks/.eslintrc.js
@@ -0,0 +1,7 @@
+"use strict";
+
+module.exports = {
+ "extends": [
+ "../../../../../testing/xpcshell/xpcshell.eslintrc.js"
+ ]
+};
diff --git a/toolkit/components/places/tests/bookmarks/head_bookmarks.js b/toolkit/components/places/tests/bookmarks/head_bookmarks.js
new file mode 100644
index 000000000..842a66b31
--- /dev/null
+++ b/toolkit/components/places/tests/bookmarks/head_bookmarks.js
@@ -0,0 +1,20 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+var Ci = Components.interfaces;
+var Cc = Components.classes;
+var Cr = Components.results;
+var Cu = Components.utils;
+
+Cu.import("resource://gre/modules/Services.jsm");
+
+// Import common head.
+{
+ let commonFile = do_get_file("../head_common.js", false);
+ let uri = Services.io.newFileURI(commonFile);
+ Services.scriptloader.loadSubScript(uri.spec, this);
+}
+
+// Put any other stuff relative to this test folder below.
diff --git a/toolkit/components/places/tests/bookmarks/test_1016953-renaming-uncompressed.js b/toolkit/components/places/tests/bookmarks/test_1016953-renaming-uncompressed.js
new file mode 100644
index 000000000..b6982987b
--- /dev/null
+++ b/toolkit/components/places/tests/bookmarks/test_1016953-renaming-uncompressed.js
@@ -0,0 +1,103 @@
+function run_test() {
+ run_next_test();
+}
+
+/* Bug 1016953 - When a previous bookmark backup exists with the same hash
+regardless of date, an automatic backup should attempt to either rename it to
+today's date if the backup was for an old date or leave it alone if it was for
+the same date. However if the file ext was json it will accidentally rename it
+to jsonlz4 while keeping the json contents
+*/
+
+add_task(function* test_same_date_same_hash() {
+ // If old file has been created on the same date and has the same hash
+ // the file should be left alone
+ let backupFolder = yield PlacesBackups.getBackupFolder();
+ // Save to profile dir to obtain hash and nodeCount to append to filename
+ let tempPath = OS.Path.join(OS.Constants.Path.profileDir,
+ "bug10169583_bookmarks.json");
+ let {count, hash} = yield BookmarkJSONUtils.exportToFile(tempPath);
+
+ // Save JSON file in backup folder with hash appended
+ let dateObj = new Date();
+ let filename = "bookmarks-" + PlacesBackups.toISODateString(dateObj) + "_" +
+ count + "_" + hash + ".json";
+ let backupFile = OS.Path.join(backupFolder, filename);
+ yield OS.File.move(tempPath, backupFile);
+
+ // Force a compressed backup which fallbacks to rename
+ yield PlacesBackups.create();
+ let mostRecentBackupFile = yield PlacesBackups.getMostRecentBackup();
+ // check to ensure not renamed to jsonlz4
+ Assert.equal(mostRecentBackupFile, backupFile);
+ // inspect contents and check if valid json
+ let converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"].
+ createInstance(Ci.nsIScriptableUnicodeConverter);
+ converter.charset = "UTF-8";
+ let result = yield OS.File.read(mostRecentBackupFile);
+ let jsonString = converter.convertFromByteArray(result, result.length);
+ do_print("Check is valid JSON");
+ JSON.parse(jsonString);
+
+ // Cleanup
+ yield OS.File.remove(backupFile);
+ yield OS.File.remove(tempPath);
+ PlacesBackups._backupFiles = null; // To force re-cache of backupFiles
+});
+
+add_task(function* test_same_date_diff_hash() {
+ // If the old file has been created on the same date, but has a different hash
+ // the existing file should be overwritten with the newer compressed version
+ let backupFolder = yield PlacesBackups.getBackupFolder();
+ let tempPath = OS.Path.join(OS.Constants.Path.profileDir,
+ "bug10169583_bookmarks.json");
+ let {count} = yield BookmarkJSONUtils.exportToFile(tempPath);
+ let dateObj = new Date();
+ let filename = "bookmarks-" + PlacesBackups.toISODateString(dateObj) + "_" +
+ count + "_" + "differentHash==" + ".json";
+ let backupFile = OS.Path.join(backupFolder, filename);
+ yield OS.File.move(tempPath, backupFile);
+ yield PlacesBackups.create(); // Force compressed backup
+ mostRecentBackupFile = yield PlacesBackups.getMostRecentBackup();
+
+ // Decode lz4 compressed file to json and check if json is valid
+ let converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"].
+ createInstance(Ci.nsIScriptableUnicodeConverter);
+ converter.charset = "UTF-8";
+ let result = yield OS.File.read(mostRecentBackupFile, { compression: "lz4" });
+ let jsonString = converter.convertFromByteArray(result, result.length);
+ do_print("Check is valid JSON");
+ JSON.parse(jsonString);
+
+ // Cleanup
+ yield OS.File.remove(mostRecentBackupFile);
+ yield OS.File.remove(tempPath);
+ PlacesBackups._backupFiles = null; // To force re-cache of backupFiles
+});
+
+add_task(function* test_diff_date_same_hash() {
+ // If the old file has been created on an older day but has the same hash
+ // it should be renamed with today's date without altering the contents.
+ let backupFolder = yield PlacesBackups.getBackupFolder();
+ let tempPath = OS.Path.join(OS.Constants.Path.profileDir,
+ "bug10169583_bookmarks.json");
+ let {count, hash} = yield BookmarkJSONUtils.exportToFile(tempPath);
+ let oldDate = new Date(2014, 1, 1);
+ let curDate = new Date();
+ let oldFilename = "bookmarks-" + PlacesBackups.toISODateString(oldDate) + "_" +
+ count + "_" + hash + ".json";
+ let newFilename = "bookmarks-" + PlacesBackups.toISODateString(curDate) + "_" +
+ count + "_" + hash + ".json";
+ let backupFile = OS.Path.join(backupFolder, oldFilename);
+ let newBackupFile = OS.Path.join(backupFolder, newFilename);
+ yield OS.File.move(tempPath, backupFile);
+
+ // Ensure file has been renamed correctly
+ yield PlacesBackups.create();
+ let mostRecentBackupFile = yield PlacesBackups.getMostRecentBackup();
+ Assert.equal(mostRecentBackupFile, newBackupFile);
+
+ // Cleanup
+ yield OS.File.remove(mostRecentBackupFile);
+ yield OS.File.remove(tempPath);
+});
diff --git a/toolkit/components/places/tests/bookmarks/test_1017502-bookmarks_foreign_count.js b/toolkit/components/places/tests/bookmarks/test_1017502-bookmarks_foreign_count.js
new file mode 100644
index 000000000..13755e576
--- /dev/null
+++ b/toolkit/components/places/tests/bookmarks/test_1017502-bookmarks_foreign_count.js
@@ -0,0 +1,112 @@
+/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* Bug 1017502 - Add a foreign_count column to moz_places
+This tests, tests the triggers that adjust the foreign_count when a bookmark is
+added or removed and also the maintenance task to fix wrong counts.
+*/
+
+const T_URI = NetUtil.newURI("https://www.mozilla.org/firefox/nightly/firstrun/");
+
+function* getForeignCountForURL(conn, url) {
+ yield PlacesTestUtils.promiseAsyncUpdates();
+ url = url instanceof Ci.nsIURI ? url.spec : url;
+ let rows = yield conn.executeCached(
+ `SELECT foreign_count FROM moz_places WHERE url_hash = hash(:t_url)
+ AND url = :t_url`, { t_url: url });
+ return rows[0].getResultByName("foreign_count");
+}
+
+function run_test() {
+ run_next_test();
+}
+
+add_task(function* add_remove_change_bookmark_test() {
+ let conn = yield PlacesUtils.promiseDBConnection();
+
+ // Simulate a visit to the url
+ yield PlacesTestUtils.addVisits(T_URI);
+ Assert.equal((yield getForeignCountForURL(conn, T_URI)), 0);
+
+ // Add 1st bookmark which should increment foreign_count by 1
+ let id1 = PlacesUtils.bookmarks.insertBookmark(PlacesUtils.unfiledBookmarksFolderId,
+ T_URI, PlacesUtils.bookmarks.DEFAULT_INDEX, "First Run");
+ Assert.equal((yield getForeignCountForURL(conn, T_URI)), 1);
+
+ // Add 2nd bookmark
+ let id2 = PlacesUtils.bookmarks.insertBookmark(PlacesUtils.bookmarksMenuFolderId,
+ T_URI, PlacesUtils.bookmarks.DEFAULT_INDEX, "First Run");
+ Assert.equal((yield getForeignCountForURL(conn, T_URI)), 2);
+
+ // Remove 2nd bookmark which should decrement foreign_count by 1
+ PlacesUtils.bookmarks.removeItem(id2);
+ Assert.equal((yield getForeignCountForURL(conn, T_URI)), 1);
+
+ // Change first bookmark's URI
+ const URI2 = NetUtil.newURI("http://www.mozilla.org");
+ PlacesUtils.bookmarks.changeBookmarkURI(id1, URI2);
+ // Check foreign count for original URI
+ Assert.equal((yield getForeignCountForURL(conn, T_URI)), 0);
+ // Check foreign count for new URI
+ Assert.equal((yield getForeignCountForURL(conn, URI2)), 1);
+
+ // Cleanup - Remove changed bookmark
+ let id = PlacesUtils.bookmarks.getBookmarkIdsForURI(URI2);
+ PlacesUtils.bookmarks.removeItem(id);
+ Assert.equal((yield getForeignCountForURL(conn, URI2)), 0);
+
+});
+
+add_task(function* maintenance_foreign_count_test() {
+ let conn = yield PlacesUtils.promiseDBConnection();
+
+ // Simulate a visit to the url
+ yield PlacesTestUtils.addVisits(T_URI);
+
+ // Adjust the foreign_count for the added entry to an incorrect value
+ let deferred = Promise.defer();
+ let stmt = DBConn().createAsyncStatement(
+ `UPDATE moz_places SET foreign_count = 10 WHERE url_hash = hash(:t_url)
+ AND url = :t_url `);
+ stmt.params.t_url = T_URI.spec;
+ stmt.executeAsync({
+ handleCompletion: function() {
+ deferred.resolve();
+ }
+ });
+ stmt.finalize();
+ yield deferred.promise;
+ Assert.equal((yield getForeignCountForURL(conn, T_URI)), 10);
+
+ // Run maintenance
+ Components.utils.import("resource://gre/modules/PlacesDBUtils.jsm");
+ let promiseMaintenanceFinished =
+ promiseTopicObserved("places-maintenance-finished");
+ PlacesDBUtils.maintenanceOnIdle();
+ yield promiseMaintenanceFinished;
+
+ // Check if the foreign_count has been adjusted to the correct value
+ Assert.equal((yield getForeignCountForURL(conn, T_URI)), 0);
+});
+
+add_task(function* add_remove_tags_test() {
+ let conn = yield PlacesUtils.promiseDBConnection();
+
+ yield PlacesTestUtils.addVisits(T_URI);
+ Assert.equal((yield getForeignCountForURL(conn, T_URI)), 0);
+
+ // Check foreign count incremented by 1 for a single tag
+ PlacesUtils.tagging.tagURI(T_URI, ["test tag"]);
+ Assert.equal((yield getForeignCountForURL(conn, T_URI)), 1);
+
+ // Check foreign count is incremented by 2 for two tags
+ PlacesUtils.tagging.tagURI(T_URI, ["one", "two"]);
+ Assert.equal((yield getForeignCountForURL(conn, T_URI)), 3);
+
+ // Check foreign count is set to 0 when all tags are removed
+ PlacesUtils.tagging.untagURI(T_URI, ["test tag", "one", "two"]);
+ Assert.equal((yield getForeignCountForURL(conn, T_URI)), 0);
+});
diff --git a/toolkit/components/places/tests/bookmarks/test_1129529.js b/toolkit/components/places/tests/bookmarks/test_1129529.js
new file mode 100644
index 000000000..da1ff708f
--- /dev/null
+++ b/toolkit/components/places/tests/bookmarks/test_1129529.js
@@ -0,0 +1,76 @@
+var now = Date.now() * 1000;
+
+// Test that importing bookmark data where a bookmark has a tag longer than 100
+// chars imports everything except the tags for that bookmark.
+add_task(function* () {
+ let aData = {
+ guid: "root________",
+ index: 0,
+ id: 1,
+ type: "text/x-moz-place-container",
+ dateAdded: now,
+ lastModified: now,
+ root: "placesRoot",
+ children: [{
+ guid: "unfiled_____",
+ index: 0,
+ id: 2,
+ type: "text/x-moz-place-container",
+ dateAdded: now,
+ lastModified: now,
+ root: "unfiledBookmarksFolder",
+ children: [
+ {
+ guid: "___guid1____",
+ index: 0,
+ id: 3,
+ charset: "UTF-8",
+ tags: "tag0",
+ type: "text/x-moz-place",
+ dateAdded: now,
+ lastModified: now,
+ uri: "http://test0.com/"
+ },
+ {
+ guid: "___guid2____",
+ index: 1,
+ id: 4,
+ charset: "UTF-8",
+ tags: "tag1," + "a" + "0123456789".repeat(10), // 101 chars
+ type: "text/x-moz-place",
+ dateAdded: now,
+ lastModified: now,
+ uri: "http://test1.com/"
+ },
+ {
+ guid: "___guid3____",
+ index: 2,
+ id: 5,
+ charset: "UTF-8",
+ tags: "tag2",
+ type: "text/x-moz-place",
+ dateAdded: now,
+ lastModified: now,
+ uri: "http://test2.com/"
+ }
+ ]
+ }]
+ };
+
+ let contentType = "application/json";
+ let uri = "data:" + contentType + "," + JSON.stringify(aData);
+ yield BookmarkJSONUtils.importFromURL(uri, false);
+
+ let [bookmarks] = yield PlacesBackups.getBookmarksTree();
+ let unsortedBookmarks = bookmarks.children[2].children;
+ Assert.equal(unsortedBookmarks.length, 3);
+
+ for (let i = 0; i < unsortedBookmarks.length; ++i) {
+ let bookmark = unsortedBookmarks[i];
+ Assert.equal(bookmark.charset, "UTF-8");
+ Assert.equal(bookmark.dateAdded, now);
+ Assert.equal(bookmark.lastModified, now);
+ Assert.equal(bookmark.uri, "http://test" + i + ".com/");
+ Assert.equal(bookmark.tags, "tag" + i);
+ }
+});
diff --git a/toolkit/components/places/tests/bookmarks/test_384228.js b/toolkit/components/places/tests/bookmarks/test_384228.js
new file mode 100644
index 000000000..9a52c9746
--- /dev/null
+++ b/toolkit/components/places/tests/bookmarks/test_384228.js
@@ -0,0 +1,98 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+ * test querying for bookmarks in multiple folders.
+ */
+add_task(function* search_bookmark_in_folder() {
+ let testFolder1 = yield PlacesUtils.bookmarks.insert({
+ parentGuid: PlacesUtils.bookmarks.unfiledGuid,
+ type: PlacesUtils.bookmarks.TYPE_FOLDER,
+ title: "bug 384228 test folder 1"
+ });
+ Assert.equal(testFolder1.index, 0);
+
+ let testFolder2 = yield PlacesUtils.bookmarks.insert({
+ parentGuid: PlacesUtils.bookmarks.unfiledGuid,
+ type: PlacesUtils.bookmarks.TYPE_FOLDER,
+ title: "bug 384228 test folder 2"
+ });
+ Assert.equal(testFolder2.index, 1);
+
+ let testFolder3 = yield PlacesUtils.bookmarks.insert({
+ parentGuid: PlacesUtils.bookmarks.unfiledGuid,
+ type: PlacesUtils.bookmarks.TYPE_FOLDER,
+ title: "bug 384228 test folder 3"
+ });
+ Assert.equal(testFolder3.index, 2);
+
+ let b1 = yield PlacesUtils.bookmarks.insert({
+ parentGuid: testFolder1.guid,
+ url: "http://foo.tld/",
+ title: "title b1 (folder 1)"
+ });
+ Assert.equal(b1.index, 0);
+
+ let b2 = yield PlacesUtils.bookmarks.insert({
+ parentGuid: testFolder1.guid,
+ url: "http://foo.tld/",
+ title: "title b2 (folder 1)"
+ });
+ Assert.equal(b2.index, 1);
+
+ let b3 = yield PlacesUtils.bookmarks.insert({
+ parentGuid: testFolder2.guid,
+ url: "http://foo.tld/",
+ title: "title b3 (folder 2)"
+ });
+ Assert.equal(b3.index, 0);
+
+ let b4 = yield PlacesUtils.bookmarks.insert({
+ parentGuid: testFolder3.guid,
+ url: "http://foo.tld/",
+ title: "title b4 (folder 3)"
+ });
+ Assert.equal(b4.index, 0);
+
+ // also test recursive search
+ let testFolder1_1 = yield PlacesUtils.bookmarks.insert({
+ parentGuid: testFolder1.guid,
+ type: PlacesUtils.bookmarks.TYPE_FOLDER,
+ title: "bug 384228 test folder 1.1"
+ });
+ Assert.equal(testFolder1_1.index, 2);
+
+ let b5 = yield PlacesUtils.bookmarks.insert({
+ parentGuid: testFolder1_1.guid,
+ url: "http://foo.tld/",
+ title: "title b5 (folder 1.1)"
+ });
+ Assert.equal(b5.index, 0);
+
+
+ // query folder 1, folder 2 and get 4 bookmarks
+ let folderIds = [];
+ folderIds.push(yield PlacesUtils.promiseItemId(testFolder1.guid));
+ folderIds.push(yield PlacesUtils.promiseItemId(testFolder2.guid));
+
+ let hs = PlacesUtils.history;
+ let options = hs.getNewQueryOptions();
+ let query = hs.getNewQuery();
+ query.searchTerms = "title";
+ options.queryType = options.QUERY_TYPE_BOOKMARKS;
+ query.setFolders(folderIds, folderIds.length);
+ let rootNode = hs.executeQuery(query, options).root;
+ rootNode.containerOpen = true;
+
+ // should not match item from folder 3
+ Assert.equal(rootNode.childCount, 4);
+ Assert.equal(rootNode.getChild(0).bookmarkGuid, b1.guid);
+ Assert.equal(rootNode.getChild(1).bookmarkGuid, b2.guid);
+ Assert.equal(rootNode.getChild(2).bookmarkGuid, b3.guid);
+ Assert.equal(rootNode.getChild(3).bookmarkGuid, b5.guid);
+
+ rootNode.containerOpen = false;
+});
diff --git a/toolkit/components/places/tests/bookmarks/test_385829.js b/toolkit/components/places/tests/bookmarks/test_385829.js
new file mode 100644
index 000000000..63beee5f3
--- /dev/null
+++ b/toolkit/components/places/tests/bookmarks/test_385829.js
@@ -0,0 +1,182 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+add_task(function* search_bookmark_by_lastModified_dateDated() {
+ // test search on folder with various sorts and max results
+ // see bug #385829 for more details
+ let folder = yield PlacesUtils.bookmarks.insert({
+ parentGuid: PlacesUtils.bookmarks.unfiledGuid,
+ type: PlacesUtils.bookmarks.TYPE_FOLDER,
+ title: "bug 385829 test"
+ });
+
+ let now = new Date();
+ // ensure some unique values for date added and last modified
+ // for date added: b1 < b2 < b3 < b4
+ // for last modified: b1 > b2 > b3 > b4
+ let b1 = yield PlacesUtils.bookmarks.insert({
+ parentGuid: folder.guid,
+ url: "http://a1.com/",
+ title: "1 title",
+ dateAdded: new Date(now.getTime() + 1000)
+ });
+ let b2 = yield PlacesUtils.bookmarks.insert({
+ parentGuid: folder.guid,
+ url: "http://a2.com/",
+ title: "2 title",
+ dateAdded: new Date(now.getTime() + 2000)
+ });
+ let b3 = yield PlacesUtils.bookmarks.insert({
+ parentGuid: folder.guid,
+ url: "http://a3.com/",
+ title: "3 title",
+ dateAdded: new Date(now.getTime() + 3000)
+ });
+ let b4 = yield PlacesUtils.bookmarks.insert({
+ parentGuid: folder.guid,
+ url: "http://a4.com/",
+ title: "4 title",
+ dateAdded: new Date(now.getTime() + 4000)
+ });
+
+ // make sure lastModified is larger than dateAdded
+ let modifiedTime = new Date(now.getTime() + 5000);
+ yield PlacesUtils.bookmarks.update({
+ guid: b1.guid,
+ lastModified: new Date(modifiedTime.getTime() + 4000)
+ });
+ yield PlacesUtils.bookmarks.update({
+ guid: b2.guid,
+ lastModified: new Date(modifiedTime.getTime() + 3000)
+ });
+ yield PlacesUtils.bookmarks.update({
+ guid: b3.guid,
+ lastModified: new Date(modifiedTime.getTime() + 2000)
+ });
+ yield PlacesUtils.bookmarks.update({
+ guid: b4.guid,
+ lastModified: new Date(modifiedTime.getTime() + 1000)
+ });
+
+ let hs = PlacesUtils.history;
+ let options = hs.getNewQueryOptions();
+ let query = hs.getNewQuery();
+ options.queryType = options.QUERY_TYPE_BOOKMARKS;
+ options.maxResults = 3;
+ let folderIds = [];
+ folderIds.push(yield PlacesUtils.promiseItemId(folder.guid));
+ query.setFolders(folderIds, 1);
+
+ let result = hs.executeQuery(query, options);
+ let rootNode = result.root;
+ rootNode.containerOpen = true;
+
+ // test SORT_BY_DATEADDED_ASCENDING (live update)
+ result.sortingMode = options.SORT_BY_DATEADDED_ASCENDING;
+ Assert.equal(rootNode.childCount, 3);
+ Assert.equal(rootNode.getChild(0).bookmarkGuid, b1.guid);
+ Assert.equal(rootNode.getChild(1).bookmarkGuid, b2.guid);
+ Assert.equal(rootNode.getChild(2).bookmarkGuid, b3.guid);
+ Assert.ok(rootNode.getChild(0).dateAdded <
+ rootNode.getChild(1).dateAdded);
+ Assert.ok(rootNode.getChild(1).dateAdded <
+ rootNode.getChild(2).dateAdded);
+
+ // test SORT_BY_DATEADDED_DESCENDING (live update)
+ result.sortingMode = options.SORT_BY_DATEADDED_DESCENDING;
+ Assert.equal(rootNode.childCount, 3);
+ Assert.equal(rootNode.getChild(0).bookmarkGuid, b3.guid);
+ Assert.equal(rootNode.getChild(1).bookmarkGuid, b2.guid);
+ Assert.equal(rootNode.getChild(2).bookmarkGuid, b1.guid);
+ Assert.ok(rootNode.getChild(0).dateAdded >
+ rootNode.getChild(1).dateAdded);
+ Assert.ok(rootNode.getChild(1).dateAdded >
+ rootNode.getChild(2).dateAdded);
+
+ // test SORT_BY_LASTMODIFIED_ASCENDING (live update)
+ result.sortingMode = options.SORT_BY_LASTMODIFIED_ASCENDING;
+ Assert.equal(rootNode.childCount, 3);
+ Assert.equal(rootNode.getChild(0).bookmarkGuid, b3.guid);
+ Assert.equal(rootNode.getChild(1).bookmarkGuid, b2.guid);
+ Assert.equal(rootNode.getChild(2).bookmarkGuid, b1.guid);
+ Assert.ok(rootNode.getChild(0).lastModified <
+ rootNode.getChild(1).lastModified);
+ Assert.ok(rootNode.getChild(1).lastModified <
+ rootNode.getChild(2).lastModified);
+
+ // test SORT_BY_LASTMODIFIED_DESCENDING (live update)
+ result.sortingMode = options.SORT_BY_LASTMODIFIED_DESCENDING;
+
+ Assert.equal(rootNode.childCount, 3);
+ Assert.equal(rootNode.getChild(0).bookmarkGuid, b1.guid);
+ Assert.equal(rootNode.getChild(1).bookmarkGuid, b2.guid);
+ Assert.equal(rootNode.getChild(2).bookmarkGuid, b3.guid);
+ Assert.ok(rootNode.getChild(0).lastModified >
+ rootNode.getChild(1).lastModified);
+ Assert.ok(rootNode.getChild(1).lastModified >
+ rootNode.getChild(2).lastModified);
+ rootNode.containerOpen = false;
+
+ // test SORT_BY_DATEADDED_ASCENDING
+ options.sortingMode = options.SORT_BY_DATEADDED_ASCENDING;
+ result = hs.executeQuery(query, options);
+ rootNode = result.root;
+ rootNode.containerOpen = true;
+ Assert.equal(rootNode.childCount, 3);
+ Assert.equal(rootNode.getChild(0).bookmarkGuid, b1.guid);
+ Assert.equal(rootNode.getChild(1).bookmarkGuid, b2.guid);
+ Assert.equal(rootNode.getChild(2).bookmarkGuid, b3.guid);
+ Assert.ok(rootNode.getChild(0).dateAdded <
+ rootNode.getChild(1).dateAdded);
+ Assert.ok(rootNode.getChild(1).dateAdded <
+ rootNode.getChild(2).dateAdded);
+ rootNode.containerOpen = false;
+
+ // test SORT_BY_DATEADDED_DESCENDING
+ options.sortingMode = options.SORT_BY_DATEADDED_DESCENDING;
+ result = hs.executeQuery(query, options);
+ rootNode = result.root;
+ rootNode.containerOpen = true;
+ Assert.equal(rootNode.childCount, 3);
+ Assert.equal(rootNode.getChild(0).bookmarkGuid, b4.guid);
+ Assert.equal(rootNode.getChild(1).bookmarkGuid, b3.guid);
+ Assert.equal(rootNode.getChild(2).bookmarkGuid, b2.guid);
+ Assert.ok(rootNode.getChild(0).dateAdded >
+ rootNode.getChild(1).dateAdded);
+ Assert.ok(rootNode.getChild(1).dateAdded >
+ rootNode.getChild(2).dateAdded);
+ rootNode.containerOpen = false;
+
+ // test SORT_BY_LASTMODIFIED_ASCENDING
+ options.sortingMode = options.SORT_BY_LASTMODIFIED_ASCENDING;
+ result = hs.executeQuery(query, options);
+ rootNode = result.root;
+ rootNode.containerOpen = true;
+ Assert.equal(rootNode.childCount, 3);
+ Assert.equal(rootNode.getChild(0).bookmarkGuid, b4.guid);
+ Assert.equal(rootNode.getChild(1).bookmarkGuid, b3.guid);
+ Assert.equal(rootNode.getChild(2).bookmarkGuid, b2.guid);
+ Assert.ok(rootNode.getChild(0).lastModified <
+ rootNode.getChild(1).lastModified);
+ Assert.ok(rootNode.getChild(1).lastModified <
+ rootNode.getChild(2).lastModified);
+ rootNode.containerOpen = false;
+
+ // test SORT_BY_LASTMODIFIED_DESCENDING
+ options.sortingMode = options.SORT_BY_LASTMODIFIED_DESCENDING;
+ result = hs.executeQuery(query, options);
+ rootNode = result.root;
+ rootNode.containerOpen = true;
+ Assert.equal(rootNode.childCount, 3);
+ Assert.equal(rootNode.getChild(0).bookmarkGuid, b1.guid);
+ Assert.equal(rootNode.getChild(1).bookmarkGuid, b2.guid);
+ Assert.equal(rootNode.getChild(2).bookmarkGuid, b3.guid);
+ Assert.ok(rootNode.getChild(0).lastModified >
+ rootNode.getChild(1).lastModified);
+ Assert.ok(rootNode.getChild(1).lastModified >
+ rootNode.getChild(2).lastModified);
+ rootNode.containerOpen = false;
+});
diff --git a/toolkit/components/places/tests/bookmarks/test_388695.js b/toolkit/components/places/tests/bookmarks/test_388695.js
new file mode 100644
index 000000000..4e313c52f
--- /dev/null
+++ b/toolkit/components/places/tests/bookmarks/test_388695.js
@@ -0,0 +1,52 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// Get bookmark service
+try {
+ var bmsvc = Cc["@mozilla.org/browser/nav-bookmarks-service;1"].
+ getService(Ci.nsINavBookmarksService);
+} catch (ex) {
+ do_throw("Could not get nav-bookmarks-service\n");
+}
+
+var gTestRoot;
+var gURI;
+var gItemId1;
+var gItemId2;
+
+// main
+function run_test() {
+ gURI = uri("http://foo.tld.com/");
+ gTestRoot = bmsvc.createFolder(bmsvc.placesRoot, "test folder",
+ bmsvc.DEFAULT_INDEX);
+
+ // test getBookmarkIdsForURI
+ // getBookmarkIdsForURI sorts by the most recently added/modified (descending)
+ //
+ // we cannot rely on dateAdded growing when doing so in a simple iteration,
+ // see PR_Now() documentation
+ do_test_pending();
+
+ gItemId1 = bmsvc.insertBookmark(gTestRoot, gURI, bmsvc.DEFAULT_INDEX, "");
+ do_timeout(100, phase2);
+}
+
+function phase2() {
+ gItemId2 = bmsvc.insertBookmark(gTestRoot, gURI, bmsvc.DEFAULT_INDEX, "");
+ var b = bmsvc.getBookmarkIdsForURI(gURI);
+ do_check_eq(b[0], gItemId2);
+ do_check_eq(b[1], gItemId1);
+ do_timeout(100, phase3);
+}
+
+function phase3() {
+ // trigger last modified change
+ bmsvc.setItemTitle(gItemId1, "");
+ var b = bmsvc.getBookmarkIdsForURI(gURI);
+ do_check_eq(b[0], gItemId1);
+ do_check_eq(b[1], gItemId2);
+ do_test_finished();
+}
diff --git a/toolkit/components/places/tests/bookmarks/test_393498.js b/toolkit/components/places/tests/bookmarks/test_393498.js
new file mode 100644
index 000000000..601f77a0a
--- /dev/null
+++ b/toolkit/components/places/tests/bookmarks/test_393498.js
@@ -0,0 +1,102 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+var observer = {
+ __proto__: NavBookmarkObserver.prototype,
+
+ onItemAdded: function (id, folder, index) {
+ this._itemAddedId = id;
+ this._itemAddedParent = folder;
+ this._itemAddedIndex = index;
+ },
+ onItemChanged: function (id, property, isAnnotationProperty, value) {
+ this._itemChangedId = id;
+ this._itemChangedProperty = property;
+ this._itemChanged_isAnnotationProperty = isAnnotationProperty;
+ this._itemChangedValue = value;
+ }
+};
+PlacesUtils.bookmarks.addObserver(observer, false);
+
+do_register_cleanup(function () {
+ PlacesUtils.bookmarks.removeObserver(observer);
+});
+
+function run_test() {
+ // We set times in the past to workaround a timing bug due to virtual
+ // machines and the skew between PR_Now() and Date.now(), see bug 427142 and
+ // bug 858377 for details.
+ const PAST_PRTIME = (Date.now() - 86400000) * 1000;
+
+ // Insert a new bookmark.
+ let testFolder = PlacesUtils.bookmarks.createFolder(
+ PlacesUtils.placesRootId, "test Folder",
+ PlacesUtils.bookmarks.DEFAULT_INDEX);
+ let bookmarkId = PlacesUtils.bookmarks.insertBookmark(
+ testFolder, uri("http://google.com/"),
+ PlacesUtils.bookmarks.DEFAULT_INDEX, "");
+
+ // Sanity check.
+ do_check_true(observer.itemChangedProperty === undefined);
+
+ // Set dateAdded in the past and verify the bookmarks cache.
+ PlacesUtils.bookmarks.setItemDateAdded(bookmarkId, PAST_PRTIME);
+ do_check_eq(observer._itemChangedProperty, "dateAdded");
+ do_check_eq(observer._itemChangedValue, PAST_PRTIME);
+ let dateAdded = PlacesUtils.bookmarks.getItemDateAdded(bookmarkId);
+ do_check_eq(dateAdded, PAST_PRTIME);
+
+ // After just inserting, modified should be the same as dateAdded.
+ do_check_eq(PlacesUtils.bookmarks.getItemLastModified(bookmarkId), dateAdded);
+
+ // Set lastModified in the past and verify the bookmarks cache.
+ PlacesUtils.bookmarks.setItemLastModified(bookmarkId, PAST_PRTIME);
+ do_check_eq(observer._itemChangedProperty, "lastModified");
+ do_check_eq(observer._itemChangedValue, PAST_PRTIME);
+ do_check_eq(PlacesUtils.bookmarks.getItemLastModified(bookmarkId),
+ PAST_PRTIME);
+
+ // Set bookmark title
+ PlacesUtils.bookmarks.setItemTitle(bookmarkId, "Google");
+
+ // Test notifications.
+ do_check_eq(observer._itemChangedId, bookmarkId);
+ do_check_eq(observer._itemChangedProperty, "title");
+ do_check_eq(observer._itemChangedValue, "Google");
+
+ // Check lastModified has been updated.
+ is_time_ordered(PAST_PRTIME,
+ PlacesUtils.bookmarks.getItemLastModified(bookmarkId));
+
+ // Check that node properties are updated.
+ let root = PlacesUtils.getFolderContents(testFolder).root;
+ do_check_eq(root.childCount, 1);
+ let childNode = root.getChild(0);
+
+ // confirm current dates match node properties
+ do_check_eq(PlacesUtils.bookmarks.getItemDateAdded(bookmarkId),
+ childNode.dateAdded);
+ do_check_eq(PlacesUtils.bookmarks.getItemLastModified(bookmarkId),
+ childNode.lastModified);
+
+ // Test live update of lastModified when setting title.
+ PlacesUtils.bookmarks.setItemLastModified(bookmarkId, PAST_PRTIME);
+ PlacesUtils.bookmarks.setItemTitle(bookmarkId, "Google");
+
+ // Check lastModified has been updated.
+ is_time_ordered(PAST_PRTIME, childNode.lastModified);
+ // Test that node value matches db value.
+ do_check_eq(PlacesUtils.bookmarks.getItemLastModified(bookmarkId),
+ childNode.lastModified);
+
+ // Test live update of the exposed date apis.
+ PlacesUtils.bookmarks.setItemDateAdded(bookmarkId, PAST_PRTIME);
+ do_check_eq(childNode.dateAdded, PAST_PRTIME);
+ PlacesUtils.bookmarks.setItemLastModified(bookmarkId, PAST_PRTIME);
+ do_check_eq(childNode.lastModified, PAST_PRTIME);
+
+ root.containerOpen = false;
+}
diff --git a/toolkit/components/places/tests/bookmarks/test_395101.js b/toolkit/components/places/tests/bookmarks/test_395101.js
new file mode 100644
index 000000000..a507e7361
--- /dev/null
+++ b/toolkit/components/places/tests/bookmarks/test_395101.js
@@ -0,0 +1,87 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// Get bookmark service
+try {
+ var bmsvc = Cc["@mozilla.org/browser/nav-bookmarks-service;1"].getService(Ci.nsINavBookmarksService);
+} catch (ex) {
+ do_throw("Could not get nav-bookmarks-service\n");
+}
+
+// Get history service
+try {
+ var histsvc = Cc["@mozilla.org/browser/nav-history-service;1"].getService(Ci.nsINavHistoryService);
+} catch (ex) {
+ do_throw("Could not get history service\n");
+}
+
+// Get tagging service
+try {
+ var tagssvc = Cc["@mozilla.org/browser/tagging-service;1"].
+ getService(Ci.nsITaggingService);
+} catch (ex) {
+ do_throw("Could not get tagging service\n");
+}
+
+// get bookmarks root id
+var root = bmsvc.bookmarksMenuFolder;
+
+// main
+function run_test() {
+ // test searching for tagged bookmarks
+
+ // test folder
+ var folder = bmsvc.createFolder(root, "bug 395101 test", bmsvc.DEFAULT_INDEX);
+
+ // create a bookmark
+ var testURI = uri("http://a1.com");
+ var b1 = bmsvc.insertBookmark(folder, testURI,
+ bmsvc.DEFAULT_INDEX, "1 title");
+
+ // tag the bookmarked URI
+ tagssvc.tagURI(testURI, ["elephant", "walrus", "giraffe", "turkey", "hiPPo", "BABOON", "alf"]);
+
+ // search for the bookmark, using a tag
+ var query = histsvc.getNewQuery();
+ query.searchTerms = "elephant";
+ var options = histsvc.getNewQueryOptions();
+ options.queryType = Ci.nsINavHistoryQueryOptions.QUERY_TYPE_BOOKMARKS;
+ query.setFolders([folder], 1);
+
+ var result = histsvc.executeQuery(query, options);
+ var rootNode = result.root;
+ rootNode.containerOpen = true;
+
+ do_check_eq(rootNode.childCount, 1);
+ do_check_eq(rootNode.getChild(0).itemId, b1);
+ rootNode.containerOpen = false;
+
+ // partial matches are okay
+ query.searchTerms = "wal";
+ result = histsvc.executeQuery(query, options);
+ rootNode = result.root;
+ rootNode.containerOpen = true;
+ do_check_eq(rootNode.childCount, 1);
+ rootNode.containerOpen = false;
+
+ // case insensitive search term
+ query.searchTerms = "WALRUS";
+ result = histsvc.executeQuery(query, options);
+ rootNode = result.root;
+ rootNode.containerOpen = true;
+ do_check_eq(rootNode.childCount, 1);
+ do_check_eq(rootNode.getChild(0).itemId, b1);
+ rootNode.containerOpen = false;
+
+ // case insensitive tag
+ query.searchTerms = "baboon";
+ result = histsvc.executeQuery(query, options);
+ rootNode = result.root;
+ rootNode.containerOpen = true;
+ do_check_eq(rootNode.childCount, 1);
+ do_check_eq(rootNode.getChild(0).itemId, b1);
+ rootNode.containerOpen = false;
+}
diff --git a/toolkit/components/places/tests/bookmarks/test_395593.js b/toolkit/components/places/tests/bookmarks/test_395593.js
new file mode 100644
index 000000000..46d8f5b80
--- /dev/null
+++ b/toolkit/components/places/tests/bookmarks/test_395593.js
@@ -0,0 +1,69 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+
+var bs = Cc["@mozilla.org/browser/nav-bookmarks-service;1"].
+ getService(Ci.nsINavBookmarksService);
+var hs = Cc["@mozilla.org/browser/nav-history-service;1"].
+ getService(Ci.nsINavHistoryService);
+
+function check_queries_results(aQueries, aOptions, aExpectedItemIds) {
+ var result = hs.executeQueries(aQueries, aQueries.length, aOptions);
+ var root = result.root;
+ root.containerOpen = true;
+
+ // Dump found nodes.
+ for (let i = 0; i < root.childCount; i++) {
+ dump("nodes[" + i + "]: " + root.getChild(0).title + "\n");
+ }
+
+ do_check_eq(root.childCount, aExpectedItemIds.length);
+ for (let i = 0; i < root.childCount; i++) {
+ do_check_eq(root.getChild(i).itemId, aExpectedItemIds[i]);
+ }
+
+ root.containerOpen = false;
+}
+
+// main
+function run_test() {
+ var id1 = bs.insertBookmark(bs.bookmarksMenuFolder, uri("http://foo.tld"),
+ bs.DEFAULT_INDEX, "123 0");
+ var id2 = bs.insertBookmark(bs.bookmarksMenuFolder, uri("http://foo.tld"),
+ bs.DEFAULT_INDEX, "456");
+ var id3 = bs.insertBookmark(bs.bookmarksMenuFolder, uri("http://foo.tld"),
+ bs.DEFAULT_INDEX, "123 456");
+ var id4 = bs.insertBookmark(bs.bookmarksMenuFolder, uri("http://foo.tld"),
+ bs.DEFAULT_INDEX, "789 456");
+
+ /**
+ * All of the query objects are ORed together. Within a query, all the terms
+ * are ANDed together. See nsINavHistory.idl.
+ */
+ var queries = [];
+ queries.push(hs.getNewQuery());
+ queries.push(hs.getNewQuery());
+ var options = hs.getNewQueryOptions();
+ options.queryType = Ci.nsINavHistoryQueryOptions.QUERY_TYPE_BOOKMARKS;
+
+ // Test 1
+ dump("Test searching for 123 OR 789\n");
+ queries[0].searchTerms = "123";
+ queries[1].searchTerms = "789";
+ check_queries_results(queries, options, [id1, id3, id4]);
+
+ // Test 2
+ dump("Test searching for 123 OR 456\n");
+ queries[0].searchTerms = "123";
+ queries[1].searchTerms = "456";
+ check_queries_results(queries, options, [id1, id2, id3, id4]);
+
+ // Test 3
+ dump("Test searching for 00 OR 789\n");
+ queries[0].searchTerms = "00";
+ queries[1].searchTerms = "789";
+ check_queries_results(queries, options, [id4]);
+}
diff --git a/toolkit/components/places/tests/bookmarks/test_405938_restore_queries.js b/toolkit/components/places/tests/bookmarks/test_405938_restore_queries.js
new file mode 100644
index 000000000..e317cc2e9
--- /dev/null
+++ b/toolkit/components/places/tests/bookmarks/test_405938_restore_queries.js
@@ -0,0 +1,221 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+var tests = [];
+
+/*
+
+Backup/restore tests example:
+
+var myTest = {
+ populate: function () { ... add bookmarks ... },
+ validate: function () { ... query for your bookmarks ... }
+}
+
+this.push(myTest);
+
+*/
+
+/*
+
+test summary:
+- create folders with content
+- create a query bookmark for those folders
+- backs up bookmarks
+- restores bookmarks
+- confirms that the query has the new ids for the same folders
+
+scenarios:
+- 1 folder (folder shortcut)
+- n folders (single query)
+- n folders (multiple queries)
+
+*/
+
+const DEFAULT_INDEX = PlacesUtils.bookmarks.DEFAULT_INDEX;
+
+var test = {
+ _testRootId: null,
+ _testRootTitle: "test root",
+ _folderIds: [],
+ _bookmarkURIs: [],
+ _count: 3,
+
+ populate: function populate() {
+ // folder to hold this test
+ PlacesUtils.bookmarks.removeFolderChildren(PlacesUtils.toolbarFolderId);
+ this._testRootId =
+ PlacesUtils.bookmarks.createFolder(PlacesUtils.toolbarFolderId,
+ this._testRootTitle, DEFAULT_INDEX);
+
+ // create test folders each with a bookmark
+ for (var i = 0; i < this._count; i++) {
+ var folderId =
+ PlacesUtils.bookmarks.createFolder(this._testRootId, "folder" + i, DEFAULT_INDEX);
+ this._folderIds.push(folderId)
+
+ var bookmarkURI = uri("http://" + i);
+ PlacesUtils.bookmarks.insertBookmark(folderId, bookmarkURI,
+ DEFAULT_INDEX, "bookmark" + i);
+ this._bookmarkURIs.push(bookmarkURI);
+ }
+
+ // create a query URI with 1 folder (ie: folder shortcut)
+ this._queryURI1 = uri("place:folder=" + this._folderIds[0] + "&queryType=1");
+ this._queryTitle1 = "query1";
+ PlacesUtils.bookmarks.insertBookmark(this._testRootId, this._queryURI1,
+ DEFAULT_INDEX, this._queryTitle1);
+
+ // create a query URI with _count folders
+ this._queryURI2 = uri("place:folder=" + this._folderIds.join("&folder=") + "&queryType=1");
+ this._queryTitle2 = "query2";
+ PlacesUtils.bookmarks.insertBookmark(this._testRootId, this._queryURI2,
+ DEFAULT_INDEX, this._queryTitle2);
+
+ // create a query URI with _count queries (each with a folder)
+ // first get a query object for each folder
+ var queries = this._folderIds.map(function(aFolderId) {
+ var query = PlacesUtils.history.getNewQuery();
+ query.setFolders([aFolderId], 1);
+ return query;
+ });
+ var options = PlacesUtils.history.getNewQueryOptions();
+ options.queryType = options.QUERY_TYPE_BOOKMARKS;
+ this._queryURI3 =
+ uri(PlacesUtils.history.queriesToQueryString(queries, queries.length, options));
+ this._queryTitle3 = "query3";
+ PlacesUtils.bookmarks.insertBookmark(this._testRootId, this._queryURI3,
+ DEFAULT_INDEX, this._queryTitle3);
+ },
+
+ clean: function () {},
+
+ validate: function validate() {
+ // Throw a wrench in the works by inserting some new bookmarks,
+ // ensuring folder ids won't be the same, when restoring.
+ for (let i = 0; i < 10; i++) {
+ PlacesUtils.bookmarks.
+ insertBookmark(PlacesUtils.bookmarksMenuFolderId, uri("http://aaaa"+i), DEFAULT_INDEX, "");
+ }
+
+ var toolbar =
+ PlacesUtils.getFolderContents(PlacesUtils.toolbarFolderId,
+ false, true).root;
+ do_check_true(toolbar.childCount, 1);
+
+ var folderNode = toolbar.getChild(0);
+ do_check_eq(folderNode.type, folderNode.RESULT_TYPE_FOLDER);
+ do_check_eq(folderNode.title, this._testRootTitle);
+ folderNode.QueryInterface(Ci.nsINavHistoryQueryResultNode);
+ folderNode.containerOpen = true;
+
+ // |_count| folders + the query node
+ do_check_eq(folderNode.childCount, this._count+3);
+
+ for (let i = 0; i < this._count; i++) {
+ var subFolder = folderNode.getChild(i);
+ do_check_eq(subFolder.title, "folder"+i);
+ subFolder.QueryInterface(Ci.nsINavHistoryContainerResultNode);
+ subFolder.containerOpen = true;
+ do_check_eq(subFolder.childCount, 1);
+ var child = subFolder.getChild(0);
+ do_check_eq(child.title, "bookmark"+i);
+ do_check_true(uri(child.uri).equals(uri("http://" + i)))
+ }
+
+ // validate folder shortcut
+ this.validateQueryNode1(folderNode.getChild(this._count));
+
+ // validate folders query
+ this.validateQueryNode2(folderNode.getChild(this._count + 1));
+
+ // validate multiple queries query
+ this.validateQueryNode3(folderNode.getChild(this._count + 2));
+
+ // clean up
+ folderNode.containerOpen = false;
+ toolbar.containerOpen = false;
+ },
+
+ validateQueryNode1: function validateQueryNode1(aNode) {
+ do_check_eq(aNode.title, this._queryTitle1);
+ do_check_true(PlacesUtils.nodeIsFolder(aNode));
+
+ aNode.QueryInterface(Ci.nsINavHistoryContainerResultNode);
+ aNode.containerOpen = true;
+ do_check_eq(aNode.childCount, 1);
+ var child = aNode.getChild(0);
+ do_check_true(uri(child.uri).equals(uri("http://0")))
+ do_check_eq(child.title, "bookmark0")
+ aNode.containerOpen = false;
+ },
+
+ validateQueryNode2: function validateQueryNode2(aNode) {
+ do_check_eq(aNode.title, this._queryTitle2);
+ do_check_true(PlacesUtils.nodeIsQuery(aNode));
+
+ aNode.QueryInterface(Ci.nsINavHistoryContainerResultNode);
+ aNode.containerOpen = true;
+ do_check_eq(aNode.childCount, this._count);
+ for (var i = 0; i < aNode.childCount; i++) {
+ var child = aNode.getChild(i);
+ do_check_true(uri(child.uri).equals(uri("http://" + i)))
+ do_check_eq(child.title, "bookmark" + i)
+ }
+ aNode.containerOpen = false;
+ },
+
+ validateQueryNode3: function validateQueryNode3(aNode) {
+ do_check_eq(aNode.title, this._queryTitle3);
+ do_check_true(PlacesUtils.nodeIsQuery(aNode));
+
+ aNode.QueryInterface(Ci.nsINavHistoryContainerResultNode);
+ aNode.containerOpen = true;
+ do_check_eq(aNode.childCount, this._count);
+ for (var i = 0; i < aNode.childCount; i++) {
+ var child = aNode.getChild(i);
+ do_check_true(uri(child.uri).equals(uri("http://" + i)))
+ do_check_eq(child.title, "bookmark" + i)
+ }
+ aNode.containerOpen = false;
+ }
+}
+tests.push(test);
+
+function run_test() {
+ run_next_test();
+}
+
+add_task(function* () {
+ // make json file
+ let jsonFile = OS.Path.join(OS.Constants.Path.profileDir, "bookmarks.json");
+
+ // populate db
+ tests.forEach(function(aTest) {
+ aTest.populate();
+ // sanity
+ aTest.validate();
+ });
+
+ // export json to file
+ yield BookmarkJSONUtils.exportToFile(jsonFile);
+
+ // clean
+ tests.forEach(function(aTest) {
+ aTest.clean();
+ });
+
+ // restore json file
+ yield BookmarkJSONUtils.importFromFile(jsonFile, true);
+
+ // validate
+ tests.forEach(function(aTest) {
+ aTest.validate();
+ });
+
+ // clean up
+ yield OS.File.remove(jsonFile);
+});
diff --git a/toolkit/components/places/tests/bookmarks/test_417228-exclude-from-backup.js b/toolkit/components/places/tests/bookmarks/test_417228-exclude-from-backup.js
new file mode 100644
index 000000000..858496856
--- /dev/null
+++ b/toolkit/components/places/tests/bookmarks/test_417228-exclude-from-backup.js
@@ -0,0 +1,141 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+const EXCLUDE_FROM_BACKUP_ANNO = "places/excludeFromBackup";
+// Menu, Toolbar, Unsorted, Tags, Mobile
+const PLACES_ROOTS_COUNT = 5;
+var tests = [];
+
+/*
+
+Backup/restore tests example:
+
+var myTest = {
+ populate: function () { ... add bookmarks ... },
+ validate: function () { ... query for your bookmarks ... }
+}
+
+this.push(myTest);
+
+*/
+
+var test = {
+ populate: function populate() {
+ // check initial size
+ var rootNode = PlacesUtils.getFolderContents(PlacesUtils.placesRootId,
+ false, false).root;
+ do_check_eq(rootNode.childCount, PLACES_ROOTS_COUNT );
+ rootNode.containerOpen = false;
+
+ var idx = PlacesUtils.bookmarks.DEFAULT_INDEX;
+
+ // create a root to be restore
+ this._restoreRootTitle = "restore root";
+ var restoreRootId = PlacesUtils.bookmarks
+ .createFolder(PlacesUtils.placesRootId,
+ this._restoreRootTitle, idx);
+ // add a test bookmark
+ this._restoreRootURI = uri("http://restore.uri");
+ PlacesUtils.bookmarks.insertBookmark(restoreRootId, this._restoreRootURI,
+ idx, "restore uri");
+ // add a test bookmark to be exclude
+ this._restoreRootExcludeURI = uri("http://exclude.uri");
+ var exItemId = PlacesUtils.bookmarks
+ .insertBookmark(restoreRootId,
+ this._restoreRootExcludeURI,
+ idx, "exclude uri");
+ // Annotate the bookmark for exclusion.
+ PlacesUtils.annotations.setItemAnnotation(exItemId,
+ EXCLUDE_FROM_BACKUP_ANNO, 1, 0,
+ PlacesUtils.annotations.EXPIRE_NEVER);
+
+ // create a root to be exclude
+ this._excludeRootTitle = "exclude root";
+ this._excludeRootId = PlacesUtils.bookmarks
+ .createFolder(PlacesUtils.placesRootId,
+ this._excludeRootTitle, idx);
+ // Annotate the root for exclusion.
+ PlacesUtils.annotations.setItemAnnotation(this._excludeRootId,
+ EXCLUDE_FROM_BACKUP_ANNO, 1, 0,
+ PlacesUtils.annotations.EXPIRE_NEVER);
+ // add a test bookmark exclude by exclusion of its parent
+ PlacesUtils.bookmarks.insertBookmark(this._excludeRootId,
+ this._restoreRootExcludeURI,
+ idx, "exclude uri");
+ },
+
+ validate: function validate(aEmptyBookmarks) {
+ var rootNode = PlacesUtils.getFolderContents(PlacesUtils.placesRootId,
+ false, false).root;
+
+ if (!aEmptyBookmarks) {
+ // since restore does not remove backup exclude items both
+ // roots should still exist.
+ do_check_eq(rootNode.childCount, PLACES_ROOTS_COUNT + 2);
+ // open exclude root and check it still contains one item
+ var restoreRootIndex = PLACES_ROOTS_COUNT;
+ var excludeRootIndex = PLACES_ROOTS_COUNT+1;
+ var excludeRootNode = rootNode.getChild(excludeRootIndex);
+ do_check_eq(this._excludeRootTitle, excludeRootNode.title);
+ excludeRootNode.QueryInterface(Ci.nsINavHistoryQueryResultNode);
+ excludeRootNode.containerOpen = true;
+ do_check_eq(excludeRootNode.childCount, 1);
+ var excludeRootChildNode = excludeRootNode.getChild(0);
+ do_check_eq(excludeRootChildNode.uri, this._restoreRootExcludeURI.spec);
+ excludeRootNode.containerOpen = false;
+ }
+ else {
+ // exclude root should not exist anymore
+ do_check_eq(rootNode.childCount, PLACES_ROOTS_COUNT + 1);
+ restoreRootIndex = PLACES_ROOTS_COUNT;
+ }
+
+ var restoreRootNode = rootNode.getChild(restoreRootIndex);
+ do_check_eq(this._restoreRootTitle, restoreRootNode.title);
+ restoreRootNode.QueryInterface(Ci.nsINavHistoryQueryResultNode);
+ restoreRootNode.containerOpen = true;
+ do_check_eq(restoreRootNode.childCount, 1);
+ var restoreRootChildNode = restoreRootNode.getChild(0);
+ do_check_eq(restoreRootChildNode.uri, this._restoreRootURI.spec);
+ restoreRootNode.containerOpen = false;
+
+ rootNode.containerOpen = false;
+ }
+}
+
+function run_test() {
+ run_next_test();
+}
+
+add_task(function*() {
+ // make json file
+ let jsonFile = OS.Path.join(OS.Constants.Path.profileDir, "bookmarks.json");
+
+ // populate db
+ test.populate();
+
+ yield BookmarkJSONUtils.exportToFile(jsonFile);
+
+ // restore json file
+ yield BookmarkJSONUtils.importFromFile(jsonFile, true);
+
+ // validate without removing all bookmarks
+ // restore do not remove backup exclude entries
+ test.validate(false);
+
+ // cleanup
+ yield PlacesUtils.bookmarks.eraseEverything();
+ // manually remove the excluded root
+ PlacesUtils.bookmarks.removeItem(test._excludeRootId);
+ // restore json file
+ yield BookmarkJSONUtils.importFromFile(jsonFile, true);
+
+ // validate after a complete bookmarks cleanup
+ test.validate(true);
+
+ // clean up
+ yield OS.File.remove(jsonFile);
+});
diff --git a/toolkit/components/places/tests/bookmarks/test_417228-other-roots.js b/toolkit/components/places/tests/bookmarks/test_417228-other-roots.js
new file mode 100644
index 000000000..1def75d2d
--- /dev/null
+++ b/toolkit/components/places/tests/bookmarks/test_417228-other-roots.js
@@ -0,0 +1,158 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+var tests = [];
+
+/*
+
+Backup/restore tests example:
+
+var myTest = {
+ populate: function () { ... add bookmarks ... },
+ validate: function () { ... query for your bookmarks ... }
+}
+
+this.push(myTest);
+
+*/
+
+tests.push({
+ excludeItemsFromRestore: [],
+ populate: function populate() {
+ // check initial size
+ var rootNode = PlacesUtils.getFolderContents(PlacesUtils.placesRootId,
+ false, false).root;
+ do_check_eq(rootNode.childCount, 5);
+
+ // create a test root
+ this._folderTitle = "test folder";
+ this._folderId =
+ PlacesUtils.bookmarks.createFolder(PlacesUtils.placesRootId,
+ this._folderTitle,
+ PlacesUtils.bookmarks.DEFAULT_INDEX);
+ do_check_eq(rootNode.childCount, 6);
+
+ // add a tag
+ this._testURI = PlacesUtils._uri("http://test");
+ this._tags = ["a", "b"];
+ PlacesUtils.tagging.tagURI(this._testURI, this._tags);
+
+ // add a child to each root, including our test root
+ this._roots = [PlacesUtils.bookmarksMenuFolderId, PlacesUtils.toolbarFolderId,
+ PlacesUtils.unfiledBookmarksFolderId, PlacesUtils.mobileFolderId,
+ this._folderId];
+ this._roots.forEach(function(aRootId) {
+ // clean slate
+ PlacesUtils.bookmarks.removeFolderChildren(aRootId);
+ // add a test bookmark
+ PlacesUtils.bookmarks.insertBookmark(aRootId, this._testURI,
+ PlacesUtils.bookmarks.DEFAULT_INDEX, "test");
+ }, this);
+
+ // add a folder to exclude from replacing during restore
+ // this will still be present post-restore
+ var excludedFolderId =
+ PlacesUtils.bookmarks.createFolder(PlacesUtils.placesRootId,
+ "excluded",
+ PlacesUtils.bookmarks.DEFAULT_INDEX);
+ do_check_eq(rootNode.childCount, 7);
+ this.excludeItemsFromRestore.push(excludedFolderId);
+
+ // add a test bookmark to it
+ PlacesUtils.bookmarks.insertBookmark(excludedFolderId, this._testURI,
+ PlacesUtils.bookmarks.DEFAULT_INDEX, "test");
+ },
+
+ inbetween: function inbetween() {
+ // add some items that should be removed by the restore
+
+ // add a folder
+ this._litterTitle = "otter";
+ PlacesUtils.bookmarks.createFolder(PlacesUtils.placesRootId,
+ this._litterTitle, 0);
+
+ // add some tags
+ PlacesUtils.tagging.tagURI(this._testURI, ["c", "d"]);
+ },
+
+ validate: function validate() {
+ // validate tags restored
+ var tags = PlacesUtils.tagging.getTagsForURI(this._testURI);
+ // also validates that litter tags are gone
+ do_check_eq(this._tags.toString(), tags.toString());
+
+ var rootNode = PlacesUtils.getFolderContents(PlacesUtils.placesRootId,
+ false, false).root;
+
+ // validate litter is gone
+ do_check_neq(rootNode.getChild(0).title, this._litterTitle);
+
+ // test root count is the same
+ do_check_eq(rootNode.childCount, 7);
+
+ var foundTestFolder = 0;
+ for (var i = 0; i < rootNode.childCount; i++) {
+ var node = rootNode.getChild(i);
+
+ do_print("validating " + node.title);
+ if (node.itemId != PlacesUtils.tagsFolderId) {
+ if (node.title == this._folderTitle) {
+ // check the test folder's properties
+ do_check_eq(node.type, node.RESULT_TYPE_FOLDER);
+ do_check_eq(node.title, this._folderTitle);
+ foundTestFolder++;
+ }
+
+ // test contents
+ node.QueryInterface(Ci.nsINavHistoryContainerResultNode).containerOpen = true;
+ do_check_eq(node.childCount, 1);
+ var child = node.getChild(0);
+ do_check_true(PlacesUtils._uri(child.uri).equals(this._testURI));
+
+ // clean up
+ node.containerOpen = false;
+ }
+ }
+ do_check_eq(foundTestFolder, 1);
+ rootNode.containerOpen = false;
+ }
+});
+
+function run_test() {
+ run_next_test();
+}
+
+add_task(function* () {
+ // make json file
+ let jsonFile = OS.Path.join(OS.Constants.Path.profileDir, "bookmarks.json");
+
+ // populate db
+ tests.forEach(function(aTest) {
+ aTest.populate();
+ // sanity
+ aTest.validate();
+
+ if (aTest.excludedItemsFromRestore)
+ excludedItemsFromRestore = excludedItems.concat(aTest.excludedItemsFromRestore);
+ });
+
+ yield BookmarkJSONUtils.exportToFile(jsonFile);
+
+ tests.forEach(function(aTest) {
+ aTest.inbetween();
+ });
+
+ // restore json file
+ yield BookmarkJSONUtils.importFromFile(jsonFile, true);
+
+ // validate
+ tests.forEach(function(aTest) {
+ aTest.validate();
+ });
+
+ // clean up
+ yield OS.File.remove(jsonFile);
+});
diff --git a/toolkit/components/places/tests/bookmarks/test_424958-json-quoted-folders.js b/toolkit/components/places/tests/bookmarks/test_424958-json-quoted-folders.js
new file mode 100644
index 000000000..7da1146cf
--- /dev/null
+++ b/toolkit/components/places/tests/bookmarks/test_424958-json-quoted-folders.js
@@ -0,0 +1,91 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+var tests = [];
+
+/*
+
+Backup/restore tests example:
+
+var myTest = {
+ populate: function () { ... add bookmarks ... },
+ validate: function () { ... query for your bookmarks ... }
+}
+
+this.push(myTest);
+
+*/
+
+var quotesTest = {
+ _folderTitle: '"quoted folder"',
+ _folderId: null,
+
+ populate: function () {
+ this._folderId =
+ PlacesUtils.bookmarks.createFolder(PlacesUtils.toolbarFolderId,
+ this._folderTitle,
+ PlacesUtils.bookmarks.DEFAULT_INDEX);
+ },
+
+ clean: function () {
+ PlacesUtils.bookmarks.removeItem(this._folderId);
+ },
+
+ validate: function () {
+ var query = PlacesUtils.history.getNewQuery();
+ query.setFolders([PlacesUtils.bookmarks.toolbarFolder], 1);
+ var result = PlacesUtils.history.executeQuery(query, PlacesUtils.history.getNewQueryOptions());
+
+ var toolbar = result.root;
+ toolbar.containerOpen = true;
+
+ // test for our quoted folder
+ do_check_true(toolbar.childCount, 1);
+ var folderNode = toolbar.getChild(0);
+ do_check_eq(folderNode.type, folderNode.RESULT_TYPE_FOLDER);
+ do_check_eq(folderNode.title, this._folderTitle);
+
+ // clean up
+ toolbar.containerOpen = false;
+ }
+}
+tests.push(quotesTest);
+
+function run_test() {
+ run_next_test();
+}
+
+add_task(function* () {
+ // make json file
+ let jsonFile = OS.Path.join(OS.Constants.Path.profileDir, "bookmarks.json");
+
+ // populate db
+ tests.forEach(function(aTest) {
+ aTest.populate();
+ // sanity
+ aTest.validate();
+ });
+
+ // export json to file
+ yield BookmarkJSONUtils.exportToFile(jsonFile);
+
+ // clean
+ tests.forEach(function(aTest) {
+ aTest.clean();
+ });
+
+ // restore json file
+ yield BookmarkJSONUtils.importFromFile(jsonFile, true);
+
+ // validate
+ tests.forEach(function(aTest) {
+ aTest.validate();
+ });
+
+ // clean up
+ yield OS.File.remove(jsonFile);
+
+});
diff --git a/toolkit/components/places/tests/bookmarks/test_448584.js b/toolkit/components/places/tests/bookmarks/test_448584.js
new file mode 100644
index 000000000..6e58bd83a
--- /dev/null
+++ b/toolkit/components/places/tests/bookmarks/test_448584.js
@@ -0,0 +1,113 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+var tests = [];
+
+// Get database connection
+try {
+ var mDBConn = PlacesUtils.history.QueryInterface(Ci.nsPIPlacesDatabase)
+ .DBConnection;
+}
+catch (ex) {
+ do_throw("Could not get database connection\n");
+}
+
+/*
+ This test is:
+ - don't try to add invalid uri nodes to a JSON backup
+*/
+
+var invalidURITest = {
+ _itemTitle: "invalid uri",
+ _itemUrl: "http://test.mozilla.org/",
+ _itemId: null,
+
+ populate: function () {
+ // add a valid bookmark
+ PlacesUtils.bookmarks.insertBookmark(PlacesUtils.toolbarFolderId,
+ PlacesUtils._uri(this._itemUrl),
+ PlacesUtils.bookmarks.DEFAULT_INDEX,
+ this._itemTitle);
+ // this bookmark will go corrupt
+ this._itemId =
+ PlacesUtils.bookmarks.insertBookmark(PlacesUtils.toolbarFolderId,
+ PlacesUtils._uri(this._itemUrl),
+ PlacesUtils.bookmarks.DEFAULT_INDEX,
+ this._itemTitle);
+ },
+
+ clean: function () {
+ PlacesUtils.bookmarks.removeItem(this._itemId);
+ },
+
+ validate: function (aExpectValidItemsCount) {
+ var query = PlacesUtils.history.getNewQuery();
+ query.setFolders([PlacesUtils.bookmarks.toolbarFolder], 1);
+ var options = PlacesUtils.history.getNewQueryOptions();
+ var result = PlacesUtils.history.executeQuery(query, options);
+
+ var toolbar = result.root;
+ toolbar.containerOpen = true;
+
+ // test for our bookmark
+ do_check_eq(toolbar.childCount, aExpectValidItemsCount);
+ for (var i = 0; i < toolbar.childCount; i++) {
+ var folderNode = toolbar.getChild(0);
+ do_check_eq(folderNode.type, folderNode.RESULT_TYPE_URI);
+ do_check_eq(folderNode.title, this._itemTitle);
+ }
+
+ // clean up
+ toolbar.containerOpen = false;
+ }
+}
+tests.push(invalidURITest);
+
+function run_test() {
+ run_next_test();
+}
+
+add_task(function*() {
+ // make json file
+ let jsonFile = OS.Path.join(OS.Constants.Path.profileDir, "bookmarks.json");
+
+ // populate db
+ tests.forEach(function(aTest) {
+ aTest.populate();
+ // sanity
+ aTest.validate(2);
+ // Something in the code went wrong and we finish up losing the place, so
+ // the bookmark uri becomes null.
+ var sql = "UPDATE moz_bookmarks SET fk = 1337 WHERE id = ?1";
+ var stmt = mDBConn.createStatement(sql);
+ stmt.bindByIndex(0, aTest._itemId);
+ try {
+ stmt.execute();
+ } finally {
+ stmt.finalize();
+ }
+ });
+
+ yield BookmarkJSONUtils.exportToFile(jsonFile);
+
+ // clean
+ tests.forEach(function(aTest) {
+ aTest.clean();
+ });
+
+ // restore json file
+ try {
+ yield BookmarkJSONUtils.importFromFile(jsonFile, true);
+ } catch (ex) { do_throw("couldn't import the exported file: " + ex); }
+
+ // validate
+ tests.forEach(function(aTest) {
+ aTest.validate(1);
+ });
+
+ // clean up
+ yield OS.File.remove(jsonFile);
+});
diff --git a/toolkit/components/places/tests/bookmarks/test_458683.js b/toolkit/components/places/tests/bookmarks/test_458683.js
new file mode 100644
index 000000000..c3722aab5
--- /dev/null
+++ b/toolkit/components/places/tests/bookmarks/test_458683.js
@@ -0,0 +1,131 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+var tests = [];
+
+// Get database connection
+try {
+ var mDBConn = PlacesUtils.history.QueryInterface(Ci.nsPIPlacesDatabase)
+ .DBConnection;
+}
+catch (ex) {
+ do_throw("Could not get database connection\n");
+}
+
+/*
+ This test is:
+ - don't block while doing backup and restore if tag containers contain
+ bogus items (separators, folders)
+*/
+
+var invalidTagChildTest = {
+ _itemTitle: "invalid uri",
+ _itemUrl: "http://test.mozilla.org/",
+ _itemId: -1,
+ _tag: "testTag",
+ _tagItemId: -1,
+
+ populate: function () {
+ // add a valid bookmark
+ this._itemId = PlacesUtils.bookmarks
+ .insertBookmark(PlacesUtils.toolbarFolderId,
+ PlacesUtils._uri(this._itemUrl),
+ PlacesUtils.bookmarks.DEFAULT_INDEX,
+ this._itemTitle);
+
+ // create a tag
+ PlacesUtils.tagging.tagURI(PlacesUtils._uri(this._itemUrl), [this._tag]);
+ // get tag folder id
+ var options = PlacesUtils.history.getNewQueryOptions();
+ var query = PlacesUtils.history.getNewQuery();
+ query.setFolders([PlacesUtils.bookmarks.tagsFolder], 1);
+ var result = PlacesUtils.history.executeQuery(query, options);
+ var tagRoot = result.root;
+ tagRoot.containerOpen = true;
+ do_check_eq(tagRoot.childCount, 1);
+ var tagNode = tagRoot.getChild(0)
+ .QueryInterface(Ci.nsINavHistoryContainerResultNode);
+ this._tagItemId = tagNode.itemId;
+ tagRoot.containerOpen = false;
+
+ // add a separator and a folder inside tag folder
+ PlacesUtils.bookmarks.insertSeparator(this._tagItemId,
+ PlacesUtils.bookmarks.DEFAULT_INDEX);
+ PlacesUtils.bookmarks.createFolder(this._tagItemId,
+ "test folder",
+ PlacesUtils.bookmarks.DEFAULT_INDEX);
+
+ // add a separator and a folder inside tag root
+ PlacesUtils.bookmarks.insertSeparator(PlacesUtils.bookmarks.tagsFolder,
+ PlacesUtils.bookmarks.DEFAULT_INDEX);
+ PlacesUtils.bookmarks.createFolder(PlacesUtils.bookmarks.tagsFolder,
+ "test tags root folder",
+ PlacesUtils.bookmarks.DEFAULT_INDEX);
+ },
+
+ clean: function () {
+ PlacesUtils.tagging.untagURI(PlacesUtils._uri(this._itemUrl), [this._tag]);
+ PlacesUtils.bookmarks.removeItem(this._itemId);
+ },
+
+ validate: function () {
+ var query = PlacesUtils.history.getNewQuery();
+ query.setFolders([PlacesUtils.bookmarks.toolbarFolder], 1);
+ var options = PlacesUtils.history.getNewQueryOptions();
+ var result = PlacesUtils.history.executeQuery(query, options);
+
+ var toolbar = result.root;
+ toolbar.containerOpen = true;
+
+ // test for our bookmark
+ do_check_eq(toolbar.childCount, 1);
+ for (var i = 0; i < toolbar.childCount; i++) {
+ var folderNode = toolbar.getChild(0);
+ do_check_eq(folderNode.type, folderNode.RESULT_TYPE_URI);
+ do_check_eq(folderNode.title, this._itemTitle);
+ }
+ toolbar.containerOpen = false;
+
+ // test for our tag
+ var tags = PlacesUtils.tagging.getTagsForURI(PlacesUtils._uri(this._itemUrl));
+ do_check_eq(tags.length, 1);
+ do_check_eq(tags[0], this._tag);
+ }
+}
+tests.push(invalidTagChildTest);
+
+function run_test() {
+ run_next_test()
+}
+
+add_task(function* () {
+ let jsonFile = OS.Path.join(OS.Constants.Path.profileDir, "bookmarks.json");
+
+ // populate db
+ tests.forEach(function(aTest) {
+ aTest.populate();
+ // sanity
+ aTest.validate();
+ });
+
+ yield BookmarkJSONUtils.exportToFile(jsonFile);
+
+ // clean
+ tests.forEach(function(aTest) {
+ aTest.clean();
+ });
+
+ // restore json file
+ yield BookmarkJSONUtils.importFromFile(jsonFile, true);
+
+ // validate
+ tests.forEach(function(aTest) {
+ aTest.validate();
+ });
+
+ // clean up
+ yield OS.File.remove(jsonFile);
+});
diff --git a/toolkit/components/places/tests/bookmarks/test_466303-json-remove-backups.js b/toolkit/components/places/tests/bookmarks/test_466303-json-remove-backups.js
new file mode 100644
index 000000000..3ce0e6ad7
--- /dev/null
+++ b/toolkit/components/places/tests/bookmarks/test_466303-json-remove-backups.js
@@ -0,0 +1,124 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// Since PlacesBackups.getbackupFiles() is a lazy getter, these tests must
+// run in the given order, to avoid making it out-of-sync.
+
+add_task(function* check_max_backups_is_respected() {
+ // Get bookmarkBackups directory
+ let backupFolder = yield PlacesBackups.getBackupFolder();
+
+ // Create 2 json dummy backups in the past.
+ let oldJsonPath = OS.Path.join(backupFolder, "bookmarks-2008-01-01.json");
+ let oldJsonFile = yield OS.File.open(oldJsonPath, { truncate: true });
+ oldJsonFile.close();
+ do_check_true(yield OS.File.exists(oldJsonPath));
+
+ let jsonPath = OS.Path.join(backupFolder, "bookmarks-2008-01-31.json");
+ let jsonFile = yield OS.File.open(jsonPath, { truncate: true });
+ jsonFile.close();
+ do_check_true(yield OS.File.exists(jsonPath));
+
+ // Export bookmarks to JSON.
+ // Allow 2 backups, the older one should be removed.
+ yield PlacesBackups.create(2);
+
+ let count = 0;
+ let lastBackupPath = null;
+ let iterator = new OS.File.DirectoryIterator(backupFolder);
+ try {
+ yield iterator.forEach(aEntry => {
+ count++;
+ if (PlacesBackups.filenamesRegex.test(aEntry.name))
+ lastBackupPath = aEntry.path;
+ });
+ } finally {
+ iterator.close();
+ }
+
+ do_check_eq(count, 2);
+ do_check_neq(lastBackupPath, null);
+ do_check_false(yield OS.File.exists(oldJsonPath));
+ do_check_true(yield OS.File.exists(jsonPath));
+});
+
+add_task(function* check_max_backups_greater_than_backups() {
+ // Get bookmarkBackups directory
+ let backupFolder = yield PlacesBackups.getBackupFolder();
+
+ // Export bookmarks to JSON.
+ // Allow 3 backups, none should be removed.
+ yield PlacesBackups.create(3);
+
+ let count = 0;
+ let lastBackupPath = null;
+ let iterator = new OS.File.DirectoryIterator(backupFolder);
+ try {
+ yield iterator.forEach(aEntry => {
+ count++;
+ if (PlacesBackups.filenamesRegex.test(aEntry.name))
+ lastBackupPath = aEntry.path;
+ });
+ } finally {
+ iterator.close();
+ }
+ do_check_eq(count, 2);
+ do_check_neq(lastBackupPath, null);
+});
+
+add_task(function* check_max_backups_null() {
+ // Get bookmarkBackups directory
+ let backupFolder = yield PlacesBackups.getBackupFolder();
+
+ // Export bookmarks to JSON.
+ // Allow infinite backups, none should be removed, a new one is not created
+ // since one for today already exists.
+ yield PlacesBackups.create(null);
+
+ let count = 0;
+ let lastBackupPath = null;
+ let iterator = new OS.File.DirectoryIterator(backupFolder);
+ try {
+ yield iterator.forEach(aEntry => {
+ count++;
+ if (PlacesBackups.filenamesRegex.test(aEntry.name))
+ lastBackupPath = aEntry.path;
+ });
+ } finally {
+ iterator.close();
+ }
+ do_check_eq(count, 2);
+ do_check_neq(lastBackupPath, null);
+});
+
+add_task(function* check_max_backups_undefined() {
+ // Get bookmarkBackups directory
+ let backupFolder = yield PlacesBackups.getBackupFolder();
+
+ // Export bookmarks to JSON.
+ // Allow infinite backups, none should be removed, a new one is not created
+ // since one for today already exists.
+ yield PlacesBackups.create();
+
+ let count = 0;
+ let lastBackupPath = null;
+ let iterator = new OS.File.DirectoryIterator(backupFolder);
+ try {
+ yield iterator.forEach(aEntry => {
+ count++;
+ if (PlacesBackups.filenamesRegex.test(aEntry.name))
+ lastBackupPath = aEntry.path;
+ });
+ } finally {
+ iterator.close();
+ }
+ do_check_eq(count, 2);
+ do_check_neq(lastBackupPath, null);
+});
+
+function run_test() {
+ run_next_test();
+}
diff --git a/toolkit/components/places/tests/bookmarks/test_477583_json-backup-in-future.js b/toolkit/components/places/tests/bookmarks/test_477583_json-backup-in-future.js
new file mode 100644
index 000000000..116352666
--- /dev/null
+++ b/toolkit/components/places/tests/bookmarks/test_477583_json-backup-in-future.js
@@ -0,0 +1,56 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+function run_test() {
+ do_test_pending();
+
+ Task.spawn(function*() {
+ let backupFolder = yield PlacesBackups.getBackupFolder();
+ let bookmarksBackupDir = new FileUtils.File(backupFolder);
+ // Remove all files from backups folder.
+ let files = bookmarksBackupDir.directoryEntries;
+ while (files.hasMoreElements()) {
+ let entry = files.getNext().QueryInterface(Ci.nsIFile);
+ entry.remove(false);
+ }
+
+ // Create a json dummy backup in the future.
+ let dateObj = new Date();
+ dateObj.setYear(dateObj.getFullYear() + 1);
+ let name = PlacesBackups.getFilenameForDate(dateObj);
+ do_check_eq(name, "bookmarks-" + PlacesBackups.toISODateString(dateObj) + ".json");
+ files = bookmarksBackupDir.directoryEntries;
+ while (files.hasMoreElements()) {
+ let entry = files.getNext().QueryInterface(Ci.nsIFile);
+ if (PlacesBackups.filenamesRegex.test(entry.leafName))
+ entry.remove(false);
+ }
+
+ let futureBackupFile = bookmarksBackupDir.clone();
+ futureBackupFile.append(name);
+ futureBackupFile.create(Ci.nsILocalFile.NORMAL_FILE_TYPE, 0o600);
+ do_check_true(futureBackupFile.exists());
+
+ do_check_eq((yield PlacesBackups.getBackupFiles()).length, 0);
+
+ yield PlacesBackups.create();
+ // Check that a backup for today has been created.
+ do_check_eq((yield PlacesBackups.getBackupFiles()).length, 1);
+ let mostRecentBackupFile = yield PlacesBackups.getMostRecentBackup();
+ do_check_neq(mostRecentBackupFile, null);
+ do_check_true(PlacesBackups.filenamesRegex.test(OS.Path.basename(mostRecentBackupFile)));
+
+ // Check that future backup has been removed.
+ do_check_false(futureBackupFile.exists());
+
+ // Cleanup.
+ mostRecentBackupFile = new FileUtils.File(mostRecentBackupFile);
+ mostRecentBackupFile.remove(false);
+ do_check_false(mostRecentBackupFile.exists());
+
+ do_test_finished()
+ });
+}
diff --git a/toolkit/components/places/tests/bookmarks/test_675416.js b/toolkit/components/places/tests/bookmarks/test_675416.js
new file mode 100644
index 000000000..08b1c3620
--- /dev/null
+++ b/toolkit/components/places/tests/bookmarks/test_675416.js
@@ -0,0 +1,56 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+function run_test() {
+ /**
+ * Requests information to the service, so that bookmark's data is cached.
+ * @param aItemId
+ * Id of the bookmark to be cached.
+ */
+ function forceBookmarkCaching(aItemId) {
+ PlacesUtils.bookmarks.getFolderIdForItem(aItemId);
+ }
+
+ let observer = {
+ onBeginUpdateBatch: () => forceBookmarkCaching(itemId1),
+ onEndUpdateBatch: () => forceBookmarkCaching(itemId1),
+ onItemAdded: forceBookmarkCaching,
+ onItemChanged: forceBookmarkCaching,
+ onItemMoved: forceBookmarkCaching,
+ onItemRemoved: function(id) {
+ try {
+ forceBookmarkCaching(id);
+ do_throw("trying to fetch a removed bookmark should throw");
+ } catch (ex) {}
+ },
+ onItemVisited: forceBookmarkCaching,
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsINavBookmarkObserver])
+ };
+ PlacesUtils.bookmarks.addObserver(observer, false);
+
+ let folderId1 = PlacesUtils.bookmarks
+ .createFolder(PlacesUtils.bookmarksMenuFolderId,
+ "Bookmarks",
+ PlacesUtils.bookmarks.DEFAULT_INDEX);
+ let itemId1 = PlacesUtils.bookmarks
+ .insertBookmark(folderId1,
+ NetUtil.newURI("http:/www.wired.com/wiredscience"),
+ PlacesUtils.bookmarks.DEFAULT_INDEX,
+ "Wired Science");
+
+ PlacesUtils.bookmarks.removeItem(folderId1);
+
+ let folderId2 = PlacesUtils.bookmarks
+ .createFolder(PlacesUtils.bookmarksMenuFolderId,
+ "Science",
+ PlacesUtils.bookmarks.DEFAULT_INDEX);
+ let folderId3 = PlacesUtils.bookmarks
+ .createFolder(folderId2,
+ "Blogs",
+ PlacesUtils.bookmarks.DEFAULT_INDEX);
+ // Check title is correctly reported.
+ do_check_eq(PlacesUtils.bookmarks.getItemTitle(folderId3), "Blogs");
+ do_check_eq(PlacesUtils.bookmarks.getItemTitle(folderId2), "Science");
+
+ PlacesUtils.bookmarks.removeObserver(observer, false);
+}
diff --git a/toolkit/components/places/tests/bookmarks/test_711914.js b/toolkit/components/places/tests/bookmarks/test_711914.js
new file mode 100644
index 000000000..3712c8a77
--- /dev/null
+++ b/toolkit/components/places/tests/bookmarks/test_711914.js
@@ -0,0 +1,56 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+function run_test() {
+ /**
+ * Requests information to the service, so that bookmark's data is cached.
+ * @param aItemId
+ * Id of the bookmark to be cached.
+ */
+ function forceBookmarkCaching(aItemId) {
+ let parent = PlacesUtils.bookmarks.getFolderIdForItem(aItemId);
+ PlacesUtils.bookmarks.getFolderIdForItem(parent);
+ }
+
+ let observer = {
+ onBeginUpdateBatch: () => forceBookmarkCaching(itemId1),
+ onEndUpdateBatch: () => forceBookmarkCaching(itemId1),
+ onItemAdded: forceBookmarkCaching,
+ onItemChanged: forceBookmarkCaching,
+ onItemMoved: forceBookmarkCaching,
+ onItemRemoved: function (id) {
+ try {
+ forceBookmarkCaching(id);
+ do_throw("trying to fetch a removed bookmark should throw");
+ } catch (ex) {}
+ },
+ onItemVisited: forceBookmarkCaching,
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsINavBookmarkObserver])
+ };
+ PlacesUtils.bookmarks.addObserver(observer, false);
+
+ let folder1 = PlacesUtils.bookmarks
+ .createFolder(PlacesUtils.bookmarksMenuFolderId,
+ "Folder1",
+ PlacesUtils.bookmarks.DEFAULT_INDEX);
+ let folder2 = PlacesUtils.bookmarks
+ .createFolder(folder1,
+ "Folder2",
+ PlacesUtils.bookmarks.DEFAULT_INDEX);
+ PlacesUtils.bookmarks.insertBookmark(folder2,
+ NetUtil.newURI("http://mozilla.org/"),
+ PlacesUtils.bookmarks.DEFAULT_INDEX,
+ "Mozilla");
+
+ PlacesUtils.bookmarks.removeFolderChildren(folder1);
+
+ // Check title is correctly reported.
+ do_check_eq(PlacesUtils.bookmarks.getItemTitle(folder1), "Folder1");
+ try {
+ PlacesUtils.bookmarks.getItemTitle(folder2);
+ do_throw("trying to fetch a removed bookmark should throw");
+ } catch (ex) {}
+
+ PlacesUtils.bookmarks.removeObserver(observer, false);
+}
diff --git a/toolkit/components/places/tests/bookmarks/test_818584-discard-duplicate-backups.js b/toolkit/components/places/tests/bookmarks/test_818584-discard-duplicate-backups.js
new file mode 100644
index 000000000..c88323478
--- /dev/null
+++ b/toolkit/components/places/tests/bookmarks/test_818584-discard-duplicate-backups.js
@@ -0,0 +1,59 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+ * Checks that automatically created bookmark backups are discarded if they are
+ * duplicate of an existing ones.
+ */
+function run_test() {
+ run_next_test();
+}
+
+add_task(function*() {
+ // Create a backup for yesterday in the backups folder.
+ let backupFolder = yield PlacesBackups.getBackupFolder();
+ let dateObj = new Date();
+ dateObj.setDate(dateObj.getDate() - 1);
+ let oldBackupName = PlacesBackups.getFilenameForDate(dateObj);
+ let oldBackup = OS.Path.join(backupFolder, oldBackupName);
+ let {count: count, hash: hash} = yield BookmarkJSONUtils.exportToFile(oldBackup);
+ do_check_true(count > 0);
+ do_check_eq(hash.length, 24);
+ oldBackupName = oldBackupName.replace(/\.json/, "_" + count + "_" + hash + ".json");
+ yield OS.File.move(oldBackup, OS.Path.join(backupFolder, oldBackupName));
+
+ // Create a backup.
+ // This should just rename the existing backup, so in the end there should be
+ // only one backup with today's date.
+ yield PlacesBackups.create();
+
+ // Get the hash of the generated backup
+ let backupFiles = yield PlacesBackups.getBackupFiles();
+ do_check_eq(backupFiles.length, 1);
+
+ let matches = OS.Path.basename(backupFiles[0]).match(PlacesBackups.filenamesRegex);
+ do_check_eq(matches[1], PlacesBackups.toISODateString(new Date()));
+ do_check_eq(matches[2], count);
+ do_check_eq(matches[3], hash);
+
+ // Add a bookmark and create another backup.
+ let bookmarkId = PlacesUtils.bookmarks.insertBookmark(PlacesUtils.bookmarks.bookmarksMenuFolder,
+ uri("http://foo.com"),
+ PlacesUtils.bookmarks.DEFAULT_INDEX,
+ "foo");
+ // We must enforce a backup since one for today already exists. The forced
+ // backup will replace the existing one.
+ yield PlacesBackups.create(undefined, true);
+ do_check_eq(backupFiles.length, 1);
+ recentBackup = yield PlacesBackups.getMostRecentBackup();
+ do_check_neq(recentBackup, OS.Path.join(backupFolder, oldBackupName));
+ matches = OS.Path.basename(recentBackup).match(PlacesBackups.filenamesRegex);
+ do_check_eq(matches[1], PlacesBackups.toISODateString(new Date()));
+ do_check_eq(matches[2], count + 1);
+ do_check_neq(matches[3], hash);
+
+ // Clean up
+ PlacesUtils.bookmarks.removeItem(bookmarkId);
+ yield PlacesBackups.create(0);
+});
diff --git a/toolkit/components/places/tests/bookmarks/test_818587_compress-bookmarks-backups.js b/toolkit/components/places/tests/bookmarks/test_818587_compress-bookmarks-backups.js
new file mode 100644
index 000000000..2c84990b3
--- /dev/null
+++ b/toolkit/components/places/tests/bookmarks/test_818587_compress-bookmarks-backups.js
@@ -0,0 +1,57 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+function run_test() {
+ run_next_test();
+}
+
+add_task(function* compress_bookmark_backups_test() {
+ // Check for jsonlz4 extension
+ let todayFilename = PlacesBackups.getFilenameForDate(new Date(2014, 4, 15), true);
+ do_check_eq(todayFilename, "bookmarks-2014-05-15.jsonlz4");
+
+ yield PlacesBackups.create();
+
+ // Check that a backup for today has been created and the regex works fine for lz4.
+ do_check_eq((yield PlacesBackups.getBackupFiles()).length, 1);
+ let mostRecentBackupFile = yield PlacesBackups.getMostRecentBackup();
+ do_check_neq(mostRecentBackupFile, null);
+ do_check_true(PlacesBackups.filenamesRegex.test(OS.Path.basename(mostRecentBackupFile)));
+
+ // The most recent backup file has to be removed since saveBookmarksToJSONFile
+ // will otherwise over-write the current backup, since it will be made on the
+ // same date
+ yield OS.File.remove(mostRecentBackupFile);
+ do_check_false((yield OS.File.exists(mostRecentBackupFile)));
+
+ // Check that, if the user created a custom backup out of the default
+ // backups folder, it gets copied (compressed) into it.
+ let jsonFile = OS.Path.join(OS.Constants.Path.profileDir, "bookmarks.json");
+ yield PlacesBackups.saveBookmarksToJSONFile(jsonFile);
+ do_check_eq((yield PlacesBackups.getBackupFiles()).length, 1);
+
+ // Check if import works from lz4 compressed json
+ let uri = NetUtil.newURI("http://www.mozilla.org/en-US/");
+ let bm = PlacesUtils.bookmarks.insertBookmark(PlacesUtils.unfiledBookmarksFolderId,
+ uri,
+ PlacesUtils.bookmarks.DEFAULT_INDEX,
+ "bookmark");
+
+ // Force create a compressed backup, Remove the bookmark, the restore the backup
+ yield PlacesBackups.create(undefined, true);
+ let recentBackup = yield PlacesBackups.getMostRecentBackup();
+ PlacesUtils.bookmarks.removeItem(bm);
+ yield BookmarkJSONUtils.importFromFile(recentBackup, true);
+ let root = PlacesUtils.getFolderContents(PlacesUtils.unfiledBookmarksFolderId).root;
+ let node = root.getChild(0);
+ do_check_eq(node.uri, uri.spec);
+
+ root.containerOpen = false;
+ PlacesUtils.bookmarks.removeFolderChildren(PlacesUtils.unfiledBookmarksFolderId);
+
+ // Cleanup.
+ yield OS.File.remove(jsonFile);
+});
diff --git a/toolkit/components/places/tests/bookmarks/test_818593-store-backup-metadata.js b/toolkit/components/places/tests/bookmarks/test_818593-store-backup-metadata.js
new file mode 100644
index 000000000..4ea07fb39
--- /dev/null
+++ b/toolkit/components/places/tests/bookmarks/test_818593-store-backup-metadata.js
@@ -0,0 +1,57 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+ * To confirm that metadata i.e. bookmark count is set and retrieved for
+ * automatic backups.
+ */
+function run_test() {
+ run_next_test();
+}
+
+add_task(function* test_saveBookmarksToJSONFile_and_create()
+{
+ // Add a bookmark
+ let uri = NetUtil.newURI("http://getfirefox.com/");
+ let bookmarkId =
+ PlacesUtils.bookmarks.insertBookmark(
+ PlacesUtils.unfiledBookmarksFolderId, uri,
+ PlacesUtils.bookmarks.DEFAULT_INDEX, "Get Firefox!");
+
+ // Test saveBookmarksToJSONFile()
+ let backupFile = FileUtils.getFile("TmpD", ["bookmarks.json"]);
+ backupFile.create(Ci.nsILocalFile.NORMAL_FILE_TYPE, parseInt("0600", 8));
+
+ let nodeCount = yield PlacesBackups.saveBookmarksToJSONFile(backupFile, true);
+ do_check_true(nodeCount > 0);
+ do_check_true(backupFile.exists());
+ do_check_eq(backupFile.leafName, "bookmarks.json");
+
+ // Ensure the backup would be copied to our backups folder when the original
+ // backup is saved somewhere else.
+ let recentBackup = yield PlacesBackups.getMostRecentBackup();
+ let matches = OS.Path.basename(recentBackup).match(PlacesBackups.filenamesRegex);
+ do_check_eq(matches[2], nodeCount);
+ do_check_eq(matches[3].length, 24);
+
+ // Clear all backups in our backups folder.
+ yield PlacesBackups.create(0);
+ do_check_eq((yield PlacesBackups.getBackupFiles()).length, 0);
+
+ // Test create() which saves bookmarks with metadata on the filename.
+ yield PlacesBackups.create();
+ do_check_eq((yield PlacesBackups.getBackupFiles()).length, 1);
+
+ mostRecentBackupFile = yield PlacesBackups.getMostRecentBackup();
+ do_check_neq(mostRecentBackupFile, null);
+ matches = OS.Path.basename(recentBackup).match(PlacesBackups.filenamesRegex);
+ do_check_eq(matches[2], nodeCount);
+ do_check_eq(matches[3].length, 24);
+
+ // Cleanup
+ backupFile.remove(false);
+ yield PlacesBackups.create(0);
+ PlacesUtils.bookmarks.removeItem(bookmarkId);
+});
+
diff --git a/toolkit/components/places/tests/bookmarks/test_992901-backup-unsorted-hierarchy.js b/toolkit/components/places/tests/bookmarks/test_992901-backup-unsorted-hierarchy.js
new file mode 100644
index 000000000..f5e9f8187
--- /dev/null
+++ b/toolkit/components/places/tests/bookmarks/test_992901-backup-unsorted-hierarchy.js
@@ -0,0 +1,48 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+ * Checks that backups properly include all of the bookmarks if the hierarchy
+ * in the database is unordered so that a hierarchy is defined before its
+ * ancestor in the bookmarks table.
+ */
+function run_test() {
+ run_next_test();
+}
+
+add_task(function*() {
+ let bm = PlacesUtils.bookmarks.insertBookmark(PlacesUtils.unfiledBookmarksFolderId,
+ NetUtil.newURI("http://mozilla.org/"),
+ PlacesUtils.bookmarks.DEFAULT_INDEX,
+ "bookmark");
+ let f2 = PlacesUtils.bookmarks.createFolder(PlacesUtils.unfiledBookmarksFolderId, "f2",
+ PlacesUtils.bookmarks.DEFAULT_INDEX);
+ PlacesUtils.bookmarks.moveItem(bm, f2, PlacesUtils.bookmarks.DEFAULT_INDEX);
+ let f1 = PlacesUtils.bookmarks.createFolder(PlacesUtils.unfiledBookmarksFolderId, "f1",
+ PlacesUtils.bookmarks.DEFAULT_INDEX);
+ PlacesUtils.bookmarks.moveItem(f2, f1, PlacesUtils.bookmarks.DEFAULT_INDEX);
+
+ // Create a backup.
+ yield PlacesBackups.create();
+
+ // Remove the bookmarks, then restore the backup.
+ PlacesUtils.bookmarks.removeItem(f1);
+ yield BookmarkJSONUtils.importFromFile((yield PlacesBackups.getMostRecentBackup()), true);
+
+ do_print("Checking first level");
+ let root = PlacesUtils.getFolderContents(PlacesUtils.unfiledBookmarksFolderId).root;
+ let level1 = root.getChild(0);
+ do_check_eq(level1.title, "f1");
+ do_print("Checking second level");
+ PlacesUtils.asContainer(level1).containerOpen = true
+ let level2 = level1.getChild(0);
+ do_check_eq(level2.title, "f2");
+ do_print("Checking bookmark");
+ PlacesUtils.asContainer(level2).containerOpen = true
+ let bookmark = level2.getChild(0);
+ do_check_eq(bookmark.title, "bookmark");
+ level2.containerOpen = false;
+ level1.containerOpen = false;
+ root.containerOpen = false;
+});
diff --git a/toolkit/components/places/tests/bookmarks/test_997030-bookmarks-html-encode.js b/toolkit/components/places/tests/bookmarks/test_997030-bookmarks-html-encode.js
new file mode 100644
index 000000000..b900887b5
--- /dev/null
+++ b/toolkit/components/places/tests/bookmarks/test_997030-bookmarks-html-encode.js
@@ -0,0 +1,37 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+ * Checks that we don't encodeURI twice when creating bookmarks.html.
+ */
+
+function run_test() {
+ run_next_test();
+}
+
+add_task(function* () {
+ let uri = NetUtil.newURI("http://bt.ktxp.com/search.php?keyword=%E5%A6%84%E6%83%B3%E5%AD%A6%E7%94%9F%E4%BC%9A");
+ let bm = PlacesUtils.bookmarks.insertBookmark(PlacesUtils.unfiledBookmarksFolderId,
+ uri,
+ PlacesUtils.bookmarks.DEFAULT_INDEX,
+ "bookmark");
+
+ let file = OS.Path.join(OS.Constants.Path.profileDir, "bookmarks.exported.997030.html");
+ if ((yield OS.File.exists(file))) {
+ yield OS.File.remove(file);
+ }
+ yield BookmarkHTMLUtils.exportToFile(file);
+
+ // Remove the bookmarks, then restore the backup.
+ PlacesUtils.bookmarks.removeItem(bm);
+ yield BookmarkHTMLUtils.importFromFile(file, true);
+
+ do_print("Checking first level");
+ let root = PlacesUtils.getFolderContents(PlacesUtils.unfiledBookmarksFolderId).root;
+ let node = root.getChild(0);
+ do_check_eq(node.uri, uri.spec);
+
+ root.containerOpen = false;
+ PlacesUtils.bookmarks.removeFolderChildren(PlacesUtils.unfiledBookmarksFolderId);
+});
diff --git a/toolkit/components/places/tests/bookmarks/test_async_observers.js b/toolkit/components/places/tests/bookmarks/test_async_observers.js
new file mode 100644
index 000000000..86d48ac24
--- /dev/null
+++ b/toolkit/components/places/tests/bookmarks/test_async_observers.js
@@ -0,0 +1,177 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/* This test checks that bookmarks service is correctly forwarding async
+ * events like visit or favicon additions. */
+
+const NOW = Date.now() * 1000;
+
+var observer = {
+ bookmarks: [],
+ observedBookmarks: 0,
+ observedVisitId: 0,
+ deferred: null,
+
+ /**
+ * Returns a promise that is resolved when the observer determines that the
+ * test can continue. This is required rather than calling run_next_test
+ * directly in the observer because there are cases where we must wait for
+ * other asynchronous events to be completed in addition to this.
+ */
+ setupCompletionPromise: function ()
+ {
+ this.observedBookmarks = 0;
+ this.deferred = Promise.defer();
+ return this.deferred.promise;
+ },
+
+ onBeginUpdateBatch: function () {},
+ onEndUpdateBatch: function () {},
+ onItemAdded: function () {},
+ onItemRemoved: function () {},
+ onItemMoved: function () {},
+ onItemChanged: function(aItemId, aProperty, aIsAnnotation, aNewValue,
+ aLastModified, aItemType)
+ {
+ do_print("Check that we got the correct change information.");
+ do_check_neq(this.bookmarks.indexOf(aItemId), -1);
+ if (aProperty == "favicon") {
+ do_check_false(aIsAnnotation);
+ do_check_eq(aNewValue, SMALLPNG_DATA_URI.spec);
+ do_check_eq(aLastModified, 0);
+ do_check_eq(aItemType, PlacesUtils.bookmarks.TYPE_BOOKMARK);
+ }
+ else if (aProperty == "cleartime") {
+ do_check_false(aIsAnnotation);
+ do_check_eq(aNewValue, "");
+ do_check_eq(aLastModified, 0);
+ do_check_eq(aItemType, PlacesUtils.bookmarks.TYPE_BOOKMARK);
+ }
+ else {
+ do_throw("Unexpected property change " + aProperty);
+ }
+
+ if (++this.observedBookmarks == this.bookmarks.length) {
+ this.deferred.resolve();
+ }
+ },
+ onItemVisited: function(aItemId, aVisitId, aTime)
+ {
+ do_print("Check that we got the correct visit information.");
+ do_check_neq(this.bookmarks.indexOf(aItemId), -1);
+ this.observedVisitId = aVisitId;
+ do_check_eq(aTime, NOW);
+ if (++this.observedBookmarks == this.bookmarks.length) {
+ this.deferred.resolve();
+ }
+ },
+
+ QueryInterface: XPCOMUtils.generateQI([
+ Ci.nsINavBookmarkObserver,
+ ])
+};
+PlacesUtils.bookmarks.addObserver(observer, false);
+
+add_task(function* test_add_visit()
+{
+ let observerPromise = observer.setupCompletionPromise();
+
+ // Add a visit to the bookmark and wait for the observer.
+ let visitId;
+ let deferUpdatePlaces = Promise.defer();
+ PlacesUtils.asyncHistory.updatePlaces({
+ uri: NetUtil.newURI("http://book.ma.rk/"),
+ visits: [{ transitionType: TRANSITION_TYPED, visitDate: NOW }]
+ }, {
+ handleError: function TAV_handleError() {
+ deferUpdatePlaces.reject(new Error("Unexpected error in adding visit."));
+ },
+ handleResult: function (aPlaceInfo) {
+ visitId = aPlaceInfo.visits[0].visitId;
+ },
+ handleCompletion: function TAV_handleCompletion() {
+ deferUpdatePlaces.resolve();
+ }
+ });
+
+ // Wait for both the observer and the asynchronous update, in any order.
+ yield deferUpdatePlaces.promise;
+ yield observerPromise;
+
+ // Check that both asynchronous results are consistent.
+ do_check_eq(observer.observedVisitId, visitId);
+});
+
+add_task(function* test_add_icon()
+{
+ let observerPromise = observer.setupCompletionPromise();
+ PlacesUtils.favicons.setAndFetchFaviconForPage(NetUtil.newURI("http://book.ma.rk/"),
+ SMALLPNG_DATA_URI, true,
+ PlacesUtils.favicons.FAVICON_LOAD_NON_PRIVATE,
+ null,
+ Services.scriptSecurityManager.getSystemPrincipal());
+ yield observerPromise;
+});
+
+add_task(function* test_remove_page()
+{
+ let observerPromise = observer.setupCompletionPromise();
+ PlacesUtils.history.removePage(NetUtil.newURI("http://book.ma.rk/"));
+ yield observerPromise;
+});
+
+add_task(function cleanup()
+{
+ PlacesUtils.bookmarks.removeObserver(observer, false);
+});
+
+add_task(function* shutdown()
+{
+ // Check that async observers don't try to create async statements after
+ // shutdown. That would cause assertions, since the async thread is gone
+ // already. Note that in such a case the notifications are not fired, so we
+ // cannot test for them.
+ // Put an history notification that triggers AsyncGetBookmarksForURI between
+ // asyncClose() and the actual connection closing. Enqueuing a main-thread
+ // event just after places-will-close-connection should ensure it runs before
+ // places-connection-closed.
+ // Notice this code is not using helpers cause it depends on a very specific
+ // order, a change in the helpers code could make this test useless.
+ let deferred = Promise.defer();
+
+ Services.obs.addObserver(function onNotification() {
+ Services.obs.removeObserver(onNotification, "places-will-close-connection");
+ do_check_true(true, "Observed fake places shutdown");
+
+ Services.tm.mainThread.dispatch(() => {
+ // WARNING: this is very bad, never use out of testing code.
+ PlacesUtils.bookmarks.QueryInterface(Ci.nsINavHistoryObserver)
+ .onPageChanged(NetUtil.newURI("http://book.ma.rk/"),
+ Ci.nsINavHistoryObserver.ATTRIBUTE_FAVICON,
+ "test", "test");
+ deferred.resolve(promiseTopicObserved("places-connection-closed"));
+ }, Ci.nsIThread.DISPATCH_NORMAL);
+ }, "places-will-close-connection", false);
+ shutdownPlaces();
+
+ yield deferred.promise;
+});
+
+function run_test()
+{
+ // Add multiple bookmarks to the same uri.
+ observer.bookmarks.push(
+ PlacesUtils.bookmarks.insertBookmark(PlacesUtils.unfiledBookmarksFolderId,
+ NetUtil.newURI("http://book.ma.rk/"),
+ PlacesUtils.bookmarks.DEFAULT_INDEX,
+ "Bookmark")
+ );
+ observer.bookmarks.push(
+ PlacesUtils.bookmarks.insertBookmark(PlacesUtils.toolbarFolderId,
+ NetUtil.newURI("http://book.ma.rk/"),
+ PlacesUtils.bookmarks.DEFAULT_INDEX,
+ "Bookmark")
+ );
+
+ run_next_test();
+}
diff --git a/toolkit/components/places/tests/bookmarks/test_bmindex.js b/toolkit/components/places/tests/bookmarks/test_bmindex.js
new file mode 100644
index 000000000..c764e4310
--- /dev/null
+++ b/toolkit/components/places/tests/bookmarks/test_bmindex.js
@@ -0,0 +1,124 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*-
+ * vim: sw=2 ts=2 et lcs=trail\:.,tab\:>~ :
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+const NUM_BOOKMARKS = 20;
+const NUM_SEPARATORS = 5;
+const NUM_FOLDERS = 10;
+const NUM_ITEMS = NUM_BOOKMARKS + NUM_SEPARATORS + NUM_FOLDERS;
+const MIN_RAND = -5;
+const MAX_RAND = 40;
+
+var bs = Cc["@mozilla.org/browser/nav-bookmarks-service;1"].
+ getService(Ci.nsINavBookmarksService);
+
+function check_contiguous_indexes(aBookmarks) {
+ var indexes = [];
+ aBookmarks.forEach(function(aBookmarkId) {
+ let bmIndex = bs.getItemIndex(aBookmarkId);
+ dump("Index: " + bmIndex + "\n");
+ dump("Checking duplicates\n");
+ do_check_eq(indexes.indexOf(bmIndex), -1);
+ dump("Checking out of range, found " + aBookmarks.length + " items\n");
+ do_check_true(bmIndex >= 0 && bmIndex < aBookmarks.length);
+ indexes.push(bmIndex);
+ });
+ dump("Checking all valid indexes have been used\n");
+ do_check_eq(indexes.length, aBookmarks.length);
+}
+
+// main
+function run_test() {
+ var bookmarks = [];
+ // Insert bookmarks with random indexes.
+ for (let i = 0; bookmarks.length < NUM_BOOKMARKS; i++) {
+ let randIndex = Math.round(MIN_RAND + (Math.random() * (MAX_RAND - MIN_RAND)));
+ try {
+ let id = bs.insertBookmark(bs.unfiledBookmarksFolder,
+ uri("http://" + i + ".mozilla.org/"),
+ randIndex, "Test bookmark " + i);
+ if (randIndex < -1)
+ do_throw("Creating a bookmark at an invalid index should throw");
+ bookmarks.push(id);
+ }
+ catch (ex) {
+ if (randIndex >= -1)
+ do_throw("Creating a bookmark at a valid index should not throw");
+ }
+ }
+ check_contiguous_indexes(bookmarks);
+
+ // Insert separators with random indexes.
+ for (let i = 0; bookmarks.length < NUM_BOOKMARKS + NUM_SEPARATORS; i++) {
+ let randIndex = Math.round(MIN_RAND + (Math.random() * (MAX_RAND - MIN_RAND)));
+ try {
+ let id = bs.insertSeparator(bs.unfiledBookmarksFolder, randIndex);
+ if (randIndex < -1)
+ do_throw("Creating a separator at an invalid index should throw");
+ bookmarks.push(id);
+ }
+ catch (ex) {
+ if (randIndex >= -1)
+ do_throw("Creating a separator at a valid index should not throw");
+ }
+ }
+ check_contiguous_indexes(bookmarks);
+
+ // Insert folders with random indexes.
+ for (let i = 0; bookmarks.length < NUM_ITEMS; i++) {
+ let randIndex = Math.round(MIN_RAND + (Math.random() * (MAX_RAND - MIN_RAND)));
+ try {
+ let id = bs.createFolder(bs.unfiledBookmarksFolder,
+ "Test folder " + i, randIndex);
+ if (randIndex < -1)
+ do_throw("Creating a folder at an invalid index should throw");
+ bookmarks.push(id);
+ }
+ catch (ex) {
+ if (randIndex >= -1)
+ do_throw("Creating a folder at a valid index should not throw");
+ }
+ }
+ check_contiguous_indexes(bookmarks);
+
+ // Execute some random bookmark delete.
+ for (let i = 0; i < Math.ceil(NUM_ITEMS / 4); i++) {
+ let id = bookmarks.splice(Math.floor(Math.random() * bookmarks.length), 1);
+ dump("Removing item with id " + id + "\n");
+ bs.removeItem(id);
+ }
+ check_contiguous_indexes(bookmarks);
+
+ // Execute some random bookmark move. This will also try to move it to
+ // invalid index values.
+ for (let i = 0; i < Math.ceil(NUM_ITEMS / 4); i++) {
+ let randIndex = Math.floor(Math.random() * bookmarks.length);
+ let id = bookmarks[randIndex];
+ let newIndex = Math.round(MIN_RAND + (Math.random() * (MAX_RAND - MIN_RAND)));
+ dump("Moving item with id " + id + " to index " + newIndex + "\n");
+ try {
+ bs.moveItem(id, bs.unfiledBookmarksFolder, newIndex);
+ if (newIndex < -1)
+ do_throw("Moving an item to a negative index should throw\n");
+ }
+ catch (ex) {
+ if (newIndex >= -1)
+ do_throw("Moving an item to a valid index should not throw\n");
+ }
+
+ }
+ check_contiguous_indexes(bookmarks);
+
+ // Ensure setItemIndex throws if we pass it a negative index.
+ try {
+ bs.setItemIndex(bookmarks[0], -1);
+ do_throw("setItemIndex should throw for a negative index");
+ } catch (ex) {}
+ // Ensure setItemIndex throws if we pass it a bad itemId.
+ try {
+ bs.setItemIndex(0, 5);
+ do_throw("setItemIndex should throw for a bad itemId");
+ } catch (ex) {}
+}
diff --git a/toolkit/components/places/tests/bookmarks/test_bookmarks.js b/toolkit/components/places/tests/bookmarks/test_bookmarks.js
new file mode 100644
index 000000000..b67482223
--- /dev/null
+++ b/toolkit/components/places/tests/bookmarks/test_bookmarks.js
@@ -0,0 +1,718 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+var bs = PlacesUtils.bookmarks;
+var hs = PlacesUtils.history;
+var anno = PlacesUtils.annotations;
+
+
+var bookmarksObserver = {
+ onBeginUpdateBatch: function() {
+ this._beginUpdateBatch = true;
+ },
+ onEndUpdateBatch: function() {
+ this._endUpdateBatch = true;
+ },
+ onItemAdded: function(id, folder, index, itemType, uri, title, dateAdded,
+ guid) {
+ this._itemAddedId = id;
+ this._itemAddedParent = folder;
+ this._itemAddedIndex = index;
+ this._itemAddedURI = uri;
+ this._itemAddedTitle = title;
+
+ // Ensure that we've created a guid for this item.
+ let stmt = DBConn().createStatement(
+ `SELECT guid
+ FROM moz_bookmarks
+ WHERE id = :item_id`
+ );
+ stmt.params.item_id = id;
+ do_check_true(stmt.executeStep());
+ do_check_false(stmt.getIsNull(0));
+ do_check_valid_places_guid(stmt.row.guid);
+ do_check_eq(stmt.row.guid, guid);
+ stmt.finalize();
+ },
+ onItemRemoved: function(id, folder, index, itemType) {
+ this._itemRemovedId = id;
+ this._itemRemovedFolder = folder;
+ this._itemRemovedIndex = index;
+ },
+ onItemChanged: function(id, property, isAnnotationProperty, value,
+ lastModified, itemType, parentId, guid, parentGuid,
+ oldValue) {
+ this._itemChangedId = id;
+ this._itemChangedProperty = property;
+ this._itemChanged_isAnnotationProperty = isAnnotationProperty;
+ this._itemChangedValue = value;
+ this._itemChangedOldValue = oldValue;
+ },
+ onItemVisited: function(id, visitID, time) {
+ this._itemVisitedId = id;
+ this._itemVisitedVistId = visitID;
+ this._itemVisitedTime = time;
+ },
+ onItemMoved: function(id, oldParent, oldIndex, newParent, newIndex,
+ itemType) {
+ this._itemMovedId = id
+ this._itemMovedOldParent = oldParent;
+ this._itemMovedOldIndex = oldIndex;
+ this._itemMovedNewParent = newParent;
+ this._itemMovedNewIndex = newIndex;
+ },
+ QueryInterface: XPCOMUtils.generateQI([
+ Ci.nsINavBookmarkObserver,
+ ])
+};
+
+
+// Get bookmarks menu folder id.
+var root = bs.bookmarksMenuFolder;
+// Index at which items should begin.
+var bmStartIndex = 0;
+
+
+function run_test() {
+ run_next_test();
+}
+
+add_task(function* test_bookmarks() {
+ bs.addObserver(bookmarksObserver, false);
+
+ // test special folders
+ do_check_true(bs.placesRoot > 0);
+ do_check_true(bs.bookmarksMenuFolder > 0);
+ do_check_true(bs.tagsFolder > 0);
+ do_check_true(bs.toolbarFolder > 0);
+ do_check_true(bs.unfiledBookmarksFolder > 0);
+
+ // test getFolderIdForItem() with bogus item id will throw
+ try {
+ bs.getFolderIdForItem(0);
+ do_throw("getFolderIdForItem accepted bad input");
+ } catch (ex) {}
+
+ // test getFolderIdForItem() with bogus item id will throw
+ try {
+ bs.getFolderIdForItem(-1);
+ do_throw("getFolderIdForItem accepted bad input");
+ } catch (ex) {}
+
+ // test root parentage
+ do_check_eq(bs.getFolderIdForItem(bs.bookmarksMenuFolder), bs.placesRoot);
+ do_check_eq(bs.getFolderIdForItem(bs.tagsFolder), bs.placesRoot);
+ do_check_eq(bs.getFolderIdForItem(bs.toolbarFolder), bs.placesRoot);
+ do_check_eq(bs.getFolderIdForItem(bs.unfiledBookmarksFolder), bs.placesRoot);
+
+ // create a folder to hold all the tests
+ // this makes the tests more tolerant of changes to default_places.html
+ let testRoot = bs.createFolder(root, "places bookmarks xpcshell tests",
+ bs.DEFAULT_INDEX);
+ do_check_eq(bookmarksObserver._itemAddedId, testRoot);
+ do_check_eq(bookmarksObserver._itemAddedParent, root);
+ do_check_eq(bookmarksObserver._itemAddedIndex, bmStartIndex);
+ do_check_eq(bookmarksObserver._itemAddedURI, null);
+ let testStartIndex = 0;
+
+ // test getItemIndex for folders
+ do_check_eq(bs.getItemIndex(testRoot), bmStartIndex);
+
+ // test getItemType for folders
+ do_check_eq(bs.getItemType(testRoot), bs.TYPE_FOLDER);
+
+ // insert a bookmark.
+ // the time before we insert, in microseconds
+ let beforeInsert = Date.now() * 1000;
+ do_check_true(beforeInsert > 0);
+
+ let newId = bs.insertBookmark(testRoot, uri("http://google.com/"),
+ bs.DEFAULT_INDEX, "");
+ do_check_eq(bookmarksObserver._itemAddedId, newId);
+ do_check_eq(bookmarksObserver._itemAddedParent, testRoot);
+ do_check_eq(bookmarksObserver._itemAddedIndex, testStartIndex);
+ do_check_true(bookmarksObserver._itemAddedURI.equals(uri("http://google.com/")));
+ do_check_eq(bs.getBookmarkURI(newId).spec, "http://google.com/");
+
+ let dateAdded = bs.getItemDateAdded(newId);
+ // dateAdded can equal beforeInsert
+ do_check_true(is_time_ordered(beforeInsert, dateAdded));
+
+ // after just inserting, modified should not be set
+ let lastModified = bs.getItemLastModified(newId);
+ do_check_eq(lastModified, dateAdded);
+
+ // The time before we set the title, in microseconds.
+ let beforeSetTitle = Date.now() * 1000;
+ do_check_true(beforeSetTitle >= beforeInsert);
+
+ // Workaround possible VM timers issues moving lastModified and dateAdded
+ // to the past.
+ lastModified -= 1000;
+ bs.setItemLastModified(newId, lastModified);
+ dateAdded -= 1000;
+ bs.setItemDateAdded(newId, dateAdded);
+
+ // set bookmark title
+ bs.setItemTitle(newId, "Google");
+ do_check_eq(bookmarksObserver._itemChangedId, newId);
+ do_check_eq(bookmarksObserver._itemChangedProperty, "title");
+ do_check_eq(bookmarksObserver._itemChangedValue, "Google");
+
+ // check that dateAdded hasn't changed
+ let dateAdded2 = bs.getItemDateAdded(newId);
+ do_check_eq(dateAdded2, dateAdded);
+
+ // check lastModified after we set the title
+ let lastModified2 = bs.getItemLastModified(newId);
+ do_print("test setItemTitle");
+ do_print("dateAdded = " + dateAdded);
+ do_print("beforeSetTitle = " + beforeSetTitle);
+ do_print("lastModified = " + lastModified);
+ do_print("lastModified2 = " + lastModified2);
+ do_check_true(is_time_ordered(lastModified, lastModified2));
+ do_check_true(is_time_ordered(dateAdded, lastModified2));
+
+ // get item title
+ let title = bs.getItemTitle(newId);
+ do_check_eq(title, "Google");
+
+ // test getItemType for bookmarks
+ do_check_eq(bs.getItemType(newId), bs.TYPE_BOOKMARK);
+
+ // get item title bad input
+ try {
+ bs.getItemTitle(-3);
+ do_throw("getItemTitle accepted bad input");
+ } catch (ex) {}
+
+ // get the folder that the bookmark is in
+ let folderId = bs.getFolderIdForItem(newId);
+ do_check_eq(folderId, testRoot);
+
+ // test getItemIndex for bookmarks
+ do_check_eq(bs.getItemIndex(newId), testStartIndex);
+
+ // create a folder at a specific index
+ let workFolder = bs.createFolder(testRoot, "Work", 0);
+ do_check_eq(bookmarksObserver._itemAddedId, workFolder);
+ do_check_eq(bookmarksObserver._itemAddedParent, testRoot);
+ do_check_eq(bookmarksObserver._itemAddedIndex, 0);
+ do_check_eq(bookmarksObserver._itemAddedURI, null);
+
+ do_check_eq(bs.getItemTitle(workFolder), "Work");
+ bs.setItemTitle(workFolder, "Work #");
+ do_check_eq(bs.getItemTitle(workFolder), "Work #");
+
+ // add item into subfolder, specifying index
+ let newId2 = bs.insertBookmark(workFolder,
+ uri("http://developer.mozilla.org/"),
+ 0, "");
+ do_check_eq(bookmarksObserver._itemAddedId, newId2);
+ do_check_eq(bookmarksObserver._itemAddedParent, workFolder);
+ do_check_eq(bookmarksObserver._itemAddedIndex, 0);
+
+ // change item
+ bs.setItemTitle(newId2, "DevMo");
+ do_check_eq(bookmarksObserver._itemChangedProperty, "title");
+
+ // insert item into subfolder
+ let newId3 = bs.insertBookmark(workFolder,
+ uri("http://msdn.microsoft.com/"),
+ bs.DEFAULT_INDEX, "");
+ do_check_eq(bookmarksObserver._itemAddedId, newId3);
+ do_check_eq(bookmarksObserver._itemAddedParent, workFolder);
+ do_check_eq(bookmarksObserver._itemAddedIndex, 1);
+
+ // change item
+ bs.setItemTitle(newId3, "MSDN");
+ do_check_eq(bookmarksObserver._itemChangedProperty, "title");
+
+ // remove item
+ bs.removeItem(newId2);
+ do_check_eq(bookmarksObserver._itemRemovedId, newId2);
+ do_check_eq(bookmarksObserver._itemRemovedFolder, workFolder);
+ do_check_eq(bookmarksObserver._itemRemovedIndex, 0);
+
+ // insert item into subfolder
+ let newId4 = bs.insertBookmark(workFolder,
+ uri("http://developer.mozilla.org/"),
+ bs.DEFAULT_INDEX, "");
+ do_check_eq(bookmarksObserver._itemAddedId, newId4);
+ do_check_eq(bookmarksObserver._itemAddedParent, workFolder);
+ do_check_eq(bookmarksObserver._itemAddedIndex, 1);
+
+ // create folder
+ let homeFolder = bs.createFolder(testRoot, "Home", bs.DEFAULT_INDEX);
+ do_check_eq(bookmarksObserver._itemAddedId, homeFolder);
+ do_check_eq(bookmarksObserver._itemAddedParent, testRoot);
+ do_check_eq(bookmarksObserver._itemAddedIndex, 2);
+
+ // insert item
+ let newId5 = bs.insertBookmark(homeFolder, uri("http://espn.com/"),
+ bs.DEFAULT_INDEX, "");
+ do_check_eq(bookmarksObserver._itemAddedId, newId5);
+ do_check_eq(bookmarksObserver._itemAddedParent, homeFolder);
+ do_check_eq(bookmarksObserver._itemAddedIndex, 0);
+
+ // change item
+ bs.setItemTitle(newId5, "ESPN");
+ do_check_eq(bookmarksObserver._itemChangedId, newId5);
+ do_check_eq(bookmarksObserver._itemChangedProperty, "title");
+
+ // insert query item
+ let uri6 = uri("place:domain=google.com&type="+
+ Ci.nsINavHistoryQueryOptions.RESULTS_AS_SITE_QUERY);
+ let newId6 = bs.insertBookmark(testRoot, uri6, bs.DEFAULT_INDEX, "");
+ do_check_eq(bookmarksObserver._itemAddedParent, testRoot);
+ do_check_eq(bookmarksObserver._itemAddedIndex, 3);
+
+ // change item
+ bs.setItemTitle(newId6, "Google Sites");
+ do_check_eq(bookmarksObserver._itemChangedProperty, "title");
+
+ // test getIdForItemAt
+ do_check_eq(bs.getIdForItemAt(testRoot, 0), workFolder);
+ // wrong parent, should return -1
+ do_check_eq(bs.getIdForItemAt(1337, 0), -1);
+ // wrong index, should return -1
+ do_check_eq(bs.getIdForItemAt(testRoot, 1337), -1);
+ // wrong parent and index, should return -1
+ do_check_eq(bs.getIdForItemAt(1337, 1337), -1);
+
+ // move folder, appending, to different folder
+ let oldParentCC = getChildCount(testRoot);
+ bs.moveItem(workFolder, homeFolder, bs.DEFAULT_INDEX);
+ do_check_eq(bookmarksObserver._itemMovedId, workFolder);
+ do_check_eq(bookmarksObserver._itemMovedOldParent, testRoot);
+ do_check_eq(bookmarksObserver._itemMovedOldIndex, 0);
+ do_check_eq(bookmarksObserver._itemMovedNewParent, homeFolder);
+ do_check_eq(bookmarksObserver._itemMovedNewIndex, 1);
+
+ // test that the new index is properly stored
+ do_check_eq(bs.getItemIndex(workFolder), 1);
+ do_check_eq(bs.getFolderIdForItem(workFolder), homeFolder);
+
+ // try to get index of the item from within the old parent folder
+ // check that it has been really removed from there
+ do_check_neq(bs.getIdForItemAt(testRoot, 0), workFolder);
+ // check the last item from within the old parent folder
+ do_check_neq(bs.getIdForItemAt(testRoot, -1), workFolder);
+ // check the index of the item within the new parent folder
+ do_check_eq(bs.getIdForItemAt(homeFolder, 1), workFolder);
+ // try to get index of the last item within the new parent folder
+ do_check_eq(bs.getIdForItemAt(homeFolder, -1), workFolder);
+ // XXX expose FolderCount, and check that the old parent has one less child?
+ do_check_eq(getChildCount(testRoot), oldParentCC-1);
+
+ // move item, appending, to different folder
+ bs.moveItem(newId5, testRoot, bs.DEFAULT_INDEX);
+ do_check_eq(bookmarksObserver._itemMovedId, newId5);
+ do_check_eq(bookmarksObserver._itemMovedOldParent, homeFolder);
+ do_check_eq(bookmarksObserver._itemMovedOldIndex, 0);
+ do_check_eq(bookmarksObserver._itemMovedNewParent, testRoot);
+ do_check_eq(bookmarksObserver._itemMovedNewIndex, 3);
+
+ // test get folder's index
+ let tmpFolder = bs.createFolder(testRoot, "tmp", 2);
+ do_check_eq(bs.getItemIndex(tmpFolder), 2);
+
+ // test setKeywordForBookmark
+ let kwTestItemId = bs.insertBookmark(testRoot, uri("http://keywordtest.com"),
+ bs.DEFAULT_INDEX, "");
+ bs.setKeywordForBookmark(kwTestItemId, "bar");
+
+ // test getKeywordForBookmark
+ let k = bs.getKeywordForBookmark(kwTestItemId);
+ do_check_eq("bar", k);
+
+ // test getURIForKeyword
+ let u = bs.getURIForKeyword("bar");
+ do_check_eq("http://keywordtest.com/", u.spec);
+
+ // test removeFolderChildren
+ // 1) add/remove each child type (bookmark, separator, folder)
+ tmpFolder = bs.createFolder(testRoot, "removeFolderChildren",
+ bs.DEFAULT_INDEX);
+ bs.insertBookmark(tmpFolder, uri("http://foo9.com/"), bs.DEFAULT_INDEX, "");
+ bs.createFolder(tmpFolder, "subfolder", bs.DEFAULT_INDEX);
+ bs.insertSeparator(tmpFolder, bs.DEFAULT_INDEX);
+ // 2) confirm that folder has 3 children
+ let options = hs.getNewQueryOptions();
+ let query = hs.getNewQuery();
+ query.setFolders([tmpFolder], 1);
+ try {
+ let result = hs.executeQuery(query, options);
+ let rootNode = result.root;
+ rootNode.containerOpen = true;
+ do_check_eq(rootNode.childCount, 3);
+ rootNode.containerOpen = false;
+ } catch (ex) {
+ do_throw("test removeFolderChildren() - querying for children failed: " + ex);
+ }
+ // 3) remove all children
+ bs.removeFolderChildren(tmpFolder);
+ // 4) confirm that folder has 0 children
+ try {
+ result = hs.executeQuery(query, options);
+ let rootNode = result.root;
+ rootNode.containerOpen = true;
+ do_check_eq(rootNode.childCount, 0);
+ rootNode.containerOpen = false;
+ } catch (ex) {
+ do_throw("removeFolderChildren(): " + ex);
+ }
+
+ // XXX - test folderReadOnly
+
+ // test bookmark id in query output
+ try {
+ options = hs.getNewQueryOptions();
+ query = hs.getNewQuery();
+ query.setFolders([testRoot], 1);
+ let result = hs.executeQuery(query, options);
+ let rootNode = result.root;
+ rootNode.containerOpen = true;
+ let cc = rootNode.childCount;
+ do_print("bookmark itemId test: CC = " + cc);
+ do_check_true(cc > 0);
+ for (let i=0; i < cc; ++i) {
+ let node = rootNode.getChild(i);
+ if (node.type == node.RESULT_TYPE_FOLDER ||
+ node.type == node.RESULT_TYPE_URI ||
+ node.type == node.RESULT_TYPE_SEPARATOR ||
+ node.type == node.RESULT_TYPE_QUERY) {
+ do_check_true(node.itemId > 0);
+ }
+ else {
+ do_check_eq(node.itemId, -1);
+ }
+ }
+ rootNode.containerOpen = false;
+ }
+ catch (ex) {
+ do_throw("bookmarks query: " + ex);
+ }
+
+ // test that multiple bookmarks with same URI show up right in bookmark
+ // folder queries, todo: also to do for complex folder queries
+ try {
+ // test uri
+ let mURI = uri("http://multiple.uris.in.query");
+
+ let testFolder = bs.createFolder(testRoot, "test Folder", bs.DEFAULT_INDEX);
+ // add 2 bookmarks
+ bs.insertBookmark(testFolder, mURI, bs.DEFAULT_INDEX, "title 1");
+ bs.insertBookmark(testFolder, mURI, bs.DEFAULT_INDEX, "title 2");
+
+ // query
+ options = hs.getNewQueryOptions();
+ query = hs.getNewQuery();
+ query.setFolders([testFolder], 1);
+ let result = hs.executeQuery(query, options);
+ let rootNode = result.root;
+ rootNode.containerOpen = true;
+ let cc = rootNode.childCount;
+ do_check_eq(cc, 2);
+ do_check_eq(rootNode.getChild(0).title, "title 1");
+ do_check_eq(rootNode.getChild(1).title, "title 2");
+ rootNode.containerOpen = false;
+ }
+ catch (ex) {
+ do_throw("bookmarks query: " + ex);
+ }
+
+ // test change bookmark uri
+ let newId10 = bs.insertBookmark(testRoot, uri("http://foo10.com/"),
+ bs.DEFAULT_INDEX, "");
+ dateAdded = bs.getItemDateAdded(newId10);
+ // after just inserting, modified should not be set
+ lastModified = bs.getItemLastModified(newId10);
+ do_check_eq(lastModified, dateAdded);
+
+ // Workaround possible VM timers issues moving lastModified and dateAdded
+ // to the past.
+ lastModified -= 1000;
+ bs.setItemLastModified(newId10, lastModified);
+ dateAdded -= 1000;
+ bs.setItemDateAdded(newId10, dateAdded);
+
+ bs.changeBookmarkURI(newId10, uri("http://foo11.com/"));
+
+ // check that lastModified is set after we change the bookmark uri
+ lastModified2 = bs.getItemLastModified(newId10);
+ do_print("test changeBookmarkURI");
+ do_print("dateAdded = " + dateAdded);
+ do_print("lastModified = " + lastModified);
+ do_print("lastModified2 = " + lastModified2);
+ do_check_true(is_time_ordered(lastModified, lastModified2));
+ do_check_true(is_time_ordered(dateAdded, lastModified2));
+
+ do_check_eq(bookmarksObserver._itemChangedId, newId10);
+ do_check_eq(bookmarksObserver._itemChangedProperty, "uri");
+ do_check_eq(bookmarksObserver._itemChangedValue, "http://foo11.com/");
+ do_check_eq(bookmarksObserver._itemChangedOldValue, "http://foo10.com/");
+
+ // test getBookmarkURI
+ let newId11 = bs.insertBookmark(testRoot, uri("http://foo11.com/"),
+ bs.DEFAULT_INDEX, "");
+ let bmURI = bs.getBookmarkURI(newId11);
+ do_check_eq("http://foo11.com/", bmURI.spec);
+
+ // test getBookmarkURI with non-bookmark items
+ try {
+ bs.getBookmarkURI(testRoot);
+ do_throw("getBookmarkURI() should throw for non-bookmark items!");
+ } catch (ex) {}
+
+ // test getItemIndex
+ let newId12 = bs.insertBookmark(testRoot, uri("http://foo11.com/"), 1, "");
+ let bmIndex = bs.getItemIndex(newId12);
+ do_check_eq(1, bmIndex);
+
+ // insert a bookmark with title ZZZXXXYYY and then search for it.
+ // this test confirms that we can find bookmarks that we haven't visited
+ // (which are "hidden") and that we can find by title.
+ // see bug #369887 for more details
+ let newId13 = bs.insertBookmark(testRoot, uri("http://foobarcheese.com/"),
+ bs.DEFAULT_INDEX, "");
+ do_check_eq(bookmarksObserver._itemAddedId, newId13);
+ do_check_eq(bookmarksObserver._itemAddedParent, testRoot);
+ do_check_eq(bookmarksObserver._itemAddedIndex, 11);
+
+ // set bookmark title
+ bs.setItemTitle(newId13, "ZZZXXXYYY");
+ do_check_eq(bookmarksObserver._itemChangedId, newId13);
+ do_check_eq(bookmarksObserver._itemChangedProperty, "title");
+ do_check_eq(bookmarksObserver._itemChangedValue, "ZZZXXXYYY");
+
+ // check if setting an item annotation triggers onItemChanged
+ bookmarksObserver._itemChangedId = -1;
+ anno.setItemAnnotation(newId3, "test-annotation", "foo", 0, 0);
+ do_check_eq(bookmarksObserver._itemChangedId, newId3);
+ do_check_eq(bookmarksObserver._itemChangedProperty, "test-annotation");
+ do_check_true(bookmarksObserver._itemChanged_isAnnotationProperty);
+ do_check_eq(bookmarksObserver._itemChangedValue, "");
+
+ // test search on bookmark title ZZZXXXYYY
+ try {
+ options = hs.getNewQueryOptions();
+ options.excludeQueries = 1;
+ options.queryType = Ci.nsINavHistoryQueryOptions.QUERY_TYPE_BOOKMARKS;
+ query = hs.getNewQuery();
+ query.searchTerms = "ZZZXXXYYY";
+ let result = hs.executeQuery(query, options);
+ let rootNode = result.root;
+ rootNode.containerOpen = true;
+ let cc = rootNode.childCount;
+ do_check_eq(cc, 1);
+ let node = rootNode.getChild(0);
+ do_check_eq(node.title, "ZZZXXXYYY");
+ do_check_true(node.itemId > 0);
+ rootNode.containerOpen = false;
+ }
+ catch (ex) {
+ do_throw("bookmarks query: " + ex);
+ }
+
+ // test dateAdded and lastModified properties
+ // for a search query
+ try {
+ options = hs.getNewQueryOptions();
+ options.excludeQueries = 1;
+ options.queryType = Ci.nsINavHistoryQueryOptions.QUERY_TYPE_BOOKMARKS;
+ query = hs.getNewQuery();
+ query.searchTerms = "ZZZXXXYYY";
+ let result = hs.executeQuery(query, options);
+ let rootNode = result.root;
+ rootNode.containerOpen = true;
+ let cc = rootNode.childCount;
+ do_check_eq(cc, 1);
+ let node = rootNode.getChild(0);
+
+ do_check_eq(typeof node.dateAdded, "number");
+ do_check_true(node.dateAdded > 0);
+
+ do_check_eq(typeof node.lastModified, "number");
+ do_check_true(node.lastModified > 0);
+
+ rootNode.containerOpen = false;
+ }
+ catch (ex) {
+ do_throw("bookmarks query: " + ex);
+ }
+
+ // test dateAdded and lastModified properties
+ // for a folder query
+ try {
+ options = hs.getNewQueryOptions();
+ query = hs.getNewQuery();
+ query.setFolders([testRoot], 1);
+ let result = hs.executeQuery(query, options);
+ let rootNode = result.root;
+ rootNode.containerOpen = true;
+ let cc = rootNode.childCount;
+ do_check_true(cc > 0);
+ for (let i = 0; i < cc; i++) {
+ let node = rootNode.getChild(i);
+
+ if (node.type == node.RESULT_TYPE_URI) {
+ do_check_eq(typeof node.dateAdded, "number");
+ do_check_true(node.dateAdded > 0);
+
+ do_check_eq(typeof node.lastModified, "number");
+ do_check_true(node.lastModified > 0);
+ break;
+ }
+ }
+ rootNode.containerOpen = false;
+ }
+ catch (ex) {
+ do_throw("bookmarks query: " + ex);
+ }
+
+ // check setItemLastModified() and setItemDateAdded()
+ let newId14 = bs.insertBookmark(testRoot, uri("http://bar.tld/"),
+ bs.DEFAULT_INDEX, "");
+ dateAdded = bs.getItemDateAdded(newId14);
+ lastModified = bs.getItemLastModified(newId14);
+ do_check_eq(lastModified, dateAdded);
+ bs.setItemLastModified(newId14, 1234000000000000);
+ let fakeLastModified = bs.getItemLastModified(newId14);
+ do_check_eq(fakeLastModified, 1234000000000000);
+ bs.setItemDateAdded(newId14, 4321000000000000);
+ let fakeDateAdded = bs.getItemDateAdded(newId14);
+ do_check_eq(fakeDateAdded, 4321000000000000);
+
+ // ensure that removing an item removes its annotations
+ do_check_true(anno.itemHasAnnotation(newId3, "test-annotation"));
+ bs.removeItem(newId3);
+ do_check_false(anno.itemHasAnnotation(newId3, "test-annotation"));
+
+ // bug 378820
+ let uri1 = uri("http://foo.tld/a");
+ bs.insertBookmark(testRoot, uri1, bs.DEFAULT_INDEX, "");
+ yield PlacesTestUtils.addVisits(uri1);
+
+ // bug 646993 - test bookmark titles longer than the maximum allowed length
+ let title15 = Array(TITLE_LENGTH_MAX + 5).join("X");
+ let title15expected = title15.substring(0, TITLE_LENGTH_MAX);
+ let newId15 = bs.insertBookmark(testRoot, uri("http://evil.com/"),
+ bs.DEFAULT_INDEX, title15);
+
+ do_check_eq(bs.getItemTitle(newId15).length,
+ title15expected.length);
+ do_check_eq(bookmarksObserver._itemAddedTitle, title15expected);
+ // test title length after updates
+ bs.setItemTitle(newId15, title15 + " updated");
+ do_check_eq(bs.getItemTitle(newId15).length,
+ title15expected.length);
+ do_check_eq(bookmarksObserver._itemChangedId, newId15);
+ do_check_eq(bookmarksObserver._itemChangedProperty, "title");
+ do_check_eq(bookmarksObserver._itemChangedValue, title15expected);
+
+ testSimpleFolderResult();
+});
+
+function testSimpleFolderResult() {
+ // the time before we create a folder, in microseconds
+ // Workaround possible VM timers issues subtracting 1us.
+ let beforeCreate = Date.now() * 1000 - 1;
+ do_check_true(beforeCreate > 0);
+
+ // create a folder
+ let parent = bs.createFolder(root, "test", bs.DEFAULT_INDEX);
+
+ let dateCreated = bs.getItemDateAdded(parent);
+ do_print("check that the folder was created with a valid dateAdded");
+ do_print("beforeCreate = " + beforeCreate);
+ do_print("dateCreated = " + dateCreated);
+ do_check_true(is_time_ordered(beforeCreate, dateCreated));
+
+ // the time before we insert, in microseconds
+ // Workaround possible VM timers issues subtracting 1ms.
+ let beforeInsert = Date.now() * 1000 - 1;
+ do_check_true(beforeInsert > 0);
+
+ // insert a separator
+ let sep = bs.insertSeparator(parent, bs.DEFAULT_INDEX);
+
+ let dateAdded = bs.getItemDateAdded(sep);
+ do_print("check that the separator was created with a valid dateAdded");
+ do_print("beforeInsert = " + beforeInsert);
+ do_print("dateAdded = " + dateAdded);
+ do_check_true(is_time_ordered(beforeInsert, dateAdded));
+
+ // re-set item title separately so can test nodes' last modified
+ let item = bs.insertBookmark(parent, uri("about:blank"),
+ bs.DEFAULT_INDEX, "");
+ bs.setItemTitle(item, "test bookmark");
+
+ // see above
+ let folder = bs.createFolder(parent, "test folder", bs.DEFAULT_INDEX);
+ bs.setItemTitle(folder, "test folder");
+
+ let longName = Array(TITLE_LENGTH_MAX + 5).join("A");
+ let folderLongName = bs.createFolder(parent, longName, bs.DEFAULT_INDEX);
+ do_check_eq(bookmarksObserver._itemAddedTitle, longName.substring(0, TITLE_LENGTH_MAX));
+
+ let options = hs.getNewQueryOptions();
+ let query = hs.getNewQuery();
+ query.setFolders([parent], 1);
+ let result = hs.executeQuery(query, options);
+ let rootNode = result.root;
+ rootNode.containerOpen = true;
+ do_check_eq(rootNode.childCount, 4);
+
+ let node = rootNode.getChild(0);
+ do_check_true(node.dateAdded > 0);
+ do_check_eq(node.lastModified, node.dateAdded);
+ do_check_eq(node.itemId, sep);
+ do_check_eq(node.title, "");
+ node = rootNode.getChild(1);
+ do_check_eq(node.itemId, item);
+ do_check_true(node.dateAdded > 0);
+ do_check_true(node.lastModified > 0);
+ do_check_eq(node.title, "test bookmark");
+ node = rootNode.getChild(2);
+ do_check_eq(node.itemId, folder);
+ do_check_eq(node.title, "test folder");
+ do_check_true(node.dateAdded > 0);
+ do_check_true(node.lastModified > 0);
+ node = rootNode.getChild(3);
+ do_check_eq(node.itemId, folderLongName);
+ do_check_eq(node.title, longName.substring(0, TITLE_LENGTH_MAX));
+ do_check_true(node.dateAdded > 0);
+ do_check_true(node.lastModified > 0);
+
+ // update with another long title
+ bs.setItemTitle(folderLongName, longName + " updated");
+ do_check_eq(bookmarksObserver._itemChangedId, folderLongName);
+ do_check_eq(bookmarksObserver._itemChangedProperty, "title");
+ do_check_eq(bookmarksObserver._itemChangedValue, longName.substring(0, TITLE_LENGTH_MAX));
+
+ node = rootNode.getChild(3);
+ do_check_eq(node.title, longName.substring(0, TITLE_LENGTH_MAX));
+
+ rootNode.containerOpen = false;
+}
+
+function getChildCount(aFolderId) {
+ let cc = -1;
+ try {
+ let options = hs.getNewQueryOptions();
+ let query = hs.getNewQuery();
+ query.setFolders([aFolderId], 1);
+ let result = hs.executeQuery(query, options);
+ let rootNode = result.root;
+ rootNode.containerOpen = true;
+ cc = rootNode.childCount;
+ rootNode.containerOpen = false;
+ } catch (ex) {
+ do_throw("getChildCount failed: " + ex);
+ }
+ return cc;
+}
diff --git a/toolkit/components/places/tests/bookmarks/test_bookmarks_eraseEverything.js b/toolkit/components/places/tests/bookmarks/test_bookmarks_eraseEverything.js
new file mode 100644
index 000000000..e8414359b
--- /dev/null
+++ b/toolkit/components/places/tests/bookmarks/test_bookmarks_eraseEverything.js
@@ -0,0 +1,116 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+add_task(function* test_eraseEverything() {
+ yield PlacesTestUtils.addVisits({ uri: NetUtil.newURI("http://example.com/") });
+ yield PlacesTestUtils.addVisits({ uri: NetUtil.newURI("http://mozilla.org/") });
+ let frecencyForExample = frecencyForUrl("http://example.com/");
+ let frecencyForMozilla = frecencyForUrl("http://example.com/");
+ Assert.ok(frecencyForExample > 0);
+ Assert.ok(frecencyForMozilla > 0);
+ let unfiledFolder = yield PlacesUtils.bookmarks.insert({ parentGuid: PlacesUtils.bookmarks.unfiledGuid,
+ type: PlacesUtils.bookmarks.TYPE_FOLDER });
+ checkBookmarkObject(unfiledFolder);
+ let unfiledBookmark = yield PlacesUtils.bookmarks.insert({ parentGuid: PlacesUtils.bookmarks.unfiledGuid,
+ type: PlacesUtils.bookmarks.TYPE_BOOKMARK,
+ url: "http://example.com/" });
+ checkBookmarkObject(unfiledBookmark);
+ let unfiledBookmarkInFolder =
+ yield PlacesUtils.bookmarks.insert({ parentGuid: unfiledFolder.guid,
+ type: PlacesUtils.bookmarks.TYPE_BOOKMARK,
+ url: "http://mozilla.org/" });
+ checkBookmarkObject(unfiledBookmarkInFolder);
+ PlacesUtils.annotations.setItemAnnotation((yield PlacesUtils.promiseItemId(unfiledBookmarkInFolder.guid)),
+ "testanno1", "testvalue1", 0, 0);
+
+ let menuFolder = yield PlacesUtils.bookmarks.insert({ parentGuid: PlacesUtils.bookmarks.menuGuid,
+ type: PlacesUtils.bookmarks.TYPE_FOLDER });
+ checkBookmarkObject(menuFolder);
+ let menuBookmark = yield PlacesUtils.bookmarks.insert({ parentGuid: PlacesUtils.bookmarks.menuGuid,
+ type: PlacesUtils.bookmarks.TYPE_BOOKMARK,
+ url: "http://example.com/" });
+ checkBookmarkObject(menuBookmark);
+ let menuBookmarkInFolder =
+ yield PlacesUtils.bookmarks.insert({ parentGuid: menuFolder.guid,
+ type: PlacesUtils.bookmarks.TYPE_BOOKMARK,
+ url: "http://mozilla.org/" });
+ checkBookmarkObject(menuBookmarkInFolder);
+ PlacesUtils.annotations.setItemAnnotation((yield PlacesUtils.promiseItemId(menuBookmarkInFolder.guid)),
+ "testanno1", "testvalue1", 0, 0);
+
+ let toolbarFolder = yield PlacesUtils.bookmarks.insert({ parentGuid: PlacesUtils.bookmarks.toolbarGuid,
+ type: PlacesUtils.bookmarks.TYPE_FOLDER });
+ checkBookmarkObject(toolbarFolder);
+ let toolbarBookmark = yield PlacesUtils.bookmarks.insert({ parentGuid: PlacesUtils.bookmarks.toolbarGuid,
+ type: PlacesUtils.bookmarks.TYPE_BOOKMARK,
+ url: "http://example.com/" });
+ checkBookmarkObject(toolbarBookmark);
+ let toolbarBookmarkInFolder =
+ yield PlacesUtils.bookmarks.insert({ parentGuid: toolbarFolder.guid,
+ type: PlacesUtils.bookmarks.TYPE_BOOKMARK,
+ url: "http://mozilla.org/" });
+ checkBookmarkObject(toolbarBookmarkInFolder);
+ PlacesUtils.annotations.setItemAnnotation((yield PlacesUtils.promiseItemId(toolbarBookmarkInFolder.guid)),
+ "testanno1", "testvalue1", 0, 0);
+
+ yield PlacesTestUtils.promiseAsyncUpdates();
+ Assert.ok(frecencyForUrl("http://example.com/") > frecencyForExample);
+ Assert.ok(frecencyForUrl("http://example.com/") > frecencyForMozilla);
+
+ yield PlacesUtils.bookmarks.eraseEverything();
+
+ Assert.equal(frecencyForUrl("http://example.com/"), frecencyForExample);
+ Assert.equal(frecencyForUrl("http://example.com/"), frecencyForMozilla);
+
+ // Check there are no orphan annotations.
+ let conn = yield PlacesUtils.promiseDBConnection();
+ let annoAttrs = yield conn.execute(`SELECT id, name FROM moz_anno_attributes`);
+ // Bug 1306445 will eventually remove the mobile root anno.
+ Assert.equal(annoAttrs.length, 1);
+ Assert.equal(annoAttrs[0].getResultByName("name"), PlacesUtils.MOBILE_ROOT_ANNO);
+ let annos = rows = yield conn.execute(`SELECT item_id, anno_attribute_id FROM moz_items_annos`);
+ Assert.equal(annos.length, 1);
+ Assert.equal(annos[0].getResultByName("item_id"), PlacesUtils.mobileFolderId);
+ Assert.equal(annos[0].getResultByName("anno_attribute_id"), annoAttrs[0].getResultByName("id"));
+});
+
+add_task(function* test_eraseEverything_roots() {
+ yield PlacesUtils.bookmarks.eraseEverything();
+
+ // Ensure the roots have not been removed.
+ Assert.ok(yield PlacesUtils.bookmarks.fetch(PlacesUtils.bookmarks.unfiledGuid));
+ Assert.ok(yield PlacesUtils.bookmarks.fetch(PlacesUtils.bookmarks.toolbarGuid));
+ Assert.ok(yield PlacesUtils.bookmarks.fetch(PlacesUtils.bookmarks.menuGuid));
+ Assert.ok(yield PlacesUtils.bookmarks.fetch(PlacesUtils.bookmarks.tagsGuid));
+ Assert.ok(yield PlacesUtils.bookmarks.fetch(PlacesUtils.bookmarks.rootGuid));
+});
+
+add_task(function* test_eraseEverything_reparented() {
+ // Create a folder with 1 bookmark in it...
+ let folder1 = yield PlacesUtils.bookmarks.insert({
+ parentGuid: PlacesUtils.bookmarks.toolbarGuid,
+ type: PlacesUtils.bookmarks.TYPE_FOLDER
+ });
+ let bookmark1 = yield PlacesUtils.bookmarks.insert({
+ parentGuid: folder1.guid,
+ url: "http://example.com/"
+ });
+ // ...and a second folder.
+ let folder2 = yield PlacesUtils.bookmarks.insert({
+ parentGuid: PlacesUtils.bookmarks.toolbarGuid,
+ type: PlacesUtils.bookmarks.TYPE_FOLDER
+ });
+
+ // Reparent the bookmark to the 2nd folder.
+ bookmark1.parentGuid = folder2.guid;
+ yield PlacesUtils.bookmarks.update(bookmark1);
+
+ // Erase everything.
+ yield PlacesUtils.bookmarks.eraseEverything();
+
+ // All the above items should no longer be in the GUIDHelper cache.
+ for (let guid of [folder1.guid, bookmark1.guid, folder2.guid]) {
+ yield Assert.rejects(PlacesUtils.promiseItemId(guid),
+ /no item found for the given GUID/);
+ }
+});
diff --git a/toolkit/components/places/tests/bookmarks/test_bookmarks_fetch.js b/toolkit/components/places/tests/bookmarks/test_bookmarks_fetch.js
new file mode 100644
index 000000000..9527f02e6
--- /dev/null
+++ b/toolkit/components/places/tests/bookmarks/test_bookmarks_fetch.js
@@ -0,0 +1,310 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+var gAccumulator = {
+ get callback() {
+ this.results = [];
+ return result => this.results.push(result);
+ }
+};
+
+add_task(function* invalid_input_throws() {
+ Assert.throws(() => PlacesUtils.bookmarks.fetch(),
+ /Input should be a valid object/);
+ Assert.throws(() => PlacesUtils.bookmarks.fetch(null),
+ /Input should be a valid object/);
+
+ Assert.throws(() => PlacesUtils.bookmarks.fetch({ guid: "123456789012",
+ parentGuid: "012345678901" }),
+ /The following properties were expected: index/);
+ Assert.throws(() => PlacesUtils.bookmarks.fetch({ guid: "123456789012",
+ index: 0 }),
+ /The following properties were expected: parentGuid/);
+
+ Assert.throws(() => PlacesUtils.bookmarks.fetch({}),
+ /Unexpected number of conditions provided: 0/);
+ Assert.throws(() => PlacesUtils.bookmarks.fetch({ guid: "123456789012",
+ parentGuid: "012345678901",
+ index: 0 }),
+ /Unexpected number of conditions provided: 2/);
+ Assert.throws(() => PlacesUtils.bookmarks.fetch({ guid: "123456789012",
+ url: "http://example.com"}),
+ /Unexpected number of conditions provided: 2/);
+
+ Assert.throws(() => PlacesUtils.bookmarks.fetch("test"),
+ /Invalid value for property 'guid'/);
+ Assert.throws(() => PlacesUtils.bookmarks.fetch(123),
+ /Invalid value for property 'guid'/);
+
+ Assert.throws(() => PlacesUtils.bookmarks.fetch({ guid: "test" }),
+ /Invalid value for property 'guid'/);
+ Assert.throws(() => PlacesUtils.bookmarks.fetch({ guid: null }),
+ /Invalid value for property 'guid'/);
+ Assert.throws(() => PlacesUtils.bookmarks.fetch({ guid: 123 }),
+ /Invalid value for property 'guid'/);
+
+ Assert.throws(() => PlacesUtils.bookmarks.fetch({ parentGuid: "test",
+ index: 0 }),
+ /Invalid value for property 'parentGuid'/);
+ Assert.throws(() => PlacesUtils.bookmarks.fetch({ parentGuid: null,
+ index: 0 }),
+ /Invalid value for property 'parentGuid'/);
+ Assert.throws(() => PlacesUtils.bookmarks.fetch({ parentGuid: 123,
+ index: 0 }),
+ /Invalid value for property 'parentGuid'/);
+
+ Assert.throws(() => PlacesUtils.bookmarks.fetch({ parentGuid: "123456789012",
+ index: "0" }),
+ /Invalid value for property 'index'/);
+ Assert.throws(() => PlacesUtils.bookmarks.fetch({ parentGuid: "123456789012",
+ index: null }),
+ /Invalid value for property 'index'/);
+ Assert.throws(() => PlacesUtils.bookmarks.fetch({ parentGuid: "123456789012",
+ index: -10 }),
+ /Invalid value for property 'index'/);
+
+ Assert.throws(() => PlacesUtils.bookmarks.fetch({ url: "http://te st/" }),
+ /Invalid value for property 'url'/);
+ Assert.throws(() => PlacesUtils.bookmarks.fetch({ url: null }),
+ /Invalid value for property 'url'/);
+ Assert.throws(() => PlacesUtils.bookmarks.fetch({ url: -10 }),
+ /Invalid value for property 'url'/);
+
+ Assert.throws(() => PlacesUtils.bookmarks.fetch("123456789012", "test"),
+ /onResult callback must be a valid function/);
+ Assert.throws(() => PlacesUtils.bookmarks.fetch("123456789012", {}),
+ /onResult callback must be a valid function/);
+});
+
+add_task(function* fetch_nonexistent_guid() {
+ let bm = yield PlacesUtils.bookmarks.fetch({ guid: "123456789012" },
+ gAccumulator.callback);
+ Assert.equal(bm, null);
+ Assert.equal(gAccumulator.results.length, 0);
+});
+
+add_task(function* fetch_bookmark() {
+ let bm1 = yield PlacesUtils.bookmarks.insert({ parentGuid: PlacesUtils.bookmarks.unfiledGuid,
+ type: PlacesUtils.bookmarks.TYPE_BOOKMARK,
+ url: "http://example.com/",
+ title: "a bookmark" });
+ checkBookmarkObject(bm1);
+
+ let bm2 = yield PlacesUtils.bookmarks.fetch(bm1.guid,
+ gAccumulator.callback);
+ checkBookmarkObject(bm2);
+ Assert.equal(gAccumulator.results.length, 1);
+ checkBookmarkObject(gAccumulator.results[0]);
+ Assert.deepEqual(gAccumulator.results[0], bm1);
+
+ Assert.deepEqual(bm1, bm2);
+ Assert.equal(bm2.parentGuid, PlacesUtils.bookmarks.unfiledGuid);
+ Assert.equal(bm2.index, 0);
+ Assert.deepEqual(bm2.dateAdded, bm2.lastModified);
+ Assert.equal(bm2.type, PlacesUtils.bookmarks.TYPE_BOOKMARK);
+ Assert.equal(bm2.url.href, "http://example.com/");
+ Assert.equal(bm2.title, "a bookmark");
+
+ yield PlacesUtils.bookmarks.remove(bm1.guid);
+});
+
+add_task(function* fetch_bookmar_empty_title() {
+ let bm1 = yield PlacesUtils.bookmarks.insert({ parentGuid: PlacesUtils.bookmarks.unfiledGuid,
+ type: PlacesUtils.bookmarks.TYPE_BOOKMARK,
+ url: "http://example.com/",
+ title: "" });
+ checkBookmarkObject(bm1);
+
+ let bm2 = yield PlacesUtils.bookmarks.fetch(bm1.guid);
+ checkBookmarkObject(bm2);
+
+ Assert.deepEqual(bm1, bm2);
+ Assert.equal(bm2.index, 0);
+ Assert.ok(!("title" in bm2));
+
+ yield PlacesUtils.bookmarks.remove(bm1.guid);
+});
+
+add_task(function* fetch_folder() {
+ let bm1 = yield PlacesUtils.bookmarks.insert({ parentGuid: PlacesUtils.bookmarks.unfiledGuid,
+ type: PlacesUtils.bookmarks.TYPE_FOLDER,
+ title: "a folder" });
+ checkBookmarkObject(bm1);
+
+ let bm2 = yield PlacesUtils.bookmarks.fetch(bm1.guid);
+ checkBookmarkObject(bm2);
+
+ Assert.deepEqual(bm1, bm2);
+ Assert.equal(bm2.parentGuid, PlacesUtils.bookmarks.unfiledGuid);
+ Assert.equal(bm2.index, 0);
+ Assert.deepEqual(bm2.dateAdded, bm2.lastModified);
+ Assert.equal(bm2.type, PlacesUtils.bookmarks.TYPE_FOLDER);
+ Assert.equal(bm2.title, "a folder");
+ Assert.ok(!("url" in bm2));
+
+ yield PlacesUtils.bookmarks.remove(bm1.guid);
+});
+
+add_task(function* fetch_folder_empty_title() {
+ let bm1 = yield PlacesUtils.bookmarks.insert({ parentGuid: PlacesUtils.bookmarks.unfiledGuid,
+ type: PlacesUtils.bookmarks.TYPE_FOLDER,
+ title: "" });
+ checkBookmarkObject(bm1);
+
+ let bm2 = yield PlacesUtils.bookmarks.fetch(bm1.guid);
+ checkBookmarkObject(bm2);
+
+ Assert.deepEqual(bm1, bm2);
+ Assert.equal(bm2.index, 0);
+ Assert.ok(!("title" in bm2));
+
+ yield PlacesUtils.bookmarks.remove(bm1.guid);
+});
+
+add_task(function* fetch_separator() {
+ let bm1 = yield PlacesUtils.bookmarks.insert({ parentGuid: PlacesUtils.bookmarks.unfiledGuid,
+ type: PlacesUtils.bookmarks.TYPE_SEPARATOR });
+ checkBookmarkObject(bm1);
+
+ let bm2 = yield PlacesUtils.bookmarks.fetch(bm1.guid);
+ checkBookmarkObject(bm2);
+
+ Assert.deepEqual(bm1, bm2);
+ Assert.equal(bm2.parentGuid, PlacesUtils.bookmarks.unfiledGuid);
+ Assert.equal(bm2.index, 0);
+ Assert.deepEqual(bm2.dateAdded, bm2.lastModified);
+ Assert.equal(bm2.type, PlacesUtils.bookmarks.TYPE_SEPARATOR);
+ Assert.ok(!("url" in bm2));
+ Assert.ok(!("title" in bm2));
+
+ yield PlacesUtils.bookmarks.remove(bm1.guid);
+});
+
+add_task(function* fetch_byposition_nonexisting_parentGuid() {
+ let bm = yield PlacesUtils.bookmarks.fetch({ parentGuid: "123456789012",
+ index: 0 },
+ gAccumulator.callback);
+ Assert.equal(bm, null);
+ Assert.equal(gAccumulator.results.length, 0);
+});
+
+add_task(function* fetch_byposition_nonexisting_index() {
+ let bm = yield PlacesUtils.bookmarks.fetch({ parentGuid: PlacesUtils.bookmarks.unfiledGuid,
+ index: 100 },
+ gAccumulator.callback);
+ Assert.equal(bm, null);
+ Assert.equal(gAccumulator.results.length, 0);
+});
+
+add_task(function* fetch_byposition() {
+ let bm1 = yield PlacesUtils.bookmarks.insert({ parentGuid: PlacesUtils.bookmarks.unfiledGuid,
+ type: PlacesUtils.bookmarks.TYPE_BOOKMARK,
+ url: "http://example.com/",
+ title: "a bookmark" });
+ checkBookmarkObject(bm1);
+
+ let bm2 = yield PlacesUtils.bookmarks.fetch({ parentGuid: bm1.parentGuid,
+ index: bm1.index },
+ gAccumulator.callback);
+ checkBookmarkObject(bm2);
+ Assert.equal(gAccumulator.results.length, 1);
+ checkBookmarkObject(gAccumulator.results[0]);
+ Assert.deepEqual(gAccumulator.results[0], bm1);
+
+ Assert.deepEqual(bm1, bm2);
+ Assert.equal(bm2.parentGuid, PlacesUtils.bookmarks.unfiledGuid);
+ Assert.equal(bm2.index, 0);
+ Assert.deepEqual(bm2.dateAdded, bm2.lastModified);
+ Assert.equal(bm2.type, PlacesUtils.bookmarks.TYPE_BOOKMARK);
+ Assert.equal(bm2.url.href, "http://example.com/");
+ Assert.equal(bm2.title, "a bookmark");
+});
+
+add_task(function* fetch_byposition_default_index() {
+ let bm1 = yield PlacesUtils.bookmarks.insert({ parentGuid: PlacesUtils.bookmarks.unfiledGuid,
+ type: PlacesUtils.bookmarks.TYPE_BOOKMARK,
+ url: "http://example.com/last",
+ title: "last child" });
+ checkBookmarkObject(bm1);
+
+ let bm2 = yield PlacesUtils.bookmarks.fetch({ parentGuid: bm1.parentGuid,
+ index: PlacesUtils.bookmarks.DEFAULT_INDEX },
+ gAccumulator.callback);
+ checkBookmarkObject(bm2);
+ Assert.equal(gAccumulator.results.length, 1);
+ checkBookmarkObject(gAccumulator.results[0]);
+ Assert.deepEqual(gAccumulator.results[0], bm1);
+
+ Assert.deepEqual(bm1, bm2);
+ Assert.equal(bm2.parentGuid, PlacesUtils.bookmarks.unfiledGuid);
+ Assert.equal(bm2.index, 1);
+ Assert.deepEqual(bm2.dateAdded, bm2.lastModified);
+ Assert.equal(bm2.type, PlacesUtils.bookmarks.TYPE_BOOKMARK);
+ Assert.equal(bm2.url.href, "http://example.com/last");
+ Assert.equal(bm2.title, "last child");
+
+ yield PlacesUtils.bookmarks.remove(bm1.guid);
+});
+
+add_task(function* fetch_byurl_nonexisting() {
+ let bm = yield PlacesUtils.bookmarks.fetch({ url: "http://nonexisting.com/" },
+ gAccumulator.callback);
+ Assert.equal(bm, null);
+ Assert.equal(gAccumulator.results.length, 0);
+});
+
+add_task(function* fetch_byurl() {
+ let bm1 = yield PlacesUtils.bookmarks.insert({ parentGuid: PlacesUtils.bookmarks.unfiledGuid,
+ type: PlacesUtils.bookmarks.TYPE_BOOKMARK,
+ url: "http://byurl.com/",
+ title: "a bookmark" });
+ checkBookmarkObject(bm1);
+
+ // Also ensure that fecth-by-url excludes the tags folder.
+ PlacesUtils.tagging.tagURI(uri(bm1.url.href), ["Test Tag"]);
+
+ let bm2 = yield PlacesUtils.bookmarks.fetch({ url: bm1.url },
+ gAccumulator.callback);
+ checkBookmarkObject(bm2);
+ Assert.equal(gAccumulator.results.length, 1);
+ checkBookmarkObject(gAccumulator.results[0]);
+ Assert.deepEqual(gAccumulator.results[0], bm1);
+
+ Assert.deepEqual(bm1, bm2);
+ Assert.equal(bm2.parentGuid, PlacesUtils.bookmarks.unfiledGuid);
+ Assert.deepEqual(bm2.dateAdded, bm2.lastModified);
+ Assert.equal(bm2.type, PlacesUtils.bookmarks.TYPE_BOOKMARK);
+ Assert.equal(bm2.url.href, "http://byurl.com/");
+ Assert.equal(bm2.title, "a bookmark");
+
+ let bm3 = yield PlacesUtils.bookmarks.insert({ parentGuid: PlacesUtils.bookmarks.unfiledGuid,
+ type: PlacesUtils.bookmarks.TYPE_BOOKMARK,
+ url: "http://byurl.com/",
+ title: "a bookmark" });
+ let bm4 = yield PlacesUtils.bookmarks.fetch({ url: bm1.url },
+ gAccumulator.callback);
+ checkBookmarkObject(bm4);
+ Assert.deepEqual(bm3, bm4);
+ Assert.equal(gAccumulator.results.length, 2);
+ gAccumulator.results.forEach(checkBookmarkObject);
+ Assert.deepEqual(gAccumulator.results[0], bm4);
+
+ // After an update the returned bookmark should change.
+ yield PlacesUtils.bookmarks.update({ guid: bm1.guid, title: "new title" });
+ let bm5 = yield PlacesUtils.bookmarks.fetch({ url: bm1.url },
+ gAccumulator.callback);
+ checkBookmarkObject(bm5);
+ // Cannot use deepEqual cause lastModified changed.
+ Assert.equal(bm1.guid, bm5.guid);
+ Assert.ok(bm5.lastModified > bm1.lastModified);
+ Assert.equal(gAccumulator.results.length, 2);
+ gAccumulator.results.forEach(checkBookmarkObject);
+ Assert.deepEqual(gAccumulator.results[0], bm5);
+
+ // cleanup
+ PlacesUtils.tagging.untagURI(uri(bm1.url.href), ["Test Tag"]);
+});
+
+function run_test() {
+ run_next_test();
+}
diff --git a/toolkit/components/places/tests/bookmarks/test_bookmarks_getRecent.js b/toolkit/components/places/tests/bookmarks/test_bookmarks_getRecent.js
new file mode 100644
index 000000000..35166bd95
--- /dev/null
+++ b/toolkit/components/places/tests/bookmarks/test_bookmarks_getRecent.js
@@ -0,0 +1,44 @@
+add_task(function* invalid_input_throws() {
+ Assert.throws(() => PlacesUtils.bookmarks.getRecent(),
+ /numberOfItems argument is required/);
+ Assert.throws(() => PlacesUtils.bookmarks.getRecent("abc"),
+ /numberOfItems argument must be an integer/);
+ Assert.throws(() => PlacesUtils.bookmarks.getRecent(1.2),
+ /numberOfItems argument must be an integer/);
+ Assert.throws(() => PlacesUtils.bookmarks.getRecent(0),
+ /numberOfItems argument must be greater than zero/);
+ Assert.throws(() => PlacesUtils.bookmarks.getRecent(-1),
+ /numberOfItems argument must be greater than zero/);
+});
+
+add_task(function* getRecent_returns_recent_bookmarks() {
+ 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/path",
+ title: "another bookmark" });
+ let bm3 = yield PlacesUtils.bookmarks.insert({ parentGuid: PlacesUtils.bookmarks.unfiledGuid,
+ url: "http://example.net/",
+ title: "another bookmark" });
+ let bm4 = yield PlacesUtils.bookmarks.insert({ parentGuid: PlacesUtils.bookmarks.unfiledGuid,
+ url: "http://example.net/path",
+ title: "yet another bookmark" });
+ checkBookmarkObject(bm1);
+ checkBookmarkObject(bm2);
+ checkBookmarkObject(bm3);
+ checkBookmarkObject(bm4);
+
+ let results = yield PlacesUtils.bookmarks.getRecent(3);
+ Assert.equal(results.length, 3);
+ checkBookmarkObject(results[0]);
+ Assert.deepEqual(bm4, results[0]);
+ checkBookmarkObject(results[1]);
+ Assert.deepEqual(bm3, results[1]);
+ checkBookmarkObject(results[2]);
+ Assert.deepEqual(bm2, results[2]);
+
+ yield PlacesUtils.bookmarks.eraseEverything();
+});
diff --git a/toolkit/components/places/tests/bookmarks/test_bookmarks_insert.js b/toolkit/components/places/tests/bookmarks/test_bookmarks_insert.js
new file mode 100644
index 000000000..0f772a92f
--- /dev/null
+++ b/toolkit/components/places/tests/bookmarks/test_bookmarks_insert.js
@@ -0,0 +1,264 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+add_task(function* invalid_input_throws() {
+ Assert.throws(() => PlacesUtils.bookmarks.insert(),
+ /Input should be a valid object/);
+ Assert.throws(() => PlacesUtils.bookmarks.insert(null),
+ /Input should be a valid object/);
+ Assert.throws(() => PlacesUtils.bookmarks.insert({}),
+ /The following properties were expected/);
+
+ Assert.throws(() => PlacesUtils.bookmarks.insert({ guid: "test" }),
+ /Invalid value for property 'guid'/);
+ Assert.throws(() => PlacesUtils.bookmarks.insert({ guid: null }),
+ /Invalid value for property 'guid'/);
+ Assert.throws(() => PlacesUtils.bookmarks.insert({ guid: 123 }),
+ /Invalid value for property 'guid'/);
+
+ Assert.throws(() => PlacesUtils.bookmarks.insert({ parentGuid: "test" }),
+ /Invalid value for property 'parentGuid'/);
+ Assert.throws(() => PlacesUtils.bookmarks.insert({ parentGuid: null }),
+ /Invalid value for property 'parentGuid'/);
+ Assert.throws(() => PlacesUtils.bookmarks.insert({ parentGuid: 123 }),
+ /Invalid value for property 'parentGuid'/);
+
+ Assert.throws(() => PlacesUtils.bookmarks.insert({ index: "1" }),
+ /Invalid value for property 'index'/);
+ Assert.throws(() => PlacesUtils.bookmarks.insert({ index: -10 }),
+ /Invalid value for property 'index'/);
+
+ Assert.throws(() => PlacesUtils.bookmarks.insert({ dateAdded: -10 }),
+ /Invalid value for property 'dateAdded'/);
+ Assert.throws(() => PlacesUtils.bookmarks.insert({ dateAdded: "today" }),
+ /Invalid value for property 'dateAdded'/);
+ Assert.throws(() => PlacesUtils.bookmarks.insert({ dateAdded: Date.now() }),
+ /Invalid value for property 'dateAdded'/);
+
+ Assert.throws(() => PlacesUtils.bookmarks.insert({ lastModified: -10 }),
+ /Invalid value for property 'lastModified'/);
+ Assert.throws(() => PlacesUtils.bookmarks.insert({ lastModified: "today" }),
+ /Invalid value for property 'lastModified'/);
+ Assert.throws(() => PlacesUtils.bookmarks.insert({ lastModified: Date.now() }),
+ /Invalid value for property 'lastModified'/);
+ let time = new Date();
+ let future = new Date(time + 86400000);
+ Assert.throws(() => PlacesUtils.bookmarks.insert({ dateAdded: future,
+ lastModified: time }),
+ /Invalid value for property 'dateAdded'/);
+ let past = new Date(time - 86400000);
+ Assert.throws(() => PlacesUtils.bookmarks.insert({ lastModified: past }),
+ /Invalid value for property 'lastModified'/);
+
+ Assert.throws(() => PlacesUtils.bookmarks.insert({ type: -1 }),
+ /Invalid value for property 'type'/);
+ Assert.throws(() => PlacesUtils.bookmarks.insert({ type: 100 }),
+ /Invalid value for property 'type'/);
+ Assert.throws(() => PlacesUtils.bookmarks.insert({ type: "bookmark" }),
+ /Invalid value for property 'type'/);
+
+ Assert.throws(() => PlacesUtils.bookmarks.insert({ type: PlacesUtils.bookmarks.TYPE_BOOKMARK,
+ title: -1 }),
+ /Invalid value for property 'title'/);
+
+ Assert.throws(() => PlacesUtils.bookmarks.insert({ type: PlacesUtils.bookmarks.TYPE_BOOKMARK,
+ url: 10 }),
+ /Invalid value for property 'url'/);
+ Assert.throws(() => PlacesUtils.bookmarks.insert({ type: PlacesUtils.bookmarks.TYPE_BOOKMARK,
+ url: "http://te st" }),
+ /Invalid value for property 'url'/);
+ let longurl = "http://www.example.com/";
+ for (let i = 0; i < 65536; i++) {
+ longurl += "a";
+ }
+ Assert.throws(() => PlacesUtils.bookmarks.insert({ type: PlacesUtils.bookmarks.TYPE_BOOKMARK,
+ url: longurl }),
+ /Invalid value for property 'url'/);
+ Assert.throws(() => PlacesUtils.bookmarks.insert({ type: PlacesUtils.bookmarks.TYPE_BOOKMARK,
+ url: NetUtil.newURI(longurl) }),
+ /Invalid value for property 'url'/);
+ Assert.throws(() => PlacesUtils.bookmarks.insert({ type: PlacesUtils.bookmarks.TYPE_BOOKMARK,
+ url: "te st" }),
+ /Invalid value for property 'url'/);
+});
+
+add_task(function* invalid_properties_for_bookmark_type() {
+ Assert.throws(() => PlacesUtils.bookmarks.insert({ type: PlacesUtils.bookmarks.TYPE_FOLDER,
+ url: "http://www.moz.com/" }),
+ /Invalid value for property 'url'/);
+ Assert.throws(() => PlacesUtils.bookmarks.insert({ type: PlacesUtils.bookmarks.TYPE_SEPARATOR,
+ url: "http://www.moz.com/" }),
+ /Invalid value for property 'url'/);
+ Assert.throws(() => PlacesUtils.bookmarks.insert({ type: PlacesUtils.bookmarks.TYPE_SEPARATOR,
+ title: "test" }),
+ /Invalid value for property 'title'/);
+});
+
+add_task(function* long_title_trim() {
+ let longtitle = "a";
+ for (let i = 0; i < 4096; i++) {
+ longtitle += "a";
+ }
+ let bm = yield PlacesUtils.bookmarks.insert({ parentGuid: PlacesUtils.bookmarks.unfiledGuid,
+ type: PlacesUtils.bookmarks.TYPE_FOLDER,
+ title: longtitle });
+ checkBookmarkObject(bm);
+ Assert.equal(bm.parentGuid, PlacesUtils.bookmarks.unfiledGuid);
+ Assert.equal(bm.index, 0);
+ Assert.equal(bm.dateAdded, bm.lastModified);
+ Assert.equal(bm.type, PlacesUtils.bookmarks.TYPE_FOLDER);
+ Assert.equal(bm.title.length, 4096, "title should have been trimmed");
+ Assert.ok(!("url" in bm), "url should not be set");
+});
+
+add_task(function* create_separator() {
+ let bm = yield PlacesUtils.bookmarks.insert({ parentGuid: PlacesUtils.bookmarks.unfiledGuid,
+ type: PlacesUtils.bookmarks.TYPE_SEPARATOR,
+ index: PlacesUtils.bookmarks.DEFAULT_INDEX });
+ checkBookmarkObject(bm);
+ Assert.equal(bm.parentGuid, PlacesUtils.bookmarks.unfiledGuid);
+ Assert.equal(bm.index, 1);
+ Assert.equal(bm.dateAdded, bm.lastModified);
+ Assert.equal(bm.type, PlacesUtils.bookmarks.TYPE_SEPARATOR);
+ Assert.ok(!("title" in bm), "title should not be set");
+});
+
+add_task(function* create_separator_w_title_fail() {
+ try {
+ yield PlacesUtils.bookmarks.insert({ parentGuid: PlacesUtils.bookmarks.unfiledGuid,
+ type: PlacesUtils.bookmarks.TYPE_SEPARATOR,
+ title: "a separator" });
+ Assert.ok(false, "Trying to set title for a separator should reject");
+ } catch (ex) {}
+});
+
+add_task(function* create_separator_invalid_parent_fail() {
+ try {
+ yield PlacesUtils.bookmarks.insert({ parentGuid: "123456789012",
+ type: PlacesUtils.bookmarks.TYPE_SEPARATOR,
+ title: "a separator" });
+ Assert.ok(false, "Trying to create an item in a non existing parent reject");
+ } catch (ex) {}
+});
+
+add_task(function* create_separator_given_guid() {
+ let bm = yield PlacesUtils.bookmarks.insert({ parentGuid: PlacesUtils.bookmarks.unfiledGuid,
+ type: PlacesUtils.bookmarks.TYPE_SEPARATOR,
+ index: PlacesUtils.bookmarks.DEFAULT_INDEX,
+ guid: "123456789012" });
+ checkBookmarkObject(bm);
+ Assert.equal(bm.guid, "123456789012");
+ Assert.equal(bm.parentGuid, PlacesUtils.bookmarks.unfiledGuid);
+ Assert.equal(bm.index, 2);
+ Assert.equal(bm.dateAdded, bm.lastModified);
+ Assert.equal(bm.type, PlacesUtils.bookmarks.TYPE_SEPARATOR);
+ Assert.ok(!("title" in bm), "title should not be set");
+});
+
+add_task(function* create_item_given_guid_no_type_fail() {
+ try {
+ yield PlacesUtils.bookmarks.insert({ parentGuid: "123456789012" });
+ Assert.ok(false, "Trying to create an item with a given guid but no type should reject");
+ } catch (ex) {}
+});
+
+add_task(function* create_separator_big_index() {
+ let bm = yield PlacesUtils.bookmarks.insert({ parentGuid: PlacesUtils.bookmarks.unfiledGuid,
+ type: PlacesUtils.bookmarks.TYPE_SEPARATOR,
+ index: 9999 });
+ checkBookmarkObject(bm);
+ Assert.equal(bm.parentGuid, PlacesUtils.bookmarks.unfiledGuid);
+ Assert.equal(bm.index, 3);
+ Assert.equal(bm.dateAdded, bm.lastModified);
+ Assert.equal(bm.type, PlacesUtils.bookmarks.TYPE_SEPARATOR);
+ Assert.ok(!("title" in bm), "title should not be set");
+});
+
+add_task(function* create_separator_given_dateAdded() {
+ let time = new Date();
+ let past = new Date(time - 86400000);
+ let bm = yield PlacesUtils.bookmarks.insert({ parentGuid: PlacesUtils.bookmarks.unfiledGuid,
+ type: PlacesUtils.bookmarks.TYPE_SEPARATOR,
+ dateAdded: past });
+ checkBookmarkObject(bm);
+ Assert.equal(bm.dateAdded, past);
+ Assert.equal(bm.lastModified, past);
+});
+
+add_task(function* create_folder() {
+ let bm = yield PlacesUtils.bookmarks.insert({ parentGuid: PlacesUtils.bookmarks.unfiledGuid,
+ type: PlacesUtils.bookmarks.TYPE_FOLDER });
+ checkBookmarkObject(bm);
+ Assert.equal(bm.parentGuid, PlacesUtils.bookmarks.unfiledGuid);
+ Assert.equal(bm.dateAdded, bm.lastModified);
+ Assert.equal(bm.type, PlacesUtils.bookmarks.TYPE_FOLDER);
+ Assert.ok(!("title" in bm), "title should not be set");
+
+ // And then create a nested folder.
+ let parentGuid = bm.guid;
+ bm = yield PlacesUtils.bookmarks.insert({ parentGuid: parentGuid,
+ type: PlacesUtils.bookmarks.TYPE_FOLDER,
+ title: "a folder" });
+ checkBookmarkObject(bm);
+ Assert.equal(bm.parentGuid, parentGuid);
+ Assert.equal(bm.index, 0);
+ Assert.equal(bm.type, PlacesUtils.bookmarks.TYPE_FOLDER);
+ Assert.strictEqual(bm.title, "a folder");
+});
+
+add_task(function* create_bookmark() {
+ let bm = yield PlacesUtils.bookmarks.insert({ parentGuid: PlacesUtils.bookmarks.unfiledGuid,
+ type: PlacesUtils.bookmarks.TYPE_FOLDER });
+ let parentGuid = bm.guid;
+
+ bm = yield PlacesUtils.bookmarks.insert({ parentGuid: parentGuid,
+ type: PlacesUtils.bookmarks.TYPE_BOOKMARK,
+ url: "http://example.com/",
+ title: "a bookmark" });
+ checkBookmarkObject(bm);
+ Assert.equal(bm.parentGuid, parentGuid);
+ Assert.equal(bm.index, 0);
+ Assert.equal(bm.type, PlacesUtils.bookmarks.TYPE_BOOKMARK);
+ Assert.equal(bm.url.href, "http://example.com/");
+ Assert.equal(bm.title, "a bookmark");
+
+ // Check parent lastModified.
+ let parent = yield PlacesUtils.bookmarks.fetch({ guid: bm.parentGuid });
+ Assert.deepEqual(parent.lastModified, bm.dateAdded);
+
+ bm = yield PlacesUtils.bookmarks.insert({ parentGuid: parentGuid,
+ type: PlacesUtils.bookmarks.TYPE_BOOKMARK,
+ url: new URL("http://example.com/") });
+ checkBookmarkObject(bm);
+ Assert.equal(bm.parentGuid, parentGuid);
+ Assert.equal(bm.index, 1);
+ Assert.equal(bm.type, PlacesUtils.bookmarks.TYPE_BOOKMARK);
+ Assert.equal(bm.url.href, "http://example.com/");
+ Assert.ok(!("title" in bm), "title should not be set");
+});
+
+add_task(function* create_bookmark_frecency() {
+ let bm = yield PlacesUtils.bookmarks.insert({ parentGuid: PlacesUtils.bookmarks.unfiledGuid,
+ type: PlacesUtils.bookmarks.TYPE_BOOKMARK,
+ url: "http://example.com/",
+ title: "a bookmark" });
+ checkBookmarkObject(bm);
+
+ yield PlacesTestUtils.promiseAsyncUpdates();
+ Assert.ok(frecencyForUrl(bm.url) > 0, "Check frecency has been updated")
+});
+
+add_task(function* create_bookmark_without_type() {
+ let bm = yield PlacesUtils.bookmarks.insert({ parentGuid: PlacesUtils.bookmarks.unfiledGuid,
+ url: "http://example.com/",
+ title: "a bookmark" });
+ checkBookmarkObject(bm);
+ Assert.equal(bm.parentGuid, PlacesUtils.bookmarks.unfiledGuid);
+ Assert.equal(bm.type, PlacesUtils.bookmarks.TYPE_BOOKMARK);
+ Assert.equal(bm.url.href, "http://example.com/");
+ Assert.equal(bm.title, "a bookmark");
+});
+
+function run_test() {
+ run_next_test();
+}
diff --git a/toolkit/components/places/tests/bookmarks/test_bookmarks_notifications.js b/toolkit/components/places/tests/bookmarks/test_bookmarks_notifications.js
new file mode 100644
index 000000000..02787425d
--- /dev/null
+++ b/toolkit/components/places/tests/bookmarks/test_bookmarks_notifications.js
@@ -0,0 +1,527 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+add_task(function* insert_separator_notification() {
+ let observer = expectNotifications();
+ let bm = yield PlacesUtils.bookmarks.insert({ type: PlacesUtils.bookmarks.TYPE_SEPARATOR,
+ parentGuid: PlacesUtils.bookmarks.unfiledGuid});
+ let itemId = yield PlacesUtils.promiseItemId(bm.guid);
+ let parentId = yield PlacesUtils.promiseItemId(bm.parentGuid);
+ observer.check([ { name: "onItemAdded",
+ arguments: [ itemId, parentId, bm.index, bm.type,
+ null, null, bm.dateAdded,
+ bm.guid, bm.parentGuid,
+ Ci.nsINavBookmarksService.SOURCE_DEFAULT ] }
+ ]);
+});
+
+add_task(function* insert_folder_notification() {
+ let observer = expectNotifications();
+ let bm = yield PlacesUtils.bookmarks.insert({ type: PlacesUtils.bookmarks.TYPE_FOLDER,
+ parentGuid: PlacesUtils.bookmarks.unfiledGuid,
+ title: "a folder" });
+ let itemId = yield PlacesUtils.promiseItemId(bm.guid);
+ let parentId = yield PlacesUtils.promiseItemId(bm.parentGuid);
+ observer.check([ { name: "onItemAdded",
+ arguments: [ itemId, parentId, bm.index, bm.type,
+ null, bm.title, bm.dateAdded,
+ bm.guid, bm.parentGuid,
+ Ci.nsINavBookmarksService.SOURCE_DEFAULT ] }
+ ]);
+});
+
+add_task(function* insert_folder_notitle_notification() {
+ let observer = expectNotifications();
+ let bm = yield PlacesUtils.bookmarks.insert({ type: PlacesUtils.bookmarks.TYPE_FOLDER,
+ parentGuid: PlacesUtils.bookmarks.unfiledGuid });
+ let itemId = yield PlacesUtils.promiseItemId(bm.guid);
+ let parentId = yield PlacesUtils.promiseItemId(bm.parentGuid);
+ observer.check([ { name: "onItemAdded",
+ arguments: [ itemId, parentId, bm.index, bm.type,
+ null, null, bm.dateAdded,
+ bm.guid, bm.parentGuid,
+ Ci.nsINavBookmarksService.SOURCE_DEFAULT ] }
+ ]);
+});
+
+add_task(function* insert_bookmark_notification() {
+ let observer = expectNotifications();
+ let bm = yield PlacesUtils.bookmarks.insert({ type: PlacesUtils.bookmarks.TYPE_BOOKMARK,
+ parentGuid: PlacesUtils.bookmarks.unfiledGuid,
+ url: new URL("http://example.com/"),
+ title: "a bookmark" });
+ let itemId = yield PlacesUtils.promiseItemId(bm.guid);
+ let parentId = yield PlacesUtils.promiseItemId(bm.parentGuid);
+ observer.check([ { name: "onItemAdded",
+ arguments: [ itemId, parentId, bm.index, bm.type,
+ bm.url, bm.title, bm.dateAdded,
+ bm.guid, bm.parentGuid,
+ Ci.nsINavBookmarksService.SOURCE_DEFAULT ] }
+ ]);
+});
+
+add_task(function* insert_bookmark_notitle_notification() {
+ let observer = expectNotifications();
+ let bm = yield PlacesUtils.bookmarks.insert({ type: PlacesUtils.bookmarks.TYPE_BOOKMARK,
+ parentGuid: PlacesUtils.bookmarks.unfiledGuid,
+ url: new URL("http://example.com/") });
+ let itemId = yield PlacesUtils.promiseItemId(bm.guid);
+ let parentId = yield PlacesUtils.promiseItemId(bm.parentGuid);
+ observer.check([ { name: "onItemAdded",
+ arguments: [ itemId, parentId, bm.index, bm.type,
+ bm.url, null, bm.dateAdded,
+ bm.guid, bm.parentGuid,
+ Ci.nsINavBookmarksService.SOURCE_DEFAULT ] }
+ ]);
+});
+
+add_task(function* insert_bookmark_tag_notification() {
+ let bm = yield PlacesUtils.bookmarks.insert({ type: PlacesUtils.bookmarks.TYPE_BOOKMARK,
+ parentGuid: PlacesUtils.bookmarks.unfiledGuid,
+ url: new URL("http://tag.example.com/") });
+ let itemId = yield PlacesUtils.promiseItemId(bm.guid);
+ let parentId = yield PlacesUtils.promiseItemId(bm.parentGuid);
+
+ let tagFolder = yield PlacesUtils.bookmarks.insert({ type: PlacesUtils.bookmarks.TYPE_FOLDER,
+ parentGuid: PlacesUtils.bookmarks.tagsGuid,
+ title: "tag" });
+ let observer = expectNotifications();
+ let tag = yield PlacesUtils.bookmarks.insert({ type: PlacesUtils.bookmarks.TYPE_BOOKMARK,
+ parentGuid: tagFolder.guid,
+ url: new URL("http://tag.example.com/") });
+ let tagId = yield PlacesUtils.promiseItemId(tag.guid);
+ let tagParentId = yield PlacesUtils.promiseItemId(tag.parentGuid);
+
+ observer.check([ { name: "onItemAdded",
+ arguments: [ tagId, tagParentId, tag.index, tag.type,
+ tag.url, null, tag.dateAdded,
+ tag.guid, tag.parentGuid,
+ Ci.nsINavBookmarksService.SOURCE_DEFAULT ] },
+ { name: "onItemChanged",
+ arguments: [ itemId, "tags", false, "",
+ bm.lastModified, bm.type, parentId,
+ bm.guid, bm.parentGuid, "",
+ Ci.nsINavBookmarksService.SOURCE_DEFAULT ] }
+ ]);
+});
+
+add_task(function* update_bookmark_lastModified() {
+ let bm = yield PlacesUtils.bookmarks.insert({ type: PlacesUtils.bookmarks.TYPE_BOOKMARK,
+ parentGuid: PlacesUtils.bookmarks.unfiledGuid,
+ url: new URL("http://lastmod.example.com/") });
+ let observer = expectNotifications();
+ bm = yield PlacesUtils.bookmarks.update({ guid: bm.guid,
+ lastModified: new Date() });
+ let itemId = yield PlacesUtils.promiseItemId(bm.guid);
+ let parentId = yield PlacesUtils.promiseItemId(bm.parentGuid);
+
+ observer.check([ { name: "onItemChanged",
+ arguments: [ itemId, "lastModified", false,
+ `${bm.lastModified * 1000}`, bm.lastModified,
+ bm.type, parentId, bm.guid, bm.parentGuid, "",
+ Ci.nsINavBookmarksService.SOURCE_DEFAULT ] }
+ ]);
+});
+
+add_task(function* update_bookmark_title() {
+ let bm = yield PlacesUtils.bookmarks.insert({ type: PlacesUtils.bookmarks.TYPE_BOOKMARK,
+ parentGuid: PlacesUtils.bookmarks.unfiledGuid,
+ url: new URL("http://title.example.com/") });
+ let observer = expectNotifications();
+ bm = yield PlacesUtils.bookmarks.update({ guid: bm.guid,
+ title: "new title" });
+ let itemId = yield PlacesUtils.promiseItemId(bm.guid);
+ let parentId = yield PlacesUtils.promiseItemId(bm.parentGuid);
+
+ observer.check([ { name: "onItemChanged",
+ arguments: [ itemId, "title", false, bm.title,
+ bm.lastModified, bm.type, parentId, bm.guid,
+ bm.parentGuid, "", Ci.nsINavBookmarksService.SOURCE_DEFAULT ] }
+ ]);
+});
+
+add_task(function* update_bookmark_uri() {
+ let bm = yield PlacesUtils.bookmarks.insert({ type: PlacesUtils.bookmarks.TYPE_BOOKMARK,
+ parentGuid: PlacesUtils.bookmarks.unfiledGuid,
+ url: new URL("http://url.example.com/") });
+ let observer = expectNotifications();
+ bm = yield PlacesUtils.bookmarks.update({ guid: bm.guid,
+ url: "http://mozilla.org/" });
+ let itemId = yield PlacesUtils.promiseItemId(bm.guid);
+ let parentId = yield PlacesUtils.promiseItemId(bm.parentGuid);
+
+ observer.check([ { name: "onItemChanged",
+ arguments: [ itemId, "uri", false, bm.url.href,
+ bm.lastModified, bm.type, parentId, bm.guid,
+ bm.parentGuid, "http://url.example.com/",
+ Ci.nsINavBookmarksService.SOURCE_DEFAULT ] }
+ ]);
+});
+
+add_task(function* update_move_same_folder() {
+ // Ensure there are at least two items in place (others test do so for us,
+ // but we don't have to depend on that).
+ yield PlacesUtils.bookmarks.insert({ type: PlacesUtils.bookmarks.TYPE_SEPARATOR,
+ parentGuid: PlacesUtils.bookmarks.unfiledGuid });
+ let bm = yield PlacesUtils.bookmarks.insert({ type: PlacesUtils.bookmarks.TYPE_BOOKMARK,
+ parentGuid: PlacesUtils.bookmarks.unfiledGuid,
+ url: new URL("http://move.example.com/") });
+ let bmItemId = yield PlacesUtils.promiseItemId(bm.guid);
+ let bmParentId = yield PlacesUtils.promiseItemId(bm.parentGuid);
+ let bmOldIndex = bm.index;
+
+ let observer = expectNotifications();
+ bm = yield PlacesUtils.bookmarks.update({ guid: bm.guid,
+ parentGuid: PlacesUtils.bookmarks.unfiledGuid,
+ index: 0 });
+ Assert.equal(bm.index, 0);
+ observer.check([ { name: "onItemMoved",
+ arguments: [ bmItemId, bmParentId, bmOldIndex, bmParentId, bm.index,
+ bm.type, bm.guid, bm.parentGuid, bm.parentGuid,
+ Ci.nsINavBookmarksService.SOURCE_DEFAULT ] }
+ ]);
+
+ // Test that we get the right index for DEFAULT_INDEX input.
+ bmOldIndex = 0;
+ observer = expectNotifications();
+ bm = yield PlacesUtils.bookmarks.update({ guid: bm.guid,
+ parentGuid: PlacesUtils.bookmarks.unfiledGuid,
+ index: PlacesUtils.bookmarks.DEFAULT_INDEX });
+ Assert.ok(bm.index > 0);
+ observer.check([ { name: "onItemMoved",
+ arguments: [ bmItemId, bmParentId, bmOldIndex, bmParentId, bm.index,
+ bm.type, bm.guid, bm.parentGuid, bm.parentGuid,
+ Ci.nsINavBookmarksService.SOURCE_DEFAULT ] }
+ ]);
+});
+
+add_task(function* update_move_different_folder() {
+ let bm = yield PlacesUtils.bookmarks.insert({ type: PlacesUtils.bookmarks.TYPE_BOOKMARK,
+ parentGuid: PlacesUtils.bookmarks.unfiledGuid,
+ url: new URL("http://move.example.com/") });
+ let folder = yield PlacesUtils.bookmarks.insert({ type: PlacesUtils.bookmarks.TYPE_FOLDER,
+ parentGuid: PlacesUtils.bookmarks.unfiledGuid });
+ let bmItemId = yield PlacesUtils.promiseItemId(bm.guid);
+ let bmOldParentId = PlacesUtils.unfiledBookmarksFolderId;
+ let bmOldIndex = bm.index;
+
+ let observer = expectNotifications();
+ bm = yield PlacesUtils.bookmarks.update({ guid: bm.guid,
+ parentGuid: folder.guid,
+ index: PlacesUtils.bookmarks.DEFAULT_INDEX });
+ Assert.equal(bm.index, 0);
+ let bmNewParentId = yield PlacesUtils.promiseItemId(folder.guid);
+ observer.check([ { name: "onItemMoved",
+ arguments: [ bmItemId, bmOldParentId, bmOldIndex, bmNewParentId,
+ bm.index, bm.type, bm.guid,
+ PlacesUtils.bookmarks.unfiledGuid,
+ bm.parentGuid,
+ Ci.nsINavBookmarksService.SOURCE_DEFAULT ] }
+ ]);
+});
+
+add_task(function* remove_bookmark() {
+ let bm = yield PlacesUtils.bookmarks.insert({ type: PlacesUtils.bookmarks.TYPE_BOOKMARK,
+ parentGuid: PlacesUtils.bookmarks.unfiledGuid,
+ url: new URL("http://remove.example.com/") });
+ let itemId = yield PlacesUtils.promiseItemId(bm.guid);
+ let parentId = yield PlacesUtils.promiseItemId(bm.parentGuid);
+
+ let observer = expectNotifications();
+ bm = yield PlacesUtils.bookmarks.remove(bm.guid);
+ // TODO (Bug 653910): onItemAnnotationRemoved notified even if there were no
+ // annotations.
+ observer.check([ { name: "onItemRemoved",
+ arguments: [ itemId, parentId, bm.index, bm.type, bm.url,
+ bm.guid, bm.parentGuid,
+ Ci.nsINavBookmarksService.SOURCE_DEFAULT ] }
+ ]);
+});
+
+add_task(function* remove_folder() {
+ let bm = yield PlacesUtils.bookmarks.insert({ type: PlacesUtils.bookmarks.TYPE_FOLDER,
+ parentGuid: PlacesUtils.bookmarks.unfiledGuid });
+ let itemId = yield PlacesUtils.promiseItemId(bm.guid);
+ let parentId = yield PlacesUtils.promiseItemId(bm.parentGuid);
+
+ let observer = expectNotifications();
+ bm = yield PlacesUtils.bookmarks.remove(bm.guid);
+ observer.check([ { name: "onItemRemoved",
+ arguments: [ itemId, parentId, bm.index, bm.type, null,
+ bm.guid, bm.parentGuid,
+ Ci.nsINavBookmarksService.SOURCE_DEFAULT ] }
+ ]);
+});
+
+add_task(function* remove_bookmark_tag_notification() {
+ let bm = yield PlacesUtils.bookmarks.insert({ type: PlacesUtils.bookmarks.TYPE_BOOKMARK,
+ parentGuid: PlacesUtils.bookmarks.unfiledGuid,
+ url: new URL("http://untag.example.com/") });
+ let itemId = yield PlacesUtils.promiseItemId(bm.guid);
+ let parentId = yield PlacesUtils.promiseItemId(bm.parentGuid);
+
+ let tagFolder = yield PlacesUtils.bookmarks.insert({ type: PlacesUtils.bookmarks.TYPE_FOLDER,
+ parentGuid: PlacesUtils.bookmarks.tagsGuid,
+ title: "tag" });
+ let tag = yield PlacesUtils.bookmarks.insert({ type: PlacesUtils.bookmarks.TYPE_BOOKMARK,
+ parentGuid: tagFolder.guid,
+ url: new URL("http://untag.example.com/") });
+ let tagId = yield PlacesUtils.promiseItemId(tag.guid);
+ let tagParentId = yield PlacesUtils.promiseItemId(tag.parentGuid);
+
+ let observer = expectNotifications();
+ yield PlacesUtils.bookmarks.remove(tag.guid);
+
+ observer.check([ { name: "onItemRemoved",
+ arguments: [ tagId, tagParentId, tag.index, tag.type,
+ tag.url, tag.guid, tag.parentGuid,
+ Ci.nsINavBookmarksService.SOURCE_DEFAULT ] },
+ { name: "onItemChanged",
+ arguments: [ itemId, "tags", false, "",
+ bm.lastModified, bm.type, parentId,
+ bm.guid, bm.parentGuid, "",
+ Ci.nsINavBookmarksService.SOURCE_DEFAULT ] }
+ ]);
+});
+
+add_task(function* remove_folder_notification() {
+ let folder1 = yield PlacesUtils.bookmarks.insert({ type: PlacesUtils.bookmarks.TYPE_FOLDER,
+ parentGuid: PlacesUtils.bookmarks.unfiledGuid });
+ let folder1Id = yield PlacesUtils.promiseItemId(folder1.guid);
+ let folder1ParentId = yield PlacesUtils.promiseItemId(folder1.parentGuid);
+
+ let bm = yield PlacesUtils.bookmarks.insert({ type: PlacesUtils.bookmarks.TYPE_BOOKMARK,
+ parentGuid: folder1.guid,
+ url: new URL("http://example.com/") });
+ let bmItemId = yield PlacesUtils.promiseItemId(bm.guid);
+
+ let folder2 = yield PlacesUtils.bookmarks.insert({ type: PlacesUtils.bookmarks.TYPE_FOLDER,
+ parentGuid: folder1.guid });
+ let folder2Id = yield PlacesUtils.promiseItemId(folder2.guid);
+
+ let bm2 = yield PlacesUtils.bookmarks.insert({ type: PlacesUtils.bookmarks.TYPE_BOOKMARK,
+ parentGuid: folder2.guid,
+ url: new URL("http://example.com/") });
+ let bm2ItemId = yield PlacesUtils.promiseItemId(bm2.guid);
+
+ let observer = expectNotifications();
+ yield PlacesUtils.bookmarks.remove(folder1.guid);
+
+ observer.check([ { name: "onItemRemoved",
+ arguments: [ bm2ItemId, folder2Id, bm2.index, bm2.type,
+ bm2.url, bm2.guid, bm2.parentGuid,
+ Ci.nsINavBookmarksService.SOURCE_DEFAULT ] },
+ { name: "onItemRemoved",
+ arguments: [ folder2Id, folder1Id, folder2.index,
+ folder2.type, null, folder2.guid,
+ folder2.parentGuid,
+ Ci.nsINavBookmarksService.SOURCE_DEFAULT ] },
+ { name: "onItemRemoved",
+ arguments: [ bmItemId, folder1Id, bm.index, bm.type,
+ bm.url, bm.guid, bm.parentGuid,
+ Ci.nsINavBookmarksService.SOURCE_DEFAULT ] },
+ { name: "onItemRemoved",
+ arguments: [ folder1Id, folder1ParentId, folder1.index,
+ folder1.type, null, folder1.guid,
+ folder1.parentGuid,
+ Ci.nsINavBookmarksService.SOURCE_DEFAULT ] }
+ ]);
+});
+
+add_task(function* eraseEverything_notification() {
+ // Let's start from a clean situation.
+ yield PlacesUtils.bookmarks.eraseEverything();
+
+ let folder1 = yield PlacesUtils.bookmarks.insert({ type: PlacesUtils.bookmarks.TYPE_FOLDER,
+ parentGuid: PlacesUtils.bookmarks.unfiledGuid });
+ let folder1Id = yield PlacesUtils.promiseItemId(folder1.guid);
+ let folder1ParentId = yield PlacesUtils.promiseItemId(folder1.parentGuid);
+
+ let bm = yield PlacesUtils.bookmarks.insert({ type: PlacesUtils.bookmarks.TYPE_BOOKMARK,
+ parentGuid: folder1.guid,
+ url: new URL("http://example.com/") });
+ let itemId = yield PlacesUtils.promiseItemId(bm.guid);
+ let parentId = yield PlacesUtils.promiseItemId(bm.parentGuid);
+
+ let folder2 = yield PlacesUtils.bookmarks.insert({ type: PlacesUtils.bookmarks.TYPE_FOLDER,
+ parentGuid: PlacesUtils.bookmarks.unfiledGuid });
+ let folder2Id = yield PlacesUtils.promiseItemId(folder2.guid);
+ let folder2ParentId = yield PlacesUtils.promiseItemId(folder2.parentGuid);
+
+ let toolbarBm = yield PlacesUtils.bookmarks.insert({ type: PlacesUtils.bookmarks.TYPE_BOOKMARK,
+ parentGuid: PlacesUtils.bookmarks.toolbarGuid,
+ url: new URL("http://example.com/") });
+ let toolbarBmId = yield PlacesUtils.promiseItemId(toolbarBm.guid);
+ let toolbarBmParentId = yield PlacesUtils.promiseItemId(toolbarBm.parentGuid);
+
+ let menuBm = yield PlacesUtils.bookmarks.insert({ type: PlacesUtils.bookmarks.TYPE_BOOKMARK,
+ parentGuid: PlacesUtils.bookmarks.menuGuid,
+ url: new URL("http://example.com/") });
+ let menuBmId = yield PlacesUtils.promiseItemId(menuBm.guid);
+ let menuBmParentId = yield PlacesUtils.promiseItemId(menuBm.parentGuid);
+
+ let observer = expectNotifications();
+ yield PlacesUtils.bookmarks.eraseEverything();
+
+ // Bookmarks should always be notified before their parents.
+ observer.check([ { name: "onItemRemoved",
+ arguments: [ itemId, parentId, bm.index, bm.type,
+ bm.url, bm.guid, bm.parentGuid,
+ Ci.nsINavBookmarksService.SOURCE_DEFAULT ] },
+ { name: "onItemRemoved",
+ arguments: [ folder2Id, folder2ParentId, folder2.index,
+ folder2.type, null, folder2.guid,
+ folder2.parentGuid,
+ Ci.nsINavBookmarksService.SOURCE_DEFAULT ] },
+ { name: "onItemRemoved",
+ arguments: [ folder1Id, folder1ParentId, folder1.index,
+ folder1.type, null, folder1.guid,
+ folder1.parentGuid,
+ Ci.nsINavBookmarksService.SOURCE_DEFAULT ] },
+ { name: "onItemRemoved",
+ arguments: [ menuBmId, menuBmParentId,
+ menuBm.index, menuBm.type,
+ menuBm.url, menuBm.guid,
+ menuBm.parentGuid,
+ Ci.nsINavBookmarksService.SOURCE_DEFAULT ] },
+ { name: "onItemRemoved",
+ arguments: [ toolbarBmId, toolbarBmParentId,
+ toolbarBm.index, toolbarBm.type,
+ toolbarBm.url, toolbarBm.guid,
+ toolbarBm.parentGuid,
+ Ci.nsINavBookmarksService.SOURCE_DEFAULT ] },
+ ]);
+});
+
+add_task(function* eraseEverything_reparented_notification() {
+ // Let's start from a clean situation.
+ yield PlacesUtils.bookmarks.eraseEverything();
+
+ let folder1 = yield PlacesUtils.bookmarks.insert({ type: PlacesUtils.bookmarks.TYPE_FOLDER,
+ parentGuid: PlacesUtils.bookmarks.unfiledGuid });
+ let folder1Id = yield PlacesUtils.promiseItemId(folder1.guid);
+ let folder1ParentId = yield PlacesUtils.promiseItemId(folder1.parentGuid);
+
+ let bm = yield PlacesUtils.bookmarks.insert({ type: PlacesUtils.bookmarks.TYPE_BOOKMARK,
+ parentGuid: folder1.guid,
+ url: new URL("http://example.com/") });
+ let itemId = yield PlacesUtils.promiseItemId(bm.guid);
+
+ let folder2 = yield PlacesUtils.bookmarks.insert({ type: PlacesUtils.bookmarks.TYPE_FOLDER,
+ parentGuid: PlacesUtils.bookmarks.unfiledGuid });
+ let folder2Id = yield PlacesUtils.promiseItemId(folder2.guid);
+ let folder2ParentId = yield PlacesUtils.promiseItemId(folder2.parentGuid);
+
+ bm.parentGuid = folder2.guid;
+ bm = yield PlacesUtils.bookmarks.update(bm);
+ let parentId = yield PlacesUtils.promiseItemId(bm.parentGuid);
+
+ let observer = expectNotifications();
+ yield PlacesUtils.bookmarks.eraseEverything();
+
+ // Bookmarks should always be notified before their parents.
+ observer.check([ { name: "onItemRemoved",
+ arguments: [ itemId, parentId, bm.index, bm.type,
+ bm.url, bm.guid, bm.parentGuid,
+ Ci.nsINavBookmarksService.SOURCE_DEFAULT ] },
+ { name: "onItemRemoved",
+ arguments: [ folder2Id, folder2ParentId, folder2.index,
+ folder2.type, null, folder2.guid,
+ folder2.parentGuid,
+ Ci.nsINavBookmarksService.SOURCE_DEFAULT ] },
+ { name: "onItemRemoved",
+ arguments: [ folder1Id, folder1ParentId, folder1.index,
+ folder1.type, null, folder1.guid,
+ folder1.parentGuid,
+ Ci.nsINavBookmarksService.SOURCE_DEFAULT ] },
+ ]);
+});
+
+add_task(function* reorder_notification() {
+ let bookmarks = [
+ { type: PlacesUtils.bookmarks.TYPE_BOOKMARK,
+ url: "http://example1.com/",
+ parentGuid: PlacesUtils.bookmarks.unfiledGuid
+ },
+ { type: PlacesUtils.bookmarks.TYPE_FOLDER,
+ parentGuid: PlacesUtils.bookmarks.unfiledGuid
+ },
+ { type: PlacesUtils.bookmarks.TYPE_SEPARATOR,
+ parentGuid: PlacesUtils.bookmarks.unfiledGuid
+ },
+ { type: PlacesUtils.bookmarks.TYPE_BOOKMARK,
+ url: "http://example2.com/",
+ parentGuid: PlacesUtils.bookmarks.unfiledGuid
+ },
+ { type: PlacesUtils.bookmarks.TYPE_BOOKMARK,
+ url: "http://example3.com/",
+ parentGuid: PlacesUtils.bookmarks.unfiledGuid
+ },
+ ];
+ let sorted = [];
+ for (let bm of bookmarks) {
+ sorted.push(yield PlacesUtils.bookmarks.insert(bm));
+ }
+
+ // Randomly reorder the array.
+ sorted.sort(() => 0.5 - Math.random());
+
+ let observer = expectNotifications();
+ yield PlacesUtils.bookmarks.reorder(PlacesUtils.bookmarks.unfiledGuid,
+ sorted.map(bm => bm.guid));
+
+ let expectedNotifications = [];
+ for (let i = 0; i < sorted.length; ++i) {
+ let child = sorted[i];
+ let childId = yield PlacesUtils.promiseItemId(child.guid);
+ expectedNotifications.push({ name: "onItemMoved",
+ arguments: [ childId,
+ PlacesUtils.unfiledBookmarksFolderId,
+ child.index,
+ PlacesUtils.unfiledBookmarksFolderId,
+ i,
+ child.type,
+ child.guid,
+ child.parentGuid,
+ child.parentGuid,
+ Ci.nsINavBookmarksService.SOURCE_DEFAULT
+ ] });
+ }
+ observer.check(expectedNotifications);
+});
+
+function expectNotifications() {
+ 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("onItem")) {
+ return (...origArgs) => {
+ let args = Array.from(origArgs, 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: name, arguments: args });
+ }
+ }
+
+ if (name in target)
+ return target[name];
+ return undefined;
+ }
+ });
+ PlacesUtils.bookmarks.addObserver(observer, false);
+ return observer;
+}
+
+function run_test() {
+ run_next_test();
+}
diff --git a/toolkit/components/places/tests/bookmarks/test_bookmarks_remove.js b/toolkit/components/places/tests/bookmarks/test_bookmarks_remove.js
new file mode 100644
index 000000000..19085a282
--- /dev/null
+++ b/toolkit/components/places/tests/bookmarks/test_bookmarks_remove.js
@@ -0,0 +1,204 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+add_task(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(function* remove_nonexistent_guid() {
+ try {
+ yield 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(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(function* remove_normal_folder_under_root_succeeds() {
+ let folder = yield PlacesUtils.bookmarks.insert({ parentGuid: PlacesUtils.bookmarks.rootGuid,
+ type: PlacesUtils.bookmarks.TYPE_FOLDER });
+ checkBookmarkObject(folder);
+ let removed_folder = yield PlacesUtils.bookmarks.remove(folder);
+ Assert.deepEqual(folder, removed_folder);
+ Assert.strictEqual((yield PlacesUtils.bookmarks.fetch(folder.guid)), null);
+});
+
+add_task(function* remove_bookmark() {
+ let bm1 = yield PlacesUtils.bookmarks.insert({ parentGuid: PlacesUtils.bookmarks.unfiledGuid,
+ type: PlacesUtils.bookmarks.TYPE_BOOKMARK,
+ url: "http://example.com/",
+ title: "a bookmark" });
+ checkBookmarkObject(bm1);
+
+ let bm2 = yield PlacesUtils.bookmarks.remove(bm1.guid);
+ checkBookmarkObject(bm2);
+
+ Assert.deepEqual(bm1, bm2);
+ Assert.equal(bm2.parentGuid, PlacesUtils.bookmarks.unfiledGuid);
+ Assert.equal(bm2.index, 0);
+ Assert.deepEqual(bm2.dateAdded, bm2.lastModified);
+ Assert.equal(bm2.type, PlacesUtils.bookmarks.TYPE_BOOKMARK);
+ Assert.equal(bm2.url.href, "http://example.com/");
+ Assert.equal(bm2.title, "a bookmark");
+});
+
+
+add_task(function* remove_bookmark_orphans() {
+ let bm1 = yield PlacesUtils.bookmarks.insert({ parentGuid: PlacesUtils.bookmarks.unfiledGuid,
+ type: PlacesUtils.bookmarks.TYPE_BOOKMARK,
+ url: "http://example.com/",
+ title: "a bookmark" });
+ checkBookmarkObject(bm1);
+ PlacesUtils.annotations.setItemAnnotation((yield PlacesUtils.promiseItemId(bm1.guid)),
+ "testanno", "testvalue", 0, 0);
+
+ let bm2 = yield PlacesUtils.bookmarks.remove(bm1.guid);
+ checkBookmarkObject(bm2);
+
+ // Check there are no orphan annotations.
+ let conn = yield PlacesUtils.promiseDBConnection();
+ let annoAttrs = yield conn.execute(`SELECT id, name FROM moz_anno_attributes`);
+ // Bug 1306445 will eventually remove the mobile root anno.
+ Assert.equal(annoAttrs.length, 1);
+ Assert.equal(annoAttrs[0].getResultByName("name"), PlacesUtils.MOBILE_ROOT_ANNO);
+ let annos = rows = yield conn.execute(`SELECT item_id, anno_attribute_id FROM moz_items_annos`);
+ Assert.equal(annos.length, 1);
+ Assert.equal(annos[0].getResultByName("item_id"), PlacesUtils.mobileFolderId);
+ Assert.equal(annos[0].getResultByName("anno_attribute_id"), annoAttrs[0].getResultByName("id"));
+});
+
+add_task(function* remove_bookmark_empty_title() {
+ let bm1 = yield PlacesUtils.bookmarks.insert({ parentGuid: PlacesUtils.bookmarks.unfiledGuid,
+ type: PlacesUtils.bookmarks.TYPE_BOOKMARK,
+ url: "http://example.com/",
+ title: "" });
+ checkBookmarkObject(bm1);
+
+ let bm2 = yield PlacesUtils.bookmarks.remove(bm1.guid);
+ checkBookmarkObject(bm2);
+
+ Assert.deepEqual(bm1, bm2);
+ Assert.equal(bm2.index, 0);
+ Assert.ok(!("title" in bm2));
+});
+
+add_task(function* remove_folder() {
+ let bm1 = yield PlacesUtils.bookmarks.insert({ parentGuid: PlacesUtils.bookmarks.unfiledGuid,
+ type: PlacesUtils.bookmarks.TYPE_FOLDER,
+ title: "a folder" });
+ checkBookmarkObject(bm1);
+
+ let bm2 = yield PlacesUtils.bookmarks.remove(bm1.guid);
+ checkBookmarkObject(bm2);
+
+ Assert.deepEqual(bm1, bm2);
+ Assert.equal(bm2.parentGuid, PlacesUtils.bookmarks.unfiledGuid);
+ Assert.equal(bm2.index, 0);
+ Assert.deepEqual(bm2.dateAdded, bm2.lastModified);
+ Assert.equal(bm2.type, PlacesUtils.bookmarks.TYPE_FOLDER);
+ Assert.equal(bm2.title, "a folder");
+ Assert.ok(!("url" in bm2));
+});
+
+add_task(function* test_nested_contents_removed() {
+ let folder1 = yield PlacesUtils.bookmarks.insert({ parentGuid: PlacesUtils.bookmarks.unfiledGuid,
+ type: PlacesUtils.bookmarks.TYPE_FOLDER,
+ title: "a folder" });
+ let folder2 = yield PlacesUtils.bookmarks.insert({ parentGuid: folder1.guid,
+ type: PlacesUtils.bookmarks.TYPE_FOLDER,
+ title: "a folder" });
+ let sep = yield PlacesUtils.bookmarks.insert({ parentGuid: folder2.guid,
+ type: PlacesUtils.bookmarks.TYPE_SEPARATOR });
+ yield PlacesUtils.bookmarks.remove(folder1);
+ Assert.strictEqual((yield PlacesUtils.bookmarks.fetch(folder1.guid)), null);
+ Assert.strictEqual((yield PlacesUtils.bookmarks.fetch(folder2.guid)), null);
+ Assert.strictEqual((yield PlacesUtils.bookmarks.fetch(sep.guid)), null);
+});
+
+add_task(function* remove_folder_empty_title() {
+ let bm1 = yield PlacesUtils.bookmarks.insert({ parentGuid: PlacesUtils.bookmarks.unfiledGuid,
+ type: PlacesUtils.bookmarks.TYPE_FOLDER,
+ title: "" });
+ checkBookmarkObject(bm1);
+
+ let bm2 = yield PlacesUtils.bookmarks.remove(bm1.guid);
+ checkBookmarkObject(bm2);
+
+ Assert.deepEqual(bm1, bm2);
+ Assert.equal(bm2.index, 0);
+ Assert.ok(!("title" in bm2));
+});
+
+add_task(function* remove_separator() {
+ let bm1 = yield PlacesUtils.bookmarks.insert({ parentGuid: PlacesUtils.bookmarks.unfiledGuid,
+ type: PlacesUtils.bookmarks.TYPE_SEPARATOR });
+ checkBookmarkObject(bm1);
+
+ let bm2 = yield PlacesUtils.bookmarks.remove(bm1.guid);
+ checkBookmarkObject(bm2);
+
+ Assert.deepEqual(bm1, bm2);
+ Assert.equal(bm2.parentGuid, PlacesUtils.bookmarks.unfiledGuid);
+ Assert.equal(bm2.index, 0);
+ Assert.deepEqual(bm2.dateAdded, bm2.lastModified);
+ Assert.equal(bm2.type, PlacesUtils.bookmarks.TYPE_SEPARATOR);
+ Assert.ok(!("url" in bm2));
+ Assert.ok(!("title" in bm2));
+});
+
+add_task(function* test_nested_content_fails_when_not_allowed() {
+ let folder1 = yield PlacesUtils.bookmarks.insert({ parentGuid: PlacesUtils.bookmarks.unfiledGuid,
+ type: PlacesUtils.bookmarks.TYPE_FOLDER,
+ title: "a folder" });
+ yield PlacesUtils.bookmarks.insert({ parentGuid: folder1.guid,
+ type: PlacesUtils.bookmarks.TYPE_FOLDER,
+ title: "a folder" });
+ yield Assert.rejects(PlacesUtils.bookmarks.remove(folder1, {preventRemovalOfNonEmptyFolders: true}),
+ /Cannot remove a non-empty folder./);
+});
+
+function run_test() {
+ run_next_test();
+}
diff --git a/toolkit/components/places/tests/bookmarks/test_bookmarks_reorder.js b/toolkit/components/places/tests/bookmarks/test_bookmarks_reorder.js
new file mode 100644
index 000000000..4f6617280
--- /dev/null
+++ b/toolkit/components/places/tests/bookmarks/test_bookmarks_reorder.js
@@ -0,0 +1,177 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+add_task(function* invalid_input_throws() {
+ Assert.throws(() => PlacesUtils.bookmarks.reorder(),
+ /Invalid value for property 'guid'/);
+ Assert.throws(() => PlacesUtils.bookmarks.reorder(null),
+ /Invalid value for property 'guid'/);
+
+ Assert.throws(() => PlacesUtils.bookmarks.reorder("test"),
+ /Invalid value for property 'guid'/);
+ Assert.throws(() => PlacesUtils.bookmarks.reorder(123),
+ /Invalid value for property 'guid'/);
+
+ Assert.throws(() => PlacesUtils.bookmarks.reorder({ guid: "test" }),
+ /Invalid value for property 'guid'/);
+
+ Assert.throws(() => PlacesUtils.bookmarks.reorder("123456789012"),
+ /Must provide a sorted array of children GUIDs./);
+ Assert.throws(() => PlacesUtils.bookmarks.reorder("123456789012", {}),
+ /Must provide a sorted array of children GUIDs./);
+ Assert.throws(() => PlacesUtils.bookmarks.reorder("123456789012", null),
+ /Must provide a sorted array of children GUIDs./);
+ Assert.throws(() => PlacesUtils.bookmarks.reorder("123456789012", []),
+ /Must provide a sorted array of children GUIDs./);
+
+ Assert.throws(() => PlacesUtils.bookmarks.reorder("123456789012", [ null ]),
+ /Invalid GUID found in the sorted children array/);
+ Assert.throws(() => PlacesUtils.bookmarks.reorder("123456789012", [ "" ]),
+ /Invalid GUID found in the sorted children array/);
+ Assert.throws(() => PlacesUtils.bookmarks.reorder("123456789012", [ {} ]),
+ /Invalid GUID found in the sorted children array/);
+ Assert.throws(() => PlacesUtils.bookmarks.reorder("123456789012", [ "012345678901", null ]),
+ /Invalid GUID found in the sorted children array/);
+});
+
+add_task(function* reorder_nonexistent_guid() {
+ yield Assert.rejects(PlacesUtils.bookmarks.reorder("123456789012", [ "012345678901" ]),
+ /No folder found for the provided GUID/,
+ "Should throw for nonexisting guid");
+});
+
+add_task(function* reorder() {
+ let bookmarks = [
+ { url: "http://example1.com/",
+ parentGuid: PlacesUtils.bookmarks.unfiledGuid
+ },
+ { type: PlacesUtils.bookmarks.TYPE_FOLDER,
+ parentGuid: PlacesUtils.bookmarks.unfiledGuid
+ },
+ { type: PlacesUtils.bookmarks.TYPE_SEPARATOR,
+ parentGuid: PlacesUtils.bookmarks.unfiledGuid
+ },
+ { url: "http://example2.com/",
+ parentGuid: PlacesUtils.bookmarks.unfiledGuid
+ },
+ { url: "http://example3.com/",
+ parentGuid: PlacesUtils.bookmarks.unfiledGuid
+ }
+ ];
+
+ let sorted = [];
+ for (let bm of bookmarks) {
+ sorted.push(yield PlacesUtils.bookmarks.insert(bm));
+ }
+
+ // Check the initial append sorting.
+ Assert.ok(sorted.every((bm, i) => bm.index == i),
+ "Initial bookmarks sorting is correct");
+
+ // Apply random sorting and run multiple tests.
+ for (let t = 0; t < 4; t++) {
+ sorted.sort(() => 0.5 - Math.random());
+ let sortedGuids = sorted.map(child => child.guid);
+ dump("Expected order: " + sortedGuids.join() + "\n");
+ // Add a nonexisting guid to the array, to ensure nothing will break.
+ sortedGuids.push("123456789012");
+ yield PlacesUtils.bookmarks.reorder(PlacesUtils.bookmarks.unfiledGuid,
+ sortedGuids);
+ for (let i = 0; i < sorted.length; ++i) {
+ let item = yield PlacesUtils.bookmarks.fetch(sorted[i].guid);
+ Assert.equal(item.index, i);
+ }
+ }
+
+ do_print("Test partial sorting");
+ // Try a partial sorting by passing only 2 entries.
+ // The unspecified entries should retain the original order.
+ sorted = [ sorted[1], sorted[0] ].concat(sorted.slice(2));
+ let sortedGuids = [ sorted[0].guid, sorted[1].guid ];
+ dump("Expected order: " + sorted.map(b => b.guid).join() + "\n");
+ yield PlacesUtils.bookmarks.reorder(PlacesUtils.bookmarks.unfiledGuid,
+ sortedGuids);
+ for (let i = 0; i < sorted.length; ++i) {
+ let item = yield PlacesUtils.bookmarks.fetch(sorted[i].guid);
+ Assert.equal(item.index, i);
+ }
+
+ // Use triangular numbers to detect skipped position.
+ let db = yield PlacesUtils.promiseDBConnection();
+ let rows = yield db.execute(
+ `SELECT parent
+ FROM moz_bookmarks
+ GROUP BY parent
+ HAVING (SUM(DISTINCT position + 1) - (count(*) * (count(*) + 1) / 2)) <> 0`);
+ Assert.equal(rows.length, 0, "All the bookmarks should have consistent positions");
+});
+
+add_task(function* move_and_reorder() {
+ // Start clean.
+ yield PlacesUtils.bookmarks.eraseEverything();
+
+ let bm1 = yield PlacesUtils.bookmarks.insert({
+ url: "http://example1.com/",
+ parentGuid: PlacesUtils.bookmarks.unfiledGuid
+ });
+ let f1 = yield PlacesUtils.bookmarks.insert({
+ type: PlacesUtils.bookmarks.TYPE_FOLDER,
+ parentGuid: PlacesUtils.bookmarks.unfiledGuid
+ });
+ let bm2 = yield PlacesUtils.bookmarks.insert({
+ url: "http://example2.com/",
+ parentGuid: f1.guid
+ });
+ let f2 = yield PlacesUtils.bookmarks.insert({
+ type: PlacesUtils.bookmarks.TYPE_FOLDER,
+ parentGuid: PlacesUtils.bookmarks.unfiledGuid
+ });
+ let bm3 = yield PlacesUtils.bookmarks.insert({
+ url: "http://example3.com/",
+ parentGuid: f2.guid
+ });
+ let bm4 = yield PlacesUtils.bookmarks.insert({
+ url: "http://example4.com/",
+ parentGuid: f2.guid
+ });
+ let bm5 = yield PlacesUtils.bookmarks.insert({
+ url: "http://example5.com/",
+ parentGuid: f2.guid
+ });
+
+ // Invert f2 children.
+ // This is critical to reproduce the bug, cause it inverts the position
+ // compared to the natural insertion order.
+ yield PlacesUtils.bookmarks.reorder(f2.guid, [bm5.guid, bm4.guid, bm3.guid]);
+
+ bm1.parentGuid = f1.guid;
+ bm1.index = 0;
+ yield PlacesUtils.bookmarks.update(bm1);
+
+ bm1 = yield PlacesUtils.bookmarks.fetch(bm1.guid);
+ Assert.equal(bm1.index, 0);
+ bm2 = yield PlacesUtils.bookmarks.fetch(bm2.guid);
+ Assert.equal(bm2.index, 1);
+ bm3 = yield PlacesUtils.bookmarks.fetch(bm3.guid);
+ Assert.equal(bm3.index, 2);
+ bm4 = yield PlacesUtils.bookmarks.fetch(bm4.guid);
+ Assert.equal(bm4.index, 1);
+ bm5 = yield PlacesUtils.bookmarks.fetch(bm5.guid);
+ Assert.equal(bm5.index, 0);
+
+ // No-op reorder on f1 children.
+ // Nothing should change. Though, due to bug 1293365 this was causing children
+ // of other folders to get messed up.
+ yield PlacesUtils.bookmarks.reorder(f1.guid, [bm1.guid, bm2.guid]);
+
+ bm1 = yield PlacesUtils.bookmarks.fetch(bm1.guid);
+ Assert.equal(bm1.index, 0);
+ bm2 = yield PlacesUtils.bookmarks.fetch(bm2.guid);
+ Assert.equal(bm2.index, 1);
+ bm3 = yield PlacesUtils.bookmarks.fetch(bm3.guid);
+ Assert.equal(bm3.index, 2);
+ bm4 = yield PlacesUtils.bookmarks.fetch(bm4.guid);
+ Assert.equal(bm4.index, 1);
+ bm5 = yield PlacesUtils.bookmarks.fetch(bm5.guid);
+ Assert.equal(bm5.index, 0);
+});
diff --git a/toolkit/components/places/tests/bookmarks/test_bookmarks_search.js b/toolkit/components/places/tests/bookmarks/test_bookmarks_search.js
new file mode 100644
index 000000000..02f7c5460
--- /dev/null
+++ b/toolkit/components/places/tests/bookmarks/test_bookmarks_search.js
@@ -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: "an on-menu bookmark" });
+ let bm4 = yield PlacesUtils.bookmarks.insert({ parentGuid: PlacesUtils.bookmarks.toolbarGuid,
+ url: "http://toolbar.org/",
+ title: "an on-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("an on-menu bookmark");
+ Assert.equal(results.length, 1);
+ checkBookmarkObject(results[0]);
+ Assert.deepEqual(bm3, results[0]);
+
+ // finds toolbar bookmarks
+ results = yield PlacesUtils.bookmarks.search("an on-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();
+});
+
diff --git a/toolkit/components/places/tests/bookmarks/test_bookmarks_update.js b/toolkit/components/places/tests/bookmarks/test_bookmarks_update.js
new file mode 100644
index 000000000..d077fd6f3
--- /dev/null
+++ b/toolkit/components/places/tests/bookmarks/test_bookmarks_update.js
@@ -0,0 +1,414 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+add_task(function* invalid_input_throws() {
+ Assert.throws(() => PlacesUtils.bookmarks.update(),
+ /Input should be a valid object/);
+ Assert.throws(() => PlacesUtils.bookmarks.update(null),
+ /Input should be a valid object/);
+ Assert.throws(() => PlacesUtils.bookmarks.update({}),
+ /The following properties were expected/);
+
+ Assert.throws(() => PlacesUtils.bookmarks.update({ guid: "test" }),
+ /Invalid value for property 'guid'/);
+ Assert.throws(() => PlacesUtils.bookmarks.update({ guid: null }),
+ /Invalid value for property 'guid'/);
+ Assert.throws(() => PlacesUtils.bookmarks.update({ guid: 123 }),
+ /Invalid value for property 'guid'/);
+
+ Assert.throws(() => PlacesUtils.bookmarks.update({ parentGuid: "test" }),
+ /Invalid value for property 'parentGuid'/);
+ Assert.throws(() => PlacesUtils.bookmarks.update({ parentGuid: null }),
+ /Invalid value for property 'parentGuid'/);
+ Assert.throws(() => PlacesUtils.bookmarks.update({ parentGuid: 123 }),
+ /Invalid value for property 'parentGuid'/);
+
+ Assert.throws(() => PlacesUtils.bookmarks.update({ index: "1" }),
+ /Invalid value for property 'index'/);
+ Assert.throws(() => PlacesUtils.bookmarks.update({ index: -10 }),
+ /Invalid value for property 'index'/);
+
+ Assert.throws(() => PlacesUtils.bookmarks.update({ dateAdded: -10 }),
+ /Invalid value for property 'dateAdded'/);
+ Assert.throws(() => PlacesUtils.bookmarks.update({ dateAdded: "today" }),
+ /Invalid value for property 'dateAdded'/);
+ Assert.throws(() => PlacesUtils.bookmarks.update({ dateAdded: Date.now() }),
+ /Invalid value for property 'dateAdded'/);
+
+ Assert.throws(() => PlacesUtils.bookmarks.update({ lastModified: -10 }),
+ /Invalid value for property 'lastModified'/);
+ Assert.throws(() => PlacesUtils.bookmarks.update({ lastModified: "today" }),
+ /Invalid value for property 'lastModified'/);
+ Assert.throws(() => PlacesUtils.bookmarks.update({ lastModified: Date.now() }),
+ /Invalid value for property 'lastModified'/);
+
+ Assert.throws(() => PlacesUtils.bookmarks.update({ type: -1 }),
+ /Invalid value for property 'type'/);
+ Assert.throws(() => PlacesUtils.bookmarks.update({ type: 100 }),
+ /Invalid value for property 'type'/);
+ Assert.throws(() => PlacesUtils.bookmarks.update({ type: "bookmark" }),
+ /Invalid value for property 'type'/);
+
+ Assert.throws(() => PlacesUtils.bookmarks.update({ url: 10 }),
+ /Invalid value for property 'url'/);
+ Assert.throws(() => PlacesUtils.bookmarks.update({ url: "http://te st" }),
+ /Invalid value for property 'url'/);
+ let longurl = "http://www.example.com/";
+ for (let i = 0; i < 65536; i++) {
+ longurl += "a";
+ }
+ Assert.throws(() => PlacesUtils.bookmarks.update({ url: longurl }),
+ /Invalid value for property 'url'/);
+
+ Assert.throws(() => PlacesUtils.bookmarks.update({ url: NetUtil.newURI(longurl) }),
+ /Invalid value for property 'url'/);
+ Assert.throws(() => PlacesUtils.bookmarks.update({ url: "te st" }),
+ /Invalid value for property 'url'/);
+
+ Assert.throws(() => PlacesUtils.bookmarks.update({ title: -1 }),
+ /Invalid value for property 'title'/);
+ Assert.throws(() => PlacesUtils.bookmarks.update({ title: {} }),
+ /Invalid value for property 'title'/);
+
+ Assert.throws(() => PlacesUtils.bookmarks.update({ guid: "123456789012" }),
+ /Not enough properties to update/);
+
+ Assert.throws(() => PlacesUtils.bookmarks.update({ guid: "123456789012",
+ parentGuid: "012345678901" }),
+ /The following properties were expected: index/);
+});
+
+add_task(function* nonexisting_bookmark_throws() {
+ try {
+ yield PlacesUtils.bookmarks.update({ guid: "123456789012",
+ title: "test" });
+ Assert.ok(false, "Should have thrown");
+ } catch (ex) {
+ Assert.ok(/No bookmarks found for the provided GUID/.test(ex));
+ }
+});
+
+add_task(function* invalid_properties_for_existing_bookmark() {
+ let bm = yield PlacesUtils.bookmarks.insert({ type: PlacesUtils.bookmarks.TYPE_BOOKMARK,
+ parentGuid: PlacesUtils.bookmarks.unfiledGuid,
+ url: "http://example.com/" });
+
+ try {
+ yield PlacesUtils.bookmarks.update({ guid: bm.guid,
+ type: PlacesUtils.bookmarks.TYPE_FOLDER });
+ Assert.ok(false, "Should have thrown");
+ } catch (ex) {
+ Assert.ok(/The bookmark type cannot be changed/.test(ex));
+ }
+
+ try {
+ yield PlacesUtils.bookmarks.update({ guid: bm.guid,
+ dateAdded: new Date() });
+ Assert.ok(false, "Should have thrown");
+ } catch (ex) {
+ Assert.ok(/The bookmark dateAdded cannot be changed/.test(ex));
+ }
+
+ try {
+ yield PlacesUtils.bookmarks.update({ guid: bm.guid,
+ dateAdded: new Date() });
+ Assert.ok(false, "Should have thrown");
+ } catch (ex) {
+ Assert.ok(/The bookmark dateAdded cannot be changed/.test(ex));
+ }
+
+ try {
+ yield PlacesUtils.bookmarks.update({ guid: bm.guid,
+ parentGuid: "123456789012",
+ index: 1 });
+ Assert.ok(false, "Should have thrown");
+ } catch (ex) {
+ Assert.ok(/No bookmarks found for the provided parentGuid/.test(ex));
+ }
+
+ let past = new Date(Date.now() - 86400000);
+ try {
+ yield PlacesUtils.bookmarks.update({ guid: bm.guid,
+ lastModified: past });
+ Assert.ok(false, "Should have thrown");
+ } catch (ex) {
+ Assert.ok(/Invalid value for property 'lastModified'/.test(ex));
+ }
+
+ let folder = yield PlacesUtils.bookmarks.insert({ type: PlacesUtils.bookmarks.TYPE_FOLDER,
+ parentGuid: PlacesUtils.bookmarks.unfiledGuid });
+ try {
+ yield PlacesUtils.bookmarks.update({ guid: folder.guid,
+ url: "http://example.com/" });
+ Assert.ok(false, "Should have thrown");
+ } catch (ex) {
+ Assert.ok(/Invalid value for property 'url'/.test(ex));
+ }
+
+ let separator = yield PlacesUtils.bookmarks.insert({ type: PlacesUtils.bookmarks.TYPE_SEPARATOR,
+ parentGuid: PlacesUtils.bookmarks.unfiledGuid });
+ try {
+ yield PlacesUtils.bookmarks.update({ guid: separator.guid,
+ url: "http://example.com/" });
+ Assert.ok(false, "Should have thrown");
+ } catch (ex) {
+ Assert.ok(/Invalid value for property 'url'/.test(ex));
+ }
+ try {
+ yield PlacesUtils.bookmarks.update({ guid: separator.guid,
+ title: "test" });
+ Assert.ok(false, "Should have thrown");
+ } catch (ex) {
+ Assert.ok(/Invalid value for property 'title'/.test(ex));
+ }
+});
+
+add_task(function* long_title_trim() {
+ let longtitle = "a";
+ for (let i = 0; i < 4096; i++) {
+ longtitle += "a";
+ }
+ let bm = yield PlacesUtils.bookmarks.insert({ parentGuid: PlacesUtils.bookmarks.unfiledGuid,
+ type: PlacesUtils.bookmarks.TYPE_FOLDER,
+ title: "title" });
+ checkBookmarkObject(bm);
+
+ bm = yield PlacesUtils.bookmarks.update({ guid: bm.guid,
+ title: longtitle });
+ let newTitle = bm.title;
+ Assert.equal(newTitle.length, 4096, "title should have been trimmed");
+
+ bm = yield PlacesUtils.bookmarks.fetch(bm.guid);
+ Assert.equal(bm.title, newTitle);
+});
+
+add_task(function* update_lastModified() {
+ let yesterday = new Date(Date.now() - 86400000);
+ let bm = yield PlacesUtils.bookmarks.insert({ parentGuid: PlacesUtils.bookmarks.unfiledGuid,
+ type: PlacesUtils.bookmarks.TYPE_FOLDER,
+ title: "title",
+ dateAdded: yesterday });
+ checkBookmarkObject(bm);
+ Assert.deepEqual(bm.lastModified, yesterday);
+
+ let time = new Date();
+ bm = yield PlacesUtils.bookmarks.update({ guid: bm.guid,
+ lastModified: time });
+ checkBookmarkObject(bm);
+ Assert.deepEqual(bm.lastModified, time);
+
+ bm = yield PlacesUtils.bookmarks.fetch(bm.guid);
+ Assert.deepEqual(bm.lastModified, time);
+
+ bm = yield PlacesUtils.bookmarks.update({ guid: bm.guid,
+ lastModified: yesterday });
+ Assert.deepEqual(bm.lastModified, yesterday);
+
+ bm = yield PlacesUtils.bookmarks.update({ guid: bm.guid,
+ title: "title2" });
+ Assert.ok(bm.lastModified >= time);
+
+ bm = yield PlacesUtils.bookmarks.update({ guid: bm.guid,
+ title: "" });
+ Assert.ok(!("title" in bm));
+
+ bm = yield PlacesUtils.bookmarks.fetch(bm.guid);
+ Assert.ok(!("title" in bm));
+});
+
+add_task(function* update_url() {
+ let bm = yield PlacesUtils.bookmarks.insert({ parentGuid: PlacesUtils.bookmarks.unfiledGuid,
+ type: PlacesUtils.bookmarks.TYPE_BOOKMARK,
+ url: "http://example.com/",
+ title: "title" });
+ checkBookmarkObject(bm);
+ let lastModified = bm.lastModified;
+ let frecency = frecencyForUrl(bm.url);
+ Assert.ok(frecency > 0, "Check frecency has been updated");
+
+ bm = yield PlacesUtils.bookmarks.update({ guid: bm.guid,
+ url: "http://mozilla.org/" });
+ checkBookmarkObject(bm);
+ Assert.ok(bm.lastModified >= lastModified);
+ Assert.equal(bm.url.href, "http://mozilla.org/");
+
+ bm = yield PlacesUtils.bookmarks.fetch(bm.guid);
+ Assert.equal(bm.url.href, "http://mozilla.org/");
+ Assert.ok(bm.lastModified >= lastModified);
+
+ Assert.equal(frecencyForUrl("http://example.com/"), frecency, "Check frecency for example.com");
+ Assert.equal(frecencyForUrl("http://mozilla.org/"), frecency, "Check frecency for mozilla.org");
+});
+
+add_task(function* update_index() {
+ let parent = yield PlacesUtils.bookmarks.insert({ parentGuid: PlacesUtils.bookmarks.unfiledGuid,
+ type: PlacesUtils.bookmarks.TYPE_FOLDER }) ;
+ let f1 = yield PlacesUtils.bookmarks.insert({ parentGuid: parent.guid,
+ type: PlacesUtils.bookmarks.TYPE_FOLDER });
+ Assert.equal(f1.index, 0);
+ let f2 = yield PlacesUtils.bookmarks.insert({ parentGuid: parent.guid,
+ type: PlacesUtils.bookmarks.TYPE_FOLDER });
+ Assert.equal(f2.index, 1);
+ let f3 = yield PlacesUtils.bookmarks.insert({ parentGuid: parent.guid,
+ type: PlacesUtils.bookmarks.TYPE_FOLDER });
+ Assert.equal(f3.index, 2);
+ let lastModified = f1.lastModified;
+
+ f1 = yield PlacesUtils.bookmarks.update({ guid: f1.guid,
+ parentGuid: f1.parentGuid,
+ index: 1});
+ checkBookmarkObject(f1);
+ Assert.equal(f1.index, 1);
+ Assert.ok(f1.lastModified >= lastModified);
+
+ parent = yield PlacesUtils.bookmarks.fetch(f1.parentGuid);
+ Assert.deepEqual(parent.lastModified, f1.lastModified);
+
+ f2 = yield PlacesUtils.bookmarks.fetch(f2.guid);
+ Assert.equal(f2.index, 0);
+
+ f3 = yield PlacesUtils.bookmarks.fetch(f3.guid);
+ Assert.equal(f3.index, 2);
+
+ f3 = yield PlacesUtils.bookmarks.update({ guid: f3.guid,
+ index: 0 });
+ f1 = yield PlacesUtils.bookmarks.fetch(f1.guid);
+ Assert.equal(f1.index, 2);
+
+ f2 = yield PlacesUtils.bookmarks.fetch(f2.guid);
+ Assert.equal(f2.index, 1);
+});
+
+add_task(function* update_move_folder_into_descendant_throws() {
+ let parent = yield PlacesUtils.bookmarks.insert({ parentGuid: PlacesUtils.bookmarks.unfiledGuid,
+ type: PlacesUtils.bookmarks.TYPE_FOLDER }) ;
+ let descendant = yield PlacesUtils.bookmarks.insert({ parentGuid: parent.guid,
+ type: PlacesUtils.bookmarks.TYPE_FOLDER });
+
+ try {
+ yield PlacesUtils.bookmarks.update({ guid: parent.guid,
+ parentGuid: parent.guid,
+ index: 0 });
+ Assert.ok(false, "Should have thrown");
+ } catch (ex) {
+ Assert.ok(/Cannot insert a folder into itself or one of its descendants/.test(ex));
+ }
+
+ try {
+ yield PlacesUtils.bookmarks.update({ guid: parent.guid,
+ parentGuid: descendant.guid,
+ index: 0 });
+ Assert.ok(false, "Should have thrown");
+ } catch (ex) {
+ Assert.ok(/Cannot insert a folder into itself or one of its descendants/.test(ex));
+ }
+});
+
+add_task(function* update_move() {
+ let parent = yield PlacesUtils.bookmarks.insert({ parentGuid: PlacesUtils.bookmarks.unfiledGuid,
+ type: PlacesUtils.bookmarks.TYPE_FOLDER }) ;
+ let bm = yield PlacesUtils.bookmarks.insert({ parentGuid: parent.guid,
+ url: "http://example.com/",
+ type: PlacesUtils.bookmarks.TYPE_BOOKMARK }) ;
+ let descendant = yield PlacesUtils.bookmarks.insert({ parentGuid: parent.guid,
+ type: PlacesUtils.bookmarks.TYPE_FOLDER });
+ Assert.equal(descendant.index, 1);
+ let lastModified = bm.lastModified;
+
+ // This is moving to a nonexisting index by purpose, it will be appended.
+ bm = yield PlacesUtils.bookmarks.update({ guid: bm.guid,
+ parentGuid: descendant.guid,
+ index: 1 });
+ checkBookmarkObject(bm);
+ Assert.equal(bm.parentGuid, descendant.guid);
+ Assert.equal(bm.index, 0);
+ Assert.ok(bm.lastModified >= lastModified);
+
+ parent = yield PlacesUtils.bookmarks.fetch(parent.guid);
+ descendant = yield PlacesUtils.bookmarks.fetch(descendant.guid);
+ Assert.deepEqual(parent.lastModified, bm.lastModified);
+ Assert.deepEqual(descendant.lastModified, bm.lastModified);
+ Assert.equal(descendant.index, 0);
+
+ bm = yield PlacesUtils.bookmarks.fetch(bm.guid);
+ Assert.equal(bm.parentGuid, descendant.guid);
+ Assert.equal(bm.index, 0);
+
+ bm = yield PlacesUtils.bookmarks.update({ guid: bm.guid,
+ parentGuid: parent.guid,
+ index: 0 });
+ Assert.equal(bm.parentGuid, parent.guid);
+ Assert.equal(bm.index, 0);
+
+ descendant = yield PlacesUtils.bookmarks.fetch(descendant.guid);
+ Assert.equal(descendant.index, 1);
+});
+
+add_task(function* update_move_append() {
+ let folder_a =
+ yield PlacesUtils.bookmarks.insert({ parentGuid: PlacesUtils.bookmarks.unfiledGuid,
+ type: PlacesUtils.bookmarks.TYPE_FOLDER });
+ checkBookmarkObject(folder_a);
+ let folder_b =
+ yield PlacesUtils.bookmarks.insert({ parentGuid: PlacesUtils.bookmarks.unfiledGuid,
+ type: PlacesUtils.bookmarks.TYPE_FOLDER });
+ checkBookmarkObject(folder_b);
+
+ /* folder_a: [sep_1, sep_2, sep_3], folder_b: [] */
+ let sep_1 = yield PlacesUtils.bookmarks.insert({ parentGuid: folder_a.guid,
+ type: PlacesUtils.bookmarks.TYPE_SEPARATOR });
+ checkBookmarkObject(sep_1);
+ let sep_2 = yield PlacesUtils.bookmarks.insert({ parentGuid: folder_a.guid,
+ type: PlacesUtils.bookmarks.TYPE_SEPARATOR });
+ checkBookmarkObject(sep_2);
+ let sep_3 = yield PlacesUtils.bookmarks.insert({ parentGuid: folder_a.guid,
+ type: PlacesUtils.bookmarks.TYPE_SEPARATOR });
+ checkBookmarkObject(sep_3);
+
+ function ensurePosition(info, parentGuid, index) {
+ checkBookmarkObject(info);
+ Assert.equal(info.parentGuid, parentGuid);
+ Assert.equal(info.index, index);
+ }
+
+ // folder_a: [sep_2, sep_3, sep_1], folder_b: []
+ sep_1.index = PlacesUtils.bookmarks.DEFAULT_INDEX;
+ // Note sep_1 includes parentGuid even though we're not moving the item to
+ // another folder
+ sep_1 = yield PlacesUtils.bookmarks.update(sep_1);
+ ensurePosition(sep_1, folder_a.guid, 2);
+ sep_2 = yield PlacesUtils.bookmarks.fetch(sep_2.guid);
+ ensurePosition(sep_2, folder_a.guid, 0);
+ sep_3 = yield PlacesUtils.bookmarks.fetch(sep_3.guid);
+ ensurePosition(sep_3, folder_a.guid, 1);
+ sep_1 = yield PlacesUtils.bookmarks.fetch(sep_1.guid);
+ ensurePosition(sep_1, folder_a.guid, 2);
+
+ // folder_a: [sep_2, sep_1], folder_b: [sep_3]
+ sep_3.index = PlacesUtils.bookmarks.DEFAULT_INDEX;
+ sep_3.parentGuid = folder_b.guid;
+ sep_3 = yield PlacesUtils.bookmarks.update(sep_3);
+ ensurePosition(sep_3, folder_b.guid, 0);
+ sep_2 = yield PlacesUtils.bookmarks.fetch(sep_2.guid);
+ ensurePosition(sep_2, folder_a.guid, 0);
+ sep_1 = yield PlacesUtils.bookmarks.fetch(sep_1.guid);
+ ensurePosition(sep_1, folder_a.guid, 1);
+ sep_3 = yield PlacesUtils.bookmarks.fetch(sep_3.guid);
+ ensurePosition(sep_3, folder_b.guid, 0);
+
+ // folder_a: [sep_1], folder_b: [sep_3, sep_2]
+ sep_2.index = Number.MAX_SAFE_INTEGER;
+ sep_2.parentGuid = folder_b.guid;
+ sep_2 = yield PlacesUtils.bookmarks.update(sep_2);
+ ensurePosition(sep_2, folder_b.guid, 1);
+ sep_1 = yield PlacesUtils.bookmarks.fetch(sep_1.guid);
+ ensurePosition(sep_1, folder_a.guid, 0);
+ sep_3 = yield PlacesUtils.bookmarks.fetch(sep_3.guid);
+ ensurePosition(sep_3, folder_b.guid, 0);
+ sep_2 = yield PlacesUtils.bookmarks.fetch(sep_2.guid);
+ ensurePosition(sep_2, folder_b.guid, 1);
+});
+
+function run_test() {
+ run_next_test();
+}
diff --git a/toolkit/components/places/tests/bookmarks/test_bookmarkstree_cache.js b/toolkit/components/places/tests/bookmarks/test_bookmarkstree_cache.js
new file mode 100644
index 000000000..f5cf34641
--- /dev/null
+++ b/toolkit/components/places/tests/bookmarks/test_bookmarkstree_cache.js
@@ -0,0 +1,18 @@
+
+// Bug 1192692 - promiseBookmarksTree caches items without adding observers to
+// invalidate the cache.
+add_task(function* boookmarks_tree_cache() {
+ // Note that for this test to be effective, it needs to use the "old" sync
+ // bookmarks methods - using, eg, PlacesUtils.bookmarks.insert() doesn't
+ // demonstrate the problem as it indirectly arranges for the observers to
+ // be added.
+ let id = PlacesUtils.bookmarks.insertBookmark(PlacesUtils.unfiledBookmarksFolderId,
+ uri("http://example.com"),
+ PlacesUtils.bookmarks.DEFAULT_INDEX,
+ "A title");
+ yield PlacesUtils.promiseBookmarksTree();
+
+ PlacesUtils.bookmarks.removeItem(id);
+
+ yield Assert.rejects(PlacesUtils.promiseItemGuid(id));
+});
diff --git a/toolkit/components/places/tests/bookmarks/test_changeBookmarkURI.js b/toolkit/components/places/tests/bookmarks/test_changeBookmarkURI.js
new file mode 100644
index 000000000..55ffecf2f
--- /dev/null
+++ b/toolkit/components/places/tests/bookmarks/test_changeBookmarkURI.js
@@ -0,0 +1,68 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// Get bookmark service
+var bmsvc = Cc["@mozilla.org/browser/nav-bookmarks-service;1"].
+ getService(Ci.nsINavBookmarksService);
+
+/**
+ * Ensures that the Places APIs recognize that aBookmarkedUri is bookmarked
+ * via aBookmarkId and that aUnbookmarkedUri is not bookmarked at all.
+ *
+ * @param aBookmarkId
+ * an item ID whose corresponding URI is aBookmarkedUri
+ * @param aBookmarkedUri
+ * a bookmarked URI that has a corresponding item ID aBookmarkId
+ * @param aUnbookmarkedUri
+ * a URI that is not currently bookmarked at all
+ */
+function checkUris(aBookmarkId, aBookmarkedUri, aUnbookmarkedUri)
+{
+ // Ensure that aBookmarkedUri equals some URI that is bookmarked
+ var uri = bmsvc.getBookmarkedURIFor(aBookmarkedUri);
+ do_check_neq(uri, null);
+ do_check_true(uri.equals(aBookmarkedUri));
+
+ // Ensure that aBookmarkedUri is considered bookmarked
+ do_check_true(bmsvc.isBookmarked(aBookmarkedUri));
+
+ // Ensure that the URI corresponding to aBookmarkId equals aBookmarkedUri
+ do_check_true(bmsvc.getBookmarkURI(aBookmarkId).equals(aBookmarkedUri));
+
+ // Ensure that aUnbookmarkedUri does not equal any URI that is bookmarked
+ uri = bmsvc.getBookmarkedURIFor(aUnbookmarkedUri);
+ do_check_eq(uri, null);
+
+ // Ensure that aUnbookmarkedUri is not considered bookmarked
+ do_check_false(bmsvc.isBookmarked(aUnbookmarkedUri));
+}
+
+// main
+function run_test() {
+ // Create a folder
+ var folderId = bmsvc.createFolder(bmsvc.toolbarFolder,
+ "test",
+ bmsvc.DEFAULT_INDEX);
+
+ // Create 2 URIs
+ var uri1 = uri("http://www.dogs.com");
+ var uri2 = uri("http://www.cats.com");
+
+ // Bookmark the first one
+ var bookmarkId = bmsvc.insertBookmark(folderId,
+ uri1,
+ bmsvc.DEFAULT_INDEX,
+ "Dogs");
+
+ // uri1 is bookmarked via bookmarkId, uri2 is not
+ checkUris(bookmarkId, uri1, uri2);
+
+ // Change the URI of the bookmark to uri2
+ bmsvc.changeBookmarkURI(bookmarkId, uri2);
+
+ // uri2 is now bookmarked via bookmarkId, uri1 is not
+ checkUris(bookmarkId, uri2, uri1);
+}
diff --git a/toolkit/components/places/tests/bookmarks/test_getBookmarkedURIFor.js b/toolkit/components/places/tests/bookmarks/test_getBookmarkedURIFor.js
new file mode 100644
index 000000000..c43e8e283
--- /dev/null
+++ b/toolkit/components/places/tests/bookmarks/test_getBookmarkedURIFor.js
@@ -0,0 +1,84 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+ /**
+ * Test bookmarksService.getBookmarkedURIFor(aURI);
+ */
+
+var hs = PlacesUtils.history;
+var bs = PlacesUtils.bookmarks;
+
+function run_test() {
+ run_next_test();
+}
+
+add_task(function* test_getBookmarkedURIFor() {
+ let now = Date.now() * 1000;
+ const sourceURI = uri("http://test.mozilla.org/");
+ // Add a visit and a bookmark.
+ yield PlacesTestUtils.addVisits({ uri: sourceURI, visitDate: now });
+ do_check_eq(bs.getBookmarkedURIFor(sourceURI), null);
+
+ let sourceItemId = bs.insertBookmark(bs.unfiledBookmarksFolder,
+ sourceURI,
+ bs.DEFAULT_INDEX,
+ "bookmark");
+ do_check_true(bs.getBookmarkedURIFor(sourceURI).equals(sourceURI));
+
+ // Add a redirected visit.
+ const permaURI = uri("http://perma.mozilla.org/");
+ yield PlacesTestUtils.addVisits({
+ uri: permaURI,
+ transition: TRANSITION_REDIRECT_PERMANENT,
+ visitDate: now++,
+ referrer: sourceURI
+ });
+ do_check_true(bs.getBookmarkedURIFor(sourceURI).equals(sourceURI));
+ do_check_true(bs.getBookmarkedURIFor(permaURI).equals(sourceURI));
+ // Add a bookmark to the destination.
+ let permaItemId = bs.insertBookmark(bs.unfiledBookmarksFolder,
+ permaURI,
+ bs.DEFAULT_INDEX,
+ "bookmark");
+ do_check_true(bs.getBookmarkedURIFor(sourceURI).equals(sourceURI));
+ do_check_true(bs.getBookmarkedURIFor(permaURI).equals(permaURI));
+ // Now remove the bookmark on the destination.
+ bs.removeItem(permaItemId);
+ // We should see the source as bookmark.
+ do_check_true(bs.getBookmarkedURIFor(permaURI).equals(sourceURI));
+
+ // Add another redirected visit.
+ const tempURI = uri("http://perma.mozilla.org/");
+ yield PlacesTestUtils.addVisits({
+ uri: tempURI,
+ transition: TRANSITION_REDIRECT_TEMPORARY,
+ visitDate: now++,
+ referrer: permaURI
+ });
+
+ do_check_true(bs.getBookmarkedURIFor(sourceURI).equals(sourceURI));
+ do_check_true(bs.getBookmarkedURIFor(tempURI).equals(sourceURI));
+ // Add a bookmark to the destination.
+ let tempItemId = bs.insertBookmark(bs.unfiledBookmarksFolder,
+ tempURI,
+ bs.DEFAULT_INDEX,
+ "bookmark");
+ do_check_true(bs.getBookmarkedURIFor(sourceURI).equals(sourceURI));
+ do_check_true(bs.getBookmarkedURIFor(tempURI).equals(tempURI));
+
+ // Now remove the bookmark on the destination.
+ bs.removeItem(tempItemId);
+ // We should see the source as bookmark.
+ do_check_true(bs.getBookmarkedURIFor(tempURI).equals(sourceURI));
+ // Remove the source bookmark as well.
+ bs.removeItem(sourceItemId);
+ do_check_eq(bs.getBookmarkedURIFor(tempURI), null);
+
+ // Try to pass in a never seen URI, should return null and a new entry should
+ // not be added to the database.
+ do_check_eq(bs.getBookmarkedURIFor(uri("http://does.not.exist/")), null);
+ do_check_false(page_in_database("http://does.not.exist/"));
+});
diff --git a/toolkit/components/places/tests/bookmarks/test_keywords.js b/toolkit/components/places/tests/bookmarks/test_keywords.js
new file mode 100644
index 000000000..149d6d0b0
--- /dev/null
+++ b/toolkit/components/places/tests/bookmarks/test_keywords.js
@@ -0,0 +1,310 @@
+const URI1 = NetUtil.newURI("http://test1.mozilla.org/");
+const URI2 = NetUtil.newURI("http://test2.mozilla.org/");
+const URI3 = NetUtil.newURI("http://test3.mozilla.org/");
+
+function check_keyword(aURI, aKeyword) {
+ if (aKeyword)
+ aKeyword = aKeyword.toLowerCase();
+
+ for (let bm of PlacesUtils.getBookmarksForURI(aURI)) {
+ let keyword = PlacesUtils.bookmarks.getKeywordForBookmark(bm);
+ if (keyword && !aKeyword) {
+ throw (`${aURI.spec} should not have a keyword`);
+ } else if (aKeyword && keyword == aKeyword) {
+ Assert.equal(keyword, aKeyword);
+ }
+ }
+
+ if (aKeyword) {
+ let uri = PlacesUtils.bookmarks.getURIForKeyword(aKeyword);
+ Assert.equal(uri.spec, aURI.spec);
+ // Check case insensitivity.
+ uri = PlacesUtils.bookmarks.getURIForKeyword(aKeyword.toUpperCase());
+ Assert.equal(uri.spec, aURI.spec);
+ }
+}
+
+function* check_orphans() {
+ let db = yield PlacesUtils.promiseDBConnection();
+ let rows = yield 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 expectNotifications() {
+ 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(id, prop, isAnno, val, lastMod, itemType, parentId, guid, parentGuid, oldVal) {
+ if (prop != "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: name, arguments: args });
+ }
+ }
+
+ return target[name];
+ }
+ });
+ PlacesUtils.bookmarks.addObserver(observer, false);
+ return observer;
+}
+
+add_task(function test_invalid_input() {
+ Assert.throws(() => PlacesUtils.bookmarks.getURIForKeyword(null),
+ /NS_ERROR_ILLEGAL_VALUE/);
+ Assert.throws(() => PlacesUtils.bookmarks.getURIForKeyword(""),
+ /NS_ERROR_ILLEGAL_VALUE/);
+ Assert.throws(() => PlacesUtils.bookmarks.getKeywordForBookmark(null),
+ /NS_ERROR_ILLEGAL_VALUE/);
+ Assert.throws(() => PlacesUtils.bookmarks.getKeywordForBookmark(0),
+ /NS_ERROR_ILLEGAL_VALUE/);
+ Assert.throws(() => PlacesUtils.bookmarks.setKeywordForBookmark(null, "k"),
+ /NS_ERROR_ILLEGAL_VALUE/);
+ Assert.throws(() => PlacesUtils.bookmarks.setKeywordForBookmark(0, "k"),
+ /NS_ERROR_ILLEGAL_VALUE/);
+});
+
+add_task(function* test_addBookmarkAndKeyword() {
+ check_keyword(URI1, null);
+ let fc = yield foreign_count(URI1);
+ let observer = expectNotifications();
+
+ let itemId =
+ PlacesUtils.bookmarks.insertBookmark(PlacesUtils.unfiledBookmarksFolderId,
+ URI1,
+ PlacesUtils.bookmarks.DEFAULT_INDEX,
+ "test");
+
+ PlacesUtils.bookmarks.setKeywordForBookmark(itemId, "keyword");
+ let bookmark = yield PlacesUtils.bookmarks.fetch({ url: URI1 });
+ observer.check([ { name: "onItemChanged",
+ arguments: [ itemId, "keyword", false, "keyword",
+ bookmark.lastModified, bookmark.type,
+ (yield PlacesUtils.promiseItemId(bookmark.parentGuid)),
+ bookmark.guid, bookmark.parentGuid, "",
+ Ci.nsINavBookmarksService.SOURCE_DEFAULT ] }
+ ]);
+ yield PlacesTestUtils.promiseAsyncUpdates();
+
+ check_keyword(URI1, "keyword");
+ Assert.equal((yield foreign_count(URI1)), fc + 2); // + 1 bookmark + 1 keyword
+
+ yield PlacesTestUtils.promiseAsyncUpdates();
+ yield check_orphans();
+});
+
+add_task(function* test_addBookmarkToURIHavingKeyword() {
+ // The uri has already a keyword.
+ check_keyword(URI1, "keyword");
+ let fc = yield foreign_count(URI1);
+
+ let itemId =
+ PlacesUtils.bookmarks.insertBookmark(PlacesUtils.unfiledBookmarksFolderId,
+ URI1,
+ PlacesUtils.bookmarks.DEFAULT_INDEX,
+ "test");
+ check_keyword(URI1, "keyword");
+ Assert.equal((yield foreign_count(URI1)), fc + 1); // + 1 bookmark
+
+ PlacesUtils.bookmarks.removeItem(itemId);
+ yield PlacesTestUtils.promiseAsyncUpdates();
+ check_orphans();
+});
+
+add_task(function* test_sameKeywordDifferentURI() {
+ let fc1 = yield foreign_count(URI1);
+ let fc2 = yield foreign_count(URI2);
+ let observer = expectNotifications();
+
+ let itemId =
+ PlacesUtils.bookmarks.insertBookmark(PlacesUtils.unfiledBookmarksFolderId,
+ URI2,
+ PlacesUtils.bookmarks.DEFAULT_INDEX,
+ "test2");
+ check_keyword(URI1, "keyword");
+ check_keyword(URI2, null);
+
+ PlacesUtils.bookmarks.setKeywordForBookmark(itemId, "kEyWoRd");
+
+ let bookmark1 = yield PlacesUtils.bookmarks.fetch({ url: URI1 });
+ let bookmark2 = yield PlacesUtils.bookmarks.fetch({ url: URI2 });
+ observer.check([ { name: "onItemChanged",
+ arguments: [ (yield PlacesUtils.promiseItemId(bookmark1.guid)),
+ "keyword", false, "",
+ bookmark1.lastModified, bookmark1.type,
+ (yield PlacesUtils.promiseItemId(bookmark1.parentGuid)),
+ bookmark1.guid, bookmark1.parentGuid, "",
+ Ci.nsINavBookmarksService.SOURCE_DEFAULT ] },
+ { name: "onItemChanged",
+ arguments: [ itemId, "keyword", false, "keyword",
+ bookmark2.lastModified, bookmark2.type,
+ (yield PlacesUtils.promiseItemId(bookmark2.parentGuid)),
+ bookmark2.guid, bookmark2.parentGuid, "",
+ Ci.nsINavBookmarksService.SOURCE_DEFAULT ] }
+ ]);
+ yield PlacesTestUtils.promiseAsyncUpdates();
+
+ // The keyword should have been "moved" to the new URI.
+ check_keyword(URI1, null);
+ Assert.equal((yield foreign_count(URI1)), fc1 - 1); // - 1 keyword
+ check_keyword(URI2, "keyword");
+ Assert.equal((yield foreign_count(URI2)), fc2 + 2); // + 1 bookmark + 1 keyword
+
+ yield PlacesTestUtils.promiseAsyncUpdates();
+ check_orphans();
+});
+
+add_task(function* test_sameURIDifferentKeyword() {
+ let fc = yield foreign_count(URI2);
+ let observer = expectNotifications();
+
+ let itemId =
+ PlacesUtils.bookmarks.insertBookmark(PlacesUtils.unfiledBookmarksFolderId,
+ URI2,
+ PlacesUtils.bookmarks.DEFAULT_INDEX,
+ "test2");
+ check_keyword(URI2, "keyword");
+
+ PlacesUtils.bookmarks.setKeywordForBookmark(itemId, "keyword2");
+
+ let bookmarks = [];
+ yield PlacesUtils.bookmarks.fetch({ url: URI2 }, bookmark => bookmarks.push(bookmark));
+ observer.check([ { name: "onItemChanged",
+ arguments: [ (yield PlacesUtils.promiseItemId(bookmarks[0].guid)),
+ "keyword", false, "keyword2",
+ bookmarks[0].lastModified, bookmarks[0].type,
+ (yield PlacesUtils.promiseItemId(bookmarks[0].parentGuid)),
+ bookmarks[0].guid, bookmarks[0].parentGuid, "",
+ Ci.nsINavBookmarksService.SOURCE_DEFAULT ] },
+ { name: "onItemChanged",
+ arguments: [ (yield PlacesUtils.promiseItemId(bookmarks[1].guid)),
+ "keyword", false, "keyword2",
+ bookmarks[1].lastModified, bookmarks[1].type,
+ (yield PlacesUtils.promiseItemId(bookmarks[1].parentGuid)),
+ bookmarks[1].guid, bookmarks[1].parentGuid, "",
+ Ci.nsINavBookmarksService.SOURCE_DEFAULT ] }
+ ]);
+ yield PlacesTestUtils.promiseAsyncUpdates();
+
+ check_keyword(URI2, "keyword2");
+ Assert.equal((yield foreign_count(URI2)), fc + 2); // + 1 bookmark + 1 keyword
+
+ yield PlacesTestUtils.promiseAsyncUpdates();
+ check_orphans();
+});
+
+add_task(function* test_removeBookmarkWithKeyword() {
+ let fc = yield foreign_count(URI2);
+ let itemId =
+ PlacesUtils.bookmarks.insertBookmark(PlacesUtils.unfiledBookmarksFolderId,
+ URI2,
+ PlacesUtils.bookmarks.DEFAULT_INDEX,
+ "test");
+
+ // The keyword should not be removed, since there are other bookmarks yet.
+ PlacesUtils.bookmarks.removeItem(itemId);
+
+ check_keyword(URI2, "keyword2");
+ Assert.equal((yield foreign_count(URI2)), fc); // + 1 bookmark - 1 bookmark
+
+ yield PlacesTestUtils.promiseAsyncUpdates();
+ check_orphans();
+});
+
+add_task(function* test_unsetKeyword() {
+ let fc = yield foreign_count(URI2);
+ let observer = expectNotifications();
+
+ let itemId =
+ PlacesUtils.bookmarks.insertBookmark(PlacesUtils.unfiledBookmarksFolderId,
+ URI2,
+ PlacesUtils.bookmarks.DEFAULT_INDEX,
+ "test");
+
+ // The keyword should be removed from any bookmark.
+ PlacesUtils.bookmarks.setKeywordForBookmark(itemId, null);
+
+ let bookmarks = [];
+ yield PlacesUtils.bookmarks.fetch({ url: URI2 }, bookmark => bookmarks.push(bookmark));
+ do_print(bookmarks.length);
+ observer.check([ { name: "onItemChanged",
+ arguments: [ (yield PlacesUtils.promiseItemId(bookmarks[0].guid)),
+ "keyword", false, "",
+ bookmarks[0].lastModified, bookmarks[0].type,
+ (yield PlacesUtils.promiseItemId(bookmarks[0].parentGuid)),
+ bookmarks[0].guid, bookmarks[0].parentGuid, "",
+ Ci.nsINavBookmarksService.SOURCE_DEFAULT ] },
+ { name: "onItemChanged",
+ arguments: [ (yield PlacesUtils.promiseItemId(bookmarks[1].guid)),
+ "keyword", false, "",
+ bookmarks[1].lastModified, bookmarks[1].type,
+ (yield PlacesUtils.promiseItemId(bookmarks[1].parentGuid)),
+ bookmarks[1].guid, bookmarks[1].parentGuid, "",
+ Ci.nsINavBookmarksService.SOURCE_DEFAULT ] },
+ { name: "onItemChanged",
+ arguments: [ (yield PlacesUtils.promiseItemId(bookmarks[2].guid)),
+ "keyword", false, "",
+ bookmarks[2].lastModified, bookmarks[2].type,
+ (yield PlacesUtils.promiseItemId(bookmarks[2].parentGuid)),
+ bookmarks[2].guid, bookmarks[2].parentGuid, "",
+ Ci.nsINavBookmarksService.SOURCE_DEFAULT ] }
+ ]);
+
+ check_keyword(URI1, null);
+ check_keyword(URI2, null);
+ Assert.equal((yield foreign_count(URI2)), fc - 1); // + 1 bookmark - 2 keyword
+
+ yield PlacesTestUtils.promiseAsyncUpdates();
+ check_orphans();
+});
+
+add_task(function* test_addRemoveBookmark() {
+ let observer = expectNotifications();
+
+ let itemId =
+ PlacesUtils.bookmarks.insertBookmark(PlacesUtils.unfiledBookmarksFolderId,
+ URI3,
+ PlacesUtils.bookmarks.DEFAULT_INDEX,
+ "test3");
+
+ PlacesUtils.bookmarks.setKeywordForBookmark(itemId, "keyword");
+ let bookmark = yield PlacesUtils.bookmarks.fetch({ url: URI3 });
+ let parentId = yield PlacesUtils.promiseItemId(bookmark.parentGuid);
+ PlacesUtils.bookmarks.removeItem(itemId);
+
+ observer.check([ { name: "onItemChanged",
+ arguments: [ itemId,
+ "keyword", false, "keyword",
+ bookmark.lastModified, bookmark.type,
+ parentId,
+ bookmark.guid, bookmark.parentGuid, "",
+ Ci.nsINavBookmarksService.SOURCE_DEFAULT ] }
+ ]);
+
+ check_keyword(URI3, null);
+ // Don't check the foreign count since the process is async.
+ // The new test_keywords.js in unit is checking this though.
+
+ yield PlacesTestUtils.promiseAsyncUpdates();
+ check_orphans();
+});
+
+function run_test() {
+ run_next_test();
+}
diff --git a/toolkit/components/places/tests/bookmarks/test_nsINavBookmarkObserver.js b/toolkit/components/places/tests/bookmarks/test_nsINavBookmarkObserver.js
new file mode 100644
index 000000000..06f45b18e
--- /dev/null
+++ b/toolkit/components/places/tests/bookmarks/test_nsINavBookmarkObserver.js
@@ -0,0 +1,640 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Tests that each nsINavBookmarksObserver method gets the correct input.
+Cu.import("resource://gre/modules/PromiseUtils.jsm");
+
+const GUID_RE = /^[a-zA-Z0-9\-_]{12}$/;
+
+var gBookmarksObserver = {
+ expected: [],
+ setup(expected) {
+ this.expected = expected;
+ this.deferred = PromiseUtils.defer();
+ return this.deferred.promise;
+ },
+ validate: function (aMethodName, aArguments) {
+ do_check_eq(this.expected[0].name, aMethodName);
+
+ let args = this.expected.shift().args;
+ do_check_eq(aArguments.length, args.length);
+ for (let i = 0; i < aArguments.length; i++) {
+ do_check_true(args[i].check(aArguments[i]), aMethodName + "(args[" + i + "]: " + args[i].name + ")");
+ }
+
+ if (this.expected.length === 0) {
+ this.deferred.resolve();
+ }
+ },
+
+ // nsINavBookmarkObserver
+ onBeginUpdateBatch() {
+ return this.validate("onBeginUpdateBatch", arguments);
+ },
+ onEndUpdateBatch() {
+ return this.validate("onEndUpdateBatch", arguments);
+ },
+ onItemAdded() {
+ return this.validate("onItemAdded", arguments);
+ },
+ onItemRemoved() {
+ return this.validate("onItemRemoved", arguments);
+ },
+ onItemChanged() {
+ return this.validate("onItemChanged", arguments);
+ },
+ onItemVisited() {
+ return this.validate("onItemVisited", arguments);
+ },
+ onItemMoved() {
+ return this.validate("onItemMoved", arguments);
+ },
+
+ // nsISupports
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsINavBookmarkObserver]),
+};
+
+var gBookmarkSkipObserver = {
+ skipTags: true,
+ skipDescendantsOnItemRemoval: true,
+
+ expected: null,
+ setup(expected) {
+ this.expected = expected;
+ this.deferred = PromiseUtils.defer();
+ return this.deferred.promise;
+ },
+ validate: function (aMethodName) {
+ do_check_eq(this.expected.shift(), aMethodName);
+ if (this.expected.length === 0) {
+ this.deferred.resolve();
+ }
+ },
+
+ // nsINavBookmarkObserver
+ onBeginUpdateBatch() {
+ return this.validate("onBeginUpdateBatch", arguments);
+ },
+ onEndUpdateBatch() {
+ return this.validate("onEndUpdateBatch", arguments);
+ },
+ onItemAdded() {
+ return this.validate("onItemAdded", arguments);
+ },
+ onItemRemoved() {
+ return this.validate("onItemRemoved", arguments);
+ },
+ onItemChanged() {
+ return this.validate("onItemChanged", arguments);
+ },
+ onItemVisited() {
+ return this.validate("onItemVisited", arguments);
+ },
+ onItemMoved() {
+ return this.validate("onItemMoved", arguments);
+ },
+
+ // nsISupports
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsINavBookmarkObserver]),
+};
+
+
+add_task(function setup() {
+ PlacesUtils.bookmarks.addObserver(gBookmarksObserver, false);
+ PlacesUtils.bookmarks.addObserver(gBookmarkSkipObserver, false);
+});
+
+add_task(function* batch() {
+ let promise = Promise.all([
+ gBookmarksObserver.setup([
+ { name: "onBeginUpdateBatch",
+ args: [] },
+ { name: "onEndUpdateBatch",
+ args: [] },
+ ]),
+ gBookmarkSkipObserver.setup([
+ "onBeginUpdateBatch", "onEndUpdateBatch"
+ ])]);
+ PlacesUtils.bookmarks.runInBatchMode({
+ runBatched: function () {
+ // Nothing.
+ }
+ }, null);
+ yield promise;
+});
+
+add_task(function* onItemAdded_bookmark() {
+ const TITLE = "Bookmark 1";
+ let uri = NetUtil.newURI("http://1.mozilla.org/");
+ let promise = Promise.all([
+ gBookmarkSkipObserver.setup([
+ "onItemAdded"
+ ]),
+ gBookmarksObserver.setup([
+ { name: "onItemAdded",
+ args: [
+ { name: "itemId", check: v => typeof(v) == "number" && v > 0 },
+ { name: "parentId", check: v => v === PlacesUtils.unfiledBookmarksFolderId },
+ { name: "index", check: v => v === 0 },
+ { name: "itemType", check: v => v === PlacesUtils.bookmarks.TYPE_BOOKMARK },
+ { name: "uri", check: v => v instanceof Ci.nsIURI && v.equals(uri) },
+ { name: "title", check: v => v === TITLE },
+ { name: "dateAdded", check: v => typeof(v) == "number" && v > 0 },
+ { name: "guid", check: v => typeof(v) == "string" && GUID_RE.test(v) },
+ { name: "parentGuid", check: v => typeof(v) == "string" && GUID_RE.test(v) },
+ { name: "source", check: v => Object.values(PlacesUtils.bookmarks.SOURCES).includes(v) },
+ ] },
+ ])]);
+ PlacesUtils.bookmarks.insertBookmark(PlacesUtils.unfiledBookmarksFolderId,
+ uri, PlacesUtils.bookmarks.DEFAULT_INDEX,
+ TITLE);
+ yield promise;
+});
+
+add_task(function* onItemAdded_separator() {
+ let promise = Promise.all([
+ gBookmarkSkipObserver.setup([
+ "onItemAdded"
+ ]),
+ gBookmarksObserver.setup([
+ { name: "onItemAdded",
+ args: [
+ { name: "itemId", check: v => typeof(v) == "number" && v > 0 },
+ { name: "parentId", check: v => v === PlacesUtils.unfiledBookmarksFolderId },
+ { name: "index", check: v => v === 1 },
+ { name: "itemType", check: v => v === PlacesUtils.bookmarks.TYPE_SEPARATOR },
+ { name: "uri", check: v => v === null },
+ { name: "title", check: v => v === null },
+ { name: "dateAdded", check: v => typeof(v) == "number" && v > 0 },
+ { name: "guid", check: v => typeof(v) == "string" && GUID_RE.test(v) },
+ { name: "parentGuid", check: v => typeof(v) == "string" && GUID_RE.test(v) },
+ { name: "source", check: v => Object.values(PlacesUtils.bookmarks.SOURCES).includes(v) },
+ ] },
+ ])]);
+ PlacesUtils.bookmarks.insertSeparator(PlacesUtils.unfiledBookmarksFolderId,
+ PlacesUtils.bookmarks.DEFAULT_INDEX);
+ yield promise;
+});
+
+add_task(function* onItemAdded_folder() {
+ const TITLE = "Folder 1";
+ let promise = Promise.all([
+ gBookmarkSkipObserver.setup([
+ "onItemAdded"
+ ]),
+ gBookmarksObserver.setup([
+ { name: "onItemAdded",
+ args: [
+ { name: "itemId", check: v => typeof(v) == "number" && v > 0 },
+ { name: "parentId", check: v => v === PlacesUtils.unfiledBookmarksFolderId },
+ { name: "index", check: v => v === 2 },
+ { name: "itemType", check: v => v === PlacesUtils.bookmarks.TYPE_FOLDER },
+ { name: "uri", check: v => v === null },
+ { name: "title", check: v => v === TITLE },
+ { name: "dateAdded", check: v => typeof(v) == "number" && v > 0 },
+ { name: "guid", check: v => typeof(v) == "string" && GUID_RE.test(v) },
+ { name: "parentGuid", check: v => typeof(v) == "string" && GUID_RE.test(v) },
+ { name: "source", check: v => Object.values(PlacesUtils.bookmarks.SOURCES).includes(v) },
+ ] },
+ ])]);
+ PlacesUtils.bookmarks.createFolder(PlacesUtils.unfiledBookmarksFolderId,
+ TITLE,
+ PlacesUtils.bookmarks.DEFAULT_INDEX);
+ yield promise;
+});
+
+add_task(function* onItemChanged_title_bookmark() {
+ let id = PlacesUtils.bookmarks.getIdForItemAt(PlacesUtils.unfiledBookmarksFolderId, 0);
+ const TITLE = "New title";
+ let promise = Promise.all([
+ gBookmarkSkipObserver.setup([
+ "onItemChanged"
+ ]),
+ gBookmarksObserver.setup([
+ { name: "onItemChanged", // This is an unfortunate effect of bug 653910.
+ args: [
+ { name: "itemId", check: v => typeof(v) == "number" && v > 0 },
+ { name: "property", check: v => v === "title" },
+ { name: "isAnno", check: v => v === false },
+ { name: "newValue", check: v => v === TITLE },
+ { name: "lastModified", check: v => typeof(v) == "number" && v > 0 },
+ { name: "itemType", check: v => v === PlacesUtils.bookmarks.TYPE_BOOKMARK },
+ { name: "parentId", check: v => v === PlacesUtils.unfiledBookmarksFolderId },
+ { name: "guid", check: v => typeof(v) == "string" && GUID_RE.test(v) },
+ { name: "parentGuid", check: v => typeof(v) == "string" && GUID_RE.test(v) },
+ { name: "oldValue", check: v => typeof(v) == "string" },
+ { name: "source", check: v => Object.values(PlacesUtils.bookmarks.SOURCES).includes(v) },
+ ] },
+ ])]);
+ PlacesUtils.bookmarks.setItemTitle(id, TITLE);
+ yield promise;
+});
+
+add_task(function* onItemChanged_tags_bookmark() {
+ let id = PlacesUtils.bookmarks.getIdForItemAt(PlacesUtils.unfiledBookmarksFolderId, 0);
+ let uri = PlacesUtils.bookmarks.getBookmarkURI(id);
+ const TAG = "tag";
+ let promise = Promise.all([
+ gBookmarkSkipObserver.setup([
+ "onItemChanged", "onItemChanged"
+ ]),
+ gBookmarksObserver.setup([
+ { name: "onItemAdded", // This is the tag folder.
+ args: [
+ { name: "itemId", check: v => typeof(v) == "number" && v > 0 },
+ { name: "parentId", check: v => v === PlacesUtils.tagsFolderId },
+ { name: "index", check: v => v === 0 },
+ { name: "itemType", check: v => v === PlacesUtils.bookmarks.TYPE_FOLDER },
+ { name: "uri", check: v => v === null },
+ { name: "title", check: v => v === TAG },
+ { name: "dateAdded", check: v => typeof(v) == "number" && v > 0 },
+ { name: "guid", check: v => typeof(v) == "string" && GUID_RE.test(v) },
+ { name: "parentGuid", check: v => typeof(v) == "string" && GUID_RE.test(v) },
+ { name: "source", check: v => Object.values(PlacesUtils.bookmarks.SOURCES).includes(v) },
+ ] },
+ { name: "onItemAdded", // This is the tag.
+ args: [
+ { name: "itemId", check: v => typeof(v) == "number" && v > 0 },
+ { name: "parentId", check: v => typeof(v) == "number" && v > 0 },
+ { name: "index", check: v => v === 0 },
+ { name: "itemType", check: v => v === PlacesUtils.bookmarks.TYPE_BOOKMARK },
+ { name: "uri", check: v => v instanceof Ci.nsIURI && v.equals(uri) },
+ { name: "title", check: v => v === null },
+ { name: "dateAdded", check: v => typeof(v) == "number" && v > 0 },
+ { name: "guid", check: v => typeof(v) == "string" && GUID_RE.test(v) },
+ { name: "parentGuid", check: v => typeof(v) == "string" && GUID_RE.test(v) },
+ { name: "source", check: v => Object.values(PlacesUtils.bookmarks.SOURCES).includes(v) },
+ ] },
+ { name: "onItemChanged",
+ args: [
+ { name: "itemId", check: v => typeof(v) == "number" && v > 0 },
+ { name: "property", check: v => v === "tags" },
+ { name: "isAnno", check: v => v === false },
+ { name: "newValue", check: v => v === "" },
+ { name: "lastModified", check: v => typeof(v) == "number" && v > 0 },
+ { name: "itemType", check: v => v === PlacesUtils.bookmarks.TYPE_BOOKMARK },
+ { name: "parentId", check: v => v === PlacesUtils.unfiledBookmarksFolderId },
+ { name: "guid", check: v => typeof(v) == "string" && GUID_RE.test(v) },
+ { name: "parentGuid", check: v => typeof(v) == "string" && GUID_RE.test(v) },
+ { name: "oldValue", check: v => typeof(v) == "string" },
+ { name: "source", check: v => Object.values(PlacesUtils.bookmarks.SOURCES).includes(v) },
+ ] },
+ { name: "onItemRemoved", // This is the tag.
+ args: [
+ { name: "itemId", check: v => typeof(v) == "number" && v > 0 },
+ { name: "parentId", check: v => typeof(v) == "number" && v > 0 },
+ { name: "index", check: v => v === 0 },
+ { name: "itemType", check: v => v === PlacesUtils.bookmarks.TYPE_BOOKMARK },
+ { name: "uri", check: v => v instanceof Ci.nsIURI && v.equals(uri) },
+ { name: "guid", check: v => typeof(v) == "string" && GUID_RE.test(v) },
+ { name: "parentGuid", check: v => typeof(v) == "string" && GUID_RE.test(v) },
+ { name: "source", check: v => Object.values(PlacesUtils.bookmarks.SOURCES).includes(v) },
+ ] },
+ { name: "onItemRemoved", // This is the tag folder.
+ args: [
+ { name: "itemId", check: v => typeof(v) == "number" && v > 0 },
+ { name: "parentId", check: v => v === PlacesUtils.tagsFolderId },
+ { name: "index", check: v => v === 0 },
+ { name: "itemType", check: v => v === PlacesUtils.bookmarks.TYPE_FOLDER },
+ { name: "uri", check: v => v === null },
+ { name: "guid", check: v => typeof(v) == "string" && GUID_RE.test(v) },
+ { name: "parentGuid", check: v => typeof(v) == "string" && GUID_RE.test(v) },
+ { name: "source", check: v => Object.values(PlacesUtils.bookmarks.SOURCES).includes(v) },
+ ] },
+ { name: "onItemChanged",
+ args: [
+ { name: "itemId", check: v => typeof(v) == "number" && v > 0 },
+ { name: "property", check: v => v === "tags" },
+ { name: "isAnno", check: v => v === false },
+ { name: "newValue", check: v => v === "" },
+ { name: "lastModified", check: v => typeof(v) == "number" && v > 0 },
+ { name: "itemType", check: v => v === PlacesUtils.bookmarks.TYPE_BOOKMARK },
+ { name: "parentId", check: v => v === PlacesUtils.unfiledBookmarksFolderId },
+ { name: "guid", check: v => typeof(v) == "string" && GUID_RE.test(v) },
+ { name: "parentGuid", check: v => typeof(v) == "string" && GUID_RE.test(v) },
+ { name: "oldValue", check: v => typeof(v) == "string" },
+ { name: "source", check: v => Object.values(PlacesUtils.bookmarks.SOURCES).includes(v) },
+ ] },
+ ])]);
+ PlacesUtils.tagging.tagURI(uri, [TAG]);
+ PlacesUtils.tagging.untagURI(uri, [TAG]);
+ yield promise;
+});
+
+add_task(function* onItemMoved_bookmark() {
+ let id = PlacesUtils.bookmarks.getIdForItemAt(PlacesUtils.unfiledBookmarksFolderId, 0);
+ let promise = Promise.all([
+ gBookmarkSkipObserver.setup([
+ "onItemMoved", "onItemMoved"
+ ]),
+ gBookmarksObserver.setup([
+ { name: "onItemMoved",
+ args: [
+ { name: "itemId", check: v => typeof(v) == "number" && v > 0 },
+ { name: "oldParentId", check: v => v === PlacesUtils.unfiledBookmarksFolderId },
+ { name: "oldIndex", check: v => v === 0 },
+ { name: "newParentId", check: v => v === PlacesUtils.toolbarFolderId },
+ { name: "newIndex", check: v => v === 0 },
+ { name: "itemType", check: v => v === PlacesUtils.bookmarks.TYPE_BOOKMARK },
+ { name: "guid", check: v => typeof(v) == "string" && GUID_RE.test(v) },
+ { name: "oldParentGuid", check: v => typeof(v) == "string" && GUID_RE.test(v) },
+ { name: "newParentGuid", check: v => typeof(v) == "string" && GUID_RE.test(v) },
+ { name: "source", check: v => Object.values(PlacesUtils.bookmarks.SOURCES).includes(v) },
+ ] },
+ { name: "onItemMoved",
+ args: [
+ { name: "itemId", check: v => typeof(v) == "number" && v > 0 },
+ { name: "oldParentId", check: v => v === PlacesUtils.toolbarFolderId },
+ { name: "oldIndex", check: v => v === 0 },
+ { name: "newParentId", check: v => v === PlacesUtils.unfiledBookmarksFolderId },
+ { name: "newIndex", check: v => v === 0 },
+ { name: "itemType", check: v => v === PlacesUtils.bookmarks.TYPE_BOOKMARK },
+ { name: "guid", check: v => typeof(v) == "string" && GUID_RE.test(v) },
+ { name: "oldParentGuid", check: v => typeof(v) == "string" && GUID_RE.test(v) },
+ { name: "newParentGuid", check: v => typeof(v) == "string" && GUID_RE.test(v) },
+ { name: "source", check: v => Object.values(PlacesUtils.bookmarks.SOURCES).includes(v) },
+ ] },
+ ])]);
+ PlacesUtils.bookmarks.moveItem(id, PlacesUtils.toolbarFolderId, 0);
+ PlacesUtils.bookmarks.moveItem(id, PlacesUtils.unfiledBookmarksFolderId, 0);
+ yield promise;
+});
+
+add_task(function* onItemMoved_bookmark() {
+ let id = PlacesUtils.bookmarks.getIdForItemAt(PlacesUtils.unfiledBookmarksFolderId, 0);
+ let uri = PlacesUtils.bookmarks.getBookmarkURI(id);
+ let promise = Promise.all([
+ gBookmarkSkipObserver.setup([
+ "onItemVisited"
+ ]),
+ gBookmarksObserver.setup([
+ { name: "onItemVisited",
+ args: [
+ { name: "itemId", check: v => typeof(v) == "number" && v > 0 },
+ { name: "visitId", check: v => typeof(v) == "number" && v > 0 },
+ { name: "time", check: v => typeof(v) == "number" && v > 0 },
+ { name: "transitionType", check: v => v === PlacesUtils.history.TRANSITION_TYPED },
+ { name: "uri", check: v => v instanceof Ci.nsIURI && v.equals(uri) },
+ { name: "parentId", check: v => v === PlacesUtils.unfiledBookmarksFolderId },
+ { name: "guid", check: v => typeof(v) == "string" && GUID_RE.test(v) },
+ { name: "parentGuid", check: v => typeof(v) == "string" && GUID_RE.test(v) },
+ ] },
+ ])]);
+ PlacesTestUtils.addVisits({ uri: uri, transition: TRANSITION_TYPED });
+ yield promise;
+});
+
+add_task(function* onItemRemoved_bookmark() {
+ let id = PlacesUtils.bookmarks.getIdForItemAt(PlacesUtils.unfiledBookmarksFolderId, 0);
+ let uri = PlacesUtils.bookmarks.getBookmarkURI(id);
+ let promise = Promise.all([
+ gBookmarkSkipObserver.setup([
+ "onItemChanged", "onItemRemoved"
+ ]),
+ gBookmarksObserver.setup([
+ { name: "onItemChanged", // This is an unfortunate effect of bug 653910.
+ args: [
+ { name: "itemId", check: v => typeof(v) == "number" && v > 0 },
+ { name: "property", check: v => v === "" },
+ { name: "isAnno", check: v => v === true },
+ { name: "newValue", check: v => v === "" },
+ { name: "lastModified", check: v => typeof(v) == "number" && v > 0 },
+ { name: "itemType", check: v => v === PlacesUtils.bookmarks.TYPE_BOOKMARK },
+ { name: "parentId", check: v => v === PlacesUtils.unfiledBookmarksFolderId },
+ { name: "guid", check: v => typeof(v) == "string" && GUID_RE.test(v) },
+ { name: "parentGuid", check: v => typeof(v) == "string" && GUID_RE.test(v) },
+ { name: "oldValue", check: v => typeof(v) == "string" },
+ { name: "source", check: v => Object.values(PlacesUtils.bookmarks.SOURCES).includes(v) },
+ ] },
+ { name: "onItemRemoved",
+ args: [
+ { name: "itemId", check: v => typeof(v) == "number" && v > 0 },
+ { name: "parentId", check: v => v === PlacesUtils.unfiledBookmarksFolderId },
+ { name: "index", check: v => v === 0 },
+ { name: "itemType", check: v => v === PlacesUtils.bookmarks.TYPE_BOOKMARK },
+ { name: "uri", check: v => v instanceof Ci.nsIURI && v.equals(uri) },
+ { name: "guid", check: v => typeof(v) == "string" && GUID_RE.test(v) },
+ { name: "parentGuid", check: v => typeof(v) == "string" && GUID_RE.test(v) },
+ { name: "source", check: v => Object.values(PlacesUtils.bookmarks.SOURCES).includes(v) },
+ ] },
+ ])]);
+ PlacesUtils.bookmarks.removeItem(id);
+ yield promise;
+});
+
+add_task(function* onItemRemoved_separator() {
+ let id = PlacesUtils.bookmarks.getIdForItemAt(PlacesUtils.unfiledBookmarksFolderId, 0);
+ let promise = Promise.all([
+ gBookmarkSkipObserver.setup([
+ "onItemChanged", "onItemRemoved"
+ ]),
+ gBookmarksObserver.setup([
+ { name: "onItemChanged", // This is an unfortunate effect of bug 653910.
+ args: [
+ { name: "itemId", check: v => typeof(v) == "number" && v > 0 },
+ { name: "property", check: v => v === "" },
+ { name: "isAnno", check: v => v === true },
+ { name: "newValue", check: v => v === "" },
+ { name: "lastModified", check: v => typeof(v) == "number" && v > 0 },
+ { name: "itemType", check: v => v === PlacesUtils.bookmarks.TYPE_SEPARATOR },
+ { name: "parentId", check: v => typeof(v) == "number" && v > 0 },
+ { name: "guid", check: v => typeof(v) == "string" && GUID_RE.test(v) },
+ { name: "parentGuid", check: v => typeof(v) == "string" && GUID_RE.test(v) },
+ { name: "oldValue", check: v => typeof(v) == "string" },
+ { name: "source", check: v => Object.values(PlacesUtils.bookmarks.SOURCES).includes(v) },
+ ] },
+ { name: "onItemRemoved",
+ args: [
+ { name: "itemId", check: v => typeof(v) == "number" && v > 0 },
+ { name: "parentId", check: v => typeof(v) == "number" && v > 0 },
+ { name: "index", check: v => v === 0 },
+ { name: "itemType", check: v => v === PlacesUtils.bookmarks.TYPE_SEPARATOR },
+ { name: "uri", check: v => v === null },
+ { name: "guid", check: v => typeof(v) == "string" && GUID_RE.test(v) },
+ { name: "parentGuid", check: v => typeof(v) == "string" && GUID_RE.test(v) },
+ { name: "source", check: v => Object.values(PlacesUtils.bookmarks.SOURCES).includes(v) },
+ ] },
+ ])]);
+ PlacesUtils.bookmarks.removeItem(id);
+ yield promise;
+});
+
+add_task(function* onItemRemoved_folder() {
+ let id = PlacesUtils.bookmarks.getIdForItemAt(PlacesUtils.unfiledBookmarksFolderId, 0);
+ let promise = Promise.all([
+ gBookmarkSkipObserver.setup([
+ "onItemChanged", "onItemRemoved"
+ ]),
+ gBookmarksObserver.setup([
+ { name: "onItemChanged", // This is an unfortunate effect of bug 653910.
+ args: [
+ { name: "itemId", check: v => typeof(v) == "number" && v > 0 },
+ { name: "property", check: v => v === "" },
+ { name: "isAnno", check: v => v === true },
+ { name: "newValue", check: v => v === "" },
+ { name: "lastModified", check: v => typeof(v) == "number" && v > 0 },
+ { name: "itemType", check: v => v === PlacesUtils.bookmarks.TYPE_FOLDER },
+ { name: "parentId", check: v => typeof(v) == "number" && v > 0 },
+ { name: "guid", check: v => typeof(v) == "string" && GUID_RE.test(v) },
+ { name: "parentGuid", check: v => typeof(v) == "string" && GUID_RE.test(v) },
+ { name: "oldValue", check: v => typeof(v) == "string" },
+ { name: "source", check: v => Object.values(PlacesUtils.bookmarks.SOURCES).includes(v) },
+ ] },
+ { name: "onItemRemoved",
+ args: [
+ { name: "itemId", check: v => typeof(v) == "number" && v > 0 },
+ { name: "parentId", check: v => typeof(v) == "number" && v > 0 },
+ { name: "index", check: v => v === 0 },
+ { name: "itemType", check: v => v === PlacesUtils.bookmarks.TYPE_FOLDER },
+ { name: "uri", check: v => v === null },
+ { name: "guid", check: v => typeof(v) == "string" && GUID_RE.test(v) },
+ { name: "parentGuid", check: v => typeof(v) == "string" && GUID_RE.test(v) },
+ { name: "source", check: v => Object.values(PlacesUtils.bookmarks.SOURCES).includes(v) },
+ ] },
+ ])]);
+ PlacesUtils.bookmarks.removeItem(id);
+ yield promise;
+});
+
+add_task(function* onItemRemoved_folder_recursive() {
+ const TITLE = "Folder 3";
+ const BMTITLE = "Bookmark 1";
+ let uri = NetUtil.newURI("http://1.mozilla.org/");
+ let promise = Promise.all([
+ gBookmarkSkipObserver.setup([
+ "onItemAdded", "onItemAdded", "onItemAdded", "onItemAdded",
+ "onItemChanged", "onItemRemoved"
+ ]),
+ gBookmarksObserver.setup([
+ { name: "onItemAdded",
+ args: [
+ { name: "itemId", check: v => typeof(v) == "number" && v > 0 },
+ { name: "parentId", check: v => v === PlacesUtils.unfiledBookmarksFolderId },
+ { name: "index", check: v => v === 0 },
+ { name: "itemType", check: v => v === PlacesUtils.bookmarks.TYPE_FOLDER },
+ { name: "uri", check: v => v === null },
+ { name: "title", check: v => v === TITLE },
+ { name: "dateAdded", check: v => typeof(v) == "number" && v > 0 },
+ { name: "guid", check: v => typeof(v) == "string" && GUID_RE.test(v) },
+ { name: "parentGuid", check: v => typeof(v) == "string" && GUID_RE.test(v) },
+ { name: "source", check: v => Object.values(PlacesUtils.bookmarks.SOURCES).includes(v) },
+ ] },
+ { name: "onItemAdded",
+ args: [
+ { name: "itemId", check: v => typeof(v) == "number" && v > 0 },
+ { name: "parentId", check: v => v === PlacesUtils.bookmarks.getIdForItemAt(PlacesUtils.unfiledBookmarksFolderId, 0) },
+ { name: "index", check: v => v === 0 },
+ { name: "itemType", check: v => v === PlacesUtils.bookmarks.TYPE_BOOKMARK },
+ { name: "uri", check: v => v instanceof Ci.nsIURI && v.equals(uri) },
+ { name: "title", check: v => v === BMTITLE },
+ { name: "dateAdded", check: v => typeof(v) == "number" && v > 0 },
+ { name: "guid", check: v => typeof(v) == "string" && GUID_RE.test(v) },
+ { name: "parentGuid", check: v => typeof(v) == "string" && GUID_RE.test(v) },
+ { name: "source", check: v => Object.values(PlacesUtils.bookmarks.SOURCES).includes(v) },
+ ] },
+ { name: "onItemAdded",
+ args: [
+ { name: "itemId", check: v => typeof(v) == "number" && v > 0 },
+ { name: "parentId", check: v => v === PlacesUtils.bookmarks.getIdForItemAt(PlacesUtils.unfiledBookmarksFolderId, 0) },
+ { name: "index", check: v => v === 1 },
+ { name: "itemType", check: v => v === PlacesUtils.bookmarks.TYPE_FOLDER },
+ { name: "uri", check: v => v === null },
+ { name: "title", check: v => v === TITLE },
+ { name: "dateAdded", check: v => typeof(v) == "number" && v > 0 },
+ { name: "guid", check: v => typeof(v) == "string" && GUID_RE.test(v) },
+ { name: "parentGuid", check: v => typeof(v) == "string" && GUID_RE.test(v) },
+ { name: "source", check: v => Object.values(PlacesUtils.bookmarks.SOURCES).includes(v) },
+ ] },
+ { name: "onItemAdded",
+ args: [
+ { name: "itemId", check: v => typeof(v) == "number" && v > 0 },
+ { name: "parentId", check: v => v === PlacesUtils.bookmarks.getIdForItemAt(PlacesUtils.bookmarks.getIdForItemAt(PlacesUtils.unfiledBookmarksFolderId, 0), 1) },
+ { name: "index", check: v => v === 0 },
+ { name: "itemType", check: v => v === PlacesUtils.bookmarks.TYPE_BOOKMARK },
+ { name: "uri", check: v => v instanceof Ci.nsIURI && v.equals(uri) },
+ { name: "title", check: v => v === BMTITLE },
+ { name: "dateAdded", check: v => typeof(v) == "number" && v > 0 },
+ { name: "guid", check: v => typeof(v) == "string" && GUID_RE.test(v) },
+ { name: "parentGuid", check: v => typeof(v) == "string" && GUID_RE.test(v) },
+ { name: "source", check: v => Object.values(PlacesUtils.bookmarks.SOURCES).includes(v) },
+ ] },
+ { name: "onItemChanged", // This is an unfortunate effect of bug 653910.
+ args: [
+ { name: "itemId", check: v => typeof(v) == "number" && v > 0 },
+ { name: "property", check: v => v === "" },
+ { name: "isAnno", check: v => v === true },
+ { name: "newValue", check: v => v === "" },
+ { name: "lastModified", check: v => typeof(v) == "number" && v > 0 },
+ { name: "itemType", check: v => v === PlacesUtils.bookmarks.TYPE_FOLDER },
+ { name: "parentId", check: v => typeof(v) == "number" && v > 0 },
+ { name: "guid", check: v => typeof(v) == "string" && GUID_RE.test(v) },
+ { name: "parentGuid", check: v => typeof(v) == "string" && GUID_RE.test(v) },
+ { name: "oldValue", check: v => typeof(v) == "string" },
+ { name: "source", check: v => Object.values(PlacesUtils.bookmarks.SOURCES).includes(v) },
+ ] },
+ { name: "onItemRemoved",
+ args: [
+ { name: "itemId", check: v => typeof(v) == "number" && v > 0 },
+ { name: "parentId", check: v => typeof(v) == "number" && v > 0 },
+ { name: "index", check: v => v === 0 },
+ { name: "itemType", check: v => v === PlacesUtils.bookmarks.TYPE_BOOKMARK },
+ { name: "uri", check: v => v instanceof Ci.nsIURI && v.equals(uri) },
+ { name: "guid", check: v => typeof(v) == "string" && GUID_RE.test(v) },
+ { name: "parentGuid", check: v => typeof(v) == "string" && GUID_RE.test(v) },
+ { name: "source", check: v => Object.values(PlacesUtils.bookmarks.SOURCES).includes(v) },
+ ] },
+ { name: "onItemRemoved",
+ args: [
+ { name: "itemId", check: v => typeof(v) == "number" && v > 0 },
+ { name: "parentId", check: v => typeof(v) == "number" && v > 0 },
+ { name: "index", check: v => v === 1 },
+ { name: "itemType", check: v => v === PlacesUtils.bookmarks.TYPE_FOLDER },
+ { name: "uri", check: v => v === null },
+ { name: "guid", check: v => typeof(v) == "string" && GUID_RE.test(v) },
+ { name: "parentGuid", check: v => typeof(v) == "string" && GUID_RE.test(v) },
+ { name: "source", check: v => Object.values(PlacesUtils.bookmarks.SOURCES).includes(v) },
+ ] },
+ { name: "onItemRemoved",
+ args: [
+ { name: "itemId", check: v => typeof(v) == "number" && v > 0 },
+ { name: "parentId", check: v => typeof(v) == "number" && v > 0 },
+ { name: "index", check: v => v === 0 },
+ { name: "itemType", check: v => v === PlacesUtils.bookmarks.TYPE_BOOKMARK },
+ { name: "uri", check: v => v instanceof Ci.nsIURI && v.equals(uri) },
+ { name: "guid", check: v => typeof(v) == "string" && GUID_RE.test(v) },
+ { name: "parentGuid", check: v => typeof(v) == "string" && GUID_RE.test(v) },
+ { name: "source", check: v => Object.values(PlacesUtils.bookmarks.SOURCES).includes(v) },
+ ] },
+ { name: "onItemRemoved",
+ args: [
+ { name: "itemId", check: v => typeof(v) == "number" && v > 0 },
+ { name: "parentId", check: v => typeof(v) == "number" && v > 0 },
+ { name: "index", check: v => v === 0 },
+ { name: "itemType", check: v => v === PlacesUtils.bookmarks.TYPE_FOLDER },
+ { name: "uri", check: v => v === null },
+ { name: "guid", check: v => typeof(v) == "string" && GUID_RE.test(v) },
+ { name: "parentGuid", check: v => typeof(v) == "string" && GUID_RE.test(v) },
+ { name: "source", check: v => Object.values(PlacesUtils.bookmarks.SOURCES).includes(v) },
+ ] },
+ ])]);
+ let folder = PlacesUtils.bookmarks.createFolder(PlacesUtils.unfiledBookmarksFolderId,
+ TITLE,
+ PlacesUtils.bookmarks.DEFAULT_INDEX);
+ PlacesUtils.bookmarks.insertBookmark(folder,
+ uri, PlacesUtils.bookmarks.DEFAULT_INDEX,
+ BMTITLE);
+ let folder2 = PlacesUtils.bookmarks.createFolder(folder, TITLE,
+ PlacesUtils.bookmarks.DEFAULT_INDEX);
+ PlacesUtils.bookmarks.insertBookmark(folder2,
+ uri, PlacesUtils.bookmarks.DEFAULT_INDEX,
+ BMTITLE);
+
+ PlacesUtils.bookmarks.removeItem(folder);
+ yield promise;
+});
+
+add_task(function cleanup()
+{
+ PlacesUtils.bookmarks.removeObserver(gBookmarksObserver);
+ PlacesUtils.bookmarks.removeObserver(gBookmarkSkipObserver);
+});
diff --git a/toolkit/components/places/tests/bookmarks/test_protectRoots.js b/toolkit/components/places/tests/bookmarks/test_protectRoots.js
new file mode 100644
index 000000000..0a59f1653
--- /dev/null
+++ b/toolkit/components/places/tests/bookmarks/test_protectRoots.js
@@ -0,0 +1,37 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+function run_test()
+{
+ const ROOTS = [
+ PlacesUtils.bookmarksMenuFolderId,
+ PlacesUtils.toolbarFolderId,
+ PlacesUtils.unfiledBookmarksFolderId,
+ PlacesUtils.tagsFolderId,
+ PlacesUtils.placesRootId,
+ PlacesUtils.mobileFolderId,
+ ];
+
+ for (let root of ROOTS) {
+ do_check_true(PlacesUtils.isRootItem(root));
+
+ try {
+ PlacesUtils.bookmarks.removeItem(root);
+ do_throw("Trying to remove a root should throw");
+ } catch (ex) {}
+
+ try {
+ PlacesUtils.bookmarks.moveItem(root, PlacesUtils.placesRootId, 0);
+ do_throw("Trying to move a root should throw");
+ } catch (ex) {}
+
+ try {
+ PlacesUtils.bookmarks.removeFolderChildren(root);
+ if (root == PlacesUtils.placesRootId)
+ do_throw("Trying to remove children of the main root should throw");
+ } catch (ex) {
+ if (root != PlacesUtils.placesRootId)
+ do_throw("Trying to remove children of other roots should not throw");
+ }
+ }
+}
diff --git a/toolkit/components/places/tests/bookmarks/test_removeFolderTransaction_reinsert.js b/toolkit/components/places/tests/bookmarks/test_removeFolderTransaction_reinsert.js
new file mode 100644
index 000000000..537974b38
--- /dev/null
+++ b/toolkit/components/places/tests/bookmarks/test_removeFolderTransaction_reinsert.js
@@ -0,0 +1,70 @@
+/**
+ * This test ensures that reinserting a folder within a transaction gives it
+ * a different GUID, and passes the GUID to the observers.
+ */
+
+add_task(function* test_removeFolderTransaction_reinsert() {
+ let folder = yield PlacesUtils.bookmarks.insert({
+ type: PlacesUtils.bookmarks.TYPE_FOLDER,
+ parentGuid: PlacesUtils.bookmarks.menuGuid,
+ title: "Test folder",
+ });
+ let folderId = yield PlacesUtils.promiseItemId(folder.guid);
+ let fx = yield PlacesUtils.bookmarks.insert({
+ parentGuid: folder.guid,
+ title: "Get Firefox!",
+ url: "http://getfirefox.com",
+ });
+ let fxId = yield PlacesUtils.promiseItemId(fx.guid);
+ let tb = yield PlacesUtils.bookmarks.insert({
+ parentGuid: folder.guid,
+ title: "Get Thunderbird!",
+ url: "http://getthunderbird.com",
+ });
+ let tbId = yield PlacesUtils.promiseItemId(tb.guid);
+
+ let notifications = [];
+ function checkNotifications(expected, message) {
+ deepEqual(notifications, expected, message);
+ notifications.length = 0;
+ }
+
+ let observer = {
+ onItemAdded(itemId, parentId, index, type, uri, title, dateAdded, guid,
+ parentGuid) {
+ notifications.push(["onItemAdded", itemId, parentId, guid, parentGuid]);
+ },
+ onItemRemoved(itemId, parentId, index, type, uri, guid, parentGuid) {
+ notifications.push(["onItemRemoved", itemId, parentId, guid, parentGuid]);
+ },
+ };
+ PlacesUtils.bookmarks.addObserver(observer, false);
+ PlacesUtils.registerShutdownFunction(function() {
+ PlacesUtils.bookmarks.removeObserver(observer);
+ });
+
+ let transaction = PlacesUtils.bookmarks.getRemoveFolderTransaction(folderId);
+ deepEqual(notifications, [], "We haven't executed the transaction yet");
+
+ transaction.doTransaction();
+ checkNotifications([
+ ["onItemRemoved", tbId, folderId, tb.guid, folder.guid],
+ ["onItemRemoved", fxId, folderId, fx.guid, folder.guid],
+ ["onItemRemoved", folderId, PlacesUtils.bookmarksMenuFolderId, folder.guid,
+ PlacesUtils.bookmarks.menuGuid],
+ ], "Executing transaction should remove folder and its descendants");
+
+ transaction.undoTransaction();
+ // At this point, the restored folder has the same ID, but a different GUID.
+ let newFolderGuid = yield PlacesUtils.promiseItemGuid(folderId);
+ checkNotifications([
+ ["onItemAdded", folderId, PlacesUtils.bookmarksMenuFolderId, newFolderGuid,
+ PlacesUtils.bookmarks.menuGuid],
+ ], "Undo should reinsert folder with same ID and different GUID");
+
+ transaction.redoTransaction();
+ checkNotifications([
+ ["onItemRemoved", folderId, PlacesUtils.bookmarksMenuFolderId,
+ newFolderGuid, PlacesUtils.bookmarks.menuGuid],
+ ], "Redo should forward new GUID to observer");
+});
diff --git a/toolkit/components/places/tests/bookmarks/test_removeItem.js b/toolkit/components/places/tests/bookmarks/test_removeItem.js
new file mode 100644
index 000000000..ec846b28e
--- /dev/null
+++ b/toolkit/components/places/tests/bookmarks/test_removeItem.js
@@ -0,0 +1,30 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+var tests = [];
+
+
+const DEFAULT_INDEX = PlacesUtils.bookmarks.DEFAULT_INDEX;
+
+function run_test() {
+ // folder to hold this test
+ var folderId =
+ PlacesUtils.bookmarks.createFolder(PlacesUtils.toolbarFolderId,
+ "", DEFAULT_INDEX);
+
+ // add a bookmark to the new folder
+ var bookmarkURI = uri("http://iasdjkf");
+ do_check_false(PlacesUtils.bookmarks.isBookmarked(bookmarkURI));
+ var bookmarkId = PlacesUtils.bookmarks.insertBookmark(folderId, bookmarkURI,
+ DEFAULT_INDEX, "");
+ do_check_eq(PlacesUtils.bookmarks.getItemTitle(bookmarkId), "");
+
+ // remove the folder using removeItem
+ PlacesUtils.bookmarks.removeItem(folderId);
+ do_check_eq(PlacesUtils.bookmarks.getBookmarkIdsForURI(bookmarkURI).length, 0);
+ do_check_false(PlacesUtils.bookmarks.isBookmarked(bookmarkURI));
+ do_check_eq(PlacesUtils.bookmarks.getItemIndex(bookmarkId), -1);
+}
diff --git a/toolkit/components/places/tests/bookmarks/test_savedsearches.js b/toolkit/components/places/tests/bookmarks/test_savedsearches.js
new file mode 100644
index 000000000..eee2c4489
--- /dev/null
+++ b/toolkit/components/places/tests/bookmarks/test_savedsearches.js
@@ -0,0 +1,209 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// get bookmarks root id
+var root = PlacesUtils.bookmarksMenuFolderId;
+
+// a search term that matches a default bookmark
+const searchTerm = "about";
+
+var testRoot;
+
+// main
+function run_test() {
+ // create a folder to hold all the tests
+ // this makes the tests more tolerant of changes to the default bookmarks set
+ // also, name it using the search term, for testing that containers that match don't show up in query results
+ testRoot = PlacesUtils.bookmarks.createFolder(
+ root, searchTerm, PlacesUtils.bookmarks.DEFAULT_INDEX);
+
+ run_next_test();
+}
+
+add_test(function test_savedsearches_bookmarks() {
+ // add a bookmark that matches the search term
+ var bookmarkId = PlacesUtils.bookmarks.insertBookmark(
+ root, uri("http://foo.com"), PlacesUtils.bookmarks.DEFAULT_INDEX,
+ searchTerm);
+
+ // create a saved-search that matches a default bookmark
+ var searchId = PlacesUtils.bookmarks.insertBookmark(
+ testRoot, uri("place:terms=" + searchTerm + "&excludeQueries=1&expandQueries=1&queryType=1"),
+ PlacesUtils.bookmarks.DEFAULT_INDEX, searchTerm);
+
+ // query for the test root, expandQueries=0
+ // the query should show up as a regular bookmark
+ try {
+ let options = PlacesUtils.history.getNewQueryOptions();
+ options.expandQueries = 0;
+ let query = PlacesUtils.history.getNewQuery();
+ query.setFolders([testRoot], 1);
+ let result = PlacesUtils.history.executeQuery(query, options);
+ let rootNode = result.root;
+ rootNode.containerOpen = true;
+ let cc = rootNode.childCount;
+ do_check_eq(cc, 1);
+ for (let i = 0; i < cc; i++) {
+ let node = rootNode.getChild(i);
+ // test that queries have valid itemId
+ do_check_true(node.itemId > 0);
+ // test that the container is closed
+ node.QueryInterface(Ci.nsINavHistoryContainerResultNode);
+ do_check_eq(node.containerOpen, false);
+ }
+ rootNode.containerOpen = false;
+ }
+ catch (ex) {
+ do_throw("expandQueries=0 query error: " + ex);
+ }
+
+ // bookmark saved search
+ // query for the test root, expandQueries=1
+ // the query should show up as a query container, with 1 child
+ try {
+ let options = PlacesUtils.history.getNewQueryOptions();
+ options.expandQueries = 1;
+ let query = PlacesUtils.history.getNewQuery();
+ query.setFolders([testRoot], 1);
+ let result = PlacesUtils.history.executeQuery(query, options);
+ let rootNode = result.root;
+ rootNode.containerOpen = true;
+ let cc = rootNode.childCount;
+ do_check_eq(cc, 1);
+ for (let i = 0; i < cc; i++) {
+ let node = rootNode.getChild(i);
+ // test that query node type is container when expandQueries=1
+ do_check_eq(node.type, node.RESULT_TYPE_QUERY);
+ // test that queries (as containers) have valid itemId
+ do_check_true(node.itemId > 0);
+ node.QueryInterface(Ci.nsINavHistoryContainerResultNode);
+ node.containerOpen = true;
+
+ // test that queries have children when excludeItems=1
+ // test that query nodes don't show containers (shouldn't have our folder that matches)
+ // test that queries don't show themselves in query results (shouldn't have our saved search)
+ do_check_eq(node.childCount, 1);
+
+ // test that bookmark shows in query results
+ var item = node.getChild(0);
+ do_check_eq(item.itemId, bookmarkId);
+
+ // XXX - FAILING - test live-update of query results - add a bookmark that matches the query
+ // var tmpBmId = PlacesUtils.bookmarks.insertBookmark(
+ // root, uri("http://" + searchTerm + ".com"),
+ // PlacesUtils.bookmarks.DEFAULT_INDEX, searchTerm + "blah");
+ // do_check_eq(query.childCount, 2);
+
+ // XXX - test live-update of query results - delete a bookmark that matches the query
+ // PlacesUtils.bookmarks.removeItem(tmpBMId);
+ // do_check_eq(query.childCount, 1);
+
+ // test live-update of query results - add a folder that matches the query
+ PlacesUtils.bookmarks.createFolder(
+ root, searchTerm + "zaa", PlacesUtils.bookmarks.DEFAULT_INDEX);
+ do_check_eq(node.childCount, 1);
+ // test live-update of query results - add a query that matches the query
+ PlacesUtils.bookmarks.insertBookmark(
+ root, uri("place:terms=foo&excludeQueries=1&expandQueries=1&queryType=1"),
+ PlacesUtils.bookmarks.DEFAULT_INDEX, searchTerm + "blah");
+ do_check_eq(node.childCount, 1);
+ }
+ rootNode.containerOpen = false;
+ }
+ catch (ex) {
+ do_throw("expandQueries=1 bookmarks query: " + ex);
+ }
+
+ // delete the bookmark search
+ PlacesUtils.bookmarks.removeItem(searchId);
+
+ run_next_test();
+});
+
+add_task(function* test_savedsearches_history() {
+ // add a visit that matches the search term
+ var testURI = uri("http://" + searchTerm + ".com");
+ yield PlacesTestUtils.addVisits({ uri: testURI, title: searchTerm });
+
+ // create a saved-search that matches the visit we added
+ var searchId = PlacesUtils.bookmarks.insertBookmark(testRoot,
+ uri("place:terms=" + searchTerm + "&excludeQueries=1&expandQueries=1&queryType=0"),
+ PlacesUtils.bookmarks.DEFAULT_INDEX, searchTerm);
+
+ // query for the test root, expandQueries=1
+ // the query should show up as a query container, with 1 child
+ try {
+ var options = PlacesUtils.history.getNewQueryOptions();
+ options.expandQueries = 1;
+ var query = PlacesUtils.history.getNewQuery();
+ query.setFolders([testRoot], 1);
+ var result = PlacesUtils.history.executeQuery(query, options);
+ var rootNode = result.root;
+ rootNode.containerOpen = true;
+ var cc = rootNode.childCount;
+ do_check_eq(cc, 1);
+ for (var i = 0; i < cc; i++) {
+ var node = rootNode.getChild(i);
+ // test that query node type is container when expandQueries=1
+ do_check_eq(node.type, node.RESULT_TYPE_QUERY);
+ // test that queries (as containers) have valid itemId
+ do_check_eq(node.itemId, searchId);
+ node.QueryInterface(Ci.nsINavHistoryContainerResultNode);
+ node.containerOpen = true;
+
+ // test that queries have children when excludeItems=1
+ // test that query nodes don't show containers (shouldn't have our folder that matches)
+ // test that queries don't show themselves in query results (shouldn't have our saved search)
+ do_check_eq(node.childCount, 1);
+
+ // test that history visit shows in query results
+ var item = node.getChild(0);
+ do_check_eq(item.type, item.RESULT_TYPE_URI);
+ do_check_eq(item.itemId, -1); // history visit
+ do_check_eq(item.uri, testURI.spec); // history visit
+
+ // test live-update of query results - add a history visit that matches the query
+ yield PlacesTestUtils.addVisits({
+ uri: uri("http://foo.com"),
+ title: searchTerm + "blah"
+ });
+ do_check_eq(node.childCount, 2);
+
+ // test live-update of query results - delete a history visit that matches the query
+ PlacesUtils.history.removePage(uri("http://foo.com"));
+ do_check_eq(node.childCount, 1);
+ node.containerOpen = false;
+ }
+
+ // test live-update of moved queries
+ var tmpFolderId = PlacesUtils.bookmarks.createFolder(
+ testRoot, "foo", PlacesUtils.bookmarks.DEFAULT_INDEX);
+ PlacesUtils.bookmarks.moveItem(
+ searchId, tmpFolderId, PlacesUtils.bookmarks.DEFAULT_INDEX);
+ var tmpFolderNode = rootNode.getChild(0);
+ do_check_eq(tmpFolderNode.itemId, tmpFolderId);
+ tmpFolderNode.QueryInterface(Ci.nsINavHistoryContainerResultNode);
+ tmpFolderNode.containerOpen = true;
+ do_check_eq(tmpFolderNode.childCount, 1);
+
+ // test live-update of renamed queries
+ PlacesUtils.bookmarks.setItemTitle(searchId, "foo");
+ do_check_eq(tmpFolderNode.title, "foo");
+
+ // test live-update of deleted queries
+ PlacesUtils.bookmarks.removeItem(searchId);
+ try {
+ tmpFolderNode = root.getChild(1);
+ do_throw("query was not removed");
+ } catch (ex) {}
+
+ tmpFolderNode.containerOpen = false;
+ rootNode.containerOpen = false;
+ }
+ catch (ex) {
+ do_throw("expandQueries=1 bookmarks query: " + ex);
+ }
+});
diff --git a/toolkit/components/places/tests/bookmarks/xpcshell.ini b/toolkit/components/places/tests/bookmarks/xpcshell.ini
new file mode 100644
index 000000000..c290fd693
--- /dev/null
+++ b/toolkit/components/places/tests/bookmarks/xpcshell.ini
@@ -0,0 +1,50 @@
+[DEFAULT]
+head = head_bookmarks.js
+tail =
+skip-if = toolkit == 'android'
+
+[test_1016953-renaming-uncompressed.js]
+[test_1017502-bookmarks_foreign_count.js]
+[test_384228.js]
+[test_385829.js]
+[test_388695.js]
+[test_393498.js]
+[test_395101.js]
+[test_395593.js]
+[test_405938_restore_queries.js]
+[test_417228-exclude-from-backup.js]
+[test_417228-other-roots.js]
+[test_424958-json-quoted-folders.js]
+[test_448584.js]
+[test_458683.js]
+[test_466303-json-remove-backups.js]
+[test_477583_json-backup-in-future.js]
+[test_675416.js]
+[test_711914.js]
+[test_818584-discard-duplicate-backups.js]
+[test_818587_compress-bookmarks-backups.js]
+[test_818593-store-backup-metadata.js]
+[test_992901-backup-unsorted-hierarchy.js]
+[test_997030-bookmarks-html-encode.js]
+[test_1129529.js]
+[test_async_observers.js]
+[test_bmindex.js]
+[test_bookmarkstree_cache.js]
+[test_bookmarks.js]
+[test_bookmarks_eraseEverything.js]
+[test_bookmarks_fetch.js]
+[test_bookmarks_getRecent.js]
+[test_bookmarks_insert.js]
+[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]
+[test_keywords.js]
+[test_nsINavBookmarkObserver.js]
+[test_protectRoots.js]
+[test_removeFolderTransaction_reinsert.js]
+[test_removeItem.js]
+[test_savedsearches.js]