Bug 1209574 - Move GeckoEditable management to nsWindow; r=esawin
authorJim Chen <nchen@mozilla.com>
Thu, 08 Oct 2015 15:25:49 -0400
changeset 266984 64460d7a57db2fbdb05213ddbb538636adcb0637
parent 266983 fd8e1746870e57c537646dfd219847e55727cba9
child 266985 d68116719755ae6f51be5198ae2a91916741f7cd
push id29504
push usercbook@mozilla.com
push dateFri, 09 Oct 2015 09:43:23 +0000
treeherdermozilla-central@d01dd42e654b [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersesawin
bugs1209574
milestone44.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1209574 - Move GeckoEditable management to nsWindow; r=esawin This patch removes the GeckoEditable code in GeckoAppShell, and make nsWindow create a GeckoEditable for itself when opening a window. Instead of calling GeckoAppShell, nsWindow can now call GeckoEditable methods directly.
mobile/android/base/GeckoAppShell.java
mobile/android/base/GeckoEditable.java
mobile/android/base/GeckoEditableListener.java
widget/android/GeneratedJNIWrappers.cpp
widget/android/GeneratedJNIWrappers.h
widget/android/nsWindow.cpp
widget/android/nsWindow.h
--- a/mobile/android/base/GeckoAppShell.java
+++ b/mobile/android/base/GeckoAppShell.java
@@ -127,18 +127,16 @@ import android.widget.Toast;
 public class GeckoAppShell
 {
     private static final String LOGTAG = "GeckoAppShell";
     private static final boolean LOGGING = false;
 
     // We have static members only.
     private GeckoAppShell() { }
 
-    private static GeckoEditableListener editableListener;
-
     private static final CrashHandler CRASH_HANDLER = new CrashHandler() {
         @Override
         protected String getAppPackageName() {
             return AppConstants.ANDROID_PACKAGE_NAME;
         }
 
         @Override
         protected Context getAppContext() {
@@ -319,27 +317,16 @@ public class GeckoAppShell
 
     private static LayerView sLayerView;
 
     public static void setLayerView(LayerView lv) {
         if (sLayerView == lv) {
             return;
         }
         sLayerView = lv;
-
-        // We should have a unique GeckoEditable instance per nsWindow instance,
-        // so even though we have a new view here, the underlying nsWindow is the same,
-        // and we don't create a new GeckoEditable.
-        if (editableListener == null) {
-            // Starting up; istall new Gecko-to-Java editable listener.
-            editableListener = new GeckoEditable();
-        } else {
-            // Bind the existing GeckoEditable instance to the new LayerView
-            GeckoAppShell.notifyIMEContext(GeckoEditableListener.IME_STATE_DISABLED, "", "", "");
-        }
     }
 
     @RobocopTarget
     public static LayerView getLayerView() {
         return sLayerView;
     }
 
     /**
@@ -414,41 +401,16 @@ public class GeckoAppShell
      *  The Gecko-side API: API methods that Gecko calls
      */
 
     @WrapForJNI(allowMultithread = true, noThrow = true)
     public static void handleUncaughtException(Thread thread, Throwable e) {
         CRASH_HANDLER.uncaughtException(thread, e);
     }
 
-    @WrapForJNI
-    public static void notifyIME(int type) {
-        if (editableListener != null) {
-            editableListener.notifyIME(type);
-        }
-    }
-
-    @WrapForJNI
-    public static void notifyIMEContext(int state, String typeHint,
-                                        String modeHint, String actionHint) {
-        if (editableListener != null) {
-            editableListener.notifyIMEContext(state, typeHint,
-                                               modeHint, actionHint);
-        }
-    }
-
-    @WrapForJNI
-    public static void notifyIMEChange(String text, int start, int end, int newEnd) {
-        if (newEnd < 0) { // Selection change
-            editableListener.onSelectionChange(start, end);
-        } else { // Text change
-            editableListener.onTextChange(text, start, end, newEnd);
-        }
-    }
-
     private static final Object sEventAckLock = new Object();
     private static boolean sWaitingForEventAck;
 
     // Block the current thread until the Gecko event loop is caught up
     public static void sendEventToGeckoSync(GeckoEvent e) {
         e.setAckNeeded(true);
 
         long time = SystemClock.uptimeMillis();
--- a/mobile/android/base/GeckoEditable.java
+++ b/mobile/android/base/GeckoEditable.java
@@ -11,16 +11,17 @@ import java.lang.reflect.InvocationHandl
 import java.lang.reflect.InvocationTargetException;
 import java.lang.reflect.Method;
 import java.lang.reflect.Proxy;
 import java.util.concurrent.ConcurrentLinkedQueue;
 import java.util.concurrent.Semaphore;
 
 import org.json.JSONObject;
 import org.mozilla.gecko.AppConstants.Versions;
+import org.mozilla.gecko.annotation.WrapForJNI;
 import org.mozilla.gecko.gfx.LayerView;
 import org.mozilla.gecko.util.GeckoEventListener;
 import org.mozilla.gecko.util.ThreadUtils;
 import org.mozilla.gecko.util.ThreadUtils.AssertBehavior;
 
 import android.os.Handler;
 import android.os.Looper;
 import android.text.Editable;
@@ -342,17 +343,22 @@ final class GeckoEditable
             }
         }
 
         boolean isEmpty() {
             return mActions.isEmpty();
         }
     }
 
+    @WrapForJNI
     GeckoEditable() {
+        if (DEBUG) {
+            // Called by nsWindow.
+            ThreadUtils.assertOnGeckoThread();
+        }
         mActionQueue = new ActionQueue();
         mSavedSelectionStart = -1;
         mUpdateGecko = true;
 
         mText = new SpannableStringBuilder();
         mChangedText = new SpannableStringBuilder();
 
         final Class<?>[] PROXY_INTERFACES = { Editable.class };
@@ -763,17 +769,17 @@ final class GeckoEditable
             for (Object span : spans) {
                 if ((mText.getSpanFlags(span) & Spanned.SPAN_COMPOSING) != 0) {
                     throw new IllegalStateException("composition not cancelled");
                 }
             }
         }
     }
 
-    @Override
+    @WrapForJNI @Override
     public 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 != NOTIFY_IME_REPLY_EVENT) {
                 Log.d(LOGTAG, "notifyIME(" +
                               getConstantName(GeckoEditableListener.class, "NOTIFY_IME_", type) +
@@ -839,22 +845,22 @@ final class GeckoEditable
         } else if (type == NOTIFY_IME_OF_FOCUS) {
             mGeckoFocused = true;
             mSuppressCompositions = false;
             EventDispatcher.getInstance().
                 registerGeckoThreadListener(this, "TextSelection:DraggingHandle");
         }
     }
 
