browser_alt_keyup_in_content.js (11793B)
1 "use strict"; 2 3 add_task(async function runTests() { 4 const menubar = document.getElementById("toolbar-menubar"); 5 const autohide = menubar.getAttribute("autohide"); 6 // This test requires that the window is active because of the limitation of 7 // menubar. Therefore, we should abort if the window becomes inactive during 8 // the tests. 9 let runningTests = true; 10 function onWindowActive(aEvent) { 11 // Don't warn after timed out. 12 if (runningTests && aEvent.target === window) { 13 info( 14 "WARNING: This window shouldn't have been inactivated during tests, but received an activated event!" 15 ); 16 } 17 } 18 function onWindowInactive(aEvent) { 19 // Don't warn after timed out. 20 if (runningTests && aEvent.target === window) { 21 info( 22 "WARNING: This window should be active during tests, but inactivated!" 23 ); 24 window.focus(); 25 } 26 } 27 let menubarActivated = false; 28 function onMenubarActive() { 29 menubarActivated = true; 30 } 31 // In this test, menu popups shouldn't be open, but this helps avoiding 32 // intermittent failure after inactivating the menubar. 33 let popupEvents = 0; 34 function getPopupInfo(aPopupEventTarget) { 35 return `<${ 36 aPopupEventTarget.nodeName 37 }${aPopupEventTarget.getAttribute("id") !== null ? ` id="${aPopupEventTarget.getAttribute("id")}"` : ""}>`; 38 } 39 function onPopupShown(aEvent) { 40 // Don't warn after timed out. 41 if (!runningTests) { 42 return; 43 } 44 popupEvents++; 45 info( 46 `A popup (${getPopupInfo( 47 aEvent.target 48 )}) is shown (visible popups: ${popupEvents})` 49 ); 50 } 51 function onPopupHidden(aEvent) { 52 // Don't warn after timed out. 53 if (!runningTests) { 54 return; 55 } 56 if (popupEvents === 0) { 57 info( 58 `WARNING: There are some unexpected popups which may be not cleaned up by the previous test (${getPopupInfo( 59 aEvent.target 60 )})` 61 ); 62 return; 63 } 64 popupEvents--; 65 info( 66 `A popup (${getPopupInfo( 67 aEvent.target 68 )}) is hidden (visible popups: ${popupEvents})` 69 ); 70 } 71 try { 72 Services.prefs.setBoolPref("ui.key.menuAccessKeyFocuses", true); 73 // If this fails, you need to replace "KEY_Alt" with a variable whose 74 // value is considered from the pref. 75 is( 76 Services.prefs.getIntPref("ui.key.menuAccessKey"), 77 18, 78 "This test assumes that Alt key activates the menubar" 79 ); 80 window.addEventListener("activate", onWindowActive); 81 window.addEventListener("deactivate", onWindowInactive); 82 window.addEventListener("popupshown", onPopupShown); 83 window.addEventListener("popuphidden", onPopupHidden); 84 menubar.addEventListener("DOMMenuBarActive", onMenubarActive); 85 async function doTest(aTest) { 86 await new Promise(resolve => { 87 if (Services.focus.activeWindow === window) { 88 resolve(); 89 return; 90 } 91 info( 92 `${aTest.description}: The testing window is inactive, trying to activate it...` 93 ); 94 Services.focus.focusedWindow = window; 95 TestUtils.waitForCondition(() => { 96 if (Services.focus.activeWindow === window) { 97 resolve(); 98 return true; 99 } 100 Services.focus.focusedWindow = window; 101 return false; 102 }, `${aTest.description}: Waiting the window is activated`); 103 }); 104 let startTime = ChromeUtils.now(); 105 info(`Start to test: ${aTest.description}...`); 106 107 async function ensureMenubarInactive() { 108 if (!menubar.querySelector("[_moz-menuactive=true]")) { 109 return; 110 } 111 info(`${aTest.description}: Inactivating the menubar...`); 112 let waitForMenuBarInactive = BrowserTestUtils.waitForEvent( 113 menubar, 114 "DOMMenuBarInactive" 115 ); 116 EventUtils.synthesizeKey("KEY_Escape", {}, window); 117 await waitForMenuBarInactive; 118 await TestUtils.waitForCondition(() => { 119 return popupEvents === 0; 120 }, `${aTest.description}: Waiting for closing all popups`); 121 } 122 123 try { 124 await BrowserTestUtils.withNewTab( 125 { 126 gBrowser, 127 url: aTest.url, 128 }, 129 async browser => { 130 info(`${aTest.description}: Waiting browser getting focus...`); 131 await SimpleTest.promiseFocus(browser); 132 await ensureMenubarInactive(); 133 menubarActivated = false; 134 135 let keyupEventFiredInContent = false; 136 BrowserTestUtils.addContentEventListener( 137 browser, 138 "keyup", 139 () => { 140 keyupEventFiredInContent = true; 141 }, 142 { capture: true }, 143 event => { 144 return event.key === "Alt"; 145 } 146 ); 147 148 // For making sure adding the above content event listener and 149 // it'll get `keyup` event, let's run `SpecialPowers.spawn` and 150 // wait for focus in the content process. 151 info( 152 `${aTest.description}: Waiting content process getting focus...` 153 ); 154 await SpecialPowers.spawn( 155 browser, 156 [aTest.description], 157 async aTestDescription => { 158 await ContentTaskUtils.waitForCondition(() => { 159 if ( 160 content.browsingContext.isActive && 161 content.document.hasFocus() 162 ) { 163 return true; 164 } 165 content.window.focus(); 166 return false; 167 }, `${aTestDescription}: Waiting for content gets focus in content process`); 168 } 169 ); 170 171 let waitForAllKeyUpEventsInChrome = new Promise(resolve => { 172 // Wait 2 `keyup` events in the main process. First one is 173 // synthesized one. The other is replay event from content. 174 let firstKeyUpEvent; 175 window.addEventListener( 176 "keyup", 177 function onKeyUpInChrome(event) { 178 if (!firstKeyUpEvent) { 179 firstKeyUpEvent = event; 180 return; 181 } 182 window.removeEventListener("keyup", onKeyUpInChrome, { 183 capture: true, 184 }); 185 resolve(); 186 }, 187 { capture: true } 188 ); 189 }); 190 191 let menubarActivatedPromise; 192 if (aTest.expectMenubarActive) { 193 menubarActivatedPromise = BrowserTestUtils.waitForEvent( 194 menubar, 195 "DOMMenuBarActive" 196 ); 197 } 198 199 EventUtils.synthesizeKey("KEY_Alt", {}, window); 200 info( 201 `${aTest.description}: Waiting keyup events of Alt in chrome...` 202 ); 203 await waitForAllKeyUpEventsInChrome; 204 info(`${aTest.description}: Waiting keyup event in content...`); 205 try { 206 await TestUtils.waitForCondition(() => { 207 return keyupEventFiredInContent; 208 }, `${aTest.description}: Waiting for content gets focus in chrome process`); 209 } catch (ex) { 210 ok( 211 false, 212 `${aTest.description}: Failed to synthesize Alt key press in the content process` 213 ); 214 return; 215 } 216 217 if (aTest.expectMenubarActive) { 218 await menubarActivatedPromise; 219 ok( 220 menubarActivated, 221 `${aTest.description}: Menubar should've been activated by the synthesized Alt key press` 222 ); 223 } else { 224 // Wait some ticks to verify not receiving "DOMMenuBarActive" event. 225 // eslint-disable-next-line mozilla/no-arbitrary-setTimeout 226 await new Promise(resolve => setTimeout(resolve, 500)); 227 ok( 228 !menubarActivated, 229 `${aTest.description}: Menubar should not have been activated by the synthesized Alt key press` 230 ); 231 } 232 } 233 ); 234 } catch (ex) { 235 ok( 236 false, 237 `${aTest.description}: Thrown an exception: ${ex.toString()}` 238 ); 239 } finally { 240 await ensureMenubarInactive(); 241 info(`End testing: ${aTest.description}`); 242 ChromeUtils.addProfilerMarker( 243 "browser-test", 244 { startTime, category: "Test" }, 245 aTest.description 246 ); 247 } 248 } 249 250 // Testcases for users who use collapsible menubar (by default) 251 menubar.setAttribute("autohide", "true"); 252 await doTest({ 253 description: "Testing menubar is shown by Alt keyup", 254 url: "data:text/html;charset=utf-8,<p>static page</p>", 255 expectMenubarActive: true, 256 }); 257 await doTest({ 258 description: 259 "Testing menubar is shown by Alt keyup when an <input> has focus", 260 url: 261 "data:text/html;charset=utf-8,<input>" + 262 '<script>document.querySelector("input").focus()</script>', 263 expectMenubarActive: true, 264 }); 265 await doTest({ 266 description: 267 "Testing menubar is shown by Alt keyup when an editing host has focus", 268 url: 269 "data:text/html;charset=utf-8,<p contenteditable></p>" + 270 '<script>document.querySelector("p[contenteditable]").focus()</script>', 271 expectMenubarActive: true, 272 }); 273 await doTest({ 274 description: 275 "Testing menubar won't be shown by Alt keyup due to suppressed by the page", 276 url: 277 "data:text/html;charset=utf-8,<p>dynamic page</p>" + 278 '<script>window.addEventListener("keyup", event => { event.preventDefault(); })</script>', 279 expectMenubarActive: false, 280 }); 281 282 // Testcases for users who always show the menubar. 283 menubar.setAttribute("autohide", "false"); 284 await doTest({ 285 description: "Testing menubar is activated by Alt keyup", 286 url: "data:text/html;charset=utf-8,<p>static page</p>", 287 expectMenubarActive: true, 288 }); 289 await doTest({ 290 description: 291 "Testing menubar is activated by Alt keyup when an <input> has focus", 292 url: 293 "data:text/html;charset=utf-8,<input>" + 294 '<script>document.querySelector("input").focus()</script>', 295 expectMenubarActive: true, 296 }); 297 await doTest({ 298 description: 299 "Testing menubar is activated by Alt keyup when an editing host has focus", 300 url: 301 "data:text/html;charset=utf-8,<p contenteditable></p>" + 302 '<script>document.querySelector("p[contenteditable]").focus()</script>', 303 expectMenubarActive: true, 304 }); 305 await doTest({ 306 description: 307 "Testing menubar won't be activated by Alt keyup due to suppressed by the page", 308 url: 309 "data:text/html;charset=utf-8,<p>dynamic page</p>" + 310 '<script>window.addEventListener("keyup", event => { event.preventDefault(); })</script>', 311 expectMenubarActive: false, 312 }); 313 runningTests = false; 314 } catch (ex) { 315 ok( 316 false, 317 `Aborting this test due to unexpected the exception (${ex.toString()})` 318 ); 319 runningTests = false; 320 } finally { 321 if (autohide !== null) { 322 menubar.setAttribute("autohide", autohide); 323 } else { 324 menubar.removeAttribute("autohide"); 325 } 326 Services.prefs.clearUserPref("ui.key.menuAccessKeyFocuses"); 327 menubar.removeEventListener("DOMMenuBarActive", onMenubarActive); 328 window.removeEventListener("activate", onWindowActive); 329 window.removeEventListener("deactivate", onWindowInactive); 330 window.removeEventListener("popupshown", onPopupShown); 331 window.removeEventListener("popuphidden", onPopupHidden); 332 } 333 });