Bug 1425553 - Only process input calls when Gecko is ready; r?esawin draft
authorJim Chen <nchen@mozilla.com>
Tue, 19 Dec 2017 17:02:46 -0500
changeset 713228 cac222cfd46b7464a93398723f72d55f35603a50
parent 713072 c9ce08c45635d29120d5360fb9306125af7bc1ca
child 744283 741ff13f06b12488f2d29326e5e80237a9146cd6
push id93586
push userbmo:nchen@mozilla.com
push dateTue, 19 Dec 2017 22:03:07 +0000
reviewersesawin
bugs1425553
milestone59.0a1
Bug 1425553 - Only process input calls when Gecko is ready; r?esawin Track the Gecko ready state in TextInputController through GeckoSession's NativeQueue, and only pass through input calls when Gecko is ready. MozReview-Commit-ID: KugQ6whg2QA
mobile/android/geckoview/src/main/java/org/mozilla/gecko/GeckoSession.java
mobile/android/geckoview/src/main/java/org/mozilla/gecko/TextInputController.java
widget/android/nsWindow.cpp
--- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/GeckoSession.java
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/GeckoSession.java
@@ -62,17 +62,17 @@ public class GeckoSession extends LayerS
     }
 
     private final NativeQueue mNativeQueue =
         new NativeQueue(State.INITIAL, State.READY);
 
     private final EventDispatcher mEventDispatcher =
         new EventDispatcher(mNativeQueue);
 
-    private final TextInputController mTextInput = new TextInputController(this);
+    private final TextInputController mTextInput = new TextInputController(this, mNativeQueue);
 
     private final GeckoSessionHandler<ContentListener> mContentHandler =
         new GeckoSessionHandler<ContentListener>(
             "GeckoViewContent", this,
             new String[]{
                 "GeckoView:ContextMenu",
                 "GeckoView:DOMTitleChanged",
                 "GeckoView:FullScreenEnter",
@@ -319,17 +319,21 @@ public class GeckoSession extends LayerS
         @WrapForJNI(dispatchTo = "proxy")
         public native void transfer(Compositor compositor, EventDispatcher dispatcher,
                                     GeckoBundle settings);
 
         @WrapForJNI(calledFrom = "gecko")
         private synchronized void onTransfer(final EventDispatcher dispatcher) {
             final NativeQueue nativeQueue = dispatcher.getNativeQueue();
             if (mNativeQueue != nativeQueue) {
+                // Set new queue to the same state as the old queue,
+                // then return the old queue to its initial state if applicable,
+                // because the old queue is no longer the active queue.
                 nativeQueue.setState(mNativeQueue.getState());
+                mNativeQueue.checkAndSetState(State.READY, State.INITIAL);
                 mNativeQueue = nativeQueue;
             }
         }
 
         @WrapForJNI(dispatchTo = "proxy")
         public native void attachEditable(IGeckoEditableParent parent,
                                           GeckoEditableChild child);
 
@@ -393,21 +397,24 @@ public class GeckoSession extends LayerS
             } else {
                 GeckoThread.queueNativeCallUntil(GeckoThread.State.PROFILE_READY,
                         mWindow, "transfer",
                         Compositor.class, mCompositor,
                         EventDispatcher.class, mEventDispatcher,
                         GeckoBundle.class, mSettings.asBundle());
             }
         }
+
+        onWindowChanged();
     }
 
     /* package */ void transferFrom(final GeckoSession session) {
         transferFrom(session.mWindow, session.mSettings);
         session.mWindow = null;
+        session.onWindowChanged();
     }
 
     @Override // Parcelable
     public int describeContents() {
         return 0;
     }
 
     @Override // Parcelable
@@ -504,33 +511,44 @@ public class GeckoSession extends LayerS
                 Window.class, mWindow,
                 Compositor.class, mCompositor,
                 EventDispatcher.class, mEventDispatcher,
                 GeckoBundle.class, mSettings.asBundle(),
                 String.class, chromeUri,
                 screenId, isPrivate);
         }
 
