fune/browser/base/content/test/permissions/browser_reservedkey.js
Masayuki Nakano 8afd3c5af5 Bug 1191862 - part 3: Make GlobalKeyListener not reserve key combination which is mapped to an edit command or a navigation command by native key bindings r=NeilDeakin,smaug
Users may map reserved shortcut keys of Firefox/Thunderbird as an editing
command or a navigation command.  Therefore if and only if an editable element
has focus and a reserved key combination is mapped to an editing command or
a navigation command by the system settings, we should allow to dispatch it
into the content and work it as what user expects.

With this change, keyboard only users may loose some shortcut keys to leave
from a web content which blocks keyboard focus in it.  However, there may
be another reserved shortcut keys to escape from such web apps only with
keyboard because it's hard to think that all reserved shortcut keys conflict
with users' settings.

Differential Revision: https://phabricator.services.mozilla.com/D138009
2022-02-15 08:00:06 +00:00

317 lines
10 KiB
JavaScript

add_task(async function test_reserved_shortcuts() {
let keyset = document.createXULElement("keyset");
let key1 = document.createXULElement("key");
key1.setAttribute("id", "kt_reserved");
key1.setAttribute("modifiers", "shift");
key1.setAttribute("key", "O");
key1.setAttribute("reserved", "true");
key1.setAttribute("count", "0");
key1.addEventListener("command", () => {
let attribute = key1.getAttribute("count");
key1.setAttribute("count", Number(attribute) + 1);
});
let key2 = document.createXULElement("key");
key2.setAttribute("id", "kt_notreserved");
key2.setAttribute("modifiers", "shift");
key2.setAttribute("key", "P");
key2.setAttribute("reserved", "false");
key2.setAttribute("count", "0");
key2.addEventListener("command", () => {
let attribute = key2.getAttribute("count");
key2.setAttribute("count", Number(attribute) + 1);
});
let key3 = document.createXULElement("key");
key3.setAttribute("id", "kt_reserveddefault");
key3.setAttribute("modifiers", "shift");
key3.setAttribute("key", "Q");
key3.setAttribute("count", "0");
key3.addEventListener("command", () => {
let attribute = key3.getAttribute("count");
key3.setAttribute("count", Number(attribute) + 1);
});
keyset.appendChild(key1);
keyset.appendChild(key2);
keyset.appendChild(key3);
let container = document.createXULElement("box");
container.appendChild(keyset);
document.documentElement.appendChild(container);
const pageUrl =
"data:text/html,<body onload='document.body.firstElementChild.focus();'><div onkeydown='event.preventDefault();' tabindex=0>Test</div></body>";
let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, pageUrl);
EventUtils.sendString("OPQ");
is(
document.getElementById("kt_reserved").getAttribute("count"),
"1",
"reserved='true' with preference off"
);
is(
document.getElementById("kt_notreserved").getAttribute("count"),
"0",
"reserved='false' with preference off"
);
is(
document.getElementById("kt_reserveddefault").getAttribute("count"),
"0",
"default reserved with preference off"
);
// Now try with reserved shortcut key handling enabled.
await new Promise(resolve => {
SpecialPowers.pushPrefEnv(
{ set: [["permissions.default.shortcuts", 2]] },
resolve
);
});
EventUtils.sendString("OPQ");
is(
document.getElementById("kt_reserved").getAttribute("count"),
"2",
"reserved='true' with preference on"
);
is(
document.getElementById("kt_notreserved").getAttribute("count"),
"0",
"reserved='false' with preference on"
);
is(
document.getElementById("kt_reserveddefault").getAttribute("count"),
"1",
"default reserved with preference on"
);
document.documentElement.removeChild(container);
BrowserTestUtils.removeTab(tab);
});
// This test checks that Alt+<key> and F10 cannot be blocked when the preference is set.
if (!navigator.platform.includes("Mac")) {
add_task(async function test_accesskeys_menus() {
await new Promise(resolve => {
SpecialPowers.pushPrefEnv(
{ set: [["permissions.default.shortcuts", 2]] },
resolve
);
});
const uri =
'data:text/html,<body onkeydown=\'if (event.key == "H" || event.key == "F10") event.preventDefault();\'>';
let tab1 = await BrowserTestUtils.openNewForegroundTab(gBrowser, uri);
// Pressing Alt+H should open the Help menu.
let helpPopup = document.getElementById("menu_HelpPopup");
let popupShown = BrowserTestUtils.waitForEvent(helpPopup, "popupshown");
EventUtils.synthesizeKey("KEY_Alt", { type: "keydown" });
EventUtils.synthesizeKey("h", { altKey: true });
EventUtils.synthesizeKey("KEY_Alt", { type: "keyup" });
await popupShown;
ok(true, "Help menu opened");
let popupHidden = BrowserTestUtils.waitForEvent(helpPopup, "popuphidden");
helpPopup.hidePopup();
await popupHidden;
// Pressing F10 should focus the menubar. On Linux, the file menu should open, but on Windows,
// pressing Down will open the file menu.
let menubar = document.getElementById("main-menubar");
let menubarActive = BrowserTestUtils.waitForEvent(
menubar,
"DOMMenuBarActive"
);
EventUtils.synthesizeKey("KEY_F10");
await menubarActive;
let filePopup = document.getElementById("menu_FilePopup");
popupShown = BrowserTestUtils.waitForEvent(filePopup, "popupshown");
if (navigator.platform.includes("Win")) {
EventUtils.synthesizeKey("KEY_ArrowDown");
}
await popupShown;
ok(true, "File menu opened");
popupHidden = BrowserTestUtils.waitForEvent(filePopup, "popuphidden");
filePopup.hidePopup();
await popupHidden;
BrowserTestUtils.removeTab(tab1);
});
}
// There is a <key> element for Backspace and delete with reserved="false",
// so make sure that it is not treated as a blocked shortcut key.
add_task(async function test_backspace_delete() {
await new Promise(resolve => {
SpecialPowers.pushPrefEnv(
{ set: [["permissions.default.shortcuts", 2]] },
resolve
);
});
// The input field is autofocused. If this test fails, backspace can go back
// in history so cancel the beforeunload event and adjust the field to make the test fail.
const uri =
'data:text/html,<body onbeforeunload=\'document.getElementById("field").value = "failed";\'>' +
"<input id='field' value='something'></body>";
let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, uri);
await SpecialPowers.spawn(tab.linkedBrowser, [], async function() {
content.document.getElementById("field").focus();
// Add a promise that resolves when the backspace key gets received
// so we can ensure the key gets received before checking the result.
content.keysPromise = new Promise(resolve => {
content.addEventListener("keyup", event => {
if (event.code == "Backspace") {
resolve(content.document.getElementById("field").value);
}
});
});
});
// Move the caret so backspace will delete the first character.
EventUtils.synthesizeKey("KEY_ArrowRight", {});
EventUtils.synthesizeKey("KEY_Backspace", {});
let fieldValue = await SpecialPowers.spawn(
tab.linkedBrowser,
[],
async function() {
return content.keysPromise;
}
);
is(fieldValue, "omething", "backspace not prevented");
// now do the same thing for the delete key:
await SpecialPowers.spawn(tab.linkedBrowser, [], async function() {
content.document.getElementById("field").focus();
// Add a promise that resolves when the backspace key gets received
// so we can ensure the key gets received before checking the result.
content.keysPromise = new Promise(resolve => {
content.addEventListener("keyup", event => {
if (event.code == "Delete") {
resolve(content.document.getElementById("field").value);
}
});
});
});
// Move the caret so backspace will delete the first character.
EventUtils.synthesizeKey("KEY_Delete", {});
fieldValue = await SpecialPowers.spawn(
tab.linkedBrowser,
[],
async function() {
return content.keysPromise;
}
);
is(fieldValue, "mething", "delete not prevented");
BrowserTestUtils.removeTab(tab);
});
// TODO: Make this to run on Windows too to have automated tests also there.
if (
navigator.platform.includes("Mac") ||
navigator.platform.includes("Linux")
) {
add_task(
async function test_reserved_shortcuts_conflict_with_user_settings() {
await new Promise(resolve => {
SpecialPowers.pushPrefEnv(
{ set: [["test.events.async.enabled", true]] },
resolve
);
});
const keyset = document.createXULElement("keyset");
const key = document.createXULElement("key");
key.setAttribute("id", "conflict_with_known_native_key_binding");
if (navigator.platform.includes("Mac")) {
// Select to end of the paragraph
key.setAttribute("modifiers", "ctrl,shift");
key.setAttribute("key", "E");
} else {
// Select All
key.setAttribute("modifiers", "ctrl");
key.setAttribute("key", "a");
}
key.setAttribute("reserved", "true");
key.setAttribute("count", "0");
key.addEventListener("command", () => {
const attribute = key.getAttribute("count");
key.setAttribute("count", Number(attribute) + 1);
});
keyset.appendChild(key);
const container = document.createXULElement("box");
container.appendChild(keyset);
document.documentElement.appendChild(container);
const pageUrl =
"data:text/html,<body onload='document.body.firstChild.focus(); getSelection().collapse(document.body.firstChild, 0)'><div contenteditable>Test</div></body>";
const tab = await BrowserTestUtils.openNewForegroundTab(
gBrowser,
pageUrl
);
await SpecialPowers.spawn(
tab.linkedBrowser,
[key.getAttribute("key")],
async function(aExpectedKeyValue) {
content.promiseTestResult = new Promise(resolve => {
content.addEventListener("keyup", event => {
if (event.key.toLowerCase() == aExpectedKeyValue.toLowerCase()) {
resolve(
content
.getSelection()
.getRangeAt(0)
.toString()
);
}
});
});
}
);
EventUtils.synthesizeKey(key.getAttribute("key"), {
ctrlKey: key.getAttribute("modifiers").includes("ctrl"),
shiftKey: key.getAttribute("modifiers").includes("shift"),
});
const selectedText = await SpecialPowers.spawn(
tab.linkedBrowser,
[],
async function() {
return content.promiseTestResult;
}
);
is(
selectedText,
"Test",
"The shortcut key should select all text in the editor"
);
is(
key.getAttribute("count"),
"0",
"The reserved shortcut key should be consumed by the focused editor instead"
);
document.documentElement.removeChild(container);
BrowserTestUtils.removeTab(tab);
}
);
}