Bug 1516326 - part 1: Move implementation of nsWindow::OnKeyPress() and nsWindow::OnKeyRelease() into KeymapWrapper r=karlt
authorMasayuki Nakano <masayuki@d-toybox.com>
Mon, 07 Jan 2019 23:15:33 +0000
changeset 509896 7f3d73861dfe53ed375f4e8ecde4843aed4b59b8
parent 509895 0b7bbe6dda1d98b81149a86c62801e66b40ea32c
child 509897 e80f4801231c431124d11d4bc4a694c101240de6
push id10547
push userffxbld-merge
push dateMon, 21 Jan 2019 13:03:58 +0000
treeherdermozilla-beta@24ec1916bffe [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerskarlt
bugs1516326, 1498823
milestone66.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1516326 - part 1: Move implementation of nsWindow::OnKeyPress() and nsWindow::OnKeyRelease() into KeymapWrapper r=karlt Move all implementation of nsWindow::OnKeyPress() and nsWindow::OnKeyRelease() into KeymapWrapper because the implementation is a little bit complicated but not loggable. When we get bug reports which depend on environment around IME/key handling like bug 1498823, it's useful to log those methods behavior too. Differential Revision: https://phabricator.services.mozilla.com/D15323
widget/gtk/IMContextWrapper.cpp
widget/gtk/nsGtkKeyUtils.cpp
widget/gtk/nsGtkKeyUtils.h
widget/gtk/nsWindow.cpp
widget/gtk/nsWindow.h
--- a/widget/gtk/IMContextWrapper.cpp
+++ b/widget/gtk/IMContextWrapper.cpp
@@ -1939,18 +1939,19 @@ bool IMContextWrapper::MaybeDispatchKeyE
     // when we're not in a dead key composition, we should mark the
     // eKeyDown and eKeyUp event as "processed by IME" since we should
     // expose raw keyCode and key value to web apps the key event is a
     // part of a dead key sequence.
     // FYI: We should ignore if default of preceding keydown or keyup
     //      event is prevented since even on the other browsers, web
     //      applications cannot cancel the following composition event.
     //      Spec bug: https://github.com/w3c/uievents/issues/180
-    lastFocusedWindow->DispatchKeyDownOrKeyUpEvent(
-        sourceEvent, !mMaybeInDeadKeySequence, &mKeyboardEventWasConsumed);
+    KeymapWrapper::DispatchKeyDownOrKeyUpEvent(lastFocusedWindow, sourceEvent,
+                                               !mMaybeInDeadKeySequence,
+                                               &mKeyboardEventWasConsumed);
     MOZ_LOG(gGtkIMLog, LogLevel::Info,
             ("0x%p   MaybeDispatchKeyEventAsProcessedByIME(), keydown or keyup "
              "event is dispatched",
              this));
 
     if (!mProcessingKeyEvent) {
       MOZ_LOG(gGtkIMLog, LogLevel::Info,
               ("0x%p   MaybeDispatchKeyEventAsProcessedByIME(), removing first "
@@ -2005,18 +2006,18 @@ bool IMContextWrapper::MaybeDispatchKeyE
       // physical key information during composition.
       fakeKeyDownEvent.mCodeNameIndex = CODE_NAME_INDEX_UNKNOWN;
 
       MOZ_LOG(gGtkIMLog, LogLevel::Info,
               ("0x%p MaybeDispatchKeyEventAsProcessedByIME("
                "aFollowingEvent=%s), dispatch fake eKeyDown event",
                this, ToChar(aFollowingEvent)));
 
-      lastFocusedWindow->DispatchKeyDownOrKeyUpEvent(
-          fakeKeyDownEvent, &mKeyboardEventWasConsumed);
+      KeymapWrapper::DispatchKeyDownOrKeyUpEvent(
+          lastFocusedWindow, fakeKeyDownEvent, &mKeyboardEventWasConsumed);
       MOZ_LOG(gGtkIMLog, LogLevel::Info,
               ("0x%p   MaybeDispatchKeyEventAsProcessedByIME(), "
                "fake keydown event is dispatched",
                this));
     }
   }
 
   if (lastFocusedWindow->IsDestroyed() ||
--- a/widget/gtk/nsGtkKeyUtils.cpp
+++ b/widget/gtk/nsGtkKeyUtils.cpp
@@ -12,25 +12,28 @@
 #include <gdk/gdkkeysyms.h>
 #include <algorithm>
 #include <gdk/gdk.h>
 #include <gdk/gdkx.h>
 #ifdef MOZ_WIDGET_GTK
 #include <gdk/gdkkeysyms-compat.h>
 #endif
 #include <X11/XKBlib.h>
+#include "IMContextWrapper.h"
 #include "WidgetUtils.h"
 #include "keysym2ucs.h"
 #include "nsContentUtils.h"
 #include "nsGtkUtils.h"
 #include "nsIBidiKeyboard.h"
 #include "nsServiceManagerUtils.h"
+#include "nsWindow.h"
 
 #include "mozilla/ArrayUtils.h"
 #include "mozilla/MouseEvents.h"
+#include "mozilla/TextEventDispatcher.h"
 #include "mozilla/TextEvents.h"
 
 #ifdef MOZ_WAYLAND
 #include <sys/mman.h>
 #endif
 
 namespace mozilla {
 namespace widget {
@@ -1088,16 +1091,256 @@ KeyNameIndex KeymapWrapper::ComputeDOMKe
 
     default:
       break;
   }
 
   return CODE_NAME_INDEX_UNKNOWN;
 }
 
+/* static */ bool KeymapWrapper::DispatchKeyDownOrKeyUpEvent(
+    nsWindow* aWindow, GdkEventKey* aGdkKeyEvent, bool aIsProcessedByIME,
+    bool* aIsCancelled) {
+  MOZ_ASSERT(aIsCancelled, "aIsCancelled must not be nullptr");
+
+  *aIsCancelled = false;
+
+  if (aGdkKeyEvent->type == GDK_KEY_PRESS && aGdkKeyEvent->keyval == GDK_Tab &&
+      AreModifiersActive(CTRL | ALT, aGdkKeyEvent->state)) {
+    return false;
+  }
+
+  EventMessage message =
+      aGdkKeyEvent->type == GDK_KEY_PRESS ? eKeyDown : eKeyUp;
+  WidgetKeyboardEvent keyEvent(true, message, aWindow);
+  KeymapWrapper::InitKeyEvent(keyEvent, aGdkKeyEvent, aIsProcessedByIME);
+  return DispatchKeyDownOrKeyUpEvent(aWindow, keyEvent, aIsCancelled);
+}
+
+/* static */ bool KeymapWrapper::DispatchKeyDownOrKeyUpEvent(
+    nsWindow* aWindow, WidgetKeyboardEvent& aKeyboardEvent,
+    bool* aIsCancelled) {
+  MOZ_ASSERT(aIsCancelled, "aIsCancelled must not be nullptr");
+
+  *aIsCancelled = false;
+
+  RefPtr<TextEventDispatcher> dispatcher = aWindow->GetTextEventDispatcher();
+  nsresult rv = dispatcher->BeginNativeInputTransaction();
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return FALSE;
+  }
+
+  nsEventStatus status = nsEventStatus_eIgnore;
+  bool dispatched = dispatcher->DispatchKeyboardEvent(
+      aKeyboardEvent.mMessage, aKeyboardEvent, status, nullptr);
+  *aIsCancelled = (status == nsEventStatus_eConsumeNoDefault);
+  return dispatched;
+}
+
+/* static */ bool KeymapWrapper::MaybeDispatchContextMenuEvent(
+    nsWindow* aWindow, const GdkEventKey* aEvent) {
+  KeyNameIndex keyNameIndex = ComputeDOMKeyNameIndex(aEvent);
+
+  // Shift+F10 and ContextMenu should cause eContextMenu event.
+  if (keyNameIndex != KEY_NAME_INDEX_F10 &&
+      keyNameIndex != KEY_NAME_INDEX_ContextMenu) {
+    return false;
+  }
+
+  WidgetMouseEvent contextMenuEvent(true, eContextMenu, aWindow,
+                                    WidgetMouseEvent::eReal,
+                                    WidgetMouseEvent::eContextMenuKey);
+
+  contextMenuEvent.mRefPoint = LayoutDeviceIntPoint(0, 0);
+  contextMenuEvent.AssignEventTime(aWindow->GetWidgetEventTime(aEvent->time));
+  contextMenuEvent.mClickCount = 1;
+  KeymapWrapper::InitInputEvent(contextMenuEvent, aEvent->state);
+
+  if (contextMenuEvent.IsControl() || contextMenuEvent.IsMeta() ||
+      contextMenuEvent.IsAlt()) {
+    return false;
+  }
+
+  // If the key is ContextMenu, then an eContextMenu mouse event is
+  // dispatched regardless of the state of the Shift modifier.  When it is
+  // pressed without the Shift modifier, a web page can prevent the default
+  // context menu action.  When pressed with the Shift modifier, the web page
+  // cannot prevent the default context menu action.
+  // (PresShell::HandleEventInternal() sets mOnlyChromeDispatch to true.)
+
+  // If the key is F10, it needs Shift state because Shift+F10 is well-known
+  // shortcut key on Linux.  However, eContextMenu with Shift state is
+  // special.  It won't fire "contextmenu" event in the web content for
+  // blocking web page to prevent its default.  Therefore, this combination
+  // should work same as ContextMenu key.
+  // XXX Should we allow to block web page to prevent its default with
+  //     Ctrl+Shift+F10 or Alt+Shift+F10 instead?
+  if (keyNameIndex == KEY_NAME_INDEX_F10) {
+    if (!contextMenuEvent.IsShift()) {
+      return false;
+    }
+    contextMenuEvent.mModifiers &= ~MODIFIER_SHIFT;
+  }
+
+  aWindow->DispatchInputEvent(&contextMenuEvent);
+  return true;
+}
+
+/* static*/ void KeymapWrapper::HandleKeyPressEvent(nsWindow* aWindow,
+                                                    GdkEventKey* aGdkKeyEvent) {
+  // if we are in the middle of composing text, XIM gets to see it
+  // before mozilla does.
+  // FYI: Don't dispatch keydown event before notifying IME of the event
+  //      because IME may send a key event synchronously and consume the
+  //      original event.
+  bool IMEWasEnabled = false;
+  KeyHandlingState handlingState = KeyHandlingState::eNotHandled;
+  RefPtr<IMContextWrapper> imContext = aWindow->GetIMContext();
+  if (imContext) {
+    IMEWasEnabled = imContext->IsEnabled();
+    handlingState = imContext->OnKeyEvent(aWindow, aGdkKeyEvent);
+    if (handlingState == KeyHandlingState::eHandled) {
+      return;
+    }
+  }
+
+  // work around for annoying things.
+  if (aGdkKeyEvent->keyval == GDK_Tab &&
+      AreModifiersActive(CTRL | ALT, aGdkKeyEvent->state)) {
+    return;
+  }
+
+  // Dispatch keydown event always.  At auto repeating, we should send
+  // KEYDOWN -> KEYPRESS -> KEYDOWN -> KEYPRESS ... -> KEYUP
+  // However, old distributions (e.g., Ubuntu 9.10) sent native key
+  // release event, so, on such platform, the DOM events will be:
+  // KEYDOWN -> KEYPRESS -> KEYUP -> KEYDOWN -> KEYPRESS -> KEYUP...
+
+  bool isKeyDownCancelled = false;
+  if (handlingState == KeyHandlingState::eNotHandled) {
+    if (DispatchKeyDownOrKeyUpEvent(aWindow, aGdkKeyEvent, false,
+                                    &isKeyDownCancelled) &&
+        (MOZ_UNLIKELY(aWindow->IsDestroyed()) || isKeyDownCancelled)) {
+      return;
+    }
+    handlingState = KeyHandlingState::eNotHandledButEventDispatched;
+  }
+
+  // If a keydown event handler causes to enable IME, i.e., it moves
+  // focus from IME unusable content to IME usable editor, we should
+  // send the native key event to IME for the first input on the editor.
+  imContext = aWindow->GetIMContext();
+  if (!IMEWasEnabled && imContext && imContext->IsEnabled()) {
+    // Notice our keydown event was already dispatched.  This prevents
+    // unnecessary DOM keydown event in the editor.
+    handlingState = imContext->OnKeyEvent(aWindow, aGdkKeyEvent, true);
+    if (handlingState == KeyHandlingState::eHandled) {
+      return;
+    }
+  }
+
+  // Look for specialized app-command keys
+  switch (aGdkKeyEvent->keyval) {
+    case GDK_Back:
+      aWindow->DispatchCommandEvent(nsGkAtoms::Back);
+      return;
+    case GDK_Forward:
+      aWindow->DispatchCommandEvent(nsGkAtoms::Forward);
+      return;
+    case GDK_Refresh:
+      aWindow->DispatchCommandEvent(nsGkAtoms::Reload);
+      return;
+    case GDK_Stop:
+      aWindow->DispatchCommandEvent(nsGkAtoms::Stop);
+      return;
+    case GDK_Search:
+      aWindow->DispatchCommandEvent(nsGkAtoms::Search);
+      return;
+    case GDK_Favorites:
+      aWindow->DispatchCommandEvent(nsGkAtoms::Bookmarks);
+      return;
+    case GDK_HomePage:
+      aWindow->DispatchCommandEvent(nsGkAtoms::Home);
+      return;
+    case GDK_Copy:
+    case GDK_F16:  // F16, F20, F18, F14 are old keysyms for Copy Cut Paste Undo
+      aWindow->DispatchContentCommandEvent(eContentCommandCopy);
+      return;
+    case GDK_Cut:
+    case GDK_F20:
+      aWindow->DispatchContentCommandEvent(eContentCommandCut);
+      return;
+    case GDK_Paste:
+    case GDK_F18:
+      aWindow->DispatchContentCommandEvent(eContentCommandPaste);
+      return;
+    case GDK_Redo:
+      aWindow->DispatchContentCommandEvent(eContentCommandRedo);
+      return;
+    case GDK_Undo:
+    case GDK_F14:
+      aWindow->DispatchContentCommandEvent(eContentCommandUndo);
+      return;
+    default:
+      break;
+  }
+
+  WidgetKeyboardEvent keypressEvent(true, eKeyPress, aWindow);
+  InitKeyEvent(keypressEvent, aGdkKeyEvent, false);
+
+  // before we dispatch a key, check if it's the context menu key.
+  // If so, send a context menu key event instead.
+  if (MaybeDispatchContextMenuEvent(aWindow, aGdkKeyEvent)) {
+    return;
+  }
+
+  RefPtr<TextEventDispatcher> textEventDispatcher =
+      aWindow->GetTextEventDispatcher();
+  nsresult rv = textEventDispatcher->BeginNativeInputTransaction();
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return;
+  }
+
+  // If the character code is in the BMP, send the key press event.
+  // Otherwise, send a compositionchange event with the equivalent UTF-16
+  // string.
+  // TODO: Investigate other browser's behavior in this case because
+  //       this hack is odd for UI Events.
+  nsEventStatus status = nsEventStatus_eIgnore;
+  if (keypressEvent.mKeyNameIndex != KEY_NAME_INDEX_USE_STRING ||
+      keypressEvent.mKeyValue.Length() == 1) {
+    textEventDispatcher->MaybeDispatchKeypressEvents(keypressEvent, status,
+                                                     aGdkKeyEvent);
+  } else {
+    WidgetEventTime eventTime = aWindow->GetWidgetEventTime(aGdkKeyEvent->time);
+    textEventDispatcher->CommitComposition(status, &keypressEvent.mKeyValue,
+                                           &eventTime);
+  }
+}
+
+/* static */ bool KeymapWrapper::HandleKeyReleaseEvent(
+    nsWindow* aWindow, GdkEventKey* aGdkKeyEvent) {
+  RefPtr<IMContextWrapper> imContext = aWindow->GetIMContext();
+  if (imContext) {
+    KeyHandlingState handlingState =
+        imContext->OnKeyEvent(aWindow, aGdkKeyEvent);
+    if (handlingState != KeyHandlingState::eNotHandled) {
+      return true;
+    }
+  }
+
+  bool isCancelled = false;
+  if (NS_WARN_IF(!DispatchKeyDownOrKeyUpEvent(aWindow, aGdkKeyEvent, false,
+                                              &isCancelled))) {
+    return false;
+  }
+
+  return true;
+}
+
 /* static */ void KeymapWrapper::InitKeyEvent(WidgetKeyboardEvent& aKeyEvent,
                                               GdkEventKey* aGdkKeyEvent,
                                               bool aIsProcessedByIME) {
   MOZ_ASSERT(
       !aIsProcessedByIME || aKeyEvent.mMessage != eKeyPress,
       "If the key event is handled by IME, keypress event shouldn't be fired");
 
   KeymapWrapper* keymapWrapper = GetInstance();
--- a/widget/gtk/nsGtkKeyUtils.h
+++ b/widget/gtk/nsGtkKeyUtils.h
@@ -13,16 +13,18 @@
 
 #include <gdk/gdk.h>
 #include <X11/XKBlib.h>
 #ifdef MOZ_WAYLAND
 #include <gdk/gdkwayland.h>
 #include <xkbcommon/xkbcommon.h>
 #endif
 
+class nsWindow;
+
 namespace mozilla {
 namespace widget {
 
 /**
  *  KeymapWrapper is a wrapper class of GdkKeymap.  GdkKeymap doesn't support
  *  all our needs, therefore, we need to access lower level APIs.
  *  But such code is usually complex and might be slow.  Against such issues,
  *  we should cache some information.
@@ -90,29 +92,16 @@ class KeymapWrapper {
    * @param aModifiers        One or more of Modifier values except
    *                          NOT_MODIFIER.
    * @return                  TRUE if all of modifieres in aModifiers are
    *                          active.  Otherwise, FALSE.
    */
   static bool AreModifiersCurrentlyActive(Modifiers aModifiers);
 
   /**
-   * AreModifiersActive() just checks whether aModifierState indicates
-   * all modifiers in aModifiers are active or not.
-   *
-   * @param aModifiers        One or more of Modifier values except
-   *                          NOT_MODIFIER.
-   * @param aModifierState    GDK's modifier states.
-   * @return                  TRUE if aGdkModifierType indecates all of
-   *                          modifiers in aModifier are active.
-   *                          Otherwise, FALSE.
-   */
-  static bool AreModifiersActive(Modifiers aModifiers, guint aModifierState);
-
-  /**
    * Utility function to compute current keyboard modifiers for
    * WidgetInputEvent
    */
   static uint32_t ComputeCurrentKeyModifiers();
 
   /**
    * Utility function to covert platform modifier state to keyboard modifiers
    * of WidgetInputEvent
@@ -133,16 +122,65 @@ class KeymapWrapper {
    *                          initialized.
    * @param aGdkKeyEvent      A native GDK key event.
    * @param aIsProcessedByIME true if aGdkKeyEvent is handled by IME.
    */
   static void InitKeyEvent(WidgetKeyboardEvent& aKeyEvent,
                            GdkEventKey* aGdkKeyEvent, bool aIsProcessedByIME);
 
   /**
+   * DispatchKeyDownOrKeyUpEvent() dispatches eKeyDown or eKeyUp event.
+   *
+   * @param aWindow           The window to dispatch a keyboard event.
+   * @param aGdkKeyEvent      A native GDK_KEY_PRESS or GDK_KEY_RELEASE
+   *                          event.
+   * @param aIsProcessedByIME true if the event is handled by IME.
+   * @param aIsCancelled      [Out] true if the default is prevented.
+   * @return                  true if eKeyDown event is actually dispatched.
+   *                          Otherwise, false.
+   */
+  static bool DispatchKeyDownOrKeyUpEvent(nsWindow* aWindow,
+                                          GdkEventKey* aGdkKeyEvent,
+                                          bool aIsProcessedByIME,
+                                          bool* aIsCancelled);
+
+  /**
+   * DispatchKeyDownOrKeyUpEvent() dispatches eKeyDown or eKeyUp event.
+   *
+   * @param aWindow           The window to dispatch aKeyboardEvent.
+   * @param aKeyboardEvent    An eKeyDown or eKeyUp event.  This will be
+   *                          dispatched as is.
+   * @param aIsCancelled      [Out] true if the default is prevented.
+   * @return                  true if eKeyDown event is actually dispatched.
+   *                          Otherwise, false.
+   */
+  static bool DispatchKeyDownOrKeyUpEvent(nsWindow* aWindow,
+                                          WidgetKeyboardEvent& aKeyboardEvent,
+                                          bool* aIsCancelled);
+
+  /**
+   * GDK_KEY_PRESS event handler.
+   *
+   * @param aWindow           The window to dispatch eKeyDown event (and maybe
+   *                          eKeyPress events).
+   * @param aGdkKeyEvent      Receivied GDK_KEY_PRESS event.
+   */
+  static void HandleKeyPressEvent(nsWindow* aWindow, GdkEventKey* aGdkKeyEvent);
+
+  /**
+   * GDK_KEY_RELEASE event handler.
+   *
+   * @param aWindow           The window to dispatch eKeyUp event.
+   * @param aGdkKeyEvent      Receivied GDK_KEY_RELEASE event.
+   * @return                  true if an event is dispatched.  Otherwise, false.
+   */
+  static bool HandleKeyReleaseEvent(nsWindow* aWindow,
+                                    GdkEventKey* aGdkKeyEvent);
+
+  /**
    * WillDispatchKeyboardEvent() is called via
    * TextEventDispatcherListener::WillDispatchKeyboardEvent().
    *
    * @param aKeyEvent         An instance of KeyboardEvent which will be
    *                          dispatched.  This method should set charCode
    *                          and alternative char codes if it's necessary.
    * @param aGdkKeyEvent      A GdkEventKey instance which caused the
    *                          aKeyEvent.
@@ -230,16 +268,29 @@ class KeymapWrapper {
    *                          If the given key code isn't a modifier key,
    *                          returns NOT_MODIFIER.
    */
   static Modifier GetModifierForGDKKeyval(guint aGdkKeyval);
 
   static const char* GetModifierName(Modifier aModifier);
 
   /**
+   * AreModifiersActive() just checks whether aModifierState indicates
+   * all modifiers in aModifiers are active or not.
+   *
+   * @param aModifiers        One or more of Modifier values except
+   *                          NOT_MODIFIER.
+   * @param aModifierState    GDK's modifier states.
+   * @return                  TRUE if aGdkModifierType indecates all of
+   *                          modifiers in aModifier are active.
+   *                          Otherwise, FALSE.
+   */
+  static bool AreModifiersActive(Modifiers aModifiers, guint aModifierState);
+
+  /**
    * mGdkKeymap is a wrapped instance by this class.
    */
   GdkKeymap* mGdkKeymap;
 
   /**
    * The base event code of XKB extension.
    */
   int mXKBBaseEventCode;
@@ -368,16 +419,30 @@ class KeymapWrapper {
    * FilterEvents() listens all events on all our windows.
    * Be careful, this may make damage to performance if you add expensive
    * code in this method.
    */
   static GdkFilterReturn FilterEvents(GdkXEvent* aXEvent, GdkEvent* aGdkEvent,
                                       gpointer aData);
 
   /**
+   * MaybeDispatchContextMenuEvent() may dispatch eContextMenu event if
+   * the given key combination should cause opening context menu.
+   *
+   * @param aWindow           The window to dispatch a contextmenu event.
+   * @param aEvent            The native key event.
+   * @return                  true if this method dispatched eContextMenu
+   *                          event.  Otherwise, false.
+   *                          Be aware, when this returns true, the
+   *                          widget may have been destroyed.
+   */
+  static bool MaybeDispatchContextMenuEvent(nsWindow* aWindow,
+                                            const GdkEventKey* aEvent);
+
+  /**
    * See the document of WillDispatchKeyboardEvent().
    */
   void WillDispatchKeyboardEventInternal(WidgetKeyboardEvent& aKeyEvent,
                                          GdkEventKey* aGdkKeyEvent);
 
 #ifdef MOZ_WAYLAND
   /**
    * Utility function to set Xkb modifier key mask.
--- a/widget/gtk/nsWindow.cpp
+++ b/widget/gtk/nsWindow.cpp
@@ -7,17 +7,16 @@
 
 #include "nsWindow.h"
 
 #include "mozilla/ArrayUtils.h"
 #include "mozilla/EventForwards.h"
 #include "mozilla/MiscEvents.h"
 #include "mozilla/MouseEvents.h"
 #include "mozilla/RefPtr.h"
-#include "mozilla/TextEventDispatcher.h"
 #include "mozilla/TextEvents.h"
 #include "mozilla/TimeStamp.h"
 #include "mozilla/TouchEvents.h"
 #include "mozilla/UniquePtrExtensions.h"
 #include "mozilla/WidgetUtils.h"
 #include "mozilla/dom/WheelEventBinding.h"
 #include <algorithm>
 
@@ -2679,57 +2678,16 @@ bool nsWindow::DispatchCommandEvent(nsAt
 
 bool nsWindow::DispatchContentCommandEvent(EventMessage aMsg) {
   nsEventStatus status;
   WidgetContentCommandEvent event(true, aMsg, this);
   DispatchEvent(&event, status);
   return TRUE;
 }
 
-static bool IsCtrlAltTab(GdkEventKey *aEvent) {
-  return aEvent->keyval == GDK_Tab &&
-         KeymapWrapper::AreModifiersActive(
-             KeymapWrapper::CTRL | KeymapWrapper::ALT, aEvent->state);
-}
-
-bool nsWindow::DispatchKeyDownOrKeyUpEvent(GdkEventKey *aEvent,
-                                           bool aIsProcessedByIME,
-                                           bool *aIsCancelled) {
-  MOZ_ASSERT(aIsCancelled, "aIsCancelled must not be nullptr");
-
-  *aIsCancelled = false;
-
-  if (aEvent->type == GDK_KEY_PRESS && IsCtrlAltTab(aEvent)) {
-    return false;
-  }
-
-  EventMessage message = aEvent->type == GDK_KEY_PRESS ? eKeyDown : eKeyUp;
-  WidgetKeyboardEvent keyEvent(true, message, this);
-  KeymapWrapper::InitKeyEvent(keyEvent, aEvent, aIsProcessedByIME);
-  return DispatchKeyDownOrKeyUpEvent(keyEvent, aIsCancelled);
-}
-bool nsWindow::DispatchKeyDownOrKeyUpEvent(WidgetKeyboardEvent &aKeyboardEvent,
-                                           bool *aIsCancelled) {
-  MOZ_ASSERT(aIsCancelled, "aIsCancelled must not be nullptr");
-
-  *aIsCancelled = false;
-
-  RefPtr<TextEventDispatcher> dispatcher = GetTextEventDispatcher();
-  nsresult rv = dispatcher->BeginNativeInputTransaction();
-  if (NS_WARN_IF(NS_FAILED(rv))) {
-    return FALSE;
-  }
-
-  nsEventStatus status = nsEventStatus_eIgnore;
-  bool dispatched = dispatcher->DispatchKeyboardEvent(
-      aKeyboardEvent.mMessage, aKeyboardEvent, status, nullptr);
-  *aIsCancelled = (status == nsEventStatus_eConsumeNoDefault);
-  return dispatched;
-}
-
 WidgetEventTime nsWindow::GetWidgetEventTime(guint32 aEventTime) {
   return WidgetEventTime(aEventTime, GetEventTimeStamp(aEventTime));
 }
 
 TimeStamp nsWindow::GetEventTimeStamp(guint32 aEventTime) {
   if (MOZ_UNLIKELY(!mGdkWindow)) {
     // nsWindow has been Destroy()ed.
     return TimeStamp::Now();
@@ -2768,192 +2726,28 @@ mozilla::CurrentX11TimeGetter *nsWindow:
     mCurrentTimeGetter = MakeUnique<CurrentX11TimeGetter>(mGdkWindow);
   }
   return mCurrentTimeGetter.get();
 }
 
 gboolean nsWindow::OnKeyPressEvent(GdkEventKey *aEvent) {
   LOGFOCUS(("OnKeyPressEvent [%p]\n", (void *)this));
 
-  // if we are in the middle of composing text, XIM gets to see it
-  // before mozilla does.
-  // FYI: Don't dispatch keydown event before notifying IME of the event
-  //      because IME may send a key event synchronously and consume the
-  //      original event.
-  bool IMEWasEnabled = false;
-  KeyHandlingState handlingState = KeyHandlingState::eNotHandled;
-  if (mIMContext) {
-    IMEWasEnabled = mIMContext->IsEnabled();
-    handlingState = mIMContext->OnKeyEvent(this, aEvent);
-    if (handlingState == KeyHandlingState::eHandled) {
-      return TRUE;
-    }
-  }
-
-  // work around for annoying things.
-  if (IsCtrlAltTab(aEvent)) {
-    return TRUE;
-  }
-
-  nsCOMPtr<nsIWidget> kungFuDeathGrip = this;
-
-  // Dispatch keydown event always.  At auto repeating, we should send
-  // KEYDOWN -> KEYPRESS -> KEYDOWN -> KEYPRESS ... -> KEYUP
-  // However, old distributions (e.g., Ubuntu 9.10) sent native key
-  // release event, so, on such platform, the DOM events will be:
-  // KEYDOWN -> KEYPRESS -> KEYUP -> KEYDOWN -> KEYPRESS -> KEYUP...
-
-  bool isKeyDownCancelled = false;
-  if (handlingState == KeyHandlingState::eNotHandled) {
-    if (DispatchKeyDownOrKeyUpEvent(aEvent, false, &isKeyDownCancelled) &&
-        (MOZ_UNLIKELY(mIsDestroyed) || isKeyDownCancelled)) {
-      return TRUE;
-    }
-    handlingState = KeyHandlingState::eNotHandledButEventDispatched;
-  }
-
-  // If a keydown event handler causes to enable IME, i.e., it moves
-  // focus from IME unusable content to IME usable editor, we should
-  // send the native key event to IME for the first input on the editor.
-  if (!IMEWasEnabled && mIMContext && mIMContext->IsEnabled()) {
-    // Notice our keydown event was already dispatched.  This prevents
-    // unnecessary DOM keydown event in the editor.
-    handlingState = mIMContext->OnKeyEvent(this, aEvent, true);
-    if (handlingState == KeyHandlingState::eHandled) {
-      return TRUE;
-    }
-  }
-
-  // Look for specialized app-command keys
-  switch (aEvent->keyval) {
-    case GDK_Back:
-      return DispatchCommandEvent(nsGkAtoms::Back);
-    case GDK_Forward:
-      return DispatchCommandEvent(nsGkAtoms::Forward);
-    case GDK_Refresh:
-      return DispatchCommandEvent(nsGkAtoms::Reload);
-    case GDK_Stop:
-      return DispatchCommandEvent(nsGkAtoms::Stop);
-    case GDK_Search:
-      return DispatchCommandEvent(nsGkAtoms::Search);
-    case GDK_Favorites:
-      return DispatchCommandEvent(nsGkAtoms::Bookmarks);
-    case GDK_HomePage:
-      return DispatchCommandEvent(nsGkAtoms::Home);
-    case GDK_Copy:
-    case GDK_F16:  // F16, F20, F18, F14 are old keysyms for Copy Cut Paste Undo
-      return DispatchContentCommandEvent(eContentCommandCopy);
-    case GDK_Cut:
-    case GDK_F20:
-      return DispatchContentCommandEvent(eContentCommandCut);
-    case GDK_Paste:
-    case GDK_F18:
-      return DispatchContentCommandEvent(eContentCommandPaste);
-    case GDK_Redo:
-      return DispatchContentCommandEvent(eContentCommandRedo);
-    case GDK_Undo:
-    case GDK_F14:
-      return DispatchContentCommandEvent(eContentCommandUndo);
-  }
-
-  WidgetKeyboardEvent keypressEvent(true, eKeyPress, this);
-  KeymapWrapper::InitKeyEvent(keypressEvent, aEvent, false);
-
-  // before we dispatch a key, check if it's the context menu key.
-  // If so, send a context menu key event instead.
-  if (MaybeDispatchContextMenuEvent(aEvent)) {
-    return TRUE;
-  }
-
-  RefPtr<TextEventDispatcher> dispatcher = GetTextEventDispatcher();
-  nsresult rv = dispatcher->BeginNativeInputTransaction();
-  if (NS_WARN_IF(NS_FAILED(rv))) {
-    return TRUE;
-  }
-
-  // If the character code is in the BMP, send the key press event.
-  // Otherwise, send a compositionchange event with the equivalent UTF-16
-  // string.
-  // TODO: Investigate other browser's behavior in this case because
-  //       this hack is odd for UI Events.
-  nsEventStatus status = nsEventStatus_eIgnore;
-  if (keypressEvent.mKeyNameIndex != KEY_NAME_INDEX_USE_STRING ||
-      keypressEvent.mKeyValue.Length() == 1) {
-    dispatcher->MaybeDispatchKeypressEvents(keypressEvent, status, aEvent);
-  } else {
-    WidgetEventTime eventTime = GetWidgetEventTime(aEvent->time);
-    dispatcher->CommitComposition(status, &keypressEvent.mKeyValue, &eventTime);
-  }
-
+  RefPtr<nsWindow> self(this);
+  KeymapWrapper::HandleKeyPressEvent(self, aEvent);
   return TRUE;
 }
 
-bool nsWindow::MaybeDispatchContextMenuEvent(const GdkEventKey *aEvent) {
-  KeyNameIndex keyNameIndex = KeymapWrapper::ComputeDOMKeyNameIndex(aEvent);
-
-  // Shift+F10 and ContextMenu should cause eContextMenu event.
-  if (keyNameIndex != KEY_NAME_INDEX_F10 &&
-      keyNameIndex != KEY_NAME_INDEX_ContextMenu) {
-    return false;
-  }
-
-  WidgetMouseEvent contextMenuEvent(true, eContextMenu, this,
-                                    WidgetMouseEvent::eReal,
-                                    WidgetMouseEvent::eContextMenuKey);
-
-  contextMenuEvent.mRefPoint = LayoutDeviceIntPoint(0, 0);
-  contextMenuEvent.AssignEventTime(GetWidgetEventTime(aEvent->time));
-  contextMenuEvent.mClickCount = 1;
-  KeymapWrapper::InitInputEvent(contextMenuEvent, aEvent->state);
-
-  if (contextMenuEvent.IsControl() || contextMenuEvent.IsMeta() ||
-      contextMenuEvent.IsAlt()) {
-    return false;
-  }
-
-  // If the key is ContextMenu, then an eContextMenu mouse event is
-  // dispatched regardless of the state of the Shift modifier.  When it is
-  // pressed without the Shift modifier, a web page can prevent the default
-  // context menu action.  When pressed with the Shift modifier, the web page
-  // cannot prevent the default context menu action.
-  // (PresShell::HandleEventInternal() sets mOnlyChromeDispatch to true.)
-
-  // If the key is F10, it needs Shift state because Shift+F10 is well-known
-  // shortcut key on Linux.  However, eContextMenu with Shift state is
-  // special.  It won't fire "contextmenu" event in the web content for
-  // blocking web page to prevent its default.  Therefore, this combination
-  // should work same as ContextMenu key.
-  // XXX Should we allow to block web page to prevent its default with
-  //     Ctrl+Shift+F10 or Alt+Shift+F10 instead?
-  if (keyNameIndex == KEY_NAME_INDEX_F10) {
-    if (!contextMenuEvent.IsShift()) {
-      return false;
-    }
-    contextMenuEvent.mModifiers &= ~MODIFIER_SHIFT;
-  }
-
-  DispatchInputEvent(&contextMenuEvent);
-  return true;
-}
-
 gboolean nsWindow::OnKeyReleaseEvent(GdkEventKey *aEvent) {
   LOGFOCUS(("OnKeyReleaseEvent [%p]\n", (void *)this));
 
-  if (mIMContext) {
-    KeyHandlingState handlingState = mIMContext->OnKeyEvent(this, aEvent);
-    if (handlingState != KeyHandlingState::eNotHandled) {
-      return TRUE;
-    }
-  }
-
-  bool isCancelled = false;
-  if (NS_WARN_IF(!DispatchKeyDownOrKeyUpEvent(aEvent, false, &isCancelled))) {
+  RefPtr<nsWindow> self(this);
+  if (NS_WARN_IF(!KeymapWrapper::HandleKeyReleaseEvent(self, aEvent))) {
     return FALSE;
   }
-
   return TRUE;
 }
 
 void nsWindow::OnScrollEvent(GdkEventScroll *aEvent) {
   // check to see if we should rollup
   if (CheckForRollup(aEvent->x_root, aEvent->y_root, true, false)) return;
 #if GTK_CHECK_VERSION(3, 4, 0)
   // check for duplicate legacy scroll event, see GNOME bug 726878
--- a/widget/gtk/nsWindow.h
+++ b/widget/gtk/nsWindow.h
@@ -178,44 +178,37 @@ class nsWindow final : public nsBaseWidg
   static guint32 GetLastUserInputTime();
 
   // utility method, -1 if no change should be made, otherwise returns a
   // value that can be passed to gdk_window_set_decorations
   gint ConvertBorderStyles(nsBorderStyle aStyle);
 
   GdkRectangle DevicePixelsToGdkRectRoundOut(LayoutDeviceIntRect aRect);
 
+  mozilla::widget::IMContextWrapper* GetIMContext() const { return mIMContext; }
+
+  bool DispatchCommandEvent(nsAtom* aCommand);
+  bool DispatchContentCommandEvent(mozilla::EventMessage aMsg);
+
   // event callbacks
   gboolean OnExposeEvent(cairo_t* cr);
   gboolean OnConfigureEvent(GtkWidget* aWidget, GdkEventConfigure* aEvent);
   void OnContainerUnrealize();
   void OnSizeAllocate(GtkAllocation* aAllocation);
   void OnDeleteEvent();
   void OnEnterNotifyEvent(GdkEventCrossing* aEvent);
   void OnLeaveNotifyEvent(GdkEventCrossing* aEvent);
   void OnMotionNotifyEvent(GdkEventMotion* aEvent);
   void OnButtonPressEvent(GdkEventButton* aEvent);
   void OnButtonReleaseEvent(GdkEventButton* aEvent);
   void OnContainerFocusInEvent(GdkEventFocus* aEvent);
   void OnContainerFocusOutEvent(GdkEventFocus* aEvent);
   gboolean OnKeyPressEvent(GdkEventKey* aEvent);
   gboolean OnKeyReleaseEvent(GdkEventKey* aEvent);
 
-  /**
-   * MaybeDispatchContextMenuEvent() may dispatch eContextMenu event if
-   * the given key combination should cause opening context menu.
-   *
-   * @param aEvent            The native key event.
-   * @return                  true if this method dispatched eContextMenu
-   *                          event.  Otherwise, false.
-   *                          Be aware, when this returns true, the
-   *                          widget may have been destroyed.
-   */
-  bool MaybeDispatchContextMenuEvent(const GdkEventKey* aEvent);
-
   void OnScrollEvent(GdkEventScroll* aEvent);
   void OnVisibilityNotifyEvent(GdkEventVisibility* aEvent);
   void OnWindowStateEvent(GtkWidget* aWidget, GdkEventWindowState* aEvent);
   void OnDragDataReceivedEvent(GtkWidget* aWidget, GdkDragContext* aDragContext,
                                gint aX, gint aY,
                                GtkSelectionData* aSelectionData, guint aInfo,
                                guint aTime, gpointer aData);
   gboolean OnPropertyNotifyEvent(GtkWidget* aWidget, GdkEventProperty* aEvent);
@@ -279,40 +272,16 @@ class nsWindow final : public nsBaseWidg
   GdkWindow* GetGdkWindow() { return mGdkWindow; }
   GtkWidget* GetGtkWidget() { return mShell; }
   bool IsDestroyed() { return mIsDestroyed; }
 
   void DispatchDragEvent(mozilla::EventMessage aMsg,
                          const LayoutDeviceIntPoint& aRefPoint, guint aTime);
   static void UpdateDragStatus(GdkDragContext* aDragContext,
                                nsIDragService* aDragService);
-  /**
-   * DispatchKeyDownOrKeyUpEvent() dispatches eKeyDown or eKeyUp event.
-   *
-   * @param aEvent            A native GDK_KEY_PRESS or GDK_KEY_RELEASE
-   *                          event.
-   * @param aProcessedByIME   true if the event is handled by IME.
-   * @param aIsCancelled      [Out] true if the default is prevented.
-   * @return                  true if eKeyDown event is actually dispatched.
-   *                          Otherwise, false.
-   */
-  bool DispatchKeyDownOrKeyUpEvent(GdkEventKey* aEvent, bool aProcessedByIME,
-                                   bool* aIsCancelled);
-
-  /**
-   * DispatchKeyDownOrKeyUpEvent() dispatches eKeyDown or eKeyUp event.
-   *
-   * @param aEvent            An eKeyDown or eKeyUp event.  This will be
-   *                          dispatched as is.
-   * @param aIsCancelled      [Out] true if the default is prevented.
-   * @return                  true if eKeyDown event is actually dispatched.
-   *                          Otherwise, false.
-   */
-  bool DispatchKeyDownOrKeyUpEvent(WidgetKeyboardEvent& aEvent,
-                                   bool* aIsCancelled);
 
   WidgetEventTime GetWidgetEventTime(guint32 aEventTime);
   mozilla::TimeStamp GetEventTimeStamp(guint32 aEventTime);
   mozilla::CurrentX11TimeGetter* GetCurrentTimeGetter();
 
   virtual void SetInputContext(const InputContext& aContext,
                                const InputContextAction& aAction) override;
   virtual InputContext GetInputContext() override;
@@ -466,18 +435,16 @@ class nsWindow final : public nsBaseWidg
   void DestroyChildWindows();
   GtkWidget* GetToplevelWidget();
   nsWindow* GetContainerWindow();
   void SetUrgencyHint(GtkWidget* top_window, bool state);
   void SetDefaultIcon(void);
   void SetWindowDecoration(nsBorderStyle aStyle);
   void InitButtonEvent(mozilla::WidgetMouseEvent& aEvent,
                        GdkEventButton* aGdkEvent);
-  bool DispatchCommandEvent(nsAtom* aCommand);
-  bool DispatchContentCommandEvent(mozilla::EventMessage aMsg);
   bool CheckForRollup(gdouble aMouseX, gdouble aMouseY, bool aIsWheel,
                       bool aAlwaysRollup);
   void CheckForRollupDuringGrab() { CheckForRollup(0, 0, false, true); }
 
   bool GetDragInfo(mozilla::WidgetMouseEvent* aMouseEvent, GdkWindow** aWindow,
                    gint* aButton, gint* aRootX, gint* aRootY);
   void ClearCachedResources();
   nsIWidgetListener* GetListener();