Bug 1339685 - Split GeckoEditable into parent and child classes; r=nalexander r=esawin r=snorp
authorJim Chen <nchen@mozilla.com>
Thu, 02 Mar 2017 13:47:14 -0500
changeset 394705 952cd4007da70fce3fdd9f8a1e4606e87db38871
parent 394704 a9c7e13a967c4752152a7e201212b62f38adf6b6
child 394706 11a578cf0754b42fdd3235570ccccb986a03b333
push id1468
push userasasaki@mozilla.com
push dateMon, 05 Jun 2017 19:31:07 +0000
treeherdermozilla-release@0641fc6ee9d1 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersnalexander, esawin, snorp
bugs1339685
milestone54.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 1339685 - Split GeckoEditable into parent and child classes; r=nalexander r=esawin r=snorp Bug 1339685 - 1. Support compiling GeckoView aidl from multiple packages; r=nalexander Specify a list of AIDL files for GeckoView so we can include AIDLs from multiple packages, and not just those from the org.mozilla.gecko.process package. Bug 1339685 - 2. Add AIDLs for GeckoEditable; r=esawin Add IGeckoEditableParent.aidl and IGeckoEditableChild.aidl for two-way communication between the parent, which lives in the main process, and the child, which lives in the main process or a child content process. Bug 1339685 - 3. Refactor some GeckoEditable code; r=esawin Auto-generate native constants for the constants in GeckoEditableClient, instead of keeping a separate set of constants in native code. Bug 1339685 - 4. Add GeckoEditableChild; r=esawin Add the GeckoEditableChild class, which is currently only used in the main process as the interface between the native nsWindow and GeckoEditable. Eventually, it will be expanded to child content processes as the interface between the native PuppetWidget and main process GeckoEditable. Bug 1339685 - 5. Use GeckoEditableChild from GeckoEditable; r=esawin Make calls to GeckoEditableChild from GeckoEditable, and remove code that exists in GeckoEditableChild from GeckoEditable. Bug 1339685 - 6. Add GetNativeObject member to proxied native calls; r=snorp Add a convenience function for getting the C++ object that is the target of the native call. Bug 1339685 - 7. Use GeckoEditableChild from native code; r=esawin Make nsWindow and GeckoEditableSupport use GeckoEditableChild for communication. nsWindow still keeps a reference to GeckoEditable for switching views. Bug 1339685 - 8. Updated generated bindings; r=me
mobile/android/base/Makefile.in
mobile/android/base/moz.build
mobile/android/geckoview/src/main/aidl/org/mozilla/gecko/IGeckoEditableChild.aidl
mobile/android/geckoview/src/main/aidl/org/mozilla/gecko/IGeckoEditableParent.aidl
mobile/android/geckoview/src/main/java/org/mozilla/gecko/GeckoEditable.java
mobile/android/geckoview/src/main/java/org/mozilla/gecko/GeckoEditableChild.java
mobile/android/geckoview/src/main/java/org/mozilla/gecko/GeckoEditableClient.java
widget/android/GeckoEditableSupport.cpp
widget/android/GeckoEditableSupport.h
widget/android/GeneratedJNINatives.h
widget/android/GeneratedJNIWrappers.cpp
widget/android/GeneratedJNIWrappers.h
widget/android/jni/Natives.h
widget/android/nsWindow.cpp
widget/android/nsWindow.h
--- a/mobile/android/base/Makefile.in
+++ b/mobile/android/base/Makefile.in
@@ -563,20 +563,28 @@ libs:: FennecJNIWrappers.cpp
 	  exit 1)
 endif
 
 libs:: classes.dex
 	$(INSTALL) classes.dex $(FINAL_TARGET)
 
 # Generate Java binder interfaces from AIDL files.
 aidl_src_path := $(srcdir)/aidl
-aidl_geckoview_src_path := $(srcdir)/../geckoview/src/main/aidl
 aidl_target_path := generated
 media_pkg := org/mozilla/gecko/media
-process_pkg := org/mozilla/gecko/process
 
 $(aidl_target_path)/$(media_pkg)/%.java:$(aidl_src_path)/$(media_pkg)/%.aidl
 	@echo "Processing AIDL: $< => $@"
 	$(AIDL) -p$(ANDROID_SDK)/framework.aidl -I$(aidl_src_path) -o$(aidl_target_path) $<
 
-$(aidl_target_path)/$(process_pkg)/%.java:$(aidl_geckoview_src_path)/$(process_pkg)/%.aidl
+GECKOVIEW_AIDLS = \
+  org/mozilla/gecko/IGeckoEditableChild.aidl \
+  org/mozilla/gecko/IGeckoEditableParent.aidl \
+  org/mozilla/gecko/process/IChildProcess.aidl \
+  $(NULL)
+
+geckoview_aidl_src_path := $(topsrcdir)/mobile/android/geckoview/src/main/aidl
+geckoview_aidl_target_path := generated
+geckoview_aidl_targets := $(addprefix $(geckoview_aidl_target_path)/,$(GECKOVIEW_AIDLS:.aidl=.java))
+
+$(geckoview_aidl_targets): $(geckoview_aidl_target_path)/%.java: $(geckoview_aidl_src_path)/%.aidl
 	@echo "Processing AIDL: $< => $@"
-	$(AIDL) -p$(ANDROID_SDK)/framework.aidl -I$(aidl_geckoview_src_path) -o$(aidl_target_path) $<
+	$(AIDL) -p$(ANDROID_SDK)/framework.aidl -I$(geckoview_aidl_src_path) -o$(geckoview_aidl_target_path) $<
--- a/mobile/android/base/moz.build
+++ b/mobile/android/base/moz.build
@@ -241,16 +241,17 @@ gvjar.sources += [geckoview_source_dir +
     'Clipboard.java',
     'ContextGetter.java',
     'CrashHandler.java',
     'EventDispatcher.java',
     'GeckoAccessibility.java',
     'GeckoAppShell.java',
     'GeckoBatteryManager.java',
     'GeckoEditable.java',
+    'GeckoEditableChild.java',
     'GeckoEditableClient.java',
     'GeckoEditableListener.java',
     'GeckoHalDefines.java',
     'GeckoInputConnection.java',
     'GeckoNetworkManager.java',
     'GeckoProfile.java',
     'GeckoProfileDirectories.java',
     'GeckoScreenOrientation.java',
@@ -1178,10 +1179,12 @@ gbjar.sources += ['generated/org/mozilla
     'media/ICodec.java',
     'media/ICodecCallbacks.java',
     'media/IMediaDrmBridge.java',
     'media/IMediaDrmBridgeCallbacks.java',
     'media/IMediaManager.java',
 ]]
 
 gvjar.sources += ['generated/org/mozilla/gecko/' + x for x in [
+    'IGeckoEditableChild.java',
+    'IGeckoEditableParent.java',
     'process/IChildProcess.java',
 ]]
new file mode 100644
--- /dev/null
+++ b/mobile/android/geckoview/src/main/aidl/org/mozilla/gecko/IGeckoEditableChild.aidl
@@ -0,0 +1,33 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+package org.mozilla.gecko;
+
+import android.view.KeyEvent;
+
+// Interface for GeckoEditable calls from parent to child
+interface IGeckoEditableChild {
+    // Process a key event.
+    void onKeyEvent(int action, int keyCode, int scanCode, int metaState,
+                    int keyPressMetaState, long time, int domPrintableKeyValue,
+                    int repeatCount, int flags, boolean isSynthesizedImeKey,
+                    in KeyEvent event);
+
+    // Request a callback to parent after performing any pending operations.
+    void onImeSynchronize();
+
+    // Replace part of current text.
+    void onImeReplaceText(int start, int end, String text);
+
+    // Store a composition range.
+    void onImeAddCompositionRange(int start, int end, int rangeType, int rangeStyles,
+                                  int rangeLineStyle, boolean rangeBoldLine,
+                                  int rangeForeColor, int rangeBackColor, int rangeLineColor);
+
+    // Change to a new composition using previously added ranges.
+    void onImeUpdateComposition(int start, int end);
+
+    // Request cursor updates from the child.
+    void onImeRequestCursorUpdates(int requestMode);
+}
new file mode 100644
--- /dev/null
+++ b/mobile/android/geckoview/src/main/aidl/org/mozilla/gecko/IGeckoEditableParent.aidl
@@ -0,0 +1,30 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+package org.mozilla.gecko;
+
+import android.view.KeyEvent;
+
+import org.mozilla.gecko.IGeckoEditableChild;
+
+// Interface for GeckoEditable calls from child to parent
+interface IGeckoEditableParent {
+    // Notify an IME event of a type defined in GeckoEditableListener.
+    void notifyIME(int type);
+
+    // Notify a change in editor state or type.
+    void notifyIMEContext(int state, String typeHint, String modeHint, String actionHint);
+
+    // Notify a change in editor selection.
+    void onSelectionChange(int start, int end);
+
+    // Notify a change in editor text.
+    void onTextChange(in CharSequence text, int start, int unboundedOldEnd);
+
+    // Perform the default action associated with a key event.
+    void onDefaultKeyEvent(in KeyEvent event);
+
+    // Update the screen location of current composition.
+    void updateCompositionRects(in RectF[] rects);
+}
--- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/GeckoEditable.java
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/GeckoEditable.java
@@ -8,26 +8,24 @@ package org.mozilla.gecko;
 import java.lang.reflect.Array;
 import java.lang.reflect.Field;
 import java.lang.reflect.InvocationHandler;
 import java.lang.reflect.InvocationTargetException;
 import java.lang.reflect.Method;
 import java.lang.reflect.Proxy;
 import java.util.concurrent.ConcurrentLinkedQueue;
 
-import org.json.JSONObject;
 import org.mozilla.gecko.annotation.WrapForJNI;
-import org.mozilla.gecko.gfx.LayerView;
-import org.mozilla.gecko.mozglue.JNIObject;
 import org.mozilla.gecko.util.ThreadUtils;
 import org.mozilla.gecko.util.ThreadUtils.AssertBehavior;
 
 import android.graphics.RectF;
 import android.os.Handler;
 import android.os.Looper;
+import android.os.RemoteException;
 import android.text.Editable;
 import android.text.InputFilter;
 import android.text.NoCopySpan;
 import android.text.Selection;
 import android.text.Spannable;
 import android.text.SpannableString;
 import android.text.SpannableStringBuilder;
 import android.text.Spanned;
@@ -38,17 +36,17 @@ import android.util.Log;
 import android.view.KeyCharacterMap;
 import android.view.KeyEvent;
 
 /*
    GeckoEditable implements only some functions of Editable
    The field mText contains the actual underlying
    SpannableStringBuilder/Editable that contains our text.
 */
