Bug 1466208 - part 39: Create PresShell::EventHandler::MaybeHandleKeyboardEventBeforeDispatch() r=smaug

`PresShell::EventHandler::HandleEventInternal()` may handle `Escape` key before
dispatching it in some cases.  This requires too many lines for somebody who
investigate the method for the other events.  Therefore, this patch moves it
into the new method.

Additionally, this patch creates `WidgetKeyboardEvent::CanTreatAsUserInput()`
and `WidgetKeyboardEvent::ShouldInteractionTimeRecorded()` because we should
manage similar checks in one place (we already have
`WidgetKeyboardEvent::CanUserGestureActivateTarget()`).  Finally, their
conditions are not enough for what the comment wants to do there since they do
not check some modifier keys.  Therefore, this patch makes them check all
possible modifier keys too.

Differential Revision: https://phabricator.services.mozilla.com/D21340

--HG--
extra : moz-landing-system : lando
This commit is contained in:
Masayuki Nakano 2019-03-08 12:46:17 +00:00
parent 87c4a3175f
commit 1b0e6d02fe
3 changed files with 119 additions and 64 deletions

View file

@ -7664,57 +7664,12 @@ nsresult PresShell::EventHandler::HandleEventInternal(
case eKeyPress:
case eKeyDown:
case eKeyUp: {
Document* doc = mPresShell->GetCurrentEventContent()
? mPresShell->mCurrentEventContent->OwnerDoc()
: nullptr;
auto keyCode = aEvent->AsKeyboardEvent()->mKeyCode;
if (keyCode == NS_VK_ESCAPE) {
Document* root = nsContentUtils::GetRootDocument(doc);
if (root && root->GetFullscreenElement()) {
// Prevent default action on ESC key press when exiting
// DOM fullscreen mode. This prevents the browser ESC key
// handler from stopping all loads in the document, which
// would cause <video> loads to stop.
// XXX We need to claim the Escape key event which will be
// dispatched only into chrome is already consumed by
// content because we need to prevent its default here
// for some reasons (not sure) but we need to detect
// if a chrome event handler will call PreventDefault()
// again and check it later.
aEvent->PreventDefaultBeforeDispatch(CrossProcessForwarding::eStop);
aEvent->mFlags.mOnlyChromeDispatch = true;
// The event listeners in chrome can prevent this ESC behavior by
// calling prevent default on the preceding keydown/press events.
if (!mPresShell->mIsLastChromeOnlyEscapeKeyConsumed &&
aEvent->mMessage == eKeyUp) {
// ESC key released while in DOM fullscreen mode.
// Fully exit all browser windows and documents from
// fullscreen mode.
Document::AsyncExitFullscreen(nullptr);
}
}
nsCOMPtr<Document> pointerLockedDoc =
do_QueryReferent(EventStateManager::sPointerLockedDoc);
if (!mPresShell->mIsLastChromeOnlyEscapeKeyConsumed &&
pointerLockedDoc) {
// XXX See above comment to understand the reason why this needs
// to claim that the Escape key event is consumed by content
// even though it will be dispatched only into chrome.
aEvent->PreventDefaultBeforeDispatch(CrossProcessForwarding::eStop);
aEvent->mFlags.mOnlyChromeDispatch = true;
if (aEvent->mMessage == eKeyUp) {
Document::UnlockPointer();
}
}
}
// Allow keys other than ESC and modifiers be marked as a
// valid user input for triggering popup, fullscreen, and
// pointer lock.
isHandlingUserInput =
keyCode != NS_VK_ESCAPE && keyCode != NS_VK_SHIFT &&
keyCode != NS_VK_CONTROL && keyCode != NS_VK_ALT &&
keyCode != NS_VK_WIN && keyCode != NS_VK_META;
WidgetKeyboardEvent* keyboardEvent = aEvent->AsKeyboardEvent();
MaybeHandleKeyboardEventBeforeDispatch(keyboardEvent);
// Not all keyboard events are treated as user input, so that popups
// can't be opened, fullscreen mode can't be started, etc at unexpected
// time.
isHandlingUserInput = keyboardEvent->CanTreatAsUserInput();
break;
}
case eMouseDown:
@ -7916,6 +7871,59 @@ bool PresShell::EventHandler::PrepareToDispatchContextMenuEvent(
return true;
}
void PresShell::EventHandler::MaybeHandleKeyboardEventBeforeDispatch(
WidgetKeyboardEvent* aKeyboardEvent) {
MOZ_ASSERT(aKeyboardEvent);
if (aKeyboardEvent->mKeyCode != NS_VK_ESCAPE) {
return;
}
// If we're in fullscreen mode, exit from it forcibly when Escape key is
// pressed.
Document* doc = mPresShell->GetCurrentEventContent()
? mPresShell->mCurrentEventContent->OwnerDoc()
: nullptr;
Document* root = nsContentUtils::GetRootDocument(doc);
if (root && root->GetFullscreenElement()) {
// Prevent default action on ESC key press when exiting
// DOM fullscreen mode. This prevents the browser ESC key
// handler from stopping all loads in the document, which
// would cause <video> loads to stop.
// XXX We need to claim the Escape key event which will be
// dispatched only into chrome is already consumed by
// content because we need to prevent its default here
// for some reasons (not sure) but we need to detect
// if a chrome event handler will call PreventDefault()
// again and check it later.
aKeyboardEvent->PreventDefaultBeforeDispatch(CrossProcessForwarding::eStop);
aKeyboardEvent->mFlags.mOnlyChromeDispatch = true;
// The event listeners in chrome can prevent this ESC behavior by
// calling prevent default on the preceding keydown/press events.
if (!mPresShell->mIsLastChromeOnlyEscapeKeyConsumed &&
aKeyboardEvent->mMessage == eKeyUp) {
// ESC key released while in DOM fullscreen mode.
// Fully exit all browser windows and documents from
// fullscreen mode.
Document::AsyncExitFullscreen(nullptr);
}
}
nsCOMPtr<Document> pointerLockedDoc =
do_QueryReferent(EventStateManager::sPointerLockedDoc);
if (!mPresShell->mIsLastChromeOnlyEscapeKeyConsumed && pointerLockedDoc) {
// XXX See above comment to understand the reason why this needs
// to claim that the Escape key event is consumed by content
// even though it will be dispatched only into chrome.
aKeyboardEvent->PreventDefaultBeforeDispatch(CrossProcessForwarding::eStop);
aKeyboardEvent->mFlags.mOnlyChromeDispatch = true;
if (aKeyboardEvent->mMessage == eKeyUp) {
Document::UnlockPointer();
}
}
}
void PresShell::EventHandler::RecordEventPreparationPerformance(
const WidgetEvent* aEvent) {
MOZ_ASSERT(aEvent);
@ -7924,19 +7932,10 @@ void PresShell::EventHandler::RecordEventPreparationPerformance(
case eKeyPress:
case eKeyDown:
case eKeyUp:
switch (aEvent->AsKeyboardEvent()->mKeyCode) {
case NS_VK_ESCAPE:
case NS_VK_SHIFT:
case NS_VK_CONTROL:
case NS_VK_ALT:
case NS_VK_WIN:
case NS_VK_META:
break;
default:
GetPresContext()->RecordInteractionTime(
nsPresContext::InteractionType::eKeyInteraction,
aEvent->mTimeStamp);
break;
if (aEvent->AsKeyboardEvent()->ShouldInteractionTimeRecorded()) {
GetPresContext()->RecordInteractionTime(
nsPresContext::InteractionType::eKeyInteraction,
aEvent->mTimeStamp);
}
Telemetry::AccumulateTimeDelta(Telemetry::INPUT_EVENT_QUEUED_KEYBOARD_MS,
aEvent->mTimeStamp);

View file

@ -1085,6 +1085,16 @@ class PresShell final : public nsIPresShell,
*/
void RecordEventHandlingResponsePerformance(const WidgetEvent* aEvent);
/**
* MaybeHandleKeyboardEventBeforeDispatch() may handle aKeyboardEvent
* if it should do something before dispatched into the DOM.
*
* @param aKeyboardEvent The handling keyboard event.
*/
MOZ_CAN_RUN_SCRIPT
void MaybeHandleKeyboardEventBeforeDispatch(
WidgetKeyboardEvent* aKeyboardEvent);
/**
* PrepareToDispatchContextMenuEvent() prepares to dispatch aEvent into
* the DOM.

View file

@ -316,6 +316,52 @@ class WidgetKeyboardEvent : public WidgetInputEvent {
!isCombiningWithOperationKeys;
}
/**
* CanTreatAsUserInput() returns true if the key is pressed for perhaps
* doing something on the web app or our UI. This means that when this
* returns false, e.g., when user presses a modifier key, user is probably
* displeased by opening popup, entering fullscreen mode, etc. Therefore,
* only when this returns true, such reactions should be allowed.
*/
bool CanTreatAsUserInput() const {
if (!IsTrusted()) {
return false;
}
switch (mKeyNameIndex) {
case KEY_NAME_INDEX_Escape:
// modifier keys:
case KEY_NAME_INDEX_Alt:
case KEY_NAME_INDEX_AltGraph:
case KEY_NAME_INDEX_CapsLock:
case KEY_NAME_INDEX_Control:
case KEY_NAME_INDEX_Fn:
case KEY_NAME_INDEX_FnLock:
case KEY_NAME_INDEX_Meta:
case KEY_NAME_INDEX_NumLock:
case KEY_NAME_INDEX_ScrollLock:
case KEY_NAME_INDEX_Shift:
case KEY_NAME_INDEX_Symbol:
case KEY_NAME_INDEX_SymbolLock:
// legacy modifier keys:
case KEY_NAME_INDEX_Hyper:
case KEY_NAME_INDEX_Super:
// obsolete modifier key:
case KEY_NAME_INDEX_OS:
return false;
default:
return true;
}
}
/**
* ShouldInteractionTimeRecorded() returns true if the handling time of
* the event should be recorded with the telemetry.
*/
bool ShouldInteractionTimeRecorded() const {
// Let's record only when we can treat the instance is a user input.
return CanTreatAsUserInput();
}
// OS translated Unicode chars which are used for accesskey and accelkey
// handling. The handlers will try from first character to last character.
nsTArray<AlternativeCharCode> mAlternativeCharCodes;