From 0dfc1d00f92bfaede91f1d2318fd794f7d18e0b2 Mon Sep 17 00:00:00 2001 From: Masayuki Nakano Date: Tue, 21 Feb 2023 22:51:43 +0000 Subject: [PATCH] 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 --- widget/gtk/IMContextWrapper.cpp | 56 +++++++++++++++++++++++++++++++++ widget/gtk/IMContextWrapper.h | 16 ++++++++++ 2 files changed, 72 insertions(+) diff --git a/widget/gtk/IMContextWrapper.cpp b/widget/gtk/IMContextWrapper.cpp index 25dfeb7c486e..4950a89a0cfd 100644 --- a/widget/gtk/IMContextWrapper.cpp +++ b/widget/gtk/IMContextWrapper.cpp @@ -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> 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> 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> 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\", " diff --git a/widget/gtk/IMContextWrapper.h b/widget/gtk/IMContextWrapper.h index 81fa861b225d..cf1d2638e0f4 100644 --- a/widget/gtk/IMContextWrapper.h +++ b/widget/gtk/IMContextWrapper.h @@ -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& 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. */