Bug 1303273 part.3 Dispatch eKeyPress events without NativeKey::HandleCharMessage() when it handles WM_(SYS)KEYDOWN message and there are following WM_(SYS)CHAR messages which includes non-control character r=m_kato

This patch creates NativeKey::DispatchKeyPressEventsWithRetrievedCharMessages() for dispatching eKeyPress event with mCommittedCharsAndModifiers when it stores following printable WM_(SYS)CHAR messages.

Using loop for dispatching eKeyPress event for every WM_(SYS)CHAR message is wrong because WidgetKeyboardEvent::mKeyValue is initialized with mCommittedCharsAndModifiers and it causes TextEventDispatcher dispatching multiple eKeyPress events at every call of MaybeDispatchKeypressEvents().  Therefore, if mKeyValue is "^^", eKeyPress event is dispatched 4 times --for the first message, eKeyPress events are fired for each "^" and for the second message, eKeyPress events are fired again for each "^"--.  Therefore, when it handles WM_(SYS)KEYDOWN and it causes inputting one or more printable characters, it's the easiest way not to use HandleCharMessage().

The new method calls TextEventDispatcher::MaybeDispatchKeypressEvents() only once and it requests to call the callback method with new argument of MaybeDispatchKeypressEvents() when it needs to dispatch 2 or more eKeyPress events.  Then, NativeKey::WillDispatchKeyboardEvent() can set each eKeyPress event to raw information of the message and proper modifier state.

With this change, we can dispatch multiple eKeyPress events with retrieved WM_(SYS)CHAR message information rather than retrieved information from active keyboard layout.  Therefore, NeedsToHandleWithoutFollowingCharMessages() doesn't return true even when mCommittedCharsAndModifiers stores two or more characters.

FYI: there is a bug in test_keycodes.xul. That is, Alt+'A' of Greek keyboard layout should cause WM_SYSCHAR with a corresponding Greek character but ASCII characters are specified.  Therefore, this patch includes the fix of these bugs

MozReview-Commit-ID: JVm7ZJVug0O

--HG--
extra : rebase_source : 414ecbe2c01c53f294d1346414b1a289aa0abfe8
This commit is contained in:
Masayuki Nakano 2016-10-06 20:52:03 +09:00
Родитель 642d448ca7
Коммит 8c926d5f82
5 изменённых файлов: 153 добавлений и 134 удалений

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

