Bug 1211704 - Convert IME event handler in nsWindow to native calls; r=esawin
authorJim Chen <nchen@mozilla.com>
Thu, 22 Oct 2015 17:45:47 -0400
changeset 304287 0855074944cd6cd317309f147b69cc0bdcafa31a
parent 304286 e1a8d414c6ea821e72291f01421e4efcbacb84b6
child 304288 5200b0f4db3f02acf75da64e8e6dbb2cc9938e6b
push id1001
push userraliiev@mozilla.com
push dateMon, 18 Jan 2016 19:06:03 +0000
treeherdermozilla-release@8b89261f3ac4 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersesawin
bugs1211704
milestone44.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1211704 - Convert IME event handler in nsWindow to native calls; r=esawin Move the IME event handler implementation in nsWindow::OnIMEEvent to individual native calls in nsWindow::Natives. This patch also moves most member variables and helper functions related to IME to inside nsWindow::Natives. This has the benefit of better organization and saves some memory because only the top-level nsWindow now keeps IME states. GetIMEComposition and RemoveIMEComposition are kept inside nsWindow because they are not strictly related to IME events, and they are used by some other event handlers in nsWindow.
widget/android/nsWindow.cpp
widget/android/nsWindow.h
--- a/widget/android/nsWindow.cpp
+++ b/widget/android/nsWindow.cpp
@@ -216,17 +216,25 @@ public:
             // event loop is not running yet. Skip the event loop here so we
             // can get a head start on opening our window.
             return aCall();
         }
         return nsAppShell::gAppShell->PostEvent(mozilla::MakeUnique<
                 WindowEvent<Functor>>(mozilla::Move(aCall)));
     }
 
-    Natives(nsWindow* aWindow) : window(*aWindow) {}
+    Natives(nsWindow* aWindow)
+        : window(*aWindow)
+        , mIMERanges(new TextRangeArray())
+        , mIMEMaskEventsCount(1) // Mask IME events since there's no focus yet
+        , mIMEUpdatingContext(false)
+        , mIMESelectionChanged(false)
+        , mIMEMaskSelectionUpdate(false)
+    {}
+
     ~Natives();
 
     /**
      * GeckoView methods
      */
     using Base::DisposeNative;
 
     // Create and attach a window.
