browser_876926_customize_mode_wrapping.js (9986B)
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 add_setup(async function () { 8 await SpecialPowers.pushPrefEnv({ 9 set: [["test.wait300msAfterTabSwitch", true]], 10 }); 11 }); 12 13 const kXULWidgetId = "a-test-button"; // we'll create a button with this ID. 14 const kAPIWidgetId = "save-page-button"; 15 const kPanel = CustomizableUI.AREA_FIXED_OVERFLOW_PANEL; 16 const kToolbar = CustomizableUI.AREA_NAVBAR; 17 const kVisiblePalette = "customization-palette"; 18 19 function checkWrapper(id) { 20 is( 21 document.querySelectorAll("#wrapper-" + id).length, 22 1, 23 "There should be exactly 1 wrapper for " + 24 id + 25 " in the customizing window." 26 ); 27 } 28 29 async function ensureVisible(node) { 30 let isInPalette = node.parentNode.parentNode == gNavToolbox.palette; 31 if (isInPalette) { 32 node.scrollIntoView(); 33 } 34 let dwu = window.windowUtils; 35 await BrowserTestUtils.waitForCondition(() => { 36 let nodeBounds = dwu.getBoundsWithoutFlushing(node); 37 if (isInPalette) { 38 let paletteBounds = dwu.getBoundsWithoutFlushing(gNavToolbox.palette); 39 if ( 40 !( 41 nodeBounds.top >= paletteBounds.top && 42 nodeBounds.bottom <= paletteBounds.bottom 43 ) 44 ) { 45 return false; 46 } 47 } 48 return nodeBounds.height && nodeBounds.width; 49 }); 50 } 51 52 var move = { 53 async drag(id, target) { 54 let targetNode = document.getElementById(target); 55 if (CustomizableUI.getCustomizationTarget(targetNode)) { 56 targetNode = CustomizableUI.getCustomizationTarget(targetNode); 57 } 58 let nodeToMove = document.getElementById(id); 59 await ensureVisible(nodeToMove); 60 61 simulateItemDrag(nodeToMove, targetNode, "end"); 62 }, 63 async dragToItem(id, target) { 64 let targetNode = document.getElementById(target); 65 if (CustomizableUI.getCustomizationTarget(targetNode)) { 66 targetNode = CustomizableUI.getCustomizationTarget(targetNode); 67 } 68 let items = targetNode.querySelectorAll("toolbarpaletteitem"); 69 if (target == kPanel) { 70 targetNode = items[items.length - 1]; 71 } else { 72 targetNode = items[0]; 73 } 74 let nodeToMove = document.getElementById(id); 75 await ensureVisible(nodeToMove); 76 simulateItemDrag(nodeToMove, targetNode, "start"); 77 }, 78 API(id, target) { 79 if (target == kVisiblePalette) { 80 return CustomizableUI.removeWidgetFromArea(id); 81 } 82 return CustomizableUI.addWidgetToArea(id, target, null); 83 }, 84 }; 85 86 function isLast(containerId, defaultPlacements, id) { 87 assertAreaPlacements(containerId, defaultPlacements.concat([id])); 88 let thisTarget = CustomizableUI.getCustomizationTarget( 89 document.getElementById(containerId) 90 ); 91 is( 92 thisTarget.lastElementChild.firstElementChild.id, 93 id, 94 "Widget " + id + " should be in " + containerId + " in customizing window." 95 ); 96 let otherTarget = CustomizableUI.getCustomizationTarget( 97 otherWin.document.getElementById(containerId) 98 ); 99 is( 100 otherTarget.lastElementChild.id, 101 id, 102 "Widget " + id + " should be in " + containerId + " in other window." 103 ); 104 } 105 106 function getLastVisibleNodeInToolbar(containerId, win = window) { 107 let container = CustomizableUI.getCustomizationTarget( 108 win.document.getElementById(containerId) 109 ); 110 let rv = container.lastElementChild; 111 while (rv?.hidden || rv?.firstElementChild?.hidden) { 112 rv = rv.previousElementSibling; 113 } 114 return rv; 115 } 116 117 function isLastVisibleInToolbar(containerId, defaultPlacements, id) { 118 let newPlacements; 119 for (let i = defaultPlacements.length - 1; i >= 0; i--) { 120 let el = document.getElementById(defaultPlacements[i]); 121 if (el && !el.hidden) { 122 newPlacements = [...defaultPlacements]; 123 newPlacements.splice(i + 1, 0, id); 124 break; 125 } 126 } 127 if (!newPlacements) { 128 assertAreaPlacements(containerId, defaultPlacements.concat([id])); 129 } else { 130 assertAreaPlacements(containerId, newPlacements); 131 } 132 is( 133 getLastVisibleNodeInToolbar(containerId).firstElementChild.id, 134 id, 135 "Widget " + id + " should be in " + containerId + " in customizing window." 136 ); 137 is( 138 getLastVisibleNodeInToolbar(containerId, otherWin).id, 139 id, 140 "Widget " + id + " should be in " + containerId + " in other window." 141 ); 142 } 143 144 function isFirst(containerId, defaultPlacements, id) { 145 assertAreaPlacements(containerId, [id].concat(defaultPlacements)); 146 let thisTarget = CustomizableUI.getCustomizationTarget( 147 document.getElementById(containerId) 148 ); 149 is( 150 thisTarget.firstElementChild.firstElementChild.id, 151 id, 152 "Widget " + id + " should be in " + containerId + " in customizing window." 153 ); 154 let otherTarget = CustomizableUI.getCustomizationTarget( 155 otherWin.document.getElementById(containerId) 156 ); 157 is( 158 otherTarget.firstElementChild.id, 159 id, 160 "Widget " + id + " should be in " + containerId + " in other window." 161 ); 162 } 163 164 async function checkToolbar(id, method) { 165 // Place at start of the toolbar: 166 let toolbarPlacements = getAreaWidgetIds(kToolbar); 167 await move[method](id, kToolbar); 168 if (method == "dragToItem") { 169 isFirst(kToolbar, toolbarPlacements, id); 170 } else if (method == "drag") { 171 isLastVisibleInToolbar(kToolbar, toolbarPlacements, id); 172 } else { 173 isLast(kToolbar, toolbarPlacements, id); 174 } 175 checkWrapper(id); 176 } 177 178 async function checkPanel(id, method) { 179 let panelPlacements = getAreaWidgetIds(kPanel); 180 await move[method](id, kPanel); 181 let children = document 182 .getElementById(kPanel) 183 .querySelectorAll("toolbarpaletteitem"); 184 let otherChildren = otherWin.document.getElementById(kPanel).children; 185 let newPlacements = panelPlacements.concat([id]); 186 // Relative position of the new item from the end: 187 let position = -1; 188 // For the drag to item case, we drag to the last item, making the dragged item the 189 // penultimate item. We can't well use the first item because the panel has complicated 190 // rules about rearranging wide items (which, by default, the first two items are). 191 if (method == "dragToItem") { 192 newPlacements.pop(); 193 newPlacements.splice(panelPlacements.length - 1, 0, id); 194 position = -2; 195 } 196 assertAreaPlacements(kPanel, newPlacements); 197 is( 198 children[children.length + position].firstElementChild.id, 199 id, 200 "Widget " + id + " should be in " + kPanel + " in customizing window." 201 ); 202 is( 203 otherChildren[otherChildren.length + position].id, 204 id, 205 "Widget " + id + " should be in " + kPanel + " in other window." 206 ); 207 checkWrapper(id); 208 } 209 210 async function checkPalette(id, method) { 211 // Move back to palette: 212 await move[method](id, kVisiblePalette); 213 ok(CustomizableUI.inDefaultState, "Should end in default state"); 214 let visibleChildren = gCustomizeMode.visiblePalette.children; 215 let expectedChild = 216 method == "dragToItem" 217 ? visibleChildren[0] 218 : visibleChildren[visibleChildren.length - 1]; 219 // Items dragged to the end of the palette should be the final item. That they're the penultimate 220 // item when dragged is tracked in bug 1395950. Once that's fixed, this hack can be removed. 221 if (method == "drag") { 222 expectedChild = expectedChild.previousElementSibling; 223 } 224 is( 225 expectedChild.firstElementChild.id, 226 id, 227 "Widget " + 228 id + 229 " was moved using " + 230 method + 231 " and should now be wrapped in palette in customizing window." 232 ); 233 if (id == kXULWidgetId) { 234 ok( 235 otherWin.gNavToolbox.palette.querySelector("#" + id), 236 "Widget " + id + " should be in invisible palette in other window." 237 ); 238 } 239 checkWrapper(id); 240 } 241 242 // This test needs a XUL button that's in the palette by default. No such 243 // button currently exists, so we create a simple one. 244 function createXULButtonForWindow(win) { 245 createDummyXULButton(kXULWidgetId, "test-button", win); 246 } 247 248 function removeXULButtonForWindow(win) { 249 win.gNavToolbox.palette.querySelector(`#${kXULWidgetId}`).remove(); 250 } 251 252 var otherWin; 253 254 // Moving widgets in two windows, one with customize mode and one without, should work. 255 add_task(async function MoveWidgetsInTwoWindows() { 256 CustomizableUI.createWidget({ 257 id: "cui-mode-wrapping-some-panel-item", 258 label: "Test panel wrapping", 259 }); 260 await startCustomizing(); 261 otherWin = await openAndLoadWindow(null, true); 262 await otherWin.PanelUI.ensureReady(); 263 // Create the XUL button to use in the test in both windows. 264 createXULButtonForWindow(window); 265 createXULButtonForWindow(otherWin); 266 ok(CustomizableUI.inDefaultState, "Should start in default state"); 267 268 for (let widgetId of [kXULWidgetId, kAPIWidgetId]) { 269 for (let method of ["API", "drag", "dragToItem"]) { 270 info("Moving widget " + widgetId + " using " + method); 271 await checkToolbar(widgetId, method); 272 // We add an item to the panel because otherwise we can't test dragging 273 // to items that are already there. We remove it because 274 // 'checkPalette' checks that we leave the browser in the default state. 275 CustomizableUI.addWidgetToArea( 276 "cui-mode-wrapping-some-panel-item", 277 CustomizableUI.AREA_FIXED_OVERFLOW_PANEL 278 ); 279 await checkPanel(widgetId, method); 280 CustomizableUI.removeWidgetFromArea("cui-mode-wrapping-some-panel-item"); 281 await checkPalette(widgetId, method); 282 CustomizableUI.addWidgetToArea( 283 "cui-mode-wrapping-some-panel-item", 284 CustomizableUI.AREA_FIXED_OVERFLOW_PANEL 285 ); 286 await checkPanel(widgetId, method); 287 await checkToolbar(widgetId, method); 288 CustomizableUI.removeWidgetFromArea("cui-mode-wrapping-some-panel-item"); 289 await checkPalette(widgetId, method); 290 } 291 } 292 await promiseWindowClosed(otherWin); 293 otherWin = null; 294 await endCustomizing(); 295 removeXULButtonForWindow(window); 296 }); 297 298 add_task(async function asyncCleanup() { 299 CustomizableUI.destroyWidget("cui-mode-wrapping-some-panel-item"); 300 await resetCustomization(); 301 });