browser_relations_general_002.js (11791B)
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 loadScripts({ name: "role.js", dir: MOCHITESTS_DIR }); 10 /* import-globals-from ../../mochitest/states.js */ 11 loadScripts({ name: "states.js", dir: MOCHITESTS_DIR }); 12 13 /** 14 * Test MEMBER_OF relation caching on HTML radio buttons 15 */ 16 addAccessibleTask( 17 ` 18 <input type="radio" id="r1">I have no name<br> 19 <input type="radio" id="r2">I also have no name<br> 20 <input type="radio" id="r3" name="n">I have a name<br> 21 <input type="radio" id="r4" name="a">I have a different name<br> 22 <fieldset role="radiogroup"> 23 <input type="radio" id="r5" name="n">I have an already used name 24 and am in a different part of the tree 25 <input type="radio" id="r6" name="r">I have a different name but am 26 in the same group 27 </fieldset>`, 28 async function (browser, accDoc) { 29 const r1 = findAccessibleChildByID(accDoc, "r1"); 30 const r2 = findAccessibleChildByID(accDoc, "r2"); 31 const r3 = findAccessibleChildByID(accDoc, "r3"); 32 const r4 = findAccessibleChildByID(accDoc, "r4"); 33 const r5 = findAccessibleChildByID(accDoc, "r5"); 34 const r6 = findAccessibleChildByID(accDoc, "r6"); 35 36 await testCachedRelation(r1, RELATION_MEMBER_OF, []); 37 await testCachedRelation(r2, RELATION_MEMBER_OF, []); 38 await testCachedRelation(r3, RELATION_MEMBER_OF, [r3, r5]); 39 await testCachedRelation(r4, RELATION_MEMBER_OF, r4); 40 await testCachedRelation(r5, RELATION_MEMBER_OF, [r3, r5]); 41 await testCachedRelation(r6, RELATION_MEMBER_OF, r6); 42 43 await invokeContentTask(browser, [], () => { 44 content.document.getElementById("r5").name = "a"; 45 }); 46 47 await testCachedRelation(r3, RELATION_MEMBER_OF, r3); 48 await testCachedRelation(r4, RELATION_MEMBER_OF, [r5, r4]); 49 await testCachedRelation(r5, RELATION_MEMBER_OF, [r5, r4]); 50 }, 51 { chrome: true, iframe: true, remoteIframe: true } 52 ); 53 54 /* 55 * Test MEMBER_OF relation caching on aria radio buttons 56 */ 57 addAccessibleTask( 58 ` 59 <div role="radio" id="r1">I have no radio group</div><br> 60 <fieldset role="radiogroup" id="fs"> 61 <div role="radio" id="r2">hello</div><br> 62 <div role="radio" id="r3">world</div><br> 63 </fieldset>`, 64 async function (browser, accDoc) { 65 const r1 = findAccessibleChildByID(accDoc, "r1"); 66 const r2 = findAccessibleChildByID(accDoc, "r2"); 67 let r3 = findAccessibleChildByID(accDoc, "r3"); 68 69 await testCachedRelation(r1, RELATION_MEMBER_OF, []); 70 await testCachedRelation(r2, RELATION_MEMBER_OF, [r2, r3]); 71 await testCachedRelation(r3, RELATION_MEMBER_OF, [r2, r3]); 72 const r = waitForEvent(EVENT_INNER_REORDER, "fs"); 73 await invokeContentTask(browser, [], () => { 74 let innerRadio = content.document.getElementById("r3"); 75 content.document.body.appendChild(innerRadio); 76 }); 77 await r; 78 79 r3 = findAccessibleChildByID(accDoc, "r3"); 80 await testCachedRelation(r1, RELATION_MEMBER_OF, []); 81 await testCachedRelation(r2, RELATION_MEMBER_OF, r2); 82 await testCachedRelation(r3, RELATION_MEMBER_OF, []); 83 }, 84 { 85 chrome: true, 86 iframe: true, 87 remoteIframe: true, 88 } 89 ); 90 91 /* 92 * Test mutation of LABEL relations via DOM ID reuse. 93 */ 94 addAccessibleTask( 95 ` 96 <div id="label">before</div><input id="input" aria-labelledby="label"> 97 `, 98 async function (browser, accDoc) { 99 let label = findAccessibleChildByID(accDoc, "label"); 100 const input = findAccessibleChildByID(accDoc, "input"); 101 102 await testCachedRelation(label, RELATION_LABEL_FOR, input); 103 await testCachedRelation(input, RELATION_LABELLED_BY, label); 104 105 const r = waitForEvent(EVENT_REORDER, accDoc); 106 await invokeContentTask(browser, [], () => { 107 content.document.getElementById("label").remove(); 108 let l = content.document.createElement("div"); 109 l.id = "label"; 110 l.textContent = "after"; 111 content.document.body.insertBefore( 112 l, 113 content.document.getElementById("input") 114 ); 115 }); 116 await r; 117 label = findAccessibleChildByID(accDoc, "label"); 118 await testCachedRelation(label, RELATION_LABEL_FOR, input); 119 await testCachedRelation(input, RELATION_LABELLED_BY, label); 120 }, 121 { 122 chrome: true, 123 iframe: true, 124 remoteIframe: true, 125 } 126 ); 127 128 /* 129 * Test LINKS_TO relation caching an anchor with multiple hashes 130 */ 131 addAccessibleTask( 132 ` 133 <a id="link" href="#foo#bar">Origin</a><br> 134 <a id="anchor" name="foo#bar">Destination`, 135 async function (browser, accDoc) { 136 const link = findAccessibleChildByID(accDoc, "link"); 137 const anchor = findAccessibleChildByID(accDoc, "anchor"); 138 139 await testCachedRelation(link, RELATION_LINKS_TO, anchor); 140 }, 141 { 142 chrome: true, 143 // IA2 doesn't have a LINKS_TO relation and Windows non-cached 144 // RemoteAccessible uses IA2, so we can't run these tests in this case. 145 topLevel: true, 146 iframe: true, 147 remoteIframe: true, 148 } 149 ); 150 151 /* 152 * Test mutation of LABEL relations via accessible shutdown. 153 */ 154 addAccessibleTask( 155 ` 156 <input id="d"></input> 157 <label id="l"> 158 <select id="s"></select> 159 </label> 160 `, 161 async function (browser, accDoc) { 162 const label = findAccessibleChildByID(accDoc, "l"); 163 const select = findAccessibleChildByID(accDoc, "s"); 164 const input = findAccessibleChildByID(accDoc, "d"); 165 166 await testCachedRelation(label, RELATION_LABEL_FOR, select); 167 await testCachedRelation(select, RELATION_LABELLED_BY, label); 168 await testCachedRelation(input, RELATION_LABELLED_BY, []); 169 await untilCacheOk(() => { 170 if (!browser.isRemoteBrowser) { 171 return true; 172 } 173 174 try { 175 // We should get an acc ID back from this, but we don't have a way of 176 // verifying its correctness -- it should be the ID of the select. 177 return label.cache.getStringProperty("for"); 178 } catch (e) { 179 ok(false, "Exception thrown while trying to read from the cache"); 180 return false; 181 } 182 }, "Label for relation exists"); 183 184 const r = waitForEvent(EVENT_INNER_REORDER, "l"); 185 await invokeContentTask(browser, [], () => { 186 content.document.getElementById("s").remove(); 187 }); 188 await r; 189 await untilCacheOk(() => { 190 if (!browser.isRemoteBrowser) { 191 return true; 192 } 193 194 try { 195 label.cache.getStringProperty("for"); 196 } catch (e) { 197 // This property should no longer exist in the cache, so we should 198 // get an exception if we try to fetch it. 199 return true; 200 } 201 return false; 202 }, "Label for relation exists"); 203 204 await invokeContentTask(browser, [], () => { 205 const l = content.document.getElementById("l"); 206 l.htmlFor = "d"; 207 }); 208 await testCachedRelation(label, RELATION_LABEL_FOR, input); 209 await testCachedRelation(input, RELATION_LABELLED_BY, label); 210 }, 211 { 212 chrome: true, 213 iframe: true, 214 remoteIframe: true, 215 topLevel: true, 216 } 217 ); 218 219 /** 220 * Test label relations on HTML figure/figcaption. 221 */ 222 addAccessibleTask( 223 ` 224 <figure id="figure1"> 225 before 226 <figcaption id="caption1">caption1</figcaption> 227 after 228 </figure> 229 <figure id="figure2" aria-labelledby="label"> 230 <figcaption id="caption2">caption2</figure> 231 </figure> 232 <div id="label">label</div> 233 `, 234 async function (browser, docAcc) { 235 const figure1 = findAccessibleChildByID(docAcc, "figure1"); 236 let caption1 = findAccessibleChildByID(docAcc, "caption1"); 237 await testCachedRelation(figure1, RELATION_LABELLED_BY, caption1); 238 await testCachedRelation(caption1, RELATION_LABEL_FOR, figure1); 239 240 info("Hiding caption1"); 241 let mutated = waitForEvent(EVENT_HIDE, caption1); 242 await invokeContentTask(browser, [], () => { 243 content.document.getElementById("caption1").hidden = true; 244 }); 245 await mutated; 246 await testCachedRelation(figure1, RELATION_LABELLED_BY, []); 247 248 info("Showing caption1"); 249 mutated = waitForEvent(EVENT_SHOW, "caption1"); 250 await invokeContentTask(browser, [], () => { 251 content.document.getElementById("caption1").hidden = false; 252 }); 253 caption1 = (await mutated).accessible; 254 await testCachedRelation(figure1, RELATION_LABELLED_BY, caption1); 255 await testCachedRelation(caption1, RELATION_LABEL_FOR, figure1); 256 257 const figure2 = findAccessibleChildByID(docAcc, "figure2"); 258 const caption2 = findAccessibleChildByID(docAcc, "caption2"); 259 const label = findAccessibleChildByID(docAcc, "label"); 260 await testCachedRelation(figure2, RELATION_LABELLED_BY, [label, caption2]); 261 await testCachedRelation(caption2, RELATION_LABEL_FOR, figure2); 262 await testCachedRelation(label, RELATION_LABEL_FOR, figure2); 263 }, 264 { chrome: true, topLevel: true } 265 ); 266 267 /** 268 * Test aria-owns morphs to controls relationship when container is combobox. 269 */ 270 addAccessibleTask( 271 ` 272 <div id="box" role="combobox" 273 aria-owns="listbox" 274 aria-expanded="true" 275 aria-haspopup="listbox" 276 aria-autocomplete="list" 277 contenteditable="true"></div> 278 <ul role="listbox" id="listbox"> 279 <li role="option">apple</li> 280 <li role="option">peach</li> 281 </ul> 282 `, 283 async (browser, accDoc) => { 284 const combobox = findAccessibleChildByID(accDoc, "box"); 285 const listbox = findAccessibleChildByID(accDoc, "listbox"); 286 287 testStates(combobox, 0, EXT_STATE_EDITABLE, 0, 0); 288 is(combobox.childCount, 0, "combobox has no children"); 289 await testCachedRelation(combobox, RELATION_CONTROLLER_FOR, [listbox]); 290 await testCachedRelation(listbox, RELATION_CONTROLLED_BY, [combobox]); 291 292 let expectedEvents = Promise.all([ 293 waitForStateChange(combobox, EXT_STATE_EDITABLE, false, true), 294 waitForEvent(EVENT_REORDER, accDoc), 295 ]); 296 await invokeContentTask(browser, [], () => { 297 content.document.getElementById("box").contentEditable = false; 298 }); 299 await expectedEvents; 300 await testCachedRelation(combobox, RELATION_CONTROLLER_FOR, []); 301 await testCachedRelation(listbox, RELATION_CONTROLLED_BY, []); 302 is(combobox.childCount, 1, "combobox has listbox"); 303 } 304 ); 305 306 /* 307 * Test relocated child preserves relation. 308 */ 309 addAccessibleTask( 310 ` 311 <div id="relocated">World</div> 312 <div id="owner" aria-owns="relocated"></div> 313 <button id="btn" aria-labelledby="relocated">Hello</button> 314 `, 315 async function testRelocationRelation(browser, docAcc) { 316 const btn = findAccessibleChildByID(docAcc, "btn"); 317 const relocated = findAccessibleChildByID(docAcc, "relocated"); 318 319 await testCachedRelation(btn, RELATION_LABELLED_BY, relocated); 320 await testCachedRelation(relocated, RELATION_LABEL_FOR, btn); 321 }, 322 { chrome: true, topLevel: true } 323 ); 324 325 /** 326 * Test table caption. 327 */ 328 addAccessibleTask( 329 ` 330 <table id="table"> 331 <caption id="caption">caption</caption> 332 <tr><th>a</th></tr> 333 </table> 334 `, 335 async function testTableCaption(browser, docAcc) { 336 const table = findAccessibleChildByID(docAcc, "table"); 337 338 const caption = findAccessibleChildByID(docAcc, "caption"); 339 await testCachedRelation(table, RELATION_LABELLED_BY, caption); 340 await testCachedRelation(caption, RELATION_LABEL_FOR, table); 341 }, 342 { chrome: true, topLevel: true } 343 ); 344 345 /** 346 * Test elements that are not label by spec do not get LABEL relations 347 */ 348 addAccessibleTask( 349 ` 350 <label id="label" for="btn">label</label> 351 <div role="button" id="btn"></div> 352 `, 353 async function testLabelOnDiv(browser, docAcc) { 354 const btn = findAccessibleChildByID(docAcc, "btn"); 355 356 const label = findAccessibleChildByID(docAcc, "label"); 357 await testCachedRelation(btn, RELATION_LABELLED_BY, []); 358 await testCachedRelation(label, RELATION_LABEL_FOR, []); 359 }, 360 { chrome: true, topLevel: true } 361 );