Bug 1210585 - Implement new key event handler in nsWindow; r=esawin
authorJim Chen <nchen@mozilla.com>
Thu, 22 Oct 2015 17:45:46 -0400
changeset 269119 ed18e273554dc11a4bd7ece904a83c61b4b47294
parent 269118 07bfa35414dcd816198fa935ceed832ce76320fd
child 269120 f580afacaa7aded487c709b20293d1b88b2acee4
push id29572
push usercbook@mozilla.com
push dateFri, 23 Oct 2015 09:34:20 +0000
treeherdermozilla-central@0625c68c0abc [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersesawin
bugs1210585
milestone44.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 1210585 - Implement new key event handler in nsWindow; r=esawin This patch implements a new key event handler in nsWindow to replace the previous implementation. nsWindow::HandleSpecialKey was removed because it's a relic from XUL Fennec and I believe we no longer need it for native Fennec.
widget/android/nsWindow.cpp
widget/android/nsWindow.h
--- a/widget/android/nsWindow.cpp
+++ b/widget/android/nsWindow.cpp
@@ -63,16 +63,17 @@ using mozilla::unused;
 #include "AndroidContentController.h"
 
 #include "nsTArray.h"
 
 #include "AndroidBridge.h"
 #include "AndroidBridgeUtilities.h"
 #include "android_npapi.h"
 #include "GeneratedJNINatives.h"
+#include "KeyEvent.h"
 
 #include "imgIEncoder.h"
 
 #include "nsString.h"
 #include "GeckoProfiler.h" // For PROFILER_LABEL
 #include "nsIXULRuntime.h"
 
 using namespace mozilla;
@@ -128,19 +129,16 @@ public:
 
         return NS_OK;
     }
 };
 
 NS_IMPL_ISUPPORTS(ContentCreationNotifier,
                   nsIObserver)
 
-static bool gMenu;
-static bool gMenuConsumed;
-
 // All the toplevel windows that have been created; these are in
 // stacking order, so the window at gAndroidBounds[0] is the topmost
 // one.
 static nsTArray<nsWindow*> gTopLevelWindows;
 
 // FIXME: because we don't support separate nsWindow for each GeckoView
 // yet, we have to attach a new GeckoView to an existing nsWindow if it
 // exists. Eventually, an nsWindow will be opened/closed as each GeckoView
@@ -149,60 +147,83 @@ static nsWindow* gGeckoViewWindow;
 
 static bool sFailedToCreateGLContext = false;
 
 // Multitouch swipe thresholds in inches
 static const double SWIPE_MAX_PINCH_DELTA_INCHES = 0.4;
 static const double SWIPE_MIN_DISTANCE_INCHES = 0.6;
 
 
-class nsWindow::Natives
+class nsWindow::Natives final
     : public GeckoView::Window::Natives<Natives>
+    , public GeckoEditable::Natives<Natives>
     , public SupportsWeakPtr<Natives>
     , public UsesGeckoThreadProxy
 {
-    typedef GeckoView::Window::Natives<Natives> Base;
-
     nsWindow& window;
 
 public:
+    typedef GeckoView::Window::Natives<Natives> Base;
+    typedef GeckoEditable::Natives<Natives> EditableBase;
+
     MOZ_DECLARE_WEAKREFERENCE_TYPENAME(Natives);
 
     template<typename Functor>
     static void OnNativeCall(Functor&& call)
     {
         if (call.IsTarget(&Open) && NS_IsMainThread()) {
             // Gecko state probably just switched to PROFILE_READY, and the
             // event loop is not running yet. Skip the event loop here so we
             // can get a head start on opening our window.
             return call();
         }
         return UsesGeckoThreadProxy::OnNativeCall(mozilla::Move(call));
     }
 
     Natives(nsWindow* w) : window(*w) {}
-
+    ~Natives();
+
+    /**
+     * GeckoView methods
+     */
     // Detach and destroy the window that we created in Open().
     void DisposeNative(const GeckoView::Window::LocalRef& instance);
 
     // Create and attach a window.
     static void Open(const jni::ClassObject::LocalRef& cls,
                      GeckoView::Window::Param gvWindow,
                      GeckoView::Param view,
                      int32_t width, int32_t height);
 
     // Set the active layer client object
     static void SetLayerClient(jni::Object::Param client)
     {
         MOZ_ASSERT(NS_IsMainThread());
         AndroidBridge::Bridge()->SetLayerClient(
                 widget::GeckoLayerClient::Ref::From(client.Get()));
     }
+
+    /**
+     * GeckoEditable methods
+     */
+    // Handle an Android KeyEvent.
+    void OnKeyEvent(int32_t action, int32_t keyCode, int32_t scanCode,
+                    int32_t metaState, int64_t time, int32_t unicodeChar,
+                    int32_t baseUnicodeChar, int32_t domPrintableKeyValue,
+                    int32_t repeatCount, int32_t flags,
+                    bool isSynthesizedImeKey);
 };
 
