526 lines
17 KiB
JavaScript
526 lines
17 KiB
JavaScript
/* 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
|
|
);
|
|
const wrappedTextNestedInlineP = findAccessibleChildByID(
|
|
accDoc,
|
|
"wrappedTextNestedInlineP"
|
|
);
|
|
const wrappedTextNestedInlineEm = findAccessibleChildByID(
|
|
accDoc,
|
|
"wrappedTextNestedInlineEm"
|
|
);
|
|
const wrappedTextNestedInlineStrong = findAccessibleChildByID(
|
|
accDoc,
|
|
"wrappedTextNestedInlineStrong"
|
|
);
|
|
await hitTest(
|
|
browser,
|
|
wrappedTextNestedInlineP,
|
|
wrappedTextNestedInlineEm,
|
|
wrappedTextNestedInlineStrong.firstChild
|
|
);
|
|
const wrappedTextPre = findAccessibleChildByID(accDoc, "wrappedTextPre");
|
|
const wrappedTextPreCode = findAccessibleChildByID(
|
|
accDoc,
|
|
"wrappedTextPreCode"
|
|
);
|
|
await hitTest(
|
|
browser,
|
|
wrappedTextPre,
|
|
wrappedTextPreCode,
|
|
wrappedTextPreCode.firstChild
|
|
);
|
|
// hitTest() can only test the first character. We need to test a subsequent
|
|
// character for this case.
|
|
let [x, y, w] = await getContentBoundsForDOMElm(
|
|
browser,
|
|
"wrappedTextPreCode"
|
|
);
|
|
// Use the top center of the element.
|
|
x = x + w / 2;
|
|
await untilCacheIs(
|
|
() => getChildAtPoint(wrappedTextPre, x, y, true),
|
|
wrappedTextPreCode.firstChild,
|
|
`Wrong deepest child accessible at the point (${x}, ${y}) of wrappedTextPre, sought wrappedTextPreCode leaf`
|
|
);
|
|
|
|
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="wrappedTextNestedInlineP" style="width: 1ch; font-family: monospace;">
|
|
<em id="wrappedTextNestedInlineEm"><strong id="wrappedTextNestedInlineStrong">y </strong>z</em>
|
|
</p>
|
|
|
|
<pre id="wrappedTextPre"><code id="wrappedTextPreCode">ab cd
|
|
e</pre>
|
|
|
|
<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 }
|
|
);
|
|
|
|
/**
|
|
* Test hit testing on elements which change to display: contents.
|
|
*/
|
|
addAccessibleTask(
|
|
`
|
|
<style>
|
|
p {
|
|
font-family: monospace;
|
|
}
|
|
</style>
|
|
<details id="detailsOpen" open>
|
|
<summary>summary</summary>
|
|
<p id="detailsOpenP">detailsOpenP</p>
|
|
</details>
|
|
<details id="details">
|
|
<summary>summary</summary>
|
|
<p id="detailsP">detailsP</p>
|
|
</details>
|
|
<div id="outer">
|
|
<p>before</p>
|
|
<div id="inner" role="group">
|
|
<p id="innerP" hidden>innerP</p>
|
|
</div>
|
|
</div>
|
|
`,
|
|
async function testChangeDisplayContents(browser, docAcc) {
|
|
const detailsOpen = findAccessibleChildByID(docAcc, "detailsOpen");
|
|
const detailsOpenP = findAccessibleChildByID(docAcc, "detailsOpenP");
|
|
await hitTest(browser, detailsOpen, detailsOpenP, detailsOpenP.firstChild);
|
|
|
|
info("Opening details");
|
|
let shown = waitForEvent(EVENT_SHOW, "detailsP");
|
|
await invokeContentTask(browser, [], () => {
|
|
content.document.getElementById("details").open = true;
|
|
});
|
|
const detailsP = (await shown).accessible;
|
|
// There is a <slot> between details and detailsP which has display:
|
|
// contents.
|
|
await hitTest(browser, detailsP.parent, detailsP, detailsP.firstChild);
|
|
|
|
info("Setting display: contents on inner, showing innerP");
|
|
shown = waitForEvent(EVENT_SHOW, "innerP");
|
|
await invokeContentTask(browser, [], () => {
|
|
content.document.getElementById("inner").style.display = "contents";
|
|
content.document.getElementById("innerP").hidden = false;
|
|
});
|
|
const innerP = (await shown).accessible;
|
|
const inner = findAccessibleChildByID(docAcc, "inner");
|
|
await hitTest(browser, inner, innerP, innerP.firstChild);
|
|
}
|
|
);
|