/* 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/. */ /* eslint-disable mozilla/no-arbitrary-setTimeout */ "use strict"; requestLongerTimeout(2); /** * Test opening and closing the bookmarks panel. */ let win; let bookmarkPanel; let bookmarkStar; let bookmarkPanelTitle; let bookmarkRemoveButton; let editBookmarkPanelRemoveButtonRect; const TEST_URL = "data:text/html,"; add_setup(async function () { win = await BrowserTestUtils.openNewBrowserWindow(); win.StarUI._createPanelIfNeeded(); win.StarUI._closePanelQuickForTesting = true; bookmarkPanel = win.document.getElementById("editBookmarkPanel"); bookmarkPanel.setAttribute("animate", false); bookmarkStar = win.BookmarkingUI.star; bookmarkPanelTitle = win.document.getElementById("editBookmarkPanelTitle"); bookmarkRemoveButton = win.document.getElementById( "editBookmarkPanelRemoveButton" ); registerCleanupFunction(async () => { delete win.StarUI._closePanelQuickForTesting; await BrowserTestUtils.closeWindow(win); }); }); function mouseout() { let mouseOutPromise = BrowserTestUtils.waitForEvent( bookmarkPanel, "mouseout" ); EventUtils.synthesizeNativeMouseEvent({ type: "mousemove", target: win.gURLBar.textbox, offsetX: 0, offsetY: 0, win, }); EventUtils.synthesizeMouse(bookmarkPanel, 0, 0, { type: "mouseout" }, win); info("Waiting for mouseout event"); return mouseOutPromise; } async function test_bookmarks_popup({ isNewBookmark, popupShowFn, popupEditFn, shouldAutoClose, popupHideFn, isBookmarkRemoved, }) { await BrowserTestUtils.withNewTab( { gBrowser: win.gBrowser, url: TEST_URL }, async function (browser) { try { if (!isNewBookmark) { await PlacesUtils.bookmarks.insert({ parentGuid: await PlacesUIUtils.defaultParentGuid, url: TEST_URL, title: "Home Page", }); } info(`BookmarkingUI.status is ${win.BookmarkingUI.status}`); await TestUtils.waitForCondition( () => win.BookmarkingUI.status != win.BookmarkingUI.STATUS_UPDATING, "BookmarkingUI should not be updating" ); Assert.equal( bookmarkStar.hasAttribute("starred"), !isNewBookmark, "Page should only be starred prior to popupshown if editing bookmark" ); Assert.equal( bookmarkPanel.state, "closed", "Panel should be 'closed' to start test" ); let shownPromise = promisePopupShown(bookmarkPanel); await popupShowFn(browser); await shownPromise; Assert.equal( bookmarkPanel.state, "open", "Panel should be 'open' after shownPromise is resolved" ); editBookmarkPanelRemoveButtonRect = bookmarkRemoveButton.getBoundingClientRect(); if (popupEditFn) { await popupEditFn(); } Assert.equal( bookmarkStar.getAttribute("starred"), "true", "Page is starred" ); Assert.equal( bookmarkPanelTitle.dataset.l10nId, isNewBookmark ? "bookmarks-add-bookmark" : "bookmarks-edit-bookmark", "title should match isEditingBookmark state" ); Assert.equal( bookmarkRemoveButton.dataset.l10nId, isNewBookmark ? "bookmark-panel-cancel" : "bookmark-panel-remove", "remove/cancel button label should match isEditingBookmark state" ); if (!shouldAutoClose) { await new Promise(resolve => setTimeout(resolve, 400)); Assert.equal( bookmarkPanel.state, "open", "Panel should still be 'open' for non-autoclose" ); } let defaultLocation = await PlacesUIUtils.defaultParentGuid; const promises = []; if (isNewBookmark && !isBookmarkRemoved) { // Expect new bookmark to be created. promises.push( PlacesTestUtils.waitForNotification("bookmark-added", events => events.some( ({ parentGuid, url }) => parentGuid == defaultLocation && TEST_URL == url ) ) ); } if (!isNewBookmark && isBookmarkRemoved) { // Expect existing bookmark to be removed. promises.push( PlacesTestUtils.waitForNotification("bookmark-removed", events => events.some( ({ parentGuid, url }) => parentGuid == defaultLocation && TEST_URL == url ) ) ); } promises.push(promisePopupHidden(bookmarkPanel)); if (popupHideFn) { await popupHideFn(); } else { // Move the mouse out of the way so that the panel will auto-close. await mouseout(); } await Promise.all(promises); Assert.equal( bookmarkStar.hasAttribute("starred"), !isBookmarkRemoved, "Page is starred after closing" ); // Count number of bookmarks. let count = 0; await PlacesUtils.bookmarks.fetch({ url: TEST_URL }, () => count++); const message = isBookmarkRemoved ? "No bookmark should exist" : "Only one bookmark should exist"; Assert.equal(count, isBookmarkRemoved ? 0 : 1, message); } finally { let bookmark = await PlacesUtils.bookmarks.fetch({ url: TEST_URL }); Assert.equal( !!bookmark, !isBookmarkRemoved, "bookmark should not be present if a panel action should've removed it" ); if (bookmark) { await PlacesUtils.bookmarks.remove(bookmark); } } } ); } add_task(async function panel_shown_for_new_bookmarks_and_autocloses() { await test_bookmarks_popup({ isNewBookmark: true, popupShowFn() { bookmarkStar.click(); }, shouldAutoClose: true, isBookmarkRemoved: false, }); }); add_task( async function panel_shown_once_for_doubleclick_on_new_bookmark_star_and_autocloses() { await test_bookmarks_popup({ isNewBookmark: true, popupShowFn() { EventUtils.synthesizeMouse( bookmarkStar, 10, 10, { clickCount: 2 }, win ); }, shouldAutoClose: true, isBookmarkRemoved: false, }); } ); add_task( async function panel_shown_once_for_slow_doubleclick_on_new_bookmark_star_and_autocloses() { todo( false, "bug 1250267, may need to add some tracking state to " + "browser-places.js for this." ); /* await test_bookmarks_popup({ isNewBookmark: true, *popupShowFn() { EventUtils.synthesizeMouse(bookmarkStar, 10, 10, window); await new Promise(resolve => setTimeout(resolve, 300)); EventUtils.synthesizeMouse(bookmarkStar, 10, 10, window); }, shouldAutoClose: true, isBookmarkRemoved: false, }); */ } ); add_task( async function panel_shown_for_keyboardshortcut_on_new_bookmark_star_and_autocloses() { await test_bookmarks_popup({ isNewBookmark: true, popupShowFn() { EventUtils.synthesizeKey("D", { accelKey: true }, win); }, shouldAutoClose: true, isBookmarkRemoved: false, }); } ); add_task(async function panel_shown_for_new_bookmarks_mousemove_mouseout() { await test_bookmarks_popup({ isNewBookmark: true, popupShowFn() { bookmarkStar.click(); }, async popupEditFn() { let mouseMovePromise = BrowserTestUtils.waitForEvent( bookmarkPanel, "mousemove" ); EventUtils.synthesizeMouseAtCenter( bookmarkPanel, { type: "mousemove" }, win ); info("Waiting for mousemove event"); await mouseMovePromise; info("Got mousemove event"); await new Promise(resolve => setTimeout(resolve, 400)); is( bookmarkPanel.state, "open", "Panel should still be open on mousemove" ); }, async popupHideFn() { await mouseout(); info("Got mouseout event, should autoclose now"); }, shouldAutoClose: false, isBookmarkRemoved: false, }); }); add_task(async function panel_shown_for_new_bookmark_close_with_ESC() { await test_bookmarks_popup({ isNewBookmark: true, popupShowFn() { bookmarkStar.click(); }, shouldAutoClose: true, popupHideFn() { EventUtils.synthesizeKey("VK_ESCAPE", { accelKey: true }, win); }, isBookmarkRemoved: true, }); }); add_task(async function panel_shown_for_editing_no_autoclose_close_with_ESC() { await test_bookmarks_popup({ isNewBookmark: false, popupShowFn() { bookmarkStar.click(); }, shouldAutoClose: false, popupHideFn() { EventUtils.synthesizeKey("VK_ESCAPE", { accelKey: true }, win); }, isBookmarkRemoved: false, }); }); add_task(async function panel_shown_for_new_bookmark_keypress_no_autoclose() { await test_bookmarks_popup({ isNewBookmark: true, popupShowFn() { bookmarkStar.click(); }, popupEditFn() { EventUtils.sendChar("VK_TAB", win); }, shouldAutoClose: false, popupHideFn() { bookmarkPanel.hidePopup(); }, isBookmarkRemoved: false, }); }); add_task(async function bookmark_with_invalid_default_folder() { await createAndRemoveDefaultFolder(); await test_bookmarks_popup({ isNewBookmark: true, shouldAutoClose: true, async popupShowFn(browser) { EventUtils.synthesizeKey("d", { accelKey: true }, win); }, }); }); add_task( async function panel_shown_for_new_bookmark_compositionstart_no_autoclose() { await test_bookmarks_popup({ isNewBookmark: true, popupShowFn() { bookmarkStar.click(); }, async popupEditFn() { let compositionStartPromise = BrowserTestUtils.waitForEvent( bookmarkPanel, "compositionstart" ); EventUtils.synthesizeComposition({ type: "compositionstart" }, win); info("Waiting for compositionstart event"); await compositionStartPromise; info("Got compositionstart event"); }, shouldAutoClose: false, popupHideFn() { EventUtils.synthesizeComposition( { type: "compositioncommitasis" }, win ); bookmarkPanel.hidePopup(); }, isBookmarkRemoved: false, }); } ); add_task( async function panel_shown_for_new_bookmark_compositionstart_mouseout_no_autoclose() { await test_bookmarks_popup({ isNewBookmark: true, popupShowFn() { bookmarkStar.click(); }, async popupEditFn() { let mouseMovePromise = BrowserTestUtils.waitForEvent( bookmarkPanel, "mousemove" ); EventUtils.synthesizeMouseAtCenter( bookmarkPanel, { type: "mousemove", }, win ); info("Waiting for mousemove event"); await mouseMovePromise; info("Got mousemove event"); let compositionStartPromise = BrowserTestUtils.waitForEvent( bookmarkPanel, "compositionstart" ); EventUtils.synthesizeComposition({ type: "compositionstart" }, win); info("Waiting for compositionstart event"); await compositionStartPromise; info("Got compositionstart event"); await mouseout(); info("Got mouseout event, but shouldn't run autoclose"); }, shouldAutoClose: false, popupHideFn() { EventUtils.synthesizeComposition( { type: "compositioncommitasis" }, win ); bookmarkPanel.hidePopup(); }, isBookmarkRemoved: false, }); } ); add_task( async function panel_shown_for_new_bookmark_compositionend_no_autoclose() { await test_bookmarks_popup({ isNewBookmark: true, popupShowFn() { bookmarkStar.click(); }, async popupEditFn() { let mouseMovePromise = BrowserTestUtils.waitForEvent( bookmarkPanel, "mousemove" ); EventUtils.synthesizeMouseAtCenter( bookmarkPanel, { type: "mousemove", }, win ); info("Waiting for mousemove event"); await mouseMovePromise; info("Got mousemove event"); EventUtils.synthesizeComposition( { type: "compositioncommit", data: "committed text", }, win ); }, popupHideFn() { bookmarkPanel.hidePopup(); }, shouldAutoClose: false, isBookmarkRemoved: false, }); } ); add_task(async function contextmenu_new_bookmark_keypress_no_autoclose() { await test_bookmarks_popup({ isNewBookmark: true, async popupShowFn(browser) { let contextMenu = win.document.getElementById("contentAreaContextMenu"); let awaitPopupShown = BrowserTestUtils.waitForEvent( contextMenu, "popupshown" ); let awaitPopupHidden = BrowserTestUtils.waitForEvent( contextMenu, "popuphidden" ); await BrowserTestUtils.synthesizeMouseAtCenter( "body", { type: "contextmenu", button: 2, }, browser ); await awaitPopupShown; contextMenu.activateItem( win.document.getElementById("context-bookmarkpage") ); await awaitPopupHidden; }, popupEditFn() { EventUtils.sendChar("VK_TAB", win); }, shouldAutoClose: false, popupHideFn() { bookmarkPanel.hidePopup(); }, isBookmarkRemoved: false, }); }); add_task(async function bookmarks_menu_new_bookmark_remove_bookmark() { await test_bookmarks_popup({ isNewBookmark: true, popupShowFn(browser) { win.document.getElementById("menu_bookmarkThisPage").doCommand(); }, shouldAutoClose: true, popupHideFn() { win.document.getElementById("editBookmarkPanelRemoveButton").click(); }, isBookmarkRemoved: true, }); }); add_task(async function ctrl_d_edit_bookmark_remove_bookmark() { await test_bookmarks_popup({ isNewBookmark: false, popupShowFn(browser) { EventUtils.synthesizeKey("D", { accelKey: true }, win); }, shouldAutoClose: true, popupHideFn() { win.document.getElementById("editBookmarkPanelRemoveButton").click(); }, isBookmarkRemoved: true, }); }); add_task(async function enter_on_remove_bookmark_should_remove_bookmark() { if (AppConstants.platform == "macosx") { // "Full Keyboard Access" is disabled by default, and thus doesn't allow // keyboard navigation to the "Remove Bookmarks" button by default. return; } await test_bookmarks_popup({ isNewBookmark: true, popupShowFn(browser) { EventUtils.synthesizeKey("D", { accelKey: true }, win); }, shouldAutoClose: true, popupHideFn() { while ( !win.document.activeElement || win.document.activeElement.id != "editBookmarkPanelRemoveButton" ) { EventUtils.sendChar("VK_TAB", win); } EventUtils.sendChar("VK_RETURN", win); }, isBookmarkRemoved: true, }); }); add_task(async function mouse_hovering_panel_should_prevent_autoclose() { if (AppConstants.platform != "win") { // This test requires synthesizing native mouse movement which is // best supported on Windows. return; } await test_bookmarks_popup({ isNewBookmark: true, async popupShowFn() { await EventUtils.promiseNativeMouseEvent({ type: "mousemove", target: win.document.documentElement, offsetX: editBookmarkPanelRemoveButtonRect.left, offsetY: editBookmarkPanelRemoveButtonRect.top, }); EventUtils.synthesizeKey("D", { accelKey: true }, win); }, shouldAutoClose: false, popupHideFn() { win.document.getElementById("editBookmarkPanelRemoveButton").click(); }, isBookmarkRemoved: true, }); }); add_task(async function ctrl_d_new_bookmark_mousedown_mouseout_no_autoclose() { await test_bookmarks_popup({ isNewBookmark: true, popupShowFn(browser) { EventUtils.synthesizeKey("D", { accelKey: true }, win); }, async popupEditFn() { let mouseMovePromise = BrowserTestUtils.waitForEvent( bookmarkPanel, "mousemove" ); EventUtils.synthesizeMouseAtCenter( bookmarkPanel, { type: "mousemove" }, win ); info("Waiting for mousemove event"); await mouseMovePromise; info("Got mousemove event"); await new Promise(resolve => setTimeout(resolve, 400)); is( bookmarkPanel.state, "open", "Panel should still be open on mousemove" ); EventUtils.synthesizeMouseAtCenter( bookmarkPanelTitle, { button: 1, type: "mousedown", }, win ); await mouseout(); }, shouldAutoClose: false, popupHideFn() { win.document.getElementById("editBookmarkPanelRemoveButton").click(); }, isBookmarkRemoved: true, }); }); add_task(async function enter_during_autocomplete_should_prevent_autoclose() { await test_bookmarks_popup({ isNewBookmark: false, async popupShowFn(browser) { PlacesUtils.tagging.tagURI(makeURI(TEST_URL), ["Abc"]); EventUtils.synthesizeKey("d", { accelKey: true }, win); }, async popupEditFn() { // Start autocomplete with the registered tag. let tagsField = win.document.getElementById("editBMPanel_tagsField"); tagsField.value = ""; let popup = win.document.getElementById("editBMPanel_tagsAutocomplete"); let promiseShown = BrowserTestUtils.waitForEvent(popup, "popupshown"); tagsField.focus(); EventUtils.sendString("a", win); await promiseShown; ok(promiseShown, "autocomplete shown"); // Select first candidate. EventUtils.synthesizeKey("KEY_ArrowDown", {}, win); // Type Enter key to choose the item. EventUtils.synthesizeKey("KEY_Enter", {}, win); Assert.equal( tagsField.value, "Abc", "Autocomplete should've inserted the selected item" ); }, shouldAutoClose: false, popupHideFn() { EventUtils.synthesizeKey("KEY_Escape", {}, win); }, isBookmarkRemoved: false, }); }); add_task(async function escape_during_autocomplete_should_prevent_autoclose() { await test_bookmarks_popup({ isNewBookmark: false, async popupShowFn(browser) { PlacesUtils.tagging.tagURI(makeURI(TEST_URL), ["Abc"]); EventUtils.synthesizeKey("d", { accelKey: true }, win); }, async popupEditFn() { // Start autocomplete with the registered tag. let tagsField = win.document.getElementById("editBMPanel_tagsField"); tagsField.value = ""; let popup = win.document.getElementById("editBMPanel_tagsAutocomplete"); let promiseShown = BrowserTestUtils.waitForEvent(popup, "popupshown"); tagsField.focus(); EventUtils.sendString("a", win); await promiseShown; ok(promiseShown, "autocomplete shown"); // Select first candidate. EventUtils.synthesizeKey("KEY_ArrowDown", {}, win); // Type Escape key to close autocomplete. EventUtils.synthesizeKey("KEY_Escape", {}, win); // The text reverts to what was typed. // Note, it's important that this is different from the previously // inserted tag, since it will test an untag/tag undo condition. Assert.equal( tagsField.value, "a", "Autocomplete should revert to what was typed" ); }, shouldAutoClose: false, popupHideFn() { EventUtils.synthesizeKey("KEY_Escape", {}, win); }, isBookmarkRemoved: false, }); });