browser_text.js (13013B)
1 /* This Source Code Form is subject to the terms of the Mozilla Public 2 * License, v. 2.0. If a copy of the MPL was not distributed with this 3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 5 "use strict"; 6 7 /* import-globals-from ../../mochitest/attributes.js */ 8 /* import-globals-from ../../mochitest/text.js */ 9 loadScripts({ name: "attributes.js", dir: MOCHITESTS_DIR }); 10 11 /** 12 * Test line and word offsets for various cases for both local and remote 13 * Accessibles. There is more extensive coverage in ../../mochitest/text. These 14 * tests don't need to duplicate all of that, since much of the underlying code 15 * is unified. They should ensure that the cache works as expected and that 16 * there is consistency between local and remote. 17 */ 18 addAccessibleTask( 19 ` 20 <p id="br">ab cd<br>ef gh</p> 21 <pre id="pre">ab cd 22 ef gh</pre> 23 <p id="linksStartEnd"><a href="https://example.com/">a</a>b<a href="https://example.com/">c</a></p> 24 <p id="linksBreaking">a<a href="https://example.com/">b<br>c</a>d</p> 25 <p id="p">a<br role="presentation">b</p> 26 <p id="leafThenWrap" style="font-family: monospace; width: 2ch; word-break: break-word;"><span>a</span>bc</p> 27 <p id="bidi" style="font-family: monospace; width: 3ch; word-break: break-word">אb גד eו זח טj</p> 28 `, 29 async function (browser, docAcc) { 30 for (const id of ["br", "pre"]) { 31 const acc = findAccessibleChildByID(docAcc, id); 32 testCharacterCount([acc], 11); 33 testTextAtOffset(acc, BOUNDARY_LINE_START, [ 34 [0, 5, "ab cd\n", 0, 6], 35 [6, 11, "ef gh", 6, 11], 36 ]); 37 testTextBeforeOffset(acc, BOUNDARY_LINE_START, [ 38 [0, 5, "", 0, 0], 39 [6, 11, "ab cd\n", 0, 6], 40 ]); 41 testTextAfterOffset(acc, BOUNDARY_LINE_START, [ 42 [0, 5, "ef gh", 6, 11], 43 [6, 11, "", 11, 11], 44 ]); 45 testTextAtOffset(acc, BOUNDARY_LINE_END, [ 46 [0, 5, "ab cd", 0, 5], 47 [6, 11, "\nef gh", 5, 11], 48 ]); 49 testTextBeforeOffset(acc, BOUNDARY_LINE_END, [ 50 [0, 5, "", 0, 0], 51 [6, 11, "ab cd", 0, 5], 52 ]); 53 testTextAfterOffset(acc, BOUNDARY_LINE_END, [ 54 [0, 5, "\nef gh", 5, 11], 55 [6, 11, "", 11, 11], 56 ]); 57 testTextAtOffset(acc, BOUNDARY_WORD_START, [ 58 [0, 2, "ab ", 0, 3], 59 [3, 5, "cd\n", 3, 6], 60 [6, 8, "ef ", 6, 9], 61 [9, 11, "gh", 9, 11], 62 ]); 63 testTextBeforeOffset(acc, BOUNDARY_WORD_START, [ 64 [0, 2, "", 0, 0], 65 [3, 5, "ab ", 0, 3], 66 [6, 8, "cd\n", 3, 6], 67 [9, 11, "ef ", 6, 9], 68 ]); 69 testTextAfterOffset(acc, BOUNDARY_WORD_START, [ 70 [0, 2, "cd\n", 3, 6], 71 [3, 5, "ef ", 6, 9], 72 [6, 8, "gh", 9, 11], 73 [9, 11, "", 11, 11], 74 ]); 75 testTextAtOffset(acc, BOUNDARY_WORD_END, [ 76 [0, 1, "ab", 0, 2], 77 [2, 4, " cd", 2, 5], 78 [5, 7, "\nef", 5, 8], 79 [8, 11, " gh", 8, 11], 80 ]); 81 testTextBeforeOffset(acc, BOUNDARY_WORD_END, [ 82 [0, 2, "", 0, 0], 83 [3, 5, "ab", 0, 2], 84 // See below for offset 6. 85 [7, 8, " cd", 2, 5], 86 [9, 11, "\nef", 5, 8], 87 ]); 88 testTextBeforeOffset(acc, BOUNDARY_WORD_END, [[6, 6, " cd", 2, 5]]); 89 testTextAfterOffset(acc, BOUNDARY_WORD_END, [ 90 [0, 2, " cd", 2, 5], 91 [3, 5, "\nef", 5, 8], 92 [6, 8, " gh", 8, 11], 93 [9, 11, "", 11, 11], 94 ]); 95 testTextAtOffset(acc, BOUNDARY_PARAGRAPH, [ 96 [0, 5, "ab cd\n", 0, 6], 97 [6, 11, "ef gh", 6, 11], 98 ]); 99 } 100 const linksStartEnd = findAccessibleChildByID(docAcc, "linksStartEnd"); 101 testTextAtOffset(linksStartEnd, BOUNDARY_LINE_START, [ 102 [0, 3, `${kEmbedChar}b${kEmbedChar}`, 0, 3], 103 ]); 104 testTextAtOffset(linksStartEnd, BOUNDARY_WORD_START, [ 105 [0, 3, `${kEmbedChar}b${kEmbedChar}`, 0, 3], 106 ]); 107 const linksBreaking = findAccessibleChildByID(docAcc, "linksBreaking"); 108 testTextAtOffset(linksBreaking, BOUNDARY_LINE_START, [ 109 [0, 0, `a${kEmbedChar}`, 0, 2], 110 [1, 1, `a${kEmbedChar}d`, 0, 3], 111 [2, 3, `${kEmbedChar}d`, 1, 3], 112 ]); 113 testTextAtOffset(linksBreaking, BOUNDARY_WORD_START, [ 114 [0, 0, `a${kEmbedChar}`, 0, 2], 115 [1, 1, `a${kEmbedChar}d`, 0, 3], 116 [2, 3, `${kEmbedChar}d`, 1, 3], 117 ]); 118 const p = findAccessibleChildByID(docAcc, "p"); 119 testTextAtOffset(p, BOUNDARY_LINE_START, [ 120 [0, 0, "a", 0, 1], 121 [1, 2, "b", 1, 2], 122 ]); 123 testTextAtOffset(p, BOUNDARY_PARAGRAPH, [[0, 2, "ab", 0, 2]]); 124 const leafThenWrap = findAccessibleChildByID(docAcc, "leafThenWrap"); 125 testTextAtOffset(leafThenWrap, BOUNDARY_LINE_START, [ 126 [0, 1, "ab", 0, 2], 127 [2, 3, "c", 2, 3], 128 ]); 129 const bidi = findAccessibleChildByID(docAcc, "bidi"); 130 testTextAtOffset(bidi, BOUNDARY_LINE_START, [ 131 [0, 2, "אb ", 0, 3], 132 [3, 5, "גד ", 3, 6], 133 [6, 8, "eו ", 6, 9], 134 [9, 11, "זח ", 9, 12], 135 [12, 14, "טj", 12, 14], 136 ]); 137 }, 138 { chrome: true, topLevel: true, iframe: true, remoteIframe: true } 139 ); 140 141 /** 142 * Test line offsets after text mutation. 143 */ 144 addAccessibleTask( 145 ` 146 <p id="initBr"><br></p> 147 <p id="rewrap" style="font-family: monospace; width: 2ch; word-break: break-word;"><span id="rewrap1">ac</span>def</p> 148 `, 149 async function (browser, docAcc) { 150 const initBr = findAccessibleChildByID(docAcc, "initBr"); 151 testTextAtOffset(initBr, BOUNDARY_LINE_START, [ 152 [0, 0, "\n", 0, 1], 153 [1, 1, "", 1, 1], 154 ]); 155 info("initBr: Inserting text before br"); 156 let reordered = waitForEvent(EVENT_REORDER, initBr); 157 await invokeContentTask(browser, [], () => { 158 const initBrNode = content.document.getElementById("initBr"); 159 initBrNode.insertBefore( 160 content.document.createTextNode("a"), 161 initBrNode.firstElementChild 162 ); 163 }); 164 await reordered; 165 testTextAtOffset(initBr, BOUNDARY_LINE_START, [ 166 [0, 1, "a\n", 0, 2], 167 [2, 2, "", 2, 2], 168 ]); 169 170 const rewrap = findAccessibleChildByID(docAcc, "rewrap"); 171 testTextAtOffset(rewrap, BOUNDARY_LINE_START, [ 172 [0, 1, "ac", 0, 2], 173 [2, 3, "de", 2, 4], 174 [4, 5, "f", 4, 5], 175 ]); 176 info("rewrap: Changing ac to abc"); 177 reordered = waitForEvent(EVENT_REORDER, rewrap); 178 await invokeContentTask(browser, [], () => { 179 const rewrap1 = content.document.getElementById("rewrap1"); 180 rewrap1.textContent = "abc"; 181 }); 182 await reordered; 183 testTextAtOffset(rewrap, BOUNDARY_LINE_START, [ 184 [0, 1, "ab", 0, 2], 185 [2, 3, "cd", 2, 4], 186 [4, 6, "ef", 4, 6], 187 ]); 188 }, 189 { chrome: true, topLevel: true, iframe: true, remoteIframe: true } 190 ); 191 192 /** 193 * Test retrieval of text offsets when an invalid offset is given. 194 */ 195 addAccessibleTask( 196 `<p id="p">test</p>`, 197 async function (browser, docAcc) { 198 const p = findAccessibleChildByID(docAcc, "p"); 199 testTextAtOffset(p, BOUNDARY_LINE_START, [[5, 5, "", 0, 0]]); 200 testTextBeforeOffset(p, BOUNDARY_LINE_START, [[5, 5, "", 0, 0]]); 201 testTextAfterOffset(p, BOUNDARY_LINE_START, [[5, 5, "", 0, 0]]); 202 }, 203 { 204 // The old HyperTextAccessible implementation doesn't crash, but it returns 205 // different offsets. This doesn't matter because they're invalid either 206 // way. Since the new HyperTextAccessibleBase implementation is all we will 207 // have soon, just test that. 208 chrome: true, 209 topLevel: true, 210 iframe: true, 211 remoteIframe: true, 212 } 213 ); 214 215 /** 216 * Test HyperText embedded object methods. 217 */ 218 addAccessibleTask( 219 `<div id="container">a<a id="link" href="https://example.com/">b</a>c</div>`, 220 async function (browser, docAcc) { 221 const container = findAccessibleChildByID(docAcc, "container", [ 222 nsIAccessibleHyperText, 223 ]); 224 is(container.linkCount, 1, "container linkCount is 1"); 225 let link = container.getLinkAt(0); 226 queryInterfaces(link, [nsIAccessible, nsIAccessibleHyperText]); 227 is(getAccessibleDOMNodeID(link), "link", "LinkAt 0 is the link"); 228 is(container.getLinkIndex(link), 0, "getLinkIndex for link is 0"); 229 is(link.startIndex, 1, "link's startIndex is 1"); 230 is(link.endIndex, 2, "link's endIndex is 2"); 231 is(container.getLinkIndexAtOffset(1), 0, "getLinkIndexAtOffset(1) is 0"); 232 is(container.getLinkIndexAtOffset(0), -1, "getLinkIndexAtOffset(0) is -1"); 233 is(link.linkCount, 0, "link linkCount is 0"); 234 }, 235 { 236 chrome: true, 237 topLevel: true, 238 iframe: true, 239 remoteIframe: true, 240 } 241 ); 242 243 /** 244 * Test HyperText embedded object methods near a list bullet. 245 */ 246 addAccessibleTask( 247 `<ul><li id="li"><a id="link" href="https://example.com/">a</a></li></ul>`, 248 async function (browser, docAcc) { 249 const li = findAccessibleChildByID(docAcc, "li", [nsIAccessibleHyperText]); 250 let link = li.getLinkAt(0); 251 queryInterfaces(link, [nsIAccessible]); 252 is(getAccessibleDOMNodeID(link), "link", "LinkAt 0 is the link"); 253 is(li.getLinkIndex(link), 0, "getLinkIndex for link is 0"); 254 is(link.startIndex, 2, "link's startIndex is 2"); 255 is(li.getLinkIndexAtOffset(2), 0, "getLinkIndexAtOffset(2) is 0"); 256 is(li.getLinkIndexAtOffset(0), -1, "getLinkIndexAtOffset(0) is -1"); 257 }, 258 { 259 chrome: true, 260 topLevel: true, 261 iframe: true, 262 remoteIframe: true, 263 } 264 ); 265 266 const boldAttrs = { "font-weight": "700" }; 267 268 /** 269 * Test text attribute methods. 270 */ 271 addAccessibleTask( 272 ` 273 <p id="plain">ab</p> 274 <p id="bold" style="font-weight: bold;">ab</p> 275 <p id="partialBold">ab<b>cd</b>ef</p> 276 <p id="consecutiveBold">ab<b>cd</b><b>ef</b>gh</p> 277 <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> 278 <p id="empty"></p> 279 <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> 280 `, 281 async function (browser, docAcc) { 282 let defAttrs = { 283 "text-position": "baseline", 284 "font-style": "normal", 285 "font-weight": "400", 286 }; 287 288 const plain = findAccessibleChildByID(docAcc, "plain"); 289 testDefaultTextAttrs(plain, defAttrs, true); 290 for (let offset = 0; offset <= 2; ++offset) { 291 testTextAttrs(plain, offset, {}, defAttrs, 0, 2, true); 292 } 293 294 const bold = findAccessibleChildByID(docAcc, "bold"); 295 defAttrs["font-weight"] = "700"; 296 testDefaultTextAttrs(bold, defAttrs, true); 297 testTextAttrs(bold, 0, {}, defAttrs, 0, 2, true); 298 299 const partialBold = findAccessibleChildByID(docAcc, "partialBold"); 300 defAttrs["font-weight"] = "400"; 301 testDefaultTextAttrs(partialBold, defAttrs, true); 302 testTextAttrs(partialBold, 0, {}, defAttrs, 0, 2, true); 303 testTextAttrs(partialBold, 2, boldAttrs, defAttrs, 2, 4, true); 304 testTextAttrs(partialBold, 4, {}, defAttrs, 4, 6, true); 305 306 const consecutiveBold = findAccessibleChildByID(docAcc, "consecutiveBold"); 307 testDefaultTextAttrs(consecutiveBold, defAttrs, true); 308 testTextAttrs(consecutiveBold, 0, {}, defAttrs, 0, 2, true); 309 testTextAttrs(consecutiveBold, 2, boldAttrs, defAttrs, 2, 6, true); 310 testTextAttrs(consecutiveBold, 6, {}, defAttrs, 6, 8, true); 311 312 const embeddedObjs = findAccessibleChildByID(docAcc, "embeddedObjs"); 313 testDefaultTextAttrs(embeddedObjs, defAttrs, true); 314 testTextAttrs(embeddedObjs, 0, {}, defAttrs, 0, 2, true); 315 for (let offset = 2; offset <= 4; ++offset) { 316 // attrs and defAttrs should be completely empty, so we pass 317 // false for aSkipUnexpectedAttrs. 318 testTextAttrs(embeddedObjs, offset, {}, {}, 2, 5, false); 319 } 320 testTextAttrs(embeddedObjs, 5, {}, defAttrs, 5, 7, true); 321 322 const empty = findAccessibleChildByID(docAcc, "empty"); 323 testDefaultTextAttrs(empty, defAttrs, true); 324 testTextAttrs(empty, 0, {}, defAttrs, 0, 0, true); 325 326 const fontFamilies = findAccessibleChildByID(docAcc, "fontFamilies", [ 327 nsIAccessibleHyperText, 328 ]); 329 testDefaultTextAttrs(fontFamilies, defAttrs, true); 330 testTextAttrs(fontFamilies, 0, {}, defAttrs, 0, 2, true); 331 testTextAttrs(fontFamilies, 2, {}, defAttrs, 2, 6, true); 332 testTextAttrs(fontFamilies, 6, {}, defAttrs, 6, 8, true); 333 }, 334 { 335 chrome: true, 336 topLevel: true, 337 iframe: true, 338 remoteIframe: true, 339 } 340 ); 341 342 /** 343 * Test cluster offsets. 344 */ 345 addAccessibleTask( 346 `<p id="clusters">À2🤦♂️🤦🏼♂️5x͇͕̦̍͂͒7È</p>`, 347 async function testCluster(browser, docAcc) { 348 const clusters = findAccessibleChildByID(docAcc, "clusters"); 349 testCharacterCount(clusters, 26); 350 testTextAtOffset(clusters, BOUNDARY_CLUSTER, [ 351 [0, 1, "À", 0, 2], 352 [2, 2, "2", 2, 3], 353 [3, 7, "🤦♂️", 3, 8], 354 [8, 14, "🤦🏼♂️", 8, 15], 355 [15, 15, "5", 15, 16], 356 [16, 22, "x͇͕̦̍͂͒", 16, 23], 357 [23, 23, "7", 23, 24], 358 [24, 25, "È", 24, 26], 359 [26, 26, "", 26, 26], 360 ]); 361 // Ensure that BOUNDARY_CHAR returns single Unicode characters. 362 testTextAtOffset(clusters, BOUNDARY_CHAR, [ 363 [0, 0, "A", 0, 1], 364 [1, 1, "̀", 1, 2], 365 ]); 366 }, 367 { chrome: true, topLevel: true } 368 );