Backed out changeset 13bd2c36c194 (bug 1137567) for android test_assign_event_data.html timeouts a=backout
authorWes Kocher <wkocher@mozilla.com>
Fri, 24 Feb 2017 16:21:32 -0800
changeset 373938 0ba0e2afff976c9c3538faa54f8b0b8a1187466b
parent 373937 6fd458fdbd07be39745f0194fa54cc7d2e6f1f4b
child 373939 d309d805ebc966fa402d20ce0a47eb12fec5b704
push id10863
push userjlorenzo@mozilla.com
push dateMon, 06 Mar 2017 23:02:23 +0000
treeherdermozilla-aurora@0931190cd725 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbackout
bugs1137567
milestone54.0a1
backs out13bd2c36c19425fb40e73db798db68c3a806771f
Backed out changeset 13bd2c36c194 (bug 1137567) for android test_assign_event_data.html timeouts a=backout MozReview-Commit-ID: Cx1OH8CBy7k
mobile/android/app/mobile.js
mobile/android/geckoview/src/main/java/org/mozilla/gecko/GeckoEditable.java
widget/android/GeckoEditableSupport.cpp
widget/android/GeckoEditableSupport.h
widget/android/GeneratedJNIWrappers.h
widget/android/moz.build
widget/android/nsWindow.cpp
widget/android/nsWindow.h
widget/nsBaseWidget.cpp
--- a/mobile/android/app/mobile.js
+++ b/mobile/android/app/mobile.js
@@ -915,10 +915,8 @@ pref("dom.presentation.receiver.enabled"
 pref("dom.audiochannel.audioCompeting", true);
 pref("dom.audiochannel.mediaControl", true);
 
 // Space separated list of URLS that are allowed to send objects (instead of
 // only strings) through webchannels. This list is duplicated in browser/app/profile/firefox.js
 pref("webchannel.allowObject.urlWhitelist", "https://accounts.firefox.com https://content.cdn.mozilla.net https://input.mozilla.org https://support.mozilla.org https://install.mozilla.org");
 
 pref("media.openUnsupportedTypeWithExternalApp", true);
-
-pref("dom.keyboardevent.dispatch_during_composition", true);
--- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/GeckoEditable.java
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/GeckoEditable.java
@@ -88,69 +88,64 @@ final class GeckoEditable extends JNIObj
     private static final int IME_RANGE_LINE_DOUBLE = 4;
     private static final int IME_RANGE_LINE_WAVY = 5;
 
     private static final int IME_RANGE_UNDERLINE = 1;
     private static final int IME_RANGE_FORECOLOR = 2;
     private static final int IME_RANGE_BACKCOLOR = 4;
     private static final int IME_RANGE_LINECOLOR = 8;
 
-    @WrapForJNI(dispatchTo = "proxy") // Dispatch a UI-activity event through proxy.
+    @WrapForJNI(dispatchTo = "proxy")
     private native void onKeyEvent(int action, int keyCode, int scanCode, int metaState,
-                                   int keyPressMetaState, long time, int domPrintableKeyValue,
-                                   int repeatCount, int flags, boolean isSynthesizedImeKey,
-                                   KeyEvent event);
+                                   long time, int unicodeChar, int baseUnicodeChar,
+                                   int domPrintableKeyValue, int repeatCount, int flags,
+                                   boolean isSynthesizedImeKey, KeyEvent event);
 
     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 unmodifiedUnicodeChar = event.getUnicodeChar(unmodifiedMetaState);
         final int domPrintableKeyValue =
                 unicodeChar >= ' '               ? unicodeChar :
-                unmodifiedMetaState != metaState ? unmodifiedUnicodeChar : 0;
-
-        // If a modifier (e.g. meta key) caused a different character to be entered, we
-        // drop that modifier from the metastate for the generated keypress event.
-        final int keyPressMetaState = (unicodeChar >= ' ' &&
-                unicodeChar != unmodifiedUnicodeChar) ? unmodifiedMetaState : metaState;
-
+                unmodifiedMetaState != metaState ? event.getUnicodeChar(unmodifiedMetaState) :
+                                                   0;
         onKeyEvent(action, event.getKeyCode(), event.getScanCode(),
-                   metaState, keyPressMetaState, event.getEventTime(),
-                   domPrintableKeyValue, event.getRepeatCount(), event.getFlags(),
-                   isSynthesizedImeKey, event);
+                   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, event);
     }
 
-    @WrapForJNI(dispatchTo = "gecko")
+    @WrapForJNI(dispatchTo = "proxy")
     private native void onImeSynchronize();
 
-    @WrapForJNI(dispatchTo = "proxy") // Dispatch a UI-activity event through proxy.
+    @WrapForJNI(dispatchTo = "proxy")
     private native void onImeReplaceText(int start, int end, String text);
 
-    @WrapForJNI(dispatchTo = "gecko")
+    @WrapForJNI(dispatchTo = "proxy")
     private native void onImeAddCompositionRange(int start, int end, int rangeType,
                                                  int rangeStyles, int rangeLineStyle,
                                                  boolean rangeBoldLine, int rangeForeColor,
                                                  int rangeBackColor, int rangeLineColor);
 
-    @WrapForJNI(dispatchTo = "proxy") // Dispatch a UI-activity event through proxy.
+    @WrapForJNI(dispatchTo = "proxy")
     private native void onImeUpdateComposition(int start, int end);
 
