Bug 1210585 - Add native calls for key events; r=esawin
authorJim Chen <nchen@mozilla.com>
Thu, 22 Oct 2015 17:45:46 -0400
changeset 304279 07bfa35414dcd816198fa935ceed832ce76320fd
parent 304278 e7d74a03c8447b9fbe5c7d532368cbb5d5d9d659
child 304280 ed18e273554dc11a4bd7ece904a83c61b4b47294
push id1001
push userraliiev@mozilla.com
push dateMon, 18 Jan 2016 19:06:03 +0000
treeherdermozilla-release@8b89261f3ac4 [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 - Add native calls for key events; r=esawin This patch adds a native call to GeckoEditable that will handle key events instead of using GeckoEvent.KEY_EVENT.
mobile/android/base/GeckoEditable.java
mobile/android/base/GeckoEditableClient.java
mobile/android/base/GeckoInputConnection.java
widget/android/GeneratedJNINatives.h
widget/android/GeneratedJNIWrappers.cpp
widget/android/GeneratedJNIWrappers.h
--- a/mobile/android/base/GeckoEditable.java
+++ b/mobile/android/base/GeckoEditable.java
@@ -13,16 +13,17 @@ import java.lang.reflect.Method;
 import java.lang.reflect.Proxy;
 import java.util.concurrent.ConcurrentLinkedQueue;
 import java.util.concurrent.Semaphore;
 
 import org.json.JSONObject;
 import org.mozilla.gecko.AppConstants.Versions;
 import org.mozilla.gecko.annotation.WrapForJNI;
 import org.mozilla.gecko.gfx.LayerView;
+import org.mozilla.gecko.mozglue.JNIObject;
 import org.mozilla.gecko.util.GeckoEventListener;
 import org.mozilla.gecko.util.ThreadUtils;
 import org.mozilla.gecko.util.ThreadUtils.AssertBehavior;
 
 import android.os.Handler;
 import android.os.Looper;
 import android.text.Editable;
 import android.text.InputFilter;
@@ -38,17 +39,17 @@ import android.util.Log;
 import android.view.KeyCharacterMap;
 import android.view.KeyEvent;
 
 /*
    GeckoEditable implements only some functions of Editable
    The field mText contains the actual underlying
    SpannableStringBuilder/Editable that contains our text.
 */
-final class GeckoEditable
+final class GeckoEditable extends JNIObject
         implements InvocationHandler, Editable,
                    GeckoEditableClient, GeckoEditableListener, GeckoEventListener {
 
     private static final boolean DEBUG = false;
     private static final String LOGTAG = "GeckoEditable";
 
     // Filters to implement Editable's filtering functionality
     private InputFilter[] mFilters;
@@ -70,16 +71,48 @@ final class GeckoEditable
     private int mIcUpdateSeqno;
     private int mLastIcUpdateSeqno;
     private boolean mUpdateGecko;
     private boolean mFocused; // Used by IC thread
     private boolean mGeckoFocused; // Used by Gecko thread
     private volatile boolean mSuppressCompositions;
     private volatile boolean mSuppressKeyUp;
 
+    @WrapForJNI
+    private native void onKeyEvent(int action, int keyCode, int scanCode, int metaState,
+                                   long time, int unicodeChar, int baseUnicodeChar,
+                                   int domPrintableKeyValue, int repeatCount, int flags,
+                                   boolean isSynthesizedImeKey);
+
+    private void onKeyEvent(KeyEvent event, int action, int savedMetaState,
+                            boolean isSynthesizedImeKey) {
+        // Use a separate action argument so we can override the key's original action,
+        // e.g. change ACTION_MULTIPLE to ACTION_DOWN. That way we don't have to allocate
+        // a new key event just to change its action field.
+        //
+        // Normally we expect event.getMetaState() to reflect the current meta-state; however,
+        // some software-generated key events may not have event.getMetaState() set, e.g. key
+        // events from Swype. Therefore, it's necessary to combine the key's meta-states
+        // with the meta-states that we keep separately in KeyListener
+        final int metaState = event.getMetaState() | savedMetaState;
+        final int unmodifiedMetaState = metaState &
+                ~(KeyEvent.META_ALT_MASK | KeyEvent.META_CTRL_MASK | KeyEvent.META_META_MASK);
+        final int unicodeChar = event.getUnicodeChar(metaState);
+        final int domPrintableKeyValue =
+                unicodeChar >= ' '               ? unicodeChar :
+                unmodifiedMetaState != metaState ? event.getUnicodeChar(unmodifiedMetaState) :
+                                                   0;
+        onKeyEvent(action, event.getKeyCode(), event.getScanCode(),
+                   metaState, event.getEventTime(), unicodeChar,
+                   // e.g. for Ctrl+A, Android returns 0 for unicodeChar,
+                   // but Gecko expects 'a', so we return that in baseUnicodeChar.
+                   event.getUnicodeChar(0), domPrintableKeyValue, event.getRepeatCount(),
+                   event.getFlags(), isSynthesizedImeKey);
+    }
+
     /* An action that alters the Editable
 
        Each action corresponds to a Gecko event. While the Gecko event is being sent to the Gecko
        thread, the action stays on top of mActions queue. After the Gecko event is processed and
        replied, the action is removed from the queue
     */
     private static final class Action {
         // For input events (keypress, etc.); use with IME_SYNCHRONIZE
@@ -288,17 +321,18 @@ final class GeckoEditable
                     continue;
                 }
                 if (event.getAction() == KeyEvent.ACTION_UP && mSuppressKeyUp) {
                     continue;
                 }
                 if (DEBUG) {
                     Log.d(LOGTAG, "sending: " + event);
                 }
-                GeckoAppShell.sendEventToGecko(GeckoEvent.createIMEKeyEvent(event));
+                onKeyEvent(event, event.getAction(),
+                           /* metaState */ 0, /* isSynthesizedImeKey */ true);
             }
         }
 
         /**
          * Remove the head of the queue. Throw if queue is empty.
          */
         void poll() {
             if (DEBUG) {
@@ -364,16 +398,20 @@ final class GeckoEditable
         final Class<?>[] PROXY_INTERFACES = { Editable.class };
         mProxy = (Editable)Proxy.newProxyInstance(
                 Editable.class.getClassLoader(),
                 PROXY_INTERFACES, this);
 
         mIcRunHandler = mIcPostHandler = ThreadUtils.getUiHandler();
     }
 
+    @Override
+    protected void disposeNative() {
+    }
+
     @WrapForJNI
     /* package */ void onViewChange(final GeckoView v) {
         if (DEBUG) {
             // Called by nsWindow.
             ThreadUtils.assertOnGeckoThread();
             Log.d(LOGTAG, "onViewChange(" + v + ")");
         }
 
@@ -574,30 +612,30 @@ final class GeckoEditable
 
         GeckoAppShell.sendEventToGecko(GeckoEvent.createIMECompositionEvent(
                 composingStart, composingEnd));
     }
 
     // GeckoEditableClient interface
 
     @Override
-    public void sendEvent(final GeckoEvent event) {
+    public void sendKeyEvent(final KeyEvent event, int action, int metaState) {
         if (DEBUG) {
             assertOnIcThread();
-            Log.d(LOGTAG, "sendEvent(" + event + ")");
+            Log.d(LOGTAG, "sendKeyEvent(" + event + ", " + action + ", " + metaState + ")");
         }
         /*
            We are actually sending two events to Gecko here,
-           1. Event from the event parameter (key event, etc.)
+           1. Event from the event parameter (key event)
            2. Sync event from the mActionQueue.offer call
-           The first event is a normal GeckoEvent that does not reply back to us,
+           The first event is a normal event that does not reply back to us,
            the second sync event will have a reply, during which we see that there is a pending
            event-type action, and update the selection/composition/etc. accordingly.
         */
-        GeckoAppShell.sendEventToGecko(event);
+        onKeyEvent(event, action, metaState, /* isSynthesizedImeKey */ false);
         mActionQueue.offer(new Action(Action.TYPE_EVENT));
     }
 
     @Override
     public Editable getEditable() {
         if (!onIcThread()) {
             // Android may be holding an old InputConnection; ignore
             if (DEBUG) {
--- a/mobile/android/base/GeckoEditableClient.java
+++ b/mobile/android/base/GeckoEditableClient.java
@@ -2,20 +2,21 @@
  * This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 package org.mozilla.gecko;
 
 import android.os.Handler;
 import android.text.Editable;
+import android.view.KeyEvent;
 
 /**
  * Interface for the IC thread.
  */
 interface GeckoEditableClient {
-    void sendEvent(GeckoEvent event);
+    void sendKeyEvent(KeyEvent event, int action, int metaState);
     Editable getEditable();
     void setUpdateGecko(boolean update);
     void setSuppressKeyUp(boolean suppress);
     Handler getInputConnectionHandler();
     boolean setInputConnectionHandler(Handler handler);
 }
--- a/mobile/android/base/GeckoInputConnection.java
+++ b/mobile/android/base/GeckoInputConnection.java
@@ -135,22 +135,24 @@ class GeckoInputConnection
             if (icHandler.getLooper() == uiHandler.getLooper()) {
                 // IC thread is UI thread; safe to run directly
                 runnable.run();
                 return;
             }
             runOnIcThread(icHandler, runnable);
         }
 
-        public void sendEventFromUiThread(final Handler uiHandler,
-                                          final GeckoEditableClient client,
-                                          final GeckoEvent event) {
+        public void sendKeyEventFromUiThread(final Handler uiHandler,
+                                             final GeckoEditableClient client,
+                                             final KeyEvent event,
+                                             final int action,
+                                             final int metaState) {
             runOnIcThread(uiHandler, client, new Runnable() {
                 @Override public void run() {
-                    client.sendEvent(event);
+                    client.sendKeyEvent(event, action, metaState);
                 }
             });
         }
 
         public Editable getEditableForUiThread(final Handler uiHandler,
                                                final GeckoEditableClient client) {
             if (DEBUG) {
                 ThreadUtils.assertOnThread(uiHandler.getLooper().getThread(), AssertBehavior.THROW);
@@ -813,36 +815,40 @@ class GeckoInputConnection
             return false;
         }
         final int action = down ? KeyEvent.ACTION_DOWN : KeyEvent.ACTION_UP;
         event = translateKey(keyCode, event);
         keyCode = event.getKeyCode();
 
         View view = getView();
         if (view == null) {
-            InputThreadUtils.sInstance.sendEventFromUiThread(ThreadUtils.getUiHandler(),
-                mEditableClient, GeckoEvent.createKeyEvent(event, action, 0));
+            InputThreadUtils.sInstance.sendKeyEventFromUiThread(
+                    ThreadUtils.getUiHandler(), mEditableClient, event, action, /* metaState */ 0);
             return true;
         }
 
         // KeyListener returns true if it handled the event for us. KeyListener is only
         // safe to use on the UI thread; therefore we need to pass a proxy Editable to it
         KeyListener keyListener = TextKeyListener.getInstance();
         Handler uiHandler = view.getRootView().getHandler();
         Editable uiEditable = InputThreadUtils.sInstance.
             getEditableForUiThread(uiHandler, mEditableClient);
         boolean skip = shouldSkipKeyListener(keyCode, event);
+
         if (down) {
             mEditableClient.setSuppressKeyUp(true);
         }
         if (skip ||
             (down && !keyListener.onKeyDown(view, uiEditable, keyCode, event)) ||
             (!down && !keyListener.onKeyUp(view, uiEditable, keyCode, event))) {
-            InputThreadUtils.sInstance.sendEventFromUiThread(uiHandler, mEditableClient,
-                GeckoEvent.createKeyEvent(event, action, TextKeyListener.getMetaState(uiEditable)));
+
+            InputThreadUtils.sInstance.sendKeyEventFromUiThread(
+                    uiHandler, mEditableClient,
+                    event, action, TextKeyListener.getMetaState(uiEditable));
+
             if (skip && down) {
                 // Usually, the down key listener call above adjusts meta states for us.
                 // However, if we skip that call above, we have to manually adjust meta
                 // states so the meta states remain consistent
                 TextKeyListener.adjustMetaAfterKeypress(uiEditable);
             }
         }
         if (down) {
--- a/widget/android/GeneratedJNINatives.h
+++ b/widget/android/GeneratedJNINatives.h
@@ -47,16 +47,31 @@ public:
                 ::template Wrap<&Impl::NotifyAlarmFired>)
     };
 };
 
 template<class Impl>
 constexpr JNINativeMethod AlarmReceiver::Natives<Impl>::methods[];
 
 template<class Impl>
+class GeckoEditable::Natives : public mozilla::jni::NativeImpl<GeckoEditable, Impl>
+{
+public:
+    static constexpr JNINativeMethod methods[] = {
+
+        mozilla::jni::MakeNativeMethod<GeckoEditable::OnKeyEvent_t>(
+                mozilla::jni::NativeStub<GeckoEditable::OnKeyEvent_t, Impl>
+                ::template Wrap<&Impl::OnKeyEvent>)
+    };
+};
+
+template<class Impl>
+constexpr JNINativeMethod GeckoEditable::Natives<Impl>::methods[];
+
+template<class Impl>
 class GeckoJavaSampler::Natives : public mozilla::jni::NativeImpl<GeckoJavaSampler, Impl>
 {
 public:
     static constexpr JNINativeMethod methods[] = {
 
         mozilla::jni::MakeNativeMethod<GeckoJavaSampler::GetProfilerTime_t>(
                 mozilla::jni::NativeStub<GeckoJavaSampler::GetProfilerTime_t, Impl>
                 ::template Wrap<&Impl::GetProfilerTime>)
--- a/widget/android/GeneratedJNIWrappers.cpp
+++ b/widget/android/GeneratedJNIWrappers.cpp
@@ -747,16 +747,19 @@ auto GeckoEditable::NotifyIME(int32_t a0
 constexpr char GeckoEditable::NotifyIMEContext_t::name[];
 constexpr char GeckoEditable::NotifyIMEContext_t::signature[];
 
 auto GeckoEditable::NotifyIMEContext(int32_t a0, mozilla::jni::String::Param a1, mozilla::jni::String::Param a2, mozilla::jni::String::Param a3) const -> void
 {
     return mozilla::jni::Method<NotifyIMEContext_t>::Call(this, nullptr, a0, a1, a2, a3);
 }
 
+constexpr char GeckoEditable::OnKeyEvent_t::name[];
+constexpr char GeckoEditable::OnKeyEvent_t::signature[];
+
 constexpr char GeckoEditable::OnSelectionChange_t::name[];
 constexpr char GeckoEditable::OnSelectionChange_t::signature[];
 
 auto GeckoEditable::OnSelectionChange(int32_t a0, int32_t a1) const -> void
 {
     return mozilla::jni::Method<OnSelectionChange_t>::Call(this, nullptr, a0, a1);
 }
 
--- a/widget/android/GeneratedJNIWrappers.h
+++ b/widget/android/GeneratedJNIWrappers.h
@@ -1791,16 +1791,42 @@ public:
         static const bool isMultithreaded = false;
         static const mozilla::jni::ExceptionMode exceptionMode =
                 mozilla::jni::ExceptionMode::ABORT;
     };
 
     auto NotifyIMEContext(int32_t, mozilla::jni::String::Param, mozilla::jni::String::Param, mozilla::jni::String::Param) const -> void;
 
 public:
+    struct OnKeyEvent_t {
+        typedef GeckoEditable Owner;
+        typedef void ReturnType;
+        typedef void SetterType;
+        typedef mozilla::jni::Args<
+                int32_t,
+                int32_t,
+                int32_t,
+                int32_t,
+                int64_t,
+                int32_t,
+                int32_t,
+                int32_t,
+                int32_t,
+                int32_t,
+                bool> Args;
+        static constexpr char name[] = "onKeyEvent";
+        static constexpr char signature[] =
+                "(IIIIJIIIIIZ)V";
+        static const bool isStatic = false;
+        static const bool isMultithreaded = false;
+        static const mozilla::jni::ExceptionMode exceptionMode =
+                mozilla::jni::ExceptionMode::ABORT;
+    };
+
+public:
     struct OnSelectionChange_t {
         typedef GeckoEditable Owner;
         typedef void ReturnType;
         typedef void SetterType;
         typedef mozilla::jni::Args<
                 int32_t,
                 int32_t> Args;
         static constexpr char name[] = "onSelectionChange";
@@ -1848,16 +1874,18 @@ public:
         static const bool isStatic = false;
         static const bool isMultithreaded = false;
         static const mozilla::jni::ExceptionMode exceptionMode =
                 mozilla::jni::ExceptionMode::ABORT;
     };
 
     auto OnViewChange(mozilla::jni::Object::Param) const -> void;
 
+public:
+    template<class Impl> class Natives;
 };
 
 class GeckoEditableListener : public mozilla::jni::Class<GeckoEditableListener>
 {
 public:
     typedef mozilla::jni::Ref<GeckoEditableListener> Ref;
     typedef mozilla::jni::LocalRef<GeckoEditableListener> LocalRef;
     typedef mozilla::jni::GlobalRef<GeckoEditableListener> GlobalRef;