browser_caching_attributes.js (27207B)
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 loadScripts({ name: "attributes.js", dir: MOCHITESTS_DIR }); 9 10 /** 11 * Default textbox accessible attributes. 12 */ 13 const defaultAttributes = { 14 "margin-top": "0px", 15 "margin-right": "0px", 16 "margin-bottom": "0px", 17 "margin-left": "0px", 18 "text-align": "start", 19 "text-indent": "0px", 20 id: "textbox", 21 tag: "input", 22 display: "inline-block", 23 }; 24 25 /** 26 * Test data has the format of: 27 * { 28 * desc {String} description for better logging 29 * expected {Object} expected attributes for given accessibles 30 * unexpected {Object} unexpected attributes for given accessibles 31 * 32 * action {?AsyncFunction} an optional action that awaits a change in 33 * attributes 34 * attrs {?Array} an optional list of attributes to update 35 * waitFor {?Number} an optional event to wait for 36 * } 37 */ 38 const attributesTests = [ 39 { 40 desc: "Initiall accessible attributes", 41 expected: defaultAttributes, 42 unexpected: { 43 "line-number": "1", 44 "explicit-name": "true", 45 "container-live": "polite", 46 live: "polite", 47 }, 48 }, 49 { 50 desc: "@line-number attribute is present when textbox is focused", 51 async action(browser) { 52 await invokeFocus(browser, "textbox"); 53 }, 54 waitFor: EVENT_FOCUS, 55 expected: Object.assign({}, defaultAttributes, { "line-number": "1" }), 56 unexpected: { 57 "explicit-name": "true", 58 "container-live": "polite", 59 live: "polite", 60 }, 61 }, 62 { 63 desc: "@aria-live sets container-live and live attributes", 64 attrs: [ 65 { 66 attr: "aria-live", 67 value: "polite", 68 }, 69 ], 70 expected: Object.assign({}, defaultAttributes, { 71 "line-number": "1", 72 "container-live": "polite", 73 live: "polite", 74 }), 75 unexpected: { 76 "explicit-name": "true", 77 }, 78 }, 79 { 80 desc: "@title attribute sets explicit-name attribute to true", 81 attrs: [ 82 { 83 attr: "title", 84 value: "textbox", 85 }, 86 ], 87 expected: Object.assign({}, defaultAttributes, { 88 "line-number": "1", 89 "explicit-name": "true", 90 "container-live": "polite", 91 live: "polite", 92 }), 93 unexpected: {}, 94 }, 95 ]; 96 97 /** 98 * Test caching of accessible object attributes 99 */ 100 addAccessibleTask( 101 ` 102 <input id="textbox" value="hello">`, 103 async function (browser, accDoc) { 104 let textbox = findAccessibleChildByID(accDoc, "textbox"); 105 for (let { 106 desc, 107 action, 108 attrs, 109 expected, 110 waitFor, 111 unexpected, 112 } of attributesTests) { 113 info(desc); 114 let onUpdate; 115 116 if (waitFor) { 117 onUpdate = waitForEvent(waitFor, "textbox"); 118 } 119 120 if (action) { 121 await action(browser); 122 } else if (attrs) { 123 for (let { attr, value } of attrs) { 124 await invokeSetAttribute(browser, "textbox", attr, value); 125 } 126 } 127 128 await onUpdate; 129 testAttrs(textbox, expected); 130 testAbsentAttrs(textbox, unexpected); 131 } 132 }, 133 { 134 // These tests don't work yet with the parent process cache. 135 topLevel: false, 136 iframe: false, 137 remoteIframe: false, 138 } 139 ); 140 141 /** 142 * Test caching of the tag attribute. 143 */ 144 addAccessibleTask( 145 ` 146 <p id="p">text</p> 147 <textarea id="textarea"></textarea> 148 `, 149 async function (browser, docAcc) { 150 testAttrs(docAcc, { tag: "body" }, true); 151 const p = findAccessibleChildByID(docAcc, "p"); 152 testAttrs(p, { tag: "p" }, true); 153 const textLeaf = p.firstChild; 154 testAbsentAttrs(textLeaf, { tag: "" }); 155 const textarea = findAccessibleChildByID(docAcc, "textarea"); 156 testAttrs(textarea, { tag: "textarea" }, true); 157 }, 158 { chrome: true, topLevel: true, iframe: true, remoteIframe: true } 159 ); 160 161 /** 162 * Test caching of the text-input-type attribute. 163 */ 164 addAccessibleTask( 165 ` 166 <input id="default"> 167 <input id="email" type="email"> 168 <input id="password" type="password"> 169 <input id="text" type="text"> 170 <input id="date" type="date"> 171 <input id="time" type="time"> 172 <input id="checkbox" type="checkbox"> 173 <input id="radio" type="radio"> 174 `, 175 async function (browser, docAcc) { 176 function testInputType(id, inputType) { 177 if (inputType == undefined) { 178 testAbsentAttrs(findAccessibleChildByID(docAcc, id), { 179 "text-input-type": "", 180 }); 181 } else { 182 testAttrs( 183 findAccessibleChildByID(docAcc, id), 184 { "text-input-type": inputType }, 185 true 186 ); 187 } 188 } 189 190 testInputType("default"); 191 testInputType("email", "email"); 192 testInputType("password", "password"); 193 testInputType("text", "text"); 194 testInputType("date", "date"); 195 testInputType("time", "time"); 196 testInputType("checkbox"); 197 testInputType("radio"); 198 }, 199 { chrome: true, topLevel: true, iframe: false, remoteIframe: false } 200 ); 201 202 /** 203 * Test caching of the display attribute. 204 */ 205 addAccessibleTask( 206 ` 207 <div id="div"> 208 <ins id="ins">a</ins> 209 <button id="button">b</button> 210 </div> 211 <p> 212 <span id="presentationalSpan" role="none" 213 style="display: block; position: absolute; top: 0; left: 0; translate: 1px;"> 214 a 215 </span> 216 </p> 217 `, 218 async function (browser, docAcc) { 219 const div = findAccessibleChildByID(docAcc, "div"); 220 testAttrs(div, { display: "block" }, true); 221 const ins = findAccessibleChildByID(docAcc, "ins"); 222 testAttrs(ins, { display: "inline" }, true); 223 const textLeaf = ins.firstChild; 224 testAbsentAttrs(textLeaf, { display: "" }); 225 const button = findAccessibleChildByID(docAcc, "button"); 226 testAttrs(button, { display: "inline-block" }, true); 227 228 await invokeContentTask(browser, [], () => { 229 content.document.getElementById("ins").style.display = "block"; 230 content.document.body.offsetTop; // Flush layout. 231 }); 232 await untilCacheIs( 233 () => ins.attributes.getStringProperty("display"), 234 "block", 235 "ins display attribute changed to block" 236 ); 237 238 // This span has role="none", but we force a generic Accessible because it 239 // has a transform. role="none" might have been used to avoid exposing 240 // display: block, so ensure we don't expose that. 241 const presentationalSpan = findAccessibleChildByID( 242 docAcc, 243 "presentationalSpan" 244 ); 245 testAbsentAttrs(presentationalSpan, { display: "" }); 246 }, 247 { chrome: true, topLevel: true, iframe: true, remoteIframe: true } 248 ); 249 250 /** 251 * Test that there is no display attribute on image map areas. 252 */ 253 addAccessibleTask( 254 ` 255 <map name="normalMap"> 256 <area id="normalArea" shape="default"> 257 </map> 258 <img src="http://example.com/a11y/accessible/tests/mochitest/moz.png" usemap="#normalMap"> 259 <audio> 260 <map name="unslottedMap"> 261 <area id="unslottedArea" shape="default"> 262 </map> 263 </audio> 264 <img src="http://example.com/a11y/accessible/tests/mochitest/moz.png" usemap="#unslottedMap"> 265 `, 266 async function (browser, docAcc) { 267 const normalArea = findAccessibleChildByID(docAcc, "normalArea"); 268 testAbsentAttrs(normalArea, { display: "" }); 269 const unslottedArea = findAccessibleChildByID(docAcc, "unslottedArea"); 270 testAbsentAttrs(unslottedArea, { display: "" }); 271 }, 272 { topLevel: true } 273 ); 274 275 /** 276 * Test caching of the explicit-name attribute. 277 */ 278 addAccessibleTask( 279 ` 280 <h1 id="h1">content</h1> 281 <button id="buttonContent">content</button> 282 <button id="buttonLabel" aria-label="label">content</button> 283 <button id="buttonEmpty"></button> 284 <button id="buttonSummary"><details><summary>test</summary></details></button> 285 <div id="div"></div> 286 `, 287 async function (browser, docAcc) { 288 const h1 = findAccessibleChildByID(docAcc, "h1"); 289 testAbsentAttrs(h1, { "explicit-name": "" }); 290 const buttonContent = findAccessibleChildByID(docAcc, "buttonContent"); 291 testAbsentAttrs(buttonContent, { "explicit-name": "" }); 292 const buttonLabel = findAccessibleChildByID(docAcc, "buttonLabel"); 293 testAttrs(buttonLabel, { "explicit-name": "true" }, true); 294 const buttonEmpty = findAccessibleChildByID(docAcc, "buttonEmpty"); 295 testAbsentAttrs(buttonEmpty, { "explicit-name": "" }); 296 const buttonSummary = findAccessibleChildByID(docAcc, "buttonSummary"); 297 testAbsentAttrs(buttonSummary, { "explicit-name": "" }); 298 const div = findAccessibleChildByID(docAcc, "div"); 299 testAbsentAttrs(div, { "explicit-name": "" }); 300 301 info("Setting aria-label on h1"); 302 let nameChanged = waitForEvent(EVENT_NAME_CHANGE, h1); 303 await invokeContentTask(browser, [], () => { 304 content.document.getElementById("h1").setAttribute("aria-label", "label"); 305 }); 306 await nameChanged; 307 testAttrs(h1, { "explicit-name": "true" }, true); 308 }, 309 { chrome: true, topLevel: true, iframe: true, remoteIframe: true } 310 ); 311 312 /** 313 * Test caching of ARIA attributes that are exposed via object attributes. 314 */ 315 addAccessibleTask( 316 ` 317 <div id="currentTrue" aria-current="true">currentTrue</div> 318 <div id="currentFalse" aria-current="false">currentFalse</div> 319 <div id="currentPage" aria-current="page">currentPage</div> 320 <div id="currentBlah" aria-current="blah">currentBlah</div> 321 <div id="haspopupMenu" aria-haspopup="menu">haspopup</div> 322 <div id="rowColCountPositive" role="table" aria-rowcount="1000" aria-colcount="1000"> 323 <div role="row"> 324 <div id="rowColIndexPositive" role="cell" aria-rowindex="100" aria-colindex="100">positive</div> 325 </div> 326 </div> 327 <div id="rowColCountNegative" role="table" aria-rowcount="-1" aria-colcount="-1"> 328 <div role="row"> 329 <div id="rowColIndexNegative" role="cell" aria-rowindex="-1" aria-colindex="-1">negative</div> 330 </div> 331 </div> 332 <div id="rowColCountInvalid" role="table" aria-rowcount="z" aria-colcount="z"> 333 <div role="row"> 334 <div id="rowColIndexInvalid" role="cell" aria-rowindex="z" aria-colindex="z">invalid</div> 335 </div> 336 </div> 337 <div id="foo" aria-foo="bar">foo</div> 338 <div id="mutate" aria-current="true">mutate</div> 339 `, 340 async function (browser, docAcc) { 341 const currentTrue = findAccessibleChildByID(docAcc, "currentTrue"); 342 testAttrs(currentTrue, { current: "true" }, true); 343 const currentFalse = findAccessibleChildByID(docAcc, "currentFalse"); 344 testAbsentAttrs(currentFalse, { current: "" }); 345 const currentPage = findAccessibleChildByID(docAcc, "currentPage"); 346 testAttrs(currentPage, { current: "page" }, true); 347 // Test that token normalization works. 348 const currentBlah = findAccessibleChildByID(docAcc, "currentBlah"); 349 testAttrs(currentBlah, { current: "true" }, true); 350 const haspopupMenu = findAccessibleChildByID(docAcc, "haspopupMenu"); 351 testAttrs(haspopupMenu, { haspopup: "menu" }, true); 352 353 // Test normalization of integer values. 354 const rowColCountPositive = findAccessibleChildByID( 355 docAcc, 356 "rowColCountPositive" 357 ); 358 testAttrs( 359 rowColCountPositive, 360 { rowcount: "1000", colcount: "1000" }, 361 true 362 ); 363 const rowColIndexPositive = findAccessibleChildByID( 364 docAcc, 365 "rowColIndexPositive" 366 ); 367 testAttrs(rowColIndexPositive, { rowindex: "100", colindex: "100" }, true); 368 const rowColCountNegative = findAccessibleChildByID( 369 docAcc, 370 "rowColCountNegative" 371 ); 372 testAttrs(rowColCountNegative, { rowcount: "-1", colcount: "-1" }, true); 373 const rowColIndexNegative = findAccessibleChildByID( 374 docAcc, 375 "rowColIndexNegative" 376 ); 377 testAbsentAttrs(rowColIndexNegative, { rowindex: "", colindex: "" }); 378 const rowColCountInvalid = findAccessibleChildByID( 379 docAcc, 380 "rowColCountInvalid" 381 ); 382 testAbsentAttrs(rowColCountInvalid, { rowcount: "", colcount: "" }); 383 const rowColIndexInvalid = findAccessibleChildByID( 384 docAcc, 385 "rowColIndexInvalid" 386 ); 387 testAbsentAttrs(rowColIndexInvalid, { rowindex: "", colindex: "" }); 388 389 // Test that unknown aria- attributes get exposed. 390 const foo = findAccessibleChildByID(docAcc, "foo"); 391 testAttrs(foo, { foo: "bar" }, true); 392 393 const mutate = findAccessibleChildByID(docAcc, "mutate"); 394 testAttrs(mutate, { current: "true" }, true); 395 info("mutate: Removing aria-current"); 396 let changed = waitForEvent(EVENT_OBJECT_ATTRIBUTE_CHANGED, mutate); 397 await invokeContentTask(browser, [], () => { 398 content.document.getElementById("mutate").removeAttribute("aria-current"); 399 }); 400 await changed; 401 testAbsentAttrs(mutate, { current: "" }); 402 info("mutate: Adding aria-current"); 403 changed = waitForEvent(EVENT_OBJECT_ATTRIBUTE_CHANGED, mutate); 404 await invokeContentTask(browser, [], () => { 405 content.document 406 .getElementById("mutate") 407 .setAttribute("aria-current", "page"); 408 }); 409 await changed; 410 testAttrs(mutate, { current: "page" }, true); 411 }, 412 { chrome: true, topLevel: true, iframe: true, remoteIframe: true } 413 ); 414 415 /** 416 * Test support for the xml-roles attribute. 417 */ 418 addAccessibleTask( 419 ` 420 <div id="knownRole" role="main">knownRole</div> 421 <div id="emptyRole" role="">emptyRole</div> 422 <div id="unknownRole" role="foo">unknownRole</div> 423 <div id="multiRole" role="foo main">multiRole</div> 424 <main id="landmarkMarkup">landmarkMarkup</main> 425 <main id="landmarkMarkupWithRole" role="banner">landmarkMarkupWithRole</main> 426 <main id="landmarkMarkupWithEmptyRole" role="">landmarkMarkupWithEmptyRole</main> 427 <article id="markup">markup</article> 428 <article id="markupWithRole" role="banner">markupWithRole</article> 429 <article id="markupWithEmptyRole" role="">markupWithEmptyRole</article> 430 `, 431 async function (browser, docAcc) { 432 const knownRole = findAccessibleChildByID(docAcc, "knownRole"); 433 testAttrs(knownRole, { "xml-roles": "main" }, true); 434 const emptyRole = findAccessibleChildByID(docAcc, "emptyRole"); 435 testAbsentAttrs(emptyRole, { "xml-roles": "" }); 436 const unknownRole = findAccessibleChildByID(docAcc, "unknownRole"); 437 testAttrs(unknownRole, { "xml-roles": "foo" }, true); 438 const multiRole = findAccessibleChildByID(docAcc, "multiRole"); 439 testAttrs(multiRole, { "xml-roles": "foo main" }, true); 440 const landmarkMarkup = findAccessibleChildByID(docAcc, "landmarkMarkup"); 441 testAttrs(landmarkMarkup, { "xml-roles": "main" }, true); 442 const landmarkMarkupWithRole = findAccessibleChildByID( 443 docAcc, 444 "landmarkMarkupWithRole" 445 ); 446 testAttrs(landmarkMarkupWithRole, { "xml-roles": "banner" }, true); 447 const landmarkMarkupWithEmptyRole = findAccessibleChildByID( 448 docAcc, 449 "landmarkMarkupWithEmptyRole" 450 ); 451 testAttrs(landmarkMarkupWithEmptyRole, { "xml-roles": "main" }, true); 452 const markup = findAccessibleChildByID(docAcc, "markup"); 453 testAttrs(markup, { "xml-roles": "article" }, true); 454 const markupWithRole = findAccessibleChildByID(docAcc, "markupWithRole"); 455 testAttrs(markupWithRole, { "xml-roles": "banner" }, true); 456 const markupWithEmptyRole = findAccessibleChildByID( 457 docAcc, 458 "markupWithEmptyRole" 459 ); 460 testAttrs(markupWithEmptyRole, { "xml-roles": "article" }, true); 461 }, 462 { chrome: true, topLevel: true, iframe: true, remoteIframe: true } 463 ); 464 465 /** 466 * Test lie region attributes. 467 */ 468 addAccessibleTask( 469 ` 470 <div id="noLive"><p>noLive</p></div> 471 <output id="liveMarkup"><p>liveMarkup</p></output> 472 <div id="ariaLive" aria-live="polite"><p>ariaLive</p></div> 473 <div id="liveRole" role="log"><p>liveRole</p></div> 474 <div id="nonLiveRole" role="group"><p>nonLiveRole</p></div> 475 <div id="other" aria-atomic="true" aria-busy="true" aria-relevant="additions"><p>other</p></div> 476 `, 477 async function (browser, docAcc) { 478 const noLive = findAccessibleChildByID(docAcc, "noLive"); 479 for (const acc of [noLive, noLive.firstChild]) { 480 testAbsentAttrs(acc, { 481 live: "", 482 "container-live": "", 483 "container-live-role": "", 484 atomic: "", 485 "container-atomic": "", 486 busy: "", 487 "container-busy": "", 488 relevant: "", 489 "container-relevant": "", 490 }); 491 } 492 const liveMarkup = findAccessibleChildByID(docAcc, "liveMarkup"); 493 testAttrs(liveMarkup, { live: "polite" }, true); 494 testAttrs(liveMarkup.firstChild, { "container-live": "polite" }, true); 495 const ariaLive = findAccessibleChildByID(docAcc, "ariaLive"); 496 testAttrs(ariaLive, { live: "polite" }, true); 497 testAttrs(ariaLive.firstChild, { "container-live": "polite" }, true); 498 const liveRole = findAccessibleChildByID(docAcc, "liveRole"); 499 testAttrs(liveRole, { live: "polite" }, true); 500 testAttrs( 501 liveRole.firstChild, 502 { "container-live": "polite", "container-live-role": "log" }, 503 true 504 ); 505 const nonLiveRole = findAccessibleChildByID(docAcc, "nonLiveRole"); 506 testAbsentAttrs(nonLiveRole, { live: "" }); 507 testAbsentAttrs(nonLiveRole.firstChild, { 508 "container-live": "", 509 "container-live-role": "", 510 }); 511 const other = findAccessibleChildByID(docAcc, "other"); 512 testAttrs( 513 other, 514 { atomic: "true", busy: "true", relevant: "additions" }, 515 true 516 ); 517 testAttrs( 518 other.firstChild, 519 { 520 "container-atomic": "true", 521 "container-busy": "true", 522 "container-relevant": "additions", 523 }, 524 true 525 ); 526 }, 527 { chrome: true, topLevel: true, iframe: true, remoteIframe: true } 528 ); 529 530 /** 531 * Test the id attribute. 532 */ 533 addAccessibleTask( 534 ` 535 <p id="withId">withId</p> 536 <div id="noIdParent"><p>noId</p></div> 537 `, 538 async function (browser, docAcc) { 539 const withId = findAccessibleChildByID(docAcc, "withId"); 540 testAttrs(withId, { id: "withId" }, true); 541 const noId = findAccessibleChildByID(docAcc, "noIdParent").firstChild; 542 testAbsentAttrs(noId, { id: "" }); 543 }, 544 { chrome: true, topLevel: true, iframe: true, remoteIframe: true } 545 ); 546 547 /** 548 * Test the valuetext attribute. 549 */ 550 addAccessibleTask( 551 ` 552 <div id="valuenow" role="slider" aria-valuenow="1"></div> 553 <div id="valuetext" role="slider" aria-valuetext="text"></div> 554 <div id="noValue" role="button"></div> 555 `, 556 async function (browser, docAcc) { 557 const valuenow = findAccessibleChildByID(docAcc, "valuenow"); 558 testAttrs(valuenow, { valuetext: "1" }, true); 559 const valuetext = findAccessibleChildByID(docAcc, "valuetext"); 560 testAttrs(valuetext, { valuetext: "text" }, true); 561 const noValue = findAccessibleChildByID(docAcc, "noValue"); 562 testAbsentAttrs(noValue, { valuetext: "valuetext" }); 563 }, 564 { chrome: true, topLevel: true, iframe: true, remoteIframe: true } 565 ); 566 567 function untilCacheAttrIs(acc, attr, val, msg) { 568 return untilCacheOk(() => { 569 try { 570 return acc.attributes.getStringProperty(attr) == val; 571 } catch (e) { 572 return false; 573 } 574 }, msg); 575 } 576 577 function untilCacheAttrAbsent(acc, attr, msg) { 578 return untilCacheOk(() => { 579 try { 580 acc.attributes.getStringProperty(attr); 581 } catch (e) { 582 return true; 583 } 584 return false; 585 }, msg); 586 } 587 588 /** 589 * Test the class attribute. 590 */ 591 addAccessibleTask( 592 ` 593 <div id="oneClass" class="c1">oneClass</div> 594 <div id="multiClass" class="c1 c2">multiClass</div> 595 <div id="noClass">noClass</div> 596 <div id="mutate">mutate</div> 597 `, 598 async function (browser, docAcc) { 599 const oneClass = findAccessibleChildByID(docAcc, "oneClass"); 600 testAttrs(oneClass, { class: "c1" }, true); 601 const multiClass = findAccessibleChildByID(docAcc, "multiClass"); 602 testAttrs(multiClass, { class: "c1 c2" }, true); 603 const noClass = findAccessibleChildByID(docAcc, "noClass"); 604 testAbsentAttrs(noClass, { class: "" }); 605 606 const mutate = findAccessibleChildByID(docAcc, "mutate"); 607 testAbsentAttrs(mutate, { class: "" }); 608 info("Adding class to mutate"); 609 await invokeContentTask(browser, [], () => { 610 content.document.getElementById("mutate").className = "c1 c2"; 611 }); 612 await untilCacheAttrIs(mutate, "class", "c1 c2", "mutate class correct"); 613 info("Removing class from mutate"); 614 await invokeContentTask(browser, [], () => { 615 content.document.getElementById("mutate").removeAttribute("class"); 616 }); 617 await untilCacheAttrAbsent(mutate, "class", "mutate class not present"); 618 }, 619 { chrome: true, topLevel: true } 620 ); 621 622 /** 623 * Test the src attribute. 624 */ 625 const kImgUrl = "https://example.com/a11y/accessible/tests/mochitest/moz.png"; 626 addAccessibleTask( 627 ` 628 <img id="noAlt" src="${kImgUrl}"> 629 <img id="alt" alt="alt" src="${kImgUrl}"> 630 <img id="mutate"> 631 `, 632 async function (browser, docAcc) { 633 const noAlt = findAccessibleChildByID(docAcc, "noAlt"); 634 testAttrs(noAlt, { src: kImgUrl }, true); 635 const alt = findAccessibleChildByID(docAcc, "alt"); 636 testAttrs(alt, { src: kImgUrl }, true); 637 638 const mutate = findAccessibleChildByID(docAcc, "mutate"); 639 testAbsentAttrs(mutate, { src: "" }); 640 info("Adding src to mutate"); 641 await invokeContentTask(browser, [kImgUrl], url => { 642 content.document.getElementById("mutate").src = url; 643 }); 644 await untilCacheAttrIs(mutate, "src", kImgUrl, "mutate src correct"); 645 info("Removing src from mutate"); 646 await invokeContentTask(browser, [], () => { 647 content.document.getElementById("mutate").removeAttribute("src"); 648 }); 649 await untilCacheAttrAbsent(mutate, "src", "mutate src not present"); 650 }, 651 { chrome: true, topLevel: true } 652 ); 653 654 /** 655 * Test the placeholder attribute. 656 */ 657 addAccessibleTask( 658 ` 659 <input id="htmlWithLabel" aria-label="label" placeholder="HTML"> 660 <input id="htmlNoLabel" placeholder="HTML"> 661 <input id="ariaWithLabel" aria-label="label" aria-placeholder="ARIA"> 662 <input id="ariaNoLabel" aria-placeholder="ARIA"> 663 <input id="both" aria-label="label" placeholder="HTML" aria-placeholder="ARIA"> 664 <input id="mutate" placeholder="HTML"> 665 `, 666 async function (browser, docAcc) { 667 const htmlWithLabel = findAccessibleChildByID(docAcc, "htmlWithLabel"); 668 testAttrs(htmlWithLabel, { placeholder: "HTML" }, true); 669 const htmlNoLabel = findAccessibleChildByID(docAcc, "htmlNoLabel"); 670 // placeholder is used as name, so not exposed as attribute. 671 testAbsentAttrs(htmlNoLabel, { placeholder: "" }); 672 const ariaWithLabel = findAccessibleChildByID(docAcc, "ariaWithLabel"); 673 testAttrs(ariaWithLabel, { placeholder: "ARIA" }, true); 674 const ariaNoLabel = findAccessibleChildByID(docAcc, "ariaNoLabel"); 675 // No label doesn't impact aria-placeholder. 676 testAttrs(ariaNoLabel, { placeholder: "ARIA" }, true); 677 const both = findAccessibleChildByID(docAcc, "both"); 678 testAttrs(both, { placeholder: "HTML" }, true); 679 680 const mutate = findAccessibleChildByID(docAcc, "mutate"); 681 testAbsentAttrs(mutate, { placeholder: "" }); 682 info("Adding label to mutate"); 683 await invokeContentTask(browser, [], () => { 684 content.document 685 .getElementById("mutate") 686 .setAttribute("aria-label", "label"); 687 }); 688 await untilCacheAttrIs( 689 mutate, 690 "placeholder", 691 "HTML", 692 "mutate placeholder correct" 693 ); 694 info("Removing mutate placeholder"); 695 await invokeContentTask(browser, [], () => { 696 content.document.getElementById("mutate").removeAttribute("placeholder"); 697 }); 698 await untilCacheAttrAbsent( 699 mutate, 700 "placeholder", 701 "mutate placeholder not present" 702 ); 703 info("Setting mutate aria-placeholder"); 704 await invokeContentTask(browser, [], () => { 705 content.document 706 .getElementById("mutate") 707 .setAttribute("aria-placeholder", "ARIA"); 708 }); 709 await untilCacheAttrIs( 710 mutate, 711 "placeholder", 712 "ARIA", 713 "mutate placeholder correct" 714 ); 715 info("Setting mutate placeholder"); 716 await invokeContentTask(browser, [], () => { 717 content.document 718 .getElementById("mutate") 719 .setAttribute("placeholder", "HTML"); 720 }); 721 await untilCacheAttrIs( 722 mutate, 723 "placeholder", 724 "HTML", 725 "mutate placeholder correct" 726 ); 727 }, 728 { chrome: true, topLevel: true } 729 ); 730 731 /** 732 * Test the ispopup attribute. 733 */ 734 addAccessibleTask( 735 `<div id="popover" popover>popover</div>`, 736 async function testIspopup(browser) { 737 info("Showing popover"); 738 let shown = waitForEvent(EVENT_SHOW, "popover"); 739 await invokeContentTask(browser, [], () => { 740 content.document.getElementById("popover").showPopover(); 741 }); 742 let popover = (await shown).accessible; 743 testAttrs(popover, { ispopup: "auto" }, true); 744 info("Setting popover to null"); 745 // Setting popover causes the Accessible to be recreated. 746 shown = waitForEvent(EVENT_SHOW, "popover"); 747 await invokeContentTask(browser, [], () => { 748 content.document.getElementById("popover").popover = null; 749 }); 750 popover = (await shown).accessible; 751 testAbsentAttrs(popover, { ispopup: "" }); 752 info("Setting popover to manual and showing"); 753 shown = waitForEvent(EVENT_SHOW, "popover"); 754 await invokeContentTask(browser, [], () => { 755 const popoverDom = content.document.getElementById("popover"); 756 popoverDom.popover = "manual"; 757 popoverDom.showPopover(); 758 }); 759 popover = (await shown).accessible; 760 testAttrs(popover, { ispopup: "manual" }, true); 761 }, 762 { chrome: true, topLevel: true } 763 ); 764 765 /** 766 * Test has-actions attribute. 767 */ 768 addAccessibleTask( 769 `<dialog aria-actions="btn" id="dlg" open> 770 Hello 771 <button id="btn">Close</button> 772 <button id="btn-hidden" hidden>Pin</button> 773 </dialog>`, 774 async function testHasActionsAttribute(browser, docAcc) { 775 function getDlgHasActions() { 776 try { 777 return dlg.attributes.getStringProperty("has-actions"); 778 } catch (e) { 779 return null; 780 } 781 } 782 783 const dlg = findAccessibleChildByID(docAcc, "dlg"); 784 is(getDlgHasActions(), "true", "dlg has-actions attribute is true"); 785 786 // Removing the 'aria-actions' attribute from the element 787 // should remove the 'has-actions' attribute from the accessible. 788 let changed = waitForEvent(EVENT_OBJECT_ATTRIBUTE_CHANGED, "dlg"); 789 await invokeSetAttribute(browser, "dlg", "aria-actions"); 790 await changed; 791 await untilCacheIs( 792 getDlgHasActions, 793 null, 794 "dlg has-actions attribute removed" 795 ); 796 797 // Setting the 'aria-actions' attribute to an empty string 798 // should make the 'has-actions' accessible attribute true. 799 changed = waitForEvent(EVENT_OBJECT_ATTRIBUTE_CHANGED, "dlg"); 800 await invokeSetAttribute(browser, "dlg", "aria-actions", ""); 801 await changed; 802 await untilCacheIs( 803 getDlgHasActions, 804 "true", 805 "dlg has-actions attribute re-added" 806 ); 807 808 // Remove again to set up for next test 809 await invokeSetAttribute(browser, "dlg", "aria-actions"); 810 await untilCacheIs( 811 getDlgHasActions, 812 null, 813 "dlg has-actions attribute removed again" 814 ); 815 816 // Setting the 'aria-actions' attribute to a hidden target 817 // should still make 'has-actions' true 818 changed = waitForEvent(EVENT_OBJECT_ATTRIBUTE_CHANGED, "dlg"); 819 await invokeSetAttribute(browser, "dlg", "aria-actions", "btn-hidden"); 820 await changed; 821 await untilCacheIs( 822 getDlgHasActions, 823 "true", 824 "dlg has-actions attribute re-added with hidden target" 825 ); 826 }, 827 { chrome: true, topLevel: true } 828 );