summaryrefslogtreecommitdiffstats
path: root/accessible/tests/browser/hittest/browser_test_general.js
blob: ca3a879c187f6b8c34a402dca764722b9fffdd59 (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
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";

async function runTests(browser, accDoc) {
  const dpr = await getContentDPR(browser);

  await testChildAtPoint(
    dpr,
    3,
    3,
    findAccessibleChildByID(accDoc, "list"),
    findAccessibleChildByID(accDoc, "listitem"),
    findAccessibleChildByID(accDoc, "inner").firstChild
  );
  todo(
    false,
    "Bug 746974 - children must match on all platforms. On Windows, " +
      "ChildAtPoint with eDeepestChild is incorrectly ignoring MustPrune " +
      "for the graphic."
  );

  const txt = findAccessibleChildByID(accDoc, "txt");
  await testChildAtPoint(dpr, 1, 1, txt, txt, txt);

  info(
    "::MustPrune case, point is outside of textbox accessible but is in document."
  );
  await testChildAtPoint(dpr, -1, -1, txt, null, null);

  info("::MustPrune case, point is outside of root accessible.");
  await testChildAtPoint(dpr, -10000, -10000, txt, null, null);

  info("Not specific case, point is inside of btn accessible.");
  const btn = findAccessibleChildByID(accDoc, "btn");
  await testChildAtPoint(dpr, 1, 1, btn, btn, btn);

  info("Not specific case, point is outside of btn accessible.");
  await testChildAtPoint(dpr, -1, -1, btn, null, null);

  info(
    "Out of flow accessible testing, do not return out of flow accessible " +
      "because it's not a child of the accessible even though visually it is."
  );
  await invokeContentTask(browser, [], () => {
    const { CommonUtils } = ChromeUtils.importESModule(
      "chrome://mochitests/content/browser/accessible/tests/browser/Common.sys.mjs"
    );
    const doc = content.document;
    const rectArea = CommonUtils.getNode("area", doc).getBoundingClientRect();
    const outOfFlow = CommonUtils.getNode("outofflow", doc);
    outOfFlow.style.left = rectArea.left + "px";
    outOfFlow.style.top = rectArea.top + "px";
  });

  const area = findAccessibleChildByID(accDoc, "area");
  await testChildAtPoint(dpr, 1, 1, area, area, area);

  info("Test image maps. Their children are not in the layout tree.");
  await waitForImageMap(browser, accDoc);
  const imgmap = findAccessibleChildByID(accDoc, "imgmap");
  ok(imgmap, "Image map exists");
  const theLetterA = imgmap.firstChild;
  await hitTest(browser, imgmap, theLetterA, theLetterA);
  await hitTest(
    browser,
    findAccessibleChildByID(accDoc, "container"),
    imgmap,
    theLetterA
  );

  info("hit testing for element contained by zero-width element");
  const container2Input = findAccessibleChildByID(accDoc, "container2_input");
  await hitTest(
    browser,
    findAccessibleChildByID(accDoc, "container2"),
    container2Input,
    container2Input
  );

  info("hittesting table, row, cells -- rows are not in the layout tree");
  const table = findAccessibleChildByID(accDoc, "table");
  const row = findAccessibleChildByID(accDoc, "row");
  const cell1 = findAccessibleChildByID(accDoc, "cell1");

  await hitTest(browser, table, row, cell1);

  info("Testing that an inaccessible child doesn't break hit testing");
  const containerWithInaccessibleChild = findAccessibleChildByID(
    accDoc,
    "containerWithInaccessibleChild"
  );
  const containerWithInaccessibleChildP2 = findAccessibleChildByID(
    accDoc,
    "containerWithInaccessibleChild_p2"
  );
  await hitTest(
    browser,
    containerWithInaccessibleChild,
    containerWithInaccessibleChildP2,
    containerWithInaccessibleChildP2.firstChild
  );

  info("Testing wrapped text");
  const wrappedTextLinkFirstP = findAccessibleChildByID(
    accDoc,
    "wrappedTextLinkFirstP"
  );
  const wrappedTextLinkFirstA = findAccessibleChildByID(
    accDoc,
    "wrappedTextLinkFirstA"
  );
  await hitTest(
    browser,
    wrappedTextLinkFirstP,
    wrappedTextLinkFirstA,
    wrappedTextLinkFirstA.firstChild
  );
  const wrappedTextLeafFirstP = findAccessibleChildByID(
    accDoc,
    "wrappedTextLeafFirstP"
  );
  const wrappedTextLeafFirstMark = findAccessibleChildByID(
    accDoc,
    "wrappedTextLeafFirstMark"
  );
  await hitTest(
    browser,
    wrappedTextLeafFirstP,
    wrappedTextLeafFirstMark,
    wrappedTextLeafFirstMark.firstChild
  );

  info("Testing image");
  const imageP = findAccessibleChildByID(accDoc, "imageP");
  const image = findAccessibleChildByID(accDoc, "image");
  await hitTest(browser, imageP, image, image);

  info("Testing image map with 0-sized area");
  const mapWith0AreaP = findAccessibleChildByID(accDoc, "mapWith0AreaP");
  const mapWith0Area = findAccessibleChildByID(accDoc, "mapWith0Area");
  await hitTest(browser, mapWith0AreaP, mapWith0Area, mapWith0Area);
}

addAccessibleTask(
  `
  <div role="list" id="list">
    <div role="listitem" id="listitem"><span title="foo" id="inner">inner</span>item</div>
  </div>

  <span role="button">button1</span><span role="button" id="btn">button2</span>

  <span role="textbox">textbox1</span><span role="textbox" id="txt">textbox2</span>

  <div id="outofflow" style="width: 10px; height: 10px; position: absolute; left: 0px; top: 0px; background-color: yellow;">
  </div>
  <div id="area" style="width: 100px; height: 100px; background-color: blue;"></div>

  <map name="atoz_map">
    <area id="thelettera" href="http://www.bbc.co.uk/radio4/atoz/index.shtml#a"
          coords="0,0,15,15" alt="thelettera" shape="rect"/>
  </map>

  <div id="container">
    <img id="imgmap" width="447" height="15" usemap="#atoz_map" src="http://example.com/a11y/accessible/tests/mochitest/letters.gif"/>
  </div>

  <div id="container2" style="width: 0px">
    <input id="container2_input">
  </div>

  <table id="table" border>
    <tr id="row">
      <td id="cell1">hello</td>
      <td id="cell2">world</td>
    </tr>
  </table>

  <div id="containerWithInaccessibleChild">
    <p>hi</p>
    <p aria-hidden="true">hi</p>
    <p id="containerWithInaccessibleChild_p2">bye</p>
  </div>

  <p id="wrappedTextLinkFirstP" style="width: 3ch; font-family: monospace;">
    <a id="wrappedTextLinkFirstA" href="https://example.com/">a</a>b cd
  </p>

  <p id="wrappedTextLeafFirstP" style="width: 3ch; font-family: monospace;">
    <mark id="wrappedTextLeafFirstMark">a</mark><a href="https://example.com/">b cd</a>
  </p>

  <p id="imageP">
    <img id="image" src="http://example.com/a11y/accessible/tests/mochitest/letters.gif">
  </p>

  <map id="0Area">
    <area shape="rect">
  </map>
  <p id="mapWith0AreaP">
    <img id="mapWith0Area" src="http://example.com/a11y/accessible/tests/mochitest/letters.gif" usemap="#0Area">
  </p>
  `,
  runTests,
  {
    iframe: true,
    remoteIframe: true,
    // Ensure that all hittest elements are in view.
    iframeAttrs: { style: "width: 600px; height: 600px; padding: 10px;" },
  }
);

addAccessibleTask(
  `
  <div id="container">
    <h1 id="a">A</h1><h1 id="b">B</h1>
  </div>
  `,
  async function (browser, accDoc) {
    const a = findAccessibleChildByID(accDoc, "a");
    const b = findAccessibleChildByID(accDoc, "b");
    const dpr = await getContentDPR(browser);
    // eslint-disable-next-line no-unused-vars
    const [x, y, w, h] = Layout.getBounds(a, dpr);
    // The point passed below will be made relative to `b`, but
    // we'd like to test a point within `a`. Pass `a`s negative
    // width for an x offset. Pass zero as a y offset,
    // assuming the headings are on the same line.
    await testChildAtPoint(dpr, -w, 0, b, null, null);
  },
  {
    iframe: true,
    remoteIframe: true,
    // Ensure that all hittest elements are in view.
    iframeAttrs: { style: "width: 600px; height: 600px; padding: 10px;" },
  }
);

addAccessibleTask(
  `
  <style>
    div {
      width: 50px;
      height: 50px;
      position: relative;
    }

    div > div {
      width: 30px;
      height: 30px;
      position: absolute;
      opacity: 0.9;
    }
  </style>
  <div id="a" style="background-color: orange;">
    <div id="aa" style="background-color: purple;"></div>
  </div>
  <div id="b" style="background-color: yellowgreen;">
    <div id="bb" style="top: -30px; background-color: turquoise"></div>
  </div>`,
  async function (browser, accDoc) {
    const a = findAccessibleChildByID(accDoc, "a");
    const aa = findAccessibleChildByID(accDoc, "aa");
    const dpr = await getContentDPR(browser);
    const [, , w, h] = Layout.getBounds(a, dpr);
    // test upper left of `a`
    await testChildAtPoint(dpr, 1, 1, a, aa, aa);
    // test upper right of `a`
    await testChildAtPoint(dpr, w - 1, 1, a, a, a);
    // test just outside upper left of `a`
    await testChildAtPoint(dpr, 1, -1, a, null, null);
    // test halfway down/left of `a`
    await testChildAtPoint(dpr, 1, Math.round(h / 2), a, a, a);
  },
  {
    chrome: true,
    topLevel: true,
    iframe: false,
    remoteIframe: false,
    // Ensure that all hittest elements are in view.
    iframeAttrs: { style: "width: 600px; height: 600px; padding: 10px;" },
  }
);

/**
 * Verify that hit testing returns the proper accessible when one acc content
 * is partially hidden due to overflow:hidden;
 */
addAccessibleTask(
  `
  <style>
    div div {
      overflow: hidden;
      font-family: monospace;
      width: 2ch;
    }
  </style>
  <div id="container" style="display: flex; flex-direction: row-reverse;">
    <div id="aNode">abcde</div><div id="fNode">fghij</div>
  </div>`,
  async function (browser, docAcc) {
    const container = findAccessibleChildByID(docAcc, "container");
    const aNode = findAccessibleChildByID(docAcc, "aNode");
    const fNode = findAccessibleChildByID(docAcc, "fNode");
    const dpr = await getContentDPR(browser);
    const [, , containerWidth] = Layout.getBounds(container, dpr);
    const [, , aNodeWidth] = Layout.getBounds(aNode, dpr);

    await testChildAtPoint(
      dpr,
      containerWidth - 1,
      1,
      container,
      aNode,
      aNode.firstChild
    );
    await testChildAtPoint(
      dpr,
      containerWidth - aNodeWidth - 1,
      1,
      container,
      fNode,
      fNode.firstChild
    );
  },
  { chrome: true, iframe: true, remoteIframe: true }
);

/**
 * Verify that hit testing is appropriately fuzzy when working with generics.
 * If we match on a generic which contains additional generics and a single text
 * leaf, we should return the text leaf as the deepest match instead of the
 * generic itself.
 */
addAccessibleTask(
  `
  <a href="example.com" id="link">
    <span style="overflow:hidden;" id="generic"><span aria-hidden="true" id="visible">I am some visible text</span><span id="invisible" style="overflow:hidden; height: 1px; width: 1px; position:absolute; clip: rect(0 0 0 0); display:block;">I am some invisible text</span></span>
  </a>`,
  async function (browser, docAcc) {
    const link = findAccessibleChildByID(docAcc, "link");
    const generic = findAccessibleChildByID(docAcc, "generic");
    const invisible = findAccessibleChildByID(docAcc, "invisible");
    const dpr = await getContentDPR(browser);

    await testChildAtPoint(
      dpr,
      1,
      1,
      link,
      generic, // Direct Child
      invisible.firstChild // Deepest Child
    );

    await testOffsetAtPoint(
      findAccessibleChildByID(docAcc, "invisible", [Ci.nsIAccessibleText]),
      1,
      1,
      COORDTYPE_PARENT_RELATIVE,
      0
    );
  },
  { chrome: false, iframe: true, remoteIframe: true }
);

/**
 * Verify that hit testing is appropriately fuzzy when working with generics with siblings.
 * We should return the deepest text leaf as the deepest match instead of the generic itself.
 */
addAccessibleTask(
  `
<div id="generic"><span aria-hidden="true" id="visible">Mozilla</span><span id="invisible" style="display: block !important;border: 0 !important;clip: rect(0 0 0 0) !important;height: 1px !important;margin: -1px !important;overflow: hidden !important;padding: 0 !important;position: absolute !important;white-space: nowrap !important;width: 1px !important;">hello world<br><div id="extraContainer">Mozilla</div></span><br>I am some other text</div>`,
  async function (browser, docAcc) {
    const generic = findAccessibleChildByID(docAcc, "generic");
    const invisible = findAccessibleChildByID(docAcc, "invisible");
    const dpr = await getContentDPR(browser);

    await testChildAtPoint(
      dpr,
      1,
      1,
      generic,
      invisible, // Direct Child
      invisible.firstChild // Deepest Child
    );
  },
  { chrome: false, iframe: true, remoteIframe: true }
);

/**
 * Verify that hit testing correctly ignores
 * elements with pointer-events: none;
 */
addAccessibleTask(
  `<div id="container" style="position:relative;"><button id="obscured">click me</button><div id="overlay" style="pointer-events:none; top:0; bottom:0; left:0; right:0; position: absolute;"></div></div><button id="clickable">I am clickable</button>`,
  async function (browser, docAcc) {
    const container = findAccessibleChildByID(docAcc, "container");
    const obscured = findAccessibleChildByID(docAcc, "obscured");
    const clickable = findAccessibleChildByID(docAcc, "clickable");
    const dpr = await getContentDPR(browser);
    let [targetX, targetY, targetW, targetH] = Layout.getBounds(obscured, dpr);
    const [x, y] = Layout.getBounds(docAcc, dpr);
    await testChildAtPoint(
      dpr,
      targetX - x + targetW / 2,
      targetY - y + targetH / 2,
      docAcc,
      container, // Direct Child
      obscured // Deepest Child
    );

    [targetX, targetY, targetW, targetH] = Layout.getBounds(clickable, dpr);
    await testChildAtPoint(
      dpr,
      targetX - x + targetW / 2,
      targetY - y + targetH / 2,
      docAcc,
      clickable, // Direct Child
      clickable // Deepest Child
    );
  },
  { chrome: false, iframe: true, remoteIframe: true }
);