@@ -244,48 +252,104 @@ public:
     }
 
     // Close and destroy the nsWindow.
     void Close();
 
     /**
      * 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
+        * Java controls the selection, and Gecko shadows the Java selection
+           through 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 ==
+                           mozilla::widget::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;
+    mozilla::widget::GeckoEditable::GlobalRef mEditable;
+    nsAutoTArray<mozilla::UniquePtr<mozilla::WidgetEvent>, 8> mIMEKeyEvents;
+    nsAutoTArray<IMETextChange, 4> mIMETextChanges;
+    InputContext mInputContext;
+    RefPtr<mozilla::TextRangeArray> mIMERanges;
+    int32_t mIMEMaskEventsCount; // Mask events when > 0
+    bool mIMEUpdatingContext;
+    bool mIMESelectionChanged;
+    bool mIMEMaskSelectionUpdate;
+
+    void SendIMEDummyKeyEvents();
+    void AddIMETextChange(const IMETextChange& aChange);
+    void PostFlushIMEChanges();
+    void FlushIMEChanges();
+
+public:
+    bool NotifyIME(const IMENotification& aIMENotification);
+    void SetInputContext(const InputContext& aContext,
+                         const InputContextAction& aAction);
+    InputContext GetInputContext();
+
     // 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);
 
     // Synchronize Gecko thread with the InputConnection thread.
     void OnImeSynchronize();
 
     // Acknowledge focus change and send new text and selection.
     void OnImeAcknowledgeFocus();
 
     // Replace a range of text with new text.
-    void OnImeReplaceText(int32_t start, int32_t end,
-                          jni::String::Param text, bool composing);
+    void OnImeReplaceText(int32_t aStart, int32_t aEnd,
+                          jni::String::Param aText, bool aComposing);
 
     // Set selection to a certain range.
-    void OnImeSetSelection(int32_t start, int32_t end);
+    void OnImeSetSelection(int32_t aStart, int32_t aEnd);
 
     // Remove any active composition.
     void OnImeRemoveComposition();
 
     // Add styling for a range within the active composition.
-    void OnImeAddCompositionRange(int32_t start, int32_t end, int32_t rangeType,
-                                  int32_t rangeStyle, int32_t rangeLineStyle,
-                                  bool rangeBoldLine, int32_t rangeForeColor,
-                                  int32_t rangeBackColor,
-                                  int32_t rangeLineColor);
+    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 start, int32_t end);
+    void OnImeUpdateComposition(int32_t aStart, int32_t aEnd);
 };
 
 nsWindow::Natives::~Natives()
 {
     // Disassociate our GeckoEditable instance with our native object.
     MOZ_ASSERT(mEditable);
     EditableBase::DisposeNative(mEditable);
 }
@@ -301,17 +365,17 @@ nsWindow::Natives::Open(const jni::Class
     PROFILER_LABEL("nsWindow", "Natives::Open",
                    js::ProfileEntry::Category::OTHER);
 
     if (gGeckoViewWindow) {
         // Should have been created the first time.
         MOZ_ASSERT(gGeckoViewWindow->mNatives);
 
         // Associate our previous GeckoEditable with the new GeckoView.
-        gGeckoViewWindow->mEditable->OnViewChange(aView);
+        gGeckoViewWindow->mNatives->mEditable->OnViewChange(aView);
 
         Base::AttachNative(GeckoView::Window::LocalRef(aCls.Env(), aWindow),
                            gGeckoViewWindow->mNatives.get());
         return;
     }
 
     nsCOMPtr<nsIWindowWatcher> ww = do_GetService(NS_WINDOWWATCHER_CONTRACTID);
     MOZ_ASSERT(ww);
@@ -341,26 +405,27 @@ nsWindow::Natives::Open(const jni::Class
     ww->OpenWindow(nullptr, url, "_blank", "chrome,dialog=no,all",
                    args, getter_AddRefs(window));
     MOZ_ASSERT(window);
 
     nsCOMPtr<nsIWidget> widget = WidgetUtils::DOMWindowToWidget(window);
     MOZ_ASSERT(widget);
 
     gGeckoViewWindow = static_cast<nsWindow*>(widget.get());
-    gGeckoViewWindow->mNatives = mozilla::MakeUnique<Natives>(gGeckoViewWindow);
+    UniquePtr<Natives> natives = mozilla::MakeUnique<Natives>(gGeckoViewWindow);
 
     // Create GeckoEditable for the new nsWindow/GeckoView pair.
     GeckoEditable::LocalRef editable = GeckoEditable::New();
-    EditableBase::AttachNative(editable, gGeckoViewWindow->mNatives.get());
+    EditableBase::AttachNative(editable, natives.get());
+    natives->mEditable = editable;
     editable->OnViewChange(aView);
-    gGeckoViewWindow->mEditable = editable;
 
     Base::AttachNative(GeckoView::Window::LocalRef(aCls.Env(), aWindow),
-                       gGeckoViewWindow->mNatives.get());
+                       natives.get());
+    gGeckoViewWindow->mNatives = mozilla::Move(natives);
 }
 
 void
 nsWindow::Natives::Close()
 {
     nsIWidgetListener* const widgetListener = window.mWidgetListener;
 
     if (!widgetListener) {
@@ -420,21 +485,16 @@ nsWindow::DumpWindows(const nsTArray<nsW
         LogWindow(w, i, indent);
         DumpWindows(w->mChildren, indent+1);
     }
 }
 
 nsWindow::nsWindow() :
     mIsVisible(false),
     mParent(nullptr),
-    mIMEMaskSelectionUpdate(false),
-    mIMEMaskEventsCount(1), // Mask IME events since there's no focus yet
-    mIMERanges(new TextRangeArray()),
-    mIMEUpdatingContext(false),
-    mIMESelectionChanged(false),
     mAwaitingFullScreen(false),
     mIsFullScreen(false)
 {
 }
 
 nsWindow::~nsWindow()
 {
     gTopLevelWindows.RemoveElement(this);
@@ -1111,21 +1171,16 @@ nsWindow::OnGlobalAndroidEvent(AndroidGe
             break;
         }
 
         case AndroidGeckoEvent::NATIVE_GESTURE_EVENT: {
             win->OnNativeGestureEvent(ae);
             break;
         }
 
-        case AndroidGeckoEvent::IME_EVENT:
-            gGeckoViewWindow->UserActivity();
-            gGeckoViewWindow->OnIMEEvent(ae);
-            break;
-
         case AndroidGeckoEvent::COMPOSITOR_PAUSE:
             // The compositor gets paused when the app is about to go into the
             // background. While the compositor is paused, we need to ensure that
             // no layer tree updates (from draw events) occur, since the compositor
             // cannot make a GL context current in order to process updates.
             if (sCompositorChild) {
                 sCompositorChild->SendPause();
             }
@@ -1794,17 +1849,17 @@ nsWindow::Natives::OnKeyEvent(int32_t aA
     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.
-        window.mIMEKeyEvents.AppendElement(
+        mIMEKeyEvents.AppendElement(
                 mozilla::UniquePtr<WidgetEvent>(event.Duplicate()));
     } else {
         window.DispatchEvent(&event, status);
     }
 
     if (window.Destroyed() ||
             status == nsEventStatus_eConsumeNoDefault ||
             msg != eKeyDown || IsModifierKey(aKeyCode)) {
@@ -1814,44 +1869,44 @@ nsWindow::Natives::OnKeyEvent(int32_t aA
 
     WidgetKeyboardEvent pressEvent(true, eKeyPress, &window);
     window.InitEvent(pressEvent, nullptr);
     InitKeyEvent(pressEvent, aAction, aKeyCode, aScanCode, aMetaState, aTime,
                  aUnicodeChar, aBaseUnicodeChar, aDomPrintableKeyValue,
                  aRepeatCount, aFlags);
 
     if (aIsSynthesizedImeKey) {
-        window.mIMEKeyEvents.AppendElement(
+        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 argb)
+ConvertAndroidColor(uint32_t aArgb)
 {
-    return NS_RGBA((argb & 0x00ff0000) >> 16,
-                   (argb & 0x0000ff00) >> 8,
-                   (argb & 0x000000ff),
-                   (argb & 0xff000000) >> 24);
+    return NS_RGBA((aArgb & 0x00ff0000) >> 16,
+                   (aArgb & 0x0000ff00) >> 8,
+                   (aArgb & 0x000000ff),
+                   (aArgb & 0xff000000) >> 24);
 }
 
 class AutoIMEMask {
 private:
     bool mOldMask, *mMask;
 public:
-    AutoIMEMask(bool &mask) : mOldMask(mask), mMask(&mask) {
-        mask = true;
+    AutoIMEMask(bool &aMask) : mOldMask(aMask), mMask(&aMask) {
+        aMask = true;
     }
     ~AutoIMEMask() {
         *mMask = mOldMask;
     }
 };
 
 /*
  * Get the current composition object, if any.
@@ -1870,509 +1925,259 @@ void
 nsWindow::RemoveIMEComposition()
 {
     // Remove composition on Gecko side
     if (!GetIMEComposition()) {
         return;
     }
 
     RefPtr<nsWindow> kungFuDeathGrip(this);
-    AutoIMEMask selMask(mIMEMaskSelectionUpdate);
-
     WidgetCompositionEvent compositionCommitEvent(true, eCompositionCommitAsIs,
                                                   this);
     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::SendIMEDummyKeyEvents()
-{
-    WidgetKeyboardEvent downEvent(true, eKeyDown, this);
-    InitEvent(downEvent, nullptr);
-    MOZ_ASSERT(downEvent.keyCode == 0);
-    DispatchEvent(&downEvent);
-
-    WidgetKeyboardEvent upEvent(true, eKeyUp, this);
-    InitEvent(upEvent, nullptr);
-    MOZ_ASSERT(upEvent.keyCode == 0);
-    DispatchEvent(&upEvent);
-}
-
-void
-nsWindow::Natives::OnImeSynchronize()
-{
-    // TODO: implement
-}
-
-void
-nsWindow::Natives::OnImeAcknowledgeFocus()
-{
-    // TODO: implement
-}
-
-void
-nsWindow::Natives::OnImeReplaceText(int32_t start, int32_t end,
-                                    jni::String::Param text, bool composing)
+nsWindow::Natives::SendIMEDummyKeyEvents()
 {
-    // TODO: implement
-}
-
-void
-nsWindow::Natives::OnImeSetSelection(int32_t start, int32_t end)
-{
-    // TODO: implement
-}
-
-void
-nsWindow::Natives::OnImeRemoveComposition()
-{
-    // TODO: implement
-}
-
-void
-nsWindow::Natives::OnImeAddCompositionRange(
-        int32_t start, int32_t end, int32_t rangeType, int32_t rangeStyle,
-        int32_t rangeLineStyle, bool rangeBoldLine, int32_t rangeForeColor,
-        int32_t rangeBackColor, int32_t rangeLineColor)
-{
-    // TODO: implement
-}
-
-void
-nsWindow::Natives::OnImeUpdateComposition(int32_t start, int32_t end)
-{
-    // TODO: implement
+    WidgetKeyboardEvent downEvent(true, eKeyDown, &window);
+    window.InitEvent(downEvent, nullptr);
+    MOZ_ASSERT(downEvent.keyCode == 0);
+    window.DispatchEvent(&downEvent);
+
+    WidgetKeyboardEvent upEvent(true, eKeyUp, &window);
+    window.InitEvent(upEvent, nullptr);
+    MOZ_ASSERT(upEvent.keyCode == 0);
+    window.DispatchEvent(&upEvent);
 }
 
 void
-nsWindow::OnIMEEvent(AndroidGeckoEvent *ae)
+nsWindow::Natives::AddIMETextChange(const IMETextChange& aChange)
 {
-    MOZ_ASSERT(!mIMEMaskSelectionUpdate);
-    /*
-        Rules for managing IME between Gecko and Java:
-
-        * Gecko controls the text content, and Java shadows the Gecko text
-           through text updates
-        * Java controls the selection, and Gecko shadows the Java selection
-           through set selection events
-        * Java controls the composition, and Gecko shadows the Java
-           composition through update composition events
-    */
-    RefPtr<nsWindow> kungFuDeathGrip(this);
-
-    if (ae->Action() == AndroidGeckoEvent::IME_ACKNOWLEDGE_FOCUS) {
-        MOZ_ASSERT(mIMEMaskEventsCount > 0);
-        mIMEMaskEventsCount--;
-        if (!mIMEMaskEventsCount) {
-            // The focusing handshake sequence is complete, and Java is waiting
-            // on Gecko. Now we can notify Java of the newly focused content
-            mIMETextChanges.Clear();
-            mIMESelectionChanged = false;
-            // NotifyIMEOfTextChange also notifies selection
-            // 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 =
-                notification.mTextChangeData.mAddedEndOffset = INT32_MAX / 2;
-            NotifyIMEOfTextChange(notification);
-            FlushIMEChanges();
-        }
-        mEditable->NotifyIME(GeckoEditableListener::NOTIFY_IME_REPLY_EVENT);
-        return;
-
-    } else if (ae->Action() == AndroidGeckoEvent::IME_UPDATE_CONTEXT) {
-        mEditable->NotifyIMEContext(mInputContext.mIMEState.mEnabled,
-                                    mInputContext.mHTMLInputType,
-                                    mInputContext.mHTMLInputInputmode,
-                                    mInputContext.mActionHint);
-        mIMEUpdatingContext = false;
-        return;
-    }
-
-    if (mIMEMaskEventsCount > 0) {
-        // Still reply to events, but don't do anything else
-        if (ae->Action() == AndroidGeckoEvent::IME_SYNCHRONIZE ||
-            ae->Action() == AndroidGeckoEvent::IME_COMPOSE_TEXT ||
-            ae->Action() == AndroidGeckoEvent::IME_REPLACE_TEXT) {
-            mEditable->NotifyIME(GeckoEditableListener::NOTIFY_IME_REPLY_EVENT);
-        }
-        return;
-    }
-
-    switch (ae->Action()) {
-    case AndroidGeckoEvent::IME_FLUSH_CHANGES:
-        {
-            FlushIMEChanges();
-        }
-        break;
-
-    case AndroidGeckoEvent::IME_SYNCHRONIZE:
-        {
-            FlushIMEChanges();
-            mEditable->NotifyIME(GeckoEditableListener::NOTIFY_IME_REPLY_EVENT);
+    mIMETextChanges.AppendElement(aChange);
+
+    // 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;
         }
-        break;
-
-    case AndroidGeckoEvent::IME_REPLACE_TEXT:
-    case AndroidGeckoEvent::IME_COMPOSE_TEXT:
-        {
-            /*
-                Replace text in Gecko thread from ae->Start() to ae->End()
-                  with the string ae->Characters()
-
-                Selection updates are masked so the result of our temporary
-                  selection event is not passed on to Java
-            */
-            AutoIMEMask selMask(mIMEMaskSelectionUpdate);
-            const auto composition(GetIMEComposition());
-            MOZ_ASSERT(!composition || !composition->IsEditorHandlingEvent());
-
-            if (!mIMEKeyEvents.IsEmpty() || !composition ||
-                uint32_t(ae->Start()) !=
-                    composition->NativeOffsetOfStartComposition() ||
-                uint32_t(ae->End()) !=
-                    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.
-                RemoveIMEComposition();
-
-                {
-                    WidgetSelectionEvent event(true, eSetSelection, this);
-                    InitEvent(event, nullptr);
-                    event.mOffset = uint32_t(ae->Start());
-                    event.mLength = uint32_t(ae->End() - ae->Start());
-                    event.mExpandToClusterBoundary = false;
-                    DispatchEvent(&event);
-                }
-
-                if (!mIMEKeyEvents.IsEmpty()) {
-                    nsEventStatus status;
-                    for (uint32_t i = 0; i < mIMEKeyEvents.Length(); i++) {
-                        const auto event = static_cast<WidgetGUIEvent*>(
-                                mIMEKeyEvents[i].get());
-                        // widget for duplicated events is initially nullptr.
-                        event->widget = this;
-                        DispatchEvent(event, status);
-                    }
-                    mIMEKeyEvents.Clear();
-                    FlushIMEChanges();
-                    mEditable->NotifyIME(
-                            GeckoEditableListener::NOTIFY_IME_REPLY_EVENT);
-                    // Break out of the switch block
-                    break;
-                }
-
-                {
-                    WidgetCompositionEvent event(true, eCompositionStart, this);
-                    InitEvent(event, nullptr);
-                    DispatchEvent(&event);
-                }
-
-            } else if (composition->String().Equals(ae->Characters())) {
-                /* 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. */
-                IMEChange dummyChange;
-                dummyChange.mStart = ae->Start();
-                dummyChange.mOldEnd = dummyChange.mNewEnd = ae->End();
-                AddIMETextChange(dummyChange);
-            }
-
-            {
-                WidgetCompositionEvent event(true, eCompositionChange, this);
-                InitEvent(event, nullptr);
-                event.mData = ae->Characters();
-
-                // Include proper text ranges to make the editor happy.
-                TextRange range;
-                range.mStartOffset = 0;
-                range.mEndOffset = event.mData.Length();
-                range.mRangeType = NS_TEXTRANGE_RAWINPUT;
-                event.mRanges = new TextRangeArray();
-                event.mRanges->AppendElement(range);
-
-                DispatchEvent(&event);
-            }
-
-            // Don't end composition when composing text.
-            if (ae->Action() != AndroidGeckoEvent::IME_COMPOSE_TEXT) {
-                WidgetCompositionEvent compositionCommitEvent(
-                        true, eCompositionCommitAsIs, this);
-                InitEvent(compositionCommitEvent, nullptr);
-                DispatchEvent(&compositionCommitEvent);
-            }
-
-            if (mInputContext.mMayBeIMEUnaware) {
-                SendIMEDummyKeyEvents();
-            }
-
-            FlushIMEChanges();
-            mEditable->NotifyIME(GeckoEditableListener::NOTIFY_IME_REPLY_EVENT);
+    }
+
+    // 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;
         }
