/* 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 ♂️♂️5 x͇͕̦̍͂͒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 }
);
Messung V0.5 in Prozent C=96 H=100 G=97
¤ Dauer der Verarbeitung: 0.30 Sekunden
(vorverarbeitet am 2026-06-10)
¤
*© Formatika GbR, Deutschland