+nsWindow::Natives::~Natives()
+{
+    // Disassociate our GeckoEditable instance with our native object.
+    MOZ_ASSERT(mEditable);
+    EditableBase::DisposeNative(mEditable);
+}
+
 void
 nsWindow::Natives::Open(const jni::ClassObject::LocalRef& cls,
                         GeckoView::Window::Param gvWindow,
                         GeckoView::Param view,
                         int32_t width, int32_t height)
 {
     MOZ_ASSERT(NS_IsMainThread());
 
@@ -211,18 +232,18 @@ nsWindow::Natives::Open(const jni::Class
 
     if (gGeckoViewWindow) {
         // Should have been created the first time.
         MOZ_ASSERT(gGeckoViewWindow->mNatives);
 
         // Associate our previous GeckoEditable with the new GeckoView.
         gGeckoViewWindow->mEditable->OnViewChange(view);
 
-        AttachNative(GeckoView::Window::LocalRef(cls.Env(), gvWindow),
-                     gGeckoViewWindow->mNatives.get());
+        Base::AttachNative(GeckoView::Window::LocalRef(cls.Env(), gvWindow),
+                           gGeckoViewWindow->mNatives.get());
         return;
     }
 
     nsCOMPtr<nsIWindowWatcher> ww = do_GetService(NS_WINDOWWATCHER_CONTRACTID);
     MOZ_ASSERT(ww);
 
     nsAdoptingCString url = Preferences::GetCString("toolkit.defaultChromeURI");
     if (!url) {
@@ -252,21 +273,23 @@ nsWindow::Natives::Open(const jni::Class
 
     nsCOMPtr<nsIWidget> widget = WidgetUtils::DOMWindowToWidget(window);
     MOZ_ASSERT(widget);
 
     gGeckoViewWindow = static_cast<nsWindow*>(widget.get());
     gGeckoViewWindow->mNatives = mozilla::MakeUnique<Natives>(gGeckoViewWindow);
 
     // Create GeckoEditable for the new nsWindow/GeckoView pair.
-    gGeckoViewWindow->mEditable = GeckoEditable::New();
-    gGeckoViewWindow->mEditable->OnViewChange(view);
-
-    AttachNative(GeckoView::Window::LocalRef(cls.Env(), gvWindow),
-                 gGeckoViewWindow->mNatives.get());
+    GeckoEditable::LocalRef editable = GeckoEditable::New();
+    EditableBase::AttachNative(editable, gGeckoViewWindow->mNatives.get());
+    editable->OnViewChange(view);
+    gGeckoViewWindow->mEditable = editable;
+
+    Base::AttachNative(GeckoView::Window::LocalRef(cls.Env(), gvWindow),
+                       gGeckoViewWindow->mNatives.get());
 }
 
 void
 nsWindow::Natives::DisposeNative(const GeckoView::Window::LocalRef& instance)
 {
     // FIXME: because we don't support separate nsWindow for each GeckoView
     // yet, we have to keep this window around in case another GeckoView
     // wants to attach.
@@ -281,17 +304,18 @@ nsWindow::Natives::DisposeNative(const G
     }
     */
     Base::DisposeNative(instance);
 }
 
 void
 nsWindow::InitNatives()
 {
-    nsWindow::Natives::Init();
+    nsWindow::Natives::Base::Init();
+    nsWindow::Natives::EditableBase::Init();
 }
 
 nsWindow*
 nsWindow::TopWindow()
 {
     if (!gTopLevelWindows.IsEmpty())
         return gTopLevelWindows[0];
     return nullptr;
@@ -405,16 +429,21 @@ nsWindow::Create(nsIWidget *aParent,
     return NS_OK;
 }
 
 NS_IMETHODIMP
 nsWindow::Destroy(void)
 {
     nsBaseWidget::mOnDestroyCalled = true;
 
+    if (mNatives) {
+        // Disassociate our native object with GeckoView.
+        mNatives = nullptr;
+    }
+
     while (mChildren.Length()) {
         // why do we still have children?
         ALOG("### Warning: Destroying window %p and reparenting child %p to null!", (void*)this, (void*)mChildren[0]);
         mChildren[0]->SetParent(nullptr);
     }
 
     if (IsTopLevel())
         gTopLevelWindows.RemoveElement(this);
@@ -1011,33 +1040,21 @@ nsWindow::OnGlobalAndroidEvent(AndroidGe
             break;
         }
 
         case AndroidGeckoEvent::NATIVE_GESTURE_EVENT: {
             win->OnNativeGestureEvent(ae);
             break;
         }
 
-        case AndroidGeckoEvent::KEY_EVENT:
-            win->UserActivity();
-            win->OnKeyEvent(ae);
-            break;
-
         case AndroidGeckoEvent::IME_EVENT:
             gGeckoViewWindow->UserActivity();
             gGeckoViewWindow->OnIMEEvent(ae);
             break;
 
-        case AndroidGeckoEvent::IME_KEY_EVENT:
-            // Keys synthesized by Java IME code are saved in the mIMEKeyEvents
-            // array until the next IME_REPLACE_TEXT event, at which point
-            // these keys are dispatched in sequence.
-            win->mIMEKeyEvents.AppendElement(*ae);
-            break;
-
         case AndroidGeckoEvent::COMPOSITOR_PAUSE:
             // The compositor gets paused when the app is about to go into the
             // background. While the compositor is paused, we need to ensure that
             // no layer tree updates (from draw events) occur, since the compositor
             // cannot make a GL context current in order to process updates.
             if (sCompositorChild) {
                 sCompositorChild->SendPause();
             }
@@ -1424,19 +1441,19 @@ static unsigned int ConvertAndroidKeyCod
         default:
             ALOG("ConvertAndroidKeyCodeToDOMKeyCode: "
                  "No DOM keycode for Android keycode %d", androidKeyCode);
         return 0;
     }
 }
 
 static KeyNameIndex
-ConvertAndroidKeyCodeToKeyNameIndex(AndroidGeckoEvent& aAndroidGeckoEvent)
+ConvertAndroidKeyCodeToKeyNameIndex(int keyCode, int action,
+                                    int domPrintableKeyValue)
 {
-    int keyCode = aAndroidGeckoEvent.KeyCode();
     // Special-case alphanumeric keycodes because they are most common.
     if (keyCode >= AKEYCODE_A && keyCode <= AKEYCODE_Z) {
         return KEY_NAME_INDEX_USE_STRING;
     }
 
     if (keyCode >= AKEYCODE_0 && keyCode <= AKEYCODE_9) {
         return KEY_NAME_INDEX_USE_STRING;
     }
@@ -1541,251 +1558,206 @@ ConvertAndroidKeyCodeToKeyNameIndex(Andr
 
         case AKEYCODE_MANNER_MODE:
         case AKEYCODE_3D_MODE:
         case AKEYCODE_CONTACTS:
             return KEY_NAME_INDEX_Unidentified;
 
         case AKEYCODE_UNKNOWN:
             MOZ_ASSERT(
-                aAndroidGeckoEvent.Action() != AKEY_EVENT_ACTION_MULTIPLE,
+                action != AKEY_EVENT_ACTION_MULTIPLE,
                 "Don't call this when action is AKEY_EVENT_ACTION_MULTIPLE!");
             // It's actually an unknown key if the action isn't ACTION_MULTIPLE.
             // However, it might cause text input.  So, let's check the value.
-            return aAndroidGeckoEvent.DOMPrintableKeyValue() ?
+            return domPrintableKeyValue ?
                 KEY_NAME_INDEX_USE_STRING : KEY_NAME_INDEX_Unidentified;
 
         default:
             ALOG("ConvertAndroidKeyCodeToKeyNameIndex: "
                  "No DOM key name index for Android keycode %d", keyCode);
             return KEY_NAME_INDEX_Unidentified;
     }
 }
 
 static CodeNameIndex
-ConvertAndroidScanCodeToCodeNameIndex(AndroidGeckoEvent& aAndroidGeckoEvent)
+ConvertAndroidScanCodeToCodeNameIndex(int scanCode)
 {
-    switch (aAndroidGeckoEvent.ScanCode()) {
+    switch (scanCode) {
 
 #define NS_NATIVE_KEY_TO_DOM_CODE_NAME_INDEX(aNativeKey, aCodeNameIndex) \
         case aNativeKey: return aCodeNameIndex;
 
 #include "NativeKeyToDOMCodeName.h"
 
 #undef NS_NATIVE_KEY_TO_DOM_CODE_NAME_INDEX
 
         default:
           return CODE_NAME_INDEX_UNKNOWN;
     }
 }
 
-static void InitPluginEvent(ANPEvent* pluginEvent, ANPKeyActions keyAction,
-                            AndroidGeckoEvent& key)
+static bool
+IsModifierKey(int32_t keyCode)
+{
+    using mozilla::widget::sdk::KeyEvent;
+    return keyCode == KeyEvent::KEYCODE_ALT_LEFT ||
+           keyCode == KeyEvent::KEYCODE_ALT_RIGHT ||
+           keyCode == KeyEvent::KEYCODE_SHIFT_LEFT ||
+           keyCode == KeyEvent::KEYCODE_SHIFT_RIGHT ||
+           keyCode == KeyEvent::KEYCODE_CTRL_LEFT ||
+           keyCode == KeyEvent::KEYCODE_CTRL_RIGHT ||
+           keyCode == KeyEvent::KEYCODE_META_LEFT ||
+           keyCode == KeyEvent::KEYCODE_META_RIGHT;
+}
+
+static Modifiers
+GetModifiers(int32_t metaState)
 {
-    int androidKeyCode = key.KeyCode();
-    uint32_t domKeyCode = ConvertAndroidKeyCodeToDOMKeyCode(androidKeyCode);
-
-    int modifiers = 0;
-    if (key.IsAltPressed())
-      modifiers |= kAlt_ANPKeyModifier;
-    if (key.IsShiftPressed())
-      modifiers |= kShift_ANPKeyModifier;
-
-    pluginEvent->inSize = sizeof(ANPEvent);
-    pluginEvent->eventType = kKey_ANPEventType;
-    pluginEvent->data.key.action = keyAction;
-    pluginEvent->data.key.nativeCode = androidKeyCode;
-    pluginEvent->data.key.virtualCode = domKeyCode;
-    pluginEvent->data.key.unichar = key.UnicodeChar();
-    pluginEvent->data.key.modifiers = modifiers;
-    pluginEvent->data.key.repeatCount = key.RepeatCount();
+    using mozilla::widget::sdk::KeyEvent;
+    return (metaState & KeyEvent::META_ALT_MASK ? MODIFIER_ALT : 0)
+        | (metaState & KeyEvent::META_SHIFT_MASK ? MODIFIER_SHIFT : 0)
+        | (metaState & KeyEvent::META_CTRL_MASK ? MODIFIER_CONTROL : 0)
+        | (metaState & KeyEvent::META_META_MASK ? MODIFIER_META : 0)
+        | (metaState & KeyEvent::META_FUNCTION_ON ? MODIFIER_FN : 0)
+        | (metaState & KeyEvent::META_CAPS_LOCK_ON ? MODIFIER_CAPSLOCK : 0)
+        | (metaState & KeyEvent::META_NUM_LOCK_ON ? MODIFIER_NUMLOCK : 0)
+        | (metaState & KeyEvent::META_SCROLL_LOCK_ON ? MODIFIER_SCROLLLOCK : 0);
 }
 
-void
-nsWindow::InitKeyEvent(WidgetKeyboardEvent& event, AndroidGeckoEvent& key,
-                       ANPEvent* pluginEvent)
+static void
+InitKeyEvent(WidgetKeyboardEvent& event,
+             int32_t action, int32_t keyCode, int32_t scanCode,
+             int32_t metaState, int64_t time, int32_t unicodeChar,
+             int32_t baseUnicodeChar, int32_t domPrintableKeyValue,
+             int32_t repeatCount, int32_t flags)
 {
-    event.mKeyNameIndex = ConvertAndroidKeyCodeToKeyNameIndex(key);
-    if (event.mKeyNameIndex == KEY_NAME_INDEX_USE_STRING) {
-        int keyValue = key.DOMPrintableKeyValue();
-        if (keyValue) {
-            event.mKeyValue = static_cast<char16_t>(keyValue);
-        }
-    }
-    event.mCodeNameIndex = ConvertAndroidScanCodeToCodeNameIndex(key);
-    uint32_t domKeyCode = ConvertAndroidKeyCodeToDOMKeyCode(key.KeyCode());
+    const uint32_t domKeyCode = ConvertAndroidKeyCodeToDOMKeyCode(keyCode);
+    const int32_t charCode = unicodeChar ? unicodeChar : baseUnicodeChar;
+
+    event.modifiers = GetModifiers(metaState);
 
     if (event.mMessage == eKeyPress) {
         // Android gives us \n, so filter out some control characters.
-        int charCode = key.UnicodeChar();
-        if (!charCode) {
-            charCode = key.BaseUnicodeChar();
-        }
         event.isChar = (charCode >= ' ');
         event.charCode = event.isChar ? charCode : 0;
-        event.keyCode = (event.charCode > 0) ? 0 : domKeyCode;
+        event.keyCode = event.isChar ? 0 : domKeyCode;
         event.mPluginEvent.Clear();
-    } else {
-#ifdef DEBUG
-        if (event.mMessage != eKeyDown && event.mMessage != eKeyUp) {
-            ALOG("InitKeyEvent: unexpected event.mMessage %d", event.mMessage);
+
+        // For keypress, if the unicode char already has modifiers applied, we
+        // don't specify extra modifiers. If UnicodeChar() != BaseUnicodeChar()
+        // it means UnicodeChar() already has modifiers applied.
+        // Note that on Android 4.x, Alt modifier isn't set when the key input
+        // causes text input even while right Alt key is pressed.  However,
+        // this is necessary for Android 2.3 compatibility.
+        if (unicodeChar && unicodeChar != baseUnicodeChar) {
+            event.modifiers &= ~(MODIFIER_ALT | MODIFIER_CONTROL
+                                              | MODIFIER_META);
         }
-#endif // DEBUG
-
-        // Flash will want a pluginEvent for keydown and keyup events.
-        ANPKeyActions action = event.mMessage == eKeyDown
-                             ? kDown_ANPKeyAction
-                             : kUp_ANPKeyAction;
-        InitPluginEvent(pluginEvent, action, key);
-
+
+    } else {
         event.isChar = false;
         event.charCode = 0;
         event.keyCode = domKeyCode;
-        event.mPluginEvent.Copy(*pluginEvent);
-    }
-
-    event.modifiers = key.DOMModifiers();
-    if (gMenu) {
-        event.modifiers |= MODIFIER_CONTROL;
-    }
-    // For keypress, if the unicode char already has modifiers applied, we
-    // don't specify extra modifiers. If UnicodeChar() != BaseUnicodeChar()
-    // it means UnicodeChar() already has modifiers applied.
-    // Note that on Android 4.x, Alt modifier isn't set when the key input
-    // causes text input even while right Alt key is pressed.  However, this
-    // is necessary for Android 2.3 compatibility.
-    if (event.mMessage == eKeyPress &&
-        key.UnicodeChar() && key.UnicodeChar() != key.BaseUnicodeChar()) {
-        event.modifiers &= ~(MODIFIER_ALT | MODIFIER_CONTROL | MODIFIER_META);
+
+        ANPEvent pluginEvent;
+        pluginEvent.inSize = sizeof(pluginEvent);
+        pluginEvent.eventType = kKey_ANPEventType;
+        pluginEvent.data.key.action = event.mMessage == eKeyDown
+                ? kDown_ANPKeyAction : kUp_ANPKeyAction;
+        pluginEvent.data.key.nativeCode = keyCode;
+        pluginEvent.data.key.virtualCode = domKeyCode;
+        pluginEvent.data.key.unichar = charCode;
+        pluginEvent.data.key.modifiers =
+                (metaState & sdk::KeyEvent::META_SHIFT_MASK
+                        ? kShift_ANPKeyModifier : 0) |
+                (metaState & sdk::KeyEvent::META_ALT_MASK
+                        ? kAlt_ANPKeyModifier : 0);
+        pluginEvent.data.key.repeatCount = repeatCount;
+        event.mPluginEvent.Copy(pluginEvent);
     }
 
     event.mIsRepeat =
         (event.mMessage == eKeyDown || event.mMessage == eKeyPress) &&
-        (!!(key.Flags() & AKEY_EVENT_FLAG_LONG_PRESS) || !!key.RepeatCount());
+        ((flags & sdk::KeyEvent::FLAG_LONG_PRESS) || repeatCount);
+
+    event.mKeyNameIndex = ConvertAndroidKeyCodeToKeyNameIndex(
+            keyCode, action, domPrintableKeyValue);
+    event.mCodeNameIndex = ConvertAndroidScanCodeToCodeNameIndex(scanCode);
+
+    if (event.mKeyNameIndex == KEY_NAME_INDEX_USE_STRING &&
+            domPrintableKeyValue) {
+        event.mKeyValue = char16_t(domPrintableKeyValue);
+    }
+
     event.location =
         WidgetKeyboardEvent::ComputeLocationFromCodeValue(event.mCodeNameIndex);
-    event.time = key.Time();
-
-    if (gMenu)
-        gMenuConsumed = true;
+    event.time = time;
 }
 
 void
-nsWindow::HandleSpecialKey(AndroidGeckoEvent *ae)
+nsWindow::Natives::OnKeyEvent(int32_t action, int32_t keyCode, int32_t scanCode,
+                              int32_t metaState, int64_t time,
+                              int32_t unicodeChar, int32_t baseUnicodeChar,
+                              int32_t domPrintableKeyValue, int32_t repeatCount,
+                              int32_t flags, bool isSynthesizedImeKey)
 {
-    RefPtr<nsWindow> kungFuDeathGrip(this);
-    nsCOMPtr<nsIAtom> command;
-    bool isDown = ae->Action() == AKEY_EVENT_ACTION_DOWN;
-    bool isLongPress = !!(ae->Flags() & AKEY_EVENT_FLAG_LONG_PRESS);
-    bool doCommand = false;
-    uint32_t keyCode = ae->KeyCode();
-
-    if (isDown) {
-        switch (keyCode) {
-            case AKEYCODE_BACK:
-                if (isLongPress) {
-                    command = nsGkAtoms::Clear;
-                    doCommand = true;
-                }
-                break;
-            case AKEYCODE_MENU:
-                gMenu = true;
-                gMenuConsumed = isLongPress;
-                break;
-        }
-    } else {
-        switch (keyCode) {
-            case AKEYCODE_BACK: {
-                // XXX Where is the keydown event for this??
-                WidgetKeyboardEvent pressEvent(true, eKeyPress, this);
-                ANPEvent pluginEvent;
-                InitKeyEvent(pressEvent, *ae, &pluginEvent);
-                DispatchEvent(&pressEvent);
-                return;
-            }
-            case AKEYCODE_MENU:
-                gMenu = false;
-                if (!gMenuConsumed) {
-                    command = nsGkAtoms::Menu;
-                    doCommand = true;
-                }
-                break;
-            case AKEYCODE_SEARCH:
-                command = nsGkAtoms::Search;
-                doCommand = true;
-                break;
-            default:
-                ALOG("Unknown special key code!");
-                return;
-        }
-    }
-    if (doCommand) {
-        WidgetCommandEvent event(true, nsGkAtoms::onAppCommand, command, this);
-        InitEvent(event);
-        DispatchEvent(&event);
-    }
-}
-
-void
-nsWindow::OnKeyEvent(AndroidGeckoEvent *ae)
-{
-    RefPtr<nsWindow> kungFuDeathGrip(this);
-    RemoveIMEComposition();
+    RefPtr<nsWindow> kungFuDeathGrip(&window);
+    window.UserActivity();
+    window.RemoveIMEComposition();
+
     EventMessage msg;
-    switch (ae->Action()) {
-    case AKEY_EVENT_ACTION_DOWN:
+    if (action == sdk::KeyEvent::ACTION_DOWN) {
         msg = eKeyDown;
-        break;
-    case AKEY_EVENT_ACTION_UP:
+    } else if (action == sdk::KeyEvent::ACTION_UP) {
         msg = eKeyUp;
-        break;
-    case AKEY_EVENT_ACTION_MULTIPLE:
+    } else if (action == sdk::KeyEvent::ACTION_MULTIPLE) {
         // Keys with multiple action are handled in Java,
         // and we should never see one here
         MOZ_CRASH("Cannot handle key with multiple action");
-    default:
+    } else {
         ALOG("Unknown key action event!");
         return;
     }
 
-    bool firePress = ae->Action() == AKEY_EVENT_ACTION_DOWN;
-    switch (ae->KeyCode()) {
-    case AKEYCODE_SHIFT_LEFT:
-    case AKEYCODE_SHIFT_RIGHT:
-    case AKEYCODE_ALT_LEFT:
-    case AKEYCODE_ALT_RIGHT:
-    case AKEYCODE_CTRL_LEFT:
-    case AKEYCODE_CTRL_RIGHT:
-        firePress = false;
-        break;
-    case AKEYCODE_BACK:
-    case AKEYCODE_MENU:
-    case AKEYCODE_SEARCH:
-        HandleSpecialKey(ae);
+    nsEventStatus status = nsEventStatus_eIgnore;
+    WidgetKeyboardEvent event(true, msg, &window);
+    window.InitEvent(event, nullptr);
+    InitKeyEvent(event, action, keyCode, scanCode, metaState, time,
+                 unicodeChar, baseUnicodeChar, domPrintableKeyValue,
+                 repeatCount, flags);
+
+    if (isSynthesizedImeKey) {
+        // Keys synthesized by Java IME code are saved in the mIMEKeyEvents
+        // array until the next IME_REPLACE_TEXT event, at which point
+        // these keys are dispatched in sequence.
+        window.mIMEKeyEvents.AppendElement(
+                mozilla::UniquePtr<WidgetEvent>(event.Duplicate()));
+    } else {
+        window.DispatchEvent(&event, status);
+    }
+
+    if (window.Destroyed() ||
+            status == nsEventStatus_eConsumeNoDefault ||
+            msg != eKeyDown || IsModifierKey(keyCode)) {
+        // Skip sending key press event.
         return;
     }
 
-    nsEventStatus status;
-    WidgetKeyboardEvent event(true, msg, this);
-    ANPEvent pluginEvent;
-    InitKeyEvent(event, *ae, &pluginEvent);
-    DispatchEvent(&event, status);
-
-    if (Destroyed())
-        return;
-    if (!firePress || status == nsEventStatus_eConsumeNoDefault) {
-        return;
+    WidgetKeyboardEvent pressEvent(true, eKeyPress, &window);
+    window.InitEvent(pressEvent, nullptr);
+    InitKeyEvent(pressEvent, action, keyCode, scanCode, metaState, time,
+                 unicodeChar, baseUnicodeChar, domPrintableKeyValue,
+                 repeatCount, flags);
+
+    if (isSynthesizedImeKey) {
+        window.mIMEKeyEvents.AppendElement(
+                mozilla::UniquePtr<WidgetEvent>(pressEvent.Duplicate()));
+    } else {
+        window.DispatchEvent(&pressEvent, status);
     }
-
-    WidgetKeyboardEvent pressEvent(true, eKeyPress, this);
-    InitKeyEvent(pressEvent, *ae, &pluginEvent);
-#ifdef DEBUG_ANDROID_WIDGET
-    __android_log_print(ANDROID_LOG_INFO, "Gecko", "Dispatching key pressEvent with keyCode %d charCode %d shift %d alt %d sym/ctrl %d metamask %d", pressEvent.keyCode, pressEvent.charCode, pressEvent.IsShift(), pressEvent.IsAlt(), pressEvent.IsControl(), ae->MetaState());
-#endif
-    DispatchEvent(&pressEvent);
 }
 
 #ifdef DEBUG_ANDROID_IME
 #define ALOGIME(args...) ALOG(args)
 #else
 #define ALOGIME(args...) ((void)0)
 #endif
 
@@ -1844,20 +1816,22 @@ nsWindow::RemoveIMEComposition()
  * Send dummy key events for pages that are unaware of input events,
  * to provide web compatibility for pages that depend on key events.
  * Our dummy key events have 0 as the keycode.
  */
 void
 nsWindow::SendIMEDummyKeyEvents()
 {
     WidgetKeyboardEvent downEvent(true, eKeyDown, this);
+    InitEvent(downEvent, nullptr);
     MOZ_ASSERT(downEvent.keyCode == 0);
     DispatchEvent(&downEvent);
 
     WidgetKeyboardEvent upEvent(true, eKeyUp, this);
+    InitEvent(upEvent, nullptr);
     MOZ_ASSERT(upEvent.keyCode == 0);
     DispatchEvent(&upEvent);
 }
 
 void
 nsWindow::OnIMEEvent(AndroidGeckoEvent *ae)
 {
     MOZ_ASSERT(!mIMEMaskSelectionUpdate);
@@ -1959,18 +1933,23 @@ nsWindow::OnIMEEvent(AndroidGeckoEvent *
                     InitEvent(event, nullptr);
                     event.mOffset = uint32_t(ae->Start());
                     event.mLength = uint32_t(ae->End() - ae->Start());
                     event.mExpandToClusterBoundary = false;
                     DispatchEvent(&event);
                 }
 
                 if (!mIMEKeyEvents.IsEmpty()) {
+                    nsEventStatus status;
                     for (uint32_t i = 0; i < mIMEKeyEvents.Length(); i++) {
-                        OnKeyEvent(&mIMEKeyEvents[i]);
+                        const auto event = static_cast<WidgetGUIEvent*>(
+                                mIMEKeyEvents[i].get());
+                        // widget for duplicated events is initially nullptr.
+                        event->widget = this;
+                        DispatchEvent(event, status);
                     }
                     mIMEKeyEvents.Clear();
                     FlushIMEChanges();
                     mEditable->NotifyIME(
                             GeckoEditableListener::NOTIFY_IME_REPLY_EVENT);
                     // Break out of the switch block
                     break;
                 }
--- a/widget/android/nsWindow.h
+++ b/widget/android/nsWindow.h
@@ -57,17 +57,16 @@ public:
     static mozilla::gfx::IntSize GetAndroidScreenBounds();
     static nsWindow* TopWindow();
 
     bool OnContextmenuEvent(mozilla::AndroidGeckoEvent *ae);
     void OnLongTapEvent(mozilla::AndroidGeckoEvent *ae);
     bool OnMultitouchEvent(mozilla::AndroidGeckoEvent *ae);
     void OnNativeGestureEvent(mozilla::AndroidGeckoEvent *ae);
     void OnMouseEvent(mozilla::AndroidGeckoEvent *ae);
-    void OnKeyEvent(mozilla::AndroidGeckoEvent *ae);
     void OnIMEEvent(mozilla::AndroidGeckoEvent *ae);
 
     void OnSizeChanged(const mozilla::gfx::IntSize& aSize);
 
     void InitEvent(mozilla::WidgetGUIEvent& event, nsIntPoint* aPoint = 0);
 
     //
     // nsIWidget
@@ -238,37 +237,33 @@ protected:
     double mLastDist;
 
     nsCOMPtr<nsIIdleServiceInternal> mIdleService;
 
     bool mIMEMaskSelectionUpdate;
     int32_t mIMEMaskEventsCount; // Mask events when > 0
     RefPtr<mozilla::TextRangeArray> mIMERanges;
     bool mIMEUpdatingContext;
-    nsAutoTArray<mozilla::AndroidGeckoEvent, 8> mIMEKeyEvents;
+    nsAutoTArray<mozilla::UniquePtr<mozilla::WidgetEvent>, 8> mIMEKeyEvents;
     nsAutoTArray<IMEChange, 4> mIMETextChanges;
     bool mIMESelectionChanged;
 
     bool mAwaitingFullScreen;
     bool mIsFullScreen;
 
     InputContext mInputContext;
 
     virtual nsresult NotifyIMEInternal(
                          const IMENotification& aIMENotification) override;
 
     static void DumpWindows();
     static void DumpWindows(const nsTArray<nsWindow*>& wins, int indent = 0);
     static void LogWindow(nsWindow *win, int index, int indent);
 
 private:
-    void InitKeyEvent(mozilla::WidgetKeyboardEvent& event,
-                      mozilla::AndroidGeckoEvent& key,
-                      ANPEvent* pluginEvent);
-    void HandleSpecialKey(mozilla::AndroidGeckoEvent *ae);
     void CreateLayerManager(int aCompositorWidth, int aCompositorHeight);
     void RedrawAll();
 
     mozilla::AndroidLayerRendererFrame mLayerRendererFrame;
 
     static mozilla::StaticRefPtr<mozilla::layers::APZCTreeManager> sApzcTreeManager;
     static mozilla::StaticRefPtr<mozilla::layers::LayerManager> sLayerManager;
     static mozilla::StaticRefPtr<mozilla::layers::CompositorParent> sCompositorParent;