-        if (mTextInput != null) {
-            mTextInput.onWindowReady(mNativeQueue, mWindow);
-        }
+        onWindowChanged();
     }
 
     public void closeWindow() {
+        ThreadUtils.assertOnUiThread();
+
+        if (!isOpen()) {
+            throw new IllegalStateException("Session is not open");
+        }
+
         if (GeckoThread.isStateAtLeast(GeckoThread.State.PROFILE_READY)) {
             mWindow.close();
             mWindow.disposeNative();
         } else {
             GeckoThread.queueNativeCallUntil(GeckoThread.State.PROFILE_READY,
                     mWindow, "close");
             GeckoThread.queueNativeCallUntil(GeckoThread.State.PROFILE_READY,
                     mWindow, "disposeNative");
         }
 
         mWindow = null;
+        onWindowChanged();
+    }
+
+    private void onWindowChanged() {
+        if (mWindow != null) {
+            mTextInput.onWindowChanged(mWindow);
+        }
     }
 
     /**
      * Get the TextInputController instance for this session.
      *
      * @return TextInputController instance.
      */
     public @NonNull TextInputController getTextInputController() {
--- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/TextInputController.java
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/TextInputController.java
@@ -85,33 +85,35 @@ public final class TextInputController {
                               String actionHint, int flag);
         void onSelectionChange();
         void onTextChange();
         void onDefaultKeyEvent(KeyEvent event);
         void updateCompositionRects(final RectF[] aRects);
     }
 
     private final GeckoSession mSession;
+    private final NativeQueue mQueue;
     private final GeckoEditable mEditable = new GeckoEditable();
     private final GeckoEditableChild mEditableChild = new GeckoEditableChild(mEditable);
     private Delegate mInputConnection;
 
-    /* package */ TextInputController(final @NonNull GeckoSession session) {
+    /* package */ TextInputController(final @NonNull GeckoSession session,
+                                      final @NonNull NativeQueue queue) {
         mSession = session;
+        mQueue = queue;
         mEditable.setDefaultEditableChild(mEditableChild);
     }
 
-    /* package */ void onWindowReady(final NativeQueue queue,
-                                     final GeckoSession.Window window) {
-        if (queue.isReady()) {
+    /* package */ void onWindowChanged(final GeckoSession.Window window) {
+        if (mQueue.isReady()) {
             window.attachEditable(mEditable, mEditableChild);
         } else {
-            queue.queueUntilReady(window, "attachEditable",
-                                  IGeckoEditableParent.class, mEditable,
-                                  GeckoEditableChild.class, mEditableChild);
+            mQueue.queueUntilReady(window, "attachEditable",
+                                   IGeckoEditableParent.class, mEditable,
+                                   GeckoEditableChild.class, mEditableChild);
         }
     }
 
     /**
      * Get a Handler for the background input method thread. In order to use a background
      * thread for input method operations on systems prior to Nougat, first override
      * {@code View.getHandler()} for the View returning the InputConnection instance, and
      * then call this method from the overridden method.
@@ -132,23 +134,28 @@ public final class TextInputController {
     public @NonNull Handler getHandler(final @NonNull Handler defHandler) {
         // May be called on any thread.
         if (mInputConnection != null) {
             return mInputConnection.getHandler(defHandler);
         }
         return defHandler;
     }
 
-    private synchronized void ensureInputConnection() {
+    private synchronized boolean ensureInputConnection() {
+        if (!mQueue.isReady()) {
+            return false;
+        }
+
         if (mInputConnection == null) {
             mInputConnection = GeckoInputConnection.create(mSession,
                                                            /* view */ null,
                                                            mEditable);
             mEditable.setListener((EditableListener) mInputConnection);
         }
+        return true;
     }
 
     /**
      * Get the current View for text input.
      *
      * @return Current text input View or null if not set.
      * @see #setView(View)
      */
