diff options
Diffstat (limited to 'toolkit/components/places/tests/bookmarks')
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] |