diff options
Diffstat (limited to 'toolkit/content/tests/widgets/popup_shared.js')
-rw-r--r-- | toolkit/content/tests/widgets/popup_shared.js | 586 |
1 files changed, 586 insertions, 0 deletions
diff --git a/toolkit/content/tests/widgets/popup_shared.js b/toolkit/content/tests/widgets/popup_shared.js new file mode 100644 index 0000000000..3799c5622d --- /dev/null +++ b/toolkit/content/tests/widgets/popup_shared.js @@ -0,0 +1,586 @@ +/* + * This script is used for menu and popup tests. Call startPopupTests to start + * the tests, passing an array of tests as an argument. Each test is an object + * with the following properties: + * testname - name of the test + * test - function to call to perform the test + * events - a list of events that are expected to be fired in sequence + * as a result of calling the 'test' function. This list should be + * an array of strings of the form "eventtype targetid" where + * 'eventtype' is the event type and 'targetid' is the id of + * target of the event. This function will be passed two + * arguments, the testname and the step argument. + * Alternatively, events may be a function which returns the array + * of events. This can be used when the events vary per platform. + * result - function to call after all the events have fired to check + * for additional results. May be null. This function will be + * passed two arguments, the testname and the step argument. + * steps - optional array of values. The test will be repeated for + * each step, passing each successive value within the array to + * the test and result functions + * autohide - if set, should be set to the id of a popup to hide after + * the test is complete. This is a convenience for some tests. + * condition - an optional function which, if it returns false, causes the + * test to be skipped. + * end - used for debugging. Set to true to stop the tests after running + * this one. + */ + +const menuactiveAttribute = "_moz-menuactive"; + +var gPopupTests = null; +var gTestIndex = -1; +var gTestStepIndex = 0; +var gTestEventIndex = 0; +var gActualEvents = []; +var gAutoHide = false; +var gExpectedEventDetails = null; +var gExpectedTriggerNode = null; +var gWindowUtils; +var gPopupWidth = -1, + gPopupHeight = -1; + +function startPopupTests(tests) { + document.addEventListener("popupshowing", eventOccurred); + document.addEventListener("popupshown", eventOccurred); + document.addEventListener("popuphiding", eventOccurred); + document.addEventListener("popuphidden", eventOccurred); + document.addEventListener("command", eventOccurred); + document.addEventListener("DOMMenuItemActive", eventOccurred); + document.addEventListener("DOMMenuItemInactive", eventOccurred); + document.addEventListener("DOMMenuInactive", eventOccurred); + document.addEventListener("DOMMenuBarActive", eventOccurred); + document.addEventListener("DOMMenuBarInactive", eventOccurred); + + // This is useful to explicitly finish a test that shouldn't trigger events. + document.addEventListener("TestDone", eventOccurred); + + gPopupTests = tests; + gWindowUtils = SpecialPowers.getDOMWindowUtils(window); + + goNext(); +} + +if (!window.opener && window.arguments) { + window.opener = window.arguments[0]; +} + +function finish() { + if (window.opener) { + window.close(); + window.opener.SimpleTest.finish(); + return; + } + SimpleTest.finish(); +} + +function ok(condition, message) { + if (window.opener) { + window.opener.SimpleTest.ok(condition, message); + } else { + SimpleTest.ok(condition, message); + } +} + +function info(message) { + if (window.opener) { + window.opener.SimpleTest.info(message); + } else { + SimpleTest.info(message); + } +} + +function is(left, right, message) { + if (window.opener) { + window.opener.SimpleTest.is(left, right, message); + } else { + SimpleTest.is(left, right, message); + } +} + +function disableNonTestMouse(aDisable) { + gWindowUtils.disableNonTestMouseEvents(aDisable); +} + +function eventOccurred(event) { + if (gPopupTests.length <= gTestIndex) { + ok(false, "Extra " + event.type + " event fired"); + return; + } + + var test = gPopupTests[gTestIndex]; + if ("autohide" in test && gAutoHide) { + if (event.type == "DOMMenuInactive") { + gAutoHide = false; + setTimeout(goNextStep, 0); + } + return; + } + + var events = test.events; + if (typeof events == "function") { + events = events(); + } + if (events) { + if (events.length <= gTestEventIndex) { + ok( + false, + "Extra " + + event.type + + " event fired for " + + event.target.id + + " " + + gPopupTests[gTestIndex].testname + ); + return; + } + + gActualEvents.push(`${event.type} ${event.target.id}`); + + var eventitem = events[gTestEventIndex].split(" "); + var matches; + if (eventitem[1] == "#tooltip") { + is( + event.originalTarget.localName, + "tooltip", + test.testname + " event.originalTarget.localName is 'tooltip'" + ); + is( + event.originalTarget.getAttribute("default"), + "true", + test.testname + " event.originalTarget default attribute is 'true'" + ); + matches = + event.originalTarget.localName == "tooltip" && + event.originalTarget.getAttribute("default") == "true"; + } else { + is( + event.type, + eventitem[0], + test.testname + " event type " + event.type + " fired" + ); + is( + event.target.id, + eventitem[1], + test.testname + " event target ID " + event.target.id + ); + matches = eventitem[0] == event.type && eventitem[1] == event.target.id; + } + + var modifiersMask = eventitem[2]; + if (modifiersMask) { + var m = ""; + m += event.altKey ? "1" : "0"; + m += event.ctrlKey ? "1" : "0"; + m += event.shiftKey ? "1" : "0"; + m += event.metaKey ? "1" : "0"; + is(m, modifiersMask, test.testname + " modifiers mask matches"); + } + + var expectedState; + switch (event.type) { + case "popupshowing": + expectedState = "showing"; + break; + case "popupshown": + expectedState = "open"; + break; + case "popuphiding": + expectedState = "hiding"; + break; + case "popuphidden": + expectedState = "closed"; + break; + } + + if (gExpectedTriggerNode && event.type == "popupshowing") { + if (gExpectedTriggerNode == "notset") { + // check against null instead + gExpectedTriggerNode = null; + } + + is( + event.originalTarget.triggerNode, + gExpectedTriggerNode, + test.testname + " popupshowing triggerNode" + ); + } + + if (expectedState) { + is( + event.originalTarget.state, + expectedState, + test.testname + " " + event.type + " state" + ); + } + + if (matches) { + gTestEventIndex++; + if (events.length <= gTestEventIndex) { + setTimeout(checkResult, 0); + } + } else { + info(`Actual events so far: ${JSON.stringify(gActualEvents)}`); + } + } +} + +async function checkResult() { + var step = null; + var test = gPopupTests[gTestIndex]; + if ("steps" in test) { + step = test.steps[gTestStepIndex]; + } + + if ("result" in test) { + await test.result(test.testname, step); + } + + if ("autohide" in test) { + gAutoHide = true; + document.getElementById(test.autohide).hidePopup(); + return; + } + + goNextStep(); +} + +function goNextStep() { + info(`events: ${JSON.stringify(gActualEvents)}`); + gTestEventIndex = 0; + gActualEvents = []; + + var step = null; + var test = gPopupTests[gTestIndex]; + if ("steps" in test) { + gTestStepIndex++; + step = test.steps[gTestStepIndex]; + if (gTestStepIndex < test.steps.length) { + test.test(test.testname, step); + return; + } + } + + goNext(); +} + +function goNext() { + // We want to continue after the next animation frame so that + // we're in a stable state and don't get spurious mouse events at unexpected targets. + window.requestAnimationFrame(function () { + setTimeout(goNextStepSync, 0); + }); +} + +function goNextStepSync() { + if ( + gTestIndex >= 0 && + "end" in gPopupTests[gTestIndex] && + gPopupTests[gTestIndex].end + ) { + finish(); + return; + } + + gTestIndex++; + gTestStepIndex = 0; + if (gTestIndex < gPopupTests.length) { + var test = gPopupTests[gTestIndex]; + // Set the location hash so it's easy to see which test is running + document.location.hash = test.testname; + info("Starting " + test.testname); + + // skip the test if the condition returns false + if ("condition" in test && !test.condition()) { + goNext(); + return; + } + + // start with the first step if there are any + var step = null; + if ("steps" in test) { + step = test.steps[gTestStepIndex]; + } + + test.test(test.testname, step); + + // no events to check for so just check the result + if (!("events" in test)) { + checkResult(); + } else if (typeof test.events == "function" && !test.events().length) { + checkResult(); + } + } else { + finish(); + } +} + +function openMenu(menu) { + if ("open" in menu) { + menu.open = true; + } else if (menu.hasMenu()) { + menu.openMenu(true); + } else { + synthesizeMouse(menu, 4, 4, {}); + } +} + +function closeMenu(menu, popup) { + if ("open" in menu) { + menu.open = false; + } else if (menu.hasMenu()) { + menu.openMenu(false); + } else { + popup.hidePopup(); + } +} + +function checkActive(popup, id, testname) { + var activeok = true; + var children = popup.childNodes; + for (var c = 0; c < children.length; c++) { + var child = children[c]; + if ( + (id == child.id && child.getAttribute(menuactiveAttribute) != "true") || + (id != child.id && child.hasAttribute(menuactiveAttribute) != "") + ) { + activeok = false; + break; + } + } + ok(activeok, testname + " item " + (id ? id : "none") + " active"); +} + +function checkOpen(menuid, testname) { + var menu = document.getElementById(menuid); + if ("open" in menu) { + ok(menu.open, testname + " " + menuid + " menu is open"); + } else if (menu.hasMenu()) { + ok( + menu.getAttribute("open") == "true", + testname + " " + menuid + " menu is open" + ); + } +} + +function checkClosed(menuid, testname) { + var menu = document.getElementById(menuid); + if ("open" in menu) { + ok(!menu.open, testname + " " + menuid + " menu is open"); + } else if (menu.hasMenu()) { + ok(!menu.hasAttribute("open"), testname + " " + menuid + " menu is closed"); + } +} + +function convertPosition(anchor, align) { + if (anchor == "topleft" && align == "topleft") { + return "overlap"; + } + if (anchor == "topleft" && align == "topright") { + return "start_before"; + } + if (anchor == "topleft" && align == "bottomleft") { + return "before_start"; + } + if (anchor == "topright" && align == "topleft") { + return "end_before"; + } + if (anchor == "topright" && align == "bottomright") { + return "before_end"; + } + if (anchor == "bottomleft" && align == "bottomright") { + return "start_after"; + } + if (anchor == "bottomleft" && align == "topleft") { + return "after_start"; + } + if (anchor == "bottomright" && align == "bottomleft") { + return "end_after"; + } + if (anchor == "bottomright" && align == "topright") { + return "after_end"; + } + return ""; +} + +/* + * When checking position of the bottom or right edge of the popup's rect, + * use this instead of strict equality check of rounded values, + * because we snap the top/left edges to pixel boundaries, + * which can shift the bottom/right up to 0.5px from its "ideal" location, + * and could cause it to round differently. (See bug 622507.) + */ +function isWithinHalfPixel(a, b, message) { + ok(Math.abs(a - b) <= 0.5, `${message}: ${a}, ${b}`); +} + +function compareEdge(anchor, popup, edge, offsetX, offsetY, testname) { + testname += " " + edge; + + checkOpen(anchor.id, testname); + + var anchorrect = anchor.getBoundingClientRect(); + var popuprect = popup.getBoundingClientRect(); + + if (gPopupWidth == -1) { + ok( + Math.round(popuprect.right) - Math.round(popuprect.left) && + Math.round(popuprect.bottom) - Math.round(popuprect.top), + testname + " size" + ); + } else { + is(Math.round(popuprect.width), gPopupWidth, testname + " width"); + is(Math.round(popuprect.height), gPopupHeight, testname + " height"); + } + + var spaceIdx = edge.indexOf(" "); + if (spaceIdx > 0) { + let cornerX, cornerY; + let [position, align] = edge.split(" "); + switch (position) { + case "topleft": + cornerX = anchorrect.left; + cornerY = anchorrect.top; + break; + case "topcenter": + cornerX = anchorrect.left + anchorrect.width / 2; + cornerY = anchorrect.top; + break; + case "topright": + cornerX = anchorrect.right; + cornerY = anchorrect.top; + break; + case "leftcenter": + cornerX = anchorrect.left; + cornerY = anchorrect.top + anchorrect.height / 2; + break; + case "rightcenter": + cornerX = anchorrect.right; + cornerY = anchorrect.top + anchorrect.height / 2; + break; + case "bottomleft": + cornerX = anchorrect.left; + cornerY = anchorrect.bottom; + break; + case "bottomcenter": + cornerX = anchorrect.left + anchorrect.width / 2; + cornerY = anchorrect.bottom; + break; + case "bottomright": + cornerX = anchorrect.right; + cornerY = anchorrect.bottom; + break; + } + + switch (align) { + case "topleft": + cornerX += offsetX; + cornerY += offsetY; + break; + case "topright": + cornerX += -popuprect.width + offsetX; + cornerY += offsetY; + break; + case "bottomleft": + cornerX += offsetX; + cornerY += -popuprect.height + offsetY; + break; + case "bottomright": + cornerX += -popuprect.width + offsetX; + cornerY += -popuprect.height + offsetY; + break; + } + + is( + Math.round(popuprect.left), + Math.round(cornerX), + testname + " x position" + ); + is( + Math.round(popuprect.top), + Math.round(cornerY), + testname + " y position" + ); + return; + } + + if (edge == "after_pointer") { + is( + Math.round(popuprect.left), + Math.round(anchorrect.left) + offsetX, + testname + " x position" + ); + is( + Math.round(popuprect.top), + Math.round(anchorrect.top) + offsetY + 21, + testname + " y position" + ); + return; + } + + if (edge == "overlap") { + is( + Math.round(anchorrect.left) + offsetY, + Math.round(popuprect.left), + testname + " position1" + ); + is( + Math.round(anchorrect.top) + offsetY, + Math.round(popuprect.top), + testname + " position2" + ); + return; + } + + if (edge.indexOf("before") == 0) { + isWithinHalfPixel( + anchorrect.top + offsetY, + popuprect.bottom, + testname + " position1" + ); + } else if (edge.indexOf("after") == 0) { + is( + Math.round(anchorrect.bottom) + offsetY, + Math.round(popuprect.top), + testname + " position1" + ); + } else if (edge.indexOf("start") == 0) { + isWithinHalfPixel( + anchorrect.left + offsetX, + popuprect.right, + testname + " position1" + ); + } else if (edge.indexOf("end") == 0) { + is( + Math.round(anchorrect.right) + offsetX, + Math.round(popuprect.left), + testname + " position1" + ); + } + + if (0 < edge.indexOf("before")) { + is( + Math.round(anchorrect.top) + offsetY, + Math.round(popuprect.top), + testname + " position2" + ); + } else if (0 < edge.indexOf("after")) { + isWithinHalfPixel( + anchorrect.bottom + offsetY, + popuprect.bottom, + testname + " position2" + ); + } else if (0 < edge.indexOf("start")) { + is( + Math.round(anchorrect.left) + offsetX, + Math.round(popuprect.left), + testname + " position2" + ); + } else if (0 < edge.indexOf("end")) { + isWithinHalfPixel( + anchorrect.right + offsetX, + popuprect.right, + testname + " position2" + ); + } +} |