browser_simplePatterns.js (18141B)
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/role.js */ 8 /* import-globals-from ../../../mochitest/states.js */ 9 loadScripts( 10 { name: "role.js", dir: MOCHITESTS_DIR }, 11 { name: "states.js", dir: MOCHITESTS_DIR } 12 ); 13 14 /* eslint-disable camelcase */ 15 const ExpandCollapseState_Collapsed = 0; 16 const ExpandCollapseState_Expanded = 1; 17 const ToggleState_Off = 0; 18 const ToggleState_On = 1; 19 const ToggleState_Indeterminate = 2; 20 /* eslint-enable camelcase */ 21 22 /** 23 * Test the Invoke pattern. 24 */ 25 addUiaTask( 26 ` 27 <button id="button">button</button> 28 <p id="p">p</p> 29 <input id="checkbox" type="checkbox"> 30 <input id="radio" type="radio"> 31 `, 32 async function testInvoke() { 33 await definePyVar("doc", `getDocUia()`); 34 await assignPyVarToUiaWithId("button"); 35 await definePyVar("pattern", `getUiaPattern(button, "Invoke")`); 36 ok(await runPython(`bool(pattern)`), "button has Invoke pattern"); 37 info("Calling Invoke on button"); 38 // The button will get focus when it is clicked. 39 let focused = waitForEvent(EVENT_FOCUS, "button"); 40 // The UIA -> IA2 proxy doesn't fire the Invoked event. 41 if (gIsUiaEnabled) { 42 await setUpWaitForUiaEvent("Invoke_Invoked", "button"); 43 } 44 await runPython(`pattern.Invoke()`); 45 await focused; 46 ok(true, "button got focus"); 47 if (gIsUiaEnabled) { 48 await waitForUiaEvent(); 49 ok(true, "button got Invoked event"); 50 } 51 52 await testPatternAbsent("p", "Invoke"); 53 // The Microsoft IA2 -> UIA proxy doesn't follow Microsoft's own rules. 54 if (gIsUiaEnabled) { 55 // Check boxes expose the Toggle pattern, so they should not expose the 56 // Invoke pattern. 57 await testPatternAbsent("checkbox", "Invoke"); 58 // Ditto for radio buttons. 59 await testPatternAbsent("radio", "Invoke"); 60 } 61 } 62 ); 63 64 /** 65 * Test the Toggle pattern. 66 */ 67 addUiaTask( 68 ` 69 <input id="checkbox" type="checkbox" checked> 70 <button id="toggleButton" aria-pressed="false">toggle</button> 71 <button id="button">button</button> 72 <p id="p">p</p> 73 74 <script> 75 // When checkbox is clicked and it is not checked, make it indeterminate. 76 document.getElementById("checkbox").addEventListener("click", evt => { 77 // Within the event listener, .checked is reversed and you can't set 78 // .indeterminate. Work around this by deferring and handling the changes 79 // ourselves. 80 evt.preventDefault(); 81 const target = evt.target; 82 setTimeout(() => { 83 if (target.checked) { 84 target.checked = false; 85 } else { 86 target.indeterminate = true; 87 } 88 }, 0); 89 }); 90 91 // When toggleButton is clicked, set aria-pressed to true. 92 document.getElementById("toggleButton").addEventListener("click", evt => { 93 evt.target.ariaPressed = "true"; 94 }); 95 </script> 96 `, 97 async function testToggle() { 98 await definePyVar("doc", `getDocUia()`); 99 await assignPyVarToUiaWithId("checkbox"); 100 await definePyVar("pattern", `getUiaPattern(checkbox, "Toggle")`); 101 ok(await runPython(`bool(pattern)`), "checkbox has Toggle pattern"); 102 is( 103 await runPython(`pattern.CurrentToggleState`), 104 ToggleState_On, 105 "checkbox has ToggleState_On" 106 ); 107 // The IA2 -> UIA proxy doesn't fire ToggleState prop change events. 108 if (gIsUiaEnabled) { 109 info("Calling Toggle on checkbox"); 110 await setUpWaitForUiaPropEvent("ToggleToggleState", "checkbox"); 111 await runPython(`pattern.Toggle()`); 112 await waitForUiaEvent(); 113 ok(true, "Got ToggleState prop change event on checkbox"); 114 is( 115 await runPython(`pattern.CurrentToggleState`), 116 ToggleState_Off, 117 "checkbox has ToggleState_Off" 118 ); 119 info("Calling Toggle on checkbox"); 120 await setUpWaitForUiaPropEvent("ToggleToggleState", "checkbox"); 121 await runPython(`pattern.Toggle()`); 122 await waitForUiaEvent(); 123 ok(true, "Got ToggleState prop change event on checkbox"); 124 is( 125 await runPython(`pattern.CurrentToggleState`), 126 ToggleState_Indeterminate, 127 "checkbox has ToggleState_Indeterminate" 128 ); 129 } 130 131 await assignPyVarToUiaWithId("toggleButton"); 132 await definePyVar("pattern", `getUiaPattern(toggleButton, "Toggle")`); 133 ok(await runPython(`bool(pattern)`), "toggleButton has Toggle pattern"); 134 is( 135 await runPython(`pattern.CurrentToggleState`), 136 ToggleState_Off, 137 "toggleButton has ToggleState_Off" 138 ); 139 if (gIsUiaEnabled) { 140 info("Calling Toggle on toggleButton"); 141 await setUpWaitForUiaPropEvent("ToggleToggleState", "toggleButton"); 142 await runPython(`pattern.Toggle()`); 143 await waitForUiaEvent(); 144 ok(true, "Got ToggleState prop change event on toggleButton"); 145 is( 146 await runPython(`pattern.CurrentToggleState`), 147 ToggleState_On, 148 "toggleButton has ToggleState_Off" 149 ); 150 } 151 152 await testPatternAbsent("button", "Toggle"); 153 await testPatternAbsent("p", "Toggle"); 154 } 155 ); 156 157 /** 158 * Test the ExpandCollapse pattern. 159 */ 160 addUiaTask( 161 ` 162 <details> 163 <summary id="summary">summary</summary> 164 details 165 </details> 166 <button id="popup" aria-haspopup="true">popup</button> 167 <button id="button">button</button> 168 <script> 169 // When popup is clicked, set aria-expanded to true. 170 document.getElementById("popup").addEventListener("click", evt => { 171 evt.target.ariaExpanded = "true"; 172 }); 173 </script> 174 `, 175 async function testExpandCollapse() { 176 await definePyVar("doc", `getDocUia()`); 177 await assignPyVarToUiaWithId("summary"); 178 await definePyVar("pattern", `getUiaPattern(summary, "ExpandCollapse")`); 179 ok(await runPython(`bool(pattern)`), "summary has ExpandCollapse pattern"); 180 is( 181 await runPython(`pattern.CurrentExpandCollapseState`), 182 ExpandCollapseState_Collapsed, 183 "summary has ExpandCollapseState_Collapsed" 184 ); 185 // The IA2 -> UIA proxy doesn't fire ToggleState prop change events, nor 186 // does it fail when Expand/Collapse is called on a control which is 187 // already in the desired state. 188 if (gIsUiaEnabled) { 189 info("Calling Expand on summary"); 190 await setUpWaitForUiaPropEvent( 191 "ExpandCollapseExpandCollapseState", 192 "summary" 193 ); 194 await runPython(`pattern.Expand()`); 195 await waitForUiaEvent(); 196 ok( 197 true, 198 "Got ExpandCollapseExpandCollapseState prop change event on summary" 199 ); 200 is( 201 await runPython(`pattern.CurrentExpandCollapseState`), 202 ExpandCollapseState_Expanded, 203 "summary has ExpandCollapseState_Expanded" 204 ); 205 info("Calling Expand on summary"); 206 await testPythonRaises(`pattern.Expand()`, "Expand on summary failed"); 207 info("Calling Collapse on summary"); 208 await setUpWaitForUiaPropEvent( 209 "ExpandCollapseExpandCollapseState", 210 "summary" 211 ); 212 await runPython(`pattern.Collapse()`); 213 await waitForUiaEvent(); 214 ok( 215 true, 216 "Got ExpandCollapseExpandCollapseState prop change event on summary" 217 ); 218 is( 219 await runPython(`pattern.CurrentExpandCollapseState`), 220 ExpandCollapseState_Collapsed, 221 "summary has ExpandCollapseState_Collapsed" 222 ); 223 info("Calling Collapse on summary"); 224 await testPythonRaises( 225 `pattern.Collapse()`, 226 "Collapse on summary failed" 227 ); 228 } 229 230 await assignPyVarToUiaWithId("popup"); 231 // Initially, popup has aria-haspopup but not aria-expanded. That should 232 // be exposed as collapsed. 233 await definePyVar("pattern", `getUiaPattern(popup, "ExpandCollapse")`); 234 ok(await runPython(`bool(pattern)`), "popup has ExpandCollapse pattern"); 235 // The IA2 -> UIA proxy doesn't expose ExpandCollapseState_Collapsed for 236 // aria-haspopup without aria-expanded. 237 if (gIsUiaEnabled) { 238 is( 239 await runPython(`pattern.CurrentExpandCollapseState`), 240 ExpandCollapseState_Collapsed, 241 "popup has ExpandCollapseState_Collapsed" 242 ); 243 info("Calling Expand on popup"); 244 await setUpWaitForUiaPropEvent( 245 "ExpandCollapseExpandCollapseState", 246 "popup" 247 ); 248 await runPython(`pattern.Expand()`); 249 await waitForUiaEvent(); 250 ok( 251 true, 252 "Got ExpandCollapseExpandCollapseState prop change event on popup" 253 ); 254 is( 255 await runPython(`pattern.CurrentExpandCollapseState`), 256 ExpandCollapseState_Expanded, 257 "popup has ExpandCollapseState_Expanded" 258 ); 259 } 260 261 await testPatternAbsent("button", "ExpandCollapse"); 262 } 263 ); 264 265 /** 266 * Test the ScrollItem pattern. 267 */ 268 addUiaTask( 269 ` 270 <hr style="height: 100vh;"> 271 <button id="button">button</button> 272 `, 273 async function testScrollItem(browser, docAcc) { 274 await definePyVar("doc", `getDocUia()`); 275 await assignPyVarToUiaWithId("button"); 276 await definePyVar("pattern", `getUiaPattern(button, "ScrollItem")`); 277 ok(await runPython(`bool(pattern)`), "button has ScrollItem pattern"); 278 const button = findAccessibleChildByID(docAcc, "button"); 279 testStates(button, STATE_OFFSCREEN); 280 info("Calling ScrollIntoView on button"); 281 // UIA doesn't have an event for this. 282 let scrolled = waitForEvent(EVENT_SCROLLING_END, docAcc); 283 await runPython(`pattern.ScrollIntoView()`); 284 await scrolled; 285 ok(true, "Document scrolled"); 286 testStates(button, 0, 0, STATE_OFFSCREEN); 287 } 288 ); 289 290 /** 291 * Test the Value pattern. 292 */ 293 addUiaTask( 294 ` 295 <input id="text" value="before"> 296 <input id="textRo" readonly value="textRo"> 297 <input id="textDis" disabled value="textDis"> 298 <select id="select"><option selected>a</option><option>b</option></select> 299 <progress id="progress" value="0.5"></progress> 300 <input id="range" type="range" aria-valuetext="02:00:00"> 301 <a id="link" href="https://example.com/">Link</a> 302 <div id="ariaTextbox" contenteditable role="textbox">before</div> 303 <button id="button">button</button> 304 `, 305 async function testValue() { 306 await definePyVar("doc", `getDocUia()`); 307 await assignPyVarToUiaWithId("text"); 308 await definePyVar("pattern", `getUiaPattern(text, "Value")`); 309 ok(await runPython(`bool(pattern)`), "text has Value pattern"); 310 ok( 311 !(await runPython(`pattern.CurrentIsReadOnly`)), 312 "text has IsReadOnly false" 313 ); 314 is( 315 await runPython(`pattern.CurrentValue`), 316 "before", 317 "text has correct Value" 318 ); 319 info("SetValue on text"); 320 await setUpWaitForUiaPropEvent("ValueValue", "text"); 321 await runPython(`pattern.SetValue("after")`); 322 await waitForUiaEvent(); 323 ok(true, "Got ValueValue prop change event on text"); 324 is( 325 await runPython(`pattern.CurrentValue`), 326 "after", 327 "text has correct Value" 328 ); 329 330 await assignPyVarToUiaWithId("textRo"); 331 await definePyVar("pattern", `getUiaPattern(textRo, "Value")`); 332 ok(await runPython(`bool(pattern)`), "textRo has Value pattern"); 333 ok( 334 await runPython(`pattern.CurrentIsReadOnly`), 335 "textRo has IsReadOnly true" 336 ); 337 is( 338 await runPython(`pattern.CurrentValue`), 339 "textRo", 340 "textRo has correct Value" 341 ); 342 info("SetValue on textRo"); 343 await testPythonRaises( 344 `pattern.SetValue("after")`, 345 "SetValue on textRo failed" 346 ); 347 348 await assignPyVarToUiaWithId("textDis"); 349 await definePyVar("pattern", `getUiaPattern(textDis, "Value")`); 350 ok(await runPython(`bool(pattern)`), "textDis has Value pattern"); 351 ok( 352 !(await runPython(`pattern.CurrentIsReadOnly`)), 353 "textDis has IsReadOnly false" 354 ); 355 is( 356 await runPython(`pattern.CurrentValue`), 357 "textDis", 358 "textDis has correct Value" 359 ); 360 // The IA2 -> UIA proxy doesn't fail SetValue for a disabled element. 361 if (gIsUiaEnabled) { 362 info("SetValue on textDis"); 363 await testPythonRaises( 364 `pattern.SetValue("after")`, 365 "SetValue on textDis failed" 366 ); 367 } 368 369 await assignPyVarToUiaWithId("select"); 370 await definePyVar("pattern", `getUiaPattern(select, "Value")`); 371 ok(await runPython(`bool(pattern)`), "select has Value pattern"); 372 ok( 373 !(await runPython(`pattern.CurrentIsReadOnly`)), 374 "select has IsReadOnly false" 375 ); 376 is( 377 await runPython(`pattern.CurrentValue`), 378 "a", 379 "select has correct Value" 380 ); 381 info("SetValue on select"); 382 await testPythonRaises( 383 `pattern.SetValue("b")`, 384 "SetValue on select failed" 385 ); 386 387 await assignPyVarToUiaWithId("progress"); 388 await definePyVar("pattern", `getUiaPattern(progress, "Value")`); 389 ok(await runPython(`bool(pattern)`), "progress has Value pattern"); 390 ok( 391 await runPython(`pattern.CurrentIsReadOnly`), 392 "progress has IsReadOnly true" 393 ); 394 is( 395 await runPython(`pattern.CurrentValue`), 396 "50%", 397 "progress has correct Value" 398 ); 399 info("SetValue on progress"); 400 await testPythonRaises( 401 `pattern.SetValue("60%")`, 402 "SetValue on progress failed" 403 ); 404 405 await assignPyVarToUiaWithId("range"); 406 await definePyVar("pattern", `getUiaPattern(range, "Value")`); 407 ok(await runPython(`bool(pattern)`), "range has Value pattern"); 408 is( 409 await runPython(`pattern.CurrentValue`), 410 "02:00:00", 411 "range has correct Value" 412 ); 413 414 await assignPyVarToUiaWithId("link"); 415 await definePyVar("pattern", `getUiaPattern(link, "Value")`); 416 ok(await runPython(`bool(pattern)`), "link has Value pattern"); 417 is( 418 await runPython(`pattern.CurrentValue`), 419 "https://example.com/", 420 "link has correct Value" 421 ); 422 423 await assignPyVarToUiaWithId("ariaTextbox"); 424 await definePyVar("pattern", `getUiaPattern(ariaTextbox, "Value")`); 425 ok(await runPython(`bool(pattern)`), "ariaTextbox has Value pattern"); 426 ok( 427 !(await runPython(`pattern.CurrentIsReadOnly`)), 428 "ariaTextbox has IsReadOnly false" 429 ); 430 is( 431 await runPython(`pattern.CurrentValue`), 432 "before", 433 "ariaTextbox has correct Value" 434 ); 435 info("SetValue on ariaTextbox"); 436 await setUpWaitForUiaPropEvent("ValueValue", "ariaTextbox"); 437 await runPython(`pattern.SetValue("after")`); 438 await waitForUiaEvent(); 439 ok(true, "Got ValueValue prop change event on ariaTextbox"); 440 is( 441 await runPython(`pattern.CurrentValue`), 442 "after", 443 "ariaTextbox has correct Value" 444 ); 445 446 await testPatternAbsent("button", "Value"); 447 } 448 ); 449 450 /** 451 * Test the Value pattern on a document. 452 */ 453 addUiaTask(``, async function testValueDoc(browser) { 454 // A test snippet is a data: URI. The accessibility engine won't return these. 455 let url = new URL("https://example.net/document-builder.sjs"); 456 url.searchParams.append("html", `<body id=${DEFAULT_CONTENT_DOC_BODY_ID}>`); 457 let loaded = waitForEvent( 458 EVENT_DOCUMENT_LOAD_COMPLETE, 459 DEFAULT_CONTENT_DOC_BODY_ID 460 ); 461 BrowserTestUtils.startLoadingURIString(browser, url.href); 462 await loaded; 463 await definePyVar("doc", `getDocUia()`); 464 await definePyVar("pattern", `getUiaPattern(doc, "Value")`); 465 ok(await runPython(`bool(pattern)`), "doc has Value pattern"); 466 is( 467 await runPython(`pattern.CurrentValue`), 468 url.href, 469 "doc has correct Value" 470 ); 471 }); 472 473 async function testRangeValueProps(id, ro, val, min, max, small, large) { 474 await assignPyVarToUiaWithId(id); 475 await definePyVar("pattern", `getUiaPattern(${id}, "RangeValue")`); 476 ok(await runPython(`bool(pattern)`), `${id} has RangeValue pattern`); 477 is( 478 !!(await runPython(`pattern.CurrentIsReadOnly`)), 479 ro, 480 `${id} has IsReadOnly ${ro}` 481 ); 482 is(await runPython(`pattern.CurrentValue`), val, `${id} has correct Value`); 483 is( 484 await runPython(`pattern.CurrentMinimum`), 485 min, 486 `${id} has correct Minimum` 487 ); 488 is( 489 await runPython(`pattern.CurrentMaximum`), 490 max, 491 `${id} has correct Maximum` 492 ); 493 // IA2 doesn't support small/large change, so the IA2 -> UIA proxy can't 494 // either. 495 if (gIsUiaEnabled) { 496 is( 497 await runPython(`pattern.CurrentSmallChange`), 498 small, 499 `${id} has correct SmallChange` 500 ); 501 is( 502 await runPython(`pattern.CurrentLargeChange`), 503 large, 504 `${id} has correct LargeChange` 505 ); 506 } 507 } 508 509 /** 510 * Test the RangeValue pattern. 511 */ 512 addUiaTask( 513 ` 514 <input id="range" type="range"> 515 <input id="rangeBig" type="range" max="1000"> 516 <progress id="progress" value="0.5"></progress> 517 <input id="numberRo" type="number" min="0" max="10" value="5" readonly> 518 <div id="ariaSlider" role="slider">slider</div> 519 <button id="button">button</button> 520 `, 521 async function testRangeValue(browser) { 522 await definePyVar("doc", `getDocUia()`); 523 await testRangeValueProps("range", false, 50, 0, 100, 1, 10); 524 info("SetValue on range"); 525 await setUpWaitForUiaPropEvent("RangeValueValue", "range"); 526 await runPython(`pattern.SetValue(20)`); 527 await waitForUiaEvent(); 528 ok(true, "Got RangeValueValue prop change event on range"); 529 is(await runPython(`pattern.CurrentValue`), 20, "range has correct Value"); 530 531 await testRangeValueProps("rangeBig", false, 500, 0, 1000, 1, 100); 532 533 await testRangeValueProps("progress", true, 0.5, 0, 1, 0, 0.1); 534 info("Calling SetValue on progress"); 535 await testPythonRaises( 536 `pattern.SetValue(0.6)`, 537 "SetValue on progress failed" 538 ); 539 540 await testRangeValueProps("numberRo", true, 5, 0, 10, 1, 1); 541 info("Calling SetValue on numberRo"); 542 await testPythonRaises( 543 `pattern.SetValue(6)`, 544 "SetValue on numberRo failed" 545 ); 546 547 await testRangeValueProps("ariaSlider", false, 50, 0, 100, null, null); 548 info("Setting aria-valuenow on ariaSlider"); 549 await setUpWaitForUiaPropEvent("RangeValueValue", "ariaSlider"); 550 await invokeSetAttribute(browser, "ariaSlider", "aria-valuenow", "60"); 551 await waitForUiaEvent(); 552 ok(true, "Got RangeValueValue prop change event on ariaSlider"); 553 is( 554 await runPython(`pattern.CurrentValue`), 555 60, 556 "ariaSlider has correct Value" 557 ); 558 559 await testPatternAbsent("button", "RangeValue"); 560 } 561 );