-    @WrapForJNI(dispatchTo = "gecko")
+    @WrapForJNI(dispatchTo = "proxy")
     private native void onImeRequestCursorUpdates(int requestMode);
 
     /**
      * Class that encapsulates asynchronous text editing. There are two copies of the
      * text, a current copy and a shadow copy. Both can be modified independently through
      * the current*** and shadow*** methods, respectively. The current copy can only be
      * modified on the Gecko side and reflects the authoritative version of the text. The
      * shadow copy can only be modified on the IC side and reflects what we think the
@@ -582,17 +577,17 @@ final class GeckoEditable extends JNIObj
                 Editable.class.getClassLoader(),
                 PROXY_INTERFACES, this);
 
         mIcRunHandler = mIcPostHandler = ThreadUtils.getUiHandler();
 
         onViewChange(v);
     }
 
-    @WrapForJNI(dispatchTo = "gecko") @Override
+    @WrapForJNI(dispatchTo = "proxy") @Override
     protected native void disposeNative();
 
     @WrapForJNI(calledFrom = "gecko")
     private void onViewChange(final GeckoView v) {
         if (DEBUG) {
             // Called by nsWindow.
             ThreadUtils.assertOnGeckoThread();
             Log.d(LOGTAG, "onViewChange(" + v + ")");
deleted file mode 100644
--- a/widget/android/GeckoEditableSupport.cpp
+++ /dev/null
@@ -1,1183 +0,0 @@
-/* -*- Mode: c++; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*-
- * vim: set sw=4 ts=4 expandtab:
- * 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/. */
-
-#include "GeckoEditableSupport.h"
-
-#include "AndroidRect.h"
-#include "KeyEvent.h"
-#include "android_npapi.h"
-#include "nsIContent.h"
-#include "nsISelection.h"
-
-#include "mozilla/IMEStateManager.h"
-#include "mozilla/TextComposition.h"
-#include "mozilla/TextEventDispatcherListener.h"
-#include "mozilla/TextEvents.h"
-
-#include <android/log.h>
-
-#ifdef DEBUG_ANDROID_IME
-#define ALOGIME(args...) __android_log_print(ANDROID_LOG_INFO, \
-                                             "GeckoEditableSupport" , ## args)
-#else
-#define ALOGIME(args...) do { } while (0)
-#endif
-
-// Sync with GeckoEditableClient 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;
-
-template<> const char
-nsWindow::NativePtr<mozilla::widget::GeckoEditableSupport>::sName[] =
-        "GeckoEditableSupport";
-
-static uint32_t
-ConvertAndroidKeyCodeToDOMKeyCode(int32_t androidKeyCode)
-{
-    // Special-case alphanumeric keycodes because they are most common.
-    if (androidKeyCode >= AKEYCODE_A &&
-        androidKeyCode <= AKEYCODE_Z) {
-        return androidKeyCode - AKEYCODE_A + NS_VK_A;
-    }
-
-    if (androidKeyCode >= AKEYCODE_0 &&
-        androidKeyCode <= AKEYCODE_9) {
-        return androidKeyCode - AKEYCODE_0 + NS_VK_0;
-    }
-
-    switch (androidKeyCode) {
-        // KEYCODE_UNKNOWN (0) ... KEYCODE_HOME (3)
-        case AKEYCODE_BACK:               return NS_VK_ESCAPE;
-        // KEYCODE_CALL (5) ... KEYCODE_POUND (18)
-        case AKEYCODE_DPAD_UP:            return NS_VK_UP;
-        case AKEYCODE_DPAD_DOWN:          return NS_VK_DOWN;
-        case AKEYCODE_DPAD_LEFT:          return NS_VK_LEFT;
-        case AKEYCODE_DPAD_RIGHT:         return NS_VK_RIGHT;
-        case AKEYCODE_DPAD_CENTER:        return NS_VK_RETURN;
-        case AKEYCODE_VOLUME_UP:          return NS_VK_VOLUME_UP;
-        case AKEYCODE_VOLUME_DOWN:        return NS_VK_VOLUME_DOWN;
-        // KEYCODE_VOLUME_POWER (26) ... KEYCODE_Z (54)
-        case AKEYCODE_COMMA:              return NS_VK_COMMA;
-        case AKEYCODE_PERIOD:             return NS_VK_PERIOD;
-        case AKEYCODE_ALT_LEFT:           return NS_VK_ALT;
-        case AKEYCODE_ALT_RIGHT:          return NS_VK_ALT;
-        case AKEYCODE_SHIFT_LEFT:         return NS_VK_SHIFT;
-        case AKEYCODE_SHIFT_RIGHT:        return NS_VK_SHIFT;
-        case AKEYCODE_TAB:                return NS_VK_TAB;
-        case AKEYCODE_SPACE:              return NS_VK_SPACE;
-        // KEYCODE_SYM (63) ... KEYCODE_ENVELOPE (65)
-        case AKEYCODE_ENTER:              return NS_VK_RETURN;
-        case AKEYCODE_DEL:                return NS_VK_BACK; // Backspace
-        case AKEYCODE_GRAVE:              return NS_VK_BACK_QUOTE;
-        // KEYCODE_MINUS (69)
-        case AKEYCODE_EQUALS:             return NS_VK_EQUALS;
-        case AKEYCODE_LEFT_BRACKET:       return NS_VK_OPEN_BRACKET;
-        case AKEYCODE_RIGHT_BRACKET:      return NS_VK_CLOSE_BRACKET;
-        case AKEYCODE_BACKSLASH:          return NS_VK_BACK_SLASH;
-        case AKEYCODE_SEMICOLON:          return NS_VK_SEMICOLON;
-        // KEYCODE_APOSTROPHE (75)
-        case AKEYCODE_SLASH:              return NS_VK_SLASH;
-        // KEYCODE_AT (77) ... KEYCODE_MEDIA_FAST_FORWARD (90)
-        case AKEYCODE_MUTE:               return NS_VK_VOLUME_MUTE;
-        case AKEYCODE_PAGE_UP:            return NS_VK_PAGE_UP;
-        case AKEYCODE_PAGE_DOWN:          return NS_VK_PAGE_DOWN;
-        // KEYCODE_PICTSYMBOLS (94) ... KEYCODE_BUTTON_MODE (110)
-        case AKEYCODE_ESCAPE:             return NS_VK_ESCAPE;
-        case AKEYCODE_FORWARD_DEL:        return NS_VK_DELETE;
-        case AKEYCODE_CTRL_LEFT:          return NS_VK_CONTROL;
-        case AKEYCODE_CTRL_RIGHT:         return NS_VK_CONTROL;
-        case AKEYCODE_CAPS_LOCK:          return NS_VK_CAPS_LOCK;
-        case AKEYCODE_SCROLL_LOCK:        return NS_VK_SCROLL_LOCK;
-        // KEYCODE_META_LEFT (117) ... KEYCODE_FUNCTION (119)
-        case AKEYCODE_SYSRQ:              return NS_VK_PRINTSCREEN;
-        case AKEYCODE_BREAK:              return NS_VK_PAUSE;
-        case AKEYCODE_MOVE_HOME:          return NS_VK_HOME;
-        case AKEYCODE_MOVE_END:           return NS_VK_END;
-        case AKEYCODE_INSERT:             return NS_VK_INSERT;
-        // KEYCODE_FORWARD (125) ... KEYCODE_MEDIA_RECORD (130)
-        case AKEYCODE_F1:                 return NS_VK_F1;
-        case AKEYCODE_F2:                 return NS_VK_F2;
-        case AKEYCODE_F3:                 return NS_VK_F3;
-        case AKEYCODE_F4:                 return NS_VK_F4;
-        case AKEYCODE_F5:                 return NS_VK_F5;
-        case AKEYCODE_F6:                 return NS_VK_F6;
-        case AKEYCODE_F7:                 return NS_VK_F7;
-        case AKEYCODE_F8:                 return NS_VK_F8;
-        case AKEYCODE_F9:                 return NS_VK_F9;
-        case AKEYCODE_F10:                return NS_VK_F10;
-        case AKEYCODE_F11:                return NS_VK_F11;
-        case AKEYCODE_F12:                return NS_VK_F12;
-        case AKEYCODE_NUM_LOCK:           return NS_VK_NUM_LOCK;
-        case AKEYCODE_NUMPAD_0:           return NS_VK_NUMPAD0;
-        case AKEYCODE_NUMPAD_1:           return NS_VK_NUMPAD1;
-        case AKEYCODE_NUMPAD_2:           return NS_VK_NUMPAD2;
-        case AKEYCODE_NUMPAD_3:           return NS_VK_NUMPAD3;
-        case AKEYCODE_NUMPAD_4:           return NS_VK_NUMPAD4;
-        case AKEYCODE_NUMPAD_5:           return NS_VK_NUMPAD5;
-        case AKEYCODE_NUMPAD_6:           return NS_VK_NUMPAD6;
-        case AKEYCODE_NUMPAD_7:           return NS_VK_NUMPAD7;
-        case AKEYCODE_NUMPAD_8:           return NS_VK_NUMPAD8;
-        case AKEYCODE_NUMPAD_9:           return NS_VK_NUMPAD9;
-        case AKEYCODE_NUMPAD_DIVIDE:      return NS_VK_DIVIDE;
-        case AKEYCODE_NUMPAD_MULTIPLY:    return NS_VK_MULTIPLY;
-        case AKEYCODE_NUMPAD_SUBTRACT:    return NS_VK_SUBTRACT;
-        case AKEYCODE_NUMPAD_ADD:         return NS_VK_ADD;
-        case AKEYCODE_NUMPAD_DOT:         return NS_VK_DECIMAL;
-        case AKEYCODE_NUMPAD_COMMA:       return NS_VK_SEPARATOR;
-        case AKEYCODE_NUMPAD_ENTER:       return NS_VK_RETURN;
-        case AKEYCODE_NUMPAD_EQUALS:      return NS_VK_EQUALS;
-        // KEYCODE_NUMPAD_LEFT_PAREN (162) ... KEYCODE_CALCULATOR (210)
-
-        // Needs to confirm the behavior.  If the key switches the open state
-        // of Japanese IME (or switches input character between Hiragana and
-        // Roman numeric characters), then, it might be better to use
-        // NS_VK_KANJI which is used for Alt+Zenkaku/Hankaku key on Windows.
-        case AKEYCODE_ZENKAKU_HANKAKU:    return 0;
-        case AKEYCODE_EISU:               return NS_VK_EISU;
-        case AKEYCODE_MUHENKAN:           return NS_VK_NONCONVERT;
-        case AKEYCODE_HENKAN:             return NS_VK_CONVERT;
-        case AKEYCODE_KATAKANA_HIRAGANA:  return 0;
-        case AKEYCODE_YEN:                return NS_VK_BACK_SLASH; // Same as other platforms.
-        case AKEYCODE_RO:                 return NS_VK_BACK_SLASH; // Same as other platforms.
-        case AKEYCODE_KANA:               return NS_VK_KANA;
-        case AKEYCODE_ASSIST:             return NS_VK_HELP;
-
-        // the A key is the action key for gamepad devices.
-        case AKEYCODE_BUTTON_A:          return NS_VK_RETURN;
-
-        default:
-            ALOG("ConvertAndroidKeyCodeToDOMKeyCode: "
-                 "No DOM keycode for Android keycode %d", int(androidKeyCode));
-        return 0;
-    }
-}
-
-static KeyNameIndex
-ConvertAndroidKeyCodeToKeyNameIndex(int32_t keyCode, int32_t action,
-                                    int32_t domPrintableKeyValue)
-{
-    // 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;
-    }
-
-    switch (keyCode) {
-
-#define NS_NATIVE_KEY_TO_DOM_KEY_NAME_INDEX(aNativeKey, aKeyNameIndex) \
-        case aNativeKey: return aKeyNameIndex;
-
-#include "NativeKeyToDOMKeyName.h"
-
-#undef NS_NATIVE_KEY_TO_DOM_KEY_NAME_INDEX
-
-        // KEYCODE_0 (7) ... KEYCODE_9 (16)
-        case AKEYCODE_STAR:               // '*' key
-        case AKEYCODE_POUND:              // '#' key
-
-        // KEYCODE_A (29) ... KEYCODE_Z (54)
-
-        case AKEYCODE_COMMA:              // ',' key
-        case AKEYCODE_PERIOD:             // '.' key
-        case AKEYCODE_SPACE:
-        case AKEYCODE_GRAVE:              // '`' key
-        case AKEYCODE_MINUS:              // '-' key
-        case AKEYCODE_EQUALS:             // '=' key
-        case AKEYCODE_LEFT_BRACKET:       // '[' key
-        case AKEYCODE_RIGHT_BRACKET:      // ']' key
-        case AKEYCODE_BACKSLASH:          // '\' key
-        case AKEYCODE_SEMICOLON:          // ';' key
-        case AKEYCODE_APOSTROPHE:         // ''' key
-        case AKEYCODE_SLASH:              // '/' key
-        case AKEYCODE_AT:                 // '@' key
-        case AKEYCODE_PLUS:               // '+' key
-
-        case AKEYCODE_NUMPAD_0:
-        case AKEYCODE_NUMPAD_1:
-        case AKEYCODE_NUMPAD_2:
-        case AKEYCODE_NUMPAD_3:
-        case AKEYCODE_NUMPAD_4:
-        case AKEYCODE_NUMPAD_5:
-        case AKEYCODE_NUMPAD_6:
-        case AKEYCODE_NUMPAD_7:
-        case AKEYCODE_NUMPAD_8:
-        case AKEYCODE_NUMPAD_9:
-        case AKEYCODE_NUMPAD_DIVIDE:
-        case AKEYCODE_NUMPAD_MULTIPLY:
-        case AKEYCODE_NUMPAD_SUBTRACT:
-        case AKEYCODE_NUMPAD_ADD:
-        case AKEYCODE_NUMPAD_DOT:
-        case AKEYCODE_NUMPAD_COMMA:
-        case AKEYCODE_NUMPAD_EQUALS:
-        case AKEYCODE_NUMPAD_LEFT_PAREN:
-        case AKEYCODE_NUMPAD_RIGHT_PAREN:
-
-        case AKEYCODE_YEN:                // yen sign key
-        case AKEYCODE_RO:                 // Japanese Ro key
-            return KEY_NAME_INDEX_USE_STRING;
-
-        case AKEYCODE_NUM:                // XXX Not sure
-        case AKEYCODE_PICTSYMBOLS:
-
-        case AKEYCODE_BUTTON_A:
-        case AKEYCODE_BUTTON_B:
-        case AKEYCODE_BUTTON_C:
-        case AKEYCODE_BUTTON_X:
-        case AKEYCODE_BUTTON_Y:
-        case AKEYCODE_BUTTON_Z:
-        case AKEYCODE_BUTTON_L1:
-        case AKEYCODE_BUTTON_R1:
-        case AKEYCODE_BUTTON_L2:
-        case AKEYCODE_BUTTON_R2:
-        case AKEYCODE_BUTTON_THUMBL:
-        case AKEYCODE_BUTTON_THUMBR:
-        case AKEYCODE_BUTTON_START:
-        case AKEYCODE_BUTTON_SELECT:
-        case AKEYCODE_BUTTON_MODE:
-
-        case AKEYCODE_MEDIA_CLOSE:
-
-        case AKEYCODE_BUTTON_1:
-        case AKEYCODE_BUTTON_2:
-        case AKEYCODE_BUTTON_3:
-        case AKEYCODE_BUTTON_4:
-        case AKEYCODE_BUTTON_5:
-        case AKEYCODE_BUTTON_6:
-        case AKEYCODE_BUTTON_7:
-        case AKEYCODE_BUTTON_8:
-        case AKEYCODE_BUTTON_9:
-        case AKEYCODE_BUTTON_10:
-        case AKEYCODE_BUTTON_11:
-        case AKEYCODE_BUTTON_12:
-        case AKEYCODE_BUTTON_13:
-        case AKEYCODE_BUTTON_14:
-        case AKEYCODE_BUTTON_15:
-        case AKEYCODE_BUTTON_16:
-            return KEY_NAME_INDEX_Unidentified;
-
-        case AKEYCODE_UNKNOWN:
-            MOZ_ASSERT(
-                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 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(int32_t 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
-InitKeyEvent(WidgetKeyboardEvent& aEvent, int32_t aAction, int32_t aKeyCode,
-             int32_t aScanCode, int32_t aMetaState, int64_t aTime,
-             int32_t aDomPrintableKeyValue, int32_t aRepeatCount,
-             int32_t aFlags)
-{
-    const uint32_t domKeyCode = ConvertAndroidKeyCodeToDOMKeyCode(aKeyCode);
-
-    aEvent.mModifiers = nsWindow::GetModifiers(aMetaState);
-    aEvent.mKeyCode = domKeyCode;
-
-    if (aEvent.mMessage != eKeyPress) {
-        ANPEvent pluginEvent;
-        pluginEvent.inSize = sizeof(pluginEvent);
-        pluginEvent.eventType = kKey_ANPEventType;
-        pluginEvent.data.key.action = (aEvent.mMessage == eKeyDown) ?
-                kDown_ANPKeyAction : kUp_ANPKeyAction;
-        pluginEvent.data.key.nativeCode = aKeyCode;
-        pluginEvent.data.key.virtualCode = domKeyCode;
-        pluginEvent.data.key.unichar = aDomPrintableKeyValue;
-        pluginEvent.data.key.modifiers =
-                (aMetaState & sdk::KeyEvent::META_SHIFT_MASK
-                        ? kShift_ANPKeyModifier : 0) |
-                (aMetaState & sdk::KeyEvent::META_ALT_MASK
-                        ? kAlt_ANPKeyModifier : 0);
-        pluginEvent.data.key.repeatCount = aRepeatCount;
-        aEvent.mPluginEvent.Copy(pluginEvent);
-    }
-
-    aEvent.mIsRepeat =
-        (aEvent.mMessage == eKeyDown || aEvent.mMessage == eKeyPress) &&
-        ((aFlags & sdk::KeyEvent::FLAG_LONG_PRESS) || aRepeatCount);
-
-    aEvent.mKeyNameIndex = ConvertAndroidKeyCodeToKeyNameIndex(
-            aKeyCode, aAction, aDomPrintableKeyValue);
-    aEvent.mCodeNameIndex = ConvertAndroidScanCodeToCodeNameIndex(aScanCode);
-
-    if (aEvent.mKeyNameIndex == KEY_NAME_INDEX_USE_STRING &&
-            aDomPrintableKeyValue) {
-        aEvent.mKeyValue = char16_t(aDomPrintableKeyValue);
-    }
-
-    aEvent.mLocation =
-        WidgetKeyboardEvent::ComputeLocationFromCodeValue(aEvent.mCodeNameIndex);
-    aEvent.mTime = aTime;
-    aEvent.mTimeStamp = nsWindow::GetEventTimeStamp(aTime);
-}
-
-static nscolor
-ConvertAndroidColor(uint32_t aArgb)
-{
-    return NS_RGBA((aArgb & 0x00ff0000) >> 16,
-                   (aArgb & 0x0000ff00) >> 8,
-                   (aArgb & 0x000000ff),
-                   (aArgb & 0xff000000) >> 24);
-}
-
-static jni::ObjectArray::LocalRef
-ConvertRectArrayToJavaRectFArray(const nsTArray<LayoutDeviceIntRect>& aRects,
-                                 const LayoutDeviceIntPoint& aOffset,
-                                 const CSSToLayoutDeviceScale aScale)
-{
-    const size_t length = aRects.Length();
-    auto rects = jni::ObjectArray::New<sdk::RectF>(length);
-
-    for (size_t i = 0; i < length; i++) {
-        LayoutDeviceIntRect tmp = aRects[i] + aOffset;
-
-        sdk::RectF::LocalRef rect(rects.Env());
-        sdk::RectF::New(tmp.x / aScale.scale, tmp.y / aScale.scale,
-                        (tmp.x + tmp.width) / aScale.scale,
-                        (tmp.y + tmp.height) / aScale.scale,
-                        &rect);
-        rects->SetElement(i, rect);
-    }
-    return rects;
-}
-
-namespace mozilla {
-namespace widget {
-
-NS_IMPL_ISUPPORTS(GeckoEditableSupport,
-                  TextEventDispatcherListener,
-                  nsISupportsWeakReference)
-
-RefPtr<TextComposition>
-GeckoEditableSupport::GetComposition() const
-{
-    nsCOMPtr<nsIWidget> widget = GetWidget();
-    return widget ? IMEStateManager::GetTextCompositionFor(widget) : nullptr;
-}
-
-void
-GeckoEditableSupport::RemoveComposition(RemoveCompositionFlag aFlag)
-{
-    if (!mDispatcher || !mDispatcher->IsComposing()) {
-        return;
-    }
-
-    nsEventStatus status = nsEventStatus_eIgnore;
-
-    NS_ENSURE_SUCCESS_VOID(mDispatcher->BeginNativeInputTransaction());
-    mDispatcher->CommitComposition(
-            status, aFlag == CANCEL_IME_COMPOSITION ? &EmptyString() : nullptr);
-}
-
-void
-GeckoEditableSupport::OnKeyEvent(int32_t aAction, int32_t aKeyCode,
-        int32_t aScanCode, int32_t aMetaState, int32_t aKeyPressMetaState,
-        int64_t aTime, int32_t aDomPrintableKeyValue, int32_t aRepeatCount,
-        int32_t aFlags, bool aIsSynthesizedImeKey,
-        jni::Object::Param aOriginalEvent)
-{
-    nsCOMPtr<nsIWidget> widget = GetWidget();
-    RefPtr<TextEventDispatcher> dispatcher =
-            mDispatcher ? mDispatcher.get() :
-            widget      ? widget->GetTextEventDispatcher() : nullptr;
-    NS_ENSURE_TRUE_VOID(dispatcher && widget);
-
-    if (!aIsSynthesizedImeKey && mWindow) {
-        mWindow->UserActivity();
-    }
-
-    EventMessage msg;
-    if (aAction == sdk::KeyEvent::ACTION_DOWN) {
-        msg = eKeyDown;
-    } else if (aAction == sdk::KeyEvent::ACTION_UP) {
-        msg = eKeyUp;
-    } else if (aAction == 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");
-    } else {
-        NS_WARNING("Unknown key action event");
-        return;
-    }
-
-    nsEventStatus status = nsEventStatus_eIgnore;
-    WidgetKeyboardEvent event(true, msg, widget);
-    InitKeyEvent(event, aAction, aKeyCode, aScanCode, aMetaState, aTime,
-                 aDomPrintableKeyValue, aRepeatCount, aFlags);
-
-    if (aIsSynthesizedImeKey) {
-        // 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.
-        mIMEKeyEvents.AppendElement(UniquePtr<WidgetEvent>(event.Duplicate()));
-    } else {
-        RemoveComposition();
-        NS_ENSURE_SUCCESS_VOID(dispatcher->BeginNativeInputTransaction());
-        dispatcher->DispatchKeyboardEvent(msg, event, status);
-        if (widget->Destroyed() || status == nsEventStatus_eConsumeNoDefault) {
-            // Skip default processing.
-            return;
-        }
-        mEditable->OnDefaultKeyEvent(aOriginalEvent);
-    }
-
-    // Only send keypress after keydown.
-    if (msg != eKeyDown) {
-        return;
-    }
-
-    WidgetKeyboardEvent pressEvent(true, eKeyPress, widget);
-    InitKeyEvent(pressEvent, aAction, aKeyCode, aScanCode, aKeyPressMetaState,
-                 aTime, aDomPrintableKeyValue, aRepeatCount, aFlags);
-
-    if (aIsSynthesizedImeKey) {
-        mIMEKeyEvents.AppendElement(
-                UniquePtr<WidgetEvent>(pressEvent.Duplicate()));
-    } else {
-        dispatcher->MaybeDispatchKeypressEvents(pressEvent, status);
-    }
-}
-
-/*
- * 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
-GeckoEditableSupport::SendIMEDummyKeyEvent(nsIWidget* aWidget, EventMessage msg)
-{
-    nsEventStatus status = nsEventStatus_eIgnore;
-    MOZ_ASSERT(mDispatcher);
-
-    WidgetKeyboardEvent event(true, msg, aWidget);
-    event.mTime = PR_Now() / 1000;
-    MOZ_ASSERT(event.mKeyCode == 0);
-    NS_ENSURE_SUCCESS_VOID(mDispatcher->BeginNativeInputTransaction());
-    mDispatcher->DispatchKeyboardEvent(msg, event, status);
-}
-
-void
-GeckoEditableSupport::AddIMETextChange(const IMETextChange& aChange)
-{
-    mIMETextChanges.AppendElement(aChange);
-
-    // We may not be in the middle of flushing,
-    // in which case this flag is meaningless.
-    mIMETextChangedDuringFlush = true;
-
-    // Now that we added a new range we need to go back and
-    // update all the ranges before that.
-    // Ranges that have offsets which follow this new range
-    // need to be updated to reflect new offsets
-    const int32_t delta = aChange.mNewEnd - aChange.mOldEnd;
-    for (int32_t i = mIMETextChanges.Length() - 2; i >= 0; i--) {
-        IMETextChange& previousChange = mIMETextChanges[i];
-        if (previousChange.mStart > aChange.mOldEnd) {
-            previousChange.mStart += delta;
-            previousChange.mOldEnd += delta;
-            previousChange.mNewEnd += delta;
-        }
-    }
-
-    // Now go through all ranges to merge any ranges that are connected
-    // srcIndex is the index of the range to merge from
-    // dstIndex is the index of the range to potentially merge into
-    int32_t srcIndex = mIMETextChanges.Length() - 1;
-    int32_t dstIndex = srcIndex;
-
-    while (--dstIndex >= 0) {
-        IMETextChange& src = mIMETextChanges[srcIndex];
-        IMETextChange& dst = mIMETextChanges[dstIndex];
-        // When merging a more recent change into an older
-        // change, we need to compare recent change's (start, oldEnd)
-        // range to the older change's (start, newEnd)
-        if (src.mOldEnd < dst.mStart || dst.mNewEnd < src.mStart) {
-            // No overlap between ranges
-            continue;
-        }
-        // When merging two ranges, there are generally four posibilities:
-        // [----(----]----), (----[----]----),
-        // [----(----)----], (----[----)----]
-        // where [----] is the first range and (----) is the second range
-        // As seen above, the start of the merged range is always the lesser
-        // of the two start offsets. OldEnd and NewEnd then need to be
-        // adjusted separately depending on the case. In any case, the change
-        // in text length of the merged range should be the sum of text length
-        // changes of the two original ranges, i.e.,
-        // newNewEnd - newOldEnd == newEnd1 - oldEnd1 + newEnd2 - oldEnd2
-        dst.mStart = std::min(dst.mStart, src.mStart);
-        if (src.mOldEnd < dst.mNewEnd) {
-            // New range overlaps or is within previous range; merge
-            dst.mNewEnd += src.mNewEnd - src.mOldEnd;
-        } else { // src.mOldEnd >= dst.mNewEnd
-            // New range overlaps previous range; merge
-            dst.mOldEnd += src.mOldEnd - dst.mNewEnd;
-            dst.mNewEnd = src.mNewEnd;
-        }
-        // src merged to dst; delete src.
-        mIMETextChanges.RemoveElementAt(srcIndex);
-        // Any ranges that we skip over between src and dst are not mergeable
-        // so we can safely continue the merge starting at dst
-        srcIndex = dstIndex;
-    }
-}
-
-void
-GeckoEditableSupport::PostFlushIMEChanges()
-{
-    if (!mIMETextChanges.IsEmpty() || mIMESelectionChanged) {
-        // Already posted
-        return;
-    }
-
-    RefPtr<GeckoEditableSupport> self(this);
-
-    nsAppShell::PostEvent([this, self] {
-        nsCOMPtr<nsIWidget> widget = GetWidget();
-        if (widget && !widget->Destroyed()) {
-            FlushIMEChanges();
-        }
-    });
-}
-
-void
-GeckoEditableSupport::FlushIMEChanges(FlushChangesFlag aFlags)
-{
-    // Only send change notifications if we are *not* masking events,
-    // i.e. if we have a focused editor,
-    NS_ENSURE_TRUE_VOID(!mIMEMaskEventsCount);
-
-    nsCOMPtr<nsIWidget> widget = GetWidget();
-    NS_ENSURE_TRUE_VOID(widget);
-
-    struct TextRecord
-    {
-        nsString text;
-        int32_t start;
-        int32_t oldEnd;
-        int32_t newEnd;
-    };
-    AutoTArray<TextRecord, 4> textTransaction;
-    textTransaction.SetCapacity(mIMETextChanges.Length());
-
-    nsEventStatus status = nsEventStatus_eIgnore;
-    mIMETextChangedDuringFlush = false;
-
-    auto shouldAbort = [=] (bool aForce) -> bool {
-        if (!aForce && !mIMETextChangedDuringFlush) {
-            return false;
-        }
-        // A query event could have triggered more text changes to come in, as
-        // indicated by our flag. If that happens, try flushing IME changes
-        // again.
-        if (aFlags == FLUSH_FLAG_NONE) {
-            FlushIMEChanges(FLUSH_FLAG_RETRY);
-        } else {
-            // Don't retry if already retrying, to avoid infinite loops.
-            __android_log_print(ANDROID_LOG_WARN, "GeckoEditableSupport",
-                    "Already retrying IME flush");
-        }
-        return true;
-    };
-
-    for (const IMETextChange &change : mIMETextChanges) {
-        if (change.mStart == change.mOldEnd &&
-                change.mStart == change.mNewEnd) {
-            continue;
-        }
-
-        WidgetQueryContentEvent event(true, eQueryTextContent, widget);
-
-        if (change.mNewEnd != change.mStart) {
-            event.InitForQueryTextContent(change.mStart,
-                                          change.mNewEnd - change.mStart);
-            widget->DispatchEvent(&event, status);
-
-            if (shouldAbort(NS_WARN_IF(!event.mSucceeded))) {
-                return;
-            }
-        }
-
-        textTransaction.AppendElement(
-                TextRecord{event.mReply.mString, change.mStart,
-                           change.mOldEnd, change.mNewEnd});
-    }
-
-    int32_t selStart = -1;
-    int32_t selEnd = -1;
-
-    if (mIMESelectionChanged) {
-        WidgetQueryContentEvent event(true, eQuerySelectedText, widget);
-        widget->DispatchEvent(&event, status);
-
-        if (shouldAbort(NS_WARN_IF(!event.mSucceeded))) {
-            return;
-        }
-
-        selStart = int32_t(event.GetSelectionStart());
-        selEnd = int32_t(event.GetSelectionEnd());
-    }
-
-    JNIEnv* const env = jni::GetGeckoThreadEnv();
-    auto flushOnException = [=] () -> bool {
-        if (!env->ExceptionCheck()) {
-            return false;
-        }
-        if (aFlags != FLUSH_FLAG_RECOVER) {
-            // First time seeing an exception; try flushing text.
-            env->ExceptionClear();
-            __android_log_print(ANDROID_LOG_WARN, "GeckoEditableSupport",
-                    "Recovering from IME exception");
-            FlushIMEText(FLUSH_FLAG_RECOVER);
-        } else {
-            // Give up because we've already tried.
-            MOZ_CATCH_JNI_EXCEPTION(env);
-        }
-        return true;
-    };
-
-    // Commit the text change and selection change transaction.
-    mIMETextChanges.Clear();
-
-    for (const TextRecord& record : textTransaction) {
-        mEditable->OnTextChange(record.text, record.start,
-                                record.oldEnd, record.newEnd);
-        if (flushOnException()) {
-            return;
-        }
-    }
-
-    if (mIMESelectionChanged) {
-        mIMESelectionChanged = false;
-        mEditable->OnSelectionChange(selStart, selEnd);
-        flushOnException();
-    }
-}
-
-void
-GeckoEditableSupport::FlushIMEText(FlushChangesFlag aFlags)
-{
-    // Notify Java of the newly focused content
-    mIMETextChanges.Clear();
-    mIMESelectionChanged = true;
-
-    // Use 'INT32_MAX / 2' here because subsequent text changes might combine
-    // with this text change, and overflow might occur if we just use
-    // INT32_MAX.
-    IMENotification notification(NOTIFY_IME_OF_TEXT_CHANGE);
-    notification.mTextChangeData.mStartOffset = 0;
-    notification.mTextChangeData.mRemovedEndOffset = INT32_MAX / 2;
-    notification.mTextChangeData.mAddedEndOffset = INT32_MAX / 2;
-    NotifyIME(mDispatcher, notification);
-
-    FlushIMEChanges(aFlags);
-}
-
-void
-GeckoEditableSupport::UpdateCompositionRects()
-{
-    nsCOMPtr<nsIWidget> widget = GetWidget();
-    RefPtr<TextComposition> composition(GetComposition());
-    NS_ENSURE_TRUE_VOID(mDispatcher && widget);
-
-    if (!composition) {
-        return;
-    }
-
-    nsEventStatus status = nsEventStatus_eIgnore;
-    uint32_t offset = composition->NativeOffsetOfStartComposition();
-    WidgetQueryContentEvent textRects(true, eQueryTextRectArray, widget);
-    textRects.InitForQueryTextRectArray(offset, composition->String().Length());
-    widget->DispatchEvent(&textRects, status);
-
-    auto rects = ConvertRectArrayToJavaRectFArray(
-            textRects.mReply.mRectArray,
-            widget->WidgetToScreenOffset(),
-            widget->GetDefaultScale());
-
-    mEditable->UpdateCompositionRects(rects);
-}
-
-void
-GeckoEditableSupport::OnImeSynchronize()
-{
-    if (!mIMEMaskEventsCount) {
-        FlushIMEChanges();
-    }
-    mEditable->NotifyIME(GeckoEditableListener::NOTIFY_IME_REPLY_EVENT);
-}
-
-void
-GeckoEditableSupport::OnImeReplaceText(int32_t aStart, int32_t aEnd,
-                                       jni::String::Param aText)
-{
-    AutoIMESynchronize as(this);
-
-    if (mIMEMaskEventsCount > 0) {
-        // Not focused; still reply to events, but don't do anything else.
-        return;
-    }
-
-    if (mWindow) {
-        mWindow->UserActivity();
-    }
-
-    /*
-        Replace text in Gecko thread from aStart to aEnd with the string text.
-    */
-    nsCOMPtr<nsIWidget> widget = GetWidget();
-    NS_ENSURE_TRUE_VOID(mDispatcher && widget);
-    NS_ENSURE_SUCCESS_VOID(mDispatcher->BeginNativeInputTransaction());
-
-    RefPtr<TextComposition> composition(GetComposition());
-    MOZ_ASSERT(!composition || !composition->IsEditorHandlingEvent());
-
-    nsString string(aText->ToString());
-    const bool composing = !mIMERanges->IsEmpty();
-    nsEventStatus status = nsEventStatus_eIgnore;
-
-    if (!mIMEKeyEvents.IsEmpty() || !mDispatcher->IsComposing() ||
-        uint32_t(aStart) != composition->NativeOffsetOfStartComposition() ||
-        uint32_t(aEnd) != composition->NativeOffsetOfStartComposition() +
-                          composition->String().Length()) {
-        // Only start a new composition if we have key events,
-        // if we don't have an existing composition, or
-        // the replaced text does not match our composition.
-        RemoveComposition();
-
-        {
-            // Use text selection to set target position(s) for
-            // insert, or replace, of text.
-            WidgetSelectionEvent event(true, eSetSelection, widget);
-            event.mOffset = uint32_t(aStart);
-            event.mLength = uint32_t(aEnd - aStart);
-            event.mExpandToClusterBoundary = false;
-            event.mReason = nsISelectionListener::IME_REASON;
-            widget->DispatchEvent(&event, status);
-        }
-
-        if (!mIMEKeyEvents.IsEmpty()) {
-            for (uint32_t i = 0; i < mIMEKeyEvents.Length(); i++) {
-                const auto event = mIMEKeyEvents[i]->AsKeyboardEvent();
-                // widget for duplicated events is initially nullptr.
-                event->mWidget = widget;
-
-                if (event->mMessage == eKeyPress) {
-                    mDispatcher->MaybeDispatchKeypressEvents(*event, status);
-                } else {
-                    mDispatcher->DispatchKeyboardEvent(
-                            event->mMessage, *event, status);
-                }
-                if (widget->Destroyed()) {
-                    break;
-                }
-            }
-            mIMEKeyEvents.Clear();
-            return;
-        }
-
-        if (aStart != aEnd) {
-            // Perform a deletion first.
-            WidgetContentCommandEvent event(
-                    true, eContentCommandDelete, widget);
-            event.mTime = PR_Now() / 1000;
-            widget->DispatchEvent(&event, status);
-            if (widget->Destroyed()) {
-                return;
-            }
-        }
-    } else if (composition->String().Equals(string)) {
-        /* If the new text is the same as the existing composition text,
-         * the NS_COMPOSITION_CHANGE event does not generate a text
-         * change notification. However, the Java side still expects
-         * one, so we manually generate a notification. */
-        IMETextChange dummyChange;
-        dummyChange.mStart = aStart;
-        dummyChange.mOldEnd = dummyChange.mNewEnd = aEnd;
-        AddIMETextChange(dummyChange);
-    }
-
-    if (mInputContext.mMayBeIMEUnaware) {
-        SendIMEDummyKeyEvent(widget, eKeyDown);
-        if (widget->Destroyed()) {
-            return;
-        }
-    }
-
-    if (composing) {
-        mDispatcher->SetPendingComposition(string, mIMERanges);
-        mDispatcher->FlushPendingComposition(status);
-        // Ensure IME ranges are empty.
-        mIMERanges->Clear();
-    } else if (!string.IsEmpty() || mDispatcher->IsComposing()) {
-        mDispatcher->CommitComposition(status, &string);
-    }
-    if (widget->Destroyed()) {
-        return;
-    }
-
-    if (mInputContext.mMayBeIMEUnaware) {
-        SendIMEDummyKeyEvent(widget, eKeyUp);
-        // Widget may be destroyed after dispatching the above event.
-    }
-}
-
-void
-GeckoEditableSupport::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)
-{
-    if (mIMEMaskEventsCount > 0) {
-        // Not focused.
-        return;
-    }
-
-    TextRange range;
-    range.mStartOffset = aStart;
-    range.mEndOffset = aEnd;
-    range.mRangeType = ToTextRangeType(aRangeType);
-    range.mRangeStyle.mDefinedStyles = aRangeStyle;
-    range.mRangeStyle.mLineStyle = aRangeLineStyle;
-    range.mRangeStyle.mIsBoldLine = aRangeBoldLine;
-    range.mRangeStyle.mForegroundColor =
-            ConvertAndroidColor(uint32_t(aRangeForeColor));
-    range.mRangeStyle.mBackgroundColor =
-            ConvertAndroidColor(uint32_t(aRangeBackColor));
-    range.mRangeStyle.mUnderlineColor =
-            ConvertAndroidColor(uint32_t(aRangeLineColor));
-    mIMERanges->AppendElement(range);
-}
-
-void
-GeckoEditableSupport::OnImeUpdateComposition(int32_t aStart, int32_t aEnd)
-{
-    if (mIMEMaskEventsCount > 0) {
-        // Not focused.
-        return;
-    }
-
-    nsCOMPtr<nsIWidget> widget = GetWidget();
-    nsEventStatus status = nsEventStatus_eIgnore;
-    NS_ENSURE_TRUE_VOID(mDispatcher && widget);
-
-    // A composition with no ranges means we want to set the selection.
-    if (mIMERanges->IsEmpty()) {
-        MOZ_ASSERT(aStart >= 0 && aEnd >= 0);
-        RemoveComposition();
-
-        WidgetSelectionEvent selEvent(true, eSetSelection, widget);
-        selEvent.mOffset = std::min(aStart, aEnd);
-        selEvent.mLength = std::max(aStart, aEnd) - selEvent.mOffset;
-        selEvent.mReversed = aStart > aEnd;
-        selEvent.mExpandToClusterBoundary = false;
-        widget->DispatchEvent(&selEvent, status);
-        return;
-    }
-
-    /**
-     * Update the composition from aStart to aEnd using information from added
-     * ranges. This is only used for visual indication and does not affect the
-     * text content.  Only the offsets are specified and not the text content
-     * to eliminate the possibility of this event altering the text content
-     * unintentionally.
-     */
-    nsString string;
-    RefPtr<TextComposition> composition(GetComposition());
-    MOZ_ASSERT(!composition || !composition->IsEditorHandlingEvent());
-
-    if (!mDispatcher->IsComposing() ||
-        uint32_t(aStart) != composition->NativeOffsetOfStartComposition() ||
-        uint32_t(aEnd) != composition->NativeOffsetOfStartComposition() +
-                          composition->String().Length()) {
-        // Only start new composition if we don't have an existing one,
-        // or if the existing composition doesn't match the new one.
-        RemoveComposition();
-
-        {
-            WidgetSelectionEvent event(true, eSetSelection, widget);
-            event.mOffset = uint32_t(aStart);
-            event.mLength = uint32_t(aEnd - aStart);
-            event.mExpandToClusterBoundary = false;
-            event.mReason = nsISelectionListener::IME_REASON;
-            widget->DispatchEvent(&event, status);
-        }
-
-        {
-            WidgetQueryContentEvent event(true, eQuerySelectedText, widget);
-            widget->DispatchEvent(&event, status);
-            MOZ_ASSERT(event.mSucceeded);
-            string = event.mReply.mString;
-        }
-    } else {
-        // If the new composition matches the existing composition,
-        // reuse the old composition.
-        string = composition->String();
-    }
-
-#ifdef DEBUG_ANDROID_IME
-    const NS_ConvertUTF16toUTF8 data(event.mData);
-    const char* text = data.get();
-    ALOGIME("IME: IME_SET_TEXT: text=\"%s\", length=%u, range=%u",
-            text, event.mData.Length(), event.mRanges->Length());
-#endif // DEBUG_ANDROID_IME
-
-    NS_ENSURE_SUCCESS_VOID(mDispatcher->BeginNativeInputTransaction());
-    mDispatcher->SetPendingComposition(string, mIMERanges);
-    mDispatcher->FlushPendingComposition(status);
-    mIMERanges->Clear();
-}
-
-void
-GeckoEditableSupport::OnImeRequestCursorUpdates(int aRequestMode)
-{
-    if (aRequestMode == IME_MONITOR_CURSOR_ONE_SHOT) {
-        UpdateCompositionRects();
-        return;
-    }
-
-    mIMEMonitorCursor = (aRequestMode == IME_MONITOR_CURSOR_START_MONITOR);
-}
-
-void
-GeckoEditableSupport::AsyncNotifyIME(int32_t aNotification)
-{
-    RefPtr<GeckoEditableSupport> self(this);
-
-    nsAppShell::PostEvent([this, self, aNotification] {
-        if (!mIMEMaskEventsCount) {
-            mEditable->NotifyIME(aNotification);
-        }
-    });
-}
-
-nsresult
-GeckoEditableSupport::NotifyIME(TextEventDispatcher* aTextEventDispatcher,
-                                const IMENotification& aNotification)
-{
-    MOZ_ASSERT(mEditable);
-
-    switch (aNotification.mMessage) {
-        case REQUEST_TO_COMMIT_COMPOSITION: {
-            ALOGIME("IME: REQUEST_TO_COMMIT_COMPOSITION");
-
-            RemoveComposition(COMMIT_IME_COMPOSITION);
-            AsyncNotifyIME(GeckoEditableListener::
-                           NOTIFY_IME_TO_COMMIT_COMPOSITION);
-            break;
-        }
-
-        case REQUEST_TO_CANCEL_COMPOSITION: {
-            ALOGIME("IME: REQUEST_TO_CANCEL_COMPOSITION");
-
-            RemoveComposition(CANCEL_IME_COMPOSITION);
-            AsyncNotifyIME(GeckoEditableListener::
-                           NOTIFY_IME_TO_CANCEL_COMPOSITION);
-            break;
-        }
-
-        case NOTIFY_IME_OF_FOCUS: {
-            ALOGIME("IME: NOTIFY_IME_OF_FOCUS");
-
-            RefPtr<GeckoEditableSupport> self(this);
-            RefPtr<TextEventDispatcher> dispatcher = aTextEventDispatcher;
-
-            // Post an event because we have to flush the text before sending a
-            // focus event, and we may not be able to flush text during the
-            // NotifyIME call.
-            nsAppShell::PostEvent([this, self, dispatcher] {
-                nsCOMPtr<nsIWidget> widget = dispatcher->GetWidget();
-
-                --mIMEMaskEventsCount;
-                if (mIMEMaskEventsCount || !widget || widget->Destroyed()) {
-                    return;
-                }
-
-                mDispatcher = dispatcher;
-                FlushIMEText();
-
-                // IME will call requestCursorUpdates after getting context.
-                // So reset cursor update mode before getting context.
-                mIMEMonitorCursor = false;
-
-                MOZ_ASSERT(mEditable);
-                mEditable->NotifyIME(
-                        GeckoEditableListener::NOTIFY_IME_OF_FOCUS);
-            });
-            break;
-        }
-
-        case NOTIFY_IME_OF_BLUR: {
-            ALOGIME("IME: NOTIFY_IME_OF_BLUR");
-
-            if (!mIMEMaskEventsCount) {
-                mEditable->NotifyIME(GeckoEditableListener::NOTIFY_IME_OF_BLUR);
-                mDispatcher = nullptr;
-            }
-
-            // Mask events because we lost focus. Unmask on the next focus.
-            mIMEMaskEventsCount++;
-            break;
-        }
-
-        case NOTIFY_IME_OF_SELECTION_CHANGE: {
-            ALOGIME("IME: NOTIFY_IME_OF_SELECTION_CHANGE");
-
-            PostFlushIMEChanges();
-            mIMESelectionChanged = true;
-            break;
-        }
-
-        case NOTIFY_IME_OF_TEXT_CHANGE: {
-            ALOGIME("IME: NotifyIMEOfTextChange: s=%d, oe=%d, ne=%d",
-                    aNotification.mTextChangeData.mStartOffset,
-                    aNotification.mTextChangeData.mRemovedEndOffset,
-                    aNotification.mTextChangeData.mAddedEndOffset);
-
-            /* Make sure Java's selection is up-to-date */
-            PostFlushIMEChanges();
-            mIMESelectionChanged = true;
-            AddIMETextChange(IMETextChange(aNotification));
-            break;
-        }
-
-        case NOTIFY_IME_OF_COMPOSITION_EVENT_HANDLED: {
-            ALOGIME("IME: NOTIFY_IME_OF_COMPOSITION_EVENT_HANDLED");
-
-            // Hardware keyboard support requires each string rect.
-            if (mIMEMonitorCursor) {
-                UpdateCompositionRects();
-            }
-            break;
-        }
-
-        default:
-            break;
-    }
-    return NS_OK;
-}
-
-void
-GeckoEditableSupport::OnRemovedFrom(TextEventDispatcher* aTextEventDispatcher)
-{
-}
-
-void
-GeckoEditableSupport::WillDispatchKeyboardEvent(
-        TextEventDispatcher* aTextEventDispatcher,
-        WidgetKeyboardEvent& aKeyboardEvent, uint32_t aIndexOfKeypress,
-        void* aData)
-{
-}
-
-nsIMEUpdatePreference
-GeckoEditableSupport::GetIMEUpdatePreference()
-{
-    // While a plugin has focus, Listener doesn't need any notifications.
-    if (GetInputContext().mIMEState.mEnabled == IMEState::PLUGIN) {
-      return nsIMEUpdatePreference();
-    }
-    return nsIMEUpdatePreference(nsIMEUpdatePreference::NOTIFY_TEXT_CHANGE);
-}
-
-void
-GeckoEditableSupport::SetInputContext(const InputContext& aContext,
-                                      const InputContextAction& aAction)
-{
-    MOZ_ASSERT(mEditable);
-
-    ALOGIME("IME: SetInputContext: s=0x%X, 0x%X, action=0x%X, 0x%X",
-            aContext.mIMEState.mEnabled, aContext.mIMEState.mOpen,
-            aAction.mCause, aAction.mFocusChange);
-
-    // Ensure that opening the virtual keyboard is allowed for this specific
-    // InputContext depending on the content.ime.strict.policy pref
-    if (aContext.mIMEState.mEnabled != IMEState::DISABLED &&
-        aContext.mIMEState.mEnabled != IMEState::PLUGIN &&
-        Preferences::GetBool("content.ime.strict_policy", false) &&
-        !aAction.ContentGotFocusByTrustedCause() &&
-        !aAction.UserMightRequestOpenVKB()) {
-        return;
-    }
-
-    IMEState::Enabled enabled = aContext.mIMEState.mEnabled;
-
-    // Only show the virtual keyboard for plugins if mOpen is set appropriately.
-    // This avoids showing it whenever a plugin is focused. Bug 747492
-    if (aContext.mIMEState.mEnabled == IMEState::PLUGIN &&
-        aContext.mIMEState.mOpen != IMEState::OPEN) {
-        enabled = IMEState::DISABLED;
-    }
-
-    mInputContext = aContext;
-    mInputContext.mIMEState.mEnabled = enabled;
-
-    if (enabled == IMEState::ENABLED && aAction.UserMightRequestOpenVKB()) {
-        // Don't reset keyboard when we should simply open the vkb
-        mEditable->NotifyIME(GeckoEditableListener::NOTIFY_IME_OPEN_VKB);
-        return;
-    }
-
-    if (mIMEUpdatingContext) {
-        return;
-    }
-    mIMEUpdatingContext = true;
-
-    RefPtr<GeckoEditableSupport> self(this);
-
-    nsAppShell::PostEvent([this, self] {
-        nsCOMPtr<nsIWidget> widget = GetWidget();
-
-        mIMEUpdatingContext = false;
-        if (!widget || widget->Destroyed()) {
-            return;
-        }
-        mEditable->NotifyIMEContext(mInputContext.mIMEState.mEnabled,
-                                    mInputContext.mHTMLInputType,
-                                    mInputContext.mHTMLInputInputmode,
-                                    mInputContext.mActionHint);
-    });
-}
-
-InputContext
-GeckoEditableSupport::GetInputContext()
-{
-    InputContext context = mInputContext;
-    context.mIMEState.mOpen = IMEState::OPEN_STATE_NOT_SUPPORTED;
-    return context;
-}
-
-} // namespace widget
-} // namespace mozilla
deleted file mode 100644
--- a/widget/android/GeckoEditableSupport.h
+++ /dev/null
@@ -1,217 +0,0 @@
-/* -*- Mode: c++; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * 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/. */
-
-#ifndef mozilla_widget_GeckoEditableSupport_h
-#define mozilla_widget_GeckoEditableSupport_h
-
-#include "GeneratedJNIWrappers.h"
-#include "nsAppShell.h"
-#include "nsIWidget.h"
-#include "nsTArray.h"
-
-#include "mozilla/TextEventDispatcher.h"
-#include "mozilla/TextEventDispatcherListener.h"
-#include "mozilla/UniquePtr.h"
-
-class nsWindow;
-
-namespace mozilla {
-
-class TextComposition;
-
-namespace widget {
-
-class GeckoEditableSupport final
-    : public TextEventDispatcherListener
-    , public java::GeckoEditable::Natives<GeckoEditableSupport>
-{
-    /*
-        Rules for managing IME between Gecko and Java:
-
-        * Gecko controls the text content, and Java shadows the Gecko text
-           through text updates
-        * Gecko and Java maintain separate selections, and synchronize when
-           needed through selection updates and set-selection events
-        * Java controls the composition, and Gecko shadows the Java
-           composition through update composition events
-    */
-
-    using EditableBase = java::GeckoEditable::Natives<GeckoEditableSupport>;
-
-    // RAII helper class that automatically sends an event reply through
-    // OnImeSynchronize, as required by events like OnImeReplaceText.
-    class AutoIMESynchronize
-    {
-        GeckoEditableSupport* const mGES;
-    public:
-        AutoIMESynchronize(GeckoEditableSupport* ges) : mGES(ges) {}
-        ~AutoIMESynchronize() { mGES->OnImeSynchronize(); }
-    };
-
-    struct IMETextChange final {
-        int32_t mStart, mOldEnd, mNewEnd;
-
-        IMETextChange() :
-            mStart(-1), mOldEnd(-1), mNewEnd(-1) {}
-
-        IMETextChange(const IMENotification& aIMENotification)
-            : mStart(aIMENotification.mTextChangeData.mStartOffset)
-            , mOldEnd(aIMENotification.mTextChangeData.mRemovedEndOffset)
-            , mNewEnd(aIMENotification.mTextChangeData.mAddedEndOffset)
-        {
-            MOZ_ASSERT(aIMENotification.mMessage == NOTIFY_IME_OF_TEXT_CHANGE,
-                       "IMETextChange initialized with wrong notification");
-            MOZ_ASSERT(aIMENotification.mTextChangeData.IsValid(),
-                       "The text change notification isn't initialized");
-            MOZ_ASSERT(aIMENotification.mTextChangeData.IsInInt32Range(),
-                       "The text change notification is out of range");
-        }
-
-        bool IsEmpty() const { return mStart < 0; }
-    };
-
-    enum FlushChangesFlag
-    {
-        // Not retrying.
-        FLUSH_FLAG_NONE,
-        // Retrying due to IME text changes during flush.
-        FLUSH_FLAG_RETRY,
-        // Retrying due to IME sync exceptions during flush.
-        FLUSH_FLAG_RECOVER
-    };
-
-    enum RemoveCompositionFlag
-    {
-        CANCEL_IME_COMPOSITION,
-        COMMIT_IME_COMPOSITION
-    };
-
-    nsWindow::WindowPtr<GeckoEditableSupport> mWindow; // Parent only
-    RefPtr<TextEventDispatcher> mDispatcher;
-    java::GeckoEditable::GlobalRef mEditable;
-    InputContext mInputContext;
-    AutoTArray<UniquePtr<mozilla::WidgetEvent>, 4> mIMEKeyEvents;
-    AutoTArray<IMETextChange, 4> mIMETextChanges;
-    RefPtr<TextRangeArray> mIMERanges;
-    int32_t mIMEMaskEventsCount; // Mask events when > 0.
-    bool mIMEUpdatingContext;
-    bool mIMESelectionChanged;
-    bool mIMETextChangedDuringFlush;
-    bool mIMEMonitorCursor;
-
-    nsIWidget* GetWidget() const
-    {
-        return mDispatcher ? mDispatcher->GetWidget() : mWindow;
-    }
-
-    virtual ~GeckoEditableSupport() {}
-
-    RefPtr<TextComposition> GetComposition() const;
-    void RemoveComposition(
-            RemoveCompositionFlag aFlag = COMMIT_IME_COMPOSITION);
-    void SendIMEDummyKeyEvent(nsIWidget* aWidget, EventMessage msg);
-    void AddIMETextChange(const IMETextChange& aChange);
-    void PostFlushIMEChanges();
-    void FlushIMEChanges(FlushChangesFlag aFlags = FLUSH_FLAG_NONE);
-    void FlushIMEText(FlushChangesFlag aFlags = FLUSH_FLAG_NONE);
-    void AsyncNotifyIME(int32_t aNotification);
-    void UpdateCompositionRects();
-
-public:
-    template<typename Functor>
-    static void OnNativeCall(Functor&& aCall)
-    {
-        struct IMEEvent : nsAppShell::LambdaEvent<Functor>
-        {
-            using nsAppShell::LambdaEvent<Functor>::LambdaEvent;
-
-            nsAppShell::Event::Type ActivityType() const override
-            {
-                return nsAppShell::Event::Type::kUIActivity;
-            }
-        };
-        nsAppShell::PostEvent(mozilla::MakeUnique<IMEEvent>(
-                mozilla::Move(aCall)));
-    }
-
-    GeckoEditableSupport(nsWindow::NativePtr<GeckoEditableSupport>* aPtr,
-                         nsWindow* aWindow,
-                         java::GeckoEditable::Param aEditable)
-        : mWindow(aPtr, aWindow)
-        , mEditable(aEditable)
-        , mIMERanges(new TextRangeArray())
-        , mIMEMaskEventsCount(1) // Mask IME events since there's no focus yet
-        , mIMEUpdatingContext(false)
-        , mIMESelectionChanged(false)
-        , mIMETextChangedDuringFlush(false)
-        , mIMEMonitorCursor(false)
-    {}
-
-    NS_DECL_ISUPPORTS
-
-    // TextEventDispatcherListener methods
-    NS_IMETHOD NotifyIME(TextEventDispatcher* aTextEventDispatcher,
-                         const IMENotification& aNotification) override;
-
-    NS_IMETHOD_(void) OnRemovedFrom(
-            TextEventDispatcher* aTextEventDispatcher) override;
-
-    NS_IMETHOD_(void) WillDispatchKeyboardEvent(
-            TextEventDispatcher* aTextEventDispatcher,
-            WidgetKeyboardEvent& aKeyboardEvent,
-            uint32_t aIndexOfKeypress,
-            void* aData) override;
-
-    nsIMEUpdatePreference GetIMEUpdatePreference();
-
-    void SetInputContext(const InputContext& aContext,
-                         const InputContextAction& aAction);
-
-    InputContext GetInputContext();
-
-    // GeckoEditable methods
-    using EditableBase::AttachNative;
-    using EditableBase::DisposeNative;
-
-    void OnDetach() {
-        mEditable->OnViewChange(nullptr);
-    }
-
-    void OnViewChange(java::GeckoView::Param aView) {
-        mEditable->OnViewChange(aView);
-    }
-
-    // Handle an Android KeyEvent.
-    void OnKeyEvent(int32_t aAction, int32_t aKeyCode, int32_t aScanCode,
-                    int32_t aMetaState, int32_t aKeyPressMetaState,
-                    int64_t aTime, int32_t aDomPrintableKeyValue,
-                    int32_t aRepeatCount, int32_t aFlags,
-                    bool aIsSynthesizedImeKey,
-                    jni::Object::Param originalEvent);
-
-    // Synchronize Gecko thread with the InputConnection thread.
-    void OnImeSynchronize();
-
-    // Replace a range of text with new text.
-    void OnImeReplaceText(int32_t aStart, int32_t aEnd,
-                          jni::String::Param aText);
-
-    // 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);
-};
-
-} // namespace widget
-} // namespace mozill
-
-#endif // mozilla_widget_GeckoEditableSupport_h
--- a/widget/android/GeneratedJNIWrappers.h
+++ b/widget/android/GeneratedJNIWrappers.h
@@ -2107,17 +2107,17 @@ public:
         static constexpr char signature[] =
                 "()V";
         static const bool isStatic = false;
         static const mozilla::jni::ExceptionMode exceptionMode =
                 mozilla::jni::ExceptionMode::ABORT;
         static const mozilla::jni::CallingThread callingThread =
                 mozilla::jni::CallingThread::ANY;
         static const mozilla::jni::DispatchTarget dispatchTarget =
-                mozilla::jni::DispatchTarget::GECKO;
+                mozilla::jni::DispatchTarget::PROXY;
     };
 
     struct NotifyIME_t {
         typedef GeckoEditable Owner;
         typedef void ReturnType;
         typedef void SetterType;
         typedef mozilla::jni::Args<
                 int32_t> Args;
@@ -2196,17 +2196,17 @@ public:
         static constexpr char signature[] =
                 "(IIIIIZIII)V";
         static const bool isStatic = false;
         static const mozilla::jni::ExceptionMode exceptionMode =
                 mozilla::jni::ExceptionMode::ABORT;
         static const mozilla::jni::CallingThread callingThread =
                 mozilla::jni::CallingThread::ANY;
         static const mozilla::jni::DispatchTarget dispatchTarget =
-                mozilla::jni::DispatchTarget::GECKO;
+                mozilla::jni::DispatchTarget::PROXY;
     };
 
     struct OnImeReplaceText_t {
         typedef GeckoEditable Owner;
         typedef void ReturnType;
         typedef void SetterType;
         typedef mozilla::jni::Args<
                 int32_t,
@@ -2234,34 +2234,34 @@ public:
         static constexpr char signature[] =
                 "(I)V";
         static const bool isStatic = false;
         static const mozilla::jni::ExceptionMode exceptionMode =
                 mozilla::jni::ExceptionMode::ABORT;
         static const mozilla::jni::CallingThread callingThread =
                 mozilla::jni::CallingThread::ANY;
         static const mozilla::jni::DispatchTarget dispatchTarget =
-                mozilla::jni::DispatchTarget::GECKO;
+                mozilla::jni::DispatchTarget::PROXY;
     };
 
     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";
         static const bool isStatic = false;
         static const mozilla::jni::ExceptionMode exceptionMode =
                 mozilla::jni::ExceptionMode::ABORT;
         static const mozilla::jni::CallingThread callingThread =
                 mozilla::jni::CallingThread::ANY;
         static const mozilla::jni::DispatchTarget dispatchTarget =
