toolkit/content/tests/chrome/test_contextmenu_list.xul
author Nick Robson <nicko.robson@gmail.com>
Thu, 30 Jul 2015 15:00:00 +0200
changeset 255593 7edc58c272f1b386e0352a83bae4671e7a94aed8
parent 210331 8d3304b7e0e0b2b8fd607c8320436b415e8d4cae
child 255641 cf4c24bd630122e5d3a8ffb1f11f1f0a8bc25ebd
permissions -rw-r--r--
Bug 1075089 - Moved popup menu frame offset to LookAndFeel, fixed default offset for OS X. r=enn

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

<window title="Context Menu on List Tests"
        onload="setTimeout(startTest, 0);"
        xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">

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

<spacer height="5"/>

<hbox style="padding-left: 10px;">
  <spacer width="5"/>
  <richlistbox id="list" context="themenu" style="padding: 0;" oncontextmenu="checkContextMenu(event)">
    <richlistitem id="item1" style="padding-top: 3px; margin: 0;"><button label="One"/></richlistitem>
    <richlistitem id="item2" height="22"><checkbox label="Checkbox"/></richlistitem>
    <richlistitem id="item3"><button label="Three"/></richlistitem>
    <richlistitem id="item4"><checkbox label="Four"/></richlistitem>
  </richlistbox>

  <tree id="tree" rows="5" flex="1" context="themenu" style="-moz-appearance: none; border: 0">
    <treecols>
      <treecol label="Name" flex="1"/>
      <splitter class="tree-splitter"/>
      <treecol label="Moons"/>
    </treecols>
    <treechildren id="treechildren">
      <treeitem>
        <treerow>
          <treecell label="Mercury"/>
          <treecell label="0"/>
        </treerow>
      </treeitem>
      <treeitem>
        <treerow>
          <treecell label="Venus"/>
          <treecell label="0"/>
        </treerow>
      </treeitem>
      <treeitem>
        <treerow>
          <treecell label="Earth"/>
          <treecell label="1"/>
        </treerow>
      </treeitem>
      <treeitem>
        <treerow>
          <treecell label="Mars"/>
          <treecell label="2"/>
        </treerow>
       </treeitem>
    </treechildren>
  </tree>

  <menu id="menu" label="Menu">
    <menupopup id="menupopup" onpopupshown="menuTests()" onpopuphidden="nextTest()"
               oncontextmenu="checkContextMenuForMenu(event)">
      <menuitem id="menu1" label="Menu 1"/>
      <menuitem id="menu2" label="Menu 2"/>
      <menuitem id="menu3" label="Menu 3"/>
    </menupopup>
  </menu>

</hbox>

<menupopup id="themenu" onpopupshowing="if (gTestId == -1) event.preventDefault()"
                        onpopupshown="checkPopup()" onpopuphidden="setTimeout(nextTest, 0);">
  <menuitem label="Item"/>
</menupopup>

