diff options
Diffstat (limited to 'browser/components/places/tests')
83 files changed, 8823 insertions, 0 deletions
diff --git a/browser/components/places/tests/browser/.eslintrc.js b/browser/components/places/tests/browser/.eslintrc.js new file mode 100644 index 000000000..7c8021192 --- /dev/null +++ b/browser/components/places/tests/browser/.eslintrc.js @@ -0,0 +1,7 @@ +"use strict"; + +module.exports = { + "extends": [ + "../../../../../testing/mochitest/browser.eslintrc.js" + ] +}; diff --git a/browser/components/places/tests/browser/bookmark_dummy_1.html b/browser/components/places/tests/browser/bookmark_dummy_1.html new file mode 100644 index 000000000..c03e0c18c --- /dev/null +++ b/browser/components/places/tests/browser/bookmark_dummy_1.html @@ -0,0 +1,9 @@ +<html> +<head> +<title>Bookmark Dummy 1</title> +<meta http-equiv="Content-Type" content="text/html;charset=utf-8"></meta> +</head> +<body> +<p>Bookmark Dummy 1</p> +</body> +</html> diff --git a/browser/components/places/tests/browser/bookmark_dummy_2.html b/browser/components/places/tests/browser/bookmark_dummy_2.html new file mode 100644 index 000000000..229a730b3 --- /dev/null +++ b/browser/components/places/tests/browser/bookmark_dummy_2.html @@ -0,0 +1,9 @@ +<html> +<head> +<title>Bookmark Dummy 2</title> +<meta http-equiv="Content-Type" content="text/html;charset=utf-8"></meta> +</head> +<body> +<p>Bookmark Dummy 2</p> +</body> +</html> diff --git a/browser/components/places/tests/browser/browser.ini b/browser/components/places/tests/browser/browser.ini new file mode 100644 index 000000000..5dce31653 --- /dev/null +++ b/browser/components/places/tests/browser/browser.ini @@ -0,0 +1,58 @@ +# 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/. + +[DEFAULT] +support-files = + head.js + framedPage.html + frameLeft.html + frameRight.html + sidebarpanels_click_test_page.html + keyword_form.html + +[browser_0_library_left_pane_migration.js] +[browser_410196_paste_into_tags.js] +subsuite = clipboard +[browser_416459_cut.js] +subsuite = clipboard +[browser_423515.js] +[browser_425884.js] +[browser_435851_copy_query.js] +subsuite = clipboard +[browser_475045.js] +[browser_555547.js] +[browser_bookmarklet_windowOpen.js] +support-files = + pageopeningwindow.html +[browser_bookmarkProperties_addFolderDefaultButton.js] +[browser_bookmarkProperties_addKeywordForThisSearch.js] +[browser_bookmarkProperties_addLivemark.js] +[browser_bookmarkProperties_editTagContainer.js] +[browser_bookmarkProperties_readOnlyRoot.js] +[browser_bookmarksProperties.js] +[browser_drag_bookmarks_on_toolbar.js] +[browser_forgetthissite_single.js] +[browser_history_sidebar_search.js] +[browser_library_batch_delete.js] +[browser_library_commands.js] +[browser_library_downloads.js] +[browser_library_infoBox.js] +[browser_library_left_pane_fixnames.js] +[browser_library_left_pane_select_hierarchy.js] +[browser_library_middleclick.js] +[browser_library_open_leak.js] +[browser_library_openFlatContainer.js] +[browser_library_panel_leak.js] +[browser_library_search.js] +[browser_library_views_liveupdate.js] +[browser_markPageAsFollowedLink.js] +[browser_sidebarpanels_click.js] +skip-if = true # temporarily disabled for breaking the treeview - bug 658744 +[browser_sort_in_library.js] +[browser_toolbarbutton_menu_context.js] +[browser_views_liveupdate.js] +[browser_bookmark_all_tabs.js] +support-files = + bookmark_dummy_1.html + bookmark_dummy_2.html diff --git a/browser/components/places/tests/browser/browser_0_library_left_pane_migration.js b/browser/components/places/tests/browser/browser_0_library_left_pane_migration.js new file mode 100644 index 000000000..a7089b497 --- /dev/null +++ b/browser/components/places/tests/browser/browser_0_library_left_pane_migration.js @@ -0,0 +1,90 @@ +/* -*- 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/. */ + +"use strict"; + +/** + * Test we correctly migrate Library left pane to the latest version. + * Note: this test MUST be the first between browser chrome tests, or results + * of next tests could be unexpected due to PlacesUIUtils getters. + */ + +const TEST_URI = "http://www.mozilla.org/"; + +add_task(function* () { + // Sanity checks. + ok(PlacesUtils, "PlacesUtils is running in chrome context"); + ok(PlacesUIUtils, "PlacesUIUtils is running in chrome context"); + ok(PlacesUIUtils.ORGANIZER_LEFTPANE_VERSION > 0, + "Left pane version in chrome context, current version is: " + PlacesUIUtils.ORGANIZER_LEFTPANE_VERSION ); + + // Check if we have any left pane folder already set, remove it eventually. + let leftPaneItems = PlacesUtils.annotations + .getItemsWithAnnotation(PlacesUIUtils.ORGANIZER_FOLDER_ANNO); + if (leftPaneItems.length > 0) { + // The left pane has already been created, touching it now would cause + // next tests to rely on wrong values (and possibly crash) + is(leftPaneItems.length, 1, "We correctly have only 1 left pane folder"); + // Check version. + let version = PlacesUtils.annotations.getItemAnnotation(leftPaneItems[0], + PlacesUIUtils.ORGANIZER_FOLDER_ANNO); + is(version, PlacesUIUtils.ORGANIZER_LEFTPANE_VERSION, "Left pane version is actual"); + ok(true, "left pane has already been created, skipping test"); + return; + } + + // Create a fake left pane folder with an old version (current version - 1). + let folder = yield PlacesUtils.bookmarks.insert({ + parentGuid: PlacesUtils.bookmarks.rootGuid, + index: PlacesUtils.bookmarks.DEFAULT_INDEX, + type: PlacesUtils.bookmarks.TYPE_FOLDER, + title: "" + }); + + let folderId = yield PlacesUtils.promiseItemId(folder.guid); + PlacesUtils.annotations.setItemAnnotation(folderId, + PlacesUIUtils.ORGANIZER_FOLDER_ANNO, + PlacesUIUtils.ORGANIZER_LEFTPANE_VERSION - 1, + 0, + PlacesUtils.annotations.EXPIRE_NEVER); + + // Check fake left pane root has been correctly created. + leftPaneItems = + PlacesUtils.annotations.getItemsWithAnnotation(PlacesUIUtils.ORGANIZER_FOLDER_ANNO); + is(leftPaneItems.length, 1, "We correctly have only 1 left pane folder"); + is(leftPaneItems[0], folderId, "left pane root itemId is correct"); + + // Check version. + let version = PlacesUtils.annotations.getItemAnnotation(folderId, + PlacesUIUtils.ORGANIZER_FOLDER_ANNO); + is(version, PlacesUIUtils.ORGANIZER_LEFTPANE_VERSION - 1, "Left pane version correctly set"); + + // Open Library, this will upgrade our left pane version. + let organizer = yield promiseLibrary(); + + // Check left pane. + ok(PlacesUIUtils.leftPaneFolderId > 0, "Left pane folder correctly created"); + leftPaneItems = + PlacesUtils.annotations.getItemsWithAnnotation(PlacesUIUtils.ORGANIZER_FOLDER_ANNO); + is(leftPaneItems.length, 1, "We correctly have only 1 left pane folder"); + let leftPaneRoot = leftPaneItems[0]; + is(leftPaneRoot, PlacesUIUtils.leftPaneFolderId, + "leftPaneFolderId getter has correct value"); + + // Check version has been upgraded. + version = PlacesUtils.annotations.getItemAnnotation(leftPaneRoot, + PlacesUIUtils.ORGANIZER_FOLDER_ANNO); + is(version, PlacesUIUtils.ORGANIZER_LEFTPANE_VERSION, + "Left pane version has been correctly upgraded"); + + // Check left pane is populated. + organizer.PlacesOrganizer.selectLeftPaneQuery("History"); + is(organizer.PlacesOrganizer._places.selectedNode.itemId, + PlacesUIUtils.leftPaneQueries["History"], + "Library left pane is populated and working"); + + yield promiseLibraryClosed(organizer); +}); diff --git a/browser/components/places/tests/browser/browser_410196_paste_into_tags.js b/browser/components/places/tests/browser/browser_410196_paste_into_tags.js new file mode 100644 index 000000000..2acb1d9b7 --- /dev/null +++ b/browser/components/places/tests/browser/browser_410196_paste_into_tags.js @@ -0,0 +1,114 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +"use strict"; + +const TEST_URL = Services.io.newURI("http://example.com/", null, null); +const MOZURISPEC = Services.io.newURI("http://mozilla.com/", null, null); + +add_task(function* () { + let organizer = yield promiseLibrary(); + + ok(PlacesUtils, "PlacesUtils in scope"); + ok(PlacesUIUtils, "PlacesUIUtils in scope"); + + let PlacesOrganizer = organizer.PlacesOrganizer; + ok(PlacesOrganizer, "Places organizer in scope"); + + let ContentTree = organizer.ContentTree; + ok(ContentTree, "ContentTree is in scope"); + + let visits = {uri: MOZURISPEC, transition: PlacesUtils.history.TRANSITION_TYPED}; + yield PlacesTestUtils.addVisits(visits); + + // create an initial tag to work with + let bm = yield PlacesUtils.bookmarks.insert({ + parentGuid: PlacesUtils.bookmarks.unfiledGuid, + index: PlacesUtils.bookmarks.DEFAULT_INDEX, + type: PlacesUtils.bookmarks.TYPE_BOOKMARK, + title: "bookmark/" + TEST_URL.spec, + url: TEST_URL + }); + + ok(bm, "A bookmark was added"); + PlacesUtils.tagging.tagURI(TEST_URL, ["foo"]); + let tags = PlacesUtils.tagging.getTagsForURI(TEST_URL); + is(tags[0], "foo", "tag is foo"); + + // focus the new tag + focusTag(PlacesOrganizer); + + let populate = () => copyHistNode(PlacesOrganizer, ContentTree); + yield promiseClipboard(populate, PlacesUtils.TYPE_X_MOZ_PLACE); + + focusTag(PlacesOrganizer); + PlacesOrganizer._places.controller.paste(); + + // re-focus the history again + PlacesOrganizer.selectLeftPaneQuery("History"); + let histContainer = PlacesOrganizer._places.selectedNode; + PlacesUtils.asContainer(histContainer); + histContainer.containerOpen = true; + PlacesOrganizer._places.selectNode(histContainer.getChild(0)); + let histNode = ContentTree.view.view.nodeForTreeIndex(0); + ok(histNode, "histNode exists: " + histNode.title); + + // check to see if the history node is tagged! + tags = PlacesUtils.tagging.getTagsForURI(MOZURISPEC); + ok(tags.length == 1, "history node is tagged: " + tags.length); + + // check if a bookmark was created + let bookmarks = []; + yield PlacesUtils.bookmarks.fetch({url: MOZURISPEC}, bm => { + bookmarks.push(bm); + }); + ok(bookmarks.length > 0, "bookmark exists for the tagged history item"); + + // is the bookmark visible in the UI? + // get the Unsorted Bookmarks node + PlacesOrganizer.selectLeftPaneQuery("UnfiledBookmarks"); + + // now we can see what is in the ContentTree tree + let unsortedNode = ContentTree.view.view.nodeForTreeIndex(1); + ok(unsortedNode, "unsortedNode is not null: " + unsortedNode.uri); + is(unsortedNode.uri, MOZURISPEC.spec, "node uri's are the same"); + + yield promiseLibraryClosed(organizer); + + // Remove new Places data we created. + PlacesUtils.tagging.untagURI(MOZURISPEC, ["foo"]); + PlacesUtils.tagging.untagURI(TEST_URL, ["foo"]); + tags = PlacesUtils.tagging.getTagsForURI(TEST_URL); + is(tags.length, 0, "tags are gone"); + + yield PlacesUtils.bookmarks.eraseEverything(); + yield PlacesTestUtils.clearHistory(); +}); + +function focusTag(PlacesOrganizer) { + PlacesOrganizer.selectLeftPaneQuery("Tags"); + let tags = PlacesOrganizer._places.selectedNode; + tags.containerOpen = true; + let fooTag = tags.getChild(0); + let tagNode = fooTag; + PlacesOrganizer._places.selectNode(fooTag); + is(tagNode.title, 'foo', "tagNode title is foo"); + let ip = PlacesOrganizer._places.insertionPoint; + ok(ip.isTag, "IP is a tag"); +} + +function copyHistNode(PlacesOrganizer, ContentTree) { + // focus the history object + PlacesOrganizer.selectLeftPaneQuery("History"); + let histContainer = PlacesOrganizer._places.selectedNode; + PlacesUtils.asContainer(histContainer); + histContainer.containerOpen = true; + PlacesOrganizer._places.selectNode(histContainer.getChild(0)); + let histNode = ContentTree.view.view.nodeForTreeIndex(0); + ContentTree.view.selectNode(histNode); + is(histNode.uri, MOZURISPEC.spec, + "historyNode exists: " + histNode.uri); + // copy the history node + ContentTree.view.controller.copy(); +} diff --git a/browser/components/places/tests/browser/browser_416459_cut.js b/browser/components/places/tests/browser/browser_416459_cut.js new file mode 100644 index 000000000..6f3f8cdd5 --- /dev/null +++ b/browser/components/places/tests/browser/browser_416459_cut.js @@ -0,0 +1,83 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +const TEST_URL = "http://example.com/"; + +add_task(function* () { + yield PlacesUtils.bookmarks.eraseEverything(); + let organizer = yield promiseLibrary(); + + registerCleanupFunction(function* () { + yield promiseLibraryClosed(organizer); + yield PlacesUtils.bookmarks.eraseEverything(); + }); + + let PlacesOrganizer = organizer.PlacesOrganizer; + let ContentTree = organizer.ContentTree; + + // Sanity checks. + ok(PlacesUtils, "PlacesUtils in scope"); + ok(PlacesUIUtils, "PlacesUIUtils in scope"); + ok(PlacesOrganizer, "PlacesOrganizer in scope"); + ok(ContentTree, "ContentTree is in scope"); + + // Test with multiple entries to ensure they retain their order. + let bookmarks = []; + bookmarks.push(yield PlacesUtils.bookmarks.insert({ + parentGuid: PlacesUtils.bookmarks.toolbarGuid, + type: PlacesUtils.bookmarks.TYPE_BOOKMARK, + url: TEST_URL, + title: "0" + })); + bookmarks.push(yield PlacesUtils.bookmarks.insert({ + parentGuid: PlacesUtils.bookmarks.toolbarGuid, + type: PlacesUtils.bookmarks.TYPE_BOOKMARK, + url: TEST_URL, + title: "1" + })); + bookmarks.push(yield PlacesUtils.bookmarks.insert({ + parentGuid: PlacesUtils.bookmarks.toolbarGuid, + type: PlacesUtils.bookmarks.TYPE_BOOKMARK, + url: TEST_URL, + title: "2" + })); + + yield selectBookmarksIn(organizer, bookmarks, "BookmarksToolbar"); + + yield promiseClipboard(() => { + info("Cutting selection"); + ContentTree.view.controller.cut(); + }, PlacesUtils.TYPE_X_MOZ_PLACE); + + info("Selecting UnfiledBookmarks in the left pane"); + PlacesOrganizer.selectLeftPaneQuery("UnfiledBookmarks"); + info("Pasting clipboard"); + ContentTree.view.controller.paste(); + + yield selectBookmarksIn(organizer, bookmarks, "UnfiledBookmarks"); +}); + +var selectBookmarksIn = Task.async(function* (organizer, bookmarks, aLeftPaneQuery) { + let PlacesOrganizer = organizer.PlacesOrganizer; + let ContentTree = organizer.ContentTree; + info("Selecting " + aLeftPaneQuery + " in the left pane"); + PlacesOrganizer.selectLeftPaneQuery(aLeftPaneQuery); + + let ids = []; + for (let {guid} of bookmarks) { + let bookmark = yield PlacesUtils.bookmarks.fetch(guid); + is (bookmark.parentGuid, PlacesOrganizer._places.selectedNode.targetFolderGuid, + "Bookmark has the right parent"); + ids.push(yield PlacesUtils.promiseItemId(bookmark.guid)); + } + + info("Selecting the bookmarks in the right pane"); + ContentTree.view.selectItems(ids); + + for (let node of ContentTree.view.selectedNodes) { + is(node.bookmarkIndex, node.title, + "Found the expected bookmark in the expected position"); + } +}); diff --git a/browser/components/places/tests/browser/browser_423515.js b/browser/components/places/tests/browser/browser_423515.js new file mode 100644 index 000000000..4d3da8fc1 --- /dev/null +++ b/browser/components/places/tests/browser/browser_423515.js @@ -0,0 +1,173 @@ +/* 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 test() { + // sanity check + ok(PlacesUtils, "checking PlacesUtils, running in chrome context?"); + ok(PlacesUIUtils, "checking PlacesUIUtils, running in chrome context?"); + ok(PlacesControllerDragHelper, "checking PlacesControllerDragHelper, running in chrome context?"); + + const IDX = PlacesUtils.bookmarks.DEFAULT_INDEX; + + // setup + var rootId = PlacesUtils.bookmarks.createFolder(PlacesUtils.toolbarFolderId, "", IDX); + var rootNode = PlacesUtils.getFolderContents(rootId, false, true).root; + is(rootNode.childCount, 0, "confirm test root is empty"); + + var tests = []; + + // add a regular folder, should be moveable + tests.push({ + populate: function() { + this.id = + PlacesUtils.bookmarks.createFolder(rootId, "", IDX); + }, + validate: function() { + is(rootNode.childCount, 1, + "populate added data to the test root"); + is(PlacesControllerDragHelper.canMoveNode(rootNode.getChild(0)), + true, "can move regular folder node"); + } + }); + + // add a regular folder shortcut, should be moveable + tests.push({ + populate: function() { + this.folderId = + PlacesUtils.bookmarks.createFolder(rootId, "foo", IDX); + this.shortcutId = + PlacesUtils.bookmarks.insertBookmark(rootId, makeURI("place:folder="+this.folderId), IDX, "bar"); + }, + validate: function() { + is(rootNode.childCount, 2, + "populated data to the test root"); + + var folderNode = rootNode.getChild(0); + is(folderNode.type, 6, "node is folder"); + is(this.folderId, folderNode.itemId, "folder id and folder node item id match"); + + var shortcutNode = rootNode.getChild(1); + is(shortcutNode.type, 9, "node is folder shortcut"); + is(this.shortcutId, shortcutNode.itemId, "shortcut id and shortcut node item id match"); + + var concreteId = PlacesUtils.getConcreteItemId(shortcutNode); + is(concreteId, folderNode.itemId, "shortcut node id and concrete id match"); + + is(PlacesControllerDragHelper.canMoveNode(shortcutNode), + true, "can move folder shortcut node"); + } + }); + + // add a regular query, should be moveable + tests.push({ + populate: function() { + this.bookmarkId = + PlacesUtils.bookmarks.insertBookmark(rootId, makeURI("http://foo.com"), IDX, "foo"); + this.queryId = + PlacesUtils.bookmarks.insertBookmark(rootId, makeURI("place:terms=foo"), IDX, "bar"); + }, + validate: function() { + is(rootNode.childCount, 2, + "populated data to the test root"); + + var bmNode = rootNode.getChild(0); + is(bmNode.itemId, this.bookmarkId, "bookmark id and bookmark node item id match"); + + var queryNode = rootNode.getChild(1); + is(queryNode.itemId, this.queryId, "query id and query node item id match"); + + is(PlacesControllerDragHelper.canMoveNode(queryNode), + true, "can move query node"); + } + }); + + // test that special folders cannot be moved + // test that special folders shortcuts can be moved + tests.push({ + folders: [PlacesUtils.bookmarksMenuFolderId, + PlacesUtils.tagsFolderId, PlacesUtils.unfiledBookmarksFolderId, + PlacesUtils.toolbarFolderId], + shortcuts: {}, + populate: function() { + for (var i = 0; i < this.folders.length; i++) { + var id = this.folders[i]; + this.shortcuts[id] = + PlacesUtils.bookmarks.insertBookmark(rootId, makeURI("place:folder=" + id), IDX, ""); + } + }, + validate: function() { + // test toolbar shortcut node + is(rootNode.childCount, this.folders.length, + "populated data to the test root"); + + function getRootChildNode(aId) { + var node = PlacesUtils.getFolderContents(PlacesUtils.placesRootId, false, true).root; + for (var i = 0; i < node.childCount; i++) { + var child = node.getChild(i); + if (child.itemId == aId) { + node.containerOpen = false; + return child; + } + } + node.containerOpen = false; + ok(false, "Unable to find child node"); + return null; + } + + for (var i = 0; i < this.folders.length; i++) { + var id = this.folders[i]; + + var node = getRootChildNode(id); + isnot(node, null, "Node found"); + is(PlacesControllerDragHelper.canMoveNode(node), + false, "shouldn't be able to move special folder node"); + + var shortcutId = this.shortcuts[id]; + var shortcutNode = rootNode.getChild(i); + + is(shortcutNode.itemId, shortcutId, "shortcut id and shortcut node item id match"); + + dump("can move shortcut node?\n"); + is(PlacesControllerDragHelper.canMoveNode(shortcutNode), + true, "should be able to move special folder shortcut node"); + } + } + }); + + // test that a tag container cannot be moved + tests.push({ + populate: function() { + // tag a uri + this.uri = makeURI("http://foo.com"); + PlacesUtils.tagging.tagURI(this.uri, ["bar"]); + registerCleanupFunction(() => PlacesUtils.tagging.untagURI(this.uri, ["bar"])); + }, + validate: function() { + // get tag root + var query = PlacesUtils.history.getNewQuery(); + var options = PlacesUtils.history.getNewQueryOptions(); + options.resultType = Ci.nsINavHistoryQueryOptions.RESULTS_AS_TAG_QUERY; + var tagsNode = PlacesUtils.history.executeQuery(query, options).root; + + tagsNode.containerOpen = true; + is(tagsNode.childCount, 1, "has new tag"); + + var tagNode = tagsNode.getChild(0); + + is(PlacesControllerDragHelper.canMoveNode(tagNode), + false, "should not be able to move tag container node"); + tagsNode.containerOpen = false; + } + }); + + tests.forEach(function(aTest) { + PlacesUtils.bookmarks.removeFolderChildren(rootId); + aTest.populate(); + aTest.validate(); + }); + + rootNode.containerOpen = false; + PlacesUtils.bookmarks.removeItem(rootId); +} diff --git a/browser/components/places/tests/browser/browser_425884.js b/browser/components/places/tests/browser/browser_425884.js new file mode 100644 index 000000000..655eb1ffd --- /dev/null +++ b/browser/components/places/tests/browser/browser_425884.js @@ -0,0 +1,127 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +/* + Deep copy of bookmark data, using the front-end codepath: + + - create test folder A + - add a subfolder to folder A, and add items to it + - validate folder A (sanity check) + - copy folder A, creating new folder B, using the front-end path + - validate folder B + - undo copy transaction + - validate folder B (empty) + - redo copy transaction + - validate folder B's contents +*/ + +add_task(function* () { + // sanity check + ok(PlacesUtils, "checking PlacesUtils, running in chrome context?"); + ok(PlacesUIUtils, "checking PlacesUIUtils, running in chrome context?"); + + let toolbarId = PlacesUtils.toolbarFolderId; + let toolbarNode = PlacesUtils.getFolderContents(toolbarId).root; + + let oldCount = toolbarNode.childCount; + let testRoot = yield PlacesUtils.bookmarks.insert({ + parentGuid: PlacesUtils.bookmarks.toolbarGuid, + type: PlacesUtils.bookmarks.TYPE_FOLDER, + title: "test root" + }); + is(toolbarNode.childCount, oldCount+1, "confirm test root node is a container, and is empty"); + + let testRootNode = toolbarNode.getChild(toolbarNode.childCount-1); + testRootNode.QueryInterface(Ci.nsINavHistoryContainerResultNode); + testRootNode.containerOpen = true; + is(testRootNode.childCount, 0, "confirm test root node is a container, and is empty"); + + // create folder A, fill it, validate its contents + let folderA = yield PlacesUtils.bookmarks.insert({ + type: PlacesUtils.bookmarks.TYPE_FOLDER, + parentGuid: testRoot.guid, + title: "A" + }); + + yield populate(folderA); + + let folderAId = yield PlacesUtils.promiseItemId(folderA.guid); + let folderANode = PlacesUtils.getFolderContents(folderAId).root; + validate(folderANode); + is(testRootNode.childCount, 1, "create test folder"); + + // copy it, using the front-end helper functions + let serializedNode = PlacesUtils.wrapNode(folderANode, PlacesUtils.TYPE_X_MOZ_PLACE_CONTAINER); + let rawNode = PlacesUtils.unwrapNodes(serializedNode, PlacesUtils.TYPE_X_MOZ_PLACE_CONTAINER).shift(); + // confirm serialization + ok(rawNode.type, "confirm json node"); + folderANode.containerOpen = false; + + let testRootId = yield PlacesUtils.promiseItemId(testRoot.guid); + let transaction = PlacesUIUtils.makeTransaction(rawNode, + PlacesUtils.TYPE_X_MOZ_PLACE_CONTAINER, + testRootId, + -1, + true); + ok(transaction, "create transaction"); + PlacesUtils.transactionManager.doTransaction(transaction); + // confirm copy + is(testRootNode.childCount, 2, "create test folder via copy"); + + // validate the copy + let folderBNode = testRootNode.getChild(1); + validate(folderBNode); + + // undo the transaction, confirm the removal + PlacesUtils.transactionManager.undoTransaction(); + is(testRootNode.childCount, 1, "confirm undo removed the copied folder"); + + // redo the transaction + PlacesUtils.transactionManager.redoTransaction(); + is(testRootNode.childCount, 2, "confirm redo re-copied the folder"); + folderBNode = testRootNode.getChild(1); + validate(folderBNode); + + // Close containers, cleaning up their observers. + testRootNode.containerOpen = false; + toolbarNode.containerOpen = false; + + // clean up + PlacesUtils.transactionManager.undoTransaction(); + yield PlacesUtils.bookmarks.remove(folderA.guid); +}); + +var populate = Task.async(function* (parentFolder) { + let folder = yield PlacesUtils.bookmarks.insert({ + type: PlacesUtils.bookmarks.TYPE_FOLDER, + parentGuid: parentFolder.guid, + title: "test folder" + }); + + yield PlacesUtils.bookmarks.insert({ + type: PlacesUtils.bookmarks.TYPE_BOOKMARK, + parentGuid: folder.guid, + title: "test bookmark", + url: "http://foo" + }); + + yield PlacesUtils.bookmarks.insert({ + type: PlacesUtils.bookmarks.TYPE_SEPARATOR, + parentGuid: folder.guid + }); +}); + +function validate(aNode) { + PlacesUtils.asContainer(aNode); + aNode.containerOpen = true; + is(aNode.childCount, 1, "confirm child count match"); + var folderNode = aNode.getChild(0); + is(folderNode.title, "test folder", "confirm folder title"); + PlacesUtils.asContainer(folderNode); + folderNode.containerOpen = true; + is(folderNode.childCount, 2, "confirm child count match"); + folderNode.containerOpen = false; + aNode.containerOpen = false; +} diff --git a/browser/components/places/tests/browser/browser_435851_copy_query.js b/browser/components/places/tests/browser/browser_435851_copy_query.js new file mode 100644 index 000000000..92f818b41 --- /dev/null +++ b/browser/components/places/tests/browser/browser_435851_copy_query.js @@ -0,0 +1,59 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +/* test that copying a non movable query or folder shortcut makes a new query with the same url, not a deep copy */ + +const SHORTCUT_URL = "place:folder=2"; +const QUERY_URL = "place:sort=8&maxResults=10"; + +add_task(function* copy_toolbar_shortcut() { + let library = yield promiseLibrary(); + + registerCleanupFunction(function () { + library.close(); + PlacesUtils.bookmarks.removeFolderChildren(PlacesUtils.unfiledBookmarksFolderId); + }); + + library.PlacesOrganizer.selectLeftPaneQuery("BookmarksToolbar"); + + yield promiseClipboard(function () { library.PlacesOrganizer._places.controller.copy(); }, + PlacesUtils.TYPE_X_MOZ_PLACE); + + library.PlacesOrganizer.selectLeftPaneQuery("UnfiledBookmarks"); + library.ContentTree.view.controller.paste(); + + let toolbarCopyNode = library.ContentTree.view.view.nodeForTreeIndex(0); + is(toolbarCopyNode.type, + Ci.nsINavHistoryResultNode.RESULT_TYPE_FOLDER_SHORTCUT, + "copy is still a folder shortcut"); + + PlacesUtils.bookmarks.removeItem(toolbarCopyNode.itemId); + library.PlacesOrganizer.selectLeftPaneQuery("BookmarksToolbar"); + is(library.PlacesOrganizer._places.selectedNode.type, + Ci.nsINavHistoryResultNode.RESULT_TYPE_FOLDER_SHORTCUT, + "original is still a folder shortcut"); +}); + +add_task(function* copy_history_query() { + let library = yield promiseLibrary(); + + library.PlacesOrganizer.selectLeftPaneQuery("History"); + + yield promiseClipboard(function () { library.PlacesOrganizer._places.controller.copy(); }, + PlacesUtils.TYPE_X_MOZ_PLACE); + + library.PlacesOrganizer.selectLeftPaneQuery("UnfiledBookmarks"); + library.ContentTree.view.controller.paste(); + + let historyCopyNode = library.ContentTree.view.view.nodeForTreeIndex(0); + is(historyCopyNode.type, + Ci.nsINavHistoryResultNode.RESULT_TYPE_QUERY, + "copy is still a query"); + + PlacesUtils.bookmarks.removeItem(historyCopyNode.itemId); + library.PlacesOrganizer.selectLeftPaneQuery("History"); + is(library.PlacesOrganizer._places.selectedNode.type, + Ci.nsINavHistoryResultNode.RESULT_TYPE_QUERY, + "original is still a query"); +}); diff --git a/browser/components/places/tests/browser/browser_475045.js b/browser/components/places/tests/browser/browser_475045.js new file mode 100644 index 000000000..7d562349b --- /dev/null +++ b/browser/components/places/tests/browser/browser_475045.js @@ -0,0 +1,65 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ +// Instead of loading EventUtils.js into the test scope in browser-test.js for all tests, +// we only need EventUtils.js for a few files which is why we are using loadSubScript. +var EventUtils = {}; +this._scriptLoader = Cc["@mozilla.org/moz/jssubscript-loader;1"]. + getService(Ci.mozIJSSubScriptLoader); +this._scriptLoader.loadSubScript("chrome://mochikit/content/tests/SimpleTest/EventUtils.js", EventUtils); + +add_task(function* test() { + // Make sure the bookmarks bar is visible and restore its state on cleanup. + let toolbar = document.getElementById("PersonalToolbar"); + ok(toolbar, "PersonalToolbar should not be null"); + + if (toolbar.collapsed) { + yield promiseSetToolbarVisibility(toolbar, true); + registerCleanupFunction(function() { + return promiseSetToolbarVisibility(toolbar, false); + }); + } + + // Setup the node we will use to be dropped. The actual node used does not + // matter because we will set its data, effect, and mimeType manually. + let placesItems = document.getElementById("PlacesToolbarItems"); + ok(placesItems, "PlacesToolbarItems should not be null"); + ok(placesItems.localName == "scrollbox", "PlacesToolbarItems should not be null"); + ok(placesItems.childNodes[0], "PlacesToolbarItems must have at least one child"); + + /** + * Simulates a drop of a URI onto the bookmarks bar. + * + * @param aEffect + * The effect to use for the drop operation: move, copy, or link. + * @param aMimeType + * The mime type to use for the drop operation. + */ + let simulateDragDrop = function(aEffect, aMimeType) { + const uriSpec = "http://www.mozilla.org/D1995729-A152-4e30-8329-469B01F30AA7"; + let uri = makeURI(uriSpec); + EventUtils.synthesizeDrop(placesItems.childNodes[0], + placesItems, + [[{type: aMimeType, + data: uriSpec}]], + aEffect, window); + + // Verify that the drop produces exactly one bookmark. + let bookmarkIds = PlacesUtils.bookmarks + .getBookmarkIdsForURI(uri); + ok(bookmarkIds.length == 1, "There should be exactly one bookmark"); + + PlacesUtils.bookmarks.removeItem(bookmarkIds[0]); + + // Verify that we removed the bookmark successfully. + ok(!PlacesUtils.bookmarks.isBookmarked(uri), "URI should be removed"); + } + + // Simulate a bookmark drop for all of the mime types and effects. + let mimeTypes = ["text/plain", "text/unicode", "text/x-moz-url"]; + let effects = ["move", "copy", "link"]; + effects.forEach(function (effect) { + mimeTypes.forEach(function (mimeType) { + simulateDragDrop(effect, mimeType); + }); + }); +}); diff --git a/browser/components/places/tests/browser/browser_555547.js b/browser/components/places/tests/browser/browser_555547.js new file mode 100644 index 000000000..0654139cb --- /dev/null +++ b/browser/components/places/tests/browser/browser_555547.js @@ -0,0 +1,66 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +"use strict"; + +add_task(function* test() { + let sidebarBox = document.getElementById("sidebar-box"); + is(sidebarBox.hidden, true, "The sidebar should be hidden"); + + // Uncollapse the personal toolbar if needed. + let toolbar = document.getElementById("PersonalToolbar"); + let wasCollapsed = toolbar.collapsed; + if (wasCollapsed) { + yield promiseSetToolbarVisibility(toolbar, true); + } + + let sidebar = yield promiseLoadedSidebar("viewBookmarksSidebar"); + registerCleanupFunction(() => { + SidebarUI.hide(); + }); + + // Focus the tree and check if its controller is returned. + let tree = sidebar.contentDocument.getElementById("bookmarks-view"); + tree.focus(); + + let controller = doGetPlacesControllerForCommand("placesCmd_copy"); + let treeController = tree.controllers + .getControllerForCommand("placesCmd_copy"); + ok(controller == treeController, "tree controller was returned"); + + // Open the context menu for a toolbar item, and check if the toolbar's + // controller is returned. + let toolbarItems = document.getElementById("PlacesToolbarItems"); + EventUtils.synthesizeMouse(toolbarItems.childNodes[0], + 4, 4, { type: "contextmenu", button: 2 }, + window); + controller = doGetPlacesControllerForCommand("placesCmd_copy"); + let toolbarController = document.getElementById("PlacesToolbar") + .controllers + .getControllerForCommand("placesCmd_copy"); + ok(controller == toolbarController, "the toolbar controller was returned"); + + document.getElementById("placesContext").hidePopup(); + + // Now that the context menu is closed, try to get the tree controller again. + tree.focus(); + controller = doGetPlacesControllerForCommand("placesCmd_copy"); + ok(controller == treeController, "tree controller was returned"); + + if (wasCollapsed) { + yield promiseSetToolbarVisibility(toolbar, false); + } +}); + +function promiseLoadedSidebar(cmd) { + return new Promise(resolve => { + let sidebar = document.getElementById("sidebar"); + sidebar.addEventListener("load", function onLoad() { + sidebar.removeEventListener("load", onLoad, true); + resolve(sidebar); + }, true); + + SidebarUI.show(cmd); + }); +} diff --git a/browser/components/places/tests/browser/browser_bookmarkProperties_addFolderDefaultButton.js b/browser/components/places/tests/browser/browser_bookmarkProperties_addFolderDefaultButton.js new file mode 100644 index 000000000..a1f091ebe --- /dev/null +++ b/browser/components/places/tests/browser/browser_bookmarkProperties_addFolderDefaultButton.js @@ -0,0 +1,53 @@ +"use strict" + +add_task(function* () { + info("Bug 475529 - Add is the default button for the new folder dialog + " + + "Bug 1206376 - Changing properties of a new bookmark while adding it " + + "acts on the last bookmark in the current container"); + + // Add a new bookmark at index 0 in the unfiled folder. + let insertionIndex = 0; + let newBookmark = yield PlacesUtils.bookmarks.insert({ + index: insertionIndex, + type: PlacesUtils.bookmarks.TYPE_BOOKMARK, + parentGuid: PlacesUtils.bookmarks.unfiledGuid, + url: "http://example.com/", + }); + let newBookmarkId = yield PlacesUtils.promiseItemId(newBookmark.guid); + + yield withSidebarTree("bookmarks", function* (tree) { + // Select the new bookmark in the sidebar. + tree.selectItems([newBookmarkId]); + ok(tree.controller.isCommandEnabled("placesCmd_new:folder"), + "'placesCmd_new:folder' on current selected node is enabled"); + + // Create a new folder. Since the new bookmark is selected, and new items + // are inserted at the index of the currently selected item, the new folder + // will be inserted at index 0. + yield withBookmarksDialog( + false, + function openDialog() { + tree.controller.doCommand("placesCmd_new:folder"); + }, + function* test(dialogWin) { + let promiseTitleChangeNotification = promiseBookmarksNotification( + "onItemChanged", (itemId, prop, isAnno, val) => prop == "title" && val =="n"); + + fillBookmarkTextField("editBMPanel_namePicker", "n", dialogWin, false); + + // Confirm and close the dialog. + EventUtils.synthesizeKey("VK_RETURN", {}, dialogWin); + yield promiseTitleChangeNotification; + + let newFolder = yield PlacesUtils.bookmarks.fetch({ + parentGuid: PlacesUtils.bookmarks.unfiledGuid, + index: insertionIndex, + }); + + is(newFolder.title, "n", "folder name has been edited"); + yield PlacesUtils.bookmarks.remove(newFolder); + yield PlacesUtils.bookmarks.remove(newBookmark); + } + ); + }); +}); diff --git a/browser/components/places/tests/browser/browser_bookmarkProperties_addKeywordForThisSearch.js b/browser/components/places/tests/browser/browser_bookmarkProperties_addKeywordForThisSearch.js new file mode 100644 index 000000000..5283a1610 --- /dev/null +++ b/browser/components/places/tests/browser/browser_bookmarkProperties_addKeywordForThisSearch.js @@ -0,0 +1,110 @@ +"use strict" + +const TEST_URL = "http://mochi.test:8888/browser/browser/components/places/tests/browser/keyword_form.html"; + +add_task(function* () { + yield BrowserTestUtils.withNewTab({ + gBrowser, + url: TEST_URL, + }, function* (browser) { + // We must wait for the context menu code to build metadata. + yield openContextMenuForContentSelector(browser, '#form1 > input[name="search"]'); + + yield withBookmarksDialog(true, AddKeywordForSearchField, function* (dialogWin) { + let acceptBtn = dialogWin.document.documentElement.getButton("accept"); + ok(acceptBtn.disabled, "Accept button is disabled"); + + let promiseKeywordNotification = promiseBookmarksNotification( + "onItemChanged", (itemId, prop, isAnno, val) => prop == "keyword" && val =="kw"); + + fillBookmarkTextField("editBMPanel_keywordField", "kw", dialogWin); + + ok(!acceptBtn.disabled, "Accept button is enabled"); + + // The dialog is instant apply. + yield promiseKeywordNotification; + + // After the notification, the keywords cache will update asynchronously. + info("Check the keyword entry has been created"); + let entry; + yield waitForCondition(function* () { + entry = yield PlacesUtils.keywords.fetch("kw"); + return !!entry; + }, "Unable to find the expected keyword"); + is(entry.keyword, "kw", "keyword is correct"); + is(entry.url.href, TEST_URL, "URL is correct"); + is(entry.postData, "accenti%3D%E0%E8%EC%F2%F9&search%3D%25s", "POST data is correct"); + + info("Check the charset has been saved"); + let charset = yield PlacesUtils.getCharsetForURI(NetUtil.newURI(TEST_URL)); + is(charset, "windows-1252", "charset is correct"); + + // Now check getShortcutOrURI. + let data = yield getShortcutOrURIAndPostData("kw test"); + is(getPostDataString(data.postData), "accenti=\u00E0\u00E8\u00EC\u00F2\u00F9&search=test", "getShortcutOrURI POST data is correct"); + is(data.url, TEST_URL, "getShortcutOrURI URL is correct"); + }); + }); +}); + +add_task(function* reopen_same_field() { + yield PlacesUtils.keywords.insert({ + url: TEST_URL, + keyword: "kw", + postData: "accenti%3D%E0%E8%EC%F2%F9&search%3D%25s" + }); + registerCleanupFunction(function* () { + yield PlacesUtils.keywords.remove("kw"); + }); + // Reopening on the same input field should show the existing keyword. + yield BrowserTestUtils.withNewTab({ + gBrowser, + url: TEST_URL, + }, function* (browser) { + // We must wait for the context menu code to build metadata. + yield openContextMenuForContentSelector(browser, '#form1 > input[name="search"]'); + + yield withBookmarksDialog(true, AddKeywordForSearchField, function* (dialogWin) { + let acceptBtn = dialogWin.document.documentElement.getButton("accept"); + ok(acceptBtn.disabled, "Accept button is disabled"); + + let elt = dialogWin.document.getElementById("editBMPanel_keywordField"); + is(elt.value, "kw"); + }); + }); +}); + +add_task(function* open_other_field() { + yield PlacesUtils.keywords.insert({ + url: TEST_URL, + keyword: "kw2", + postData: "search%3D%25s" + }); + registerCleanupFunction(function* () { + yield PlacesUtils.keywords.remove("kw2"); + }); + // Reopening on another field of the same page that has different postData + // should not show the existing keyword. + yield BrowserTestUtils.withNewTab({ + gBrowser, + url: TEST_URL, + }, function* (browser) { + // We must wait for the context menu code to build metadata. + yield openContextMenuForContentSelector(browser, '#form2 > input[name="search"]'); + + yield withBookmarksDialog(true, AddKeywordForSearchField, function* (dialogWin) { + let acceptBtn = dialogWin.document.documentElement.getButton("accept"); + ok(acceptBtn.disabled, "Accept button is disabled"); + + let elt = dialogWin.document.getElementById("editBMPanel_keywordField"); + is(elt.value, ""); + }); + }); +}); + +function getPostDataString(stream) { + let sis = Cc["@mozilla.org/scriptableinputstream;1"] + .createInstance(Ci.nsIScriptableInputStream); + sis.init(stream); + return sis.read(stream.available()).split("\n").pop(); +} diff --git a/browser/components/places/tests/browser/browser_bookmarkProperties_addLivemark.js b/browser/components/places/tests/browser/browser_bookmarkProperties_addLivemark.js new file mode 100644 index 000000000..d9f4c07d7 --- /dev/null +++ b/browser/components/places/tests/browser/browser_bookmarkProperties_addLivemark.js @@ -0,0 +1,39 @@ +"use strict" + +add_task(function* () { + info("Add a live bookmark editing its data"); + + yield withSidebarTree("bookmarks", function* (tree) { + let itemId = PlacesUIUtils.leftPaneQueries["UnfiledBookmarks"]; + tree.selectItems([itemId]); + + yield withBookmarksDialog( + true, + function openDialog() { + PlacesCommandHook.addLiveBookmark("http://livemark.com/", + "livemark", "description"); + }, + function* test(dialogWin) { + let promiseTitleChangeNotification = promiseBookmarksNotification( + "onItemChanged", (itemId, prop, isAnno, val) => prop == "title" && val == "modified"); + + fillBookmarkTextField("editBMPanel_namePicker", "modified", dialogWin); + + yield promiseTitleChangeNotification; + + let bookmark = yield PlacesUtils.bookmarks.fetch({ + parentGuid: PlacesUtils.bookmarks.toolbarGuid, + index: PlacesUtils.bookmarks.DEFAULT_INDEX + }); + + is(bookmark.title, "modified", "folder name has been edited"); + + let livemark = yield PlacesUtils.livemarks.getLivemark({ + guid: bookmark.guid + }); + is(livemark.feedURI.spec, "http://livemark.com/", "livemark has the correct url"); + is(livemark.title, "modified", "livemark has the correct title"); + } + ); + }); +}); diff --git a/browser/components/places/tests/browser/browser_bookmarkProperties_editTagContainer.js b/browser/components/places/tests/browser/browser_bookmarkProperties_editTagContainer.js new file mode 100644 index 000000000..fde9ea272 --- /dev/null +++ b/browser/components/places/tests/browser/browser_bookmarkProperties_editTagContainer.js @@ -0,0 +1,71 @@ +"use strict" + +add_task(function* () { + info("Bug 479348 - Properties on a root should be read-only."); + let uri = NetUtil.newURI("http://example.com/"); + let bm = yield PlacesUtils.bookmarks.insert({ + url: uri.spec, + parentGuid: PlacesUtils.bookmarks.unfiledGuid + }); + registerCleanupFunction(function* () { + yield PlacesUtils.bookmarks.remove(bm); + }); + + PlacesUtils.tagging.tagURI(uri, ["tag1"]); + + let library = yield promiseLibrary(); + let PlacesOrganizer = library.PlacesOrganizer; + registerCleanupFunction(function* () { + yield promiseLibraryClosed(library); + }); + + PlacesOrganizer.selectLeftPaneQuery("Tags"); + let tree = PlacesOrganizer._places; + let tagsContainer = tree.selectedNode; + tagsContainer.containerOpen = true; + let fooTag = tagsContainer.getChild(0); + let tagNode = fooTag; + tree.selectNode(fooTag); + is(tagNode.title, 'tag1', "tagNode title is correct"); + + ok(tree.controller.isCommandEnabled("placesCmd_show:info"), + "'placesCmd_show:info' on current selected node is enabled"); + + yield withBookmarksDialog( + true, + function openDialog() { + tree.controller.doCommand("placesCmd_show:info"); + }, + function* test(dialogWin) { + // Check that the dialog is not read-only. + ok(!dialogWin.gEditItemOverlay.readOnly, "Dialog should not be read-only"); + + // Check that name picker is not read only + let namepicker = dialogWin.document.getElementById("editBMPanel_namePicker"); + ok(!namepicker.readOnly, "Name field should not be read-only"); + is(namepicker.value, "tag1", "Node title is correct"); + + let promiseTitleChangeNotification = promiseBookmarksNotification( + "onItemChanged", (itemId, prop, isAnno, val) => prop == "title" && val == "tag2"); + + fillBookmarkTextField("editBMPanel_namePicker", "tag2", dialogWin); + + yield promiseTitleChangeNotification; + + is(namepicker.value, "tag2", "Node title has been properly edited"); + + // Check the shortcut's title. + is(tree.selectedNode.title, "tag2", "The node has the correct title"); + + // Check the tags have been edited. + let tags = PlacesUtils.tagging.getTagsForURI(uri); + is(tags.length, 1, "Found the right number of tags"); + ok(tags.includes("tag2"), "Found the expected tag"); + } + ); + + // Check the tag change has been reverted. + let tags = PlacesUtils.tagging.getTagsForURI(uri); + is(tags.length, 1, "Found the right number of tags"); + ok(tags.includes("tag1"), "Found the expected tag"); +}); diff --git a/browser/components/places/tests/browser/browser_bookmarkProperties_readOnlyRoot.js b/browser/components/places/tests/browser/browser_bookmarkProperties_readOnlyRoot.js new file mode 100644 index 000000000..6f499888c --- /dev/null +++ b/browser/components/places/tests/browser/browser_bookmarkProperties_readOnlyRoot.js @@ -0,0 +1,42 @@ +"use strict" + +add_task(function* () { + info("Bug 479348 - Properties on a root should be read-only."); + + yield withSidebarTree("bookmarks", function* (tree) { + let itemId = PlacesUIUtils.leftPaneQueries["UnfiledBookmarks"]; + tree.selectItems([itemId]); + ok(tree.controller.isCommandEnabled("placesCmd_show:info"), + "'placesCmd_show:info' on current selected node is enabled"); + + yield withBookmarksDialog( + true, + function openDialog() { + tree.controller.doCommand("placesCmd_show:info"); + }, + function* test(dialogWin) { + // Check that the dialog is read-only. + ok(dialogWin.gEditItemOverlay.readOnly, "Dialog is read-only"); + // Check that accept button is disabled + let acceptButton = dialogWin.document.documentElement.getButton("accept"); + ok(acceptButton.disabled, "Accept button is disabled"); + + // Check that name picker is read only + let namepicker = dialogWin.document.getElementById("editBMPanel_namePicker"); + ok(namepicker.readOnly, "Name field is read-only"); + is(namepicker.value, + PlacesUtils.bookmarks.getItemTitle(PlacesUtils.unfiledBookmarksFolderId), + "Node title is correct"); + // Blur the field and ensure root's name has not been changed. + namepicker.blur(); + is(namepicker.value, + PlacesUtils.bookmarks.getItemTitle(PlacesUtils.unfiledBookmarksFolderId), + "Root title is correct"); + // Check the shortcut's title. + let bookmark = yield PlacesUtils.bookmarks.fetch(tree.selectedNode.bookmarkGuid); + is(bookmark.title, null, + "Shortcut title is null"); + } + ); + }); +}); diff --git a/browser/components/places/tests/browser/browser_bookmark_all_tabs.js b/browser/components/places/tests/browser/browser_bookmark_all_tabs.js new file mode 100644 index 000000000..afd32b78a --- /dev/null +++ b/browser/components/places/tests/browser/browser_bookmark_all_tabs.js @@ -0,0 +1,37 @@ +/** + * Test for Bug 446171 - Name field of bookmarks saved via 'Bookmark All Tabs' + * has '(null)' value if history is disabled or just in private browsing mode + */ +"use strict" + +add_task(function* () { + const BASE_URL = "http://example.org/browser/browser/components/places/tests/browser/"; + const TEST_PAGES = [ + BASE_URL + "bookmark_dummy_1.html", + BASE_URL + "bookmark_dummy_2.html", + BASE_URL + "bookmark_dummy_1.html" + ]; + + function promiseAddTab(url) { + return BrowserTestUtils.openNewForegroundTab(gBrowser, url); + } + + let tabs = yield Promise.all(TEST_PAGES.map(promiseAddTab)); + + let URIs = PlacesCommandHook.uniqueCurrentPages; + is(URIs.length, 3, "Only unique pages are returned"); + + Assert.deepEqual(URIs.map(URI => URI.uri.spec), [ + "about:blank", + BASE_URL + "bookmark_dummy_1.html", + BASE_URL + "bookmark_dummy_2.html" + ], "Correct URIs are returned"); + + Assert.deepEqual(URIs.map(URI => URI.title), [ + "New Tab", "Bookmark Dummy 1", "Bookmark Dummy 2" + ], "Correct titles are returned"); + + registerCleanupFunction(function* () { + yield Promise.all(tabs.map(BrowserTestUtils.removeTab)); + }); +}); diff --git a/browser/components/places/tests/browser/browser_bookmarklet_windowOpen.js b/browser/components/places/tests/browser/browser_bookmarklet_windowOpen.js new file mode 100644 index 000000000..85ce25311 --- /dev/null +++ b/browser/components/places/tests/browser/browser_bookmarklet_windowOpen.js @@ -0,0 +1,61 @@ +"use strict"; + +const TEST_URL = 'http://example.com/browser/browser/components/places/tests/browser/pageopeningwindow.html'; + +function makeBookmarkFor(url, keyword) { + return Promise.all([ + PlacesUtils.bookmarks.insert({ parentGuid: PlacesUtils.bookmarks.unfiledGuid, + title: "bookmarklet", + url: url }), + PlacesUtils.keywords.insert({url: url, + keyword: keyword}) + ]); + +} + +add_task(function* openKeywordBookmarkWithWindowOpen() { + // This is the current default, but let's not assume that... + yield new Promise((resolve, reject) => { + SpecialPowers.pushPrefEnv({ 'set': [[ 'browser.link.open_newwindow', 3 ], + [ 'dom.disable_open_during_load', true ]] }, + resolve); + }); + + let moztab; + let tabOpened = BrowserTestUtils.openNewForegroundTab(gBrowser, "about:mozilla") + .then((tab) => { moztab = tab; }); + let keywordForBM = "openmeatab"; + + let bookmarkInfo; + let bookmarkCreated = + makeBookmarkFor("javascript:void open('" + TEST_URL + "')", keywordForBM) + .then((values) => { + bookmarkInfo = values[0]; + }); + yield Promise.all([tabOpened, bookmarkCreated]); + + registerCleanupFunction(function() { + return Promise.all([ + PlacesUtils.bookmarks.remove(bookmarkInfo), + PlacesUtils.keywords.remove(keywordForBM) + ]); + }); + gURLBar.value = keywordForBM; + gURLBar.focus(); + + let tabCreatedPromise = BrowserTestUtils.waitForEvent(gBrowser.tabContainer, "TabOpen"); + EventUtils.synthesizeKey("VK_RETURN", {}); + + info("Waiting for tab being created"); + let {target: tab} = yield tabCreatedPromise; + info("Got tab"); + let browser = tab.linkedBrowser; + if (!browser.currentURI || browser.currentURI.spec != TEST_URL) { + info("Waiting for browser load"); + yield BrowserTestUtils.browserLoaded(browser); + } + is(browser.currentURI && browser.currentURI.spec, TEST_URL, "Tab with expected URL loaded."); + info("Waiting to remove tab"); + yield Promise.all([ BrowserTestUtils.removeTab(tab), + BrowserTestUtils.removeTab(moztab) ]); +}); diff --git a/browser/components/places/tests/browser/browser_bookmarksProperties.js b/browser/components/places/tests/browser/browser_bookmarksProperties.js new file mode 100644 index 000000000..f7f9f4762 --- /dev/null +++ b/browser/components/places/tests/browser/browser_bookmarksProperties.js @@ -0,0 +1,450 @@ +/* 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/. */ + +/** + * Tests the bookmarks Properties dialog. + */ + +// DOM ids of Places sidebar trees. +const SIDEBAR_HISTORY_TREE_ID = "historyTree"; +const SIDEBAR_BOOKMARKS_TREE_ID = "bookmarks-view"; + +const SIDEBAR_HISTORY_ID = "viewHistorySidebar"; +const SIDEBAR_BOOKMARKS_ID = "viewBookmarksSidebar"; + +// For history sidebar. +const SIDEBAR_HISTORY_BYLASTVISITED_VIEW = "bylastvisited"; +const SIDEBAR_HISTORY_BYMOSTVISITED_VIEW = "byvisited"; +const SIDEBAR_HISTORY_BYDATE_VIEW = "byday"; +const SIDEBAR_HISTORY_BYSITE_VIEW = "bysite"; +const SIDEBAR_HISTORY_BYDATEANDSITE_VIEW = "bydateandsite"; + +// Action to execute on the current node. +const ACTION_EDIT = 0; +const ACTION_ADD = 1; + +// If action is ACTION_ADD, set type to one of those, to define what do you +// want to create. +const TYPE_FOLDER = 0; +const TYPE_BOOKMARK = 1; + +const TEST_URL = "http://www.example.com/"; + +const DIALOG_URL = "chrome://browser/content/places/bookmarkProperties.xul"; +const DIALOG_URL_MINIMAL_UI = "chrome://browser/content/places/bookmarkProperties2.xul"; + +Cu.import("resource:///modules/RecentWindow.jsm"); +var win = RecentWindow.getMostRecentBrowserWindow(); +var ww = Cc["@mozilla.org/embedcomp/window-watcher;1"]. + getService(Ci.nsIWindowWatcher); + +function add_bookmark(aURI) { + var bId = PlacesUtils.bookmarks + .insertBookmark(PlacesUtils.unfiledBookmarksFolderId, + aURI, + PlacesUtils.bookmarks.DEFAULT_INDEX, + "bookmark/" + aURI.spec); + return bId; +} + +// Each test is an obj w/ a desc property and run method. +var gTests = []; +var gCurrentTest = null; + +// ------------------------------------------------------------------------------ +// Bug 462662 - Pressing Enter to select tag from autocomplete closes bookmarks properties dialog +gTests.push({ + desc: "Bug 462662 - Pressing Enter to select tag from autocomplete closes bookmarks properties dialog", + sidebar: SIDEBAR_BOOKMARKS_ID, + action: ACTION_EDIT, + itemType: null, + window: null, + _itemId: null, + _cleanShutdown: false, + + setup: function(aCallback) { + // Add a bookmark in unsorted bookmarks folder. + this._itemId = add_bookmark(PlacesUtils._uri(TEST_URL)); + ok(this._itemId > 0, "Correctly added a bookmark"); + // Add a tag to this bookmark. + PlacesUtils.tagging.tagURI(PlacesUtils._uri(TEST_URL), + ["testTag"]); + var tags = PlacesUtils.tagging.getTagsForURI(PlacesUtils._uri(TEST_URL)); + is(tags[0], "testTag", "Correctly added a tag"); + aCallback(); + }, + + selectNode: function(tree) { + tree.selectItems([PlacesUtils.unfiledBookmarksFolderId]); + PlacesUtils.asContainer(tree.selectedNode).containerOpen = true; + tree.selectItems([this._itemId]); + is(tree.selectedNode.itemId, this._itemId, "Bookmark has been selected"); + }, + + run: function() { + // open tags autocomplete and press enter + var tagsField = this.window.document.getElementById("editBMPanel_tagsField"); + var self = this; + + this.window.addEventListener("unload", function(event) { + self.window.removeEventListener("unload", arguments.callee, true); + tagsField.popup.removeEventListener("popuphidden", popupListener, true); + ok(self._cleanShutdown, "Dialog window should not be closed by pressing Enter on the autocomplete popup"); + executeSoon(function () { + self.finish(); + }); + }, true); + + var popupListener = { + handleEvent: function(aEvent) { + switch (aEvent.type) { + case "popuphidden": + // Everything worked fine, we can stop observing the window. + self._cleanShutdown = true; + self.window.document.documentElement.cancelDialog(); + break; + case "popupshown": + tagsField.popup.removeEventListener("popupshown", this, true); + // In case this test fails the window will close, the test will fail + // since we didn't set _cleanShutdown. + var tree = tagsField.popup.tree; + // Focus and select first result. + isnot(tree, null, "Autocomplete results tree exists"); + is(tree.view.rowCount, 1, "We have 1 autocomplete result"); + tagsField.popup.selectedIndex = 0; + is(tree.view.selection.count, 1, + "We have selected a tag from the autocomplete popup"); + info("About to focus the autocomplete results tree"); + tree.focus(); + EventUtils.synthesizeKey("VK_RETURN", {}, self.window); + break; + default: + ok(false, "unknown event: " + aEvent.type); + return; + } + } + }; + tagsField.popup.addEventListener("popupshown", popupListener, true); + tagsField.popup.addEventListener("popuphidden", popupListener, true); + + // Open tags autocomplete popup. + info("About to focus the tagsField"); + executeSoon(() => { + tagsField.focus(); + tagsField.value = ""; + EventUtils.synthesizeKey("t", {}, this.window); + }); + }, + + finish: function() { + SidebarUI.hide(); + runNextTest(); + }, + + cleanup: function() { + // Check tags have not changed. + var tags = PlacesUtils.tagging.getTagsForURI(PlacesUtils._uri(TEST_URL)); + is(tags[0], "testTag", "Tag on node has not changed"); + + // Cleanup. + PlacesUtils.tagging.untagURI(PlacesUtils._uri(TEST_URL), ["testTag"]); + PlacesUtils.bookmarks.removeItem(this._itemId); + } +}); + +// ------------------------------------------------------------------------------ +// Bug 476020 - Pressing Esc while having the tag autocomplete open closes the bookmarks panel + +gTests.push({ + desc: "Bug 476020 - Pressing Esc while having the tag autocomplete open closes the bookmarks panel", + sidebar: SIDEBAR_BOOKMARKS_ID, + action: ACTION_EDIT, + itemType: null, + window: null, + _itemId: null, + _cleanShutdown: false, + + setup: function(aCallback) { + // Add a bookmark in unsorted bookmarks folder. + this._itemId = add_bookmark(PlacesUtils._uri(TEST_URL)); + ok(this._itemId > 0, "Correctly added a bookmark"); + // Add a tag to this bookmark. + PlacesUtils.tagging.tagURI(PlacesUtils._uri(TEST_URL), + ["testTag"]); + var tags = PlacesUtils.tagging.getTagsForURI(PlacesUtils._uri(TEST_URL)); + is(tags[0], "testTag", "Correctly added a tag"); + aCallback(); + }, + + selectNode: function(tree) { + tree.selectItems([PlacesUtils.unfiledBookmarksFolderId]); + PlacesUtils.asContainer(tree.selectedNode).containerOpen = true; + tree.selectItems([this._itemId]); + is(tree.selectedNode.itemId, this._itemId, "Bookmark has been selected"); + }, + + run: function() { + // open tags autocomplete and press enter + var tagsField = this.window.document.getElementById("editBMPanel_tagsField"); + var self = this; + + this.window.addEventListener("unload", function(event) { + self.window.removeEventListener("unload", arguments.callee, true); + tagsField.popup.removeEventListener("popuphidden", popupListener, true); + ok(self._cleanShutdown, "Dialog window should not be closed by pressing Escape on the autocomplete popup"); + executeSoon(function () { + self.finish(); + }); + }, true); + + var popupListener = { + handleEvent: function(aEvent) { + switch (aEvent.type) { + case "popuphidden": + // Everything worked fine. + self._cleanShutdown = true; + self.window.document.documentElement.cancelDialog(); + break; + case "popupshown": + tagsField.popup.removeEventListener("popupshown", this, true); + // In case this test fails the window will close, the test will fail + // since we didn't set _cleanShutdown. + var tree = tagsField.popup.tree; + // Focus and select first result. + isnot(tree, null, "Autocomplete results tree exists"); + is(tree.view.rowCount, 1, "We have 1 autocomplete result"); + tagsField.popup.selectedIndex = 0; + is(tree.view.selection.count, 1, + "We have selected a tag from the autocomplete popup"); + info("About to focus the autocomplete results tree"); + tree.focus(); + EventUtils.synthesizeKey("VK_ESCAPE", {}, self.window); + break; + default: + ok(false, "unknown event: " + aEvent.type); + return; + } + } + }; + tagsField.popup.addEventListener("popupshown", popupListener, true); + tagsField.popup.addEventListener("popuphidden", popupListener, true); + + // Open tags autocomplete popup. + info("About to focus the tagsField"); + tagsField.focus(); + tagsField.value = ""; + EventUtils.synthesizeKey("t", {}, this.window); + }, + + finish: function() { + SidebarUI.hide(); + runNextTest(); + }, + + cleanup: function() { + // Check tags have not changed. + var tags = PlacesUtils.tagging.getTagsForURI(PlacesUtils._uri(TEST_URL)); + is(tags[0], "testTag", "Tag on node has not changed"); + + // Cleanup. + PlacesUtils.tagging.untagURI(PlacesUtils._uri(TEST_URL), + ["testTag"]); + PlacesUtils.bookmarks.removeItem(this._itemId); + } +}); + +// ------------------------------------------------------------------------------ +// Bug 491269 - Test that editing folder name in bookmarks properties dialog does not accept the dialog + +gTests.push({ + desc: " Bug 491269 - Test that editing folder name in bookmarks properties dialog does not accept the dialog", + sidebar: SIDEBAR_HISTORY_ID, + action: ACTION_ADD, + historyView: SIDEBAR_HISTORY_BYLASTVISITED_VIEW, + window: null, + + setup: function(aCallback) { + // Add a visit. + PlacesTestUtils.addVisits( + {uri: PlacesUtils._uri(TEST_URL), + transition: PlacesUtils.history.TRANSITION_TYPED} + ).then(aCallback); + }, + + selectNode: function(tree) { + var visitNode = tree.view.nodeForTreeIndex(0); + tree.selectNode(visitNode); + is(tree.selectedNode.uri, TEST_URL, "The correct visit has been selected"); + is(tree.selectedNode.itemId, -1, "The selected node is not bookmarked"); + }, + + run: function() { + // Open folder selector. + var foldersExpander = this.window.document.getElementById("editBMPanel_foldersExpander"); + var folderTree = this.window.document.getElementById("editBMPanel_folderTree"); + var self = this; + + this.window.addEventListener("unload", function(event) { + self.window.removeEventListener("unload", arguments.callee, true); + ok(self._cleanShutdown, "Dialog window should not be closed by pressing ESC in folder name textbox"); + executeSoon(function () { + self.finish(); + }); + }, true); + + folderTree.addEventListener("DOMAttrModified", function onDOMAttrModified(event) { + if (event.attrName != "place") + return; + folderTree.removeEventListener("DOMAttrModified", arguments.callee, false); + executeSoon(function () { + // Create a new folder. + var newFolderButton = self.window.document.getElementById("editBMPanel_newFolderButton"); + newFolderButton.doCommand(); + ok(folderTree.hasAttribute("editing"), + "We are editing new folder name in folder tree"); + + // Press Escape to discard editing new folder name. + EventUtils.synthesizeKey("VK_ESCAPE", {}, self.window); + ok(!folderTree.hasAttribute("editing"), + "We have finished editing folder name in folder tree"); + self._cleanShutdown = true; + self.window.document.documentElement.cancelDialog(); + }); + }, false); + foldersExpander.doCommand(); + }, + + finish: function() { + SidebarUI.hide(); + runNextTest(); + }, + + cleanup: function() { + return PlacesTestUtils.clearHistory(); + } +}); + +// ------------------------------------------------------------------------------ + +function test() { + waitForExplicitFinish(); + // This test can take some time, if we timeout too early it could run + // in the middle of other tests, or hang them. + requestLongerTimeout(2); + + // Sanity checks. + ok(PlacesUtils, "PlacesUtils in context"); + ok(PlacesUIUtils, "PlacesUIUtils in context"); + + // kick off tests + runNextTest(); +} + +function runNextTest() { + // Cleanup from previous test. + if (gCurrentTest) { + Promise.resolve(gCurrentTest.cleanup()).then(() => { + info("End of test: " + gCurrentTest.desc); + gCurrentTest = null; + waitForAsyncUpdates(runNextTest); + }); + return; + } + + if (gTests.length > 0) { + // Goto next tests. + gCurrentTest = gTests.shift(); + info("Start of test: " + gCurrentTest.desc); + gCurrentTest.setup(function() { + execute_test_in_sidebar(); + }); + } + else { + // Finished all tests. + finish(); + } +} + +/** + * Global functions to run a test in Properties dialog context. + */ + +function execute_test_in_sidebar() { + var sidebar = document.getElementById("sidebar"); + sidebar.addEventListener("load", function() { + sidebar.removeEventListener("load", arguments.callee, true); + // Need to executeSoon since the tree is initialized on sidebar load. + executeSoon(open_properties_dialog); + }, true); + SidebarUI.show(gCurrentTest.sidebar); +} + +function open_properties_dialog() { + var sidebar = document.getElementById("sidebar"); + + // If this is history sidebar, set the required view. + if (gCurrentTest.sidebar == SIDEBAR_HISTORY_ID) + sidebar.contentDocument.getElementById(gCurrentTest.historyView).doCommand(); + + // Get sidebar's Places tree. + var sidebarTreeID = gCurrentTest.sidebar == SIDEBAR_BOOKMARKS_ID ? + SIDEBAR_BOOKMARKS_TREE_ID : + SIDEBAR_HISTORY_TREE_ID; + var tree = sidebar.contentDocument.getElementById(sidebarTreeID); + ok(tree, "Sidebar tree has been loaded"); + + // Ask current test to select the node to edit. + gCurrentTest.selectNode(tree); + ok(tree.selectedNode, + "We have a places node selected: " + tree.selectedNode.title); + + // Wait for the Properties dialog. + function windowObserver(aSubject, aTopic, aData) { + if (aTopic != "domwindowopened") + return; + ww.unregisterNotification(windowObserver); + let win = aSubject.QueryInterface(Ci.nsIDOMWindow); + waitForFocus(() => { + // Windows has been loaded, execute our test now. + executeSoon(function () { + // Ensure overlay is loaded + ok(win.gEditItemOverlay.initialized, "EditItemOverlay is initialized"); + gCurrentTest.window = win; + try { + gCurrentTest.run(); + } catch (ex) { + ok(false, "An error occured during test run: " + ex.message); + } + }); + }, win); + } + ww.registerNotification(windowObserver); + + var command = null; + switch (gCurrentTest.action) { + case ACTION_EDIT: + command = "placesCmd_show:info"; + break; + case ACTION_ADD: + if (gCurrentTest.sidebar == SIDEBAR_BOOKMARKS_ID) { + if (gCurrentTest.itemType == TYPE_FOLDER) + command = "placesCmd_new:folder"; + else if (gCurrentTest.itemType == TYPE_BOOKMARK) + command = "placesCmd_new:bookmark"; + else + ok(false, "You didn't set a valid itemType for adding an item"); + } + else + command = "placesCmd_createBookmark"; + break; + default: + ok(false, "You didn't set a valid action for this test"); + } + // Ensure command is enabled for this node. + ok(tree.controller.isCommandEnabled(command), + " command '" + command + "' on current selected node is enabled"); + + // This will open the dialog. + tree.controller.doCommand(command); +} diff --git a/browser/components/places/tests/browser/browser_drag_bookmarks_on_toolbar.js b/browser/components/places/tests/browser/browser_drag_bookmarks_on_toolbar.js new file mode 100644 index 000000000..1ab9411f3 --- /dev/null +++ b/browser/components/places/tests/browser/browser_drag_bookmarks_on_toolbar.js @@ -0,0 +1,256 @@ +/* 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 TEST_URL = "http://www.mozilla.org"; +const TEST_TITLE = "example_title"; + +var gBookmarksToolbar = window.document.getElementById("PlacesToolbar"); +var dragDirections = { LEFT: 0, UP: 1, RIGHT: 2, DOWN: 3 }; + +/** + * Tests dragging on toolbar. + * + * We must test these 2 cases: + * - Dragging toward left, top, right should start a drag. + * - Dragging toward down should should open the container if the item is a + * container, drag the item otherwise. + * + * @param aElement + * DOM node element we will drag + * @param aExpectedDragData + * Array of flavors and values in the form: + * [ ["text/plain: sometext", "text/html: <b>sometext</b>"], [...] ] + * Pass an empty array to check that drag even has been canceled. + * @param aDirection + * Direction for the dragging gesture, see dragDirections helper object. + */ +function synthesizeDragWithDirection(aElement, aExpectedDragData, aDirection, aCallback) { + // Dragstart listener function. + gBookmarksToolbar.addEventListener("dragstart", function(event) + { + info("A dragstart event has been trapped."); + var dataTransfer = event.dataTransfer; + is(dataTransfer.mozItemCount, aExpectedDragData.length, + "Number of dragged items should be the same."); + + for (var t = 0; t < dataTransfer.mozItemCount; t++) { + var types = dataTransfer.mozTypesAt(t); + var expecteditem = aExpectedDragData[t]; + is(types.length, expecteditem.length, + "Number of flavors for item " + t + " should be the same."); + + for (var f = 0; f < types.length; f++) { + is(types[f], expecteditem[f].substring(0, types[f].length), + "Flavor " + types[f] + " for item " + t + " should be the same."); + is(dataTransfer.mozGetDataAt(types[f], t), + expecteditem[f].substring(types[f].length + 2), + "Contents for item " + t + " with flavor " + types[f] + " should be the same."); + } + } + + if (!aExpectedDragData.length) + ok(event.defaultPrevented, "Drag has been canceled."); + + event.preventDefault(); + event.stopPropagation(); + + gBookmarksToolbar.removeEventListener("dragstart", arguments.callee, false); + + // This is likely to cause a click event, and, in case we are dragging a + // bookmark, an unwanted page visit. Prevent the click event. + aElement.addEventListener("click", prevent, false); + EventUtils.synthesizeMouse(aElement, + startingPoint.x + xIncrement * 9, + startingPoint.y + yIncrement * 9, + { type: "mouseup" }); + aElement.removeEventListener("click", prevent, false); + + // Cleanup eventually opened menus. + if (aElement.localName == "menu" && aElement.open) + aElement.open = false; + aCallback() + }, false); + + var prevent = function(aEvent) { aEvent.preventDefault(); } + + var xIncrement = 0; + var yIncrement = 0; + + switch (aDirection) { + case dragDirections.LEFT: + xIncrement = -1; + break; + case dragDirections.RIGHT: + xIncrement = +1; + break; + case dragDirections.UP: + yIncrement = -1; + break; + case dragDirections.DOWN: + yIncrement = +1; + break; + } + + var rect = aElement.getBoundingClientRect(); + var startingPoint = { x: (rect.right - rect.left)/2, + y: (rect.bottom - rect.top)/2 }; + + EventUtils.synthesizeMouse(aElement, + startingPoint.x, + startingPoint.y, + { type: "mousedown" }); + EventUtils.synthesizeMouse(aElement, + startingPoint.x + xIncrement * 1, + startingPoint.y + yIncrement * 1, + { type: "mousemove" }); + EventUtils.synthesizeMouse(aElement, + startingPoint.x + xIncrement * 9, + startingPoint.y + yIncrement * 9, + { type: "mousemove" }); +} + +function getToolbarNodeForItemId(aItemId) { + var children = document.getElementById("PlacesToolbarItems").childNodes; + var node = null; + for (var i = 0; i < children.length; i++) { + if (aItemId == children[i]._placesNode.itemId) { + node = children[i]; + break; + } + } + return node; +} + +function getExpectedDataForPlacesNode(aNode) { + var wrappedNode = []; + var flavors = ["text/x-moz-place", + "text/x-moz-url", + "text/plain", + "text/html"]; + + flavors.forEach(function(aFlavor) { + var wrappedFlavor = aFlavor + ": " + + PlacesUtils.wrapNode(aNode, aFlavor); + wrappedNode.push(wrappedFlavor); + }); + + return [wrappedNode]; +} + +var gTests = [ + +// ------------------------------------------------------------------------------ + + { + desc: "Drag a folder on toolbar", + run: function() { + // Create a test folder to be dragged. + var folderId = PlacesUtils.bookmarks + .createFolder(PlacesUtils.toolbarFolderId, + TEST_TITLE, + PlacesUtils.bookmarks.DEFAULT_INDEX); + var element = getToolbarNodeForItemId(folderId); + isnot(element, null, "Found node on toolbar"); + + isnot(element._placesNode, null, "Toolbar node has an associated Places node."); + var expectedData = getExpectedDataForPlacesNode(element._placesNode); + + info("Dragging left"); + synthesizeDragWithDirection(element, expectedData, dragDirections.LEFT, + function () + { + info("Dragging right"); + synthesizeDragWithDirection(element, expectedData, dragDirections.RIGHT, + function () + { + info("Dragging up"); + synthesizeDragWithDirection(element, expectedData, dragDirections.UP, + function () + { + info("Dragging down"); + synthesizeDragWithDirection(element, new Array(), dragDirections.DOWN, + function () { + // Cleanup. + PlacesUtils.bookmarks.removeItem(folderId); + nextTest(); + }); + }); + }); + }); + } + }, + +// ------------------------------------------------------------------------------ + + { + desc: "Drag a bookmark on toolbar", + run: function() { + // Create a test bookmark to be dragged. + var itemId = PlacesUtils.bookmarks + .insertBookmark(PlacesUtils.toolbarFolderId, + PlacesUtils._uri(TEST_URL), + PlacesUtils.bookmarks.DEFAULT_INDEX, + TEST_TITLE); + var element = getToolbarNodeForItemId(itemId); + isnot(element, null, "Found node on toolbar"); + + isnot(element._placesNode, null, "Toolbar node has an associated Places node."); + var expectedData = getExpectedDataForPlacesNode(element._placesNode); + + info("Dragging left"); + synthesizeDragWithDirection(element, expectedData, dragDirections.LEFT, + function () + { + info("Dragging right"); + synthesizeDragWithDirection(element, expectedData, dragDirections.RIGHT, + function () + { + info("Dragging up"); + synthesizeDragWithDirection(element, expectedData, dragDirections.UP, + function () + { + info("Dragging down"); + synthesizeDragWithDirection(element, expectedData, dragDirections.DOWN, + function () { + // Cleanup. + PlacesUtils.bookmarks.removeItem(itemId); + nextTest(); + }); + }); + }); + }); + } + }, +]; + +function nextTest() { + if (gTests.length) { + var test = gTests.shift(); + waitForFocus(function() { + info("Start of test: " + test.desc); + test.run(); + }); + } + else if (wasCollapsed) { + // Collapse the personal toolbar if needed. + promiseSetToolbarVisibility(toolbar, false).then(finish); + } else { + finish(); + } +} + +var toolbar = document.getElementById("PersonalToolbar"); +var wasCollapsed = toolbar.collapsed; + +function test() { + waitForExplicitFinish(); + + // Uncollapse the personal toolbar if needed. + if (wasCollapsed) { + promiseSetToolbarVisibility(toolbar, true).then(nextTest); + } else { + nextTest(); + } +} + diff --git a/browser/components/places/tests/browser/browser_forgetthissite_single.js b/browser/components/places/tests/browser/browser_forgetthissite_single.js new file mode 100644 index 000000000..b1d7936e9 --- /dev/null +++ b/browser/components/places/tests/browser/browser_forgetthissite_single.js @@ -0,0 +1,78 @@ +/* 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/. */ + +"use strict"; + +const TEST_URIs = [ + "http://www.mozilla.org/test1", + "http://www.mozilla.org/test2" +]; + +// This test makes sure that the Forget This Site command is hidden for multiple +// selections. +add_task(function* () { + // Add a history entry. + ok(PlacesUtils, "checking PlacesUtils, running in chrome context?"); + + let places = []; + let transition = PlacesUtils.history.TRANSITION_TYPED; + TEST_URIs.forEach(uri => places.push({uri: PlacesUtils._uri(uri), transition})); + + yield PlacesTestUtils.addVisits(places); + yield testForgetThisSiteVisibility(1); + yield testForgetThisSiteVisibility(2); + + // Cleanup. + yield PlacesTestUtils.clearHistory(); +}); + +var testForgetThisSiteVisibility = Task.async(function* (selectionCount) { + let organizer = yield promiseLibrary(); + + // Select History in the left pane. + organizer.PlacesOrganizer.selectLeftPaneQuery("History"); + let PO = organizer.PlacesOrganizer; + let histContainer = PO._places.selectedNode.QueryInterface(Ci.nsINavHistoryContainerResultNode); + histContainer.containerOpen = true; + PO._places.selectNode(histContainer.getChild(0)); + + // Select the first history entry. + let doc = organizer.document; + let tree = doc.getElementById("placeContent"); + let selection = tree.view.selection; + selection.clearSelection(); + selection.rangedSelect(0, selectionCount - 1, true); + is(selection.count, selectionCount, "The selected range is as big as expected"); + + // Open the context menu. + let contextmenu = doc.getElementById("placesContext"); + let popupShown = promisePopupShown(contextmenu); + + // Get cell coordinates. + let rect = tree.treeBoxObject.getCoordsForCellItem(0, tree.columns[0], "text"); + // Initiate a context menu for the selected cell. + EventUtils.synthesizeMouse(tree.body, rect.x + rect.width / 2, rect.y + rect.height / 2, {type: "contextmenu", button: 2}, organizer); + yield popupShown; + + let forgetThisSite = doc.getElementById("placesContext_deleteHost"); + let hideForgetThisSite = (selectionCount != 1); + is(forgetThisSite.hidden, hideForgetThisSite, + `The Forget this site menu item should ${hideForgetThisSite ? "" : "not "}` + + ` be hidden with ${selectionCount} items selected`); + + // Close the context menu. + contextmenu.hidePopup(); + + // Close the library window. + yield promiseLibraryClosed(organizer); +}); + +function promisePopupShown(popup) { + return new Promise(resolve => { + popup.addEventListener("popupshown", function onShown() { + popup.removeEventListener("popupshown", onShown, true); + resolve(); + }, true); + }); +} diff --git a/browser/components/places/tests/browser/browser_history_sidebar_search.js b/browser/components/places/tests/browser/browser_history_sidebar_search.js new file mode 100644 index 000000000..89472c4ab --- /dev/null +++ b/browser/components/places/tests/browser/browser_history_sidebar_search.js @@ -0,0 +1,64 @@ +add_task(function* test () { + let sidebar = document.getElementById("sidebar"); + + // Visited pages listed by descending visit date. + let pages = [ + "http://sidebar.mozilla.org/a", + "http://sidebar.mozilla.org/b", + "http://sidebar.mozilla.org/c", + "http://www.mozilla.org/d", + ]; + + // Number of pages that will be filtered out by the search. + const FILTERED_COUNT = 1; + + yield PlacesTestUtils.clearHistory(); + + // Add some visited page. + let time = Date.now(); + let places = []; + for (let i = 0; i < pages.length; i++) { + places.push({ uri: NetUtil.newURI(pages[i]), + visitDate: (time - i) * 1000, + transition: PlacesUtils.history.TRANSITION_TYPED }); + } + yield PlacesTestUtils.addVisits(places); + + yield withSidebarTree("history", function* () { + info("Set 'by last visited' view"); + sidebar.contentDocument.getElementById("bylastvisited").doCommand(); + let tree = sidebar.contentDocument.getElementById("historyTree"); + check_tree_order(tree, pages); + + // Set a search value. + let searchBox = sidebar.contentDocument.getElementById("search-box"); + ok(searchBox, "search box is in context"); + searchBox.value = "sidebar.mozilla"; + searchBox.doCommand(); + check_tree_order(tree, pages, -FILTERED_COUNT); + + info("Reset the search"); + searchBox.value = ""; + searchBox.doCommand(); + check_tree_order(tree, pages); + }); + + yield PlacesTestUtils.clearHistory(); +}); + +function check_tree_order(tree, pages, aNumberOfRowsDelta = 0) { + let treeView = tree.view; + let columns = tree.columns; + is(columns.count, 1, "There should be only 1 column in the sidebar"); + + let found = 0; + for (let i = 0; i < treeView.rowCount; i++) { + let node = treeView.nodeForTreeIndex(i); + // We could inherit delayed visits from previous tests, skip them. + if (!pages.includes(node.uri)) + continue; + is(node.uri, pages[i], "Node is in correct position based on its visit date"); + found++; + } + ok(found, pages.length + aNumberOfRowsDelta, "Found all expected results"); +} diff --git a/browser/components/places/tests/browser/browser_library_batch_delete.js b/browser/components/places/tests/browser/browser_library_batch_delete.js new file mode 100644 index 000000000..6a907c70f --- /dev/null +++ b/browser/components/places/tests/browser/browser_library_batch_delete.js @@ -0,0 +1,114 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Tests that Library handles correctly batch deletes. + */ + +const TEST_URL = "http://www.batch.delete.me/"; + +var gTests = []; +var gLibrary; + +// ------------------------------------------------------------------------------ + +gTests.push({ + desc: "Create and batch remove bookmarks", + run: function() { + let testURI = makeURI(TEST_URL); + PlacesUtils.history.runInBatchMode({ + runBatched: function (aUserData) { + // Create a folder in unserted and populate it with bookmarks. + let folder = PlacesUtils.bookmarks.createFolder( + PlacesUtils.unfiledBookmarksFolderId, "deleteme", + PlacesUtils.bookmarks.DEFAULT_INDEX + ); + PlacesUtils.bookmarks.createFolder( + PlacesUtils.unfiledBookmarksFolderId, "keepme", + PlacesUtils.bookmarks.DEFAULT_INDEX + ); + for (let i = 0; i < 10; i++) { + PlacesUtils.bookmarks.insertBookmark(folder, + testURI, + PlacesUtils.bookmarks.DEFAULT_INDEX, + "bm" + i); + } + } + }, null); + + // Select and open the left pane "History" query. + let PO = gLibrary.PlacesOrganizer; + PO.selectLeftPaneQuery("UnfiledBookmarks"); + isnot(PO._places.selectedNode, null, "Selected unsorted bookmarks"); + + let unsortedNode = PlacesUtils.asContainer(PO._places.selectedNode); + unsortedNode.containerOpen = true; + is(unsortedNode.childCount, 2, "Unsorted node has 2 children"); + let folderNode = unsortedNode.getChild(0); + is(folderNode.title, "deleteme", "Folder found in unsorted bookmarks"); + // Check delete command is available. + PO._places.selectNode(folderNode); + is(PO._places.selectedNode.title, "deleteme", "Folder node selected"); + ok(PO._places.controller.isCommandEnabled("cmd_delete"), + "Delete command is enabled"); + // Execute the delete command and check bookmark has been removed. + PO._places.controller.doCommand("cmd_delete"); + ok(!PlacesUtils.bookmarks.isBookmarked(testURI), + "Bookmark has been correctly removed"); + // Test live update. + is(unsortedNode.childCount, 1, "Unsorted node has 1 child"); + is(PO._places.selectedNode.title, "keepme", "Folder node selected"); + unsortedNode.containerOpen = false; + nextTest(); + } +}); + +// ------------------------------------------------------------------------------ + +gTests.push({ + desc: "Ensure correct selection and functionality in Library", + run: function() { + let PO = gLibrary.PlacesOrganizer; + let ContentTree = gLibrary.ContentTree; + // Move selection forth and back. + PO.selectLeftPaneQuery("History"); + PO.selectLeftPaneQuery("UnfiledBookmarks"); + // Now select the "keepme" folder in the right pane and delete it. + ContentTree.view.selectNode(ContentTree.view.result.root.getChild(0)); + is(ContentTree.view.selectedNode.title, "keepme", + "Found folder in content pane"); + // Test live update. + PlacesUtils.bookmarks.insertBookmark(PlacesUtils.unfiledBookmarksFolderId, + makeURI(TEST_URL), + PlacesUtils.bookmarks.DEFAULT_INDEX, + "bm"); + is(ContentTree.view.result.root.childCount, 2, + "Right pane was correctly updated"); + nextTest(); + } +}); + +// ------------------------------------------------------------------------------ + +function test() { + waitForExplicitFinish(); + registerCleanupFunction(function () { + PlacesUtils.bookmarks + .removeFolderChildren(PlacesUtils.unfiledBookmarksFolderId); + }); + + gLibrary = openLibrary(nextTest); +} + +function nextTest() { + if (gTests.length) { + var test = gTests.shift(); + info("Start of test: " + test.desc); + test.run(); + } + else { + // Close Library window. + gLibrary.close(); + finish(); + } +} diff --git a/browser/components/places/tests/browser/browser_library_commands.js b/browser/components/places/tests/browser/browser_library_commands.js new file mode 100644 index 000000000..e3bb75a34 --- /dev/null +++ b/browser/components/places/tests/browser/browser_library_commands.js @@ -0,0 +1,235 @@ +/* -*- 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 enabled commands in the left pane folder of the Library. + */ + +const TEST_URI = NetUtil.newURI("http://www.mozilla.org/"); + +registerCleanupFunction(function* () { + yield PlacesUtils.bookmarks.eraseEverything(); + yield PlacesTestUtils.clearHistory(); +}); + +add_task(function* test_date_container() { + let library = yield promiseLibrary(); + info("Ensure date containers under History cannot be cut but can be deleted"); + + yield PlacesTestUtils.addVisits(TEST_URI); + + // Select and open the left pane "History" query. + let PO = library.PlacesOrganizer; + + PO.selectLeftPaneQuery('History'); + isnot(PO._places.selectedNode, null, "We correctly selected History"); + + // Check that both delete and cut commands are disabled, cause this is + // a child of the left pane folder. + ok(PO._places.controller.isCommandEnabled("cmd_copy"), + "Copy command is enabled"); + ok(!PO._places.controller.isCommandEnabled("cmd_cut"), + "Cut command is disabled"); + ok(!PO._places.controller.isCommandEnabled("cmd_delete"), + "Delete command is disabled"); + let historyNode = PlacesUtils.asContainer(PO._places.selectedNode); + historyNode.containerOpen = true; + + // Check that we have a child container. It is "Today" container. + is(historyNode.childCount, 1, "History node has one child"); + let todayNode = historyNode.getChild(0); + let todayNodeExpectedTitle = PlacesUtils.getString("finduri-AgeInDays-is-0"); + is(todayNode.title, todayNodeExpectedTitle, + "History child is the expected container"); + + // Select "Today" container. + PO._places.selectNode(todayNode); + is(PO._places.selectedNode, todayNode, + "We correctly selected Today container"); + // Check that delete command is enabled but cut command is disabled, cause + // this is an history item. + ok(PO._places.controller.isCommandEnabled("cmd_copy"), + "Copy command is enabled"); + ok(!PO._places.controller.isCommandEnabled("cmd_cut"), + "Cut command is disabled"); + ok(PO._places.controller.isCommandEnabled("cmd_delete"), + "Delete command is enabled"); + + // Execute the delete command and check visit has been removed. + let promiseURIRemoved = promiseHistoryNotification("onDeleteURI", + v => TEST_URI.equals(v)); + PO._places.controller.doCommand("cmd_delete"); + yield promiseURIRemoved; + + // Test live update of "History" query. + is(historyNode.childCount, 0, "History node has no more children"); + + historyNode.containerOpen = false; + + ok(!(yield promiseIsURIVisited(TEST_URI)), "Visit has been removed"); + + library.close(); +}); + +add_task(function* test_query_on_toolbar() { + let library = yield promiseLibrary(); + info("Ensure queries can be cut or deleted"); + + // Select and open the left pane "Bookmarks Toolbar" folder. + let PO = library.PlacesOrganizer; + + PO.selectLeftPaneQuery('BookmarksToolbar'); + isnot(PO._places.selectedNode, null, "We have a valid selection"); + is(PlacesUtils.getConcreteItemId(PO._places.selectedNode), + PlacesUtils.toolbarFolderId, + "We have correctly selected bookmarks toolbar node."); + + // Check that both cut and delete commands are disabled, cause this is a child + // of AllBookmarksFolderId. + ok(PO._places.controller.isCommandEnabled("cmd_copy"), + "Copy command is enabled"); + ok(!PO._places.controller.isCommandEnabled("cmd_cut"), + "Cut command is disabled"); + ok(!PO._places.controller.isCommandEnabled("cmd_delete"), + "Delete command is disabled"); + + let toolbarNode = PlacesUtils.asContainer(PO._places.selectedNode); + toolbarNode.containerOpen = true; + + // Add an History query to the toolbar. + let query = yield PlacesUtils.bookmarks.insert({ type: PlacesUtils.bookmarks.TYPE_BOOKMARK, + url: "place:sort=4", + title: "special_query", + parentGuid: PlacesUtils.bookmarks.toolbarGuid, + index: 0 }); + + // Get first child and check it is the just inserted query. + ok(toolbarNode.childCount > 0, "Toolbar node has children"); + let queryNode = toolbarNode.getChild(0); + is(queryNode.title, "special_query", "Query node is correctly selected"); + + // Select query node. + PO._places.selectNode(queryNode); + is(PO._places.selectedNode, queryNode, "We correctly selected query node"); + + // Check that both cut and delete commands are enabled. + ok(PO._places.controller.isCommandEnabled("cmd_copy"), + "Copy command is enabled"); + ok(PO._places.controller.isCommandEnabled("cmd_cut"), + "Cut command is enabled"); + ok(PO._places.controller.isCommandEnabled("cmd_delete"), + "Delete command is enabled"); + + // Execute the delete command and check bookmark has been removed. + let promiseItemRemoved = promiseBookmarksNotification("onItemRemoved", + (...args) => query.guid == args[5]); + PO._places.controller.doCommand("cmd_delete"); + yield promiseItemRemoved; + + is((yield PlacesUtils.bookmarks.fetch(query.guid)), null, + "Query node bookmark has been correctly removed"); + + toolbarNode.containerOpen = false; + + library.close(); +}); + +add_task(function* test_search_contents() { + yield PlacesUtils.bookmarks.insert({ type: PlacesUtils.bookmarks.TYPE_BOOKMARK, + url: "http://example.com/", + title: "example page", + parentGuid: PlacesUtils.bookmarks.unfiledGuid, + index: 0 }); + + let library = yield promiseLibrary(); + info("Ensure query contents can be cut or deleted"); + + // Select and open the left pane "Bookmarks Toolbar" folder. + let PO = library.PlacesOrganizer; + + PO.selectLeftPaneQuery('BookmarksToolbar'); + isnot(PO._places.selectedNode, null, "We have a valid selection"); + is(PlacesUtils.getConcreteItemId(PO._places.selectedNode), + PlacesUtils.toolbarFolderId, + "We have correctly selected bookmarks toolbar node."); + + let searchBox = library.document.getElementById("searchFilter"); + searchBox.value = "example"; + library.PlacesSearchBox.search(searchBox.value); + + let bookmarkNode = library.ContentTree.view.selectedNode; + is(bookmarkNode.uri, "http://example.com/", "Found the expected bookmark"); + + // Check that both cut and delete commands are enabled. + ok(library.ContentTree.view.controller.isCommandEnabled("cmd_copy"), + "Copy command is enabled"); + ok(library.ContentTree.view.controller.isCommandEnabled("cmd_cut"), + "Cut command is enabled"); + ok(library.ContentTree.view.controller.isCommandEnabled("cmd_delete"), + "Delete command is enabled"); + + library.close(); +}); + +add_task(function* test_tags() { + yield PlacesUtils.bookmarks.insert({ type: PlacesUtils.bookmarks.TYPE_BOOKMARK, + url: "http://example.com/", + title: "example page", + parentGuid: PlacesUtils.bookmarks.unfiledGuid, + index: 0 }); + PlacesUtils.tagging.tagURI(NetUtil.newURI("http://example.com/"), ["test"]); + + let library = yield promiseLibrary(); + info("Ensure query contents can be cut or deleted"); + + // Select and open the left pane "Bookmarks Toolbar" folder. + let PO = library.PlacesOrganizer; + + PO.selectLeftPaneQuery('Tags'); + let tagsNode = PO._places.selectedNode; + isnot(tagsNode, null, "We have a valid selection"); + let tagsTitle = PlacesUtils.getString("TagsFolderTitle"); + is(tagsNode.title, tagsTitle, + "Tags has been properly selected"); + + // Check that both cut and delete commands are disabled. + ok(PO._places.controller.isCommandEnabled("cmd_copy"), + "Copy command is enabled"); + ok(!PO._places.controller.isCommandEnabled("cmd_cut"), + "Cut command is disabled"); + ok(!PO._places.controller.isCommandEnabled("cmd_delete"), + "Delete command is disabled"); + + // Now select the tag. + PlacesUtils.asContainer(tagsNode).containerOpen = true; + let tag = tagsNode.getChild(0); + PO._places.selectNode(tag); + is(PO._places.selectedNode.title, "test", + "The created tag has been properly selected"); + + // Check that cut is disabled but delete is enabled. + ok(PO._places.controller.isCommandEnabled("cmd_copy"), + "Copy command is enabled"); + ok(!PO._places.controller.isCommandEnabled("cmd_cut"), + "Cut command is disabled"); + ok(PO._places.controller.isCommandEnabled("cmd_delete"), + "Delete command is enabled"); + + let bookmarkNode = library.ContentTree.view.selectedNode; + is(bookmarkNode.uri, "http://example.com/", "Found the expected bookmark"); + + // Check that both cut and delete commands are enabled. + ok(library.ContentTree.view.controller.isCommandEnabled("cmd_copy"), + "Copy command is enabled"); + ok(!library.ContentTree.view.controller.isCommandEnabled("cmd_cut"), + "Cut command is disabled"); + ok(library.ContentTree.view.controller.isCommandEnabled("cmd_delete"), + "Delete command is enabled"); + + tagsNode.containerOpen = false; + + library.close(); +}); diff --git a/browser/components/places/tests/browser/browser_library_downloads.js b/browser/components/places/tests/browser/browser_library_downloads.js new file mode 100644 index 000000000..81daadd71 --- /dev/null +++ b/browser/components/places/tests/browser/browser_library_downloads.js @@ -0,0 +1,70 @@ +/* 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/. */ + +/* + * Tests bug 564900: Add folder specifically for downloads to Library left pane. + * https://bugzilla.mozilla.org/show_bug.cgi?id=564900 + * This test visits various pages then opens the Library and ensures + * that both the Downloads folder shows up and that the correct visits + * are shown in it. + */ + +var now = Date.now(); + +function test() { + waitForExplicitFinish(); + + let onLibraryReady = function(win) { + // Add visits to compare contents with. + let places = [ + { uri: NetUtil.newURI("http://mozilla.com"), + visits: [ new VisitInfo(PlacesUtils.history.TRANSITION_TYPED) ] + }, + { uri: NetUtil.newURI("http://google.com"), + visits: [ new VisitInfo(PlacesUtils.history.TRANSITION_DOWNLOAD) ] + }, + { uri: NetUtil.newURI("http://en.wikipedia.org"), + visits: [ new VisitInfo(PlacesUtils.history.TRANSITION_TYPED) ] + }, + { uri: NetUtil.newURI("http://ubuntu.org"), + visits: [ new VisitInfo(PlacesUtils.history.TRANSITION_DOWNLOAD) ] + }, + ] + PlacesUtils.asyncHistory.updatePlaces(places, { + handleResult: function () {}, + handleError: function () { + ok(false, "gHistory.updatePlaces() failed"); + }, + handleCompletion: function () { + // Make sure Downloads is present. + isnot(win.PlacesOrganizer._places.selectedNode, null, + "Downloads is present and selected"); + + + // Check results. + let contentRoot = win.ContentArea.currentView.result.root; + let len = contentRoot.childCount; + const TEST_URIS = ["http://ubuntu.org/", "http://google.com/"]; + for (let i = 0; i < len; i++) { + is(contentRoot.getChild(i).uri, TEST_URIS[i], + "Comparing downloads shown at index " + i); + } + + win.close(); + PlacesTestUtils.clearHistory().then(finish); + } + }) + } + + openLibrary(onLibraryReady, "Downloads"); +} + +function VisitInfo(aTransitionType) +{ + this.transitionType = + aTransitionType === undefined ? + PlacesUtils.history.TRANSITION_LINK : aTransitionType; + this.visitDate = now++ * 1000; +} +VisitInfo.prototype = {} diff --git a/browser/components/places/tests/browser/browser_library_infoBox.js b/browser/components/places/tests/browser/browser_library_infoBox.js new file mode 100644 index 000000000..17cd78f8c --- /dev/null +++ b/browser/components/places/tests/browser/browser_library_infoBox.js @@ -0,0 +1,197 @@ +/* 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 appropriate visibility of infoBoxExpanderWrapper and + * additionalInfoFields in infoBox section of library + */ + +const TEST_URI = "http://www.mozilla.org/"; + +var gTests = []; +var gLibrary; + +// ------------------------------------------------------------------------------ + +gTests.push({ + desc: "Bug 430148 - Remove or hide the more/less button in details pane...", + run: function() { + var PO = gLibrary.PlacesOrganizer; + let ContentTree = gLibrary.ContentTree; + var infoBoxExpanderWrapper = getAndCheckElmtById("infoBoxExpanderWrapper"); + + function addVisitsCallback() { + // open all bookmarks node + PO.selectLeftPaneQuery("AllBookmarks"); + isnot(PO._places.selectedNode, null, + "Correctly selected all bookmarks node."); + checkInfoBoxSelected(PO); + ok(infoBoxExpanderWrapper.hidden, + "Expander button is hidden for all bookmarks node."); + checkAddInfoFieldsCollapsed(PO); + + // open history node + PO.selectLeftPaneQuery("History"); + isnot(PO._places.selectedNode, null, "Correctly selected history node."); + checkInfoBoxSelected(PO); + ok(infoBoxExpanderWrapper.hidden, + "Expander button is hidden for history node."); + checkAddInfoFieldsCollapsed(PO); + + // open history child node + var historyNode = PO._places.selectedNode. + QueryInterface(Ci.nsINavHistoryContainerResultNode); + historyNode.containerOpen = true; + var childNode = historyNode.getChild(0); + isnot(childNode, null, "History node first child is not null."); + PO._places.selectNode(childNode); + checkInfoBoxSelected(PO); + ok(infoBoxExpanderWrapper.hidden, + "Expander button is hidden for history child node."); + checkAddInfoFieldsCollapsed(PO); + + // open history item + var view = ContentTree.view.view; + ok(view.rowCount > 0, "History item exists."); + view.selection.select(0); + ok(infoBoxExpanderWrapper.hidden, + "Expander button is hidden for history item."); + checkAddInfoFieldsCollapsed(PO); + + historyNode.containerOpen = false; + + // open bookmarks menu node + PO.selectLeftPaneQuery("BookmarksMenu"); + isnot(PO._places.selectedNode, null, + "Correctly selected bookmarks menu node."); + checkInfoBoxSelected(PO); + ok(infoBoxExpanderWrapper.hidden, + "Expander button is hidden for bookmarks menu node."); + checkAddInfoFieldsCollapsed(PO); + + // open recently bookmarked node + PlacesUtils.bookmarks.insertBookmark(PlacesUtils.bookmarksMenuFolderId, + NetUtil.newURI("place:folder=BOOKMARKS_MENU" + + "&folder=UNFILED_BOOKMARKS" + + "&folder=TOOLBAR" + + "&queryType=" + Ci.nsINavHistoryQueryOptions.QUERY_TYPE_BOOKMARKS + + "&sort=" + Ci.nsINavHistoryQueryOptions.SORT_BY_DATEADDED_DESCENDING + + "&maxResults=10" + + "&excludeQueries=1"), + 0, "Recent Bookmarks"); + PlacesUtils.bookmarks.insertBookmark(PlacesUtils.bookmarksMenuFolderId, + NetUtil.newURI("http://mozilla.org/"), + 1, "Mozilla"); + var menuNode = PO._places.selectedNode. + QueryInterface(Ci.nsINavHistoryContainerResultNode); + menuNode.containerOpen = true; + childNode = menuNode.getChild(0); + isnot(childNode, null, "Bookmarks menu child node exists."); + is(childNode.title, "Recent Bookmarks", + "Correctly selected recently bookmarked node."); + PO._places.selectNode(childNode); + checkInfoBoxSelected(PO); + ok(!infoBoxExpanderWrapper.hidden, + "Expander button is not hidden for recently bookmarked node."); + checkAddInfoFieldsNotCollapsed(PO); + + // open first bookmark + view = ContentTree.view.view; + ok(view.rowCount > 0, "Bookmark item exists."); + view.selection.select(0); + checkInfoBoxSelected(PO); + ok(!infoBoxExpanderWrapper.hidden, + "Expander button is not hidden for bookmark item."); + checkAddInfoFieldsNotCollapsed(PO); + checkAddInfoFields(PO, "bookmark item"); + + menuNode.containerOpen = false; + + PlacesTestUtils.clearHistory().then(nextTest); + } + // add a visit to browser history + PlacesTestUtils.addVisits( + { uri: PlacesUtils._uri(TEST_URI), visitDate: Date.now() * 1000, + transition: PlacesUtils.history.TRANSITION_TYPED } + ).then(addVisitsCallback); + } +}); + +function checkInfoBoxSelected(PO) { + is(getAndCheckElmtById("detailsDeck").selectedIndex, 1, + "Selected element in detailsDeck is infoBox."); +} + +function checkAddInfoFieldsCollapsed(PO) { + PO._additionalInfoFields.forEach(function (id) { + ok(getAndCheckElmtById(id).collapsed, + "Additional info field correctly collapsed: #" + id); + }); +} + +function checkAddInfoFieldsNotCollapsed(PO) { + ok(PO._additionalInfoFields.some(function (id) { + return !getAndCheckElmtById(id).collapsed; + }), "Some additional info field correctly not collapsed"); +} + +function checkAddInfoFields(PO, nodeName) { + ok(true, "Checking additional info fields visibiity for node: " + nodeName); + var expanderButton = getAndCheckElmtById("infoBoxExpander"); + + // make sure additional fields are hidden by default + PO._additionalInfoFields.forEach(function (id) { + ok(getAndCheckElmtById(id).hidden, + "Additional info field correctly hidden by default: #" + id); + }); + + // toggle fields and make sure they are hidden/unhidden as expected + expanderButton.click(); + PO._additionalInfoFields.forEach(function (id) { + ok(!getAndCheckElmtById(id).hidden, + "Additional info field correctly unhidden after toggle: #" + id); + }); + expanderButton.click(); + PO._additionalInfoFields.forEach(function (id) { + ok(getAndCheckElmtById(id).hidden, + "Additional info field correctly hidden after toggle: #" + id); + }); +} + +function getAndCheckElmtById(id) { + var elmt = gLibrary.document.getElementById(id); + isnot(elmt, null, "Correctly got element: #" + id); + return elmt; +} + +// ------------------------------------------------------------------------------ + +function nextTest() { + if (gTests.length) { + var test = gTests.shift(); + ok(true, "TEST: " + test.desc); + dump("TEST: " + test.desc + "\n"); + test.run(); + } + else { + // Close Library window. + gLibrary.close(); + // No need to cleanup anything, we have a correct left pane now. + finish(); + } +} + +function test() { + waitForExplicitFinish(); + // Sanity checks. + ok(PlacesUtils, "PlacesUtils is running in chrome context"); + ok(PlacesUIUtils, "PlacesUIUtils is running in chrome context"); + + // Open Library. + openLibrary(function (library) { + gLibrary = library; + gLibrary.PlacesOrganizer._places.focus(); + nextTest(gLibrary); + }); +} diff --git a/browser/components/places/tests/browser/browser_library_left_pane_fixnames.js b/browser/components/places/tests/browser/browser_library_left_pane_fixnames.js new file mode 100644 index 000000000..7cea38f20 --- /dev/null +++ b/browser/components/places/tests/browser/browser_library_left_pane_fixnames.js @@ -0,0 +1,94 @@ +/* -*- 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 we correctly fix broken Library left pane queries names. + */ + +// Array of left pane queries objects, each one has the following properties: +// name: query's identifier got from annotations, +// itemId: query's itemId, +// correctTitle: original and correct query's title. +var leftPaneQueries = []; + +function onLibraryReady(organizer) { + // Check titles have been fixed. + for (var i = 0; i < leftPaneQueries.length; i++) { + var query = leftPaneQueries[i]; + is(PlacesUtils.bookmarks.getItemTitle(query.itemId), + query.correctTitle, "Title is correct for query " + query.name); + if ("concreteId" in query) { + is(PlacesUtils.bookmarks.getItemTitle(query.concreteId), + query.concreteTitle, "Concrete title is correct for query " + query.name); + } + } + + // Close Library window. + organizer.close(); + // No need to cleanup anything, we have a correct left pane now. + finish(); +} + +function test() { + waitForExplicitFinish(); + // Sanity checks. + ok(PlacesUtils, "PlacesUtils is running in chrome context"); + ok(PlacesUIUtils, "PlacesUIUtils is running in chrome context"); + ok(PlacesUIUtils.ORGANIZER_LEFTPANE_VERSION > 0, + "Left pane version in chrome context, current version is: " + PlacesUIUtils.ORGANIZER_LEFTPANE_VERSION ); + + // Ensure left pane is initialized. + ok(PlacesUIUtils.leftPaneFolderId > 0, "left pane folder is initialized"); + + // Get the left pane folder. + var leftPaneItems = PlacesUtils.annotations + .getItemsWithAnnotation(PlacesUIUtils.ORGANIZER_FOLDER_ANNO); + + is(leftPaneItems.length, 1, "We correctly have only 1 left pane folder"); + // Check version. + var version = PlacesUtils.annotations + .getItemAnnotation(leftPaneItems[0], + PlacesUIUtils.ORGANIZER_FOLDER_ANNO); + is(version, PlacesUIUtils.ORGANIZER_LEFTPANE_VERSION, "Left pane version is actual"); + + // Get all left pane queries. + var items = PlacesUtils.annotations + .getItemsWithAnnotation(PlacesUIUtils.ORGANIZER_QUERY_ANNO); + // Get current queries names. + for (var i = 0; i < items.length; i++) { + var itemId = items[i]; + var queryName = PlacesUtils.annotations + .getItemAnnotation(items[i], + PlacesUIUtils.ORGANIZER_QUERY_ANNO); + var query = { name: queryName, + itemId: itemId, + correctTitle: PlacesUtils.bookmarks.getItemTitle(itemId) } + switch (queryName) { + case "BookmarksToolbar": + query.concreteId = PlacesUtils.toolbarFolderId; + query.concreteTitle = PlacesUtils.bookmarks.getItemTitle(query.concreteId); + break; + case "BookmarksMenu": + query.concreteId = PlacesUtils.bookmarksMenuFolderId; + query.concreteTitle = PlacesUtils.bookmarks.getItemTitle(query.concreteId); + break; + case "UnfiledBookmarks": + query.concreteId = PlacesUtils.unfiledBookmarksFolderId; + query.concreteTitle = PlacesUtils.bookmarks.getItemTitle(query.concreteId); + break; + } + leftPaneQueries.push(query); + // Rename to a bad title. + PlacesUtils.bookmarks.setItemTitle(query.itemId, "badName"); + if ("concreteId" in query) + PlacesUtils.bookmarks.setItemTitle(query.concreteId, "badName"); + } + + PlacesUIUtils.__defineGetter__("leftPaneFolderId", cachedLeftPaneFolderIdGetter); + + // Open Library, this will kick-off left pane code. + openLibrary(onLibraryReady); +} diff --git a/browser/components/places/tests/browser/browser_library_left_pane_select_hierarchy.js b/browser/components/places/tests/browser/browser_library_left_pane_select_hierarchy.js new file mode 100644 index 000000000..b90df120c --- /dev/null +++ b/browser/components/places/tests/browser/browser_library_left_pane_select_hierarchy.js @@ -0,0 +1,41 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +function test() { + waitForExplicitFinish(); + openLibrary(onLibraryReady); +} + +function onLibraryReady(aLibrary) { + let hierarchy = [ "AllBookmarks", "BookmarksMenu" ]; + + let folder1 = PlacesUtils.bookmarks + .createFolder(PlacesUtils.bookmarksMenuFolderId, + "Folder 1", + PlacesUtils.bookmarks.DEFAULT_INDEX); + hierarchy.push(folder1); + let folder2 = PlacesUtils.bookmarks + .createFolder(folder1, "Folder 2", + PlacesUtils.bookmarks.DEFAULT_INDEX); + hierarchy.push(folder2); + let bookmark = PlacesUtils.bookmarks + .insertBookmark(folder2, NetUtil.newURI("http://example.com/"), + PlacesUtils.bookmarks.DEFAULT_INDEX, + "Bookmark"); + + registerCleanupFunction(function() { + PlacesUtils.bookmarks.removeItem(folder1); + aLibrary.close(); + }); + + aLibrary.PlacesOrganizer.selectLeftPaneContainerByHierarchy(hierarchy); + + is(aLibrary.PlacesOrganizer._places.selectedNode.itemId, folder2, + "Found the expected left pane selected node"); + + is(aLibrary.ContentTree.view.view.nodeForTreeIndex(0).itemId, bookmark, + "Found the expected right pane contents"); + + finish(); +} diff --git a/browser/components/places/tests/browser/browser_library_middleclick.js b/browser/components/places/tests/browser/browser_library_middleclick.js new file mode 100644 index 000000000..894f89446 --- /dev/null +++ b/browser/components/places/tests/browser/browser_library_middleclick.js @@ -0,0 +1,279 @@ +/* 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/. */ + + /** + * Tests middle-clicking items in the Library. + */ + +const ENABLE_HISTORY_PREF = "places.history.enabled"; + +var gLibrary = null; +var gTests = []; +var gCurrentTest = null; + +// Listener for TabOpen and tabs progress. +var gTabsListener = { + _loadedURIs: [], + _openTabsCount: 0, + + handleEvent: function(aEvent) { + if (aEvent.type != "TabOpen") + return; + + if (++this._openTabsCount == gCurrentTest.URIs.length) { + is(gBrowser.tabs.length, gCurrentTest.URIs.length + 1, + "We have opened " + gCurrentTest.URIs.length + " new tab(s)"); + } + + var tab = aEvent.target; + is(tab.ownerGlobal, window, + "Tab has been opened in current browser window"); + }, + + onLocationChange: function(aBrowser, aWebProgress, aRequest, aLocationURI, + aFlags) { + var spec = aLocationURI.spec; + ok(true, spec); + // When a new tab is opened, location is first set to "about:blank", so + // we can ignore those calls. + // Ignore multiple notifications for the same URI too. + if (spec == "about:blank" || this._loadedURIs.includes(spec)) + return; + + ok(gCurrentTest.URIs.includes(spec), + "Opened URI found in list: " + spec); + + if (gCurrentTest.URIs.includes(spec)) + this._loadedURIs.push(spec); + + if (this._loadedURIs.length == gCurrentTest.URIs.length) { + // We have correctly opened all URIs. + + // Reset arrays. + this._loadedURIs.length = 0; + + this._openTabsCount = 0; + + executeSoon(function () { + // Close all tabs. + while (gBrowser.tabs.length > 1) + gBrowser.removeCurrentTab(); + + // Test finished. This will move to the next one. + waitForFocus(gCurrentTest.finish, gBrowser.ownerGlobal); + }); + } + } +} + +// ------------------------------------------------------------------------------ +// Open bookmark in a new tab. + +gTests.push({ + desc: "Open bookmark in a new tab.", + URIs: ["about:buildconfig"], + _itemId: -1, + + setup: function() { + var bs = PlacesUtils.bookmarks; + // Add a new unsorted bookmark. + this._itemId = bs.insertBookmark(bs.unfiledBookmarksFolder, + PlacesUtils._uri(this.URIs[0]), + bs.DEFAULT_INDEX, + "Title"); + // Select unsorted bookmarks root in the left pane. + gLibrary.PlacesOrganizer.selectLeftPaneQuery("UnfiledBookmarks"); + isnot(gLibrary.PlacesOrganizer._places.selectedNode, null, + "We correctly have selection in the Library left pane"); + // Get our bookmark in the right pane. + var bookmarkNode = gLibrary.ContentTree.view.view.nodeForTreeIndex(0); + is(bookmarkNode.uri, this.URIs[0], "Found bookmark in the right pane"); + }, + + finish: function() { + setTimeout(runNextTest, 0); + }, + + cleanup: function() { + PlacesUtils.bookmarks.removeItem(this._itemId); + } +}); + +// ------------------------------------------------------------------------------ +// Open a folder in tabs. + +gTests.push({ + desc: "Open a folder in tabs.", + URIs: ["about:buildconfig", "about:"], + _folderId: -1, + + setup: function() { + var bs = PlacesUtils.bookmarks; + // Create a new folder. + var folderId = bs.createFolder(bs.unfiledBookmarksFolder, + "Folder", + bs.DEFAULT_INDEX); + this._folderId = folderId; + + // Add bookmarks in folder. + this.URIs.forEach(function(aURI) { + bs.insertBookmark(folderId, + PlacesUtils._uri(aURI), + bs.DEFAULT_INDEX, + "Title"); + }); + + // Select unsorted bookmarks root in the left pane. + gLibrary.PlacesOrganizer.selectLeftPaneQuery("UnfiledBookmarks"); + isnot(gLibrary.PlacesOrganizer._places.selectedNode, null, + "We correctly have selection in the Library left pane"); + // Get our bookmark in the right pane. + var folderNode = gLibrary.ContentTree.view.view.nodeForTreeIndex(0); + is(folderNode.title, "Folder", "Found folder in the right pane"); + }, + + finish: function() { + setTimeout(runNextTest, 0); + }, + + cleanup: function() { + PlacesUtils.bookmarks.removeItem(this._folderId); + } +}); + +// ------------------------------------------------------------------------------ +// Open a query in tabs. + +gTests.push({ + desc: "Open a query in tabs.", + URIs: ["about:buildconfig", "about:"], + _folderId: -1, + _queryId: -1, + + setup: function() { + var bs = PlacesUtils.bookmarks; + // Create a new folder. + var folderId = bs.createFolder(bs.unfiledBookmarksFolder, + "Folder", + bs.DEFAULT_INDEX); + this._folderId = folderId; + + // Add bookmarks in folder. + this.URIs.forEach(function(aURI) { + bs.insertBookmark(folderId, + PlacesUtils._uri(aURI), + bs.DEFAULT_INDEX, + "Title"); + }); + + // Create a bookmarks query containing our bookmarks. + var hs = PlacesUtils.history; + var options = hs.getNewQueryOptions(); + options.queryType = Ci.nsINavHistoryQueryOptions.QUERY_TYPE_BOOKMARKS; + var query = hs.getNewQuery(); + // The colon included in the terms selects only about: URIs. If not included + // we also may get pages like about.html included in the query result. + query.searchTerms = "about:"; + var queryString = hs.queriesToQueryString([query], 1, options); + this._queryId = bs.insertBookmark(bs.unfiledBookmarksFolder, + PlacesUtils._uri(queryString), + 0, // It must be the first. + "Query"); + + // Select unsorted bookmarks root in the left pane. + gLibrary.PlacesOrganizer.selectLeftPaneQuery("UnfiledBookmarks"); + isnot(gLibrary.PlacesOrganizer._places.selectedNode, null, + "We correctly have selection in the Library left pane"); + // Get our bookmark in the right pane. + var folderNode = gLibrary.ContentTree.view.view.nodeForTreeIndex(0); + is(folderNode.title, "Query", "Found query in the right pane"); + }, + + finish: function() { + setTimeout(runNextTest, 0); + }, + + cleanup: function() { + PlacesUtils.bookmarks.removeItem(this._folderId); + PlacesUtils.bookmarks.removeItem(this._queryId); + } +}); + +// ------------------------------------------------------------------------------ + +function test() { + waitForExplicitFinish(); + // Increase timeout, this test can be quite slow due to waitForFocus calls. + requestLongerTimeout(2); + + // Sanity checks. + ok(PlacesUtils, "PlacesUtils in context"); + ok(PlacesUIUtils, "PlacesUIUtils in context"); + + // Add tabs listeners. + gBrowser.tabContainer.addEventListener("TabOpen", gTabsListener, false); + gBrowser.addTabsProgressListener(gTabsListener); + + // Temporary disable history, so we won't record pages navigation. + gPrefService.setBoolPref(ENABLE_HISTORY_PREF, false); + + // Open Library window. + openLibrary(function (library) { + gLibrary = library; + // Kick off tests. + runNextTest(); + }); +} + +function runNextTest() { + // Cleanup from previous test. + if (gCurrentTest) + gCurrentTest.cleanup(); + + if (gTests.length > 0) { + // Goto next test. + gCurrentTest = gTests.shift(); + info("Start of test: " + gCurrentTest.desc); + // Test setup will set Library so that the bookmark to be opened is the + // first node in the content (right pane) tree. + gCurrentTest.setup(); + + // Middle click on first node in the content tree of the Library. + gLibrary.focus(); + waitForFocus(function() { + mouseEventOnCell(gLibrary.ContentTree.view, 0, 0, { button: 1 }); + }, gLibrary); + } + else { + // No more tests. + + // Close Library window. + gLibrary.close(); + + // Remove tabs listeners. + gBrowser.tabContainer.removeEventListener("TabOpen", gTabsListener, false); + gBrowser.removeTabsProgressListener(gTabsListener); + + // Restore history. + try { + gPrefService.clearUserPref(ENABLE_HISTORY_PREF); + } catch (ex) {} + + finish(); + } +} + +function mouseEventOnCell(aTree, aRowIndex, aColumnIndex, aEventDetails) { + var selection = aTree.view.selection; + selection.select(aRowIndex); + aTree.treeBoxObject.ensureRowIsVisible(aRowIndex); + var column = aTree.columns[aColumnIndex]; + + // get cell coordinates + var rect = aTree.treeBoxObject.getCoordsForCellItem(aRowIndex, column, "text"); + + EventUtils.synthesizeMouse(aTree.body, rect.x, rect.y, + aEventDetails, gLibrary); +} diff --git a/browser/components/places/tests/browser/browser_library_openFlatContainer.js b/browser/components/places/tests/browser/browser_library_openFlatContainer.js new file mode 100644 index 000000000..167b33031 --- /dev/null +++ b/browser/components/places/tests/browser/browser_library_openFlatContainer.js @@ -0,0 +1,42 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +/** + * Test opening a flat container in the right pane even if its parent in the + * left pane is closed. + */ + +add_task(function* () { + let folder = PlacesUtils.bookmarks + .createFolder(PlacesUtils.unfiledBookmarksFolderId, + "Folder", + PlacesUtils.bookmarks.DEFAULT_INDEX); + let bookmark = PlacesUtils.bookmarks + .insertBookmark(folder, NetUtil.newURI("http://example.com/"), + PlacesUtils.bookmarks.DEFAULT_INDEX, + "Bookmark"); + + let library = yield promiseLibrary("AllBookmarks"); + registerCleanupFunction(function () { + library.close(); + PlacesUtils.bookmarks.removeFolderChildren(PlacesUtils.unfiledBookmarksFolderId); + }); + + // Select unfiled later, to ensure it's closed. + library.PlacesOrganizer.selectLeftPaneQuery("UnfiledBookmarks"); + ok(!library.PlacesOrganizer._places.selectedNode.containerOpen, + "Unfiled container is closed"); + + let folderNode = library.ContentTree.view.view.nodeForTreeIndex(0); + is(folderNode.itemId, folder, + "Found the expected folder in the right pane"); + // Select the folder node in the right pane. + library.ContentTree.view.selectNode(folderNode); + + synthesizeClickOnSelectedTreeCell(library.ContentTree.view, + { clickCount: 2 }); + + is(library.ContentTree.view.view.nodeForTreeIndex(0).itemId, bookmark, + "Found the expected bookmark in the right pane"); +}); diff --git a/browser/components/places/tests/browser/browser_library_open_leak.js b/browser/components/places/tests/browser/browser_library_open_leak.js new file mode 100644 index 000000000..f002236a9 --- /dev/null +++ b/browser/components/places/tests/browser/browser_library_open_leak.js @@ -0,0 +1,23 @@ +/* -*- 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/. */ + +/** + * Bug 474831 + * https://bugzilla.mozilla.org/show_bug.cgi?id=474831 + * + * Tests for leaks caused by simply opening and closing the Places Library + * window. Opens the Places Library window, waits for it to load, closes it, + * and finishes. + */ + +function test() { + waitForExplicitFinish(); + openLibrary(function (win) { + ok(true, "Library has been correctly opened"); + win.close(); + finish(); + }); +} diff --git a/browser/components/places/tests/browser/browser_library_panel_leak.js b/browser/components/places/tests/browser/browser_library_panel_leak.js new file mode 100644 index 000000000..643a261fb --- /dev/null +++ b/browser/components/places/tests/browser/browser_library_panel_leak.js @@ -0,0 +1,54 @@ +/* -*- 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/. */ + +/** + * Bug 433231 - Places Library leaks the nsGlobalWindow when closed with a + * history entry selected. + * https://bugzilla.mozilla.org/show_bug.cgi?id=433231 + * + * STRs: Open Library, select an history entry in History, close Library. + * ISSUE: We were adding a bookmarks observer when editing a bookmark, when + * selecting an history entry the panel was not un-initialized, and + * since an histroy entry does not have an itemId, the observer was + * never removed. + */ + +const TEST_URI = "http://www.mozilla.org/"; + +function test() { + function onLibraryReady(organizer) { + let contentTree = organizer.document.getElementById("placeContent"); + isnot(contentTree, null, "Sanity check: placeContent tree should exist"); + isnot(organizer.PlacesOrganizer, null, "Sanity check: PlacesOrganizer should exist"); + isnot(organizer.gEditItemOverlay, null, "Sanity check: gEditItemOverlay should exist"); + + ok(organizer.gEditItemOverlay.initialized, "gEditItemOverlay is initialized"); + isnot(organizer.gEditItemOverlay.itemId, -1, "Editing a bookmark"); + + // Select History in the left pane. + organizer.PlacesOrganizer.selectLeftPaneQuery('History'); + // Select the first history entry. + let selection = contentTree.view.selection; + selection.clearSelection(); + selection.rangedSelect(0, 0, true); + // Check the panel is editing the history entry. + is(organizer.gEditItemOverlay.itemId, -1, "Editing an history entry"); + // Close Library window. + organizer.close(); + // Clean up history. + PlacesTestUtils.clearHistory().then(finish); + } + + waitForExplicitFinish(); + // Add an history entry. + ok(PlacesUtils, "checking PlacesUtils, running in chrome context?"); + PlacesTestUtils.addVisits( + {uri: PlacesUtils._uri(TEST_URI), visitDate: Date.now() * 1000, + transition: PlacesUtils.history.TRANSITION_TYPED} + ).then(() => { + openLibrary(onLibraryReady); + }); +} diff --git a/browser/components/places/tests/browser/browser_library_search.js b/browser/components/places/tests/browser/browser_library_search.js new file mode 100644 index 000000000..93af22363 --- /dev/null +++ b/browser/components/places/tests/browser/browser_library_search.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/. */ + +/** + * Bug 451151 + * https://bugzilla.mozilla.org/show_bug.cgi?id=451151 + * + * Summary: + * Tests frontend Places Library searching -- search, search reset, search scope + * consistency. + * + * Details: + * Each test below + * 1. selects a folder in the left pane and ensures that the content tree is + * appropriately updated, + * 2. performs a search and ensures that the content tree is correct for the + * folder and search and that the search UI is visible and appropriate to + * folder, + * 5. resets the search and ensures that the content tree is correct and that + * the search UI is hidden, and + * 6. if folder scope was clicked, searches again and ensures folder scope + * remains selected. + */ + +const TEST_URL = "http://dummy.mozilla.org/"; +const TEST_DOWNLOAD_URL = "http://dummy.mozilla.org/dummy.pdf"; + +var gLibrary; + +var testCases = [ + function allBookmarksScope() { + let defScope = getDefaultScope(PlacesUIUtils.allBookmarksFolderId); + search(PlacesUIUtils.allBookmarksFolderId, "dummy", defScope); + }, + + function historyScope() { + let defScope = getDefaultScope(PlacesUIUtils.leftPaneQueries["History"]); + search(PlacesUIUtils.leftPaneQueries["History"], "dummy", defScope); + }, + + function downloadsScope() { + let defScope = getDefaultScope(PlacesUIUtils.leftPaneQueries["Downloads"]); + search(PlacesUIUtils.leftPaneQueries["Downloads"], "dummy", defScope); + }, +]; + +/** + * Returns the default search scope for a given folder. + * + * @param aFolderId + * the item ID of a node in the left pane's tree + * @return the default scope when the folder is newly selected + */ +function getDefaultScope(aFolderId) { + switch (aFolderId) { + case PlacesUIUtils.leftPaneQueries["History"]: + return "scopeBarHistory" + case PlacesUIUtils.leftPaneQueries["Downloads"]: + return "scopeBarDownloads"; + default: + return "scopeBarAll"; + } +} + +/** + * Returns the single nsINavHistoryQuery represented by a given place URI. + * + * @param aPlaceURI + * a URI that represents a single query + * @return an nsINavHistoryQuery object + */ +function queryStringToQuery(aPlaceURI) { + let queries = {}; + PlacesUtils.history.queryStringToQueries(aPlaceURI, queries, {}, {}); + return queries.value[0]; +} + +/** + * Resets the search by clearing the search box's text and ensures that the + * search scope remains as expected. + * + * @param aExpectedScopeButtonId + * this button should be selected after the reset + */ +function resetSearch(aExpectedScopeButtonId) { + search(null, "", aExpectedScopeButtonId); +} + +/** + * Performs a search for a given folder and search string and ensures that the + * URI of the right pane's content tree is as expected for the folder and search + * string. Also ensures that the search scope button is as expected after the + * search. + * + * @param aFolderId + * the item ID of a node in the left pane's tree + * @param aSearchStr + * the search text; may be empty to reset the search + * @param aExpectedScopeButtonId + * after searching the selected scope button should be this + */ +function search(aFolderId, aSearchStr, aExpectedScopeButtonId) { + let doc = gLibrary.document; + let folderTree = doc.getElementById("placesList"); + let contentTree = doc.getElementById("placeContent"); + + // First, ensure that selecting the folder in the left pane updates the + // content tree properly. + if (aFolderId) { + folderTree.selectItems([aFolderId]); + isnot(folderTree.selectedNode, null, + "Sanity check: left pane tree should have selection after selecting!"); + + // getFolders() on a History query returns an empty array, so no use + // comparing against aFolderId in that case. + if (aFolderId !== PlacesUIUtils.leftPaneQueries["History"] && + aFolderId !== PlacesUIUtils.leftPaneQueries["Downloads"]) { + // contentTree.place should be equal to contentTree.result.root.uri, + // but it's not until bug 476952 is fixed. + let query = queryStringToQuery(contentTree.result.root.uri); + is(query.getFolders()[0], aFolderId, + "Content tree's folder should be what was selected in the left pane"); + } + } + + // Second, ensure that searching updates the content tree and search UI + // properly. + let searchBox = doc.getElementById("searchFilter"); + searchBox.value = aSearchStr; + gLibrary.PlacesSearchBox.search(searchBox.value); + let query = queryStringToQuery(contentTree.result.root.uri); + if (aSearchStr) { + is(query.searchTerms, aSearchStr, + "Content tree's searchTerms should be text in search box"); + } + else { + is(query.hasSearchTerms, false, + "Content tree's searchTerms should not exist after search reset"); + } +} + +/** + * test() contains window-launching boilerplate that calls this to really kick + * things off. Add functions to the testCases array, and this will call them. + */ +function onLibraryAvailable() { + testCases.forEach(aTest => aTest()); + + gLibrary.close(); + gLibrary = null; + + // Cleanup. + PlacesUtils.tagging.untagURI(PlacesUtils._uri(TEST_URL), ["dummyTag"]); + PlacesUtils.bookmarks.removeFolderChildren(PlacesUtils.unfiledBookmarksFolderId); + PlacesTestUtils.clearHistory().then(finish); +} + +function test() { + waitForExplicitFinish(); + + // Sanity: + ok(PlacesUtils, "PlacesUtils in context"); + + // Add visits, a bookmark and a tag. + PlacesTestUtils.addVisits( + [{ uri: PlacesUtils._uri(TEST_URL), visitDate: Date.now() * 1000, + transition: PlacesUtils.history.TRANSITION_TYPED }, + { uri: PlacesUtils._uri(TEST_DOWNLOAD_URL), visitDate: Date.now() * 1000, + transition: PlacesUtils.history.TRANSITION_DOWNLOAD }] + ).then(() => { + PlacesUtils.bookmarks.insertBookmark(PlacesUtils.unfiledBookmarksFolderId, + PlacesUtils._uri(TEST_URL), + PlacesUtils.bookmarks.DEFAULT_INDEX, + "dummy"); + PlacesUtils.tagging.tagURI(PlacesUtils._uri(TEST_URL), ["dummyTag"]); + + gLibrary = openLibrary(onLibraryAvailable); + }); +} diff --git a/browser/components/places/tests/browser/browser_library_views_liveupdate.js b/browser/components/places/tests/browser/browser_library_views_liveupdate.js new file mode 100644 index 000000000..c78ed641a --- /dev/null +++ b/browser/components/places/tests/browser/browser_library_views_liveupdate.js @@ -0,0 +1,300 @@ +/* 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/. */ + +/** + * Tests Library Left pane view for liveupdate. + */ + +var gLibrary = null; + +function test() { + waitForExplicitFinish(); + // This test takes quite some time, and timeouts frequently, so we require + // more time to run. + // See Bug 525610. + requestLongerTimeout(2); + + // Sanity checks. + ok(PlacesUtils, "PlacesUtils in context"); + ok(PlacesUIUtils, "PlacesUIUtils in context"); + + // Open Library, we will check the left pane. + openLibrary(function (library) { + gLibrary = library; + startTest(); + }); +} + +/** + * Adds bookmarks observer, and executes a bunch of bookmarks operations. + */ +function startTest() { + var bs = PlacesUtils.bookmarks; + // Add observers. + bs.addObserver(bookmarksObserver, false); + PlacesUtils.annotations.addObserver(bookmarksObserver, false); + var addedBookmarks = []; + + // MENU + ok(true, "*** Acting on menu bookmarks"); + var id = bs.insertBookmark(bs.bookmarksMenuFolder, + PlacesUtils._uri("http://bm1.mozilla.org/"), + bs.DEFAULT_INDEX, + "bm1"); + addedBookmarks.push(id); + id = bs.insertBookmark(bs.bookmarksMenuFolder, + PlacesUtils._uri("place:"), + bs.DEFAULT_INDEX, + "bm2"); + bs.setItemTitle(id, "bm2_edited"); + addedBookmarks.push(id); + id = bs.insertSeparator(bs.bookmarksMenuFolder, bs.DEFAULT_INDEX); + addedBookmarks.push(id); + id = bs.createFolder(bs.bookmarksMenuFolder, + "bmf", + bs.DEFAULT_INDEX); + bs.setItemTitle(id, "bmf_edited"); + addedBookmarks.push(id); + id = bs.insertBookmark(id, + PlacesUtils._uri("http://bmf1.mozilla.org/"), + bs.DEFAULT_INDEX, + "bmf1"); + addedBookmarks.push(id); + bs.moveItem(id, bs.bookmarksMenuFolder, 0); + + // TOOLBAR + ok(true, "*** Acting on toolbar bookmarks"); + bs.insertBookmark(bs.toolbarFolder, + PlacesUtils._uri("http://tb1.mozilla.org/"), + bs.DEFAULT_INDEX, + "tb1"); + bs.setItemTitle(id, "tb1_edited"); + addedBookmarks.push(id); + id = bs.insertBookmark(bs.toolbarFolder, + PlacesUtils._uri("place:"), + bs.DEFAULT_INDEX, + "tb2"); + bs.setItemTitle(id, "tb2_edited"); + addedBookmarks.push(id); + id = bs.insertSeparator(bs.toolbarFolder, bs.DEFAULT_INDEX); + addedBookmarks.push(id); + id = bs.createFolder(bs.toolbarFolder, + "tbf", + bs.DEFAULT_INDEX); + bs.setItemTitle(id, "tbf_edited"); + addedBookmarks.push(id); + id = bs.insertBookmark(id, + PlacesUtils._uri("http://tbf1.mozilla.org/"), + bs.DEFAULT_INDEX, + "bmf1"); + addedBookmarks.push(id); + bs.moveItem(id, bs.toolbarFolder, 0); + + // UNSORTED + ok(true, "*** Acting on unsorted bookmarks"); + id = bs.insertBookmark(bs.unfiledBookmarksFolder, + PlacesUtils._uri("http://ub1.mozilla.org/"), + bs.DEFAULT_INDEX, + "ub1"); + bs.setItemTitle(id, "ub1_edited"); + addedBookmarks.push(id); + id = bs.insertBookmark(bs.unfiledBookmarksFolder, + PlacesUtils._uri("place:"), + bs.DEFAULT_INDEX, + "ub2"); + bs.setItemTitle(id, "ub2_edited"); + addedBookmarks.push(id); + id = bs.insertSeparator(bs.unfiledBookmarksFolder, bs.DEFAULT_INDEX); + addedBookmarks.push(id); + id = bs.createFolder(bs.unfiledBookmarksFolder, + "ubf", + bs.DEFAULT_INDEX); + bs.setItemTitle(id, "ubf_edited"); + addedBookmarks.push(id); + id = bs.insertBookmark(id, + PlacesUtils._uri("http://ubf1.mozilla.org/"), + bs.DEFAULT_INDEX, + "ubf1"); + addedBookmarks.push(id); + bs.moveItem(id, bs.unfiledBookmarksFolder, 0); + + // Remove all added bookmarks. + addedBookmarks.forEach(function (aItem) { + // If we remove an item after its containing folder has been removed, + // this will throw, but we can ignore that. + try { + bs.removeItem(aItem); + } catch (ex) {} + }); + + // Remove observers. + bs.removeObserver(bookmarksObserver); + PlacesUtils.annotations.removeObserver(bookmarksObserver); + finishTest(); +} + +/** + * Restores browser state and calls finish. + */ +function finishTest() { + // Close Library window. + gLibrary.close(); + finish(); +} + +/** + * The observer is where magic happens, for every change we do it will look for + * nodes positions in the affected views. + */ +var bookmarksObserver = { + QueryInterface: XPCOMUtils.generateQI([ + Ci.nsINavBookmarkObserver + , Ci.nsIAnnotationObserver + ]), + + // nsIAnnotationObserver + onItemAnnotationSet: function() {}, + onItemAnnotationRemoved: function() {}, + onPageAnnotationSet: function() {}, + onPageAnnotationRemoved: function() {}, + + // nsINavBookmarkObserver + onItemAdded: function PSB_onItemAdded(aItemId, aFolderId, aIndex, aItemType, + aURI) { + var node = null; + var index = null; + [node, index] = getNodeForTreeItem(aItemId, gLibrary.PlacesOrganizer._places); + // Left pane should not be updated for normal bookmarks or separators. + switch (aItemType) { + case PlacesUtils.bookmarks.TYPE_BOOKMARK: + var uriString = aURI.spec; + var isQuery = uriString.substr(0, 6) == "place:"; + if (isQuery) { + isnot(node, null, "Found new Places node in left pane"); + ok(index >= 0, "Node is at index " + index); + break; + } + // Fallback to separator case if this is not a query. + case PlacesUtils.bookmarks.TYPE_SEPARATOR: + is(node, null, "New Places node not added in left pane"); + break; + default: + isnot(node, null, "Found new Places node in left pane"); + ok(index >= 0, "Node is at index " + index); + } + }, + + onItemRemoved: function PSB_onItemRemoved(aItemId, aFolder, aIndex) { + var node = null; + [node, ] = getNodeForTreeItem(aItemId, gLibrary.PlacesOrganizer._places); + is(node, null, "Places node not found in left pane"); + }, + + onItemMoved: function(aItemId, + aOldFolderId, aOldIndex, + aNewFolderId, aNewIndex, aItemType) { + var node = null; + var index = null; + [node, index] = getNodeForTreeItem(aItemId, gLibrary.PlacesOrganizer._places); + // Left pane should not be updated for normal bookmarks or separators. + switch (aItemType) { + case PlacesUtils.bookmarks.TYPE_BOOKMARK: + var uriString = PlacesUtils.bookmarks.getBookmarkURI(aItemId).spec; + var isQuery = uriString.substr(0, 6) == "place:"; + if (isQuery) { + isnot(node, null, "Found new Places node in left pane"); + ok(index >= 0, "Node is at index " + index); + break; + } + // Fallback to separator case if this is not a query. + case PlacesUtils.bookmarks.TYPE_SEPARATOR: + is(node, null, "New Places node not added in left pane"); + break; + default: + isnot(node, null, "Found new Places node in left pane"); + ok(index >= 0, "Node is at index " + index); + } + }, + + onBeginUpdateBatch: function PSB_onBeginUpdateBatch() {}, + onEndUpdateBatch: function PSB_onEndUpdateBatch() {}, + onItemVisited: function() {}, + onItemChanged: function PSB_onItemChanged(aItemId, aProperty, + aIsAnnotationProperty, aNewValue) { + if (aProperty == "title") { + let validator = function(aTreeRowIndex) { + let tree = gLibrary.PlacesOrganizer._places; + let cellText = tree.view.getCellText(aTreeRowIndex, + tree.columns.getColumnAt(0)); + return cellText == aNewValue; + } + let [node, , valid] = getNodeForTreeItem(aItemId, gLibrary.PlacesOrganizer._places, validator); + if (node) // Only visible nodes. + ok(valid, "Title cell value has been correctly updated"); + } + } +}; + + +/** + * Get places node and index for an itemId in a tree view. + * + * @param aItemId + * item id of the item to search. + * @param aTree + * Tree to search in. + * @param aValidator [optional] + * function to check row validity if found. Defaults to {return true;}. + * @returns [node, index, valid] or [null, null, false] if not found. + */ +function getNodeForTreeItem(aItemId, aTree, aValidator) { + + function findNode(aContainerIndex) { + if (aTree.view.isContainerEmpty(aContainerIndex)) + return [null, null, false]; + + // The rowCount limit is just for sanity, but we will end looping when + // we have checked the last child of this container or we have found node. + for (var i = aContainerIndex + 1; i < aTree.view.rowCount; i++) { + var node = aTree.view.nodeForTreeIndex(i); + + if (node.itemId == aItemId) { + // Minus one because we want relative index inside the container. + let valid = aValidator ? aValidator(i) : true; + return [node, i - aTree.view.getParentIndex(i) - 1, valid]; + } + + if (PlacesUtils.nodeIsFolder(node)) { + // Open container. + aTree.view.toggleOpenState(i); + // Search inside it. + var foundNode = findNode(i); + // Close container. + aTree.view.toggleOpenState(i); + // Return node if found. + if (foundNode[0] != null) + return foundNode; + } + + // We have finished walking this container. + if (!aTree.view.hasNextSibling(aContainerIndex + 1, i)) + break; + } + return [null, null, false] + } + + // Root node is hidden, so we need to manually walk the first level. + for (var i = 0; i < aTree.view.rowCount; i++) { + // Open container. + aTree.view.toggleOpenState(i); + // Search inside it. + var foundNode = findNode(i); + // Close container. + aTree.view.toggleOpenState(i); + // Return node if found. + if (foundNode[0] != null) + return foundNode; + } + return [null, null, false]; +} diff --git a/browser/components/places/tests/browser/browser_markPageAsFollowedLink.js b/browser/components/places/tests/browser/browser_markPageAsFollowedLink.js new file mode 100644 index 000000000..02d564e28 --- /dev/null +++ b/browser/components/places/tests/browser/browser_markPageAsFollowedLink.js @@ -0,0 +1,68 @@ +/** + * Tests that visits across frames are correctly represented in the database. + */ + +const BASE_URL = "http://mochi.test:8888/browser/browser/components/places/tests/browser"; +const PAGE_URL = BASE_URL + "/framedPage.html"; +const LEFT_URL = BASE_URL + "/frameLeft.html"; +const RIGHT_URL = BASE_URL + "/frameRight.html"; + +add_task(function* test() { + // We must wait for both frames to be loaded and the visits to be registered. + let deferredLeftFrameVisit = PromiseUtils.defer(); + let deferredRightFrameVisit = PromiseUtils.defer(); + + Services.obs.addObserver(function observe(subject) { + Task.spawn(function* () { + let url = subject.QueryInterface(Ci.nsIURI).spec; + if (url == LEFT_URL ) { + is((yield getTransitionForUrl(url)), null, + "Embed visits should not get a database entry."); + deferredLeftFrameVisit.resolve(); + } + else if (url == RIGHT_URL ) { + is((yield getTransitionForUrl(url)), + PlacesUtils.history.TRANSITION_FRAMED_LINK, + "User activated visits should get a FRAMED_LINK transition."); + Services.obs.removeObserver(observe, "uri-visit-saved"); + deferredRightFrameVisit.resolve(); + } + }); + }, "uri-visit-saved", false); + + // Open a tab and wait for all the subframes to load. + let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, PAGE_URL); + + // Wait for the left frame visit to be registered. + info("Waiting left frame visit"); + yield deferredLeftFrameVisit.promise; + + // Click on the link in the left frame to cause a page load in the + // right frame. + info("Clicking link"); + yield ContentTask.spawn(tab.linkedBrowser, {}, function* () { + content.frames[0].document.getElementById("clickme").click(); + }); + + // Wait for the right frame visit to be registered. + info("Waiting right frame visit"); + yield deferredRightFrameVisit.promise; + + yield BrowserTestUtils.removeTab(tab); +}); + +function* getTransitionForUrl(url) { + // Ensure all the transactions completed. + yield PlacesTestUtils.promiseAsyncUpdates(); + let db = yield PlacesUtils.promiseDBConnection(); + let rows = yield db.execute(` + SELECT visit_type + FROM moz_historyvisits + JOIN moz_places h ON place_id = h.id + WHERE url_hash = hash(:url) AND url = :url`, + { url }); + if (rows.length) { + return rows[0].getResultByName("visit_type"); + } + return null; +} diff --git a/browser/components/places/tests/browser/browser_sidebarpanels_click.js b/browser/components/places/tests/browser/browser_sidebarpanels_click.js new file mode 100644 index 000000000..80ed2eb2b --- /dev/null +++ b/browser/components/places/tests/browser/browser_sidebarpanels_click.js @@ -0,0 +1,157 @@ +/* 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/. */ + +// This test makes sure that the items in the bookmarks and history sidebar +// panels are clickable in both LTR and RTL modes. + +function test() { + waitForExplicitFinish(); + ignoreAllUncaughtExceptions(); + + const BOOKMARKS_SIDEBAR_ID = "viewBookmarksSidebar"; + const BOOKMARKS_SIDEBAR_TREE_ID = "bookmarks-view"; + const HISTORY_SIDEBAR_ID = "viewHistorySidebar"; + const HISTORY_SIDEBAR_TREE_ID = "historyTree"; + const TEST_URL = "http://mochi.test:8888/browser/browser/components/places/tests/browser/sidebarpanels_click_test_page.html"; + + // If a sidebar is already open, close it. + if (!document.getElementById("sidebar-box").hidden) { + info("Unexpected sidebar found - a previous test failed to cleanup correctly"); + SidebarUI.hide(); + } + + let sidebar = document.getElementById("sidebar"); + let tests = []; + let currentTest; + + tests.push({ + _itemID: null, + init: function(aCallback) { + // Add a bookmark to the Unfiled Bookmarks folder. + this._itemID = PlacesUtils.bookmarks.insertBookmark( + PlacesUtils.unfiledBookmarksFolderId, PlacesUtils._uri(TEST_URL), + PlacesUtils.bookmarks.DEFAULT_INDEX, "test" + ); + aCallback(); + }, + prepare: function() { + }, + selectNode: function(tree) { + tree.selectItems([this._itemID]); + }, + cleanup: function(aCallback) { + PlacesUtils.bookmarks.removeFolderChildren(PlacesUtils.unfiledBookmarksFolderId); + executeSoon(aCallback); + }, + sidebarName: BOOKMARKS_SIDEBAR_ID, + treeName: BOOKMARKS_SIDEBAR_TREE_ID, + desc: "Bookmarks sidebar test" + }); + + tests.push({ + init: function(aCallback) { + // Add a history entry. + let uri = PlacesUtils._uri(TEST_URL); + PlacesTestUtils.addVisits({ + uri: uri, visitDate: Date.now() * 1000, + transition: PlacesUtils.history.TRANSITION_TYPED + }).then(aCallback); + }, + prepare: function() { + sidebar.contentDocument.getElementById("byvisited").doCommand(); + }, + selectNode: function(tree) { + tree.selectNode(tree.view.nodeForTreeIndex(0)); + is(tree.selectedNode.uri, TEST_URL, "The correct visit has been selected"); + is(tree.selectedNode.itemId, -1, "The selected node is not bookmarked"); + }, + cleanup: function(aCallback) { + PlacesTestUtils.clearHistory().then(aCallback); + }, + sidebarName: HISTORY_SIDEBAR_ID, + treeName: HISTORY_SIDEBAR_TREE_ID, + desc: "History sidebar test" + }); + + function testPlacesPanel(preFunc, postFunc) { + currentTest.init(function() { + SidebarUI.show(currentTest.sidebarName); + }); + + sidebar.addEventListener("load", function() { + sidebar.removeEventListener("load", arguments.callee, true); + executeSoon(function() { + currentTest.prepare(); + + if (preFunc) + preFunc(); + + function observer(aSubject, aTopic, aData) { + info("alert dialog observed as expected"); + Services.obs.removeObserver(observer, "common-dialog-loaded"); + Services.obs.removeObserver(observer, "tabmodal-dialog-loaded"); + + aSubject.Dialog.ui.button0.click(); + + executeSoon(function () { + SidebarUI.hide(); + currentTest.cleanup(postFunc); + }); + } + Services.obs.addObserver(observer, "common-dialog-loaded", false); + Services.obs.addObserver(observer, "tabmodal-dialog-loaded", false); + + let tree = sidebar.contentDocument.getElementById(currentTest.treeName); + + // Select the inserted places item. + currentTest.selectNode(tree); + + synthesizeClickOnSelectedTreeCell(tree); + // Now, wait for the observer to catch the alert dialog. + // If something goes wrong, the test will time out at this stage. + // Note that for the history sidebar, the URL itself is not opened, + // and Places will show the load-js-data-url-error prompt as an alert + // box, which means that the click actually worked, so it's good enough + // for the purpose of this test. + }); + }, true); + } + + function changeSidebarDirection(aDirection) { + sidebar.contentDocument.documentElement.style.direction = aDirection; + } + + function runNextTest() { + // Remove eventual tabs created by previous sub-tests. + while (gBrowser.tabs.length > 1) { + gBrowser.removeTab(gBrowser.tabContainer.lastChild); + } + + if (tests.length == 0) { + finish(); + } + else { + // Create a new tab and run the test. + gBrowser.selectedTab = gBrowser.addTab(); + currentTest = tests.shift(); + testPlacesPanel(function() { + changeSidebarDirection("ltr"); + info("Running " + currentTest.desc + " in LTR mode"); + }, + function() { + testPlacesPanel(function() { + // Run the test in RTL mode. + changeSidebarDirection("rtl"); + info("Running " + currentTest.desc + " in RTL mode"); + }, + function() { + runNextTest(); + }); + }); + } + } + + // Ensure history is clean before starting the test. + PlacesTestUtils.clearHistory().then(runNextTest); +} diff --git a/browser/components/places/tests/browser/browser_sort_in_library.js b/browser/components/places/tests/browser/browser_sort_in_library.js new file mode 100644 index 000000000..af9c35e59 --- /dev/null +++ b/browser/components/places/tests/browser/browser_sort_in_library.js @@ -0,0 +1,249 @@ +/* -*- 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/. */ + +/** + * Tests the following bugs: + * + * Bug 443745 - View>Sort>of "alpha" sort items is default to Z>A instead of A>Z + * https://bugzilla.mozilla.org/show_bug.cgi?id=443745 + * + * Bug 444179 - Library>Views>Sort>Sort by Tags does nothing + * https://bugzilla.mozilla.org/show_bug.cgi?id=444179 + * + * Basically, fully tests sorting the placeContent tree in the Places Library + * window. Sorting is verified by comparing the nsINavHistoryResult returned by + * placeContent.result to the expected sort values. + */ + +// Two properties of nsINavHistoryResult control the sort of the tree: +// sortingMode and sortingAnnotation. sortingMode's value is one of the +// nsINavHistoryQueryOptions.SORT_BY_* constants. sortingAnnotation is the +// annotation used to sort for SORT_BY_ANNOTATION_* mode. +// +// This lookup table maps the possible values of anonid's of the treecols to +// objects that represent the treecols' correct state after the user sorts the +// previously unsorted tree by selecting a column from the Views > Sort menu. +// sortingMode is constructed from the key and dir properties (i.e., +// SORT_BY_<key>_<dir>) and sortingAnnotation is checked against anno. anno +// may be undefined if key is not "ANNOTATION". +const SORT_LOOKUP_TABLE = { + title: { key: "TITLE", dir: "ASCENDING" }, + tags: { key: "TAGS", dir: "ASCENDING" }, + url: { key: "URI", dir: "ASCENDING" }, + date: { key: "DATE", dir: "DESCENDING" }, + visitCount: { key: "VISITCOUNT", dir: "DESCENDING" }, + dateAdded: { key: "DATEADDED", dir: "DESCENDING" }, + lastModified: { key: "LASTMODIFIED", dir: "DESCENDING" }, + description: { key: "ANNOTATION", + dir: "ASCENDING", + anno: "bookmarkProperties/description" } +}; + +// This is the column that's sorted if one is not specified and the tree is +// currently unsorted. Set it to a key substring in the name of one of the +// nsINavHistoryQueryOptions.SORT_BY_* constants, e.g., "TITLE", "URI". +// Method ViewMenu.setSortColumn in browser/components/places/content/places.js +// determines this value. +const DEFAULT_SORT_KEY = "TITLE"; + +// Part of the test is checking that sorts stick, so each time we sort we need +// to remember it. +var prevSortDir = null; +var prevSortKey = null; + +/** + * Ensures that the sort of aTree is aSortingMode and aSortingAnno. + * + * @param aTree + * the tree to check + * @param aSortingMode + * one of the Ci.nsINavHistoryQueryOptions.SORT_BY_* constants + * @param aSortingAnno + * checked only if sorting mode is one of the + * Ci.nsINavHistoryQueryOptions.SORT_BY_ANNOTATION_* constants + */ +function checkSort(aTree, aSortingMode, aSortingAnno) { + // The placeContent tree's sort is determined by the nsINavHistoryResult it + // stores. Get it and check that the sort is what the caller expects. + let res = aTree.result; + isnot(res, null, + "sanity check: placeContent.result should not return null"); + + // Check sortingMode. + is(res.sortingMode, aSortingMode, + "column should now have sortingMode " + aSortingMode); + + // Check sortingAnnotation, but only if sortingMode is ANNOTATION. + if ([Ci.nsINavHistoryQueryOptions.SORT_BY_ANNOTATION_ASCENDING, + Ci.nsINavHistoryQueryOptions.SORT_BY_ANNOTATION_DESCENDING]. + indexOf(aSortingMode) >= 0) { + is(res.sortingAnnotation, aSortingAnno, + "column should now have sorting annotation " + aSortingAnno); + } +} + +/** + * Sets the sort of aTree. + * + * @param aOrganizerWin + * the Places window + * @param aTree + * the tree to sort + * @param aUnsortFirst + * true if the sort should be set to SORT_BY_NONE before sorting by aCol + * and aDir + * @param aShouldFail + * true if setSortColumn should fail on aCol or aDir + * @param aCol + * the column of aTree by which to sort + * @param aDir + * either "ascending" or "descending" + */ +function setSort(aOrganizerWin, aTree, aUnsortFirst, aShouldFail, aCol, aDir) { + if (aUnsortFirst) { + aOrganizerWin.ViewMenu.setSortColumn(); + checkSort(aTree, Ci.nsINavHistoryQueryOptions.SORT_BY_NONE, ""); + + // Remember the sort key and direction. + prevSortKey = null; + prevSortDir = null; + } + + let failed = false; + try { + aOrganizerWin.ViewMenu.setSortColumn(aCol, aDir); + + // Remember the sort key and direction. + if (!aCol && !aDir) { + prevSortKey = null; + prevSortDir = null; + } + else { + if (aCol) + prevSortKey = SORT_LOOKUP_TABLE[aCol.getAttribute("anonid")].key; + else if (prevSortKey === null) + prevSortKey = DEFAULT_SORT_KEY; + + if (aDir) + prevSortDir = aDir.toUpperCase(); + else if (prevSortDir === null) + prevSortDir = SORT_LOOKUP_TABLE[aCol.getAttribute("anonid")].dir; + } + } catch (exc) { + failed = true; + } + + is(failed, !!aShouldFail, + "setSortColumn on column " + + (aCol ? aCol.getAttribute("anonid") : "(no column)") + + " with direction " + (aDir || "(no direction)") + + " and table previously " + (aUnsortFirst ? "unsorted" : "sorted") + + " should " + (aShouldFail ? "" : "not ") + "fail"); +} + +/** + * Tries sorting by an invalid column and sort direction. + * + * @param aOrganizerWin + * the Places window + * @param aPlaceContentTree + * the placeContent tree in aOrganizerWin + */ +function testInvalid(aOrganizerWin, aPlaceContentTree) { + // Invalid column should fail by throwing an exception. + let bogusCol = document.createElement("treecol"); + bogusCol.setAttribute("anonid", "bogusColumn"); + setSort(aOrganizerWin, aPlaceContentTree, true, true, bogusCol, "ascending"); + + // Invalid direction reverts to SORT_BY_NONE. + setSort(aOrganizerWin, aPlaceContentTree, false, false, null, "bogus dir"); + checkSort(aPlaceContentTree, Ci.nsINavHistoryQueryOptions.SORT_BY_NONE, ""); +} + +/** + * Tests sorting aPlaceContentTree by column only and then by both column + * and direction. + * + * @param aOrganizerWin + * the Places window + * @param aPlaceContentTree + * the placeContent tree in aOrganizerWin + * @param aUnsortFirst + * true if, before each sort we try, we should sort to SORT_BY_NONE + */ +function testSortByColAndDir(aOrganizerWin, aPlaceContentTree, aUnsortFirst) { + let cols = aPlaceContentTree.getElementsByTagName("treecol"); + ok(cols.length > 0, "sanity check: placeContent should contain columns"); + + for (let i = 0; i < cols.length; i++) { + let col = cols.item(i); + ok(col.hasAttribute("anonid"), + "sanity check: column " + col.id + " should have anonid"); + + let colId = col.getAttribute("anonid"); + ok(colId in SORT_LOOKUP_TABLE, + "sanity check: unexpected placeContent column anonid"); + + let sortConst = + "SORT_BY_" + SORT_LOOKUP_TABLE[colId].key + "_" + + (aUnsortFirst ? SORT_LOOKUP_TABLE[colId].dir : prevSortDir); + let expectedSortMode = Ci.nsINavHistoryQueryOptions[sortConst]; + let expectedAnno = SORT_LOOKUP_TABLE[colId].anno || ""; + + // Test sorting by only a column. + setSort(aOrganizerWin, aPlaceContentTree, aUnsortFirst, false, col); + checkSort(aPlaceContentTree, expectedSortMode, expectedAnno); + + // Test sorting by both a column and a direction. + ["ascending", "descending"].forEach(function (dir) { + let sortConst = + "SORT_BY_" + SORT_LOOKUP_TABLE[colId].key + "_" + dir.toUpperCase(); + let expectedSortMode = Ci.nsINavHistoryQueryOptions[sortConst]; + setSort(aOrganizerWin, aPlaceContentTree, aUnsortFirst, false, col, dir); + checkSort(aPlaceContentTree, expectedSortMode, expectedAnno); + }); + } +} + +/** + * Tests sorting aPlaceContentTree by direction only. + * + * @param aOrganizerWin + * the Places window + * @param aPlaceContentTree + * the placeContent tree in aOrganizerWin + * @param aUnsortFirst + * true if, before each sort we try, we should sort to SORT_BY_NONE + */ +function testSortByDir(aOrganizerWin, aPlaceContentTree, aUnsortFirst) { + ["ascending", "descending"].forEach(function (dir) { + let key = (aUnsortFirst ? DEFAULT_SORT_KEY : prevSortKey); + let sortConst = "SORT_BY_" + key + "_" + dir.toUpperCase(); + let expectedSortMode = Ci.nsINavHistoryQueryOptions[sortConst]; + setSort(aOrganizerWin, aPlaceContentTree, aUnsortFirst, false, null, dir); + checkSort(aPlaceContentTree, expectedSortMode, ""); + }); +} + +function test() { + waitForExplicitFinish(); + + openLibrary(function (win) { + let tree = win.document.getElementById("placeContent"); + isnot(tree, null, "sanity check: placeContent tree should exist"); + // Run the tests. + testSortByColAndDir(win, tree, true); + testSortByColAndDir(win, tree, false); + testSortByDir(win, tree, true); + testSortByDir(win, tree, false); + testInvalid(win, tree); + // Reset the sort to SORT_BY_NONE. + setSort(win, tree, false, false); + // Close the window and finish. + win.close(); + finish(); + }); +} diff --git a/browser/components/places/tests/browser/browser_toolbarbutton_menu_context.js b/browser/components/places/tests/browser/browser_toolbarbutton_menu_context.js new file mode 100644 index 000000000..7a0eec22f --- /dev/null +++ b/browser/components/places/tests/browser/browser_toolbarbutton_menu_context.js @@ -0,0 +1,53 @@ +var bookmarksMenuButton = document.getElementById("bookmarks-menu-button"); +var BMB_menuPopup = document.getElementById("BMB_bookmarksPopup"); +var BMB_showAllBookmarks = document.getElementById("BMB_bookmarksShowAll"); +var contextMenu = document.getElementById("placesContext"); +var newBookmarkItem = document.getElementById("placesContext_new:bookmark"); + +waitForExplicitFinish(); +add_task(function* testPopup() { + info("Checking popup context menu before moving the bookmarks button"); + yield checkPopupContextMenu(); + let pos = CustomizableUI.getPlacementOfWidget("bookmarks-menu-button").position; + CustomizableUI.addWidgetToArea("bookmarks-menu-button", CustomizableUI.AREA_PANEL); + CustomizableUI.addWidgetToArea("bookmarks-menu-button", CustomizableUI.AREA_NAVBAR, pos); + info("Checking popup context menu after moving the bookmarks button"); + yield checkPopupContextMenu(); +}); + +function* checkPopupContextMenu() { + let dropmarker = document.getAnonymousElementByAttribute(bookmarksMenuButton, "anonid", "dropmarker"); + BMB_menuPopup.setAttribute("style", "transition: none;"); + let popupShownPromise = onPopupEvent(BMB_menuPopup, "shown"); + EventUtils.synthesizeMouseAtCenter(dropmarker, {}); + info("Waiting for bookmarks menu to be shown."); + yield popupShownPromise; + let contextMenuShownPromise = onPopupEvent(contextMenu, "shown"); + EventUtils.synthesizeMouseAtCenter(BMB_showAllBookmarks, {type: "contextmenu", button: 2 }); + info("Waiting for context menu on bookmarks menu to be shown."); + yield contextMenuShownPromise; + ok(!newBookmarkItem.hasAttribute("disabled"), "New bookmark item shouldn't be disabled"); + let contextMenuHiddenPromise = onPopupEvent(contextMenu, "hidden"); + contextMenu.hidePopup(); + BMB_menuPopup.removeAttribute("style"); + info("Waiting for context menu on bookmarks menu to be hidden."); + yield contextMenuHiddenPromise; + let popupHiddenPromise = onPopupEvent(BMB_menuPopup, "hidden"); + // Can't use synthesizeMouseAtCenter because the dropdown panel is in the way + EventUtils.synthesizeKey("VK_ESCAPE", {}); + info("Waiting for bookmarks menu to be hidden."); + yield popupHiddenPromise; +} + +function onPopupEvent(popup, evt) { + let fullEvent = "popup" + evt; + let deferred = new Promise.defer(); + let onPopupHandler = (e) => { + if (e.target == popup) { + popup.removeEventListener(fullEvent, onPopupHandler); + deferred.resolve(); + } + }; + popup.addEventListener(fullEvent, onPopupHandler); + return deferred.promise; +} diff --git a/browser/components/places/tests/browser/browser_views_liveupdate.js b/browser/components/places/tests/browser/browser_views_liveupdate.js new file mode 100644 index 000000000..735d6b168 --- /dev/null +++ b/browser/components/places/tests/browser/browser_views_liveupdate.js @@ -0,0 +1,475 @@ +/* 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/. */ + +/** + * Tests Places views (menu, toolbar, tree) for liveupdate. + */ + +var toolbar = document.getElementById("PersonalToolbar"); +var wasCollapsed = toolbar.collapsed; + +function test() { + waitForExplicitFinish(); + + // Uncollapse the personal toolbar if needed. + if (wasCollapsed) { + promiseSetToolbarVisibility(toolbar, true).then(openBookmarksSidebar); + } else { + openBookmarksSidebar(); + } +} + +function openBookmarksSidebar() { + // Sanity checks. + ok(PlacesUtils, "PlacesUtils in context"); + ok(PlacesUIUtils, "PlacesUIUtils in context"); + + // Open bookmarks menu. + var popup = document.getElementById("bookmarksMenuPopup"); + ok(popup, "Menu popup element exists"); + fakeOpenPopup(popup); + + // Open bookmarks sidebar. + var sidebar = document.getElementById("sidebar"); + sidebar.addEventListener("load", function() { + sidebar.removeEventListener("load", arguments.callee, true); + // Need to executeSoon since the tree is initialized on sidebar load. + executeSoon(startTest); + }, true); + SidebarUI.show("viewBookmarksSidebar"); +} + +/** + * Simulates popup opening causing it to populate. + * We cannot just use menu.open, since it would not work on Mac due to native menubar. + */ +function fakeOpenPopup(aPopup) { + var popupEvent = document.createEvent("MouseEvent"); + popupEvent.initMouseEvent("popupshowing", true, true, window, 0, + 0, 0, 0, 0, false, false, false, false, + 0, null); + aPopup.dispatchEvent(popupEvent); +} + +/** + * Adds bookmarks observer, and executes a bunch of bookmarks operations. + */ +function startTest() { + var bs = PlacesUtils.bookmarks; + // Add observers. + bs.addObserver(bookmarksObserver, false); + PlacesUtils.annotations.addObserver(bookmarksObserver, false); + var addedBookmarks = []; + + // MENU + info("*** Acting on menu bookmarks"); + var id = bs.insertBookmark(bs.bookmarksMenuFolder, + PlacesUtils._uri("http://bm1.mozilla.org/"), + bs.DEFAULT_INDEX, + "bm1"); + bs.setItemTitle(id, "bm1_edited"); + addedBookmarks.push(id); + id = bs.insertBookmark(bs.bookmarksMenuFolder, + PlacesUtils._uri("place:"), + bs.DEFAULT_INDEX, + "bm2"); + bs.setItemTitle(id, ""); + addedBookmarks.push(id); + id = bs.insertSeparator(bs.bookmarksMenuFolder, bs.DEFAULT_INDEX); + addedBookmarks.push(id); + id = bs.createFolder(bs.bookmarksMenuFolder, + "bmf", + bs.DEFAULT_INDEX); + bs.setItemTitle(id, "bmf_edited"); + addedBookmarks.push(id); + id = bs.insertBookmark(id, + PlacesUtils._uri("http://bmf1.mozilla.org/"), + bs.DEFAULT_INDEX, + "bmf1"); + bs.setItemTitle(id, "bmf1_edited"); + addedBookmarks.push(id); + bs.moveItem(id, bs.bookmarksMenuFolder, 0); + + // TOOLBAR + info("*** Acting on toolbar bookmarks"); + id = bs.insertBookmark(bs.toolbarFolder, + PlacesUtils._uri("http://tb1.mozilla.org/"), + bs.DEFAULT_INDEX, + "tb1"); + bs.setItemTitle(id, "tb1_edited"); + addedBookmarks.push(id); + // Test live update of title. + bs.setItemTitle(id, "tb1_edited"); + id = bs.insertBookmark(bs.toolbarFolder, + PlacesUtils._uri("place:"), + bs.DEFAULT_INDEX, + "tb2"); + bs.setItemTitle(id, ""); + addedBookmarks.push(id); + id = bs.insertSeparator(bs.toolbarFolder, bs.DEFAULT_INDEX); + addedBookmarks.push(id); + id = bs.createFolder(bs.toolbarFolder, + "tbf", + bs.DEFAULT_INDEX); + bs.setItemTitle(id, "tbf_edited"); + addedBookmarks.push(id); + id = bs.insertBookmark(id, + PlacesUtils._uri("http://tbf1.mozilla.org/"), + bs.DEFAULT_INDEX, + "tbf1"); + bs.setItemTitle(id, "tbf1_edited"); + addedBookmarks.push(id); + bs.moveItem(id, bs.toolbarFolder, 0); + + // UNSORTED + info("*** Acting on unsorted bookmarks"); + id = bs.insertBookmark(bs.unfiledBookmarksFolder, + PlacesUtils._uri("http://ub1.mozilla.org/"), + bs.DEFAULT_INDEX, + "ub1"); + bs.setItemTitle(id, "ub1_edited"); + addedBookmarks.push(id); + id = bs.insertBookmark(bs.unfiledBookmarksFolder, + PlacesUtils._uri("place:"), + bs.DEFAULT_INDEX, + "ub2"); + bs.setItemTitle(id, "ub2_edited"); + addedBookmarks.push(id); + id = bs.insertSeparator(bs.unfiledBookmarksFolder, bs.DEFAULT_INDEX); + addedBookmarks.push(id); + id = bs.createFolder(bs.unfiledBookmarksFolder, + "ubf", + bs.DEFAULT_INDEX); + bs.setItemTitle(id, "ubf_edited"); + addedBookmarks.push(id); + id = bs.insertBookmark(id, + PlacesUtils._uri("http://ubf1.mozilla.org/"), + bs.DEFAULT_INDEX, + "bubf1"); + bs.setItemTitle(id, "bubf1_edited"); + addedBookmarks.push(id); + bs.moveItem(id, bs.unfiledBookmarksFolder, 0); + + // Remove all added bookmarks. + addedBookmarks.forEach(function (aItem) { + // If we remove an item after its containing folder has been removed, + // this will throw, but we can ignore that. + try { + bs.removeItem(aItem); + } catch (ex) {} + }); + + // Remove observers. + bs.removeObserver(bookmarksObserver); + PlacesUtils.annotations.removeObserver(bookmarksObserver); + finishTest(); +} + +/** + * Restores browser state and calls finish. + */ +function finishTest() { + // Close bookmarks sidebar. + SidebarUI.hide(); + + // Collapse the personal toolbar if needed. + if (wasCollapsed) { + promiseSetToolbarVisibility(toolbar, false).then(finish); + } else { + finish(); + } +} + +/** + * The observer is where magic happens, for every change we do it will look for + * nodes positions in the affected views. + */ +var bookmarksObserver = { + QueryInterface: XPCOMUtils.generateQI([ + Ci.nsINavBookmarkObserver + , Ci.nsIAnnotationObserver + ]), + + // nsIAnnotationObserver + onItemAnnotationSet: function() {}, + onItemAnnotationRemoved: function() {}, + onPageAnnotationSet: function() {}, + onPageAnnotationRemoved: function() {}, + + // nsINavBookmarkObserver + onItemAdded: function PSB_onItemAdded(aItemId, aFolderId, aIndex, + aItemType, aURI) { + var views = getViewsForFolder(aFolderId); + ok(views.length > 0, "Found affected views (" + views.length + "): " + views); + + // Check that item has been added in the correct position. + for (var i = 0; i < views.length; i++) { + var [node, index] = searchItemInView(aItemId, views[i]); + isnot(node, null, "Found new Places node in " + views[i]); + is(index, aIndex, "Node is at index " + index); + } + }, + + onItemRemoved: function PSB_onItemRemoved(aItemId, aFolderId, aIndex, + aItemType) { + var views = getViewsForFolder(aFolderId); + ok(views.length > 0, "Found affected views (" + views.length + "): " + views); + // Check that item has been removed. + for (var i = 0; i < views.length; i++) { + var node = null; + [node, ] = searchItemInView(aItemId, views[i]); + is(node, null, "Places node not found in " + views[i]); + } + }, + + onItemMoved: function(aItemId, + aOldFolderId, aOldIndex, + aNewFolderId, aNewIndex, + aItemType) { + var views = getViewsForFolder(aNewFolderId); + ok(views.length > 0, "Found affected views: " + views); + + // Check that item has been moved in the correct position. + for (var i = 0; i < views.length; i++) { + var node = null; + var index = null; + [node, index] = searchItemInView(aItemId, views[i]); + isnot(node, null, "Found new Places node in " + views[i]); + is(index, aNewIndex, "Node is at index " + index); + } + }, + + onBeginUpdateBatch: function PSB_onBeginUpdateBatch() {}, + onEndUpdateBatch: function PSB_onEndUpdateBatch() {}, + onItemVisited: function() {}, + + onItemChanged: function PSB_onItemChanged(aItemId, aProperty, + aIsAnnotationProperty, aNewValue, + aLastModified, aItemType, + aParentId) { + if (aProperty !== "title") + return; + + var views = getViewsForFolder(aParentId); + ok(views.length > 0, "Found affected views (" + views.length + "): " + views); + + // Check that item has been moved in the correct position. + let validator = function(aElementOrTreeIndex) { + if (typeof(aElementOrTreeIndex) == "number") { + var sidebar = document.getElementById("sidebar"); + var tree = sidebar.contentDocument.getElementById("bookmarks-view"); + let cellText = tree.view.getCellText(aElementOrTreeIndex, + tree.columns.getColumnAt(0)); + if (!aNewValue) + return cellText == PlacesUIUtils.getBestTitle(tree.view.nodeForTreeIndex(aElementOrTreeIndex), true); + return cellText == aNewValue; + } + if (!aNewValue && aElementOrTreeIndex.localName != "toolbarbutton") { + return aElementOrTreeIndex.getAttribute("label") == PlacesUIUtils.getBestTitle(aElementOrTreeIndex._placesNode); + } + return aElementOrTreeIndex.getAttribute("label") == aNewValue; + }; + + for (var i = 0; i < views.length; i++) { + var [node, , valid] = searchItemInView(aItemId, views[i], validator); + isnot(node, null, "Found changed Places node in " + views[i]); + is(node.title, aNewValue, "Node has correct title: " + aNewValue); + ok(valid, "Node element has correct label: " + aNewValue); + } + } +}; + +/** + * Search an item id in a view. + * + * @param aItemId + * item id of the item to search. + * @param aView + * either "toolbar", "menu" or "sidebar" + * @param aValidator + * function to check validity of the found node element. + * @returns [node, index, valid] or [null, null, false] if not found. + */ +function searchItemInView(aItemId, aView, aValidator) { + switch (aView) { + case "toolbar": + return getNodeForToolbarItem(aItemId, aValidator); + case "menu": + return getNodeForMenuItem(aItemId, aValidator); + case "sidebar": + return getNodeForSidebarItem(aItemId, aValidator); + } + + return [null, null, false]; +} + +/** + * Get places node and index for an itemId in bookmarks toolbar view. + * + * @param aItemId + * item id of the item to search. + * @returns [node, index] or [null, null] if not found. + */ +function getNodeForToolbarItem(aItemId, aValidator) { + var toolbar = document.getElementById("PlacesToolbarItems"); + + function findNode(aContainer) { + var children = aContainer.childNodes; + for (var i = 0, staticNodes = 0; i < children.length; i++) { + var child = children[i]; + + // Is this a Places node? + if (!child._placesNode || child.hasAttribute("simulated-places-node")) { + staticNodes++; + continue; + } + + if (child._placesNode.itemId == aItemId) { + let valid = aValidator ? aValidator(child) : true; + return [child._placesNode, i - staticNodes, valid]; + } + + // Don't search in queries, they could contain our item in a + // different position. Search only folders + if (PlacesUtils.nodeIsFolder(child._placesNode)) { + var popup = child.lastChild; + popup.showPopup(popup); + var foundNode = findNode(popup); + popup.hidePopup(); + if (foundNode[0] != null) + return foundNode; + } + } + return [null, null]; + } + + return findNode(toolbar); +} + +/** + * Get places node and index for an itemId in bookmarks menu view. + * + * @param aItemId + * item id of the item to search. + * @returns [node, index] or [null, null] if not found. + */ +function getNodeForMenuItem(aItemId, aValidator) { + var menu = document.getElementById("bookmarksMenu"); + + function findNode(aContainer) { + var children = aContainer.childNodes; + for (var i = 0, staticNodes = 0; i < children.length; i++) { + var child = children[i]; + + // Is this a Places node? + if (!child._placesNode || child.hasAttribute("simulated-places-node")) { + staticNodes++; + continue; + } + + if (child._placesNode.itemId == aItemId) { + let valid = aValidator ? aValidator(child) : true; + return [child._placesNode, i - staticNodes, valid]; + } + + // Don't search in queries, they could contain our item in a + // different position. Search only folders + if (PlacesUtils.nodeIsFolder(child._placesNode)) { + var popup = child.lastChild; + fakeOpenPopup(popup); + var foundNode = findNode(popup); + + child.open = false; + if (foundNode[0] != null) + return foundNode; + } + } + return [null, null, false]; + } + + return findNode(menu.lastChild); +} + +/** + * Get places node and index for an itemId in sidebar tree view. + * + * @param aItemId + * item id of the item to search. + * @returns [node, index] or [null, null] if not found. + */ +function getNodeForSidebarItem(aItemId, aValidator) { + var sidebar = document.getElementById("sidebar"); + var tree = sidebar.contentDocument.getElementById("bookmarks-view"); + + function findNode(aContainerIndex) { + if (tree.view.isContainerEmpty(aContainerIndex)) + return [null, null, false]; + + // The rowCount limit is just for sanity, but we will end looping when + // we have checked the last child of this container or we have found node. + for (var i = aContainerIndex + 1; i < tree.view.rowCount; i++) { + var node = tree.view.nodeForTreeIndex(i); + + if (node.itemId == aItemId) { + // Minus one because we want relative index inside the container. + let valid = aValidator ? aValidator(i) : true; + return [node, i - tree.view.getParentIndex(i) - 1, valid]; + } + + if (PlacesUtils.nodeIsFolder(node)) { + // Open container. + tree.view.toggleOpenState(i); + // Search inside it. + var foundNode = findNode(i); + // Close container. + tree.view.toggleOpenState(i); + // Return node if found. + if (foundNode[0] != null) + return foundNode; + } + + // We have finished walking this container. + if (!tree.view.hasNextSibling(aContainerIndex + 1, i)) + break; + } + return [null, null, false] + } + + // Root node is hidden, so we need to manually walk the first level. + for (var i = 0; i < tree.view.rowCount; i++) { + // Open container. + tree.view.toggleOpenState(i); + // Search inside it. + var foundNode = findNode(i); + // Close container. + tree.view.toggleOpenState(i); + // Return node if found. + if (foundNode[0] != null) + return foundNode; + } + return [null, null, false]; +} + +/** + * Get views affected by changes to a folder. + * + * @param aFolderId: + * item id of the folder we have changed. + * @returns a subset of views: ["toolbar", "menu", "sidebar"] + */ +function getViewsForFolder(aFolderId) { + var rootId = aFolderId; + while (!PlacesUtils.isRootItem(rootId)) + rootId = PlacesUtils.bookmarks.getFolderIdForItem(rootId); + + switch (rootId) { + case PlacesUtils.toolbarFolderId: + return ["toolbar", "sidebar"] + case PlacesUtils.bookmarksMenuFolderId: + return ["menu", "sidebar"] + case PlacesUtils.unfiledBookmarksFolderId: + return ["sidebar"] + } + return new Array(); +} diff --git a/browser/components/places/tests/browser/frameLeft.html b/browser/components/places/tests/browser/frameLeft.html new file mode 100644 index 000000000..5a54fe353 --- /dev/null +++ b/browser/components/places/tests/browser/frameLeft.html @@ -0,0 +1,8 @@ +<html> + <head> + <title>Left frame</title> + </head> + <body> + <a id="clickme" href="frameRight.html" target="right">Open page in the right frame.</a> + </body> +</html> diff --git a/browser/components/places/tests/browser/frameRight.html b/browser/components/places/tests/browser/frameRight.html new file mode 100644 index 000000000..226accc34 --- /dev/null +++ b/browser/components/places/tests/browser/frameRight.html @@ -0,0 +1,8 @@ +<html> + <head> + <title>Right Frame</title> + </head> + <body> + This is the right frame. + </body> +</html> diff --git a/browser/components/places/tests/browser/framedPage.html b/browser/components/places/tests/browser/framedPage.html new file mode 100644 index 000000000..d388562e6 --- /dev/null +++ b/browser/components/places/tests/browser/framedPage.html @@ -0,0 +1,9 @@ +<html> + <head> + <title>Framed page</title> + </head> + <frameset cols="*,*"> + <frame name="left" src="frameLeft.html"> + <frame name="right" src="about:mozilla"> + </frameset> +</html> diff --git a/browser/components/places/tests/browser/head.js b/browser/components/places/tests/browser/head.js new file mode 100644 index 000000000..aaf78332e --- /dev/null +++ b/browser/components/places/tests/browser/head.js @@ -0,0 +1,460 @@ +XPCOMUtils.defineLazyModuleGetter(this, "NetUtil", + "resource://gre/modules/NetUtil.jsm"); +XPCOMUtils.defineLazyModuleGetter(this, "Promise", + "resource://gre/modules/Promise.jsm"); +XPCOMUtils.defineLazyModuleGetter(this, "PlacesTestUtils", + "resource://testing-common/PlacesTestUtils.jsm"); + +// We need to cache this before test runs... +var cachedLeftPaneFolderIdGetter; +var getter = PlacesUIUtils.__lookupGetter__("leftPaneFolderId"); +if (!cachedLeftPaneFolderIdGetter && typeof(getter) == "function") { + cachedLeftPaneFolderIdGetter = getter; +} + +// ...And restore it when test ends. +registerCleanupFunction(function() { + let getter = PlacesUIUtils.__lookupGetter__("leftPaneFolderId"); + if (cachedLeftPaneFolderIdGetter && typeof(getter) != "function") { + PlacesUIUtils.__defineGetter__("leftPaneFolderId", cachedLeftPaneFolderIdGetter); + } +}); + +function openLibrary(callback, aLeftPaneRoot) { + let library = window.openDialog("chrome://browser/content/places/places.xul", + "", "chrome,toolbar=yes,dialog=no,resizable", + aLeftPaneRoot); + waitForFocus(function () { + callback(library); + }, library); + + return library; +} + +/** + * Returns a handle to a Library window. + * If one is opens returns itm otherwise it opens a new one. + * + * @param aLeftPaneRoot + * Hierarchy to open and select in the left pane. + */ +function promiseLibrary(aLeftPaneRoot) { + return new Promise(resolve => { + let library = Services.wm.getMostRecentWindow("Places:Organizer"); + if (library && !library.closed) { + if (aLeftPaneRoot) { + library.PlacesOrganizer.selectLeftPaneContainerByHierarchy(aLeftPaneRoot); + } + resolve(library); + } + else { + openLibrary(resolve, aLeftPaneRoot); + } + }); +} + +function promiseLibraryClosed(organizer) { + return new Promise(resolve => { + // Wait for the Organizer window to actually be closed + organizer.addEventListener("unload", function onUnload() { + organizer.removeEventListener("unload", onUnload); + resolve(); + }); + + // Close Library window. + organizer.close(); + }); +} + +/** + * Waits for a clipboard operation to complete, looking for the expected type. + * + * @see waitForClipboard + * + * @param aPopulateClipboardFn + * Function to populate the clipboard. + * @param aFlavor + * Data flavor to expect. + */ +function promiseClipboard(aPopulateClipboardFn, aFlavor) { + return new Promise(resolve => { + waitForClipboard(data => !!data, aPopulateClipboardFn, resolve, aFlavor); + }); +} + +/** + * Waits for all pending async statements on the default connection, before + * proceeding with aCallback. + * + * @param aCallback + * Function to be called when done. + * @param aScope + * Scope for the callback. + * @param aArguments + * Arguments array for the callback. + * + * @note The result is achieved by asynchronously executing a query requiring + * a write lock. Since all statements on the same connection are + * serialized, the end of this write operation means that all writes are + * complete. Note that WAL makes so that writers don't block readers, but + * this is a problem only across different connections. + */ +function waitForAsyncUpdates(aCallback, aScope, aArguments) +{ + let scope = aScope || this; + let args = aArguments || []; + let db = PlacesUtils.history.QueryInterface(Ci.nsPIPlacesDatabase) + .DBConnection; + let begin = db.createAsyncStatement("BEGIN EXCLUSIVE"); + begin.executeAsync(); + begin.finalize(); + + let commit = db.createAsyncStatement("COMMIT"); + commit.executeAsync({ + handleResult: function() {}, + handleError: function() {}, + handleCompletion: function(aReason) + { + aCallback.apply(scope, args); + } + }); + commit.finalize(); +} + +function synthesizeClickOnSelectedTreeCell(aTree, aOptions) { + let tbo = aTree.treeBoxObject; + if (tbo.view.selection.count != 1) + throw new Error("The test node should be successfully selected"); + // Get selection rowID. + let min = {}, max = {}; + tbo.view.selection.getRangeAt(0, min, max); + let rowID = min.value; + tbo.ensureRowIsVisible(rowID); + // Calculate the click coordinates. + var rect = tbo.getCoordsForCellItem(rowID, aTree.columns[0], "text"); + var x = rect.x + rect.width / 2; + var y = rect.y + rect.height / 2; + // Simulate the click. + EventUtils.synthesizeMouse(aTree.body, x, y, aOptions || {}, + aTree.ownerGlobal); +} + +/** + * Asynchronously check a url is visited. + * + * @param aURI The URI. + * @return {Promise} + * @resolves When the check has been added successfully. + * @rejects JavaScript exception. + */ +function promiseIsURIVisited(aURI) { + let deferred = Promise.defer(); + + PlacesUtils.asyncHistory.isURIVisited(aURI, function(aURI, aIsVisited) { + deferred.resolve(aIsVisited); + }); + + return deferred.promise; +} + +function promiseBookmarksNotification(notification, conditionFn) { + info(`promiseBookmarksNotification: waiting for ${notification}`); + return new Promise((resolve) => { + let proxifiedObserver = new Proxy({}, { + get: (target, name) => { + if (name == "QueryInterface") + return XPCOMUtils.generateQI([ Ci.nsINavBookmarkObserver ]); + info(`promiseBookmarksNotification: got ${name} notification`); + if (name == notification) + return (...args) => { + if (conditionFn.apply(this, args)) { + PlacesUtils.bookmarks.removeObserver(proxifiedObserver, false); + executeSoon(resolve); + } else { + info(`promiseBookmarksNotification: skip cause condition doesn't apply to ${JSON.stringify(args)}`); + } + } + return () => {}; + } + }); + PlacesUtils.bookmarks.addObserver(proxifiedObserver, false); + }); +} + +function promiseHistoryNotification(notification, conditionFn) { + info(`Waiting for ${notification}`); + return new Promise((resolve) => { + let proxifiedObserver = new Proxy({}, { + get: (target, name) => { + if (name == "QueryInterface") + return XPCOMUtils.generateQI([ Ci.nsINavHistoryObserver ]); + if (name == notification) + return (...args) => { + if (conditionFn.apply(this, args)) { + PlacesUtils.history.removeObserver(proxifiedObserver, false); + executeSoon(resolve); + } + } + return () => {}; + } + }); + PlacesUtils.history.addObserver(proxifiedObserver, false); + }); +} + +/** + * Makes the specified toolbar visible or invisible and returns a Promise object + * that is resolved when the toolbar has completed any animations associated + * with hiding or showing the toolbar. + * + * Note that this code assumes that changes to a toolbar's visibility trigger + * a transition on the max-height property of the toolbar element. + * Changes to this styling could cause the returned Promise object to be + * resolved too early or not at all. + * + * @param aToolbar + * The toolbar to update. + * @param aVisible + * True to make the toolbar visible, false to make it hidden. + * + * @return {Promise} + * @resolves Any animation associated with updating the toolbar's visibility has + * finished. + * @rejects Never. + */ +function promiseSetToolbarVisibility(aToolbar, aVisible, aCallback) { + return new Promise((resolve, reject) => { + function listener(event) { + if (event.propertyName == "max-height") { + aToolbar.removeEventListener("transitionend", listener); + resolve(); + } + } + + let transitionProperties = + window.getComputedStyle(aToolbar).transitionProperty.split(", "); + if (isToolbarVisible(aToolbar) != aVisible && + transitionProperties.some( + prop => prop == "max-height" || prop == "all" + )) { + // Just because max-height is a transitionable property doesn't mean + // a transition will be triggered, but it's more likely. + aToolbar.addEventListener("transitionend", listener); + setToolbarVisibility(aToolbar, aVisible); + return; + } + + // No animation to wait for + setToolbarVisibility(aToolbar, aVisible); + resolve(); + }); +} + +/** + * Helper function to determine if the given toolbar is in the visible + * state according to its autohide/collapsed attribute. + * + * @aToolbar The toolbar to query. + * + * @returns True if the relevant attribute on |aToolbar| indicates it is + * visible, false otherwise. + */ +function isToolbarVisible(aToolbar) { + let hidingAttribute = aToolbar.getAttribute("type") == "menubar" + ? "autohide" + : "collapsed"; + let hidingValue = aToolbar.getAttribute(hidingAttribute).toLowerCase(); + // Check for both collapsed="true" and collapsed="collapsed" + return hidingValue !== "true" && hidingValue !== hidingAttribute; +} + +/** + * Executes a task after opening the bookmarks dialog, then cancels the dialog. + * + * @param autoCancel + * whether to automatically cancel the dialog at the end of the task + * @param openFn + * generator function causing the dialog to open + * @param task + * the task to execute once the dialog is open + */ +var withBookmarksDialog = Task.async(function* (autoCancel, openFn, taskFn) { + let closed = false; + let dialogPromise = new Promise(resolve => { + Services.ww.registerNotification(function winObserver(subject, topic, data) { + if (topic == "domwindowopened") { + let win = subject.QueryInterface(Ci.nsIDOMWindow); + win.addEventListener("load", function load() { + win.removeEventListener("load", load); + ok(win.location.href.startsWith("chrome://browser/content/places/bookmarkProperties"), + "The bookmark properties dialog is open"); + // This is needed for the overlay. + waitForFocus(() => { + resolve(win); + }, win); + }); + } else if (topic == "domwindowclosed") { + Services.ww.unregisterNotification(winObserver); + closed = true; + } + }); + }); + + info("withBookmarksDialog: opening the dialog"); + // The dialog might be modal and could block our events loop, so executeSoon. + executeSoon(openFn); + + info("withBookmarksDialog: waiting for the dialog"); + let dialogWin = yield dialogPromise; + + // Ensure overlay is loaded + info("waiting for the overlay to be loaded"); + yield waitForCondition(() => dialogWin.gEditItemOverlay.initialized, + "EditItemOverlay should be initialized"); + + // Check the first textbox is focused. + let doc = dialogWin.document; + let elt = doc.querySelector("textbox:not([collapsed=true])"); + if (elt) { + info("waiting for focus on the first textfield"); + yield waitForCondition(() => doc.activeElement == elt.inputField, + "The first non collapsed textbox should have been focused"); + } + + info("withBookmarksDialog: executing the task"); + try { + yield taskFn(dialogWin); + } finally { + if (!closed) { + if (!autoCancel) { + ok(false, "The test should have closed the dialog!"); + } + info("withBookmarksDialog: canceling the dialog"); + doc.documentElement.cancelDialog(); + } + } +}); + +/** + * Opens the contextual menu on the element pointed by the given selector. + * + * @param selector + * Valid selector syntax + * @return Promise + * Returns a Promise that resolves once the context menu has been + * opened. + */ +var openContextMenuForContentSelector = Task.async(function* (browser, selector) { + info("wait for the context menu"); + let contextPromise = BrowserTestUtils.waitForEvent(document.getElementById("contentAreaContextMenu"), + "popupshown"); + yield ContentTask.spawn(browser, { selector }, function* (args) { + let doc = content.document; + let elt = doc.querySelector(args.selector) + dump(`openContextMenuForContentSelector: found ${elt}\n`); + + /* Open context menu so chrome can access the element */ + const domWindowUtils = + content.QueryInterface(Components.interfaces.nsIInterfaceRequestor) + .getInterface(Components.interfaces.nsIDOMWindowUtils); + let rect = elt.getBoundingClientRect(); + let left = rect.left + rect.width / 2; + let top = rect.top + rect.height / 2; + domWindowUtils.sendMouseEvent("contextmenu", left, top, 2, + 1, 0, false, 0, 0, true); + }); + yield contextPromise; +}); + +/** + * Waits for a specified condition to happen. + * + * @param conditionFn + * a Function or a generator function, returning a boolean for whether + * the condition is fulfilled. + * @param errorMsg + * Error message to use if the condition has not been satisfied after a + * meaningful amount of tries. + */ +var waitForCondition = Task.async(function* (conditionFn, errorMsg) { + for (let tries = 0; tries < 100; ++tries) { + if ((yield conditionFn())) + return; + yield new Promise(resolve => { + if (!waitForCondition._timers) { + waitForCondition._timers = new Set(); + registerCleanupFunction(() => { + is(waitForCondition._timers.size, 0, "All the wait timers have been removed"); + delete waitForCondition._timers; + }); + } + let timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer); + waitForCondition._timers.add(timer); + timer.init(() => { + waitForCondition._timers.delete(timer); + resolve(); + }, 100, Ci.nsITimer.TYPE_ONE_SHOT); + }); + } + ok(false, errorMsg); +}); + +/** + * Fills a bookmarks dialog text field ensuring to cause expected edit events. + * + * @param id + * id of the text field + * @param text + * text to fill in + * @param win + * dialog window + * @param [optional] blur + * whether to blur at the end. + */ +function fillBookmarkTextField(id, text, win, blur = true) { + let elt = win.document.getElementById(id); + elt.focus(); + elt.select(); + for (let c of text.split("")) { + EventUtils.synthesizeKey(c, {}, win); + } + if (blur) + elt.blur(); +} + +/** + * Executes a task after opening the bookmarks or history sidebar. Takes care + * of closing the sidebar once done. + * + * @param type + * either "bookmarks" or "history". + * @param taskFn + * The task to execute once the sidebar is ready. Will get the Places + * tree view as input. + */ +var withSidebarTree = Task.async(function* (type, taskFn) { + let sidebar = document.getElementById("sidebar"); + info("withSidebarTree: waiting sidebar load"); + let sidebarLoadedPromise = new Promise(resolve => { + sidebar.addEventListener("load", function load() { + sidebar.removeEventListener("load", load, true); + resolve(); + }, true); + }); + let sidebarId = type == "bookmarks" ? "viewBookmarksSidebar" + : "viewHistorySidebar"; + SidebarUI.show(sidebarId); + yield sidebarLoadedPromise; + + let treeId = type == "bookmarks" ? "bookmarks-view" + : "historyTree"; + let tree = sidebar.contentDocument.getElementById(treeId); + + // Need to executeSoon since the tree is initialized on sidebar load. + info("withSidebarTree: executing the task"); + try { + yield taskFn(tree); + } finally { + SidebarUI.hide(); + } +}); diff --git a/browser/components/places/tests/browser/keyword_form.html b/browser/components/places/tests/browser/keyword_form.html new file mode 100644 index 000000000..a881c0d5a --- /dev/null +++ b/browser/components/places/tests/browser/keyword_form.html @@ -0,0 +1,17 @@ +<!DOCTYPE HTML> + +<html lang="en"> +<head> + <meta http-equiv="Content-Type" content="text/html;charset=windows-1252"> +</head> +<body> + <form id="form1" method="POST" action="keyword_form.html"> + <input type="hidden" name="accenti" value="àèìòù"> + <input type="text" name="search"> + </form> + <form id="form2" method="POST" action="keyword_form.html"> + <input type="hidden" name="accenti" value="ùòìèà"> + <input type="text" name="search"> + </form> +</body> +</html> diff --git a/browser/components/places/tests/browser/pageopeningwindow.html b/browser/components/places/tests/browser/pageopeningwindow.html new file mode 100644 index 000000000..282f9c593 --- /dev/null +++ b/browser/components/places/tests/browser/pageopeningwindow.html @@ -0,0 +1,9 @@ +<meta charset="UTF-8"> +Hi, I was opened via a <script>document.write(location.search ? + "popup call from the opened window... uh oh, that shouldn't happen!" : + "bookmarklet, and I will open a new window myself.")</script><br> +<script> + if (!location.search) { + open(location.href + "?donotopen=true", '_blank'); + } +</script> diff --git a/browser/components/places/tests/browser/sidebarpanels_click_test_page.html b/browser/components/places/tests/browser/sidebarpanels_click_test_page.html new file mode 100644 index 000000000..c73eaa540 --- /dev/null +++ b/browser/components/places/tests/browser/sidebarpanels_click_test_page.html @@ -0,0 +1,7 @@ +<html> +<head> + <title>browser_sidebarpanels_click.js test page</title> +</head> +<body onload="alert('test');"> +</body> +</html> diff --git a/browser/components/places/tests/chrome/.eslintrc.js b/browser/components/places/tests/chrome/.eslintrc.js new file mode 100644 index 000000000..8c0f4f574 --- /dev/null +++ b/browser/components/places/tests/chrome/.eslintrc.js @@ -0,0 +1,7 @@ +"use strict"; + +module.exports = { + "extends": [ + "../../../../../testing/mochitest/chrome.eslintrc.js" + ] +}; diff --git a/browser/components/places/tests/chrome/chrome.ini b/browser/components/places/tests/chrome/chrome.ini new file mode 100644 index 000000000..d7b4a55c8 --- /dev/null +++ b/browser/components/places/tests/chrome/chrome.ini @@ -0,0 +1,15 @@ +[DEFAULT] +support-files = head.js + +[test_0_bug510634.xul] +[test_0_multiple_left_pane.xul] +[test_bug1163447_selectItems_through_shortcut.xul] +[test_bug427633_no_newfolder_if_noip.xul] +[test_bug485100-change-case-loses-tag.xul] +[test_bug549192.xul] +[test_bug549491.xul] +[test_bug631374_tags_selector_scroll.xul] +[test_editBookmarkOverlay_keywords.xul] +[test_editBookmarkOverlay_tags_liveUpdate.xul] +[test_selectItems_on_nested_tree.xul] +[test_treeview_date.xul] diff --git a/browser/components/places/tests/chrome/head.js b/browser/components/places/tests/chrome/head.js new file mode 100644 index 000000000..26b97f6d7 --- /dev/null +++ b/browser/components/places/tests/chrome/head.js @@ -0,0 +1,7 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +Components.utils.import("resource://gre/modules/XPCOMUtils.jsm"); + +XPCOMUtils.defineLazyModuleGetter(this, "PlacesTestUtils", + "resource://testing-common/PlacesTestUtils.jsm"); diff --git a/browser/components/places/tests/chrome/test_0_bug510634.xul b/browser/components/places/tests/chrome/test_0_bug510634.xul new file mode 100644 index 000000000..86e102180 --- /dev/null +++ b/browser/components/places/tests/chrome/test_0_bug510634.xul @@ -0,0 +1,99 @@ +<?xml version="1.0"?> + +<!-- 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/. --> + +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" + type="text/css"?> + +<?xml-stylesheet href="chrome://browser/content/places/places.css"?> +<?xml-stylesheet href="chrome://browser/skin/places/places.css"?> +<?xul-overlay href="chrome://browser/content/places/placesOverlay.xul"?> + +<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + title="510634: Wrong icons on bookmarks sidebar" + onload="runTest();"> + + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" /> + + <body xmlns="http://www.w3.org/1999/xhtml" /> + + <tree id="tree" + type="places" + flex="1"> + <treecols> + <treecol label="Title" id="title" anonid="title" primary="true" ordinal="1" flex="1"/> + </treecols> + <treechildren flex="1"/> + </tree> + + <script type="application/javascript"> + <![CDATA[ + + /** + * Bug 510634 - Wrong icons on bookmarks sidebar + * https://bugzilla.mozilla.org/show_bug.cgi?id=510634 + * + * Ensures that properties for special queries are set on their tree nodes, + * even if PlacesUIUtils.leftPaneFolderId was not initialized. + */ + + SimpleTest.waitForExplicitFinish(); + + function runTest() { + // We need to cache and restore this getter in order to simulate + // Bug 510634 + let cachedLeftPaneFolderIdGetter = + PlacesUIUtils.__lookupGetter__("leftPaneFolderId"); + // Must also cache and restore this getter as it is affected by + // leftPaneFolderId, from bug 564900. + let cachedAllBookmarksFolderIdGetter = + PlacesUIUtils.__lookupGetter__("allBookmarksFolderId"); + + let leftPaneFolderId = PlacesUIUtils.leftPaneFolderId; + + // restore the getter + PlacesUIUtils.__defineGetter__("leftPaneFolderId", cachedLeftPaneFolderIdGetter); + + // Setup the places tree contents. + let tree = document.getElementById("tree"); + tree.place = "place:queryType=1&folder=" + leftPaneFolderId; + + // The query-property is set on the title column for each row. + let titleColumn = tree.treeBoxObject.columns.getColumnAt(0); + + // Open All Bookmarks + tree.selectItems([PlacesUIUtils.leftPaneQueries["AllBookmarks"]]); + PlacesUtils.asContainer(tree.selectedNode).containerOpen = true; + is(PlacesUIUtils.allBookmarksFolderId, tree.selectedNode.itemId, + "Opened All Bookmarks"); + + ["History", "Downloads", "Tags", "AllBookmarks", "BookmarksToolbar", + "BookmarksMenu", "UnfiledBookmarks"].forEach( + function(aQueryName, aRow) { + let found = false; + for (let i = 0; i < tree.view.rowCount && !found; i++) { + rowProperties = tree.view.getCellProperties(i, titleColumn).split(" "); + found = rowProperties.includes("OrganizerQuery_" + aQueryName); + } + ok(found, "OrganizerQuery_" + aQueryName + " is set"); + } + ); + + // Close the root node + tree.result.root.containerOpen = false; + + // Restore the getters for the next test. + PlacesUIUtils.__defineGetter__("leftPaneFolderId", cachedLeftPaneFolderIdGetter); + PlacesUIUtils.__defineGetter__("allBookmarksFolderId", + cachedAllBookmarksFolderIdGetter); + + SimpleTest.finish(); + } + + ]]> + </script> +</window> diff --git a/browser/components/places/tests/chrome/test_0_multiple_left_pane.xul b/browser/components/places/tests/chrome/test_0_multiple_left_pane.xul new file mode 100644 index 000000000..09a4d2054 --- /dev/null +++ b/browser/components/places/tests/chrome/test_0_multiple_left_pane.xul @@ -0,0 +1,85 @@ +<?xml version="1.0"?> + +<!-- 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 466422: + - Check that we replace the left pane with a correct one if it gets corrupted + - and we end up having more than one. --> + +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" + type="text/css"?> + +<?xml-stylesheet href="chrome://browser/content/places/places.css"?> +<?xml-stylesheet href="chrome://browser/skin/places/places.css"?> + +<?xul-overlay href="chrome://browser/content/places/placesOverlay.xul"?> + +<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + title="Test handling of multiple left pane folders" + onload="runTest();"> + + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" /> + + <body xmlns="http://www.w3.org/1999/xhtml"> + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"></pre> + </body> + + <script type="application/javascript"> + <![CDATA[ + + function runTest() { + SimpleTest.waitForExplicitFinish(); + + Task.spawn(function* () { + // Sanity checks. + ok(PlacesUtils, "PlacesUtils is running in chrome context"); + ok(PlacesUIUtils, "PlacesUIUtils is running in chrome context"); + ok(PlacesUIUtils.ORGANIZER_LEFTPANE_VERSION > 0, + "Left pane version in chrome context, " + + "current version is: " + PlacesUIUtils.ORGANIZER_LEFTPANE_VERSION ); + + let fakeLeftPanes = []; + // We need 2 left pane folders to simulate a corrupt profile. + do { + let leftPaneItems = PlacesUtils.annotations.getItemsWithAnnotation(PlacesUIUtils.ORGANIZER_FOLDER_ANNO); + + // Create a fake left pane folder. + let folder = yield PlacesUtils.bookmarks.insert({ + parentGuid: PlacesUtils.bookmarks.rootGuid, + index: PlacesUtils.bookmarks.DEFAULT_INDEX, + type: PlacesUtils.bookmarks.TYPE_FOLDER + }); + + let fakeLeftPaneRoot = yield PlacesUtils.promiseItemId(folder.guid); + PlacesUtils.annotations.setItemAnnotation(fakeLeftPaneRoot, PlacesUIUtils.ORGANIZER_FOLDER_ANNO, + PlacesUIUtils.ORGANIZER_LEFTPANE_VERSION, 0, + PlacesUtils.annotations.EXPIRE_NEVER); + fakeLeftPanes.push(folder.guid); + } while (fakeLeftPanes.length < 2); + + // Initialize the left pane queries. + PlacesUIUtils.leftPaneFolderId; + + // Check left pane. + ok(PlacesUIUtils.leftPaneFolderId > 0, + "Left pane folder correctly created"); + let leftPaneItems = PlacesUtils.annotations.getItemsWithAnnotation(PlacesUIUtils.ORGANIZER_FOLDER_ANNO); + is(leftPaneItems.length, 1, + "We correctly have only 1 left pane folder"); + + // Check that all old left pane items have been removed. + for (let guid of fakeLeftPanes) { + ok(!(yield PlacesUtils.bookmarks.fetch({guid})), "This folder should have been removed"); + } + }).then(() => SimpleTest.finish()); + } + ]]> + </script> + +</window> diff --git a/browser/components/places/tests/chrome/test_bug1163447_selectItems_through_shortcut.xul b/browser/components/places/tests/chrome/test_bug1163447_selectItems_through_shortcut.xul new file mode 100644 index 000000000..8e3a99533 --- /dev/null +++ b/browser/components/places/tests/chrome/test_bug1163447_selectItems_through_shortcut.xul @@ -0,0 +1,89 @@ +<?xml version="1.0"?> + +<!-- + Any copyright is dedicated to the Public Domain. + http://creativecommons.org/licenses/publicdomain/ + --> + +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" + type="text/css"?> + +<?xml-stylesheet href="chrome://browser/content/places/places.css"?> +<?xml-stylesheet href="chrome://browser/skin/places/places.css"?> +<?xul-overlay href="chrome://browser/content/places/placesOverlay.xul"?> + +<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + title="1163447: selectItems in Places no longer selects items within Toolbar or Sidebar folders" + onload="runTest();"> + + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" /> + <script type="application/javascript" src="head.js" /> + + <body xmlns="http://www.w3.org/1999/xhtml" /> + + <tree id="tree" + type="places" + flex="1"> + <treecols> + <treecol label="Title" id="title" anonid="title" primary="true" ordinal="1" flex="1"/> + </treecols> + <treechildren flex="1"/> + </tree> + + <script type="application/javascript"><![CDATA[ + + /** + * Bug 1163447: places-tree should be able to select an item within the toolbar, and + * unfiled bookmarks. Yet not follow recursive folder-shortcuts infinitely. + */ + + function runTest() { + SimpleTest.waitForExplicitFinish(); + + Task.spawn(function* () { + let bmu = PlacesUtils.bookmarks; + + yield bmu.insert({ + parentGuid: bmu.toolbarGuid, + index: bmu.DEFAULT_INDEX, + type: bmu.TYPE_BOOKMARK, + url: "place:folder=TOOLBAR", + title: "shortcut to self - causing infinite recursion if not handled properly" + }); + + yield bmu.insert({ + parentGuid: bmu.toolbarGuid, + index: bmu.DEFAULT_INDEX, + type: bmu.TYPE_BOOKMARK, + url: "place:folder=UNFILED_BOOKMARKS", + title: "shortcut to unfiled, within toolbar" + }); + + let folder = yield bmu.insert({ + parentGuid: bmu.unfiledGuid, + index: bmu.DEFAULT_INDEX, + type: bmu.TYPE_FOLDER, + title: "folder within unfiled" + }); + + // Setup the places tree contents. + let tree = document.getElementById("tree"); + tree.place = "place:folder=TOOLBAR"; + + // Select the folder via the selectItems(itemId) API being tested + let itemId = yield PlacesUtils.promiseItemId(folder.guid); + tree.selectItems([itemId]); + + is(tree.selectedNode && tree.selectedNode.itemId, itemId, "The node was selected through the shortcut"); + + // Cleanup + yield bmu.eraseEverything(); + + }).catch(err => { + ok(false, `Uncaught error: ${err}`); + }).then(SimpleTest.finish); + } + ]]></script> +</window> diff --git a/browser/components/places/tests/chrome/test_bug427633_no_newfolder_if_noip.xul b/browser/components/places/tests/chrome/test_bug427633_no_newfolder_if_noip.xul new file mode 100644 index 000000000..b659b2b46 --- /dev/null +++ b/browser/components/places/tests/chrome/test_bug427633_no_newfolder_if_noip.xul @@ -0,0 +1,91 @@ +<?xml version="1.0"?> + +<!-- 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/. --> + +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" + type="text/css"?> + +<?xml-stylesheet href="chrome://browser/skin/places/editBookmarkOverlay.css"?> +<?xml-stylesheet href="chrome://browser/content/places/places.css"?> +<?xml-stylesheet href="chrome://browser/skin/places/places.css"?> + +<?xul-overlay href="chrome://browser/content/places/placesOverlay.xul"?> +<?xul-overlay href="chrome://browser/content/places/editBookmarkOverlay.xul"?> + +<!DOCTYPE window [ + <!ENTITY % editBookmarkOverlayDTD SYSTEM "chrome://browser/locale/places/editBookmarkOverlay.dtd"> + %editBookmarkOverlayDTD; +]> + +<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + title="Bug 427633 - Disable creating a New Folder in the bookmarks dialogs if insertionPoint is invalid" + onload="runTest();"> + + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" /> + <script type="application/javascript" + src="chrome://browser/content/places/editBookmarkOverlay.js"/> + + <body xmlns="http://www.w3.org/1999/xhtml" /> + + <vbox id="editBookmarkPanelContent"/> + + <script type="application/javascript"> + <![CDATA[ + + /** + * Bug 427633 - Disable creating a New Folder in the bookmarks dialogs if + * insertionPoint is invalid. + */ + + function runTest() { + SimpleTest.waitForExplicitFinish(); + + Task.spawn(function* () { + // Add a bookmark. + let bm = yield PlacesUtils.bookmarks.insert({ + parentGuid: PlacesUtils.bookmarks.toolbarGuid, + url: "http://www.example.com/", + index: PlacesUtils.bookmarks.DEFAULT_INDEX, + type: PlacesUtils.bookmarks.TYPE_BOOKMARK, + title: "mozilla" + }); + + // Init panel. + ok(gEditItemOverlay, "gEditItemOverlay is in context"); + let node = yield PlacesUIUtils.promiseNodeLikeFromFetchInfo(bm); + gEditItemOverlay.initPanel({ node }); + ok(gEditItemOverlay.initialized, "gEditItemOverlay is initialized"); + + let tree = gEditItemOverlay._element("folderTree"); + yield openFolderTree(tree); + + tree.view.selection.clearSelection(); + ok(document.getElementById("editBMPanel_newFolderButton").disabled, + "New folder button is disabled if there's no selection"); + + // Cleanup. + yield PlacesUtils.bookmarks.remove(bm.guid); + }).then(() => SimpleTest.finish()); + } + + function openFolderTree(tree) { + return new Promise(resolve => { + tree.addEventListener("DOMAttrModified", function onAttrModified(event) { + if (event.attrName == "place") { + tree.removeEventListener("DOMAttrModified", onAttrModified); + resolve(); + } + }); + + // Open the folder tree. + document.getElementById("editBMPanel_foldersExpander").doCommand(); + }); + } + ]]> + </script> + +</window> diff --git a/browser/components/places/tests/chrome/test_bug485100-change-case-loses-tag.xul b/browser/components/places/tests/chrome/test_bug485100-change-case-loses-tag.xul new file mode 100644 index 000000000..afad950cb --- /dev/null +++ b/browser/components/places/tests/chrome/test_bug485100-change-case-loses-tag.xul @@ -0,0 +1,83 @@ +<?xml version="1.0"?> + +<!-- 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/. --> + +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" + type="text/css"?> + +<?xml-stylesheet href="chrome://browser/skin/places/editBookmarkOverlay.css"?> +<?xml-stylesheet href="chrome://browser/content/places/places.css"?> +<?xml-stylesheet href="chrome://browser/skin/places/places.css"?> + +<?xul-overlay href="chrome://browser/content/places/placesOverlay.xul"?> +<?xul-overlay href="chrome://browser/content/places/editBookmarkOverlay.xul"?> + +<!DOCTYPE window [ + <!ENTITY % editBookmarkOverlayDTD SYSTEM "chrome://browser/locale/places/editBookmarkOverlay.dtd"> + %editBookmarkOverlayDTD; +]> + +<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + title="485100: Exchanging a letter of a tag name with its big/small equivalent removes tag from bookmark" + onload="runTest();"> + + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" /> + <script type="application/javascript" + src="chrome://browser/content/places/editBookmarkOverlay.js"/> + + <body xmlns="http://www.w3.org/1999/xhtml" /> + + <vbox id="editBookmarkPanelContent"/> + + <script type="application/javascript"> + <![CDATA[ + + function runTest() { + SimpleTest.waitForExplicitFinish(); + + Task.spawn(function* () { + let testTag = "foo"; + let testTagUpper = "Foo"; + let testURI = Services.io.newURI("http://www.example.com/", null, null); + + // Add a bookmark. + let bm = yield PlacesUtils.bookmarks.insert({ + parentGuid: PlacesUtils.bookmarks.toolbarGuid, + index: PlacesUtils.bookmarks.DEFAULT_INDEX, + type: PlacesUtils.bookmarks.TYPE_BOOKMARK, + title: "mozilla", + url: testURI + }); + + // Init panel + ok(gEditItemOverlay, "gEditItemOverlay is in context"); + let node = yield PlacesUIUtils.promiseNodeLikeFromFetchInfo(bm); + gEditItemOverlay.initPanel({ node }); + + // add a tag + document.getElementById("editBMPanel_tagsField").value = testTag; + gEditItemOverlay.onTagsFieldChange(); + + // test that the tag has been added in the backend + is(PlacesUtils.tagging.getTagsForURI(testURI)[0], testTag, "tags match"); + + // change the tag + document.getElementById("editBMPanel_tagsField").value = testTagUpper; + gEditItemOverlay.onTagsFieldChange(); + + // test that the tag has been added in the backend + is(PlacesUtils.tagging.getTagsForURI(testURI)[0], testTagUpper, "tags match"); + + // Cleanup. + PlacesUtils.tagging.untagURI(testURI, [testTag]); + yield PlacesUtils.bookmarks.remove(bm.guid); + }).then(() => SimpleTest.finish()); + } + ]]> + </script> + +</window> diff --git a/browser/components/places/tests/chrome/test_bug549192.xul b/browser/components/places/tests/chrome/test_bug549192.xul new file mode 100644 index 000000000..4e6a89bb1 --- /dev/null +++ b/browser/components/places/tests/chrome/test_bug549192.xul @@ -0,0 +1,120 @@ +<?xml version="1.0"?> + +<!-- + Any copyright is dedicated to the Public Domain. + http://creativecommons.org/licenses/publicdomain/ + --> + +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" + type="text/css"?> + +<?xml-stylesheet href="chrome://browser/content/places/places.css"?> +<?xml-stylesheet href="chrome://browser/skin/places/places.css"?> +<?xul-overlay href="chrome://browser/content/places/placesOverlay.xul"?> + +<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + title="549192: History view not updated after deleting entry" + onload="runTest();"> + + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" /> + <script type="application/javascript" src="head.js" /> + + <body xmlns="http://www.w3.org/1999/xhtml" /> + + <tree id="tree" + type="places" + flatList="true" + flex="1"> + <treecols> + <treecol label="Title" id="title" anonid="title" primary="true" ordinal="1" flex="1"/> + </treecols> + <treechildren flex="1"/> + </tree> + + <script type="application/javascript"><![CDATA[ + /** + * Bug 874407 + * Ensures that history views are updated properly after visits. + * Bug 549192 + * Ensures that history views are updated after deleting entries. + */ + + function runTest() { + SimpleTest.waitForExplicitFinish(); + + Task.spawn(function* () { + yield PlacesTestUtils.clearHistory(); + + // Add some visits. + let timeInMicroseconds = PlacesUtils.toPRTime(Date.now() - 10000); + + function newTimeInMicroseconds() { + timeInMicroseconds = timeInMicroseconds + 1000; + return timeInMicroseconds; + } + + let vtime = Date.now() * 1000; + const ttype = PlacesUtils.history.TRANSITION_TYPED; + let places = + [{ uri: Services.io.newURI("http://example.tld/", null, null), + visitDate: newTimeInMicroseconds(), transition: ttype }, + { uri: Services.io.newURI("http://example2.tld/", null, null), + visitDate: newTimeInMicroseconds(), transition: ttype }, + { uri: Services.io.newURI("http://example3.tld/", null, null), + visitDate: newTimeInMicroseconds(), transition: ttype }]; + + yield PlacesTestUtils.addVisits(places); + + // Make a history query. + let query = PlacesUtils.history.getNewQuery(); + let opts = PlacesUtils.history.getNewQueryOptions(); + opts.sortingMode = opts.SORT_BY_DATE_DESCENDING; + let queryURI = PlacesUtils.history.queriesToQueryString([query], 1, opts); + + // Setup the places tree contents. + var tree = document.getElementById("tree"); + tree.place = queryURI; + + // loop through the rows and check them. + let treeView = tree.view; + let selection = treeView.selection; + let rc = treeView.rowCount; + + for (let i = 0; i < rc; i++) { + selection.select(i); + let node = tree.selectedNode; + is(node.uri, places[rc - i - 1].uri.spec, + "Found expected node at position " + i + "."); + } + + is(rc, 3, "Found expected number of rows."); + + // First check live-update of the view when adding visits. + places.forEach(place => place.visitDate = newTimeInMicroseconds()); + yield PlacesTestUtils.addVisits(places); + + for (let i = 0; i < rc; i++) { + selection.select(i); + let node = tree.selectedNode; + is(node.uri, places[rc - i - 1].uri.spec, + "Found expected node at position " + i + "."); + } + + // Now remove the pages and verify live-update again. + for (let i = 0; i < rc; i++) { + selection.select(0); + let node = tree.selectedNode; + tree.controller.remove("Removing page"); + ok(treeView.treeIndexForNode(node) == Ci.nsINavHistoryResultTreeViewer.INDEX_INVISIBLE, + node.uri + " removed."); + ok(treeView.rowCount == rc - i - 1, "Rows count decreased"); + } + + // Cleanup. + yield PlacesTestUtils.clearHistory(); + }).then(() => SimpleTest.finish()); + } + ]]></script> +</window> diff --git a/browser/components/places/tests/chrome/test_bug549491.xul b/browser/components/places/tests/chrome/test_bug549491.xul new file mode 100644 index 000000000..5ec7a765a --- /dev/null +++ b/browser/components/places/tests/chrome/test_bug549491.xul @@ -0,0 +1,78 @@ +<?xml version="1.0"?> + +<!-- + Any copyright is dedicated to the Public Domain. + http://creativecommons.org/licenses/publicdomain/ + --> + +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" + type="text/css"?> + +<?xml-stylesheet href="chrome://browser/content/places/places.css"?> +<?xml-stylesheet href="chrome://browser/skin/places/places.css"?> +<?xul-overlay href="chrome://browser/content/places/placesOverlay.xul"?> + +<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + title="549491: 'The root node is never visible' exception when details of the root node are modified " + onload="runTest();"> + + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" /> + <script type="application/javascript" src="head.js" /> + + <body xmlns="http://www.w3.org/1999/xhtml" /> + + <tree id="tree" + type="places" + flatList="true" + flex="1"> + <treecols> + <treecol label="Title" id="title" anonid="title" primary="true" ordinal="1" flex="1"/> + <splitter class="tree-splitter"/> + <treecol label="Date" anonid="date" flex="1"/> + </treecols> + <treechildren flex="1"/> + </tree> + + <script type="application/javascript"><![CDATA[ + /** + * Bug 549491 + * https://bugzilla.mozilla.org/show_bug.cgi?id=549491 + * + * Ensures that changing the details of places tree's root-node doesn't + * throw. + */ + + function runTest() { + SimpleTest.waitForExplicitFinish(); + + Task.spawn(function* () { + yield PlacesTestUtils.clearHistory(); + + yield PlacesTestUtils.addVisits({ + uri: Services.io.newURI("http://example.tld/", null, null), + transition: PlacesUtils.history.TRANSITION_TYPED + }); + + // Make a history query. + let query = PlacesUtils.history.getNewQuery(); + let opts = PlacesUtils.history.getNewQueryOptions(); + let queryURI = PlacesUtils.history.queriesToQueryString([query], 1, opts); + + // Setup the places tree contents. + let tree = document.getElementById("tree"); + tree.place = queryURI; + + let rootNode = tree.result.root; + let obs = tree.view.QueryInterface(Ci.nsINavHistoryResultObserver); + obs.nodeHistoryDetailsChanged(rootNode, rootNode.time, rootNode.accessCount); + obs.nodeTitleChanged(rootNode, rootNode.title); + ok(true, "No exceptions thrown"); + + // Cleanup. + yield PlacesTestUtils.clearHistory(); + }).then(SimpleTest.finish); + } + ]]></script> +</window> diff --git a/browser/components/places/tests/chrome/test_bug631374_tags_selector_scroll.xul b/browser/components/places/tests/chrome/test_bug631374_tags_selector_scroll.xul new file mode 100644 index 000000000..b1d73017f --- /dev/null +++ b/browser/components/places/tests/chrome/test_bug631374_tags_selector_scroll.xul @@ -0,0 +1,170 @@ +<?xml version="1.0"?> + +<!-- Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ --> + +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" + type="text/css"?> + +<?xml-stylesheet href="chrome://browser/skin/places/editBookmarkOverlay.css"?> +<?xml-stylesheet href="chrome://browser/content/places/places.css"?> +<?xml-stylesheet href="chrome://browser/skin/places/places.css"?> + +<?xul-overlay href="chrome://browser/content/places/placesOverlay.xul"?> +<?xul-overlay href="chrome://browser/content/places/editBookmarkOverlay.xul"?> + +<!DOCTYPE window [ + <!ENTITY % editBookmarkOverlayDTD SYSTEM "chrome://browser/locale/places/editBookmarkOverlay.dtd"> + %editBookmarkOverlayDTD; +]> + +<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + title="Bug 631374 - Editing tags in the selector scrolls up the listbox" + onload="runTest();"> + + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" /> + <script type="application/javascript" + src="chrome://browser/content/places/editBookmarkOverlay.js"/> + + <body xmlns="http://www.w3.org/1999/xhtml" /> + + <vbox id="editBookmarkPanelContent"/> + + <script type="application/javascript"> + <![CDATA[ + + /** + * This test checks that editing tags doesn't scroll the tags selector + * listbox to wrong positions. + */ + + function runTest() { + SimpleTest.waitForExplicitFinish(); + + Task.spawn(function* () { + let bs = PlacesUtils.bookmarks; + + let tags = ["a", "b", "c", "d", "e", "f", "g", + "h", "i", "l", "m", "n", "o", "p"]; + + // Add a bookmark and tag it. + let uri1 = Services.io.newURI("http://www1.mozilla.org/", null, null); + let bm1 = yield bs.insert({ + parentGuid: bs.toolbarGuid, + index: bs.DEFAULT_INDEX, + type: bs.TYPE_BOOKMARK, + title: "mozilla", + url: uri1.spec + }); + PlacesUtils.tagging.tagURI(uri1, tags); + + // Add a second bookmark so that tags won't disappear when unchecked. + let uri2 = Services.io.newURI("http://www2.mozilla.org/", null, null); + let bm2 = yield bs.insert({ + parentGuid: bs.toolbarGuid, + index: bs.DEFAULT_INDEX, + type: bs.TYPE_BOOKMARK, + title: "mozilla", + url: uri2.spec + }); + PlacesUtils.tagging.tagURI(uri2, tags); + + // Init panel. + ok(gEditItemOverlay, "gEditItemOverlay is in context"); + let node1 = yield PlacesUIUtils.promiseNodeLikeFromFetchInfo(bm1); + gEditItemOverlay.initPanel({ node: node1 }); + ok(gEditItemOverlay.initialized, "gEditItemOverlay is initialized"); + + yield openTagSelector(); + let tagsSelector = document.getElementById("editBMPanel_tagsSelector"); + + // Go by two so there is some untouched tag in the middle. + for (let i = 8; i < tags.length; i += 2) { + tagsSelector.selectedIndex = i; + let listItem = tagsSelector.selectedItem; + isnot(listItem, null, "Valid listItem found"); + + tagsSelector.ensureElementIsVisible(listItem); + let visibleIndex = tagsSelector.getIndexOfFirstVisibleRow(); + + ok(listItem.checked, "Item is checked " + i); + let selectedTag = listItem.label; + + // Uncheck the tag. + listItem.checked = false; + is(visibleIndex, tagsSelector.getIndexOfFirstVisibleRow(), + "Scroll position did not change"); + + // The listbox is rebuilt, so we have to get the new element. + let newItem = tagsSelector.selectedItem; + isnot(newItem, null, "Valid new listItem found"); + ok(!newItem.checked, "New listItem is unchecked " + i); + is(newItem.label, selectedTag, "Correct tag is still selected"); + + // Check the tag. + newItem.checked = true; + is(visibleIndex, tagsSelector.getIndexOfFirstVisibleRow(), + "Scroll position did not change"); + } + + // Remove the second bookmark, then nuke some of the tags. + yield bs.remove(bm2.guid); + + // Doing this backwords tests more interesting paths. + for (let i = tags.length - 1; i >= 0 ; i -= 2) { + tagsSelector.selectedIndex = i; + let listItem = tagsSelector.selectedItem; + isnot(listItem, null, "Valid listItem found"); + + tagsSelector.ensureElementIsVisible(listItem); + let firstVisibleTag = tags[tagsSelector.getIndexOfFirstVisibleRow()]; + + ok(listItem.checked, "Item is checked " + i); + let selectedTag = listItem.label; + + // Uncheck the tag. + listItem.checked = false; + + // Ensure the first visible tag is still visible in the list. + let firstVisibleIndex = tagsSelector.getIndexOfFirstVisibleRow(); + let lastVisibleIndex = firstVisibleIndex + tagsSelector.getNumberOfVisibleRows() -1; + let expectedTagIndex = tags.indexOf(firstVisibleTag); + ok(expectedTagIndex >= firstVisibleIndex && + expectedTagIndex <= lastVisibleIndex, + "Scroll position is correct"); + + // The listbox is rebuilt, so we have to get the new element. + let newItem = tagsSelector.selectedItem; + isnot(newItem, null, "Valid new listItem found"); + ok(newItem.checked, "New listItem is checked " + i); + is(tagsSelector.selectedItem.label, + tags[Math.min(i + 1, tags.length - 2)], + "The next tag is now selected"); + } + + // Cleanup. + yield bs.remove(bm1.guid); + }).then(SimpleTest.finish).catch(alert); + } + + function openTagSelector() { + // Wait for the tags selector to be open. + let promise = new Promise(resolve => { + let row = document.getElementById("editBMPanel_tagsSelectorRow"); + row.addEventListener("DOMAttrModified", function onAttrModified() { + row.removeEventListener("DOMAttrModified", onAttrModified); + resolve(); + }); + }); + + // Open the tags selector. + document.getElementById("editBMPanel_tagsSelectorExpander").doCommand(); + + return promise; + } + ]]> + </script> + +</window> diff --git a/browser/components/places/tests/chrome/test_editBookmarkOverlay_keywords.xul b/browser/components/places/tests/chrome/test_editBookmarkOverlay_keywords.xul new file mode 100644 index 000000000..f553d018b --- /dev/null +++ b/browser/components/places/tests/chrome/test_editBookmarkOverlay_keywords.xul @@ -0,0 +1,99 @@ +<?xml version="1.0"?> + +<!-- Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ --> + +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" + type="text/css"?> + +<?xml-stylesheet href="chrome://browser/skin/places/editBookmarkOverlay.css"?> +<?xml-stylesheet href="chrome://browser/content/places/places.css"?> +<?xml-stylesheet href="chrome://browser/skin/places/places.css"?> + +<?xul-overlay href="chrome://browser/content/places/placesOverlay.xul"?> +<?xul-overlay href="chrome://browser/content/places/editBookmarkOverlay.xul"?> + +<!DOCTYPE window [ + <!ENTITY % editBookmarkOverlayDTD SYSTEM "chrome://browser/locale/places/editBookmarkOverlay.dtd"> + %editBookmarkOverlayDTD; +]> + +<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + title="Bug 1343256 - Bookmark keywords disappear from one bookmark when adding a keyword to another bookmark" + onload="runTest();"> + + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" /> + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"/> + <script type="application/javascript" + src="chrome://browser/content/places/editBookmarkOverlay.js"/> + + <body xmlns="http://www.w3.org/1999/xhtml" /> + + <vbox id="editBookmarkPanelContent"/> + + <script type="application/javascript"> + <![CDATA[ + function runTest() { + SimpleTest.waitForExplicitFinish(); + Task.spawn(test.bind(this)) + .catch(ex => ok(false, ex)) + .then(() => PlacesUtils.bookmarks.eraseEverything()) + .then(SimpleTest.finish); + } + + function promiseOnItemChanged() { + return new Promise(resolve => { + PlacesUtils.bookmarks.addObserver({ + onBeginUpdateBatch() {}, + onEndUpdateBatch() {}, + onItemAdded() {}, + onItemRemoved() {}, + onItemVisited() {}, + onItemMoved() {}, + onItemChanged(id, property, isAnno, value) { + PlacesUtils.bookmarks.removeObserver(this); + resolve({ property, value }); + }, + QueryInterface: XPCOMUtils.generateQI([Ci.nsINavBookmarkObserver]) + }, false); + }); + } + + function* test() { + ok(gEditItemOverlay, "Sanity check: gEditItemOverlay is in context"); + let keywordField = document.getElementById("editBMPanel_keywordField"); + + for (let i = 0; i < 2; ++i) { + let bm = yield PlacesUtils.bookmarks.insert({ + url: `http://www.test${i}.me/`, + parentGuid: PlacesUtils.bookmarks.unfiledGuid, + }); + info(`Init panel on bookmark #${i+1}`); + let node = yield PlacesUIUtils.promiseNodeLikeFromFetchInfo(bm); + gEditItemOverlay.initPanel({ node }); + is(document.getElementById("editBMPanel_keywordField").value, "", + "The keyword field should be empty"); + info("Add a keyword to the bookmark"); + let promise = promiseOnItemChanged(); + keywordField.focus(); + keywordField.value = "kw"; + synthesizeKey(i.toString(), {}); + synthesizeKey("VK_RETURN", {}); + keywordField.blur(); + let {property, value} = yield promise; + is(property, "keyword", "The keyword should have been changed"); + is(value, `kw${i}`, "The new keyword value is correct"); + } + + for (let i = 0; i < 2; ++i) { + let entry = yield PlacesUtils.keywords.fetch({ url: `http://www.test${i}.me/` }); + is(entry.keyword, `kw${i}`, `The keyword for http://www.test${i}.me/ is correct`); + } + }; + ]]> + </script> + +</window> diff --git a/browser/components/places/tests/chrome/test_editBookmarkOverlay_tags_liveUpdate.xul b/browser/components/places/tests/chrome/test_editBookmarkOverlay_tags_liveUpdate.xul new file mode 100644 index 000000000..1b1cc6473 --- /dev/null +++ b/browser/components/places/tests/chrome/test_editBookmarkOverlay_tags_liveUpdate.xul @@ -0,0 +1,204 @@ +<?xml version="1.0"?> + +<!-- Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ --> + +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" + type="text/css"?> + +<?xml-stylesheet href="chrome://browser/skin/places/editBookmarkOverlay.css"?> +<?xml-stylesheet href="chrome://browser/content/places/places.css"?> +<?xml-stylesheet href="chrome://browser/skin/places/places.css"?> + +<?xul-overlay href="chrome://browser/content/places/placesOverlay.xul"?> +<?xul-overlay href="chrome://browser/content/places/editBookmarkOverlay.xul"?> + +<!DOCTYPE window [ + <!ENTITY % editBookmarkOverlayDTD SYSTEM "chrome://browser/locale/places/editBookmarkOverlay.dtd"> + %editBookmarkOverlayDTD; +]> + +<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + title="485100: Exchanging a letter of a tag name with its big/small equivalent removes tag from bookmark" + onload="runTest();"> + + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" /> + <script type="application/javascript" + src="chrome://browser/content/places/editBookmarkOverlay.js"/> + + <body xmlns="http://www.w3.org/1999/xhtml" /> + + <vbox id="editBookmarkPanelContent"/> + + <script type="application/javascript"> + <![CDATA[ + function checkTagsSelector(aAvailableTags, aCheckedTags) { + is(PlacesUtils.tagging.allTags.length, aAvailableTags.length, + "tagging service is in sync."); + let tagsSelector = document.getElementById("editBMPanel_tagsSelector"); + let children = tagsSelector.childNodes; + is(children.length, aAvailableTags.length, + "Found expected number of tags in the tags selector"); + + Array.prototype.forEach.call(children, function (aChild) { + let tag = aChild.getAttribute("label"); + ok(true, "Found tag '" + tag + "' in the selector"); + ok(aAvailableTags.includes(tag), "Found expected tag"); + let checked = aChild.getAttribute("checked") == "true"; + is(checked, aCheckedTags.includes(tag), + "Tag is correctly marked"); + }); + } + + function runTest() { + SimpleTest.waitForExplicitFinish(); + + Task.spawn(function* () { + const TEST_URI = Services.io.newURI("http://www.test.me/", null, null); + const TEST_URI2 = Services.io.newURI("http://www.test.again.me/", null, null); + const TEST_TAG = "test-tag"; + + ok(gEditItemOverlay, "Sanity check: gEditItemOverlay is in context"); + + // Open the tags selector. + document.getElementById("editBMPanel_tagsSelectorRow").collapsed = false; + + // Add a bookmark. + let bm = yield PlacesUtils.bookmarks.insert({ + parentGuid: PlacesUtils.bookmarks.unfiledGuid, + index: PlacesUtils.bookmarks.DEFAULT_INDEX, + type: PlacesUtils.bookmarks.TYPE_BOOKMARK, + url: TEST_URI.spec, + title: "test.me" + }); + + // Init panel. + let node = yield PlacesUIUtils.promiseNodeLikeFromFetchInfo(bm); + gEditItemOverlay.initPanel({ node }); + + // Add a tag. + PlacesUtils.tagging.tagURI(TEST_URI, [TEST_TAG]); + + is(PlacesUtils.tagging.getTagsForURI(TEST_URI)[0], TEST_TAG, + "Correctly added tag to a single bookmark"); + is(document.getElementById("editBMPanel_tagsField").value, TEST_TAG, + "Editing a single bookmark shows the added tag"); + checkTagsSelector([TEST_TAG], [TEST_TAG]); + + // Remove tag. + PlacesUtils.tagging.untagURI(TEST_URI, [TEST_TAG]); + is(PlacesUtils.tagging.getTagsForURI(TEST_URI)[0], undefined, + "The tag has been removed"); + is(document.getElementById("editBMPanel_tagsField").value, "", + "Editing a single bookmark should not show any tag"); + checkTagsSelector([], []); + + // Add a second bookmark. + let bm2 = yield PlacesUtils.bookmarks.insert({ + parentGuid: PlacesUtils.bookmarks.unfiledGuid, + index: PlacesUtils.bookmarks.DEFAULT_INDEX, + type: PlacesUtils.bookmarks.TYPE_BOOKMARK, + title: "test.again.me", + url: TEST_URI2.spec + }); + + // Init panel with multiple uris. + gEditItemOverlay.initPanel({ uris: [TEST_URI, TEST_URI2] }); + + // Add a tag to the first uri. + PlacesUtils.tagging.tagURI(TEST_URI, [TEST_TAG]); + is(PlacesUtils.tagging.getTagsForURI(TEST_URI)[0], TEST_TAG, + "Correctly added a tag to the first bookmark."); + is(document.getElementById("editBMPanel_tagsField").value, "", + "Editing multiple bookmarks without matching tags should not show any tag."); + checkTagsSelector([TEST_TAG], []); + + // Add a tag to the second uri. + PlacesUtils.tagging.tagURI(TEST_URI2, [TEST_TAG]); + is(PlacesUtils.tagging.getTagsForURI(TEST_URI2)[0], TEST_TAG, + "Correctly added a tag to the second bookmark."); + is(document.getElementById("editBMPanel_tagsField").value, TEST_TAG, + "Editing multiple bookmarks should show matching tags."); + checkTagsSelector([TEST_TAG], [TEST_TAG]); + + // Remove tag from the first bookmark. + PlacesUtils.tagging.untagURI(TEST_URI, [TEST_TAG]); + is(PlacesUtils.tagging.getTagsForURI(TEST_URI)[0], undefined, + "Correctly removed tag from the first bookmark."); + is(document.getElementById("editBMPanel_tagsField").value, "", + "Editing multiple bookmarks without matching tags should not show any tag."); + checkTagsSelector([TEST_TAG], []); + + // Remove tag from the second bookmark. + PlacesUtils.tagging.untagURI(TEST_URI2, [TEST_TAG]); + is(PlacesUtils.tagging.getTagsForURI(TEST_URI2)[0], undefined, + "Correctly removed tag from the second bookmark."); + is(document.getElementById("editBMPanel_tagsField").value, "", + "Editing multiple bookmarks without matching tags should not show any tag."); + checkTagsSelector([], []); + + // Init panel with a nsIURI entry. + gEditItemOverlay.initPanel({ uris: [TEST_URI] }); + + // Add a tag. + PlacesUtils.tagging.tagURI(TEST_URI, [TEST_TAG]); + is(PlacesUtils.tagging.getTagsForURI(TEST_URI)[0], TEST_TAG, + "Correctly added tag to the first entry."); + is(document.getElementById("editBMPanel_tagsField").value, TEST_TAG, + "Editing a single nsIURI entry shows the added tag"); + checkTagsSelector([TEST_TAG], [TEST_TAG]); + + // Remove tag. + PlacesUtils.tagging.untagURI(TEST_URI, [TEST_TAG]); + is(PlacesUtils.tagging.getTagsForURI(TEST_URI)[0], undefined, + "Correctly removed tag from the nsIURI entry."); + is(document.getElementById("editBMPanel_tagsField").value, "", + "Editing a single nsIURI entry should not show any tag"); + checkTagsSelector([], []); + + // Init panel with multiple nsIURI entries. + gEditItemOverlay.initPanel({ uris: [TEST_URI, TEST_URI2] }); + + // Add a tag to the first entry. + PlacesUtils.tagging.tagURI(TEST_URI, [TEST_TAG]); + is(PlacesUtils.tagging.getTagsForURI(TEST_URI)[0], TEST_TAG, + "Tag correctly added."); + is(document.getElementById("editBMPanel_tagsField").value, "", + "Editing multiple nsIURIs without matching tags should not show any tag."); + checkTagsSelector([TEST_TAG], []); + + // Add a tag to the second entry. + PlacesUtils.tagging.tagURI(TEST_URI2, [TEST_TAG]); + is(PlacesUtils.tagging.getTagsForURI(TEST_URI2)[0], TEST_TAG, + "Tag correctly added."); + is(document.getElementById("editBMPanel_tagsField").value, TEST_TAG, + "Editing multiple nsIURIs should show matching tags"); + checkTagsSelector([TEST_TAG], [TEST_TAG]); + + // Remove tag from the first entry. + PlacesUtils.tagging.untagURI(TEST_URI, [TEST_TAG]); + is(PlacesUtils.tagging.getTagsForURI(TEST_URI)[0], undefined, + "Correctly removed tag from the first entry."); + is(document.getElementById("editBMPanel_tagsField").value, "", + "Editing multiple nsIURIs without matching tags should not show any tag."); + checkTagsSelector([TEST_TAG], []); + + // Remove tag from the second entry. + PlacesUtils.tagging.untagURI(TEST_URI2, [TEST_TAG]); + is(PlacesUtils.tagging.getTagsForURI(TEST_URI2)[0], undefined, + "Correctly removed tag from the second entry."); + is(document.getElementById("editBMPanel_tagsField").value, "", + "Editing multiple nsIURIs without matching tags should not show any tag."); + checkTagsSelector([], []); + + // Cleanup. + yield PlacesUtils.bookmarks.remove(bm.guid); + yield PlacesUtils.bookmarks.remove(bm2.guid); + }).then(SimpleTest.finish); + } + ]]> + </script> + +</window> diff --git a/browser/components/places/tests/chrome/test_selectItems_on_nested_tree.xul b/browser/components/places/tests/chrome/test_selectItems_on_nested_tree.xul new file mode 100644 index 000000000..032c7a258 --- /dev/null +++ b/browser/components/places/tests/chrome/test_selectItems_on_nested_tree.xul @@ -0,0 +1,86 @@ +<?xml version="1.0"?> + +<!-- + Any copyright is dedicated to the Public Domain. + http://creativecommons.org/licenses/publicdomain/ + --> + +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" + type="text/css"?> + +<?xml-stylesheet href="chrome://browser/content/places/places.css"?> +<?xml-stylesheet href="chrome://browser/skin/places/places.css"?> +<?xul-overlay href="chrome://browser/content/places/placesOverlay.xul"?> + +<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + title="549192: History view not updated after deleting entry" + onload="runTest();"> + + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" /> + <script type="application/javascript" src="head.js" /> + + <body xmlns="http://www.w3.org/1999/xhtml" /> + + <tree id="tree" + type="places" + flex="1"> + <treecols> + <treecol label="Title" id="title" anonid="title" primary="true" ordinal="1" flex="1"/> + </treecols> + <treechildren flex="1"/> + </tree> + + <script type="application/javascript"><![CDATA[ + /** + * Ensure that selectItems doesn't recurse infinitely in nested trees. + */ + + function runTest() { + SimpleTest.waitForExplicitFinish(); + + Task.spawn(function* () { + yield PlacesUtils.bookmarks.insert({ + parentGuid: PlacesUtils.bookmarks.unfiledGuid, + index: PlacesUtils.bookmarks.DEFAULT_INDEX, + type: PlacesUtils.bookmarks.TYPE_BOOKMARK, + url: "place:folder=UNFILED_BOOKMARKS", + title: "shortcut" + }); + + yield PlacesUtils.bookmarks.insert({ + parentGuid: PlacesUtils.bookmarks.unfiledGuid, + index: PlacesUtils.bookmarks.DEFAULT_INDEX, + type: PlacesUtils.bookmarks.TYPE_BOOKMARK, + url: "place:folder=UNFILED_BOOKMARKS&maxResults=10", + title: "query" + }); + + let folder = yield PlacesUtils.bookmarks.insert({ + parentGuid: PlacesUtils.bookmarks.unfiledGuid, + index: PlacesUtils.bookmarks.DEFAULT_INDEX, + type: PlacesUtils.bookmarks.TYPE_FOLDER, + title: "folder" + }); + + let bm = yield PlacesUtils.bookmarks.insert({ + parentGuid: folder.guid, + index: PlacesUtils.bookmarks.DEFAULT_INDEX, + type: PlacesUtils.bookmarks.TYPE_BOOKMARK, + url: "http://www.mozilla.org/", + title: "bookmark" + }); + + // Setup the places tree contents. + let tree = document.getElementById("tree"); + tree.place = "place:folder=UNFILED_BOOKMARKS"; + + // Select the last bookmark. + let itemId = yield PlacesUtils.promiseItemId(bm.guid); + tree.selectItems([itemId]); + is (tree.selectedNode.itemId, itemId, "The right node was selected"); + }).then(SimpleTest.finish); + } + ]]></script> +</window> diff --git a/browser/components/places/tests/chrome/test_treeview_date.xul b/browser/components/places/tests/chrome/test_treeview_date.xul new file mode 100644 index 000000000..559232611 --- /dev/null +++ b/browser/components/places/tests/chrome/test_treeview_date.xul @@ -0,0 +1,159 @@ +<?xml version="1.0"?> + +<!-- 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/. --> + +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" + type="text/css"?> + +<?xml-stylesheet href="chrome://browser/content/places/places.css"?> +<?xml-stylesheet href="chrome://browser/skin/places/places.css"?> +<?xul-overlay href="chrome://browser/content/places/placesOverlay.xul"?> + +<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + title="435322: Places tree view's formatting" + onload="runTest();"> + + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" /> + <script type="application/javascript" src="head.js" /> + + <body xmlns="http://www.w3.org/1999/xhtml" /> + + <tree id="tree" + type="places" + flatList="true" + flex="1"> + <treecols> + <treecol label="Title" id="title" anonid="title" primary="true" ordinal="1" flex="1"/> + <splitter class="tree-splitter"/> + <treecol label="Tags" id="tags" anonid="tags" flex="1"/> + <splitter class="tree-splitter"/> + <treecol label="Url" id="url" anonid="url" flex="1"/> + <splitter class="tree-splitter"/> + <treecol label="Visit Date" id="date" anonid="date" flex="1"/> + <splitter class="tree-splitter"/> + <treecol label="Visit Count" id="visitCount" anonid="visitCount" flex="1"/> + </treecols> + <treechildren flex="1"/> + </tree> + + <script type="application/javascript"> + <![CDATA[ + + /** + * Bug 435322 + * https://bugzilla.mozilla.org/show_bug.cgi?id=435322 + * + * Ensures that date in places treeviews is correctly formatted. + */ + + function runTest() { + SimpleTest.waitForExplicitFinish(); + + function uri(spec) { + return Services.io.newURI(spec, null, null); + } + + Task.spawn(function* () { + yield PlacesTestUtils.clearHistory(); + + let midnight = new Date(); + midnight.setHours(0); + midnight.setMinutes(0); + midnight.setSeconds(0); + midnight.setMilliseconds(0); + + // Add a visit 1ms before midnight, a visit at midnight, and + // a visit 1ms after midnight. + yield PlacesTestUtils.addVisits([ + {uri: uri("http://before.midnight.com/"), + visitDate: (midnight.getTime() - 1) * 1000, + transition: PlacesUtils.history.TRANSITION_TYPED}, + {uri: uri("http://at.midnight.com/"), + visitDate: (midnight.getTime()) * 1000, + transition: PlacesUtils.history.TRANSITION_TYPED}, + {uri: uri("http://after.midnight.com/"), + visitDate: (midnight.getTime() + 1) * 1000, + transition: PlacesUtils.history.TRANSITION_TYPED} + ]); + + // add a bookmark to the midnight visit + let bm = yield PlacesUtils.bookmarks.insert({ + parentGuid: PlacesUtils.bookmarks.toolbarGuid, + index: PlacesUtils.bookmarks.DEFAULT_INDEX, + url: "http://at.midnight.com/", + title: "A bookmark at midnight", + type: PlacesUtils.bookmarks.TYPE_BOOKMARK + }); + + // Make a history query. + let query = PlacesUtils.history.getNewQuery(); + let opts = PlacesUtils.history.getNewQueryOptions(); + let queryURI = PlacesUtils.history.queriesToQueryString([query], 1, opts); + + // Setup the places tree contents. + let tree = document.getElementById("tree"); + tree.place = queryURI; + + // loop through the rows and check formatting + let treeView = tree.view; + let rc = treeView.rowCount; + ok(rc >= 3, "Rows found"); + let columns = tree.columns; + ok(columns.count > 0, "Columns found"); + const locale = Cc["@mozilla.org/chrome/chrome-registry;1"] + .getService(Ci.nsIXULChromeRegistry) + .getSelectedLocale("global", true); + for (let r = 0; r < rc; r++) { + let node = treeView.nodeForTreeIndex(r); + ok(node, "Places node found"); + for (let ci = 0; ci < columns.count; ci++) { + let c = columns.getColumnAt(ci); + let text = treeView.getCellText(r, c); + switch (c.element.getAttribute("anonid")) { + case "title": + // The title can differ, we did not set any title so we would + // expect null, but in such a case the view will generate a title + // through PlacesUIUtils.getBestTitle. + if (node.title) + is(text, node.title, "Title is correct"); + break; + case "url": + is(text, node.uri, "Uri is correct"); + break; + case "date": + let timeObj = new Date(node.time / 1000); + // Default is short date format. + let dtOptions = { year: 'numeric', month: 'numeric', day: 'numeric', + hour: 'numeric', minute: 'numeric' }; + // For today's visits we don't show date portion. + if (node.uri == "http://at.midnight.com/" || + node.uri == "http://after.midnight.com/") { + dtOptions = { hour: 'numeric', minute: 'numeric' }; + } else if (node.uri != "http://before.midnight.com/") { + // Avoid to test spurious uris, due to how the test works + // a redirecting uri could be put in the tree while we test. + break; + } + let timeStr = timeObj.toLocaleString(locale, dtOptions); + + is(text, timeStr, "Date format is correct"); + break; + case "visitCount": + is(text, 1, "Visit count is correct"); + break; + } + } + } + + // Cleanup. + yield PlacesUtils.bookmarks.remove(bm.guid); + yield PlacesTestUtils.clearHistory(); + }).then(SimpleTest.finish); + } + ]]> + </script> +</window> diff --git a/browser/components/places/tests/unit/.eslintrc.js b/browser/components/places/tests/unit/.eslintrc.js new file mode 100644 index 000000000..d35787cd2 --- /dev/null +++ b/browser/components/places/tests/unit/.eslintrc.js @@ -0,0 +1,7 @@ +"use strict"; + +module.exports = { + "extends": [ + "../../../../../testing/xpcshell/xpcshell.eslintrc.js" + ] +}; diff --git a/browser/components/places/tests/unit/bookmarks.glue.html b/browser/components/places/tests/unit/bookmarks.glue.html new file mode 100644 index 000000000..07b22e9b3 --- /dev/null +++ b/browser/components/places/tests/unit/bookmarks.glue.html @@ -0,0 +1,16 @@ +<!DOCTYPE NETSCAPE-Bookmark-file-1> +<!-- This is an automatically generated file. + It will be read and overwritten. + DO NOT EDIT! --> +<META HTTP-EQUIV="Content-Type" CONTENT="text/html; charset=UTF-8"> +<TITLE>Bookmarks</TITLE> +<H1>Bookmarks Menu</H1> + +<DL><p> + <DT><A HREF="http://example.com/" ADD_DATE="1233157972" LAST_MODIFIED="1233157984">example</A> + <DT><H3 ADD_DATE="1233157910" LAST_MODIFIED="1233157972" PERSONAL_TOOLBAR_FOLDER="true">Bookmarks Toolbar</H3> +<DD>Add bookmarks to this folder to see them displayed on the Bookmarks Toolbar + <DL><p> + <DT><A HREF="http://example.com/" ADD_DATE="1233157972" LAST_MODIFIED="1233157984">example</A> + </DL><p> +</DL><p> diff --git a/browser/components/places/tests/unit/bookmarks.glue.json b/browser/components/places/tests/unit/bookmarks.glue.json new file mode 100644 index 000000000..95900e176 --- /dev/null +++ b/browser/components/places/tests/unit/bookmarks.glue.json @@ -0,0 +1 @@ +{"title":"","id":1,"dateAdded":1233157910552624,"lastModified":1233157955206833,"type":"text/x-moz-place-container","root":"placesRoot","children":[{"title":"Bookmarks Menu","id":2,"parent":1,"dateAdded":1233157910552624,"lastModified":1233157993171424,"type":"text/x-moz-place-container","root":"bookmarksMenuFolder","children":[{"title":"examplejson","id":27,"parent":2,"dateAdded":1233157972101126,"lastModified":1233157984999673,"type":"text/x-moz-place","uri":"http://example.com/"}]},{"index":1,"title":"Bookmarks Toolbar","id":3,"parent":1,"dateAdded":1233157910552624,"lastModified":1233157972101126,"annos":[{"name":"bookmarkProperties/description","flags":0,"expires":4,"mimeType":null,"type":3,"value":"Add bookmarks to this folder to see them displayed on the Bookmarks Toolbar"}],"type":"text/x-moz-place-container","root":"toolbarFolder","children":[{"title":"examplejson","id":26,"parent":3,"dateAdded":1233157972101126,"lastModified":1233157984999673,"type":"text/x-moz-place","uri":"http://example.com/"}]},{"index":2,"title":"Tags","id":4,"parent":1,"dateAdded":1233157910552624,"lastModified":1233157910582667,"type":"text/x-moz-place-container","root":"tagsFolder","children":[]},{"index":3,"title":"Other Bookmarks","id":5,"parent":1,"dateAdded":1233157910552624,"lastModified":1233157911033315,"type":"text/x-moz-place-container","root":"unfiledBookmarksFolder","children":[]}]} diff --git a/browser/components/places/tests/unit/corruptDB.sqlite b/browser/components/places/tests/unit/corruptDB.sqlite Binary files differnew file mode 100644 index 000000000..b234246ca --- /dev/null +++ b/browser/components/places/tests/unit/corruptDB.sqlite diff --git a/browser/components/places/tests/unit/distribution.ini b/browser/components/places/tests/unit/distribution.ini new file mode 100644 index 000000000..93e73cb5c --- /dev/null +++ b/browser/components/places/tests/unit/distribution.ini @@ -0,0 +1,27 @@ +# Distribution Configuration File +# Bug 516444 demo + +[Global] +id=516444 +version=1.0 +about=Test distribution file + +[BookmarksToolbar] +item.1.title=Toolbar Link Before +item.1.link=https://example.org/toolbar/before/ +item.1.keyword=e:t:b +item.1.icon=https://example.org/favicon.png +item.1.iconData=data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAAAAAA6fptVAAAACklEQVQI12NgAAAAAgAB4iG8MwAAAABJRU5ErkJggg== +item.2.type=default +item.3.title=Toolbar Link After +item.3.link=https://example.org/toolbar/after/ +item.3.keyword=e:t:a + +[BookmarksMenu] +item.1.title=Menu Link Before +item.1.link=https://example.org/menu/before/ +item.1.icon=https://example.org/favicon.png +item.1.iconData=data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAAAAAA6fptVAAAACklEQVQI12NgAAAAAgAB4iG8MwAAAABJRU5ErkJggg== +item.2.type=default +item.3.title=Menu Link After +item.3.link=https://example.org/menu/after/ diff --git a/browser/components/places/tests/unit/head_bookmarks.js b/browser/components/places/tests/unit/head_bookmarks.js new file mode 100644 index 000000000..460295f96 --- /dev/null +++ b/browser/components/places/tests/unit/head_bookmarks.js @@ -0,0 +1,133 @@ +/* -*- 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"); +Cu.import("resource://gre/modules/LoadContextInfo.jsm"); + +// Import common head. +var commonFile = do_get_file("../../../../../toolkit/components/places/tests/head_common.js", false); +if (commonFile) { + let uri = Services.io.newFileURI(commonFile); + Services.scriptloader.loadSubScript(uri.spec, this); +} + +// Put any other stuff relative to this test folder below. + +XPCOMUtils.defineLazyGetter(this, "PlacesUIUtils", function() { + Cu.import("resource:///modules/PlacesUIUtils.jsm"); + return PlacesUIUtils; +}); + +const ORGANIZER_FOLDER_ANNO = "PlacesOrganizer/OrganizerFolder"; +const ORGANIZER_QUERY_ANNO = "PlacesOrganizer/OrganizerQuery"; + +// Needed by some test that relies on having an app registered. +Cu.import("resource://testing-common/AppInfo.jsm", this); +updateAppInfo({ + name: "PlacesTest", + ID: "{230de50e-4cd1-11dc-8314-0800200c9a66}", + version: "1", + platformVersion: "", +}); + +// Smart bookmarks constants. +const SMART_BOOKMARKS_VERSION = 8; +const SMART_BOOKMARKS_ON_TOOLBAR = 1; +const SMART_BOOKMARKS_ON_MENU = 2; // Takes into account the additional separator. + +// Default bookmarks constants. +const DEFAULT_BOOKMARKS_ON_TOOLBAR = 1; +const DEFAULT_BOOKMARKS_ON_MENU = 1; + +const SMART_BOOKMARKS_ANNO = "Places/SmartBookmark"; + +function checkItemHasAnnotation(guid, name) { + return PlacesUtils.promiseItemId(guid).then(id => { + let hasAnnotation = PlacesUtils.annotations.itemHasAnnotation(id, name); + Assert.ok(hasAnnotation, `Expected annotation ${name}`); + }); +} + +var createCorruptDB = Task.async(function* () { + let dbPath = OS.Path.join(OS.Constants.Path.profileDir, "places.sqlite"); + yield OS.File.remove(dbPath); + + // Create a corrupt database. + let dir = yield OS.File.getCurrentDirectory(); + let src = OS.Path.join(dir, "corruptDB.sqlite"); + yield OS.File.copy(src, dbPath); + + // Check there's a DB now. + Assert.ok((yield OS.File.exists(dbPath)), "should have a DB now"); +}); + +/** + * Rebuilds smart bookmarks listening to console output to report any message or + * exception generated. + * + * @return {Promise} + * Resolved when done. + */ +function rebuildSmartBookmarks() { + let consoleListener = { + observe(aMsg) { + if (aMsg.message.startsWith("[JavaScript Warning:")) { + // TODO (Bug 1300416): Ignore spurious strict warnings. + return; + } + do_throw("Got console message: " + aMsg.message); + }, + QueryInterface: XPCOMUtils.generateQI([ Ci.nsIConsoleListener ]), + }; + Services.console.reset(); + Services.console.registerListener(consoleListener); + do_register_cleanup(() => { + try { + Services.console.unregisterListener(consoleListener); + } catch (ex) { /* will likely fail */ } + }); + Cc["@mozilla.org/browser/browserglue;1"] + .getService(Ci.nsIObserver) + .observe(null, "browser-glue-test", "smart-bookmarks-init"); + return promiseTopicObserved("test-smart-bookmarks-done").then(() => { + Services.console.unregisterListener(consoleListener); + }); +} + +const SINGLE_TRY_TIMEOUT = 100; +const NUMBER_OF_TRIES = 30; + +/** + * Similar to waitForConditionPromise, but poll for an asynchronous value + * every SINGLE_TRY_TIMEOUT ms, for no more than tryCount times. + * + * @param promiseFn + * A function to generate a promise, which resolves to the expected + * asynchronous value. + * @param timeoutMsg + * The reason to reject the returned promise with. + * @param [optional] tryCount + * Maximum times to try before rejecting the returned promise with + * timeoutMsg, defaults to NUMBER_OF_TRIES. + * @return {Promise} + * @resolves to the asynchronous value being polled. + * @rejects if the asynchronous value is not available after tryCount attempts. + */ +var waitForResolvedPromise = Task.async(function* (promiseFn, timeoutMsg, tryCount=NUMBER_OF_TRIES) { + let tries = 0; + do { + try { + let value = yield promiseFn(); + return value; + } catch (ex) {} + yield new Promise(resolve => do_timeout(SINGLE_TRY_TIMEOUT, resolve)); + } while (++tries <= tryCount); + throw new Error(timeoutMsg); +}); diff --git a/browser/components/places/tests/unit/test_421483.js b/browser/components/places/tests/unit/test_421483.js new file mode 100644 index 000000000..a0d138372 --- /dev/null +++ b/browser/components/places/tests/unit/test_421483.js @@ -0,0 +1,103 @@ +/* -*- 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 SMART_BOOKMARKS_PREF = "browser.places.smartBookmarksVersion"; + +var gluesvc = Cc["@mozilla.org/browser/browserglue;1"]. + getService(Ci.nsIObserver); +// Avoid default bookmarks import. +gluesvc.observe(null, "initial-migration-will-import-default-bookmarks", ""); + +function run_test() { + run_next_test(); +} + +add_task(function* smart_bookmarks_disabled() { + Services.prefs.setIntPref("browser.places.smartBookmarksVersion", -1); + yield rebuildSmartBookmarks(); + + let smartBookmarkItemIds = + PlacesUtils.annotations.getItemsWithAnnotation(SMART_BOOKMARKS_ANNO); + Assert.equal(smartBookmarkItemIds.length, 0); + + do_print("check that pref has not been bumped up"); + Assert.equal(Services.prefs.getIntPref("browser.places.smartBookmarksVersion"), -1); +}); + +add_task(function* create_smart_bookmarks() { + Services.prefs.setIntPref("browser.places.smartBookmarksVersion", 0); + yield rebuildSmartBookmarks(); + + let smartBookmarkItemIds = + PlacesUtils.annotations.getItemsWithAnnotation(SMART_BOOKMARKS_ANNO); + Assert.notEqual(smartBookmarkItemIds.length, 0); + + do_print("check that pref has been bumped up"); + Assert.ok(Services.prefs.getIntPref("browser.places.smartBookmarksVersion") > 0); +}); + +add_task(function* remove_smart_bookmark_and_restore() { + let smartBookmarkItemIds = + PlacesUtils.annotations.getItemsWithAnnotation(SMART_BOOKMARKS_ANNO); + let smartBookmarksCount = smartBookmarkItemIds.length; + do_print("remove one smart bookmark and restore"); + + let guid = yield PlacesUtils.promiseItemGuid(smartBookmarkItemIds[0]); + yield PlacesUtils.bookmarks.remove(guid); + Services.prefs.setIntPref("browser.places.smartBookmarksVersion", 0); + + yield rebuildSmartBookmarks(); + smartBookmarkItemIds = + PlacesUtils.annotations.getItemsWithAnnotation(SMART_BOOKMARKS_ANNO); + Assert.equal(smartBookmarkItemIds.length, smartBookmarksCount); + + do_print("check that pref has been bumped up"); + Assert.ok(Services.prefs.getIntPref("browser.places.smartBookmarksVersion") > 0); +}); + +add_task(function* move_smart_bookmark_rename_and_restore() { + let smartBookmarkItemIds = + PlacesUtils.annotations.getItemsWithAnnotation(SMART_BOOKMARKS_ANNO); + let smartBookmarksCount = smartBookmarkItemIds.length; + do_print("smart bookmark should be restored in place"); + + let guid = yield PlacesUtils.promiseItemGuid(smartBookmarkItemIds[0]); + let bm = yield PlacesUtils.bookmarks.fetch(guid); + let oldTitle = bm.title; + + // create a subfolder and move inside it + let subfolder = yield PlacesUtils.bookmarks.insert({ + parentGuid: bm.parentGuid, + title: "test", + index: PlacesUtils.bookmarks.DEFAULT_INDEX, + type: PlacesUtils.bookmarks.TYPE_FOLDER + }); + + // change title and move into new subfolder + yield PlacesUtils.bookmarks.update({ + guid: guid, + parentGuid: subfolder.guid, + index: PlacesUtils.bookmarks.DEFAULT_INDEX, + title: "new title" + }); + + // restore + Services.prefs.setIntPref("browser.places.smartBookmarksVersion", 0); + yield rebuildSmartBookmarks(); + + smartBookmarkItemIds = + PlacesUtils.annotations.getItemsWithAnnotation(SMART_BOOKMARKS_ANNO); + Assert.equal(smartBookmarkItemIds.length, smartBookmarksCount); + + guid = yield PlacesUtils.promiseItemGuid(smartBookmarkItemIds[0]); + bm = yield PlacesUtils.bookmarks.fetch(guid); + Assert.equal(bm.parentGuid, subfolder.guid); + Assert.equal(bm.title, oldTitle); + + do_print("check that pref has been bumped up"); + Assert.ok(Services.prefs.getIntPref("browser.places.smartBookmarksVersion") > 0); +}); diff --git a/browser/components/places/tests/unit/test_PUIU_makeTransaction.js b/browser/components/places/tests/unit/test_PUIU_makeTransaction.js new file mode 100644 index 000000000..c0626f53b --- /dev/null +++ b/browser/components/places/tests/unit/test_PUIU_makeTransaction.js @@ -0,0 +1,361 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +function waitForBookmarkNotification(aNotification, aCallback, aProperty) +{ + PlacesUtils.bookmarks.addObserver({ + validate: function (aMethodName, aData) + { + if (aMethodName == aNotification && + (!aProperty || aProperty == aData.property)) { + PlacesUtils.bookmarks.removeObserver(this); + aCallback(aData); + } + }, + + // nsINavBookmarkObserver + QueryInterface: XPCOMUtils.generateQI([Ci.nsINavBookmarkObserver]), + onBeginUpdateBatch: function onBeginUpdateBatch() { + return this.validate(arguments.callee.name, arguments); + }, + onEndUpdateBatch: function onEndUpdateBatch() { + return this.validate(arguments.callee.name, arguments); + }, + onItemAdded: function onItemAdded(aItemId, aParentId, aIndex, aItemType, + aURI, aTitle) + { + return this.validate(arguments.callee.name, { id: aItemId, + index: aIndex, + type: aItemType, + url: aURI ? aURI.spec : null, + title: aTitle }); + }, + onItemRemoved: function onItemRemoved() { + return this.validate(arguments.callee.name, arguments); + }, + onItemChanged: function onItemChanged(id, property, aIsAnno, + aNewValue, aLastModified, type) + { + return this.validate(arguments.callee.name, + { id, + get index() { + return PlacesUtils.bookmarks.getItemIndex(this.id); + }, + type, + property, + get url() { + return type == PlacesUtils.bookmarks.TYPE_BOOKMARK ? + PlacesUtils.bookmarks.getBookmarkURI(this.id).spec : + null; + }, + get title() { + return PlacesUtils.bookmarks.getItemTitle(this.id); + }, + }); + }, + onItemVisited: function onItemVisited() { + return this.validate(arguments.callee.name, arguments); + }, + onItemMoved: function onItemMoved(aItemId, aOldParentId, aOldIndex, + aNewParentId, aNewIndex, aItemType) + { + this.validate(arguments.callee.name, { id: aItemId, + index: aNewIndex, + type: aItemType }); + } + }, false); +} + +function wrapNodeByIdAndParent(aItemId, aParentId) +{ + let wrappedNode; + let root = PlacesUtils.getFolderContents(aParentId, false, false).root; + for (let i = 0; i < root.childCount; ++i) { + let node = root.getChild(i); + if (node.itemId == aItemId) { + let type; + if (PlacesUtils.nodeIsContainer(node)) { + type = PlacesUtils.TYPE_X_MOZ_PLACE_CONTAINER; + } + else if (PlacesUtils.nodeIsURI(node)) { + type = PlacesUtils.TYPE_X_MOZ_PLACE; + } + else if (PlacesUtils.nodeIsSeparator(node)) { + type = PlacesUtils.TYPE_X_MOZ_PLACE_SEPARATOR; + } + else { + do_throw("Unknown node type"); + } + wrappedNode = PlacesUtils.wrapNode(node, type); + } + } + root.containerOpen = false; + return JSON.parse(wrappedNode); +} + +add_test(function test_text_paste() +{ + const TEST_URL = "http://places.moz.org/" + const TEST_TITLE = "Places bookmark" + + waitForBookmarkNotification("onItemAdded", function(aData) + { + do_check_eq(aData.title, TEST_TITLE); + do_check_eq(aData.url, TEST_URL); + do_check_eq(aData.type, PlacesUtils.bookmarks.TYPE_BOOKMARK); + do_check_eq(aData.index, 0); + run_next_test(); + }); + + let txn = PlacesUIUtils.makeTransaction( + { title: TEST_TITLE, uri: TEST_URL }, + PlacesUtils.TYPE_X_MOZ_URL, + PlacesUtils.unfiledBookmarksFolderId, + PlacesUtils.bookmarks.DEFAULT_INDEX, + true // Unused for text. + ); + PlacesUtils.transactionManager.doTransaction(txn); +}); + +add_test(function test_container() +{ + const TEST_TITLE = "Places folder" + + waitForBookmarkNotification("onItemChanged", function(aChangedData) + { + do_check_eq(aChangedData.title, TEST_TITLE); + do_check_eq(aChangedData.type, PlacesUtils.bookmarks.TYPE_FOLDER); + do_check_eq(aChangedData.index, 1); + + waitForBookmarkNotification("onItemAdded", function(aAddedData) + { + do_check_eq(aAddedData.title, TEST_TITLE); + do_check_eq(aAddedData.type, PlacesUtils.bookmarks.TYPE_FOLDER); + do_check_eq(aAddedData.index, 2); + let id = aAddedData.id; + + waitForBookmarkNotification("onItemMoved", function(aMovedData) + { + do_check_eq(aMovedData.id, id); + do_check_eq(aMovedData.type, PlacesUtils.bookmarks.TYPE_FOLDER); + do_check_eq(aMovedData.index, 1); + + run_next_test(); + }); + + let txn = PlacesUIUtils.makeTransaction( + wrapNodeByIdAndParent(aAddedData.id, PlacesUtils.unfiledBookmarksFolderId), + 0, // Unused for real nodes. + PlacesUtils.unfiledBookmarksFolderId, + 1, // Move to position 1. + false + ); + PlacesUtils.transactionManager.doTransaction(txn); + }); + + try { + let txn = PlacesUIUtils.makeTransaction( + wrapNodeByIdAndParent(aChangedData.id, PlacesUtils.unfiledBookmarksFolderId), + 0, // Unused for real nodes. + PlacesUtils.unfiledBookmarksFolderId, + PlacesUtils.bookmarks.DEFAULT_INDEX, + true + ); + PlacesUtils.transactionManager.doTransaction(txn); + } catch (ex) { + do_throw(ex); + } + }, "random-anno"); + + let id = PlacesUtils.bookmarks.createFolder(PlacesUtils.unfiledBookmarksFolderId, + TEST_TITLE, + PlacesUtils.bookmarks.DEFAULT_INDEX); + PlacesUtils.annotations.setItemAnnotation(id, PlacesUIUtils.DESCRIPTION_ANNO, + "description", 0, + PlacesUtils.annotations.EXPIRE_NEVER); + PlacesUtils.annotations.setItemAnnotation(id, "random-anno", + "random-value", 0, + PlacesUtils.annotations.EXPIRE_NEVER); +}); + + +add_test(function test_separator() +{ + waitForBookmarkNotification("onItemChanged", function(aChangedData) + { + do_check_eq(aChangedData.type, PlacesUtils.bookmarks.TYPE_SEPARATOR); + do_check_eq(aChangedData.index, 3); + + waitForBookmarkNotification("onItemAdded", function(aAddedData) + { + do_check_eq(aAddedData.type, PlacesUtils.bookmarks.TYPE_SEPARATOR); + do_check_eq(aAddedData.index, 4); + let id = aAddedData.id; + + waitForBookmarkNotification("onItemMoved", function(aMovedData) + { + do_check_eq(aMovedData.id, id); + do_check_eq(aMovedData.type, PlacesUtils.bookmarks.TYPE_SEPARATOR); + do_check_eq(aMovedData.index, 1); + + run_next_test(); + }); + + let txn = PlacesUIUtils.makeTransaction( + wrapNodeByIdAndParent(aAddedData.id, PlacesUtils.unfiledBookmarksFolderId), + 0, // Unused for real nodes. + PlacesUtils.unfiledBookmarksFolderId, + 1, // Move to position 1. + false + ); + PlacesUtils.transactionManager.doTransaction(txn); + }); + + try { + let txn = PlacesUIUtils.makeTransaction( + wrapNodeByIdAndParent(aChangedData.id, PlacesUtils.unfiledBookmarksFolderId), + 0, // Unused for real nodes. + PlacesUtils.unfiledBookmarksFolderId, + PlacesUtils.bookmarks.DEFAULT_INDEX, + true + ); + PlacesUtils.transactionManager.doTransaction(txn); + } catch (ex) { + do_throw(ex); + } + }, "random-anno"); + + let id = PlacesUtils.bookmarks.insertSeparator(PlacesUtils.unfiledBookmarksFolderId, + PlacesUtils.bookmarks.DEFAULT_INDEX); + PlacesUtils.annotations.setItemAnnotation(id, "random-anno", + "random-value", 0, + PlacesUtils.annotations.EXPIRE_NEVER); +}); + +add_test(function test_bookmark() +{ + const TEST_URL = "http://places.moz.org/" + const TEST_TITLE = "Places bookmark" + + waitForBookmarkNotification("onItemChanged", function(aChangedData) + { + do_check_eq(aChangedData.title, TEST_TITLE); + do_check_eq(aChangedData.url, TEST_URL); + do_check_eq(aChangedData.type, PlacesUtils.bookmarks.TYPE_BOOKMARK); + do_check_eq(aChangedData.index, 5); + + waitForBookmarkNotification("onItemAdded", function(aAddedData) + { + do_check_eq(aAddedData.title, TEST_TITLE); + do_check_eq(aAddedData.url, TEST_URL); + do_check_eq(aAddedData.type, PlacesUtils.bookmarks.TYPE_BOOKMARK); + do_check_eq(aAddedData.index, 6); + let id = aAddedData.id; + + waitForBookmarkNotification("onItemMoved", function(aMovedData) + { + do_check_eq(aMovedData.id, id); + do_check_eq(aMovedData.type, PlacesUtils.bookmarks.TYPE_BOOKMARK); + do_check_eq(aMovedData.index, 1); + + run_next_test(); + }); + + let txn = PlacesUIUtils.makeTransaction( + wrapNodeByIdAndParent(aAddedData.id, PlacesUtils.unfiledBookmarksFolderId), + 0, // Unused for real nodes. + PlacesUtils.unfiledBookmarksFolderId, + 1, // Move to position 1. + false + ); + PlacesUtils.transactionManager.doTransaction(txn); + }); + + try { + let txn = PlacesUIUtils.makeTransaction( + wrapNodeByIdAndParent(aChangedData.id, PlacesUtils.unfiledBookmarksFolderId), + 0, // Unused for real nodes. + PlacesUtils.unfiledBookmarksFolderId, + PlacesUtils.bookmarks.DEFAULT_INDEX, + true + ); + PlacesUtils.transactionManager.doTransaction(txn); + } catch (ex) { + do_throw(ex); + } + }, "random-anno"); + + let id = PlacesUtils.bookmarks.insertBookmark(PlacesUtils.unfiledBookmarksFolderId, + NetUtil.newURI(TEST_URL), + PlacesUtils.bookmarks.DEFAULT_INDEX, + TEST_TITLE); + PlacesUtils.annotations.setItemAnnotation(id, PlacesUIUtils.DESCRIPTION_ANNO, + "description", 0, + PlacesUtils.annotations.EXPIRE_NEVER); + PlacesUtils.annotations.setItemAnnotation(id, "random-anno", + "random-value", 0, + PlacesUtils.annotations.EXPIRE_NEVER); +}); + +add_test(function test_visit() +{ + const TEST_URL = "http://places.moz.org/" + const TEST_TITLE = "Places bookmark" + + waitForBookmarkNotification("onItemAdded", function(aAddedData) + { + do_check_eq(aAddedData.title, TEST_TITLE); + do_check_eq(aAddedData.url, TEST_URL); + do_check_eq(aAddedData.type, PlacesUtils.bookmarks.TYPE_BOOKMARK); + do_check_eq(aAddedData.index, 7); + + waitForBookmarkNotification("onItemAdded", function(aAddedData2) + { + do_check_eq(aAddedData2.title, TEST_TITLE); + do_check_eq(aAddedData2.url, TEST_URL); + do_check_eq(aAddedData2.type, PlacesUtils.bookmarks.TYPE_BOOKMARK); + do_check_eq(aAddedData2.index, 8); + run_next_test(); + }); + + try { + let node = wrapNodeByIdAndParent(aAddedData.id, PlacesUtils.unfiledBookmarksFolderId); + // Simulate a not-bookmarked node, will copy it to a new bookmark. + node.id = -1; + let txn = PlacesUIUtils.makeTransaction( + node, + 0, // Unused for real nodes. + PlacesUtils.unfiledBookmarksFolderId, + PlacesUtils.bookmarks.DEFAULT_INDEX, + true + ); + PlacesUtils.transactionManager.doTransaction(txn); + } catch (ex) { + do_throw(ex); + } + }); + + PlacesUtils.bookmarks.insertBookmark(PlacesUtils.unfiledBookmarksFolderId, + NetUtil.newURI(TEST_URL), + PlacesUtils.bookmarks.DEFAULT_INDEX, + TEST_TITLE); +}); + +add_test(function check_annotations() { + // As last step check how many items for each annotation exist. + + // Copies should retain the description annotation. + let descriptions = + PlacesUtils.annotations.getItemsWithAnnotation(PlacesUIUtils.DESCRIPTION_ANNO, {}); + do_check_eq(descriptions.length, 4); + + // Only the original bookmarks should have this annotation. + let others = PlacesUtils.annotations.getItemsWithAnnotation("random-anno", {}); + do_check_eq(others.length, 3); + run_next_test(); +}); + +function run_test() +{ + run_next_test(); +} diff --git a/browser/components/places/tests/unit/test_browserGlue_bookmarkshtml.js b/browser/components/places/tests/unit/test_browserGlue_bookmarkshtml.js new file mode 100644 index 000000000..4db21555f --- /dev/null +++ b/browser/components/places/tests/unit/test_browserGlue_bookmarkshtml.js @@ -0,0 +1,33 @@ +/* -*- 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/. */ + +/** + * Tests that nsBrowserGlue correctly exports bookmarks.html at shutdown if + * browser.bookmarks.autoExportHTML is set to true. + */ + +function run_test() { + run_next_test(); +} + +add_task(function* () { + remove_bookmarks_html(); + + Services.prefs.setBoolPref("browser.bookmarks.autoExportHTML", true); + do_register_cleanup(() => Services.prefs.clearUserPref("browser.bookmarks.autoExportHTML")); + + // Initialize nsBrowserGlue before Places. + Cc["@mozilla.org/browser/browserglue;1"].getService(Ci.nsISupports); + + // Initialize Places through the History Service. + Cc["@mozilla.org/browser/nav-history-service;1"] + .getService(Ci.nsINavHistoryService); + + Services.obs.addObserver(function observer() { + Services.obs.removeObserver(observer, "profile-before-change"); + check_bookmarks_html(); + }, "profile-before-change", false); +}); diff --git a/browser/components/places/tests/unit/test_browserGlue_corrupt.js b/browser/components/places/tests/unit/test_browserGlue_corrupt.js new file mode 100644 index 000000000..5b2a09068 --- /dev/null +++ b/browser/components/places/tests/unit/test_browserGlue_corrupt.js @@ -0,0 +1,59 @@ +/* -*- 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/. */ + +/** + * Tests that nsBrowserGlue correctly restores bookmarks from a JSON backup if + * database is corrupt and one backup is available. + */ + +function run_test() { + // Create our bookmarks.html from bookmarks.glue.html. + create_bookmarks_html("bookmarks.glue.html"); + + remove_all_JSON_backups(); + + // Create our JSON backup from bookmarks.glue.json. + create_JSON_backup("bookmarks.glue.json"); + + run_next_test(); +} + +do_register_cleanup(function () { + remove_bookmarks_html(); + remove_all_JSON_backups(); + return PlacesUtils.bookmarks.eraseEverything(); +}); + +add_task(function* test_main() { + // Create a corrupt database. + yield createCorruptDB(); + + // Initialize nsBrowserGlue before Places. + Cc["@mozilla.org/browser/browserglue;1"].getService(Ci.nsISupports); + + // Check the database was corrupt. + // nsBrowserGlue uses databaseStatus to manage initialization. + Assert.equal(PlacesUtils.history.databaseStatus, + PlacesUtils.history.DATABASE_STATUS_CORRUPT); + + // The test will continue once restore has finished and smart bookmarks + // have been created. + yield promiseTopicObserved("places-browser-init-complete"); + + let bm = yield PlacesUtils.bookmarks.fetch({ + parentGuid: PlacesUtils.bookmarks.toolbarGuid, + index: 0 + }); + yield checkItemHasAnnotation(bm.guid, SMART_BOOKMARKS_ANNO); + + // Check that JSON backup has been restored. + // Notice restore from JSON notification is fired before smart bookmarks creation. + bm = yield PlacesUtils.bookmarks.fetch({ + parentGuid: PlacesUtils.bookmarks.toolbarGuid, + index: SMART_BOOKMARKS_ON_TOOLBAR + }); + Assert.equal(bm.title, "examplejson"); +}); diff --git a/browser/components/places/tests/unit/test_browserGlue_corrupt_nobackup.js b/browser/components/places/tests/unit/test_browserGlue_corrupt_nobackup.js new file mode 100644 index 000000000..7cb4e5e4c --- /dev/null +++ b/browser/components/places/tests/unit/test_browserGlue_corrupt_nobackup.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/. */ + +/** + * Tests that nsBrowserGlue correctly imports from bookmarks.html if database + * is corrupt but a JSON backup is not available. + */ + +function run_test() { + // Create our bookmarks.html from bookmarks.glue.html. + create_bookmarks_html("bookmarks.glue.html"); + + // Remove JSON backup from profile. + remove_all_JSON_backups(); + + run_next_test(); +} + +do_register_cleanup(remove_bookmarks_html); + +add_task(function* () { + // Create a corrupt database. + yield createCorruptDB(); + + // Initialize nsBrowserGlue before Places. + Cc["@mozilla.org/browser/browserglue;1"].getService(Ci.nsISupports); + + // Check the database was corrupt. + // nsBrowserGlue uses databaseStatus to manage initialization. + Assert.equal(PlacesUtils.history.databaseStatus, + PlacesUtils.history.DATABASE_STATUS_CORRUPT); + + // The test will continue once import has finished and smart bookmarks + // have been created. + yield promiseTopicObserved("places-browser-init-complete"); + + let bm = yield PlacesUtils.bookmarks.fetch({ + parentGuid: PlacesUtils.bookmarks.toolbarGuid, + index: 0 + }); + yield checkItemHasAnnotation(bm.guid, SMART_BOOKMARKS_ANNO); + + // Check that bookmarks html has been restored. + bm = yield PlacesUtils.bookmarks.fetch({ + parentGuid: PlacesUtils.bookmarks.toolbarGuid, + index: SMART_BOOKMARKS_ON_TOOLBAR + }); + Assert.equal(bm.title, "example"); +}); diff --git a/browser/components/places/tests/unit/test_browserGlue_corrupt_nobackup_default.js b/browser/components/places/tests/unit/test_browserGlue_corrupt_nobackup_default.js new file mode 100644 index 000000000..480420091 --- /dev/null +++ b/browser/components/places/tests/unit/test_browserGlue_corrupt_nobackup_default.js @@ -0,0 +1,55 @@ +/* -*- 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/. */ + +/** + * Tests that nsBrowserGlue correctly restores default bookmarks if database is + * corrupt, nor a JSON backup nor bookmarks.html are available. + */ + +Components.utils.import("resource://gre/modules/AppConstants.jsm"); + +function run_test() { + // Remove bookmarks.html from profile. + remove_bookmarks_html(); + + // Remove JSON backup from profile. + remove_all_JSON_backups(); + + run_next_test(); +} + +add_task(function* () { + // Create a corrupt database. + yield createCorruptDB(); + + // Initialize nsBrowserGlue before Places. + Cc["@mozilla.org/browser/browserglue;1"].getService(Ci.nsISupports); + + // Check the database was corrupt. + // nsBrowserGlue uses databaseStatus to manage initialization. + Assert.equal(PlacesUtils.history.databaseStatus, + PlacesUtils.history.DATABASE_STATUS_CORRUPT); + + // The test will continue once import has finished and smart bookmarks + // have been created. + yield promiseTopicObserved("places-browser-init-complete"); + + let bm = yield PlacesUtils.bookmarks.fetch({ + parentGuid: PlacesUtils.bookmarks.toolbarGuid, + index: 0 + }); + yield checkItemHasAnnotation(bm.guid, SMART_BOOKMARKS_ANNO); + + // Check that default bookmarks have been restored. + bm = yield PlacesUtils.bookmarks.fetch({ + parentGuid: PlacesUtils.bookmarks.toolbarGuid, + index: SMART_BOOKMARKS_ON_TOOLBAR + }); + + // Bug 1283076: Nightly bookmark points to Get Involved page, not Getting Started one + let chanTitle = AppConstants.NIGHTLY_BUILD ? "Get Involved" : "Getting Started"; + do_check_eq(bm.title, chanTitle); +}); diff --git a/browser/components/places/tests/unit/test_browserGlue_distribution.js b/browser/components/places/tests/unit/test_browserGlue_distribution.js new file mode 100644 index 000000000..c3d6e1d9e --- /dev/null +++ b/browser/components/places/tests/unit/test_browserGlue_distribution.js @@ -0,0 +1,125 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Tests that nsBrowserGlue correctly imports bookmarks from distribution.ini. + */ + +const PREF_SMART_BOOKMARKS_VERSION = "browser.places.smartBookmarksVersion"; +const PREF_BMPROCESSED = "distribution.516444.bookmarksProcessed"; +const PREF_DISTRIBUTION_ID = "distribution.id"; + +const TOPICDATA_DISTRIBUTION_CUSTOMIZATION = "force-distribution-customization"; +const TOPIC_CUSTOMIZATION_COMPLETE = "distribution-customization-complete"; +const TOPIC_BROWSERGLUE_TEST = "browser-glue-test"; + +function run_test() { + // Set special pref to load distribution.ini from the profile folder. + Services.prefs.setBoolPref("distribution.testing.loadFromProfile", true); + + // Copy distribution.ini file to the profile dir. + let distroDir = gProfD.clone(); + distroDir.leafName = "distribution"; + let iniFile = distroDir.clone(); + iniFile.append("distribution.ini"); + if (iniFile.exists()) { + iniFile.remove(false); + print("distribution.ini already exists, did some test forget to cleanup?"); + } + + let testDistributionFile = gTestDir.clone(); + testDistributionFile.append("distribution.ini"); + testDistributionFile.copyTo(distroDir, "distribution.ini"); + Assert.ok(testDistributionFile.exists()); + + run_next_test(); +} + +do_register_cleanup(function () { + // Remove the distribution file, even if the test failed, otherwise all + // next tests will import it. + let iniFile = gProfD.clone(); + iniFile.leafName = "distribution"; + iniFile.append("distribution.ini"); + if (iniFile.exists()) { + iniFile.remove(false); + } + Assert.ok(!iniFile.exists()); +}); + +add_task(function* () { + // Disable Smart Bookmarks creation. + Services.prefs.setIntPref(PREF_SMART_BOOKMARKS_VERSION, -1); + + // Initialize Places through the History Service and check that a new + // database has been created. + Assert.equal(PlacesUtils.history.databaseStatus, + PlacesUtils.history.DATABASE_STATUS_CREATE); + + // Force distribution. + let glue = Cc["@mozilla.org/browser/browserglue;1"].getService(Ci.nsIObserver) + glue.observe(null, TOPIC_BROWSERGLUE_TEST, TOPICDATA_DISTRIBUTION_CUSTOMIZATION); + + // Test will continue on customization complete notification. + yield promiseTopicObserved(TOPIC_CUSTOMIZATION_COMPLETE); + + // Check the custom bookmarks exist on menu. + let menuItem = yield PlacesUtils.bookmarks.fetch({ + parentGuid: PlacesUtils.bookmarks.menuGuid, + index: 0 + }); + Assert.equal(menuItem.title, "Menu Link Before"); + + menuItem = yield PlacesUtils.bookmarks.fetch({ + parentGuid: PlacesUtils.bookmarks.menuGuid, + index: 1 + DEFAULT_BOOKMARKS_ON_MENU + }); + Assert.equal(menuItem.title, "Menu Link After"); + + // Check no favicon or keyword exists for this bookmark + yield Assert.rejects(waitForResolvedPromise(() => { + return PlacesUtils.promiseFaviconData(menuItem.url.href); + }, "Favicon not found", 10), /Favicon\snot\sfound/, "Favicon not found"); + + let keywordItem = yield PlacesUtils.keywords.fetch({ + url: menuItem.url.href + }); + Assert.strictEqual(keywordItem, null); + + // Check the custom bookmarks exist on toolbar. + let toolbarItem = yield PlacesUtils.bookmarks.fetch({ + parentGuid: PlacesUtils.bookmarks.toolbarGuid, + index: 0 + }); + Assert.equal(toolbarItem.title, "Toolbar Link Before"); + + // Check the custom favicon and keyword exist for this bookmark + let faviconItem = yield waitForResolvedPromise(() => { + return PlacesUtils.promiseFaviconData(toolbarItem.url.href); + }, "Favicon not found", 10); + Assert.equal(faviconItem.uri.spec, "https://example.org/favicon.png"); + Assert.greater(faviconItem.dataLen, 0); + Assert.equal(faviconItem.mimeType, "image/png"); + + let base64Icon = "data:image/png;base64," + + base64EncodeString(String.fromCharCode.apply(String, faviconItem.data)); + Assert.equal(base64Icon, SMALLPNG_DATA_URI.spec); + + keywordItem = yield PlacesUtils.keywords.fetch({ + url: toolbarItem.url.href + }); + Assert.notStrictEqual(keywordItem, null); + Assert.equal(keywordItem.keyword, "e:t:b"); + + toolbarItem = yield PlacesUtils.bookmarks.fetch({ + parentGuid: PlacesUtils.bookmarks.toolbarGuid, + index: 1 + DEFAULT_BOOKMARKS_ON_TOOLBAR + }); + Assert.equal(toolbarItem.title, "Toolbar Link After"); + + // Check the bmprocessed pref has been created. + Assert.ok(Services.prefs.getBoolPref(PREF_BMPROCESSED)); + + // Check distribution prefs have been created. + Assert.equal(Services.prefs.getCharPref(PREF_DISTRIBUTION_ID), "516444"); +}); diff --git a/browser/components/places/tests/unit/test_browserGlue_migrate.js b/browser/components/places/tests/unit/test_browserGlue_migrate.js new file mode 100644 index 000000000..817f10c81 --- /dev/null +++ b/browser/components/places/tests/unit/test_browserGlue_migrate.js @@ -0,0 +1,70 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Tests that nsBrowserGlue does not overwrite bookmarks imported from the + * migrators. They usually run before nsBrowserGlue, so if we find any + * bookmark on init, we should not try to import. + */ + +const PREF_SMART_BOOKMARKS_VERSION = "browser.places.smartBookmarksVersion"; + +function run_test() { + // Create our bookmarks.html from bookmarks.glue.html. + create_bookmarks_html("bookmarks.glue.html"); + + // Remove current database file. + clearDB(); + + run_next_test(); +} + +do_register_cleanup(remove_bookmarks_html); + +add_task(function* test_migrate_bookmarks() { + // Initialize Places through the History Service and check that a new + // database has been created. + Assert.equal(PlacesUtils.history.databaseStatus, + PlacesUtils.history.DATABASE_STATUS_CREATE); + + // A migrator would run before nsBrowserGlue Places initialization, so mimic + // that behavior adding a bookmark and notifying the migration. + let bg = Cc["@mozilla.org/browser/browserglue;1"].getService(Ci.nsIObserver); + bg.observe(null, "initial-migration-will-import-default-bookmarks", null); + + yield PlacesUtils.bookmarks.insert({ + parentGuid: PlacesUtils.bookmarks.menuGuid, + index: PlacesUtils.bookmarks.DEFAULT_INDEX, + type: PlacesUtils.bookmarks.TYPE_BOOKMARK, + url: "http://mozilla.org/", + title: "migrated" + }); + + let promise = promiseTopicObserved("places-browser-init-complete"); + bg.observe(null, "initial-migration-did-import-default-bookmarks", null); + yield promise; + + let bm = yield PlacesUtils.bookmarks.fetch({ + parentGuid: PlacesUtils.bookmarks.toolbarGuid, + index: 0 + }); + yield checkItemHasAnnotation(bm.guid, SMART_BOOKMARKS_ANNO); + + // Check the created bookmark still exists. + bm = yield PlacesUtils.bookmarks.fetch({ + parentGuid: PlacesUtils.bookmarks.menuGuid, + index: SMART_BOOKMARKS_ON_MENU + }); + Assert.equal(bm.title, "migrated"); + + // Check that we have not imported any new bookmark. + Assert.ok(!(yield PlacesUtils.bookmarks.fetch({ + parentGuid: PlacesUtils.bookmarks.menuGuid, + index: SMART_BOOKMARKS_ON_MENU + 1 + }))); + + Assert.ok(!(yield PlacesUtils.bookmarks.fetch({ + parentGuid: PlacesUtils.bookmarks.toolbarGuid, + index: SMART_BOOKMARKS_ON_MENU + }))); +}); diff --git a/browser/components/places/tests/unit/test_browserGlue_prefs.js b/browser/components/places/tests/unit/test_browserGlue_prefs.js new file mode 100644 index 000000000..9f3504636 --- /dev/null +++ b/browser/components/places/tests/unit/test_browserGlue_prefs.js @@ -0,0 +1,240 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Tests that nsBrowserGlue is correctly interpreting the preferences settable + * by the user or by other components. + */ + +const PREF_IMPORT_BOOKMARKS_HTML = "browser.places.importBookmarksHTML"; +const PREF_RESTORE_DEFAULT_BOOKMARKS = "browser.bookmarks.restore_default_bookmarks"; +const PREF_SMART_BOOKMARKS_VERSION = "browser.places.smartBookmarksVersion"; +const PREF_AUTO_EXPORT_HTML = "browser.bookmarks.autoExportHTML"; + +const TOPIC_BROWSERGLUE_TEST = "browser-glue-test"; +const TOPICDATA_FORCE_PLACES_INIT = "force-places-init"; + +var bg = Cc["@mozilla.org/browser/browserglue;1"]. + getService(Ci.nsIObserver); + +function run_test() { + // Create our bookmarks.html from bookmarks.glue.html. + create_bookmarks_html("bookmarks.glue.html"); + + remove_all_JSON_backups(); + + // Create our JSON backup from bookmarks.glue.json. + create_JSON_backup("bookmarks.glue.json"); + + run_next_test(); +} + +do_register_cleanup(function () { + remove_bookmarks_html(); + remove_all_JSON_backups(); + + return PlacesUtils.bookmarks.eraseEverything(); +}); + +function simulatePlacesInit() { + do_print("Simulate Places init"); + // Force nsBrowserGlue::_initPlaces(). + bg.observe(null, TOPIC_BROWSERGLUE_TEST, TOPICDATA_FORCE_PLACES_INIT); + return promiseTopicObserved("places-browser-init-complete"); +} + +add_task(function* test_checkPreferences() { + // Initialize Places through the History Service and check that a new + // database has been created. + Assert.equal(PlacesUtils.history.databaseStatus, + PlacesUtils.history.DATABASE_STATUS_CREATE); + + // Wait for Places init notification. + yield promiseTopicObserved("places-browser-init-complete"); + + // Ensure preferences status. + Assert.ok(!Services.prefs.getBoolPref(PREF_AUTO_EXPORT_HTML)); + + Assert.throws(() => Services.prefs.getBoolPref(PREF_IMPORT_BOOKMARKS_HTML)); + Assert.throws(() => Services.prefs.getBoolPref(PREF_RESTORE_DEFAULT_BOOKMARKS)); +}); + +add_task(function* test_import() { + do_print("Import from bookmarks.html if importBookmarksHTML is true."); + + yield PlacesUtils.bookmarks.eraseEverything(); + + // Sanity check: we should not have any bookmark on the toolbar. + Assert.ok(!(yield PlacesUtils.bookmarks.fetch({ + parentGuid: PlacesUtils.bookmarks.toolbarGuid, + index: 0 + }))); + + // Set preferences. + Services.prefs.setBoolPref(PREF_IMPORT_BOOKMARKS_HTML, true); + + yield simulatePlacesInit(); + + // Check bookmarks.html has been imported, and a smart bookmark has been + // created. + let bm = yield PlacesUtils.bookmarks.fetch({ + parentGuid: PlacesUtils.bookmarks.toolbarGuid, + index: SMART_BOOKMARKS_ON_TOOLBAR + }); + Assert.equal(bm.title, "example"); + + // Check preferences have been reverted. + Assert.ok(!Services.prefs.getBoolPref(PREF_IMPORT_BOOKMARKS_HTML)); +}); + +add_task(function* test_import_noSmartBookmarks() { + do_print("import from bookmarks.html, but don't create smart bookmarks " + + "if they are disabled"); + + yield PlacesUtils.bookmarks.eraseEverything(); + + // Sanity check: we should not have any bookmark on the toolbar. + Assert.ok(!(yield PlacesUtils.bookmarks.fetch({ + parentGuid: PlacesUtils.bookmarks.toolbarGuid, + index: 0 + }))); + + // Set preferences. + Services.prefs.setIntPref(PREF_SMART_BOOKMARKS_VERSION, -1); + Services.prefs.setBoolPref(PREF_IMPORT_BOOKMARKS_HTML, true); + + yield simulatePlacesInit(); + + // Check bookmarks.html has been imported, but smart bookmarks have not + // been created. + let bm = yield PlacesUtils.bookmarks.fetch({ + parentGuid: PlacesUtils.bookmarks.toolbarGuid, + index: 0 + }); + Assert.equal(bm.title, "example"); + + // Check preferences have been reverted. + Assert.ok(!Services.prefs.getBoolPref(PREF_IMPORT_BOOKMARKS_HTML)); +}); + +add_task(function* test_import_autoExport_updatedSmartBookmarks() { + do_print("Import from bookmarks.html, but don't create smart bookmarks " + + "if autoExportHTML is true and they are at latest version"); + + yield PlacesUtils.bookmarks.eraseEverything(); + + // Sanity check: we should not have any bookmark on the toolbar. + Assert.ok(!(yield PlacesUtils.bookmarks.fetch({ + parentGuid: PlacesUtils.bookmarks.toolbarGuid, + index: 0 + }))); + + // Set preferences. + Services.prefs.setIntPref(PREF_SMART_BOOKMARKS_VERSION, 999); + Services.prefs.setBoolPref(PREF_AUTO_EXPORT_HTML, true); + Services.prefs.setBoolPref(PREF_IMPORT_BOOKMARKS_HTML, true); + + yield simulatePlacesInit(); + + // Check bookmarks.html has been imported, but smart bookmarks have not + // been created. + let bm = yield PlacesUtils.bookmarks.fetch({ + parentGuid: PlacesUtils.bookmarks.toolbarGuid, + index: 0 + }); + Assert.equal(bm.title, "example"); + + // Check preferences have been reverted. + Assert.ok(!Services.prefs.getBoolPref(PREF_IMPORT_BOOKMARKS_HTML)); + + Services.prefs.setBoolPref(PREF_AUTO_EXPORT_HTML, false); +}); + +add_task(function* test_import_autoExport_oldSmartBookmarks() { + do_print("Import from bookmarks.html, and create smart bookmarks if " + + "autoExportHTML is true and they are not at latest version."); + + yield PlacesUtils.bookmarks.eraseEverything(); + + // Sanity check: we should not have any bookmark on the toolbar. + Assert.ok(!(yield PlacesUtils.bookmarks.fetch({ + parentGuid: PlacesUtils.bookmarks.toolbarGuid, + index: 0 + }))); + + // Set preferences. + Services.prefs.setIntPref(PREF_SMART_BOOKMARKS_VERSION, 0); + Services.prefs.setBoolPref(PREF_AUTO_EXPORT_HTML, true); + Services.prefs.setBoolPref(PREF_IMPORT_BOOKMARKS_HTML, true); + + yield simulatePlacesInit(); + + // Check bookmarks.html has been imported, but smart bookmarks have not + // been created. + let bm = yield PlacesUtils.bookmarks.fetch({ + parentGuid: PlacesUtils.bookmarks.toolbarGuid, + index: SMART_BOOKMARKS_ON_TOOLBAR + }); + Assert.equal(bm.title, "example"); + + // Check preferences have been reverted. + Assert.ok(!Services.prefs.getBoolPref(PREF_IMPORT_BOOKMARKS_HTML)); + + Services.prefs.setBoolPref(PREF_AUTO_EXPORT_HTML, false); +}); + +add_task(function* test_restore() { + do_print("restore from default bookmarks.html if " + + "restore_default_bookmarks is true."); + + yield PlacesUtils.bookmarks.eraseEverything(); + + // Sanity check: we should not have any bookmark on the toolbar. + Assert.ok(!(yield PlacesUtils.bookmarks.fetch({ + parentGuid: PlacesUtils.bookmarks.toolbarGuid, + index: 0 + }))); + + // Set preferences. + Services.prefs.setBoolPref(PREF_RESTORE_DEFAULT_BOOKMARKS, true); + + yield simulatePlacesInit(); + + // Check bookmarks.html has been restored. + Assert.ok(yield PlacesUtils.bookmarks.fetch({ + parentGuid: PlacesUtils.bookmarks.toolbarGuid, + index: SMART_BOOKMARKS_ON_TOOLBAR + })); + + // Check preferences have been reverted. + Assert.ok(!Services.prefs.getBoolPref(PREF_RESTORE_DEFAULT_BOOKMARKS)); +}); + +add_task(function* test_restore_import() { + do_print("setting both importBookmarksHTML and " + + "restore_default_bookmarks should restore defaults."); + + yield PlacesUtils.bookmarks.eraseEverything(); + + // Sanity check: we should not have any bookmark on the toolbar. + Assert.ok(!(yield PlacesUtils.bookmarks.fetch({ + parentGuid: PlacesUtils.bookmarks.toolbarGuid, + index: 0 + }))); + + // Set preferences. + Services.prefs.setBoolPref(PREF_IMPORT_BOOKMARKS_HTML, true); + Services.prefs.setBoolPref(PREF_RESTORE_DEFAULT_BOOKMARKS, true); + + yield simulatePlacesInit(); + + // Check bookmarks.html has been restored. + Assert.ok(yield PlacesUtils.bookmarks.fetch({ + parentGuid: PlacesUtils.bookmarks.toolbarGuid, + index: SMART_BOOKMARKS_ON_TOOLBAR + })); + + // Check preferences have been reverted. + Assert.ok(!Services.prefs.getBoolPref(PREF_RESTORE_DEFAULT_BOOKMARKS)); + Assert.ok(!Services.prefs.getBoolPref(PREF_IMPORT_BOOKMARKS_HTML)); +}); diff --git a/browser/components/places/tests/unit/test_browserGlue_restore.js b/browser/components/places/tests/unit/test_browserGlue_restore.js new file mode 100644 index 000000000..9d7ac5ac1 --- /dev/null +++ b/browser/components/places/tests/unit/test_browserGlue_restore.js @@ -0,0 +1,62 @@ +/* -*- 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/. */ + +/** + * Tests that nsBrowserGlue correctly restores bookmarks from a JSON backup if + * database has been created and one backup is available. + */ + +function run_test() { + // Create our bookmarks.html from bookmarks.glue.html. + create_bookmarks_html("bookmarks.glue.html"); + + remove_all_JSON_backups(); + + // Create our JSON backup from bookmarks.glue.json. + create_JSON_backup("bookmarks.glue.json"); + + // Remove current database file. + clearDB(); + + run_next_test(); +} + +do_register_cleanup(function () { + remove_bookmarks_html(); + remove_all_JSON_backups(); + return PlacesUtils.bookmarks.eraseEverything(); +}); + +add_task(function* test_main() { + // Initialize nsBrowserGlue before Places. + Cc["@mozilla.org/browser/browserglue;1"].getService(Ci.nsISupports); + + // Initialize Places through the History Service. + let hs = Cc["@mozilla.org/browser/nav-history-service;1"]. + getService(Ci.nsINavHistoryService); + + // Check a new database has been created. + // nsBrowserGlue uses databaseStatus to manage initialization. + Assert.equal(hs.databaseStatus, hs.DATABASE_STATUS_CREATE); + + // The test will continue once restore has finished and smart bookmarks + // have been created. + yield promiseTopicObserved("places-browser-init-complete"); + + let bm = yield PlacesUtils.bookmarks.fetch({ + parentGuid: PlacesUtils.bookmarks.toolbarGuid, + index: 0 + }); + yield checkItemHasAnnotation(bm.guid, SMART_BOOKMARKS_ANNO); + + // Check that JSON backup has been restored. + // Notice restore from JSON notification is fired before smart bookmarks creation. + bm = yield PlacesUtils.bookmarks.fetch({ + parentGuid: PlacesUtils.bookmarks.toolbarGuid, + index: SMART_BOOKMARKS_ON_TOOLBAR + }); + Assert.equal(bm.title, "examplejson"); +}); diff --git a/browser/components/places/tests/unit/test_browserGlue_smartBookmarks.js b/browser/components/places/tests/unit/test_browserGlue_smartBookmarks.js new file mode 100644 index 000000000..6ecaec4fe --- /dev/null +++ b/browser/components/places/tests/unit/test_browserGlue_smartBookmarks.js @@ -0,0 +1,285 @@ +/* -*- 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/. */ + +/** + * Tests that nsBrowserGlue is correctly interpreting the preferences settable + * by the user or by other components. + */ + +const PREF_SMART_BOOKMARKS_VERSION = "browser.places.smartBookmarksVersion"; +const PREF_AUTO_EXPORT_HTML = "browser.bookmarks.autoExportHTML"; +const PREF_IMPORT_BOOKMARKS_HTML = "browser.places.importBookmarksHTML"; +const PREF_RESTORE_DEFAULT_BOOKMARKS = "browser.bookmarks.restore_default_bookmarks"; + +function run_test() { + remove_bookmarks_html(); + remove_all_JSON_backups(); + run_next_test(); +} + +do_register_cleanup(() => PlacesUtils.bookmarks.eraseEverything()); + +function countFolderChildren(aFolderItemId) { + let rootNode = PlacesUtils.getFolderContents(aFolderItemId).root; + let cc = rootNode.childCount; + // Dump contents. + for (let i = 0; i < cc ; i++) { + let node = rootNode.getChild(i); + let title = PlacesUtils.nodeIsSeparator(node) ? "---" : node.title; + print("Found child(" + i + "): " + title); + } + rootNode.containerOpen = false; + return cc; +} + +add_task(function* setup() { + // Initialize browserGlue, but remove it's listener to places-init-complete. + Cc["@mozilla.org/browser/browserglue;1"].getService(Ci.nsIObserver); + + // Initialize Places. + PlacesUtils.history; + + // Wait for Places init notification. + yield promiseTopicObserved("places-browser-init-complete"); + + // Ensure preferences status. + Assert.ok(!Services.prefs.getBoolPref(PREF_AUTO_EXPORT_HTML)); + Assert.ok(!Services.prefs.getBoolPref(PREF_RESTORE_DEFAULT_BOOKMARKS)); + Assert.throws(() => Services.prefs.getBoolPref(PREF_IMPORT_BOOKMARKS_HTML)); +}); + +add_task(function* test_version_0() { + do_print("All smart bookmarks are created if smart bookmarks version is 0."); + + // Sanity check: we should have default bookmark. + Assert.ok(yield PlacesUtils.bookmarks.fetch({ + parentGuid: PlacesUtils.bookmarks.toolbarGuid, + index: 0 + })); + + Assert.ok(yield PlacesUtils.bookmarks.fetch({ + parentGuid: PlacesUtils.bookmarks.menuGuid, + index: 0 + })); + + // Set preferences. + Services.prefs.setIntPref(PREF_SMART_BOOKMARKS_VERSION, 0); + + yield rebuildSmartBookmarks(); + + // Count items. + Assert.equal(countFolderChildren(PlacesUtils.toolbarFolderId), + SMART_BOOKMARKS_ON_TOOLBAR + DEFAULT_BOOKMARKS_ON_TOOLBAR); + Assert.equal(countFolderChildren(PlacesUtils.bookmarksMenuFolderId), + SMART_BOOKMARKS_ON_MENU + DEFAULT_BOOKMARKS_ON_MENU); + + // Check version has been updated. + Assert.equal(Services.prefs.getIntPref(PREF_SMART_BOOKMARKS_VERSION), + SMART_BOOKMARKS_VERSION); +}); + +add_task(function* test_version_change() { + do_print("An existing smart bookmark is replaced when version changes."); + + // Sanity check: we have a smart bookmark on the toolbar. + let bm = yield PlacesUtils.bookmarks.fetch({ + parentGuid: PlacesUtils.bookmarks.toolbarGuid, + index: 0 + }); + yield checkItemHasAnnotation(bm.guid, SMART_BOOKMARKS_ANNO); + + // Change its title. + yield PlacesUtils.bookmarks.update({guid: bm.guid, title: "new title"}); + bm = yield PlacesUtils.bookmarks.fetch({guid: bm.guid}); + Assert.equal(bm.title, "new title"); + + // Sanity check items. + Assert.equal(countFolderChildren(PlacesUtils.toolbarFolderId), + SMART_BOOKMARKS_ON_TOOLBAR + DEFAULT_BOOKMARKS_ON_TOOLBAR); + Assert.equal(countFolderChildren(PlacesUtils.bookmarksMenuFolderId), + SMART_BOOKMARKS_ON_MENU + DEFAULT_BOOKMARKS_ON_MENU); + + // Set preferences. + Services.prefs.setIntPref(PREF_SMART_BOOKMARKS_VERSION, 1); + + yield rebuildSmartBookmarks(); + + // Count items. + Assert.equal(countFolderChildren(PlacesUtils.toolbarFolderId), + SMART_BOOKMARKS_ON_TOOLBAR + DEFAULT_BOOKMARKS_ON_TOOLBAR); + Assert.equal(countFolderChildren(PlacesUtils.bookmarksMenuFolderId), + SMART_BOOKMARKS_ON_MENU + DEFAULT_BOOKMARKS_ON_MENU); + + // Check smart bookmark has been replaced, itemId has changed. + bm = yield PlacesUtils.bookmarks.fetch({ + parentGuid: PlacesUtils.bookmarks.toolbarGuid, + index: 0 + }); + yield checkItemHasAnnotation(bm.guid, SMART_BOOKMARKS_ANNO); + Assert.notEqual(bm.title, "new title"); + + // Check version has been updated. + Assert.equal(Services.prefs.getIntPref(PREF_SMART_BOOKMARKS_VERSION), + SMART_BOOKMARKS_VERSION); +}); + +add_task(function* test_version_change_pos() { + do_print("bookmarks position is retained when version changes."); + + // Sanity check items. + Assert.equal(countFolderChildren(PlacesUtils.toolbarFolderId), + SMART_BOOKMARKS_ON_TOOLBAR + DEFAULT_BOOKMARKS_ON_TOOLBAR); + Assert.equal(countFolderChildren(PlacesUtils.bookmarksMenuFolderId), + SMART_BOOKMARKS_ON_MENU + DEFAULT_BOOKMARKS_ON_MENU); + + let bm = yield PlacesUtils.bookmarks.fetch({ + parentGuid: PlacesUtils.bookmarks.menuGuid, + index: 0 + }); + yield checkItemHasAnnotation(bm.guid, SMART_BOOKMARKS_ANNO); + let firstItemTitle = bm.title; + + // Set preferences. + Services.prefs.setIntPref(PREF_SMART_BOOKMARKS_VERSION, 1); + + yield rebuildSmartBookmarks(); + + // Count items. + Assert.equal(countFolderChildren(PlacesUtils.toolbarFolderId), + SMART_BOOKMARKS_ON_TOOLBAR + DEFAULT_BOOKMARKS_ON_TOOLBAR); + Assert.equal(countFolderChildren(PlacesUtils.bookmarksMenuFolderId), + SMART_BOOKMARKS_ON_MENU + DEFAULT_BOOKMARKS_ON_MENU); + + // Check smart bookmarks are still in correct position. + bm = yield PlacesUtils.bookmarks.fetch({ + parentGuid: PlacesUtils.bookmarks.menuGuid, + index: 0 + }); + yield checkItemHasAnnotation(bm.guid, SMART_BOOKMARKS_ANNO); + Assert.equal(bm.title, firstItemTitle); + + // Check version has been updated. + Assert.equal(Services.prefs.getIntPref(PREF_SMART_BOOKMARKS_VERSION), + SMART_BOOKMARKS_VERSION); +}); + +add_task(function* test_version_change_pos_moved() { + do_print("moved bookmarks position is retained when version changes."); + + // Sanity check items. + Assert.equal(countFolderChildren(PlacesUtils.toolbarFolderId), + SMART_BOOKMARKS_ON_TOOLBAR + DEFAULT_BOOKMARKS_ON_TOOLBAR); + Assert.equal(countFolderChildren(PlacesUtils.bookmarksMenuFolderId), + SMART_BOOKMARKS_ON_MENU + DEFAULT_BOOKMARKS_ON_MENU); + + let bm1 = yield PlacesUtils.bookmarks.fetch({ + parentGuid: PlacesUtils.bookmarks.menuGuid, + index: 0 + }); + yield checkItemHasAnnotation(bm1.guid, SMART_BOOKMARKS_ANNO); + let firstItemTitle = bm1.title; + + // Move the first smart bookmark to the end of the menu. + yield PlacesUtils.bookmarks.update({ + parentGuid: PlacesUtils.bookmarks.menuGuid, + guid: bm1.guid, + index: PlacesUtils.bookmarks.DEFAULT_INDEX + }); + + let bm = yield PlacesUtils.bookmarks.fetch({ + parentGuid: PlacesUtils.bookmarks.menuGuid, + index: PlacesUtils.bookmarks.DEFAULT_INDEX + }); + Assert.equal(bm.guid, bm1.guid); + + // Set preferences. + Services.prefs.setIntPref(PREF_SMART_BOOKMARKS_VERSION, 1); + + yield rebuildSmartBookmarks(); + + // Count items. + Assert.equal(countFolderChildren(PlacesUtils.toolbarFolderId), + SMART_BOOKMARKS_ON_TOOLBAR + DEFAULT_BOOKMARKS_ON_TOOLBAR); + Assert.equal(countFolderChildren(PlacesUtils.bookmarksMenuFolderId), + SMART_BOOKMARKS_ON_MENU + DEFAULT_BOOKMARKS_ON_MENU); + + bm1 = yield PlacesUtils.bookmarks.fetch({ + parentGuid: PlacesUtils.bookmarks.menuGuid, + index: PlacesUtils.bookmarks.DEFAULT_INDEX + }); + yield checkItemHasAnnotation(bm1.guid, SMART_BOOKMARKS_ANNO); + Assert.equal(bm1.title, firstItemTitle); + + // Move back the smart bookmark to the original position. + yield PlacesUtils.bookmarks.update({ + parentGuid: PlacesUtils.bookmarks.menuGuid, + guid: bm1.guid, + index: 1 + }); + + // Check version has been updated. + Assert.equal(Services.prefs.getIntPref(PREF_SMART_BOOKMARKS_VERSION), + SMART_BOOKMARKS_VERSION); +}); + +add_task(function* test_recreation() { + do_print("An explicitly removed smart bookmark should not be recreated."); + + // Remove toolbar's smart bookmarks + let bm = yield PlacesUtils.bookmarks.fetch({ + parentGuid: PlacesUtils.bookmarks.toolbarGuid, + index: 0 + }); + yield PlacesUtils.bookmarks.remove(bm.guid); + + // Sanity check items. + Assert.equal(countFolderChildren(PlacesUtils.toolbarFolderId), + DEFAULT_BOOKMARKS_ON_TOOLBAR); + Assert.equal(countFolderChildren(PlacesUtils.bookmarksMenuFolderId), + SMART_BOOKMARKS_ON_MENU + DEFAULT_BOOKMARKS_ON_MENU); + + // Set preferences. + Services.prefs.setIntPref(PREF_SMART_BOOKMARKS_VERSION, 1); + + yield rebuildSmartBookmarks(); + + // Count items. + // We should not have recreated the smart bookmark on toolbar. + Assert.equal(countFolderChildren(PlacesUtils.toolbarFolderId), + DEFAULT_BOOKMARKS_ON_TOOLBAR); + Assert.equal(countFolderChildren(PlacesUtils.bookmarksMenuFolderId), + SMART_BOOKMARKS_ON_MENU + DEFAULT_BOOKMARKS_ON_MENU); + + // Check version has been updated. + Assert.equal(Services.prefs.getIntPref(PREF_SMART_BOOKMARKS_VERSION), + SMART_BOOKMARKS_VERSION); +}); + +add_task(function* test_recreation_version_0() { + do_print("Even if a smart bookmark has been removed recreate it if version is 0."); + + // Sanity check items. + Assert.equal(countFolderChildren(PlacesUtils.toolbarFolderId), + DEFAULT_BOOKMARKS_ON_TOOLBAR); + Assert.equal(countFolderChildren(PlacesUtils.bookmarksMenuFolderId), + SMART_BOOKMARKS_ON_MENU + DEFAULT_BOOKMARKS_ON_MENU); + + // Set preferences. + Services.prefs.setIntPref(PREF_SMART_BOOKMARKS_VERSION, 0); + + yield rebuildSmartBookmarks(); + + // Count items. + // We should not have recreated the smart bookmark on toolbar. + Assert.equal(countFolderChildren(PlacesUtils.toolbarFolderId), + SMART_BOOKMARKS_ON_TOOLBAR + DEFAULT_BOOKMARKS_ON_TOOLBAR); + Assert.equal(countFolderChildren(PlacesUtils.bookmarksMenuFolderId), + SMART_BOOKMARKS_ON_MENU + DEFAULT_BOOKMARKS_ON_MENU); + + // Check version has been updated. + Assert.equal(Services.prefs.getIntPref(PREF_SMART_BOOKMARKS_VERSION), + SMART_BOOKMARKS_VERSION); +}); diff --git a/browser/components/places/tests/unit/test_browserGlue_urlbar_defaultbehavior_migration.js b/browser/components/places/tests/unit/test_browserGlue_urlbar_defaultbehavior_migration.js new file mode 100644 index 000000000..072056b3f --- /dev/null +++ b/browser/components/places/tests/unit/test_browserGlue_urlbar_defaultbehavior_migration.js @@ -0,0 +1,150 @@ +/* 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 UI_VERSION = 26; +const TOPIC_BROWSERGLUE_TEST = "browser-glue-test"; +const TOPICDATA_BROWSERGLUE_TEST = "force-ui-migration"; +const DEFAULT_BEHAVIOR_PREF = "browser.urlbar.default.behavior"; +const AUTOCOMPLETE_PREF = "browser.urlbar.autocomplete.enabled"; + +var gBrowserGlue = Cc["@mozilla.org/browser/browserglue;1"] + .getService(Ci.nsIObserver); +var gGetBoolPref = Services.prefs.getBoolPref; + +function run_test() { + run_next_test(); +} + +do_register_cleanup(cleanup); + +function cleanup() { + let prefix = "browser.urlbar.suggest."; + for (let type of ["history", "bookmark", "openpage", "history.onlyTyped"]) { + Services.prefs.clearUserPref(prefix + type); + } + Services.prefs.clearUserPref("browser.migration.version"); + Services.prefs.clearUserPref(AUTOCOMPLETE_PREF); +} + +function setupBehaviorAndMigrate(aDefaultBehavior, aAutocompleteEnabled = true) { + cleanup(); + // Migrate browser.urlbar.default.behavior preference. + Services.prefs.setIntPref("browser.migration.version", UI_VERSION - 1); + Services.prefs.setIntPref(DEFAULT_BEHAVIOR_PREF, aDefaultBehavior); + Services.prefs.setBoolPref(AUTOCOMPLETE_PREF, aAutocompleteEnabled); + // Simulate a migration. + gBrowserGlue.observe(null, TOPIC_BROWSERGLUE_TEST, TOPICDATA_BROWSERGLUE_TEST); +} + +add_task(function*() { + do_print("Migrate default.behavior = 0"); + setupBehaviorAndMigrate(0); + + Assert.ok(gGetBoolPref("browser.urlbar.suggest.history"), + "History preference should be true."); + Assert.ok(gGetBoolPref("browser.urlbar.suggest.bookmark"), + "Bookmark preference should be true."); + Assert.ok(gGetBoolPref("browser.urlbar.suggest.openpage"), + "Openpage preference should be true."); + Assert.equal(gGetBoolPref("browser.urlbar.suggest.history.onlyTyped"), false, + "Typed preference should be false."); +}); + +add_task(function*() { + do_print("Migrate default.behavior = 1"); + setupBehaviorAndMigrate(1); + + Assert.ok(gGetBoolPref("browser.urlbar.suggest.history"), + "History preference should be true."); + Assert.equal(gGetBoolPref("browser.urlbar.suggest.bookmark"), false, + "Bookmark preference should be false."); + Assert.equal(gGetBoolPref("browser.urlbar.suggest.openpage"), false, + "Openpage preference should be false"); + Assert.equal(gGetBoolPref("browser.urlbar.suggest.history.onlyTyped"), false, + "Typed preference should be false"); +}); + +add_task(function*() { + do_print("Migrate default.behavior = 2"); + setupBehaviorAndMigrate(2); + + Assert.equal(gGetBoolPref("browser.urlbar.suggest.history"), false, + "History preference should be false."); + Assert.ok(gGetBoolPref("browser.urlbar.suggest.bookmark"), + "Bookmark preference should be true."); + Assert.equal(gGetBoolPref("browser.urlbar.suggest.openpage"), false, + "Openpage preference should be false"); + Assert.equal(gGetBoolPref("browser.urlbar.suggest.history.onlyTyped"), false, + "Typed preference should be false"); +}); + +add_task(function*() { + do_print("Migrate default.behavior = 3"); + setupBehaviorAndMigrate(3); + + Assert.ok(gGetBoolPref("browser.urlbar.suggest.history"), + "History preference should be true."); + Assert.ok(gGetBoolPref("browser.urlbar.suggest.bookmark"), + "Bookmark preference should be true."); + Assert.equal(gGetBoolPref("browser.urlbar.suggest.openpage"), false, + "Openpage preference should be false"); + Assert.equal(gGetBoolPref("browser.urlbar.suggest.history.onlyTyped"), false, + "Typed preference should be false"); +}); + +add_task(function*() { + do_print("Migrate default.behavior = 19"); + setupBehaviorAndMigrate(19); + + Assert.ok(gGetBoolPref("browser.urlbar.suggest.history"), + "History preference should be true."); + Assert.ok(gGetBoolPref("browser.urlbar.suggest.bookmark"), + "Bookmark preference should be true."); + Assert.equal(gGetBoolPref("browser.urlbar.suggest.openpage"), false, + "Openpage preference should be false"); + Assert.equal(gGetBoolPref("browser.urlbar.suggest.history.onlyTyped"), false, + "Typed preference should be false"); +}); + +add_task(function*() { + do_print("Migrate default.behavior = 33"); + setupBehaviorAndMigrate(33); + + Assert.ok(gGetBoolPref("browser.urlbar.suggest.history"), + "History preference should be true."); + Assert.equal(gGetBoolPref("browser.urlbar.suggest.bookmark"), false, + "Bookmark preference should be false."); + Assert.equal(gGetBoolPref("browser.urlbar.suggest.openpage"), false, + "Openpage preference should be false"); + Assert.ok(gGetBoolPref("browser.urlbar.suggest.history.onlyTyped"), + "Typed preference should be true"); +}); + +add_task(function*() { + do_print("Migrate default.behavior = 129"); + setupBehaviorAndMigrate(129); + + Assert.ok(gGetBoolPref("browser.urlbar.suggest.history"), + "History preference should be true."); + Assert.equal(gGetBoolPref("browser.urlbar.suggest.bookmark"), false, + "Bookmark preference should be false."); + Assert.ok(gGetBoolPref("browser.urlbar.suggest.openpage"), + "Openpage preference should be true"); + Assert.equal(gGetBoolPref("browser.urlbar.suggest.history.onlyTyped"), false, + "Typed preference should be false"); +}); + +add_task(function*() { + do_print("Migrate default.behavior = 0, autocomplete.enabled = false"); + setupBehaviorAndMigrate(0, false); + + Assert.equal(gGetBoolPref("browser.urlbar.suggest.history"), false, + "History preference should be false."); + Assert.equal(gGetBoolPref("browser.urlbar.suggest.bookmark"), false, + "Bookmark preference should be false."); + Assert.equal(gGetBoolPref("browser.urlbar.suggest.openpage"), false, + "Openpage preference should be false"); + Assert.equal(gGetBoolPref("browser.urlbar.suggest.history.onlyTyped"), false, + "Typed preference should be false"); +}); diff --git a/browser/components/places/tests/unit/test_clearHistory_shutdown.js b/browser/components/places/tests/unit/test_clearHistory_shutdown.js new file mode 100644 index 000000000..0c1d78801 --- /dev/null +++ b/browser/components/places/tests/unit/test_clearHistory_shutdown.js @@ -0,0 +1,181 @@ +/* -*- 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/. */ + +/** + * Tests that requesting clear history at shutdown will really clear history. + */ + +const URIS = [ + "http://a.example1.com/" +, "http://b.example1.com/" +, "http://b.example2.com/" +, "http://c.example3.com/" +]; + +const TOPIC_CONNECTION_CLOSED = "places-connection-closed"; + +var EXPECTED_NOTIFICATIONS = [ + "places-shutdown" +, "places-will-close-connection" +, "places-expiration-finished" +, "places-connection-closed" +]; + +const UNEXPECTED_NOTIFICATIONS = [ + "xpcom-shutdown" +]; + +const FTP_URL = "ftp://localhost/clearHistoryOnShutdown/"; + +// Send the profile-after-change notification to the form history component to ensure +// that it has been initialized. +var formHistoryStartup = Cc["@mozilla.org/satchel/form-history-startup;1"]. + getService(Ci.nsIObserver); +formHistoryStartup.observe(null, "profile-after-change", null); +XPCOMUtils.defineLazyModuleGetter(this, "FormHistory", + "resource://gre/modules/FormHistory.jsm"); + +var timeInMicroseconds = Date.now() * 1000; + +function run_test() { + run_next_test(); +} + +add_task(function* test_execute() { + do_print("Initialize browserglue before Places"); + + // Avoid default bookmarks import. + let glue = Cc["@mozilla.org/browser/browserglue;1"]. + getService(Ci.nsIObserver); + glue.observe(null, "initial-migration-will-import-default-bookmarks", null); + glue.observe(null, "test-initialize-sanitizer", null); + + + Services.prefs.setBoolPref("privacy.clearOnShutdown.cache", true); + Services.prefs.setBoolPref("privacy.clearOnShutdown.cookies", true); + Services.prefs.setBoolPref("privacy.clearOnShutdown.offlineApps", true); + Services.prefs.setBoolPref("privacy.clearOnShutdown.history", true); + Services.prefs.setBoolPref("privacy.clearOnShutdown.downloads", true); + Services.prefs.setBoolPref("privacy.clearOnShutdown.cookies", true); + Services.prefs.setBoolPref("privacy.clearOnShutdown.formData", true); + Services.prefs.setBoolPref("privacy.clearOnShutdown.sessions", true); + Services.prefs.setBoolPref("privacy.clearOnShutdown.siteSettings", true); + + Services.prefs.setBoolPref("privacy.sanitize.sanitizeOnShutdown", true); + + do_print("Add visits."); + for (let aUrl of URIS) { + yield PlacesTestUtils.addVisits({ + uri: uri(aUrl), visitDate: timeInMicroseconds++, + transition: PlacesUtils.history.TRANSITION_TYPED + }); + } + do_print("Add cache."); + yield storeCache(FTP_URL, "testData"); + do_print("Add form history."); + yield addFormHistory(); + Assert.equal((yield getFormHistoryCount()), 1, "Added form history"); + + do_print("Simulate and wait shutdown."); + yield shutdownPlaces(); + + Assert.equal((yield getFormHistoryCount()), 0, "Form history cleared"); + + let stmt = DBConn(true).createStatement( + "SELECT id FROM moz_places WHERE url = :page_url " + ); + + try { + URIS.forEach(function(aUrl) { + stmt.params.page_url = aUrl; + do_check_false(stmt.executeStep()); + stmt.reset(); + }); + } finally { + stmt.finalize(); + } + + do_print("Check cache"); + // Check cache. + yield checkCache(FTP_URL); +}); + +function addFormHistory() { + return new Promise(resolve => { + let now = Date.now() * 1000; + FormHistory.update({ op: "add", + fieldname: "testfield", + value: "test", + timesUsed: 1, + firstUsed: now, + lastUsed: now + }, + { handleCompletion(reason) { resolve(); } }); + }); +} + +function getFormHistoryCount() { + return new Promise((resolve, reject) => { + let count = -1; + FormHistory.count({ fieldname: "testfield" }, + { handleResult(result) { count = result; }, + handleCompletion(reason) { resolve(count); } + }); + }); +} + +function storeCache(aURL, aContent) { + let cache = Services.cache2; + let storage = cache.diskCacheStorage(LoadContextInfo.default, false); + + return new Promise(resolve => { + let storeCacheListener = { + onCacheEntryCheck: function (entry, appcache) { + return Ci.nsICacheEntryOpenCallback.ENTRY_WANTED; + }, + + onCacheEntryAvailable: function (entry, isnew, appcache, status) { + do_check_eq(status, Cr.NS_OK); + + entry.setMetaDataElement("servertype", "0"); + var os = entry.openOutputStream(0); + + var written = os.write(aContent, aContent.length); + if (written != aContent.length) { + do_throw("os.write has not written all data!\n" + + " Expected: " + written + "\n" + + " Actual: " + aContent.length + "\n"); + } + os.close(); + entry.close(); + resolve(); + } + }; + + storage.asyncOpenURI(Services.io.newURI(aURL, null, null), "", + Ci.nsICacheStorage.OPEN_NORMALLY, + storeCacheListener); + }); +} + + +function checkCache(aURL) { + let cache = Services.cache2; + let storage = cache.diskCacheStorage(LoadContextInfo.default, false); + + return new Promise(resolve => { + let checkCacheListener = { + onCacheEntryAvailable: function (entry, isnew, appcache, status) { + do_check_eq(status, Cr.NS_ERROR_CACHE_KEY_NOT_FOUND); + resolve(); + } + }; + + storage.asyncOpenURI(Services.io.newURI(aURL, null, null), "", + Ci.nsICacheStorage.OPEN_READONLY, + checkCacheListener); + }); +} diff --git a/browser/components/places/tests/unit/test_leftpane_corruption_handling.js b/browser/components/places/tests/unit/test_leftpane_corruption_handling.js new file mode 100644 index 000000000..0af6f4e95 --- /dev/null +++ b/browser/components/places/tests/unit/test_leftpane_corruption_handling.js @@ -0,0 +1,174 @@ +/* -*- 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/. */ + +/** + * Tests that we build a working leftpane in various corruption situations. + */ + +// Used to store the original leftPaneFolderId getter. +var gLeftPaneFolderIdGetter; +var gAllBookmarksFolderIdGetter; +// Used to store the original left Pane status as a JSON string. +var gReferenceHierarchy; +var gLeftPaneFolderId; + +add_task(function* () { + // We want empty roots. + yield PlacesUtils.bookmarks.eraseEverything(); + + // Sanity check. + Assert.ok(!!PlacesUIUtils); + + // Check getters. + gLeftPaneFolderIdGetter = Object.getOwnPropertyDescriptor(PlacesUIUtils, "leftPaneFolderId"); + Assert.equal(typeof(gLeftPaneFolderIdGetter.get), "function"); + gAllBookmarksFolderIdGetter = Object.getOwnPropertyDescriptor(PlacesUIUtils, "allBookmarksFolderId"); + Assert.equal(typeof(gAllBookmarksFolderIdGetter.get), "function"); + + do_register_cleanup(() => PlacesUtils.bookmarks.eraseEverything()); +}); + +add_task(function* () { + // Add a third party bogus annotated item. Should not be removed. + let folder = yield PlacesUtils.bookmarks.insert({ + parentGuid: PlacesUtils.bookmarks.unfiledGuid, + title: "test", + index: PlacesUtils.bookmarks.DEFAULT_INDEX, + type: PlacesUtils.bookmarks.TYPE_FOLDER + }); + + let folderId = yield PlacesUtils.promiseItemId(folder.guid); + PlacesUtils.annotations.setItemAnnotation(folderId, ORGANIZER_QUERY_ANNO, + "test", 0, + PlacesUtils.annotations.EXPIRE_NEVER); + + // Create the left pane, and store its current status, it will be used + // as reference value. + gLeftPaneFolderId = PlacesUIUtils.leftPaneFolderId; + gReferenceHierarchy = folderIdToHierarchy(gLeftPaneFolderId); + + while (gTests.length) { + // Run current test. + yield Task.spawn(gTests.shift()); + + // Regenerate getters. + Object.defineProperty(PlacesUIUtils, "leftPaneFolderId", gLeftPaneFolderIdGetter); + gLeftPaneFolderId = PlacesUIUtils.leftPaneFolderId; + Object.defineProperty(PlacesUIUtils, "allBookmarksFolderId", gAllBookmarksFolderIdGetter); + + // Check the new left pane folder. + let leftPaneHierarchy = folderIdToHierarchy(gLeftPaneFolderId) + Assert.equal(gReferenceHierarchy, leftPaneHierarchy); + + folder = yield PlacesUtils.bookmarks.fetch({guid: folder.guid}); + Assert.equal(folder.title, "test"); + } +}); + +// Corruption cases. +var gTests = [ + + function* test1() { + print("1. Do nothing, checks test calibration."); + }, + + function* test2() { + print("2. Delete the left pane folder."); + let guid = yield PlacesUtils.promiseItemGuid(gLeftPaneFolderId); + yield PlacesUtils.bookmarks.remove(guid); + }, + + function* test3() { + print("3. Delete a child of the left pane folder."); + let guid = yield PlacesUtils.promiseItemGuid(gLeftPaneFolderId); + let bm = yield PlacesUtils.bookmarks.fetch({parentGuid: guid, index: 0}); + yield PlacesUtils.bookmarks.remove(bm.guid); + }, + + function* test4() { + print("4. Delete AllBookmarks."); + let guid = yield PlacesUtils.promiseItemGuid(PlacesUIUtils.allBookmarksFolderId); + yield PlacesUtils.bookmarks.remove(guid); + }, + + function* test5() { + print("5. Create a duplicated left pane folder."); + let folder = yield PlacesUtils.bookmarks.insert({ + parentGuid: PlacesUtils.bookmarks.unfiledGuid, + title: "PlacesRoot", + index: PlacesUtils.bookmarks.DEFAULT_INDEX, + type: PlacesUtils.bookmarks.TYPE_FOLDER + }); + + let folderId = yield PlacesUtils.promiseItemId(folder.guid); + PlacesUtils.annotations.setItemAnnotation(folderId, ORGANIZER_FOLDER_ANNO, + "PlacesRoot", 0, + PlacesUtils.annotations.EXPIRE_NEVER); + }, + + function* test6() { + print("6. Create a duplicated left pane query."); + let folder = yield PlacesUtils.bookmarks.insert({ + parentGuid: PlacesUtils.bookmarks.unfiledGuid, + title: "AllBookmarks", + index: PlacesUtils.bookmarks.DEFAULT_INDEX, + type: PlacesUtils.bookmarks.TYPE_FOLDER + }); + + let folderId = yield PlacesUtils.promiseItemId(folder.guid); + PlacesUtils.annotations.setItemAnnotation(folderId, ORGANIZER_QUERY_ANNO, + "AllBookmarks", 0, + PlacesUtils.annotations.EXPIRE_NEVER); + }, + + function* test7() { + print("7. Remove the left pane folder annotation."); + PlacesUtils.annotations.removeItemAnnotation(gLeftPaneFolderId, + ORGANIZER_FOLDER_ANNO); + }, + + function* test8() { + print("8. Remove a left pane query annotation."); + PlacesUtils.annotations.removeItemAnnotation(PlacesUIUtils.allBookmarksFolderId, + ORGANIZER_QUERY_ANNO); + }, + + function* test9() { + print("9. Remove a child of AllBookmarks."); + let guid = yield PlacesUtils.promiseItemGuid(PlacesUIUtils.allBookmarksFolderId); + let bm = yield PlacesUtils.bookmarks.fetch({parentGuid: guid, index: 0}); + yield PlacesUtils.bookmarks.remove(bm.guid); + } + +]; + +/** + * Convert a folder item id to a JSON representation of it and its contents. + */ +function folderIdToHierarchy(aFolderId) { + let root = PlacesUtils.getFolderContents(aFolderId).root; + let hier = JSON.stringify(hierarchyToObj(root)); + root.containerOpen = false; + return hier; +} + +function hierarchyToObj(aNode) { + let o = {} + o.title = aNode.title; + o.annos = PlacesUtils.getAnnotationsForItem(aNode.itemId) + if (PlacesUtils.nodeIsURI(aNode)) { + o.uri = aNode.uri; + } + else if (PlacesUtils.nodeIsFolder(aNode)) { + o.children = []; + PlacesUtils.asContainer(aNode).containerOpen = true; + for (let i = 0; i < aNode.childCount; ++i) { + o.children.push(hierarchyToObj(aNode.getChild(i))); + } + aNode.containerOpen = false; + } + return o; +} diff --git a/browser/components/places/tests/unit/xpcshell.ini b/browser/components/places/tests/unit/xpcshell.ini new file mode 100644 index 000000000..1c40e1c53 --- /dev/null +++ b/browser/components/places/tests/unit/xpcshell.ini @@ -0,0 +1,25 @@ +[DEFAULT] +head = head_bookmarks.js +tail = +firefox-appdir = browser +skip-if = toolkit == 'android' +support-files = + bookmarks.glue.html + bookmarks.glue.json + corruptDB.sqlite + distribution.ini + +[test_421483.js] +[test_browserGlue_bookmarkshtml.js] +[test_browserGlue_corrupt.js] +[test_browserGlue_corrupt_nobackup.js] +[test_browserGlue_corrupt_nobackup_default.js] +[test_browserGlue_distribution.js] +[test_browserGlue_migrate.js] +[test_browserGlue_prefs.js] +[test_browserGlue_restore.js] +[test_browserGlue_smartBookmarks.js] +[test_browserGlue_urlbar_defaultbehavior_migration.js] +[test_clearHistory_shutdown.js] +[test_leftpane_corruption_handling.js] +[test_PUIU_makeTransaction.js] |