@@ -176,86 +183,97 @@ public final class TextInputController {
 
     /**
      * Get an InputConnection instance. For full functionality, call {@link
      * #setView(View)} first before calling this method.
      *
      * @param outAttrs EditorInfo instance to be filled on return.
      * @return InputConnection instance or null if input method is not active.
      */
-    public synchronized @Nullable InputConnection onCreateInputConnection(
-            final @NonNull EditorInfo attrs) {
+    public @Nullable InputConnection onCreateInputConnection(final @NonNull EditorInfo attrs) {
         // May be called on any thread.
-        ensureInputConnection();
+        if (!ensureInputConnection()) {
+            return null;
+        }
         return mInputConnection.onCreateInputConnection(attrs);
     }
 
     /**
      * Process a KeyEvent as a pre-IME event.
      *
      * @param keyCode Key code.
      * @param event KeyEvent instance.
      * @return True if the event was handled.
      */
     public boolean onKeyPreIme(final int keyCode, final @NonNull KeyEvent event) {
         ThreadUtils.assertOnUiThread();
-        ensureInputConnection();
+        if (!ensureInputConnection()) {
+            return false;
+        }
         return mInputConnection.onKeyPreIme(keyCode, event);
     }
 
     /**
      * Process a KeyEvent as a key-down event.
      *
      * @param keyCode Key code.
      * @param event KeyEvent instance.
      * @return True if the event was handled.
      */
     public boolean onKeyDown(final int keyCode, final @NonNull KeyEvent event) {
         ThreadUtils.assertOnUiThread();
-        ensureInputConnection();
+        if (!ensureInputConnection()) {
+            return false;
+        }
         return mInputConnection.onKeyDown(keyCode, event);
     }
 
     /**
      * Process a KeyEvent as a key-up event.
      *
      * @param keyCode Key code.
      * @param event KeyEvent instance.
      * @return True if the event was handled.
      */
     public boolean onKeyUp(final int keyCode, final @NonNull KeyEvent event) {
         ThreadUtils.assertOnUiThread();
-        ensureInputConnection();
+        if (!ensureInputConnection()) {
+            return false;
+        }
         return mInputConnection.onKeyUp(keyCode, event);
     }
 
     /**
      * Process a KeyEvent as a long-press event.
      *
      * @param keyCode Key code.
      * @param event KeyEvent instance.
      * @return True if the event was handled.
      */
     public boolean onKeyLongPress(final int keyCode, final @NonNull KeyEvent event) {
         ThreadUtils.assertOnUiThread();
-        ensureInputConnection();
+        if (!ensureInputConnection()) {
+            return false;
+        }
         return mInputConnection.onKeyLongPress(keyCode, event);
     }
 
     /**
      * Process a KeyEvent as a multiple-press event.
      *
      * @param keyCode Key code.
      * @param event KeyEvent instance.
      * @return True if the event was handled.
      */
     public boolean onKeyMultiple(final int keyCode, final int repeatCount,
                                  final @NonNull KeyEvent event) {
         ThreadUtils.assertOnUiThread();
-        ensureInputConnection();
+        if (!ensureInputConnection()) {
+            return false;
+        }
         return mInputConnection.onKeyMultiple(keyCode, repeatCount, event);
     }
 
     /**
      * Return whether there is an active input connection, usually as a result of a
      * focused input field.
      *
      * @return True if input is active.
--- a/widget/android/nsWindow.cpp
+++ b/widget/android/nsWindow.cpp
@@ -1370,30 +1370,35 @@ nsWindow::GeckoViewSupport::Transfer(con
 void
 nsWindow::GeckoViewSupport::AttachEditable(const GeckoSession::Window::LocalRef& inst,
                                            jni::Object::Param aEditableParent,
                                            jni::Object::Param aEditableChild)
 {
     java::GeckoEditableChild::LocalRef editableChild(inst.Env());
     editableChild = java::GeckoEditableChild::Ref::From(aEditableChild);
 
-    MOZ_ASSERT(!window.mEditableSupport);
+    if (window.mEditableSupport) {
+        window.mEditableSupport.Detach();
+    }
+
     window.mEditableSupport.Attach(editableChild, &window, editableChild);
     window.mEditableParent = aEditableParent;
 }
 
 void
 nsWindow::InitNatives()
 {
     nsWindow::GeckoViewSupport::Base::Init();
     nsWindow::LayerViewSupport::Init();
     nsWindow::NPZCSupport::Init();
     if (jni::IsFennec()) {
         nsWindow::PMPMSupport::Init();
     }
+
+    GeckoEditableSupport::Init();
 }
 
 nsWindow*
 nsWindow::TopWindow()
 {
     if (!gTopLevelWindows.IsEmpty())
         return gTopLevelWindows[0];
     return nullptr;