package org.chromium.content.browser;
+import android.annotation.SuppressLint;
import android.app.Activity;
import android.app.SearchManager;
+import android.content.ClipboardManager;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.database.ContentObserver;
import android.graphics.Bitmap;
import android.graphics.Canvas;
-import android.graphics.Color;
import android.graphics.Rect;
-import android.graphics.RectF;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.provider.Browser;
import android.provider.Settings;
import android.text.Editable;
+import android.text.Selection;
import android.text.TextUtils;
import android.util.Log;
import android.util.Pair;
import android.view.InputDevice;
import android.view.KeyEvent;
import android.view.MotionEvent;
-import android.view.Surface;
import android.view.View;
import android.view.ViewGroup;
-import android.view.WindowManager;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityManager;
import android.view.accessibility.AccessibilityManager.AccessibilityStateChangeListener;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputConnection;
import android.view.inputmethod.InputMethodManager;
-import android.widget.AbsoluteLayout;
import android.widget.FrameLayout;
-import com.google.common.annotations.VisibleForTesting;
-
+import org.chromium.base.ApiCompatibilityUtils;
import org.chromium.base.CalledByNative;
import org.chromium.base.CommandLine;
import org.chromium.base.JNINamespace;
import org.chromium.base.ObserverList;
import org.chromium.base.ObserverList.RewindableIterator;
import org.chromium.base.TraceEvent;
-import org.chromium.base.WeakContext;
+import org.chromium.base.VisibleForTesting;
import org.chromium.content.R;
-import org.chromium.content.browser.ContentViewGestureHandler.MotionEventDelegate;
+import org.chromium.content.browser.ScreenOrientationListener.ScreenOrientationObserver;
import org.chromium.content.browser.accessibility.AccessibilityInjector;
import org.chromium.content.browser.accessibility.BrowserAccessibilityManager;
import org.chromium.content.browser.input.AdapterInputConnection;
-import org.chromium.content.browser.input.HandleView;
+import org.chromium.content.browser.input.GamepadList;
import org.chromium.content.browser.input.ImeAdapter;
import org.chromium.content.browser.input.ImeAdapter.AdapterInputConnectionFactory;
import org.chromium.content.browser.input.InputMethodManagerWrapper;
-import org.chromium.content.browser.input.InsertionHandleController;
+import org.chromium.content.browser.input.PastePopupMenu;
+import org.chromium.content.browser.input.PastePopupMenu.PastePopupMenuDelegate;
+import org.chromium.content.browser.input.PopupTouchHandleDrawable;
+import org.chromium.content.browser.input.PopupTouchHandleDrawable.PopupTouchHandleDrawableDelegate;
+import org.chromium.content.browser.input.SelectPopup;
import org.chromium.content.browser.input.SelectPopupDialog;
+import org.chromium.content.browser.input.SelectPopupDropdown;
import org.chromium.content.browser.input.SelectPopupItem;
-import org.chromium.content.browser.input.SelectionHandleController;
+import org.chromium.content.browser.input.SelectionEventType;
import org.chromium.content.common.ContentSwitches;
import org.chromium.content_public.browser.GestureStateListener;
import org.chromium.content_public.browser.WebContents;
+import org.chromium.ui.base.DeviceFormFactor;
import org.chromium.ui.base.ViewAndroid;
import org.chromium.ui.base.ViewAndroidDelegate;
import org.chromium.ui.base.WindowAndroid;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
+import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
+import java.util.Map.Entry;
/**
* Provides a Java-side 'wrapper' around a WebContent (native) instance.
*/
@JNINamespace("content")
public class ContentViewCore
- implements MotionEventDelegate, NavigationClient, AccessibilityStateChangeListener {
+ implements AccessibilityStateChangeListener, ScreenOrientationObserver {
private static final String TAG = "ContentViewCore";
private static final int IS_LONG_PRESS = 1;
private static final int IS_LONG_TAP = 2;
- // Length of the delay (in ms) before fading in handles after the last page movement.
- private static final int TEXT_HANDLE_FADE_IN_DELAY = 300;
+ private static final ZoomControlsDelegate NO_OP_ZOOM_CONTROLS_DELEGATE =
+ new ZoomControlsDelegate() {
+ @Override
+ public void invokeZoomPicker() {}
+ @Override
+ public void dismissZoomPicker() {}
+ @Override
+ public void updateZoomControls() {}
+ };
// If the embedder adds a JavaScript interface object that contains an indirect reference to
// the ContentViewCore, then storing a strong ref to the interface object on the native
// native side. However we still need a strong reference on the Java side to
// prevent garbage collection if the embedder doesn't maintain their own ref to the
// interface object - the Java side ref won't create a new GC root.
- // This map stores those refernces. We put into the map on addJavaScriptInterface()
- // and remove from it in removeJavaScriptInterface().
- private final Map<String, Object> mJavaScriptInterfaces = new HashMap<String, Object>();
+ // This map stores those references. We put into the map on addJavaScriptInterface()
+ // and remove from it in removeJavaScriptInterface(). The annotation class is stored for
+ // the purpose of migrating injected objects from one instance of CVC to another, which
+ // is used by Android WebView to support WebChromeClient.onCreateWindow scenario.
+ private final Map<String, Pair<Object, Class>> mJavaScriptInterfaces =
+ new HashMap<String, Pair<Object, Class>>();
// Additionally, we keep track of all Java bound JS objects that are in use on the
// current page to ensure that they are not garbage collected until the page is
private final HashSet<Object> mRetainedJavaScriptObjects = new HashSet<Object>();
/**
+ * A {@link ViewAndroidDelegate} that delegates to the current container view.
+ *
+ * <p>This delegate handles the replacement of container views transparently so
+ * that clients can safely hold to instances of this class.
+ */
+ private class ContentViewAndroidDelegate implements ViewAndroidDelegate {
+ /**
+ * Represents the position of an anchor view.
+ */
+ @VisibleForTesting
+ private class Position {
+ private final float mX;
+ private final float mY;
+ private final float mWidth;
+ private final float mHeight;
+
+ public Position(float x, float y, float width, float height) {
+ mX = x;
+ mY = y;
+ mWidth = width;
+ mHeight = height;
+ }
+ }
+
+ /**
+ * The current container view. This view can be updated with
+ * {@link #updateCurrentContainerView()}.
+ */
+ private ViewGroup mCurrentContainerView;
+
+ /**
+ * List of anchor views stored in the order in which they were acquired mapped
+ * to their position.
+ */
+ private Map<View, Position> mAnchorViews = new LinkedHashMap<View, Position>();
+
+ @Override
+ public View acquireAnchorView() {
+ View anchorView = new View(mContext);
+ mAnchorViews.put(anchorView, null);
+ mCurrentContainerView.addView(anchorView);
+ return anchorView;
+ }
+
+ @Override
+ public void setAnchorViewPosition(
+ View view, float x, float y, float width, float height) {
+ mAnchorViews.put(view, new Position(x, y, width, height));
+ doSetAnchorViewPosition(view, x, y, width, height);
+ }
+
+ @SuppressWarnings("deprecation") // AbsoluteLayout
+ private void doSetAnchorViewPosition(
+ View view, float x, float y, float width, float height) {
+ if (view.getParent() == null) {
+ // Ignore. setAnchorViewPosition has been called after the anchor view has
+ // already been released.
+ return;
+ }
+ assert view.getParent() == mCurrentContainerView;
+
+ float scale = (float) DeviceDisplayInfo.create(mContext).getDIPScale();
+
+ // The anchor view should not go outside the bounds of the ContainerView.
+ int leftMargin = Math.round(x * scale);
+ int topMargin = Math.round(mRenderCoordinates.getContentOffsetYPix() + y * scale);
+ int scaledWidth = Math.round(width * scale);
+ // ContentViewCore currently only supports these two container view types.
+ if (mCurrentContainerView instanceof FrameLayout) {
+ int startMargin;
+ if (ApiCompatibilityUtils.isLayoutRtl(mCurrentContainerView)) {
+ startMargin = mCurrentContainerView.getMeasuredWidth()
+ - Math.round((width + x) * scale);
+ } else {
+ startMargin = leftMargin;
+ }
+ if (scaledWidth + startMargin > mCurrentContainerView.getWidth()) {
+ scaledWidth = mCurrentContainerView.getWidth() - startMargin;
+ }
+ FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams(
+ scaledWidth, Math.round(height * scale));
+ ApiCompatibilityUtils.setMarginStart(lp, startMargin);
+ lp.topMargin = topMargin;
+ view.setLayoutParams(lp);
+ } else if (mCurrentContainerView instanceof android.widget.AbsoluteLayout) {
+ // This fixes the offset due to a difference in
+ // scrolling model of WebView vs. Chrome.
+ // TODO(sgurun) fix this to use mContainerViewAtCreation.getScroll[X/Y]()
+ // as it naturally accounts for scroll differences between
+ // these models.
+ leftMargin += mRenderCoordinates.getScrollXPixInt();
+ topMargin += mRenderCoordinates.getScrollYPixInt();
+
+ android.widget.AbsoluteLayout.LayoutParams lp =
+ new android.widget.AbsoluteLayout.LayoutParams(
+ scaledWidth, (int) (height * scale), leftMargin, topMargin);
+ view.setLayoutParams(lp);
+ } else {
+ Log.e(TAG, "Unknown layout " + mCurrentContainerView.getClass().getName());
+ }
+ }
+
+ @Override
+ public void releaseAnchorView(View anchorView) {
+ mAnchorViews.remove(anchorView);
+ mCurrentContainerView.removeView(anchorView);
+ }
+
+ /**
+ * Updates (or sets for the first time) the current container view to which
+ * this class delegates. Existing anchor views are transferred from the old to
+ * the new container view.
+ */
+ void updateCurrentContainerView() {
+ ViewGroup oldContainerView = mCurrentContainerView;
+ mCurrentContainerView = mContainerView;
+ for (Entry<View, Position> entry : mAnchorViews.entrySet()) {
+ View anchorView = entry.getKey();
+ Position position = entry.getValue();
+ oldContainerView.removeView(anchorView);
+ mCurrentContainerView.addView(anchorView);
+ if (position != null) {
+ doSetAnchorViewPosition(anchorView,
+ position.mX, position.mY, position.mWidth, position.mHeight);
+ }
+ }
+ }
+ }
+
+ /**
* Interface that consumers of {@link ContentViewCore} must implement to allow the proper
* dispatching of view methods through the containing view.
*
* extractSmartClipData are available.
*/
public interface SmartClipDataListener {
- public void onSmartClipDataExtracted(String result);
- }
-
- private VSyncManager.Provider mVSyncProvider;
- private VSyncManager.Listener mVSyncListener;
- private int mVSyncSubscriberCount;
- private boolean mVSyncListenerRegistered;
-
- // To avoid IPC delay we use input events to directly trigger a vsync signal in the renderer.
- // When we do this, we also need to avoid sending the real vsync signal for the current
- // frame to avoid double-ticking. This flag is used to inhibit the next vsync notification.
- private boolean mDidSignalVSyncUsingInputEvent;
-
- public VSyncManager.Listener getVSyncListener(VSyncManager.Provider vsyncProvider) {
- if (mVSyncProvider != null && mVSyncListenerRegistered) {
- mVSyncProvider.unregisterVSyncListener(mVSyncListener);
- mVSyncListenerRegistered = false;
- }
-
- mVSyncProvider = vsyncProvider;
- mVSyncListener = new VSyncManager.Listener() {
- @Override
- public void updateVSync(long tickTimeMicros, long intervalMicros) {
- if (mNativeContentViewCore != 0) {
- nativeUpdateVSyncParameters(mNativeContentViewCore, tickTimeMicros,
- intervalMicros);
- }
- }
-
- @Override
- public void onVSync(long frameTimeMicros) {
- animateIfNecessary(frameTimeMicros);
-
- if (mRequestedVSyncForInput) {
- mRequestedVSyncForInput = false;
- removeVSyncSubscriber();
- }
- if (mNativeContentViewCore != 0) {
- nativeOnVSync(mNativeContentViewCore, frameTimeMicros);
- }
- }
- };
-
- if (mVSyncSubscriberCount > 0) {
- // addVSyncSubscriber() is called before getVSyncListener.
- vsyncProvider.registerVSyncListener(mVSyncListener);
- mVSyncListenerRegistered = true;
- }
-
- return mVSyncListener;
- }
-
- @CalledByNative
- void addVSyncSubscriber() {
- if (!isVSyncNotificationEnabled()) {
- mDidSignalVSyncUsingInputEvent = false;
- }
- if (mVSyncProvider != null && !mVSyncListenerRegistered) {
- mVSyncProvider.registerVSyncListener(mVSyncListener);
- mVSyncListenerRegistered = true;
- }
- mVSyncSubscriberCount++;
- }
-
- @CalledByNative
- void removeVSyncSubscriber() {
- if (mVSyncProvider != null && mVSyncSubscriberCount == 1) {
- assert mVSyncListenerRegistered;
- mVSyncProvider.unregisterVSyncListener(mVSyncListener);
- mVSyncListenerRegistered = false;
- }
- mVSyncSubscriberCount--;
- assert mVSyncSubscriberCount >= 0;
- }
-
- @CalledByNative
- private void resetVSyncNotification() {
- while (isVSyncNotificationEnabled()) removeVSyncSubscriber();
- mVSyncSubscriberCount = 0;
- mVSyncListenerRegistered = false;
- mNeedAnimate = false;
- }
-
- private boolean isVSyncNotificationEnabled() {
- return mVSyncProvider != null && mVSyncListenerRegistered;
- }
-
- @CalledByNative
- private void setNeedsAnimate() {
- if (!mNeedAnimate) {
- mNeedAnimate = true;
- addVSyncSubscriber();
- }
+ public void onSmartClipDataExtracted(String text, String html, Rect clipRect);
}
private final Context mContext;
private ViewGroup mContainerView;
private InternalAccessDelegate mContainerViewInternals;
private WebContents mWebContents;
- private WebContentsObserverAndroid mWebContentsObserver;
+ private WebContentsObserver mWebContentsObserver;
private ContentViewClient mContentViewClient;
// Native pointer to C++ ContentViewCoreImpl object which will be set by nativeInit().
private long mNativeContentViewCore = 0;
- private boolean mInForeground = false;
-
- private ContentViewGestureHandler mContentViewGestureHandler;
private final ObserverList<GestureStateListener> mGestureStateListeners;
private final RewindableIterator<GestureStateListener> mGestureStateListenersIterator;
private ZoomControlsDelegate mZoomControlsDelegate;
private PopupZoomer mPopupZoomer;
+ private SelectPopup mSelectPopup;
+ private long mNativeSelectPopupSourceFrame = 0;
private Runnable mFakeMouseMoveRunnable = null;
private ImeAdapter mImeAdapter;
private ImeAdapter.AdapterInputConnectionFactory mAdapterInputConnectionFactory;
private AdapterInputConnection mInputConnection;
+ private InputMethodManagerWrapper mInputMethodManagerWrapper;
- private SelectionHandleController mSelectionHandleController;
- private InsertionHandleController mInsertionHandleController;
+ // Lazily created paste popup menu, triggered either via long press in an
+ // editable region or from tapping the insertion handle.
+ private PastePopupMenu mPastePopupMenu;
+ private boolean mWasPastePopupShowingOnInsertionDragStart;
- private Runnable mDeferredHandleFadeInRunnable;
+ private PopupTouchHandleDrawableDelegate mTouchHandleDelegate;
private PositionObserver mPositionObserver;
- private PositionObserver.Listener mPositionListener;
// Size of the viewport in physical pixels as set from onSizeChanged.
private int mViewportWidthPix;
private int mViewportHeightPix;
private int mPhysicalBackingWidthPix;
private int mPhysicalBackingHeightPix;
- private int mOverdrawBottomHeightPix;
- private int mViewportSizeOffsetWidthPix;
- private int mViewportSizeOffsetHeightPix;
- private int mLocationInWindowX;
- private int mLocationInWindowY;
+ private int mTopControlsLayoutHeightPix;
// Cached copy of all positions and scales as reported by the renderer.
private final RenderCoordinates mRenderCoordinates;
- private final RenderCoordinates.NormalizedPoint mStartHandlePoint;
- private final RenderCoordinates.NormalizedPoint mEndHandlePoint;
- private final RenderCoordinates.NormalizedPoint mInsertionHandlePoint;
-
// Tracks whether a selection is currently active. When applied to selected text, indicates
// whether the last selected text is still highlighted.
private boolean mHasSelection;
+ private boolean mHasInsertion;
private String mLastSelectedText;
- private boolean mSelectionEditable;
+ private boolean mFocusedNodeEditable;
private ActionMode mActionMode;
private boolean mUnselectAllOnActionModeDismiss;
+ private boolean mPreserveSelectionOnNextLossOfFocus;
+ private SelectActionModeCallback.ActionHandler mActionHandler;
// Delegate that will handle GET downloads, and be notified of completion of POST downloads.
private ContentViewDownloadDelegate mDownloadDelegate;
// System accessibility service.
private final AccessibilityManager mAccessibilityManager;
+ // Accessibility touch exploration state.
+ private boolean mTouchExplorationEnabled;
+
+ // Whether accessibility focus should be set to the page when it finishes loading.
+ // This only applies if an accessibility service like TalkBack is running.
+ // This is desirable behavior for a browser window, but not for an embedded
+ // WebView.
+ private boolean mShouldSetAccessibilityFocusOnPageLoad;
+
// Allows us to dynamically respond when the accessibility script injection flag changes.
private ContentObserver mAccessibilityScriptInjectionObserver;
// Temporary notification to tell onSizeChanged to focus a form element,
// because the OSK was just brought up.
- private boolean mUnfocusOnNextSizeChanged = false;
private final Rect mFocusPreOSKViewportRect = new Rect();
- // Used to keep track of whether we should try to undo the last zoom-to-textfield operation.
- private boolean mScrolledAndZoomedFocusedEditableNode = false;
-
- // Whether we received a new frame since consumePendingRendererFrame() was last called.
- private boolean mPendingRendererFrame = false;
+ // On tap this will store the x, y coordinates of the touch.
+ private int mLastTapX;
+ private int mLastTapY;
- // Whether we should animate at the next vsync tick.
- private boolean mNeedAnimate = false;
+ // 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.
+ private boolean mTouchScrollInProgress;
- // Whether we requested a proactive vsync event in response to touch input.
- // This reduces the latency of responding to input by ensuring the renderer
- // is sent a BeginFrame for every touch event we receive. Otherwise the
- // renderer's SetNeedsBeginFrame message would get serviced at the next
- // vsync.
- private boolean mRequestedVSyncForInput = false;
-
- // Used for tracking UMA ActionAfterDoubleTap to tell user's immediate
- // action after a double tap.
- private long mLastDoubleTapTimeMs;
-
- // On single tap this will store the x, y coordinates of the touch.
- private int mSingleTapX;
- private int mSingleTapY;
+ // The outstanding fling start events that hasn't got fling end yet. It may be > 1 because
+ // onNativeFlingStopped() is called asynchronously.
+ private int mPotentiallyActiveFlingCount;
private ViewAndroid mViewAndroid;
private SmartClipDataListener mSmartClipDataListener = null;
- /** ActionAfterDoubleTap defined in tools/metrics/histograms/histograms.xml. */
- private static class UMAActionAfterDoubleTap {
- public static final int NAVIGATE_BACK = 0;
- public static final int NAVIGATE_STOP = 1;
- public static final int NO_ACTION = 2;
- public static final int COUNT = 3;
- }
-
- /** TapDelayType defined in tools/metrics/histograms/histograms.xml. */
- private static class UMASingleTapType {
- public static final int DELAYED_TAP = 0;
- public static final int UNDELAYED_TAP = 1;
- public static final int COUNT = 2;
- }
+ // This holds the state of editable text (e.g. contents of <input>, contenteditable) of
+ // a focused element.
+ // Every time the user, IME, javascript (Blink), autofill etc. modifies the content, the new
+ // state must be reflected to this to keep consistency.
+ private final Editable mEditable;
/**
- * Used by UMA stat for tracking accidental double tap navigations. Specifies the amount of
- * time after a double tap within which actions will be recorded to the UMA stat.
+ * PID used to indicate an invalid render process.
*/
- private static final long ACTION_AFTER_DOUBLE_TAP_WINDOW_MS = 5000;
+ // Keep in sync with the value returned from ContentViewCoreImpl::GetCurrentRendererProcessId()
+ // if there is no render process.
+ public static final int INVALID_RENDER_PROCESS_PID = 0;
+
+ // Offsets for the events that passes through this ContentViewCore.
+ private float mCurrentTouchOffsetX;
+ private float mCurrentTouchOffsetY;
+
+ // Offsets for smart clip
+ private int mSmartClipOffsetX;
+ private int mSmartClipOffsetY;
+
+ // Whether the ContentViewCore requires the WebContents to be fullscreen in order to lock the
+ // screen orientation.
+ private boolean mFullscreenRequiredForOrientationLock = true;
+
+ // A ViewAndroidDelegate that delegates to the current container view.
+ private ContentViewAndroidDelegate mViewAndroidDelegate;
/**
* Constructs a new ContentViewCore. Embedders must call initialize() after constructing
public ContentViewCore(Context context) {
mContext = context;
- WeakContext.initializeWeakContext(context);
- HeapStatsLogger.init(mContext.getApplicationContext());
mAdapterInputConnectionFactory = new AdapterInputConnectionFactory();
+ mInputMethodManagerWrapper = new InputMethodManagerWrapper(mContext);
mRenderCoordinates = new RenderCoordinates();
- mRenderCoordinates.setDeviceScaleFactor(
- getContext().getResources().getDisplayMetrics().density);
- mStartHandlePoint = mRenderCoordinates.createNormalizedPoint();
- mEndHandlePoint = mRenderCoordinates.createNormalizedPoint();
- mInsertionHandlePoint = mRenderCoordinates.createNormalizedPoint();
+ float deviceScaleFactor = getContext().getResources().getDisplayMetrics().density;
+ String forceScaleFactor = CommandLine.getInstance().getSwitchValue(
+ ContentSwitches.FORCE_DEVICE_SCALE_FACTOR);
+ if (forceScaleFactor != null) {
+ deviceScaleFactor = Float.valueOf(forceScaleFactor);
+ }
+ mRenderCoordinates.setDeviceScaleFactor(deviceScaleFactor);
mAccessibilityManager = (AccessibilityManager)
getContext().getSystemService(Context.ACCESSIBILITY_SERVICE);
mGestureStateListeners = new ObserverList<GestureStateListener>();
mGestureStateListenersIterator = mGestureStateListeners.rewindableIterator();
+
+ mEditable = Editable.Factory.getInstance().newEditable("");
+ Selection.setSelection(mEditable, 0);
}
/**
return mWebContents;
}
+ /* TODO(aelias): Remove this after downstream callers switch to setTopControlsLayoutHeight. */
+ public void setViewportSizeOffset(int offsetXPix, int offsetYPix) {
+ setTopControlsLayoutHeight(offsetYPix);
+ }
+
/**
- * Specifies how much smaller the WebKit layout size should be relative to the size of this
+ * Specifies how much smaller the Blink layout size should be relative to the size of this
* view.
- * @param offsetXPix The X amount in pixels to shrink the viewport by.
- * @param offsetYPix The Y amount in pixels to shrink the viewport by.
+ * @param topControlsLayoutHeightPix The Y amount in pixels to shrink the viewport by.
*/
- public void setViewportSizeOffset(int offsetXPix, int offsetYPix) {
- if (offsetXPix != mViewportSizeOffsetWidthPix ||
- offsetYPix != mViewportSizeOffsetHeightPix) {
- mViewportSizeOffsetWidthPix = offsetXPix;
- mViewportSizeOffsetHeightPix = offsetYPix;
+ public void setTopControlsLayoutHeight(int topControlsLayoutHeightPix) {
+ if (topControlsLayoutHeightPix != mTopControlsLayoutHeightPix) {
+ mTopControlsLayoutHeightPix = topControlsLayoutHeightPix;
if (mNativeContentViewCore != 0) nativeWasResized(mNativeContentViewCore);
}
}
/**
- * Returns a delegate that can be used to add and remove views from the ContainerView.
+ * Returns a delegate that can be used to add and remove views from the current
+ * container view. Clients can safely hold to instances of this class as it handles the
+ * replacement of container views transparently.
*
- * NOTE: Use with care, as not all ContentViewCore users setup their ContainerView in the same
+ * NOTE: Use with care, as not all ContentViewCore users setup their container view in the same
* way. In particular, the Android WebView has limitations on what implementation details can
* be provided via a child view, as they are visible in the API and could introduce
* compatibility breaks with existing applications. If in doubt, contact the
*
* @return A ViewAndroidDelegate that can be used to add and remove views.
*/
- @VisibleForTesting
public ViewAndroidDelegate getViewAndroidDelegate() {
- return new ViewAndroidDelegate() {
- @Override
- public View acquireAnchorView() {
- View anchorView = new View(getContext());
- mContainerView.addView(anchorView);
- return anchorView;
- }
-
- @Override
- @SuppressWarnings("deprecation") // AbsoluteLayout.LayoutParams
- public void setAnchorViewPosition(
- View view, float x, float y, float width, float height) {
- assert view.getParent() == mContainerView;
-
- float scale = (float) DeviceDisplayInfo.create(getContext()).getDIPScale();
-
- // The anchor view should not go outside the bounds of the ContainerView.
- int leftMargin = Math.round(x * scale);
- int topMargin = Math.round(mRenderCoordinates.getContentOffsetYPix() + y * scale);
- int scaledWidth = Math.round(width * scale);
- // ContentViewCore currently only supports these two container view types.
- if (mContainerView instanceof FrameLayout) {
- if (scaledWidth + leftMargin > mContainerView.getWidth()) {
- scaledWidth = mContainerView.getWidth() - leftMargin;
- }
- FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams(
- scaledWidth, Math.round(height * scale));
- lp.leftMargin = leftMargin;
- lp.topMargin = topMargin;
- view.setLayoutParams(lp);
- } else if (mContainerView instanceof AbsoluteLayout) {
- // This fixes the offset due to a difference in
- // scrolling model of WebView vs. Chrome.
- // TODO(sgurun) fix this to use mContainerView.getScroll[X/Y]()
- // as it naturally accounts for scroll differences between
- // these models.
- leftMargin += mRenderCoordinates.getScrollXPixInt();
- topMargin += mRenderCoordinates.getScrollYPixInt();
-
- android.widget.AbsoluteLayout.LayoutParams lp =
- new android.widget.AbsoluteLayout.LayoutParams(
- scaledWidth, (int) (height * scale), leftMargin, topMargin);
- view.setLayoutParams(lp);
- } else {
- Log.e(TAG, "Unknown layout " + mContainerView.getClass().getName());
- }
- }
-
- @Override
- public void releaseAnchorView(View anchorView) {
- mContainerView.removeView(anchorView);
- }
- };
+ return mViewAndroidDelegate;
}
@VisibleForTesting
}
@VisibleForTesting
+ public void setInputMethodManagerWrapperForTest(InputMethodManagerWrapper immw) {
+ mInputMethodManagerWrapper = immw;
+ }
+
+ @VisibleForTesting
public AdapterInputConnection getInputConnectionForTest() {
return mInputConnection;
}
+ @VisibleForTesting
+ ViewAndroid getViewAndroid() {
+ return mViewAndroid;
+ }
+
private ImeAdapter createImeAdapter(Context context) {
- return new ImeAdapter(new InputMethodManagerWrapper(context),
+ return new ImeAdapter(mInputMethodManagerWrapper,
new ImeAdapter.ImeAdapterDelegate() {
@Override
- public void onImeEvent(boolean isFinish) {
+ public void onImeEvent() {
+ mPopupZoomer.hide(true);
getContentViewClient().onImeEvent();
- if (!isFinish) {
- hideHandles();
- undoScrollFocusedEditableNodeIntoViewIfNeeded(false);
- }
- }
-
- @Override
- public void onSetFieldValue() {
- scrollFocusedEditableNodeIntoView();
+ if (mFocusedNodeEditable) dismissTextHandles();
}
@Override
@Override
public void onReceiveResult(int resultCode, Bundle resultData) {
getContentViewClient().onImeStateChangeRequested(
- resultCode == InputMethodManager.RESULT_SHOWN ||
- resultCode == InputMethodManager.RESULT_UNCHANGED_SHOWN);
+ resultCode == InputMethodManager.RESULT_SHOWN
+ || resultCode == InputMethodManager.RESULT_UNCHANGED_SHOWN);
if (resultCode == InputMethodManager.RESULT_SHOWN) {
// If OSK is newly shown, delay the form focus until
// the onSizeChanged (in order to adjust relative to the
// always be called, crbug.com/294908.
getContainerView().getWindowVisibleDisplayFrame(
mFocusPreOSKViewportRect);
- } else if (resultCode ==
- InputMethodManager.RESULT_UNCHANGED_SHOWN) {
+ } else if (hasFocus() && resultCode
+ == InputMethodManager.RESULT_UNCHANGED_SHOWN) {
// If the OSK was already there, focus the form immediately.
- scrollFocusedEditableNodeIntoView();
- } else {
- undoScrollFocusedEditableNodeIntoViewIfNeeded(false);
+ assert mWebContents != null;
+ mWebContents.scrollFocusedEditableNodeIntoView();
}
}
};
// deleting it after destroying the ContentViewCore.
public void initialize(ViewGroup containerView, InternalAccessDelegate internalDispatcher,
long nativeWebContents, WindowAndroid windowAndroid) {
- mContainerView = containerView;
- mPositionObserver = new ViewPositionObserver(mContainerView);
- mPositionListener = new PositionObserver.Listener() {
- @Override
- public void onPositionChanged(int x, int y) {
- if (isSelectionHandleShowing() || isInsertionHandleShowing()) {
- temporarilyHideTextHandles();
- }
- }
- };
-
- long windowNativePointer = windowAndroid != null ? windowAndroid.getNativePointer() : 0;
+ createContentViewAndroidDelegate();
+ setContainerView(containerView);
+ long windowNativePointer = windowAndroid.getNativePointer();
+ assert windowNativePointer != 0;
+ createViewAndroid(windowAndroid);
- long viewAndroidNativePointer = 0;
- if (windowNativePointer != 0) {
- mViewAndroid = new ViewAndroid(windowAndroid, getViewAndroidDelegate());
- viewAndroidNativePointer = mViewAndroid.getNativePointer();
- }
+ long viewAndroidNativePointer = mViewAndroid.getNativePointer();
+ assert viewAndroidNativePointer != 0;
- // Note ContentViewGestureHandler initialization must occur before nativeInit
- // because nativeInit may callback into hasTouchEventHandlers.
- mContentViewGestureHandler = new ContentViewGestureHandler(mContext, this);
- mZoomControlsDelegate = new ZoomControlsDelegate() {
- @Override
- public void invokeZoomPicker() {}
- @Override
- public void dismissZoomPicker() {}
- @Override
- public void updateZoomControls() {}
- };
+ mZoomControlsDelegate = NO_OP_ZOOM_CONTROLS_DELEGATE;
mNativeContentViewCore = nativeInit(
- nativeWebContents, viewAndroidNativePointer, windowNativePointer);
+ nativeWebContents, viewAndroidNativePointer, windowNativePointer,
+ mRetainedJavaScriptObjects);
mWebContents = nativeGetWebContentsAndroid(mNativeContentViewCore);
mContentSettings = new ContentSettings(this, mNativeContentViewCore);
- initializeContainerView(internalDispatcher);
+
+ setContainerViewInternals(internalDispatcher);
+ mRenderCoordinates.reset();
+ initPopupZoomer(mContext);
+ mImeAdapter = createImeAdapter(mContext);
+ attachImeAdapter();
mAccessibilityInjector = AccessibilityInjector.newInstance(this);
- String contentDescription = "Web View";
- if (R.string.accessibility_content_view == 0) {
- Log.w(TAG, "Setting contentDescription to 'Web View' as no value was specified.");
- } else {
- contentDescription = mContext.getResources().getString(
- R.string.accessibility_content_view);
- }
- mContainerView.setContentDescription(contentDescription);
- mWebContentsObserver = new WebContentsObserverAndroid(this) {
+ mWebContentsObserver = new WebContentsObserver(mWebContents) {
+ @Override
+ public void didFailLoad(boolean isProvisionalLoad, boolean isMainFrame, int errorCode,
+ String description, String failingUrl) {
+ // Navigation that fails the provisional load will have the strong binding removed
+ // here. One for which the provisional load is commited will have the strong binding
+ // removed in navigationEntryCommitted() below.
+ if (isProvisionalLoad) determinedProcessVisibility();
+ }
+
+ @Override
+ public void didNavigateMainFrame(String url, String baseUrl,
+ boolean isNavigationToDifferentPage, boolean isFragmentNavigation) {
+ if (!isNavigationToDifferentPage) return;
+ hidePopupsAndClearSelection();
+ resetScrollInProgress();
+ resetGestureDetection();
+ }
+
@Override
- public void didStartLoading(String url) {
- hidePopupDialog();
- resetGestureDetectors();
+ public void renderProcessGone(boolean wasOomProtected) {
+ hidePopupsAndClearSelection();
+ resetScrollInProgress();
+ // No need to reset gesture detection as the detector will have
+ // been destroyed in the RenderWidgetHostView.
+ }
+
+ @Override
+ public void navigationEntryCommitted() {
+ determinedProcessVisibility();
+ }
+
+ private void determinedProcessVisibility() {
+ // Signal to the process management logic that we can now rely on the process
+ // visibility signal for binding management. Before the navigation commits, its
+ // renderer is considered background even if the pending navigation happens in the
+ // foreground renderer.
+ ChildProcessLauncher.determinedVisibility(getCurrentRenderProcessId());
}
};
+ }
+
+ @VisibleForTesting
+ void createContentViewAndroidDelegate() {
+ mViewAndroidDelegate = new ContentViewAndroidDelegate();
+ }
+
+ @VisibleForTesting
+ void createViewAndroid(WindowAndroid windowAndroid) {
+ mViewAndroid = new ViewAndroid(windowAndroid, mViewAndroidDelegate);
+ }
- sendOrientationChangeEvent();
+ /**
+ * Sets a new container view for this {@link ContentViewCore}.
+ *
+ * <p>WARNING: This method can also be used to replace the existing container view,
+ * but you should only do it if you have a very good reason to. Replacing the
+ * container view has been designed to support fullscreen in the Webview so it
+ * might not be appropriate for other use cases.
+ *
+ * <p>This method only performs a small part of replacing the container view and
+ * embedders are responsible for:
+ * <ul>
+ * <li>Disconnecting the old container view from this ContentViewCore</li>
+ * <li>Updating the InternalAccessDelegate</li>
+ * <li>Reconciling the state of this ContentViewCore with the new container view</li>
+ * <li>Tearing down and recreating the native GL rendering where appropriate</li>
+ * <li>etc.</li>
+ * </ul>
+ */
+ public void setContainerView(ViewGroup containerView) {
+ TraceEvent.begin();
+ if (mContainerView != null) {
+ mPastePopupMenu = null;
+ mInputConnection = null;
+ hidePopupsAndClearSelection();
+ }
+
+ mContainerView = containerView;
+ mPositionObserver = new ViewPositionObserver(mContainerView);
+ mContainerView.setClickable(true);
+ mViewAndroidDelegate.updateCurrentContainerView();
+ TraceEvent.end();
}
@CalledByNative
mContainerViewInternals = internalDispatcher;
}
- /**
- * Initializes the View that will contain all Views created by the ContentViewCore.
- *
- * @param internalDispatcher Handles dispatching all hidden or super methods to the
- * containerView.
- */
- private void initializeContainerView(InternalAccessDelegate internalDispatcher) {
- TraceEvent.begin();
- mContainerViewInternals = internalDispatcher;
-
- mContainerView.setWillNotDraw(false);
- mContainerView.setClickable(true);
-
- mRenderCoordinates.reset();
- onRenderCoordinatesUpdated();
-
- initPopupZoomer(mContext);
- mImeAdapter = createImeAdapter(mContext);
- TraceEvent.end();
- }
-
- private void initPopupZoomer(Context context) {
+ @VisibleForTesting
+ void initPopupZoomer(Context context) {
mPopupZoomer = new PopupZoomer(context);
mPopupZoomer.setOnVisibilityChangedListener(new PopupZoomer.OnVisibilityChangedListener() {
+ // mContainerView can change, but this OnVisibilityChangedListener can only be used
+ // to add and remove views from the mContainerViewAtCreation.
+ private final ViewGroup mContainerViewAtCreation = mContainerView;
+
@Override
public void onPopupZoomerShown(final PopupZoomer zoomer) {
- mContainerView.post(new Runnable() {
+ mContainerViewAtCreation.post(new Runnable() {
@Override
public void run() {
- if (mContainerView.indexOfChild(zoomer) == -1) {
- mContainerView.addView(zoomer);
- } else {
- assert false : "PopupZoomer should never be shown without being hidden";
+ if (mContainerViewAtCreation.indexOfChild(zoomer) == -1) {
+ mContainerViewAtCreation.addView(zoomer);
}
}
});
@Override
public void onPopupZoomerHidden(final PopupZoomer zoomer) {
- mContainerView.post(new Runnable() {
+ mContainerViewAtCreation.post(new Runnable() {
@Override
public void run() {
- if (mContainerView.indexOfChild(zoomer) != -1) {
- mContainerView.removeView(zoomer);
- mContainerView.invalidate();
- } else {
- assert false : "PopupZoomer should never be hidden without being shown";
+ if (mContainerViewAtCreation.indexOfChild(zoomer) != -1) {
+ mContainerViewAtCreation.removeView(zoomer);
+ mContainerViewAtCreation.invalidate();
}
}
});
// TODO(yongsheng): LONG_TAP is not enabled in PopupZoomer. So need to dispatch a LONG_TAP
// gesture if a user completes a tap on PopupZoomer UI after a LONG_PRESS gesture.
PopupZoomer.OnTapListener listener = new PopupZoomer.OnTapListener() {
+ // mContainerView can change, but this OnTapListener can only be used
+ // with the mContainerViewAtCreation.
+ private final ViewGroup mContainerViewAtCreation = mContainerView;
+
@Override
public boolean onSingleTap(View v, MotionEvent e) {
- mContainerView.requestFocus();
+ mContainerViewAtCreation.requestFocus();
if (mNativeContentViewCore != 0) {
- nativeSingleTap(mNativeContentViewCore, e.getEventTime(),
- e.getX(), e.getY(), true);
+ nativeSingleTap(mNativeContentViewCore, e.getEventTime(), e.getX(), e.getY());
}
return true;
}
@Override
public boolean onLongPress(View v, MotionEvent e) {
if (mNativeContentViewCore != 0) {
- nativeLongPress(mNativeContentViewCore, e.getEventTime(),
- e.getX(), e.getY(), true);
+ nativeLongPress(mNativeContentViewCore, e.getEventTime(), e.getX(), e.getY());
}
return true;
}
mPopupZoomer.setOnTapListener(listener);
}
+ @VisibleForTesting
+ public void setPopupZoomerForTest(PopupZoomer popupZoomer) {
+ mPopupZoomer = popupZoomer;
+ }
+
/**
* Destroy the internal state of the ContentView. This method may only be
* called after the ContentView has been removed from the view system. No
if (mNativeContentViewCore != 0) {
nativeOnJavaContentViewCoreDestroyed(mNativeContentViewCore);
}
+ mWebContentsObserver.detachFromWebContents();
+ mWebContentsObserver = null;
+ setSmartClipDataListener(null);
+ setZoomControlsDelegate(null);
+ // 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;
- resetVSyncNotification();
- mVSyncProvider = null;
if (mViewAndroid != null) mViewAndroid.destroy();
mNativeContentViewCore = 0;
mContentSettings = null;
mRetainedJavaScriptObjects.clear();
unregisterAccessibilityContentObserver();
mGestureStateListeners.clear();
+ ScreenOrientationListener.getInstance().removeObserver(this);
+ mPositionObserver.clearListener();
}
private void unregisterAccessibilityContentObserver() {
mContentViewClient = client;
}
- ContentViewClient getContentViewClient() {
+ @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
return mContentViewClient;
}
- public int getBackgroundColor() {
- if (mNativeContentViewCore != 0) {
- return nativeGetBackgroundColor(mNativeContentViewCore);
- }
- return Color.WHITE;
- }
-
@CalledByNative
private void onBackgroundColorChanged(int color) {
getContentViewClient().onBackgroundColorChanged(color);
}
/**
- * Load url without fixing up the url string. Consumers of ContentView are responsible for
- * ensuring the URL passed in is properly formatted (i.e. the scheme has been added if left
- * off during user input).
- *
- * @param params Parameters for this load.
+ * @return Viewport width in physical pixels as set from onSizeChanged.
*/
- public void loadUrl(LoadUrlParams params) {
- if (mNativeContentViewCore == 0) return;
-
- nativeLoadUrl(mNativeContentViewCore,
- params.mUrl,
- params.mLoadUrlType,
- params.mTransitionType,
- params.mUaOverrideOption,
- params.getExtraHeadersString(),
- params.mPostData,
- params.mBaseUrlForDataUrl,
- params.mVirtualUrlForDataUrl,
- params.mCanLoadLocalResources);
+ @CalledByNative
+ public int getViewportWidthPix() {
+ return mViewportWidthPix;
}
/**
- * Stops loading the current web contents.
+ * @return Viewport height in physical pixels as set from onSizeChanged.
*/
- public void stopLoading() {
- reportActionAfterDoubleTapUMA(UMAActionAfterDoubleTap.NAVIGATE_STOP);
- if (mNativeContentViewCore != 0) nativeStopLoading(mNativeContentViewCore);
+ @CalledByNative
+ public int getViewportHeightPix() {
+ return mViewportHeightPix;
}
/**
- * Get the URL of the current page.
- *
- * @return The URL of the current page.
+ * @return Width of underlying physical surface.
*/
- public String getUrl() {
- if (mNativeContentViewCore != 0) return nativeGetURL(mNativeContentViewCore);
- return null;
+ @CalledByNative
+ public int getPhysicalBackingWidthPix() {
+ return mPhysicalBackingWidthPix;
}
/**
- * Get the title of the current page.
- *
- * @return The title of the current page.
+ * @return Height of underlying physical surface.
*/
- public String getTitle() {
- if (mNativeContentViewCore != 0) return nativeGetTitle(mNativeContentViewCore);
- return null;
+ @CalledByNative
+ public int getPhysicalBackingHeightPix() {
+ return mPhysicalBackingHeightPix;
}
- /**
- * Shows an interstitial page driven by the passed in delegate.
- *
- * @param url The URL being blocked by the interstitial.
- * @param delegate The delegate handling the interstitial.
- */
+ /* TODO(aelias): Remove these when downstream callers disappear. */
@VisibleForTesting
- public void showInterstitialPage(
- String url, InterstitialPageDelegateAndroid delegate) {
- if (mNativeContentViewCore == 0) return;
- nativeShowInterstitialPage(mNativeContentViewCore, url, delegate.getNative());
- }
-
- /**
- * @return Whether the page is currently showing an interstitial, such as a bad HTTPS page.
- */
- public boolean isShowingInterstitialPage() {
- return mNativeContentViewCore == 0 ?
- false : nativeIsShowingInterstitialPage(mNativeContentViewCore);
+ public int getViewportSizeOffsetWidthPix() {
+ return 0;
}
- /**
- * Mark any new frames that have arrived since this function was last called as non-pending.
- *
- * @return Whether there was a pending frame from the renderer.
- */
- public boolean consumePendingRendererFrame() {
- boolean hadPendingFrame = mPendingRendererFrame;
- mPendingRendererFrame = false;
- return hadPendingFrame;
+ @VisibleForTesting
+ public int getViewportSizeOffsetHeightPix() {
+ return getTopControlsLayoutHeightPix();
}
/**
- * @return Viewport width in physical pixels as set from onSizeChanged.
+ * @return The amount that the viewport size given to Blink is shrunk by the URL-bar..
*/
@CalledByNative
- public int getViewportWidthPix() { return mViewportWidthPix; }
+ public int getTopControlsLayoutHeightPix() {
+ return mTopControlsLayoutHeightPix;
+ }
/**
- * @return Viewport height in physical pixels as set from onSizeChanged.
+ * @see android.webkit.WebView#getContentHeight()
*/
- @CalledByNative
- public int getViewportHeightPix() { return mViewportHeightPix; }
+ public float getContentHeightCss() {
+ return mRenderCoordinates.getContentHeightCss();
+ }
/**
- * @return Width of underlying physical surface.
+ * @see android.webkit.WebView#getContentWidth()
*/
- @CalledByNative
- public int getPhysicalBackingWidthPix() { return mPhysicalBackingWidthPix; }
+ public float getContentWidthCss() {
+ return mRenderCoordinates.getContentWidthCss();
+ }
/**
- * @return Height of underlying physical surface.
+ * @return The selected text (empty if no text selected).
*/
- @CalledByNative
- public int getPhysicalBackingHeightPix() { return mPhysicalBackingHeightPix; }
+ public String getSelectedText() {
+ return mHasSelection ? mLastSelectedText : "";
+ }
/**
- * @return Amount the output surface extends past the bottom of the window viewport.
+ * @return Whether the current selection is editable (false if no text selected).
*/
- @CalledByNative
- public int getOverdrawBottomHeightPix() { return mOverdrawBottomHeightPix; }
+ public boolean isSelectionEditable() {
+ return mHasSelection ? mFocusedNodeEditable : false;
+ }
/**
- * @return The amount to shrink the viewport relative to {@link #getViewportWidthPix()}.
+ * @return Whether the current focused node is editable.
*/
- @CalledByNative
- public int getViewportSizeOffsetWidthPix() { return mViewportSizeOffsetWidthPix; }
+ public boolean isFocusedNodeEditable() {
+ return mFocusedNodeEditable;
+ }
- /**
- * @return The amount to shrink the viewport relative to {@link #getViewportHeightPix()}.
- */
- @CalledByNative
- public int getViewportSizeOffsetHeightPix() { return mViewportSizeOffsetHeightPix; }
+ // End FrameLayout overrides.
/**
- * @see android.webkit.WebView#getContentHeight()
+ * @see View#onTouchEvent(MotionEvent)
*/
- public float getContentHeightCss() {
- return mRenderCoordinates.getContentHeightCss();
+ public boolean onTouchEvent(MotionEvent event) {
+ final boolean isTouchHandleEvent = false;
+ return onTouchEventImpl(event, isTouchHandleEvent);
}
- /**
- * @see android.webkit.WebView#getContentWidth()
- */
- public float getContentWidthCss() {
- return mRenderCoordinates.getContentWidthCss();
- }
-
- public Bitmap getBitmap() {
- return getBitmap(getViewportWidthPix(), getViewportHeightPix());
- }
-
- public Bitmap getBitmap(int width, int height) {
- if (width == 0 || height == 0
- || getViewportWidthPix() == 0 || getViewportHeightPix() == 0) {
- return null;
- }
-
- Bitmap b = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
+ private boolean onTouchEventImpl(MotionEvent event, boolean isTouchHandleEvent) {
+ TraceEvent.begin("onTouchEvent");
+ try {
+ int eventAction = event.getActionMasked();
- if (mNativeContentViewCore != 0 &&
- nativePopulateBitmapFromCompositor(mNativeContentViewCore, b)) {
- // If we successfully grabbed a bitmap, check if we have to draw the Android overlay
- // components as well.
- if (mContainerView.getChildCount() > 0) {
- Canvas c = new Canvas(b);
- c.scale(width / (float) getViewportWidthPix(),
- height / (float) getViewportHeightPix());
- mContainerView.draw(c);
+ if (eventAction == MotionEvent.ACTION_DOWN) {
+ cancelRequestToScrollFocusedEditableNodeIntoView();
}
- return b;
- }
-
- return null;
- }
-
- /**
- * Generates a bitmap of the content that is performance optimized based on capture time.
- *
- * <p>
- * To have a consistent capture time across devices, we will scale down the captured bitmap
- * where necessary to reduce the time to generate the bitmap.
- *
- * @param width The width of the content to be captured.
- * @param height The height of the content to be captured.
- * @return A pair of the generated bitmap, and the scale that needs to be applied to return the
- * bitmap to it's original size (i.e. if the bitmap is scaled down 50%, this
- * will be 2).
- */
- public Pair<Bitmap, Float> getScaledPerformanceOptimizedBitmap(int width, int height) {
- float scale = 1f;
- // On tablets, always scale down to MDPI for performance reasons.
- if (DeviceUtils.isTablet(getContext())) {
- scale = getContext().getResources().getDisplayMetrics().density;
- }
- return Pair.create(
- getBitmap((int) (width / scale), (int) (height / scale)),
- scale);
- }
-
- // TODO(teddchoc): Remove all these navigation controller methods from here and have the
- // embedders manage it.
- /**
- * @return Whether the current WebContents has a previous navigation entry.
- */
- public boolean canGoBack() {
- return mWebContents != null && mWebContents.getNavigationController().canGoBack();
- }
-
- /**
- * @return Whether the current WebContents has a navigation entry after the current one.
- */
- public boolean canGoForward() {
- return mWebContents != null && mWebContents.getNavigationController().canGoForward();
- }
- /**
- * @param offset The offset into the navigation history.
- * @return Whether we can move in history by given offset
- */
- public boolean canGoToOffset(int offset) {
- return mWebContents != null &&
- mWebContents.getNavigationController().canGoToOffset(offset);
- }
+ if (SPenSupport.isSPenSupported(mContext))
+ eventAction = SPenSupport.convertSPenEventAction(eventAction);
+ if (!isValidTouchEventActionForNative(eventAction)) return false;
- /**
- * Navigates to the specified offset from the "current entry". Does nothing if the offset is out
- * of bounds.
- * @param offset The offset into the navigation history.
- */
- public void goToOffset(int offset) {
- if (mWebContents != null) mWebContents.getNavigationController().goToOffset(offset);
- }
+ if (mNativeContentViewCore == 0) return false;
- @Override
- public void goToNavigationIndex(int index) {
- if (mWebContents != null) {
- mWebContents.getNavigationController().goToNavigationIndex(index);
- }
- }
-
- /**
- * Goes to the navigation entry before the current one.
- */
- public void goBack() {
- if (mWebContents != null) mWebContents.getNavigationController().goBack();
- }
-
- /**
- * Goes to the navigation entry following the current one.
- */
- public void goForward() {
- if (mWebContents != null) mWebContents.getNavigationController().goForward();
- }
-
- /**
- * Loads the current navigation if there is a pending lazy load (after tab restore).
- */
- public void loadIfNecessary() {
- if (mNativeContentViewCore != 0) nativeLoadIfNecessary(mNativeContentViewCore);
- }
-
- /**
- * Requests the current navigation to be loaded upon the next call to loadIfNecessary().
- */
- public void requestRestoreLoad() {
- if (mNativeContentViewCore != 0) nativeRequestRestoreLoad(mNativeContentViewCore);
- }
-
- /**
- * Reload the current page.
- */
- public void reload(boolean checkForRepost) {
- mAccessibilityInjector.addOrRemoveAccessibilityApisIfNecessary();
- if (mNativeContentViewCore != 0) {
- nativeReload(mNativeContentViewCore, checkForRepost);
- }
- }
+ // A zero offset is quite common, in which case the unnecessary copy should be avoided.
+ MotionEvent offset = null;
+ if (mCurrentTouchOffsetX != 0 || mCurrentTouchOffsetY != 0) {
+ offset = createOffsetMotionEvent(event);
+ event = offset;
+ }
- /**
- * Reload the current page, ignoring the contents of the cache.
- */
- public void reloadIgnoringCache(boolean checkForRepost) {
- mAccessibilityInjector.addOrRemoveAccessibilityApisIfNecessary();
- if (mNativeContentViewCore != 0) {
- nativeReloadIgnoringCache(mNativeContentViewCore, checkForRepost);
+ final int pointerCount = event.getPointerCount();
+ final boolean consumed = nativeOnTouchEvent(mNativeContentViewCore, event,
+ event.getEventTime(), eventAction,
+ pointerCount, event.getHistorySize(), event.getActionIndex(),
+ event.getX(), event.getY(),
+ pointerCount > 1 ? event.getX(1) : 0,
+ pointerCount > 1 ? event.getY(1) : 0,
+ event.getPointerId(0), pointerCount > 1 ? event.getPointerId(1) : -1,
+ event.getTouchMajor(), pointerCount > 1 ? event.getTouchMajor(1) : 0,
+ event.getTouchMinor(), pointerCount > 1 ? event.getTouchMinor(1) : 0,
+ event.getOrientation(), pointerCount > 1 ? event.getOrientation(1) : 0,
+ event.getRawX(), event.getRawY(),
+ event.getToolType(0),
+ pointerCount > 1 ? event.getToolType(1) : MotionEvent.TOOL_TYPE_UNKNOWN,
+ event.getButtonState(),
+ event.getMetaState(),
+ isTouchHandleEvent);
+
+ if (offset != null) offset.recycle();
+ return consumed;
+ } finally {
+ TraceEvent.end("onTouchEvent");
}
}
- /**
- * Cancel the pending reload.
- */
- public void cancelPendingReload() {
- if (mNativeContentViewCore != 0) nativeCancelPendingReload(mNativeContentViewCore);
+ private static boolean isValidTouchEventActionForNative(int eventAction) {
+ // Only these actions have any effect on gesture detection. Other
+ // actions have no corresponding WebTouchEvent type and may confuse the
+ // touch pipline, so we ignore them entirely.
+ return eventAction == MotionEvent.ACTION_DOWN
+ || eventAction == MotionEvent.ACTION_UP
+ || eventAction == MotionEvent.ACTION_CANCEL
+ || eventAction == MotionEvent.ACTION_MOVE
+ || eventAction == MotionEvent.ACTION_POINTER_DOWN
+ || eventAction == MotionEvent.ACTION_POINTER_UP;
}
- /**
- * Continue the pending reload.
- */
- public void continuePendingReload() {
- if (mNativeContentViewCore != 0) nativeContinuePendingReload(mNativeContentViewCore);
- }
-
- /**
- * Clears the ContentViewCore's page history in both the backwards and
- * forwards directions.
- */
- public void clearHistory() {
- if (mNativeContentViewCore != 0) nativeClearHistory(mNativeContentViewCore);
- }
-
- /**
- * @return The selected text (empty if no text selected).
- */
- public String getSelectedText() {
- return mHasSelection ? mLastSelectedText : "";
- }
-
- /**
- * @return Whether the current selection is editable (false if no text selected).
- */
- public boolean isSelectionEditable() {
- return mHasSelection ? mSelectionEditable : false;
- }
-
- // End FrameLayout overrides.
-
- /**
- * @see View#onTouchEvent(MotionEvent)
- */
- public boolean onTouchEvent(MotionEvent event) {
- undoScrollFocusedEditableNodeIntoViewIfNeeded(false);
- if (!mRequestedVSyncForInput) {
- mRequestedVSyncForInput = true;
- addVSyncSubscriber();
- }
- return mContentViewGestureHandler.onTouchEvent(event);
+ public void setIgnoreRemainingTouchEvents() {
+ resetGestureDetection();
}
- /** @see ContentViewGestureHandler#setIgnoreRemainingTouchEvents */
- public void setIgnoreRemainingTouchEvents() {
- mContentViewGestureHandler.setIgnoreRemainingTouchEvents();
+ public boolean isScrollInProgress() {
+ return mTouchScrollInProgress || mPotentiallyActiveFlingCount > 0;
}
@SuppressWarnings("unused")
@CalledByNative
private void onFlingStartEventConsumed(int vx, int vy) {
- temporarilyHideTextHandles();
+ mTouchScrollInProgress = false;
+ mPotentiallyActiveFlingCount++;
for (mGestureStateListenersIterator.rewind();
mGestureStateListenersIterator.hasNext();) {
mGestureStateListenersIterator.next().onFlingStartGesture(
@SuppressWarnings("unused")
@CalledByNative
private void onFlingStartEventHadNoConsumer(int vx, int vy) {
+ mTouchScrollInProgress = false;
for (mGestureStateListenersIterator.rewind();
mGestureStateListenersIterator.hasNext();) {
mGestureStateListenersIterator.next().onUnhandledFlingStartEvent(vx, vy);
@SuppressWarnings("unused")
@CalledByNative
private void onScrollBeginEventAck() {
- temporarilyHideTextHandles();
+ mTouchScrollInProgress = true;
+ hidePastePopup();
mZoomControlsDelegate.invokeZoomPicker();
updateGestureStateListener(GestureEventType.SCROLL_START);
}
@SuppressWarnings("unused")
@CalledByNative
private void onScrollEndEventAck() {
+ if (!mTouchScrollInProgress) return;
+ mTouchScrollInProgress = false;
updateGestureStateListener(GestureEventType.SCROLL_END);
}
@SuppressWarnings("unused")
@CalledByNative
private void onPinchBeginEventAck() {
- temporarilyHideTextHandles();
updateGestureStateListener(GestureEventType.PINCH_BEGIN);
}
@SuppressWarnings("unused")
@CalledByNative
- private void onDoubleTapEventAck() {
- temporarilyHideTextHandles();
+ private void onSingleTapEventAck(boolean consumed, int x, int y) {
+ for (mGestureStateListenersIterator.rewind();
+ mGestureStateListenersIterator.hasNext();) {
+ mGestureStateListenersIterator.next().onSingleTap(consumed, x, y);
+ }
}
/**
return true;
}
updateForTapOrPress(type, x, y);
- updateForDoubleTapUMA(type);
return false;
}
- @Override
- public void onTouchEventHandlingBegin(MotionEvent event) {
- if (mNativeContentViewCore == 0) return;
- nativeOnTouchEventHandlingBegin(mNativeContentViewCore,event);
- }
-
- @Override
- public void onTouchEventHandlingEnd() {
- if (mNativeContentViewCore == 0) return;
- nativeOnTouchEventHandlingEnd(mNativeContentViewCore);
- }
-
- /**
- * Note: These events may or may not actually be forwarded to the renderer,
- * depending on ack disposition of the underlying touch events. All listening
- * for sent gestures should take place in {@link #filterGestureEvent(int, int, int)}.
- */
- @Override
- public boolean onGestureEventCreated(int type, long timeMs, int x, int y, Bundle b) {
- if (mNativeContentViewCore == 0) return false;
- switch (type) {
- case GestureEventType.SHOW_PRESS:
- nativeShowPress(mNativeContentViewCore, timeMs, x, y);
- return true;
- case GestureEventType.TAP_CANCEL:
- nativeTapCancel(mNativeContentViewCore, timeMs, x, y);
- return true;
- case GestureEventType.TAP_DOWN:
- nativeTapDown(mNativeContentViewCore, timeMs, x, y);
- return true;
- case GestureEventType.DOUBLE_TAP:
- nativeDoubleTap(mNativeContentViewCore, timeMs, x, y);
- return true;
- case GestureEventType.SINGLE_TAP_UP:
- nativeSingleTap(mNativeContentViewCore, timeMs, x, y, false);
- return true;
- case GestureEventType.SINGLE_TAP_CONFIRMED:
- if (!b.getBoolean(ContentViewGestureHandler.SHOW_PRESS, false)) {
- nativeShowPress(mNativeContentViewCore, timeMs, x, y);
- }
- nativeSingleTap(mNativeContentViewCore, timeMs, x, y, false);
- return true;
- case GestureEventType.SINGLE_TAP_UNCONFIRMED:
- nativeSingleTapUnconfirmed(mNativeContentViewCore, timeMs, x, y);
- return true;
- case GestureEventType.LONG_PRESS:
- nativeLongPress(mNativeContentViewCore, timeMs, x, y, false);
- return true;
- case GestureEventType.LONG_TAP:
- nativeLongTap(mNativeContentViewCore, timeMs, x, y, false);
- return true;
- case GestureEventType.SCROLL_START: {
- int dx = b.getInt(ContentViewGestureHandler.DELTA_HINT_X);
- int dy = b.getInt(ContentViewGestureHandler.DELTA_HINT_Y);
- nativeScrollBegin(mNativeContentViewCore, timeMs, x, y, dx, dy);
- return true;
- }
- case GestureEventType.SCROLL_BY: {
- int dx = b.getInt(ContentViewGestureHandler.DISTANCE_X);
- int dy = b.getInt(ContentViewGestureHandler.DISTANCE_Y);
- nativeScrollBy(mNativeContentViewCore, timeMs, x, y, dx, dy);
- return true;
- }
- case GestureEventType.SCROLL_END:
- nativeScrollEnd(mNativeContentViewCore, timeMs);
- return true;
- case GestureEventType.FLING_START:
- nativeFlingStart(mNativeContentViewCore, timeMs, x, y,
- b.getInt(ContentViewGestureHandler.VELOCITY_X, 0),
- b.getInt(ContentViewGestureHandler.VELOCITY_Y, 0));
- return true;
- case GestureEventType.FLING_CANCEL:
- nativeFlingCancel(mNativeContentViewCore, timeMs);
- return true;
- case GestureEventType.PINCH_BEGIN:
- nativePinchBegin(mNativeContentViewCore, timeMs, x, y);
- return true;
- case GestureEventType.PINCH_BY:
- nativePinchBy(mNativeContentViewCore, timeMs, x, y,
- b.getFloat(ContentViewGestureHandler.DELTA, 0));
- return true;
- case GestureEventType.PINCH_END:
- nativePinchEnd(mNativeContentViewCore, timeMs);
- return true;
- default:
- return false;
- }
- }
-
@VisibleForTesting
public void sendDoubleTapForTest(long timeMs, int x, int y) {
if (mNativeContentViewCore == 0) return;
}
/**
+ * Cancel any fling gestures active.
+ * @param timeMs Current time (in milliseconds).
+ */
+ public void cancelFling(long timeMs) {
+ if (mNativeContentViewCore == 0) return;
+ nativeFlingCancel(mNativeContentViewCore, timeMs);
+ }
+
+ /**
* Add a listener that gets alerted on gesture state changes.
* @param listener Listener to add.
*/
}
}
- /** Callback interface for evaluateJavaScript(). */
- public interface JavaScriptCallback {
- void handleJavaScriptResult(String jsonResult);
- }
-
/**
- * Injects the passed Javascript code in the current page and evaluates it.
- * If a result is required, pass in a callback.
- * Used in automation tests.
- *
- * @param script The Javascript to execute.
- * @param callback The callback to be fired off when a result is ready. The script's
- * result will be json encoded and passed as the parameter, and the call
- * will be made on the main thread.
- * If no result is required, pass null.
+ * To be called when the ContentView is shown.
*/
- public void evaluateJavaScript(String script, JavaScriptCallback callback) {
- if (mNativeContentViewCore == 0) return;
- nativeEvaluateJavaScript(mNativeContentViewCore, script, callback, false);
+ public void onShow() {
+ assert mWebContents != null;
+ mWebContents.onShow();
+ setAccessibilityState(mAccessibilityManager.isEnabled());
+ restoreSelectionPopupsIfNecessary();
}
/**
- * Injects the passed Javascript code in the current page and evaluates it.
- * If there is no page existing, a new one will be created.
- *
- * @param script The Javascript to execute.
+ * @return The ID of the renderer process that backs this tab or
+ * {@link #INVALID_RENDER_PROCESS_PID} if there is none.
*/
- public void evaluateJavaScriptEvenIfNotYetNavigated(String script) {
- if (mNativeContentViewCore == 0) return;
- nativeEvaluateJavaScript(mNativeContentViewCore, script, null, true);
- }
-
- /**
- * To be called when the ContentView is shown.
- */
- public void onShow() {
- assert mNativeContentViewCore != 0;
- if (!mInForeground) {
- int pid = nativeGetCurrentRenderProcessId(mNativeContentViewCore);
- ChildProcessLauncher.getBindingManager().setInForeground(pid, true);
- }
- mInForeground = true;
- nativeOnShow(mNativeContentViewCore);
- setAccessibilityState(mAccessibilityManager.isEnabled());
+ public int getCurrentRenderProcessId() {
+ return nativeGetCurrentRenderProcessId(mNativeContentViewCore);
}
/**
* To be called when the ContentView is hidden.
*/
public void onHide() {
- assert mNativeContentViewCore != 0;
- if (mInForeground) {
- int pid = nativeGetCurrentRenderProcessId(mNativeContentViewCore);
- ChildProcessLauncher.getBindingManager().setInForeground(pid, false);
- }
- mInForeground = false;
- hidePopupDialog();
+ assert mWebContents != null;
+ hidePopupsAndPreserveSelection();
setInjectedAccessibility(false);
- nativeOnHide(mNativeContentViewCore);
+ mWebContents.onHide();
}
/**
return mContentSettings;
}
- private void onRenderCoordinatesUpdated() {
- if (mContentViewGestureHandler == null) return;
+ private void hidePopupsAndClearSelection() {
+ mUnselectAllOnActionModeDismiss = true;
+ hidePopups();
+ // Clear the selection. The selection is cleared on destroying IME
+ // and also here since we may receive destroy first, for example
+ // when focus is lost in webview.
+ clearUserSelection();
+ }
- // We disable double tap zoom for pages that have a width=device-width
- // or narrower viewport (indicating that this is a mobile-optimized or
- // responsive web design, so text will be legible without zooming).
- // We also disable it for pages that disallow the user from zooming in
- // or out (even if they don't have a device-width or narrower viewport).
- mContentViewGestureHandler.updateShouldDisableDoubleTap(
- mRenderCoordinates.hasMobileViewport() || mRenderCoordinates.hasFixedPageScale());
+ private void hidePopupsAndPreserveSelection() {
+ mUnselectAllOnActionModeDismiss = false;
+ hidePopups();
+ }
+
+ private void clearUserSelection() {
+ if (mFocusedNodeEditable) {
+ if (mInputConnection != null) {
+ int selectionEnd = Selection.getSelectionEnd(mEditable);
+ mInputConnection.setSelection(selectionEnd, selectionEnd);
+ }
+ } else if (mImeAdapter != null) {
+ mImeAdapter.unselect();
+ }
}
- private void hidePopupDialog() {
- SelectPopupDialog.hide(this);
- hideHandles();
+ private void hidePopups() {
hideSelectActionBar();
+ hidePastePopup();
+ hideSelectPopup();
+ mPopupZoomer.hide(false);
+ if (mUnselectAllOnActionModeDismiss) dismissTextHandles();
+ }
+
+ private void restoreSelectionPopupsIfNecessary() {
+ if (mHasSelection && mActionMode == null) showSelectActionBar();
}
- void hideSelectActionBar() {
+ public void hideSelectActionBar() {
if (mActionMode != null) {
mActionMode.finish();
mActionMode = null;
return mActionMode != null;
}
- private void resetGestureDetectors() {
- mContentViewGestureHandler.resetGestureHandlers();
+ private void resetGestureDetection() {
+ if (mNativeContentViewCore == 0) return;
+ nativeResetGestureDetection(mNativeContentViewCore);
}
/**
@SuppressWarnings("javadoc")
public void onAttachedToWindow() {
setAccessibilityState(mAccessibilityManager.isEnabled());
+ setTextHandlesTemporarilyHidden(false);
+ restoreSelectionPopupsIfNecessary();
+ ScreenOrientationListener.getInstance().addObserver(this, mContext);
+ GamepadList.onAttachedToWindow(mContext);
}
/**
* @see View#onDetachedFromWindow()
*/
@SuppressWarnings("javadoc")
+ @SuppressLint("MissingSuperCall")
public void onDetachedFromWindow() {
setInjectedAccessibility(false);
- hidePopupDialog();
mZoomControlsDelegate.dismissZoomPicker();
unregisterAccessibilityContentObserver();
+
+ ScreenOrientationListener.getInstance().removeObserver(this);
+ GamepadList.onDetachedFromWindow();
+
+ // WebView uses PopupWindows for handle rendering, which may remain
+ // unintentionally visible even after the WebView has been detached.
+ // Override the handle visibility explicitly to address this, but
+ // preserve the underlying selection for detachment cases like screen
+ // locking and app switching.
+ setTextHandlesTemporarilyHidden(true);
+ hidePopupsAndPreserveSelection();
}
/**
// enter fullscreen mode.
outAttrs.imeOptions = EditorInfo.IME_FLAG_NO_FULLSCREEN;
}
- mInputConnection =
- mAdapterInputConnectionFactory.get(mContainerView, mImeAdapter, outAttrs);
+ mInputConnection = mAdapterInputConnectionFactory.get(mContainerView, mImeAdapter,
+ mEditable, outAttrs);
return mInputConnection;
}
+ @VisibleForTesting
+ public AdapterInputConnection getAdapterInputConnectionForTest() {
+ return mInputConnection;
+ }
+
+ @VisibleForTesting
public Editable getEditableForTest() {
- return mInputConnection.getEditable();
+ return mEditable;
}
/**
TraceEvent.begin();
if (newConfig.keyboard != Configuration.KEYBOARD_NOKEYS) {
- mImeAdapter.attach(nativeGetNativeImeAdapter(mNativeContentViewCore),
- ImeAdapter.getTextInputTypeNone(),
- AdapterInputConnection.INVALID_SELECTION,
- AdapterInputConnection.INVALID_SELECTION);
- InputMethodManager manager = (InputMethodManager)
- getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
- manager.restartInput(mContainerView);
+ if (mNativeContentViewCore != 0) {
+ mImeAdapter.attach(nativeGetNativeImeAdapter(mNativeContentViewCore),
+ ImeAdapter.getTextInputTypeNone(), 0 /* no flags */);
+ }
+ mInputMethodManagerWrapper.restartInput(mContainerView);
}
mContainerViewInternals.super_onConfigurationChanged(newConfig);
- // Make sure the size is up to date in JavaScript's window.onorientationchanged.
- mContainerView.addOnLayoutChangeListener(new View.OnLayoutChangeListener() {
- @Override
- public void onLayoutChange(View v, int left, int top, int right, int bottom,
- int oldLeft, int oldTop, int oldRight, int oldBottom) {
- mContainerView.removeOnLayoutChangeListener(this);
- sendOrientationChangeEvent();
- }
- });
+
// To request layout has side effect, but it seems OK as it only happen in
// onConfigurationChange and layout has to be changed in most case.
mContainerView.requestLayout();
}
/**
- * Called when the ContentView's position in the activity window changed. This information is
- * used for cropping screenshots.
- */
- public void onLocationInWindowChanged(int x, int y) {
- mLocationInWindowX = x;
- mLocationInWindowY = y;
- }
-
- /**
* Called when the underlying surface the compositor draws to changes size.
* This may be larger than the viewport size.
*/
}
}
- /**
- * Called when the amount the surface is overdrawing off the bottom has changed.
- * @param overdrawHeightPix The overdraw height.
- */
+ /* TODO(aelias): Remove this after downstream callers disappear. */
public void onOverdrawBottomHeightChanged(int overdrawHeightPix) {
- if (mOverdrawBottomHeightPix == overdrawHeightPix) return;
-
- mOverdrawBottomHeightPix = overdrawHeightPix;
-
- if (mNativeContentViewCore != 0) {
- nativeWasResized(mNativeContentViewCore);
- }
}
private void updateAfterSizeChanged() {
if (!rect.equals(mFocusPreOSKViewportRect)) {
// Only assume the OSK triggered the onSizeChanged if width was preserved.
if (rect.width() == mFocusPreOSKViewportRect.width()) {
- scrollFocusedEditableNodeIntoView();
+ assert mWebContents != null;
+ mWebContents.scrollFocusedEditableNodeIntoView();
}
- mFocusPreOSKViewportRect.setEmpty();
+ cancelRequestToScrollFocusedEditableNodeIntoView();
}
- } else if (mUnfocusOnNextSizeChanged) {
- undoScrollFocusedEditableNodeIntoViewIfNeeded(true);
- mUnfocusOnNextSizeChanged = false;
}
}
- private void scrollFocusedEditableNodeIntoView() {
- if (mNativeContentViewCore != 0) {
- Runnable scrollTask = new Runnable() {
- @Override
- public void run() {
- if (mNativeContentViewCore != 0) {
- nativeScrollFocusedEditableNodeIntoView(mNativeContentViewCore);
- }
- }
- };
-
- scrollTask.run();
-
- // The native side keeps track of whether the zoom and scroll actually occurred. It is
- // more efficient to do it this way and sometimes fire an unnecessary message rather
- // than synchronize with the renderer and always have an additional message.
- mScrolledAndZoomedFocusedEditableNode = true;
- }
- }
-
- private void undoScrollFocusedEditableNodeIntoViewIfNeeded(boolean backButtonPressed) {
- // The only call to this function that matters is the first call after the
- // scrollFocusedEditableNodeIntoView function call.
- // If the first call to this function is a result of a back button press we want to undo the
- // preceding scroll. If the call is a result of some other action we don't want to perform
- // an undo.
- // All subsequent calls are ignored since only the scroll function sets
- // mScrolledAndZoomedFocusedEditableNode to true.
- if (mScrolledAndZoomedFocusedEditableNode && backButtonPressed &&
- mNativeContentViewCore != 0) {
- Runnable scrollTask = new Runnable() {
- @Override
- public void run() {
- if (mNativeContentViewCore != 0) {
- nativeUndoScrollFocusedEditableNodeIntoView(mNativeContentViewCore);
- }
- }
- };
-
- scrollTask.run();
- }
- mScrolledAndZoomedFocusedEditableNode = false;
+ private void cancelRequestToScrollFocusedEditableNodeIntoView() {
+ // Zero-ing the rect will prevent |updateAfterSizeChanged()| from
+ // issuing the delayed form focus event.
+ mFocusPreOSKViewportRect.setEmpty();
}
/**
* @see View#onWindowFocusChanged(boolean)
*/
public void onWindowFocusChanged(boolean hasWindowFocus) {
- if (!hasWindowFocus) {
- mContentViewGestureHandler.onWindowFocusLost();
- }
+ if (!hasWindowFocus) resetGestureDetection();
}
public void onFocusChanged(boolean gainFocus) {
- if (!gainFocus) getContentViewClient().onImeStateChangeRequested(false);
+ if (gainFocus) {
+ restoreSelectionPopupsIfNecessary();
+ } else {
+ hideImeIfNeeded();
+ cancelRequestToScrollFocusedEditableNodeIntoView();
+ if (mPreserveSelectionOnNextLossOfFocus) {
+ mPreserveSelectionOnNextLossOfFocus = false;
+ hidePopupsAndPreserveSelection();
+ } else {
+ hidePopupsAndClearSelection();
+ }
+ }
if (mNativeContentViewCore != 0) nativeSetFocus(mNativeContentViewCore, gainFocus);
}
public boolean dispatchKeyEventPreIme(KeyEvent event) {
try {
TraceEvent.begin();
- if (event.getKeyCode() == KeyEvent.KEYCODE_BACK && mImeAdapter.isActive()) {
- mUnfocusOnNextSizeChanged = true;
- } else {
- undoScrollFocusedEditableNodeIntoViewIfNeeded(false);
- }
return mContainerViewInternals.super_dispatchKeyEventPreIme(event);
} finally {
TraceEvent.end();
* @see View#dispatchKeyEvent(KeyEvent)
*/
public boolean dispatchKeyEvent(KeyEvent event) {
+ if (GamepadList.dispatchKeyEvent(event)) return true;
if (getContentViewClient().shouldOverrideKeyEvent(event)) {
return mContainerViewInternals.super_dispatchKeyEvent(event);
}
- if (event.getKeyCode() == KeyEvent.KEYCODE_DPAD_CENTER) {
- showImeIfNeeded();
- // Event is not consumed here, because ImeAdapter might interpret
- // it as "Enter".
- // showImeIfNeeded respects the policy of
- // InputMethodService.onEvaluateInputViewShown. So IME will not be
- // shown if you have QWERTY physical keyboard attached.
- // Also, IME will not be shown if the focus is not on the input
- // field. See ImeAdapter.attachAndShowIfNeeded
- }
-
if (mImeAdapter.dispatchKeyEvent(event)) return true;
return mContainerViewInternals.super_dispatchKeyEvent(event);
*/
public boolean onHoverEvent(MotionEvent event) {
TraceEvent.begin("onHoverEvent");
- mContainerView.removeCallbacks(mFakeMouseMoveRunnable);
- if (mBrowserAccessibilityManager != null) {
- return mBrowserAccessibilityManager.onHoverEvent(event);
- }
- if (mNativeContentViewCore != 0) {
- nativeSendMouseMoveEvent(mNativeContentViewCore, event.getEventTime(),
- event.getX(), event.getY());
+ MotionEvent offset = createOffsetMotionEvent(event);
+ try {
+ if (mBrowserAccessibilityManager != null) {
+ return mBrowserAccessibilityManager.onHoverEvent(offset);
+ }
+
+ // Work around Android bug where the x, y coordinates of a hover exit
+ // event are incorrect when touch exploration is on.
+ if (mTouchExplorationEnabled && offset.getAction() == MotionEvent.ACTION_HOVER_EXIT) {
+ return true;
+ }
+
+ mContainerView.removeCallbacks(mFakeMouseMoveRunnable);
+ if (mNativeContentViewCore != 0) {
+ nativeSendMouseMoveEvent(mNativeContentViewCore, offset.getEventTime(),
+ offset.getX(), offset.getY());
+ }
+ return true;
+ } finally {
+ offset.recycle();
+ TraceEvent.end("onHoverEvent");
}
- TraceEvent.end("onHoverEvent");
- return true;
}
/**
* @see View#onGenericMotionEvent(MotionEvent)
*/
public boolean onGenericMotionEvent(MotionEvent event) {
+ if (GamepadList.onGenericMotionEvent(event)) return true;
if ((event.getSource() & InputDevice.SOURCE_CLASS_POINTER) != 0) {
switch (event.getAction()) {
case MotionEvent.ACTION_SCROLL:
+ if (mNativeContentViewCore == 0) return false;
+
nativeSendMouseWheelEvent(mNativeContentViewCore, event.getEventTime(),
event.getX(), event.getY(),
event.getAxisValue(MotionEvent.AXIS_VSCROLL));
@Override
public void run() {
onHoverEvent(eventFakeMouseMove);
+ eventFakeMouseMove.recycle();
}
};
mContainerView.postDelayed(mFakeMouseMoveRunnable, 250);
}
/**
+ * Sets the current amount to offset incoming touch events by. This is used to handle content
+ * moving and not lining up properly with the android input system.
+ * @param dx The X offset in pixels to shift touch events.
+ * @param dy The Y offset in pixels to shift touch events.
+ */
+ public void setCurrentMotionEventOffsets(float dx, float dy) {
+ mCurrentTouchOffsetX = dx;
+ mCurrentTouchOffsetY = dy;
+ }
+
+ private MotionEvent createOffsetMotionEvent(MotionEvent src) {
+ MotionEvent dst = MotionEvent.obtain(src);
+ dst.offsetLocation(mCurrentTouchOffsetX, mCurrentTouchOffsetY);
+ return dst;
+ }
+
+ /**
* @see View#scrollBy(int, int)
* Currently the ContentView scrolling happens in the native side. In
* the Java view system, it is always pinned at (0, 0). scrollBy() and scrollTo()
public void scrollBy(int xPix, int yPix) {
if (mNativeContentViewCore != 0) {
nativeScrollBy(mNativeContentViewCore,
- System.currentTimeMillis(), 0, 0, xPix, yPix);
+ SystemClock.uptimeMillis(), 0, 0, xPix, yPix);
}
}
final float dxPix = xPix - xCurrentPix;
final float dyPix = yPix - yCurrentPix;
if (dxPix != 0 || dyPix != 0) {
- long time = System.currentTimeMillis();
+ long time = SystemClock.uptimeMillis();
nativeScrollBegin(mNativeContentViewCore, time,
xCurrentPix, yCurrentPix, -dxPix, -dyPix);
nativeScrollBy(mNativeContentViewCore,
if (!mPopupZoomer.isShowing()) mPopupZoomer.setLastTouch(xPix, yPix);
- if (type == GestureEventType.LONG_PRESS
- || type == GestureEventType.LONG_TAP) {
- getInsertionHandleController().allowAutomaticShowing();
- getSelectionHandleController().allowAutomaticShowing();
- } else {
- setClickXAndY((int) xPix, (int) yPix);
- if (mSelectionEditable) getInsertionHandleController().allowAutomaticShowing();
- }
- }
-
- private void setClickXAndY(int x, int y) {
- mSingleTapX = x;
- mSingleTapY = y;
+ mLastTapX = (int) xPix;
+ mLastTapY = (int) yPix;
}
/**
- * @return The x coordinate for the last point that a singleTap gesture was initiated from.
+ * @return The x coordinate for the last point that a tap or press gesture was initiated from.
*/
- public int getSingleTapX() {
- return mSingleTapX;
+ public int getLastTapX() {
+ return mLastTapX;
}
/**
- * @return The y coordinate for the last point that a singleTap gesture was initiated from.
+ * @return The y coordinate for the last point that a tap or press gesture was initiated from.
*/
- public int getSingleTapY() {
- return mSingleTapY;
- }
-
- // Watch for the UMA "action after double tap" timer expiring and reset
- // the timer if necessary.
- private void updateDoubleTapUmaTimer() {
- if (mLastDoubleTapTimeMs == 0) return;
-
- long nowMs = SystemClock.uptimeMillis();
- if ((nowMs - mLastDoubleTapTimeMs) >= ACTION_AFTER_DOUBLE_TAP_WINDOW_MS) {
- // Time expired, user took no action (that we care about).
- sendActionAfterDoubleTapUMA(UMAActionAfterDoubleTap.NO_ACTION);
- mLastDoubleTapTimeMs = 0;
- }
- }
-
- private void updateForDoubleTapUMA(int type) {
- updateDoubleTapUmaTimer();
-
- if (type == GestureEventType.SINGLE_TAP_UP
- || type == GestureEventType.SINGLE_TAP_CONFIRMED) {
- sendSingleTapUMA(mContentViewGestureHandler.isDoubleTapDisabled() ?
- UMASingleTapType.UNDELAYED_TAP : UMASingleTapType.DELAYED_TAP);
- } else if (type == GestureEventType.DOUBLE_TAP) {
- // Make sure repeated double taps don't get silently dropped from
- // the statistics.
- if (mLastDoubleTapTimeMs > 0) {
- sendActionAfterDoubleTapUMA(UMAActionAfterDoubleTap.NO_ACTION);
- }
-
- mLastDoubleTapTimeMs = SystemClock.uptimeMillis();
- }
- }
-
- private void reportActionAfterDoubleTapUMA(int type) {
- updateDoubleTapUmaTimer();
-
- if (mLastDoubleTapTimeMs == 0) return;
-
- long nowMs = SystemClock.uptimeMillis();
- if ((nowMs - mLastDoubleTapTimeMs) < ACTION_AFTER_DOUBLE_TAP_WINDOW_MS) {
- sendActionAfterDoubleTapUMA(type);
- mLastDoubleTapTimeMs = 0;
- }
- }
-
- private void sendSingleTapUMA(int type) {
- if (mNativeContentViewCore == 0) return;
- nativeSendSingleTapUma(
- mNativeContentViewCore,
- type,
- UMASingleTapType.COUNT);
- }
-
- private void sendActionAfterDoubleTapUMA(int type) {
- if (mNativeContentViewCore == 0) return;
- nativeSendActionAfterDoubleTapUma(
- mNativeContentViewCore,
- type,
- !mContentViewGestureHandler.isClickDelayDisabled(),
- UMAActionAfterDoubleTap.COUNT);
+ public int getLastTapY() {
+ return mLastTapY;
}
public void setZoomControlsDelegate(ZoomControlsDelegate zoomControlsDelegate) {
+ if (zoomControlsDelegate == null) {
+ mZoomControlsDelegate = NO_OP_ZOOM_CONTROLS_DELEGATE;
+ return;
+ }
mZoomControlsDelegate = zoomControlsDelegate;
}
public void updateMultiTouchZoomSupport(boolean supportsMultiTouchZoom) {
- mContentViewGestureHandler.updateMultiTouchSupport(supportsMultiTouchZoom);
+ if (mNativeContentViewCore == 0) return;
+ nativeSetMultiTouchZoomSupportEnabled(mNativeContentViewCore, supportsMultiTouchZoom);
}
public void updateDoubleTapSupport(boolean supportsDoubleTap) {
- mContentViewGestureHandler.updateDoubleTapSupport(supportsDoubleTap);
+ if (mNativeContentViewCore == 0) return;
+ nativeSetDoubleTapSupportEnabled(mNativeContentViewCore, supportsDoubleTap);
}
public void selectPopupMenuItems(int[] indices) {
if (mNativeContentViewCore != 0) {
- nativeSelectPopupMenuItems(mNativeContentViewCore, indices);
+ nativeSelectPopupMenuItems(mNativeContentViewCore, mNativeSelectPopupSourceFrame,
+ indices);
}
+ mNativeSelectPopupSourceFrame = 0;
+ mSelectPopup = null;
}
/**
- * Get the screen orientation from the OS and push it to WebKit.
- *
- * TODO(husky): Add a hook for mock orientations.
+ * Send the screen orientation value to the renderer.
*/
- private void sendOrientationChangeEvent() {
+ @VisibleForTesting
+ void sendOrientationChangeEvent(int orientation) {
if (mNativeContentViewCore == 0) return;
- WindowManager windowManager =
- (WindowManager) getContext().getSystemService(Context.WINDOW_SERVICE);
- switch (windowManager.getDefaultDisplay().getRotation()) {
- case Surface.ROTATION_90:
- nativeSendOrientationChangeEvent(mNativeContentViewCore, 90);
- break;
- case Surface.ROTATION_180:
- nativeSendOrientationChangeEvent(mNativeContentViewCore, 180);
- break;
- case Surface.ROTATION_270:
- nativeSendOrientationChangeEvent(mNativeContentViewCore, -90);
- break;
- case Surface.ROTATION_0:
- nativeSendOrientationChangeEvent(mNativeContentViewCore, 0);
- break;
- default:
- Log.w(TAG, "Unknown rotation!");
- break;
- }
+ nativeSendOrientationChangeEvent(mNativeContentViewCore, orientation);
}
/**
return mDownloadDelegate;
}
- private SelectionHandleController getSelectionHandleController() {
- if (mSelectionHandleController == null) {
- mSelectionHandleController = new SelectionHandleController(
- getContainerView(), mPositionObserver) {
+ @VisibleForTesting
+ public SelectActionModeCallback.ActionHandler getSelectActionHandler() {
+ return mActionHandler;
+ }
+
+ private void showSelectActionBar() {
+ if (mActionMode != null) {
+ mActionMode.invalidate();
+ return;
+ }
+
+ // Start a new action mode with a SelectActionModeCallback.
+ if (mActionHandler == null) {
+ mActionHandler = new SelectActionModeCallback.ActionHandler() {
@Override
- public void selectBetweenCoordinates(int x1, int y1, int x2, int y2) {
- if (mNativeContentViewCore != 0 && !(x1 == x2 && y1 == y2)) {
- nativeSelectBetweenCoordinates(mNativeContentViewCore,
- x1, y1 - mRenderCoordinates.getContentOffsetYPix(),
- x2, y2 - mRenderCoordinates.getContentOffsetYPix());
- }
+ public void selectAll() {
+ mImeAdapter.selectAll();
}
@Override
- public void showHandles(int startDir, int endDir) {
- super.showHandles(startDir, endDir);
- showSelectActionBar();
+ public void cut() {
+ mImeAdapter.cut();
}
- };
-
- mSelectionHandleController.hideAndDisallowAutomaticShowing();
- }
-
- return mSelectionHandleController;
- }
-
- private InsertionHandleController getInsertionHandleController() {
- if (mInsertionHandleController == null) {
- mInsertionHandleController = new InsertionHandleController(
- getContainerView(), mPositionObserver) {
- private static final int AVERAGE_LINE_HEIGHT = 14;
-
@Override
- public void setCursorPosition(int x, int y) {
- if (mNativeContentViewCore != 0) {
- nativeMoveCaret(mNativeContentViewCore,
- x, y - mRenderCoordinates.getContentOffsetYPix());
- }
+ public void copy() {
+ mImeAdapter.copy();
}
@Override
public void paste() {
mImeAdapter.paste();
- hideHandles();
}
@Override
- public int getLineHeight() {
- return (int) Math.ceil(
- mRenderCoordinates.fromLocalCssToPix(AVERAGE_LINE_HEIGHT));
+ public void share() {
+ final String query = getSelectedText();
+ if (TextUtils.isEmpty(query)) return;
+
+ Intent send = new Intent(Intent.ACTION_SEND);
+ send.setType("text/plain");
+ send.putExtra(Intent.EXTRA_TEXT, query);
+ try {
+ Intent i = Intent.createChooser(send, getContext().getString(
+ R.string.actionbar_share));
+ i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ getContext().startActivity(i);
+ } catch (android.content.ActivityNotFoundException ex) {
+ // If no app handles it, do nothing.
+ }
}
@Override
- public void showHandle() {
- super.showHandle();
- }
- };
-
- mInsertionHandleController.hideAndDisallowAutomaticShowing();
- }
-
- return mInsertionHandleController;
- }
-
- @VisibleForTesting
- public InsertionHandleController getInsertionHandleControllerForTest() {
- return mInsertionHandleController;
- }
-
- @VisibleForTesting
- public SelectionHandleController getSelectionHandleControllerForTest() {
- return mSelectionHandleController;
- }
-
- private void updateHandleScreenPositions() {
- if (isSelectionHandleShowing()) {
- mSelectionHandleController.setStartHandlePosition(
- mStartHandlePoint.getXPix(), mStartHandlePoint.getYPix());
- mSelectionHandleController.setEndHandlePosition(
- mEndHandlePoint.getXPix(), mEndHandlePoint.getYPix());
- }
-
- if (isInsertionHandleShowing()) {
- mInsertionHandleController.setHandlePosition(
- mInsertionHandlePoint.getXPix(), mInsertionHandlePoint.getYPix());
- }
- }
-
- private void hideHandles() {
- if (mSelectionHandleController != null) {
- mSelectionHandleController.hideAndDisallowAutomaticShowing();
- }
- if (mInsertionHandleController != null) {
- mInsertionHandleController.hideAndDisallowAutomaticShowing();
- }
- mPositionObserver.removeListener(mPositionListener);
- }
-
- private void showSelectActionBar() {
- if (mActionMode != null) {
- mActionMode.invalidate();
- return;
- }
-
- // Start a new action mode with a SelectActionModeCallback.
- SelectActionModeCallback.ActionHandler actionHandler =
- new SelectActionModeCallback.ActionHandler() {
- @Override
- public void selectAll() {
- mImeAdapter.selectAll();
- }
-
- @Override
- public void cut() {
- mImeAdapter.cut();
- }
-
- @Override
- public void copy() {
- mImeAdapter.copy();
- }
-
- @Override
- public void paste() {
- mImeAdapter.paste();
- }
+ public void search() {
+ final String query = getSelectedText();
+ if (TextUtils.isEmpty(query)) return;
+
+ // See if ContentViewClient wants to override
+ if (getContentViewClient().doesPerformWebSearch()) {
+ getContentViewClient().performWebSearch(query);
+ return;
+ }
- @Override
- public void share() {
- final String query = getSelectedText();
- if (TextUtils.isEmpty(query)) return;
-
- Intent send = new Intent(Intent.ACTION_SEND);
- send.setType("text/plain");
- send.putExtra(Intent.EXTRA_TEXT, query);
- try {
- Intent i = Intent.createChooser(send, getContext().getString(
- R.string.actionbar_share));
- i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- getContext().startActivity(i);
- } catch (android.content.ActivityNotFoundException ex) {
- // If no app handles it, do nothing.
+ Intent i = new Intent(Intent.ACTION_WEB_SEARCH);
+ i.putExtra(SearchManager.EXTRA_NEW_SEARCH, true);
+ i.putExtra(SearchManager.QUERY, query);
+ i.putExtra(Browser.EXTRA_APPLICATION_ID, getContext().getPackageName());
+ if (!(getContext() instanceof Activity)) {
+ i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ }
+ try {
+ getContext().startActivity(i);
+ } catch (android.content.ActivityNotFoundException ex) {
+ // If no app handles it, do nothing.
+ }
}
- }
- @Override
- public void search() {
- final String query = getSelectedText();
- if (TextUtils.isEmpty(query)) return;
-
- // See if ContentViewClient wants to override
- if (getContentViewClient().doesPerformWebSearch()) {
- getContentViewClient().performWebSearch(query);
- return;
+ @Override
+ public boolean isSelectionPassword() {
+ return mImeAdapter.isSelectionPassword();
}
- Intent i = new Intent(Intent.ACTION_WEB_SEARCH);
- i.putExtra(SearchManager.EXTRA_NEW_SEARCH, true);
- i.putExtra(SearchManager.QUERY, query);
- i.putExtra(Browser.EXTRA_APPLICATION_ID, getContext().getPackageName());
- if (!(getContext() instanceof Activity)) {
- i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- }
- try {
- getContext().startActivity(i);
- } catch (android.content.ActivityNotFoundException ex) {
- // If no app handles it, do nothing.
+ @Override
+ public boolean isSelectionEditable() {
+ return mFocusedNodeEditable;
}
- }
- @Override
- public boolean isSelectionEditable() {
- return mSelectionEditable;
- }
-
- @Override
- public void onDestroyActionMode() {
- mActionMode = null;
- if (mUnselectAllOnActionModeDismiss) mImeAdapter.unselect();
- getContentViewClient().onContextualActionBarHidden();
- }
-
- @Override
- public boolean isShareAvailable() {
- Intent intent = new Intent(Intent.ACTION_SEND);
- intent.setType("text/plain");
- return getContext().getPackageManager().queryIntentActivities(intent,
- PackageManager.MATCH_DEFAULT_ONLY).size() > 0;
- }
+ @Override
+ public void onDestroyActionMode() {
+ mActionMode = null;
+ if (mUnselectAllOnActionModeDismiss) {
+ dismissTextHandles();
+ clearUserSelection();
+ }
+ getContentViewClient().onContextualActionBarHidden();
+ }
- @Override
- public boolean isWebSearchAvailable() {
- if (getContentViewClient().doesPerformWebSearch()) return true;
- Intent intent = new Intent(Intent.ACTION_WEB_SEARCH);
- intent.putExtra(SearchManager.EXTRA_NEW_SEARCH, true);
- return getContext().getPackageManager().queryIntentActivities(intent,
- PackageManager.MATCH_DEFAULT_ONLY).size() > 0;
- }
- };
+ @Override
+ public boolean isShareAvailable() {
+ Intent intent = new Intent(Intent.ACTION_SEND);
+ intent.setType("text/plain");
+ return getContext().getPackageManager().queryIntentActivities(intent,
+ PackageManager.MATCH_DEFAULT_ONLY).size() > 0;
+ }
+
+ @Override
+ public boolean isWebSearchAvailable() {
+ if (getContentViewClient().doesPerformWebSearch()) return true;
+ Intent intent = new Intent(Intent.ACTION_WEB_SEARCH);
+ intent.putExtra(SearchManager.EXTRA_NEW_SEARCH, true);
+ return getContext().getPackageManager().queryIntentActivities(intent,
+ PackageManager.MATCH_DEFAULT_ONLY).size() > 0;
+ }
+ };
+ }
mActionMode = null;
// On ICS, startActionMode throws an NPE when getParent() is null.
if (mContainerView.getParent() != null) {
+ assert mWebContents != null;
mActionMode = mContainerView.startActionMode(
- getContentViewClient().getSelectActionModeCallback(getContext(), actionHandler,
- nativeIsIncognito(mNativeContentViewCore)));
+ getContentViewClient().getSelectActionModeCallback(getContext(), mActionHandler,
+ mWebContents.isIncognito()));
}
mUnselectAllOnActionModeDismiss = true;
if (mActionMode == null) {
}
}
- public boolean getUseDesktopUserAgent() {
- if (mNativeContentViewCore != 0) {
- return nativeGetUseDesktopUserAgent(mNativeContentViewCore);
- }
- return false;
+ /**
+ * Clears the current text selection.
+ */
+ public void clearSelection() {
+ mImeAdapter.unselect();
}
/**
- * Set whether or not we're using a desktop user agent for the currently loaded page.
- * @param override If true, use a desktop user agent. Use a mobile one otherwise.
- * @param reloadOnChange Reload the page if the UA has changed.
+ * Ensure the selection is preserved the next time the view loses focus.
*/
- public void setUseDesktopUserAgent(boolean override, boolean reloadOnChange) {
- if (mNativeContentViewCore != 0) {
- nativeSetUseDesktopUserAgent(mNativeContentViewCore, override, reloadOnChange);
- }
+ public void preserveSelectionOnNextLossOfFocus() {
+ mPreserveSelectionOnNextLossOfFocus = true;
}
- public void clearSslPreferences() {
- nativeClearSslPreferences(mNativeContentViewCore);
+ /**
+ * @return Whether the page has an active, touch-controlled selection region.
+ */
+ @VisibleForTesting
+ public boolean hasSelection() {
+ return mHasSelection;
}
- private boolean isSelectionHandleShowing() {
- return mSelectionHandleController != null && mSelectionHandleController.isShowing();
+ /**
+ * @return Whether the page has an active, touch-controlled insertion handle.
+ */
+ @VisibleForTesting
+ protected boolean hasInsertion() {
+ return mHasInsertion;
}
- private boolean isInsertionHandleShowing() {
- return mInsertionHandleController != null && mInsertionHandleController.isShowing();
+ private void hidePastePopup() {
+ if (mPastePopupMenu == null) return;
+ mPastePopupMenu.hide();
}
- // Makes the insertion/selection handles invisible. They will fade back in shortly after the
- // last call to scheduleTextHandleFadeIn (or temporarilyHideTextHandles).
- private void temporarilyHideTextHandles() {
- if (isSelectionHandleShowing() && !mSelectionHandleController.isDragging()) {
- mSelectionHandleController.setHandleVisibility(HandleView.INVISIBLE);
- }
- if (isInsertionHandleShowing() && !mInsertionHandleController.isDragging()) {
- mInsertionHandleController.setHandleVisibility(HandleView.INVISIBLE);
- }
- scheduleTextHandleFadeIn();
- }
+ @CalledByNative
+ private void onSelectionEvent(int eventType, float posXDip, float posYDip) {
+ switch (eventType) {
+ case SelectionEventType.SELECTION_SHOWN:
+ mHasSelection = true;
+ mUnselectAllOnActionModeDismiss = true;
+ // TODO(cjhopman): Remove this when there is a better signal that long press caused
+ // a selection. See http://crbug.com/150151.
+ mContainerView.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
+ showSelectActionBar();
+ break;
- private boolean allowTextHandleFadeIn() {
- if (mContentViewGestureHandler.isNativeScrolling() ||
- mContentViewGestureHandler.isNativePinching()) {
- return false;
- }
+ case SelectionEventType.SELECTION_CLEARED:
+ mHasSelection = false;
+ mUnselectAllOnActionModeDismiss = false;
+ hideSelectActionBar();
+ break;
- if (mPopupZoomer.isShowing()) return false;
+ case SelectionEventType.SELECTION_DRAG_STARTED:
+ break;
- return true;
- }
+ case SelectionEventType.SELECTION_DRAG_STOPPED:
+ break;
- // Cancels any pending fade in and schedules a new one.
- private void scheduleTextHandleFadeIn() {
- if (!isInsertionHandleShowing() && !isSelectionHandleShowing()) return;
+ case SelectionEventType.INSERTION_SHOWN:
+ mHasInsertion = true;
+ break;
- if (mDeferredHandleFadeInRunnable == null) {
- mDeferredHandleFadeInRunnable = new Runnable() {
- @Override
- public void run() {
- if (!allowTextHandleFadeIn()) {
- // Delay fade in until it is allowed.
- scheduleTextHandleFadeIn();
- } else {
- if (isSelectionHandleShowing()) {
- mSelectionHandleController.beginHandleFadeIn();
- }
- if (isInsertionHandleShowing()) {
- mInsertionHandleController.beginHandleFadeIn();
- }
- }
+ case SelectionEventType.INSERTION_MOVED:
+ if (mPastePopupMenu == null) break;
+ if (!isScrollInProgress() && mPastePopupMenu.isShowing()) {
+ showPastePopup((int) posXDip, (int) posYDip);
+ } else {
+ hidePastePopup();
}
- };
+ break;
+
+ case SelectionEventType.INSERTION_TAPPED:
+ if (mWasPastePopupShowingOnInsertionDragStart)
+ hidePastePopup();
+ else
+ showPastePopup((int) posXDip, (int) posYDip);
+ break;
+
+ case SelectionEventType.INSERTION_CLEARED:
+ mHasInsertion = false;
+ hidePastePopup();
+ break;
+
+ case SelectionEventType.INSERTION_DRAG_STARTED:
+ mWasPastePopupShowingOnInsertionDragStart =
+ mPastePopupMenu != null && mPastePopupMenu.isShowing();
+ hidePastePopup();
+ break;
+
+ default:
+ assert false : "Invalid selection event type.";
}
- mContainerView.removeCallbacks(mDeferredHandleFadeInRunnable);
- mContainerView.postDelayed(mDeferredHandleFadeInRunnable, TEXT_HANDLE_FADE_IN_DELAY);
+ final float scale = mRenderCoordinates.getDeviceScaleFactor();
+ getContentViewClient().onSelectionEvent(eventType, posXDip * scale, posYDip * scale);
+ }
+
+ private void dismissTextHandles() {
+ mHasSelection = false;
+ mHasInsertion = false;
+ if (mNativeContentViewCore != 0) nativeDismissTextHandles(mNativeContentViewCore);
+ }
+
+ private void setTextHandlesTemporarilyHidden(boolean hide) {
+ if (mNativeContentViewCore == 0) return;
+ nativeSetTextHandlesTemporarilyHidden(mNativeContentViewCore, hide);
}
/**
- * Shows the IME if the focused widget could accept text input.
+ * Hides the IME if the containerView is the active view for IME.
*/
- public void showImeIfNeeded() {
- if (mNativeContentViewCore != 0) nativeShowImeIfNeeded(mNativeContentViewCore);
+ public void hideImeIfNeeded() {
+ // Hide input method window from the current view synchronously
+ // because ImeAdapter does so asynchronouly with a delay, and
+ // by the time when ImeAdapter dismisses the input, the
+ // containerView may have lost focus.
+ // We cannot trust ContentViewClient#onImeStateChangeRequested to
+ // hide the input window because it has an empty default implementation.
+ // So we need to explicitly hide the input method window here.
+ if (mInputMethodManagerWrapper.isActive(mContainerView)) {
+ mInputMethodManagerWrapper.hideSoftInputFromWindow(
+ mContainerView.getWindowToken(), 0, null);
+ }
+ getContentViewClient().onImeStateChangeRequested(false);
}
@SuppressWarnings("unused")
float pageScaleFactor, float minPageScaleFactor, float maxPageScaleFactor,
float contentWidth, float contentHeight,
float viewportWidth, float viewportHeight,
- float controlsOffsetYCss, float contentOffsetYCss,
- float overdrawBottomHeightCss) {
- TraceEvent.instant("ContentViewCore:updateFrameInfo");
+ float controlsOffsetYCss, float contentOffsetYCss) {
+ TraceEvent.begin("ContentViewCore:updateFrameInfo");
// Adjust contentWidth/Height to be always at least as big as
// the actual viewport (as set by onSizeChanged).
+ final float deviceScale = mRenderCoordinates.getDeviceScaleFactor();
contentWidth = Math.max(contentWidth,
- mRenderCoordinates.fromPixToLocalCss(mViewportWidthPix));
+ mViewportWidthPix / (deviceScale * pageScaleFactor));
contentHeight = Math.max(contentHeight,
- mRenderCoordinates.fromPixToLocalCss(mViewportHeightPix));
-
+ mViewportHeightPix / (deviceScale * pageScaleFactor));
final float contentOffsetYPix = mRenderCoordinates.fromDipToPix(contentOffsetYCss);
final boolean contentSizeChanged =
final boolean needHidePopupZoomer = contentSizeChanged || scrollChanged;
final boolean needUpdateZoomControls = scaleLimitsChanged || scrollChanged;
- final boolean needTemporarilyHideHandles = scrollChanged;
if (needHidePopupZoomer) mPopupZoomer.hide(true);
viewportWidth, viewportHeight,
pageScaleFactor, minPageScaleFactor, maxPageScaleFactor,
contentOffsetYPix);
- onRenderCoordinatesUpdated();
if (scrollChanged || contentOffsetChanged) {
for (mGestureStateListenersIterator.rewind();
}
}
- if (needTemporarilyHideHandles) temporarilyHideTextHandles();
if (needUpdateZoomControls) mZoomControlsDelegate.updateZoomControls();
- if (contentOffsetChanged) updateHandleScreenPositions();
// Update offsets for fullscreen.
- final float deviceScale = mRenderCoordinates.getDeviceScaleFactor();
final float controlsOffsetPix = controlsOffsetYCss * deviceScale;
- final float overdrawBottomHeightPix = overdrawBottomHeightCss * deviceScale;
+ // TODO(aelias): Remove last argument after downstream removes it.
getContentViewClient().onOffsetsForFullscreenChanged(
- controlsOffsetPix, contentOffsetYPix, overdrawBottomHeightPix);
+ controlsOffsetPix, contentOffsetYPix, 0);
- mPendingRendererFrame = true;
if (mBrowserAccessibilityManager != null) {
mBrowserAccessibilityManager.notifyFrameInfoInitialized();
}
-
- // Update geometry for external video surface.
- getContentViewClient().onGeometryChanged(-1, null);
+ TraceEvent.end("ContentViewCore:updateFrameInfo");
}
@CalledByNative
- private void updateImeAdapter(int nativeImeAdapterAndroid, int textInputType,
- String text, int selectionStart, int selectionEnd,
- int compositionStart, int compositionEnd, boolean showImeIfNeeded, boolean requireAck) {
+ private void updateImeAdapter(long nativeImeAdapterAndroid, int textInputType,
+ int textInputFlags, String text, int selectionStart, int selectionEnd,
+ int compositionStart, int compositionEnd, boolean showImeIfNeeded,
+ boolean isNonImeChange) {
TraceEvent.begin();
- mSelectionEditable = (textInputType != ImeAdapter.getTextInputTypeNone());
-
- if (mActionMode != null) mActionMode.invalidate();
+ mFocusedNodeEditable = (textInputType != ImeAdapter.getTextInputTypeNone());
+ if (!mFocusedNodeEditable) hidePastePopup();
- mImeAdapter.attachAndShowIfNeeded(nativeImeAdapterAndroid, textInputType,
- selectionStart, selectionEnd, showImeIfNeeded);
+ mImeAdapter.updateKeyboardVisibility(
+ nativeImeAdapterAndroid, textInputType, textInputFlags, showImeIfNeeded);
if (mInputConnection != null) {
mInputConnection.updateState(text, selectionStart, selectionEnd, compositionStart,
- compositionEnd, requireAck);
+ compositionEnd, isNonImeChange);
}
+
+ if (mActionMode != null) mActionMode.invalidate();
TraceEvent.end();
}
/**
* Called (from native) when the <select> popup needs to be shown.
+ * @param nativeSelectPopupSourceFrame The native RenderFrameHost that owns the popup.
* @param items Items to show.
* @param enabled POPUP_ITEM_TYPEs for items.
* @param multiple Whether the popup menu should support multi-select.
*/
@SuppressWarnings("unused")
@CalledByNative
- private void showSelectPopup(String[] items, int[] enabled, boolean multiple,
- int[] selectedIndices) {
+ private void showSelectPopup(long nativeSelectPopupSourceFrame, Rect bounds, String[] items,
+ int[] enabled, boolean multiple, int[] selectedIndices) {
+ if (mContainerView.getParent() == null || mContainerView.getVisibility() != View.VISIBLE) {
+ mNativeSelectPopupSourceFrame = nativeSelectPopupSourceFrame;
+ selectPopupMenuItems(null);
+ return;
+ }
+
+ hidePopupsAndClearSelection();
+ assert mNativeSelectPopupSourceFrame == 0 : "Zombie popup did not clear the frame source";
+
assert items.length == enabled.length;
List<SelectPopupItem> popupItems = new ArrayList<SelectPopupItem>();
for (int i = 0; i < items.length; i++) {
popupItems.add(new SelectPopupItem(items[i], enabled[i]));
}
- SelectPopupDialog.show(this, popupItems, multiple, selectedIndices);
+ if (DeviceFormFactor.isTablet(mContext) && !multiple && !isTouchExplorationEnabled()) {
+ mSelectPopup = new SelectPopupDropdown(this, popupItems, bounds, selectedIndices);
+ } else {
+ mSelectPopup = new SelectPopupDialog(this, popupItems, multiple, selectedIndices);
+ }
+ mNativeSelectPopupSourceFrame = nativeSelectPopupSourceFrame;
+ mSelectPopup.show();
+ }
+
+ /**
+ * Called when the <select> popup needs to be hidden.
+ */
+ @CalledByNative
+ private void hideSelectPopup() {
+ if (mSelectPopup != null) mSelectPopup.hide();
+ }
+
+ /**
+ * @return The visible select popup being shown.
+ */
+ public SelectPopup getSelectPopupForTest() {
+ return mSelectPopup;
}
@SuppressWarnings("unused")
private void showDisambiguationPopup(Rect targetRect, Bitmap zoomedBitmap) {
mPopupZoomer.setBitmap(zoomedBitmap);
mPopupZoomer.show(targetRect);
- temporarilyHideTextHandles();
}
@SuppressWarnings("unused")
@SuppressWarnings("unused")
@CalledByNative
- private void onSelectionChanged(String text) {
- mLastSelectedText = text;
- }
-
- @SuppressWarnings("unused")
- @CalledByNative
- private void onSelectionBoundsChanged(Rect anchorRectDip, int anchorDir, Rect focusRectDip,
- int focusDir, boolean isAnchorFirst) {
- // All coordinates are in DIP.
- int x1 = anchorRectDip.left;
- int y1 = anchorRectDip.bottom;
- int x2 = focusRectDip.left;
- int y2 = focusRectDip.bottom;
-
- if (x1 != x2 || y1 != y2 ||
- (mSelectionHandleController != null && mSelectionHandleController.isDragging())) {
- if (mInsertionHandleController != null) {
- mInsertionHandleController.hide();
- }
- if (isAnchorFirst) {
- mStartHandlePoint.setLocalDip(x1, y1);
- mEndHandlePoint.setLocalDip(x2, y2);
- } else {
- mStartHandlePoint.setLocalDip(x2, y2);
- mEndHandlePoint.setLocalDip(x1, y1);
- }
-
- boolean wereSelectionHandlesShowing = getSelectionHandleController().isShowing();
-
- getSelectionHandleController().onSelectionChanged(anchorDir, focusDir);
- updateHandleScreenPositions();
- mHasSelection = true;
-
- if (!wereSelectionHandlesShowing && getSelectionHandleController().isShowing()) {
- // TODO(cjhopman): Remove this when there is a better signal that long press caused
- // a selection. See http://crbug.com/150151.
- mContainerView.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
- }
-
- } else {
- mUnselectAllOnActionModeDismiss = false;
- hideSelectActionBar();
- if (x1 != 0 && y1 != 0 && mSelectionEditable) {
- // Selection is a caret, and a text field is focused.
- if (mSelectionHandleController != null) {
- mSelectionHandleController.hide();
+ private PopupTouchHandleDrawable createPopupTouchHandleDrawable() {
+ if (mTouchHandleDelegate == null) {
+ mTouchHandleDelegate = new PopupTouchHandleDrawableDelegate() {
+ @Override
+ public View getParent() {
+ return getContainerView();
}
- mInsertionHandlePoint.setLocalDip(x1, y1);
-
- getInsertionHandleController().onCursorPositionChanged();
- updateHandleScreenPositions();
- InputMethodManager manager = (InputMethodManager)
- getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
- if (manager.isWatchingCursor(mContainerView)) {
- final int xPix = (int) mInsertionHandlePoint.getXPix();
- final int yPix = (int) mInsertionHandlePoint.getYPix();
- manager.updateCursor(mContainerView, xPix, yPix, xPix, yPix);
+
+ @Override
+ public PositionObserver getParentPositionObserver() {
+ return mPositionObserver;
}
- } else {
- // Deselection
- if (mSelectionHandleController != null) {
- mSelectionHandleController.hideAndDisallowAutomaticShowing();
+
+ @Override
+ public boolean onTouchHandleEvent(MotionEvent event) {
+ final boolean isTouchHandleEvent = true;
+ return onTouchEventImpl(event, isTouchHandleEvent);
}
- if (mInsertionHandleController != null) {
- mInsertionHandleController.hideAndDisallowAutomaticShowing();
+
+ @Override
+ public boolean isScrollInProgress() {
+ return ContentViewCore.this.isScrollInProgress();
}
- }
- mHasSelection = false;
- }
- if (isSelectionHandleShowing() || isInsertionHandleShowing()) {
- mPositionObserver.addListener(mPositionListener);
+ };
}
+ return new PopupTouchHandleDrawable(mTouchHandleDelegate);
}
@SuppressWarnings("unused")
@CalledByNative
- private static void onEvaluateJavaScriptResult(
- String jsonResult, JavaScriptCallback callback) {
- callback.handleJavaScriptResult(jsonResult);
+ private void onSelectionChanged(String text) {
+ mLastSelectedText = text;
+ getContentViewClient().onSelectionChanged(text);
}
@SuppressWarnings("unused")
@CalledByNative
- private void showPastePopup(int xDip, int yDip) {
- mInsertionHandlePoint.setLocalDip(xDip, yDip);
- getInsertionHandleController().showHandle();
- updateHandleScreenPositions();
- getInsertionHandleController().showHandleWithPastePopup();
+ private void showPastePopupWithFeedback(int xDip, int yDip) {
+ // TODO(jdduke): Remove this when there is a better signal that long press caused
+ // showing of the paste popup. See http://crbug.com/150151.
+ if (showPastePopup(xDip, yDip)) {
+ mContainerView.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
+ }
}
- @SuppressWarnings("unused")
- @CalledByNative
- private void onRenderProcessSwap(int oldPid, int newPid) {
- if (!mInForeground) {
- ChildProcessLauncher.getBindingManager().setInForeground(newPid, false);
- } else if (oldPid != newPid) {
- ChildProcessLauncher.getBindingManager().setInForeground(oldPid, false);
- ChildProcessLauncher.getBindingManager().setInForeground(newPid, true);
+ private boolean showPastePopup(int xDip, int yDip) {
+ if (!mHasInsertion || !canPaste()) return false;
+ final float contentOffsetYPix = mRenderCoordinates.getContentOffsetYPix();
+ getPastePopup().showAt(
+ (int) mRenderCoordinates.fromDipToPix(xDip),
+ (int) (mRenderCoordinates.fromDipToPix(yDip) + contentOffsetYPix));
+ return true;
+ }
+
+ private PastePopupMenu getPastePopup() {
+ if (mPastePopupMenu == null) {
+ mPastePopupMenu = new PastePopupMenu(getContainerView(),
+ new PastePopupMenuDelegate() {
+ @Override
+ public void paste() {
+ mImeAdapter.paste();
+ dismissTextHandles();
+ }
+ });
}
+ return mPastePopupMenu;
+ }
- attachImeAdapter();
+ @VisibleForTesting
+ public PastePopupMenu getPastePopupForTest() {
+ return getPastePopup();
+ }
+
+ private boolean canPaste() {
+ if (!mFocusedNodeEditable) return false;
+ return ((ClipboardManager) mContext.getSystemService(
+ Context.CLIPBOARD_SERVICE)).hasPrimaryClip();
}
@SuppressWarnings("unused")
@CalledByNative
- private void onWebContentsConnected() {
+ private void onRenderProcessChange() {
attachImeAdapter();
}
* @see View#hasFocus()
*/
@CalledByNative
- public boolean hasFocus() {
+ private boolean hasFocus() {
+ // 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();
}
public boolean pinchByDelta(float delta) {
if (mNativeContentViewCore == 0) return false;
- long timeMs = System.currentTimeMillis();
+ long timeMs = SystemClock.uptimeMillis();
int xPix = getViewportWidthPix() / 2;
int yPix = getViewportHeightPix() / 2;
}
/**
+ * Enables or disables inspection of JavaScript objects added via
+ * {@link #addJavascriptInterface(Object, String)} by means of Object.keys() method and
+ * "for .. in" loop. Being able to inspect JavaScript objects is useful
+ * when debugging hybrid Android apps, but can't be enabled for legacy applications due
+ * to compatibility risks.
+ *
+ * @param allow Whether to allow JavaScript objects inspection.
+ */
+ public void setAllowJavascriptInterfacesInspection(boolean allow) {
+ nativeSetAllowJavascriptInterfacesInspection(mNativeContentViewCore, allow);
+ }
+
+ /**
+ * Returns JavaScript interface objects previously injected via
+ * {@link #addJavascriptInterface(Object, String)}.
+ *
+ * @return the mapping of names to interface objects and corresponding annotation classes
+ */
+ public Map<String, Pair<Object, Class>> getJavascriptInterfaces() {
+ return mJavaScriptInterfaces;
+ }
+
+ /**
* This will mimic {@link #addPossiblyUnsafeJavascriptInterface(Object, String, Class)}
* and automatically pass in {@link JavascriptInterface} as the required annotation.
*
public void addPossiblyUnsafeJavascriptInterface(Object object, String name,
Class<? extends Annotation> requiredAnnotation) {
if (mNativeContentViewCore != 0 && object != null) {
- mJavaScriptInterfaces.put(name, object);
- nativeAddJavascriptInterface(mNativeContentViewCore, object, name, requiredAnnotation,
- mRetainedJavaScriptObjects);
+ mJavaScriptInterfaces.put(name, new Pair<Object, Class>(object, requiredAnnotation));
+ nativeAddJavascriptInterface(mNativeContentViewCore, object, name, requiredAnnotation);
}
}
* Return the current scale of the ContentView.
* @return The current page scale factor.
*/
+ @VisibleForTesting
public float getScale() {
return mRenderCoordinates.getPageScaleFactor();
}
- /**
- * If the view is ready to draw contents to the screen. In hardware mode,
- * the initialization of the surface texture may not occur until after the
- * view has been added to the layout. This method will return {@code true}
- * once the texture is actually ready.
- */
- public boolean isReady() {
- if (mNativeContentViewCore == 0) return false;
- return nativeIsRenderWidgetHostViewReady(mNativeContentViewCore);
- }
-
@CalledByNative
private void startContentIntent(String contentUrl) {
getContentViewClient().onStartContentIntent(getContext(), contentUrl);
return mBrowserAccessibilityManager.getAccessibilityNodeProvider();
}
- if (mNativeAccessibilityAllowed &&
- !mNativeAccessibilityEnabled &&
- mNativeContentViewCore != 0 &&
- Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
+ if (mNativeAccessibilityAllowed && !mNativeAccessibilityEnabled
+ && mNativeContentViewCore != 0
+ && Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
mNativeAccessibilityEnabled = true;
nativeSetAccessibilityEnabled(mNativeContentViewCore, true);
}
event.setScrollable(maxScrollXPix > 0 || maxScrollYPix > 0);
// Setting the maximum scroll values requires API level 15 or higher.
- final int SDK_VERSION_REQUIRED_TO_SET_SCROLL = 15;
- if (Build.VERSION.SDK_INT >= SDK_VERSION_REQUIRED_TO_SET_SCROLL) {
+ final int sdkVersionRequiredToSetScroll = 15;
+ if (Build.VERSION.SDK_INT >= sdkVersionRequiredToSetScroll) {
event.setMaxScrollX(maxScrollXPix);
event.setMaxScrollY(maxScrollYPix);
}
*/
public boolean isDeviceAccessibilityScriptInjectionEnabled() {
try {
- if (CommandLine.getInstance().hasSwitch(
- ContentSwitches.DISABLE_ACCESSIBILITY_SCRIPT_INJECTION)) {
+ // On JellyBean and higher, native accessibility is the default so script
+ // injection is only allowed if enabled via a flag.
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN
+ && !CommandLine.getInstance().hasSwitch(
+ ContentSwitches.ENABLE_ACCESSIBILITY_SCRIPT_INJECTION)) {
return false;
}
}
};
contentResolver.registerContentObserver(
- Settings.Secure.getUriFor(accessibilityScriptInjection),
- false,
- contentObserver);
+ Settings.Secure.getUriFor(accessibilityScriptInjection),
+ false,
+ contentObserver);
mAccessibilityScriptInjectionObserver = contentObserver;
}
}
/**
+ * Returns true if accessibility is on and touch exploration is enabled.
+ */
+ public boolean isTouchExplorationEnabled() {
+ return mTouchExplorationEnabled;
+ }
+
+ /**
* Turns browser accessibility on or off.
* If |state| is |false|, this turns off both native and injected accessibility.
* Otherwise, if accessibility script injection is enabled, this will enable the injected
if (!state) {
setInjectedAccessibility(false);
mNativeAccessibilityAllowed = false;
+ mTouchExplorationEnabled = false;
} else {
boolean useScriptInjection = isDeviceAccessibilityScriptInjectionEnabled();
setInjectedAccessibility(useScriptInjection);
mNativeAccessibilityAllowed = !useScriptInjection;
+ mTouchExplorationEnabled = mAccessibilityManager.isTouchExplorationEnabled();
}
}
}
/**
- * Inform WebKit that Fullscreen mode has been exited by the user.
- */
- public void exitFullscreen() {
- if (mNativeContentViewCore != 0) nativeExitFullscreen(mNativeContentViewCore);
- }
-
- /**
- * Changes whether hiding the top controls is enabled.
- *
- * @param enableHiding Whether hiding the top controls should be enabled or not.
- * @param enableShowing Whether showing the top controls should be enabled or not.
- * @param animate Whether the transition should be animated or not.
- */
- public void updateTopControlsState(boolean enableHiding, boolean enableShowing,
- boolean animate) {
- if (mNativeContentViewCore != 0) {
- nativeUpdateTopControlsState(
- mNativeContentViewCore, enableHiding, enableShowing, animate);
- }
- }
-
- /**
- * Callback factory method for nativeGetNavigationHistory().
- */
- @CalledByNative
- private void addToNavigationHistory(Object history, int index, String url, String virtualUrl,
- String originalUrl, String title, Bitmap favicon) {
- NavigationEntry entry = new NavigationEntry(
- index, url, virtualUrl, originalUrl, title, favicon);
- ((NavigationHistory) history).addEntry(entry);
- }
-
- /**
- * Get a copy of the navigation history of the view.
+ * Return whether or not we should set accessibility focus on page load.
*/
- public NavigationHistory getNavigationHistory() {
- NavigationHistory history = new NavigationHistory();
- if (mNativeContentViewCore != 0) {
- int currentIndex = nativeGetNavigationHistory(mNativeContentViewCore, history);
- history.setCurrentEntryIndex(currentIndex);
- }
- return history;
- }
-
- @Override
- public NavigationHistory getDirectedNavigationHistory(boolean isForward, int itemLimit) {
- NavigationHistory history = new NavigationHistory();
- if (mNativeContentViewCore != 0) {
- nativeGetDirectedNavigationHistory(
- mNativeContentViewCore, history, isForward, itemLimit);
- }
- return history;
+ public boolean shouldSetAccessibilityFocusOnPageLoad() {
+ return mShouldSetAccessibilityFocusOnPageLoad;
}
/**
- * @return The original request URL for the current navigation entry, or null if there is no
- * current entry.
+ * Sets whether or not we should set accessibility focus on page load.
+ * This only applies if an accessibility service like TalkBack is running.
+ * This is desirable behavior for a browser window, but not for an embedded
+ * WebView.
*/
- public String getOriginalUrlForActiveNavigationEntry() {
- if (mNativeContentViewCore != 0) {
- return nativeGetOriginalUrlForActiveNavigationEntry(mNativeContentViewCore);
- }
- return "";
+ public void setShouldSetAccessibilityFocusOnPageLoad(boolean on) {
+ mShouldSetAccessibilityFocusOnPageLoad = on;
}
/**
+ *
* @return The cached copy of render positions and scales.
*/
public RenderCoordinates getRenderCoordinates() {
}
@CalledByNative
- private int getLocationInWindowX() {
- return mLocationInWindowX;
- }
-
- @CalledByNative
- private int getLocationInWindowY() {
- return mLocationInWindowY;
- }
-
- @CalledByNative
private static Rect createRect(int x, int y, int right, int bottom) {
return new Rect(x, y, right, bottom);
}
- public void attachExternalVideoSurface(int playerId, Surface surface) {
+ public void extractSmartClipData(int x, int y, int width, int height) {
if (mNativeContentViewCore != 0) {
- nativeAttachExternalVideoSurface(mNativeContentViewCore, playerId, surface);
+ x += mSmartClipOffsetX;
+ y += mSmartClipOffsetY;
+ nativeExtractSmartClipData(mNativeContentViewCore, x, y, width, height);
}
}
- public void detachExternalVideoSurface(int playerId) {
- if (mNativeContentViewCore != 0) {
- nativeDetachExternalVideoSurface(mNativeContentViewCore, playerId);
- }
+ /**
+ * Set offsets for smart clip.
+ *
+ * <p>This should be called if there is a viewport change introduced by,
+ * e.g., show and hide of a location bar.
+ *
+ * @param offsetX Offset for X position.
+ * @param offsetY Offset for Y position.
+ */
+ public void setSmartClipOffsets(int offsetX, int offsetY) {
+ mSmartClipOffsetX = offsetX;
+ mSmartClipOffsetY = offsetY;
}
- private boolean onAnimate(long frameTimeMicros) {
- if (mNativeContentViewCore == 0) return false;
- return nativeOnAnimate(mNativeContentViewCore, frameTimeMicros);
- }
+ @CalledByNative
+ private void onSmartClipDataExtracted(String text, String html, Rect clipRect) {
+ // Translate the positions by the offsets introduced by location bar. Note that the
+ // coordinates are in dp scale, and that this definitely has the potential to be
+ // different from the offsets when extractSmartClipData() was called. However,
+ // as long as OEM has a UI that consumes all the inputs and waits until the
+ // callback is called, then there shouldn't be any difference.
+ // TODO(changwan): once crbug.com/416432 is resolved, try to pass offsets as
+ // separate params for extractSmartClipData(), and apply them not the new offset
+ // values in the callback.
+ final float deviceScale = mRenderCoordinates.getDeviceScaleFactor();
+ final int offsetXInDp = (int) (mSmartClipOffsetX / deviceScale);
+ final int offsetYInDp = (int) (mSmartClipOffsetY / deviceScale);
+ clipRect.offset(-offsetXInDp, -offsetYInDp);
- private void animateIfNecessary(long frameTimeMicros) {
- if (mNeedAnimate) {
- mNeedAnimate = onAnimate(frameTimeMicros);
- if (!mNeedAnimate) removeVSyncSubscriber();
+ if (mSmartClipDataListener != null) {
+ mSmartClipDataListener.onSmartClipDataExtracted(text, html, clipRect);
}
}
- @CalledByNative
- private void notifyExternalSurface(
- int playerId, boolean isRequest, float x, float y, float width, float height) {
- if (isRequest) getContentViewClient().onExternalVideoSurfaceRequested(playerId);
- getContentViewClient().onGeometryChanged(playerId, new RectF(x, y, x + width, y + height));
+ public void setSmartClipDataListener(SmartClipDataListener listener) {
+ mSmartClipDataListener = listener;
}
- public void extractSmartClipData(int x, int y, int width, int height) {
+ public void setBackgroundOpaque(boolean opaque) {
if (mNativeContentViewCore != 0) {
- nativeExtractSmartClipData(mNativeContentViewCore, x, y, width, height);
- }
- }
-
- @CalledByNative
- private void onSmartClipDataExtracted(String result) {
- if (mSmartClipDataListener != null ) {
- mSmartClipDataListener.onSmartClipDataExtracted(result);
+ nativeSetBackgroundOpaque(mNativeContentViewCore, opaque);
}
}
- public void setSmartClipDataListener(SmartClipDataListener listener) {
- mSmartClipDataListener = listener;
- }
-
/**
* Offer a long press gesture to the embedding View, primarily for WebView compatibility.
*
return mContainerView.performLongClick();
}
+ /**
+ * Reset scroll and fling accounting, notifying listeners as appropriate.
+ * This is useful as a failsafe when the input stream may have been interruped.
+ */
+ private void resetScrollInProgress() {
+ if (!isScrollInProgress()) return;
+
+ final boolean touchScrollInProgress = mTouchScrollInProgress;
+ final int potentiallyActiveFlingCount = mPotentiallyActiveFlingCount;
+
+ mTouchScrollInProgress = false;
+ mPotentiallyActiveFlingCount = 0;
+
+ if (touchScrollInProgress) updateGestureStateListener(GestureEventType.SCROLL_END);
+ if (potentiallyActiveFlingCount > 0) updateGestureStateListener(GestureEventType.FLING_END);
+ }
+
private native long nativeInit(long webContentsPtr,
- long viewAndroidPtr, long windowAndroidPtr);
+ long viewAndroidPtr, long windowAndroidPtr, HashSet<Object> retainedObjectSet);
- @CalledByNative
- private ContentVideoViewClient getContentVideoViewClient() {
+ ContentVideoViewClient getContentVideoViewClient() {
return getContentViewClient().getContentVideoViewClient();
}
@CalledByNative
private void onNativeFlingStopped() {
+ // Note that mTouchScrollInProgress should normally be false at this
+ // point, but we reset it anyway as another failsafe.
+ mTouchScrollInProgress = false;
+ if (mPotentiallyActiveFlingCount <= 0) return;
+ mPotentiallyActiveFlingCount--;
updateGestureStateListener(GestureEventType.FLING_END);
}
- private native WebContents nativeGetWebContentsAndroid(long nativeContentViewCoreImpl);
-
- private native void nativeOnJavaContentViewCoreDestroyed(long nativeContentViewCoreImpl);
-
- private native void nativeLoadUrl(
- long nativeContentViewCoreImpl,
- String url,
- int loadUrlType,
- int transitionType,
- int uaOverrideOption,
- String extraHeaders,
- byte[] postData,
- String baseUrlForDataUrl,
- String virtualUrlForDataUrl,
- boolean canLoadLocalResources);
+ @Override
+ public void onScreenOrientationChanged(int orientation) {
+ sendOrientationChangeEvent(orientation);
+ }
- private native String nativeGetURL(long nativeContentViewCoreImpl);
+ /**
+ * Set whether the ContentViewCore requires the WebContents to be fullscreen in order to lock
+ * the screen orientation.
+ */
+ public void setFullscreenRequiredForOrientationLock(boolean value) {
+ mFullscreenRequiredForOrientationLock = value;
+ }
- private native String nativeGetTitle(long nativeContentViewCoreImpl);
+ @CalledByNative
+ private boolean isFullscreenRequiredForOrientationLock() {
+ return mFullscreenRequiredForOrientationLock;
+ }
- private native void nativeShowInterstitialPage(
- long nativeContentViewCoreImpl, String url, long nativeInterstitialPageDelegateAndroid);
- private native boolean nativeIsShowingInterstitialPage(long nativeContentViewCoreImpl);
+ private native WebContents nativeGetWebContentsAndroid(long nativeContentViewCoreImpl);
- private native boolean nativeIsIncognito(long nativeContentViewCoreImpl);
+ private native void nativeOnJavaContentViewCoreDestroyed(long nativeContentViewCoreImpl);
private native void nativeSetFocus(long nativeContentViewCoreImpl, boolean focused);
long nativeContentViewCoreImpl, int orientation);
// All touch events (including flings, scrolls etc) accept coordinates in physical pixels.
- private native void nativeOnTouchEventHandlingBegin(
- long nativeContentViewCoreImpl, MotionEvent event);
-
- private native void nativeOnTouchEventHandlingEnd(long nativeContentViewCoreImpl);
+ private native boolean nativeOnTouchEvent(
+ long nativeContentViewCoreImpl, MotionEvent event,
+ long timeMs, int action, int pointerCount, int historySize, int actionIndex,
+ float x0, float y0, float x1, float y1,
+ int pointerId0, int pointerId1,
+ float touchMajor0, float touchMajor1,
+ float touchMinor0, float touchMinor1,
+ float orientation0, float orientation1,
+ float rawX, float rawY,
+ int androidToolType0, int androidToolType1,
+ int androidButtonState, int androidMetaState,
+ boolean isTouchHandleEvent);
private native int nativeSendMouseMoveEvent(
long nativeContentViewCoreImpl, long timeMs, float x, float y);
private native void nativeFlingCancel(long nativeContentViewCoreImpl, long timeMs);
private native void nativeSingleTap(
- long nativeContentViewCoreImpl, long timeMs, float x, float y, boolean linkPreviewTap);
-
- private native void nativeSingleTapUnconfirmed(
- long nativeContentViewCoreImpl, long timeMs, float x, float y);
-
- private native void nativeShowPress(
- long nativeContentViewCoreImpl, long timeMs, float x, float y);
-
- private native void nativeTapCancel(
- long nativeContentViewCoreImpl, long timeMs, float x, float y);
-
- private native void nativeTapDown(
long nativeContentViewCoreImpl, long timeMs, float x, float y);
private native void nativeDoubleTap(
long nativeContentViewCoreImpl, long timeMs, float x, float y);
private native void nativeLongPress(
- long nativeContentViewCoreImpl, long timeMs, float x, float y, boolean linkPreviewTap);
-
- private native void nativeLongTap(
- long nativeContentViewCoreImpl, long timeMs, float x, float y, boolean linkPreviewTap);
+ long nativeContentViewCoreImpl, long timeMs, float x, float y);
private native void nativePinchBegin(
long nativeContentViewCoreImpl, long timeMs, float x, float y);
private native void nativeMoveCaret(long nativeContentViewCoreImpl, float x, float y);
- private native void nativeLoadIfNecessary(long nativeContentViewCoreImpl);
- private native void nativeRequestRestoreLoad(long nativeContentViewCoreImpl);
-
- private native void nativeStopLoading(long nativeContentViewCoreImpl);
+ private native void nativeDismissTextHandles(long nativeContentViewCoreImpl);
+ private native void nativeSetTextHandlesTemporarilyHidden(
+ long nativeContentViewCoreImpl, boolean hidden);
- private native void nativeReload(long nativeContentViewCoreImpl, boolean checkForRepost);
- private native void nativeReloadIgnoringCache(
- long nativeContentViewCoreImpl, boolean checkForRepost);
+ private native void nativeResetGestureDetection(long nativeContentViewCoreImpl);
- private native void nativeCancelPendingReload(long nativeContentViewCoreImpl);
-
- private native void nativeContinuePendingReload(long nativeContentViewCoreImpl);
-
- private native void nativeSelectPopupMenuItems(long nativeContentViewCoreImpl, int[] indices);
+ private native void nativeSetDoubleTapSupportEnabled(
+ long nativeContentViewCoreImpl, boolean enabled);
- private native void nativeScrollFocusedEditableNodeIntoView(long nativeContentViewCoreImpl);
- private native void nativeUndoScrollFocusedEditableNodeIntoView(long nativeContentViewCoreImpl);
+ private native void nativeSetMultiTouchZoomSupportEnabled(
+ long nativeContentViewCoreImpl, boolean enabled);
- private native void nativeClearHistory(long nativeContentViewCoreImpl);
+ private native void nativeSelectPopupMenuItems(long nativeContentViewCoreImpl,
+ long nativeSelectPopupSourceFrame, int[] indices);
- private native void nativeEvaluateJavaScript(long nativeContentViewCoreImpl,
- String script, JavaScriptCallback callback, boolean startRenderer);
- private native int nativeGetNativeImeAdapter(long nativeContentViewCoreImpl);
+ private native long nativeGetNativeImeAdapter(long nativeContentViewCoreImpl);
private native int nativeGetCurrentRenderProcessId(long nativeContentViewCoreImpl);
- private native int nativeGetBackgroundColor(long nativeContentViewCoreImpl);
-
- private native void nativeOnShow(long nativeContentViewCoreImpl);
- private native void nativeOnHide(long nativeContentViewCoreImpl);
-
- private native void nativeSetUseDesktopUserAgent(long nativeContentViewCoreImpl,
- boolean enabled, boolean reloadOnChange);
- private native boolean nativeGetUseDesktopUserAgent(long nativeContentViewCoreImpl);
-
- private native void nativeClearSslPreferences(long nativeContentViewCoreImpl);
+ private native void nativeSetAllowJavascriptInterfacesInspection(
+ long nativeContentViewCoreImpl, boolean allow);
private native void nativeAddJavascriptInterface(long nativeContentViewCoreImpl, Object object,
- String name, Class requiredAnnotation, HashSet<Object> retainedObjectSet);
+ String name, Class requiredAnnotation);
private native void nativeRemoveJavascriptInterface(long nativeContentViewCoreImpl,
String name);
- private native int nativeGetNavigationHistory(long nativeContentViewCoreImpl, Object context);
- private native void nativeGetDirectedNavigationHistory(long nativeContentViewCoreImpl,
- Object context, boolean isForward, int maxEntries);
- private native String nativeGetOriginalUrlForActiveNavigationEntry(
- long nativeContentViewCoreImpl);
-
- private native void nativeUpdateVSyncParameters(long nativeContentViewCoreImpl,
- long timebaseMicros, long intervalMicros);
-
- private native void nativeOnVSync(long nativeContentViewCoreImpl, long frameTimeMicros);
-
- private native boolean nativeOnAnimate(long nativeContentViewCoreImpl, long frameTimeMicros);
-
- private native boolean nativePopulateBitmapFromCompositor(long nativeContentViewCoreImpl,
- Bitmap bitmap);
-
private native void nativeWasResized(long nativeContentViewCoreImpl);
- private native boolean nativeIsRenderWidgetHostViewReady(long nativeContentViewCoreImpl);
-
- private native void nativeExitFullscreen(long nativeContentViewCoreImpl);
- private native void nativeUpdateTopControlsState(long nativeContentViewCoreImpl,
- boolean enableHiding, boolean enableShowing, boolean animate);
-
- private native void nativeShowImeIfNeeded(long nativeContentViewCoreImpl);
-
- private native void nativeAttachExternalVideoSurface(
- long nativeContentViewCoreImpl, int playerId, Surface surface);
-
- private native void nativeDetachExternalVideoSurface(
- long nativeContentViewCoreImpl, int playerId);
-
private native void nativeSetAccessibilityEnabled(
long nativeContentViewCoreImpl, boolean enabled);
- private native void nativeSendSingleTapUma(long nativeContentViewCoreImpl,
- int type, int count);
-
- private native void nativeSendActionAfterDoubleTapUma(long nativeContentViewCoreImpl,
- int type, boolean hasDelay, int count);
-
private native void nativeExtractSmartClipData(long nativeContentViewCoreImpl,
int x, int y, int w, int h);
+
+ private native void nativeSetBackgroundOpaque(long nativeContentViewCoreImpl, boolean opaque);
}