1<?xml version="1.0"?> 2<?xml-stylesheet href="chrome://global/skin" type="text/css"?> 3<?xml-stylesheet href="/tests/SimpleTest/test.css" type="text/css"?> 4 5<window title="Popup Anchor Tests" 6 xmlns:html="http://www.w3.org/1999/xhtml" 7 xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> 8 9 <panel id="testPanel" 10 type="arrow" 11 animate="false" 12 noautohide="true"> 13 </panel> 14 15 <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> 16 17<script> 18<![CDATA[ 19var anchor, panel, arrow; 20 21function is_close(got, exp, msg) { 22 // on some platforms we see differences of a fraction of a pixel - so 23 // allow any difference of < 1 pixels as being OK. 24 ok(Math.abs(got - exp) < 1, msg + ": " + got + " should be equal(-ish) to " + exp); 25} 26 27function isArrowPositionedOn(side, offset) { 28 var arrowRect = arrow.getBoundingClientRect(); 29 var arrowMidX = (arrowRect.left + arrowRect.right) / 2; 30 var arrowMidY = (arrowRect.top + arrowRect.bottom) / 2; 31 var panelRect = panel.getBoundingClientRect(); 32 var panelMidX = (panelRect.left + panelRect.right) / 2; 33 var panelMidY = (panelRect.top + panelRect.bottom) / 2; 34 // First check the "flip" of the panel is correct. If we are expecting the 35 // arrow to be pointing to the left side of the anchor, the arrow must 36 // also be on the left side of the panel (and vice-versa) 37 // XXX - on OSX, the arrow seems to always be exactly in the center, hence 38 // the 'equals' sign in the "<=" and ">=" comparisons. NFI why though... 39 switch (side) { 40 case "left": 41 ok(arrowMidX <= panelMidX, "arrow should be on the left of the panel"); 42 break; 43 case "right": 44 ok(arrowMidX >= panelMidX, "arrow should be on the right of the panel"); 45 break; 46 case "top": 47 ok(arrowMidY <= panelMidY, "arrow should be on the top of the panel"); 48 break; 49 case "bottom": 50 ok(arrowMidY >= panelMidY, "arrow should be on the bottom of the panel"); 51 break; 52 default: 53 ok(false, "invalid position " + where); 54 break; 55 } 56 // Now check the arrow really is pointing where we expect. The middle of 57 // the arrow should be pointing exactly to the left (or right) side of the 58 // anchor rect, +- any offsets. 59 if (offset === null) // special case - explicit 'null' means 'don't check offset' 60 return; 61 offset = offset || 0; // no param means no offset expected. 62 var anchorRect = anchor.getBoundingClientRect(); 63 var anchorPos = anchorRect[side]; 64 switch (side) { 65 case "left": 66 case "right": 67 is_close(arrowMidX - anchorPos, offset, "arrow should be " + offset + "px from " + side + " side of anchor"); 68 is_close(panelRect.top, anchorRect.bottom, "top of panel should be at bottom of anchor"); 69 break; 70 case "top": 71 case "bottom": 72 is_close(arrowMidY - anchorPos, offset, "arrow should be " + offset + "px from " + side + " side of anchor"); 73 is_close(panelRect.right, anchorRect.left, "right of panel should be left of anchor"); 74 break; 75 default: 76 ok(false, "unknown side " + side); 77 break; 78 } 79} 80 81function openSlidingPopup(position, callback) { 82 panel.setAttribute("flip", "slide"); 83 _openPopup(position, callback); 84} 85 86function openPopup(position, callback) { 87 panel.setAttribute("flip", "both"); 88 _openPopup(position, callback); 89} 90 91function waitForPopupPositioned(actionFn, callback) 92{ 93 panel.addEventListener("popuppositioned", function listener() { 94 panel.removeEventListener("popuppositioned", listener, false); 95 callback(); 96 }, false); 97 actionFn(); 98} 99 100function _openPopup(position, callback) { 101 // this is very ugly: the panel CSS sets the arrow's list-style-image based 102 // on the 'side' attribute. If the setting of the 'side' attribute causes 103 // the image to change, we may get the popupshown event before the new 104 // image has loaded - which causes the size of the arrow to be incorrect 105 // for a brief moment - right when we are measuring it! 106 // So we work around this in 2 steps: 107 // * Force the 'side' attribute to a value which causes the CSS to not 108 // specify an image - then when the popup gets shown, the correct image 109 // is set, causing a load() event on the image element. 110 // * Listen to *both* popupshown and the image load event. When both have 111 // fired (the order is indeterminate) we start the test. 112 panel.setAttribute("side", "noside"); 113 var numEvents = 0; 114 function onEvent() { 115 if (++numEvents == 2) // after both panel 'popupshown' and image 'load' 116 callback(); 117 }; 118 panel.addEventListener("popupshown", function popupshown() { 119 panel.removeEventListener("popupshown", popupshown); 120 onEvent(); 121 }); 122 arrow.addEventListener("load", function imageload() { 123 arrow.removeEventListener("load", imageload); 124 onEvent(); 125 }); 126 panel.openPopup(anchor, position); 127} 128 129var tests = [ 130 // A panel with the anchor after_end - the anchor should not move on resize 131 ['simpleResizeHorizontal', 'middle', function(next) { 132 openPopup("after_end", function() { 133 isArrowPositionedOn("right"); 134 var origPanelRect = panel.getBoundingClientRect(); 135 panel.sizeTo(100, 100); 136 isArrowPositionedOn("right"); // should not have flipped, so still "right" 137 panel.sizeTo(origPanelRect.width, origPanelRect.height); 138 isArrowPositionedOn("right"); // should not have flipped, so still "right" 139 next(); 140 }); 141 }], 142 143 ['simpleResizeVertical', 'middle', function(next) { 144 openPopup("start_after", function() { 145 isArrowPositionedOn("bottom"); 146 var origPanelRect = panel.getBoundingClientRect(); 147 panel.sizeTo(100, 100); 148 isArrowPositionedOn("bottom"); // should not have flipped 149 panel.sizeTo(origPanelRect.width, origPanelRect.height); 150 isArrowPositionedOn("bottom"); // should not have flipped 151 next(); 152 }); 153 }], 154 155 ['flippingResizeHorizontal', 'middle', function(next) { 156 openPopup("after_end", function() { 157 isArrowPositionedOn("right"); 158 panel.sizeTo(anchor.getBoundingClientRect().left + 50, 50); 159 isArrowPositionedOn("left"); // check it flipped and has zero offset. 160 next(); 161 }); 162 }], 163 164 ['flippingResizeVertical', 'middle', function(next) { 165 openPopup("start_after", function() { 166 isArrowPositionedOn("bottom"); 167 panel.sizeTo(50, anchor.getBoundingClientRect().top + 50); 168 isArrowPositionedOn("top"); // check it flipped and has zero offset. 169 next(); 170 }); 171 }], 172 173 ['simpleMoveToAnchorHorizontal', 'middle', function(next) { 174 openPopup("after_end", function() { 175 isArrowPositionedOn("right"); 176 panel.moveToAnchor(anchor, "after_end", 20, 0); 177 // the anchor and the panel should have moved 20px right without flipping. 178 isArrowPositionedOn("right", 20); 179 panel.moveToAnchor(anchor, "after_end", -20, 0); 180 // the anchor and the panel should have moved 20px left without flipping. 181 isArrowPositionedOn("right", -20); 182 next(); 183 }); 184 }], 185 186 ['simpleMoveToAnchorVertical', 'middle', function(next) { 187 openPopup("start_after", function() { 188 isArrowPositionedOn("bottom"); 189 panel.moveToAnchor(anchor, "start_after", 0, 20); 190 // the anchor and the panel should have moved 20px down without flipping. 191 isArrowPositionedOn("bottom", 20); 192 panel.moveToAnchor(anchor, "start_after", 0, -20); 193 // the anchor and the panel should have moved 20px up without flipping. 194 isArrowPositionedOn("bottom", -20); 195 next(); 196 }); 197 }], 198 199 // Do a moveToAnchor that causes the panel to flip horizontally 200 ['flippingMoveToAnchorHorizontal', 'middle', function(next) { 201 var anchorRight = anchor.getBoundingClientRect().right; 202 // Size the panel such that it only just fits from the left-hand side of 203 // the window to the right of the anchor - thus, it will fit when 204 // anchored to the right-hand side of the anchor. 205 panel.sizeTo(anchorRight - 10, 100); 206 openPopup("after_end", function() { 207 isArrowPositionedOn("right"); 208 // Ask for it to be anchored 1/2 way between the left edge of the window 209 // and the anchor right - it can't fit with the panel on the left/arrow 210 // on the right, so it must flip (arrow on the left, panel on the right) 211 var offset = Math.floor(-anchorRight / 2); 212 213 waitForPopupPositioned( 214 () => panel.moveToAnchor(anchor, "after_end", offset, 0), 215 () => { 216 isArrowPositionedOn("left", offset); // should have flipped and have the offset. 217 // resize back to original and move to a zero offset - it should flip back. 218 219 panel.sizeTo(anchorRight - 10, 100); 220 waitForPopupPositioned( 221 () => panel.moveToAnchor(anchor, "after_end", 0, 0), 222 () => { 223 isArrowPositionedOn("right"); // should have flipped back and no offset 224 next(); 225 }); 226 }); 227 }); 228 }], 229 230 // Do a moveToAnchor that causes the panel to flip vertically 231 ['flippingMoveToAnchorVertical', 'middle', function(next) { 232 var anchorBottom = anchor.getBoundingClientRect().bottom; 233 // See comments above in flippingMoveToAnchorHorizontal, but read 234 // "top/bottom" instead of "left/right" 235 panel.sizeTo(100, anchorBottom - 10); 236 openPopup("start_after", function() { 237 isArrowPositionedOn("bottom"); 238 var offset = Math.floor(-anchorBottom / 2); 239 240 waitForPopupPositioned( 241 () => panel.moveToAnchor(anchor, "start_after", 0, offset), 242 () => { 243 isArrowPositionedOn("top", offset); 244 panel.sizeTo(100, anchorBottom - 10); 245 246 waitForPopupPositioned( 247 () => panel.moveToAnchor(anchor, "start_after", 0, 0), 248 () => { 249 isArrowPositionedOn("bottom"); 250 next(); 251 }); 252 }); 253 }); 254 }], 255 256 ['veryWidePanel-after_end', 'middle', function(next) { 257 openSlidingPopup("after_end", function() { 258 var origArrowRect = arrow.getBoundingClientRect(); 259 // Now move it such that the arrow can't be at either end of the panel but 260 // instead somewhere in the middle as that is the only way things fit, 261 // meaning the arrow should "slide" down the panel. 262 panel.sizeTo(window.innerWidth - 10, 60); 263 is(panel.getBoundingClientRect().width, window.innerWidth - 10, "width is what we requested.") 264 // the arrow should not have moved. 265 var curArrowRect = arrow.getBoundingClientRect(); 266 is_close(curArrowRect.left, origArrowRect.left, "arrow should not have moved"); 267 is_close(curArrowRect.top, origArrowRect.top, "arrow should not have moved up or down"); 268 next(); 269 }); 270 }], 271 272 ['veryWidePanel-before_start', 'middle', function(next) { 273 openSlidingPopup("before_start", function() { 274 var origArrowRect = arrow.getBoundingClientRect(); 275 // Now size it such that the arrow can't be at either end of the panel but 276 // instead somewhere in the middle as that is the only way things fit. 277 panel.sizeTo(window.innerWidth - 10, 60); 278 is(panel.getBoundingClientRect().width, window.innerWidth - 10, "width is what we requested") 279 // the arrow should not have moved. 280 var curArrowRect = arrow.getBoundingClientRect(); 281 is_close(curArrowRect.left, origArrowRect.left, "arrow should not have moved"); 282 is_close(curArrowRect.top, origArrowRect.top, "arrow should not have moved up or down"); 283 next(); 284 }); 285 }], 286 287 ['veryTallPanel-start_after', 'middle', function(next) { 288 openSlidingPopup("start_after", function() { 289 var origArrowRect = arrow.getBoundingClientRect(); 290 // Now move it such that the arrow can't be at either end of the panel but 291 // instead somewhere in the middle as that is the only way things fit, 292 // meaning the arrow should "slide" down the panel. 293 panel.sizeTo(100, window.innerHeight - 10); 294 is(panel.getBoundingClientRect().height, window.innerHeight - 10, "height is what we requested.") 295 // the arrow should not have moved. 296 var curArrowRect = arrow.getBoundingClientRect(); 297 is_close(curArrowRect.left, origArrowRect.left, "arrow should not have moved"); 298 is_close(curArrowRect.top, origArrowRect.top, "arrow should not have moved up or down"); 299 next(); 300 }); 301 }], 302 303 ['veryTallPanel-start_before', 'middle', function(next) { 304 openSlidingPopup("start_before", function() { 305 var origArrowRect = arrow.getBoundingClientRect(); 306 // Now size it such that the arrow can't be at either end of the panel but 307 // instead somewhere in the middle as that is the only way things fit. 308 panel.sizeTo(100, window.innerHeight - 10); 309 is(panel.getBoundingClientRect().height, window.innerHeight - 10, "height is what we requested") 310 // the arrow should not have moved. 311 var curArrowRect = arrow.getBoundingClientRect(); 312 is_close(curArrowRect.left, origArrowRect.left, "arrow should not have moved"); 313 is_close(curArrowRect.top, origArrowRect.top, "arrow should not have moved up or down"); 314 next(); 315 }); 316 }], 317 318 // Tests against the anchor at the right-hand side of the window 319 ['afterend', 'right', function(next) { 320 openPopup("after_end", function() { 321 // when we request too far to the right/bottom, the panel gets shrunk 322 // and moved. The amount it is shrunk by is how far it is moved. 323 var panelRect = panel.getBoundingClientRect(); 324 // panel was requested 100px wide - calc offset based on actual width. 325 var offset = panelRect.width - 100; 326 isArrowPositionedOn("right", offset); 327 next(); 328 }); 329 }], 330 331 ['after_start', 'right', function(next) { 332 openPopup("after_start", function() { 333 // See above - we are still too far to the right, but the anchor is 334 // on the other side. 335 var panelRect = panel.getBoundingClientRect(); 336 var offset = panelRect.width - 100; 337 isArrowPositionedOn("right", offset); 338 next(); 339 }); 340 }], 341 342 // Tests against the anchor at the left-hand side of the window 343 ['after_start', 'left', function(next) { 344 openPopup("after_start", function() { 345 var panelRect = panel.getBoundingClientRect(); 346 is(panelRect.left, 0, "panel remains within the screen"); 347 // not sure how to determine the offset here, so given we have checked 348 // the panel is as left as possible while still being inside the window, 349 // we just don't check the offset. 350 isArrowPositionedOn("left", null); 351 next(); 352 }); 353 }], 354] 355 356function runTests() { 357 function runNextTest() { 358 let result = tests.shift(); 359 if (!result) { 360 // out of tests 361 panel.hidePopup(); 362 SimpleTest.finish(); 363 return; 364 } 365 let [name, anchorPos, test] = result; 366 SimpleTest.info("sub-test " + anchorPos + "." + name + " starting"); 367 // first arrange for the anchor to be where the test requires it. 368 panel.hidePopup(); 369 panel.sizeTo(100, 50); 370 // hide all the anchors here, then later we make one of them visible. 371 document.getElementById("anchor-left-wrapper").style.display = "none"; 372 document.getElementById("anchor-middle-wrapper").style.display = "none"; 373 document.getElementById("anchor-right-wrapper").style.display = "none"; 374 switch(anchorPos) { 375 case 'middle': 376 anchor = document.getElementById("anchor-middle"); 377 document.getElementById("anchor-middle-wrapper").style.display = "block"; 378 break; 379 case 'left': 380 anchor = document.getElementById("anchor-left"); 381 document.getElementById("anchor-left-wrapper").style.display = "block"; 382 break; 383 case 'right': 384 anchor = document.getElementById("anchor-right"); 385 document.getElementById("anchor-right-wrapper").style.display = "block"; 386 break; 387 default: 388 SimpleTest.ok(false, "Bad anchorPos: " + anchorPos); 389 runNextTest(); 390 return; 391 } 392 try { 393 test(runNextTest); 394 } catch (ex) { 395 SimpleTest.ok(false, "sub-test " + anchorPos + "." + name + " failed: " + ex.toString() + "\n" + ex.stack); 396 runNextTest(); 397 } 398 } 399 runNextTest(); 400} 401 402SimpleTest.waitForExplicitFinish(); 403 404addEventListener("load", function() { 405 // anchor is set by the test runner above 406 panel = document.getElementById("testPanel"); 407 408 arrow = SpecialPowers.wrap(document).getAnonymousElementByAttribute(panel, "anonid", "arrow"); 409 runTests(); 410}); 411 412]]> 413</script> 414 415<body xmlns="http://www.w3.org/1999/xhtml"> 416<!-- Our tests assume at least 100px around the anchor on all sides, else the 417 panel may flip when we don't expect it to 418--> 419<div id="anchor-middle-wrapper" style="margin: 100px 100px 100px 100px;"> 420 <p>The anchor --> <span id="anchor-middle">v</span> <--</p> 421</div> 422<div id="anchor-left-wrapper" style="text-align: left; display: none;"> 423 <p><span id="anchor-left">v</span> <-- The anchor;</p> 424</div> 425<div id="anchor-right-wrapper" style="text-align: right; display: none;"> 426 <p>The anchor --> <span id="anchor-right">v</span></p> 427</div> 428</body> 429 430</window> 431