@ -397,7 +397,8 @@ TextEventDispatcher::DispatchKeyboardEventInternal(
const WidgetKeyboardEvent& aKeyboardEvent,
nsEventStatus& aStatus,
void* aData,
uint32_t aIndexOfKeypress)
uint32_t aIndexOfKeypress,
bool aNeedsCallback)
{
// Note that this method is also used for dispatching key events on a plugin
// because key events on a plugin should be dispatched same as normal key
@ -501,7 +502,7 @@ TextEventDispatcher::DispatchKeyboardEventInternal(
keyEvent.mAlternativeCharCodes.Clear();
if ((WidgetKeyboardEvent::IsKeyDownOrKeyDownOnPlugin(aMessage) ||
aMessage == eKeyPress) &&
(keyEvent.IsControl() || keyEvent.IsAlt() ||
(aNeedsCallback || keyEvent.IsControl() || keyEvent.IsAlt() ||
keyEvent.IsMeta() || keyEvent.IsOS())) {
nsCOMPtr<TextEventDispatcherListener> listener =
do_QueryReferent(mListener);
@ -538,7 +539,8 @@ bool
TextEventDispatcher::MaybeDispatchKeypressEvents(
const WidgetKeyboardEvent& aKeyboardEvent,
nsEventStatus& aStatus,
void* aData)
void* aData,
bool aNeedsCallback)
{
// If the key event was consumed, keypress event shouldn't be fired.
if (aStatus == nsEventStatus_eConsumeNoDefault) {
@ -563,7 +565,7 @@ TextEventDispatcher::MaybeDispatchKeypressEvents(
for (size_t i = 0; i < keypressCount; i++) {
aStatus = nsEventStatus_eIgnore;
if (!DispatchKeyboardEventInternal(eKeyPress, aKeyboardEvent,
aStatus, aData, i)) {
aStatus, aData, i, aNeedsCallback)) {
// The widget must have been gone.
break;
}

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

@ -284,12 +284,16 @@ public:
* @param aData Calling this method may cause calling
* WillDispatchKeyboardEvent() of the listener.
* aData will be set to its argument.
* @param aNeedsCallback Set true when caller needs to initialize each
* eKeyPress event immediately before dispatch.
* Then, WillDispatchKeyboardEvent() is always called.
* @return true if one or more events are dispatched.
* Otherwise, false.
*/
bool MaybeDispatchKeypressEvents(const WidgetKeyboardEvent& aKeyboardEvent,
nsEventStatus& aStatus,
void* aData = nullptr);
void* aData = nullptr,
bool aNeedsCallback = false);
private:
// mWidget is owner of the instance. When this is created, this is set.
@ -463,13 +467,17 @@ private:
* text, this must be between 0 and
* mKeyValue.Length() - 1 since keypress events
* sending only one character per event.
* @param aNeedsCallback Set true when caller needs to initialize each
* eKeyPress event immediately before dispatch.
* Then, WillDispatchKeyboardEvent() is always called.
* @return true if an event is dispatched. Otherwise, false.
*/
bool DispatchKeyboardEventInternal(EventMessage aMessage,
const WidgetKeyboardEvent& aKeyboardEvent,
nsEventStatus& aStatus,
void* aData,
uint32_t aIndexOfKeypress = 0);
uint32_t aIndexOfKeypress = 0,
bool aNeedsCallback = false);
};
} // namespace widget

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

@ -3760,17 +3760,17 @@ function* runAccessKeyTests()
// Greek layout can activate a Latin accesskey
yield testKey({layout:KEYBOARD_LAYOUT_GREEK, keyCode:WIN_VK_A,
modifiers:{shiftKey:1, altKey:1}, chars:"A"},
modifiers:{shiftKey:1, altKey:1}, chars:"\u0391"},
"a", true);
yield testKey({layout:KEYBOARD_LAYOUT_GREEK, keyCode:WIN_VK_A,
modifiers:{shiftKey:1, altKey:1}, chars:"A"},
modifiers:{shiftKey:1, altKey:1}, chars:"\u0391"},
"A", true);
// ... and a Greek accesskey!
yield testKey({layout:KEYBOARD_LAYOUT_GREEK, keyCode:WIN_VK_A,
modifiers:{shiftKey:1, altKey:1}, chars:"A"},
modifiers:{shiftKey:1, altKey:1}, chars:"\u0391"},
"\u03b1", true);
yield testKey({layout:KEYBOARD_LAYOUT_GREEK, keyCode:WIN_VK_A,
modifiers:{shiftKey:1, altKey:1}, chars:"A"},
modifiers:{shiftKey:1, altKey:1}, chars:"\u0391"},
"\u0391", true);
// bug 359638

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