-final class GeckoEditable extends JNIObject
+final class GeckoEditable extends IGeckoEditableParent.Stub
         implements InvocationHandler, Editable, GeckoEditableClient {
 
     private static final boolean DEBUG = false;
     private static final String LOGTAG = "GeckoEditable";
 
     // Filters to implement Editable's filtering functionality
     private InputFilter[] mFilters;
 
@@ -58,16 +56,17 @@ final class GeckoEditable extends JNIObj
     private KeyCharacterMap mKeyMap;
 
     // mIcRunHandler is the Handler that currently runs Gecko-to-IC Runnables
     // mIcPostHandler is the Handler to post Gecko-to-IC Runnables to
     // The two can be different when switching from one handler to another
     private Handler mIcRunHandler;
     private Handler mIcPostHandler;
 
+    /* package */ IGeckoEditableChild mEditableChild;
     /* package */ GeckoEditableListener mListener;
     /* package */ GeckoView mView;
 
     /* package */ boolean mInBatchMode; // Used by IC thread
     /* package */ boolean mNeedSync; // Used by IC thread
     // Gecko side needs an updated composition from Java;
     private boolean mNeedUpdateComposition; // Used by IC thread
     private boolean mSuppressKeyUp; // Used by IC thread
@@ -88,24 +87,18 @@ 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.
-    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);
-
     private void onKeyEvent(KeyEvent event, int action, int savedMetaState,
-                            boolean isSynthesizedImeKey) {
+                            boolean isSynthesizedImeKey) throws RemoteException {
         // 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
@@ -119,40 +112,22 @@ final class GeckoEditable extends JNIObj
                 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;
 
-        onKeyEvent(action, event.getKeyCode(), event.getScanCode(),
+        mEditableChild.onKeyEvent(action, event.getKeyCode(), event.getScanCode(),
                    metaState, keyPressMetaState, event.getEventTime(),
                    domPrintableKeyValue, event.getRepeatCount(), event.getFlags(),
                    isSynthesizedImeKey, event);
     }
 
-    @WrapForJNI(dispatchTo = "gecko")
-    private native void onImeSynchronize();
-
-    @WrapForJNI(dispatchTo = "proxy") // Dispatch a UI-activity event through proxy.
-    private native void onImeReplaceText(int start, int end, String text);
-
-    @WrapForJNI(dispatchTo = "gecko")
-    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.
-    private native void onImeUpdateComposition(int start, int end);
-
-    @WrapForJNI(dispatchTo = "gecko")
-    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
      * current text is. Periodically, the shadow copy can be synced to the current copy
      * through syncShadowText, so the shadow copy once again refers to the same text as
@@ -465,60 +440,71 @@ final class GeckoEditable extends JNIObj
 
         if (mListener == null) {
             // We haven't initialized or we've been destroyed.
             return;
         }
 
         mActions.offer(action);
 
+        try {
+            icPerformAction(action);
+        } catch (final RemoteException e) {
+            Log.e(LOGTAG, "Remote call failed", e);
+            // Undo the offer.
+            mActions.remove(action);
+        }
+    }
+
+    private void icPerformAction(final Action action) throws RemoteException {
         switch (action.mType) {
         case Action.TYPE_EVENT:
         case Action.TYPE_SET_HANDLER:
-            onImeSynchronize();
+            mEditableChild.onImeSynchronize();
             break;
 
         case Action.TYPE_SET_SPAN:
             mText.shadowSetSpan(action.mSpanObject, action.mStart,
                                 action.mEnd, action.mSpanFlags);
             action.mSequence = TextUtils.substring(
                     mText.getShadowText(), action.mStart, action.mEnd);
 
             mNeedUpdateComposition |= (action.mSpanFlags & Spanned.SPAN_INTERMEDIATE) == 0 &&
                     ((action.mSpanFlags & Spanned.SPAN_COMPOSING) != 0 ||
                      action.mSpanObject == Selection.SELECTION_START ||
                      action.mSpanObject == Selection.SELECTION_END);
 
-            onImeSynchronize();
+            mEditableChild.onImeSynchronize();
             break;
 
         case Action.TYPE_REMOVE_SPAN:
             final int flags = mText.getShadowText().getSpanFlags(action.mSpanObject);
             mText.shadowRemoveSpan(action.mSpanObject);
 
             mNeedUpdateComposition |= (flags & Spanned.SPAN_INTERMEDIATE) == 0 &&
                     (flags & Spanned.SPAN_COMPOSING) != 0;
 
-            onImeSynchronize();
+            mEditableChild.onImeSynchronize();
             break;
 
         case Action.TYPE_REPLACE_TEXT:
             // Always sync text after a replace action, so that if the Gecko
             // text is not changed, we will revert the shadow text to before.
             mNeedSync = true;
 
             // Because we get composition styling here essentially for free,
             // we don't need to check if we're in batch mode.
             if (!icMaybeSendComposition(
                     action.mSequence, /* useEntireText */ true, /* notifyGecko */ false)) {
                 // Since we don't have a composition, we can try sending key events.
                 sendCharKeyEvents(action);
             }
             mText.shadowReplace(action.mStart, action.mEnd, action.mSequence);
-            onImeReplaceText(action.mStart, action.mEnd, action.mSequence.toString());
+            mEditableChild.onImeReplaceText(
+                    action.mStart, action.mEnd, action.mSequence.toString());
             break;
 
         default:
             throw new IllegalStateException("Action not processed");
         }
     }
 
     private KeyEvent [] synthesizeKeyEvents(CharSequence cs) {
@@ -534,17 +520,17 @@ final class GeckoEditable extends JNIObj
         }
         KeyEvent [] keyEvents = mKeyMap.getEvents(cs.toString().toCharArray());
         if (keyEvents == null || keyEvents.length == 0) {
             return null;
         }
         return keyEvents;
     }
 
-    private void sendCharKeyEvents(Action action) {
+    private void sendCharKeyEvents(Action action) throws RemoteException {
         if (action.mSequence.length() != 1 ||
             (action.mSequence instanceof Spannable &&
             ((Spannable)action.mSequence).nextSpanTransition(
                 -1, Integer.MAX_VALUE, null) < Integer.MAX_VALUE)) {
             // Spans are not preserved when we use key events,
             // so we need the sequence to not have any spans
             return;
         }
@@ -563,17 +549,17 @@ final class GeckoEditable extends JNIObj
                 Log.d(LOGTAG, "sending: " + event);
             }
             onKeyEvent(event, event.getAction(),
                        /* metaState */ 0, /* isSynthesizedImeKey */ true);
         }
     }
 
     @WrapForJNI(calledFrom = "gecko")
-    GeckoEditable(final GeckoView v) {
+    private GeckoEditable(final GeckoView v) {
         if (DEBUG) {
             // Called by nsWindow.
             ThreadUtils.assertOnGeckoThread();
         }
 
         mText = new AsyncText();
         mActions = new ConcurrentLinkedQueue<Action>();
 
@@ -582,18 +568,20 @@ final class GeckoEditable extends JNIObj
                 Editable.class.getClassLoader(),
                 PROXY_INTERFACES, this);
 
         mIcRunHandler = mIcPostHandler = ThreadUtils.getUiHandler();
 
         onViewChange(v);
     }
 
