Bug 1810406 - Handle asynchronous composition without synthesized GDK_KEY_PRESS event r=m_kato

IME for ibus may send composition after filtering `GDK_KEY_PRESS` event
asynchronously.  In that case, IME or ibus usually synthesize `GDK_KEY_PRESS`
again for letting the application know what's being handled.  However, according
to the bug report, IME may send composition without synthesizing the
`GDK_KEY_PRESS` event.

Without this patch, `IMContextWrapper` dispatches only
`eContentCommandInsertText` event.  Then, it'll cause only a set of
`beforeinput` and `input` events.  Therefore, web apps may fail to do something
if they listen only composition and keyboard events only in Gecko.  For avoiding
Gecko only failure in this case, we should make `IMContentWrapper` handle the
composition with `GDK_KEY_PRESS` event in the queue which it has not handled
yet.  Then, web apps can work with `keydown` events whose `key` is `"Process"`.

Differential Revision: https://phabricator.services.mozilla.com/D170031
This commit is contained in:
Masayuki Nakano 2023-02-21 22:51:43 +00:00
parent 0df404cf45
commit 0dfc1d00f9
2 changed files with 72 additions and 0 deletions

View File

@ -1059,6 +1059,17 @@ KeyHandlingState IMContextWrapper::OnKeyEvent(
mMaybeInDeadKeySequence = false;
}
if (aEvent->type == GDK_KEY_RELEASE) {
if (const GdkEventKey* pendingKeyPressEvent =
mPostingKeyEvents.GetCorrespondingKeyPressEvent(aEvent)) {
MOZ_LOG(gIMELog, LogLevel::Warning,
("0x%p OnKeyEvent(), forgetting a pending GDK_KEY_PRESS event "
"because GDK_KEY_RELEASE for the event is handled",
this));
mPostingKeyEvents.RemoveEvent(pendingKeyPressEvent);
}
}
MOZ_LOG(
gIMELog, LogLevel::Debug,
("0x%p OnKeyEvent(), succeeded, filterThisEvent=%s "
@ -1615,6 +1626,21 @@ void IMContextWrapper::OnStartCompositionCallback(GtkIMContext* aContext,
}
void IMContextWrapper::OnStartCompositionNative(GtkIMContext* aContext) {
// IME may synthesize composition asynchronously after filtering a
// GDK_KEY_PRESS event. In that case, we should handle composition with
// emulating the usual case, i.e., this is called in the stack of
// OnKeyEvent().
Maybe<AutoRestore<GdkEventKey*>> maybeRestoreProcessingKeyEvent;
if (!mProcessingKeyEvent && !mPostingKeyEvents.IsEmpty()) {
GdkEventKey* keyEvent = mPostingKeyEvents.GetFirstEvent();
if (keyEvent && keyEvent->type == GDK_KEY_PRESS &&
KeymapWrapper::ComputeDOMKeyNameIndex(keyEvent) ==
KEY_NAME_INDEX_USE_STRING) {
maybeRestoreProcessingKeyEvent.emplace(mProcessingKeyEvent);
mProcessingKeyEvent = mPostingKeyEvents.GetFirstEvent();
}
}
MOZ_LOG(gIMELog, LogLevel::Info,
("0x%p OnStartCompositionNative(aContext=0x%p), "
"current context=0x%p, mComposingContext=0x%p",
@ -1702,6 +1728,21 @@ void IMContextWrapper::OnChangeCompositionCallback(GtkIMContext* aContext,
}
void IMContextWrapper::OnChangeCompositionNative(GtkIMContext* aContext) {
// IME may synthesize composition asynchronously after filtering a
// GDK_KEY_PRESS event. In that case, we should handle composition with
// emulating the usual case, i.e., this is called in the stack of
// OnKeyEvent().
Maybe<AutoRestore<GdkEventKey*>> maybeRestoreProcessingKeyEvent;
if (!mProcessingKeyEvent && !mPostingKeyEvents.IsEmpty()) {
GdkEventKey* keyEvent = mPostingKeyEvents.GetFirstEvent();
if (keyEvent && keyEvent->type == GDK_KEY_PRESS &&
KeymapWrapper::ComputeDOMKeyNameIndex(keyEvent) ==
KEY_NAME_INDEX_USE_STRING) {
maybeRestoreProcessingKeyEvent.emplace(mProcessingKeyEvent);
mProcessingKeyEvent = mPostingKeyEvents.GetFirstEvent();
}
}
MOZ_LOG(gIMELog, LogLevel::Info,
("0x%p OnChangeCompositionNative(aContext=0x%p), "
"mComposingContext=0x%p",
@ -1833,6 +1874,21 @@ void IMContextWrapper::OnCommitCompositionNative(GtkIMContext* aContext,
const gchar* commitString = aUTF8Char ? aUTF8Char : &emptyStr;
NS_ConvertUTF8toUTF16 utf16CommitString(commitString);
// IME may synthesize composition asynchronously after filtering a
// GDK_KEY_PRESS event. In that case, we should handle composition with
// emulating the usual case, i.e., this is called in the stack of
// OnKeyEvent().
Maybe<AutoRestore<GdkEventKey*>> maybeRestoreProcessingKeyEvent;
if (!mProcessingKeyEvent && !mPostingKeyEvents.IsEmpty()) {
GdkEventKey* keyEvent = mPostingKeyEvents.GetFirstEvent();
if (keyEvent && keyEvent->type == GDK_KEY_PRESS &&
KeymapWrapper::ComputeDOMKeyNameIndex(keyEvent) ==
KEY_NAME_INDEX_USE_STRING) {
maybeRestoreProcessingKeyEvent.emplace(mProcessingKeyEvent);
mProcessingKeyEvent = mPostingKeyEvents.GetFirstEvent();
}
}
MOZ_LOG(gIMELog, LogLevel::Info,
("0x%p OnCommitCompositionNative(aContext=0x%p), "
"current context=0x%p, active context=0x%p, commitString=\"%s\", "

View File

@ -267,6 +267,22 @@ class IMContextWrapper final : public TextEventDispatcherListener {
mEvents.RemoveElementAt(index);
}
/**
* Return corresponding GDK_KEY_PRESS event for aEvent. aEvent must be a
* GDK_KEY_RELEASE event.
*/
const GdkEventKey* GetCorrespondingKeyPressEvent(
const GdkEventKey* aEvent) const {
MOZ_ASSERT(aEvent->type == GDK_KEY_RELEASE);
for (const GUniquePtr<GdkEventKey>& pendingKeyEvent : mEvents) {
if (pendingKeyEvent->type == GDK_KEY_PRESS &&
aEvent->hardware_keycode == pendingKeyEvent->hardware_keycode) {
return pendingKeyEvent.get();
}
}
return nullptr;
}
/**
* FirstEvent() returns oldest event in the queue.
*/