-    @Override
+    @WrapForJNI @Override
     public void notifyIMEContext(final int state, final String typeHint,
-                          final String modeHint, final String actionHint) {
-        // Because we want to be able to bind GeckoEditable to the newest LayerView instance,
-        // this can be called from the Java IC thread in addition to the Gecko thread.
+                                 final String modeHint, final String actionHint) {
         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() {
             @Override
             public void run() {
                 // Make sure there are no other things going on
@@ -867,17 +873,17 @@ final class GeckoEditable
                     mListener = GeckoInputConnection.create(v, GeckoEditable.this);
                     v.setInputConnectionListener((InputConnectionListener) mListener);
                     mListener.notifyIMEContext(state, typeHint, modeHint, actionHint);
                 }
             }
         });
     }
 
-    @Override
+    @WrapForJNI @Override
     public void onSelectionChange(final int start, final int end) {
         if (DEBUG) {
             // GeckoEditableListener methods should all be called from the Gecko thread
             ThreadUtils.assertOnGeckoThread();
             Log.d(LOGTAG, "onSelectionChange(" + start + ", " + end + ")");
         }
         if (start < 0 || start > mText.length() || end < 0 || end > mText.length()) {
             Log.e(LOGTAG, "invalid selection notification range: " +
@@ -923,19 +929,19 @@ final class GeckoEditable
         mText.insert(start, newText);
     }
 
     private boolean isSameText(int start, int oldEnd, CharSequence newText) {
         return oldEnd - start == newText.length() &&
                TextUtils.regionMatches(mText, start, newText, 0, oldEnd - start);
     }
 
-    @Override
+    @WrapForJNI @Override
     public void onTextChange(final CharSequence text, final int start,
-                      final int unboundedOldEnd, final int unboundedNewEnd) {
+                             final int unboundedOldEnd, final int unboundedNewEnd) {
         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(")");
--- a/mobile/android/base/GeckoEditableListener.java
+++ b/mobile/android/base/GeckoEditableListener.java
@@ -1,22 +1,26 @@
 /* -*- 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;
+
 /**
  * Interface for the Editable to listen on the Gecko thread, as well as for the IC thread to listen
  * to the Editable.
  */
 interface GeckoEditableListener {
     // IME notification type for notifyIME(), corresponding to NotificationToIME enum in Gecko
+    @WrapForJNI
     int NOTIFY_IME_OPEN_VKB = -2;
+    @WrapForJNI
     int NOTIFY_IME_REPLY_EVENT = -1;
     int NOTIFY_IME_OF_FOCUS = 1;
     int NOTIFY_IME_OF_BLUR = 2;
     int NOTIFY_IME_TO_COMMIT_COMPOSITION = 8;
     int NOTIFY_IME_TO_CANCEL_COMPOSITION = 9;
     // IME enabled state for notifyIMEContext()
     int IME_STATE_DISABLED = 0;
     int IME_STATE_ENABLED = 1;
--- a/widget/android/GeneratedJNIWrappers.cpp
+++ b/widget/android/GeneratedJNIWrappers.cpp
@@ -545,40 +545,16 @@ auto GeckoAppShell::NetworkLinkType() ->
 constexpr char GeckoAppShell::NotifyDefaultPrevented_t::name[];
 constexpr char GeckoAppShell::NotifyDefaultPrevented_t::signature[];
 
 auto GeckoAppShell::NotifyDefaultPrevented(bool a0) -> void
 {
     return mozilla::jni::Method<NotifyDefaultPrevented_t>::Call(nullptr, nullptr, a0);
 }
 
-constexpr char GeckoAppShell::NotifyIME_t::name[];
-constexpr char GeckoAppShell::NotifyIME_t::signature[];
-
-auto GeckoAppShell::NotifyIME(int32_t a0) -> void
-{
-    return mozilla::jni::Method<NotifyIME_t>::Call(nullptr, nullptr, a0);
-}
-
-constexpr char GeckoAppShell::NotifyIMEChange_t::name[];
-constexpr char GeckoAppShell::NotifyIMEChange_t::signature[];
-
-auto GeckoAppShell::NotifyIMEChange(mozilla::jni::String::Param a0, int32_t a1, int32_t a2, int32_t a3) -> void
-{
-    return mozilla::jni::Method<NotifyIMEChange_t>::Call(nullptr, nullptr, a0, a1, a2, a3);
-}
-
-constexpr char GeckoAppShell::NotifyIMEContext_t::name[];
-constexpr char GeckoAppShell::NotifyIMEContext_t::signature[];
-
-auto GeckoAppShell::NotifyIMEContext(int32_t a0, mozilla::jni::String::Param a1, mozilla::jni::String::Param a2, mozilla::jni::String::Param a3) -> void
-{
-    return mozilla::jni::Method<NotifyIMEContext_t>::Call(nullptr, nullptr, a0, a1, a2, a3);
-}
-
 constexpr char GeckoAppShell::NotifyWakeLockChanged_t::name[];
 constexpr char GeckoAppShell::NotifyWakeLockChanged_t::signature[];
 
 auto GeckoAppShell::NotifyWakeLockChanged(mozilla::jni::String::Param a0, mozilla::jni::String::Param a1) -> void
 {
     return mozilla::jni::Method<NotifyWakeLockChanged_t>::Call(nullptr, nullptr, a0, a1);
 }
 
@@ -737,16 +713,60 @@ auto GeckoAppShell::Vibrate1(int64_t a0)
 constexpr char GeckoAppShell::VibrateA_t::name[];
 constexpr char GeckoAppShell::VibrateA_t::signature[];
 
 auto GeckoAppShell::VibrateA(mozilla::jni::LongArray::Param a0, int32_t a1) -> void
 {
     return mozilla::jni::Method<VibrateA_t>::Call(nullptr, nullptr, a0, a1);
 }
 
+constexpr char GeckoEditable::name[];
+
+constexpr char GeckoEditable::New_t::name[];
+constexpr char GeckoEditable::New_t::signature[];
+
+auto GeckoEditable::New() -> GeckoEditable::LocalRef
+{
+    return mozilla::jni::Constructor<New_t>::Call(nullptr, nullptr);
+}
+
+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(this, 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(this, nullptr, a0, a1, a2, a3);
+}
+
+constexpr char GeckoEditable::OnSelectionChange_t::name[];
+constexpr char GeckoEditable::OnSelectionChange_t::signature[];
+
+auto GeckoEditable::OnSelectionChange(int32_t a0, int32_t a1) const -> void
+{
+    return mozilla::jni::Method<OnSelectionChange_t>::Call(this, nullptr, a0, a1);
+}
+
+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(this, nullptr, a0, a1, a2, a3);
+}
+
+constexpr char GeckoEditableListener::name[];
+
 constexpr char GeckoJavaSampler::name[];
 
 constexpr char GeckoJavaSampler::GetFrameNameJavaProfilingWrapper_t::name[];
 constexpr char GeckoJavaSampler::GetFrameNameJavaProfilingWrapper_t::signature[];
 
 auto GeckoJavaSampler::GetFrameNameJavaProfilingWrapper(int32_t a0, int32_t a1, int32_t a2) -> mozilla::jni::String::LocalRef
 {
     return mozilla::jni::Method<GetFrameNameJavaProfilingWrapper_t>::Call(nullptr, nullptr, a0, a1, a2);
--- a/widget/android/GeneratedJNIWrappers.h
+++ b/widget/android/GeneratedJNIWrappers.h
@@ -1309,76 +1309,16 @@ public:
         static const bool isMultithreaded = false;
         static const mozilla::jni::ExceptionMode exceptionMode =
                 mozilla::jni::ExceptionMode::ABORT;
     };
 
     static auto NotifyDefaultPrevented(bool) -> void;
 
 public:
-    struct NotifyIME_t {
-        typedef GeckoAppShell 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 = true;
-        static const bool isMultithreaded = false;
-        static const mozilla::jni::ExceptionMode exceptionMode =
-                mozilla::jni::ExceptionMode::ABORT;
-    };
-
-    static auto NotifyIME(int32_t) -> void;
-
-public:
-    struct NotifyIMEChange_t {
-        typedef GeckoAppShell 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[] = "notifyIMEChange";
-        static constexpr char signature[] =
-                "(Ljava/lang/String;III)V";
-        static const bool isStatic = true;
-        static const bool isMultithreaded = false;
-        static const mozilla::jni::ExceptionMode exceptionMode =
-                mozilla::jni::ExceptionMode::ABORT;
-    };
-
-    static auto NotifyIMEChange(mozilla::jni::String::Param, int32_t, int32_t, int32_t) -> void;
-
-public:
-    struct NotifyIMEContext_t {
-        typedef GeckoAppShell 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";
-        static constexpr char signature[] =
-                "(ILjava/lang/String;Ljava/lang/String;Ljava/lang/String;)V";
-        static const bool isStatic = true;
-        static const bool isMultithreaded = false;
-        static const mozilla::jni::ExceptionMode exceptionMode =
-                mozilla::jni::ExceptionMode::ABORT;
-    };
-
-    static auto NotifyIMEContext(int32_t, mozilla::jni::String::Param, mozilla::jni::String::Param, mozilla::jni::String::Param) -> void;
-
-public:
     struct NotifyWakeLockChanged_t {
         typedef GeckoAppShell Owner;
         typedef void ReturnType;
         typedef void SetterType;
         typedef mozilla::jni::Args<
                 mozilla::jni::String::Param,
                 mozilla::jni::String::Param> Args;
         static constexpr char name[] = "notifyWakeLockChanged";
@@ -1759,16 +1699,150 @@ public:
         static const mozilla::jni::ExceptionMode exceptionMode =
                 mozilla::jni::ExceptionMode::ABORT;
     };
 
     static auto VibrateA(mozilla::jni::LongArray::Param, int32_t) -> void;
 
 };
 
+class GeckoEditable : public mozilla::jni::Class<GeckoEditable>
+{
+public:
+    typedef mozilla::jni::Ref<GeckoEditable> Ref;
+    typedef mozilla::jni::LocalRef<GeckoEditable> LocalRef;
+    typedef mozilla::jni::GlobalRef<GeckoEditable> GlobalRef;
+    typedef const mozilla::jni::Param<GeckoEditable>& Param;
+
+    static constexpr char name[] =
+            "org/mozilla/gecko/GeckoEditable";
+
+protected:
+    GeckoEditable(jobject instance) : Class(instance) {}
+
+public:
+    struct New_t {
+        typedef GeckoEditable Owner;
+        typedef GeckoEditable::LocalRef ReturnType;
+        typedef GeckoEditable::Param SetterType;
+        typedef mozilla::jni::Args<> Args;
+        static constexpr char name[] = "<init>";
+        static constexpr char signature[] =
+                "()V";
+        static const bool isStatic = false;
+        static const bool isMultithreaded = false;
+        static const mozilla::jni::ExceptionMode exceptionMode =
+                mozilla::jni::ExceptionMode::ABORT;
+    };
+
+    static auto New() -> GeckoEditable::LocalRef;
+
+public:
+    struct NotifyIME_t {
+        typedef GeckoEditable 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;
+        static const bool isMultithreaded = false;
+        static const mozilla::jni::ExceptionMode exceptionMode =
+                mozilla::jni::ExceptionMode::ABORT;
+    };
+
+    auto NotifyIME(int32_t) const -> void;
+
+public:
+    struct NotifyIMEContext_t {
+        typedef GeckoEditable 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";
+        static constexpr char signature[] =
+                "(ILjava/lang/String;Ljava/lang/String;Ljava/lang/String;)V";
+        static const bool isStatic = false;
+        static const bool isMultithreaded = false;
+        static const mozilla::jni::ExceptionMode exceptionMode =
+                mozilla::jni::ExceptionMode::ABORT;
+    };
+
+    auto NotifyIMEContext(int32_t, mozilla::jni::String::Param, mozilla::jni::String::Param, mozilla::jni::String::Param) const -> void;
+
+public:
+    struct OnSelectionChange_t {
+        typedef GeckoEditable Owner;
+        typedef void ReturnType;
+        typedef void SetterType;
+        typedef mozilla::jni::Args<
+                int32_t,
+                int32_t> Args;
+        static constexpr char name[] = "onSelectionChange";
+        static constexpr char signature[] =
+                "(II)V";
+        static const bool isStatic = false;
+        static const bool isMultithreaded = false;
+        static const mozilla::jni::ExceptionMode exceptionMode =
+                mozilla::jni::ExceptionMode::ABORT;
+    };
+
+    auto OnSelectionChange(int32_t, int32_t) const -> void;
+
+public:
+    struct OnTextChange_t {
+        typedef GeckoEditable 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";
+        static constexpr char signature[] =
+                "(Ljava/lang/CharSequence;III)V";
+        static const bool isStatic = false;
+        static const bool isMultithreaded = false;
+        static const mozilla::jni::ExceptionMode exceptionMode =
+                mozilla::jni::ExceptionMode::ABORT;
+    };
+
+    auto OnTextChange(mozilla::jni::String::Param, int32_t, int32_t, int32_t) const -> void;
+
+};
+
+class GeckoEditableListener : public mozilla::jni::Class<GeckoEditableListener>
+{
+public:
+    typedef mozilla::jni::Ref<GeckoEditableListener> Ref;
+    typedef mozilla::jni::LocalRef<GeckoEditableListener> LocalRef;
+    typedef mozilla::jni::GlobalRef<GeckoEditableListener> GlobalRef;
+    typedef const mozilla::jni::Param<GeckoEditableListener>& Param;
+
+    static constexpr char name[] =
+            "org/mozilla/gecko/GeckoEditableListener";
+
+protected:
+    GeckoEditableListener(jobject instance) : Class(instance) {}
+
+public:
+    static const int32_t NOTIFY_IME_OPEN_VKB = -2;
+
+public:
+    static const int32_t NOTIFY_IME_REPLY_EVENT = -1;
+
+};
+
 class GeckoJavaSampler : public mozilla::jni::Class<GeckoJavaSampler>
 {
 public:
     typedef mozilla::jni::Ref<GeckoJavaSampler> Ref;
     typedef mozilla::jni::LocalRef<GeckoJavaSampler> LocalRef;
     typedef mozilla::jni::GlobalRef<GeckoJavaSampler> GlobalRef;
     typedef const mozilla::jni::Param<GeckoJavaSampler>& Param;
 
--- a/widget/android/nsWindow.cpp
+++ b/widget/android/nsWindow.cpp
@@ -244,16 +244,20 @@ nsWindow::Natives::Open(const jni::Class
                    args, getter_AddRefs(window));
     MOZ_ASSERT(window);
 
     nsCOMPtr<nsIWidget> widget = WidgetUtils::DOMWindowToWidget(window);
     MOZ_ASSERT(widget);
 
     gGeckoViewWindow = static_cast<nsWindow*>(widget.get());
     gGeckoViewWindow->mNatives = mozilla::MakeUnique<Natives>(gGeckoViewWindow);
+
+    // Create GeckoEditable for the new nsWindow/GeckoView pair.
+    gGeckoViewWindow->mEditable = GeckoEditable::New();
+
     AttachNative(GeckoView::Window::LocalRef(cls.Env(), gvWindow),
                  gGeckoViewWindow->mNatives.get());
 }
 
 void
 nsWindow::Natives::DisposeNative(const GeckoView::Window::LocalRef& instance)
 {
     // FIXME: because we don't support separate nsWindow for each GeckoView
@@ -1006,18 +1010,18 @@ nsWindow::OnGlobalAndroidEvent(AndroidGe
         }
 
         case AndroidGeckoEvent::KEY_EVENT:
             win->UserActivity();
             win->OnKeyEvent(ae);
             break;
 
         case AndroidGeckoEvent::IME_EVENT:
-            win->UserActivity();
-            win->OnIMEEvent(ae);
+            gGeckoViewWindow->UserActivity();
+            gGeckoViewWindow->OnIMEEvent(ae);
             break;
 
         case AndroidGeckoEvent::IME_KEY_EVENT:
             // Keys synthesized by Java IME code are saved in the mIMEKeyEvents
             // array until the next IME_REPLACE_TEXT event, at which point
             // these keys are dispatched in sequence.
             win->mIMEKeyEvents.AppendElement(*ae);
             break;
@@ -1803,17 +1807,17 @@ public:
 };
 
 /*
  * Get the current composition object, if any.
  */
 nsRefPtr<mozilla::TextComposition>
 nsWindow::GetIMEComposition()
 {
-    MOZ_ASSERT(this == TopWindow());
+    MOZ_ASSERT(this == FindTopLevel());
     return mozilla::IMEStateManager::GetTextCompositionFor(this);
 }
 
 /*
     Remove the composition but leave the text content as-is
 */
 void
 nsWindow::RemoveIMEComposition()
@@ -1879,49 +1883,49 @@ nsWindow::OnIMEEvent(AndroidGeckoEvent *
             // we just use INT32_MAX
             IMENotification notification(NOTIFY_IME_OF_TEXT_CHANGE);
             notification.mTextChangeData.mStartOffset = 0;
             notification.mTextChangeData.mRemovedEndOffset =
                 notification.mTextChangeData.mAddedEndOffset = INT32_MAX / 2;
             NotifyIMEOfTextChange(notification);
             FlushIMEChanges();
         }
-        GeckoAppShell::NotifyIME(AndroidBridge::NOTIFY_IME_REPLY_EVENT);
+        mEditable->NotifyIME(GeckoEditableListener::NOTIFY_IME_REPLY_EVENT);
         return;
 
     } else if (ae->Action() == AndroidGeckoEvent::IME_UPDATE_CONTEXT) {
-        GeckoAppShell::NotifyIMEContext(mInputContext.mIMEState.mEnabled,
-                                        mInputContext.mHTMLInputType,
-                                        mInputContext.mHTMLInputInputmode,
-                                        mInputContext.mActionHint);
+        mEditable->NotifyIMEContext(mInputContext.mIMEState.mEnabled,
+                                    mInputContext.mHTMLInputType,
+                                    mInputContext.mHTMLInputInputmode,
+                                    mInputContext.mActionHint);
         mIMEUpdatingContext = false;
         return;
     }
 
     if (mIMEMaskEventsCount > 0) {
         // Still reply to events, but don't do anything else
         if (ae->Action() == AndroidGeckoEvent::IME_SYNCHRONIZE ||
             ae->Action() == AndroidGeckoEvent::IME_COMPOSE_TEXT ||
             ae->Action() == AndroidGeckoEvent::IME_REPLACE_TEXT) {
-            GeckoAppShell::NotifyIME(AndroidBridge::NOTIFY_IME_REPLY_EVENT);
+            mEditable->NotifyIME(GeckoEditableListener::NOTIFY_IME_REPLY_EVENT);
         }
         return;
     }
 
     switch (ae->Action()) {
     case AndroidGeckoEvent::IME_FLUSH_CHANGES:
         {
             FlushIMEChanges();
         }
         break;
 
     case AndroidGeckoEvent::IME_SYNCHRONIZE:
         {
             FlushIMEChanges();
-            GeckoAppShell::NotifyIME(AndroidBridge::NOTIFY_IME_REPLY_EVENT);
+            mEditable->NotifyIME(GeckoEditableListener::NOTIFY_IME_REPLY_EVENT);
         }
         break;
 
     case AndroidGeckoEvent::IME_REPLACE_TEXT:
     case AndroidGeckoEvent::IME_COMPOSE_TEXT:
         {
             /*
                 Replace text in Gecko thread from ae->Start() to ae->End()
@@ -1956,17 +1960,18 @@ nsWindow::OnIMEEvent(AndroidGeckoEvent *
                 }
 
                 if (!mIMEKeyEvents.IsEmpty()) {
                     for (uint32_t i = 0; i < mIMEKeyEvents.Length(); i++) {
                         OnKeyEvent(&mIMEKeyEvents[i]);
                     }
                     mIMEKeyEvents.Clear();
                     FlushIMEChanges();
-                    GeckoAppShell::NotifyIME(AndroidBridge::NOTIFY_IME_REPLY_EVENT);
+                    mEditable->NotifyIME(
+                            GeckoEditableListener::NOTIFY_IME_REPLY_EVENT);
                     // Break out of the switch block
                     break;
                 }
 
                 {
                     WidgetCompositionEvent event(true, eCompositionStart, this);
                     InitEvent(event, nullptr);
                     DispatchEvent(&event);
@@ -2007,17 +2012,17 @@ nsWindow::OnIMEEvent(AndroidGeckoEvent *
                 DispatchEvent(&compositionCommitEvent);
             }
 
             if (mInputContext.mMayBeIMEUnaware) {
                 SendIMEDummyKeyEvents();
             }
 
             FlushIMEChanges();
-            GeckoAppShell::NotifyIME(AndroidBridge::NOTIFY_IME_REPLY_EVENT);
+            mEditable->NotifyIME(GeckoEditableListener::NOTIFY_IME_REPLY_EVENT);
         }
         break;
 
     case AndroidGeckoEvent::IME_SET_SELECTION:
         {
             /*
                 Set Gecko selection to ae->Start() to ae->End()
 
@@ -2171,23 +2176,27 @@ nsWindow::UserActivity()
   if (mIdleService) {
     mIdleService->ResetIdleTimeOut(0);
   }
 }
 
 nsresult
 nsWindow::NotifyIMEInternal(const IMENotification& aIMENotification)
 {
-    MOZ_ASSERT(this == TopWindow());
+    MOZ_ASSERT(this == FindTopLevel());
+
+    if (!mEditable) {
+        return NS_ERROR_NOT_AVAILABLE;
+    }
 
     switch (aIMENotification.mMessage) {
         case REQUEST_TO_COMMIT_COMPOSITION:
             //ALOGIME("IME: REQUEST_TO_COMMIT_COMPOSITION: s=%d", aState);
             RemoveIMEComposition();
-            GeckoAppShell::NotifyIME(REQUEST_TO_COMMIT_COMPOSITION);
+            mEditable->NotifyIME(REQUEST_TO_COMMIT_COMPOSITION);
             return NS_OK;
 
         case REQUEST_TO_CANCEL_COMPOSITION:
             ALOGIME("IME: REQUEST_TO_CANCEL_COMPOSITION");
 
             // Cancel composition on Gecko side
             if (!!GetIMEComposition()) {
                 nsRefPtr<nsWindow> kungFuDeathGrip(this);
@@ -2195,33 +2204,33 @@ nsWindow::NotifyIMEInternal(const IMENot
                 WidgetCompositionEvent compositionCommitEvent(
                                          true, eCompositionCommit, this);
                 InitEvent(compositionCommitEvent, nullptr);
                 // Dispatch it with empty mData value for canceling the
                 // composition
                 DispatchEvent(&compositionCommitEvent);
             }
 
-            GeckoAppShell::NotifyIME(REQUEST_TO_CANCEL_COMPOSITION);
+            mEditable->NotifyIME(REQUEST_TO_CANCEL_COMPOSITION);
             return NS_OK;
 
         case NOTIFY_IME_OF_FOCUS:
             ALOGIME("IME: NOTIFY_IME_OF_FOCUS");
-            GeckoAppShell::NotifyIME(NOTIFY_IME_OF_FOCUS);
+            mEditable->NotifyIME(NOTIFY_IME_OF_FOCUS);
             return NS_OK;
 
         case NOTIFY_IME_OF_BLUR:
             ALOGIME("IME: NOTIFY_IME_OF_BLUR");
 
             // Mask events because we lost focus. On the next focus event,
             // Gecko will notify Java, and Java will send an acknowledge focus
             // event back to Gecko. That is where we unmask event handling
             mIMEMaskEventsCount++;
 
-            GeckoAppShell::NotifyIME(NOTIFY_IME_OF_BLUR);
+            mEditable->NotifyIME(NOTIFY_IME_OF_BLUR);
             return NS_OK;
 
         case NOTIFY_IME_OF_SELECTION_CHANGE:
             if (mIMEMaskSelectionUpdate) {
                 return NS_OK;
             }
 
             ALOGIME("IME: NOTIFY_IME_OF_SELECTION_CHANGE");
@@ -2241,26 +2250,30 @@ nsWindow::NotifyIMEInternal(const IMENot
 NS_IMETHODIMP_(void)
 nsWindow::SetInputContext(const InputContext& aContext,
                           const InputContextAction& aAction)
 {
 #ifdef MOZ_B2GDROID
     // Disable the Android keyboard on b2gdroid.
     return;
 #endif
-    nsWindow *top = TopWindow();
+    nsWindow *top = FindTopLevel();
     if (top && this != top) {
         // We are using an IME event later to notify Java, and the IME event
         // will be processed by the top window. Therefore, to ensure the
         // IME event uses the correct mInputContext, we need to let the top
         // window process SetInputContext
         top->SetInputContext(aContext, aAction);
         return;
     }
 
+    if (!mEditable) {
+        return;
+    }
+
     ALOGIME("IME: SetInputContext: s=0x%X, 0x%X, action=0x%X, 0x%X",
             aContext.mIMEState.mEnabled, aContext.mIMEState.mOpen,
             aAction.mCause, aAction.mFocusChange);
 
     // Ensure that opening the virtual keyboard is allowed for this specific
     // InputContext depending on the content.ime.strict.policy pref
     if (aContext.mIMEState.mEnabled != IMEState::DISABLED &&
         aContext.mIMEState.mEnabled != IMEState::PLUGIN &&
@@ -2279,33 +2292,33 @@ nsWindow::SetInputContext(const InputCon
         enabled = IMEState::DISABLED;
     }
 
     mInputContext = aContext;
     mInputContext.mIMEState.mEnabled = enabled;
 
     if (enabled == IMEState::ENABLED && aAction.UserMightRequestOpenVKB()) {
         // Don't reset keyboard when we should simply open the vkb
-        GeckoAppShell::NotifyIME(AndroidBridge::NOTIFY_IME_OPEN_VKB);
+        mEditable->NotifyIME(GeckoEditableListener::NOTIFY_IME_OPEN_VKB);
         return;
     }
 
     if (mIMEUpdatingContext) {
         return;
     }
     AndroidGeckoEvent *event = AndroidGeckoEvent::MakeIMEEvent(
             AndroidGeckoEvent::IME_UPDATE_CONTEXT);
     nsAppShell::gAppShell->PostEvent(event);
     mIMEUpdatingContext = true;
 }
 
 NS_IMETHODIMP_(InputContext)
 nsWindow::GetInputContext()
 {
-    nsWindow *top = TopWindow();
+    nsWindow *top = FindTopLevel();
     if (top && this != top) {
         // We let the top window process SetInputContext,
         // so we should let it process GetInputContext as well.
         return top->GetInputContext();
     }
     InputContext context = mInputContext;
     context.mIMEState.mOpen = IMEState::OPEN_STATE_NOT_SUPPORTED;
     // We assume that there is only one context per process on Android
@@ -2355,39 +2368,40 @@ nsWindow::FlushIMEChanges()
             InitEvent(event, nullptr);
             event.InitForQueryTextContent(change.mStart,
                                           change.mNewEnd - change.mStart);
             DispatchEvent(&event);
             NS_ENSURE_TRUE_VOID(event.mSucceeded);
             NS_ENSURE_TRUE_VOID(event.mReply.mContentsRoot == imeRoot.get());
         }
 
-        GeckoAppShell::NotifyIMEChange(event.mReply.mString, change.mStart,
-                                       change.mOldEnd, change.mNewEnd);
+        mEditable->OnTextChange(event.mReply.mString, change.mStart,
+                                change.mOldEnd, change.mNewEnd);
     }
     mIMETextChanges.Clear();
 
     if (mIMESelectionChanged) {
         WidgetQueryContentEvent event(true, eQuerySelectedText, this);
         InitEvent(event, nullptr);
 
         DispatchEvent(&event);
         NS_ENSURE_TRUE_VOID(event.mSucceeded);
         NS_ENSURE_TRUE_VOID(event.mReply.mContentsRoot == imeRoot.get());
 
-        GeckoAppShell::NotifyIMEChange(EmptyString(),
-                                       int32_t(event.GetSelectionStart()),
-                                       int32_t(event.GetSelectionEnd()), -1);
+        mEditable->OnSelectionChange(int32_t(event.GetSelectionStart()),
+                                     int32_t(event.GetSelectionEnd()));
         mIMESelectionChanged = false;
     }
 }
 
 nsresult
 nsWindow::NotifyIMEOfTextChange(const IMENotification& aIMENotification)
 {
+    MOZ_ASSERT(this == FindTopLevel());
+
     MOZ_ASSERT(aIMENotification.mMessage == NOTIFY_IME_OF_TEXT_CHANGE,
                "NotifyIMEOfTextChange() is called with invaild notification");
 
     ALOGIME("IME: NotifyIMEOfTextChange: s=%d, oe=%d, ne=%d",
             aIMENotification.mTextChangeData.mStartOffset,
             aIMENotification.mTextChangeData.mRemovedEndOffset,
             aIMENotification.mTextChangeData.mAddedEndOffset);
 
--- a/widget/android/nsWindow.h
+++ b/widget/android/nsWindow.h
@@ -44,16 +44,19 @@ public:
 
     NS_DECL_ISUPPORTS_INHERITED
 
     static void InitNatives();
     class Natives;
     // Object that implements native GeckoView calls;
     // nullptr for nsWindows that were not opened from GeckoView.
     mozilla::UniquePtr<Natives> mNatives;
+    // GeckoEditable instance used by this nsWindow;
+    // nullptr for nsWindows that are not GeckoViews.
+    mozilla::widget::GeckoEditable::GlobalRef mEditable;
 
     static void OnGlobalAndroidEvent(mozilla::AndroidGeckoEvent *ae);
     static mozilla::gfx::IntSize GetAndroidScreenBounds();
     static nsWindow* TopWindow();
 
     bool OnContextmenuEvent(mozilla::AndroidGeckoEvent *ae);
     void OnLongTapEvent(mozilla::AndroidGeckoEvent *ae);
     bool OnMultitouchEvent(mozilla::AndroidGeckoEvent *ae);