summaryrefslogtreecommitdiffstats
path: root/toolkit/content/tests/widgets/popup_shared.js
diff options
context:
space:
mode:
Diffstat (limited to 'toolkit/content/tests/widgets/popup_shared.js')
-rw-r--r--toolkit/content/tests/widgets/popup_shared.js586
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"
+ );
+ }
+}