author | Jim Chen <nchen@mozilla.com> |
Thu, 22 Oct 2015 17:45:46 -0400 | |
changeset 269021 | 07bfa35414dcd816198fa935ceed832ce76320fd |
parent 269020 | e7d74a03c8447b9fbe5c7d532368cbb5d5d9d659 |
child 269022 | ed18e273554dc11a4bd7ece904a83c61b4b47294 |
push id | 67000 |
push user | nchen@mozilla.com |
push date | Thu, 22 Oct 2015 21:47:22 +0000 |
treeherder | mozilla-inbound@d9882266e0b3 [default view] [failures only] |
perfherder | [talos] [build metrics] [platform microbench] (compared to previous push) |
reviewers | esawin |
bugs | 1210585 |
milestone | 44.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
|
--- 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;