Source code

Revision control

Copy as Markdown

Other Tools

/*
* 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 "topcenter":
cornerX += -popuprect.width / 2 + offsetX;
cornerY += offsetY;
break;
case "topright":
cornerX += -popuprect.width + offsetX;
cornerY += offsetY;
break;
case "leftcenter":
cornerX += offsetX;
cornerY += -popuprect.height / 2 + offsetY;
break;
case "rightcenter":
cornerX += -popuprect.width + offsetX;
cornerY += -popuprect.height / 2 + offsetY;
break;
case "bottomleft":
cornerX += offsetX;
cornerY += -popuprect.height + offsetY;
break;
case "bottomcenter":
cornerX += -popuprect.width / 2 + 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"
);
}
}