browser_984455_bookmarks_items_reparenting.js (11623B)
1 /* This Source Code Form is subject to the terms of the Mozilla Public 2 * License, v. 2.0. If a copy of the MPL was not distributed with this 3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 5 "use strict"; 6 7 var gNavBar = document.getElementById(CustomizableUI.AREA_NAVBAR); 8 var gOverflowList = document.getElementById( 9 gNavBar.getAttribute("default-overflowtarget") 10 ); 11 12 const kBookmarksButton = "bookmarks-menu-button"; 13 const kBookmarksItems = "personal-bookmarks"; 14 const kOriginalWindowWidth = window.outerWidth; 15 16 /** 17 * Helper function that opens the bookmarks menu, and returns a Promise that 18 * resolves as soon as the menu is ready for interaction. 19 */ 20 function bookmarksMenuPanelShown() { 21 return new Promise(resolve => { 22 let bookmarksMenuPopup = document.getElementById("BMB_bookmarksPopup"); 23 let onPopupShown = e => { 24 if (e.target == bookmarksMenuPopup) { 25 bookmarksMenuPopup.removeEventListener("popupshown", onPopupShown); 26 resolve(); 27 } 28 }; 29 bookmarksMenuPopup.addEventListener("popupshown", onPopupShown); 30 }); 31 } 32 33 /** 34 * Checks that the placesContext menu is correctly attached to the 35 * controller of some view. Returns a Promise that resolves as soon 36 * as the context menu is closed. 37 * 38 * @param aItemWithContextMenu the item that we need to synthesize the 39 * right click on in order to open the context menu. 40 */ 41 function checkPlacesContextMenu(aItemWithContextMenu) { 42 return (async function () { 43 let contextMenu = document.getElementById("placesContext"); 44 let newBookmarkItem = document.getElementById("placesContext_new:bookmark"); 45 info("Waiting for context menu on " + aItemWithContextMenu.id); 46 let shownPromise = popupShown(contextMenu); 47 EventUtils.synthesizeMouseAtCenter(aItemWithContextMenu, { 48 type: "contextmenu", 49 button: 2, 50 }); 51 await shownPromise; 52 53 ok( 54 !newBookmarkItem.hasAttribute("disabled"), 55 "New bookmark item shouldn't be disabled" 56 ); 57 58 info("Closing context menu"); 59 let hiddenPromise = popupHidden(contextMenu); 60 // Use hidePopup instead of the closePopup helper because macOS native 61 // context menus can't be closed by synthesized ESC in automation. 62 contextMenu.hidePopup(); 63 await hiddenPromise; 64 })(); 65 } 66 67 /** 68 * Opens the bookmarks menu panel, and then opens each of the "special" 69 * submenus in that list. Then it checks that those submenu's context menus 70 * are properly hooked up to a controller. 71 */ 72 function checkSpecialContextMenus() { 73 return (async function () { 74 let bookmarksMenuButton = document.getElementById(kBookmarksButton); 75 let bookmarksMenuPopup = document.getElementById("BMB_bookmarksPopup"); 76 77 const kSpecialItemIDs = { 78 BMB_bookmarksToolbar: "BMB_bookmarksToolbarPopup", 79 BMB_unsortedBookmarks: "BMB_unsortedBookmarksPopup", 80 }; 81 82 // Open the bookmarks menu button context menus and ensure that 83 // they have the proper views attached. 84 let shownPromise = bookmarksMenuPanelShown(); 85 86 EventUtils.synthesizeMouseAtCenter(bookmarksMenuButton, {}); 87 info("Waiting for bookmarks menu popup to show after clicking dropmarker."); 88 await shownPromise; 89 90 for (let menuID in kSpecialItemIDs) { 91 let menuItem = document.getElementById(menuID); 92 let menuPopup = document.getElementById(kSpecialItemIDs[menuID]); 93 info("Waiting to open menu for " + menuID); 94 shownPromise = popupShown(menuPopup); 95 menuPopup.openPopup(menuItem, null, 0, 0, false, false, null); 96 await shownPromise; 97 98 await checkPlacesContextMenu(menuPopup); 99 info("Closing menu for " + menuID); 100 await closePopup(menuPopup); 101 } 102 103 info("Closing bookmarks menu"); 104 await closePopup(bookmarksMenuPopup); 105 })(); 106 } 107 108 /** 109 * Closes a focused popup by simulating pressing the Escape key, 110 * and returns a Promise that resolves as soon as the popup is closed. 111 * 112 * @param aPopup the popup node to close. 113 */ 114 function closePopup(aPopup) { 115 let hiddenPromise = popupHidden(aPopup); 116 EventUtils.synthesizeKey("KEY_Escape"); 117 return hiddenPromise; 118 } 119 120 /** 121 * Helper function that checks that the context menu of the 122 * bookmark toolbar items chevron popup is correctly hooked up 123 * to the controller of a view. 124 */ 125 function checkBookmarksItemsChevronContextMenu() { 126 return (async function () { 127 let chevronPopup = document.getElementById("PlacesChevronPopup"); 128 let shownPromise = popupShown(chevronPopup); 129 let chevron = document.getElementById("PlacesChevron"); 130 EventUtils.synthesizeMouseAtCenter(chevron, {}); 131 info("Waiting for bookmark toolbar item chevron popup to show"); 132 await shownPromise; 133 await TestUtils.waitForCondition(() => { 134 for (let child of chevronPopup.children) { 135 if (child.style.visibility != "hidden") { 136 return true; 137 } 138 } 139 return false; 140 }); 141 await checkPlacesContextMenu(chevronPopup); 142 info("Waiting for bookmark toolbar item chevron popup to close"); 143 await closePopup(chevronPopup); 144 })(); 145 } 146 147 /** 148 * Forces the window to a width that causes the nav-bar to overflow 149 * its contents. Returns a Promise that resolves as soon as the 150 * overflowable nav-bar is showing its chevron. 151 */ 152 function overflowEverything(win) { 153 info("Waiting for overflow"); 154 let waitOverflowing = BrowserTestUtils.waitForMutationCondition( 155 gNavBar, 156 { attributes: true, attributeFilter: ["overflowing"] }, 157 () => gNavBar.hasAttribute("overflowing") 158 ); 159 ensureToolbarOverflow(win, false); 160 return waitOverflowing; 161 } 162 163 /** 164 * Returns the window to its original size from the start of the test, 165 * and returns a Promise that resolves when the nav-bar is no longer 166 * overflowing. 167 */ 168 function stopOverflowing(win, originalWindowWidth) { 169 info("Waiting until we stop overflowing"); 170 let waitOverflowing = BrowserTestUtils.waitForMutationCondition( 171 gNavBar, 172 { attributes: true, attributeFilter: ["overflowing"] }, 173 () => !gNavBar.hasAttribute("overflowing") 174 ); 175 unensureToolbarOverflow(win, originalWindowWidth); 176 return waitOverflowing; 177 } 178 179 /** 180 * Ensure bookmarks are visible on the toolbar. 181 * @param {DOMWindow} win the browser window 182 */ 183 async function waitBookmarksToolbarIsUpdated(win = window) { 184 await TestUtils.waitForCondition( 185 async () => (await win.PlacesToolbarHelper.getIsEmpty()) === false, 186 "Waiting for the Bookmarks toolbar to have been rebuilt and not be empty" 187 ); 188 if ( 189 win.PlacesToolbarHelper._viewElt._placesView._updateNodesVisibilityTimer 190 ) { 191 await BrowserTestUtils.waitForEvent( 192 win, 193 "BookmarksToolbarVisibilityUpdated" 194 ); 195 } 196 } 197 198 /** 199 * Checks that an item with ID aID is overflowing in the nav-bar. 200 * 201 * @param aID the ID of the node to check for overflowingness. 202 */ 203 function checkOverflowing(aID) { 204 ok( 205 !gNavBar.querySelector("#" + aID), 206 "Item with ID " + aID + " should no longer be in the gNavBar" 207 ); 208 let item = gOverflowList.querySelector("#" + aID); 209 ok(item, "Item with ID " + aID + " should be overflowing"); 210 is( 211 item.getAttribute("overflowedItem"), 212 "true", 213 "Item with ID " + aID + " should have overflowedItem attribute" 214 ); 215 } 216 217 /** 218 * Checks that an item with ID aID is not overflowing in the nav-bar. 219 * 220 * @param aID the ID of hte node to check for non-overflowingness. 221 */ 222 function checkNotOverflowing(aID) { 223 ok( 224 !gOverflowList.querySelector("#" + aID), 225 "Item with ID " + aID + " should no longer be overflowing" 226 ); 227 let item = gNavBar.querySelector("#" + aID); 228 ok(item, "Item with ID " + aID + " should be in the nav bar"); 229 ok( 230 !item.hasAttribute("overflowedItem"), 231 "Item with ID " + aID + " should not have overflowedItem attribute" 232 ); 233 } 234 235 /** 236 * Test that overflowing the bookmarks menu button doesn't break the 237 * context menus for the Unsorted and Bookmarks Toolbar menu items. 238 */ 239 add_task(async function testOverflowingBookmarksButtonContextMenu() { 240 ok(CustomizableUI.inDefaultState, "Should start in default state."); 241 // The DevEdition has the DevTools button in the toolbar by default. Remove it 242 // to prevent branch-specific available toolbar space. 243 CustomizableUI.removeWidgetFromArea("developer-button"); 244 CustomizableUI.removeWidgetFromArea( 245 "library-button", 246 CustomizableUI.AREA_NAVBAR 247 ); 248 CustomizableUI.addWidgetToArea(kBookmarksButton, CustomizableUI.AREA_NAVBAR); 249 ok( 250 !gNavBar.hasAttribute("overflowing"), 251 "Should start with a non-overflowing toolbar." 252 ); 253 254 // Open the Unsorted and Bookmarks Toolbar context menus and ensure 255 // that they have views attached. 256 await checkSpecialContextMenus(); 257 258 const originalWindowWidth = window.outerWidth; 259 await overflowEverything(window); 260 checkOverflowing(kBookmarksButton); 261 262 await stopOverflowing(window, originalWindowWidth); 263 checkNotOverflowing(kBookmarksButton); 264 265 await checkSpecialContextMenus(); 266 }); 267 268 /** 269 * Test that the bookmarks toolbar items context menu still works if moved 270 * to the menu from the overflow panel, and then back to the toolbar. 271 */ 272 add_task(async function testOverflowingBookmarksItemsContextMenu() { 273 info("Adding a bookmark to the bookmarks toolbar."); 274 let addedBookmark = await PlacesUtils.bookmarks.insert({ 275 parentGuid: PlacesUtils.bookmarks.toolbarGuid, 276 title: "Test", 277 url: "https://example.com", 278 }); 279 280 registerCleanupFunction(async () => { 281 await PlacesUtils.bookmarks.remove(addedBookmark); 282 }); 283 284 info("Ensuring panel is ready."); 285 await PanelUI.ensureReady(); 286 287 let bookmarksToolbarItems = document.getElementById(kBookmarksItems); 288 await gCustomizeMode.addToToolbar(bookmarksToolbarItems); 289 await waitBookmarksToolbarIsUpdated(); 290 await checkPlacesContextMenu(bookmarksToolbarItems); 291 292 let originalWindowWidth = window.outerWidth; 293 await overflowEverything(window); 294 checkOverflowing(kBookmarksItems); 295 296 await gCustomizeMode.addToPanel(bookmarksToolbarItems); 297 298 await stopOverflowing(window, originalWindowWidth); 299 300 await gCustomizeMode.addToToolbar(bookmarksToolbarItems); 301 await waitBookmarksToolbarIsUpdated(); 302 await checkPlacesContextMenu(bookmarksToolbarItems); 303 }); 304 305 /** 306 * Test that overflowing the bookmarks toolbar items doesn't cause the 307 * context menu in the bookmarks toolbar items chevron to stop working. 308 */ 309 add_task(async function testOverflowingBookmarksItemsChevronContextMenu() { 310 // If it's not already there, let's move the bookmarks toolbar items to 311 // the nav-bar. 312 let bookmarksToolbarItems = document.getElementById(kBookmarksItems); 313 await gCustomizeMode.addToToolbar(bookmarksToolbarItems); 314 315 // We make the PlacesToolbarItems element be super tiny in order to force 316 // the bookmarks toolbar items into overflowing and making the chevron 317 // show itself. 318 let placesToolbarItems = document.getElementById("PlacesToolbarItems"); 319 let placesChevron = document.getElementById("PlacesChevron"); 320 placesToolbarItems.style.maxWidth = "10px"; 321 info("Waiting for chevron to no longer be collapsed"); 322 await TestUtils.waitForCondition(() => !placesChevron.collapsed); 323 324 await checkBookmarksItemsChevronContextMenu(); 325 326 let originalWindowWidth = window.outerWidth; 327 await overflowEverything(window); 328 checkOverflowing(kBookmarksItems); 329 330 await stopOverflowing(window, originalWindowWidth); 331 checkNotOverflowing(kBookmarksItems); 332 333 await waitBookmarksToolbarIsUpdated(); 334 await checkBookmarksItemsChevronContextMenu(); 335 336 placesToolbarItems.style.removeProperty("max-width"); 337 }); 338 339 add_task(async function asyncCleanup() { 340 window.resizeTo(kOriginalWindowWidth, window.outerHeight); 341 await resetCustomization(); 342 });