-                mozilla::jni::DispatchTarget::GECKO;
+                mozilla::jni::DispatchTarget::PROXY;
     };
 
     struct OnImeUpdateComposition_t {
         typedef GeckoEditable Owner;
         typedef void ReturnType;
         typedef void SetterType;
         typedef mozilla::jni::Args<
                 int32_t,
@@ -2282,26 +2282,27 @@ public:
         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,
-                int64_t,
+                int32_t,
                 int32_t,
                 int32_t,
                 int32_t,
                 bool,
                 mozilla::jni::Object::Param> Args;
         static constexpr char name[] = "onKeyEvent";
         static constexpr char signature[] =
-                "(IIIIIJIIIZLandroid/view/KeyEvent;)V";
+                "(IIIIJIIIIIZLandroid/view/KeyEvent;)V";
         static const bool isStatic = false;
         static const mozilla::jni::ExceptionMode exceptionMode =
                 mozilla::jni::ExceptionMode::ABORT;
         static const mozilla::jni::CallingThread callingThread =
                 mozilla::jni::CallingThread::ANY;
         static const mozilla::jni::DispatchTarget dispatchTarget =
                 mozilla::jni::DispatchTarget::PROXY;
     };
--- a/widget/android/moz.build
+++ b/widget/android/moz.build
@@ -35,17 +35,16 @@ UNIFIED_SOURCES += [
     'AndroidCompositorWidget.cpp',
     'AndroidContentController.cpp',
     'AndroidJavaWrappers.cpp',
     'AndroidJNI.cpp',
     'AndroidJNIWrapper.cpp',
     'AndroidUiThread.cpp',
     'ANRReporter.cpp',
     'EventDispatcher.cpp',
-    'GeckoEditableSupport.cpp',
     'GeneratedJNIWrappers.cpp',
     'GfxInfo.cpp',
     'nsAndroidProtocolHandler.cpp',
     'nsAppShell.cpp',
     'nsClipboard.cpp',
     'nsDeviceContextAndroid.cpp',
     'nsIdleServiceAndroid.cpp',
     'nsLookAndFeel.cpp',
--- a/widget/android/nsWindow.cpp
+++ b/widget/android/nsWindow.cpp
@@ -5,18 +5,21 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include <android/log.h>
 #include <android/native_window.h>
 #include <android/native_window_jni.h>
 #include <math.h>
 #include <unistd.h>
 
+#include "mozilla/IMEStateManager.h"
 #include "mozilla/MiscEvents.h"
 #include "mozilla/MouseEvents.h"
+#include "mozilla/TextComposition.h"
+#include "mozilla/TextEvents.h"
 #include "mozilla/TouchEvents.h"
 #include "mozilla/TypeTraits.h"
 #include "mozilla/WeakPtr.h"
 
 #include "mozilla/dom/ContentParent.h"
 #include "mozilla/dom/ContentChild.h"
 #include "mozilla/Unused.h"
 #include "mozilla/Preferences.h"
@@ -25,19 +28,19 @@
 
 using mozilla::dom::ContentParent;
 using mozilla::dom::ContentChild;
 using mozilla::Unused;
 
 #include "nsWindow.h"
 
 #include "nsIBaseWindow.h"
-#include "nsIBrowserDOMWindow.h"
 #include "nsIDOMChromeWindow.h"
 #include "nsIObserverService.h"
+#include "nsISelection.h"
 #include "nsISupportsPrimitives.h"
 #include "nsIWidgetListener.h"
 #include "nsIWindowWatcher.h"
 #include "nsIXULWindow.h"
 
 #include "nsAppShell.h"
 #include "nsFocusManager.h"
 #include "nsIdleService.h"
@@ -69,17 +72,16 @@ using mozilla::Unused;
 #include "nsTArray.h"
 
 #include "AndroidBridge.h"
 #include "AndroidBridgeUtilities.h"
 #include "AndroidUiThread.h"
 #include "android_npapi.h"
 #include "FennecJNINatives.h"
 #include "GeneratedJNINatives.h"
-#include "GeckoEditableSupport.h"
 #include "KeyEvent.h"
 #include "MotionEvent.h"
 
 #include "imgIEncoder.h"
 
 #include "nsString.h"
 #include "GeckoProfiler.h" // For PROFILER_LABEL
 #include "nsIXULRuntime.h"
@@ -95,27 +97,48 @@ NS_IMPL_ISUPPORTS_INHERITED0(nsWindow, n
 
 #include "mozilla/layers/CompositorBridgeChild.h"
 #include "mozilla/layers/CompositorSession.h"
 #include "mozilla/layers/LayerTransactionParent.h"
 #include "mozilla/layers/UiCompositorControllerChild.h"
 #include "mozilla/Services.h"
 #include "nsThreadUtils.h"
 
+static TimeStamp
+GetEventTimeStamp(int64_t aEventTime)
+{
+    // Android's event time is SystemClock.uptimeMillis that is counted in ms
+    // since OS was booted.
+    // (https://developer.android.com/reference/android/os/SystemClock.html)
+    // and this SystemClock.uptimeMillis uses SYSTEM_TIME_MONOTONIC.
+    // Our posix implemententaion of TimeStamp::Now uses SYSTEM_TIME_MONOTONIC
+    //  too. Due to same implementation, we can use this via FromSystemTime.
+    int64_t tick =
+        BaseTimeDurationPlatformUtils::TicksFromMilliseconds(aEventTime);
+    return TimeStamp::FromSystemTime(tick);
+}
+
 // All the toplevel windows that have been created; these are in
 // stacking order, so the window at gTopLevelWindows[0] is the topmost
 // one.
 static nsTArray<nsWindow*> gTopLevelWindows;
 
 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;
 
     bool IsStaleCall()
     {
@@ -165,48 +188,29 @@ public:
     }
 
     Event::Type ActivityType() const override
     {
         return mEventType;
     }
 };
 
-namespace {
-    template<class Instance, class Impl> typename EnableIf<
-        jni::detail::NativePtrPicker<Impl>::value ==
-        jni::detail::REFPTR, void>::Type
-    CallAttachNative(Instance aInstance, Impl* aImpl)
-    {
-        Impl::AttachNative(aInstance, RefPtr<Impl>(aImpl).get());
-    }
-
-    template<class Instance, class Impl> typename EnableIf<
-        jni::detail::NativePtrPicker<Impl>::value ==
-        jni::detail::OWNING, void>::Type
-    CallAttachNative(Instance aInstance, Impl* aImpl)
-    {
-        Impl::AttachNative(aInstance, UniquePtr<Impl>(aImpl));
-    }
-} // namespace
-
 template<class Impl>
 template<class Instance, typename... Args> void
 nsWindow::NativePtr<Impl>::Attach(Instance aInstance, nsWindow* aWindow,
                                   Args&&... aArgs)
 {
     MOZ_ASSERT(NS_IsMainThread());
     MOZ_ASSERT(!mPtr && !mImpl);
 
-    Impl* const impl = new Impl(
+    auto impl = mozilla::MakeUnique<Impl>(
             this, aWindow, mozilla::Forward<Args>(aArgs)...);
-    mImpl = impl;
-
-    // CallAttachNative transfers ownership of impl.
-    CallAttachNative<Instance, Impl>(aInstance, impl);
+    mImpl = impl.get();
+
+    Impl::AttachNative(aInstance, mozilla::Move(impl));
 }
 
 template<class Impl> void
 nsWindow::NativePtr<Impl>::Detach()
 {
     MOZ_ASSERT(NS_IsMainThread());
     MOZ_ASSERT(mPtr && mImpl);
 
@@ -232,54 +236,125 @@ public:
         : MutexAutoLock(aPtr.mImplLock)
         , mImpl(aPtr.mImpl)
     {}
 
     operator Impl*() const { return mImpl; }
     Impl* operator->() const { return mImpl; }
 };
 
+template<class Impl>
+class nsWindow::WindowPtr final
+{
+    friend NativePtr<Impl>;
+
+    NativePtr<Impl>* mPtr;
+    nsWindow* mWindow;
+    Mutex mWindowLock;
+
+public:
+    class Locked final : private MutexAutoLock
+    {
+        nsWindow* const mWindow;
+
+    public:
+        Locked(WindowPtr<Impl>& aPtr)
+            : MutexAutoLock(aPtr.mWindowLock)
+            , mWindow(aPtr.mWindow)
+        {}
+
+        operator nsWindow*() const { return mWindow; }
+        nsWindow* operator->() const { return mWindow; }
+    };
+
+    WindowPtr(NativePtr<Impl>* aPtr, nsWindow* aWindow)
+        : mPtr(aPtr)
+        , mWindow(aWindow)
+        , mWindowLock(NativePtr<Impl>::sName)
+    {
+        MOZ_ASSERT(NS_IsMainThread());
+        mPtr->mPtr = this;
+    }
+
+    ~WindowPtr()
+    {
+        MOZ_ASSERT(NS_IsMainThread());
+        if (!mPtr) {
+            return;
+        }
+        mPtr->mPtr = nullptr;
+        mPtr->mImpl = nullptr;
+    }
+
+    operator nsWindow*() const
+    {
+        MOZ_ASSERT(NS_IsMainThread());
+        return mWindow;
+    }
+
+    nsWindow* operator->() const { return operator nsWindow*(); }
+};
+
 
 class nsWindow::GeckoViewSupport final
     : public GeckoView::Window::Natives<GeckoViewSupport>
+    , public GeckoEditable::Natives<GeckoViewSupport>
     , public SupportsWeakPtr<GeckoViewSupport>
 {
     nsWindow& window;
 
 public:
     typedef GeckoView::Window::Natives<GeckoViewSupport> Base;
+    typedef GeckoEditable::Natives<GeckoViewSupport> EditableBase;
     typedef SupportsWeakPtr<GeckoViewSupport> SupportsWeakPtr;
 
     MOZ_DECLARE_WEAKREFERENCE_TYPENAME(GeckoViewSupport);
 
     template<typename Functor>
     static void OnNativeCall(Functor&& aCall)
     {
         if (aCall.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 aCall();
         }
 
+        const nsAppShell::Event::Type eventType =
+                aCall.IsTarget(&GeckoViewSupport::OnKeyEvent) ||
+                aCall.IsTarget(&GeckoViewSupport::OnImeReplaceText) ||
+                aCall.IsTarget(&GeckoViewSupport::OnImeUpdateComposition) ?
+                nsAppShell::Event::Type::kUIActivity :
+                nsAppShell::Event::Type::kGeneralActivity;
+
         nsAppShell::PostEvent(mozilla::MakeUnique<WindowEvent<Functor>>(
-                mozilla::Move(aCall)));
+                mozilla::Move(aCall), eventType));
     }
 
     GeckoViewSupport(nsWindow* aWindow,
                      const GeckoView::Window::LocalRef& aInstance,
                      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, static_cast<SupportsWeakPtr*>(this));
+        EditableBase::AttachNative(
+                mEditable, static_cast<SupportsWeakPtr*>(this));
     }
 
     ~GeckoViewSupport();
 
     using Base::DisposeNative;
