browser_caching_actions.js (10389B)
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 const gClickEvents = ["mousedown", "mouseup", "click"]; 8 9 const gActionDescrMap = { 10 jump: "Jump", 11 press: "Press", 12 check: "Check", 13 uncheck: "Uncheck", 14 select: "Select", 15 open: "Open", 16 close: "Close", 17 switch: "Switch", 18 click: "Click", 19 collapse: "Collapse", 20 expand: "Expand", 21 activate: "Activate", 22 cycle: "Cycle", 23 clickAncestor: "Click ancestor", 24 }; 25 26 async function testActions(browser, docAcc, id, expectedActions, domEvents) { 27 info(`Testing element ${id}`); 28 const acc = findAccessibleChildByID(docAcc, id); 29 is(acc.actionCount, expectedActions.length, "Correct action count"); 30 31 let actionNames = []; 32 let actionDescriptions = []; 33 for (let i = 0; i < acc.actionCount; i++) { 34 actionNames.push(acc.getActionName(i)); 35 actionDescriptions.push(acc.getActionDescription(i)); 36 } 37 38 is(actionNames.join(","), expectedActions.join(","), "Correct action names"); 39 is( 40 actionDescriptions.join(","), 41 expectedActions.map(a => gActionDescrMap[a]).join(","), 42 "Correct action descriptions" 43 ); 44 45 if (!domEvents) { 46 return; 47 } 48 49 // We need to set up the listener, and wait for the promise in two separate 50 // content tasks. 51 await invokeContentTask(browser, [id, domEvents], (_id, _domEvents) => { 52 let promises = _domEvents.map( 53 evtName => 54 new Promise(resolve => { 55 const listener = e => { 56 if (e.target.id == _id) { 57 content.removeEventListener(evtName, listener); 58 resolve(42); 59 } 60 }; 61 content.addEventListener(evtName, listener); 62 }) 63 ); 64 content.evtPromise = Promise.all(promises); 65 }); 66 67 acc.doAction(0); 68 69 let eventFired = await invokeContentTask(browser, [], async () => { 70 await content.evtPromise; 71 content.evtPromise = null; 72 return true; 73 }); 74 75 ok(eventFired, `DOM events fired '${domEvents}'`); 76 } 77 78 addAccessibleTask( 79 `<ul> 80 <li id="li_clickable1" data-event="click">Clickable list item</li> 81 <li id="li_clickable2" data-event="mousedown">Clickable list item</li> 82 <li id="li_clickable3" data-event="mouseup">Clickable list item</li> 83 </ul> 84 85 <img id="onclick_img" data-event="click" 86 src="http://example.com/a11y/accessible/tests/mochitest/moz.png"> 87 88 <a id="link1" href="#">linkable textleaf accessible</a> 89 <div id="link2" data-event="click">linkable textleaf accessible</div> 90 91 <a id="link3" href="#"> 92 <img id="link3img" alt="image in link" 93 src="http://example.com/a11y/accessible/tests/mochitest/moz.png"> 94 </a> 95 96 <a href="about:mozilla" id="link4" target="_blank" rel="opener"> 97 <img src="../moz.png" id="link4img"> 98 </a> 99 <a id="link5" data-event="mousedown"> 100 <img src="../moz.png" id="link5img"> 101 </a> 102 <a id="link6" data-event="click"> 103 <img src="../moz.png" id="link6img"> 104 </a> 105 <a id="link7" data-event="mouseup"> 106 <img src="../moz.png" id="link7img"> 107 </a> 108 109 <div> 110 <label for="TextBox_t2" id="label1"> 111 <span>Explicit</span> 112 </label> 113 <input name="in2" id="TextBox_t2" type="text" maxlength="17"> 114 </div> 115 116 <div data-event="click"><p id="p_in_clickable_div">p in clickable div</p></div> 117 118 <img id="map_img" usemap="#map" src="http://example.com/a11y/accessible/tests/mochitest/moz.png" alt="map_img"> 119 <map name="map"> 120 <!-- These coords are deliberately small so that the area does not include 121 the center of the image. 122 --> 123 <area id="area" href="#" shape="rect" coords="0,0,2,2" alt="area"> 124 </map> 125 `, 126 async function (browser, docAcc) { 127 is(docAcc.actionCount, 0, "Doc should not have any actions"); 128 129 const _testActions = async (id, expectedActions, domEvents) => { 130 await testActions(browser, docAcc, id, expectedActions, domEvents); 131 }; 132 133 await _testActions("li_clickable1", ["click"], gClickEvents); 134 await _testActions("li_clickable2", ["click"], gClickEvents); 135 await _testActions("li_clickable3", ["click"], gClickEvents); 136 137 await _testActions("onclick_img", ["click"], gClickEvents); 138 await _testActions("link1", ["jump"], gClickEvents); 139 await _testActions("link2", ["click"], gClickEvents); 140 await _testActions("link3", ["jump"], gClickEvents); 141 await _testActions("link3img", ["clickAncestor"], gClickEvents); 142 await _testActions("link4", ["jump"], gClickEvents); 143 await _testActions("link4img", ["clickAncestor"], gClickEvents); 144 await _testActions("link5", ["click"], gClickEvents); 145 await _testActions("link5img", ["clickAncestor"], gClickEvents); 146 await _testActions("link6", ["click"], gClickEvents); 147 await _testActions("link6img", ["clickAncestor"], gClickEvents); 148 await _testActions("link7", ["click"], gClickEvents); 149 await _testActions("link7img", ["clickAncestor"], gClickEvents); 150 await _testActions("label1", ["click"], gClickEvents); 151 await _testActions("p_in_clickable_div", ["clickAncestor"], gClickEvents); 152 await _testActions("area", ["jump"], gClickEvents); 153 154 await invokeContentTask(browser, [], () => { 155 content.document.getElementById("li_clickable1").onclick = null; 156 }); 157 158 let acc = findAccessibleChildByID(docAcc, "li_clickable1"); 159 await untilCacheIs(() => acc.actionCount, 0, "li has no actions"); 160 let thrown = false; 161 try { 162 acc.doAction(0); 163 } catch (e) { 164 thrown = true; 165 } 166 ok(thrown, "doAction should throw exception"); 167 168 // Remove 'for' from label 169 await invokeContentTask(browser, [], () => { 170 content.document.getElementById("label1").removeAttribute("for"); 171 }); 172 acc = findAccessibleChildByID(docAcc, "label1"); 173 await untilCacheIs(() => acc.actionCount, 0, "label has no actions"); 174 thrown = false; 175 try { 176 acc.doAction(0); 177 ok(false, "doAction should throw exception"); 178 } catch (e) { 179 thrown = true; 180 } 181 ok(thrown, "doAction should throw exception"); 182 183 // Add 'longdesc' to image 184 await invokeContentTask(browser, [], () => { 185 content.document 186 .getElementById("onclick_img") 187 // eslint-disable-next-line @microsoft/sdl/no-insecure-url 188 .setAttribute("longdesc", "http://example.com"); 189 }); 190 acc = findAccessibleChildByID(docAcc, "onclick_img"); 191 await untilCacheIs(() => acc.actionCount, 2, "img has 2 actions"); 192 await _testActions("onclick_img", ["click", "showlongdesc"]); 193 194 // Remove 'onclick' from image with 'longdesc' 195 await invokeContentTask(browser, [], () => { 196 content.document.getElementById("onclick_img").onclick = null; 197 }); 198 acc = findAccessibleChildByID(docAcc, "onclick_img"); 199 await untilCacheIs(() => acc.actionCount, 1, "img has 1 actions"); 200 await _testActions("onclick_img", ["showlongdesc"]); 201 202 // Remove 'href' from link and test linkable child 203 let link1Acc = findAccessibleChildByID(docAcc, "link1"); 204 is( 205 link1Acc.firstChild.getActionName(0), 206 "clickAncestor", 207 "linkable child has clickAncestor action" 208 ); 209 let onRecreation = waitForEvents({ 210 expected: [ 211 [EVENT_HIDE, link1Acc], 212 [EVENT_SHOW, "link1"], 213 ], 214 }); 215 await invokeContentTask(browser, [], () => { 216 let link1 = content.document.getElementById("link1"); 217 link1.removeAttribute("href"); 218 }); 219 await onRecreation; 220 link1Acc = findAccessibleChildByID(docAcc, "link1"); 221 await untilCacheIs(() => link1Acc.actionCount, 0, "link has no actions"); 222 is(link1Acc.firstChild.actionCount, 0, "linkable child's actions removed"); 223 224 // Add a click handler to the body. Ensure it propagates to descendants. 225 await invokeContentTask(browser, [], () => { 226 content.document.body.onclick = () => {}; 227 }); 228 await untilCacheIs(() => docAcc.actionCount, 1, "Doc has 1 action"); 229 await _testActions("link1", ["clickAncestor"]); 230 231 await invokeContentTask(browser, [], () => { 232 content.document.body.onclick = null; 233 }); 234 await untilCacheIs(() => docAcc.actionCount, 0, "Doc has no actions"); 235 is(link1Acc.actionCount, 0, "link has no actions"); 236 237 // Add a click handler to the root element. Ensure it propagates to 238 // descendants. 239 await invokeContentTask(browser, [], () => { 240 content.document.documentElement.onclick = () => {}; 241 }); 242 await untilCacheIs(() => docAcc.actionCount, 1, "Doc has 1 action"); 243 await _testActions("link1", ["clickAncestor"]); 244 }, 245 { 246 chrome: true, 247 topLevel: true, 248 iframe: true, 249 remoteIframe: true, 250 contentSetup: async function contentSetup() { 251 // Attach dummy event handlers here, because inline event handler attributes 252 // will be blocked in the chrome context. 253 for (const el of content.document.querySelectorAll("[data-event]")) { 254 el["on" + el.dataset.event] = () => {}; 255 } 256 }, 257 } 258 ); 259 260 /** 261 * Test access key. 262 */ 263 addAccessibleTask( 264 ` 265 <button id="noKey">noKey</button> 266 <button id="key" accesskey="a">key</button> 267 `, 268 async function (browser, docAcc) { 269 const noKey = findAccessibleChildByID(docAcc, "noKey"); 270 is(noKey.accessKey, "", "noKey has no accesskey"); 271 const key = findAccessibleChildByID(docAcc, "key"); 272 is(key.accessKey, MAC ? "⌃⌥a" : "Alt+Shift+a", "key has correct accesskey"); 273 274 info("Changing accesskey"); 275 await invokeContentTask(browser, [], () => { 276 content.document.getElementById("key").accessKey = "b"; 277 }); 278 await untilCacheIs( 279 () => key.accessKey, 280 MAC ? "⌃⌥b" : "Alt+Shift+b", 281 "Correct accesskey after change" 282 ); 283 284 info("Removing accesskey"); 285 await invokeContentTask(browser, [], () => { 286 content.document.getElementById("key").removeAttribute("accesskey"); 287 }); 288 await untilCacheIs( 289 () => key.accessKey, 290 "", 291 "Empty accesskey after removal" 292 ); 293 294 info("Adding accesskey"); 295 await invokeContentTask(browser, [], () => { 296 content.document.getElementById("key").accessKey = "c"; 297 }); 298 await untilCacheIs( 299 () => key.accessKey, 300 MAC ? "⌃⌥c" : "Alt+Shift+c", 301 "Correct accesskey after addition" 302 ); 303 }, 304 { 305 chrome: true, 306 topLevel: true, 307 iframe: false, // Bug 1796846 308 remoteIframe: false, // Bug 1796846 309 } 310 );