-    @WrapForJNI(dispatchTo = "gecko") @Override
-    protected native void disposeNative();
+    @WrapForJNI(calledFrom = "gecko")
+    private void setDefaultEditableChild(final IGeckoEditableChild child) {
+        mEditableChild = child;
+    }
 
     @WrapForJNI(calledFrom = "gecko")
     private void onViewChange(final GeckoView v) {
         if (DEBUG) {
             // Called by nsWindow.
             ThreadUtils.assertOnGeckoThread();
             Log.d(LOGTAG, "onViewChange(" + v + ")");
         }
@@ -604,23 +592,16 @@ final class GeckoEditable extends JNIObj
         final Runnable setListenerRunnable = new Runnable() {
             @Override
             public void run() {
                 if (DEBUG) {
                     Log.d(LOGTAG, "onViewChange (set listener)");
                 }
 
                 mListener = newListener;
-
-                if (newListener == null) {
-                    // We're being destroyed. By this point, we should have cleared all
-                    // pending Runnables on the IC thread, so it's safe to call
-                    // disposeNative here.
-                    GeckoEditable.this.disposeNative();
-                }
             }
         };
 
         // Post to UI thread first to make sure any code that is using the old input
         // connection has finished running, before we switch to a new input connection or
         // before we clear the input connection on destruction.
         final Handler icHandler = mIcPostHandler;
         ThreadUtils.postToUiThread(new Runnable() {
@@ -648,35 +629,28 @@ final class GeckoEditable extends JNIObj
     private boolean onIcThread() {
         return mIcRunHandler.getLooper() == Looper.myLooper();
     }
 
     private void assertOnIcThread() {
         ThreadUtils.assertOnThread(mIcRunHandler.getLooper().getThread(), AssertBehavior.THROW);
     }
 
-    private void geckoPostToIc(Runnable runnable) {
-        if (DEBUG) {
-            ThreadUtils.assertOnGeckoThread();
-        }
-        mIcPostHandler.post(runnable);
-    }
-
     private Object getField(Object obj, String field, Object def) {
         try {
             return obj.getClass().getField(field).get(obj);
         } catch (Exception e) {
             return def;
         }
     }
 
     /**
      * Send composition ranges to Gecko for the entire shadow text.
      */
-    private void icMaybeSendComposition() {
+    private void icMaybeSendComposition() throws RemoteException {
         if (!mNeedUpdateComposition) {
             return;
         }
 
         icMaybeSendComposition(mText.getShadowText(),
                                /* useEntireText */ false, /* notifyGecko */ true);
     }
 
@@ -687,17 +661,17 @@ final class GeckoEditable extends JNIObj
      * @param useEntireText If text has composing spans, treat the entire text as
      *                      a Gecko composition, instead of just the spanned part.
      * @param notifyGecko Notify Gecko of the new composition ranges;
      *                    otherwise, the caller is responsible for notifying Gecko.
      * @return Whether there was a composition
      */
     private boolean icMaybeSendComposition(final CharSequence sequence,
                                            final boolean useEntireText,
-                                           final boolean notifyGecko) {
+                                           final boolean notifyGecko) throws RemoteException {
         mNeedUpdateComposition = false;
 
         int selStart = Selection.getSelectionStart(sequence);
         int selEnd = Selection.getSelectionEnd(sequence);
 
         if (sequence instanceof Spanned) {
             final Spanned text = (Spanned) sequence;
             final Object[] spans = text.getSpans(0, text.length(), Object.class);
@@ -722,48 +696,49 @@ final class GeckoEditable extends JNIObj
             if (useEntireText && (selStart < 0 || selEnd < 0)) {
                 selStart = composingEnd;
                 selEnd = composingEnd;
             }
 
             if (found) {
                 icSendComposition(text, selStart, selEnd, composingStart, composingEnd);
                 if (notifyGecko) {
-                    onImeUpdateComposition(composingStart, composingEnd);
+                    mEditableChild.onImeUpdateComposition(composingStart, composingEnd);
                 }
                 return true;
             }
         }
 
         if (notifyGecko) {
             // Set the selection by using a composition without ranges
-            onImeUpdateComposition(selStart, selEnd);
+            mEditableChild.onImeUpdateComposition(selStart, selEnd);
         }
 
         if (DEBUG) {
             Log.d(LOGTAG, "icSendComposition(): no composition");
         }
         return false;
     }
 
     private void icSendComposition(final Spanned text,
                                    final int selStart, final int selEnd,
-                                   final int composingStart, final int composingEnd) {
+                                   final int composingStart, final int composingEnd)
+            throws RemoteException {
         if (DEBUG) {
             assertOnIcThread();
             Log.d(LOGTAG, "icSendComposition(\"" + text + "\", " +
                                              composingStart + ", " + composingEnd + ")");
         }
         if (DEBUG) {
             Log.d(LOGTAG, " range = " + composingStart + "-" + composingEnd);
             Log.d(LOGTAG, " selection = " + selStart + "-" + selEnd);
         }
 
         if (selEnd >= composingStart && selEnd <= composingEnd) {
-            onImeAddCompositionRange(
+            mEditableChild.onImeAddCompositionRange(
                     selEnd - composingStart, selEnd - composingStart,
                     IME_RANGE_CARETPOSITION, 0, 0, false, 0, 0, 0);
         }
 
         int rangeStart = composingStart;
         TextPaint tp = new TextPaint();
         TextPaint emptyTp = new TextPaint();
         // set initial foreground color to 0, because we check for tp.getColor() == 0
@@ -826,17 +801,17 @@ final class GeckoEditable extends JNIObj
                     rangeStyles |= IME_RANGE_FORECOLOR;
                     rangeForeColor = tp.getColor();
                 }
                 if (tp.bgColor != 0) {
                     rangeStyles |= IME_RANGE_BACKCOLOR;
                     rangeBackColor = tp.bgColor;
                 }
             }
-            onImeAddCompositionRange(
+            mEditableChild.onImeAddCompositionRange(
                     rangeStart - composingStart, rangeEnd - composingStart,
                     rangeType, rangeStyles, rangeLineStyle, rangeBoldLine,
                     rangeForeColor, rangeBackColor, rangeLineColor);
             rangeStart = rangeEnd;
 
             if (DEBUG) {
                 Log.d(LOGTAG, " added " + rangeType +
                               " : " + Integer.toHexString(rangeStyles) +
@@ -857,19 +832,23 @@ final class GeckoEditable extends JNIObj
         /*
            We are actually sending two events to Gecko here,
            1. Event from the event parameter (key event)
            2. Sync event from the icOfferAction call
            The first event is a normal event that does not reply back to us,
            the second sync event will have a reply, during which we see that there is a pending
            event-type action, and update the shadow text accordingly.
         */
-        icMaybeSendComposition();
-        onKeyEvent(event, action, metaState, /* isSynthesizedImeKey */ false);
-        icOfferAction(new Action(Action.TYPE_EVENT));
+        try {
+            icMaybeSendComposition();
+            onKeyEvent(event, action, metaState, /* isSynthesizedImeKey */ false);
+            icOfferAction(new Action(Action.TYPE_EVENT));
+        } catch (final RemoteException e) {
+            Log.e(LOGTAG, "Remote call failed", e);
+        }
     }
 
     @Override
     public Editable getEditable() {
         if (!onIcThread()) {
             // Android may be holding an old InputConnection; ignore
             if (DEBUG) {
                 Log.i(LOGTAG, "getEditable() called on non-IC thread");
@@ -910,28 +889,16 @@ final class GeckoEditable extends JNIObj
             mNeedSync = true;
             return;
         }
 
         mNeedSync = false;
         mText.syncShadowText(mListener);
     }
 
-    private void geckoScheduleSyncShadowText() {
-        if (DEBUG) {
-            ThreadUtils.assertOnGeckoThread();
-        }
-        geckoPostToIc(new Runnable() {
-            @Override
-            public void run() {
-                icSyncShadowText();
-            }
-        });
-    }
-
     @Override
     public void setSuppressKeyUp(boolean suppress) {
         if (DEBUG) {
             assertOnIcThread();
         }
         // Suppress key up event generated as a result of
         // translating characters to key events
         mSuppressKeyUp = suppress;
@@ -979,47 +946,53 @@ final class GeckoEditable extends JNIObj
 
     @Override // GeckoEditableClient
     public void postToInputConnection(final Runnable runnable) {
         mIcPostHandler.post(runnable);
     }
 
     @Override // GeckoEditableClient
     public void requestCursorUpdates(int requestMode) {
-        onImeRequestCursorUpdates(requestMode);
+        try {
+            mEditableChild.onImeRequestCursorUpdates(requestMode);
+        } catch (final RemoteException e) {
+            Log.e(LOGTAG, "Remote call failed", e);
+        }
     }
 
     private void geckoSetIcHandler(final Handler newHandler) {
-        geckoPostToIc(new Runnable() { // posting to old IC thread
+        if (DEBUG) {
+            ThreadUtils.assertOnGeckoThread();
+        }
+        mIcPostHandler.post(new Runnable() { // posting to old IC thread
             @Override
             public void run() {
                 synchronized (newHandler) {
                     mIcRunHandler = newHandler;
                     newHandler.notify();
                 }
             }
         });
 
         // At this point, all future Runnables should be posted to the new IC thread, but
         // we don't switch mIcRunHandler yet because there may be pending Runnables on the
         // old IC thread still waiting to run.
         mIcPostHandler = newHandler;
     }
 
     private void geckoActionReply(final Action action) {
+        // On Gecko or binder thread.
         if (!mGeckoFocused) {
             if (DEBUG) {
                 Log.d(LOGTAG, "discarding stale reply");
             }
             return;
         }
 
         if (DEBUG) {
-            // GeckoEditableListener methods should all be called from the Gecko thread
-            ThreadUtils.assertOnGeckoThread();
             Log.d(LOGTAG, "reply: Action(" +
                           getConstantName(Action.class, "TYPE_", action.mType) + ")");
         }
         switch (action.mType) {
         case Action.TYPE_SET_SPAN:
             final int len = mText.getCurrentText().length();
             if (action.mStart > len || action.mEnd > len ||
                     !TextUtils.substring(mText.getCurrentText(), action.mStart,
@@ -1037,65 +1010,37 @@ final class GeckoEditable extends JNIObj
             break;
 
         case Action.TYPE_SET_HANDLER:
             geckoSetIcHandler(action.mHandler);
             break;
         }
     }
 
-    private void notifyCommitComposition() {
-        // Gecko already committed its composition. However, Android keyboards
-        // have trouble dealing with us removing the composition manually on
-        // the Java side. Therefore, we keep the composition intact on the Java
-        // side. The text content should still be in-sync on both sides.
-    }
-
-    private void notifyCancelComposition() {
-        // Composition should have been canceled on our side
-        // through text update notifications; verify that here.
+    @Override // IGeckoEditableParent
+    public void notifyIME(final int type) {
+        // On Gecko or binder thread.
         if (DEBUG) {
-            final Spanned text = mText.getCurrentText();
-            final Object[] spans = text.getSpans(0, text.length(), Object.class);
-            for (Object span : spans) {
-                if ((text.getSpanFlags(span) & Spanned.SPAN_COMPOSING) != 0) {
-                    throw new IllegalStateException("composition not cancelled");
-                }
-            }
-        }
-    }
-
-    @WrapForJNI(calledFrom = "gecko")
-    private void notifyIME(final int type) {
-        if (DEBUG) {
-            // GeckoEditableListener methods should all be called from the Gecko thread
-            ThreadUtils.assertOnGeckoThread();
             // NOTIFY_IME_REPLY_EVENT is logged separately, inside geckoActionReply()
             if (type != GeckoEditableListener.NOTIFY_IME_REPLY_EVENT) {
                 Log.d(LOGTAG, "notifyIME(" +
                               getConstantName(GeckoEditableListener.class, "NOTIFY_IME_", type) +
                               ")");
             }
         }
 
         if (type == GeckoEditableListener.NOTIFY_IME_REPLY_EVENT) {
             geckoActionReply(mActions.poll());
-            if (!mGeckoFocused || !mActions.isEmpty()) {
+            if (!mActions.isEmpty()) {
                 // Only post to IC thread below when the queue is empty.
                 return;
             }
-        } else if (type == GeckoEditableListener.NOTIFY_IME_TO_COMMIT_COMPOSITION) {
-            notifyCommitComposition();
-            return;
-        } else if (type == GeckoEditableListener.NOTIFY_IME_TO_CANCEL_COMPOSITION) {
-            notifyCancelComposition();
-            return;
         }
 
-        geckoPostToIc(new Runnable() {
+        mIcPostHandler.post(new Runnable() {
             @Override
             public void run() {
                 if (type == GeckoEditableListener.NOTIFY_IME_REPLY_EVENT) {
                     if (mNeedSync) {
                         icSyncShadowText();
                     }
                     return;
                 }
@@ -1114,103 +1059,83 @@ final class GeckoEditable extends JNIObj
         // Update the mGeckoFocused flag.
         if (type == GeckoEditableListener.NOTIFY_IME_OF_BLUR) {
             mGeckoFocused = false;
         } else if (type == GeckoEditableListener.NOTIFY_IME_OF_FOCUS) {
             mGeckoFocused = true;
         }
     }
 
-    @WrapForJNI(calledFrom = "gecko")
-    private void notifyIMEContext(final int state, final String typeHint,
+    @Override // IGeckoEditableParent
+    public void notifyIMEContext(final int state, final String typeHint,
                                   final String modeHint, final String actionHint) {
+        // On Gecko or binder thread.
         if (DEBUG) {
-            // GeckoEditableListener methods should all be called from the Gecko thread
-            ThreadUtils.assertOnGeckoThread();
             Log.d(LOGTAG, "notifyIMEContext(" +
                           getConstantName(GeckoEditableListener.class, "IME_STATE_", state) +
                           ", \"" + typeHint + "\", \"" + modeHint + "\", \"" + actionHint + "\")");
         }
-        geckoPostToIc(new Runnable() {
+
+        mIcPostHandler.post(new Runnable() {
             @Override
             public void run() {
                 if (mListener == null) {
                     return;
                 }
                 mListener.notifyIMEContext(state, typeHint, modeHint, actionHint);
             }
         });
     }
 
-    @WrapForJNI(calledFrom = "gecko", exceptionMode = "ignore")
-    private void onSelectionChange(final int start, final int end) {
+    @Override // IGeckoEditableParent
+    public void onSelectionChange(final int start, final int end) {
+        // On Gecko or binder thread.
         if (DEBUG) {
-            // GeckoEditableListener methods should all be called from the Gecko thread
-            ThreadUtils.assertOnGeckoThread();
             Log.d(LOGTAG, "onSelectionChange(" + start + ", " + end + ")");
         }
 
-        final int currentLength = mText.getCurrentText().length();
-        if (start < 0 || start > currentLength || end < 0 || end > currentLength) {
-            Log.e(LOGTAG, "invalid selection notification range: " +
-                  start + " to " + end + ", length: " + currentLength);
-            throw new IllegalArgumentException("invalid selection notification range");
-        }
-
         if (mIgnoreSelectionChange) {
             mIgnoreSelectionChange = false;
         } else {
             mText.currentSetSelection(start, end);
         }
 
-        geckoScheduleSyncShadowText();
+        mIcPostHandler.post(new Runnable() {
+            @Override
+            public void run() {
+                icSyncShadowText();
+            }
+        });
     }
 
     private boolean geckoIsSameText(int start, int oldEnd, CharSequence newText) {
-        return oldEnd - start == newText.length() &&
-               TextUtils.regionMatches(mText.getCurrentText(), start, newText, 0, oldEnd - start);
+        return oldEnd - start == newText.length() && TextUtils.regionMatches(
+                mText.getCurrentText(), start, newText, 0, oldEnd - start);
     }
 
-    @WrapForJNI(calledFrom = "gecko", exceptionMode = "ignore")
-    private void onTextChange(final CharSequence text, final int start,
-                              final int unboundedOldEnd, final int unboundedNewEnd) {
+    @Override // IGeckoEditableParent
+    public void onTextChange(final CharSequence text, final int start,
+                             final int unboundedOldEnd) {
+        // On Gecko or binder thread.
         if (DEBUG) {
-            // GeckoEditableListener methods should all be called from the Gecko thread
-            ThreadUtils.assertOnGeckoThread();
             StringBuilder sb = new StringBuilder("onTextChange(");
-            debugAppend(sb, text);
-            sb.append(", ").append(start).append(", ")
-                .append(unboundedOldEnd).append(", ")
-                .append(unboundedNewEnd).append(")");
+            debugAppend(sb, text).append(", ").append(start).append(", ")
+                                 .append(unboundedOldEnd).append(")");
             Log.d(LOGTAG, sb.toString());
         }
-        if (start < 0 || start > unboundedOldEnd) {
-            Log.e(LOGTAG, "invalid text notification range: " +
-                  start + " to " + unboundedOldEnd);
-            throw new IllegalArgumentException("invalid text notification range");
-        }
 
         final int currentLength = mText.getCurrentText().length();
-
-        /* For the "end" parameters, Gecko can pass in a large
-           number to denote "end of the text". Fix that here */
         final int oldEnd = unboundedOldEnd > currentLength ? currentLength : unboundedOldEnd;
-        // new end should always match text
-        if (unboundedOldEnd <= currentLength && unboundedNewEnd != (start + text.length())) {
-            Log.e(LOGTAG, "newEnd does not match text: " + unboundedNewEnd + " vs " +
-                  (start + text.length()));
-            throw new IllegalArgumentException("newEnd does not match text");
-        }
-
         final int newEnd = start + text.length();
         final Action action = mActions.peek();
 
         if (start == 0 && unboundedOldEnd > currentLength) {
-            // Simply replace the text for newly-focused editors. Replace in two steps to
-            // properly clear composing spans that span the whole range.
+            // | oldEnd > currentLength | signals entire text is cleared (e.g. for
+            // newly-focused editors). Simply replace the text in that case; replace in
+            // two steps to properly clear composing spans that span the whole range.
             mText.currentReplace(0, currentLength, "");
             mText.currentReplace(0, 0, text);
 
             // Don't ignore the next selection change because we are re-syncing with Gecko
             mIgnoreSelectionChange = false;
 
         } else if (action != null &&
                 action.mType == Action.TYPE_REPLACE_TEXT &&
@@ -1296,55 +1221,54 @@ final class GeckoEditable extends JNIObj
             // with Gecko here.
             mIgnoreSelectionChange = false;
         }
 
         // onTextChange is always followed by onSelectionChange, so we let
         // onSelectionChange schedule a shadow text sync.
     }
 
-    @WrapForJNI(calledFrom = "gecko")
-    private void onDefaultKeyEvent(final KeyEvent event) {
+    @Override // IGeckoEditableParent
+    public void onDefaultKeyEvent(final KeyEvent event) {
+        // On Gecko or binder thread.
         if (DEBUG) {
-            // GeckoEditableListener methods should all be called from the Gecko thread
-            ThreadUtils.assertOnGeckoThread();
             StringBuilder sb = new StringBuilder("onDefaultKeyEvent(");
             sb.append("action=").append(event.getAction()).append(", ")
                 .append("keyCode=").append(event.getKeyCode()).append(", ")
                 .append("metaState=").append(event.getMetaState()).append(", ")
                 .append("time=").append(event.getEventTime()).append(", ")
                 .append("repeatCount=").append(event.getRepeatCount()).append(")");
             Log.d(LOGTAG, sb.toString());
         }
 
-        geckoPostToIc(new Runnable() {
+        mIcPostHandler.post(new Runnable() {
             @Override
             public void run() {
                 if (mListener == null) {
                     return;
                 }
                 mListener.onDefaultKeyEvent(event);
             }
         });
     }
 
-    @WrapForJNI(calledFrom = "gecko")
-    private void updateCompositionRects(final RectF[] aRects) {
+    @Override // IGeckoEditableParent
+    public void updateCompositionRects(final RectF[] rects) {
+        // On Gecko or binder thread.
         if (DEBUG) {
-            // GeckoEditableListener methods should all be called from the Gecko thread
-            ThreadUtils.assertOnGeckoThread();
-            Log.d(LOGTAG, "updateCompositionRects(aRects.length = " + aRects.length + ")");
+            Log.d(LOGTAG, "updateCompositionRects(rects.length = " + rects.length + ")");
         }
-        geckoPostToIc(new Runnable() {
+
+        mIcPostHandler.post(new Runnable() {
             @Override
             public void run() {
                 if (mListener == null) {
                     return;
                 }
-                mListener.updateCompositionRects(aRects);
+                mListener.updateCompositionRects(rects);
             }
         });
     }
 
     // InvocationHandler interface
 
     static String getConstantName(Class<?> cls, String prefix, Object value) {
         for (Field fld : cls.getDeclaredFields()) {
@@ -1359,16 +1283,18 @@ final class GeckoEditable extends JNIObj
         return String.valueOf(value);
     }
 
     static StringBuilder debugAppend(StringBuilder sb, Object obj) {
         if (obj == null) {
             sb.append("null");
         } else if (obj instanceof GeckoEditable) {
             sb.append("GeckoEditable");
+        } else if (obj instanceof GeckoEditableChild) {
+            sb.append("GeckoEditableChild");
         } else if (Proxy.isProxyClass(obj.getClass())) {
             debugAppend(sb, Proxy.getInvocationHandler(obj));
         } else if (obj instanceof CharSequence) {
             sb.append('"').append(obj.toString().replace('\n', '\u21b2')).append('"');
         } else if (obj.getClass().isArray()) {
             sb.append(obj.getClass().getComponentType().getSimpleName()).append('[')
               .append(Array.getLength(obj)).append(']');
         } else {
new file mode 100644
--- /dev/null
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/GeckoEditableChild.java
@@ -0,0 +1,207 @@
+/* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; 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/. */
+
+package org.mozilla.gecko;
+
+import org.mozilla.gecko.annotation.WrapForJNI;
+import org.mozilla.gecko.mozglue.JNIObject;
+import org.mozilla.gecko.util.ThreadUtils;
+
+import android.graphics.RectF;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.Log;
+import android.view.KeyEvent;
+
+/**
+ * GeckoEditableChild implements the Gecko-facing side of IME operation. Each
+ * nsWindow in the main process and each PuppetWidget in each child content
+ * process has an instance of GeckoEditableChild, which communicates with the
+ * GeckoEditableParent instance in the main process.
+ */
+final class GeckoEditableChild extends JNIObject implements IGeckoEditableChild {
+
+    private static final boolean DEBUG = false;
+    private static final String LOGTAG = "GeckoEditableChild";
+
+    private final IGeckoEditableParent mEditableParent;
+
+    private int mCurrentTextLength; // Used by Gecko thread
+
+    @WrapForJNI(calledFrom = "gecko")
+    /* package */ GeckoEditableChild(final IGeckoEditableParent editableParent) {
+        mEditableParent = editableParent;
+    }
+
+    @WrapForJNI(dispatchTo = "proxy") @Override // IGeckoEditableChild
+    public 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);
+
+    @WrapForJNI(dispatchTo = "proxy") @Override // IGeckoEditableChild
+    public native void onImeSynchronize();
+
+    @WrapForJNI(dispatchTo = "proxy") @Override // IGeckoEditableChild
+    public native void onImeReplaceText(int start, int end, String text);
+
+    @WrapForJNI(dispatchTo = "proxy") @Override // IGeckoEditableChild
+    public native void onImeAddCompositionRange(int start, int end, int rangeType,
+                                                int rangeStyles, int rangeLineStyle,
+                                                boolean rangeBoldLine, int rangeForeColor,
+                                                int rangeBackColor, int rangeLineColor);
+
+    @WrapForJNI(dispatchTo = "proxy") @Override // IGeckoEditableChild
+    public native void onImeUpdateComposition(int start, int end);
+
+    @WrapForJNI(dispatchTo = "proxy") @Override // IGeckoEditableChild
+    public native void onImeRequestCursorUpdates(int requestMode);
+
+    @Override // JNIObject
+    protected void disposeNative() {
+        // Disposal happens in native code.
+        throw new UnsupportedOperationException();
+    }
+
+    @Override // IInterface
+    public IBinder asBinder() {
+        return null;
+    }
+
+    @WrapForJNI(calledFrom = "gecko")
+    private void notifyIME(final int type) {
+        if (DEBUG) {
+            ThreadUtils.assertOnGeckoThread();
+            Log.d(LOGTAG, "notifyIME(" + GeckoEditable.getConstantName(
+                          GeckoEditableListener.class, "NOTIFY_IME_", type) + ")");
+        }
+        if (type == GeckoEditableListener.NOTIFY_IME_TO_COMMIT_COMPOSITION) {
+            // Gecko already committed its composition. However, Android keyboards
+            // have trouble dealing with us removing the composition manually on
+            // the Java side. Therefore, we keep the composition intact on the Java
+            // side. The text content should still be in-sync on both sides.
+            return;
+        } else if (type == GeckoEditableListener.NOTIFY_IME_TO_CANCEL_COMPOSITION) {
+            // Composition should have been canceled on the parent side through text
+            // update notifications. We cannot verify that here because we don't
+            // keep track of spans on the child side, but it's simple to add the
+            // check to the parent side if ever needed.
+            return;
+        }
+
+        try {
+            mEditableParent.notifyIME(type);
+        } catch (final RemoteException e) {
+            Log.e(LOGTAG, "Remote call failed", e);
+            return;
+        }
+    }
+
+    @WrapForJNI(calledFrom = "gecko")
+    private void notifyIMEContext(final int state, final String typeHint,
+                                  final String modeHint, final String actionHint) {
+        if (DEBUG) {
+            ThreadUtils.assertOnGeckoThread();
+            Log.d(LOGTAG, "notifyIMEContext(" + GeckoEditable.getConstantName(
+                          GeckoEditableListener.class, "IME_STATE_", state) + ", \"" +
+                          typeHint + "\", \"" + modeHint + "\", \"" + actionHint + "\")");
+        }
+
+        try {
+            mEditableParent.notifyIMEContext(state, typeHint, modeHint, actionHint);
+        } catch (final RemoteException e) {
+            Log.e(LOGTAG, "Remote call failed", e);
+        }
+    }
+
+    @WrapForJNI(calledFrom = "gecko", exceptionMode = "ignore")
+    private void onSelectionChange(final int start, final int end) throws RemoteException {
+        if (DEBUG) {
+            ThreadUtils.assertOnGeckoThread();
+            Log.d(LOGTAG, "onSelectionChange(" + start + ", " + end + ")");
+        }
+
+        final int currentLength = mCurrentTextLength;
+        if (start < 0 || start > currentLength || end < 0 || end > currentLength) {
+            Log.e(LOGTAG, "invalid selection notification range: " +
+                  start + " to " + end + ", length: " + currentLength);
+            throw new IllegalArgumentException("invalid selection notification range");
+        }
+
+        mEditableParent.onSelectionChange(start, end);
+    }
+
+    @WrapForJNI(calledFrom = "gecko", exceptionMode = "ignore")
+    private void onTextChange(final CharSequence text, final int start,
+                              final int unboundedOldEnd, final int unboundedNewEnd)
+            throws RemoteException {
+        if (DEBUG) {
+            ThreadUtils.assertOnGeckoThread();
+            StringBuilder sb = new StringBuilder("onTextChange(");
+            GeckoEditable.debugAppend(sb, text);
+            sb.append(", ").append(start).append(", ")
+                .append(unboundedOldEnd).append(", ")
+                .append(unboundedNewEnd).append(")");
+            Log.d(LOGTAG, sb.toString());
+        }
+
+        if (start < 0 || start > unboundedOldEnd) {
+            Log.e(LOGTAG, "invalid text notification range: " +
+                  start + " to " + unboundedOldEnd);
+            throw new IllegalArgumentException("invalid text notification range");
+        }
+
+        /* For the "end" parameters, Gecko can pass in a large
+           number to denote "end of the text". Fix that here */
+        final int currentLength = mCurrentTextLength;
+        final int oldEnd = unboundedOldEnd > currentLength ? currentLength : unboundedOldEnd;
+        // new end should always match text
+        if (unboundedOldEnd <= currentLength && unboundedNewEnd != (start + text.length())) {
+            Log.e(LOGTAG, "newEnd does not match text: " + unboundedNewEnd + " vs " +
+                  (start + text.length()));
+            throw new IllegalArgumentException("newEnd does not match text");
+        }
+
+        mCurrentTextLength += start + text.length() - oldEnd;
+        // Need unboundedOldEnd so GeckoEditable can distinguish changed text vs cleared text.
+        mEditableParent.onTextChange(text, start, unboundedOldEnd);
+    }
+
+    @WrapForJNI(calledFrom = "gecko")
+    private void onDefaultKeyEvent(final KeyEvent event) {
+        if (DEBUG) {
+            // GeckoEditableListener methods should all be called from the Gecko thread
+            ThreadUtils.assertOnGeckoThread();
+            StringBuilder sb = new StringBuilder("onDefaultKeyEvent(");
+            sb.append("action=").append(event.getAction()).append(", ")
+                .append("keyCode=").append(event.getKeyCode()).append(", ")
+                .append("metaState=").append(event.getMetaState()).append(", ")
+                .append("time=").append(event.getEventTime()).append(", ")
+                .append("repeatCount=").append(event.getRepeatCount()).append(")");
+            Log.d(LOGTAG, sb.toString());
+        }
+
+        try {
+            mEditableParent.onDefaultKeyEvent(event);
+        } catch (final RemoteException e) {
+            Log.e(LOGTAG, "Remote call failed", e);
+        }
+    }
+
+    @WrapForJNI(calledFrom = "gecko")
+    private void updateCompositionRects(final RectF[] rects) {
+        if (DEBUG) {
+            // GeckoEditableListener methods should all be called from the Gecko thread
+            ThreadUtils.assertOnGeckoThread();
+            Log.d(LOGTAG, "updateCompositionRects(rects.length = " + rects.length + ")");
+        }
+
+        try {
+            mEditableParent.updateCompositionRects(rects);
+        } catch (final RemoteException e) {
+            Log.e(LOGTAG, "Remote call failed", e);
+        }
+    }
+}
--- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/GeckoEditableClient.java
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/GeckoEditableClient.java
@@ -1,15 +1,17 @@
 /* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; 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/. */
 
 package org.mozilla.gecko;
 
+import org.mozilla.gecko.annotation.WrapForJNI;
+
 import android.os.Handler;
 import android.text.Editable;
 import android.view.KeyEvent;
 
 /**
  * Interface for the IC thread.
  */
 interface GeckoEditableClient {
@@ -18,16 +20,19 @@ interface GeckoEditableClient {
     void setBatchMode(boolean isBatchMode);
     void setSuppressKeyUp(boolean suppress);
     Handler setInputConnectionHandler(Handler handler);
     void postToInputConnection(Runnable runnable);
 
     // The following value is used by requestCursorUpdates
 
     // ONE_SHOT calls updateCompositionRects() after getting current composing character rects.
+    @WrapForJNI
     public static final int ONE_SHOT = 1;
     // START_MONITOR start the monitor for composing character rects.  If is is updaed,  call updateCompositionRects()
+    @WrapForJNI
     public static final int START_MONITOR = 2;
     // ENDT_MONITOR stops the monitor for composing character rects.
+    @WrapForJNI
     public static final int END_MONITOR = 3;
 
     void requestCursorUpdates(int requestMode);
 }
--- a/widget/android/GeckoEditableSupport.cpp
+++ b/widget/android/GeckoEditableSupport.cpp
@@ -21,21 +21,16 @@
 
 #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.
@@ -411,16 +406,19 @@ GeckoEditableSupport::OnKeyEvent(int32_t
     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();
+    } else if (aIsSynthesizedImeKey && mIMEMaskEventsCount > 0) {
+        // Don't synthesize editor keys when not focused.
+        return;
     }
 
     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) {
@@ -958,22 +956,23 @@ GeckoEditableSupport::OnImeUpdateComposi
     mDispatcher->SetPendingComposition(string, mIMERanges);
     mDispatcher->FlushPendingComposition(status);
     mIMERanges->Clear();
 }
 
 void
 GeckoEditableSupport::OnImeRequestCursorUpdates(int aRequestMode)
 {
-    if (aRequestMode == IME_MONITOR_CURSOR_ONE_SHOT) {
+    if (aRequestMode == java::GeckoEditableClient::ONE_SHOT) {
         UpdateCompositionRects();
         return;
     }
 
-    mIMEMonitorCursor = (aRequestMode == IME_MONITOR_CURSOR_START_MONITOR);
+    mIMEMonitorCursor =
+            (aRequestMode == java::GeckoEditableClient::START_MONITOR);
 }
 
 void
 GeckoEditableSupport::AsyncNotifyIME(int32_t aNotification)
 {
     RefPtr<GeckoEditableSupport> self(this);
 
     nsAppShell::PostEvent([this, self, aNotification] {
@@ -1021,23 +1020,23 @@ GeckoEditableSupport::NotifyIME(TextEven
                 nsCOMPtr<nsIWidget> widget = dispatcher->GetWidget();
 
                 --mIMEMaskEventsCount;
                 if (mIMEMaskEventsCount || !widget || widget->Destroyed()) {
                     return;
                 }
 
                 mDispatcher = dispatcher;
+                mIMEKeyEvents.Clear();
                 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");
--- a/widget/android/GeckoEditableSupport.h
+++ b/widget/android/GeckoEditableSupport.h
@@ -20,30 +20,31 @@ class nsWindow;
 namespace mozilla {
 
 class TextComposition;
 
 namespace widget {
 
 class GeckoEditableSupport final
     : public TextEventDispatcherListener
-    , public java::GeckoEditable::Natives<GeckoEditableSupport>
+    , public java::GeckoEditableChild::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>;
+    using EditableBase =
+            java::GeckoEditableChild::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) {}
@@ -85,17 +86,17 @@ class GeckoEditableSupport final
     enum RemoveCompositionFlag
     {
         CANCEL_IME_COMPOSITION,
         COMMIT_IME_COMPOSITION
     };
 
     nsWindow::WindowPtr<GeckoEditableSupport> mWindow; // Parent only
     RefPtr<TextEventDispatcher> mDispatcher;
-    java::GeckoEditable::GlobalRef mEditable;
+    java::GeckoEditableChild::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;
@@ -120,32 +121,49 @@ class GeckoEditableSupport final
     void UpdateCompositionRects();
 
 public:
     template<typename Functor>
     static void OnNativeCall(Functor&& aCall)
     {
         struct IMEEvent : nsAppShell::LambdaEvent<Functor>
         {
-            using nsAppShell::LambdaEvent<Functor>::LambdaEvent;
+            using Base = nsAppShell::LambdaEvent<Functor>;
+            using Base::LambdaEvent;
 
             nsAppShell::Event::Type ActivityType() const override
             {
-                return nsAppShell::Event::Type::kUIActivity;
+                using GES = GeckoEditableSupport;
+                if (Base::lambda.IsTarget(&GES::OnKeyEvent) ||
+                        Base::lambda.IsTarget(&GES::OnImeReplaceText) ||
+                        Base::lambda.IsTarget(&GES::OnImeUpdateComposition)) {
+                    return nsAppShell::Event::Type::kUIActivity;
+                }
+                return nsAppShell::Event::Type::kGeneralActivity;
+            }
+
+            void Run() override
+            {
+                if (!Base::lambda.GetNativeObject()) {
+                    // Ignore stale calls after disposal.
+                    jni::GetGeckoThreadEnv()->ExceptionClear();
+                    return;
+                }
+                Base::Run();
             }
         };
         nsAppShell::PostEvent(mozilla::MakeUnique<IMEEvent>(
                 mozilla::Move(aCall)));
     }
 
     GeckoEditableSupport(nsWindow::NativePtr<GeckoEditableSupport>* aPtr,
                          nsWindow* aWindow,
-                         java::GeckoEditable::Param aEditable)
+                         java::GeckoEditableChild::Param aEditableChild)
         : mWindow(aPtr, aWindow)
-        , mEditable(aEditable)
+        , mEditable(aEditableChild)
         , mIMERanges(new TextRangeArray())
         , mIMEMaskEventsCount(1) // Mask IME events since there's no focus yet
         , mIMEUpdatingContext(false)
         , mIMESelectionChanged(false)
         , mIMETextChangedDuringFlush(false)
         , mIMEMonitorCursor(false)
     {}
 
@@ -166,26 +184,24 @@ public:
 
     nsIMEUpdatePreference GetIMEUpdatePreference();
 
     void SetInputContext(const InputContext& aContext,
                          const InputContextAction& aAction);
 
     InputContext GetInputContext();
 
-    // GeckoEditable methods
+    // GeckoEditableChild methods
     using EditableBase::AttachNative;
-    using EditableBase::DisposeNative;
 
     void OnDetach() {
-        mEditable->OnViewChange(nullptr);
-    }
-
-    void OnViewChange(java::GeckoView::Param aView) {
-        mEditable->OnViewChange(aView);
+        RefPtr<GeckoEditableSupport> self(this);
+        nsAppShell::PostEvent([self] {
+            DisposeNative(self->mEditable);
+        });
     }
 
     // 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,
--- a/widget/android/GeneratedJNINatives.h
+++ b/widget/android/GeneratedJNINatives.h
@@ -166,51 +166,47 @@ template<class Impl>
 const JNINativeMethod GeckoBatteryManager::Natives<Impl>::methods[] = {
 
     mozilla::jni::MakeNativeMethod<GeckoBatteryManager::OnBatteryChange_t>(
             mozilla::jni::NativeStub<GeckoBatteryManager::OnBatteryChange_t, Impl>
             ::template Wrap<&Impl::OnBatteryChange>)
 };
 
 template<class Impl>
-class GeckoEditable::Natives : public mozilla::jni::NativeImpl<GeckoEditable, Impl>
+class GeckoEditableChild::Natives : public mozilla::jni::NativeImpl<GeckoEditableChild, Impl>
 {
 public:
-    static const JNINativeMethod methods[7];
+    static const JNINativeMethod methods[6];
 };
 
 template<class Impl>
-const JNINativeMethod GeckoEditable::Natives<Impl>::methods[] = {
+const JNINativeMethod GeckoEditableChild::Natives<Impl>::methods[] = {
 
-    mozilla::jni::MakeNativeMethod<GeckoEditable::DisposeNative_t>(
-            mozilla::jni::NativeStub<GeckoEditable::DisposeNative_t, Impl>
-            ::template Wrap<&Impl::DisposeNative>),
-
-    mozilla::jni::MakeNativeMethod<GeckoEditable::OnImeAddCompositionRange_t>(
-            mozilla::jni::NativeStub<GeckoEditable::OnImeAddCompositionRange_t, Impl>
+    mozilla::jni::MakeNativeMethod<GeckoEditableChild::OnImeAddCompositionRange_t>(
+            mozilla::jni::NativeStub<GeckoEditableChild::OnImeAddCompositionRange_t, Impl>
             ::template Wrap<&Impl::OnImeAddCompositionRange>),
 
-    mozilla::jni::MakeNativeMethod<GeckoEditable::OnImeReplaceText_t>(
-            mozilla::jni::NativeStub<GeckoEditable::OnImeReplaceText_t, Impl>
+    mozilla::jni::MakeNativeMethod<GeckoEditableChild::OnImeReplaceText_t>(
+            mozilla::jni::NativeStub<GeckoEditableChild::OnImeReplaceText_t, Impl>
             ::template Wrap<&Impl::OnImeReplaceText>),
 
-    mozilla::jni::MakeNativeMethod<GeckoEditable::OnImeRequestCursorUpdates_t>(
-            mozilla::jni::NativeStub<GeckoEditable::OnImeRequestCursorUpdates_t, Impl>
+    mozilla::jni::MakeNativeMethod<GeckoEditableChild::OnImeRequestCursorUpdates_t>(
+            mozilla::jni::NativeStub<GeckoEditableChild::OnImeRequestCursorUpdates_t, Impl>
             ::template Wrap<&Impl::OnImeRequestCursorUpdates>),
 
-    mozilla::jni::MakeNativeMethod<GeckoEditable::OnImeSynchronize_t>(
-            mozilla::jni::NativeStub<GeckoEditable::OnImeSynchronize_t, Impl>
+    mozilla::jni::MakeNativeMethod<GeckoEditableChild::OnImeSynchronize_t>(
+            mozilla::jni::NativeStub<GeckoEditableChild::OnImeSynchronize_t, Impl>
             ::template Wrap<&Impl::OnImeSynchronize>),
 
-    mozilla::jni::MakeNativeMethod<GeckoEditable::OnImeUpdateComposition_t>(
-            mozilla::jni::NativeStub<GeckoEditable::OnImeUpdateComposition_t, Impl>
+    mozilla::jni::MakeNativeMethod<GeckoEditableChild::OnImeUpdateComposition_t>(
+            mozilla::jni::NativeStub<GeckoEditableChild::OnImeUpdateComposition_t, Impl>
             ::template Wrap<&Impl::OnImeUpdateComposition>),
 
-    mozilla::jni::MakeNativeMethod<GeckoEditable::OnKeyEvent_t>(
-            mozilla::jni::NativeStub<GeckoEditable::OnKeyEvent_t, Impl>
+    mozilla::jni::MakeNativeMethod<GeckoEditableChild::OnKeyEvent_t>(
+            mozilla::jni::NativeStub<GeckoEditableChild::OnKeyEvent_t, Impl>
             ::template Wrap<&Impl::OnKeyEvent>)
 };
 
 template<class Impl>
 class GeckoNetworkManager::Natives : public mozilla::jni::NativeImpl<GeckoNetworkManager, Impl>
 {
 public:
     static const JNINativeMethod methods[2];
--- a/widget/android/GeneratedJNIWrappers.cpp
+++ b/widget/android/GeneratedJNIWrappers.cpp
@@ -729,93 +729,112 @@ const char GeckoEditable::name[] =
 constexpr char GeckoEditable::New_t::name[];
 constexpr char GeckoEditable::New_t::signature[];
 
 auto GeckoEditable::New(mozilla::jni::Object::Param a0) -> GeckoEditable::LocalRef
 {
     return mozilla::jni::Constructor<New_t>::Call(GeckoEditable::Context(), nullptr, a0);
 }
 
-constexpr char GeckoEditable::DisposeNative_t::name[];
-constexpr char GeckoEditable::DisposeNative_t::signature[];
-
-constexpr char GeckoEditable::NotifyIME_t::name[];
-constexpr char GeckoEditable::NotifyIME_t::signature[];
-
-auto GeckoEditable::NotifyIME(int32_t a0) const -> void
-{
-    return mozilla::jni::Method<NotifyIME_t>::Call(GeckoEditable::mCtx, nullptr, a0);
-}
-
-constexpr char GeckoEditable::NotifyIMEContext_t::name[];
-constexpr char GeckoEditable::NotifyIMEContext_t::signature[];
-
-auto GeckoEditable::NotifyIMEContext(int32_t a0, mozilla::jni::String::Param a1, mozilla::jni::String::Param a2, mozilla::jni::String::Param a3) const -> void
-{
-    return mozilla::jni::Method<NotifyIMEContext_t>::Call(GeckoEditable::mCtx, nullptr, a0, a1, a2, a3);
-}
-
-constexpr char GeckoEditable::OnDefaultKeyEvent_t::name[];
-constexpr char GeckoEditable::OnDefaultKeyEvent_t::signature[];
-
-auto GeckoEditable::OnDefaultKeyEvent(mozilla::jni::Object::Param a0) const -> void
-{
-    return mozilla::jni::Method<OnDefaultKeyEvent_t>::Call(GeckoEditable::mCtx, nullptr, a0);
-}
-
-constexpr char GeckoEditable::OnImeAddCompositionRange_t::name[];
-constexpr char GeckoEditable::OnImeAddCompositionRange_t::signature[];
-
-constexpr char GeckoEditable::OnImeReplaceText_t::name[];
-constexpr char GeckoEditable::OnImeReplaceText_t::signature[];
-
-constexpr char GeckoEditable::OnImeRequestCursorUpdates_t::name[];
-constexpr char GeckoEditable::OnImeRequestCursorUpdates_t::signature[];
-
-constexpr char GeckoEditable::OnImeSynchronize_t::name[];
-constexpr char GeckoEditable::OnImeSynchronize_t::signature[];
-
-constexpr char GeckoEditable::OnImeUpdateComposition_t::name[];
-constexpr char GeckoEditable::OnImeUpdateComposition_t::signature[];
-
-constexpr char GeckoEditable::OnKeyEvent_t::name[];
-constexpr char GeckoEditable::OnKeyEvent_t::signature[];
-
-constexpr char GeckoEditable::OnSelectionChange_t::name[];
-constexpr char GeckoEditable::OnSelectionChange_t::signature[];
-
-auto GeckoEditable::OnSelectionChange(int32_t a0, int32_t a1) const -> void
-{
-    return mozilla::jni::Method<OnSelectionChange_t>::Call(GeckoEditable::mCtx, nullptr, a0, a1);
-}
-
-constexpr char GeckoEditable::OnTextChange_t::name[];
-constexpr char GeckoEditable::OnTextChange_t::signature[];
-
-auto GeckoEditable::OnTextChange(mozilla::jni::String::Param a0, int32_t a1, int32_t a2, int32_t a3) const -> void
-{
-    return mozilla::jni::Method<OnTextChange_t>::Call(GeckoEditable::mCtx, nullptr, a0, a1, a2, a3);
-}
-
 constexpr char GeckoEditable::OnViewChange_t::name[];
 constexpr char GeckoEditable::OnViewChange_t::signature[];
 
 auto GeckoEditable::OnViewChange(mozilla::jni::Object::Param a0) const -> void
 {
     return mozilla::jni::Method<OnViewChange_t>::Call(GeckoEditable::mCtx, nullptr, a0);
 }
 
-constexpr char GeckoEditable::UpdateCompositionRects_t::name[];
-constexpr char GeckoEditable::UpdateCompositionRects_t::signature[];
+constexpr char GeckoEditable::SetDefaultEditableChild_t::name[];
+constexpr char GeckoEditable::SetDefaultEditableChild_t::signature[];
+
+auto GeckoEditable::SetDefaultEditableChild(mozilla::jni::Object::Param a0) const -> void
+{
+    return mozilla::jni::Method<SetDefaultEditableChild_t>::Call(GeckoEditable::mCtx, nullptr, a0);
+}
+
+const char GeckoEditableChild::name[] =
+        "org/mozilla/gecko/GeckoEditableChild";
+
+constexpr char GeckoEditableChild::New_t::name[];
+constexpr char GeckoEditableChild::New_t::signature[];
+
+auto GeckoEditableChild::New(mozilla::jni::Object::Param a0) -> GeckoEditableChild::LocalRef
+{
+    return mozilla::jni::Constructor<New_t>::Call(GeckoEditableChild::Context(), nullptr, a0);
+}
+
+constexpr char GeckoEditableChild::NotifyIME_t::name[];
+constexpr char GeckoEditableChild::NotifyIME_t::signature[];
+
+auto GeckoEditableChild::NotifyIME(int32_t a0) const -> void
+{
+    return mozilla::jni::Method<NotifyIME_t>::Call(GeckoEditableChild::mCtx, nullptr, a0);
+}
+
+constexpr char GeckoEditableChild::NotifyIMEContext_t::name[];
+constexpr char GeckoEditableChild::NotifyIMEContext_t::signature[];
+
+auto GeckoEditableChild::NotifyIMEContext(int32_t a0, mozilla::jni::String::Param a1, mozilla::jni::String::Param a2, mozilla::jni::String::Param a3) const -> void
+{
+    return mozilla::jni::Method<NotifyIMEContext_t>::Call(GeckoEditableChild::mCtx, nullptr, a0, a1, a2, a3);
+}
+
+constexpr char GeckoEditableChild::OnDefaultKeyEvent_t::name[];
+constexpr char GeckoEditableChild::OnDefaultKeyEvent_t::signature[];
+
+auto GeckoEditableChild::OnDefaultKeyEvent(mozilla::jni::Object::Param a0) const -> void
+{
+    return mozilla::jni::Method<OnDefaultKeyEvent_t>::Call(GeckoEditableChild::mCtx, nullptr, a0);
+}
 
-auto GeckoEditable::UpdateCompositionRects(mozilla::jni::ObjectArray::Param a0) const -> void
+constexpr char GeckoEditableChild::OnImeAddCompositionRange_t::name[];
+constexpr char GeckoEditableChild::OnImeAddCompositionRange_t::signature[];
+
+constexpr char GeckoEditableChild::OnImeReplaceText_t::name[];
+constexpr char GeckoEditableChild::OnImeReplaceText_t::signature[];
+
+constexpr char GeckoEditableChild::OnImeRequestCursorUpdates_t::name[];
+constexpr char GeckoEditableChild::OnImeRequestCursorUpdates_t::signature[];
+
+constexpr char GeckoEditableChild::OnImeSynchronize_t::name[];
+constexpr char GeckoEditableChild::OnImeSynchronize_t::signature[];
+
+constexpr char GeckoEditableChild::OnImeUpdateComposition_t::name[];
+constexpr char GeckoEditableChild::OnImeUpdateComposition_t::signature[];
+
+constexpr char GeckoEditableChild::OnKeyEvent_t::name[];
+constexpr char GeckoEditableChild::OnKeyEvent_t::signature[];
+
+constexpr char GeckoEditableChild::OnSelectionChange_t::name[];
+constexpr char GeckoEditableChild::OnSelectionChange_t::signature[];
+
+auto GeckoEditableChild::OnSelectionChange(int32_t a0, int32_t a1) const -> void
 {
-    return mozilla::jni::Method<UpdateCompositionRects_t>::Call(GeckoEditable::mCtx, nullptr, a0);
+    return mozilla::jni::Method<OnSelectionChange_t>::Call(GeckoEditableChild::mCtx, nullptr, a0, a1);
+}
+
+constexpr char GeckoEditableChild::OnTextChange_t::name[];
+constexpr char GeckoEditableChild::OnTextChange_t::signature[];
+
+auto GeckoEditableChild::OnTextChange(mozilla::jni::String::Param a0, int32_t a1, int32_t a2, int32_t a3) const -> void
+{
+    return mozilla::jni::Method<OnTextChange_t>::Call(GeckoEditableChild::mCtx, nullptr, a0, a1, a2, a3);
 }
 
+constexpr char GeckoEditableChild::UpdateCompositionRects_t::name[];
+constexpr char GeckoEditableChild::UpdateCompositionRects_t::signature[];
+
+auto GeckoEditableChild::UpdateCompositionRects(mozilla::jni::ObjectArray::Param a0) const -> void
+{
+    return mozilla::jni::Method<UpdateCompositionRects_t>::Call(GeckoEditableChild::mCtx, nullptr, a0);
+}
+
+const char GeckoEditableClient::name[] =
+        "org/mozilla/gecko/GeckoEditableClient";
+
 const char GeckoEditableListener::name[] =
         "org/mozilla/gecko/GeckoEditableListener";
 
 const char GeckoNetworkManager::name[] =
         "org/mozilla/gecko/GeckoNetworkManager";
 
 constexpr char GeckoNetworkManager::OnConnectionChanged_t::name[];
 constexpr char GeckoNetworkManager::OnConnectionChanged_t::signature[];
--- a/widget/android/GeneratedJNIWrappers.h
+++ b/widget/android/GeneratedJNIWrappers.h
@@ -2093,35 +2093,90 @@ public:
         static const mozilla::jni::CallingThread callingThread =
                 mozilla::jni::CallingThread::GECKO;
         static const mozilla::jni::DispatchTarget dispatchTarget =
                 mozilla::jni::DispatchTarget::CURRENT;
     };
 
     static auto New(mozilla::jni::Object::Param) -> GeckoEditable::LocalRef;
 
-    struct DisposeNative_t {
+    struct OnViewChange_t {
+        typedef GeckoEditable Owner;
+        typedef void ReturnType;
+        typedef void SetterType;
+        typedef mozilla::jni::Args<
+                mozilla::jni::Object::Param> Args;
+        static constexpr char name[] = "onViewChange";
+        static constexpr char signature[] =
+                "(Lorg/mozilla/gecko/GeckoView;)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::GECKO;
+        static const mozilla::jni::DispatchTarget dispatchTarget =
+                mozilla::jni::DispatchTarget::CURRENT;
+    };
+
+    auto OnViewChange(mozilla::jni::Object::Param) const -> void;
+
+    struct SetDefaultEditableChild_t {
         typedef GeckoEditable Owner;
         typedef void ReturnType;
         typedef void SetterType;
-        typedef mozilla::jni::Args<> Args;
-        static constexpr char name[] = "disposeNative";
-        static constexpr char signature[] =
-                "()V";
+        typedef mozilla::jni::Args<
+                mozilla::jni::Object::Param> Args;
+        static constexpr char name[] = "setDefaultEditableChild";
+        static constexpr char signature[] =
+                "(Lorg/mozilla/gecko/IGeckoEditableChild;)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::CallingThread::GECKO;
+        static const mozilla::jni::DispatchTarget dispatchTarget =
+                mozilla::jni::DispatchTarget::CURRENT;
+    };
+
+    auto SetDefaultEditableChild(mozilla::jni::Object::Param) const -> void;
+
+    static const mozilla::jni::CallingThread callingThread =
+            mozilla::jni::CallingThread::GECKO;
+
+};
+
+class GeckoEditableChild : public mozilla::jni::ObjectBase<GeckoEditableChild>
+{
+public:
+    static const char name[];
+
+    explicit GeckoEditableChild(const Context& ctx) : ObjectBase<GeckoEditableChild>(ctx) {}
+
+    struct New_t {
+        typedef GeckoEditableChild Owner;
+        typedef GeckoEditableChild::LocalRef ReturnType;
+        typedef GeckoEditableChild::Param SetterType;
+        typedef mozilla::jni::Args<
+                mozilla::jni::Object::Param> Args;
+        static constexpr char name[] = "<init>";
+        static constexpr char signature[] =
+                "(Lorg/mozilla/gecko/IGeckoEditableParent;)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::GECKO;
+        static const mozilla::jni::DispatchTarget dispatchTarget =
+                mozilla::jni::DispatchTarget::CURRENT;
+    };
+
+    static auto New(mozilla::jni::Object::Param) -> GeckoEditableChild::LocalRef;
 
     struct NotifyIME_t {
-        typedef GeckoEditable Owner;
+        typedef GeckoEditableChild Owner;
         typedef void ReturnType;
         typedef void SetterType;
         typedef mozilla::jni::Args<
                 int32_t> Args;
         static constexpr char name[] = "notifyIME";
         static constexpr char signature[] =
                 "(I)V";
         static const bool isStatic = false;
@@ -2131,17 +2186,17 @@ public:
                 mozilla::jni::CallingThread::GECKO;
         static const mozilla::jni::DispatchTarget dispatchTarget =
                 mozilla::jni::DispatchTarget::CURRENT;
     };
 
     auto NotifyIME(int32_t) const -> void;
 
     struct NotifyIMEContext_t {
-        typedef GeckoEditable Owner;
+        typedef GeckoEditableChild Owner;
         typedef void ReturnType;
         typedef void SetterType;
         typedef mozilla::jni::Args<
                 int32_t,
                 mozilla::jni::String::Param,
                 mozilla::jni::String::Param,
                 mozilla::jni::String::Param> Args;
         static constexpr char name[] = "notifyIMEContext";
@@ -2154,17 +2209,17 @@ public:
                 mozilla::jni::CallingThread::GECKO;
         static const mozilla::jni::DispatchTarget dispatchTarget =
                 mozilla::jni::DispatchTarget::CURRENT;
     };
 
     auto NotifyIMEContext(int32_t, mozilla::jni::String::Param, mozilla::jni::String::Param, mozilla::jni::String::Param) const -> void;
 
     struct OnDefaultKeyEvent_t {
-        typedef GeckoEditable Owner;
+        typedef GeckoEditableChild Owner;
         typedef void ReturnType;
         typedef void SetterType;
         typedef mozilla::jni::Args<
                 mozilla::jni::Object::Param> Args;
         static constexpr char name[] = "onDefaultKeyEvent";
         static constexpr char signature[] =
                 "(Landroid/view/KeyEvent;)V";
         static const bool isStatic = false;
@@ -2174,17 +2229,17 @@ public:
                 mozilla::jni::CallingThread::GECKO;
         static const mozilla::jni::DispatchTarget dispatchTarget =
                 mozilla::jni::DispatchTarget::CURRENT;
     };
 
     auto OnDefaultKeyEvent(mozilla::jni::Object::Param) const -> void;
 
     struct OnImeAddCompositionRange_t {
-        typedef GeckoEditable Owner;
+        typedef GeckoEditableChild Owner;
         typedef void ReturnType;
         typedef void SetterType;
         typedef mozilla::jni::Args<
                 int32_t,
                 int32_t,
                 int32_t,
                 int32_t,
                 int32_t,
@@ -2196,21 +2251,21 @@ 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 GeckoEditableChild Owner;
         typedef void ReturnType;
         typedef void SetterType;
         typedef mozilla::jni::Args<
                 int32_t,
                 int32_t,
                 mozilla::jni::String::Param> Args;
         static constexpr char name[] = "onImeReplaceText";
         static constexpr char signature[] =
@@ -2220,52 +2275,52 @@ public:
                 mozilla::jni::ExceptionMode::ABORT;
         static const mozilla::jni::CallingThread callingThread =
                 mozilla::jni::CallingThread::ANY;
         static const mozilla::jni::DispatchTarget dispatchTarget =
                 mozilla::jni::DispatchTarget::PROXY;
     };
 
     struct OnImeRequestCursorUpdates_t {
-        typedef GeckoEditable Owner;
+        typedef GeckoEditableChild Owner;
         typedef void ReturnType;
         typedef void SetterType;
         typedef mozilla::jni::Args<
                 int32_t> Args;
         static constexpr char name[] = "onImeRequestCursorUpdates";
         static constexpr char signature[] =
                 "(I)V";
         static const bool isStatic = false;
         static const mozilla::jni::ExceptionMode exceptionMode =
                 mozilla::jni::ExceptionMode::ABORT;
         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 GeckoEditableChild 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 GeckoEditableChild Owner;
         typedef void ReturnType;
         typedef void SetterType;
         typedef mozilla::jni::Args<
                 int32_t,
                 int32_t> Args;
         static constexpr char name[] = "onImeUpdateComposition";
         static constexpr char signature[] =
                 "(II)V";
@@ -2274,17 +2329,17 @@ public:
                 mozilla::jni::ExceptionMode::ABORT;
         static const mozilla::jni::CallingThread callingThread =
                 mozilla::jni::CallingThread::ANY;
         static const mozilla::jni::DispatchTarget dispatchTarget =
                 mozilla::jni::DispatchTarget::PROXY;
     };
 
     struct OnKeyEvent_t {
-        typedef GeckoEditable Owner;
+        typedef GeckoEditableChild Owner;
         typedef void ReturnType;
         typedef void SetterType;
         typedef mozilla::jni::Args<
                 int32_t,
                 int32_t,
                 int32_t,
                 int32_t,
                 int32_t,
@@ -2302,17 +2357,17 @@ public:
                 mozilla::jni::ExceptionMode::ABORT;
         static const mozilla::jni::CallingThread callingThread =
                 mozilla::jni::CallingThread::ANY;
         static const mozilla::jni::DispatchTarget dispatchTarget =
                 mozilla::jni::DispatchTarget::PROXY;
     };
 
     struct OnSelectionChange_t {
-        typedef GeckoEditable Owner;
+        typedef GeckoEditableChild Owner;
         typedef void ReturnType;
         typedef void SetterType;
         typedef mozilla::jni::Args<
                 int32_t,
                 int32_t> Args;
         static constexpr char name[] = "onSelectionChange";
         static constexpr char signature[] =
                 "(II)V";
@@ -2323,17 +2378,17 @@ public:
                 mozilla::jni::CallingThread::GECKO;
         static const mozilla::jni::DispatchTarget dispatchTarget =
                 mozilla::jni::DispatchTarget::CURRENT;
     };
 
     auto OnSelectionChange(int32_t, int32_t) const -> void;
 
     struct OnTextChange_t {
-        typedef GeckoEditable Owner;
+        typedef GeckoEditableChild Owner;
         typedef void ReturnType;
         typedef void SetterType;
         typedef mozilla::jni::Args<
                 mozilla::jni::String::Param,
                 int32_t,
                 int32_t,
                 int32_t> Args;
         static constexpr char name[] = "onTextChange";
@@ -2345,38 +2400,18 @@ public:
         static const mozilla::jni::CallingThread callingThread =
                 mozilla::jni::CallingThread::GECKO;
         static const mozilla::jni::DispatchTarget dispatchTarget =
                 mozilla::jni::DispatchTarget::CURRENT;
     };
 
     auto OnTextChange(mozilla::jni::String::Param, int32_t, int32_t, int32_t) const -> void;
 
-    struct OnViewChange_t {
-        typedef GeckoEditable Owner;
-        typedef void ReturnType;
-        typedef void SetterType;
-        typedef mozilla::jni::Args<
-                mozilla::jni::Object::Param> Args;
-        static constexpr char name[] = "onViewChange";
-        static constexpr char signature[] =
-                "(Lorg/mozilla/gecko/GeckoView;)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::GECKO;
-        static const mozilla::jni::DispatchTarget dispatchTarget =
-                mozilla::jni::DispatchTarget::CURRENT;
-    };
-
-    auto OnViewChange(mozilla::jni::Object::Param) const -> void;
-
     struct UpdateCompositionRects_t {
-        typedef GeckoEditable Owner;
+        typedef GeckoEditableChild Owner;
         typedef void ReturnType;
         typedef void SetterType;
         typedef mozilla::jni::Args<
                 mozilla::jni::ObjectArray::Param> Args;
         static constexpr char name[] = "updateCompositionRects";
         static constexpr char signature[] =
                 "([Landroid/graphics/RectF;)V";
         static const bool isStatic = false;
@@ -2391,16 +2426,34 @@ public:
     auto UpdateCompositionRects(mozilla::jni::ObjectArray::Param) const -> void;
 
     static const mozilla::jni::CallingThread callingThread =
             mozilla::jni::CallingThread::ANY;
 
     template<class Impl> class Natives;
 };
 
+class GeckoEditableClient : public mozilla::jni::ObjectBase<GeckoEditableClient>
+{
+public:
+    static const char name[];
+
+    explicit GeckoEditableClient(const Context& ctx) : ObjectBase<GeckoEditableClient>(ctx) {}
+
+    static const int32_t END_MONITOR = 3;
+
+    static const int32_t ONE_SHOT = 1;
+
+    static const int32_t START_MONITOR = 2;
+
+    static const mozilla::jni::CallingThread callingThread =
+            mozilla::jni::CallingThread::ANY;
+
+};
+
 class GeckoEditableListener : public mozilla::jni::ObjectBase<GeckoEditableListener>
 {
 public:
     static const char name[];
 
     explicit GeckoEditableListener(const Context& ctx) : ObjectBase<GeckoEditableListener>(ctx) {}
 
     static const int32_t NOTIFY_IME_OF_BLUR = 2;
--- a/widget/android/jni/Natives.h
+++ b/widget/android/jni/Natives.h
@@ -421,16 +421,23 @@ class ProxyNativeCall : public AbstractC
     void Clear(JNIEnv* env, mozilla::IndexSequence<Indices...>)
     {
         int dummy[] = {
             (ProxyArg<Args>::Clear(env, Get<Indices>(mArgs)), 0)...
         };
         mozilla::Unused << dummy;
     }
 
+    static Impl* GetNativeObject(Class::Param thisArg) { return nullptr; }
+
+    static Impl* GetNativeObject(typename Owner::Param thisArg)
+    {
+        return NativePtr<Impl>::Get(GetEnvForThread(), thisArg.Get());
+    }
+
 public:
     // The class that implements the call target.
     typedef Impl TargetClass;
     typedef typename ThisArgClass::Param ThisArgType;
 
     static const bool isStatic = IsStatic;
 
     ProxyNativeCall(ThisArgJNIType thisArg,
@@ -443,16 +450,20 @@ public:
     {}
 
     ProxyNativeCall(ProxyNativeCall&&) = default;
     ProxyNativeCall(const ProxyNativeCall&) = default;
 
     // Get class ref for static calls or object ref for instance calls.
     typename ThisArgClass::Param GetThisArg() const { return mThisArg; }
 
+    // Get the native object targeted by this call.
+    // Returns nullptr for static calls.
+    Impl* GetNativeObject() const { return GetNativeObject(mThisArg); }
+
     // Return if target is the given function pointer / pointer-to-member.
     // Because we can only compare pointers of the same type, we use a
     // templated overload that is chosen only if given a different type of
     // pointer than our target pointer type.
     bool IsTarget(NativeCallType call) const { return call == mNativeCall; }
     template<typename T> bool IsTarget(T&&) const { return false; }
 
     // Redirect the call to another function / class member with the same
--- a/widget/android/nsWindow.cpp
+++ b/widget/android/nsWindow.cpp
@@ -1181,20 +1181,20 @@ public:
 
 ANativeWindow* nsWindow::PMPMSupport::sWindow;
 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);
+    MOZ_ASSERT(window.mEditableSupport && window.mEditable);
     window.mEditableSupport.Detach();
+    window.mEditable->OnViewChange(nullptr);
+    window.mEditable = nullptr;
 
     if (window.mNPZCSupport) {
         window.mNPZCSupport.Detach();
     }
 
     if (window.mLayerViewSupport) {
         window.mLayerViewSupport.Detach();
     }
@@ -1251,17 +1251,20 @@ nsWindow::GeckoViewSupport::Open(const j
     // Attach a new GeckoView support object to the new window.
     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);
+    auto editableChild = GeckoEditableChild::New(editable);
+    editable->SetDefaultEditableChild(editableChild);
+    window->mEditable = editable;
+    window->mEditableSupport.Attach(editableChild, window, editableChild);
 
     // 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(
@@ -1296,18 +1299,18 @@ 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);
+    MOZ_ASSERT(window.mEditable);
+    window.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);
--- a/widget/android/nsWindow.h
+++ b/widget/android/nsWindow.h
@@ -181,16 +181,17 @@ private:
     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;
+    mozilla::java::GeckoEditable::GlobalRef mEditable;
 
     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;