toolkit/content/tests/widgets/popup_shared.js

changeset 0
6474c204b198
     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 +}

mercurial