Bug 1094729 - Part 2. Support floating candidate window for hardware keyboard on Android 5+. r=jchen
authorMakoto Kato <m_kato@ga2.so-net.ne.jp>
Wed, 20 Jul 2016 16:19:05 +0900
changeset 331532 30f12e33d4ea79d667788e327af851550ba594f9
parent 331531 9ee0cce1cbe5d50a1fca200fa8bf2ec2a1003bf9
child 331533 62eb0641f4be4cd04b959944ae3308c2b58d3486
push id9858
push userjlund@mozilla.com
push dateMon, 01 Aug 2016 14:37:10 +0000
treeherdermozilla-aurora@203106ef6cb6 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjchen
bugs1094729
milestone50.0a1
Bug 1094729 - Part 2. Support floating candidate window for hardware keyboard on Android 5+. r=jchen MozReview-Commit-ID: G148os7J9wO
mobile/android/base/java/org/mozilla/gecko/GeckoEditable.java
mobile/android/base/java/org/mozilla/gecko/GeckoEditableClient.java
mobile/android/base/java/org/mozilla/gecko/GeckoEditableListener.java
mobile/android/base/java/org/mozilla/gecko/GeckoInputConnection.java
widget/android/GeneratedJNINatives.h
widget/android/GeneratedJNIWrappers.cpp
widget/android/GeneratedJNIWrappers.h
widget/android/nsWindow.cpp
--- a/mobile/android/base/java/org/mozilla/gecko/GeckoEditable.java
+++ b/mobile/android/base/java/org/mozilla/gecko/GeckoEditable.java
@@ -18,16 +18,17 @@ 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.graphics.RectF;
 import android.os.Handler;
 import android.os.Looper;
 import android.text.Editable;
 import android.text.InputFilter;
 import android.text.NoCopySpan;
 import android.text.Selection;
 import android.text.Spannable;
 import android.text.SpannableString;
@@ -138,16 +139,19 @@ final class GeckoEditable extends JNIObj
     private native void onImeAddCompositionRange(int start, int end, int rangeType,
                                                  int rangeStyles, int rangeLineStyle,
                                                  boolean rangeBoldLine, int rangeForeColor,
                                                  int rangeBackColor, int rangeLineColor);
 
     @WrapForJNI
     private native void onImeUpdateComposition(int start, int end);
 
+    @WrapForJNI
+    private native void onImeRequestCursorUpdates(int requestMode);
+
     /* 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 onImeSynchronize
@@ -774,16 +778,21 @@ final class GeckoEditable extends JNIObj
         return handler;
     }
 
     @Override // GeckoEditableClient
     public void postToInputConnection(final Runnable runnable) {
         mIcPostHandler.post(runnable);
     }
 
+    @Override // GeckoEditableClient
+    public void requestCursorUpdates(int requestMode) {
+        onImeRequestCursorUpdates(requestMode);
+    }
+
     private void geckoSetIcHandler(final Handler newHandler) {
         geckoPostToIc(new Runnable() { // posting to old IC thread
             @Override
             public void run() {
                 synchronized (newHandler) {
                     mIcRunHandler = newHandler;
                     newHandler.notify();
                 }
@@ -1135,16 +1144,34 @@ final class GeckoEditable extends JNIObj
                 if (mListener == null) {
                     return;
                 }
                 mListener.onDefaultKeyEvent(event);
             }
         });
     }
 
+    @WrapForJNI @Override
+    public void updateCompositionRects(final RectF[] aRects) {
+        if (DEBUG) {
+            // GeckoEditableListener methods should all be called from the Gecko thread
+            ThreadUtils.assertOnGeckoThread();
+            Log.d(LOGTAG, "updateCompositionRects(aRects.length = " + aRects.length + ")");
+        }
+        geckoPostToIc(new Runnable() {
+            @Override
+            public void run() {
+                if (mListener == null) {
+                    return;
+                }
+                mListener.updateCompositionRects(aRects);
+            }
+        });
+    }
+
     // InvocationHandler interface
 
     static String getConstantName(Class<?> cls, String prefix, Object value) {
         for (Field fld : cls.getDeclaredFields()) {
             try {
                 if (fld.getName().startsWith(prefix) &&
                     fld.get(null).equals(value)) {
                     return fld.getName();
--- a/mobile/android/base/java/org/mozilla/gecko/GeckoEditableClient.java
+++ b/mobile/android/base/java/org/mozilla/gecko/GeckoEditableClient.java
@@ -14,9 +14,20 @@ import android.view.KeyEvent;
  */
 interface GeckoEditableClient {
     void sendKeyEvent(KeyEvent event, int action, int metaState);
     Editable getEditable();
     void setBatchMode(boolean isBatchMode);
     void setSuppressKeyUp(boolean suppress);
     Handler setInputConnectionHandler(Handler handler);
     void postToInputConnection(Runnable runnable);
+
+    // The following value is used by requestCursorUpdates
+
+    // ONE_SHOT calls updateCompositionRects() after getting current composing character rects.
+    public static final int ONE_SHOT = 1;
+    // START_MONITOR start the monitor for composing character rects.  If is is updaed,  call updateCompositionRects()
+    public static final int START_MONITOR = 2;
+    // ENDT_MONITOR stops the monitor for composing character rects.
+    public static final int END_MONITOR = 3;
+
+    void requestCursorUpdates(int requestMode);
 }
