browser_reservedkey.js (10252B)
1 add_task(async function test_reserved_shortcuts() { 2 let keyset = document.createXULElement("keyset"); 3 let key1 = document.createXULElement("key"); 4 key1.setAttribute("id", "kt_reserved"); 5 key1.setAttribute("modifiers", "shift"); 6 key1.setAttribute("key", "O"); 7 key1.setAttribute("reserved", "true"); 8 key1.setAttribute("count", "0"); 9 key1.addEventListener("command", () => { 10 let attribute = key1.getAttribute("count"); 11 key1.setAttribute("count", Number(attribute) + 1); 12 }); 13 14 let key2 = document.createXULElement("key"); 15 key2.setAttribute("id", "kt_notreserved"); 16 key2.setAttribute("modifiers", "shift"); 17 key2.setAttribute("key", "P"); 18 key2.setAttribute("reserved", "false"); 19 key2.setAttribute("count", "0"); 20 key2.addEventListener("command", () => { 21 let attribute = key2.getAttribute("count"); 22 key2.setAttribute("count", Number(attribute) + 1); 23 }); 24 25 let key3 = document.createXULElement("key"); 26 key3.setAttribute("id", "kt_reserveddefault"); 27 key3.setAttribute("modifiers", "shift"); 28 key3.setAttribute("key", "Q"); 29 key3.setAttribute("count", "0"); 30 key3.addEventListener("command", () => { 31 let attribute = key3.getAttribute("count"); 32 key3.setAttribute("count", Number(attribute) + 1); 33 }); 34 35 keyset.appendChild(key1); 36 keyset.appendChild(key2); 37 keyset.appendChild(key3); 38 let container = document.createXULElement("box"); 39 container.appendChild(keyset); 40 document.documentElement.appendChild(container); 41 42 const pageUrl = 43 "data:text/html,<body onload='document.body.firstElementChild.focus();'><div onkeydown='event.preventDefault();' tabindex=0>Test</div></body>"; 44 let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, pageUrl); 45 46 EventUtils.sendString("OPQ"); 47 48 is( 49 document.getElementById("kt_reserved").getAttribute("count"), 50 "1", 51 "reserved='true' with preference off" 52 ); 53 is( 54 document.getElementById("kt_notreserved").getAttribute("count"), 55 "0", 56 "reserved='false' with preference off" 57 ); 58 is( 59 document.getElementById("kt_reserveddefault").getAttribute("count"), 60 "0", 61 "default reserved with preference off" 62 ); 63 64 // Now try with reserved shortcut key handling enabled. 65 await new Promise(resolve => { 66 SpecialPowers.pushPrefEnv( 67 { set: [["permissions.default.shortcuts", 2]] }, 68 resolve 69 ); 70 }); 71 72 EventUtils.sendString("OPQ"); 73 74 is( 75 document.getElementById("kt_reserved").getAttribute("count"), 76 "2", 77 "reserved='true' with preference on" 78 ); 79 is( 80 document.getElementById("kt_notreserved").getAttribute("count"), 81 "0", 82 "reserved='false' with preference on" 83 ); 84 is( 85 document.getElementById("kt_reserveddefault").getAttribute("count"), 86 "1", 87 "default reserved with preference on" 88 ); 89 90 document.documentElement.removeChild(container); 91 92 BrowserTestUtils.removeTab(tab); 93 }); 94 95 // This test checks that Alt+<key> and F10 cannot be blocked when the preference is set. 96 if (!navigator.platform.includes("Mac")) { 97 add_task(async function test_accesskeys_menus() { 98 await new Promise(resolve => { 99 SpecialPowers.pushPrefEnv( 100 { set: [["permissions.default.shortcuts", 2]] }, 101 resolve 102 ); 103 }); 104 105 const uri = 106 'data:text/html,<body onkeydown=\'if (event.key == "H" || event.key == "F10") event.preventDefault();\'>'; 107 let tab1 = await BrowserTestUtils.openNewForegroundTab(gBrowser, uri); 108 109 // Pressing Alt+H should open the Help menu. 110 let helpPopup = document.getElementById("menu_HelpPopup"); 111 let popupShown = BrowserTestUtils.waitForEvent(helpPopup, "popupshown"); 112 EventUtils.synthesizeKey("KEY_Alt", { type: "keydown" }); 113 EventUtils.synthesizeKey("h", { altKey: true }); 114 EventUtils.synthesizeKey("KEY_Alt", { type: "keyup" }); 115 await popupShown; 116 117 ok(true, "Help menu opened"); 118 119 let popupHidden = BrowserTestUtils.waitForEvent(helpPopup, "popuphidden"); 120 helpPopup.hidePopup(); 121 await popupHidden; 122 123 // Pressing F10 should focus the menubar. On Linux, the file menu should open, but on Windows, 124 // pressing Down will open the file menu. 125 let menubar = document.getElementById("main-menubar"); 126 let menubarActive = BrowserTestUtils.waitForEvent( 127 menubar, 128 "DOMMenuBarActive" 129 ); 130 EventUtils.synthesizeKey("KEY_F10"); 131 await menubarActive; 132 133 let filePopup = document.getElementById("menu_FilePopup"); 134 popupShown = BrowserTestUtils.waitForEvent(filePopup, "popupshown"); 135 if (navigator.platform.includes("Win")) { 136 EventUtils.synthesizeKey("KEY_ArrowDown"); 137 } 138 await popupShown; 139 140 ok(true, "File menu opened"); 141 142 popupHidden = BrowserTestUtils.waitForEvent(filePopup, "popuphidden"); 143 filePopup.hidePopup(); 144 await popupHidden; 145 146 BrowserTestUtils.removeTab(tab1); 147 }); 148 } 149 150 // There is a <key> element for Backspace and delete with reserved="false", 151 // so make sure that it is not treated as a blocked shortcut key. 152 add_task(async function test_backspace_delete() { 153 await new Promise(resolve => { 154 SpecialPowers.pushPrefEnv( 155 { set: [["permissions.default.shortcuts", 2]] }, 156 resolve 157 ); 158 }); 159 160 // The input field is autofocused. If this test fails, backspace can go back 161 // in history so cancel the beforeunload event and adjust the field to make the test fail. 162 const uri = 163 'data:text/html,<body onbeforeunload=\'document.getElementById("field").value = "failed";\'>' + 164 "<input id='field' value='something'></body>"; 165 let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, uri); 166 167 await SpecialPowers.spawn(tab.linkedBrowser, [], async function () { 168 content.document.getElementById("field").focus(); 169 170 // Add a promise that resolves when the backspace key gets received 171 // so we can ensure the key gets received before checking the result. 172 content.keysPromise = new Promise(resolve => { 173 content.addEventListener("keyup", event => { 174 if (event.code == "Backspace") { 175 resolve(content.document.getElementById("field").value); 176 } 177 }); 178 }); 179 }); 180 181 // Move the caret so backspace will delete the first character. 182 EventUtils.synthesizeKey("KEY_ArrowRight", {}); 183 EventUtils.synthesizeKey("KEY_Backspace", {}); 184 185 let fieldValue = await SpecialPowers.spawn( 186 tab.linkedBrowser, 187 [], 188 async function () { 189 return content.keysPromise; 190 } 191 ); 192 is(fieldValue, "omething", "backspace not prevented"); 193 194 // now do the same thing for the delete key: 195 await SpecialPowers.spawn(tab.linkedBrowser, [], async function () { 196 content.document.getElementById("field").focus(); 197 198 // Add a promise that resolves when the backspace key gets received 199 // so we can ensure the key gets received before checking the result. 200 content.keysPromise = new Promise(resolve => { 201 content.addEventListener("keyup", event => { 202 if (event.code == "Delete") { 203 resolve(content.document.getElementById("field").value); 204 } 205 }); 206 }); 207 }); 208 209 // Move the caret so backspace will delete the first character. 210 EventUtils.synthesizeKey("KEY_Delete", {}); 211 212 fieldValue = await SpecialPowers.spawn( 213 tab.linkedBrowser, 214 [], 215 async function () { 216 return content.keysPromise; 217 } 218 ); 219 is(fieldValue, "mething", "delete not prevented"); 220 221 BrowserTestUtils.removeTab(tab); 222 }); 223 224 // TODO: Make this to run on Windows too to have automated tests also there. 225 if ( 226 navigator.platform.includes("Mac") || 227 navigator.platform.includes("Linux") 228 ) { 229 add_task( 230 async function test_reserved_shortcuts_conflict_with_user_settings() { 231 await new Promise(resolve => { 232 SpecialPowers.pushPrefEnv( 233 { set: [["test.events.async.enabled", true]] }, 234 resolve 235 ); 236 }); 237 238 const keyset = document.createXULElement("keyset"); 239 const key = document.createXULElement("key"); 240 key.setAttribute("id", "conflict_with_known_native_key_binding"); 241 if (navigator.platform.includes("Mac")) { 242 // Select to end of the paragraph 243 key.setAttribute("modifiers", "ctrl,shift"); 244 key.setAttribute("key", "E"); 245 } else { 246 // Select All 247 key.setAttribute("modifiers", "ctrl"); 248 key.setAttribute("key", "a"); 249 } 250 key.setAttribute("reserved", "true"); 251 key.setAttribute("count", "0"); 252 key.addEventListener("command", () => { 253 const attribute = key.getAttribute("count"); 254 key.setAttribute("count", Number(attribute) + 1); 255 }); 256 257 keyset.appendChild(key); 258 const container = document.createXULElement("box"); 259 container.appendChild(keyset); 260 document.documentElement.appendChild(container); 261 262 const pageUrl = 263 "data:text/html,<body onload='document.body.firstChild.focus(); getSelection().collapse(document.body.firstChild, 0)'><div contenteditable>Test</div></body>"; 264 const tab = await BrowserTestUtils.openNewForegroundTab( 265 gBrowser, 266 pageUrl 267 ); 268 269 await SpecialPowers.spawn( 270 tab.linkedBrowser, 271 [key.getAttribute("key")], 272 async function (aExpectedKeyValue) { 273 content.promiseTestResult = new Promise(resolve => { 274 content.addEventListener("keyup", event => { 275 if (event.key.toLowerCase() == aExpectedKeyValue.toLowerCase()) { 276 resolve(content.getSelection().getRangeAt(0).toString()); 277 } 278 }); 279 }); 280 } 281 ); 282 283 EventUtils.synthesizeKey(key.getAttribute("key"), { 284 ctrlKey: key.getAttribute("modifiers").includes("ctrl"), 285 shiftKey: key.getAttribute("modifiers").includes("shift"), 286 }); 287 288 const selectedText = await SpecialPowers.spawn( 289 tab.linkedBrowser, 290 [], 291 async function () { 292 return content.promiseTestResult; 293 } 294 ); 295 is( 296 selectedText, 297 "Test", 298 "The shortcut key should select all text in the editor" 299 ); 300 301 is( 302 key.getAttribute("count"), 303 "0", 304 "The reserved shortcut key should be consumed by the focused editor instead" 305 ); 306 307 document.documentElement.removeChild(container); 308 309 BrowserTestUtils.removeTab(tab); 310 } 311 ); 312 }