toolkit/content/tests/widgets/test_popupanchor.xul
author Sebastian Hengst <archaeopteryx@coole-files.de>
Sat, 10 Sep 2016 23:41:18 +0200
changeset 313450 43725e6c61b396f0ae965f060f6ccc02a1d6a382
parent 313443 0c9c5b508ccd245bdd091f311a62bffffcef3898
child 315850 bca4534616adca94e6e9b5dd85f18919768e174a
permissions -rw-r--r--
Backed out changeset 0c9c5b508ccd (bug 1206133) for failing browser-chrome test browser_popupNotification_checkbox.js. r=backout

<?xml version="1.0"?>
<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
<?xml-stylesheet href="/tests/SimpleTest/test.css" type="text/css"?>

<window title="Popup Anchor Tests"
  xmlns:html="http://www.w3.org/1999/xhtml"
  xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">

  <panel id="testPanel"
         type="arrow"
         animate="false"
         noautohide="true">
  </panel>

  <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>

<script>
<![CDATA[
var anchor, panel, arrow;

function is_close(got, exp, msg) {
  // on some platforms we see differences of a fraction of a pixel - so
  // allow any difference of < 1 pixels as being OK.
  ok(Math.abs(got - exp) < 1, msg + ": " + got + " should be equal(-ish) to " + exp);
}

function isArrowPositionedOn(side, offset) {
  var arrowRect = arrow.getBoundingClientRect();
  var arrowMidX = (arrowRect.left + arrowRect.right) / 2;
  var arrowMidY = (arrowRect.top + arrowRect.bottom) / 2;
  var panelRect = panel.getBoundingClientRect();
  var panelMidX = (panelRect.left + panelRect.right) / 2;
  var panelMidY = (panelRect.top + panelRect.bottom) / 2;
  // First check the "flip" of the panel is correct.  If we are expecting the
  // arrow to be pointing to the left side of the anchor, the arrow must
  // also be on the left side of the panel (and vice-versa)
  // XXX - on OSX, the arrow seems to always be exactly in the center, hence
  // the 'equals' sign in the "<=" and ">=" comparisons.  NFI why though...
  switch (side) {
    case "left":
      ok(arrowMidX <= panelMidX, "arrow should be on the left of the panel");
      break;
    case "right":
      ok(arrowMidX >= panelMidX, "arrow should be on the right of the panel");
      break;
    case "top":
      ok(arrowMidY <= panelMidY, "arrow should be on the top of the panel");
      break;
    case "bottom":
      ok(arrowMidY >= panelMidY, "arrow should be on the bottom of the panel");
      break;
    default:
      ok(false, "invalid position " + where);
      break;
  }
  // Now check the arrow really is pointing where we expect.  The middle of
  // the arrow should be pointing exactly to the left (or right) side of the
  // anchor rect, +- any offsets.
  if (offset === null) // special case - explicit 'null' means 'don't check offset'
    return;
  offset = offset || 0; // no param means no offset expected.
  var anchorRect = anchor.getBoundingClientRect();
  var anchorPos = anchorRect[side];
  switch (side) {
    case "left":
    case "right":
      is_close(arrowMidX - anchorPos, offset, "arrow should be " + offset + "px from " + side + " side of anchor");
      is_close(panelRect.top, anchorRect.bottom, "top of panel should be at bottom of anchor");
      break;
    case "top":
    case "bottom":
      is_close(arrowMidY - anchorPos, offset, "arrow should be " + offset + "px from " + side + " side of anchor");
      is_close(panelRect.right, anchorRect.left, "right of panel should be left of anchor");
      break;
    default:
      ok(false, "unknown side " + side);
      break;
  }
}

function openSlidingPopup(position, callback) {
  panel.setAttribute("flip", "slide");
  _openPopup(position, callback);
}

function openPopup(position, callback) {
  panel.setAttribute("flip", "both");
  _openPopup(position, callback);
}

function _openPopup(position, callback) {
  // this is very ugly: the panel CSS sets the arrow's list-style-image based
  // on the 'side' attribute.  If the setting of the 'side' attribute causes
  // the image to change, we may get the popupshown event before the new
  // image has loaded - which causes the size of the arrow to be incorrect
  // for a brief moment - right when we are measuring it!
  // So we work around this in 2 steps:
  // * Force the 'side' attribute to a value which causes the CSS to not
  //   specify an image - then when the popup gets shown, the correct image
  //   is set, causing a load() event on the image element.
  // * Listen to *both* popupshown and the image load event.  When both have
  //   fired (the order is indeterminate) we start the test.
  panel.setAttribute("side", "noside");
  var numEvents = 0;
  function onEvent() {
    if (++numEvents == 2) // after both panel 'popupshown' and image 'load'
      callback();
  };
  panel.addEventListener("popupshown", function popupshown() {
    panel.removeEventListener("popupshown", popupshown);
    onEvent();
  });
  arrow.addEventListener("load", function imageload() {
    arrow.removeEventListener("load", imageload);
    onEvent();
  });
  panel.openPopup(anchor, position);
}

var tests = [
  // A panel with the anchor after_end - the anchor should not move on resize
  ['simpleResizeHorizontal', 'middle', function(next) {
    openPopup("after_end", function() {
      isArrowPositionedOn("right");
      var origPanelRect = panel.getBoundingClientRect();
      panel.sizeTo(100, 100);
      isArrowPositionedOn("right"); // should not have flipped, so still "right"
      panel.sizeTo(origPanelRect.width, origPanelRect.height);
      isArrowPositionedOn("right"); // should not have flipped, so still "right"
      next();
    });
  }],

  ['simpleResizeVertical', 'middle', function(next) {
    openPopup("start_after", function() {
      isArrowPositionedOn("bottom");
      var origPanelRect = panel.getBoundingClientRect();
      panel.sizeTo(100, 100);
      isArrowPositionedOn("bottom"); // should not have flipped
      panel.sizeTo(origPanelRect.width, origPanelRect.height);
      isArrowPositionedOn("bottom"); // should not have flipped
      next();
    });
  }],

  ['flippingResizeHorizontal', 'middle', function(next) {
    openPopup("after_end", function() {
      isArrowPositionedOn("right");
      panel.sizeTo(anchor.getBoundingClientRect().left + 50, 50);
      isArrowPositionedOn("left"); // check it flipped and has zero offset.
      next();
    });
  }],

  ['flippingResizeVertical', 'middle', function(next) {
    openPopup("start_after", function() {
      isArrowPositionedOn("bottom");
      panel.sizeTo(50, anchor.getBoundingClientRect().top + 50);
      isArrowPositionedOn("top"); // check it flipped and has zero offset.
      next();
    });
  }],

  ['simpleMoveToAnchorHorizontal', 'middle', function(next) {
    openPopup("after_end", function() {
      isArrowPositionedOn("right");
      panel.moveToAnchor(anchor, "after_end", 20, 0);
      // the anchor and the panel should have moved 20px right without flipping.
      isArrowPositionedOn("right", 20);
      panel.moveToAnchor(anchor, "after_end", -20, 0);
      // the anchor and the panel should have moved 20px left without flipping.
      isArrowPositionedOn("right", -20);
      next();
    });
  }],

  ['simpleMoveToAnchorVertical', 'middle', function(next) {
    openPopup("start_after", function() {
      isArrowPositionedOn("bottom");
      panel.moveToAnchor(anchor, "start_after", 0, 20);
      // the anchor and the panel should have moved 20px down without flipping.
      isArrowPositionedOn("bottom", 20);
      panel.moveToAnchor(anchor, "start_after", 0, -20);
      // the anchor and the panel should have moved 20px up without flipping.
      isArrowPositionedOn("bottom", -20);
      next();
    });
  }],

  // Do a moveToAnchor that causes the panel to flip horizontally
  ['flippingMoveToAnchorHorizontal', 'middle', function(next) {
    var anchorRight = anchor.getBoundingClientRect().right;
    // Size the panel such that it only just fits from the left-hand side of
    // the window to the right of the anchor - thus, it will fit when
    // anchored to the right-hand side of the anchor.
    panel.sizeTo(anchorRight - 10, 100);
    openPopup("after_end", function() {
      isArrowPositionedOn("right");
      // Ask for it to be anchored 1/2 way between the left edge of the window
      // and the anchor right - it can't fit with the panel on the left/arrow
      // on the right, so it must flip (arrow on the left, panel on the right)
      var offset = Math.floor(-anchorRight / 2);
      panel.moveToAnchor(anchor, "after_end", offset, 0);
      isArrowPositionedOn("left", offset); // should have flipped and have the offset.
      // resize back to original and move to a zero offset - it should flip back.
      panel.sizeTo(anchorRight - 10, 100);
      panel.moveToAnchor(anchor, "after_end", 0, 0);
      isArrowPositionedOn("right"); // should have flipped back and no offset
      next();
    });
  }],

  // Do a moveToAnchor that causes the panel to flip vertically
  ['flippingMoveToAnchorVertical', 'middle', function(next) {
    var anchorBottom = anchor.getBoundingClientRect().bottom;
    // See comments above in flippingMoveToAnchorHorizontal, but read
    // "top/bottom" instead of "left/right"
    panel.sizeTo(100, anchorBottom - 10);
    openPopup("start_after", function() {
      isArrowPositionedOn("bottom");
      var offset = Math.floor(-anchorBottom / 2);
      panel.moveToAnchor(anchor, "start_after", 0, offset);
      isArrowPositionedOn("top", offset);
      panel.sizeTo(100, anchorBottom - 10);
      panel.moveToAnchor(anchor, "start_after", 0, 0);
      isArrowPositionedOn("bottom");
      next();
    });
  }],

  ['veryWidePanel-after_end', 'middle', function(next) {
    openSlidingPopup("after_end", function() {
      var origArrowRect = arrow.getBoundingClientRect();
      // Now move it such that the arrow can't be at either end of the panel but
      // instead somewhere in the middle as that is the only way things fit,
      // meaning the arrow should "slide" down the panel.
      panel.sizeTo(window.innerWidth - 10, 60);
      is(panel.getBoundingClientRect().width, window.innerWidth - 10, "width is what we requested.")
      // the arrow should not have moved.
      var curArrowRect = arrow.getBoundingClientRect();
      is_close(curArrowRect.left, origArrowRect.left, "arrow should not have moved");
      is_close(curArrowRect.top, origArrowRect.top, "arrow should not have moved up or down");
      next();
    });
  }],

  ['veryWidePanel-before_start', 'middle', function(next) {
    openSlidingPopup("before_start", function() {
      var origArrowRect = arrow.getBoundingClientRect();
      // Now size it such that the arrow can't be at either end of the panel but
      // instead somewhere in the middle as that is the only way things fit.
      panel.sizeTo(window.innerWidth - 10, 60);
      is(panel.getBoundingClientRect().width, window.innerWidth - 10, "width is what we requested")
      // the arrow should not have moved.
      var curArrowRect = arrow.getBoundingClientRect();
      is_close(curArrowRect.left, origArrowRect.left, "arrow should not have moved");
      is_close(curArrowRect.top, origArrowRect.top, "arrow should not have moved up or down");
      next();
    });
  }],

  ['veryTallPanel-start_after', 'middle', function(next) {
    openSlidingPopup("start_after", function() {
      var origArrowRect = arrow.getBoundingClientRect();
      // Now move it such that the arrow can't be at either end of the panel but
      // instead somewhere in the middle as that is the only way things fit,
      // meaning the arrow should "slide" down the panel.
      panel.sizeTo(100, window.innerHeight - 10);
      is(panel.getBoundingClientRect().height, window.innerHeight - 10, "height is what we requested.")
      // the arrow should not have moved.
      var curArrowRect = arrow.getBoundingClientRect();
      is_close(curArrowRect.left, origArrowRect.left, "arrow should not have moved");
      is_close(curArrowRect.top, origArrowRect.top, "arrow should not have moved up or down");
      next();
    });
  }],

  ['veryTallPanel-start_before', 'middle', function(next) {
    openSlidingPopup("start_before", function() {
      var origArrowRect = arrow.getBoundingClientRect();
      // Now size it such that the arrow can't be at either end of the panel but
      // instead somewhere in the middle as that is the only way things fit.
      panel.sizeTo(100, window.innerHeight - 10);
      is(panel.getBoundingClientRect().height, window.innerHeight - 10, "height is what we requested")
      // the arrow should not have moved.
      var curArrowRect = arrow.getBoundingClientRect();
      is_close(curArrowRect.left, origArrowRect.left, "arrow should not have moved");
      is_close(curArrowRect.top, origArrowRect.top, "arrow should not have moved up or down");
      next();
    });
  }],

  // Tests against the anchor at the right-hand side of the window
  ['afterend', 'right', function(next) {
    openPopup("after_end", function() {
      // when we request too far to the right/bottom, the panel gets shrunk
      // and moved.  The amount it is shrunk by is how far it is moved.
      var panelRect = panel.getBoundingClientRect();
      // panel was requested 100px wide - calc offset based on actual width.
      var offset = panelRect.width - 100;
      isArrowPositionedOn("right", offset);
      next();
    });
  }],

  ['after_start', 'right', function(next) {
    openPopup("after_start", function() {
      // See above - we are still too far to the right, but the anchor is
      // on the other side.
      var panelRect = panel.getBoundingClientRect();
      var offset = panelRect.width - 100;
      isArrowPositionedOn("right", offset);
      next();
    });
  }],

  // Tests against the anchor at the left-hand side of the window
  ['after_start', 'left', function(next) {
    openPopup("after_start", function() {
      var panelRect = panel.getBoundingClientRect();
      is(panelRect.left, 0, "panel remains within the screen");
      // not sure how to determine the offset here, so given we have checked
      // the panel is as left as possible while still being inside the window,
      // we just don't check the offset.
      isArrowPositionedOn("left", null);
      next();
    });
  }],
]

function runTests() {
  function runNextTest() {
    let result = tests.shift();
    if (!result) {
      // out of tests
      panel.hidePopup();
      SimpleTest.finish();
      return;
    }
    let [name, anchorPos, test] = result;
    SimpleTest.info("sub-test " + anchorPos + "." + name + " starting");
    // first arrange for the anchor to be where the test requires it.
    panel.hidePopup();
    panel.sizeTo(100, 50);
    // hide all the anchors here, then later we make one of them visible.
    document.getElementById("anchor-left-wrapper").style.display = "none";
    document.getElementById("anchor-middle-wrapper").style.display = "none";
    document.getElementById("anchor-right-wrapper").style.display = "none";
    switch(anchorPos) {
      case 'middle':
        anchor = document.getElementById("anchor-middle");
        document.getElementById("anchor-middle-wrapper").style.display = "block";
        break;
      case 'left':
        anchor = document.getElementById("anchor-left");
        document.getElementById("anchor-left-wrapper").style.display = "block";
        break;
      case 'right':
        anchor = document.getElementById("anchor-right");
        document.getElementById("anchor-right-wrapper").style.display = "block";
        break;
      default:
        SimpleTest.ok(false, "Bad anchorPos: " + anchorPos);
        runNextTest();
        return;
    }
    try {
      test(runNextTest);
    } catch (ex) {
      SimpleTest.ok(false, "sub-test " + anchorPos + "." + name + " failed: " + ex.toString() + "\n" + ex.stack);
      runNextTest();
    }
  }
  runNextTest();
}

SimpleTest.waitForExplicitFinish();

addEventListener("load", function() {
  // anchor is set by the test runner above
  panel = document.getElementById("testPanel");
  arrow = SpecialPowers.wrap(document).getAnonymousElementByAttribute(panel, "anonid", "arrow");
  // Cancel the arrow panel slide-in transition (bug 767133) so the size and
  // position are "stable" enough to test without jumping through hoops...
  arrow.style.transition = "none";
  runTests();
});

]]>
</script>

<body xmlns="http://www.w3.org/1999/xhtml">
<!-- Our tests assume at least 100px around the anchor on all sides, else the
     panel may flip when we don't expect it to
-->
<div id="anchor-middle-wrapper" style="margin: 100px 100px 100px 100px;">
  <p>The anchor --&gt; <span id="anchor-middle">v</span> &lt;--</p>
</div>
<div id="anchor-left-wrapper" style="text-align: left; display: none;">
  <p><span id="anchor-left">v</span> &lt;-- The anchor;</p>
</div>
<div id="anchor-right-wrapper" style="text-align: right; display: none;">
  <p>The anchor --&gt; <span id="anchor-right">v</span></p>
</div>
</body>

</window>