| Index: content/public/android/java/src/org/chromium/content/browser/ContentViewCore.java
|
| diff --git a/content/public/android/java/src/org/chromium/content/browser/ContentViewCore.java b/content/public/android/java/src/org/chromium/content/browser/ContentViewCore.java
|
| index 68d484b08d3fa8644d8b3d7f837068f86a9549ff..c1795c6bb116b04909059022d43449d1410e6635 100644
|
| --- a/content/public/android/java/src/org/chromium/content/browser/ContentViewCore.java
|
| +++ b/content/public/android/java/src/org/chromium/content/browser/ContentViewCore.java
|
| @@ -17,6 +17,8 @@ import android.graphics.Bitmap;
|
| import android.graphics.Rect;
|
| import android.os.Build;
|
| import android.os.Bundle;
|
| +import android.os.Handler;
|
| +import android.os.ResultReceiver;
|
| import android.os.SystemClock;
|
| import android.util.Pair;
|
| import android.view.ActionMode;
|
| @@ -35,6 +37,7 @@ import android.view.accessibility.AccessibilityManager.AccessibilityStateChangeL
|
| import android.view.accessibility.AccessibilityNodeProvider;
|
| import android.view.inputmethod.EditorInfo;
|
| import android.view.inputmethod.InputConnection;
|
| +import android.view.inputmethod.InputMethodManager;
|
|
|
| import org.chromium.base.ObserverList;
|
| import org.chromium.base.ObserverList.RewindableIterator;
|
| @@ -58,7 +61,6 @@ import org.chromium.content_public.browser.AccessibilitySnapshotCallback;
|
| import org.chromium.content_public.browser.AccessibilitySnapshotNode;
|
| import org.chromium.content_public.browser.ActionModeCallbackHelper;
|
| import org.chromium.content_public.browser.GestureStateListener;
|
| -import org.chromium.content_public.browser.ImeEventObserver;
|
| import org.chromium.content_public.browser.WebContents;
|
| import org.chromium.content_public.browser.WebContentsObserver;
|
| import org.chromium.device.gamepad.GamepadList;
|
| @@ -66,6 +68,7 @@ import org.chromium.ui.base.DeviceFormFactor;
|
| import org.chromium.ui.base.EventForwarder;
|
| import org.chromium.ui.base.ViewAndroidDelegate;
|
| import org.chromium.ui.base.WindowAndroid;
|
| +import org.chromium.ui.base.ime.TextInputType;
|
| import org.chromium.ui.display.DisplayAndroid;
|
| import org.chromium.ui.display.DisplayAndroid.DisplayAndroidObserver;
|
|
|
| @@ -89,7 +92,7 @@ import java.util.Map;
|
| public class ContentViewCore
|
| implements AccessibilityStateChangeListener, DisplayAndroidObserver,
|
| SystemCaptioningBridge.SystemCaptioningBridgeListener, WindowAndroidProvider,
|
| - JoystickZoomProvider.PinchZoomHandler, ImeEventObserver {
|
| + JoystickZoomProvider.PinchZoomHandler {
|
| private static final String TAG = "cr_ContentViewCore";
|
|
|
| // Used to avoid enabling zooming in / out if resulting zooming will
|
| @@ -171,6 +174,32 @@ public class ContentViewCore
|
| }
|
|
|
| /**
|
| + * {@ResultReceiver} passed in InputMethodManager#showSoftInput}. We need this to scroll to the
|
| + * editable node at the right timing, which is after input method window shows up.
|
| + */
|
| + // TODO(crbug.com/635567): Fix this properly.
|
| + @SuppressLint("ParcelCreator")
|
| + private static class ShowKeyboardResultReceiver extends ResultReceiver {
|
| +
|
| + // Unfortunately, the memory life cycle of ResultReceiver object, once passed in
|
| + // showSoftInput(), is in the control of Android's input method framework and IME app,
|
| + // so we use a weakref to avoid tying CVC's lifetime to that of ResultReceiver object.
|
| + private final WeakReference<ContentViewCore> mContentViewCore;
|
| +
|
| + public ShowKeyboardResultReceiver(ContentViewCore contentViewCore, Handler handler) {
|
| + super(handler);
|
| + mContentViewCore = new WeakReference<>(contentViewCore);
|
| + }
|
| +
|
| + @Override
|
| + public void onReceiveResult(int resultCode, Bundle resultData) {
|
| + ContentViewCore contentViewCore = mContentViewCore.get();
|
| + if (contentViewCore == null) return;
|
| + contentViewCore.onShowKeyboardReceiveResult(resultCode);
|
| + }
|
| + }
|
| +
|
| + /**
|
| * Interface that consumers of {@link ContentViewCore} must implement to allow the proper
|
| * dispatching of view methods through the containing view.
|
| *
|
| @@ -223,6 +252,8 @@ public class ContentViewCore
|
| private WebContents mWebContents;
|
| private WebContentsObserver mWebContentsObserver;
|
|
|
| + private ContentViewClient mContentViewClient;
|
| +
|
| // Native pointer to C++ ContentViewCoreImpl object which will be set by nativeInit().
|
| private long mNativeContentViewCore;
|
|
|
| @@ -291,6 +322,10 @@ public class ContentViewCore
|
| // WebView.
|
| private boolean mShouldSetAccessibilityFocusOnPageLoad;
|
|
|
| + // Temporary notification to tell onSizeChanged to focus a form element,
|
| + // because the OSK was just brought up.
|
| + private final Rect mFocusPreOSKViewportRect = new Rect();
|
| +
|
| // Whether a touch scroll sequence is active, used to hide text selection
|
| // handles. Note that a scroll sequence will *always* bound a pinch
|
| // sequence, so this will also be true for the duration of a pinch gesture.
|
| @@ -321,6 +356,10 @@ public class ContentViewCore
|
| // A ViewAndroidDelegate that delegates to the current container view.
|
| private ViewAndroidDelegate mViewAndroidDelegate;
|
|
|
| + // NOTE: This object will not be released by Android framework until the matching
|
| + // ResultReceiver in the InputMethodService (IME app) gets gc'ed.
|
| + private ShowKeyboardResultReceiver mShowKeyboardResultReceiver;
|
| +
|
| private Boolean mHasViewFocus;
|
|
|
| // The list of observers that are notified when ContentViewCore changes its WindowAndroid.
|
| @@ -435,10 +474,6 @@ public class ContentViewCore
|
| if (mNativeContentViewCore != 0) nativeWasResized(mNativeContentViewCore);
|
| }
|
|
|
| - public void addImeEventObserver(ImeEventObserver imeEventObserver) {
|
| - mImeAdapter.addEventObserver(imeEventObserver);
|
| - }
|
| -
|
| @VisibleForTesting
|
| public void setImeAdapterForTest(ImeAdapter imeAdapter) {
|
| mImeAdapter = imeAdapter;
|
| @@ -449,6 +484,55 @@ public class ContentViewCore
|
| return mImeAdapter;
|
| }
|
|
|
| + private ImeAdapter createImeAdapter() {
|
| + return new ImeAdapter(
|
| + new InputMethodManagerWrapper(mContext), new ImeAdapter.ImeAdapterDelegate() {
|
| + @Override
|
| + public void onImeEvent() {
|
| + mPopupZoomer.hide(true);
|
| + getContentViewClient().onImeEvent();
|
| + if (isFocusedNodeEditable()) mWebContents.dismissTextHandles();
|
| + }
|
| +
|
| + @Override
|
| + public void onKeyboardBoundsUnchanged() {
|
| + assert mWebContents != null;
|
| + mWebContents.scrollFocusedEditableNodeIntoView();
|
| + }
|
| +
|
| + @Override
|
| + public boolean performContextMenuAction(int id) {
|
| + assert mWebContents != null;
|
| + switch (id) {
|
| + case android.R.id.selectAll:
|
| + mWebContents.selectAll();
|
| + return true;
|
| + case android.R.id.cut:
|
| + mWebContents.cut();
|
| + return true;
|
| + case android.R.id.copy:
|
| + mWebContents.copy();
|
| + return true;
|
| + case android.R.id.paste:
|
| + mWebContents.paste();
|
| + return true;
|
| + default:
|
| + return false;
|
| + }
|
| + }
|
| +
|
| + @Override
|
| + public View getAttachedView() {
|
| + return mContainerView;
|
| + }
|
| +
|
| + @Override
|
| + public ResultReceiver getNewShowKeyboardReceiver() {
|
| + return ContentViewCore.this.getNewShowKeyboardReceiver();
|
| + }
|
| + });
|
| + }
|
| +
|
| /**
|
| *
|
| * @param viewDelegate Delegate to add/remove anchor views.
|
| @@ -486,14 +570,13 @@ public class ContentViewCore
|
| setContainerViewInternals(internalDispatcher);
|
|
|
| initPopupZoomer(mContext);
|
| - mImeAdapter = new ImeAdapter(
|
| - mWebContents, mContainerView, new InputMethodManagerWrapper(mContext));
|
| - mImeAdapter.addEventObserver(this);
|
| + mImeAdapter = createImeAdapter();
|
| + attachImeAdapter();
|
|
|
| - mSelectionPopupController = new SelectionPopupController(
|
| - mContext, windowAndroid, webContents, mContainerView, mRenderCoordinates);
|
| + mSelectionPopupController = new SelectionPopupController(mContext, windowAndroid,
|
| + webContents, viewDelegate.getContainerView(), mRenderCoordinates, mImeAdapter);
|
| mSelectionPopupController.setCallback(ActionModeCallbackHelper.EMPTY_CALLBACK);
|
| - mSelectionPopupController.setContainerView(mContainerView);
|
| + mSelectionPopupController.setContainerView(getContainerView());
|
|
|
| mWebContentsObserver = new ContentViewWebContentsObserver(this);
|
|
|
| @@ -581,7 +664,6 @@ public class ContentViewCore
|
| if (mContainerView != null) {
|
| hideSelectPopupWithCancelMessage();
|
| mPopupZoomer.hide(false);
|
| - mImeAdapter.setContainerView(containerView);
|
| }
|
|
|
| mContainerView = containerView;
|
| @@ -687,6 +769,12 @@ public class ContentViewCore
|
| mWebContentsObserver.destroy();
|
| mWebContentsObserver = null;
|
| mImeAdapter.resetAndHideKeyboard();
|
| + // TODO(igsolla): address TODO in ContentViewClient because ContentViewClient is not
|
| + // currently a real Null Object.
|
| + //
|
| + // Instead of deleting the client we use the Null Object pattern to avoid null checks
|
| + // in this class.
|
| + mContentViewClient = new ContentViewClient();
|
| mWebContents = null;
|
| mNativeContentViewCore = 0;
|
| mJavaScriptInterfaces.clear();
|
| @@ -709,6 +797,27 @@ public class ContentViewCore
|
| return mNativeContentViewCore != 0;
|
| }
|
|
|
| + public void setContentViewClient(ContentViewClient client) {
|
| + if (client == null) {
|
| + throw new IllegalArgumentException("The client can't be null.");
|
| + }
|
| + mContentViewClient = client;
|
| + }
|
| +
|
| + @VisibleForTesting
|
| + public ContentViewClient getContentViewClient() {
|
| + if (mContentViewClient == null) {
|
| + // We use the Null Object pattern to avoid having to perform a null check in this class.
|
| + // We create it lazily because most of the time a client will be set almost immediately
|
| + // after ContentView is created.
|
| + mContentViewClient = new ContentViewClient();
|
| + // We don't set the native ContentViewClient pointer here on purpose. The native
|
| + // implementation doesn't mind a null delegate and using one is better than passing a
|
| + // Null Object, since we cut down on the number of JNI calls.
|
| + }
|
| + return mContentViewClient;
|
| + }
|
| +
|
| /**
|
| * @return Viewport width in physical pixels as set from onSizeChanged.
|
| */
|
| @@ -726,6 +835,14 @@ public class ContentViewCore
|
| }
|
|
|
| /**
|
| + * @return Viewport height when the OSK is hidden in physical pixels as set from onSizeChanged.
|
| + */
|
| + @CalledByNative
|
| + public int getViewportHeightWithOSKHiddenPix() {
|
| + return mViewportHeightPix + getContentViewClient().getSystemWindowInsetBottom();
|
| + }
|
| +
|
| + /**
|
| * @return Width of underlying physical surface.
|
| */
|
| @CalledByNative
|
| @@ -1179,13 +1296,12 @@ public class ContentViewCore
|
|
|
| // Execute a delayed form focus operation because the OSK was brought
|
| // up earlier.
|
| - Rect focusPreOSKViewportRect = mImeAdapter.getFocusPreOSKViewportRect();
|
| - if (!focusPreOSKViewportRect.isEmpty()) {
|
| + if (!mFocusPreOSKViewportRect.isEmpty()) {
|
| Rect rect = new Rect();
|
| getContainerView().getWindowVisibleDisplayFrame(rect);
|
| - if (!rect.equals(focusPreOSKViewportRect)) {
|
| + if (!rect.equals(mFocusPreOSKViewportRect)) {
|
| // Only assume the OSK triggered the onSizeChanged if width was preserved.
|
| - if (rect.width() == focusPreOSKViewportRect.width()) {
|
| + if (rect.width() == mFocusPreOSKViewportRect.width()) {
|
| assert mWebContents != null;
|
| mWebContents.scrollFocusedEditableNodeIntoView();
|
| }
|
| @@ -1197,7 +1313,7 @@ public class ContentViewCore
|
| private void cancelRequestToScrollFocusedEditableNodeIntoView() {
|
| // Zero-ing the rect will prevent |updateAfterSizeChanged()| from
|
| // issuing the delayed form focus event.
|
| - mImeAdapter.getFocusPreOSKViewportRect().setEmpty();
|
| + mFocusPreOSKViewportRect.setEmpty();
|
| }
|
|
|
| /**
|
| @@ -1676,17 +1792,38 @@ public class ContentViewCore
|
| TraceEvent.end("ContentViewCore:updateFrameInfo");
|
| }
|
|
|
| - // ImeEventObserver
|
| -
|
| - @Override
|
| - public void onImeEvent() {
|
| - mPopupZoomer.hide(true);
|
| + @CalledByNative
|
| + private void updateImeAdapter(long nativeImeAdapterAndroid, int textInputType,
|
| + int textInputFlags, int textInputMode, String text, int selectionStart,
|
| + int selectionEnd, int compositionStart, int compositionEnd, boolean showImeIfNeeded,
|
| + boolean replyToRequest) {
|
| + try {
|
| + TraceEvent.begin("ContentViewCore.updateImeAdapter");
|
| + boolean focusedNodeEditable = (textInputType != TextInputType.NONE);
|
| + boolean focusedNodeIsPassword = (textInputType == TextInputType.PASSWORD);
|
| +
|
| + mImeAdapter.attach(nativeImeAdapterAndroid);
|
| + mImeAdapter.updateState(textInputType, textInputFlags, textInputMode, showImeIfNeeded,
|
| + text, selectionStart, selectionEnd, compositionStart, compositionEnd,
|
| + replyToRequest);
|
| +
|
| + boolean editableToggled = (focusedNodeEditable != isFocusedNodeEditable());
|
| + mSelectionPopupController.updateSelectionState(focusedNodeEditable,
|
| + focusedNodeIsPassword);
|
| + if (editableToggled) {
|
| + if (mJoystickScrollProvider != null) {
|
| + mJoystickScrollProvider.setEnabled(!focusedNodeEditable);
|
| + }
|
| + getContentViewClient().onFocusedNodeEditabilityChanged(focusedNodeEditable);
|
| + }
|
| + } finally {
|
| + TraceEvent.end("ContentViewCore.updateImeAdapter");
|
| + }
|
| }
|
|
|
| - @Override
|
| - public void onNodeAttributeUpdated(boolean editable, boolean password) {
|
| - if (mJoystickScrollProvider != null) mJoystickScrollProvider.setEnabled(!editable);
|
| - mSelectionPopupController.updateSelectionState(editable, password);
|
| + @CalledByNative
|
| + private void forceUpdateImeAdapter(long nativeImeAdapterAndroid) {
|
| + mImeAdapter.attach(nativeImeAdapterAndroid);
|
| }
|
|
|
| /**
|
| @@ -1799,16 +1936,29 @@ public class ContentViewCore
|
| @SuppressWarnings("unused")
|
| @CalledByNative
|
| private void onRenderProcessChange() {
|
| + attachImeAdapter();
|
| // Immediately sync closed caption settings to the new render process.
|
| mSystemCaptioningBridge.syncToListener(this);
|
| }
|
|
|
| /**
|
| + * Attaches the native ImeAdapter object to the java ImeAdapter to allow communication via JNI.
|
| + */
|
| + public void attachImeAdapter() {
|
| + if (mImeAdapter != null && mNativeContentViewCore != 0) {
|
| + mImeAdapter.attach(nativeGetNativeImeAdapter(mNativeContentViewCore));
|
| + }
|
| + }
|
| +
|
| + /**
|
| * @see View#hasFocus()
|
| */
|
| @CalledByNative
|
| private boolean hasFocus() {
|
| - return ViewUtils.hasFocus(mContainerView);
|
| + // If the container view is not focusable, we consider it always focused from
|
| + // Chromium's point of view.
|
| + if (!mContainerView.isFocusable()) return true;
|
| + return mContainerView.hasFocus();
|
| }
|
|
|
| /**
|
| @@ -2432,6 +2582,35 @@ public class ContentViewCore
|
| mSelectionPopupController.setSelectionClient(selectionClient);
|
| }
|
|
|
| + /**
|
| + * Call this when we get result from ResultReceiver passed in calling showSoftInput().
|
| + * @param resultCode The result of showSoftInput() as defined in InputMethodManager.
|
| + */
|
| + public void onShowKeyboardReceiveResult(int resultCode) {
|
| + if (resultCode == InputMethodManager.RESULT_SHOWN) {
|
| + // If OSK is newly shown, delay the form focus until
|
| + // the onSizeChanged (in order to adjust relative to the
|
| + // new size).
|
| + // TODO(jdduke): We should not assume that onSizeChanged will
|
| + // always be called, crbug.com/294908.
|
| + getContainerView().getWindowVisibleDisplayFrame(mFocusPreOSKViewportRect);
|
| + } else if (hasFocus() && resultCode == InputMethodManager.RESULT_UNCHANGED_SHOWN) {
|
| + // If the OSK was already there, focus the form immediately.
|
| + if (mWebContents != null) {
|
| + mWebContents.scrollFocusedEditableNodeIntoView();
|
| + }
|
| + }
|
| + }
|
| +
|
| + @VisibleForTesting
|
| + public ResultReceiver getNewShowKeyboardReceiver() {
|
| + if (mShowKeyboardResultReceiver == null) {
|
| + // Note: the returned object will get leaked by Android framework.
|
| + mShowKeyboardResultReceiver = new ShowKeyboardResultReceiver(this, new Handler());
|
| + }
|
| + return mShowKeyboardResultReceiver;
|
| + }
|
| +
|
| private native long nativeInit(WebContents webContents, ViewAndroidDelegate viewAndroidDelegate,
|
| long windowAndroidPtr, float dipScale, HashSet<Object> retainedObjectSet);
|
| private static native ContentViewCore nativeFromWebContentsAndroid(WebContents webContents);
|
| @@ -2492,6 +2671,9 @@ public class ContentViewCore
|
| private native void nativeSelectPopupMenuItems(long nativeContentViewCoreImpl,
|
| long nativeSelectPopupSourceFrame, int[] indices);
|
|
|
| +
|
| + private native long nativeGetNativeImeAdapter(long nativeContentViewCoreImpl);
|
| +
|
| private native int nativeGetCurrentRenderProcessId(long nativeContentViewCoreImpl);
|
|
|
| private native void nativeSetAllowJavascriptInterfacesInspection(
|
|
|