Source code

Revision control

Copy as Markdown

Other Tools

Test Info:

/* 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";
/* import-globals-from ../../mochitest/attributes.js */
/* import-globals-from ../../mochitest/text.js */
loadScripts({ name: "attributes.js", dir: MOCHITESTS_DIR });
/**
* Test line and word offsets for various cases for both local and remote
* Accessibles. There is more extensive coverage in ../../mochitest/text. These
* tests don't need to duplicate all of that, since much of the underlying code
* is unified. They should ensure that the cache works as expected and that
* there is consistency between local and remote.
*/
addAccessibleTask(
`
<p id="br">ab cd<br>ef gh</p>
<pre id="pre">ab cd
ef gh</pre>
<p id="linksStartEnd"><a href="https://example.com/">a</a>b<a href="https://example.com/">c</a></p>
<p id="linksBreaking">a<a href="https://example.com/">b<br>c</a>d</p>
<p id="p">a<br role="presentation">b</p>
<p id="leafThenWrap" style="font-family: monospace; width: 2ch; word-break: break-word;"><span>a</span>bc</p>
<p id="bidi" style="font-family: monospace; width: 3ch; word-break: break-word">אb גד eו זח טj</p>
`,
async function (browser, docAcc) {
for (const id of ["br", "pre"]) {
const acc = findAccessibleChildByID(docAcc, id);
testCharacterCount([acc], 11);
testTextAtOffset(acc, BOUNDARY_LINE_START, [
[0, 5, "ab cd\n", 0, 6],
[6, 11, "ef gh", 6, 11],
]);
testTextBeforeOffset(acc, BOUNDARY_LINE_START, [
[0, 5, "", 0, 0],
[6, 11, "ab cd\n", 0, 6],
]);
testTextAfterOffset(acc, BOUNDARY_LINE_START, [
[0, 5, "ef gh", 6, 11],
[6, 11, "", 11, 11],
]);
testTextAtOffset(acc, BOUNDARY_LINE_END, [
[0, 5, "ab cd", 0, 5],
[6, 11, "\nef gh", 5, 11],
]);
testTextBeforeOffset(acc, BOUNDARY_LINE_END, [
[0, 5, "", 0, 0],
[6, 11, "ab cd", 0, 5],
]);
testTextAfterOffset(acc, BOUNDARY_LINE_END, [
[0, 5, "\nef gh", 5, 11],
[6, 11, "", 11, 11],
]);
testTextAtOffset(acc, BOUNDARY_WORD_START, [
[0, 2, "ab ", 0, 3],
[3, 5, "cd\n", 3, 6],
[6, 8, "ef ", 6, 9],
[9, 11, "gh", 9, 11],
]);
testTextBeforeOffset(acc, BOUNDARY_WORD_START, [
[0, 2, "", 0, 0],
[3, 5, "ab ", 0, 3],
[6, 8, "cd\n", 3, 6],
[9, 11, "ef ", 6, 9],
]);
testTextAfterOffset(acc, BOUNDARY_WORD_START, [
[0, 2, "cd\n", 3, 6],
[3, 5, "ef ", 6, 9],
[6, 8, "gh", 9, 11],
[9, 11, "", 11, 11],
]);
testTextAtOffset(acc, BOUNDARY_WORD_END, [
[0, 1, "ab", 0, 2],
[2, 4, " cd", 2, 5],
[5, 7, "\nef", 5, 8],
[8, 11, " gh", 8, 11],
]);
testTextBeforeOffset(acc, BOUNDARY_WORD_END, [
[0, 2, "", 0, 0],
[3, 5, "ab", 0, 2],
// See below for offset 6.
[7, 8, " cd", 2, 5],
[9, 11, "\nef", 5, 8],
]);
testTextBeforeOffset(acc, BOUNDARY_WORD_END, [[6, 6, " cd", 2, 5]]);
testTextAfterOffset(acc, BOUNDARY_WORD_END, [
[0, 2, " cd", 2, 5],
[3, 5, "\nef", 5, 8],
[6, 8, " gh", 8, 11],
[9, 11, "", 11, 11],
]);
testTextAtOffset(acc, BOUNDARY_PARAGRAPH, [
[0, 5, "ab cd\n", 0, 6],
[6, 11, "ef gh", 6, 11],
]);
}
const linksStartEnd = findAccessibleChildByID(docAcc, "linksStartEnd");
testTextAtOffset(linksStartEnd, BOUNDARY_LINE_START, [
[0, 3, `${kEmbedChar}b${kEmbedChar}`, 0, 3],
]);
testTextAtOffset(linksStartEnd, BOUNDARY_WORD_START, [
[0, 3, `${kEmbedChar}b${kEmbedChar}`, 0, 3],
]);
const linksBreaking = findAccessibleChildByID(docAcc, "linksBreaking");
testTextAtOffset(linksBreaking, BOUNDARY_LINE_START, [
[0, 0, `a${kEmbedChar}`, 0, 2],
[1, 1, `a${kEmbedChar}d`, 0, 3],
[2, 3, `${kEmbedChar}d`, 1, 3],
]);
const p = findAccessibleChildByID(docAcc, "p");
testTextAtOffset(p, BOUNDARY_LINE_START, [
[0, 0, "a", 0, 1],
[1, 2, "b", 1, 2],
]);
testTextAtOffset(p, BOUNDARY_PARAGRAPH, [[0, 2, "ab", 0, 2]]);
const leafThenWrap = findAccessibleChildByID(docAcc, "leafThenWrap");
testTextAtOffset(leafThenWrap, BOUNDARY_LINE_START, [
[0, 1, "ab", 0, 2],
[2, 3, "c", 2, 3],
]);
const bidi = findAccessibleChildByID(docAcc, "bidi");
testTextAtOffset(bidi, BOUNDARY_LINE_START, [
[0, 2, "אb ", 0, 3],
[3, 5, "גד ", 3, 6],
[6, 8, "eו ", 6, 9],
[9, 11, "זח ", 9, 12],
[12, 14, "טj", 12, 14],
]);
},
{ chrome: true, topLevel: true, iframe: true, remoteIframe: true }
);
/**
* Test line offsets after text mutation.
*/
addAccessibleTask(
`
<p id="initBr"><br></p>
<p id="rewrap" style="font-family: monospace; width: 2ch; word-break: break-word;"><span id="rewrap1">ac</span>def</p>
`,
async function (browser, docAcc) {
const initBr = findAccessibleChildByID(docAcc, "initBr");
testTextAtOffset(initBr, BOUNDARY_LINE_START, [
[0, 0, "\n", 0, 1],
[1, 1, "", 1, 1],
]);
info("initBr: Inserting text before br");
let reordered = waitForEvent(EVENT_REORDER, initBr);
await invokeContentTask(browser, [], () => {
const initBrNode = content.document.getElementById("initBr");
initBrNode.insertBefore(
content.document.createTextNode("a"),
initBrNode.firstElementChild
);
});
await reordered;
testTextAtOffset(initBr, BOUNDARY_LINE_START, [
[0, 1, "a\n", 0, 2],
[2, 2, "", 2, 2],
]);
const rewrap = findAccessibleChildByID(docAcc, "rewrap");
testTextAtOffset(rewrap, BOUNDARY_LINE_START, [
[0, 1, "ac", 0, 2],
[2, 3, "de", 2, 4],
[4, 5, "f", 4, 5],
]);
info("rewrap: Changing ac to abc");
reordered = waitForEvent(EVENT_REORDER, rewrap);
await invokeContentTask(browser, [], () => {
const rewrap1 = content.document.getElementById("rewrap1");
rewrap1.textContent = "abc";
});
await reordered;
testTextAtOffset(rewrap, BOUNDARY_LINE_START, [
[0, 1, "ab", 0, 2],
[2, 3, "cd", 2, 4],
[4, 6, "ef", 4, 6],
]);
},
{ chrome: true, topLevel: true, iframe: true, remoteIframe: true }
);
/**
* Test retrieval of text offsets when an invalid offset is given.
*/
addAccessibleTask(
`<p id="p">test</p>`,
async function (browser, docAcc) {
const p = findAccessibleChildByID(docAcc, "p");
testTextAtOffset(p, BOUNDARY_LINE_START, [[5, 5, "", 0, 0]]);
testTextBeforeOffset(p, BOUNDARY_LINE_START, [[5, 5, "", 0, 0]]);
testTextAfterOffset(p, BOUNDARY_LINE_START, [[5, 5, "", 0, 0]]);
},
{
// The old HyperTextAccessible implementation doesn't crash, but it returns
// different offsets. This doesn't matter because they're invalid either
// way. Since the new HyperTextAccessibleBase implementation is all we will
// have soon, just test that.
chrome: true,
topLevel: true,
iframe: true,
remoteIframe: true,
}
);
/**
* Test HyperText embedded object methods.
*/
addAccessibleTask(
`<div id="container">a<a id="link" href="https://example.com/">b</a>c</div>`,
async function (browser, docAcc) {
const container = findAccessibleChildByID(docAcc, "container", [
nsIAccessibleHyperText,
]);
is(container.linkCount, 1, "container linkCount is 1");
let link = container.getLinkAt(0);
queryInterfaces(link, [nsIAccessible, nsIAccessibleHyperText]);
is(getAccessibleDOMNodeID(link), "link", "LinkAt 0 is the link");
is(container.getLinkIndex(link), 0, "getLinkIndex for link is 0");
is(link.startIndex, 1, "link's startIndex is 1");
is(link.endIndex, 2, "link's endIndex is 2");
is(container.getLinkIndexAtOffset(1), 0, "getLinkIndexAtOffset(1) is 0");
is(container.getLinkIndexAtOffset(0), -1, "getLinkIndexAtOffset(0) is -1");
is(link.linkCount, 0, "link linkCount is 0");
},
{
chrome: true,
topLevel: true,
iframe: true,
remoteIframe: true,
}
);
/**
* Test HyperText embedded object methods near a list bullet.
*/
addAccessibleTask(
`<ul><li id="li"><a id="link" href="https://example.com/">a</a></li></ul>`,
async function (browser, docAcc) {
const li = findAccessibleChildByID(docAcc, "li", [nsIAccessibleHyperText]);
let link = li.getLinkAt(0);
queryInterfaces(link, [nsIAccessible]);
is(getAccessibleDOMNodeID(link), "link", "LinkAt 0 is the link");
is(li.getLinkIndex(link), 0, "getLinkIndex for link is 0");
is(link.startIndex, 2, "link's startIndex is 2");
is(li.getLinkIndexAtOffset(2), 0, "getLinkIndexAtOffset(2) is 0");
is(li.getLinkIndexAtOffset(0), -1, "getLinkIndexAtOffset(0) is -1");
},
{
chrome: true,
topLevel: true,
iframe: true,
remoteIframe: true,
}
);
const boldAttrs = { "font-weight": "700" };
/**
* Test text attribute methods.
*/
addAccessibleTask(
`
<p id="plain">ab</p>
<p id="bold" style="font-weight: bold;">ab</p>
<p id="partialBold">ab<b>cd</b>ef</p>
<p id="consecutiveBold">ab<b>cd</b><b>ef</b>gh</p>
<p id="embeddedObjs">ab<a href="https://example.com/">cd</a><a href="https://example.com/">ef</a><a href="https://example.com/">gh</a>ij</p>
<p id="empty"></p>
<p id="fontFamilies" style="font-family: sans-serif;">ab<span style="font-family: monospace;">cd</span><span style="font-family: monospace;">ef</span>gh</p>
`,
async function (browser, docAcc) {
let defAttrs = {
"text-position": "baseline",
"font-style": "normal",
"font-weight": "400",
};
const plain = findAccessibleChildByID(docAcc, "plain");
testDefaultTextAttrs(plain, defAttrs, true);
for (let offset = 0; offset <= 2; ++offset) {
testTextAttrs(plain, offset, {}, defAttrs, 0, 2, true);
}
const bold = findAccessibleChildByID(docAcc, "bold");
defAttrs["font-weight"] = "700";
testDefaultTextAttrs(bold, defAttrs, true);
testTextAttrs(bold, 0, {}, defAttrs, 0, 2, true);
const partialBold = findAccessibleChildByID(docAcc, "partialBold");
defAttrs["font-weight"] = "400";
testDefaultTextAttrs(partialBold, defAttrs, true);
testTextAttrs(partialBold, 0, {}, defAttrs, 0, 2, true);
testTextAttrs(partialBold, 2, boldAttrs, defAttrs, 2, 4, true);
testTextAttrs(partialBold, 4, {}, defAttrs, 4, 6, true);
const consecutiveBold = findAccessibleChildByID(docAcc, "consecutiveBold");
testDefaultTextAttrs(consecutiveBold, defAttrs, true);
testTextAttrs(consecutiveBold, 0, {}, defAttrs, 0, 2, true);
testTextAttrs(consecutiveBold, 2, boldAttrs, defAttrs, 2, 6, true);
testTextAttrs(consecutiveBold, 6, {}, defAttrs, 6, 8, true);
const embeddedObjs = findAccessibleChildByID(docAcc, "embeddedObjs");
testDefaultTextAttrs(embeddedObjs, defAttrs, true);
testTextAttrs(embeddedObjs, 0, {}, defAttrs, 0, 2, true);
for (let offset = 2; offset <= 4; ++offset) {
// attrs and defAttrs should be completely empty, so we pass
// false for aSkipUnexpectedAttrs.
testTextAttrs(embeddedObjs, offset, {}, {}, 2, 5, false);
}
testTextAttrs(embeddedObjs, 5, {}, defAttrs, 5, 7, true);
const empty = findAccessibleChildByID(docAcc, "empty");
testDefaultTextAttrs(empty, defAttrs, true);
testTextAttrs(empty, 0, {}, defAttrs, 0, 0, true);
const fontFamilies = findAccessibleChildByID(docAcc, "fontFamilies", [
nsIAccessibleHyperText,
]);
testDefaultTextAttrs(fontFamilies, defAttrs, true);
testTextAttrs(fontFamilies, 0, {}, defAttrs, 0, 2, true);
testTextAttrs(fontFamilies, 2, {}, defAttrs, 2, 6, true);
testTextAttrs(fontFamilies, 6, {}, defAttrs, 6, 8, true);
},
{
chrome: true,
topLevel: true,
iframe: true,
remoteIframe: true,
}
);
/**
* Test cluster offsets.
*/
addAccessibleTask(
`<p id="clusters">À2🤦‍♂️🤦🏼‍♂️5x͇͕̦̍͂͒7È</p>`,
async function testCluster(browser, docAcc) {
const clusters = findAccessibleChildByID(docAcc, "clusters");
testCharacterCount(clusters, 26);
testTextAtOffset(clusters, BOUNDARY_CLUSTER, [
[0, 1, "À", 0, 2],
[2, 2, "2", 2, 3],
[3, 7, "🤦‍♂️", 3, 8],
[8, 14, "🤦🏼‍♂️", 8, 15],
[15, 15, "5", 15, 16],
[16, 22, "x͇͕̦̍͂͒", 16, 23],
[23, 23, "7", 23, 24],
[24, 25, "È", 24, 26],
[26, 26, "", 26, 26],
]);
// Ensure that BOUNDARY_CHAR returns single Unicode characters.
testTextAtOffset(clusters, BOUNDARY_CHAR, [
[0, 0, "A", 0, 1],
[1, 1, "̀", 1, 2],
]);
},
{ chrome: true, topLevel: true }
);