+    using EditableBase::DisposeNative;
 
     /**
      * GeckoView methods
      */
 private:
     nsCOMPtr<nsPIDOMWindowOuter> mDOMWindow;
 
 public:
@@ -296,16 +371,123 @@ public:
     void Close();
 
     // Reattach this nsWindow to a new GeckoView.
     void Reattach(const GeckoView::Window::LocalRef& inst,
                   GeckoView::Param aView, jni::Object::Param aCompositor,
                   jni::Object::Param aDispatcher);
 
     void LoadUri(jni::String::Param aUri, int32_t aFlags);
+
+    /**
+     * GeckoEditable methods
+     */
+private:
+    /*
+        Rules for managing IME between Gecko and Java:
+
+        * Gecko controls the text content, and Java shadows the Gecko text
+           through text updates
+        * Gecko and Java maintain separate selections, and synchronize when
+           needed through selection updates and set-selection events
+        * Java controls the composition, and Gecko shadows the Java
+           composition through update composition events
+    */
+
+    struct IMETextChange final {
+        int32_t mStart, mOldEnd, mNewEnd;
+
+        IMETextChange() :
+            mStart(-1), mOldEnd(-1), mNewEnd(-1) {}
+
+        IMETextChange(const IMENotification& aIMENotification)
+            : mStart(aIMENotification.mTextChangeData.mStartOffset)
+            , mOldEnd(aIMENotification.mTextChangeData.mRemovedEndOffset)
+            , mNewEnd(aIMENotification.mTextChangeData.mAddedEndOffset)
+        {
+            MOZ_ASSERT(aIMENotification.mMessage == NOTIFY_IME_OF_TEXT_CHANGE,
+                       "IMETextChange initialized with wrong notification");
+            MOZ_ASSERT(aIMENotification.mTextChangeData.IsValid(),
+                       "The text change notification isn't initialized");
+            MOZ_ASSERT(aIMENotification.mTextChangeData.IsInInt32Range(),
+                       "The text change notification is out of range");
+        }
+
+        bool IsEmpty() const { return mStart < 0; }
+    };
+
+    // GeckoEditable instance used by this nsWindow;
+    java::GeckoEditable::GlobalRef mEditable;
+    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 {
+        // Not retrying.
+        FLUSH_FLAG_NONE,
+        // Retrying due to IME text changes during flush.
+        FLUSH_FLAG_RETRY,
+        // Retrying due to IME sync exceptions during flush.
+        FLUSH_FLAG_RECOVER
+    };
+    void PostFlushIMEChanges();
+    void FlushIMEChanges(FlushChangesFlag aFlags = FLUSH_FLAG_NONE);
+    void FlushIMEText(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
+    // OnImeSynchronize, as required by events like OnImeReplaceText.
+    class AutoIMESynchronize {
+        GeckoViewSupport* const mGVS;
+    public:
+        AutoIMESynchronize(GeckoViewSupport* gvs) : mGVS(gvs) {}
+        ~AutoIMESynchronize() { mGVS->OnImeSynchronize(); }
+    };
+
+    // Handle an Android KeyEvent.
+    void OnKeyEvent(int32_t aAction, int32_t aKeyCode, int32_t aScanCode,
+                    int32_t aMetaState, int64_t aTime, int32_t aUnicodeChar,
+                    int32_t aBaseUnicodeChar, int32_t aDomPrintableKeyValue,
+                    int32_t aRepeatCount, int32_t aFlags,
+                    bool aIsSynthesizedImeKey, jni::Object::Param originalEvent);
+
+    // Synchronize Gecko thread with the InputConnection thread.
+    void OnImeSynchronize();
+
+    // Replace a range of text with new text.
+    void OnImeReplaceText(int32_t aStart, int32_t aEnd,
+                          jni::String::Param aText);
+
+    // 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>
@@ -1183,18 +1365,18 @@ ANativeWindow* nsWindow::PMPMSupport::sW
 EGLSurface nsWindow::PMPMSupport::sSurface;
 
 
 nsWindow::GeckoViewSupport::~GeckoViewSupport()
 {
     // Disassociate our GeckoEditable instance with our native object.
     // OnDestroy will call disposeNative after any pending native calls have
     // been made.
-    MOZ_ASSERT(window.mEditableSupport);
-    window.mEditableSupport.Detach();
+    MOZ_ASSERT(mEditable);
+    mEditable->OnViewChange(nullptr);
 
     if (window.mNPZCSupport) {
         window.mNPZCSupport.Detach();
     }
 
     if (window.mLayerViewSupport) {
         window.mLayerViewSupport.Detach();
     }
@@ -1244,25 +1426,21 @@ nsWindow::GeckoViewSupport::Open(const j
             nsPIDOMWindowOuter::From(domWindow);
     nsCOMPtr<nsIWidget> widget = WidgetUtils::DOMWindowToWidget(pdomWindow);
     MOZ_ASSERT(widget);
 
     const auto window = static_cast<nsWindow*>(widget.get());
     window->SetScreenId(aScreenId);
 
     // Attach a new GeckoView support object to the new window.
-    window->mGeckoViewSupport = mozilla::MakeUnique<GeckoViewSupport>(
+    window->mGeckoViewSupport  = mozilla::MakeUnique<GeckoViewSupport>(
             window, GeckoView::Window::LocalRef(aCls.Env(), aWindow), aView);
 
     window->mGeckoViewSupport->mDOMWindow = pdomWindow;
 
-    // Attach a new GeckoEditable support object to the new window.
-    auto editable = GeckoEditable::New(aView);
-    window->mEditableSupport.Attach(editable, window, editable);
-
     // Attach the Compositor to the new window.
     auto compositor = LayerView::Compositor::LocalRef(
             aCls.Env(), LayerView::Compositor::Ref::From(aCompositor));
     window->mLayerViewSupport.Attach(compositor, window, compositor);
 
     // Attach again using the new window.
     androidView->mEventDispatcher->Attach(
             java::EventDispatcher::Ref::From(aDispatcher), pdomWindow);
@@ -1296,18 +1474,17 @@ nsWindow::GeckoViewSupport::Close()
 
 void
 nsWindow::GeckoViewSupport::Reattach(const GeckoView::Window::LocalRef& inst,
                                      GeckoView::Param aView,
                                      jni::Object::Param aCompositor,
                                      jni::Object::Param aDispatcher)
 {
     // Associate our previous GeckoEditable with the new GeckoView.
-    MOZ_ASSERT(window.mEditableSupport);
-    window.mEditableSupport->OnViewChange(aView);
+    mEditable->OnViewChange(aView);
 
     // mNPZCSupport might have already been detached through the Java side calling
     // NativePanZoomController.destroy().
     if (window.mNPZCSupport) {
         window.mNPZCSupport.Detach();
     }
 
     MOZ_ASSERT(window.mLayerViewSupport);
@@ -1356,16 +1533,17 @@ nsWindow::GeckoViewSupport::LoadUri(jni:
         NS_WARNING("Failed to open URI");
     }
 }
 
 void
 nsWindow::InitNatives()
 {
     nsWindow::GeckoViewSupport::Base::Init();
+    nsWindow::GeckoViewSupport::EditableBase::Init();
     nsWindow::LayerViewSupport::Init();
     nsWindow::NPZCSupport::Init();
     if (jni::IsFennec()) {
         nsWindow::PMPMSupport::Init();
     }
 }
 
 nsWindow*
@@ -2028,116 +2206,1309 @@ nsWindow::DispatchHitTest(const WidgetTo
         DispatchEvent(&hittest, status);
 
         if (mAPZEventState && hittest.hitCluster) {
             mAPZEventState->ProcessClusterHit();
         }
     }
 }
 
-mozilla::Modifiers
-nsWindow::GetModifiers(int32_t metaState)
+static unsigned int ConvertAndroidKeyCodeToDOMKeyCode(int androidKeyCode)
+{
+    // Special-case alphanumeric keycodes because they are most common.
+    if (androidKeyCode >= AKEYCODE_A &&
+        androidKeyCode <= AKEYCODE_Z) {
+        return androidKeyCode - AKEYCODE_A + NS_VK_A;
+    }
+
+    if (androidKeyCode >= AKEYCODE_0 &&
+        androidKeyCode <= AKEYCODE_9) {
+        return androidKeyCode - AKEYCODE_0 + NS_VK_0;
+    }
+
+    switch (androidKeyCode) {
+        // KEYCODE_UNKNOWN (0) ... KEYCODE_HOME (3)
+        case AKEYCODE_BACK:               return NS_VK_ESCAPE;
+        // KEYCODE_CALL (5) ... KEYCODE_POUND (18)
+        case AKEYCODE_DPAD_UP:            return NS_VK_UP;
+        case AKEYCODE_DPAD_DOWN:          return NS_VK_DOWN;
+        case AKEYCODE_DPAD_LEFT:          return NS_VK_LEFT;
+        case AKEYCODE_DPAD_RIGHT:         return NS_VK_RIGHT;
+        case AKEYCODE_DPAD_CENTER:        return NS_VK_RETURN;
+        case AKEYCODE_VOLUME_UP:          return NS_VK_VOLUME_UP;
+        case AKEYCODE_VOLUME_DOWN:        return NS_VK_VOLUME_DOWN;
+        // KEYCODE_VOLUME_POWER (26) ... KEYCODE_Z (54)
+        case AKEYCODE_COMMA:              return NS_VK_COMMA;
+        case AKEYCODE_PERIOD:             return NS_VK_PERIOD;
+        case AKEYCODE_ALT_LEFT:           return NS_VK_ALT;
+        case AKEYCODE_ALT_RIGHT:          return NS_VK_ALT;
+        case AKEYCODE_SHIFT_LEFT:         return NS_VK_SHIFT;
+        case AKEYCODE_SHIFT_RIGHT:        return NS_VK_SHIFT;
+        case AKEYCODE_TAB:                return NS_VK_TAB;
+        case AKEYCODE_SPACE:              return NS_VK_SPACE;
+        // KEYCODE_SYM (63) ... KEYCODE_ENVELOPE (65)
+        case AKEYCODE_ENTER:              return NS_VK_RETURN;
+        case AKEYCODE_DEL:                return NS_VK_BACK; // Backspace
+        case AKEYCODE_GRAVE:              return NS_VK_BACK_QUOTE;
+        // KEYCODE_MINUS (69)
+        case AKEYCODE_EQUALS:             return NS_VK_EQUALS;
+        case AKEYCODE_LEFT_BRACKET:       return NS_VK_OPEN_BRACKET;
+        case AKEYCODE_RIGHT_BRACKET:      return NS_VK_CLOSE_BRACKET;
+        case AKEYCODE_BACKSLASH:          return NS_VK_BACK_SLASH;
+        case AKEYCODE_SEMICOLON:          return NS_VK_SEMICOLON;
+        // KEYCODE_APOSTROPHE (75)
+        case AKEYCODE_SLASH:              return NS_VK_SLASH;
+        // KEYCODE_AT (77) ... KEYCODE_MEDIA_FAST_FORWARD (90)
+        case AKEYCODE_MUTE:               return NS_VK_VOLUME_MUTE;
+        case AKEYCODE_PAGE_UP:            return NS_VK_PAGE_UP;
+        case AKEYCODE_PAGE_DOWN:          return NS_VK_PAGE_DOWN;
+        // KEYCODE_PICTSYMBOLS (94) ... KEYCODE_BUTTON_MODE (110)
+        case AKEYCODE_ESCAPE:             return NS_VK_ESCAPE;
+        case AKEYCODE_FORWARD_DEL:        return NS_VK_DELETE;
+        case AKEYCODE_CTRL_LEFT:          return NS_VK_CONTROL;
+        case AKEYCODE_CTRL_RIGHT:         return NS_VK_CONTROL;
+        case AKEYCODE_CAPS_LOCK:          return NS_VK_CAPS_LOCK;
+        case AKEYCODE_SCROLL_LOCK:        return NS_VK_SCROLL_LOCK;
+        // KEYCODE_META_LEFT (117) ... KEYCODE_FUNCTION (119)
+        case AKEYCODE_SYSRQ:              return NS_VK_PRINTSCREEN;
+        case AKEYCODE_BREAK:              return NS_VK_PAUSE;
+        case AKEYCODE_MOVE_HOME:          return NS_VK_HOME;
+        case AKEYCODE_MOVE_END:           return NS_VK_END;
+        case AKEYCODE_INSERT:             return NS_VK_INSERT;
+        // KEYCODE_FORWARD (125) ... KEYCODE_MEDIA_RECORD (130)
+        case AKEYCODE_F1:                 return NS_VK_F1;
+        case AKEYCODE_F2:                 return NS_VK_F2;
+        case AKEYCODE_F3:                 return NS_VK_F3;
+        case AKEYCODE_F4:                 return NS_VK_F4;
+        case AKEYCODE_F5:                 return NS_VK_F5;
+        case AKEYCODE_F6:                 return NS_VK_F6;
+        case AKEYCODE_F7:                 return NS_VK_F7;
+        case AKEYCODE_F8:                 return NS_VK_F8;
+        case AKEYCODE_F9:                 return NS_VK_F9;
+        case AKEYCODE_F10:                return NS_VK_F10;
+        case AKEYCODE_F11:                return NS_VK_F11;
+        case AKEYCODE_F12:                return NS_VK_F12;
+        case AKEYCODE_NUM_LOCK:           return NS_VK_NUM_LOCK;
+        case AKEYCODE_NUMPAD_0:           return NS_VK_NUMPAD0;
+        case AKEYCODE_NUMPAD_1:           return NS_VK_NUMPAD1;
+        case AKEYCODE_NUMPAD_2:           return NS_VK_NUMPAD2;
+        case AKEYCODE_NUMPAD_3:           return NS_VK_NUMPAD3;
+        case AKEYCODE_NUMPAD_4:           return NS_VK_NUMPAD4;
+        case AKEYCODE_NUMPAD_5:           return NS_VK_NUMPAD5;
+        case AKEYCODE_NUMPAD_6:           return NS_VK_NUMPAD6;
+        case AKEYCODE_NUMPAD_7:           return NS_VK_NUMPAD7;
+        case AKEYCODE_NUMPAD_8:           return NS_VK_NUMPAD8;
+        case AKEYCODE_NUMPAD_9:           return NS_VK_NUMPAD9;
+        case AKEYCODE_NUMPAD_DIVIDE:      return NS_VK_DIVIDE;
+        case AKEYCODE_NUMPAD_MULTIPLY:    return NS_VK_MULTIPLY;
+        case AKEYCODE_NUMPAD_SUBTRACT:    return NS_VK_SUBTRACT;
+        case AKEYCODE_NUMPAD_ADD:         return NS_VK_ADD;
+        case AKEYCODE_NUMPAD_DOT:         return NS_VK_DECIMAL;
+        case AKEYCODE_NUMPAD_COMMA:       return NS_VK_SEPARATOR;
+        case AKEYCODE_NUMPAD_ENTER:       return NS_VK_RETURN;
+        case AKEYCODE_NUMPAD_EQUALS:      return NS_VK_EQUALS;
+        // KEYCODE_NUMPAD_LEFT_PAREN (162) ... KEYCODE_CALCULATOR (210)
+
+        // Needs to confirm the behavior.  If the key switches the open state
+        // of Japanese IME (or switches input character between Hiragana and
+        // Roman numeric characters), then, it might be better to use
+        // NS_VK_KANJI which is used for Alt+Zenkaku/Hankaku key on Windows.
+        case AKEYCODE_ZENKAKU_HANKAKU:    return 0;
+        case AKEYCODE_EISU:               return NS_VK_EISU;
+        case AKEYCODE_MUHENKAN:           return NS_VK_NONCONVERT;
+        case AKEYCODE_HENKAN:             return NS_VK_CONVERT;
+        case AKEYCODE_KATAKANA_HIRAGANA:  return 0;
+        case AKEYCODE_YEN:                return NS_VK_BACK_SLASH; // Same as other platforms.
+        case AKEYCODE_RO:                 return NS_VK_BACK_SLASH; // Same as other platforms.
+        case AKEYCODE_KANA:               return NS_VK_KANA;
+        case AKEYCODE_ASSIST:             return NS_VK_HELP;
+
+        // the A key is the action key for gamepad devices.
+        case AKEYCODE_BUTTON_A:          return NS_VK_RETURN;
+
+        default:
+            ALOG("ConvertAndroidKeyCodeToDOMKeyCode: "
+                 "No DOM keycode for Android keycode %d", androidKeyCode);
+        return 0;
+    }
+}
+
+static KeyNameIndex
+ConvertAndroidKeyCodeToKeyNameIndex(int keyCode, int action,
+                                    int domPrintableKeyValue)
+{
+    // 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;
+    }
+
+    switch (keyCode) {
+
+#define NS_NATIVE_KEY_TO_DOM_KEY_NAME_INDEX(aNativeKey, aKeyNameIndex) \
+        case aNativeKey: return aKeyNameIndex;
+
+#include "NativeKeyToDOMKeyName.h"
+
+#undef NS_NATIVE_KEY_TO_DOM_KEY_NAME_INDEX
+
+        // KEYCODE_0 (7) ... KEYCODE_9 (16)
+        case AKEYCODE_STAR:               // '*' key
+        case AKEYCODE_POUND:              // '#' key
+
+        // KEYCODE_A (29) ... KEYCODE_Z (54)
+
+        case AKEYCODE_COMMA:              // ',' key
+        case AKEYCODE_PERIOD:             // '.' key
+        case AKEYCODE_SPACE:
+        case AKEYCODE_GRAVE:              // '`' key
+        case AKEYCODE_MINUS:              // '-' key
+        case AKEYCODE_EQUALS:             // '=' key
+        case AKEYCODE_LEFT_BRACKET:       // '[' key
+        case AKEYCODE_RIGHT_BRACKET:      // ']' key
+        case AKEYCODE_BACKSLASH:          // '\' key
+        case AKEYCODE_SEMICOLON:          // ';' key
+        case AKEYCODE_APOSTROPHE:         // ''' key
+        case AKEYCODE_SLASH:              // '/' key
+        case AKEYCODE_AT:                 // '@' key
+        case AKEYCODE_PLUS:               // '+' key
+
+        case AKEYCODE_NUMPAD_0:
+        case AKEYCODE_NUMPAD_1:
+        case AKEYCODE_NUMPAD_2:
+        case AKEYCODE_NUMPAD_3:
+        case AKEYCODE_NUMPAD_4:
+        case AKEYCODE_NUMPAD_5:
+        case AKEYCODE_NUMPAD_6:
+        case AKEYCODE_NUMPAD_7:
+        case AKEYCODE_NUMPAD_8:
+        case AKEYCODE_NUMPAD_9:
+        case AKEYCODE_NUMPAD_DIVIDE:
+        case AKEYCODE_NUMPAD_MULTIPLY:
+        case AKEYCODE_NUMPAD_SUBTRACT:
+        case AKEYCODE_NUMPAD_ADD:
+        case AKEYCODE_NUMPAD_DOT:
+        case AKEYCODE_NUMPAD_COMMA:
+        case AKEYCODE_NUMPAD_EQUALS:
+        case AKEYCODE_NUMPAD_LEFT_PAREN:
+        case AKEYCODE_NUMPAD_RIGHT_PAREN:
+
+        case AKEYCODE_YEN:                // yen sign key
+        case AKEYCODE_RO:                 // Japanese Ro key
+            return KEY_NAME_INDEX_USE_STRING;
+
+        case AKEYCODE_NUM:                // XXX Not sure
+        case AKEYCODE_PICTSYMBOLS:
+
+        case AKEYCODE_BUTTON_A:
+        case AKEYCODE_BUTTON_B:
+        case AKEYCODE_BUTTON_C:
+        case AKEYCODE_BUTTON_X:
+        case AKEYCODE_BUTTON_Y:
+        case AKEYCODE_BUTTON_Z:
+        case AKEYCODE_BUTTON_L1:
+        case AKEYCODE_BUTTON_R1:
+        case AKEYCODE_BUTTON_L2:
+        case AKEYCODE_BUTTON_R2:
+        case AKEYCODE_BUTTON_THUMBL:
+        case AKEYCODE_BUTTON_THUMBR:
+        case AKEYCODE_BUTTON_START:
+        case AKEYCODE_BUTTON_SELECT:
+        case AKEYCODE_BUTTON_MODE:
+
+        case AKEYCODE_MEDIA_CLOSE:
+
+        case AKEYCODE_BUTTON_1:
+        case AKEYCODE_BUTTON_2:
+        case AKEYCODE_BUTTON_3:
+        case AKEYCODE_BUTTON_4:
+        case AKEYCODE_BUTTON_5:
+        case AKEYCODE_BUTTON_6:
+        case AKEYCODE_BUTTON_7:
+        case AKEYCODE_BUTTON_8:
+        case AKEYCODE_BUTTON_9:
+        case AKEYCODE_BUTTON_10:
+        case AKEYCODE_BUTTON_11:
+        case AKEYCODE_BUTTON_12:
+        case AKEYCODE_BUTTON_13:
+        case AKEYCODE_BUTTON_14:
+        case AKEYCODE_BUTTON_15:
+        case AKEYCODE_BUTTON_16:
+            return KEY_NAME_INDEX_Unidentified;
+
+        case AKEYCODE_UNKNOWN:
+            MOZ_ASSERT(
+                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 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(int 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 bool
+IsModifierKey(int32_t keyCode)
+{
+    using mozilla::java::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)
 {
     using mozilla::java::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);
 }
 
-TimeStamp
-nsWindow::GetEventTimeStamp(int64_t aEventTime)
+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)
+{
+    const uint32_t domKeyCode = ConvertAndroidKeyCodeToDOMKeyCode(keyCode);
+    const int32_t charCode = unicodeChar ? unicodeChar : baseUnicodeChar;
+
+    event.mModifiers = GetModifiers(metaState);
+
+    if (event.mMessage == eKeyPress) {
+        // Android gives us \n, so filter out some control characters.
+        event.mIsChar = (charCode >= ' ');
+        event.mCharCode = event.mIsChar ? charCode : 0;
+        event.mKeyCode = event.mIsChar ? 0 : domKeyCode;
+        event.mPluginEvent.Clear();
+
+        // 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.mModifiers &= ~(MODIFIER_ALT | MODIFIER_CONTROL
+                                               | MODIFIER_META);
+        }
+
+    } else {
+        event.mIsChar = false;
+        event.mCharCode = 0;
+        event.mKeyCode = domKeyCode;
+
+        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) &&
+        ((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.mLocation =
+        WidgetKeyboardEvent::ComputeLocationFromCodeValue(event.mCodeNameIndex);
+    event.mTime = time;
+    event.mTimeStamp = GetEventTimeStamp(time);
+}
+
+void
+nsWindow::GeckoViewSupport::OnKeyEvent(int32_t aAction, int32_t aKeyCode,
+        int32_t aScanCode, int32_t aMetaState, int64_t aTime,
+        int32_t aUnicodeChar, int32_t aBaseUnicodeChar,
+        int32_t aDomPrintableKeyValue, int32_t aRepeatCount, int32_t aFlags,
+        bool aIsSynthesizedImeKey, jni::Object::Param originalEvent)
+{
+    RefPtr<nsWindow> kungFuDeathGrip(&window);
+    if (!aIsSynthesizedImeKey) {
+        window.UserActivity();
+        window.RemoveIMEComposition();
+    }
+
+    EventMessage msg;
+    if (aAction == sdk::KeyEvent::ACTION_DOWN) {
+        msg = eKeyDown;
+    } else if (aAction == sdk::KeyEvent::ACTION_UP) {
+        msg = eKeyUp;
+    } else if (aAction == 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");
+    } else {
+        ALOG("Unknown key action event!");
+        return;
+    }
+
+    nsEventStatus status = nsEventStatus_eIgnore;
+    WidgetKeyboardEvent event(true, msg, &window);
+    window.InitEvent(event, nullptr);
+    InitKeyEvent(event, aAction, aKeyCode, aScanCode, aMetaState, aTime,
+                 aUnicodeChar, aBaseUnicodeChar, aDomPrintableKeyValue,
+                 aRepeatCount, aFlags);
+
+    if (aIsSynthesizedImeKey) {
+        // 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.
+        mIMEKeyEvents.AppendElement(
+                mozilla::UniquePtr<WidgetEvent>(event.Duplicate()));
+
+    } else {
+        window.DispatchEvent(&event, status);
+
+        if (window.Destroyed() || status == nsEventStatus_eConsumeNoDefault) {
+            // Skip default processing.
+            return;
+        }
+
+        mEditable->OnDefaultKeyEvent(originalEvent);
+    }
+
+    if (msg != eKeyDown || IsModifierKey(aKeyCode)) {
+        // Skip sending key press event.
+        return;
+    }
+
+    WidgetKeyboardEvent pressEvent(true, eKeyPress, &window);
+    window.InitEvent(pressEvent, nullptr);
+    InitKeyEvent(pressEvent, aAction, aKeyCode, aScanCode, aMetaState, aTime,
+                 aUnicodeChar, aBaseUnicodeChar, aDomPrintableKeyValue,
+                 aRepeatCount, aFlags);
+
+    if (aIsSynthesizedImeKey) {
+        mIMEKeyEvents.AppendElement(
+                mozilla::UniquePtr<WidgetEvent>(pressEvent.Duplicate()));
+    } else {
+        window.DispatchEvent(&pressEvent, status);
+    }
+}
+
+#ifdef DEBUG_ANDROID_IME
+#define ALOGIME(args...) ALOG(args)
+#else
+#define ALOGIME(args...) ((void)0)
+#endif
+
+static nscolor
+ConvertAndroidColor(uint32_t aArgb)
+{
+    return NS_RGBA((aArgb & 0x00ff0000) >> 16,
+                   (aArgb & 0x0000ff00) >> 8,
+                   (aArgb & 0x000000ff),
+                   (aArgb & 0xff000000) >> 24);
+}
+
+/*
+ * Get the current composition object, if any.
+ */
+RefPtr<mozilla::TextComposition>
+nsWindow::GetIMEComposition()
+{
+    MOZ_ASSERT(this == FindTopLevel());
+    return mozilla::IMEStateManager::GetTextCompositionFor(this);
+}
+
+/*
+    Remove the composition but leave the text content as-is
+*/
+void
+nsWindow::RemoveIMEComposition(RemoveIMECompositionFlag aFlag)
+{
+    // Remove composition on Gecko side
+    const RefPtr<mozilla::TextComposition> composition(GetIMEComposition());
+    if (!composition) {
+        return;
+    }
+
+    RefPtr<nsWindow> kungFuDeathGrip(this);
+
+    WidgetCompositionEvent compositionCommitEvent(
+            true, eCompositionCommit, this);
+    if (aFlag == COMMIT_IME_COMPOSITION) {
+        compositionCommitEvent.mMessage = eCompositionCommitAsIs;
+    }
+    InitEvent(compositionCommitEvent, nullptr);
+    DispatchEvent(&compositionCommitEvent);
+}
+
+/*
+ * 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::GeckoViewSupport::SendIMEDummyKeyEvents()
+{
+    WidgetKeyboardEvent downEvent(true, eKeyDown, &window);
+    window.InitEvent(downEvent, nullptr);
+    MOZ_ASSERT(downEvent.mKeyCode == 0);
+    window.DispatchEvent(&downEvent);
+
+    WidgetKeyboardEvent upEvent(true, eKeyUp, &window);
+    window.InitEvent(upEvent, nullptr);
+    MOZ_ASSERT(upEvent.mKeyCode == 0);
+    window.DispatchEvent(&upEvent);
+}
+
+void
+nsWindow::GeckoViewSupport::AddIMETextChange(const IMETextChange& aChange)
+{
+    mIMETextChanges.AppendElement(aChange);
+
+    // We may not be in the middle of flushing,
+    // in which case this flag is meaningless.
+    mIMETextChangedDuringFlush = true;
+
+    // Now that we added a new range we need to go back and
+    // update all the ranges before that.
+    // Ranges that have offsets which follow this new range
+    // need to be updated to reflect new offsets
+    const int32_t delta = aChange.mNewEnd - aChange.mOldEnd;
+    for (int32_t i = mIMETextChanges.Length() - 2; i >= 0; i--) {
+        IMETextChange& previousChange = mIMETextChanges[i];
+        if (previousChange.mStart > aChange.mOldEnd) {
+            previousChange.mStart += delta;
+            previousChange.mOldEnd += delta;
+            previousChange.mNewEnd += delta;
+        }
+    }
+
+    // Now go through all ranges to merge any ranges that are connected
+    // srcIndex is the index of the range to merge from
+    // dstIndex is the index of the range to potentially merge into
+    int32_t srcIndex = mIMETextChanges.Length() - 1;
+    int32_t dstIndex = srcIndex;
+
+    while (--dstIndex >= 0) {
+        IMETextChange& src = mIMETextChanges[srcIndex];
+        IMETextChange& dst = mIMETextChanges[dstIndex];
+        // When merging a more recent change into an older
+        // change, we need to compare recent change's (start, oldEnd)
+        // range to the older change's (start, newEnd)
+        if (src.mOldEnd < dst.mStart || dst.mNewEnd < src.mStart) {
+            // No overlap between ranges
+            continue;
+        }
+        // When merging two ranges, there are generally four posibilities:
+        // [----(----]----), (----[----]----),
+        // [----(----)----], (----[----)----]
+        // where [----] is the first range and (----) is the second range
+        // As seen above, the start of the merged range is always the lesser
+        // of the two start offsets. OldEnd and NewEnd then need to be
+        // adjusted separately depending on the case. In any case, the change
+        // in text length of the merged range should be the sum of text length
+        // changes of the two original ranges, i.e.,
+        // newNewEnd - newOldEnd == newEnd1 - oldEnd1 + newEnd2 - oldEnd2
+        dst.mStart = std::min(dst.mStart, src.mStart);
+        if (src.mOldEnd < dst.mNewEnd) {
+            // New range overlaps or is within previous range; merge
+            dst.mNewEnd += src.mNewEnd - src.mOldEnd;
+        } else { // src.mOldEnd >= dst.mNewEnd
+            // New range overlaps previous range; merge
+            dst.mOldEnd += src.mOldEnd - dst.mNewEnd;
+            dst.mNewEnd = src.mNewEnd;
+        }
+        // src merged to dst; delete src.
+        mIMETextChanges.RemoveElementAt(srcIndex);
+        // Any ranges that we skip over between src and dst are not mergeable
+        // so we can safely continue the merge starting at dst
+        srcIndex = dstIndex;
+    }
+}
+
+void
+nsWindow::GeckoViewSupport::PostFlushIMEChanges()
+{
+    if (!mIMETextChanges.IsEmpty() || mIMESelectionChanged) {
+        // Already posted
+        return;
+    }
+
+    // Keep a strong reference to the window to keep 'this' alive.
+    RefPtr<nsWindow> window(&this->window);
+
+    nsAppShell::PostEvent([this, window] {
+        if (!window->Destroyed()) {
+            FlushIMEChanges();
+        }
+    });
+}
+
+void
+nsWindow::GeckoViewSupport::FlushIMEChanges(FlushChangesFlag aFlags)
+{
+    // Only send change notifications if we are *not* masking events,
+    // i.e. if we have a focused editor,
+    NS_ENSURE_TRUE_VOID(!mIMEMaskEventsCount);
+
+    nsCOMPtr<nsISelection> imeSelection;
+    nsCOMPtr<nsIContent> imeRoot;
+
+    // If we are receiving notifications, we must have selection/root content.
+    nsresult rv = IMEStateManager::GetFocusSelectionAndRoot(
+            getter_AddRefs(imeSelection), getter_AddRefs(imeRoot));
+
+    // With e10s enabled, GetFocusSelectionAndRoot will fail because the IME
+    // content observer is out of process.
+    const bool e10sEnabled = rv == NS_ERROR_NOT_AVAILABLE;
+    if (!e10sEnabled) {
+        MOZ_ALWAYS_SUCCEEDS(rv);
+    }
+
+    // Make sure we still have a valid selection/root. We can potentially get
+    // a stale selection/root if the editor becomes hidden, for example.
+    NS_ENSURE_TRUE_VOID(e10sEnabled || imeRoot->IsInComposedDoc());
+
+    RefPtr<nsWindow> kungFuDeathGrip(&window);
+    window.UserActivity();
+
+    struct TextRecord {
+        nsString text;
+        int32_t start;
+        int32_t oldEnd;
+        int32_t newEnd;
+    };
+    AutoTArray<TextRecord, 4> textTransaction;
+    if (mIMETextChanges.Length() > textTransaction.Capacity()) {
+        textTransaction.SetCapacity(mIMETextChanges.Length());
+    }
+
+    mIMETextChangedDuringFlush = false;
+
+    auto shouldAbort = [=] () -> bool {
+        if (!mIMETextChangedDuringFlush) {
+            return false;
+        }
+        // A query event could have triggered more text changes to come in, as
+        // indicated by our flag. If that happens, try flushing IME changes
+        // again.
+        if (aFlags == FLUSH_FLAG_NONE) {
+            FlushIMEChanges(FLUSH_FLAG_RETRY);
+        } else {
+            // Don't retry if already retrying, to avoid infinite loops.
+            __android_log_print(ANDROID_LOG_WARN, "GeckoViewSupport",
+                    "Already retrying IME flush");
+        }
+        return true;
+    };
+
+    for (const IMETextChange &change : mIMETextChanges) {
+        if (change.mStart == change.mOldEnd &&
+                change.mStart == change.mNewEnd) {
+            continue;
+        }
+
+        WidgetQueryContentEvent event(true, eQueryTextContent, &window);
+
+        if (change.mNewEnd != change.mStart) {
+            window.InitEvent(event, nullptr);
+            event.InitForQueryTextContent(change.mStart,
+                                          change.mNewEnd - change.mStart);
+            window.DispatchEvent(&event);
+            NS_ENSURE_TRUE_VOID(event.mSucceeded);
+            NS_ENSURE_TRUE_VOID(e10sEnabled || event.mReply.mContentsRoot == imeRoot.get());
+        }
+
+        if (shouldAbort()) {
+            return;
+        }
+
+        textTransaction.AppendElement(
+                TextRecord{event.mReply.mString, change.mStart,
+                           change.mOldEnd, change.mNewEnd});
+    }
+
+    int32_t selStart = -1;
+    int32_t selEnd = -1;
+
+    if (mIMESelectionChanged) {
+        WidgetQueryContentEvent event(true, eQuerySelectedText, &window);
+        window.InitEvent(event, nullptr);
+        window.DispatchEvent(&event);
+
+        NS_ENSURE_TRUE_VOID(event.mSucceeded);
+        NS_ENSURE_TRUE_VOID(e10sEnabled || event.mReply.mContentsRoot == imeRoot.get());
+
+        if (shouldAbort()) {
+            return;
+        }
+
+        selStart = int32_t(event.GetSelectionStart());
+        selEnd = int32_t(event.GetSelectionEnd());
+    }
+
+    JNIEnv* const env = jni::GetGeckoThreadEnv();
+    auto flushOnException = [=] () -> bool {
+        if (!env->ExceptionCheck()) {
+            return false;
+        }
+        if (aFlags != FLUSH_FLAG_RECOVER) {
+            // First time seeing an exception; try flushing text.
+            env->ExceptionClear();
+            __android_log_print(ANDROID_LOG_WARN, "GeckoViewSupport",
+                    "Recovering from IME exception");
+            FlushIMEText(FLUSH_FLAG_RECOVER);
+        } else {
+            // Give up because we've already tried.
+            MOZ_CATCH_JNI_EXCEPTION(env);
+        }
+        return true;
+    };
+
+    // Commit the text change and selection change transaction.
+    mIMETextChanges.Clear();
+
+    for (const TextRecord& record : textTransaction) {
+        mEditable->OnTextChange(record.text, record.start,
+                                record.oldEnd, record.newEnd);
+        if (flushOnException()) {
+            return;
+        }
+    }
+
+    if (mIMESelectionChanged) {
+        mIMESelectionChanged = false;
+        mEditable->OnSelectionChange(selStart, selEnd);
+        flushOnException();
+    }
+}
+
+void
+nsWindow::GeckoViewSupport::FlushIMEText(FlushChangesFlag aFlags)
+{
+    // Notify Java of the newly focused content
+    mIMETextChanges.Clear();
+    mIMESelectionChanged = true;
+
+    // Use 'INT32_MAX / 2' here because subsequent text changes might combine
+    // with this text change, and overflow might occur if we just use
+    // INT32_MAX.
+    IMENotification notification(NOTIFY_IME_OF_TEXT_CHANGE);
+    notification.mTextChangeData.mStartOffset = 0;
+    notification.mTextChangeData.mRemovedEndOffset = INT32_MAX / 2;
+    notification.mTextChangeData.mAddedEndOffset = INT32_MAX / 2;
+    NotifyIME(notification);
+
+    FlushIMEChanges(aFlags);
+}
+
+static jni::ObjectArray::LocalRef
+ConvertRectArrayToJavaRectFArray(JNIEnv* aJNIEnv, const nsTArray<LayoutDeviceIntRect>& aRects, const LayoutDeviceIntPoint& aOffset, const CSSToLayoutDeviceScale aScale)
 {
-    // Android's event time is SystemClock.uptimeMillis that is counted in ms
-    // since OS was booted.
-    // (https://developer.android.com/reference/android/os/SystemClock.html)
-    // and this SystemClock.uptimeMillis uses SYSTEM_TIME_MONOTONIC.
-    // Our posix implemententaion of TimeStamp::Now uses SYSTEM_TIME_MONOTONIC
-    //  too. Due to same implementation, we can use this via FromSystemTime.
-    int64_t tick =
-        BaseTimeDurationPlatformUtils::TicksFromMilliseconds(aEventTime);
-    return TimeStamp::FromSystemTime(tick);
+    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) {
+            return;
+        }
+
+        mEditable->NotifyIME(aNotification);
+    });
+}
+
+bool
+nsWindow::GeckoViewSupport::NotifyIME(const IMENotification& aIMENotification)
+{
+    MOZ_ASSERT(mEditable);
+
+    switch (aIMENotification.mMessage) {
+        case REQUEST_TO_COMMIT_COMPOSITION: {
+            ALOGIME("IME: REQUEST_TO_COMMIT_COMPOSITION");
+
+            window.RemoveIMEComposition();
+
+            AsyncNotifyIME(GeckoEditableListener::
+                           NOTIFY_IME_TO_COMMIT_COMPOSITION);
+            return true;
+        }
+
+        case REQUEST_TO_CANCEL_COMPOSITION: {
+            ALOGIME("IME: REQUEST_TO_CANCEL_COMPOSITION");
+
+            window.RemoveIMEComposition(CANCEL_IME_COMPOSITION);
+
+            AsyncNotifyIME(GeckoEditableListener::
+                           NOTIFY_IME_TO_CANCEL_COMPOSITION);
+            return true;
+        }
+
+        case NOTIFY_IME_OF_FOCUS: {
+            ALOGIME("IME: NOTIFY_IME_OF_FOCUS");
+            // Keep a strong reference to the window to keep 'this' alive.
+            RefPtr<nsWindow> window(&this->window);
+
+            // Post an event because we have to flush the text before sending a
+            // focus event, and we may not be able to flush text during the
+            // NotifyIME call.
+            nsAppShell::PostEvent([this, window] {
+                --mIMEMaskEventsCount;
+                if (mIMEMaskEventsCount || window->Destroyed()) {
+                    return;
+                }
+
+                FlushIMEText();
+
+                // IME will call requestCursorUpdates after getting context.
+                // So reset cursor update mode before getting context.
+                mIMEMonitorCursor = false;
+
+                MOZ_ASSERT(mEditable);
+                mEditable->NotifyIME(GeckoEditableListener::NOTIFY_IME_OF_FOCUS);
+            });
+            return true;
+        }
+
+        case NOTIFY_IME_OF_BLUR: {
+            ALOGIME("IME: NOTIFY_IME_OF_BLUR");
+
+            if (!mIMEMaskEventsCount) {
+                mEditable->NotifyIME(GeckoEditableListener::NOTIFY_IME_OF_BLUR);
+            }
+
+            // Mask events because we lost focus. Unmask on the next focus.
+            mIMEMaskEventsCount++;
+            return true;
+        }
+
+        case NOTIFY_IME_OF_SELECTION_CHANGE: {
+            ALOGIME("IME: NOTIFY_IME_OF_SELECTION_CHANGE");
+
+            PostFlushIMEChanges();
+            mIMESelectionChanged = true;
+            return true;
+        }
+
+        case NOTIFY_IME_OF_TEXT_CHANGE: {
+            ALOGIME("IME: NotifyIMEOfTextChange: s=%d, oe=%d, ne=%d",
+                    aIMENotification.mTextChangeData.mStartOffset,
+                    aIMENotification.mTextChangeData.mRemovedEndOffset,
+                    aIMENotification.mTextChangeData.mAddedEndOffset);
+
+            /* 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)
+{
+    MOZ_ASSERT(mEditable);
+
+    ALOGIME("IME: SetInputContext: s=0x%X, 0x%X, action=0x%X, 0x%X",
+            aContext.mIMEState.mEnabled, aContext.mIMEState.mOpen,
+            aAction.mCause, aAction.mFocusChange);
+
+    // Ensure that opening the virtual keyboard is allowed for this specific
+    // InputContext depending on the content.ime.strict.policy pref
+    if (aContext.mIMEState.mEnabled != IMEState::DISABLED &&
+        aContext.mIMEState.mEnabled != IMEState::PLUGIN &&
+        Preferences::GetBool("content.ime.strict_policy", false) &&
+        !aAction.ContentGotFocusByTrustedCause() &&
+        !aAction.UserMightRequestOpenVKB()) {
+        return;
+    }
+
+    IMEState::Enabled enabled = aContext.mIMEState.mEnabled;
+
+    // Only show the virtual keyboard for plugins if mOpen is set appropriately.
+    // This avoids showing it whenever a plugin is focused. Bug 747492
+    if (aContext.mIMEState.mEnabled == IMEState::PLUGIN &&
+        aContext.mIMEState.mOpen != IMEState::OPEN) {
+        enabled = IMEState::DISABLED;
+    }
+
+    mInputContext = aContext;
+    mInputContext.mIMEState.mEnabled = enabled;
+
+    if (enabled == IMEState::ENABLED && aAction.UserMightRequestOpenVKB()) {
+        // Don't reset keyboard when we should simply open the vkb
+        mEditable->NotifyIME(GeckoEditableListener::NOTIFY_IME_OPEN_VKB);
+        return;
+    }
+
+    if (mIMEUpdatingContext) {
+        return;
+    }
+
+    // Keep a strong reference to the window to keep 'this' alive.
+    RefPtr<nsWindow> window(&this->window);
+    mIMEUpdatingContext = true;
+
+    nsAppShell::PostEvent([this, window] {
+        mIMEUpdatingContext = false;
+        if (window->Destroyed()) {
+            return;
+        }
+        MOZ_ASSERT(mEditable);
+        mEditable->NotifyIMEContext(mInputContext.mIMEState.mEnabled,
+                                    mInputContext.mHTMLInputType,
+                                    mInputContext.mHTMLInputInputmode,
+                                    mInputContext.mActionHint);
+    });
+}
+
+InputContext
+nsWindow::GeckoViewSupport::GetInputContext()
+{
+    InputContext context = mInputContext;
+    context.mIMEState.mOpen = IMEState::OPEN_STATE_NOT_SUPPORTED;
+    return context;
+}
+
+void
+nsWindow::GeckoViewSupport::OnImeSynchronize()
+{
+    if (!mIMEMaskEventsCount) {
+        FlushIMEChanges();
+    }
+    mEditable->NotifyIME(GeckoEditableListener::NOTIFY_IME_REPLY_EVENT);
+}
+
+void
+nsWindow::GeckoViewSupport::OnImeReplaceText(int32_t aStart, int32_t aEnd,
+                                             jni::String::Param aText)
+{
+    AutoIMESynchronize as(this);
+
+    if (mIMEMaskEventsCount > 0) {
+        // Not focused; still reply to events, but don't do anything else.
+        return;
+    }
+
+    /*
+        Replace text in Gecko thread from aStart to aEnd with the string text.
+    */
+    RefPtr<nsWindow> kungFuDeathGrip(&window);
+    nsString string(aText->ToString());
+
+    const auto composition(window.GetIMEComposition());
+    MOZ_ASSERT(!composition || !composition->IsEditorHandlingEvent());
+
+    const bool composing = !mIMERanges->IsEmpty();
+
+    if (!mIMEKeyEvents.IsEmpty() || !composition ||
+        uint32_t(aStart) != composition->NativeOffsetOfStartComposition() ||
+        uint32_t(aEnd) != composition->NativeOffsetOfStartComposition() +
+                          composition->String().Length())
+    {
+        // Only start a new composition if we have key events,
+        // if we don't have an existing composition, or
+        // the replaced text does not match our composition.
+        window.RemoveIMEComposition();
+
+        {
+            // Use text selection to set target postion(s) for
+            // insert, or replace, of text.
+            WidgetSelectionEvent event(true, eSetSelection, &window);
+            window.InitEvent(event, nullptr);
+            event.mOffset = uint32_t(aStart);
+            event.mLength = uint32_t(aEnd - aStart);
+            event.mExpandToClusterBoundary = false;
+            event.mReason = nsISelectionListener::IME_REASON;
+            window.DispatchEvent(&event);
+        }
+
+        if (!mIMEKeyEvents.IsEmpty()) {
+            nsEventStatus status;
+            for (uint32_t i = 0; i < mIMEKeyEvents.Length(); i++) {
+                const auto event = static_cast<WidgetGUIEvent*>(
+                        mIMEKeyEvents[i].get());
+                if (event->mMessage == eKeyPress &&
+                        status == nsEventStatus_eConsumeNoDefault) {
+                    MOZ_ASSERT(i > 0 &&
+                            mIMEKeyEvents[i - 1]->mMessage == eKeyDown);
+                    // The previous key down event resulted in eConsumeNoDefault
+                    // so we should not dispatch the current key press event.
+                    continue;
+                }
+                // widget for duplicated events is initially nullptr.
+                event->mWidget = &window;
+                window.DispatchEvent(event, status);
+            }
+            mIMEKeyEvents.Clear();
+            return;
+        }
+
+        if (aStart != aEnd) {
+            // Perform a deletion first.
+            WidgetContentCommandEvent event(
+                    true, eContentCommandDelete, &window);
+            window.InitEvent(event, nullptr);
+            window.DispatchEvent(&event);
+        }
+
+        // Start a composition if we're not just performing a deletion.
+        if (composing || !string.IsEmpty()) {
+            WidgetCompositionEvent event(true, eCompositionStart, &window);
+            window.InitEvent(event, nullptr);
+            window.DispatchEvent(&event);
+        }
+
+    } else if (composition->String().Equals(string)) {
+        /* If the new text is the same as the existing composition text,
+         * the NS_COMPOSITION_CHANGE event does not generate a text
+         * change notification. However, the Java side still expects
+         * one, so we manually generate a notification. */
+        IMETextChange dummyChange;
+        dummyChange.mStart = aStart;
+        dummyChange.mOldEnd = dummyChange.mNewEnd = aEnd;
+        AddIMETextChange(dummyChange);
+    }
+
+    // Check composition again because previous events may have destroyed our
+    // composition; in which case we should just skip the next event.
+    if (window.GetIMEComposition()) {
+        WidgetCompositionEvent event(true, eCompositionChange, &window);
+        window.InitEvent(event, nullptr);
+        event.mData = string;
+
+        if (composing) {
+            event.mRanges = new TextRangeArray();
+            mIMERanges.swap(event.mRanges);
+        } else {
+            event.mMessage = eCompositionCommit;
+        }
+
+        window.DispatchEvent(&event);
+
+    } else if (composing) {
+        // Ensure IME ranges are empty.
+        mIMERanges->Clear();
+    }
+
+    if (mInputContext.mMayBeIMEUnaware) {
+        SendIMEDummyKeyEvents();
+    }
+}
+
+void
+nsWindow::GeckoViewSupport::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)
+{
+    if (mIMEMaskEventsCount > 0) {
+        // Not focused.
+        return;
+    }
+
+    TextRange range;
+    range.mStartOffset = aStart;
+    range.mEndOffset = aEnd;
+    range.mRangeType = ToTextRangeType(aRangeType);
+    range.mRangeStyle.mDefinedStyles = aRangeStyle;
+    range.mRangeStyle.mLineStyle = aRangeLineStyle;
+    range.mRangeStyle.mIsBoldLine = aRangeBoldLine;
+    range.mRangeStyle.mForegroundColor =
+            ConvertAndroidColor(uint32_t(aRangeForeColor));
+    range.mRangeStyle.mBackgroundColor =
+            ConvertAndroidColor(uint32_t(aRangeBackColor));
+    range.mRangeStyle.mUnderlineColor =
+            ConvertAndroidColor(uint32_t(aRangeLineColor));
+    mIMERanges->AppendElement(range);
+}
+
+void
+nsWindow::GeckoViewSupport::OnImeUpdateComposition(int32_t aStart, int32_t aEnd)
+{
+    if (mIMEMaskEventsCount > 0) {
+        // Not focused.
+        return;
+    }
+
+    RefPtr<nsWindow> kungFuDeathGrip(&window);
+
+    // A composition with no ranges means we want to set the selection.
+    if (mIMERanges->IsEmpty()) {
+        MOZ_ASSERT(aStart >= 0 && aEnd >= 0);
+        window.RemoveIMEComposition();
+
+        WidgetSelectionEvent selEvent(true, eSetSelection, &window);
+        window.InitEvent(selEvent, nullptr);
+
+        selEvent.mOffset = std::min(aStart, aEnd);
+        selEvent.mLength = std::max(aStart, aEnd) - selEvent.mOffset;
+        selEvent.mReversed = aStart > aEnd;
+        selEvent.mExpandToClusterBoundary = false;
+
+        window.DispatchEvent(&selEvent);
+        return;
+    }
+
+    /*
+        Update the composition from aStart to aEnd using
+          information from added ranges. This is only used for
+          visual indication and does not affect the text content.
+          Only the offsets are specified and not the text content
+          to eliminate the possibility of this event altering the
+          text content unintentionally.
+    */
+    const auto composition(window.GetIMEComposition());
+    MOZ_ASSERT(!composition || !composition->IsEditorHandlingEvent());
+
+    WidgetCompositionEvent event(true, eCompositionChange, &window);
+    window.InitEvent(event, nullptr);
+
+    event.mRanges = new TextRangeArray();
+    mIMERanges.swap(event.mRanges);
+
+    if (!composition ||
+        uint32_t(aStart) != composition->NativeOffsetOfStartComposition() ||
+        uint32_t(aEnd) != composition->NativeOffsetOfStartComposition() +
+                          composition->String().Length())
+    {
+        // Only start new composition if we don't have an existing one,
+        // or if the existing composition doesn't match the new one.
+        window.RemoveIMEComposition();
+
+        {
+            WidgetSelectionEvent event(true, eSetSelection, &window);
+            window.InitEvent(event, nullptr);
+            event.mOffset = uint32_t(aStart);
+            event.mLength = uint32_t(aEnd - aStart);
+            event.mExpandToClusterBoundary = false;
+            event.mReason = nsISelectionListener::IME_REASON;
+            window.DispatchEvent(&event);
+        }
+
+        {
+            WidgetQueryContentEvent queryEvent(true, eQuerySelectedText,
+                                               &window);
+            window.InitEvent(queryEvent, nullptr);
+            window.DispatchEvent(&queryEvent);
+            MOZ_ASSERT(queryEvent.mSucceeded);
+            event.mData = queryEvent.mReply.mString;
+        }
+
+        {
+            WidgetCompositionEvent event(true, eCompositionStart, &window);
+            window.InitEvent(event, nullptr);
+            window.DispatchEvent(&event);
+        }
+
+    } else {
+        // If the new composition matches the existing composition,
+        // reuse the old composition.
+        event.mData = composition->String();
+    }
+
+#ifdef DEBUG_ANDROID_IME
+    const NS_ConvertUTF16toUTF8 data(event.mData);
+    const char* text = data.get();
+    ALOGIME("IME: IME_SET_TEXT: text=\"%s\", length=%u, range=%u",
+            text, event.mData.Length(), event.mRanges->Length());
+#endif // DEBUG_ANDROID_IME
+
+    // 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);
   }
 }
 
-TextEventDispatcherListener*
-nsWindow::GetNativeTextEventDispatcherListener()
+nsresult
+nsWindow::NotifyIMEInternal(const IMENotification& aIMENotification)
 {
-    nsWindow* top = FindTopLevel();
-    MOZ_ASSERT(top);
-
-    if (!top->mEditableSupport) {
+    MOZ_ASSERT(this == FindTopLevel());
+
+    if (!mGeckoViewSupport) {
         // Non-GeckoView windows don't support IME operations.
-        return nullptr;
+        return NS_ERROR_NOT_AVAILABLE;
     }
-    return top->mEditableSupport;
+
+    if (mGeckoViewSupport->NotifyIME(aIMENotification)) {
+        return NS_OK;
+    }
+    return NS_ERROR_NOT_IMPLEMENTED;
 }
 
 void
 nsWindow::SetInputContext(const InputContext& aContext,
                           const InputContextAction& aAction)
 {
     nsWindow* top = FindTopLevel();
     MOZ_ASSERT(top);
 
-    if (!top->mEditableSupport) {
+    if (!top->mGeckoViewSupport) {
         // Non-GeckoView windows don't support IME operations.
         return;
     }
 
     // We are using an IME event later to notify Java, and the IME event
     // will be processed by the top window. Therefore, to ensure the
     // IME event uses the correct mInputContext, we need to let the top
     // window process SetInputContext
-    top->mEditableSupport->SetInputContext(aContext, aAction);
+    top->mGeckoViewSupport->SetInputContext(aContext, aAction);
 }
 
 InputContext
 nsWindow::GetInputContext()
 {
     nsWindow* top = FindTopLevel();
     MOZ_ASSERT(top);
 
-    if (!top->mEditableSupport) {
+    if (!top->mGeckoViewSupport) {
         // Non-GeckoView windows don't support IME operations.
         return InputContext();
     }
 
     // We let the top window process SetInputContext,
     // so we should let it process GetInputContext as well.
-    return top->mEditableSupport->GetInputContext();
+    return top->mGeckoViewSupport->GetInputContext();
 }
 
 nsIMEUpdatePreference
 nsWindow::GetIMEUpdatePreference()
 {
-    nsWindow* top = FindTopLevel();
-    MOZ_ASSERT(top);
-
-    if (!top->mEditableSupport) {
-        // Non-GeckoView windows don't support IME operations.
-        return nsIMEUpdatePreference();
+    // While a plugin has focus, nsWindow for Android doesn't need any
+    // notifications.
+    if (GetInputContext().mIMEState.mEnabled == IMEState::PLUGIN) {
+      return nsIMEUpdatePreference();
     }
-
-    return top->mEditableSupport->GetIMEUpdatePreference();
+    return nsIMEUpdatePreference(nsIMEUpdatePreference::NOTIFY_TEXT_CHANGE);
 }
 
 nsresult
 nsWindow::SynthesizeNativeTouchPoint(uint32_t aPointerId,
                                      TouchPointerState aPointerState,
                                      LayoutDeviceIntPoint aPoint,
                                      double aPointerPressure,
                                      uint32_t aPointerOrientation,
--- a/widget/android/nsWindow.h
+++ b/widget/android/nsWindow.h
@@ -18,27 +18,24 @@
 #include "mozilla/Mutex.h"
 #include "mozilla/StaticPtr.h"
 #include "mozilla/TextRange.h"
 #include "mozilla/UniquePtr.h"
 
 struct ANPEvent;
 
 namespace mozilla {
+    class TextComposition;
     class WidgetTouchEvent;
 
     namespace layers {
         class CompositorBridgeChild;
         class LayerManager;
         class APZCTreeManager;
     }
-
-    namespace widget {
-        class GeckoEditableSupport;
-    } // namespace widget
 }
 
 class nsWindow : public nsBaseWidget
 {
 private:
     virtual ~nsWindow();
 
 public:
@@ -56,17 +53,16 @@ private:
 
     // An Event subclass that guards against stale events.
     template<typename Lambda,
              bool IsStatic = Lambda::isStatic,
              typename InstanceType = typename Lambda::ThisArgType,
              class Impl = typename Lambda::TargetClass>
     class WindowEvent;
 
-public:
     // Smart pointer for holding a pointer back to the nsWindow inside a native
     // object class. The nsWindow pointer is automatically cleared when the
     // nsWindow is destroyed, and a WindowPtr<Impl>::Locked class is provided
     // for thread-safe access to the nsWindow pointer off of the Gecko thread.
     template<class Impl> class WindowPtr;
 
     // Smart pointer for holding a pointer to a native object class. The
     // pointer is automatically cleared when the object is destroyed.
@@ -95,69 +91,16 @@ public:
 
         Impl* operator->() const { return operator Impl*(); }
 
         template<class Instance, typename... Args>
         void Attach(Instance aInstance, nsWindow* aWindow, Args&&... aArgs);
         void Detach();
     };
 
-    template<class Impl>
-    class WindowPtr final
-    {
-        friend NativePtr<Impl>;
-
-        NativePtr<Impl>* mPtr;
-        nsWindow* mWindow;
-        mozilla::Mutex mWindowLock;
-
-    public:
-        class Locked final : private mozilla::MutexAutoLock
-        {
-            nsWindow* const mWindow;
-
-        public:
-            Locked(WindowPtr<Impl>& aPtr)
-                : mozilla::MutexAutoLock(aPtr.mWindowLock)
-                , mWindow(aPtr.mWindow)
-            {}
-
-            operator nsWindow*() const { return mWindow; }
-            nsWindow* operator->() const { return mWindow; }
-        };
-
-        WindowPtr(NativePtr<Impl>* aPtr, nsWindow* aWindow)
-            : mPtr(aPtr)
-            , mWindow(aWindow)
-            , mWindowLock(NativePtr<Impl>::sName)
-        {
-            MOZ_ASSERT(NS_IsMainThread());
-            mPtr->mPtr = this;
-        }
-
-        ~WindowPtr()
-        {
-            MOZ_ASSERT(NS_IsMainThread());
-            if (!mPtr) {
-                return;
-            }
-            mPtr->mPtr = nullptr;
-            mPtr->mImpl = nullptr;
-        }
-
-        operator nsWindow*() const
-        {
-            MOZ_ASSERT(NS_IsMainThread());
-            return mWindow;
-        }
-
-        nsWindow* operator->() const { return operator nsWindow*(); }
-    };
-
-private:
     class AndroidView final : public nsIAndroidView
     {
         virtual ~AndroidView() {}
 
     public:
         const RefPtr<mozilla::widget::EventDispatcher> mEventDispatcher{
             new mozilla::widget::EventDispatcher()};
 
@@ -178,36 +121,29 @@ private:
     // Owned by the Java LayerView instance.
     NativePtr<LayerViewSupport> mLayerViewSupport;
 
     class NPZCSupport;
     // Object that implements native NativePanZoomController calls.
     // Owned by the Java NativePanZoomController instance.
     NativePtr<NPZCSupport> mNPZCSupport;
 
-    // Object that implements native GeckoEditable calls.
-    // Strong referenced by the Java instance.
-    NativePtr<mozilla::widget::GeckoEditableSupport> mEditableSupport;
-
     class GeckoViewSupport;
     // Object that implements native GeckoView calls and associated states.
     // nullptr for nsWindows that were not opened from GeckoView.
     // Because other objects get destroyed in the mGeckOViewSupport destructor,
     // keep it last in the list, so its destructor is called first.
     mozilla::UniquePtr<GeckoViewSupport> mGeckoViewSupport;
 
     // Class that implements native PresentationMediaPlayerManager calls.
     class PMPMSupport;
 
 public:
     static nsWindow* TopWindow();
 
-    static mozilla::Modifiers GetModifiers(int32_t aMetaState);
-    static mozilla::TimeStamp GetEventTimeStamp(int64_t aEventTime);
-
     void OnSizeChanged(const mozilla::gfx::IntSize& aSize);
 
     void InitEvent(mozilla::WidgetGUIEvent& event,
                    LayoutDeviceIntPoint* aPoint = 0);
 
     void UpdateOverscrollVelocity(const float aX, const float aY);
     void UpdateOverscrollOffset(const float aX, const float aY);
     void SetScrollingRootContent(const bool isRootContent);
@@ -261,18 +197,16 @@ public:
     virtual void SetCursor(nsCursor aCursor) override {}
     virtual nsresult SetCursor(imgIContainer* aCursor, uint32_t aHotspotX,
                                uint32_t aHotspotY) override { return NS_ERROR_NOT_IMPLEMENTED; }
     void* GetNativeData(uint32_t aDataType) override;
     void SetNativeData(uint32_t aDataType, uintptr_t aVal) override;
     virtual nsresult SetTitle(const nsAString& aTitle) override { return NS_OK; }
     virtual MOZ_MUST_USE nsresult GetAttention(int32_t aCycleCount) override { return NS_ERROR_NOT_IMPLEMENTED; }
 
-
-    TextEventDispatcherListener* GetNativeTextEventDispatcherListener() override;
     virtual void SetInputContext(const InputContext& aContext,
                                  const InputContextAction& aAction) override;
     virtual InputContext GetInputContext() override;
     virtual nsIMEUpdatePreference GetIMEUpdatePreference() override;
 
     void SetSelectionDragState(bool aState);
     LayerManager* GetLayerManager(PLayerTransactionChild* aShadowManager = nullptr,
                                   LayersBackend aBackendHint = mozilla::layers::LayersBackend::LAYERS_NONE,
@@ -305,42 +239,52 @@ public:
                                         nsIObserver* aObserver) override;
     nsresult SynthesizeNativeMouseMove(LayoutDeviceIntPoint aPoint,
                                        nsIObserver* aObserver) override;
 
     CompositorBridgeChild* GetCompositorBridgeChild() const;
 
     mozilla::jni::DependentRef<mozilla::java::GeckoLayerClient> GetLayerClient();
 
-    // Call this function when the users activity is the direct cause of an
-    // event (like a keypress or mouse click).
-    void UserActivity();
-
 protected:
     void BringToFront();
     nsWindow *FindTopLevel();
     bool IsTopLevel();
 
+    RefPtr<mozilla::TextComposition> GetIMEComposition();
+    enum RemoveIMECompositionFlag {
+        CANCEL_IME_COMPOSITION,
+        COMMIT_IME_COMPOSITION
+    };
+    void RemoveIMEComposition(RemoveIMECompositionFlag aFlag = COMMIT_IME_COMPOSITION);
+
     void ConfigureAPZControllerThread() override;
     void DispatchHitTest(const mozilla::WidgetTouchEvent& aEvent);
 
     already_AddRefed<GeckoContentController> CreateRootContentController() override;
 
+    // Call this function when the users activity is the direct cause of an
+    // event (like a keypress or mouse click).
+    void UserActivity();
+
     bool mIsVisible;
     nsTArray<nsWindow*> mChildren;
     nsWindow* mParent;
 
     double mStartDist;
     double mLastDist;
 
     nsCOMPtr<nsIIdleServiceInternal> mIdleService;
 
     bool mAwaitingFullScreen;
     bool mIsFullScreen;
 
+    virtual nsresult NotifyIMEInternal(
+                         const IMENotification& aIMENotification) override;
+
     bool UseExternalCompositingSurface() const override {
       return true;
     }
 
     static void DumpWindows();
     static void DumpWindows(const nsTArray<nsWindow*>& wins, int indent = 0);
     static void LogWindow(nsWindow *win, int index, int indent);
 
--- a/widget/nsBaseWidget.cpp
+++ b/widget/nsBaseWidget.cpp
@@ -1218,18 +1218,17 @@ nsBaseWidget::DispatchInputEvent(WidgetI
     }
     WidgetWheelEvent* wheelEvent = aEvent->AsWheelEvent();
     if (wheelEvent) {
       RefPtr<Runnable> r =
         new DispatchWheelInputOnControllerThread(*wheelEvent, mAPZC, this);
       APZThreadUtils::RunOnControllerThread(r.forget());
       return nsEventStatus_eConsumeDoDefault;
     }
-    // Allow dispatching keyboard events on Gecko thread.
-    MOZ_ASSERT(aEvent->AsKeyboardEvent());
+    MOZ_CRASH();
   }
 
   nsEventStatus status;
   DispatchEvent(aEvent, status);
   return status;
 }
 
 void