--- a/mobile/android/base/java/org/mozilla/gecko/GeckoEditableListener.java
+++ b/mobile/android/base/java/org/mozilla/gecko/GeckoEditableListener.java
@@ -2,16 +2,17 @@
  * 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 org.mozilla.gecko.annotation.WrapForJNI;
 
+import android.graphics.RectF;
 import android.view.KeyEvent;
 
 /**
  * Interface for the Editable to listen on the Gecko thread, as well as for the IC thread to listen
  * to the Editable.
  */
 interface GeckoEditableListener {
     // IME notification type for notifyIME(), corresponding to NotificationToIME enum in Gecko
@@ -33,9 +34,10 @@ interface GeckoEditableListener {
     int IME_STATE_PASSWORD = 2;
     int IME_STATE_PLUGIN = 3;
 
     void notifyIME(int type);
     void notifyIMEContext(int state, String typeHint, String modeHint, String actionHint);
     void onSelectionChange(int start, int end);
     void onTextChange(CharSequence text, int start, int oldEnd, int newEnd);
     void onDefaultKeyEvent(KeyEvent event);
+    void updateCompositionRects(final RectF[] aRects);
 }
--- a/mobile/android/base/java/org/mozilla/gecko/GeckoInputConnection.java
+++ b/mobile/android/base/java/org/mozilla/gecko/GeckoInputConnection.java
@@ -6,40 +6,45 @@
 package org.mozilla.gecko;
 
 import java.lang.reflect.InvocationHandler;
 import java.lang.reflect.Method;
 import java.lang.reflect.Proxy;
 import java.util.concurrent.SynchronousQueue;
 
 import org.mozilla.gecko.AppConstants.Versions;
+import org.mozilla.gecko.gfx.DynamicToolbarAnimator;
 import org.mozilla.gecko.util.Clipboard;
 import org.mozilla.gecko.util.GamepadUtils;
 import org.mozilla.gecko.util.ThreadUtils;
 import org.mozilla.gecko.util.ThreadUtils.AssertBehavior;
 
 import android.annotation.SuppressLint;
+import android.anontation.TargetApi;
 import android.content.Context;
 import android.content.res.Configuration;
+import android.graphics.Matrix;
+import android.graphics.RectF;
 import android.media.AudioManager;
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.Looper;
 import android.os.SystemClock;
 import android.text.Editable;
 import android.text.InputType;
 import android.text.Selection;
 import android.text.SpannableString;
 import android.text.method.KeyListener;
 import android.text.method.TextKeyListener;
 import android.util.DisplayMetrics;
 import android.util.Log;
 import android.view.KeyEvent;
 import android.view.View;
 import android.view.inputmethod.BaseInputConnection;
+import android.view.inputmethod.CursorAnchorInfo;
 import android.view.inputmethod.EditorInfo;
 import android.view.inputmethod.ExtractedText;
 import android.view.inputmethod.ExtractedTextRequest;
 import android.view.inputmethod.InputConnection;
 import android.view.inputmethod.InputMethodManager;
 
 class GeckoInputConnection
     extends BaseInputConnection
@@ -67,16 +72,17 @@ class GeckoInputConnection
     private final View mView;
     private final GeckoEditableClient mEditableClient;
     protected int mBatchEditCount;
     private ExtractedTextRequest mUpdateRequest;
     private final ExtractedText mUpdateExtract = new ExtractedText();
     private boolean mBatchSelectionChanged;
     private boolean mBatchTextChanged;
     private final InputConnection mKeyInputConnection;
+    private CursorAnchorInfo.Builder mCursorAnchorInfoBuilder;
 
     // Prevent showSoftInput and hideSoftInput from causing reentrant calls on some devices.
     private volatile boolean mSoftInputReentrancyGuard;
 
     public static GeckoEditableListener create(View targetView,
                                                GeckoEditableClient editable) {
         if (DEBUG)
             return DebugGeckoInputConnection.create(targetView, editable);
@@ -387,16 +393,106 @@ class GeckoInputConnection
         if (imm == null || v == null || editable == null) {
             return;
         }
         imm.updateSelection(v, start, end, getComposingSpanStart(editable),
                             getComposingSpanEnd(editable));
     }
 
     @Override
+    public void updateCompositionRects(final RectF[] aRects) {
+        if (!Versions.feature21Plus) {
+            return;
+        }
+
+        if (mCursorAnchorInfoBuilder == null) {
+            mCursorAnchorInfoBuilder = new CursorAnchorInfo.Builder();
+        }
+        mCursorAnchorInfoBuilder.reset();
+
+        // Calculate Gecko logical coords to screen coords
+        final View v = getView();
+        if (v == null) {
+            return;
+        }
+
+        int[] viewCoords = new int[2];
+        v.getLocationOnScreen(viewCoords);
+
+        DynamicToolbarAnimator animator = GeckoAppShell.getLayerView().getDynamicToolbarAnimator();
+        float toolbarHeight = animator.getMaxTranslation() - animator.getToolbarTranslation();
+
+        Matrix matrix = GeckoAppShell.getLayerView().getMatrixForLayerRectToViewRect();
+        if (matrix == null) {
+            if (DEBUG) {
+                Log.d(LOGTAG, "Cannot get Matrix to convert from Gecko coords to layer view coords");
+            }
+            return;
+        }
+        matrix.postTranslate(viewCoords[0], viewCoords[1] + toolbarHeight);
+        mCursorAnchorInfoBuilder.setMatrix(matrix);
+
+        final Editable content = getEditable();
+        if (content == null) {
+            return;
+        }
+        int composingStart = getComposingSpanStart(content);
+        int composingEnd = getComposingSpanEnd(content);
+        if (composingStart < 0 || composingEnd < 0) {
+            if (DEBUG) {
+                Log.d(LOGTAG, "No composition for updates");
+            }
+            return;
+        }
+
+        for (int i = 0; i < aRects.length; i++) {
+            mCursorAnchorInfoBuilder.addCharacterBounds(i,
+                                                        aRects[i].left,
+                                                        aRects[i].top,
+                                                        aRects[i].right,
+                                                        aRects[i].bottom,
+                                                        CursorAnchorInfo.FLAG_HAS_VISIBLE_REGION);
+        }
+
+        mCursorAnchorInfoBuilder.setComposingText(0, content.subSequence(composingStart, composingEnd));
+
+        updateCursor();
+    }
+
+    @TargetApi(21)
+    private void updateCursor() {
+        if (mCursorAnchorInfoBuilder == null) {
+            return;
+        }
+
+        final InputMethodManager imm = getInputMethodManager();
+        final View v = getView();
+        if (imm == null || v == null) {
+            return;
+        }
+
+        imm.updateCursorAnchorInfo(v, mCursorAnchorInfoBuilder.build());
+    }
+
+    @Override
+    public boolean requestCursorUpdates(int cursorUpdateMode) {
+
+        if ((cursorUpdateMode & InputConnection.CURSOR_UPDATE_IMMEDIATE) != 0) {
+            mEditableClient.requestCursorUpdates(GeckoEditableClient.ONE_SHOT);
+        }
+
+        if ((cursorUpdateMode & InputConnection.CURSOR_UPDATE_MONITOR) != 0) {
+            mEditableClient.requestCursorUpdates(GeckoEditableClient.START_MONITOR);
+        } else {
+            mEditableClient.requestCursorUpdates(GeckoEditableClient.END_MONITOR);
+        }
+        return true;
+    }
+
+    @Override
     public void onDefaultKeyEvent(final KeyEvent event) {
         ThreadUtils.postToUiThread(new Runnable() {
             @Override
             public void run() {
                 GeckoInputConnection.this.performDefaultKeyAction(event);
             }
         });
     }
--- a/widget/android/GeneratedJNINatives.h
+++ b/widget/android/GeneratedJNINatives.h
@@ -81,17 +81,17 @@ const JNINativeMethod GeckoAppShell::Nat
             mozilla::jni::NativeStub<GeckoAppShell::SyncNotifyObservers_t, Impl>
             ::template Wrap<&Impl::SyncNotifyObservers>)
 };
 
 template<class Impl>
 class GeckoEditable::Natives : public mozilla::jni::NativeImpl<GeckoEditable, Impl>
 {
 public:
-    static const JNINativeMethod methods[7];
+    static const JNINativeMethod methods[8];
 };
 
 template<class Impl>
 const JNINativeMethod GeckoEditable::Natives<Impl>::methods[] = {
 
     mozilla::jni::MakeNativeMethod<GeckoEditable::DisposeNative_t>(
             mozilla::jni::NativeStub<GeckoEditable::DisposeNative_t, Impl>
             ::template Wrap<&Impl::DisposeNative>),
@@ -103,16 +103,20 @@ const JNINativeMethod GeckoEditable::Nat
     mozilla::jni::MakeNativeMethod<GeckoEditable::OnImeAddCompositionRange_t>(
             mozilla::jni::NativeStub<GeckoEditable::OnImeAddCompositionRange_t, Impl>
             ::template Wrap<&Impl::OnImeAddCompositionRange>),
 
     mozilla::jni::MakeNativeMethod<GeckoEditable::OnImeReplaceText_t>(
             mozilla::jni::NativeStub<GeckoEditable::OnImeReplaceText_t, Impl>
             ::template Wrap<&Impl::OnImeReplaceText>),
 
+    mozilla::jni::MakeNativeMethod<GeckoEditable::OnImeRequestCursorUpdates_t>(
+            mozilla::jni::NativeStub<GeckoEditable::OnImeRequestCursorUpdates_t, Impl>
+            ::template Wrap<&Impl::OnImeRequestCursorUpdates>),
+
     mozilla::jni::MakeNativeMethod<GeckoEditable::OnImeSynchronize_t>(
             mozilla::jni::NativeStub<GeckoEditable::OnImeSynchronize_t, Impl>
             ::template Wrap<&Impl::OnImeSynchronize>),
 
     mozilla::jni::MakeNativeMethod<GeckoEditable::OnImeUpdateComposition_t>(
             mozilla::jni::NativeStub<GeckoEditable::OnImeUpdateComposition_t, Impl>
             ::template Wrap<&Impl::OnImeUpdateComposition>),
 
--- a/widget/android/GeneratedJNIWrappers.cpp
+++ b/widget/android/GeneratedJNIWrappers.cpp
@@ -803,16 +803,19 @@ constexpr char GeckoEditable::OnImeAckno
 constexpr char GeckoEditable::OnImeAcknowledgeFocus_t::signature[];
 
 constexpr char GeckoEditable::OnImeAddCompositionRange_t::name[];
 constexpr char GeckoEditable::OnImeAddCompositionRange_t::signature[];
 
 constexpr char GeckoEditable::OnImeReplaceText_t::name[];
 constexpr char GeckoEditable::OnImeReplaceText_t::signature[];
 
+constexpr char GeckoEditable::OnImeRequestCursorUpdates_t::name[];
+constexpr char GeckoEditable::OnImeRequestCursorUpdates_t::signature[];
+
 constexpr char GeckoEditable::OnImeSynchronize_t::name[];
 constexpr char GeckoEditable::OnImeSynchronize_t::signature[];
 
 constexpr char GeckoEditable::OnImeUpdateComposition_t::name[];
 constexpr char GeckoEditable::OnImeUpdateComposition_t::signature[];
 
 constexpr char GeckoEditable::OnKeyEvent_t::name[];
 constexpr char GeckoEditable::OnKeyEvent_t::signature[];
@@ -836,16 +839,24 @@ auto GeckoEditable::OnTextChange(mozilla
 constexpr char GeckoEditable::OnViewChange_t::name[];
 constexpr char GeckoEditable::OnViewChange_t::signature[];
 
 auto GeckoEditable::OnViewChange(mozilla::jni::Object::Param a0) const -> void
 {
     return mozilla::jni::Method<OnViewChange_t>::Call(GeckoEditable::mCtx, nullptr, a0);
 }
 
+constexpr char GeckoEditable::UpdateCompositionRects_t::name[];
+constexpr char GeckoEditable::UpdateCompositionRects_t::signature[];
+
+auto GeckoEditable::UpdateCompositionRects(mozilla::jni::ObjectArray::Param a0) const -> void
+{
+    return mozilla::jni::Method<UpdateCompositionRects_t>::Call(GeckoEditable::mCtx, nullptr, a0);
+}
+
 const char GeckoEditableListener::name[] =
         "org/mozilla/gecko/GeckoEditableListener";
 
 const char GeckoJavaSampler::name[] =
         "org/mozilla/gecko/GeckoJavaSampler";
 
 constexpr char GeckoJavaSampler::GetFrameNameJavaProfilingWrapper_t::name[];
 constexpr char GeckoJavaSampler::GetFrameNameJavaProfilingWrapper_t::signature[];
--- a/widget/android/GeneratedJNIWrappers.h
+++ b/widget/android/GeneratedJNIWrappers.h
@@ -1776,16 +1776,30 @@ public:
         static constexpr char name[] = "onImeReplaceText";
         static constexpr char signature[] =
                 "(IILjava/lang/String;)V";
         static const bool isStatic = false;
         static const mozilla::jni::ExceptionMode exceptionMode =
                 mozilla::jni::ExceptionMode::ABORT;
     };
 
+    struct OnImeRequestCursorUpdates_t {
+        typedef GeckoEditable Owner;
+        typedef void ReturnType;
+        typedef void SetterType;
+        typedef mozilla::jni::Args<
+                int32_t> Args;
+        static constexpr char name[] = "onImeRequestCursorUpdates";
+        static constexpr char signature[] =
+                "(I)V";
+        static const bool isStatic = false;
+        static const mozilla::jni::ExceptionMode exceptionMode =
+                mozilla::jni::ExceptionMode::ABORT;
+    };
+
     struct OnImeSynchronize_t {
         typedef GeckoEditable Owner;
         typedef void ReturnType;
         typedef void SetterType;
         typedef mozilla::jni::Args<> Args;
         static constexpr char name[] = "onImeSynchronize";
         static constexpr char signature[] =
                 "()V";
@@ -1881,16 +1895,32 @@ public:
                 "(Lorg/mozilla/gecko/GeckoView;)V";
         static const bool isStatic = false;
         static const mozilla::jni::ExceptionMode exceptionMode =
                 mozilla::jni::ExceptionMode::ABORT;
     };
 
     auto OnViewChange(mozilla::jni::Object::Param) const -> void;
 
+    struct UpdateCompositionRects_t {
+        typedef GeckoEditable Owner;
+        typedef void ReturnType;
+        typedef void SetterType;
+        typedef mozilla::jni::Args<
+                mozilla::jni::ObjectArray::Param> Args;
+        static constexpr char name[] = "updateCompositionRects";
+        static constexpr char signature[] =
+                "([Landroid/graphics/RectF;)V";
+        static const bool isStatic = false;
+        static const mozilla::jni::ExceptionMode exceptionMode =
+                mozilla::jni::ExceptionMode::ABORT;
+    };
+
+    auto UpdateCompositionRects(mozilla::jni::ObjectArray::Param) const -> void;
+
     static const bool isMultithreaded = false;
 
     template<class Impl> class Natives;
 };
 
 class GeckoEditableListener : public mozilla::jni::ObjectBase<GeckoEditableListener, jobject>
 {
 public:
--- a/widget/android/nsWindow.cpp
+++ b/widget/android/nsWindow.cpp
@@ -109,16 +109,21 @@ static nsTArray<nsWindow*> gTopLevelWind
 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;
 
+// Sync with GeckoEditableView class
+static const int IME_MONITOR_CURSOR_ONE_SHOT = 1;
+static const int IME_MONITOR_CURSOR_START_MONITOR = 2;
+static const int IME_MONITOR_CURSOR_END_MONITOR = 3;
+
 static Modifiers GetModifiers(int32_t metaState);
 
 template<typename Lambda, bool IsStatic, typename InstanceType, class Impl>
 class nsWindow::WindowEvent : public nsAppShell::LambdaEvent<Lambda>
 {
     typedef nsAppShell::Event Event;
     typedef nsAppShell::LambdaEvent<Lambda> Base;
 
@@ -215,16 +220,17 @@ public:
                      GeckoView::Param aView)
         : window(*aWindow)
         , mEditable(GeckoEditable::New(aView))
         , mIMERanges(new TextRangeArray())
         , mIMEMaskEventsCount(1) // Mask IME events since there's no focus yet
         , mIMEUpdatingContext(false)
         , mIMESelectionChanged(false)
         , mIMETextChangedDuringFlush(false)
+        , mIMEMonitorCursor(false)
     {
         Base::AttachNative(aInstance, this);
         EditableBase::AttachNative(mEditable, this);
     }
 
     ~GeckoViewSupport();
 
     using Base::DisposeNative;
@@ -294,27 +300,29 @@ private:
     AutoTArray<mozilla::UniquePtr<mozilla::WidgetEvent>, 8> mIMEKeyEvents;
     AutoTArray<IMETextChange, 4> mIMETextChanges;
     InputContext mInputContext;
     RefPtr<mozilla::TextRangeArray> mIMERanges;
     int32_t mIMEMaskEventsCount; // Mask events when > 0
     bool mIMEUpdatingContext;
     bool mIMESelectionChanged;
     bool mIMETextChangedDuringFlush;
+    bool mIMEMonitorCursor;
 
     void SendIMEDummyKeyEvents();
     void AddIMETextChange(const IMETextChange& aChange);
 
     enum FlushChangesFlag {
         FLUSH_FLAG_NONE,
         FLUSH_FLAG_RETRY
     };
     void PostFlushIMEChanges();
     void FlushIMEChanges(FlushChangesFlag aFlags = FLUSH_FLAG_NONE);
     void AsyncNotifyIME(int32_t aNotification);
+    void UpdateCompositionRects();
 
 public:
     bool NotifyIME(const IMENotification& aIMENotification);
     void SetInputContext(const InputContext& aContext,
                          const InputContextAction& aAction);
     InputContext GetInputContext();
 
     // RAII helper class that automatically sends an event reply through
@@ -346,16 +354,19 @@ public:
     // Add styling for a range within the active composition.
     void OnImeAddCompositionRange(int32_t aStart, int32_t aEnd,
             int32_t aRangeType, int32_t aRangeStyle, int32_t aRangeLineStyle,
             bool aRangeBoldLine, int32_t aRangeForeColor,
             int32_t aRangeBackColor, int32_t aRangeLineColor);
 
     // Update styling for the active composition using previous-added ranges.
     void OnImeUpdateComposition(int32_t aStart, int32_t aEnd);
+
+    // Set cursor mode whether IME requests
+    void OnImeRequestCursorUpdates(int aRequestMode);
 };
 
 /**
  * NativePanZoomController handles its native calls on the UI thread, so make
  * it separate from GeckoViewSupport.
  */
 class nsWindow::NPZCSupport final
     : public NativePanZoomController::Natives<NPZCSupport>