-        break;
-
-    case AndroidGeckoEvent::IME_SET_SELECTION:
-        {
-            /*
-                Set Gecko selection to ae->Start() to ae->End()
-
-                Selection updates are masked to prevent Java from being
-                  notified of the new selection
-            */
-            AutoIMEMask selMask(mIMEMaskSelectionUpdate);
-            RemoveIMEComposition();
-            WidgetSelectionEvent selEvent(true, eSetSelection, this);
-            InitEvent(selEvent, nullptr);
-
-            int32_t start = ae->Start(), end = ae->End();
-
-            if (start < 0 || end < 0) {
-                WidgetQueryContentEvent event(true, eQuerySelectedText, this);
-                InitEvent(event, nullptr);
-                DispatchEvent(&event);
-                MOZ_ASSERT(event.mSucceeded);
-
-                if (start < 0)
-                    start = int32_t(event.GetSelectionStart());
-                if (end < 0)
-                    end = int32_t(event.GetSelectionEnd());
-            }
-
-            selEvent.mOffset = std::min(start, end);
-            selEvent.mLength = std::max(start, end) - selEvent.mOffset;
-            selEvent.mReversed = start > end;
-            selEvent.mExpandToClusterBoundary = false;
-
-            DispatchEvent(&selEvent);
-        }
-        break;
-    case AndroidGeckoEvent::IME_ADD_COMPOSITION_RANGE:
-        {
-            TextRange range;
-            range.mStartOffset = ae->Start();
-            range.mEndOffset = ae->End();
-            range.mRangeType = ae->RangeType();
-            range.mRangeStyle.mDefinedStyles = ae->RangeStyles();
-            range.mRangeStyle.mLineStyle = ae->RangeLineStyle();
-            range.mRangeStyle.mIsBoldLine = ae->RangeBoldLine();
-            range.mRangeStyle.mForegroundColor =
-                    ConvertAndroidColor(uint32_t(ae->RangeForeColor()));
-            range.mRangeStyle.mBackgroundColor =
-                    ConvertAndroidColor(uint32_t(ae->RangeBackColor()));
-            range.mRangeStyle.mUnderlineColor =
-                    ConvertAndroidColor(uint32_t(ae->RangeLineColor()));
-            mIMERanges->AppendElement(range);
+        // 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;
         }
