1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/toolkit/content/tests/widgets/test_popupanchor.xul Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,410 @@ 1.4 +<?xml version="1.0"?> 1.5 +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> 1.6 +<?xml-stylesheet href="/tests/SimpleTest/test.css" type="text/css"?> 1.7 + 1.8 +<window title="Popup Anchor Tests" 1.9 + xmlns:html="http://www.w3.org/1999/xhtml" 1.10 + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> 1.11 + 1.12 + <panel id="testPanel" 1.13 + type="arrow" 1.14 + animate="false" 1.15 + noautohide="true"> 1.16 + </panel> 1.17 + 1.18 + <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> 1.19 + 1.20 +<script> 1.21 +<![CDATA[ 1.22 +var anchor, panel, arrow; 1.23 + 1.24 +function is_close(got, exp, msg) { 1.25 + // on some platforms we see differences of a fraction of a pixel - so 1.26 + // allow any difference of < 1 pixels as being OK. 1.27 + ok(Math.abs(got - exp) < 1, msg + ": " + got + " should be equal(-ish) to " + exp); 1.28 +} 1.29 + 1.30 +function isArrowPositionedOn(side, offset) { 1.31 + var arrowRect = arrow.getBoundingClientRect(); 1.32 + var arrowMidX = (arrowRect.left + arrowRect.right) / 2; 1.33 + var arrowMidY = (arrowRect.top + arrowRect.bottom) / 2; 1.34 + var panelRect = panel.getBoundingClientRect(); 1.35 + var panelMidX = (panelRect.left + panelRect.right) / 2; 1.36 + var panelMidY = (panelRect.top + panelRect.bottom) / 2; 1.37 + // First check the "flip" of the panel is correct. If we are expecting the 1.38 + // arrow to be pointing to the left side of the anchor, the arrow must 1.39 + // also be on the left side of the panel (and vice-versa) 1.40 + // XXX - on OSX, the arrow seems to always be exactly in the center, hence 1.41 + // the 'equals' sign in the "<=" and ">=" comparisons. NFI why though... 1.42 + switch (side) { 1.43 + case "left": 1.44 + ok(arrowMidX <= panelMidX, "arrow should be on the left of the panel"); 1.45 + break; 1.46 + case "right": 1.47 + ok(arrowMidX >= panelMidX, "arrow should be on the right of the panel"); 1.48 + break; 1.49 + case "top": 1.50 + ok(arrowMidY <= panelMidY, "arrow should be on the top of the panel"); 1.51 + break; 1.52 + case "bottom": 1.53 + ok(arrowMidY >= panelMidY, "arrow should be on the bottom of the panel"); 1.54 + break; 1.55 + default: 1.56 + ok(false, "invalid position " + where); 1.57 + break; 1.58 + } 1.59 + // Now check the arrow really is pointing where we expect. The middle of 1.60 + // the arrow should be pointing exactly to the left (or right) side of the 1.61 + // anchor rect, +- any offsets. 1.62 + if (offset === null) // special case - explicit 'null' means 'don't check offset' 1.63 + return; 1.64 + offset = offset || 0; // no param means no offset expected. 1.65 + var anchorRect = anchor.getBoundingClientRect(); 1.66 + var anchorPos = anchorRect[side]; 1.67 + switch (side) { 1.68 + case "left": 1.69 + case "right": 1.70 + is_close(arrowMidX - anchorPos, offset, "arrow should be " + offset + "px from " + side + " side of anchor"); 1.71 + is_close(panelRect.top, anchorRect.bottom, "top of panel should be at bottom of anchor"); 1.72 + break; 1.73 + case "top": 1.74 + case "bottom": 1.75 + is_close(arrowMidY - anchorPos, offset, "arrow should be " + offset + "px from " + side + " side of anchor"); 1.76 + is_close(panelRect.right, anchorRect.left, "right of panel should be left of anchor"); 1.77 + break; 1.78 + default: 1.79 + ok(false, "unknown side " + side); 1.80 + break; 1.81 + } 1.82 +} 1.83 + 1.84 +function openSlidingPopup(position, callback) { 1.85 + panel.setAttribute("flip", "slide"); 1.86 + _openPopup(position, callback); 1.87 +} 1.88 + 1.89 +function openPopup(position, callback) { 1.90 + panel.setAttribute("flip", "both"); 1.91 + _openPopup(position, callback); 1.92 +} 1.93 + 1.94 +function _openPopup(position, callback) { 1.95 + // this is very ugly: the panel CSS sets the arrow's list-style-image based 1.96 + // on the 'side' attribute. If the setting of the 'side' attribute causes 1.97 + // the image to change, we may get the popupshown event before the new 1.98 + // image has loaded - which causes the size of the arrow to be incorrect 1.99 + // for a brief moment - right when we are measuring it! 1.100 + // So we work around this in 2 steps: 1.101 + // * Force the 'side' attribute to a value which causes the CSS to not 1.102 + // specify an image - then when the popup gets shown, the correct image 1.103 + // is set, causing a load() event on the image element. 1.104 + // * Listen to *both* popupshown and the image load event. When both have 1.105 + // fired (the order is indeterminate) we start the test. 1.106 + panel.setAttribute("side", "noside"); 1.107 + var numEvents = 0; 1.108 + function onEvent() { 1.109 + if (++numEvents == 2) // after both panel 'popupshown' and image 'load' 1.110 + callback(); 1.111 + }; 1.112 + panel.addEventListener("popupshown", function popupshown() { 1.113 + panel.removeEventListener("popupshown", popupshown); 1.114 + onEvent(); 1.115 + }); 1.116 + arrow.addEventListener("load", function imageload() { 1.117 + arrow.removeEventListener("load", imageload); 1.118 + onEvent(); 1.119 + }); 1.120 + panel.openPopup(anchor, position); 1.121 +} 1.122 + 1.123 +var tests = [ 1.124 + // A panel with the anchor after_end - the anchor should not move on resize 1.125 + ['simpleResizeHorizontal', 'middle', function(next) { 1.126 + openPopup("after_end", function() { 1.127 + isArrowPositionedOn("right"); 1.128 + var origPanelRect = panel.getBoundingClientRect(); 1.129 + panel.sizeTo(100, 100); 1.130 + isArrowPositionedOn("right"); // should not have flipped, so still "right" 1.131 + panel.sizeTo(origPanelRect.width, origPanelRect.height); 1.132 + isArrowPositionedOn("right"); // should not have flipped, so still "right" 1.133 + next(); 1.134 + }); 1.135 + }], 1.136 + 1.137 + ['simpleResizeVertical', 'middle', function(next) { 1.138 + openPopup("start_after", function() { 1.139 + isArrowPositionedOn("bottom"); 1.140 + var origPanelRect = panel.getBoundingClientRect(); 1.141 + panel.sizeTo(100, 100); 1.142 + isArrowPositionedOn("bottom"); // should not have flipped 1.143 + panel.sizeTo(origPanelRect.width, origPanelRect.height); 1.144 + isArrowPositionedOn("bottom"); // should not have flipped 1.145 + next(); 1.146 + }); 1.147 + }], 1.148 + 1.149 + ['flippingResizeHorizontal', 'middle', function(next) { 1.150 + openPopup("after_end", function() { 1.151 + isArrowPositionedOn("right"); 1.152 + panel.sizeTo(anchor.getBoundingClientRect().left + 50, 50); 1.153 + isArrowPositionedOn("left"); // check it flipped and has zero offset. 1.154 + next(); 1.155 + }); 1.156 + }], 1.157 + 1.158 + ['flippingResizeVertical', 'middle', function(next) { 1.159 + openPopup("start_after", function() { 1.160 + isArrowPositionedOn("bottom"); 1.161 + panel.sizeTo(50, anchor.getBoundingClientRect().top + 50); 1.162 + isArrowPositionedOn("top"); // check it flipped and has zero offset. 1.163 + next(); 1.164 + }); 1.165 + }], 1.166 + 1.167 + ['simpleMoveToAnchorHorizontal', 'middle', function(next) { 1.168 + openPopup("after_end", function() { 1.169 + isArrowPositionedOn("right"); 1.170 + panel.moveToAnchor(anchor, "after_end", 20, 0); 1.171 + // the anchor and the panel should have moved 20px right without flipping. 1.172 + isArrowPositionedOn("right", 20); 1.173 + panel.moveToAnchor(anchor, "after_end", -20, 0); 1.174 + // the anchor and the panel should have moved 20px left without flipping. 1.175 + isArrowPositionedOn("right", -20); 1.176 + next(); 1.177 + }); 1.178 + }], 1.179 + 1.180 + ['simpleMoveToAnchorVertical', 'middle', function(next) { 1.181 + openPopup("start_after", function() { 1.182 + isArrowPositionedOn("bottom"); 1.183 + panel.moveToAnchor(anchor, "start_after", 0, 20); 1.184 + // the anchor and the panel should have moved 20px down without flipping. 1.185 + isArrowPositionedOn("bottom", 20); 1.186 + panel.moveToAnchor(anchor, "start_after", 0, -20); 1.187 + // the anchor and the panel should have moved 20px up without flipping. 1.188 + isArrowPositionedOn("bottom", -20); 1.189 + next(); 1.190 + }); 1.191 + }], 1.192 + 1.193 + // Do a moveToAnchor that causes the panel to flip horizontally 1.194 + ['flippingMoveToAnchorHorizontal', 'middle', function(next) { 1.195 + var anchorRight = anchor.getBoundingClientRect().right; 1.196 + // Size the panel such that it only just fits from the left-hand side of 1.197 + // the window to the right of the anchor - thus, it will fit when 1.198 + // anchored to the right-hand side of the anchor. 1.199 + panel.sizeTo(anchorRight - 10, 100); 1.200 + openPopup("after_end", function() { 1.201 + isArrowPositionedOn("right"); 1.202 + // Ask for it to be anchored 1/2 way between the left edge of the window 1.203 + // and the anchor right - it can't fit with the panel on the left/arrow 1.204 + // on the right, so it must flip (arrow on the left, panel on the right) 1.205 + var offset = Math.floor(-anchorRight / 2); 1.206 + panel.moveToAnchor(anchor, "after_end", offset, 0); 1.207 + isArrowPositionedOn("left", offset); // should have flipped and have the offset. 1.208 + // resize back to original and move to a zero offset - it should flip back. 1.209 + panel.sizeTo(anchorRight - 10, 100); 1.210 + panel.moveToAnchor(anchor, "after_end", 0, 0); 1.211 + isArrowPositionedOn("right"); // should have flipped back and no offset 1.212 + next(); 1.213 + }); 1.214 + }], 1.215 + 1.216 + // Do a moveToAnchor that causes the panel to flip vertically 1.217 + ['flippingMoveToAnchorVertical', 'middle', function(next) { 1.218 + var anchorBottom = anchor.getBoundingClientRect().bottom; 1.219 + // See comments above in flippingMoveToAnchorHorizontal, but read 1.220 + // "top/bottom" instead of "left/right" 1.221 + panel.sizeTo(100, anchorBottom - 10); 1.222 + openPopup("start_after", function() { 1.223 + isArrowPositionedOn("bottom"); 1.224 + var offset = Math.floor(-anchorBottom / 2); 1.225 + panel.moveToAnchor(anchor, "start_after", 0, offset); 1.226 + isArrowPositionedOn("top", offset); 1.227 + panel.sizeTo(100, anchorBottom - 10); 1.228 + panel.moveToAnchor(anchor, "start_after", 0, 0); 1.229 + isArrowPositionedOn("bottom"); 1.230 + next(); 1.231 + }); 1.232 + }], 1.233 + 1.234 + ['veryWidePanel-after_end', 'middle', function(next) { 1.235 + openSlidingPopup("after_end", function() { 1.236 + var origArrowRect = arrow.getBoundingClientRect(); 1.237 + // Now move it such that the arrow can't be at either end of the panel but 1.238 + // instead somewhere in the middle as that is the only way things fit, 1.239 + // meaning the arrow should "slide" down the panel. 1.240 + panel.sizeTo(window.innerWidth - 10, 60); 1.241 + is(panel.getBoundingClientRect().width, window.innerWidth - 10, "width is what we requested.") 1.242 + // the arrow should not have moved. 1.243 + var curArrowRect = arrow.getBoundingClientRect(); 1.244 + is_close(curArrowRect.left, origArrowRect.left, "arrow should not have moved"); 1.245 + is_close(curArrowRect.top, origArrowRect.top, "arrow should not have moved up or down"); 1.246 + next(); 1.247 + }); 1.248 + }], 1.249 + 1.250 + ['veryWidePanel-before_start', 'middle', function(next) { 1.251 + openSlidingPopup("before_start", function() { 1.252 + var origArrowRect = arrow.getBoundingClientRect(); 1.253 + // Now size it such that the arrow can't be at either end of the panel but 1.254 + // instead somewhere in the middle as that is the only way things fit. 1.255 + panel.sizeTo(window.innerWidth - 10, 60); 1.256 + is(panel.getBoundingClientRect().width, window.innerWidth - 10, "width is what we requested") 1.257 + // the arrow should not have moved. 1.258 + var curArrowRect = arrow.getBoundingClientRect(); 1.259 + is_close(curArrowRect.left, origArrowRect.left, "arrow should not have moved"); 1.260 + is_close(curArrowRect.top, origArrowRect.top, "arrow should not have moved up or down"); 1.261 + next(); 1.262 + }); 1.263 + }], 1.264 + 1.265 + ['veryTallPanel-start_after', 'middle', function(next) { 1.266 + openSlidingPopup("start_after", function() { 1.267 + var origArrowRect = arrow.getBoundingClientRect(); 1.268 + // Now move it such that the arrow can't be at either end of the panel but 1.269 + // instead somewhere in the middle as that is the only way things fit, 1.270 + // meaning the arrow should "slide" down the panel. 1.271 + panel.sizeTo(100, window.innerHeight - 10); 1.272 + is(panel.getBoundingClientRect().height, window.innerHeight - 10, "height is what we requested.") 1.273 + // the arrow should not have moved. 1.274 + var curArrowRect = arrow.getBoundingClientRect(); 1.275 + is_close(curArrowRect.left, origArrowRect.left, "arrow should not have moved"); 1.276 + is_close(curArrowRect.top, origArrowRect.top, "arrow should not have moved up or down"); 1.277 + next(); 1.278 + }); 1.279 + }], 1.280 + 1.281 + ['veryTallPanel-start_before', 'middle', function(next) { 1.282 + openSlidingPopup("start_before", function() { 1.283 + var origArrowRect = arrow.getBoundingClientRect(); 1.284 + // Now size it such that the arrow can't be at either end of the panel but 1.285 + // instead somewhere in the middle as that is the only way things fit. 1.286 + panel.sizeTo(100, window.innerHeight - 10); 1.287 + is(panel.getBoundingClientRect().height, window.innerHeight - 10, "height is what we requested") 1.288 + // the arrow should not have moved. 1.289 + var curArrowRect = arrow.getBoundingClientRect(); 1.290 + is_close(curArrowRect.left, origArrowRect.left, "arrow should not have moved"); 1.291 + is_close(curArrowRect.top, origArrowRect.top, "arrow should not have moved up or down"); 1.292 + next(); 1.293 + }); 1.294 + }], 1.295 + 1.296 + // Tests against the anchor at the right-hand side of the window 1.297 + ['afterend', 'right', function(next) { 1.298 + openPopup("after_end", function() { 1.299 + // when we request too far to the right/bottom, the panel gets shrunk 1.300 + // and moved. The amount it is shrunk by is how far it is moved. 1.301 + var panelRect = panel.getBoundingClientRect(); 1.302 + // panel was requested 100px wide - calc offset based on actual width. 1.303 + var offset = panelRect.width - 100; 1.304 + isArrowPositionedOn("right", offset); 1.305 + next(); 1.306 + }); 1.307 + }], 1.308 + 1.309 + ['after_start', 'right', function(next) { 1.310 + openPopup("after_start", function() { 1.311 + // See above - we are still too far to the right, but the anchor is 1.312 + // on the other side. 1.313 + var panelRect = panel.getBoundingClientRect(); 1.314 + var offset = panelRect.width - 100; 1.315 + isArrowPositionedOn("right", offset); 1.316 + next(); 1.317 + }); 1.318 + }], 1.319 + 1.320 + // Tests against the anchor at the left-hand side of the window 1.321 + ['after_start', 'left', function(next) { 1.322 + openPopup("after_start", function() { 1.323 + var panelRect = panel.getBoundingClientRect(); 1.324 + is(panelRect.left, 0, "panel remains within the screen"); 1.325 + // not sure how to determine the offset here, so given we have checked 1.326 + // the panel is as left as possible while still being inside the window, 1.327 + // we just don't check the offset. 1.328 + isArrowPositionedOn("left", null); 1.329 + next(); 1.330 + }); 1.331 + }], 1.332 +] 1.333 + 1.334 +function runTests() { 1.335 + var testIter = Iterator(tests); 1.336 + function runNextTest() { 1.337 + let name, anchorPos, test; 1.338 + try { 1.339 + let index; 1.340 + [index, [name, anchorPos, test]] = testIter.next(); 1.341 + } catch (err if err instanceof StopIteration) { 1.342 + // out of tests 1.343 + panel.hidePopup(); 1.344 + SimpleTest.finish(); 1.345 + return; 1.346 + } 1.347 + SimpleTest.info("sub-test " + anchorPos + "." + name + " starting"); 1.348 + // first arrange for the anchor to be where the test requires it. 1.349 + panel.hidePopup(); 1.350 + panel.sizeTo(100, 50); 1.351 + // hide all the anchors here, then later we make one of them visible. 1.352 + document.getElementById("anchor-left-wrapper").style.display = "none"; 1.353 + document.getElementById("anchor-middle-wrapper").style.display = "none"; 1.354 + document.getElementById("anchor-right-wrapper").style.display = "none"; 1.355 + switch(anchorPos) { 1.356 + case 'middle': 1.357 + anchor = document.getElementById("anchor-middle"); 1.358 + document.getElementById("anchor-middle-wrapper").style.display = "block"; 1.359 + break; 1.360 + case 'left': 1.361 + anchor = document.getElementById("anchor-left"); 1.362 + document.getElementById("anchor-left-wrapper").style.display = "block"; 1.363 + break; 1.364 + case 'right': 1.365 + anchor = document.getElementById("anchor-right"); 1.366 + document.getElementById("anchor-right-wrapper").style.display = "block"; 1.367 + break; 1.368 + default: 1.369 + SimpleTest.ok(false, "Bad anchorPos: " + anchorPos); 1.370 + runNextTest(); 1.371 + return; 1.372 + } 1.373 + try { 1.374 + test(runNextTest); 1.375 + } catch (ex) { 1.376 + SimpleTest.ok(false, "sub-test " + anchorPos + "." + name + " failed: " + ex.toString() + "\n" + ex.stack); 1.377 + runNextTest(); 1.378 + } 1.379 + } 1.380 + runNextTest(); 1.381 +} 1.382 + 1.383 +SimpleTest.waitForExplicitFinish(); 1.384 + 1.385 +addEventListener("load", function() { 1.386 + // anchor is set by the test runner above 1.387 + panel = document.getElementById("testPanel"); 1.388 + arrow = SpecialPowers.wrap(document).getAnonymousElementByAttribute(panel, "anonid", "arrow"); 1.389 + // Cancel the arrow panel slide-in transition (bug 767133) so the size and 1.390 + // position are "stable" enough to test without jumping through hoops... 1.391 + arrow.style.transition = "none"; 1.392 + runTests(); 1.393 +}); 1.394 + 1.395 +]]> 1.396 +</script> 1.397 + 1.398 +<body xmlns="http://www.w3.org/1999/xhtml"> 1.399 +<!-- Our tests assume at least 100px around the anchor on all sides, else the 1.400 + panel may flip when we don't expect it to 1.401 +--> 1.402 +<div id="anchor-middle-wrapper" style="margin: 100px 100px 100px 100px;"> 1.403 + <p>The anchor --> <span id="anchor-middle">v</span> <--</p> 1.404 +</div> 1.405 +<div id="anchor-left-wrapper" style="text-align: left; display: none;"> 1.406 + <p><span id="anchor-left">v</span> <-- The anchor;</p> 1.407 +</div> 1.408 +<div id="anchor-right-wrapper" style="text-align: right; display: none;"> 1.409 + <p>The anchor --> <span id="anchor-right">v</span></p> 1.410 +</div> 1.411 +</body> 1.412 + 1.413 +</window>