summaryrefslogtreecommitdiffstats
path: root/toolkit/content/tests/chrome/test_contextmenu_list.xhtml
blob: d5d2b1a10b640dd8a34da9788d89fab9f4de071d (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
<?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 src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>      
  <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>      

<spacer style="height: 5px"/>

<hbox style="padding-left: 10px;">
  <spacer style="width: 5ps"/>
  <richlistbox id="list" context="themenu" style="padding: 0;" oncontextmenu="checkContextMenu(event)">
    <richlistitem id="item1" style="padding-top: 4px; margin: 0;"><button label="One"/></richlistitem>
    <richlistitem id="item2" style="height: 22px"><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;

async function startTest()
{
  // These tests check behavior of non-native menus, and use anchored and non-anchored popups
  // somewhat interchangeably. So disable native context menus for this test.
  // We will have separate tests for native context menu behavior, see bug 1700727.
  await SpecialPowers.pushPrefEnv({ set: [["widget.macos.native-context-menus", false]] });

  // 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").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 + window.mozInnerScreenX), Math.round(b + window.mozInnerScreenX), msg);
}

function isRoundedY(a, b, msg)
{
  is(Math.round(a + window.mozInnerScreenY), Math.round(b + window.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:
      // eslint-disable-next-line no-nested-ternary
      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;
  contextMenuOffsetY += parseFloat(getComputedStyle($("themenu")).marginTop);
  contextMenuOffsetX += parseFloat(getComputedStyle($("themenu")).marginLeft);

  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.rowHeight * 3 + contextMenuOffsetY,
         "tree selection keyboard top");
    }
  }
  else if (gTestId == 1) {
    // activating a context menu with the mouse from position (7, 4).
    let elementrect = $(gTestElement).getBoundingClientRect();
    isRoundedX(menurect.left, elementrect.left + 7 + contextMenuOffsetX,
       gTestElement + " mouse left");
    isRoundedY(menurect.top, elementrect.top + 4 + contextMenuOffsetY,
       gTestElement + " mouse top");
  }
  else {
    let 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>