Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(8)

Unified Diff: content/public/android/java/src/org/chromium/content/browser/ContentViewCore.java

Issue 2827263006: Revert "Refactor ContentViewClient (6/6)" (Closed)
Patch Set: Revert "Let ImeAdapterAndroid have the same lifecycle as its Java peer" Created 3 years, 8 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
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(

Powered by Google App Engine
This is Rietveld 408576698