author | Jim Chen <nchen@mozilla.com> |
Wed, 13 Dec 2017 22:57:21 -0500 | |
changeset 396427 | 3e18c58d0320ef834bcf6b9f76dad2f8f1397315 |
parent 396365 | 57a108d1c90a9979e2b79dbf138e055ee07ed97b |
child 396428 | 29a3c1e94980304151b3b9b58f03d83b4a70ad5b |
push id | 98313 |
push user | nbeleuzu@mozilla.com |
push date | Fri, 15 Dec 2017 01:48:15 +0000 |
treeherder | mozilla-inbound@cebcea19cfb0 [default view] [failures only] |
perfherder | [talos] [build metrics] [platform microbench] (compared to previous push) |
reviewers | esawin |
bugs | 1416918 |
milestone | 59.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
|
--- a/mobile/android/base/moz.build +++ b/mobile/android/base/moz.build @@ -446,16 +446,17 @@ gvjar.sources += [geckoview_source_dir + 'process/GeckoProcessManager.java', 'process/GeckoServiceChildProcess.java', 'ScreenManagerHelper.java', 'ScreenOrientationDelegate.java', 'sqlite/ByteBufferInputStream.java', 'sqlite/MatrixBlobCursor.java', 'sqlite/SQLiteBridge.java', 'sqlite/SQLiteBridgeException.java', + 'TextInputController.java', 'TouchEventInterceptor.java', 'WakeLockDelegate.java', ]] if CONFIG['MOZ_ANDROID_HLS_SUPPORT'] and CONFIG['MOZ_BUILD_MOBILE_ANDROID_WITH_GRADLE']: gvjar.sources += [geckoview_source_dir + 'java/org/mozilla/gecko/' + x for x in [ 'media/GeckoHlsAudioRenderer.java', 'media/GeckoHlsPlayer.java',
--- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/GeckoSession.java +++ b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/GeckoSession.java @@ -11,28 +11,30 @@ import java.util.ArrayList; import java.util.Arrays; import org.mozilla.gecko.annotation.WrapForJNI; import org.mozilla.gecko.gfx.LayerSession; import org.mozilla.gecko.mozglue.JNIObject; import org.mozilla.gecko.util.BundleEventListener; import org.mozilla.gecko.util.EventCallback; import org.mozilla.gecko.util.GeckoBundle; +import org.mozilla.gecko.util.ThreadUtils; import android.content.ContentResolver; import android.content.Context; import android.content.res.Resources; import android.database.Cursor; import android.net.Uri; import android.os.Binder; import android.os.IBinder; import android.os.IInterface; import android.os.Parcel; import android.os.Parcelable; import android.os.SystemClock; +import android.support.annotation.NonNull; import android.util.AttributeSet; import android.util.DisplayMetrics; import android.util.Log; public class GeckoSession extends LayerSession implements Parcelable { private static final String LOGTAG = "GeckoSession"; private static final boolean DEBUG = false; @@ -60,16 +62,18 @@ 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 GeckoSessionHandler<ContentListener> mContentHandler = new GeckoSessionHandler<ContentListener>( "GeckoViewContent", this, new String[]{ "GeckoView:ContextMenu", "GeckoView:DOMTitleChanged", "GeckoView:FullScreenEnter", "GeckoView:FullScreenExit" @@ -323,16 +327,20 @@ public class GeckoSession extends LayerS nativeQueue.setState(mNativeQueue.getState()); mNativeQueue = nativeQueue; } } @WrapForJNI(dispatchTo = "proxy") public native void attach(GeckoView view); + @WrapForJNI(dispatchTo = "proxy") + public native void attachEditable(IGeckoEditableParent parent, + GeckoEditableChild child); + @WrapForJNI(calledFrom = "gecko") private synchronized void onReady() { if (mNativeQueue.checkAndSetState(State.INITIAL, State.READY)) { Log.i(LOGTAG, "zerdatime " + SystemClock.elapsedRealtime() + " - chrome startup finished"); } } } @@ -466,16 +474,18 @@ public class GeckoSession extends LayerS } } public boolean isOpen() { return mWindow != null; } public void openWindow(final Context appContext) { + ThreadUtils.assertOnUiThread(); + if (isOpen()) { throw new IllegalStateException("Session is open"); } if (!GeckoThread.isLaunched()) { final boolean multiprocess = mSettings.getBoolean(GeckoSessionSettings.USE_MULTIPROCESS); preload(appContext, /* geckoArgs */ null, multiprocess); @@ -496,16 +506,20 @@ public class GeckoSession extends LayerS Window.class, "open", 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); + } } public void attachView(final GeckoView view) { if (view == null) { return; } if (GeckoThread.isStateAtLeast(GeckoThread.State.PROFILE_READY)) { @@ -527,16 +541,26 @@ public class GeckoSession extends LayerS GeckoThread.queueNativeCallUntil(GeckoThread.State.PROFILE_READY, mWindow, "disposeNative"); } mWindow = null; } /** + * Get the TextInputController instance for this session. + * + * @return TextInputController instance. + */ + public @NonNull TextInputController getTextInputController() { + // May be called on any thread. + return mTextInput; + } + + /** * Load the given URI. * @param uri The URI of the resource to load. */ public void loadUri(String uri) { final GeckoBundle msg = new GeckoBundle(); msg.putString("uri", uri); mEventDispatcher.dispatch("GeckoView:LoadUri", msg); }
new file mode 100644 --- /dev/null +++ b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/TextInputController.java @@ -0,0 +1,213 @@ +/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; 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.util.ThreadUtils; + +import android.os.Handler; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.view.KeyEvent; +import android.view.View; +import android.view.inputmethod.EditorInfo; +import android.view.inputmethod.InputConnection; + +/** + * TextInputController handles text input for GeckoSession through key events or input + * methods. It is typically used to implement certain methods in View such as {@code + * onCreateInputConnection()}, by forwarding such calls to corresponding methods in + * TextInputController. + */ +public final class TextInputController { + + /* package */ interface Delegate { + View getView(); + Handler getHandler(Handler defHandler); + InputConnection onCreateInputConnection(EditorInfo attrs); + boolean onKeyPreIme(int keyCode, KeyEvent event); + boolean onKeyDown(int keyCode, KeyEvent event); + boolean onKeyUp(int keyCode, KeyEvent event); + boolean onKeyLongPress(int keyCode, KeyEvent event); + boolean onKeyMultiple(int keyCode, int repeatCount, KeyEvent event); + boolean isInputActive(); + } + + private final GeckoSession mSession; + private final GeckoEditable mEditable = new GeckoEditable(); + private final GeckoEditableChild mEditableChild = new GeckoEditableChild(mEditable); + private Delegate mInputConnection; + + /* package */ TextInputController(final @NonNull GeckoSession session) { + mSession = session; + mEditable.setDefaultEditableChild(mEditableChild); + } + + /* package */ void onWindowReady(final NativeQueue queue, + final GeckoSession.Window window) { + if (queue.isReady()) { + window.attachEditable(mEditable, mEditableChild); + } else { + queue.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. + * + * For example: <pre>{@code + * @Override + * public Handler getHandler() { + * if (Build.VERSION.SDK_INT >= 24) { + * return super.getHandler(); + * } + * return getSession().getTextInputController().getHandler(super.getHandler()); + * } + * }</pre> + * + * @param defHandler Handler returned by the system {@code getHandler} implementation. + * @return Handler to return to the system through {@code getHandler}. + */ + 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() { + if (mInputConnection == null) { + mInputConnection = GeckoInputConnection.create(mSession, + /* view */ null, + mEditable); + mEditable.setListener((GeckoEditableListener) mInputConnection); + } + } + + /** + * Get the current View for text input. + * + * @return Current text input View or null if not set. + * @see #setView(View) + */ + public @Nullable View getView() { + ThreadUtils.assertOnUiThread(); + return mInputConnection != null ? mInputConnection.getView() : null; + } + + /** + * Set the View for text input. The current View is used to interact with the system + * input method manager and to display certain text input UI elements. + * + * @param view Text input View or null to clear current View. + */ + public synchronized void setView(final @Nullable View view) { + ThreadUtils.assertOnUiThread(); + + if (view == null) { + mInputConnection = null; + } else if (mInputConnection == null || mInputConnection.getView() != view) { + mInputConnection = GeckoInputConnection.create(mSession, view, mEditable); + } + mEditable.setListener((GeckoEditableListener) mInputConnection); + } + + /** + * 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) { + // May be called on any thread. + ensureInputConnection(); + 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(); + 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(); + 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(); + 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(); + 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(); + 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. + */ + public boolean isInputActive() { + ThreadUtils.assertOnUiThread(); + return mInputConnection != null && mInputConnection.isInputActive(); + } +}
--- a/widget/android/nsWindow.cpp +++ b/widget/android/nsWindow.cpp @@ -296,16 +296,20 @@ public: jni::Object::Param aCompositor, jni::Object::Param aDispatcher, jni::Object::Param aSettings); // Reattach this nsWindow to a new GeckoView. void Attach(const GeckoSession::Window::LocalRef& inst, jni::Object::Param aView); + void AttachEditable(const GeckoSession::Window::LocalRef& inst, + jni::Object::Param aEditableParent, + jni::Object::Param aEditableChild); + void EnableEventDispatcher(); }; /** * NativePanZoomController handles its native calls on the UI thread, so make * it separate from GeckoViewSupport. */ class nsWindow::NPZCSupport final @@ -1222,20 +1226,20 @@ public: ANativeWindow* nsWindow::PMPMSupport::sWindow; EGLSurface nsWindow::PMPMSupport::sSurface; nsWindow::GeckoViewSupport::~GeckoViewSupport() { // Disassociate our GeckoEditable instance with our native object. - MOZ_ASSERT(window.mEditableSupport && window.mEditable); - window.mEditableSupport.Detach(); - window.mEditable->OnViewChange(nullptr); - window.mEditable = nullptr; + if (window.mEditableSupport) { + window.mEditableSupport.Detach(); + window.mEditableParent = nullptr; + } if (window.mNPZCSupport) { window.mNPZCSupport.Detach(); } if (window.mLayerViewSupport) { window.mLayerViewSupport.Detach(); } @@ -1297,23 +1301,16 @@ nsWindow::GeckoViewSupport::Open(const j mozilla::MakeUnique<GeckoViewSupport>(window, sessionWindow); window->mGeckoViewSupport->mDOMWindow = pdomWindow; window->mAndroidView = androidView; // Attach other session support objects. window->mGeckoViewSupport->Transfer( sessionWindow, aCompositor, aDispatcher, aSettings); - // Attach a new GeckoEditable support object to the new window. - auto editable = GeckoEditable::New(); - auto editableChild = GeckoEditableChild::New(editable); - editable->SetDefaultEditableChild(editableChild); - window->mEditable = editable; - window->mEditableSupport.Attach(editableChild, window, editableChild); - if (window->mWidgetListener) { nsCOMPtr<nsIXULWindow> xulWindow( window->mWidgetListener->GetXULWindow()); if (xulWindow) { // Our window is not intrinsically sized, so tell nsXULWindow to // not set a size for us. xulWindow->SetIntrinsicallySized(false); } @@ -1373,19 +1370,29 @@ nsWindow::GeckoViewSupport::Transfer(con bridge->SendForceIsFirstPaint(); } } void nsWindow::GeckoViewSupport::Attach(const GeckoSession::Window::LocalRef& inst, jni::Object::Param aView) { - // Associate our previous GeckoEditable with the new GeckoView. - MOZ_ASSERT(window.mEditable); - window.mEditable->OnViewChange(aView); +} + +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); + window.mEditableSupport.Attach(editableChild, &window, editableChild); + window.mEditableParent = aEditableParent; } void nsWindow::InitNatives() { nsWindow::GeckoViewSupport::Base::Init(); nsWindow::LayerViewSupport::Init(); nsWindow::NPZCSupport::Init();
--- a/widget/android/nsWindow.h +++ b/widget/android/nsWindow.h @@ -179,17 +179,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; + mozilla::jni::Object::GlobalRef mEditableParent; 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; @@ -304,17 +304,17 @@ public: void SetContentDocumentDisplayed(bool aDisplayed); bool IsContentDocumentDisplayed(); // Call this function when the users activity is the direct cause of an // event (like a keypress or mouse click). void UserActivity(); - mozilla::java::GeckoEditable::Ref& GetEditableParent() { return mEditable; } + mozilla::jni::Object::Ref& GetEditableParent() { return mEditableParent; } void RecvToolbarAnimatorMessageFromCompositor(int32_t aMessage) override; void UpdateRootFrameMetrics(const ScreenPoint& aScrollOffset, const CSSToScreenScale& aZoom) override; void RecvScreenPixels(mozilla::ipc::Shmem&& aMem, const ScreenIntSize& aSize) override; protected: void BringToFront(); nsWindow *FindTopLevel(); bool IsTopLevel();