@ -1519,7 +1519,7 @@ NativeKey::InitWithKeyChar()
// modifier state if this key event won't cause text input actually.
// They will be used for setting mAlternativeCharCodes in the callback
// method which will be called by TextEventDispatcher.
if (NeedsToHandleWithoutFollowingCharMessages()) {
if (!IsFollowedByPrintableCharMessage()) {
ComputeInputtingStringWithKeyboardLayout();
}
// Remove odd char messages if there are.
@ -1978,13 +1978,8 @@ NativeKey::InitKeyEvent(WidgetKeyboardEvent& aKeyEvent,
aKeyEvent.mLocation = GetKeyLocation();
aModKeyState.InitInputEvent(aKeyEvent);
NPEvent pluginEvent;
if (aMsgSentToPlugin &&
mWidget->GetInputContext().mIMEState.mEnabled == IMEState::PLUGIN) {
pluginEvent.event = aMsgSentToPlugin->message;
pluginEvent.wParam = aMsgSentToPlugin->wParam;
pluginEvent.lParam = aMsgSentToPlugin->lParam;
aKeyEvent.mPluginEvent.Copy(pluginEvent);
if (aMsgSentToPlugin) {
MaybeInitPluginEventOfKeyEvent(aKeyEvent, *aMsgSentToPlugin);
}
KeyboardLayout::NotifyIdleServiceOfUserActivity();
@ -2006,6 +2001,20 @@ NativeKey::InitKeyEvent(WidgetKeyboardEvent& aKeyEvent,
nsEventStatus_eIgnore;
}
void
NativeKey::MaybeInitPluginEventOfKeyEvent(WidgetKeyboardEvent& aKeyEvent,
const MSG& aMsgSentToPlugin) const
{
if (mWidget->GetInputContext().mIMEState.mEnabled != IMEState::PLUGIN) {
return;
}
NPEvent pluginEvent;
pluginEvent.event = aMsgSentToPlugin.message;
pluginEvent.wParam = aMsgSentToPlugin.wParam;
pluginEvent.lParam = aMsgSentToPlugin.lParam;
aKeyEvent.mPluginEvent.Copy(pluginEvent);
}
bool
NativeKey::DispatchCommandEvent(uint32_t aEventCommand) const
{
@ -2444,6 +2453,15 @@ NativeKey::HandleKeyDownMessage(bool* aEventDispatched) const
return true;
}
// If mCommittedCharsAndModifiers was initialized with following char
// messages, we should dispatch keypress events with its information.
if (IsFollowedByPrintableCharOrSysCharMessage()) {
MOZ_LOG(sNativeKeyLogger, LogLevel::Info,
("%p NativeKey::HandleKeyDownMessage(), tries to be dispatching "
"keypress events with retrieved char messages...", this));
return DispatchKeyPressEventsWithRetrievedCharMessages();
}
// If we won't be getting a WM_CHAR, WM_SYSCHAR or WM_DEADCHAR, synthesize a
// keypress for almost all keys
if (NeedsToHandleWithoutFollowingCharMessages()) {
@ -2454,34 +2472,6 @@ NativeKey::HandleKeyDownMessage(bool* aEventDispatched) const
DispatchKeyPressEventsWithoutCharMessage());
}
if (!mFollowingCharMsgs.IsEmpty()) {
bool consumed = false;
for (size_t i = 0; i < mFollowingCharMsgs.Length(); ++i) {
MOZ_LOG(sNativeKeyLogger, LogLevel::Info,
("%p NativeKey::HandleKeyDownMessage(), stopped dispatching "
"keypress events for remaining char messages, consumed=%s, "
"mFollowingCharMsgs[%u]=%s, mMsg=%s, "
"mFocusedWndBeforeDispatch=0x%p, ::GetFocus()=0x%p",
this, GetBoolName(consumed), i,
ToString(mFollowingCharMsgs[i]).get(),
ToString(mMsg).get(), mFocusedWndBeforeDispatch, ::GetFocus()));
consumed =
DispatchKeyPressEventForFollowingCharMessage(mFollowingCharMsgs[i]) ||
consumed;
if (mWidget->Destroyed() || IsFocusedWindowChanged()) {
MOZ_LOG(sNativeKeyLogger, LogLevel::Info,
("%p NativeKey::HandleKeyDownMessage(), %s event caused "
"destroying the widget", this));
return true;
}
}
MOZ_LOG(sNativeKeyLogger, LogLevel::Info,
("%p NativeKey::HandleKeyDownMessage(), handled all following char "
"messages, consumed=%s",
this, GetBoolName(consumed)));
return consumed;
}
// If WM_KEYDOWN of VK_PACKET isn't followed by WM_CHAR, we don't need to
// dispatch keypress events.
if (mVirtualKeyCode == VK_PACKET) {
@ -2715,16 +2705,10 @@ NativeKey::NeedsToHandleWithoutFollowingCharMessages() const
return true;
}
// If inputting two or more characters, should be dispatched after removing
// whole following char messages.
if (mCommittedCharsAndModifiers.mLength > 1) {
return true;
}
// If keydown message is followed by WM_CHAR whose wParam isn't a control
// character, we should dispatch keypress event with the char message
// even with any modifier state.
if (IsFollowedByPrintableCharMessage()) {
// If keydown message is followed by WM_CHAR or WM_SYSCHAR whose wParam isn't
// a control character, we should dispatch keypress event with the char
// message even with any modifier state.
if (IsFollowedByPrintableCharOrSysCharMessage()) {
return false;
}
@ -3203,6 +3187,56 @@ NativeKey::ComputeInputtingStringWithKeyboardLayout()
}
}
bool
NativeKey::DispatchKeyPressEventsWithRetrievedCharMessages() const
{
MOZ_ASSERT(IsKeyDownMessage());
MOZ_ASSERT(IsFollowedByPrintableCharOrSysCharMessage());
nsresult rv = mDispatcher->BeginNativeInputTransaction();
if (NS_WARN_IF(NS_FAILED(rv))) {
MOZ_LOG(sNativeKeyLogger, LogLevel::Error,
("%p NativeKey::DispatchKeyPressEventsWithRetrievedCharMessages(), "
"FAILED due to BeginNativeInputTransaction() failure", this));
return true;
}
WidgetKeyboardEvent keypressEvent(true, eKeyPress, mWidget);
MOZ_LOG(sNativeKeyLogger, LogLevel::Debug,
("%p NativeKey::DispatchKeyPressEventsWithRetrievedCharMessages(), "
"initializing keypress event...", this));
ModifierKeyState modKeyState(mModKeyState);
if (IsFollowedByPrintableCharMessage()) {
// If eKeyPress event should cause inputting text in focused editor,
// we need to remove Alt and Ctrl state.
modKeyState.Unset(MODIFIER_ALT | MODIFIER_CONTROL);
}
// We don't need to send char message here if there are two or more retrieved
// messages because we need to set each message to each eKeyPress event.
bool needsCallback = mFollowingCharMsgs.Length() > 1;
nsEventStatus status =
InitKeyEvent(keypressEvent, modKeyState,
!needsCallback ? &mFollowingCharMsgs[0] : nullptr);
MOZ_LOG(sNativeKeyLogger, LogLevel::Info,
("%p NativeKey::DispatchKeyPressEventsWithRetrievedCharMessages(), "
"dispatching keypress event(s)...", this));
bool dispatched =
mDispatcher->MaybeDispatchKeypressEvents(keypressEvent, status,
const_cast<NativeKey*>(this),
needsCallback);
if (mWidget->Destroyed()) {
MOZ_LOG(sNativeKeyLogger, LogLevel::Info,
("%p NativeKey::DispatchKeyPressEventsWithRetrievedCharMessages(), "
"keypress event(s) caused destroying the widget", this));
return true;
}
bool consumed = status == nsEventStatus_eConsumeNoDefault;
MOZ_LOG(sNativeKeyLogger, LogLevel::Info,
("%p NativeKey::DispatchKeyPressEventsWithRetrievedCharMessages(), "
"dispatched keypress event(s), dispatched=%s, consumed=%s",
this, GetBoolName(dispatched), GetBoolName(consumed)));
return consumed;
}
bool
NativeKey::DispatchKeyPressEventsWithoutCharMessage() const
{
@ -3250,6 +3284,46 @@ void
NativeKey::WillDispatchKeyboardEvent(WidgetKeyboardEvent& aKeyboardEvent,
uint32_t aIndex)
{
// If it's an eKeyPress event and it's generated from retrieved char message,
// we need to set raw message information for plugins.
if (aKeyboardEvent.mMessage == eKeyPress &&
IsFollowedByPrintableCharOrSysCharMessage()) {
MOZ_RELEASE_ASSERT(aIndex < mCommittedCharsAndModifiers.mLength);
uint32_t foundPrintableCharMessages = 0;
for (size_t i = 0; i < mFollowingCharMsgs.Length(); ++i) {
if (!IsPrintableCharOrSysCharMessage(mFollowingCharMsgs[i])) {
// XXX Should we dispatch a plugin event for WM_*DEADCHAR messages and
// WM_CHAR with a control character here? But we're not sure
// how can we create such message queue (i.e., WM_CHAR or
// WM_SYSCHAR with a printable character and such message are
// generated by a keydown). So, let's ignore such case until
// we'd get some bug reports.
MOZ_LOG(sNativeKeyLogger, LogLevel::Warning,
("%p NativeKey::WillDispatchKeyboardEvent(), WARNING, "
"ignoring %uth message due to non-printable char message, %s",
this, i + 1, ToString(mFollowingCharMsgs[i]).get()));
continue;
}
if (foundPrintableCharMessages++ == aIndex) {
// Found message which caused the eKeyPress event. Let's set the
// message for plugin if it's necessary.
MaybeInitPluginEventOfKeyEvent(aKeyboardEvent, mFollowingCharMsgs[i]);
break;
}
}
// Set modifier state from mCommittedCharsAndModifiers because some of them
// might be different. For example, Shift key was pressed at inputting
// dead char but Shift key was released before inputting next character.
ModifierKeyState modKeyState(mModKeyState);
modKeyState.Unset(MODIFIER_SHIFT | MODIFIER_CONTROL | MODIFIER_ALT |
MODIFIER_ALTGRAPH | MODIFIER_CAPSLOCK);
modKeyState.Set(mCommittedCharsAndModifiers.mModifiers[aIndex]);
modKeyState.InitInputEvent(aKeyboardEvent);
MOZ_LOG(sNativeKeyLogger, LogLevel::Info,
("%p NativeKey::WillDispatchKeyboardEvent(), "
"setting %uth modifier state to %s",
this, aIndex + 1, ToString(modKeyState).get()));
}
uint32_t longestLength =
std::max(mInputtingStringAndModifiers.mLength,
std::max(mShiftedString.mLength, mUnshiftedString.mLength));
@ -3384,77 +3458,6 @@ NativeKey::WillDispatchKeyboardEvent(WidgetKeyboardEvent& aKeyboardEvent,
}
}
bool
NativeKey::DispatchKeyPressEventForFollowingCharMessage(
const MSG& aCharMsg) const
{
MOZ_ASSERT(IsKeyDownMessage());
if (mFakeCharMsgs) {
if (IsDeadCharMessage(aCharMsg)) {
return false;
}
#ifdef DEBUG
if (mIsPrintableKey) {
nsPrintfCString log(
"mOriginalVirtualKeyCode=0x%02X, mCommittedCharsAndModifiers={ "
"mChars=[ 0x%04X, 0x%04X, 0x%04X, 0x%04X, 0x%04X ], mLength=%d }, "
"wParam=0x%04X",
mOriginalVirtualKeyCode, mCommittedCharsAndModifiers.mChars[0],
mCommittedCharsAndModifiers.mChars[1],
mCommittedCharsAndModifiers.mChars[2],
mCommittedCharsAndModifiers.mChars[3],
mCommittedCharsAndModifiers.mChars[4],
mCommittedCharsAndModifiers.mLength, aCharMsg.wParam);
if (mCommittedCharsAndModifiers.IsEmpty()) {
log.Insert("length is zero: ", 0);
NS_ERROR(log.get());
NS_ABORT();
} else if (mCommittedCharsAndModifiers.mChars[0] != aCharMsg.wParam) {
log.Insert("character mismatch: ", 0);
NS_ERROR(log.get());
NS_ABORT();
}
}
#endif // #ifdef DEBUG
return HandleCharMessage(aCharMsg);
}
if (IsDeadCharMessage(aCharMsg)) {
if (!mWidget->PluginHasFocus()) {
MOZ_LOG(sNativeKeyLogger, LogLevel::Info,
("%p NativeKey::DispatchKeyPressEventForFollowingCharMessage(), "
"plugin doesn't have focus", this));
return false;
}
MOZ_LOG(sNativeKeyLogger, LogLevel::Info,
("%p NativeKey::DispatchKeyPressEventForFollowingCharMessage(), "
"dispatching plugin event...", this));
bool ok = mWidget->DispatchPluginEvent(aCharMsg) || mWidget->Destroyed();
MOZ_LOG(sNativeKeyLogger, LogLevel::Info,
("%p NativeKey::DispatchKeyPressEventForFollowingCharMessage(), "
"dispatched plugin event, result=%s, mWidget->Destroyed()=%s",
this, GetBoolName(ok), GetBoolName(mWidget->Destroyed())));
}
bool defaultPrevented = HandleCharMessage(aCharMsg);
// If a syschar keypress wasn't processed, Windows may want to
// handle it to activate a native menu.
if (!defaultPrevented && IsSysCharMessage(aCharMsg)) {
MOZ_LOG(sNativeKeyLogger, LogLevel::Info,
("%p NativeKey::DispatchKeyPressEventForFollowingCharMessage(), "
"calling DefWindowProcW(aCharMsg=%s)...",
this, ToString(aCharMsg).get()));
::DefWindowProcW(aCharMsg.hwnd, aCharMsg.message,
aCharMsg.wParam, aCharMsg.lParam);
MOZ_LOG(sNativeKeyLogger, LogLevel::Info,
("%p NativeKey::DispatchKeyPressEventForFollowingCharMessage(), "
"called DefWindowProcW(aCharMsg=%s)",
this, ToString(aCharMsg).get()));
}
return defaultPrevented;
}
/*****************************************************************************
* mozilla::widget::KeyboardLayout
*****************************************************************************/

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

@ -550,12 +550,25 @@ private:
nsEventStatus InitKeyEvent(WidgetKeyboardEvent& aKeyEvent,
const MSG* aMsgSentToPlugin = nullptr) const;
/**
* MaybeInitPluginEventOfKeyEvent() may initialize aKeyEvent::mPluginEvent
* with aMsgSentToPlugin if it's necessary.
*/
void MaybeInitPluginEventOfKeyEvent(WidgetKeyboardEvent& aKeyEvent,
const MSG& aMsgSentToPlugin) const;
/**
* Dispatches a command event for aEventCommand.
* Returns true if the event is consumed. Otherwise, false.
*/
bool DispatchCommandEvent(uint32_t aEventCommand) const;
/**
* DispatchKeyPressEventsWithRetrievedCharMessages() dispatches keypress
* event(s) with retrieved char messages.
*/
bool DispatchKeyPressEventsWithRetrievedCharMessages() const;
/**
* DispatchKeyPressEventsWithoutCharMessage() dispatches keypress event(s)
* without char messages. So, this should be used only when there are no
@ -571,13 +584,6 @@ private:
*/
bool MaybeDispatchPluginEventsForRemovedCharMessages() const;
/**
* DispatchKeyPressEventForFollowingCharMessage() dispatches keypress event
* for following WM_*CHAR message which is removed and set to aCharMsg.
* Returns true if the event is consumed. Otherwise, false.
*/
bool DispatchKeyPressEventForFollowingCharMessage(const MSG& aCharMsg) const;
/**
* Checkes whether the key event down message is handled without following
* WM_CHAR messages. For example, if following WM_CHAR message indicates