browser_accesskeys.js (7534B)
1 add_task(async function () { 2 await pushPrefs( 3 ["test.wait300msAfterTabSwitch", true], 4 ["ui.key.contentAccess", 5], 5 ["ui.key.chromeAccess", 5] 6 ); 7 8 const gPageURL1 = 9 "data:text/html,<body><p>" + 10 "<button id='button' accesskey='y'>Button</button>" + 11 "<input id='checkbox' type='checkbox' accesskey='z'>Checkbox" + 12 "</p></body>"; 13 let tab1 = await BrowserTestUtils.openNewForegroundTab(gBrowser, gPageURL1); 14 15 Services.focus.clearFocus(window); 16 17 // Press an accesskey in the child document while the chrome is focused. 18 let focusedId = await performAccessKey(tab1.linkedBrowser, "y"); 19 is(focusedId, "button", "button accesskey"); 20 21 // Press an accesskey in the child document while the content document is focused. 22 focusedId = await performAccessKey(tab1.linkedBrowser, "z"); 23 is(focusedId, "checkbox", "checkbox accesskey"); 24 25 // Add an element with an accesskey to the chrome and press its accesskey while the chrome is focused. 26 let newButton = document.createXULElement("button"); 27 newButton.id = "chromebutton"; 28 newButton.setAttribute("aria-label", "chromebutton"); 29 newButton.setAttribute("accesskey", "z"); 30 document.documentElement.appendChild(newButton); 31 Services.focus.clearFocus(window); 32 33 newButton.getBoundingClientRect(); // Accesskey registration happens during frame construction. 34 35 focusedId = await performAccessKeyForChrome("z"); 36 is(focusedId, "chromebutton", "chromebutton accesskey"); 37 38 // Add a second tab and ensure that accesskey from the first tab is not used. 39 const gPageURL2 = 40 "data:text/html,<body>" + 41 "<button id='tab2button' accesskey='y'>Button in Tab 2</button>" + 42 "</body>"; 43 let tab2 = await BrowserTestUtils.openNewForegroundTab(gBrowser, gPageURL2); 44 45 Services.focus.clearFocus(window); 46 47 focusedId = await performAccessKey(tab2.linkedBrowser, "y"); 48 is(focusedId, "tab2button", "button accesskey in tab2"); 49 50 // Press the accesskey for the chrome element while the content document is focused. 51 focusedId = await performAccessKeyForChrome("z"); 52 is(focusedId, "chromebutton", "chromebutton accesskey"); 53 54 gBrowser.removeTab(tab1); 55 gBrowser.removeTab(tab2); 56 57 // Test whether access key for the newButton isn't available when content 58 // consumes the key event. 59 60 // When content in the tab3 consumes all keydown events. 61 const gPageURL3 = 62 "data:text/html,<body id='tab3body'>" + 63 "<button id='tab3button' accesskey='y'>Button in Tab 3</button>" + 64 "<script>" + 65 "document.body.addEventListener('keydown', (event)=>{ event.preventDefault(); });" + 66 "</script></body>"; 67 let tab3 = await BrowserTestUtils.openNewForegroundTab(gBrowser, gPageURL3); 68 69 Services.focus.clearFocus(window); 70 71 focusedId = await performAccessKey(tab3.linkedBrowser, "y"); 72 is(focusedId, "tab3button", "button accesskey in tab3 should be focused"); 73 74 newButton.onfocus = () => { 75 ok(false, "chromebutton shouldn't get focus during testing with tab3"); 76 }; 77 78 // Press the accesskey for the chrome element while the content document is focused. 79 focusedId = await performAccessKey(tab3.linkedBrowser, "z"); 80 is( 81 focusedId, 82 "tab3body", 83 "button accesskey in tab3 should keep having focus" 84 ); 85 86 newButton.onfocus = null; 87 88 gBrowser.removeTab(tab3); 89 90 // When content in the tab4 consumes all keypress events. 91 const gPageURL4 = 92 "data:text/html,<body id='tab4body'>" + 93 "<button id='tab4button' accesskey='y'>Button in Tab 4</button>" + 94 "<script>" + 95 "document.body.addEventListener('keypress', (event)=>{ event.preventDefault(); });" + 96 "</script></body>"; 97 let tab4 = await BrowserTestUtils.openNewForegroundTab(gBrowser, gPageURL4); 98 99 Services.focus.clearFocus(window); 100 101 focusedId = await performAccessKey(tab4.linkedBrowser, "y"); 102 is(focusedId, "tab4button", "button accesskey in tab4 should be focused"); 103 104 newButton.onfocus = () => { 105 // EventStateManager handles accesskey before dispatching keypress event 106 // into the DOM tree, therefore, chrome accesskey always wins focus from 107 // content. However, this is different from shortcut keys. 108 todo(false, "chromebutton shouldn't get focus during testing with tab4"); 109 }; 110 111 // Press the accesskey for the chrome element while the content document is focused. 112 focusedId = await performAccessKey(tab4.linkedBrowser, "z"); 113 is( 114 focusedId, 115 "tab4body", 116 "button accesskey in tab4 should keep having focus" 117 ); 118 119 newButton.onfocus = null; 120 121 gBrowser.removeTab(tab4); 122 123 newButton.remove(); 124 125 // Accesskey on input label in shadow host should work 126 const gPageURL5 = `data:text/html, 127 <body> 128 <div id="host"> 129 <template shadowrootmode="open"> 130 <label for="tab5Checkbox" accesskey="y">Label accesskey is "y":</label> 131 <input id="tab5Checkbox" type="checkbox"> 132 </template> 133 </div> 134 </body>`; 135 let tab5 = await BrowserTestUtils.openNewForegroundTab(gBrowser, gPageURL5); 136 Services.focus.clearFocus(window); 137 focusedId = await performAccessKey(tab5.linkedBrowser, "y"); 138 139 is(focusedId, "host", "the shadow host in tab5 should be focused"); 140 141 gBrowser.removeTab(tab5); 142 }); 143 144 function performAccessKey(browser, key) { 145 return new Promise(resolve => { 146 let removeFocus, removeKeyDown, removeKeyUp; 147 function callback() { 148 removeFocus(); 149 removeKeyUp(); 150 removeKeyDown(); 151 152 SpecialPowers.spawn(browser, [], () => { 153 let oldFocusedElement = content._oldFocusedElement; 154 delete content._oldFocusedElement; 155 return oldFocusedElement.id; 156 }).then(oldFocus => resolve(oldFocus)); 157 } 158 159 removeFocus = BrowserTestUtils.addContentEventListener( 160 browser, 161 "focus", 162 callback, 163 { capture: true }, 164 event => { 165 if (!HTMLElement.isInstance(event.target)) { 166 return false; // ignore window and document focus events 167 } 168 169 event.target.ownerGlobal._sent = true; 170 let focusedElement = event.target.ownerGlobal.document.activeElement; 171 event.target.ownerGlobal._oldFocusedElement = focusedElement; 172 focusedElement.blur(); 173 return true; 174 } 175 ); 176 177 removeKeyDown = BrowserTestUtils.addContentEventListener( 178 browser, 179 "keydown", 180 () => {}, 181 { capture: true }, 182 event => { 183 event.target.ownerGlobal._sent = false; 184 return true; 185 } 186 ); 187 188 removeKeyUp = BrowserTestUtils.addContentEventListener( 189 browser, 190 "keyup", 191 callback, 192 {}, 193 event => { 194 if (!event.target.ownerGlobal._sent) { 195 event.target.ownerGlobal._sent = true; 196 let focusedElement = event.target.ownerGlobal.document.activeElement; 197 event.target.ownerGlobal._oldFocusedElement = focusedElement; 198 focusedElement.blur(); 199 return true; 200 } 201 202 return false; 203 } 204 ); 205 206 // Spawn an no-op content task to better ensure that the messages 207 // for adding the event listeners above get handled. 208 SpecialPowers.spawn(browser, [], () => {}).then(() => { 209 EventUtils.synthesizeKey(key, { altKey: true, shiftKey: true }); 210 }); 211 }); 212 } 213 214 // This version is used when a chrome element is expected to be found for an accesskey. 215 async function performAccessKeyForChrome(key) { 216 let waitFocusChangePromise = BrowserTestUtils.waitForEvent( 217 document, 218 "focus", 219 true 220 ); 221 EventUtils.synthesizeKey(key, { altKey: true, shiftKey: true }); 222 await waitFocusChangePromise; 223 return document.activeElement.id; 224 }