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
--- 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;