<script class="testbody" type="application/javascript">
<![CDATA[

SimpleTest.waitForExplicitFinish();

var gTestId = -1;
var gTestElement = "list";
var gSelectionStep = 0;
var gContextMenuFired = false;

function startTest()
{
  // first, check if the richlistbox selection changes on a contextmenu mouse event
  var element = $("list");
  synthesizeMouse(element.getItemAtIndex(3), 7, 1, { type : "mousedown", button: 2, ctrlKey: true });
  synthesizeMouse(element, 7, 4, { type : "contextmenu", button: 2 });

  gSelectionStep++;
  synthesizeMouse(element.getItemAtIndex(1), 7, 1, { type : "mousedown", button: 2, ctrlKey: true, shiftKey: true });
  synthesizeMouse(element, 7, 4, { type : "contextmenu", button: 2 });

  gSelectionStep++;
  synthesizeMouse(element.getItemAtIndex(1), 7, 1, { type : "mousedown", button: 2 });
  synthesizeMouse(element, 7, 4, { type : "contextmenu", button: 2 });

  $("menu").open = true;
}

function menuTests()
{
  gSelectionStep = 0;
  var element = $("menu");
  synthesizeMouse(element, 0, 0, { type : "contextmenu", button: 0 });
  is(gContextMenuFired, true, "context menu fired when menu open");

  gSelectionStep = 1;
  $("menu").boxObject.activeChild = $("menu2");
  synthesizeMouse(element, 0, 0, { type : "contextmenu", button: 0 });

  $("menu").open = false;
}

function nextTest()
{
  gTestId++;
  if (gTestId > 2) {
    if (gTestElement == "list") {
      gTestElement = "tree";
      gTestId = 0;
    }
    else {
      SimpleTest.finish();
      return;
    }
  }
  var element = $(gTestElement);
  element.focus();
  if (gTestId == 0) {
    if (gTestElement == "list")
      element.selectedIndex = 2;
    element.currentIndex = 2;
    synthesizeMouse(element, 0, 0, { type : "contextmenu", button: 0 });
  }
  else if (gTestId == 1) {
    synthesizeMouse(element, 7, 4, { type : "contextmenu", button: 2 });
  }
  else {
    element.currentIndex = -1;
    element.selectedIndex = -1;
    synthesizeMouse(element, 0, 0, { type : "contextmenu", button: 0 });
  }
}

// This is nasty so I'd better explain what's going on.
// The basic problem is that the synthetic mouse coordinate generated
// by DOMWindowUtils.sendMouseEvent and also the synthetic mouse coordinate
// generated internally when contextmenu events are redirected to the focused
// element are rounded to the nearest device pixel. But this rounding is done
// while the coordinates are relative to the nearest widget. When this test
// is run in the mochitest harness, the nearest widget is the main mochitest
// window, and our document can have a fractional position within that
// mochitest window. So when we round coordinates for comparison in this
// test, we need to do so very carefully, especially if the target element
// also has a fractional position within our document.
//
// For example, if the y-offset of our containing IFRAME is 100.4px,
// and the offset of our expected point is 10.3px in our document, the actual
// mouse event is dispatched to round(110.7) == 111px. This comes back
// with a clientY of round(111 - 100.4) == round(10.6) == 11. This is not
// equal to round(10.3) as you might expect.

function isRoundedX(a, b, msg)
{
  is(Math.round(a + mozInnerScreenX), Math.round(b + mozInnerScreenX), msg);
}

function isRoundedY(a, b, msg)
{
  is(Math.round(a + mozInnerScreenY), Math.round(b + mozInnerScreenY), msg);
}

function checkContextMenu(event)
{
  var rect = $(gTestElement).getBoundingClientRect();

  var frombase = (gTestId == -1 || gTestId == 1);
  if (!frombase)
    rect = event.originalTarget.getBoundingClientRect();
  var left = frombase ? rect.left + 7 : rect.left;
  var top = frombase ? rect.top + 4 : rect.bottom;

  isRoundedX(event.clientX, left, gTestElement + " clientX " + gSelectionStep + " " + gTestId + "," + frombase);
  isRoundedY(event.clientY, top, gTestElement + " clientY " + gSelectionStep + " " + gTestId);
  ok(event.screenX > left, gTestElement + " screenX " + gSelectionStep + " " + gTestId);
  ok(event.screenY > top, gTestElement + " screenY " + gSelectionStep + " " + gTestId);

  // context menu from mouse click
  switch (gTestId) {
    case -1:
      var expected = gSelectionStep == 2 ? 1 : (platformIsMac() ? 3 : 0);
      is($(gTestElement).selectedIndex, expected, "index after click " + gSelectionStep);
      break;
    case 0:
      if (gTestElement == "list")
        is(event.originalTarget, $("item3"), "list selection target");
      else
        is(event.originalTarget, $("treechildren"), "tree selection target");
      break;
    case 1:
      is(event.originalTarget.id, $("item1").id, "list mouse selection target");
      break;
    case 2:
      is(event.originalTarget, $("list"), "list no selection target");
      break;
  }
}

function checkContextMenuForMenu(event)
{
  gContextMenuFired = true;

  var popuprect = (gSelectionStep ? $("menu2") : $("menupopup")).getBoundingClientRect();
  is(event.clientX, Math.round(popuprect.left), "menu left " + gSelectionStep);
  // the clientY is off by one sometimes on Windows (when loaded in the testing iframe
  // but not when loaded separately) so just check for both cases for now
  ok(event.clientY == Math.round(popuprect.bottom) ||
     event.clientY - 1 == Math.round(popuprect.bottom), "menu top " + gSelectionStep);
}

function checkPopup()
{
  var menurect = $("themenu").getBoundingClientRect();
  
  // Context menus are offset by a number of pixels from the mouse click
  // which activates them. This is so that they don't appear exactly
  // under the mouse which can cause them to be mistakenly dismissed.
  // The number of pixels depends on the platform  and is defined in
  // each platform's nsLookAndFeel
  var contextMenuOffsetX = platformIsMac() ? 1 : 2;
  var contextMenuOffsetY = platformIsMac() ? -6 : 2;

  if (gTestId == 0) {
    if (gTestElement == "list") {
      var itemrect = $("item3").getBoundingClientRect();
      isRoundedX(menurect.left, itemrect.left + contextMenuOffsetX,
         "list selection keyboard left");
      isRoundedY(menurect.top, itemrect.bottom + contextMenuOffsetY,
         "list selection keyboard top");
    }
    else {
      var tree = $("tree");
      var bodyrect = $("treechildren").getBoundingClientRect();
      isRoundedX(menurect.left, bodyrect.left + contextMenuOffsetX,
         "tree selection keyboard left");
      isRoundedY(menurect.top, bodyrect.top +
         tree.treeBoxObject.rowHeight * 3 + contextMenuOffsetY,
         "tree selection keyboard top");
    }
  }
  else if (gTestId == 1) {
    // activating a context menu with the mouse from position (7, 4).
    var elementrect = $(gTestElement).getBoundingClientRect();
    isRoundedX(menurect.left, elementrect.left + 7 + contextMenuOffsetX,
       gTestElement + " mouse left");
    isRoundedY(menurect.top, elementrect.top + 4 + contextMenuOffsetY,
       gTestElement + " mouse top");
  }
  else {
    var elementrect = $(gTestElement).getBoundingClientRect();
    isRoundedX(menurect.left, elementrect.left + contextMenuOffsetX,
       gTestElement + " no selection keyboard left");
    isRoundedY(menurect.top, elementrect.bottom + contextMenuOffsetY,
       gTestElement + " no selection keyboard top");
  }

  $("themenu").hidePopup();
}

function platformIsMac()
{
  return navigator.platform.indexOf("Mac") > -1;
}

]]>
</script>

<body xmlns="http://www.w3.org/1999/xhtml">
<p id="display">
</p>
<div id="content" style="display: none">
</div>
<pre id="test">
</pre>
</body>

</window>