browser_caching_states.js (28418B)
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 requestLongerTimeout(2); 7 8 /* import-globals-from ../../mochitest/role.js */ 9 /* import-globals-from ../../mochitest/states.js */ 10 loadScripts( 11 { name: "role.js", dir: MOCHITESTS_DIR }, 12 { name: "states.js", dir: MOCHITESTS_DIR } 13 ); 14 15 /** 16 * Test data has the format of: 17 * { 18 * desc {String} description for better logging 19 * expected {Array} expected states for a given accessible that have the 20 * following format: 21 * [ 22 * expected state, 23 * expected extra state, 24 * absent state, 25 * absent extra state 26 * ] 27 * attrs {?Array} an optional list of attributes to update 28 * } 29 */ 30 31 // State caching tests for attribute changes 32 const attributeTests = [ 33 { 34 desc: 35 "Checkbox with @checked attribute set to true should have checked " + 36 "state", 37 attrs: [ 38 { 39 attr: "checked", 40 value: "true", 41 }, 42 ], 43 expected: [STATE_CHECKED, 0], 44 }, 45 { 46 desc: "Checkbox with no @checked attribute should not have checked state", 47 attrs: [ 48 { 49 attr: "checked", 50 }, 51 ], 52 expected: [0, 0, STATE_CHECKED], 53 }, 54 ]; 55 56 // State caching tests for ARIA changes 57 const ariaTests = [ 58 { 59 desc: "File input has busy state when @aria-busy attribute is set to true", 60 attrs: [ 61 { 62 attr: "aria-busy", 63 value: "true", 64 }, 65 ], 66 expected: [STATE_BUSY, 0, STATE_REQUIRED | STATE_INVALID], 67 }, 68 { 69 desc: 70 "File input has required state when @aria-required attribute is set " + 71 "to true", 72 attrs: [ 73 { 74 attr: "aria-required", 75 value: "true", 76 }, 77 ], 78 expected: [STATE_REQUIRED, 0, STATE_INVALID], 79 }, 80 { 81 desc: 82 "File input has invalid state when @aria-invalid attribute is set to " + 83 "true", 84 attrs: [ 85 { 86 attr: "aria-invalid", 87 value: "true", 88 }, 89 ], 90 expected: [STATE_INVALID, 0], 91 }, 92 ]; 93 94 // Extra state caching tests 95 const extraStateTests = [ 96 { 97 desc: 98 "Input has no extra enabled state when aria and native disabled " + 99 "attributes are set at once", 100 attrs: [ 101 { 102 attr: "aria-disabled", 103 value: "true", 104 }, 105 { 106 attr: "disabled", 107 value: "true", 108 }, 109 ], 110 expected: [0, 0, 0, EXT_STATE_ENABLED], 111 }, 112 { 113 desc: 114 "Input has an extra enabled state when aria and native disabled " + 115 "attributes are unset at once", 116 attrs: [ 117 { 118 attr: "aria-disabled", 119 }, 120 { 121 attr: "disabled", 122 }, 123 ], 124 expected: [0, EXT_STATE_ENABLED], 125 }, 126 ]; 127 128 async function runStateTests(browser, accDoc, id, tests) { 129 let acc = findAccessibleChildByID(accDoc, id); 130 for (let { desc, attrs, expected } of tests) { 131 const [expState, expExtState, absState, absExtState] = expected; 132 info(desc); 133 let onUpdate = waitForEvent(EVENT_STATE_CHANGE, evt => { 134 if (getAccessibleDOMNodeID(evt.accessible) != id) { 135 return false; 136 } 137 // Events can be fired for states other than the ones we're interested 138 // in. If this happens, the states we're expecting might not be exposed 139 // yet. 140 const scEvt = evt.QueryInterface(nsIAccessibleStateChangeEvent); 141 if (scEvt.isExtraState) { 142 if (scEvt.state & expExtState || scEvt.state & absExtState) { 143 return true; 144 } 145 return false; 146 } 147 return scEvt.state & expState || scEvt.state & absState; 148 }); 149 for (let { attr, value } of attrs) { 150 await invokeSetAttribute(browser, id, attr, value); 151 } 152 await onUpdate; 153 testStates(acc, ...expected); 154 } 155 } 156 157 /** 158 * Test caching of accessible object states 159 */ 160 addAccessibleTask( 161 ` 162 <input id="checkbox" type="checkbox"> 163 <input id="file" type="file"> 164 <input id="text">`, 165 async function (browser, accDoc) { 166 await runStateTests(browser, accDoc, "checkbox", attributeTests); 167 await runStateTests(browser, accDoc, "file", ariaTests); 168 await runStateTests(browser, accDoc, "text", extraStateTests); 169 }, 170 { iframe: true, remoteIframe: true } 171 ); 172 173 /** 174 * Test caching of the focused state. 175 */ 176 addAccessibleTask( 177 ` 178 <button id="b1">b1</button> 179 <button id="b2">b2</button> 180 `, 181 async function (browser, docAcc) { 182 const b1 = findAccessibleChildByID(docAcc, "b1"); 183 const b2 = findAccessibleChildByID(docAcc, "b2"); 184 185 let focused = waitForEvent(EVENT_FOCUS, b1); 186 await invokeFocus(browser, "b1"); 187 await focused; 188 testStates(docAcc, 0, 0, STATE_FOCUSED); 189 testStates(b1, STATE_FOCUSED); 190 testStates(b2, 0, 0, STATE_FOCUSED); 191 192 focused = waitForEvent(EVENT_FOCUS, b2); 193 await invokeFocus(browser, "b2"); 194 await focused; 195 testStates(b2, STATE_FOCUSED); 196 testStates(b1, 0, 0, STATE_FOCUSED); 197 }, 198 { iframe: true, remoteIframe: true } 199 ); 200 201 /** 202 * Test that the document initially gets the focused state. 203 * We can't do this in the test above because that test runs in iframes as well 204 * as a top level document. 205 */ 206 addAccessibleTask( 207 ` 208 <button id="b1">b1</button> 209 <button id="b2">b2</button> 210 `, 211 async function (browser, docAcc) { 212 testStates(docAcc, STATE_FOCUSED); 213 } 214 ); 215 216 /** 217 * Test caching of the focused state in iframes. 218 */ 219 addAccessibleTask( 220 ` 221 <button id="button">button</button> 222 `, 223 async function (browser, iframeDocAcc, topDocAcc) { 224 testStates(topDocAcc, STATE_FOCUSED); 225 const button = findAccessibleChildByID(iframeDocAcc, "button"); 226 testStates(button, 0, 0, STATE_FOCUSED); 227 let focused = waitForEvent(EVENT_FOCUS, button); 228 info("Focusing button in iframe"); 229 button.takeFocus(); 230 await focused; 231 testStates(topDocAcc, 0, 0, STATE_FOCUSED); 232 testStates(button, STATE_FOCUSED); 233 }, 234 { topLevel: false, iframe: true, remoteIframe: true } 235 ); 236 237 /** 238 * Test caching of the focusable state in iframes which are initially visibility: hidden. 239 */ 240 addAccessibleTask( 241 ` 242 <button id="button"></button> 243 <span id="span" tabindex="-1">span</span>`, 244 async function (browser, topDocAcc) { 245 info("Changing visibility on iframe"); 246 let reordered = waitForEvent(EVENT_REORDER, topDocAcc); 247 await SpecialPowers.spawn(browser, [DEFAULT_IFRAME_ID], iframeId => { 248 content.document.getElementById(iframeId).style.visibility = ""; 249 }); 250 await reordered; 251 // The iframe doc a11y tree might not be built yet. 252 const iframeDoc = await TestUtils.waitForCondition(() => 253 findAccessibleChildByID(topDocAcc, DEFAULT_IFRAME_DOC_BODY_ID) 254 ); 255 // Log/verify whether this is an in-process or OOP iframe. 256 await comparePIDs(browser, gIsRemoteIframe); 257 const button = findAccessibleChildByID(iframeDoc, "button"); 258 testStates(button, STATE_FOCUSABLE); 259 const span = findAccessibleChildByID(iframeDoc, "span"); 260 ok(span, "span Accessible exists"); 261 testStates(span, STATE_FOCUSABLE); 262 }, 263 { 264 topLevel: false, 265 iframe: true, 266 remoteIframe: true, 267 iframeAttrs: { style: "visibility: hidden;" }, 268 skipFissionDocLoad: true, 269 } 270 ); 271 272 function checkOpacity(acc, present) { 273 let [, extraState] = getStates(acc); 274 let currOpacity = extraState & EXT_STATE_OPAQUE; 275 return present ? currOpacity : !currOpacity; 276 } 277 278 /** 279 * Test caching of the OPAQUE1 state. 280 */ 281 addAccessibleTask( 282 ` 283 <div id="div">hello world</div> 284 `, 285 async function (browser, docAcc) { 286 const div = findAccessibleChildByID(docAcc, "div"); 287 await untilCacheOk(() => checkOpacity(div, true), "Found opaque state"); 288 289 await invokeContentTask(browser, [], () => { 290 let elm = content.document.getElementById("div"); 291 elm.style = "opacity: 0.4;"; 292 elm.offsetTop; // Flush layout. 293 }); 294 295 await untilCacheOk( 296 () => checkOpacity(div, false), 297 "Did not find opaque state" 298 ); 299 300 await invokeContentTask(browser, [], () => { 301 let elm = content.document.getElementById("div"); 302 elm.style = "opacity: 1;"; 303 elm.offsetTop; // Flush layout. 304 }); 305 306 await untilCacheOk(() => checkOpacity(div, true), "Found opaque state"); 307 }, 308 { iframe: true, remoteIframe: true, chrome: true } 309 ); 310 311 /** 312 * Test caching of the editable state. 313 */ 314 addAccessibleTask( 315 ` 316 <div id="div" contenteditable><p id="p">hello</p></div> 317 <input id="input"> 318 `, 319 async function (browser, docAcc) { 320 const div = findAccessibleChildByID(docAcc, "div"); 321 const p = findAccessibleChildByID(docAcc, "p"); 322 testStates(div, 0, EXT_STATE_EDITABLE, 0, 0); 323 testStates(p, 0, EXT_STATE_EDITABLE, 0, 0); 324 // Ensure that a contentEditable descendant doesn't cause editable to be 325 // exposed on the document. 326 testStates(docAcc, STATE_READONLY, 0, 0, EXT_STATE_EDITABLE); 327 328 info("Setting contentEditable on the body"); 329 let stateChanged = Promise.all([ 330 waitForStateChange(docAcc, EXT_STATE_EDITABLE, true, true), 331 waitForStateChange(docAcc, STATE_READONLY, false, false), 332 ]); 333 await invokeContentTask(browser, [], () => { 334 content.document.body.contentEditable = true; 335 }); 336 await stateChanged; 337 testStates(docAcc, 0, EXT_STATE_EDITABLE, STATE_READONLY, 0); 338 339 info("Clearing contentEditable on the body"); 340 stateChanged = Promise.all([ 341 waitForStateChange(docAcc, EXT_STATE_EDITABLE, false, true), 342 waitForStateChange(docAcc, STATE_READONLY, true, false), 343 ]); 344 await invokeContentTask(browser, [], () => { 345 content.document.body.contentEditable = false; 346 }); 347 await stateChanged; 348 testStates(docAcc, STATE_READONLY, 0, 0, EXT_STATE_EDITABLE); 349 350 info("Clearing contentEditable on div"); 351 stateChanged = waitForStateChange(div, EXT_STATE_EDITABLE, false, true); 352 await invokeContentTask(browser, [], () => { 353 content.document.getElementById("div").contentEditable = false; 354 }); 355 await stateChanged; 356 testStates(div, 0, 0, 0, EXT_STATE_EDITABLE); 357 testStates(p, 0, 0, 0, EXT_STATE_EDITABLE); 358 359 info("Setting contentEditable on div"); 360 stateChanged = waitForStateChange(div, EXT_STATE_EDITABLE, true, true); 361 await invokeContentTask(browser, [], () => { 362 content.document.getElementById("div").contentEditable = true; 363 }); 364 await stateChanged; 365 testStates(div, 0, EXT_STATE_EDITABLE, 0, 0); 366 367 info("Setting designMode on document"); 368 stateChanged = Promise.all([ 369 waitForStateChange(docAcc, EXT_STATE_EDITABLE, true, true), 370 waitForStateChange(docAcc, STATE_READONLY, false, false), 371 ]); 372 await invokeContentTask(browser, [], () => { 373 content.document.designMode = "on"; 374 }); 375 await stateChanged; 376 testStates(docAcc, 0, EXT_STATE_EDITABLE, STATE_READONLY, 0); 377 378 info("Clearing designMode on document"); 379 stateChanged = Promise.all([ 380 waitForStateChange(docAcc, EXT_STATE_EDITABLE, false, true), 381 waitForStateChange(docAcc, STATE_READONLY, true, false), 382 ]); 383 await invokeContentTask(browser, [], () => { 384 content.document.designMode = "off"; 385 }); 386 await stateChanged; 387 testStates(docAcc, STATE_READONLY, 0, 0, EXT_STATE_EDITABLE); 388 389 const input = findAccessibleChildByID(docAcc, "input"); 390 testStates(input, 0, EXT_STATE_EDITABLE, STATE_UNAVAILABLE, 0); 391 info("Setting disabled on input"); 392 stateChanged = waitForEvents({ 393 expected: [stateChangeEventArgs(input, STATE_UNAVAILABLE, true)], 394 unexpected: [ 395 stateChangeEventArgs(input, EXT_STATE_EDITABLE, false, true), 396 ], 397 }); 398 await invokeContentTask(browser, [], () => { 399 content.document.getElementById("input").disabled = true; 400 }); 401 await stateChanged; 402 }, 403 { topLevel: true, iframe: true, remoteIframe: true, chrome: true } 404 ); 405 406 /** 407 * Test caching of the stale and busy states. 408 */ 409 addAccessibleTask( 410 `<iframe id="iframe"></iframe>`, 411 async function (browser, docAcc) { 412 const iframe = findAccessibleChildByID(docAcc, "iframe"); 413 info("Setting iframe src"); 414 // This iframe won't finish loading. Thus, it will get the stale state and 415 // won't fire a document load complete event. We use the reorder event on 416 // the iframe to know when the document has been created. 417 let reordered = waitForEvent(EVENT_REORDER, iframe); 418 await invokeContentTask(browser, [], () => { 419 content.document.getElementById("iframe").src = 420 'data:text/html,<img src="http://example.com/a11y/accessible/tests/mochitest/events/slow_image.sjs">'; 421 }); 422 const iframeDoc = (await reordered).accessible.firstChild; 423 testStates(iframeDoc, STATE_BUSY, EXT_STATE_STALE, 0, 0); 424 425 info("Finishing load of iframe doc"); 426 let loadCompleted = waitForEvent(EVENT_DOCUMENT_LOAD_COMPLETE, iframeDoc); 427 await fetch( 428 "https://example.com/a11y/accessible/tests/mochitest/events/slow_image.sjs?complete" 429 ); 430 await loadCompleted; 431 testStates(iframeDoc, 0, 0, STATE_BUSY, EXT_STATE_STALE); 432 }, 433 { topLevel: true, chrome: true } 434 ); 435 436 /** 437 * Test invalid state determined via DOM. 438 */ 439 addAccessibleTask( 440 `<input type="email" id="email">`, 441 async function (browser, docAcc) { 442 const email = findAccessibleChildByID(docAcc, "email"); 443 info("Focusing email"); 444 let focused = waitForEvent(EVENT_FOCUS, email); 445 email.takeFocus(); 446 await focused; 447 info("Typing a"); 448 let invalidChanged = waitForStateChange(email, STATE_INVALID, true); 449 EventUtils.sendString("a"); 450 await invalidChanged; 451 testStates(email, STATE_INVALID); 452 info("Typing @b"); 453 invalidChanged = waitForStateChange(email, STATE_INVALID, false); 454 EventUtils.sendString("@b"); 455 await invalidChanged; 456 testStates(email, 0, 0, STATE_INVALID); 457 info("Typing backspace"); 458 invalidChanged = waitForStateChange(email, STATE_INVALID, true); 459 EventUtils.synthesizeKey("KEY_Backspace"); 460 await invalidChanged; 461 testStates(email, STATE_INVALID); 462 }, 463 { chrome: true, topLevel: true, remoteIframe: true } 464 ); 465 466 /** 467 * Test caching of the expanded state for the popovertarget content attribute. 468 */ 469 addAccessibleTask( 470 ` 471 <button id="show-popover-btn" popovertarget="mypopover" popovertargetaction="show">Show popover</button> 472 <button id="hide-popover-btn" popovertarget="mypopover" popovertargetaction="hide">Hide popover</button> 473 <button id="toggle">toggle</button> 474 <div id="mypopover" popover> 475 Popover content 476 <button id="hide-inside" popovertarget="mypopover" popovertargetaction="hide">Hide inside popover</button> 477 </div> 478 `, 479 async function (browser, docAcc) { 480 const show = findAccessibleChildByID(docAcc, "show-popover-btn"); 481 const hide = findAccessibleChildByID(docAcc, "hide-popover-btn"); 482 testStates(show, STATE_COLLAPSED, 0); 483 testStates(hide, STATE_COLLAPSED, 0); 484 const toggle = findAccessibleChildByID(docAcc, "toggle"); 485 testStates( 486 toggle, 487 0, 488 0, 489 STATE_EXPANDED | STATE_COLLAPSED, 490 EXT_STATE_EXPANDABLE 491 ); 492 493 info("Setting toggle's popovertarget"); 494 let stateChanged = waitForStateChange( 495 toggle, 496 EXT_STATE_EXPANDABLE, 497 true, 498 true 499 ); 500 await invokeContentTask(browser, [], () => { 501 content.document 502 .getElementById("toggle") 503 .setAttribute("popovertarget", "mypopover"); 504 }); 505 await stateChanged; 506 507 // Changes to the popover should fire events on all invokers. 508 const changeEvents = [ 509 [EVENT_STATE_CHANGE, show], 510 [EVENT_STATE_CHANGE, hide], 511 [EVENT_STATE_CHANGE, toggle], 512 ]; 513 info("Expanding popover"); 514 let onShowing = waitForEvents(changeEvents); 515 await show.doAction(0); 516 await onShowing; 517 testStates(show, STATE_EXPANDED, 0); 518 testStates(hide, STATE_EXPANDED, 0); 519 testStates(toggle, STATE_EXPANDED, 0); 520 const hideInside = findAccessibleChildByID(show, "hide-inside"); 521 testStates(hideInside, 0, 0, STATE_EXPANDED | STATE_COLLAPSED, 0); 522 523 info("Collapsing popover"); 524 let onHiding = waitForEvents(changeEvents); 525 await hide.doAction(0); 526 await onHiding; 527 testStates(hide, STATE_COLLAPSED, 0); 528 testStates(show, STATE_COLLAPSED, 0); 529 testStates(toggle, STATE_COLLAPSED, 0); 530 }, 531 { chrome: true, topLevel: true, remoteIframe: true } 532 ); 533 534 /** 535 * Test caching of the expanded state for the popoverTargetElement WebIDL 536 * attribute. 537 */ 538 addAccessibleTask( 539 ` 540 <button id="toggle1">toggle</button> 541 <div id="popover1" popover>popover1</div> 542 <button id="toggle2">toggle2</button> 543 <button id="toggle3">toggle3</button> 544 <div id="shadowHost"><template shadowrootmode="open"> 545 <button id="toggle4">toggle4</button> 546 <div id="popover2" popover>popover2</div> 547 <button id="toggle5">toggle5</button> 548 </template></div> 549 `, 550 async function (browser, docAcc) { 551 const toggle1 = findAccessibleChildByID(docAcc, "toggle1"); 552 // toggle1's popover target is set and connected to the document. 553 testStates(toggle1, STATE_COLLAPSED); 554 555 const toggle2 = findAccessibleChildByID(docAcc, "toggle2"); 556 // toggle2's popover target isn't set yet. 557 testStates( 558 toggle2, 559 0, 560 0, 561 STATE_EXPANDED | STATE_COLLAPSED, 562 EXT_STATE_EXPANDABLE 563 ); 564 info("Setting toggle2's popoverTargetElement"); 565 let changed = waitForStateChange(toggle2, EXT_STATE_EXPANDABLE, true, true); 566 await invokeContentTask(browser, [], () => { 567 const toggle2Dom = content.document.getElementById("toggle2"); 568 const popover1 = content.document.getElementById("popover1"); 569 toggle2Dom.popoverTargetElement = popover1; 570 }); 571 await changed; 572 testStates(toggle2, STATE_COLLAPSED); 573 574 const toggle5 = findAccessibleChildByID(docAcc, "toggle5"); 575 // toggle5 is inside the shadow DOM and popover1 is outside, so the target 576 // is valid. 577 testStates(toggle5, STATE_COLLAPSED); 578 579 // Changes to the popover should fire events on all invokers. 580 const changeEvents = [ 581 [EVENT_STATE_CHANGE, toggle1], 582 [EVENT_STATE_CHANGE, toggle2], 583 [EVENT_STATE_CHANGE, toggle5], 584 ]; 585 info("Showing popover1"); 586 changed = waitForEvents(changeEvents); 587 toggle1.doAction(0); 588 await changed; 589 testStates(toggle1, STATE_EXPANDED); 590 testStates(toggle2, STATE_EXPANDED); 591 592 info("Hiding popover1"); 593 changed = waitForEvents(changeEvents); 594 toggle1.doAction(0); 595 await changed; 596 testStates(toggle1, STATE_COLLAPSED); 597 testStates(toggle2, STATE_COLLAPSED); 598 599 info("Clearing toggle1's popover target"); 600 changed = waitForStateChange(toggle1, EXT_STATE_EXPANDABLE, false, true); 601 await invokeContentTask(browser, [], () => { 602 const toggle1Dom = content.document.getElementById("toggle1"); 603 toggle1Dom.popoverTargetElement = null; 604 }); 605 await changed; 606 testStates( 607 toggle1, 608 0, 609 0, 610 STATE_EXPANDED | STATE_COLLAPSED, 611 EXT_STATE_EXPANDABLE 612 ); 613 614 info("Setting toggle2's popover target to a disconnected node"); 615 changed = waitForStateChange(toggle2, EXT_STATE_EXPANDABLE, false, true); 616 await invokeContentTask(browser, [], () => { 617 const toggle2Dom = content.document.getElementById("toggle2"); 618 const popover3 = content.document.createElement("div"); 619 popover3.popover = "auto"; 620 popover3.textContent = "popover3"; 621 // We don't append popover3 anywhere, so it is disconnected. 622 toggle2Dom.popoverTargetElement = popover3; 623 }); 624 await changed; 625 testStates( 626 toggle2, 627 0, 628 0, 629 STATE_EXPANDED | STATE_COLLAPSED, 630 EXT_STATE_EXPANDABLE 631 ); 632 633 const toggle3 = findAccessibleChildByID(docAcc, "toggle3"); 634 // toggle3 is outside popover2's shadow DOM, so the target isn't valid. 635 testStates( 636 toggle3, 637 0, 638 0, 639 STATE_EXPANDED | STATE_COLLAPSED, 640 EXT_STATE_EXPANDABLE 641 ); 642 const toggle4 = findAccessibleChildByID(docAcc, "toggle4"); 643 // toggle4 is in the same shadow DOM as popover2. 644 testStates(toggle4, STATE_COLLAPSED); 645 }, 646 { 647 chrome: true, 648 topLevel: true, 649 contentSetup: async function contentSetup() { 650 const doc = content.document; 651 const toggle1 = doc.getElementById("toggle1"); 652 const popover1 = doc.getElementById("popover1"); 653 toggle1.popoverTargetElement = popover1; 654 const toggle3 = doc.getElementById("toggle3"); 655 const shadow = doc.getElementById("shadowHost").shadowRoot; 656 const toggle4 = shadow.getElementById("toggle4"); 657 const popover2 = shadow.getElementById("popover2"); 658 toggle3.popoverTargetElement = popover2; 659 toggle4.popoverTargetElement = popover2; 660 const toggle5 = shadow.getElementById("toggle5"); 661 toggle5.popoverTargetElement = popover1; 662 }, 663 } 664 ); 665 666 /** 667 * Test the mixed state of indeterminate HTML checkboxes. 668 */ 669 addAccessibleTask( 670 `<input type="checkbox" id="checkbox">`, 671 async function testHTMLCheckboxMixed(browser, docAcc) { 672 const checkbox = findAccessibleChildByID(docAcc, "checkbox"); 673 testStates(checkbox, 0, 0, STATE_MIXED); 674 info("Setting indeterminate on checkbox"); 675 let changed = waitForStateChange(checkbox, STATE_MIXED, true); 676 await invokeContentTask(browser, [], () => { 677 content.document.getElementById("checkbox").indeterminate = true; 678 }); 679 await changed; 680 testStates(checkbox, STATE_MIXED); 681 info("Clearing indeterminate on checkbox"); 682 changed = waitForStateChange(checkbox, STATE_MIXED, false); 683 await invokeContentTask(browser, [], () => { 684 content.document.getElementById("checkbox").indeterminate = false; 685 }); 686 await changed; 687 testStates(checkbox, 0, 0, STATE_MIXED); 688 }, 689 { chrome: true, topLevel: true, iframe: true, remoteIframe: true } 690 ); 691 692 /** 693 * Test the readonly state on progress bars. 694 */ 695 addAccessibleTask( 696 ` 697 <progress id="htmlProgress"></progress> 698 <div id="ariaProgress" role="progressbar">ariaProgress</div> 699 <div id="ariaProgressRoFalse" role="progressbar" aria-readonly="false">ariaProgressRoFalse</div> 700 `, 701 async function testProgressBarReadOnly(browser, docAcc) { 702 const htmlProgress = findAccessibleChildByID(docAcc, "htmlProgress"); 703 testStates(htmlProgress, STATE_READONLY); 704 const ariaProgress = findAccessibleChildByID(docAcc, "ariaProgress"); 705 testStates(ariaProgress, STATE_READONLY); 706 // aria-readonly isn't valid and has no effect on a progress bar. 707 const ariaProgressRoFalse = findAccessibleChildByID( 708 docAcc, 709 "ariaProgressRoFalse" 710 ); 711 testStates(ariaProgressRoFalse, STATE_READONLY); 712 }, 713 { chrome: true, topLevel: true } 714 ); 715 716 /** 717 * Test the unavailable state. 718 */ 719 addAccessibleTask( 720 ` 721 <input id="input" disabled> 722 <fieldset id="fieldset" disabled> 723 <input id="fieldsetInput"> 724 </fieldset> 725 <div id="ariaDisabled" aria-disabled="true" role="button">ariaDisabled</div> 726 <input id="enabled"> 727 <div id="ariaDisabledGroup" aria-disabled="true"><input id="inAriaDisabledGroup"></div> 728 `, 729 async function testUnavailable(browser, docAcc) { 730 const input = findAccessibleChildByID(docAcc, "input"); 731 testStates(input, STATE_UNAVAILABLE, 0, STATE_FOCUSABLE); 732 info("Enabling input"); 733 let changed = waitForEvents([ 734 stateChangeEventArgs(input, STATE_UNAVAILABLE, false), 735 stateChangeEventArgs(input, STATE_FOCUSABLE, true), 736 ]); 737 await invokeSetAttribute(browser, "input", "disabled", null); 738 await changed; 739 testStates(input, STATE_FOCUSABLE, 0, STATE_UNAVAILABLE); 740 info("Disabling input"); 741 changed = waitForEvents([ 742 stateChangeEventArgs(input, STATE_UNAVAILABLE, true), 743 stateChangeEventArgs(input, STATE_FOCUSABLE, false), 744 ]); 745 await invokeSetAttribute(browser, "input", "disabled", "true"); 746 await changed; 747 testStates(input, STATE_UNAVAILABLE, 0, STATE_FOCUSABLE); 748 749 const fieldset = findAccessibleChildByID(docAcc, "fieldset"); 750 testStates(fieldset, STATE_UNAVAILABLE); 751 const fieldsetInput = findAccessibleChildByID(docAcc, "fieldsetInput"); 752 testStates(fieldsetInput, STATE_UNAVAILABLE, 0, STATE_FOCUSABLE); 753 info("Enabling fieldset"); 754 changed = waitForEvents([ 755 stateChangeEventArgs(fieldset, STATE_UNAVAILABLE, false), 756 stateChangeEventArgs(fieldsetInput, STATE_UNAVAILABLE, false), 757 stateChangeEventArgs(fieldsetInput, STATE_FOCUSABLE, true), 758 ]); 759 await invokeSetAttribute(browser, "fieldset", "disabled", null); 760 await changed; 761 testStates(fieldset, 0, 0, STATE_UNAVAILABLE); 762 testStates(fieldsetInput, STATE_FOCUSABLE, 0, STATE_UNAVAILABLE); 763 info("Disabling fieldset"); 764 changed = waitForEvents([ 765 stateChangeEventArgs(fieldset, STATE_UNAVAILABLE, true), 766 stateChangeEventArgs(fieldsetInput, STATE_UNAVAILABLE, true), 767 stateChangeEventArgs(fieldsetInput, STATE_FOCUSABLE, false), 768 ]); 769 await invokeSetAttribute(browser, "fieldset", "disabled", "true"); 770 await changed; 771 testStates(fieldset, STATE_UNAVAILABLE); 772 testStates(fieldsetInput, STATE_UNAVAILABLE, 0, STATE_FOCUSABLE); 773 774 const ariaDisabled = findAccessibleChildByID(docAcc, "ariaDisabled"); 775 testStates(ariaDisabled, STATE_UNAVAILABLE); 776 info("Enabling ariaDisabled"); 777 changed = waitForStateChange(ariaDisabled, STATE_UNAVAILABLE, false); 778 await invokeSetAttribute(browser, "ariaDisabled", "aria-disabled", null); 779 await changed; 780 testStates(ariaDisabled, 0, 0, STATE_UNAVAILABLE); 781 info("Disabling ariaDisabled"); 782 changed = waitForStateChange(ariaDisabled, STATE_UNAVAILABLE, true); 783 await invokeSetAttribute(browser, "ariaDisabled", "aria-disabled", "true"); 784 await changed; 785 testStates(ariaDisabled, STATE_UNAVAILABLE); 786 787 // Test a control that is initially enabled. 788 const enabled = findAccessibleChildByID(docAcc, "enabled"); 789 testStates(enabled, 0, 0, STATE_UNAVAILABLE); 790 791 const ariaDisabledGroup = findAccessibleChildByID( 792 docAcc, 793 "ariaDisabledGroup" 794 ); 795 const inAriaDisabledGroup = findAccessibleChildByID( 796 docAcc, 797 "inAriaDisabledGroup" 798 ); 799 testStates(ariaDisabledGroup, STATE_UNAVAILABLE); 800 testStates(inAriaDisabledGroup, STATE_UNAVAILABLE); 801 info("Enabling ariaDisabledGroup"); 802 changed = waitForStateChange(ariaDisabledGroup, STATE_UNAVAILABLE, false); 803 await invokeSetAttribute( 804 browser, 805 "ariaDisabledGroup", 806 "aria-disabled", 807 null 808 ); 809 await changed; 810 testStates(ariaDisabledGroup, 0, 0, STATE_UNAVAILABLE); 811 testStates(inAriaDisabledGroup, 0, 0, STATE_UNAVAILABLE); 812 }, 813 { chrome: true, topLevel: true } 814 ); 815 816 /** 817 * Test the protected state. 818 */ 819 addAccessibleTask( 820 ` 821 <input id="input"> 822 <input id="inputPassword" type="password"> 823 <textarea id="textareaPassword" type="password"></textarea> 824 `, 825 async function testProtected(browser, docAcc) { 826 const input = findAccessibleChildByID(docAcc, "input"); 827 testStates(input, 0, 0, STATE_PROTECTED); 828 const inputPassword = findAccessibleChildByID(docAcc, "inputPassword"); 829 testStates(inputPassword, STATE_PROTECTED); 830 // type="password" is not valid on textarea. 831 const textareaPassword = findAccessibleChildByID( 832 docAcc, 833 "textareaPassword" 834 ); 835 testStates(textareaPassword, 0, 0, STATE_PROTECTED); 836 }, 837 { chrome: true, topLevel: true } 838 ); 839 840 /** 841 * Test the selectable text state. 842 */ 843 addAccessibleTask( 844 ` 845 <p id="selectableP">selectableP</p> 846 <p id="unselectableP" style="user-select: none;">unselectableP</p> 847 `, 848 async function testSelectableText(browser, docAcc) { 849 testStates(docAcc, 0, EXT_STATE_SELECTABLE_TEXT); 850 const selectableP = findAccessibleChildByID(docAcc, "selectableP"); 851 testStates(selectableP, 0, EXT_STATE_SELECTABLE_TEXT); 852 const unselectableP = findAccessibleChildByID(docAcc, "unselectableP"); 853 testStates(unselectableP, 0, 0, 0, EXT_STATE_SELECTABLE_TEXT); 854 }, 855 { chrome: true, topLevel: true } 856 ); 857 858 /** 859 * Test the selectable text state on an unselectable body. 860 */ 861 addAccessibleTask( 862 ` 863 <style> 864 body { 865 user-select: none; 866 } 867 <p id="p">p</p> 868 `, 869 async function testSelectableTextUnselectableBody(browser, docAcc) { 870 testStates(docAcc, 0, 0, 0, EXT_STATE_SELECTABLE_TEXT); 871 const p = findAccessibleChildByID(docAcc, "p"); 872 testStates(p, 0, 0, 0, EXT_STATE_SELECTABLE_TEXT); 873 }, 874 { 875 chrome: true, 876 topLevel: true, 877 } 878 );