Bug 1821886 - Reserve shortcut keys exiting from the fullscreen mode r=Gijs,edgar,smaug

Chrome for Windows does not dispatch `keydown` event for shortcut keys existing
from the fullscreen mode.  Therefore, we can follow it.

For reserving only shortcut keys in fullscreen mode, we need to duplicate XUL
`<key>` elements which define the shortcut keys (only one in Windows/Linux,
but 3 in macOS).  Then, their `disabled` attributes should be managed when
toggling the fullscreen mode.

Finally, we need to make `XULKeySetGlobalKeyListener` check the `disabled`
attribute **of** `<key>` elements because it's check in `DispatchXULKeyCommand`
in the final step:
https://searchfox.org/mozilla-central/rev/11a4d97a7b5cdfa133f4bda4525649f651703018/dom/events/KeyEventHandler.cpp#315-316

and it stops handling everything with doing nothing. I'm not sure whether this
was intentionally implemented or just a inefficient code which we didn't take
care the performance. However, I think that ignoring the disabled `<key>`
elements is reasonable behavior from `<key>` element users point of view.

(I found only one `<key>` which is disabled by default:
https://searchfox.org/mozilla-central/rev/11a4d97a7b5cdfa133f4bda4525649f651703018/browser/base/content/browser-sets.inc#225-233)

Differential Revision: https://phabricator.services.mozilla.com/D178262
This commit is contained in:
Masayuki Nakano 2023-05-24 00:50:17 +00:00
Родитель 9e821bba0e
Коммит 6b54cbe573
9 изменённых файлов: 173 добавлений и 7 удалений

Просмотреть файл

@ -365,6 +365,7 @@ var FullScreen = {
this._isPopupOpen = false;
this.cleanup();
}
this._toggleShortcutKeys();
},
exitDomFullScreen() {
@ -556,6 +557,25 @@ var FullScreen = {
}
},
_toggleShortcutKeys() {
const kEnterKeyIds = [
"key_enterFullScreen",
"key_enterFullScreen_old",
"key_enterFullScreen_compat",
];
const kExitKeyIds = [
"key_exitFullScreen",
"key_exitFullScreen_old",
"key_exitFullScreen_compat",
];
for (let id of window.fullScreen ? kEnterKeyIds : kExitKeyIds) {
document.getElementById(id)?.setAttribute("disabled", "true");
}
for (let id of window.fullScreen ? kExitKeyIds : kEnterKeyIds) {
document.getElementById(id)?.removeAttribute("disabled");
}
},
/**
* Clean up full screen, starting from the request origin's first ancestor
* frame that is OOP.

Просмотреть файл

@ -215,12 +215,16 @@
#ifndef XP_MACOSX
<key id="showAllHistoryKb" data-l10n-id="history-show-all-shortcut" command="Browser:ShowAllHistory" modifiers="accel,shift"/>
<key keycode="VK_F5" command="Browser:ReloadSkipCache" modifiers="accel"/>
<key id="key_fullScreen" keycode="VK_F11" command="View:FullScreen"/>
<key id="key_enterFullScreen" keycode="VK_F11" command="View:FullScreen"/>
<key id="key_exitFullScreen" keycode="VK_F11" command="View:FullScreen" reserved="true" disabled="true"/>
#else
<key id="showAllHistoryKb" data-l10n-id="history-show-all-shortcut-mac" command="Browser:ShowAllHistory" modifiers="accel"/>
<key id="key_fullScreen" data-l10n-id="full-screen-shortcut" command="View:FullScreen" modifiers="accel,control"/>
<key id="key_fullScreen_old" data-l10n-id="full-screen-shortcut" command="View:FullScreen" modifiers="accel,shift"/>
<key keycode="VK_F11" command="View:FullScreen"/>
<key id="key_enterFullScreen" data-l10n-id="full-screen-shortcut" command="View:FullScreen" modifiers="accel,control"/>
<key id="key_enterFullScreen_old" data-l10n-id="full-screen-shortcut" command="View:FullScreen" modifiers="accel,shift"/>
<key id="key_enterFullScreen_compat" keycode="VK_F11" command="View:FullScreen"/>
<key id="key_exitFullScreen" data-l10n-id="full-screen-shortcut" command="View:FullScreen" modifiers="accel,control" reserved="true" disabled="true"/>
<key id="key_exitFullScreen_old" data-l10n-id="full-screen-shortcut" command="View:FullScreen" modifiers="accel,shift" reserved="true" disabled="true"/>
<key id="key_exitFullScreen_compat" keycode="VK_F11" command="View:FullScreen" reserved="true" disabled="true"/>
#endif
<key id="key_toggleReaderMode"
command="View:ReaderView"

Просмотреть файл

@ -16,6 +16,7 @@ support-files = fullscreen.html fullscreen_frame.html
skip-if = (os == 'mac') || (os == 'linux') # Bug 1648649
[browser_fullscreen_from_minimize.js]
skip-if = (os == 'linux') || (os == 'win') # Bug 1818795 and Bug 1818796
[browser_fullscreen_keydown_reservation.js]
[browser_fullscreen_newtab.js]
[browser_fullscreen_newwindow.js]
[browser_fullscreen_permissions_prompt.js]

Просмотреть файл

@ -47,5 +47,10 @@ add_task(async function test() {
await onToolboxHidden;
Assert.ok(true, "Nav toolbox hidden");
info("Waiting for exiting from the fullscreen mode...");
onFullscreen = BrowserTestUtils.waitForEvent(window, "fullscreen");
document.getElementById("View:FullScreen").doCommand();
await onFullscreen;
});
});

Просмотреть файл

@ -0,0 +1,112 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
// This test verifies that whether shortcut keys of toggling fullscreen modes
// are reserved.
add_task(async function test_keydown_event_reservation_toggling_fullscreen() {
await SpecialPowers.pushPrefEnv({
set: [
["full-screen-api.transition-duration.enter", "0 0"],
["full-screen-api.transition-duration.leave", "0 0"],
],
});
let shortcutKeys = [{ key: "KEY_F11", modifiers: {} }];
if (navigator.platform.startsWith("Mac")) {
shortcutKeys.push({
key: "f",
modifiers: { metaKey: true, ctrlKey: true },
});
shortcutKeys.push({
key: "F",
modifiers: { metaKey: true, shiftKey: true },
});
}
function shortcutDescription(aShortcutKey) {
return `${
aShortcutKey.metaKey ? "Meta + " : ""
}${aShortcutKey.shiftKey ? "Shift + " : ""}${aShortcutKey.ctrlKey ? "Ctrl + " : ""}${aShortcutKey.key.replace("KEY_", "")}`;
}
for (const shortcutKey of shortcutKeys) {
const tab = await BrowserTestUtils.openNewForegroundTab(
gBrowser,
"https://example.org/browser/browser/base/content/test/fullscreen/fullscreen.html"
);
await SimpleTest.promiseFocus(tab.linkedBrowser);
const fullScreenEntered = BrowserTestUtils.waitForEvent(
window,
"fullscreen"
);
await SpecialPowers.spawn(tab.linkedBrowser, [], async () => {
content.wrappedJSObject.keydown = null;
content.window.addEventListener("keydown", event => {
switch (event.key) {
case "Shift":
case "Meta":
case "Control":
break;
default:
content.wrappedJSObject.keydown = event;
}
});
});
EventUtils.synthesizeKey(shortcutKey.key, shortcutKey.modifiers);
info(
`Waiting for entering the fullscreen mode with synthesizing ${shortcutDescription(
shortcutKey
)}...`
);
await fullScreenEntered;
info("Retrieving the result...");
Assert.ok(
await SpecialPowers.spawn(
tab.linkedBrowser,
[],
async () => !!content.wrappedJSObject.keydown
),
`Entering the fullscreen mode with ${shortcutDescription(
shortcutKey
)} should cause "keydown" event`
);
const fullScreenExited = BrowserTestUtils.waitForEvent(
window,
"fullscreen"
);
await SpecialPowers.spawn(tab.linkedBrowser, [], async () => {
content.wrappedJSObject.keydown = null;
});
EventUtils.synthesizeKey(shortcutKey.key, shortcutKey.modifiers);
info(
`Waiting for exiting from the fullscreen mode with synthesizing ${shortcutDescription(
shortcutKey
)}...`
);
await fullScreenExited;
info("Retrieving the result...");
Assert.ok(
await SpecialPowers.spawn(
tab.linkedBrowser,
[],
async () => !content.wrappedJSObject.keydown
),
`Exiting from the fullscreen mode with ${shortcutDescription(
shortcutKey
)} should not cause "keydown" event`
);
BrowserTestUtils.removeTab(tab);
}
});

Просмотреть файл

@ -23,6 +23,11 @@ function receiveExpectedKeyEvents(aBrowser, aKeyCode, aTrusted) {
let events = trusted
? ["keydown", "keyup"]
: ["keydown", "keypress", "keyup"];
if (trusted && keyCode == content.wrappedJSObject.KeyEvent.DOM_VK_F11) {
// trusted `F11` key shouldn't be fired because of reserved when it's
// a shortcut key for exiting from the full screen mode.
events.shift();
}
function listener(event) {
let expected = events.shift();
Assert.equal(
@ -197,7 +202,7 @@ add_task(async function () {
"correct number of fullscreen events occurred"
);
if (!suppressed) {
expectedKeyEventsCount += keyCode == "VK_F11" ? 2 : 3;
expectedKeyEventsCount += keyCode == "VK_F11" ? 1 : 3;
}
is(
keyEventsCount,

Просмотреть файл

@ -623,6 +623,11 @@ already_AddRefed<dom::EventTarget> XULKeySetGlobalKeyListener::GetHandlerTarget(
bool XULKeySetGlobalKeyListener::CanHandle(KeyEventHandler* aHandler,
bool aWillExecute) const {
// If the <key> element itself is disabled, ignore it.
if (aHandler->KeyElementIsDisabled()) {
return false;
}
nsCOMPtr<dom::Element> commandElement;
if (!GetElementForHandler(aHandler, getter_AddRefs(commandElement))) {
return false;

Просмотреть файл

@ -166,7 +166,14 @@ bool KeyEventHandler::TryConvertToKeyboardShortcut(
return true;
}
already_AddRefed<dom::Element> KeyEventHandler::GetHandlerElement() {
bool KeyEventHandler::KeyElementIsDisabled() const {
RefPtr<dom::Element> keyElement = GetHandlerElement();
return keyElement &&
keyElement->AttrValueIs(kNameSpaceID_None, nsGkAtoms::disabled,
nsGkAtoms::_true, eCaseMatters);
}
already_AddRefed<dom::Element> KeyEventHandler::GetHandlerElement() const {
if (mIsXULKey) {
nsCOMPtr<dom::Element> element = do_QueryReferent(mHandlerElement);
return element.forget();

Просмотреть файл

@ -75,7 +75,14 @@ class KeyEventHandler final {
uint32_t aCharCode,
const IgnoreModifierState& aIgnoreModifierState);
already_AddRefed<dom::Element> GetHandlerElement();
/**
* Check whether the handler element is disabled. Note that this requires
* a QI to getting GetHandlerELement(). Therefore, this should not be used
* first in multiple checks.
*/
bool KeyElementIsDisabled() const;
already_AddRefed<dom::Element> GetHandlerElement() const;
ReservedKey GetIsReserved() { return mReserved; }