@@ -2893,16 +2904,56 @@ nsWindow::GeckoViewSupport::FlushIMEChan
     }
 
     if (mIMESelectionChanged) {
         mIMESelectionChanged = false;
         mEditable->OnSelectionChange(selStart, selEnd);
     }
 }
 
+static jni::ObjectArray::LocalRef
+ConvertRectArrayToJavaRectFArray(JNIEnv* aJNIEnv, const nsTArray<LayoutDeviceIntRect>& aRects, const LayoutDeviceIntPoint& aOffset, const CSSToLayoutDeviceScale aScale)
+{
+    size_t length = aRects.Length();
+    jobjectArray rects = aJNIEnv->NewObjectArray(length, sdk::RectF::Context().ClassRef(), nullptr);
+    auto rectsRef = jni::ObjectArray::LocalRef::Adopt(aJNIEnv, rects);
+    for (size_t i = 0; i < length; i++) {
+        sdk::RectF::LocalRef rect(aJNIEnv);
+        LayoutDeviceIntRect tmp = aRects[i] + aOffset;
+        sdk::RectF::New(tmp.x / aScale.scale, tmp.y / aScale.scale,
+                        (tmp.x + tmp.width) / aScale.scale,
+                        (tmp.y + tmp.height) / aScale.scale,
+                        &rect);
+        rectsRef->SetElement(i, rect);
+    }
+    return rectsRef;
+}
+
+void
+nsWindow::GeckoViewSupport::UpdateCompositionRects()
+{
+    const auto composition(window.GetIMEComposition());
+    if (NS_WARN_IF(!composition)) {
+        return;
+    }
+
+    uint32_t offset = composition->NativeOffsetOfStartComposition();
+    WidgetQueryContentEvent textRects(true, eQueryTextRectArray, &window);
+    textRects.InitForQueryTextRectArray(offset, composition->String().Length());
+    window.DispatchEvent(&textRects);
+
+    auto rects =
+        ConvertRectArrayToJavaRectFArray(jni::GetGeckoThreadEnv(),
+                                         textRects.mReply.mRectArray,
+                                         window.WidgetToScreenOffset(),
+                                         window.GetDefaultScale());
+
+    mEditable->UpdateCompositionRects(rects);
+}
+
 void
 nsWindow::GeckoViewSupport::AsyncNotifyIME(int32_t aNotification)
 {
     // Keep a strong reference to the window to keep 'this' alive.
     RefPtr<nsWindow> window(&this->window);
 
     nsAppShell::PostEvent([this, window, aNotification] {
         if (mIMEMaskEventsCount) {
@@ -2944,16 +2995,19 @@ nsWindow::GeckoViewSupport::NotifyIME(co
 
             AsyncNotifyIME(GeckoEditableListener::
                            NOTIFY_IME_TO_CANCEL_COMPOSITION);
             return true;
         }
 
         case NOTIFY_IME_OF_FOCUS: {
             ALOGIME("IME: NOTIFY_IME_OF_FOCUS");
+            // IME will call requestCursorUpdates after getting context.
+            // So reset cursor update mode before getting context.
+            mIMEMonitorCursor = false;
             mEditable->NotifyIME(GeckoEditableListener::NOTIFY_IME_OF_FOCUS);
             return true;
         }
 
         case NOTIFY_IME_OF_BLUR: {
             ALOGIME("IME: NOTIFY_IME_OF_BLUR");
 
             // Mask events because we lost focus. On the next focus event,
@@ -2981,16 +3035,26 @@ nsWindow::GeckoViewSupport::NotifyIME(co
 
             /* Make sure Java's selection is up-to-date */
             PostFlushIMEChanges();
             mIMESelectionChanged = true;
             AddIMETextChange(IMETextChange(aIMENotification));
             return true;
         }
 
+        case NOTIFY_IME_OF_COMPOSITION_EVENT_HANDLED: {
+            ALOGIME("IME: NOTIFY_IME_OF_COMPOSITION_EVENT_HANDLED");
+
+            // Hardware keyboard support requires each string rect.
+            if (AndroidBridge::Bridge() && AndroidBridge::Bridge()->GetAPIVersion() >= 21 && mIMEMonitorCursor) {
+                UpdateCompositionRects();
+            }
+            return true;
+        }
+
         default:
             return false;
     }
 }
 
 void
 nsWindow::GeckoViewSupport::SetInputContext(const InputContext& aContext,
                                             const InputContextAction& aAction)
@@ -3335,16 +3399,27 @@ nsWindow::GeckoViewSupport::OnImeUpdateC
 
     // Previous events may have destroyed our composition; bail in that case.
     if (window.GetIMEComposition()) {
         window.DispatchEvent(&event);
     }
 }
 
 void
+nsWindow::GeckoViewSupport::OnImeRequestCursorUpdates(int aRequestMode)
+{
+    if (aRequestMode == IME_MONITOR_CURSOR_ONE_SHOT) {
+        UpdateCompositionRects();
+        return;
+    }
+
+    mIMEMonitorCursor = (aRequestMode == IME_MONITOR_CURSOR_START_MONITOR);
+}
+
+void
 nsWindow::UserActivity()
 {
   if (!mIdleService) {
     mIdleService = do_GetService("@mozilla.org/widget/idleservice;1");
   }
 
   if (mIdleService) {
     mIdleService->ResetIdleTimeOut(0);