browser_caching_value.js (13246B)
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/states.js */ 8 loadScripts({ name: "states.js", dir: MOCHITESTS_DIR }); 9 10 /** 11 * Test data has the format of: 12 * { 13 * desc {String} description for better logging 14 * id {String} given accessible DOMNode ID 15 * expected {String} expected value for a given accessible 16 * action {?AsyncFunction} an optional action that awaits a value change 17 * attrs {?Array} an optional list of attributes to update 18 * waitFor {?Number} an optional value change event to wait for 19 * } 20 */ 21 const valueTests = [ 22 { 23 desc: "Initially value is set to 1st element of select", 24 id: "select", 25 expected: "1st", 26 }, 27 { 28 desc: "Value should update to 3rd when 3 is pressed", 29 id: "select", 30 async action(browser) { 31 await invokeFocus(browser, "select"); 32 await invokeContentTask(browser, [], () => { 33 const { ContentTaskUtils } = ChromeUtils.importESModule( 34 "resource://testing-common/ContentTaskUtils.sys.mjs" 35 ); 36 const EventUtils = ContentTaskUtils.getEventUtils(content); 37 EventUtils.synthesizeKey("3", {}, content); 38 }); 39 }, 40 waitFor: EVENT_TEXT_VALUE_CHANGE, 41 expected: "3rd", 42 }, 43 { 44 desc: "Initially value is set to @aria-valuenow for slider", 45 id: "slider", 46 expected: ["5", 5, 0, 7, 0], 47 }, 48 { 49 desc: "Value should change when @aria-valuenow is updated", 50 id: "slider", 51 attrs: [ 52 { 53 attr: "aria-valuenow", 54 value: "6", 55 }, 56 ], 57 waitFor: EVENT_VALUE_CHANGE, 58 expected: ["6", 6, 0, 7, 0], 59 }, 60 { 61 desc: "Value should change when @aria-valuetext is set", 62 id: "slider", 63 attrs: [ 64 { 65 attr: "aria-valuetext", 66 value: "plain", 67 }, 68 ], 69 waitFor: EVENT_TEXT_VALUE_CHANGE, 70 expected: ["plain", 6, 0, 7, 0], 71 }, 72 { 73 desc: "Value should change when @aria-valuetext is updated", 74 id: "slider", 75 attrs: [ 76 { 77 attr: "aria-valuetext", 78 value: "hey!", 79 }, 80 ], 81 waitFor: EVENT_TEXT_VALUE_CHANGE, 82 expected: ["hey!", 6, 0, 7, 0], 83 }, 84 { 85 desc: "Value should change to @aria-valuetext when @aria-valuenow is removed", 86 id: "slider", 87 attrs: [ 88 { 89 attr: "aria-valuenow", 90 }, 91 ], 92 expected: ["hey!", 3.5, 0, 7, 0], 93 }, 94 { 95 desc: "Initially value is not set for combobox", 96 id: "combobox", 97 expected: "", 98 }, 99 { 100 desc: "Value should change when @value attribute is updated", 101 id: "combobox", 102 attrs: [ 103 { 104 attr: "value", 105 value: "hello", 106 }, 107 ], 108 waitFor: EVENT_TEXT_VALUE_CHANGE, 109 expected: "hello", 110 }, 111 { 112 desc: "Initially value corresponds to @value attribute for progress", 113 id: "progress", 114 expected: "22%", 115 }, 116 { 117 desc: "Value should change when @value attribute is updated", 118 id: "progress", 119 attrs: [ 120 { 121 attr: "value", 122 value: "50", 123 }, 124 ], 125 waitFor: EVENT_VALUE_CHANGE, 126 expected: "50%", 127 }, 128 { 129 desc: "Setting currentValue on a progress accessible should fail", 130 id: "progress", 131 async action(browser, acc) { 132 acc.QueryInterface(nsIAccessibleValue); 133 try { 134 acc.currentValue = 25; 135 ok(false, "Setting currValue on progress element should fail"); 136 } catch (e) {} 137 }, 138 expected: "50%", 139 }, 140 { 141 desc: "Initially value corresponds to @value attribute for range", 142 id: "range", 143 expected: "6", 144 }, 145 { 146 desc: "Value should change when slider is moved", 147 id: "range", 148 async action(browser) { 149 await invokeFocus(browser, "range"); 150 await invokeContentTask(browser, [], () => { 151 const { ContentTaskUtils } = ChromeUtils.importESModule( 152 "resource://testing-common/ContentTaskUtils.sys.mjs" 153 ); 154 const EventUtils = ContentTaskUtils.getEventUtils(content); 155 EventUtils.synthesizeKey("VK_LEFT", {}, content); 156 }); 157 }, 158 waitFor: EVENT_VALUE_CHANGE, 159 expected: "5", 160 }, 161 { 162 desc: "Value should change when currentValue is called", 163 id: "range", 164 async action(browser, acc) { 165 acc.QueryInterface(nsIAccessibleValue); 166 acc.currentValue = 4; 167 }, 168 waitFor: EVENT_VALUE_CHANGE, 169 expected: "4", 170 }, 171 { 172 desc: "Initially textbox value is text subtree", 173 id: "textbox", 174 expected: "Some rich text", 175 }, 176 { 177 desc: "Textbox value changes when subtree changes", 178 id: "textbox", 179 async action(browser) { 180 await invokeContentTask(browser, [], () => { 181 let boldText = content.document.createElement("strong"); 182 boldText.textContent = " bold"; 183 content.document.getElementById("textbox").appendChild(boldText); 184 }); 185 }, 186 waitFor: EVENT_TEXT_VALUE_CHANGE, 187 expected: "Some rich text bold", 188 }, 189 ]; 190 191 /** 192 * Like testValue in accessible/tests/mochitest/value.js, but waits for cache 193 * updates. 194 */ 195 async function testValue(acc, value, currValue, minValue, maxValue, minIncr) { 196 const pretty = prettyName(acc); 197 await untilCacheIs(() => acc.value, value, `Wrong value of ${pretty}`); 198 199 await untilCacheIs( 200 () => acc.currentValue, 201 currValue, 202 `Wrong current value of ${pretty}` 203 ); 204 await untilCacheIs( 205 () => acc.minimumValue, 206 minValue, 207 `Wrong minimum value of ${pretty}` 208 ); 209 await untilCacheIs( 210 () => acc.maximumValue, 211 maxValue, 212 `Wrong maximum value of ${pretty}` 213 ); 214 await untilCacheIs( 215 () => acc.minimumIncrement, 216 minIncr, 217 `Wrong minimum increment value of ${pretty}` 218 ); 219 } 220 221 /** 222 * Test caching of accessible object values 223 */ 224 addAccessibleTask( 225 ` 226 <div id="slider" role="slider" aria-valuenow="5" 227 aria-valuemin="0" aria-valuemax="7">slider</div> 228 <select id="select"> 229 <option>1st</option> 230 <option>2nd</option> 231 <option>3rd</option> 232 </select> 233 <input id="combobox" role="combobox" aria-autocomplete="inline"> 234 <progress id="progress" value="22" max="100"></progress> 235 <input type="range" id="range" min="0" max="10" value="6"> 236 <div contenteditable="yes" role="textbox" id="textbox">Some <a href="#">rich</a> text</div>`, 237 async function (browser, accDoc) { 238 for (let { desc, id, action, attrs, expected, waitFor } of valueTests) { 239 info(desc); 240 let acc = findAccessibleChildByID(accDoc, id); 241 let onUpdate; 242 243 if (waitFor) { 244 onUpdate = waitForEvent(waitFor, id); 245 } 246 247 if (action) { 248 await action(browser, acc); 249 } else if (attrs) { 250 for (let { attr, value } of attrs) { 251 await invokeSetAttribute(browser, id, attr, value); 252 } 253 } 254 255 await onUpdate; 256 if (Array.isArray(expected)) { 257 acc.QueryInterface(nsIAccessibleValue); 258 await testValue(acc, ...expected); 259 } else { 260 is(acc.value, expected, `Correct value for ${prettyName(acc)}`); 261 } 262 } 263 }, 264 { iframe: true, remoteIframe: true } 265 ); 266 267 /** 268 * Test caching of link URL values. 269 */ 270 addAccessibleTask( 271 `<a id="link" href="https://example.com/">Test</a>`, 272 async function (browser, docAcc) { 273 let link = findAccessibleChildByID(docAcc, "link"); 274 is(link.value, "https://example.com/", "link initial value correct"); 275 const textLeaf = link.firstChild; 276 is(textLeaf.value, "https://example.com/", "link initial value correct"); 277 278 info("Changing link href"); 279 await invokeSetAttribute(browser, "link", "href", "https://example.net/"); 280 await untilCacheIs( 281 () => link.value, 282 "https://example.net/", 283 "link value correct after change" 284 ); 285 286 info("Removing link href"); 287 let onRecreation = waitForEvents({ 288 expected: [ 289 [EVENT_HIDE, link], 290 [EVENT_SHOW, "link"], 291 ], 292 }); 293 await invokeSetAttribute(browser, "link", "href"); 294 await onRecreation; 295 link = findAccessibleChildByID(docAcc, "link"); 296 await untilCacheIs(() => link.value, "", "link value empty after removal"); 297 298 info("Setting link href"); 299 onRecreation = waitForEvents({ 300 expected: [ 301 [EVENT_HIDE, link], 302 [EVENT_SHOW, "link"], 303 ], 304 }); 305 await invokeSetAttribute(browser, "link", "href", "https://example.com/"); 306 await onRecreation; 307 link = findAccessibleChildByID(docAcc, "link"); 308 await untilCacheIs( 309 () => link.value, 310 "https://example.com/", 311 "link value correct after change" 312 ); 313 }, 314 { chrome: true, topLevel: true, iframe: true, remoteIframe: true } 315 ); 316 317 /** 318 * Test caching of active state for select options - see bug 1788143. 319 */ 320 addAccessibleTask( 321 ` 322 <select id="select"> 323 <option id="first_option">First</option> 324 <option id="second_option">Second</option> 325 </select>`, 326 async function (browser, docAcc) { 327 const select = findAccessibleChildByID(docAcc, "select"); 328 is(select.value, "First", "Select initial value correct"); 329 330 // Focus the combo box. 331 await invokeFocus(browser, "select"); 332 333 // Select the second option (drop-down collapsed). 334 let p = waitForEvents({ 335 expected: [ 336 [EVENT_SELECTION, "second_option"], 337 [EVENT_TEXT_VALUE_CHANGE, "select"], 338 ], 339 unexpected: [ 340 stateChangeEventArgs("second_option", EXT_STATE_ACTIVE, true, true), 341 stateChangeEventArgs("first_option", EXT_STATE_ACTIVE, false, true), 342 ], 343 }); 344 await invokeContentTask(browser, [], () => { 345 content.document.getElementById("select").selectedIndex = 1; 346 }); 347 await p; 348 349 is(select.value, "Second", "Select value correct after changing option"); 350 351 // Expand the combobox dropdown. 352 p = waitForEvent(EVENT_STATE_CHANGE, "ContentSelectDropdown"); 353 EventUtils.synthesizeKey("VK_SPACE"); 354 await p; 355 356 p = waitForEvents({ 357 expected: [ 358 [EVENT_SELECTION, "first_option"], 359 [EVENT_TEXT_VALUE_CHANGE, "select"], 360 [EVENT_HIDE, "ContentSelectDropdown"], 361 ], 362 unexpected: [ 363 stateChangeEventArgs("first_option", EXT_STATE_ACTIVE, true, true), 364 stateChangeEventArgs("second_option", EXT_STATE_ACTIVE, false, true), 365 ], 366 }); 367 368 // Press the up arrow to select the first option (drop-down expanded). 369 // Then, press Enter to confirm the selection and close the dropdown. 370 // We do both of these together to unify testing across platforms, since 371 // events are not entirely consistent on Windows vs. Linux + macOS. 372 EventUtils.synthesizeKey("VK_UP"); 373 EventUtils.synthesizeKey("VK_RETURN"); 374 await p; 375 376 is( 377 select.value, 378 "First", 379 "Select value correct after changing option back" 380 ); 381 }, 382 { chrome: true, topLevel: true, iframe: true, remoteIframe: true } 383 ); 384 385 /** 386 * Test combobox values for non-editable comboboxes. 387 */ 388 addAccessibleTask( 389 ` 390 <div id="combo-div-1" role="combobox">value</div> 391 <div id="combo-div-2" role="combobox"> 392 <div role="listbox"> 393 <div role="option">value</div> 394 </div> 395 </div> 396 <div id="combo-div-3" role="combobox"> 397 <div role="group">value</div> 398 </div> 399 <div id="combo-div-4" role="combobox">foo 400 <div role="listbox"> 401 <div role="option">bar</div> 402 </div> 403 </div> 404 405 <input id="combo-input-1" role="combobox" value="value" disabled></input> 406 <input id="combo-input-2" role="combobox" value="value" disabled>testing</input> 407 408 <div id="combo-div-selected" role="combobox"> 409 <div role="listbox"> 410 <div aria-selected="true" role="option">value</div> 411 </div> 412 </div> 413 `, 414 async function (browser, docAcc) { 415 const comboDiv1 = findAccessibleChildByID(docAcc, "combo-div-1"); 416 const comboDiv2 = findAccessibleChildByID(docAcc, "combo-div-2"); 417 const comboDiv3 = findAccessibleChildByID(docAcc, "combo-div-3"); 418 const comboDiv4 = findAccessibleChildByID(docAcc, "combo-div-4"); 419 const comboInput1 = findAccessibleChildByID(docAcc, "combo-input-1"); 420 const comboInput2 = findAccessibleChildByID(docAcc, "combo-input-2"); 421 const comboDivSelected = findAccessibleChildByID( 422 docAcc, 423 "combo-div-selected" 424 ); 425 426 // Text as a descendant of the combobox: included in the value. 427 is(comboDiv1.value, "value", "Combobox value correct"); 428 429 // Text as the descendant of a listbox: excluded from the value. 430 is(comboDiv2.value, "", "Combobox value correct"); 431 432 // Text as the descendant of some other role that includes text in name computation. 433 // Here, the group role contains the text node with "value" in it. 434 is(comboDiv3.value, "value", "Combobox value correct"); 435 436 // Some descendant text included, but text descendant of a listbox excluded. 437 is(comboDiv4.value, "foo", "Combobox value correct"); 438 439 // Combobox inputs with explicit value report that value. 440 is(comboInput1.value, "value", "Combobox value correct"); 441 is(comboInput2.value, "value", "Combobox value correct"); 442 443 // Combobox role with aria-selected reports correct value. 444 is(comboDivSelected.value, "value", "Combobox value correct"); 445 }, 446 { chrome: true, iframe: true, remoteIframe: true } 447 );