-        break;
-    case AndroidGeckoEvent::IME_UPDATE_COMPOSITION:
-        {
-            /*
-                Update the composition from ae->Start() to ae->End() 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.
-
-                Selection updates are masked so the result of
-                  temporary events are not passed on to Java
-            */
-            AutoIMEMask selMask(mIMEMaskSelectionUpdate);
-            const auto composition(GetIMEComposition());
-            MOZ_ASSERT(!composition || !composition->IsEditorHandlingEvent());
-
-            WidgetCompositionEvent event(true, eCompositionChange, this);
-            InitEvent(event, nullptr);
-
-            event.mRanges = new TextRangeArray();
-            mIMERanges.swap(event.mRanges);
-
-            if (!composition ||
-                uint32_t(ae->Start()) !=
-                    composition->NativeOffsetOfStartComposition() ||
-                uint32_t(ae->End()) !=
-                    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.
-                RemoveIMEComposition();
-
-                {
-                    WidgetSelectionEvent event(true, eSetSelection, this);
-                    InitEvent(event, nullptr);
-                    event.mOffset = uint32_t(ae->Start());
-                    event.mLength = uint32_t(ae->End() - ae->Start());
-                    event.mExpandToClusterBoundary = false;
-                    DispatchEvent(&event);
-                }
-
-                {
-                    WidgetQueryContentEvent queryEvent(true, eQuerySelectedText,
-                                                       this);
-                    InitEvent(queryEvent, nullptr);
-                    DispatchEvent(&queryEvent);
-                    MOZ_ASSERT(queryEvent.mSucceeded);
-                    event.mData = queryEvent.mReply.mString;
-                }
-
-                {
-                    WidgetCompositionEvent event(true, eCompositionStart, this);
-                    InitEvent(event, nullptr);
-                    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
-
-            DispatchEvent(&event);
-        }
-        break;
-
-    case AndroidGeckoEvent::IME_REMOVE_COMPOSITION:
-        {
-            /*
-             *  Remove any previous composition.  This is only used for
-             *    visual indication and does not affect the text content.
-             *
-             *  Selection updates are masked so the result of
-             *    temporary events are not passed on to Java
-             */
-            AutoIMEMask selMask(mIMEMaskSelectionUpdate);
-            RemoveIMEComposition();
-            mIMERanges->Clear();
-        }
-        break;
+        // 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::UserActivity()
+nsWindow::Natives::PostFlushIMEChanges()
 {
-  if (!mIdleService) {
-    mIdleService = do_GetService("@mozilla.org/widget/idleservice;1");
-  }
-
-  if (mIdleService) {
-    mIdleService->ResetIdleTimeOut(0);
-  }
+    if (!mIMETextChanges.IsEmpty() || mIMESelectionChanged) {
+        // Already posted
+        return;
+    }
+
+    // Keep a strong reference to the window to keep 'this' alive.
+    RefPtr<nsWindow> window(&this->window);
+
+    nsAppShell::gAppShell->PostEvent([this, window] {
+        if (!window->Destroyed()) {
+            FlushIMEChanges();
+        }
+    });
 }
 
-nsresult
-nsWindow::NotifyIMEInternal(const IMENotification& aIMENotification)
+void
+nsWindow::Natives::FlushIMEChanges()
 {
-    MOZ_ASSERT(this == FindTopLevel());
-
-    if (!mEditable) {
-        return NS_ERROR_NOT_AVAILABLE;
+    // 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.
+    MOZ_ALWAYS_TRUE(NS_SUCCEEDED(IMEStateManager::GetFocusSelectionAndRoot(
+            getter_AddRefs(imeSelection), getter_AddRefs(imeRoot))));
+
+    RefPtr<nsWindow> kungFuDeathGrip(&window);
+    window.UserActivity();
+
+    for (uint32_t i = 0; i < mIMETextChanges.Length(); i++) {
+        IMETextChange &change = mIMETextChanges[i];
+
+        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(event.mReply.mContentsRoot == imeRoot.get());
+        }
+
+        mEditable->OnTextChange(event.mReply.mString, change.mStart,
+                                change.mOldEnd, change.mNewEnd);
     }
+    mIMETextChanges.Clear();
+
+    if (mIMESelectionChanged) {
+        WidgetQueryContentEvent event(true, eQuerySelectedText, &window);
+        window.InitEvent(event, nullptr);
+        window.DispatchEvent(&event);
+
+        NS_ENSURE_TRUE_VOID(event.mSucceeded);
+        NS_ENSURE_TRUE_VOID(event.mReply.mContentsRoot == imeRoot.get());
+
+        mEditable->OnSelectionChange(int32_t(event.GetSelectionStart()),
+                                     int32_t(event.GetSelectionEnd()));
+        mIMESelectionChanged = false;
+    }
+}
+
+bool
+nsWindow::Natives::NotifyIME(const IMENotification& aIMENotification)
+{
+    MOZ_ASSERT(mEditable);
 
     switch (aIMENotification.mMessage) {
-        case REQUEST_TO_COMMIT_COMPOSITION:
-            //ALOGIME("IME: REQUEST_TO_COMMIT_COMPOSITION: s=%d", aState);
-            RemoveIMEComposition();
+        case REQUEST_TO_COMMIT_COMPOSITION: {
+            ALOGIME("IME: REQUEST_TO_COMMIT_COMPOSITION");
+            window.RemoveIMEComposition();
             mEditable->NotifyIME(REQUEST_TO_COMMIT_COMPOSITION);
-            return NS_OK;
-
-        case REQUEST_TO_CANCEL_COMPOSITION:
+            return true;
+        }
+
+        case REQUEST_TO_CANCEL_COMPOSITION: {
             ALOGIME("IME: REQUEST_TO_CANCEL_COMPOSITION");
 
             // Cancel composition on Gecko side
-            if (!!GetIMEComposition()) {
-                RefPtr<nsWindow> kungFuDeathGrip(this);
-
+            if (window.GetIMEComposition()) {
+                RefPtr<nsWindow> kungFuDeathGrip(&window);
                 WidgetCompositionEvent compositionCommitEvent(
-                                         true, eCompositionCommit, this);
-                InitEvent(compositionCommitEvent, nullptr);
-                // Dispatch it with empty mData value for canceling the
-                // composition
-                DispatchEvent(&compositionCommitEvent);
+                        true, eCompositionCommit, &window);
+                window.InitEvent(compositionCommitEvent, nullptr);
+                // Dispatch it with empty mData value for canceling composition.
+                window.DispatchEvent(&compositionCommitEvent);
             }
 
             mEditable->NotifyIME(REQUEST_TO_CANCEL_COMPOSITION);
-            return NS_OK;
-
-        case NOTIFY_IME_OF_FOCUS:
+            return true;
+        }
+
+        case NOTIFY_IME_OF_FOCUS: {
             ALOGIME("IME: NOTIFY_IME_OF_FOCUS");
             mEditable->NotifyIME(NOTIFY_IME_OF_FOCUS);
-            return NS_OK;
-
-        case NOTIFY_IME_OF_BLUR:
+            return true;
+        }
+
+        case NOTIFY_IME_OF_BLUR: {
             ALOGIME("IME: NOTIFY_IME_OF_BLUR");
 
             // Mask events because we lost focus. On the next focus event,
             // Gecko will notify Java, and Java will send an acknowledge focus
             // event back to Gecko. That is where we unmask event handling
             mIMEMaskEventsCount++;
 
             mEditable->NotifyIME(NOTIFY_IME_OF_BLUR);
-            return NS_OK;
-
-        case NOTIFY_IME_OF_SELECTION_CHANGE:
+            return true;
+        }
+
+        case NOTIFY_IME_OF_SELECTION_CHANGE: {
             if (mIMEMaskSelectionUpdate) {
-                return NS_OK;
+                return true;
             }
 
             ALOGIME("IME: NOTIFY_IME_OF_SELECTION_CHANGE");
 
             PostFlushIMEChanges();
             mIMESelectionChanged = true;
-            return NS_OK;
-
-        case NOTIFY_IME_OF_TEXT_CHANGE:
-            return NotifyIMEOfTextChange(aIMENotification);
+            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;
+        }
 
         default:
-            return NS_ERROR_NOT_IMPLEMENTED;
+            return false;
     }
 }
 
-NS_IMETHODIMP_(void)
-nsWindow::SetInputContext(const InputContext& aContext,
-                          const InputContextAction& aAction)
+void
+nsWindow::Natives::SetInputContext(const InputContext& aContext,
+                                   const InputContextAction& aAction)
 {
-#ifdef MOZ_B2GDROID
-    // Disable the Android keyboard on b2gdroid.
-    return;
-#endif
-    nsWindow *top = FindTopLevel();
-    if (top && this != top) {
-        // 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->SetInputContext(aContext, aAction);
-        return;
-    }
-
-    if (!mEditable) {
-        return;
-    }
+    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 &&
@@ -2399,194 +2204,444 @@ nsWindow::SetInputContext(const InputCon
         // Don't reset keyboard when we should simply open the vkb
         mEditable->NotifyIME(GeckoEditableListener::NOTIFY_IME_OPEN_VKB);
         return;
     }
 
     if (mIMEUpdatingContext) {
         return;
     }
-    AndroidGeckoEvent *event = AndroidGeckoEvent::MakeIMEEvent(
-            AndroidGeckoEvent::IME_UPDATE_CONTEXT);
-    nsAppShell::gAppShell->PostEvent(event);
+
+    // Keep a strong reference to the window to keep 'this' alive.
+    RefPtr<nsWindow> window(&this->window);
     mIMEUpdatingContext = true;
+
+    nsAppShell::gAppShell->PostEvent([this, window] {
+        mIMEUpdatingContext = false;
+        if (window->Destroyed()) {
+            return;
+        }
+        MOZ_ASSERT(mEditable);
+        mEditable->NotifyIMEContext(mInputContext.mIMEState.mEnabled,
+                                    mInputContext.mHTMLInputType,
+                                    mInputContext.mHTMLInputInputmode,
+                                    mInputContext.mActionHint);
+    });
 }
 
-NS_IMETHODIMP_(InputContext)
-nsWindow::GetInputContext()
+InputContext
+nsWindow::Natives::GetInputContext()
 {
-    nsWindow *top = FindTopLevel();
-    if (top && this != top) {
-        // We let the top window process SetInputContext,
-        // so we should let it process GetInputContext as well.
-        return top->GetInputContext();
-    }
     InputContext context = mInputContext;
     context.mIMEState.mOpen = IMEState::OPEN_STATE_NOT_SUPPORTED;
     // We assume that there is only one context per process on Android
     context.mNativeIMEContext = nullptr;
     return context;
 }
 
 void
-nsWindow::PostFlushIMEChanges()
+nsWindow::Natives::OnImeSynchronize()
+{
+    if (!mIMEMaskEventsCount) {
+        FlushIMEChanges();
+    }
+    mEditable->NotifyIME(GeckoEditableListener::NOTIFY_IME_REPLY_EVENT);
+}
+
+void
+nsWindow::Natives::OnImeAcknowledgeFocus()
+{
+    MOZ_ASSERT(!mIMEMaskSelectionUpdate);
+    MOZ_ASSERT(mIMEMaskEventsCount > 0);
+
+    if (--mIMEMaskEventsCount > 0) {
+        // Still not focused; reply to events, but don't do anything else.
+        return OnImeSynchronize();
+    }
+
+    // The focusing handshake sequence is complete, and Java is waiting
+    // on Gecko. Now we can notify Java of the newly focused content
+    mIMETextChanges.Clear();
+    mIMESelectionChanged = false;
+    // NotifyIMEOfTextChange also notifies selection
+    // 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 =
+        notification.mTextChangeData.mAddedEndOffset = INT32_MAX / 2;
+    NotifyIME(notification);
+    OnImeSynchronize();
+}
+
+void
+nsWindow::Natives::OnImeReplaceText(int32_t aStart, int32_t aEnd,
+                                    jni::String::Param aText, bool aComposing)
 {
-    if (!mIMETextChanges.IsEmpty() || mIMESelectionChanged) {
-        // Already posted
-        return;
+    MOZ_ASSERT(!mIMEMaskSelectionUpdate);
+
+    if (mIMEMaskEventsCount > 0) {
+        // Not focused; still reply to events, but don't do anything else.
+        return OnImeSynchronize();
     }
-    AndroidGeckoEvent *event = AndroidGeckoEvent::MakeIMEEvent(
-            AndroidGeckoEvent::IME_FLUSH_CHANGES);
-    nsAppShell::gAppShell->PostEvent(event);
+
+    /*
+        Replace text in Gecko thread from aStart to aEnd with the string text.
+
+        Selection updates are masked so the result of our temporary
+          selection event is not passed on to Java
+    */
+    RefPtr<nsWindow> kungFuDeathGrip(&window);
+    AutoIMEMask selMask(mIMEMaskSelectionUpdate);
+    nsString string(aText);
+
+    const auto composition(window.GetIMEComposition());
+    MOZ_ASSERT(!composition || !composition->IsEditorHandlingEvent());
+
+    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();
+
+        {
+            WidgetSelectionEvent event(true, eSetSelection, &window);
+            window.InitEvent(event, nullptr);
+            event.mOffset = uint32_t(aStart);
+            event.mLength = uint32_t(aEnd - aStart);
+            event.mExpandToClusterBoundary = false;
+            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->widget = &window;
+                window.DispatchEvent(event, status);
+            }
+            mIMEKeyEvents.Clear();
+            return OnImeSynchronize();
+        }
+
+        {
+            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);
+    }
+
+    // Previous events may have destroyed our composition; bail in that case.
+    if (window.GetIMEComposition()) {
+        WidgetCompositionEvent event(true, eCompositionChange, &window);
+        window.InitEvent(event, nullptr);
+        event.mData = string;
+
+        // Include proper text ranges to make the editor happy.
+        TextRange range;
+        range.mStartOffset = 0;
+        range.mEndOffset = event.mData.Length();
+        range.mRangeType = NS_TEXTRANGE_RAWINPUT;
+        event.mRanges = new TextRangeArray();
+        event.mRanges->AppendElement(range);
+
+        window.DispatchEvent(&event);
+    }
+
+    // Don't end composition when composing text or composition was destroyed.
+    if (!aComposing && window.GetIMEComposition()) {
+        WidgetCompositionEvent compositionCommitEvent(
+                true, eCompositionCommitAsIs, &window);
+        window.InitEvent(compositionCommitEvent, nullptr);
+        window.DispatchEvent(&compositionCommitEvent);
+    }
+
+    if (mInputContext.mMayBeIMEUnaware) {
+        SendIMEDummyKeyEvents();
+    }
+    OnImeSynchronize();
 }
 
 void
-nsWindow::FlushIMEChanges()
+nsWindow::Natives::OnImeSetSelection(int32_t aStart, int32_t aEnd)
+{
+    MOZ_ASSERT(!mIMEMaskSelectionUpdate);
+
+    if (mIMEMaskEventsCount > 0) {
+        // Not focused.
+        return;
+    }
+
+    /*
+        Set Gecko selection to aStart to aEnd.
+
+        Selection updates are masked to prevent Java from being
+          notified of the new selection
+    */
+    RefPtr<nsWindow> kungFuDeathGrip(&window);
+    AutoIMEMask selMask(mIMEMaskSelectionUpdate);
+    WidgetSelectionEvent selEvent(true, eSetSelection, &window);
+
+    window.InitEvent(selEvent, nullptr);
+    window.RemoveIMEComposition();
+
+    if (aStart < 0 || aEnd < 0) {
+        WidgetQueryContentEvent event(true, eQuerySelectedText, &window);
+        window.InitEvent(event, nullptr);
+        window.DispatchEvent(&event);
+        MOZ_ASSERT(event.mSucceeded);
+
+        if (aStart < 0)
+            aStart = int32_t(event.GetSelectionStart());
+        if (aEnd < 0)
+            aEnd = int32_t(event.GetSelectionEnd());
+    }
+
+    selEvent.mOffset = std::min(aStart, aEnd);
+    selEvent.mLength = std::max(aStart, aEnd) - selEvent.mOffset;
+    selEvent.mReversed = aStart > aEnd;
+    selEvent.mExpandToClusterBoundary = false;
+
+    window.DispatchEvent(&selEvent);
+}
+
+void
+nsWindow::Natives::OnImeRemoveComposition()
+{
+    MOZ_ASSERT(!mIMEMaskSelectionUpdate);
+
+    if (mIMEMaskEventsCount > 0) {
+        // Not focused.
+        return;
+    }
+
+    /*
+     *  Remove any previous composition.  This is only used for
+     *    visual indication and does not affect the text content.
+     *
+     *  Selection updates are masked so the result of
+     *    temporary events are not passed on to Java
+     */
+    AutoIMEMask selMask(mIMEMaskSelectionUpdate);
+    window.RemoveIMEComposition();
+    mIMERanges->Clear();
+}
+
+void
+nsWindow::Natives::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)
 {
-    // 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.
-    MOZ_ALWAYS_TRUE(NS_SUCCEEDED(IMEStateManager::GetFocusSelectionAndRoot(
-            getter_AddRefs(imeSelection), getter_AddRefs(imeRoot))));
-
-    RefPtr<nsWindow> kungFuDeathGrip(this);
-
-    for (uint32_t i = 0; i < mIMETextChanges.Length(); i++) {
-        IMEChange &change = mIMETextChanges[i];
-
-        if (change.mStart == change.mOldEnd &&
-                change.mStart == change.mNewEnd) {
-            continue;
+    MOZ_ASSERT(!mIMEMaskSelectionUpdate);
+
+    if (mIMEMaskEventsCount > 0) {
+        // Not focused.
+        return;
+    }
+
+    TextRange range;
+    range.mStartOffset = aStart;
+    range.mEndOffset = aEnd;
+    range.mRangeType = 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::Natives::OnImeUpdateComposition(int32_t aStart, int32_t aEnd)
+{
+    MOZ_ASSERT(!mIMEMaskSelectionUpdate);
+
+    if (mIMEMaskEventsCount > 0) {
+        // Not focused.
+        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.
+
+        Selection updates are masked so the result of
+          temporary events are not passed on to Java
+    */
+    RefPtr<nsWindow> kungFuDeathGrip(&window);
+    AutoIMEMask selMask(mIMEMaskSelectionUpdate);
+    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;
+            window.DispatchEvent(&event);
         }
 
-        WidgetQueryContentEvent event(true, eQueryTextContent, this);
-
-        if (change.mNewEnd != change.mStart) {
-            InitEvent(event, nullptr);
-            event.InitForQueryTextContent(change.mStart,
-                                          change.mNewEnd - change.mStart);
-            DispatchEvent(&event);
-            NS_ENSURE_TRUE_VOID(event.mSucceeded);
-            NS_ENSURE_TRUE_VOID(event.mReply.mContentsRoot == imeRoot.get());
+        {
+            WidgetQueryContentEvent queryEvent(true, eQuerySelectedText,
+                                               &window);
+            window.InitEvent(queryEvent, nullptr);
+            window.DispatchEvent(&queryEvent);
+            MOZ_ASSERT(queryEvent.mSucceeded);
+            event.mData = queryEvent.mReply.mString;
         }
 
-        mEditable->OnTextChange(event.mReply.mString, change.mStart,
-                                change.mOldEnd, change.mNewEnd);
+        {
+            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();
     }
-    mIMETextChanges.Clear();
-
-    if (mIMESelectionChanged) {
-        WidgetQueryContentEvent event(true, eQuerySelectedText, this);
-        InitEvent(event, nullptr);
-
-        DispatchEvent(&event);
-        NS_ENSURE_TRUE_VOID(event.mSucceeded);
-        NS_ENSURE_TRUE_VOID(event.mReply.mContentsRoot == imeRoot.get());
-
-        mEditable->OnSelectionChange(int32_t(event.GetSelectionStart()),
-                                     int32_t(event.GetSelectionEnd()));
-        mIMESelectionChanged = false;
+
+#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::UserActivity()
+{
+  if (!mIdleService) {
+    mIdleService = do_GetService("@mozilla.org/widget/idleservice;1");
+  }
+
+  if (mIdleService) {
+    mIdleService->ResetIdleTimeOut(0);
+  }
+}
+
 nsresult
-nsWindow::NotifyIMEOfTextChange(const IMENotification& aIMENotification)
+nsWindow::NotifyIMEInternal(const IMENotification& aIMENotification)
 {
     MOZ_ASSERT(this == FindTopLevel());
 
-    MOZ_ASSERT(aIMENotification.mMessage == NOTIFY_IME_OF_TEXT_CHANGE,
-               "NotifyIMEOfTextChange() is called with invaild notification");
-
-    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 */
-    mIMESelectionChanged = false;
-    NotifyIME(NOTIFY_IME_OF_SELECTION_CHANGE);
-
-    PostFlushIMEChanges();
-    AddIMETextChange(IMEChange(aIMENotification));
-    return NS_OK;
-}
-
-void
-nsWindow::AddIMETextChange(const IMEChange& aChange) {
-
-    mIMETextChanges.AppendElement(aChange);
-
-    // 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--) {
-        IMEChange &previousChange = mIMETextChanges[i];
-        if (previousChange.mStart > aChange.mOldEnd) {
-            previousChange.mStart += delta;
-            previousChange.mOldEnd += delta;
-            previousChange.mNewEnd += delta;
-        }
+    if (!mNatives) {
+        // Non-GeckoView windows don't support IME operations.
+        return NS_ERROR_NOT_AVAILABLE;
+    }
+
+    if (mNatives->NotifyIME(aIMENotification)) {
+        return NS_OK;
     }
-
-    // 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) {
-        IMEChange &src = mIMETextChanges[srcIndex];
-        IMEChange &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;
+    return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP_(void)
+nsWindow::SetInputContext(const InputContext& aContext,
+                          const InputContextAction& aAction)
+{
+#ifdef MOZ_B2GDROID
+    // Disable the Android keyboard on b2gdroid.
+    return;
+#endif
+
+    nsWindow* top = FindTopLevel();
+    MOZ_ASSERT(top);
+
+    if (!top->mNatives) {
+        // 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->mNatives->SetInputContext(aContext, aAction);
+}
+
+NS_IMETHODIMP_(InputContext)
+nsWindow::GetInputContext()
+{
+    nsWindow* top = FindTopLevel();
+    MOZ_ASSERT(top);
+
+    if (!top->mNatives) {
+        // 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->mNatives->GetInputContext();
 }
 
 nsIMEUpdatePreference
 nsWindow::GetIMEUpdatePreference()
 {
     // While a plugin has focus, nsWindow for Android doesn't need any
     // notifications.
-    if (mInputContext.mIMEState.mEnabled == IMEState::PLUGIN) {
+    if (GetInputContext().mIMEState.mEnabled == IMEState::PLUGIN) {
       return nsIMEUpdatePreference();
     }
     return nsIMEUpdatePreference(
         nsIMEUpdatePreference::NOTIFY_SELECTION_CHANGE |
         nsIMEUpdatePreference::NOTIFY_TEXT_CHANGE);
 }
 
 void
--- a/widget/android/nsWindow.h
+++ b/widget/android/nsWindow.h
@@ -44,30 +44,26 @@ public:
 
     NS_DECL_ISUPPORTS_INHERITED
 
     static void InitNatives();
     class Natives;
     // Object that implements native GeckoView calls;
     // nullptr for nsWindows that were not opened from GeckoView.
     mozilla::UniquePtr<Natives> mNatives;
-    // GeckoEditable instance used by this nsWindow;
-    // nullptr for nsWindows that are not GeckoViews.
-    mozilla::widget::GeckoEditable::GlobalRef mEditable;
 
     static void OnGlobalAndroidEvent(mozilla::AndroidGeckoEvent *ae);
     static mozilla::gfx::IntSize GetAndroidScreenBounds();
     static nsWindow* TopWindow();
 
     bool OnContextmenuEvent(mozilla::AndroidGeckoEvent *ae);
     void OnLongTapEvent(mozilla::AndroidGeckoEvent *ae);
     bool OnMultitouchEvent(mozilla::AndroidGeckoEvent *ae);
     void OnNativeGestureEvent(mozilla::AndroidGeckoEvent *ae);
     void OnMouseEvent(mozilla::AndroidGeckoEvent *ae);
-    void OnIMEEvent(mozilla::AndroidGeckoEvent *ae);
 
     void OnSizeChanged(const mozilla::gfx::IntSize& aSize);
 
     void InitEvent(mozilla::WidgetGUIEvent& event, nsIntPoint* aPoint = 0);
 
     //
     // nsIWidget
     //
@@ -138,18 +134,16 @@ public:
                                int32_t aVertical) override
     {
         return NS_ERROR_NOT_IMPLEMENTED;
     }
 
     NS_IMETHOD_(void) SetInputContext(const InputContext& aContext,
                                       const InputContextAction& aAction) override;
     NS_IMETHOD_(InputContext) GetInputContext() override;
-
-    nsresult NotifyIMEOfTextChange(const IMENotification& aIMENotification);
     virtual nsIMEUpdatePreference GetIMEUpdatePreference() override;
 
     LayerManager* GetLayerManager (PLayerTransactionChild* aShadowManager = nullptr,
                                    LayersBackend aBackendHint = mozilla::layers::LayersBackend::LAYERS_NONE,
                                    LayerManagerPersistence aPersistence = LAYER_MANAGER_CURRENT,
                                    bool* aAllowRetaining = nullptr) override;
 
     NS_IMETHOD ReparentNativeWidget(nsIWidget* aNewParent) override;
@@ -182,48 +176,18 @@ public:
                                const FrameMetrics::ViewID& aViewId,
                                const mozilla::Maybe<ZoomConstraints>& aConstraints) override;
 
 protected:
     void BringToFront();
     nsWindow *FindTopLevel();
     bool IsTopLevel();
 
-    struct IMEChange {
-        int32_t mStart, mOldEnd, mNewEnd;
-
-        IMEChange() :
-            mStart(-1), mOldEnd(-1), mNewEnd(-1)
-        {
-        }
-        IMEChange(const IMENotification& aIMENotification)
-            : mStart(aIMENotification.mTextChangeData.mStartOffset)
-            , mOldEnd(aIMENotification.mTextChangeData.mRemovedEndOffset)
-            , mNewEnd(aIMENotification.mTextChangeData.mAddedEndOffset)
-        {
-            MOZ_ASSERT(aIMENotification.mMessage ==
-                           mozilla::widget::NOTIFY_IME_OF_TEXT_CHANGE,
-                       "IMEChange 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;
-        }
-    };
-
     RefPtr<mozilla::TextComposition> GetIMEComposition();
     void RemoveIMEComposition();
-    void SendIMEDummyKeyEvents();
-    void AddIMETextChange(const IMEChange& aChange);
-    void PostFlushIMEChanges();
-    void FlushIMEChanges();
 
     void ConfigureAPZCTreeManager() override;
     void ConfigureAPZControllerThread() override;
 
     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).
@@ -233,29 +197,19 @@ protected:
     nsTArray<nsWindow*> mChildren;
     nsWindow* mParent;
 
     double mStartDist;
     double mLastDist;
 
     nsCOMPtr<nsIIdleServiceInternal> mIdleService;
 
-    bool mIMEMaskSelectionUpdate;
-    int32_t mIMEMaskEventsCount; // Mask events when > 0
-    RefPtr<mozilla::TextRangeArray> mIMERanges;
-    bool mIMEUpdatingContext;
-    nsAutoTArray<mozilla::UniquePtr<mozilla::WidgetEvent>, 8> mIMEKeyEvents;
-    nsAutoTArray<IMEChange, 4> mIMETextChanges;
-    bool mIMESelectionChanged;
-
     bool mAwaitingFullScreen;
     bool mIsFullScreen;
 
-    InputContext mInputContext;
-
     virtual nsresult NotifyIMEInternal(
                          const IMENotification& aIMENotification) override;
 
     static void DumpWindows();
     static void DumpWindows(const nsTArray<nsWindow*>& wins, int indent = 0);
     static void LogWindow(nsWindow *win, int index, int indent);
 
 private: