1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/toolkit/content/tests/widgets/popup_shared.js Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,424 @@ 1.4 +/* 1.5 + * This script is used for menu and popup tests. Call startPopupTests to start 1.6 + * the tests, passing an array of tests as an argument. Each test is an object 1.7 + * with the following properties: 1.8 + * testname - name of the test 1.9 + * test - function to call to perform the test 1.10 + * events - a list of events that are expected to be fired in sequence 1.11 + * as a result of calling the 'test' function. This list should be 1.12 + * an array of strings of the form "eventtype targetid" where 1.13 + * 'eventtype' is the event type and 'targetid' is the id of 1.14 + * target of the event. This function will be passed two 1.15 + * arguments, the testname and the step argument. 1.16 + * Alternatively, events may be a function which returns the array 1.17 + * of events. This can be used when the events vary per platform. 1.18 + * result - function to call after all the events have fired to check 1.19 + * for additional results. May be null. This function will be 1.20 + * passed two arguments, the testname and the step argument. 1.21 + * steps - optional array of values. The test will be repeated for 1.22 + * each step, passing each successive value within the array to 1.23 + * the test and result functions 1.24 + * autohide - if set, should be set to the id of a popup to hide after 1.25 + * the test is complete. This is a convenience for some tests. 1.26 + * condition - an optional function which, if it returns false, causes the 1.27 + * test to be skipped. 1.28 + * end - used for debugging. Set to true to stop the tests after running 1.29 + * this one. 1.30 + */ 1.31 + 1.32 +const menuactiveAttribute = "_moz-menuactive"; 1.33 + 1.34 +var gPopupTests = null; 1.35 +var gTestIndex = -1; 1.36 +var gTestStepIndex = 0; 1.37 +var gTestEventIndex = 0; 1.38 +var gAutoHide = false; 1.39 +var gExpectedEventDetails = null; 1.40 +var gExpectedTriggerNode = null; 1.41 +var gWindowUtils; 1.42 +var gPopupWidth = -1, gPopupHeight = -1; 1.43 + 1.44 +function startPopupTests(tests) 1.45 +{ 1.46 + document.addEventListener("popupshowing", eventOccurred, false); 1.47 + document.addEventListener("popupshown", eventOccurred, false); 1.48 + document.addEventListener("popuphiding", eventOccurred, false); 1.49 + document.addEventListener("popuphidden", eventOccurred, false); 1.50 + document.addEventListener("command", eventOccurred, false); 1.51 + document.addEventListener("DOMMenuItemActive", eventOccurred, false); 1.52 + document.addEventListener("DOMMenuItemInactive", eventOccurred, false); 1.53 + document.addEventListener("DOMMenuInactive", eventOccurred, false); 1.54 + document.addEventListener("DOMMenuBarActive", eventOccurred, false); 1.55 + document.addEventListener("DOMMenuBarInactive", eventOccurred, false); 1.56 + 1.57 + gPopupTests = tests; 1.58 + gWindowUtils = SpecialPowers.getDOMWindowUtils(window); 1.59 + 1.60 + goNext(); 1.61 +} 1.62 + 1.63 +function finish() 1.64 +{ 1.65 + if (window.opener) { 1.66 + window.close(); 1.67 + window.opener.SimpleTest.finish(); 1.68 + return; 1.69 + } 1.70 + SimpleTest.finish(); 1.71 + return; 1.72 +} 1.73 + 1.74 +function ok(condition, message) { 1.75 + if (window.opener) 1.76 + window.opener.SimpleTest.ok(condition, message); 1.77 + else 1.78 + SimpleTest.ok(condition, message); 1.79 +} 1.80 + 1.81 +function is(left, right, message) { 1.82 + if (window.opener) 1.83 + window.opener.SimpleTest.is(left, right, message); 1.84 + else 1.85 + SimpleTest.is(left, right, message); 1.86 +} 1.87 + 1.88 +function disableNonTestMouse(aDisable) { 1.89 + gWindowUtils.disableNonTestMouseEvents(aDisable); 1.90 +} 1.91 + 1.92 +function eventOccurred(event) 1.93 +{ 1.94 + if (gPopupTests.length <= gTestIndex) { 1.95 + ok(false, "Extra " + event.type + " event fired"); 1.96 + return; 1.97 + } 1.98 + 1.99 + var test = gPopupTests[gTestIndex]; 1.100 + if ("autohide" in test && gAutoHide) { 1.101 + if (event.type == "DOMMenuInactive") { 1.102 + gAutoHide = false; 1.103 + setTimeout(goNextStep, 0); 1.104 + } 1.105 + return; 1.106 + } 1.107 + 1.108 + var events = test.events; 1.109 + if (typeof events == "function") 1.110 + events = events(); 1.111 + if (events) { 1.112 + if (events.length <= gTestEventIndex) { 1.113 + ok(false, "Extra " + event.type + " event fired for " + event.target.id + 1.114 + " " +gPopupTests[gTestIndex].testname); 1.115 + return; 1.116 + } 1.117 + 1.118 + var eventitem = events[gTestEventIndex].split(" "); 1.119 + var matches; 1.120 + if (eventitem[1] == "#tooltip") { 1.121 + is(event.originalTarget.localName, "tooltip", 1.122 + test.testname + " event.originalTarget.localName is 'tooltip'"); 1.123 + is(event.originalTarget.getAttribute("default"), "true", 1.124 + test.testname + " event.originalTarget default attribute is 'true'"); 1.125 + matches = event.originalTarget.localName == "tooltip" && 1.126 + event.originalTarget.getAttribute("default") == "true"; 1.127 + } else { 1.128 + is(event.type, eventitem[0], 1.129 + test.testname + " event type " + event.type + " fired"); 1.130 + is(event.target.id, eventitem[1], 1.131 + test.testname + " event target ID " + event.target.id); 1.132 + matches = eventitem[0] == event.type && eventitem[1] == event.target.id; 1.133 + } 1.134 + 1.135 + var modifiersMask = eventitem[2]; 1.136 + if (modifiersMask) { 1.137 + var m = ""; 1.138 + m += event.altKey ? '1' : '0'; 1.139 + m += event.ctrlKey ? '1' : '0'; 1.140 + m += event.shiftKey ? '1' : '0'; 1.141 + m += event.metaKey ? '1' : '0'; 1.142 + is(m, modifiersMask, test.testname + " modifiers mask matches"); 1.143 + } 1.144 + 1.145 + var expectedState; 1.146 + switch (event.type) { 1.147 + case "popupshowing": expectedState = "showing"; break; 1.148 + case "popupshown": expectedState = "open"; break; 1.149 + case "popuphiding": expectedState = "hiding"; break; 1.150 + case "popuphidden": expectedState = "closed"; break; 1.151 + } 1.152 + 1.153 + if (gExpectedTriggerNode && event.type == "popupshowing") { 1.154 + if (gExpectedTriggerNode == "notset") // check against null instead 1.155 + gExpectedTriggerNode = null; 1.156 + 1.157 + is(event.originalTarget.triggerNode, gExpectedTriggerNode, test.testname + " popupshowing triggerNode"); 1.158 + var isTooltip = (event.target.localName == "tooltip"); 1.159 + is(document.popupNode, isTooltip ? null : gExpectedTriggerNode, 1.160 + test.testname + " popupshowing document.popupNode"); 1.161 + is(document.tooltipNode, isTooltip ? gExpectedTriggerNode : null, 1.162 + test.testname + " popupshowing document.tooltipNode"); 1.163 + } 1.164 + 1.165 + if (expectedState) 1.166 + is(event.originalTarget.state, expectedState, 1.167 + test.testname + " " + event.type + " state"); 1.168 + 1.169 + if (matches) { 1.170 + gTestEventIndex++ 1.171 + if (events.length <= gTestEventIndex) 1.172 + setTimeout(checkResult, 0); 1.173 + } 1.174 + } 1.175 +} 1.176 + 1.177 +function checkResult() 1.178 +{ 1.179 + var step = null; 1.180 + var test = gPopupTests[gTestIndex]; 1.181 + if ("steps" in test) 1.182 + step = test.steps[gTestStepIndex]; 1.183 + 1.184 + if ("result" in test) 1.185 + test.result(test.testname, step); 1.186 + 1.187 + if ("autohide" in test) { 1.188 + gAutoHide = true; 1.189 + document.getElementById(test.autohide).hidePopup(); 1.190 + return; 1.191 + } 1.192 + 1.193 + goNextStep(); 1.194 +} 1.195 + 1.196 +function goNextStep() 1.197 +{ 1.198 + gTestEventIndex = 0; 1.199 + 1.200 + var step = null; 1.201 + var test = gPopupTests[gTestIndex]; 1.202 + if ("steps" in test) { 1.203 + gTestStepIndex++; 1.204 + step = test.steps[gTestStepIndex]; 1.205 + if (gTestStepIndex < test.steps.length) { 1.206 + test.test(test.testname, step); 1.207 + return; 1.208 + } 1.209 + } 1.210 + 1.211 + goNext(); 1.212 +} 1.213 + 1.214 +function goNext() 1.215 +{ 1.216 + // We want to continue after the next animation frame so that 1.217 + // we're in a stable state and don't get spurious mouse events at unexpected targets. 1.218 + window.requestAnimationFrame( 1.219 + function() { 1.220 + setTimeout(goNextStepSync, 0); 1.221 + } 1.222 + ); 1.223 +} 1.224 + 1.225 +function goNextStepSync() 1.226 +{ 1.227 + if (gTestIndex >= 0 && "end" in gPopupTests[gTestIndex] && gPopupTests[gTestIndex].end) { 1.228 + finish(); 1.229 + return; 1.230 + } 1.231 + 1.232 + gTestIndex++; 1.233 + gTestStepIndex = 0; 1.234 + if (gTestIndex < gPopupTests.length) { 1.235 + var test = gPopupTests[gTestIndex]; 1.236 + // Set the location hash so it's easy to see which test is running 1.237 + document.location.hash = test.testname; 1.238 + 1.239 + // skip the test if the condition returns false 1.240 + if ("condition" in test && !test.condition()) { 1.241 + goNext(); 1.242 + return; 1.243 + } 1.244 + 1.245 + // start with the first step if there are any 1.246 + var step = null; 1.247 + if ("steps" in test) 1.248 + step = test.steps[gTestStepIndex]; 1.249 + 1.250 + test.test(test.testname, step); 1.251 + 1.252 + // no events to check for so just check the result 1.253 + if (!("events" in test)) 1.254 + checkResult(); 1.255 + } 1.256 + else { 1.257 + finish(); 1.258 + } 1.259 +} 1.260 + 1.261 +function openMenu(menu) 1.262 +{ 1.263 + if ("open" in menu) { 1.264 + menu.open = true; 1.265 + } 1.266 + else { 1.267 + var bo = menu.boxObject; 1.268 + if (bo instanceof SpecialPowers.Ci.nsIMenuBoxObject) 1.269 + bo.openMenu(true); 1.270 + else 1.271 + synthesizeMouse(menu, 4, 4, { }); 1.272 + } 1.273 +} 1.274 + 1.275 +function closeMenu(menu, popup) 1.276 +{ 1.277 + if ("open" in menu) { 1.278 + menu.open = false; 1.279 + } 1.280 + else { 1.281 + var bo = menu.boxObject; 1.282 + if (bo instanceof SpecialPowers.Ci.nsIMenuBoxObject) 1.283 + bo.openMenu(false); 1.284 + else 1.285 + popup.hidePopup(); 1.286 + } 1.287 +} 1.288 + 1.289 +function checkActive(popup, id, testname) 1.290 +{ 1.291 + var activeok = true; 1.292 + var children = popup.childNodes; 1.293 + for (var c = 0; c < children.length; c++) { 1.294 + var child = children[c]; 1.295 + if ((id == child.id && child.getAttribute(menuactiveAttribute) != "true") || 1.296 + (id != child.id && child.hasAttribute(menuactiveAttribute) != "")) { 1.297 + activeok = false; 1.298 + break; 1.299 + } 1.300 + } 1.301 + ok(activeok, testname + " item " + (id ? id : "none") + " active"); 1.302 +} 1.303 + 1.304 +function checkOpen(menuid, testname) 1.305 +{ 1.306 + var menu = document.getElementById(menuid); 1.307 + if ("open" in menu) 1.308 + ok(menu.open, testname + " " + menuid + " menu is open"); 1.309 + else if (menu.boxObject instanceof SpecialPowers.Ci.nsIMenuBoxObject) 1.310 + ok(menu.getAttribute("open") == "true", testname + " " + menuid + " menu is open"); 1.311 +} 1.312 + 1.313 +function checkClosed(menuid, testname) 1.314 +{ 1.315 + var menu = document.getElementById(menuid); 1.316 + if ("open" in menu) 1.317 + ok(!menu.open, testname + " " + menuid + " menu is open"); 1.318 + else if (menu.boxObject instanceof SpecialPowers.Ci.nsIMenuBoxObject) 1.319 + ok(!menu.hasAttribute("open"), testname + " " + menuid + " menu is closed"); 1.320 +} 1.321 + 1.322 +function convertPosition(anchor, align) 1.323 +{ 1.324 + if (anchor == "topleft" && align == "topleft") return "overlap"; 1.325 + if (anchor == "topleft" && align == "topright") return "start_before"; 1.326 + if (anchor == "topleft" && align == "bottomleft") return "before_start"; 1.327 + if (anchor == "topright" && align == "topleft") return "end_before"; 1.328 + if (anchor == "topright" && align == "bottomright") return "before_end"; 1.329 + if (anchor == "bottomleft" && align == "bottomright") return "start_after"; 1.330 + if (anchor == "bottomleft" && align == "topleft") return "after_start"; 1.331 + if (anchor == "bottomright" && align == "bottomleft") return "end_after"; 1.332 + if (anchor == "bottomright" && align == "topright") return "after_end"; 1.333 + return ""; 1.334 +} 1.335 + 1.336 +/* 1.337 + * When checking position of the bottom or right edge of the popup's rect, 1.338 + * use this instead of strict equality check of rounded values, 1.339 + * because we snap the top/left edges to pixel boundaries, 1.340 + * which can shift the bottom/right up to 0.5px from its "ideal" location, 1.341 + * and could cause it to round differently. (See bug 622507.) 1.342 + */ 1.343 +function isWithinHalfPixel(a, b) 1.344 +{ 1.345 + return Math.abs(a - b) <= 0.5; 1.346 +} 1.347 + 1.348 +function compareEdge(anchor, popup, edge, offsetX, offsetY, testname) 1.349 +{ 1.350 + testname += " " + edge; 1.351 + 1.352 + checkOpen(anchor.id, testname); 1.353 + 1.354 + var anchorrect = anchor.getBoundingClientRect(); 1.355 + var popuprect = popup.getBoundingClientRect(); 1.356 + var check1 = false, check2 = false; 1.357 + 1.358 + if (gPopupWidth == -1) { 1.359 + ok((Math.round(popuprect.right) - Math.round(popuprect.left)) && 1.360 + (Math.round(popuprect.bottom) - Math.round(popuprect.top)), 1.361 + testname + " size"); 1.362 + } 1.363 + else { 1.364 + is(Math.round(popuprect.width), gPopupWidth, testname + " width"); 1.365 + is(Math.round(popuprect.height), gPopupHeight, testname + " height"); 1.366 + } 1.367 + 1.368 + var spaceIdx = edge.indexOf(" "); 1.369 + if (spaceIdx > 0) { 1.370 + let cornerX, cornerY; 1.371 + let [anchor, align] = edge.split(" "); 1.372 + switch (anchor) { 1.373 + case "topleft": cornerX = anchorrect.left; cornerY = anchorrect.top; break; 1.374 + case "topcenter": cornerX = anchorrect.left + anchorrect.width / 2; cornerY = anchorrect.top; break; 1.375 + case "topright": cornerX = anchorrect.right; cornerY = anchorrect.top; break; 1.376 + case "leftcenter": cornerX = anchorrect.left; cornerY = anchorrect.top + anchorrect.height / 2; break; 1.377 + case "rightcenter": cornerX = anchorrect.right; cornerY = anchorrect.top + anchorrect.height / 2; break; 1.378 + case "bottomleft": cornerX = anchorrect.left; cornerY = anchorrect.bottom; break; 1.379 + case "bottomcenter": cornerX = anchorrect.left + anchorrect.width / 2; cornerY = anchorrect.bottom; break; 1.380 + case "bottomright": cornerX = anchorrect.right; cornerY = anchorrect.bottom; break; 1.381 + } 1.382 + 1.383 + switch (align) { 1.384 + case "topleft": cornerX += offsetX; cornerY += offsetY; break; 1.385 + case "topright": cornerX += -popuprect.width + offsetX; cornerY += offsetY; break; 1.386 + case "bottomleft": cornerX += offsetX; cornerY += -popuprect.height + offsetY; break; 1.387 + case "bottomright": cornerX += -popuprect.width + offsetX; cornerY += -popuprect.height + offsetY; break; 1.388 + } 1.389 + 1.390 + is(Math.round(popuprect.left), Math.round(cornerX), testname + " x position"); 1.391 + is(Math.round(popuprect.top), Math.round(cornerY), testname + " y position"); 1.392 + return; 1.393 + } 1.394 + 1.395 + if (edge == "after_pointer") { 1.396 + is(Math.round(popuprect.left), Math.round(anchorrect.left) + offsetX, testname + " x position"); 1.397 + is(Math.round(popuprect.top), Math.round(anchorrect.top) + offsetY + 21, testname + " y position"); 1.398 + return; 1.399 + } 1.400 + 1.401 + if (edge == "overlap") { 1.402 + ok(Math.round(anchorrect.left) + offsetY == Math.round(popuprect.left) && 1.403 + Math.round(anchorrect.top) + offsetY == Math.round(popuprect.top), 1.404 + testname + " position"); 1.405 + return; 1.406 + } 1.407 + 1.408 + if (edge.indexOf("before") == 0) 1.409 + check1 = isWithinHalfPixel(anchorrect.top + offsetY, popuprect.bottom); 1.410 + else if (edge.indexOf("after") == 0) 1.411 + check1 = (Math.round(anchorrect.bottom) + offsetY == Math.round(popuprect.top)); 1.412 + else if (edge.indexOf("start") == 0) 1.413 + check1 = isWithinHalfPixel(anchorrect.left + offsetX, popuprect.right); 1.414 + else if (edge.indexOf("end") == 0) 1.415 + check1 = (Math.round(anchorrect.right) + offsetX == Math.round(popuprect.left)); 1.416 + 1.417 + if (0 < edge.indexOf("before")) 1.418 + check2 = (Math.round(anchorrect.top) + offsetY == Math.round(popuprect.top)); 1.419 + else if (0 < edge.indexOf("after")) 1.420 + check2 = isWithinHalfPixel(anchorrect.bottom + offsetY, popuprect.bottom); 1.421 + else if (0 < edge.indexOf("start")) 1.422 + check2 = (Math.round(anchorrect.left) + offsetX == Math.round(popuprect.left)); 1.423 + else if (0 < edge.indexOf("end")) 1.424 + check2 = isWithinHalfPixel(anchorrect.right + offsetX, popuprect.right); 1.425 + 1.426 + ok(check1 && check2, testname + " position"); 1.427 +}